@@ -1998,6 +1998,189 @@ def test_pyros_certain_params_ipopt_degrees_of_freedom(self):
1998
1998
self .assertEqual (m .x .value , 1 )
1999
1999
2000
2000
2001
+ @unittest .skipUnless (baron_available , "BARON not available" )
2002
+ class TestReformulateSecondStageEqualitiesDiscrete (unittest .TestCase ):
2003
+ """
2004
+ Test behavior of PyROS solver when the uncertainty set is
2005
+ discrete and there are second-stage
2006
+ equality constraints that are state-variable independent,
2007
+ and therefore, subject to reformulation.
2008
+ """
2009
+
2010
+ def build_single_stage_model (self ):
2011
+ m = ConcreteModel ()
2012
+ m .x = Var (range (3 ), bounds = [- 2 , 2 ], initialize = 0 )
2013
+ m .q = Param (range (3 ), initialize = 0 , mutable = True )
2014
+ m .c = Param (range (3 ), initialize = {0 : 1 , 1 : 0 , 2 : 1 })
2015
+ m .obj = Objective (expr = sum (m .x [i ] * m .c [i ] for i in m .x ), sense = maximize )
2016
+ # when uncertainty set is discrete, the
2017
+ # preprocessor should write out this constraint for
2018
+ # each scenario as a first-stage constraint
2019
+ m .xq_con = Constraint (expr = sum (m .x [i ] * m .q [i ] for i in m .x ) == 0 )
2020
+ return m
2021
+
2022
+ def build_two_stage_model (self ):
2023
+ m = ConcreteModel ()
2024
+ m .x = Var (bounds = [None , None ], initialize = 0 )
2025
+ m .z = Var (bounds = [- 2 , 2 ], initialize = 0 )
2026
+ m .q = Param (initialize = 2 , mutable = True )
2027
+ m .obj = Objective (expr = m .x + m .z , sense = maximize )
2028
+ # when uncertainty set is discrete, the
2029
+ # preprocessor should write out this constraint for
2030
+ # each scenario as a first-stage constraint
2031
+ m .xz_con = Constraint (expr = m .x + m .q * m .z == 0 )
2032
+ return m
2033
+
2034
+ def test_single_stage_discrete_set_fullrank (self ):
2035
+ m = self .build_single_stage_model ()
2036
+ uncertainty_set = DiscreteScenarioSet (
2037
+ # reformulating second-stage equality for these scenarios
2038
+ # should result in first-stage equalities finally being
2039
+ # (full-column-rank matrix) @ (x) == 0
2040
+ # so x=0 is sole robust feasible solution
2041
+ scenarios = [
2042
+ [0 ] * len (m .q ),
2043
+ [1 ] * len (m .q ),
2044
+ list (range (1 , len (m .q ) + 1 )),
2045
+ [(idx + 1 ) ** 2 for idx in m .q ],
2046
+ ]
2047
+ )
2048
+ baron = SolverFactory ("baron" )
2049
+ res = SolverFactory ("pyros" ).solve (
2050
+ model = m ,
2051
+ first_stage_variables = m .x ,
2052
+ second_stage_variables = [],
2053
+ uncertain_params = m .q ,
2054
+ uncertainty_set = uncertainty_set ,
2055
+ local_solver = baron ,
2056
+ global_solver = baron ,
2057
+ solve_master_globally = True ,
2058
+ bypass_local_separation = True ,
2059
+ objective_focus = "worst_case" ,
2060
+ )
2061
+ self .assertEqual (
2062
+ res .pyros_termination_condition , pyrosTerminationCondition .robust_optimal
2063
+ )
2064
+ self .assertEqual (res .iterations , 1 )
2065
+ self .assertAlmostEqual (res .final_objective_value , 0 , places = 4 )
2066
+ self .assertAlmostEqual (m .x [0 ].value , 0 , places = 4 )
2067
+ self .assertAlmostEqual (m .x [1 ].value , 0 , places = 4 )
2068
+ self .assertAlmostEqual (m .x [2 ].value , 0 , places = 4 )
2069
+
2070
+ def test_single_stage_discrete_set_rank2 (self ):
2071
+ m = self .build_single_stage_model ()
2072
+ uncertainty_set = DiscreteScenarioSet (
2073
+ # reformulating second-stage equality for these scenarios
2074
+ # should make the optimal solution unique
2075
+ scenarios = [[0 ] * len (m .q ), [1 ] * len (m .q ), [(idx + 1 ) ** 2 for idx in m .q ]]
2076
+ )
2077
+ baron = SolverFactory ("baron" )
2078
+ res = SolverFactory ("pyros" ).solve (
2079
+ model = m ,
2080
+ first_stage_variables = m .x ,
2081
+ second_stage_variables = [],
2082
+ uncertain_params = m .q ,
2083
+ uncertainty_set = uncertainty_set ,
2084
+ local_solver = baron ,
2085
+ global_solver = baron ,
2086
+ solve_master_globally = True ,
2087
+ bypass_local_separation = True ,
2088
+ objective_focus = "worst_case" ,
2089
+ )
2090
+ self .assertEqual (
2091
+ res .pyros_termination_condition , pyrosTerminationCondition .robust_optimal
2092
+ )
2093
+ self .assertEqual (res .iterations , 1 )
2094
+ self .assertAlmostEqual (res .final_objective_value , 2 , places = 4 )
2095
+ # optimal solution is unique
2096
+ self .assertAlmostEqual (m .x [0 ].value , 5 / 4 , places = 4 )
2097
+ self .assertAlmostEqual (m .x [1 ].value , - 2 , places = 4 )
2098
+ self .assertAlmostEqual (m .x [2 ].value , 3 / 4 , places = 4 )
2099
+
2100
+ def test_single_stage_discrete_set_rank1 (self ):
2101
+ m = self .build_single_stage_model ()
2102
+ uncertainty_set = DiscreteScenarioSet (
2103
+ scenarios = [[0 ] * len (m .q ), [2 ] * len (m .q ), [3 ] * len (m .q )]
2104
+ )
2105
+ baron = SolverFactory ("baron" )
2106
+ res = SolverFactory ("pyros" ).solve (
2107
+ model = m ,
2108
+ first_stage_variables = m .x ,
2109
+ second_stage_variables = [],
2110
+ uncertain_params = m .q ,
2111
+ uncertainty_set = uncertainty_set ,
2112
+ local_solver = baron ,
2113
+ global_solver = baron ,
2114
+ solve_master_globally = True ,
2115
+ bypass_local_separation = True ,
2116
+ objective_focus = "worst_case" ,
2117
+ )
2118
+ self .assertEqual (
2119
+ res .pyros_termination_condition , pyrosTerminationCondition .robust_optimal
2120
+ )
2121
+ self .assertEqual (res .iterations , 1 )
2122
+ self .assertAlmostEqual (res .final_objective_value , 2 , places = 4 )
2123
+ # subject to these scenarios, the optimal solution is non-unique,
2124
+ # but should satisfy this check
2125
+ self .assertAlmostEqual (m .x [1 ].value , - 2 , places = 4 )
2126
+
2127
+ def test_two_stage_discrete_set_rank2_affine_dr (self ):
2128
+ m = self .build_two_stage_model ()
2129
+ uncertainty_set = DiscreteScenarioSet ([[2 ], [3 ]])
2130
+ baron = SolverFactory ("baron" )
2131
+ res = SolverFactory ("pyros" ).solve (
2132
+ model = m ,
2133
+ first_stage_variables = m .x ,
2134
+ second_stage_variables = m .z ,
2135
+ uncertain_params = m .q ,
2136
+ uncertainty_set = uncertainty_set ,
2137
+ local_solver = baron ,
2138
+ global_solver = baron ,
2139
+ solve_master_globally = True ,
2140
+ bypass_local_separation = True ,
2141
+ decision_rule_order = 1 ,
2142
+ objective_focus = "worst_case" ,
2143
+ )
2144
+ self .assertEqual (
2145
+ res .pyros_termination_condition , pyrosTerminationCondition .robust_optimal
2146
+ )
2147
+ self .assertEqual (res .iterations , 1 )
2148
+ self .assertAlmostEqual (res .final_objective_value , 0 , places = 4 )
2149
+ # note: this solution is suboptimal (in the context of nonstatic DRs),
2150
+ # but follows from the current efficiency for DRs
2151
+ # (i.e. in first iteration, static DRs required)
2152
+ self .assertAlmostEqual (m .x .value , 0 , places = 4 )
2153
+ self .assertAlmostEqual (m .z .value , 0 , places = 4 )
2154
+
2155
+ def test_two_stage_discrete_set_fullrank_affine_dr (self ):
2156
+ m = self .build_two_stage_model ()
2157
+ uncertainty_set = DiscreteScenarioSet ([[2 ], [3 ], [4 ]])
2158
+ baron = SolverFactory ("baron" )
2159
+ res = SolverFactory ("pyros" ).solve (
2160
+ model = m ,
2161
+ first_stage_variables = m .x ,
2162
+ second_stage_variables = m .z ,
2163
+ uncertain_params = m .q ,
2164
+ uncertainty_set = uncertainty_set ,
2165
+ local_solver = baron ,
2166
+ global_solver = baron ,
2167
+ solve_master_globally = True ,
2168
+ bypass_local_separation = True ,
2169
+ decision_rule_order = 1 ,
2170
+ objective_focus = "worst_case" ,
2171
+ )
2172
+ self .assertEqual (
2173
+ res .pyros_termination_condition , pyrosTerminationCondition .robust_optimal
2174
+ )
2175
+ self .assertEqual (res .iterations , 1 )
2176
+ self .assertAlmostEqual (res .final_objective_value , 0 , places = 4 )
2177
+ # the second-stage equalities are a full rank linear system
2178
+ # in x and the DR variables, with RHS 0, so all
2179
+ # variables must be 0
2180
+ self .assertAlmostEqual (m .x .value , 0 , places = 4 )
2181
+ self .assertAlmostEqual (m .z .value , 0 , places = 4 )
2182
+
2183
+
2001
2184
@unittest .skipUnless (ipopt_available , "IPOPT not available." )
2002
2185
class TestPyROSVarsAsUncertainParams (unittest .TestCase ):
2003
2186
"""
0 commit comments