Skip to content

Commit 9a7ff8d

Browse files
authored
Merge pull request #3581 from shermanjasonaf/pyros-sep-priorities-update
Update the PyROS Separation Priority Ordering Interface
2 parents e4c0ec0 + 7b04294 commit 9a7ff8d

File tree

10 files changed

+817
-157
lines changed

10 files changed

+817
-157
lines changed

doc/OnlineDocs/explanation/solvers/pyros.rst

Lines changed: 70 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -963,10 +963,10 @@ Observe that the log contains the following information:
963963
:linenos:
964964
965965
==============================================================================
966-
PyROS: The Pyomo Robust Optimization Solver, v1.3.6.
967-
Pyomo version: 6.9.2
968-
Commit hash: 41cd797e0
969-
Invoked at UTC 2025-03-13T16:20:31.105320+00:00
966+
PyROS: The Pyomo Robust Optimization Solver, v1.3.8.
967+
Pyomo version: 6.9.3dev0
968+
Commit hash: unknown
969+
Invoked at UTC 2025-05-05T00:00:00.000000+00:00
970970
971971
Developed by: Natalie M. Isenberg (1), Jason A. F. Sherman (1),
972972
John D. Siirola (2), Chrysanthos E. Gounaris (1)
@@ -1025,34 +1025,33 @@ Observe that the log contains the following information:
10251025
------------------------------------------------------------------------------
10261026
Itn Objective 1-Stg Shift 2-Stg Shift #CViol Max Viol Wall Time (s)
10271027
------------------------------------------------------------------------------
1028-
0 3.5838e+07 - - 5 1.8832e+04 0.693
1029-
1 3.5838e+07 1.2289e-09 1.5876e-12 5 3.7762e+04 1.514
1030-
2 3.6129e+07 2.7244e-01 3.6878e-01 3 1.1093e+02 2.486
1031-
3 3.6269e+07 3.7352e-01 4.3227e-01 1 2.7711e+01 3.667
1032-
4 3.6285e+07 7.6526e-01 2.8426e-11 0 4.3364e-05g 6.291
1028+
0 3.5838e+07 - - 5 1.8832e+04 0.759
1029+
1 3.5838e+07 2.9329e-09 5.0030e-10 5 2.1295e+04 1.573
1030+
2 3.6285e+07 7.6526e-01 2.0398e-01 2 2.2457e+02 2.272
1031+
3 3.6285e+07 7.7212e-13 1.2525e-10 0 7.2940e-08g 5.280
10331032
------------------------------------------------------------------------------
10341033
Robust optimal solution identified.
10351034
------------------------------------------------------------------------------
10361035
Timing breakdown:
10371036
10381037
Identifier ncalls cumtime percall %
10391038
-----------------------------------------------------------
1040-
main 1 6.291 6.291 100.0
1039+
main 1 5.281 5.281 100.0
10411040
------------------------------------------------------
1042-
dr_polishing 4 0.334 0.083 5.3
1043-
global_separation 27 0.954 0.035 15.2
1044-
local_separation 135 3.046 0.023 48.4
1045-
master 5 1.027 0.205 16.3
1046-
master_feasibility 4 0.133 0.033 2.1
1047-
preprocessing 1 0.013 0.013 0.2
1048-
other n/a 0.785 n/a 12.5
1041+
dr_polishing 3 0.155 0.052 2.9
1042+
global_separation 27 1.280 0.047 24.2
1043+
local_separation 108 2.200 0.020 41.7
1044+
master 4 0.727 0.182 13.8
1045+
master_feasibility 3 0.103 0.034 1.9
1046+
preprocessing 1 0.021 0.021 0.4
1047+
other n/a 0.794 n/a 15.0
10491048
======================================================
10501049
===========================================================
10511050
10521051
------------------------------------------------------------------------------
10531052
Termination stats:
1054-
Iterations : 5
1055-
Solve time (wall s) : 6.291
1053+
Iterations : 4
1054+
Solve time (wall s) : 5.281
10561055
Final objective value : 3.6285e+07
10571056
Termination condition : pyrosTerminationCondition.robust_optimal
10581057
------------------------------------------------------------------------------
@@ -1133,6 +1132,58 @@ The constituent columns are defined in the
11331132
current iteration.
11341133

