|
67 | 67 |
|
68 | 68 | from pyomo.contrib.pyros.solve_data import ROSolveResults
|
69 | 69 | from pyomo.contrib.pyros.uncertainty_sets import (
|
70 |
| - BoxSet, |
| 70 | + _setup_standard_uncertainty_set_constraint_block, |
71 | 71 | AxisAlignedEllipsoidalSet,
|
| 72 | + BoxSet, |
| 73 | + DiscreteScenarioSet, |
72 | 74 | FactorModelSet,
|
| 75 | + Geometry, |
73 | 76 | IntersectionSet,
|
74 |
| - DiscreteScenarioSet, |
| 77 | + UncertaintyQuantification, |
| 78 | + UncertaintySet, |
75 | 79 | )
|
76 | 80 | from pyomo.contrib.pyros.util import (
|
77 | 81 | IterationLogRecord,
|
@@ -3771,6 +3775,217 @@ def test_pyros_write_separation_problem(self):
|
3771 | 3775 | )
|
3772 | 3776 |
|
3773 | 3777 |
|
| 3778 | +class ZeroDimensionalSet(UncertaintySet): |
| 3779 | + @property |
| 3780 | + def geometry(self): |
| 3781 | + return Geometry.LINEAR |
| 3782 | + |
| 3783 | + @property |
| 3784 | + def parameter_bounds(self): |
| 3785 | + return [] |
| 3786 | + |
| 3787 | + @property |
| 3788 | + def dim(self): |
| 3789 | + return 0 |
| 3790 | + |
| 3791 | + @property |
| 3792 | + def type(self): |
| 3793 | + return "zero-d" |
| 3794 | + |
| 3795 | + def validate(self, config): |
| 3796 | + pass |
| 3797 | + |
| 3798 | + def point_in_set(self, point): |
| 3799 | + if list(point): |
| 3800 | + raise ValueError |
| 3801 | + return True |
| 3802 | + |
| 3803 | + def set_as_constraint(self, uncertain_params=None, block=None): |
| 3804 | + block, params, cons, auxvars = _setup_standard_uncertainty_set_constraint_block( |
| 3805 | + block=block, |
| 3806 | + uncertain_param_vars=uncertain_params, |
| 3807 | + num_auxiliary_vars=None, |
| 3808 | + dim=0, |
| 3809 | + ) |
| 3810 | + return UncertaintyQuantification( |
| 3811 | + block=block, |
| 3812 | + uncertain_param_vars=params, |
| 3813 | + auxiliary_vars=auxvars, |
| 3814 | + uncertainty_cons=list(cons.values()), |
| 3815 | + ) |
| 3816 | + |
| 3817 | + |
| 3818 | +class TestPyROSNoVarsParams(unittest.TestCase): |
| 3819 | + """ |
| 3820 | + Test PyROS is capable of solving models without variables |
| 3821 | + or uncertain parameters. |
| 3822 | + """ |
| 3823 | + |
| 3824 | + @unittest.skipUnless(ipopt_available, "IPOPT is not available") |
| 3825 | + def test_pyros_trivial_block(self): |
| 3826 | + """ |
| 3827 | + Test PyROS solver successfully operates on a model |
| 3828 | + with no variables, constraints, or uncertain parameters. |
| 3829 | + """ |
| 3830 | + mdl = ConcreteModel() |
| 3831 | + mdl.obj = Objective(expr=0) |
| 3832 | + |
| 3833 | + # prepare solvers |
| 3834 | + ipopt = SolverFactory("ipopt") |
| 3835 | + pyros = SolverFactory("pyros") |
| 3836 | + |
| 3837 | + with LoggingIntercept(level=logging.WARNING) as LOG: |
| 3838 | + res = pyros.solve( |
| 3839 | + model=mdl, |
| 3840 | + first_stage_variables=[], |
| 3841 | + second_stage_variables=[], |
| 3842 | + uncertain_params=[], |
| 3843 | + uncertainty_set=ZeroDimensionalSet(), |
| 3844 | + local_solver=ipopt, |
| 3845 | + global_solver=ipopt, |
| 3846 | + objective_focus="worst_case", |
| 3847 | + ) |
| 3848 | + |
| 3849 | + log_msg = LOG.getvalue() |
| 3850 | + self.assertRegex( |
| 3851 | + log_msg, |
| 3852 | + "NOTE: No variables.*appear in the active model objective.*constraints", |
| 3853 | + ) |
| 3854 | + # need 2 iterations to satisfy epigraph constraint |
| 3855 | + # due to worst-case objective focus |
| 3856 | + self.assertEqual(res.iterations, 1) |
| 3857 | + self.assertAlmostEqual(res.final_objective_value, 0) |
| 3858 | + self.assertEqual( |
| 3859 | + res.pyros_termination_condition, pyrosTerminationCondition.robust_feasible |
| 3860 | + ) |
| 3861 | + |
| 3862 | + @parameterized.expand([[True], [False]]) |
| 3863 | + @unittest.skipUnless(ipopt_available, "IPOPT is not available") |
| 3864 | + def test_pyros_only_state_vars(self, add_x_out_of_scope): |
| 3865 | + """ |
| 3866 | + Test PyROS solver successfully operates on a model with |
| 3867 | + no first-stage variables or second-stage variables in |
| 3868 | + the problem scope. |
| 3869 | + """ |
| 3870 | + mdl = ConcreteModel() |
| 3871 | + mdl.q = Param(initialize=0.5, mutable=True) |
| 3872 | + if add_x_out_of_scope: |
| 3873 | + mdl.x = Var(bounds=[1, 2]) |
| 3874 | + mdl.y = Var(initialize=0.5) |
| 3875 | + mdl.eq = Constraint(expr=mdl.y == mdl.q) |
| 3876 | + mdl.obj = Objective(expr=mdl.y) |
| 3877 | + |
| 3878 | + # prepare solvers |
| 3879 | + ipopt = SolverFactory("ipopt") |
| 3880 | + pyros = SolverFactory("pyros") |
| 3881 | + |
| 3882 | + with LoggingIntercept(level=logging.WARNING) as LOG: |
| 3883 | + res = pyros.solve( |
| 3884 | + model=mdl, |
| 3885 | + # note: if 'x' was declared, then it is out of scope, |
| 3886 | + # (not in active objective or constraints) |
| 3887 | + # so still no DOF variables in scope |
| 3888 | + first_stage_variables=mdl.x if add_x_out_of_scope else [], |
| 3889 | + second_stage_variables=[], |
| 3890 | + uncertain_params=[mdl.q], |
| 3891 | + uncertainty_set=BoxSet([[0, 1]]), |
| 3892 | + local_solver=ipopt, |
| 3893 | + global_solver=ipopt, |
| 3894 | + objective_focus="worst_case", |
| 3895 | + ) |
| 3896 | + |
| 3897 | + log_msg = LOG.getvalue() |
| 3898 | + self.assertRegex( |
| 3899 | + log_msg, "NOTE: No user-provided first-stage variables or second-stage.*" |
| 3900 | + ) |
| 3901 | + # need 2 iterations to satisfy epigraph constraint |
| 3902 | + # due to worst-case objective focus |
| 3903 | + self.assertEqual(res.iterations, 2) |
| 3904 | + self.assertAlmostEqual(res.final_objective_value, 1) |
| 3905 | + self.assertEqual( |
| 3906 | + res.pyros_termination_condition, pyrosTerminationCondition.robust_feasible |
| 3907 | + ) |
| 3908 | + |
| 3909 | + @parameterized.expand([[True], [False]]) |
| 3910 | + @unittest.skipUnless(ipopt_available, "IPOPT is not available") |
| 3911 | + def test_pyros_no_vars(self, add_var_out_of_scope): |
| 3912 | + """ |
| 3913 | + Test PyROS solver successfully operates on a model with |
| 3914 | + no variables appearing in the active model objective |
| 3915 | + or constraints. |
| 3916 | + """ |
| 3917 | + mdl = ConcreteModel() |
| 3918 | + mdl.q = Param(initialize=0.5, mutable=True) |
| 3919 | + if add_var_out_of_scope: |
| 3920 | + # note: if declared, does not appear in active |
| 3921 | + # objective/constraints, so out of scope |
| 3922 | + mdl.x = Var(bounds=[1, mdl.q]) |
| 3923 | + mdl.obj = Objective(expr=mdl.q) |
| 3924 | + |
| 3925 | + # prepare solvers |
| 3926 | + ipopt = SolverFactory("ipopt") |
| 3927 | + pyros = SolverFactory("pyros") |
| 3928 | + |
| 3929 | + with LoggingIntercept(level=logging.WARNING) as LOG: |
| 3930 | + res = pyros.solve( |
| 3931 | + model=mdl, |
| 3932 | + first_stage_variables=[], |
| 3933 | + second_stage_variables=[], |
| 3934 | + uncertain_params=[mdl.q], |
| 3935 | + uncertainty_set=BoxSet([[0, 1]]), |
| 3936 | + local_solver=ipopt, |
| 3937 | + global_solver=ipopt, |
| 3938 | + ) |
| 3939 | + |
| 3940 | + log_msg = LOG.getvalue() |
| 3941 | + self.assertRegex( |
| 3942 | + log_msg, |
| 3943 | + "NOTE: No variables.*appear in the active model objective.*constraints", |
| 3944 | + ) |
| 3945 | + self.assertEqual(res.iterations, 1) |
| 3946 | + self.assertAlmostEqual(res.final_objective_value, 0.5) |
| 3947 | + self.assertEqual( |
| 3948 | + res.pyros_termination_condition, pyrosTerminationCondition.robust_feasible |
| 3949 | + ) |
| 3950 | + |
| 3951 | + @unittest.skipUnless(ipopt_available, "IPOPT is not available") |
| 3952 | + def test_pyros_no_uncertain_params(self): |
| 3953 | + """ |
| 3954 | + Test PyROS successfully operates on a model with no uncertain |
| 3955 | + parameters (zero-dimensional uncertainty set). |
| 3956 | + """ |
| 3957 | + |
| 3958 | + m = ConcreteModel() |
| 3959 | + m.x = Var(bounds=(1, 2)) |
| 3960 | + m.z = Var(bounds=(1, 2)) |
| 3961 | + m.y = Var(bounds=(1, 2)) |
| 3962 | + m.obj = Objective(expr=m.x**2 + m.z**2 + m.y**2) |
| 3963 | + |
| 3964 | + ipopt = SolverFactory("ipopt") |
| 3965 | + pyros = SolverFactory("pyros") |
| 3966 | + |
| 3967 | + res = pyros.solve( |
| 3968 | + model=m, |
| 3969 | + first_stage_variables=m.x, |
| 3970 | + second_stage_variables=m.z, |
| 3971 | + uncertain_params=[], |
| 3972 | + uncertainty_set=ZeroDimensionalSet(), |
| 3973 | + local_solver=ipopt, |
| 3974 | + global_solver=ipopt, |
| 3975 | + decision_rule_order=1, |
| 3976 | + ) |
| 3977 | + |
| 3978 | + # check results |
| 3979 | + self.assertEqual(res.iterations, 1) |
| 3980 | + self.assertAlmostEqual(res.final_objective_value, 3, places=6) |
| 3981 | + self.assertAlmostEqual(m.x.value, 1) |
| 3982 | + self.assertAlmostEqual(m.z.value, 1) |
| 3983 | + self.assertAlmostEqual(m.y.value, 1) |
| 3984 | + self.assertEqual( |
| 3985 | + res.pyros_termination_condition, pyrosTerminationCondition.robust_feasible |
| 3986 | + ) |
| 3987 | + |
| 3988 | + |
3774 | 3989 | class TestPyROSSolverAdvancedValidation(unittest.TestCase):
|
3775 | 3990 | """
|
3776 | 3991 | Test PyROS solver validation routines result in
|
@@ -3832,35 +4047,6 @@ def test_pyros_multiple_objectives(self):
|
3832 | 4047 | global_solver=global_solver,
|
3833 | 4048 | )
|
3834 | 4049 |
|
3835 |
| - def test_pyros_empty_dof_vars(self): |
3836 |
| - """ |
3837 |
| - Test PyROS solver raises exception raised if there are no |
3838 |
| - first-stage variables or second-stage variables. |
3839 |
| - """ |
3840 |
| - # build model |
3841 |
| - mdl = self.build_simple_test_model() |
3842 |
| - |
3843 |
| - # prepare solvers |
3844 |
| - pyros = SolverFactory("pyros") |
3845 |
| - local_solver = SimpleTestSolver() |
3846 |
| - global_solver = SimpleTestSolver() |
3847 |
| - |
3848 |
| - # perform checks |
3849 |
| - exc_str = ( |
3850 |
| - "Arguments `first_stage_variables` and " |
3851 |
| - "`second_stage_variables` are both empty lists." |
3852 |
| - ) |
3853 |
| - with self.assertRaisesRegex(ValueError, exc_str): |
3854 |
| - pyros.solve( |
3855 |
| - model=mdl, |
3856 |
| - first_stage_variables=[], |
3857 |
| - second_stage_variables=[], |
3858 |
| - uncertain_params=[mdl.u], |
3859 |
| - uncertainty_set=BoxSet([[1 / 4, 2]]), |
3860 |
| - local_solver=local_solver, |
3861 |
| - global_solver=global_solver, |
3862 |
| - ) |
3863 |
| - |
3864 | 4050 | def test_pyros_overlap_dof_vars(self):
|
3865 | 4051 | """
|
3866 | 4052 | Test PyROS solver raises exception raised if there are Vars
|
|
0 commit comments