Skip to content

Update the PyROS Separation Priority Ordering Interface #3581

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Jun 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
96025dd
Merge branch 'pyros-discrete-coefficient-matching' into pyros-sep-pri…
shermanjasonaf Mar 27, 2025
f898ff9
Merge branch 'pyros-discrete-coefficient-matching' into pyros-sep-pri…
shermanjasonaf Mar 27, 2025
c576fef
Make separation priority interface more flexible
shermanjasonaf Mar 28, 2025
2f1736d
Add custom bypassing of selected separation problems
shermanjasonaf Mar 28, 2025
be0e9ec
Merge branch 'pyros-discrete-coefficient-matching' into pyros-sep-pri…
shermanjasonaf Apr 9, 2025
23a883b
Merge branch 'main' into pyros-sep-priorities-update
shermanjasonaf Apr 9, 2025
1044bab
Merge branch 'Pyomo:main' into pyros-sep-priorities-update
shermanjasonaf Apr 9, 2025
994892d
(Tentatively) impose bypassed second-stage ineqs only in nominal mast…
shermanjasonaf Apr 10, 2025
f6374d8
Undo temporary change to bypassed separation priorities
shermanjasonaf Apr 19, 2025
673d6eb
Reimplement and correct separation priority for constraint bypassing
shermanjasonaf Apr 28, 2025
4991725
Merge branch 'main' into pyros-sep-priorities-update
shermanjasonaf Apr 28, 2025
01ee2f8
Add PyROS docs note on separation priority ordering
shermanjasonaf Apr 28, 2025
c0f62e0
Deprecate argument 'separation_priority_order'
shermanjasonaf Apr 28, 2025
33c5dd1
Apply black
shermanjasonaf Apr 28, 2025
c21beb1
Fix typo in PyROS solver test
shermanjasonaf Apr 28, 2025
f881565
Update online docs section on separation priorities
shermanjasonaf Apr 29, 2025
f7e9caa
Add deprecation notice to `separation_priority_order` docs
shermanjasonaf Apr 29, 2025
c869745
Make separation priority tests more rigorous
shermanjasonaf May 5, 2025
52b9ffd
Merge branch 'main' into pyros-sep-priorities-update
shermanjasonaf May 5, 2025
13054f6
Update version number, changelog
shermanjasonaf May 5, 2025
84ae11d
Update online docs logging example
shermanjasonaf May 5, 2025
4d96831
Correct the updated online docs logging example
shermanjasonaf May 5, 2025
05ce867
Update separation priority ordering online doc
shermanjasonaf May 9, 2025
7b6b4db
Further refine online separation priority order docs
shermanjasonaf May 9, 2025
bc77284
Modify doc for argument `separation_priority_order`
shermanjasonaf May 9, 2025
374a7ab
Merge branch 'main' into pyros-sep-priorities-update
shermanjasonaf May 9, 2025
599f5e4
Merge branch 'main' into pyros-sep-priorities-update
mrmundt May 20, 2025
7ca6ea8
Merge branch 'main' into pyros-sep-priorities-update
shermanjasonaf Jun 10, 2025
7b04294
Merge branch 'main' into pyros-sep-priorities-update
jsiirola Jun 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 70 additions & 19 deletions doc/OnlineDocs/explanation/solvers/pyros.rst
Original file line number Diff line number Diff line change
Expand Up @@ -963,10 +963,10 @@ Observe that the log contains the following information:
:linenos:
==============================================================================
PyROS: The Pyomo Robust Optimization Solver, v1.3.6.
Pyomo version: 6.9.2
Commit hash: 41cd797e0
Invoked at UTC 2025-03-13T16:20:31.105320+00:00
PyROS: The Pyomo Robust Optimization Solver, v1.3.8.
Pyomo version: 6.9.3dev0
Commit hash: unknown
Invoked at UTC 2025-05-05T00:00:00.000000+00:00
Developed by: Natalie M. Isenberg (1), Jason A. F. Sherman (1),
John D. Siirola (2), Chrysanthos E. Gounaris (1)
Expand Down Expand Up @@ -1025,34 +1025,33 @@ Observe that the log contains the following information:
------------------------------------------------------------------------------
Itn Objective 1-Stg Shift 2-Stg Shift #CViol Max Viol Wall Time (s)
------------------------------------------------------------------------------
0 3.5838e+07 - - 5 1.8832e+04 0.693
1 3.5838e+07 1.2289e-09 1.5876e-12 5 3.7762e+04 1.514
2 3.6129e+07 2.7244e-01 3.6878e-01 3 1.1093e+02 2.486
3 3.6269e+07 3.7352e-01 4.3227e-01 1 2.7711e+01 3.667
4 3.6285e+07 7.6526e-01 2.8426e-11 0 4.3364e-05g 6.291
0 3.5838e+07 - - 5 1.8832e+04 0.759
1 3.5838e+07 2.9329e-09 5.0030e-10 5 2.1295e+04 1.573
2 3.6285e+07 7.6526e-01 2.0398e-01 2 2.2457e+02 2.272
3 3.6285e+07 7.7212e-13 1.2525e-10 0 7.2940e-08g 5.280
------------------------------------------------------------------------------
Robust optimal solution identified.
------------------------------------------------------------------------------
Timing breakdown:
Identifier ncalls cumtime percall %
-----------------------------------------------------------
main 1 6.291 6.291 100.0
main 1 5.281 5.281 100.0
------------------------------------------------------
dr_polishing 4 0.334 0.083 5.3
global_separation 27 0.954 0.035 15.2
local_separation 135 3.046 0.023 48.4
master 5 1.027 0.205 16.3
master_feasibility 4 0.133 0.033 2.1
preprocessing 1 0.013 0.013 0.2
other n/a 0.785 n/a 12.5
dr_polishing 3 0.155 0.052 2.9
global_separation 27 1.280 0.047 24.2
local_separation 108 2.200 0.020 41.7
master 4 0.727 0.182 13.8
master_feasibility 3 0.103 0.034 1.9
preprocessing 1 0.021 0.021 0.4
other n/a 0.794 n/a 15.0
======================================================
===========================================================
------------------------------------------------------------------------------
Termination stats:
Iterations : 5
Solve time (wall s) : 6.291
Iterations : 4
Solve time (wall s) : 5.281
Final objective value : 3.6285e+07
Termination condition : pyrosTerminationCondition.robust_optimal
------------------------------------------------------------------------------
Expand Down Expand Up @@ -1133,6 +1132,58 @@ The constituent columns are defined in the
current iteration.


