Skip to content

Commit ae2750e

Browse files
authored
Merge pull request #3562 from shermanjasonaf/pyros-discrete-coefficient-matching
Modify PyROS DR Order Efficiency
2 parents 6f2a50e + bb610f5 commit ae2750e

File tree

6 files changed

+225
-71
lines changed

6 files changed

+225
-71
lines changed

Diff for: pyomo/contrib/pyros/master_problem_methods.py

+18-5
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
call_solver,
3131
DR_POLISHING_PARAM_PRODUCT_ZERO_TOL,
3232
enforce_dr_degree,
33+
get_all_first_stage_eq_cons,
3334
get_dr_expression,
3435
check_time_limit_reached,
3536
generate_all_decision_rule_var_data_objects,
@@ -137,7 +138,7 @@ def add_scenario_block_to_master_problem(
137138
new_blk = master_model.scenarios[scenario_idx]
138139
for con in new_blk.first_stage.inequality_cons.values():
139140
con.deactivate()
140-
for con in new_blk.first_stage.equality_cons.values():
141+
for con in get_all_first_stage_eq_cons(new_blk):
141142
con.deactivate()
142143

143144

@@ -588,13 +589,21 @@ def minimize_dr_vars(master_data):
588589
def get_master_dr_degree(master_data):
589590
"""
590591
Determine DR polynomial degree to enforce based on
591-
the iteration number.
592+
the iteration number and/or the presence of first-stage
593+
equality constraints that depend on the decision rule variables.
592594
593-
Currently, the degree is set to:
595+
If there are first-stage equality constraints that depend
596+
on the decision rule variables, such as equalities derived
597+
from coefficient matching or discretization of state-variable
598+
independent equalities, then the degree is set to
599+
``config.decision_rule_order``.
600+
601+
Otherwise, the degree is set to:
594602
595603
- 0 if iteration number is 0
596604
- min(1, config.decision_rule_order) if iteration number
597-
otherwise does not exceed number of uncertain parameters
605+
otherwise does not exceed number of effective
606+
uncertain parameters
598607
- min(2, config.decision_rule_order) otherwise.
599608
600609
Parameters
@@ -607,9 +616,13 @@ def get_master_dr_degree(master_data):
607616
int
608617
DR order, or polynomial degree, to enforce.
609618
"""
619+
nom_scenario_blk = master_data.master_model.scenarios[0, 0]
620+
if nom_scenario_blk.first_stage.dr_dependent_equality_cons:
621+
return master_data.config.decision_rule_order
622+
610623
if master_data.iteration == 0:
611624
return 0
612-
elif master_data.iteration <= len(master_data.config.uncertain_params):
625+
elif master_data.iteration <= len(nom_scenario_blk.effective_uncertain_params):
613626
return min(1, master_data.config.decision_rule_order)
614627
else:
615628
return min(2, master_data.config.decision_rule_order)

Diff for: pyomo/contrib/pyros/separation_problem_methods.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
ABS_CON_CHECK_FEAS_TOL,
3939
call_solver,
4040
check_time_limit_reached,
41+
get_all_first_stage_eq_cons,
4142
)
4243

4344

@@ -121,7 +122,7 @@ def construct_separation_problem(model_data):
121122
# fix/deactivate all nonadjustable components
122123
for var in separation_model.all_nonadjustable_variables:
123124
var.fix()
124-
for fs_eqcon in separation_model.first_stage.equality_cons.values():
125+
for fs_eqcon in get_all_first_stage_eq_cons(separation_model):
125126
fs_eqcon.deactivate()
126127
for fs_ineqcon in separation_model.first_stage.inequality_cons.values():
127128
fs_ineqcon.deactivate()

Diff for: pyomo/contrib/pyros/tests/test_grcs.py

+55-6
Original file line numberDiff line numberDiff line change
@@ -1997,6 +1997,58 @@ def test_pyros_certain_params_ipopt_degrees_of_freedom(self):
19971997
)
19981998
self.assertEqual(m.x.value, 1)
19991999

