Skip to content

Add grey box objectives to Pyomo.DoE #3606

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 163 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
163 commits
Select commit Hold shift + click to select a range
ecb4aef
trying to test grey box
djlaky Dec 4, 2024
c9340d7
Confirmed code works fine on Mac
djlaky Dec 4, 2024
ab2f06f
Added skeleton for grey box
djlaky Dec 5, 2024
abf74d4
Added grey box utilities to init.py
djlaky Dec 6, 2024
b95479b
Add log-det greybox functionality to doe
djlaky Dec 6, 2024
ea6831b
Update to call greybox solver
djlaky Dec 10, 2024
c58471e
Merge branch 'Pyomo:main' into add-greybox
djlaky Dec 10, 2024
2483217
Testing math to make sure formula is correct
djlaky Dec 12, 2024
b90f908
Updates for debugging
djlaky Dec 18, 2024
bedb89a
Switch to ma57
djlaky Dec 18, 2024
8486af8
Remove tolerance changes
djlaky Dec 18, 2024
e49ffed
Fixed square solving bug while building scenarios
djlaky Jan 15, 2025
de4090d
Merge branch 'Pyomo:main' into add-greybox
djlaky Mar 13, 2025
d3adc4d
improve output stats on D-opt test
djlaky Mar 13, 2025
1d53b5a
generalizing to E-opt
djlaky Mar 13, 2025
4773e93
more brief changes
djlaky Mar 13, 2025
0298309
Added ME optimality
djlaky Mar 27, 2025
81fea9f
Changed to lower triangle FIM
djlaky Mar 27, 2025
5faf6da
Ran Black
djlaky Mar 27, 2025
652082a
Merge branch 'Pyomo:main' into add-greybox
djlaky Mar 27, 2025
6e0906a
Trying to fix bugs
djlaky Mar 27, 2025
19d8e85
Attempt at adding hessian for D opt
djlaky Mar 27, 2025
41d1817
Reverted lower triangle change
djlaky Apr 3, 2025
52891aa
Merge branch 'Pyomo:main' into add-greybox
djlaky Apr 3, 2025
afb2a41
changed solvers options for CRC solving
Apr 3, 2025
55f4e92
Merge branch 'Pyomo:main' into add-greybox
djlaky Apr 23, 2025
01929f4
Trying triangular FIM again, also add A-opt
djlaky Apr 24, 2025
bb09c9c
Starting to add testing
djlaky Apr 24, 2025
a39766f
Merge branch 'Pyomo:main' into add-greybox
djlaky Apr 28, 2025
73e10b4
Began adding tests. Ran Black
djlaky Apr 28, 2025
b093c48
Add first test.
djlaky Apr 28, 2025
d764cc0
Finishing Jacobian Tests
djlaky Apr 28, 2025
3899d86
Corrected test names.
djlaky Apr 28, 2025
0bb706b
Adding input and output value tests.
djlaky Apr 28, 2025
0670562
Ran black.
djlaky Apr 28, 2025
15b0ecc
Fixed typos
djlaky Apr 28, 2025
364c15b
Remove grey_box_test file
djlaky Apr 28, 2025
2e5ba68
Move grey box objective to its own function
djlaky Apr 29, 2025
94c23b0
Cleaned up some comments/documentation
djlaky Apr 29, 2025
3b583b4
More cleaning
djlaky Apr 29, 2025
aee4ef8
Ran Black.
djlaky Apr 29, 2025
e4c7239
Using MUMPS as solver for now
djlaky Apr 29, 2025
1f23a30
tracking down non-determinism of grey box
djlaky May 14, 2025
2c1375f
Updated simple grey box case study.
djlaky May 14, 2025
dea92fd
Merge branch 'Pyomo:main' into add-greybox
djlaky May 15, 2025
ab7b16c
Removing grey box determinism file
djlaky May 15, 2025
f9548bf
Changed D-opt name, added A-opt to naming
djlaky May 15, 2025
863c245
Set default solver back to ma57
djlaky May 15, 2025
4a3b310
Add input/output name testing
djlaky May 15, 2025
cad480e
Ran black
djlaky May 15, 2025
cf8a1a0
Update D-opt name in doe.py file
djlaky May 15, 2025
cef85c7
Fixed test naming to not overwrite input test
djlaky May 15, 2025
46b929b
Better output initialization for greybox
djlaky May 15, 2025
cb45414
Ran black
djlaky May 15, 2025
5be3e0b
Add a few more tests
djlaky May 15, 2025
f6c364e
Ran black
djlaky May 15, 2025
7f77ec5
Fixed misnamed test.
djlaky May 15, 2025
b2963c5
Checkpoint - trying to add build tests
djlaky May 15, 2025
cf75f32
Found a bug while testing!
djlaky May 15, 2025
7902a66
Fix A-opt build test.
djlaky May 15, 2025
902f99f
Added more tests
djlaky May 15, 2025
917f73b
Adding back Hessian calculation
djlaky May 15, 2025
9c9cc9f
Ran black
djlaky May 15, 2025
e58e0f8
Updated hessian computation for log-det
djlaky May 15, 2025
d8cfda1
Added D-opt hessian test!
djlaky May 15, 2025
3dfbd00
Adding A-opt hessian test!
djlaky May 15, 2025
7dbf0df
Ran black
djlaky May 15, 2025
55bed44
Adding E-opt hessian and test
djlaky May 15, 2025
9bc8c81
Fixed a typo in hessian calculation
djlaky May 15, 2025
cb983ff
Ran black
djlaky May 15, 2025
e9aed55
Added check for objective option in constructor
djlaky May 15, 2025
1931582
Ran black
djlaky May 15, 2025
472feda
Adding objective errors using Enum messages.
djlaky May 15, 2025
2177d5c
Added 4 tests for bad objective lib options
djlaky May 15, 2025
ea96bed
Ran black
djlaky May 15, 2025
e4c99b4
Relaxed the numpy `isclose` tolerances
djlaky May 16, 2025
0085c76
Trying to make vanilla det solve more stable
djlaky May 16, 2025
2c62b04
Merge branch 'Pyomo:main' into add-greybox
djlaky May 20, 2025
3567ad2
Make sure the hessian is lower triangular
djlaky May 21, 2025
7913064
Remove forcing MUMPS as linear solver for tests
djlaky May 21, 2025
61c002c
Changing default solver options for debugging
djlaky May 21, 2025
e1c8b32
Remove rogue debugging print statement
djlaky May 21, 2025
e4ee26c
Trying some solver conditions for grey box
djlaky May 22, 2025
ee4122c
Removing default mumps solving
djlaky May 22, 2025
4374bcc
Added prior for grey box comparison
djlaky May 22, 2025
fe56da0
Adjusting initial point to be close to D-optimal
djlaky May 22, 2025
e92086b
Merge branch 'Pyomo:main' into add-greybox
djlaky May 22, 2025
bf94c30
Ran Black
djlaky May 22, 2025
019fbc7
Added simple rooney biegler example for DoE
djlaky May 23, 2025
3e4b80a
Update comment to describe file.
djlaky May 23, 2025
cf0d7ef
Get rid of unused code.
djlaky May 23, 2025
bdc6441
Added test for solving models with grey box
djlaky May 23, 2025
4a2a7f4
Removed incorrect skip?
djlaky May 23, 2025
e5d0757
Ran black
djlaky May 23, 2025
9e7bfe2
Remove attribute error in DoE class
djlaky May 23, 2025
2a788eb
Cleaning up temporary solver changes
djlaky May 23, 2025
8295b38
Add test for experiment=None in doe costructor
djlaky May 23, 2025
cb8843e
Use the user-provided grey box solver
djlaky May 23, 2025
41b620c
Added scipy dependency
djlaky May 23, 2025
ee6ce8c
Fixed typo
djlaky May 23, 2025
48ef6a8
Add conditional for FIMExternalGreyBox import
djlaky May 23, 2025
17369c4
Add scipy available flag
djlaky May 23, 2025
bf6fa53
Add cyipopt available flag
djlaky May 23, 2025
f6bc331
Updated 'inv' to 'pinv' function to be consistent
djlaky May 23, 2025
ea35361
Delete pyomo/contrib/doe/examples/grey_box_D_opt_comparison.py
djlaky May 23, 2025
ea1cadb
Delete pyomo/contrib/doe/examples/grey_box_E_opt.py
djlaky May 23, 2025
f1f9af2
Delete pyomo/contrib/doe/examples/grey_box_ME_opt.py
djlaky May 23, 2025
f4e225e
Delete pyomo/contrib/doe/examples/grey_box_test_hessian.py
djlaky May 23, 2025
793c6be
Trying to loosen isclose tolerance for cyipopt
djlaky May 23, 2025
7f15294
Remove matplotlib dependency for example file.
djlaky May 27, 2025
bd9dfb0
Updating import statements to avoid scipy import
djlaky May 27, 2025
844e1b8
Added specific solvers for the solve cases.
djlaky May 27, 2025
f6b34e9
Adding test skipping if numpy and scipy are not available.
djlaky May 27, 2025
6c80755
Fixed `pyo` prefix problem.
djlaky May 27, 2025
b77f5c7
Added grey box tee separate from standard tee
djlaky May 28, 2025
e676c93
Adding terminationmessage to check for testing cyipopt
May 29, 2025
c2df2bd
adding check for if cyipopt call is working properly
May 29, 2025
cbc9566
making decoding bytestring more robust
May 29, 2025
3398fb0
More verbose tracking for debugging
May 29, 2025
bf8e031
Moved descriptive message to D-optimal solve, ran black
djlaky May 29, 2025
ad6a0b6
reran black
May 29, 2025
6913070
More test debugging.
djlaky May 29, 2025
cc37d9d
Merge branch 'add-greybox' of https://github.com/djlaky/pyomo into ad…
djlaky May 29, 2025
b00ae6d
fixed typo
djlaky May 29, 2025
7b8277f
More debugging
djlaky May 29, 2025
3f96308
Attempting ma57 failures.
djlaky May 29, 2025
30ad7f1
Final bugfix for skipping cyipopt solves.
djlaky May 29, 2025
6a04415
Fixing import issues and removing old debug stuff
djlaky May 29, 2025
750cd48
fixed scipy bug
djlaky May 29, 2025
a93601d
Merge branch 'Pyomo:main' into add-greybox
djlaky Jun 3, 2025
3b03fda
Add `lazy` safe pynumero imports
djlaky Jun 3, 2025
76830f7
Add safe pynumero imports to doe.py as well
djlaky Jun 3, 2025
635af52
Added safe object naming for FIMExternalGreyBox
djlaky Jun 3, 2025
faeadee
Moved scipy/numpy skip check in testing
djlaky Jun 4, 2025
df420d5
Merge branch 'Pyomo:main' into add-greybox
djlaky Jun 6, 2025
e00d856
Starting wrap of Pyomo.DoE code to 88 lines
djlaky Jun 11, 2025
1965bcf
More 88-line wrapping fixes
djlaky Jun 11, 2025
0a69d97
Further 88-line wrapping cleanup
djlaky Jun 11, 2025
3591bd7
More 88-line wrapping cleanup
djlaky Jun 11, 2025
b2ed0b3
Final 88-line wrapping in doe.py file
djlaky Jun 11, 2025
066d862
Reformatted grey_box_utilities to be 88 per line
djlaky Jun 11, 2025
56e34b1
Removed Param to Var swapping function comment
djlaky Jun 11, 2025
d61bcde
Ran Black
djlaky Jun 11, 2025
e61c10e
Also fixed testing errors file to be 88 per line
djlaky Jun 11, 2025
4e1ba4d
Fixed test doe solve to be 88 per line
djlaky Jun 11, 2025
cc35e84
Fixed test_doe_build 88 per line
djlaky Jun 11, 2025
d5af294
Merge branch 'main' into add-greybox
mrmundt Jun 17, 2025
22c6a12
Merge branch 'Pyomo:main' into add-greybox
djlaky Jun 23, 2025
131b198
Merge branch 'Pyomo:main' into add-greybox
djlaky Jun 24, 2025
9273fff
Reverting some changes
djlaky Jun 25, 2025
4674f34
Fix copyright year.
djlaky Jun 26, 2025
8881cfb
Remove old code
djlaky Jun 26, 2025
4684c5e
TODO vs ToDo nit solved.
djlaky Jun 26, 2025
94b6589
Remove old bug testing line
djlaky Jun 26, 2025
d7b642d
Updated TODO in this file as well.
djlaky Jun 26, 2025
05e47a8
Merge branch 'Pyomo:main' into add-greybox
djlaky Jun 26, 2025
4012fbe
Merge branch 'main' into add-greybox
mrmundt Jun 27, 2025
c440b20
Merge branch 'main' into add-greybox
mrmundt Jun 30, 2025
4e445ec
Updated attribute errors to Dev errors
djlaky Jul 15, 2025
89813ca
Typo fix by @mrmundt
djlaky Jul 15, 2025
4fe5ec8
Merge branch 'add-greybox' of https://github.com/djlaky/pyomo into ad…
djlaky Jul 15, 2025
2ae4004
Removed old code, add verbose comment
djlaky Jul 15, 2025
d634267
Merge branch 'main' into add-greybox
djlaky Jul 15, 2025
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
1 change: 1 addition & 0 deletions pyomo/contrib/doe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
# ___________________________________________________________________________
from .doe import DesignOfExperiments, ObjectiveLib, FiniteDifferenceStep
from .utils import rescale_FIM
from .grey_box_utilities import FIMExternalGreyBox