Separation Priority Ordering
----------------------------
The PyROS solver supports custom prioritization of
the separation subproblems (and, thus, the constraints)
that are automatically derived from
a given model for robust optimization.
Users may specify separation priorities through:

- (Recommended) :class:`~pyomo.core.base.suffix.Suffix` components
with local name ``pyros_separation_priority``,
declared on the model or any of its sub-blocks.
Each entry of every such
:class:`~pyomo.core.base.suffix.Suffix`
should map a
:class:`~pyomo.core.base.var.Var`
or :class:`~pyomo.core.base.constraint.Constraint`
component to a value that specifies the separation
priority of all constraints derived from that component
- The optional argument ``separation_priority_order``
to the PyROS :py:meth:`~pyomo.contrib.pyros.pyros.PyROS.solve`
method. The argument should be castable to a :py:obj:`dict`,
of which each entry maps the full name of a
:class:`~pyomo.core.base.var.Var`
or :class:`~pyomo.core.base.constraint.Constraint`
component to a value that specifies the
separation priority of all constraints
derived from that component

Specification via :class:`~pyomo.core.base.suffix.Suffix` components
takes precedence over specification via the solver argument
``separation_priority_order``.
Moreover, the precedence ordering among
:class:`~pyomo.core.base.suffix.Suffix`
components is handled by the Pyomo
:class:`~pyomo.core.base.suffix.SuffixFinder` utility.

A separation priority can be either
a (real) number (i.e., of type :py:class:`int`, :py:class:`float`, etc.)
or :py:obj:`None`.
A higher number indicates a higher priority.
The default priority for all constraints is 0.
Therefore a constraint can be prioritized [or deprioritized]
over the default by mapping the constraint to a positive [or negative] number.
In practice, critical or dominant constraints are often
prioritized over algorithmic or implied constraints.

