Skip to content

Add core symbolic gradient support to Pyomo.DoE#3928

Open
snarasi2 wants to merge 46 commits into
Pyomo:mainfrom
snarasi2:clean_symbolic
Open

Add core symbolic gradient support to Pyomo.DoE#3928
snarasi2 wants to merge 46 commits into
Pyomo:mainfrom
snarasi2:clean_symbolic

Conversation

@snarasi2
Copy link
Copy Markdown

@snarasi2 snarasi2 commented May 4, 2026

Fixes # .

This PR ports the core symbolic-gradient functionality from the historical pyomo-doe-symbolic work into the current pyomo.contrib.doe implementation.

Summary/Motivation:

Rather than merging the old branch directly, this change transplants the symbolic DoE pieces onto current main so that symbolic sensitivities work with the newer DoE implementation already present in Pyomo, including the current objective and GreyBox-oriented code paths.

This PR ports the core symbolic-gradient functionality from adowling2#7 onto current Pyomo main and adapts it to the current pyomo.contrib.doe implementation.

This contribution was prepared with coding assistance from OpenAI Codex. All design decisions, validation, testing, and quality-assurance responsibility remain with Shilpa Narasimhan.

Changes proposed in this PR:

  • Add GradientMethod support to DesignOfExperiments
  • Preserve the existing finite-difference workflow through the new gradient-method interface
  • Add a symbolic / PyNumero gradient path for DoE
  • Add an analytic FIM computation path for non-optimization FIM evaluation
  • Export ExperimentGradients from pyomo.contrib.doe
  • Refactor ExperimentGradients so symbolic and automatic differentiation are set up together
  • Add supporting symbolic-gradient utilities for labeled experiment models
  • Update DoE model construction so symbolic sensitivities can be used in the Jacobian-based DoE machinery
  • Add a guard preventing run_doe() from being called with GradientMethod.kaug
  • Add the polynomial example and polynomial-focused regression coverage
  • Add broader symbolic-versus-automatic gradient consistency tests
  • Add factorial-result dataframe tests
  • Add reactor regression tests against expected solutions
  • Clarify GreyBox cyipopt / MA57-HSL test behavior where relevant

Validation performed locally:

  • Black: python -m black -S -C --check --diff pyomo/contrib/doe → passed
  • Typos: typos --config .github/workflows/typos.toml pyomo/contrib/doe DOE_SYMBOLIC_PR_NOTES.md → passed
  • Focused DoE/GreyBox validation: 134 passed, 0 failed, 0 skipped
  • GreyBox cyipopt tests were run with MA57/HSL available in the local environment
  • The existing K_AUG-dependent DoE test path was validated locally after configuring the required runtime library path

Legal Acknowledgement

By contributing to this software project, I have read the contribution guide and agree to the following terms and conditions for my contribution:

  1. I agree my contributions are submitted under the BSD license.
  2. I represent I am authorized to make the contributions and grant the license. If my employer has rights to intellectual property that includes these contributions, I represent that I have received permission to make contributions and grant the required license on behalf of that employer.

Shilpa Narasimhan added 30 commits April 1, 2026 14:20
Add schema, filtering, and error-path tests for factorial-result tables and DataFrame input handling.

Validation:
- 101 passed, 5 warnings, 10 subtests passed in 30.00s

Notes:
- plotting tests emit expected matplotlib warnings under the non-interactive Agg backend because draw_factorial_figure() calls plt.show().
Add a determinant-based reactor regression test, guard Codecov uploads when coverage.xml is absent, and align local formatting with Black expectations.

Validation:
- focused reactor regression subset: 2 passed, 28 deselected in 10.08s
- full DoE suite: 102 passed, 5 warnings, 10 subtests passed in 30.29s

Notes:
- plotting-related tests continue to emit expected matplotlib warnings under the non-interactive Agg backend because draw_factorial_figure() calls plt.show().
Add coverage for draw_factorial_figure guard branches covering more-than-two sensitivity variables and missing fixed design variables.

Validation:
- focused plotting-guard subset: 2 passed, 37 deselected in 1.92s
- full DoE suite: 104 passed, 5 warnings, 10 subtests passed in 31.47s

Notes:
- plotting-related tests continue to emit expected matplotlib warnings under the non-interactive Agg backend because draw_factorial_figure() calls plt.show().
@snarasi2
Copy link
Copy Markdown
Author

snarasi2 commented May 4, 2026

@adowling2 @slilonfe5 @smondal13 @sscini This is the new symbolic PR (created from the unpolluted branch)

@snarasi2
Copy link
Copy Markdown
Author

snarasi2 commented May 4, 2026

@blnicho — this PR (#3928) is the clean replacement for #3898. The original PR included unintended extra changes; this one contains only the intended symbolic DoE work and has been fully validated.

Could you please switch review/merge to this PR and we can close #3898?

@mrmundt
Copy link
Copy Markdown
Contributor

mrmundt commented May 4, 2026

@blnicho — this PR (#3928) is the clean replacement for #3898. The original PR included unintended extra changes; this one contains only the intended symbolic DoE work and has been fully validated.

Could you please switch review/merge to this PR and we can close #3898?

@snarasi2 - Done. #3898 is closed in favor of this one.

@sscini sscini moved this from Todo to Ready for design review in ParmEst & Pyomo.DoE Development May 6, 2026
Comment thread pyomo/contrib/doe/doe.py
experiment_grad = None
if self._gradient_method == GradientMethod.pynumero:
experiment_grad = ExperimentGradients(
model.scenario_blocks[0], automatic=True, symbolic=True
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adowling2 @smondal13 , per discussion with Shuvo on 05/11: Shuvo and I decided that the cleanest path forward to address potential conflicts with Shuvo's multi-experiment PR #3866 is to leave symbolic PR as is for now. When PR3866 is merged, we will address the conflict with fd_scenario_blocks and fd_scenarios within multi-experiment vs scenatio_blocks and scenarios within the current Pyomo main and the symbolic PR.

Alternative would be to merge this PR with multi-experiment as of now, but it will create a dependency and we are not sure we should proceed this way.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shilpa to double-check and address: If main does not use model.scenarios currently, @snarasi2 will modify all occurrences of model.scenarios to to model.fd_scenarios to facilitate multi-experiment merge. Additionally, this will help distinguish scenario building for finite difference methods for parameter perturbations vs parameter scenarios for uncertainty-aware design.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@smondal13 @adowling2 I checked upstream/main and confirmed that literal model.scenarios already exists in pyomo/contrib/doe/doe.py and pyomo/contrib/doe/tests/test_doe_build.py. Since the proposed rename was conditional on main not currently using model.scenarios, I did not rename model.scenarios to model.fd_scenarios in this PR. I’m leaving this deferred to the multi-experiment merge as discussed on 05/11.

# focused on replacing active general-purpose coverage with
# Rooney-Biegler or polynomial examples rather than rewriting inactive,
# branch-specific tests.
def DISABLE_test_rooney_biegler_obj_cholesky_solve_bad_prior(self):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should be deleted? It's been disabled for a while, and it'll still live in the commit history.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the disabled Rooney-Biegler Cholesky/bad-prior test based on Miranda’s review comment and alignment from @slilonfe5 and @smondal13 on Slack. Commit: 5ad99f0

# The GreyBox path here uses cyipopt rather than the standalone
# IPOPT executable, and the local failure mode we diagnosed was a
# missing MA57/HSL runtime on that cyipopt solve path.
cyipopt_skip_reason = (
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nitpick. All of the skip reasons are variations of the same thing (MA57/HSL issues). Do you really need all three different messages? Or can you just standardize to the one that's on line 315?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — standardized the MA57/HSL-related skip reasons to use the same message as line 315 in ab2f729.

Comment thread pyomo/contrib/doe/tests/test_utils.py Outdated


@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available")
@unittest.skipIf(not numpy_available, "Numpy is not available")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't these numpy_available checks redundant (because of lines 29-30)?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — removed the redundant NumPy skip decorators in test_utils.py because the file-level SkipTest already handles missing NumPy in f1c9a8f.

Comment thread pyomo/contrib/doe/tests/test_utils.py Outdated
self.assertEqual(jacobian.shape, expected.shape)
self.assertTrue(np.allclose(jacobian, expected, atol=1e-7, rtol=1e-7))

@unittest.skipIf(not scipy_available, "scipy is not available")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar comment - isn't this scipy_available check redundant?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — removed the redundant SciPy skip decorator in test_utils.py because the file-level SkipTest already handles missing SciPy in dcbfee6.

Copy link
Copy Markdown
Author

@snarasi2 snarasi2 May 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mrmundt, @adowling2 I also noticed the same pattern in test_greybox.py: the file has a file-level SkipTest for NumPy/SciPy, but the TestFIMExternalGreyBox class still has NumPy/SciPy skipIf decorators. Would you prefer that I keep this PR scoped to the test_utils.py comments, or should I also remove the redundant decorators within test_greybox.py and similar files for consistency?

Comment thread pyomo/contrib/doe/doe.py
default: None --> don't save

"""
if self._gradient_method == GradientMethod.kaug:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I am misunderstanding - kaug is listed as an acceptable option in the docstring, but it's not actually?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — I checked the code path more closely. kaug is supported for FIM computation through compute_FIM(), but run_doe() explicitly rejects GradientMethod.kaug because it is not supported for DoE optimization. I clarified that scope in the gradient_method docstring in 1096e52.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Ready for design review

Development

Successfully merging this pull request may close these issues.

4 participants