Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
50cbc4a
Add symbolic gradient support to Pyomo.DoE
Apr 1, 2026
85116d5
Add symbolic DoE regression tests
Apr 1, 2026
12e5e95
Allow writeable in typos config
Apr 1, 2026
b35253a
Port polynomial and reactor symbolic tests
Apr 1, 2026
431b28e
Document symbolic gradient methods in DoE
Apr 1, 2026
f55a294
Refactor ExperimentGradients setup
Apr 1, 2026
ca66ac6
Add polynomial DoE regression coverage
Apr 1, 2026
8d8b8bd
Add broader gradient consistency tests
Apr 2, 2026
5c373fb
Add factorial results dataframe tests
Apr 2, 2026
103a121
Add reactor regression and CI fixes
Apr 2, 2026
76f53c4
Add plotting guard coverage tests
Apr 2, 2026
f28d86d
Clarify cyipopt HSL skip reason
Apr 2, 2026
80552f2
Reuse polynomial example in DoE tests
Apr 9, 2026
d708d55
Drop unused data arg from polynomial example
Apr 9, 2026
f53fe17
Clarify symbolic sensitivity math comments
Apr 9, 2026
11d475e
Remove no-op polynomial finalization
Apr 9, 2026
e704f68
Simplify polynomial test imports
Apr 9, 2026
0d0a14f
Remove redundant polynomial FIM smoke test
Apr 10, 2026
401c03a
Strengthen polynomial DoE regression test
Apr 10, 2026
0dd08eb
Use symbolic path for dataframe tests
Apr 10, 2026
65571da
Align symbolic DoE test coverage
Apr 10, 2026
9eb4959
Guard DoE utils tests on ipopt
Apr 10, 2026
702dc89
Strengthen symbolic Jacobian tests
Apr 10, 2026
d652cea
Remove out-of-scope FIM metric test
Apr 10, 2026
b5ea7f1
Tighten Jacobian regression assertions
Apr 10, 2026
9a78180
Drop workflow changes from symbolic PR
Apr 10, 2026
c593f0b
Checkpoint reactor test conversions to RB/polynomial
Apr 11, 2026
36d5694
Checkpoint remaining reactor test cleanup
Apr 11, 2026
dae72f4
Replace remaining reactor DoE tests
Apr 11, 2026
0545dcf
Clarify GreyBox MA57 skip reason
Apr 11, 2026
270bf8c
Add symbolic DoE PR notes
Apr 11, 2026
33d0b98
Refactor Jacobian rule setup
Apr 11, 2026
4209df5
Tighten FD scenario validation
Apr 11, 2026
653e3be
Format DoE module with black
Apr 11, 2026
2b8ba66
Merge branch 'Pyomo:main' into clean_symbolic
snarasi2 May 1, 2026
734fd58
Cleanup: remove trailing whitespace in DOE tests and utils
snarasi2 May 4, 2026
06dda52
Format DOE files with Black
snarasi2 May 4, 2026
67f23c8
Guard DOE test requiring pandas (optional dependency)
snarasi2 May 4, 2026
5ad99f0
Remove disabled Rooney-Biegler Cholesky test
snarasi2 May 21, 2026
ab2f729
Standardize GreyBox MA57 skip reason
snarasi2 May 21, 2026
f1c9a8f
Remove redundant NumPy skips in DoE utils tests
snarasi2 May 21, 2026
dcbfee6
Remove redundant SciPy skip in DoE utils tests
snarasi2 May 21, 2026
1096e52
Clarify k_aug gradient method scope
snarasi2 May 21, 2026
1e46960
Merge remote-tracking branch 'upstream/main' into clean_symbolic
snarasi2 May 21, 2026
cbb1582
Defer DoE optional dependency imports
snarasi2 May 21, 2026
53c80be
Apply black formatting to DoE imports
snarasi2 May 21, 2026
15c0677
Remove redundant GreyBox dependency skips
snarasi2 May 22, 2026
ba6d769
Remove redundant DoE dependency skips
snarasi2 May 22, 2026
512451e
Address symbolic DoE test review comments
snarasi2 May 22, 2026
70f7a59
Remove symbolic PR notes file
snarasi2 Jun 18, 2026
e6ce422
Narrow k_aug missing output exception
snarasi2 Jun 18, 2026
d9b7c55
Factor synthetic polynomial plotting results
snarasi2 Jun 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pyomo/contrib/doe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
# Solutions of Sandia, LLC, the U.S. Government retains certain rights in this
# software. This software is distributed under the 3-clause BSD License.
# ____________________________________________________________________________________
from .doe import DesignOfExperiments, ObjectiveLib, FiniteDifferenceStep
from .utils import rescale_FIM
from .doe import DesignOfExperiments, ObjectiveLib, FiniteDifferenceStep, GradientMethod
from .utils import rescale_FIM, ExperimentGradients
from .grey_box_utilities import FIMExternalGreyBox