2000+
@parameterized.expand([[True, 1], [True, 2], [False, 1], [False, 2]])
2001+
def test_two_stage_set_nonstatic_dr_robust_opt(self, use_discrete_set, dr_order):
2002+
"""
2003+
Test problems that are sensitive to the DR order efficiency.
2004+
2005+
If the efficiency is not switched off properly, then
2006+
PyROS may terminate prematurely with a(n inaccurate)
2007+
robust infeasibility status.
2008+
"""
2009+
m = ConcreteModel()
2010+
m.x = Var(bounds=[-2, 2], initialize=0)
2011+
m.z = Var(bounds=[-10, 10], initialize=0)
2012+
m.q = Param(initialize=2, mutable=True)
2013+
m.obj = Objective(expr=m.x + m.z, sense=maximize)
2014+
# when uncertainty set is discrete, the
2015+
# preprocessor should write out this constraint for
2016+
# each scenario as a first-stage constraint
2017+
# otherwise, coefficient matching constraint
2018+
# requires only the affine DR coefficient be nonzero
2019+
m.xz_con = Constraint(expr=m.z == m.q)
2020+
2021+
uncertainty_set = (
2022+
DiscreteScenarioSet([[2], [3]]) if use_discrete_set else BoxSet([[2, 3]])
2023+
)
2024+
baron = SolverFactory("baron")
2025+
res = SolverFactory("pyros").solve(
2026+
model=m,
2027+
first_stage_variables=m.x,
2028+
second_stage_variables=m.z,
2029+
uncertain_params=m.q,
2030+
uncertainty_set=uncertainty_set,
2031+
local_solver=baron,
2032+
global_solver=baron,
2033+
solve_master_globally=True,
2034+
bypass_local_separation=True,
2035+
decision_rule_order=dr_order,
2036+
objective_focus="worst_case",
2037+
)
2038+
self.assertEqual(
2039+
# DR efficiency should have been switched off due to
2040+
# DR-dependent equalities, so robust optimal
2041+
# if the DR efficiency was not switched off, then
2042+
# robust infeasibililty would have been prematurely reported
2043+
res.pyros_termination_condition,
2044+
pyrosTerminationCondition.robust_optimal,
2045+
)
2046+
self.assertEqual(res.iterations, 1)
2047+
# optimal solution evaluated under worst-case scenario
2048+
self.assertAlmostEqual(res.final_objective_value, 4, places=4)
2049+
self.assertAlmostEqual(m.x.value, 2, places=4)
2050+
self.assertAlmostEqual(m.z.value, 2, places=4)
2051+
20002052

20012053
@unittest.skipUnless(baron_available, "BARON not available")
20022054
class TestReformulateSecondStageEqualitiesDiscrete(unittest.TestCase):
@@ -2145,12 +2197,9 @@ def test_two_stage_discrete_set_rank2_affine_dr(self):
21452197
res.pyros_termination_condition, pyrosTerminationCondition.robust_optimal
21462198
)
21472199
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)
2200+
self.assertAlmostEqual(res.final_objective_value, 2, places=4)
2201+
self.assertAlmostEqual(m.x.value, 4, places=4)
2202+
self.assertAlmostEqual(m.z.value, -2, places=4)
21542203

21552204
def test_two_stage_discrete_set_fullrank_affine_dr(self):
21562205
m = self.build_two_stage_model()

Diff for: pyomo/contrib/pyros/tests/test_master.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from pyomo.contrib.pyros.util import (
4040
ModelData,
4141
preprocess_model_data,
42+
get_all_first_stage_eq_cons,
4243
ObjectiveType,
4344
time_code,
4445
TimingData,
@@ -195,8 +196,8 @@ def test_add_scenario_block_to_master(self):
195196
)
196197

197198
nadj_eq_con_zip = zip(
198-
master_model.scenarios[0, 0].first_stage.equality_cons.values(),
199-
master_model.scenarios[0, 1].first_stage.equality_cons.values(),
199+
get_all_first_stage_eq_cons(master_model.scenarios[0, 0]),
200+
get_all_first_stage_eq_cons(master_model.scenarios[0, 1]),
200201
)
201202
for eq_con_00, eq_con_01 in nadj_eq_con_zip:
202203
self.assertIsNot(

0 commit comments

Comments
 (0)