# Deprecation errors for old Pyomo.DoE interface classes and structures
from pyomo.common.deprecation import deprecated
Expand Down
477 changes: 349 additions & 128 deletions pyomo/contrib/doe/doe.py

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pyomo/contrib/doe/examples/reactor_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ def finalize_model(self):
for t in m.t:
if t in control_points:
cv = control_points[t]
m.T[t].fix()
m.T[t].setlb(self.data["T_bounds"][0])
m.T[t].setub(self.data["T_bounds"][1])
m.T[t] = cv
Expand Down
2 changes: 1 addition & 1 deletion pyomo/contrib/doe/examples/result.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"CA0": 5.0, "CA_bounds": [1.0, 5.0], "CB0": 0.0, "CC0": 0.0, "t_range": [0, 1], "control_points": {"0": 500, "0.125": 300, "0.25": 300, "0.375": 300, "0.5": 300, "0.625": 300, "0.75": 300, "0.875": 300, "1": 300}, "T_bounds": [300, 700], "A1": 84.79, "A2": 371.72, "E1": 7.78, "E2": 15.05}
{"CA0": 5.0, "CA_bounds": [1.0, 5.0], "CB0": 0.0, "CC0": 0.0, "t_range": [0, 1], "control_points": {"0": 500, "0.125": 300, "0.25": 300, "0.375": 300, "0.5": 300, "0.625": 300, "0.75": 300, "0.875": 300, "1": 300}, "T_bounds": [300, 700], "A1": 84.79, "A2": 371.72, "E1": 7.78, "E2": 15.05}
148 changes: 148 additions & 0 deletions pyomo/contrib/doe/examples/rooney_biegler_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# ___________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2025
# 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.
# ___________________________________________________________________________
"""
Rooney Biegler model, based on Rooney, W. C. and Biegler, L. T. (2001). Design for
model parameter uncertainty using nonlinear confidence regions. AIChE Journal,
47(8), 1794-1804.
"""
from pyomo.common.dependencies import numpy as np, pathlib