# Deprecation errors for old Pyomo.DoE interface classes and structures
Expand Down
310 changes: 243 additions & 67 deletions pyomo/contrib/doe/doe.py

Large diffs are not rendered by default.

99 changes: 99 additions & 0 deletions pyomo/contrib/doe/examples/polynomial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# ____________________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2026 National Technology and Engineering Solutions of Sandia, LLC
# Under the terms of Contract DE-NA0003525 with National Technology and Engineering
# Solutions of Sandia, LLC, the U.S. Government retains certain rights in this
# software. This software is distributed under the 3-clause BSD License.
# ____________________________________________________________________________________

import pyomo.environ as pyo

from pyomo.contrib.doe import DesignOfExperiments
from pyomo.contrib.parmest.experiment import Experiment


class PolynomialExperiment(Experiment):
"""A small algebraic experiment used for symbolic-gradient testing."""

def __init__(self):
self.model = None

def get_labeled_model(self):
"""Build and label the experiment model on first access."""
if self.model is None:
self.create_model()
self.label_experiment()
return self.model

def create_model(self):
"""Define a polynomial model for testing symbolic sensitivities.

y = a*x1 + b*x2 + c*x1*x2 + d
"""

m = self.model = pyo.ConcreteModel()

m.x1 = pyo.Var(bounds=(-5, 5), initialize=2.0)
m.x2 = pyo.Var(bounds=(-5, 5), initialize=3.0)

m.a = pyo.Var(bounds=(-5, 5), initialize=2)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It appears a, b, c, and d are parameters. I think these should be fixed after defining them. E.g., after you defined m.a = pyo.Var(bounds=(-5, 5), initialize=2), you should add a m.a.fix() to the new line after the definition. Similar for m.b, m.c, and m.d.

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, modified as suggested

m.a.fix()
m.b = pyo.Var(bounds=(-5, 5), initialize=-1)
m.b.fix()
m.c = pyo.Var(bounds=(-5, 5), initialize=0.5)
m.c.fix()
m.d = pyo.Var(bounds=(-5, 5), initialize=-1)
m.d.fix()

m.y = pyo.Var(initialize=0)

@m.Constraint()
def output_equation(m):
return m.y == m.a * m.x1 + m.b * m.x2 + m.c * m.x1 * m.x2 + m.d

def label_experiment(self):
"""Attach the standard DoE suffixes to the polynomial model."""
m = self.model

m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL)
m.experiment_outputs[m.y] = None

m.measurement_error = pyo.Suffix(direction=pyo.Suffix.LOCAL)
m.measurement_error[m.y] = 1

m.experiment_inputs = pyo.Suffix(direction=pyo.Suffix.LOCAL)
m.experiment_inputs[m.x1] = None
m.experiment_inputs[m.x2] = None

m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL)
m.unknown_parameters.update((k, pyo.value(k)) for k in [m.a, m.b, m.c, m.d])


def run_polynomial_doe():
"""Run a small symbolic DoE FIM calculation for the polynomial model."""
experiment = PolynomialExperiment()

doe_obj = DesignOfExperiments(
experiment,
gradient_method="pynumero",
step=1e-3,
objective_option="determinant",
scale_constant_value=1,
scale_nominal_param_value=False,
prior_FIM=None,
jac_initial=None,
fim_initial=None,
L_diagonal_lower_bound=1e-7,
solver=pyo.SolverFactory("ipopt"),
tee=False,
get_labeled_model_args=None,
_Cholesky_option=True,
_only_compute_fim_lower=True,
)

return doe_obj.compute_FIM()


if __name__ == "__main__":
run_polynomial_doe()
76 changes: 48 additions & 28 deletions pyomo/contrib/doe/tests/test_doe_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
# Solutions of Sandia, LLC, the U.S. Government retains certain rights in this
# software. This software is distributed under the 3-clause BSD License.
# ____________________________________________________________________________________
import json
import os.path

from pyomo.common.dependencies import (
Expand All @@ -17,20 +16,16 @@
scipy_available,
)

from pyomo.common.fileutils import this_file_dir
import pyomo.common.unittest as unittest

if not (numpy_available and scipy_available):
raise unittest.SkipTest("Pyomo.DoE needs scipy and numpy to run tests")

if scipy_available:
from pyomo.contrib.doe import DesignOfExperiments
from pyomo.contrib.doe.examples.reactor_example import (
ReactorExperiment as FullReactorExperiment,
)
from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import (
RooneyBieglerExperiment,
)
from pyomo.contrib.doe import DesignOfExperiments
from pyomo.contrib.doe.examples.polynomial import PolynomialExperiment
from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import (
RooneyBieglerExperiment,
)