Constraints that have been assigned a priority of :py:obj:`None`
are enforced subject to only the nominal uncertain parameter realization
provided by the user. Therefore, these constraints are not imposed robustly
and, in particular, are excluded from the separation problems.


Feedback and Reporting Issues
-------------------------------
Please provide feedback and/or report any problems by opening an issue on
Expand Down
13 changes: 13 additions & 0 deletions pyomo/contrib/pyros/CHANGELOG.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,24 @@ PyROS CHANGELOG
===============


-------------------------------------------------------------------------------
PyROS 1.3.8 28 Apr 2025
-------------------------------------------------------------------------------
- Add Suffix-based interface for prioritizing separation problems
- Allow user to, through the separation priority ordering interface,
specify constraints that should be imposed subject to
the nominal realization only (and, therefore, not separated)
- Deprecate the optional argument `separation_priority_order`.


-------------------------------------------------------------------------------
PyROS 1.3.7 06 Mar 2025
-------------------------------------------------------------------------------
- Modify reformulation of state-variable independent second-stage
equality constraints for problems with discrete uncertainty sets
- Lift the iteration-wise DR efficiency for problems with DR variable-dependent
first-stage equality constraints
(such as those derived from coefficient matching)


-------------------------------------------------------------------------------
Expand Down
68 changes: 59 additions & 9 deletions pyomo/contrib/pyros/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
InEnum,
Path,
)
from pyomo.common.deprecation import deprecation_warning
from pyomo.common.errors import ApplicationError, PyomoException
from pyomo.core.base import Var, VarData
from pyomo.core.base.param import Param, ParamData
Expand Down Expand Up @@ -58,6 +59,38 @@ def positive_int_or_minus_one(obj):
positive_int_or_minus_one.domain_name = "positive int or -1"


def _deprecated_separation_priority_order(obj):
"""
Domain validator for argument `separation_priority_order`.

As this argument has been deprecated, a deprecation warning
is issued through a WARNING-level logger message if
the argument is cast to a nonempty dict.

Parameters
----------
obj : object
Argument value.

Returns
-------
separation_priority_order : dict
Argument value, cast to a dict.
"""
separation_priority_order = dict(obj)
if separation_priority_order:
deprecation_warning(
"The argument 'separation_priority_order' is deprecated. "
"Consider specifying separation priorities by declaring, on your "
"model, Suffix components with local name `pyros_separation_priority`.",
version="6.9.3dev0",
)
return separation_priority_order


_deprecated_separation_priority_order.domain_name = dict.__name__


def uncertain_param_validator(uncertain_obj):
"""
Check that a component object modeling an
Expand Down Expand Up @@ -720,17 +753,34 @@ def pyros_config():
"separation_priority_order",
ConfigValue(
default={},
domain=dict,
domain=_deprecated_separation_priority_order,
doc=(
"""
Mapping from model inequality constraint names
to positive integers specifying the priorities
of their corresponding separation subproblems.
A higher integer value indicates a higher priority.
Constraints not referenced in the `dict` assume
a priority of 0.
Separation subproblems are solved in order of decreasing
priority.
(DEPRECATED)
A dict-like object, each entry of which
maps the full name of a model ``Var`` or ``Constraint``
component to a value specifying the separation priority
for all constraints derived from the component.
A separation priority can be a numeric value or None.
A higher numeric value indicates a higher priority.
For all constraints, the default priority is 0.
(Inequality and equality) constraints with a
priority of None are excluded from
the separation problems and enforced subject to only
the nominal uncertain parameter realization in the master
problems.
Separation problems corresponding to inequality
constraints with numeric priorities are grouped by
priority. In every iteration, the groups are traversed
in descending order of priority,
until, within a group, constraint violations
are detected.