11351134

1135+
Separation Priority Ordering
1136+
----------------------------
1137+
The PyROS solver supports custom prioritization of
1138+
the separation subproblems (and, thus, the constraints)
1139+
that are automatically derived from
1140+
a given model for robust optimization.
1141+
Users may specify separation priorities through:
1142+
1143+
- (Recommended) :class:`~pyomo.core.base.suffix.Suffix` components
1144+
with local name ``pyros_separation_priority``,
1145+
declared on the model or any of its sub-blocks.
1146+
Each entry of every such
1147+
:class:`~pyomo.core.base.suffix.Suffix`
1148+
should map a
1149+
:class:`~pyomo.core.base.var.Var`
1150+
or :class:`~pyomo.core.base.constraint.Constraint`
1151+
component to a value that specifies the separation
1152+
priority of all constraints derived from that component
1153+
- The optional argument ``separation_priority_order``
1154+
to the PyROS :py:meth:`~pyomo.contrib.pyros.pyros.PyROS.solve`
1155+
method. The argument should be castable to a :py:obj:`dict`,
1156+
of which each entry maps the full name of a
1157+
:class:`~pyomo.core.base.var.Var`
1158+
or :class:`~pyomo.core.base.constraint.Constraint`
1159+
component to a value that specifies the
1160+
separation priority of all constraints
1161+
derived from that component
1162+
1163+
Specification via :class:`~pyomo.core.base.suffix.Suffix` components
1164+
takes precedence over specification via the solver argument
1165+
``separation_priority_order``.
1166+
Moreover, the precedence ordering among
1167+
:class:`~pyomo.core.base.suffix.Suffix`
1168+
components is handled by the Pyomo
1169+
:class:`~pyomo.core.base.suffix.SuffixFinder` utility.
1170+
1171+
A separation priority can be either
1172+
a (real) number (i.e., of type :py:class:`int`, :py:class:`float`, etc.)
1173+
or :py:obj:`None`.
1174+
A higher number indicates a higher priority.
1175+
The default priority for all constraints is 0.
1176+
Therefore a constraint can be prioritized [or deprioritized]
1177+
over the default by mapping the constraint to a positive [or negative] number.
1178+
In practice, critical or dominant constraints are often
1179+
prioritized over algorithmic or implied constraints.
1180+
1181+
Constraints that have been assigned a priority of :py:obj:`None`
1182+
are enforced subject to only the nominal uncertain parameter realization
1183+
provided by the user. Therefore, these constraints are not imposed robustly
1184+
and, in particular, are excluded from the separation problems.
1185+
1186+
11361187
Feedback and Reporting Issues
11371188
-------------------------------
11381189
Please provide feedback and/or report any problems by opening an issue on

pyomo/contrib/pyros/CHANGELOG.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,24 @@ PyROS CHANGELOG
33
===============
44

55

6+
-------------------------------------------------------------------------------
7+
PyROS 1.3.8 28 Apr 2025
8+
-------------------------------------------------------------------------------
9+
- Add Suffix-based interface for prioritizing separation problems
10+
- Allow user to, through the separation priority ordering interface,
11+
specify constraints that should be imposed subject to
12+
the nominal realization only (and, therefore, not separated)
13+
- Deprecate the optional argument `separation_priority_order`.
14+
15+
616
-------------------------------------------------------------------------------
717
PyROS 1.3.7 06 Mar 2025
818
-------------------------------------------------------------------------------
919
- Modify reformulation of state-variable independent second-stage
1020
equality constraints for problems with discrete uncertainty sets
21+
- Lift the iteration-wise DR efficiency for problems with DR variable-dependent
22+
first-stage equality constraints
23+
(such as those derived from coefficient matching)
1124

