Skip to content

Commit 20d01a5

Browse files
Fix and test issues with loading DR coefficients
1 parent 5bd3bf4 commit 20d01a5

File tree

2 files changed

+59
-6
lines changed

2 files changed

+59
-6
lines changed

pyomo/contrib/pyros/tests/test_grcs.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2186,6 +2186,50 @@ def test_pyros_intersection_aux_vars(self):
21862186
self.assertAlmostEqual(m.x1.value, 1)
21872187
self.assertAlmostEqual(m.x2.value, 1)
21882188

2189+
@unittest.skipUnless(ipopt_available, "IPOPT is not available.")
2190+
def test_pyros_dr_interface_ordering(self):
2191+
"""
2192+
Test PyROS DR interface returns coefficients in expected order.
2193+
"""
2194+
m = ConcreteModel()
2195+
m.q1 = Param(initialize=0.5, mutable=True)
2196+
m.q2 = Param(initialize=0.5, mutable=True)
2197+
m.x1 = Var(bounds=[1, 1])
2198+
m.x2 = Var(bounds=[m.q1, m.q1])
2199+
x3_bound_expr = 3 + m.q1 * m.q2
2200+
m.x3 = Var(bounds=[x3_bound_expr, x3_bound_expr])
2201+
m.obj = Objective(expr=m.x1 + m.x2 + m.x3 + m.q2)
2202+
res = SolverFactory("pyros").solve(
2203+
model=m,
2204+
first_stage_variables=[],
2205+
second_stage_variables=[m.x1, m.x2, m.x3],
2206+
uncertain_params=[m.q1, m.q2],
2207+
uncertainty_set=BoxSet([[0, 1]] * 2),
2208+
local_solver="ipopt",
2209+
global_solver="ipopt",
2210+
decision_rule_order=2,
2211+
)
2212+
self.assertEqual(
2213+
res.pyros_termination_condition,
2214+
pyrosTerminationCondition.robust_feasible,
2215+
)
2216+
self.assertEqual(m.x1.value, 1)
2217+
self.assertAlmostEqual(m.x2.value, 0.5) # nominal realization
2218+
self.assertAlmostEqual(m.x3.value, 3.25)
2219+
# nominal objective
2220+
self.assertAlmostEqual(res.final_objective_value, 5.25)
2221+
# test DR coefficients: easily inferred from the
2222+
# variable bound expressions
2223+
np.testing.assert_allclose(res.decision_rule_coeffs["static"], [1, 0, 3])
2224+
np.testing.assert_allclose(
2225+
res.decision_rule_coeffs["affine"],
2226+
[[0, 0], [1, 0], [0, 0]],
2227+
)
2228+
np.testing.assert_allclose(
2229+
res.decision_rule_coeffs["quadratic"],
2230+
[[[0, 0], [0, 0]]] * 2 + [[[0, 0.5], [0.5, 0]]],
2231+
)
2232+
21892233

21902234
@unittest.skipUnless(ipopt_available, "IPOPT not available.")
21912235
class TestPyROSSeparationPriorityOrder(unittest.TestCase):
@@ -2540,6 +2584,10 @@ def test_two_stage_discrete_set_rank2_affine_dr(self):
25402584
self.assertAlmostEqual(res.final_objective_value, 2, places=4)
25412585
self.assertAlmostEqual(m.x.value, 4, places=4)
25422586
self.assertAlmostEqual(m.z.value, -2, places=4)
2587+
# optimal DR can be computed analytically
2588+
np.testing.assert_allclose(res.decision_rule_coeffs["static"], [-10/3])
2589+
np.testing.assert_allclose(res.decision_rule_coeffs["affine"], [[2/3]])
2590+
self.assertIsNone(res.decision_rule_coeffs["quadratic"])
25432591

25442592
def test_two_stage_discrete_set_fullrank_affine_dr(self):
25452593
m = self.build_two_stage_model()
@@ -2568,6 +2616,10 @@ def test_two_stage_discrete_set_fullrank_affine_dr(self):
25682616
# variables must be 0
25692617
self.assertAlmostEqual(m.x.value, 0, places=4)
25702618
self.assertAlmostEqual(m.z.value, 0, places=4)
2619+
# optimal DR can be calculated analytically
2620+
np.testing.assert_allclose(res.decision_rule_coeffs["static"], [0])
2621+
np.testing.assert_allclose(res.decision_rule_coeffs["affine"], [[0]])
2622+
self.assertIsNone(res.decision_rule_coeffs["quadratic"])
25712623

25722624

25732625
@unittest.skipUnless(ipopt_available, "IPOPT not available.")

pyomo/contrib/pyros/util.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3251,7 +3251,7 @@ def load_final_solution(model_data, master_soln, original_user_var_partitioning)
32513251
for orig_var, master_blk_var in zip(original_model_vars, master_soln_vars):
32523252
orig_var.set_value(master_blk_var.value, skip_validation=True)
32533253

3254-
dr_coeffs_tuple = assemble_final_dr_coefficients(soln_master_blk)
3254+
dr_coeffs_tuple = assemble_final_dr_coefficients(soln_master_blk, config)
32553255
dr_coeffs_dict = dict(
32563256
static=dr_coeffs_tuple[0],
32573257
affine=dr_coeffs_tuple[1] if config.decision_rule_order >= 1 else None,
@@ -3265,7 +3265,7 @@ def load_final_solution(model_data, master_soln, original_user_var_partitioning)
32653265
return dr_coeffs_dict, worst_case_param_realization
32663266

32673267

3268-
def assemble_final_dr_coefficients(working_model):
3268+
def assemble_final_dr_coefficients(working_model, config):
32693269
"""
32703270
Assemble final DR coefficients into matrices.
32713271
"""
@@ -3286,11 +3286,12 @@ def assemble_final_dr_coefficients(working_model):
32863286
param_to_idx_map = ComponentMap(
32873287
(param, idx) for idx, param in enumerate(uncertain_params_set)
32883288
)
3289-
for ss_idx, ss_var in enumerate(second_stage_vars_set):
3290-
if ss_var not in effective_second_stage_vars_set:
3291-
static_coeffs[ss_idx] = ss_var.value
3289+
for ss_idx, ss_var in enumerate(config.second_stage_variables):
3290+
working_ss_var = working_model.user_model.find_component(ss_var)
3291+
if working_ss_var not in effective_second_stage_vars_set:
3292+
static_coeffs[ss_idx] = working_ss_var.value
32923293
else:
3293-
indexed_dr_var = working_model.eff_ss_var_to_dr_var_map[ss_var]
3294+
indexed_dr_var = working_model.eff_ss_var_to_dr_var_map[working_ss_var]
32943295
for dr_vdata in indexed_dr_var.values():
32953296
dr_monomial_params = working_model.dr_var_to_param_combo_map[dr_vdata]
32963297
if not dr_monomial_params:

0 commit comments

Comments
 (0)