Skip to content

Commit dcaba42

Browse files
authored
Merge pull request Pyomo#3786 from shermanjasonaf/fix-pyros-intersection-set
Fix PyROS `IntersectionSet` Implementation
2 parents 13facca + edf458c commit dcaba42

File tree

5 files changed

+447
-84
lines changed

5 files changed

+447
-84
lines changed

pyomo/contrib/pyros/CHANGELOG.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@ PyROS CHANGELOG
33
===============
44

55

6+
-------------------------------------------------------------------------------
7+
PyROS 1.3.12 15 Nov 2025
8+
-------------------------------------------------------------------------------
9+
- Fix implementation of PyROS IntersectionSet class,
10+
particularly for cases involving discrete sets and/or sets defined
11+
with auxiliary parameters
12+
- Tweak and more thoroughly test implementations of common
13+
uncertainty set attributes
14+
15+
616
-------------------------------------------------------------------------------
717
PyROS 1.3.11 17 Oct 2025
818
-------------------------------------------------------------------------------

pyomo/contrib/pyros/pyros.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
)
3535

3636

37-
__version__ = "1.3.11"
37+
__version__ = "1.3.12"
3838

3939

4040
default_pyros_solver_logger = setup_pyros_logger()

pyomo/contrib/pyros/tests/test_grcs.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2069,6 +2069,94 @@ def test_two_stage_set_nonstatic_dr_robust_opt(self, use_discrete_set, dr_order)
20692069
self.assertAlmostEqual(m.x.value, 2, places=4)
20702070
self.assertAlmostEqual(m.z.value, 2, places=4)
20712071

2072+
@unittest.skipUnless(baron_available, "BARON is not available.")
2073+
def test_pyros_discrete_intersection(self):
2074+
"""
2075+
Test PyROS properly supports intersection set involving
2076+
discrete set.
2077+
"""
2078+
m = ConcreteModel()
2079+
m.q1 = Param(initialize=0.5, mutable=True)
2080+
m.q2 = Param(initialize=0.5, mutable=True)
2081+
m.x1 = Var(bounds=[0, 1])
2082+
m.x2 = Var(bounds=[0, 1])
2083+
m.obj = Objective(expr=m.x1 + m.x2)
2084+
m.con1 = Constraint(expr=m.x1 >= m.q1)
2085+
m.con2 = Constraint(expr=m.x2 >= m.q2)
2086+
iset = IntersectionSet(
2087+
set1=BoxSet(bounds=[[0, 2]] * 2),
2088+
set2=DiscreteScenarioSet([[0, 0], [0.5, 0.5], [1, 1], [3, 3]]),
2089+
)
2090+
res = SolverFactory("pyros").solve(
2091+
model=m,
2092+
first_stage_variables=[m.x1, m.x2],
2093+
second_stage_variables=[],
2094+
uncertain_params=[m.q1, m.q2],
2095+
uncertainty_set=iset,
2096+
# note: using BARON instead of IPOPT.
2097+
# when IPOPT is used, this test will fail,
2098+
# as the discrete separation routine does not
2099+
# account for the case where there are no
2100+
# adjustable variables in the model
2101+
# (i.e. separation models without any variables).
2102+
# will be addressed later when the subproblem
2103+
# solve routines are refactored
2104+
local_solver="baron",
2105+
global_solver="baron",
2106+
solve_master_globally=True,
2107+
objective_focus="worst_case",
2108+
)
2109+
self.assertEqual(
2110+
res.pyros_termination_condition, pyrosTerminationCondition.robust_optimal
2111+
)
2112+
self.assertEqual(res.iterations, 2)
2113+
# check worst-case optimal solution
2114+
self.assertAlmostEqual(res.final_objective_value, 2)
2115+
self.assertAlmostEqual(m.x1.value, 1)
2116+
self.assertAlmostEqual(m.x2.value, 1)
2117+
2118+
@unittest.skipUnless(ipopt_available, "IPOPT is not available.")
2119+
def test_pyros_intersection_aux_vars(self):
2120+
"""
2121+
Test PyROS properly supports intersection set
2122+
in which at least one of the intersected sets
2123+
is defined with auxiliary variables.
2124+
"""
2125+
m = ConcreteModel()
2126+
m.q1 = Param(initialize=0.5, mutable=True)
2127+
m.q2 = Param(initialize=0.5, mutable=True)
2128+
m.x1 = Var(bounds=[0, 1])
2129+
m.x2 = Var(bounds=[0, 1])
2130+
m.obj = Objective(expr=m.x1 + m.x2)
2131+
m.con1 = Constraint(expr=m.x1 >= m.q1)
2132+
m.con2 = Constraint(expr=m.x2 >= m.q2)
2133+
iset = IntersectionSet(
2134+
set1=AxisAlignedEllipsoidalSet(center=(0, 0), half_lengths=(1, 1)),
2135+
# factor model set requires auxiliary variables
2136+
set2=FactorModelSet(
2137+
origin=[0, 0], psi_mat=np.eye(2), number_of_factors=2, beta=0.5
2138+
),
2139+
)
2140+
res = SolverFactory("pyros").solve(
2141+
model=m,
2142+
first_stage_variables=[m.x1, m.x2],
2143+
second_stage_variables=[],
2144+
uncertain_params=[m.q1, m.q2],
2145+
uncertainty_set=iset,
2146+
local_solver="ipopt",
2147+
global_solver="ipopt",
2148+
solve_master_globally=True,
2149+
objective_focus="worst_case",
2150+
)
2151+
self.assertEqual(
2152+
res.pyros_termination_condition, pyrosTerminationCondition.robust_optimal
2153+
)
2154+
self.assertEqual(res.iterations, 3)
2155+
# check worst-case optimal solution
2156+
self.assertAlmostEqual(res.final_objective_value, 2, places=5)
2157+
self.assertAlmostEqual(m.x1.value, 1)
2158+
self.assertAlmostEqual(m.x2.value, 1)
2159+
20722160

20732161
@unittest.skipUnless(ipopt_available, "IPOPT not available.")
20742162
class TestPyROSSeparationPriorityOrder(unittest.TestCase):

0 commit comments

Comments
 (0)