1225

1326
-------------------------------------------------------------------------------

pyomo/contrib/pyros/config.py

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
InEnum,
1414
Path,
1515
)
16+
from pyomo.common.deprecation import deprecation_warning
1617
from pyomo.common.errors import ApplicationError, PyomoException
1718
from pyomo.core.base import Var, VarData
1819
from pyomo.core.base.param import Param, ParamData
@@ -58,6 +59,38 @@ def positive_int_or_minus_one(obj):
5859
positive_int_or_minus_one.domain_name = "positive int or -1"
5960

6061

62+
def _deprecated_separation_priority_order(obj):
63+
"""
64+
Domain validator for argument `separation_priority_order`.
65+
66+
As this argument has been deprecated, a deprecation warning
67+
is issued through a WARNING-level logger message if
68+
the argument is cast to a nonempty dict.
69+
70+
Parameters
71+
----------
72+
obj : object
73+
Argument value.
74+
75+
Returns
76+
-------
77+
separation_priority_order : dict
78+
Argument value, cast to a dict.
79+
"""
80+
separation_priority_order = dict(obj)
81+
if separation_priority_order:
82+
deprecation_warning(
83+
"The argument 'separation_priority_order' is deprecated. "
84+
"Consider specifying separation priorities by declaring, on your "
85+
"model, Suffix components with local name `pyros_separation_priority`.",
86+
version="6.9.3dev0",
87+
)
88+
return separation_priority_order
89+
90+
91+
_deprecated_separation_priority_order.domain_name = dict.__name__
92+
93+
6194
def uncertain_param_validator(uncertain_obj):
6295
"""
6396
Check that a component object modeling an
@@ -720,17 +753,34 @@ def pyros_config():
720753
"separation_priority_order",
721754
ConfigValue(
722755
default={},
723-
domain=dict,
756+
domain=_deprecated_separation_priority_order,
724757
doc=(
725758
"""
726-
Mapping from model inequality constraint names
727-
to positive integers specifying the priorities
728-
of their corresponding separation subproblems.
729-
A higher integer value indicates a higher priority.
730-
Constraints not referenced in the `dict` assume
731-
a priority of 0.
732-
Separation subproblems are solved in order of decreasing
733-
priority.
759+
(DEPRECATED)
760+
A dict-like object, each entry of which
761+
maps the full name of a model ``Var`` or ``Constraint``
762+
component to a value specifying the separation priority
763+
for all constraints derived from the component.
764+
A separation priority can be a numeric value or None.
765+
A higher numeric value indicates a higher priority.
766+
For all constraints, the default priority is 0.
767+
(Inequality and equality) constraints with a
768+
priority of None are excluded from
769+
the separation problems and enforced subject to only
770+
the nominal uncertain parameter realization in the master
771+
problems.
772+
Separation problems corresponding to inequality
773+
constraints with numeric priorities are grouped by
774+
priority. In every iteration, the groups are traversed
775+
in descending order of priority,
776+
until, within a group, constraint violations
777+
are detected.
778+
779+
*Deprecated since Pyomo 6.9.3dev0*: The argument
780+
`separation_priority_order` is deprecated.
781+
Specify separation priorities by declaring, on your
782+
model, `Suffix` components with local name
783+
'pyros_separation_priority'.
734784
"""
735785
),
736786
),

pyomo/contrib/pyros/pyros.py

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

3535

36-
__version__ = "1.3.7"
36+
__version__ = "1.3.8"
3737

3838

3939
default_pyros_solver_logger = setup_pyros_logger()

pyomo/contrib/pyros/pyros_algorithm_methods.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ def ROSolver_iterative_solve(model_data):
327327
from_block=nominal_master_blk,
328328
clone_first_stage_components=False,
329329
)
330+
330331
separation_data.points_added_to_master[(k + 1, 0)] = (
331332
separation_results.violating_param_realization
332333
)

pyomo/contrib/pyros/separation_problem_methods.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -387,22 +387,44 @@ def group_ss_ineq_constraints_by_priority(separation_data):
387387
Keys are sorted in descending order
388388
(i.e. highest priority first).
389389
"""
390+
separation_data.config.progress_logger.debug(
391+
"Grouping second-stage inequality constraints by separation priority..."
392+
)
393+
390394
ss_ineq_cons = separation_data.separation_model.second_stage.inequality_cons
391395
separation_priority_groups = dict()
392396
for name, ss_ineq_con in ss_ineq_cons.items():
393-
# by default, priority set to 0
394397
priority = separation_data.separation_priority_order[name]
395398
cons_with_same_priority = separation_priority_groups.setdefault(priority, [])
396399
cons_with_same_priority.append(ss_ineq_con)
397400