from pyomo.contrib.doe.examples.rooney_biegler_experiment import (
RooneyBieglerExperimentDoE,
)
from pyomo.contrib.doe import DesignOfExperiments

import pyomo.environ as pyo

import json
import sys


# Example comparing Cholesky factorization
# (standard solve) with grey box objective
# solve for the log-deteriminant of the FIM
# (D-optimality)
def run_rooney_biegler_doe():
# Create a RooneyBiegler Experiment
experiment = RooneyBieglerExperimentDoE(data={'hour': 2, 'y': 10.3})

# Use a central difference, with step size 1e-3
fd_formula = "central"
step_size = 1e-3

# Use the determinant objective with scaled sensitivity matrix
objective_option = "determinant"
scale_nominal_param_value = True

data = [[1, 8.3], [7, 19.8], [2, 10.3], [5, 15.6], [3, 19.0], [4, 16.0]]
FIM_prior = np.zeros((2, 2))
# Calculate prior using existing experiments
for i in range(len(data)):
if i > int(sys.argv[1]):
break
prev_experiment = RooneyBieglerExperimentDoE(
data={'hour': data[i][0], 'y': data[i][1]}
)
doe_obj = DesignOfExperiments(
prev_experiment,
fd_formula=fd_formula,
step=step_size,
objective_option=objective_option,
scale_nominal_param_value=scale_nominal_param_value,
prior_FIM=None,
tee=False,
)