*Deprecated since Pyomo 6.9.3dev0*: The argument
`separation_priority_order` is deprecated.
Specify separation priorities by declaring, on your
model, `Suffix` components with local name
'pyros_separation_priority'.
"""
),
),
Expand Down
2 changes: 1 addition & 1 deletion pyomo/contrib/pyros/pyros.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
)


__version__ = "1.3.7"
__version__ = "1.3.8"


default_pyros_solver_logger = setup_pyros_logger()
Expand Down
1 change: 1 addition & 0 deletions pyomo/contrib/pyros/pyros_algorithm_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ def ROSolver_iterative_solve(model_data):
from_block=nominal_master_blk,
clone_first_stage_components=False,
)

separation_data.points_added_to_master[(k + 1, 0)] = (
separation_results.violating_param_realization
)
Expand Down
39 changes: 32 additions & 7 deletions pyomo/contrib/pyros/separation_problem_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,22 +387,44 @@ def group_ss_ineq_constraints_by_priority(separation_data):
Keys are sorted in descending order
(i.e. highest priority first).
"""
separation_data.config.progress_logger.debug(
"Grouping second-stage inequality constraints by separation priority..."
)

ss_ineq_cons = separation_data.separation_model.second_stage.inequality_cons
separation_priority_groups = dict()
for name, ss_ineq_con in ss_ineq_cons.items():
# by default, priority set to 0
priority = separation_data.separation_priority_order[name]
cons_with_same_priority = separation_priority_groups.setdefault(priority, [])
cons_with_same_priority.append(ss_ineq_con)

# sort separation priority groups
return {
numeric_priority_grp_items = [
(priority, cons) for priority, cons in separation_priority_groups.items()
]
sorted_priority_groups = {
priority: ss_ineq_cons
for priority, ss_ineq_cons in sorted(
separation_priority_groups.items(), reverse=True
)
for priority, ss_ineq_cons in sorted(numeric_priority_grp_items, reverse=True)
}

num_priority_groups = len(sorted_priority_groups)
separation_data.config.progress_logger.debug(
f"Found {num_priority_groups} separation "
f"priority group{'s' if num_priority_groups != 1 else ''}."
)
separation_data.config.progress_logger.debug(
"Separation priority grouping statistics:"
)
separation_data.config.progress_logger.debug(
f" {'Priority':20s}{'# Ineq Cons':15s}"
)
for priority, cons in sorted_priority_groups.items():
separation_data.config.progress_logger.debug(
f" {priority:<20d}{len(cons):<15d}"
)

return sorted_priority_groups


def get_worst_discrete_separation_solution(
ss_ineq_con, config, ss_ineq_cons_to_evaluate, discrete_solve_results
Expand Down Expand Up @@ -567,7 +589,7 @@ def perform_separation_loop(separation_data, master_data, solve_globally):
master_data=master_data,
ss_ineq_cons=all_ss_ineq_constraints,
)
sorted_priority_groups = group_ss_ineq_constraints_by_priority(separation_data)
sorted_priority_groups = separation_data.separation_priority_groups
uncertainty_set_is_discrete = (
config.uncertainty_set.geometry == Geometry.DISCRETE_SCENARIOS
)
Expand Down Expand Up @@ -629,11 +651,12 @@ def perform_separation_loop(separation_data, master_data, solve_globally):

all_solve_call_results = ComponentMap()
priority_groups_enum = enumerate(sorted_priority_groups.items())
solve_adverb = "Globally" if solve_globally else "Locally"
for group_idx, (priority, ss_ineq_constraints) in priority_groups_enum:
priority_group_solve_call_results = ComponentMap()

for idx, ss_ineq_con in enumerate(ss_ineq_constraints):
# log progress of separation loop
solve_adverb = "Globally" if solve_globally else "Locally"
config.progress_logger.debug(
f"{solve_adverb} separating second-stage inequality constraint "
f"{get_con_name_repr(separation_data.separation_model, ss_ineq_con)} "
Expand Down Expand Up @@ -1294,6 +1317,8 @@ def __init__(self, model_data):
else:
self.idxs_of_master_scenarios = None

self.separation_priority_groups = group_ss_ineq_constraints_by_priority(self)

def solve_separation(self, master_data):
"""
Solve the separation problem.
Expand Down
Loading
Loading