398401
# sort separation priority groups
399-
return {
402+
numeric_priority_grp_items = [
403+
(priority, cons) for priority, cons in separation_priority_groups.items()
404+
]
405+
sorted_priority_groups = {
400406
priority: ss_ineq_cons
401-
for priority, ss_ineq_cons in sorted(
402-
separation_priority_groups.items(), reverse=True
403-
)
407+
for priority, ss_ineq_cons in sorted(numeric_priority_grp_items, reverse=True)
404408
}
405409

410+
num_priority_groups = len(sorted_priority_groups)
411+
separation_data.config.progress_logger.debug(
412+
f"Found {num_priority_groups} separation "
413+
f"priority group{'s' if num_priority_groups != 1 else ''}."
414+
)
415+
separation_data.config.progress_logger.debug(
416+
"Separation priority grouping statistics:"
417+
)
418+
separation_data.config.progress_logger.debug(
419+
f" {'Priority':20s}{'# Ineq Cons':15s}"
420+
)
421+
for priority, cons in sorted_priority_groups.items():
422+
separation_data.config.progress_logger.debug(
423+
f" {priority:<20d}{len(cons):<15d}"
424+
)
425+
426+
return sorted_priority_groups
427+
406428

407429
def get_worst_discrete_separation_solution(
408430
ss_ineq_con, config, ss_ineq_cons_to_evaluate, discrete_solve_results
@@ -567,7 +589,7 @@ def perform_separation_loop(separation_data, master_data, solve_globally):
567589
master_data=master_data,
568590
ss_ineq_cons=all_ss_ineq_constraints,
569591
)
570-
sorted_priority_groups = group_ss_ineq_constraints_by_priority(separation_data)
592+
sorted_priority_groups = separation_data.separation_priority_groups
571593
uncertainty_set_is_discrete = (
572594
config.uncertainty_set.geometry == Geometry.DISCRETE_SCENARIOS
573595
)
@@ -629,11 +651,12 @@ def perform_separation_loop(separation_data, master_data, solve_globally):
629651

630652
all_solve_call_results = ComponentMap()
631653
priority_groups_enum = enumerate(sorted_priority_groups.items())
654+
solve_adverb = "Globally" if solve_globally else "Locally"
632655
for group_idx, (priority, ss_ineq_constraints) in priority_groups_enum:
633656
priority_group_solve_call_results = ComponentMap()
657+
634658
for idx, ss_ineq_con in enumerate(ss_ineq_constraints):
635659
# log progress of separation loop
636-
solve_adverb = "Globally" if solve_globally else "Locally"
637660
config.progress_logger.debug(
638661
f"{solve_adverb} separating second-stage inequality constraint "
639662
f"{get_con_name_repr(separation_data.separation_model, ss_ineq_con)} "
@@ -1294,6 +1317,8 @@ def __init__(self, model_data):
12941317
else:
12951318
self.idxs_of_master_scenarios = None
12961319

1320+
self.separation_priority_groups = group_ss_ineq_constraints_by_priority(self)
1321+
12971322
def solve_separation(self, master_data):
12981323
"""
12991324
Solve the separation problem.

0 commit comments

Comments
 (0)