from pyomo.contrib.doe.examples.rooney_biegler_doe_example import run_rooney_biegler_doe
import pyomo.environ as pyo
Expand All @@ -39,14 +34,6 @@

ipopt_available = SolverFactory("ipopt").available()

currdir = this_file_dir()
file_path = os.path.join(currdir, "..", "examples", "result.json")

with open(file_path) as f:
data_ex = json.load(f)

data_ex["control_points"] = {float(k): v for k, v in data_ex["control_points"].items()}


def get_rooney_biegler_experiment():
"""Get a fresh RooneyBieglerExperiment instance for testing.
Expand Down Expand Up @@ -148,8 +135,7 @@ def get_standard_args(experiment, fd_method, obj_used):


@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available")
@unittest.skipIf(not numpy_available, "Numpy is not available")
@unittest.skipIf(not scipy_available, "scipy is not available")
# Tests require NumPy/SciPy, but availability is checked by the file-level SkipTest.
@unittest.skipIf(not pandas_available, "pandas is not available")
class TestDoeBuild(unittest.TestCase):
def test_rooney_biegler_fd_central_check_fd_eqns(self):
Expand Down Expand Up @@ -267,6 +253,35 @@ def test_rooney_biegler_fd_forward_check_fd_eqns(self):
other_param_val = pyo.value(k)
self.assertAlmostEqual(other_param_val, v)

def test_polynomial_example_labels(self):
experiment = PolynomialExperiment()
model = experiment.get_labeled_model()

self.assertEqual(len(model.experiment_outputs), 1)
self.assertEqual(len(model.measurement_error), 1)
self.assertEqual(len(model.experiment_inputs), 2)
self.assertEqual(len(model.unknown_parameters), 4)

self.assertIn(model.y, model.experiment_outputs)
self.assertIn(model.y, model.measurement_error)
self.assertIn(model.x1, model.experiment_inputs)
self.assertIn(model.x2, model.experiment_inputs)

def test_polynomial_example_create_doe_model_pynumero(self):
experiment = PolynomialExperiment()

DoE_args = get_standard_args(
experiment, fd_method="central", obj_used="determinant"
)
DoE_args["gradient_method"] = "pynumero"

doe_obj = DesignOfExperiments(**DoE_args)
doe_obj.create_doe_model()

model = doe_obj.model
self.assertEqual(len(model.scenarios), 1)
self.assertTrue(hasattr(model.scenario_blocks[0], "jac_variables_wrt_param"))

def test_rooney_biegler_fd_central_design_fixing(self):
fd_method = "central"
obj_used = "pseudo_trace"
Expand Down Expand Up @@ -510,21 +525,26 @@ def test_generate_blocks_without_model(self):
)


class TestReactorExample(unittest.TestCase):
def test_reactor_update_suffix_items(self):
"""Test the reactor example with updating suffix items."""
from pyomo.contrib.doe.examples.update_suffix_doe_example import main
class TestRooneyBieglerExample(unittest.TestCase):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am a little confused why this PR changes these files. I thought we already swapped out the reactor example for the consolidated RooneyBiegler example in a previous PR. My practical recommendation is to merge #3866 first, as it is really close and it is the last active Pyomo.DoE PR besides this current PR. After we merge @smondal13's PR, we can look at what this PR is changing relative to the new main.

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.

@snarasi2 , I think the class name here is misleading

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.

After merging and reviewing merge conflicts, decide if this test should be moved to test_utils.py

@unittest.skipIf(not pandas_available, "pandas is not available")
def test_rooney_biegler_update_suffix_items(self):
"""Test updating suffix items on the lightweight Rooney-Biegler model."""
from pyomo.contrib.parmest.utils.model_utils import update_model_from_suffix

experiment = get_rooney_biegler_experiment()
model = experiment.get_labeled_model()
suffix_obj = model.measurement_error
orig_vals = np.array(list(suffix_obj.values()))
new_vals = orig_vals + 1

# Run the reactor update suffix items example
suffix_obj, _, new_vals = main()
update_model_from_suffix(suffix_obj, new_vals)

# Check that the suffix object has been updated correctly
for i, v in enumerate(suffix_obj.values()):
self.assertAlmostEqual(v, new_vals[i], places=6)


@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available")
@unittest.skipIf(not numpy_available, "Numpy is not available")
# Test requires NumPy, but availability is checked by the file-level SkipTest.
@unittest.skipIf(not pandas_available, "pandas is not available")
class TestDoEObjectiveOptions(unittest.TestCase):
def test_trace_constraints(self):
Expand Down
Loading