FIM_prior += doe_obj.compute_FIM(method='sequential')

if sys.argv[1] == 0:
FIM_prior[0][0] += 1e-6
FIM_prior[1][1] += 1e-6

# Create the DesignOfExperiments object
# We will not be passing any prior information in this example
# and allow the experiment object and the DesignOfExperiments
# call of ``run_doe`` perform model initialization.
doe_obj = DesignOfExperiments(
experiment,
fd_formula=fd_formula,
step=step_size,
objective_option=objective_option,
scale_constant_value=1,
scale_nominal_param_value=scale_nominal_param_value,
prior_FIM=FIM_prior,
jac_initial=None,
fim_initial=None,
L_diagonal_lower_bound=1e-7,
solver=None,
tee=False,
get_labeled_model_args=None,
_Cholesky_option=True,
_only_compute_fim_lower=True,
)

# Begin optimal DoE
####################
doe_obj.run_doe()

# Print out a results summary
print("Optimal experiment values: ")
print(
"\tOptimal measurement time: {:.2f}".format(
doe_obj.results["Experiment Design"][0]
)
)
print("FIM at optimal design:\n {}".format(np.array(doe_obj.results["FIM"])))
print(
"Objective value at optimal design: {:.2f}".format(
pyo.value(doe_obj.model.objective)
)
)

print(doe_obj.results["Experiment Design Names"])

###################
# End optimal DoE

# Begin optimal greybox DoE
############################
doe_obj_gb = DesignOfExperiments(
experiment,
fd_formula=fd_formula,
step=step_size,
objective_option=objective_option,
use_grey_box_objective=True,
scale_nominal_param_value=scale_nominal_param_value,
prior_FIM=FIM_prior,
tee=False,
)

doe_obj_gb.run_doe()

print("Optimal experiment values: ")
print(
"\tOptimal measurement time: {:.2f}".format(
doe_obj_gb.results["Experiment Design"][0]
)
)
print("FIM at optimal design:\n {}".format(np.array(doe_obj_gb.results["FIM"])))
print(
"Objective value at optimal design: {:.2f}".format(
np.log10(np.exp(pyo.value(doe_obj_gb.model.objective)))
)
)

############################
# End optimal greybox DoE


if __name__ == "__main__":
run_rooney_biegler_doe()
100 changes: 100 additions & 0 deletions pyomo/contrib/doe/examples/rooney_biegler_experiment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# ___________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2025
# 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.
# ___________________________________________________________________________

"""
Rooney Biegler model, based on Rooney, W. C. and Biegler, L. T. (2001). Design for
model parameter uncertainty using nonlinear confidence regions. AIChE Journal,
47(8), 1794-1804.
"""

from pyomo.common.dependencies import pandas as pd
import pyomo.environ as pyo
from pyomo.contrib.parmest.experiment import Experiment


class RooneyBieglerExperimentDoE(Experiment):
def __init__(self, data=None, theta=None):
if data is None:
self.data = {}
self.data['hour'] = 1
self.data['y'] = 8.3
else:
self.data = data
if theta is None:
self.theta = {}
self.theta['asymptote'] = 19.143
self.theta['rate constant'] = 0.5311
else:
self.theta = theta
self.model = None

def create_model(self):
# Creates Roony-Biegler model for
# individual data points as
# an experimental decision.
m = self.model = pyo.ConcreteModel()

# Specify the unknown parameters
m.asymptote = pyo.Var(initialize=self.theta['asymptote'])
m.rate_constant = pyo.Var(initialize=self.theta['rate constant'])

# Fix the unknown parameters
m.asymptote.fix()
m.rate_constant.fix()

# Add the experiment inputs
m.hour = pyo.Var(initialize=self.data['hour'], bounds=(0, 10))

# Fix the experimental design variable
m.hour.fix()

# Add the experiment outputs
m.y = pyo.Var(initialize=self.data['y'])

# Add governing equation
m.response_function = pyo.Constraint(
expr=m.y - m.asymptote * (1 - pyo.exp(-m.rate_constant * m.hour)) == 0
)

def finalize_model(self):
m = self.model
pass

def label_model(self):
m = self.model

# Add y value as experiment output
m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL)
m.experiment_outputs[m.y] = m.y()

# Add measurement error associated with y
# We are assuming a flat error of 0.3
# or about 1-3 percent
m.measurement_error = pyo.Suffix(direction=pyo.Suffix.LOCAL)
m.measurement_error[m.y] = 1

# Add hour as experiment input
# We are deciding when to sample
m.experiment_inputs = pyo.Suffix(direction=pyo.Suffix.LOCAL)
m.experiment_inputs[m.hour] = m.hour()

# Adding the unknown parameters
m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL)
m.unknown_parameters.update(
(k, k.value) for k in [m.asymptote, m.rate_constant]
)

def get_labeled_model(self):
self.create_model()
self.finalize_model()
self.label_model()

return self.model
Loading
Loading