-
Notifications
You must be signed in to change notification settings - Fork 577
PyROS Add caching for computed uncertain parameter bounds #3877
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
base: main
Are you sure you want to change the base?
Changes from 30 commits
53bbc30
b6a03aa
65e9564
a483ab6
549d5b8
bb7fca5
a6ef4f5
3a955f6
d003145
c573512
f0e86b6
2af61c8
bb53c9d
be21c04
7619f3e
81a98ab
4f78486
581a9f7
7aeeef5
6de83b7
31e96fd
43f09d8
ee87245
c1145cd
d905ab4
5518ee8
d954aba
c242b6a
e0863b0
4ebca37
6259595
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3985,7 +3985,7 @@ def solve(self, model, *args, **kwargs): | |||||||||||||||||||
|
|
||||||||||||||||||||
| class TestPyROSUnavailableSubsolvers(unittest.TestCase): | ||||||||||||||||||||
| """ | ||||||||||||||||||||
| Check that appropriate exceptionsa are raised if | ||||||||||||||||||||
| Check that appropriate exceptions are raised if | ||||||||||||||||||||
| PyROS is invoked with unavailable subsolvers. | ||||||||||||||||||||
| """ | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
@@ -5232,5 +5232,252 @@ def test_discrete_set_subsolver_error_recovery(self, name, sec_con_UB): | |||||||||||||||||||
| ) | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| # @SolverFactory.register("slow_solver") | ||||||||||||||||||||
| class SlowSolver: | ||||||||||||||||||||
| """ | ||||||||||||||||||||
| Solver which sleeps for a specified time before solving. | ||||||||||||||||||||
| """ | ||||||||||||||||||||
|
|
||||||||||||||||||||
| def __init__(self, sleep_time, sub_solver): | ||||||||||||||||||||
| self.sleep_time = sleep_time | ||||||||||||||||||||
| self.sub_solver = sub_solver | ||||||||||||||||||||
|
|
||||||||||||||||||||
| self.options = Bunch() | ||||||||||||||||||||
|
|
||||||||||||||||||||
| def available(self, exception_flag=True): | ||||||||||||||||||||
| return True | ||||||||||||||||||||
|
|
||||||||||||||||||||
| def license_is_valid(self): | ||||||||||||||||||||
| return True | ||||||||||||||||||||
|
|
||||||||||||||||||||
| def __enter__(self): | ||||||||||||||||||||
| return self | ||||||||||||||||||||
|
|
||||||||||||||||||||
| def __exit__(self, et, ev, tb): | ||||||||||||||||||||
| pass | ||||||||||||||||||||
|
|
||||||||||||||||||||
| def solve(self, model, **kwargs): | ||||||||||||||||||||
| """ | ||||||||||||||||||||
| Sleep, then solve a model. | ||||||||||||||||||||
|
|
||||||||||||||||||||
| Parameters | ||||||||||||||||||||
| ---------- | ||||||||||||||||||||
| model : ConcreteModel | ||||||||||||||||||||
| Model of interest. | ||||||||||||||||||||
|
|
||||||||||||||||||||
| Returns | ||||||||||||||||||||
| ------- | ||||||||||||||||||||
| results : SolverResults | ||||||||||||||||||||
| Solver results. | ||||||||||||||||||||
| """ | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # ensure only one active objective | ||||||||||||||||||||
| active_objs = [ | ||||||||||||||||||||
| obj for obj in model.component_data_objects(Objective, active=True) | ||||||||||||||||||||
| ] | ||||||||||||||||||||
| assert len(active_objs) == 1 | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # sleep for specified time | ||||||||||||||||||||
| time.sleep(self.sleep_time) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| print("I slept. Now, I will solve.") | ||||||||||||||||||||
| # invoke subsolver | ||||||||||||||||||||
| results = self.sub_solver.solve(model, **kwargs) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| return results | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| class CustomExactBoundsUncertaintySet(BoxSet): | ||||||||||||||||||||
| """ | ||||||||||||||||||||
| Custom uncertainty set that always solves optimization bounding problems. | ||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider modifying this to be more precise about the "optimization bounding problems". Something to the effect of:
Suggested change
|
||||||||||||||||||||
| """ | ||||||||||||||||||||
|
|
||||||||||||||||||||
| def __init__(self, bounds, sleep_time, cache): | ||||||||||||||||||||
| super().__init__(bounds) | ||||||||||||||||||||
| self.sleep_time = sleep_time | ||||||||||||||||||||
| self.cache = cache | ||||||||||||||||||||
|
|
||||||||||||||||||||
| @property | ||||||||||||||||||||
| def parameter_bounds(self): | ||||||||||||||||||||
| """ | ||||||||||||||||||||
| Solve bounding problems to calculate exact parameter bounds. | ||||||||||||||||||||
| """ | ||||||||||||||||||||
| solver = SlowSolver( | ||||||||||||||||||||
| sub_solver=SolverFactory("baron"), sleep_time=self.sleep_time | ||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you sure that BARON is needed here? If so, then shouldn't the tests defined in |
||||||||||||||||||||
| ) | ||||||||||||||||||||
| bounds = self._compute_exact_parameter_bounds(solver=solver) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if not self.cache: | ||||||||||||||||||||
| self._cache.clear() | ||||||||||||||||||||
|
|
||||||||||||||||||||
| return bounds | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| @unittest.skipUnless(ipopt_available, "IPOPT is not available.") | ||||||||||||||||||||
| class TestPyROSCache(unittest.TestCase): | ||||||||||||||||||||
| """ | ||||||||||||||||||||
| Test PyROS cache creation and clearing. | ||||||||||||||||||||
| """ | ||||||||||||||||||||
|
Comment on lines
+5317
to
+5320
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider altering the name and docstring here to be more descriptive (of the "cache"). Perhaps something to the effect of
Suggested change
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| def test_pyros_cache_creation(self): | ||||||||||||||||||||
| """ | ||||||||||||||||||||
| Check that PyROS creates a cache for storing computed exact parameter bounds. | ||||||||||||||||||||
| """ | ||||||||||||||||||||
| m = build_leyffer_two_cons() | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Define the uncertainty set | ||||||||||||||||||||
| interval = CustomExactBoundsUncertaintySet( | ||||||||||||||||||||
| bounds=[(0.25, 2)], sleep_time=0, cache=True | ||||||||||||||||||||
| ) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Instantiate the PyROS solver | ||||||||||||||||||||
| pyros_solver = SolverFactory("pyros") | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Define subsolvers utilized in the algorithm | ||||||||||||||||||||
| local_subsolver = SolverFactory("ipopt") | ||||||||||||||||||||
| global_subsolver = SolverFactory("ipopt") | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # check cache exists | ||||||||||||||||||||
| self.assertTrue(hasattr(interval, "_cache")) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Call the PyROS solver | ||||||||||||||||||||
| results = pyros_solver.solve( | ||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since
Suggested change
|
||||||||||||||||||||
| model=m, | ||||||||||||||||||||
| first_stage_variables=[m.x1], | ||||||||||||||||||||
| second_stage_variables=[m.x2], | ||||||||||||||||||||
| uncertain_params=[m.u], | ||||||||||||||||||||
| uncertainty_set=interval, | ||||||||||||||||||||
| local_solver=local_subsolver, | ||||||||||||||||||||
| global_solver=global_subsolver, | ||||||||||||||||||||
| options={ | ||||||||||||||||||||
| "objective_focus": ObjectiveType.worst_case, | ||||||||||||||||||||
| "solve_master_globally": True, | ||||||||||||||||||||
| }, | ||||||||||||||||||||
| ) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # check cache has been cleared after solve | ||||||||||||||||||||
| self.assertTrue(hasattr(interval, "_cache")) | ||||||||||||||||||||
| self.assertEqual( | ||||||||||||||||||||
| interval._cache, {}, msg="Did not clear uncertainty set cache after solve." | ||||||||||||||||||||
| ) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| def test_pyros_cache_time(self): | ||||||||||||||||||||
| """ | ||||||||||||||||||||
| Check that caching improves solve time. | ||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||
| """ | ||||||||||||||||||||
| m = build_leyffer_two_cons() | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Define the uncertainty set | ||||||||||||||||||||
| interval_cache = CustomExactBoundsUncertaintySet( | ||||||||||||||||||||
| bounds=[(0.25, 2)], sleep_time=0.1, cache=True | ||||||||||||||||||||
| ) | ||||||||||||||||||||
| interval_no_cache = CustomExactBoundsUncertaintySet( | ||||||||||||||||||||
| bounds=[(0.25, 2)], sleep_time=0.1, cache=False | ||||||||||||||||||||
| ) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Instantiate the PyROS solver | ||||||||||||||||||||
| pyros_solver = SolverFactory("pyros") | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Define subsolvers utilized in the algorithm | ||||||||||||||||||||
| local_subsolver = SolverFactory("ipopt") | ||||||||||||||||||||
| global_subsolver = SolverFactory("ipopt") | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Call the PyROS solver | ||||||||||||||||||||
| results_cache = pyros_solver.solve( | ||||||||||||||||||||
| model=m, | ||||||||||||||||||||
| first_stage_variables=[m.x1], | ||||||||||||||||||||
| second_stage_variables=[m.x2], | ||||||||||||||||||||
| uncertain_params=[m.u], | ||||||||||||||||||||
| uncertainty_set=interval_cache, | ||||||||||||||||||||
| local_solver=local_subsolver, | ||||||||||||||||||||
| global_solver=global_subsolver, | ||||||||||||||||||||
| options={ | ||||||||||||||||||||
| "objective_focus": ObjectiveType.worst_case, | ||||||||||||||||||||
| "solve_master_globally": True, | ||||||||||||||||||||
| }, | ||||||||||||||||||||
| ) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| results_no_cache = pyros_solver.solve( | ||||||||||||||||||||
| model=m, | ||||||||||||||||||||
| first_stage_variables=[m.x1], | ||||||||||||||||||||
| second_stage_variables=[m.x2], | ||||||||||||||||||||
| uncertain_params=[m.u], | ||||||||||||||||||||
| uncertainty_set=interval_no_cache, | ||||||||||||||||||||
| local_solver=local_subsolver, | ||||||||||||||||||||
| global_solver=global_subsolver, | ||||||||||||||||||||
| options={ | ||||||||||||||||||||
| "objective_focus": ObjectiveType.worst_case, | ||||||||||||||||||||
| "solve_master_globally": True, | ||||||||||||||||||||
| }, | ||||||||||||||||||||
| ) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # caching should always result in less time, | ||||||||||||||||||||
| # as not caching reruns the slow solver multiple times | ||||||||||||||||||||
| self.assertGreater(results_no_cache.time, results_cache.time) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| def test_pyros_cache_solutions(self): | ||||||||||||||||||||
| """ | ||||||||||||||||||||
| Check that PyROS clears cache before/after and yields accurate results. | ||||||||||||||||||||
| """ | ||||||||||||||||||||
|
Comment on lines
+5418
to
+5421
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test name and docstring here can be modified for consistency with what this test is checking. It seems this test checks that an
Suggested change
|
||||||||||||||||||||
| m = build_leyffer_two_cons() | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Define the uncertainty set | ||||||||||||||||||||
| interval = CustomExactBoundsUncertaintySet( | ||||||||||||||||||||
| bounds=[(25, 200)], sleep_time=0, cache=True | ||||||||||||||||||||
| ) | ||||||||||||||||||||
| self.assertEqual(interval.parameter_bounds, [(25, 200)]) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # change set attributes, leading to outdated parameter bounds | ||||||||||||||||||||
| interval.bounds = [(0.25, 2)] | ||||||||||||||||||||
| self.assertEqual(interval.parameter_bounds, [(25, 200)]) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Instantiate the PyROS solver | ||||||||||||||||||||
| pyros_solver = SolverFactory("pyros") | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Define subsolvers utilized in the algorithm | ||||||||||||||||||||
| local_subsolver = SolverFactory("ipopt") | ||||||||||||||||||||
| global_subsolver = SolverFactory("ipopt") | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Solve with PyROS | ||||||||||||||||||||
| exc_str = r"Uncertainty set cache has been modified." | ||||||||||||||||||||
| with self.assertRaisesRegex(AssertionError, exc_str): | ||||||||||||||||||||
| results = pyros_solver.solve( | ||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since
Suggested change
|
||||||||||||||||||||
| model=m, | ||||||||||||||||||||
| first_stage_variables=[m.x1], | ||||||||||||||||||||
| second_stage_variables=[m.x2], | ||||||||||||||||||||
| uncertain_params=[m.u], | ||||||||||||||||||||
| uncertainty_set=interval, | ||||||||||||||||||||
| local_solver=local_subsolver, | ||||||||||||||||||||
| global_solver=global_subsolver, | ||||||||||||||||||||
| options={ | ||||||||||||||||||||
| "objective_focus": ObjectiveType.worst_case, | ||||||||||||||||||||
| "solve_master_globally": True, | ||||||||||||||||||||
| }, | ||||||||||||||||||||
| ) | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You may also want to check here that state of the cache is unchanged by PyROS if the
Suggested change
|
||||||||||||||||||||
| # modify the cache | ||||||||||||||||||||
| interval._cache[0, minimize] = 25 | ||||||||||||||||||||
| interval._cache[0, maximize] = 200 | ||||||||||||||||||||
|
|
||||||||||||||||||||
| self.assertEqual(interval.parameter_bounds, [(25, 200)]) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Solve with PyROS | ||||||||||||||||||||
| exc_str = r"Uncertainty set cache has been modified." | ||||||||||||||||||||
| with self.assertRaisesRegex(AssertionError, exc_str): | ||||||||||||||||||||
| results = pyros_solver.solve( | ||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since
Suggested change
|
||||||||||||||||||||
| model=m, | ||||||||||||||||||||
| first_stage_variables=[m.x1], | ||||||||||||||||||||
| second_stage_variables=[m.x2], | ||||||||||||||||||||
| uncertain_params=[m.u], | ||||||||||||||||||||
| uncertainty_set=interval, | ||||||||||||||||||||
| local_solver=local_subsolver, | ||||||||||||||||||||
| global_solver=global_subsolver, | ||||||||||||||||||||
| options={ | ||||||||||||||||||||
| "objective_focus": ObjectiveType.worst_case, | ||||||||||||||||||||
| "solve_master_globally": True, | ||||||||||||||||||||
| }, | ||||||||||||||||||||
| ) | ||||||||||||||||||||
|
Comment on lines
+5458
to
+5479
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems that the first two assignments here: interval._cache[0, minimize] = 25
interval._cache[0, maximize] = 200do not alter the state of |
||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| if __name__ == "__main__": | ||||||||||||||||||||
| unittest.main() | ||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After PR #3951 is either merged or closed, you may update the PyROS version number (in
pyros.py) and further update the changelog here according to this comment. In particular, if #3951 is closed rather than merged, then the final version number here will be 1.3.14.