[Pyomo.DoE] Add simultaneous design of multiple experiments#3866
[Pyomo.DoE] Add simultaneous design of multiple experiments#3866smondal13 wants to merge 204 commits into
Conversation
…`experiment` argument is provided.
…d replace `self.experiment` with `self.experiment_list[0]`. `doe/reactor_example.py` runs successfully.
…sing the `doe/reactor_multi_experiment.py`
…ultiexperiment.py` both for 3 experiments
…etric-uncertainty
…parametric-uncertainty
…nt for `optimize_experiments()` and `compute_FIM()`
… both of sensitivity and optimize_experiments()
…A-opt gave different result for grid and optimization
adowling2
left a comment
There was a problem hiding this comment.
I think this is practically ready. I requested a few small comments. Also, there is a merge conflict (I think).
@adowling2 I have addressed your comments. @adowling2 @mrmundt, I have resolved the conflicts. The conflicts were from @adowling2's recent PR #3867 merge into |
|
@mrmundt @blnicho, the following two tests are failing. I think these two are not part of my PR |
|
@smondal13 those failures were resolved in #3965 and I merged main into your PR to pick up the fix. |
blnicho
left a comment
There was a problem hiding this comment.
@smondal13 I still have 4 more files to go through but here are my comments so far.
| if hasattr(m, "sym_break_cons"): | ||
| m.sym_break_cons.clear() | ||
| else: | ||
| m.sym_break_cons = pyo.Suffix(direction=pyo.Suffix.LOCAL) | ||
| m.sym_break_cons[m.hour] = None |
There was a problem hiding this comment.
Why is this needed? Are you adding a new required sym_break_cons Suffix as part of this PR?
There was a problem hiding this comment.
Yes — in multi-experiment design, sym_break_cons is an optional suffix that lets the user specify which experiment input should be used for symmetry breaking. If the user does not provide it, we automatically use the first variable in the experiment_inputs suffix.
We do this to reduce permutation symmetry and save solver time. In multi-experiment design, the order of experiments does not matter. For example, if we are designing two experiments with design variable T, then the designs (exp 1: T = 300 K, exp 2: T = 350 K) and (exp 1: T = 350 K, exp 2: T = 300 K) are equivalent. The symmetry-breaking constraint helps restrict these equivalent permutations so the solver only considers one ordering.
| m.temp = pyo.Var(initialize=self.temp, bounds=(280.0, 340.0)) | ||
| m.temp.fix() | ||
|
|
||
| # Replace base Rooney-Biegler response with two-input synthetic variant | ||
| # used only for symmetry-breaking tests. | ||
| m.del_component(m.response_function) | ||
| m.response_function = pyo.Constraint( | ||
| expr=m.y | ||
| == m.asymptote * (1 - pyo.exp(-m.rate_constant * m.hour)) + 0.01 * m.temp | ||
| ) | ||
| m.experiment_inputs[m.temp] = self.temp |
There was a problem hiding this comment.
If this is only used for symmetry-breaking tests, should this code be moved inside the if-statement below that is checking if the sym_break_flag is set?
There was a problem hiding this comment.
We keep that code outside the sym_break_flag conditional because this helper is intended to be a two-input experiment in all cases. The flag only controls whether we add an explicit sym_break_cons suffix.
We still need the temp input and modified response when sym_break_flag=0, because that case is used to test the automatic fallback behavior when no explicit symmetry-breaking marker is provided. In that path, the code should infer the symmetry-breaking variable from experiment_inputs. Moving the two-input setup inside the conditional would remove that coverage.
| # The exact number of initialization solves is implementation-dependent, | ||
| # but they must all occur before the one final main-solver call. |
There was a problem hiding this comment.
Shouldn't this number be deterministic and known for this test?
There was a problem hiding this comment.
In this test, init_solver is also used during finite-difference scenario construction and the square initialization solve, so it is expected to be called multiple times (deterministically) before the final optimization solve.
There was a problem hiding this comment.
Right but my point is that for this specific example problem you should know exactly how many times init_solver will be called so why does the comment imply that the exact number is "unknowable" and the test check for a positive number of calls instead of the exact number?
There was a problem hiding this comment.
You’re right. For this test, the init_solver call count is deterministic, so the assertion should be exact rather than a lower bound. I have added self.assertEqual(init_solver.calls, 11) instead of the previously used assertGreaterEqual.
…cstring for clarity and update test cases to use make_ipopt_solver() directly
… reflect deterministic behavior
blnicho
left a comment
There was a problem hiding this comment.
@smondal13 here is my next set of comments. I have 2 more files to look at.
Co-authored-by: Bethany Nicholson <blnicho@users.noreply.github.com>
… make_ipopt_solver directly
Co-authored-by: Bethany Nicholson <blnicho@users.noreply.github.com>
- Update the square-solve stub helper to use direct solver attribute access so the merged test helper matches the latest upstream cleanup. - Preserve the local DOE error-test change that calls make_ipopt_solver() directly, which keeps the solver setup explicit and avoids the extra wrapper.
…zation test with deterministic FIM input
…g test with fixed expected points
Fixes # .
Summary/Motivation:
This PR adds a new
DesignOfExperiments.optimize_experiments()API inpyomo/contrib/doe/doe.pyto support simultaneous optimization of multiple experiments in one workflow. The motivation is to provide a multi-experiment DoE interface with stronger initialization options, clearer mode handling (template vs. user-initialized experiments), and richer diagnostics/results than the existing single-experiment path.Changes proposed in this PR:
optimize_experiments()for multi-experiment DoE optimization.n_exp.n_expis inferred/validated.initialization_method="lhs") with controls for:sym_break_conssuffix,run_info._DoEResultsJSONEncoderfor numpy/Pyomo-enum values when writingresults_file.Note:
documentation.mdwhich describes the API. This documentation is to help the reviewers to understand the API and will not be merged intoPyomo:mainRemove before merging
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: