Skip to content

Commit 7b2a0a0

Browse files
Merge branch 'pyros-sep-priorities-update' into pyros-multistage-2025
2 parents 1ac0910 + 2f1736d commit 7b2a0a0

File tree

6 files changed

+1016
-218
lines changed

6 files changed

+1016
-218
lines changed

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

+9
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,15 @@ def ROSolver_iterative_solve(model_data):
297297
# or in the event of bypassing global separation, no violations
298298
robustness_certified = separation_results.robustness_certified
299299
if robustness_certified:
300+
if separation_data.ss_ineq_cons_to_bypass:
301+
config.progress_logger.warning(
302+
"Separation of "
303+
f"{len(separation_data.ss_ineq_cons_to_bypass)} "
304+
"second-stage inequality constraints was bypassed "
305+
"due to user separation priority order specification. "
306+
"Thus, robust feasibility/optimality of the reported "
307+
"solution is not guaranteed."
308+
)
300309
if config.bypass_global_separation:
301310
config.progress_logger.warning(
302311
"Option to bypass global separation was chosen. "

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

+56-7
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from pyomo.contrib.pyros.uncertainty_sets import Geometry
3737
from pyomo.contrib.pyros.util import (
3838
ABS_CON_CHECK_FEAS_TOL,
39+
BYPASSING_SEPARATION_PRIORITY,
3940
call_solver,
4041
check_time_limit_reached,
4142
)
@@ -386,21 +387,50 @@ def group_ss_ineq_constraints_by_priority(separation_data):
386387
Keys are sorted in descending order
387388
(i.e. highest priority first).
388389
"""
390+
separation_data.config.progress_logger.debug(
391+
"Grouping second-stage inequality constraints by separation priority..."
392+
)
393+
389394
ss_ineq_cons = separation_data.separation_model.second_stage.inequality_cons
390395
separation_priority_groups = dict()
391396
for name, ss_ineq_con in ss_ineq_cons.items():
392-
# by default, priority set to 0
393397
priority = separation_data.separation_priority_order[name]
394398
cons_with_same_priority = separation_priority_groups.setdefault(priority, [])
395399
cons_with_same_priority.append(ss_ineq_con)
396400

397401
# sort separation priority groups
398-
return {
402+
numeric_priority_grp_items = [
403+
(priority, cons)
404+
for priority, cons in separation_priority_groups.items()
405+
if priority is not BYPASSING_SEPARATION_PRIORITY
406+
]
407+
sorted_priority_groups = {
399408
priority: ss_ineq_cons
400-
for priority, ss_ineq_cons in sorted(
401-
separation_priority_groups.items(), reverse=True
402-
)
409+
for priority, ss_ineq_cons in sorted(numeric_priority_grp_items, reverse=True)
403410
}
411+
if BYPASSING_SEPARATION_PRIORITY in separation_priority_groups:
412+
sorted_priority_groups[BYPASSING_SEPARATION_PRIORITY] = (
413+
separation_priority_groups[BYPASSING_SEPARATION_PRIORITY]
414+
)
415+
416+
num_priority_groups = len(sorted_priority_groups)
417+
separation_data.config.progress_logger.debug(
418+
f"Found {num_priority_groups} separation "
419+
f"priority group{'s' if num_priority_groups != 1 else ''}."
420+
)
421+
separation_data.config.progress_logger.debug(
422+
"Separation priority grouping statistics:"
423+
)
424+
separation_data.config.progress_logger.debug(
425+
f" {'Priority':20s}{'# Ineq Cons':15s}"
426+
)
427+
for priority, cons in sorted_priority_groups.items():
428+
priority_str = str(priority) + (" (bypass)" if priority is None else "")
429+
separation_data.config.progress_logger.debug(
430+
f" {priority_str:20s}{len(cons):<15d}"
431+
)
432+
433+
return sorted_priority_groups
404434

405435

406436
def get_worst_discrete_separation_solution(
@@ -566,7 +596,7 @@ def perform_separation_loop(separation_data, master_data, solve_globally):
566596
master_data=master_data,
567597
ss_ineq_cons=all_ss_ineq_constraints,
568598
)
569-
sorted_priority_groups = group_ss_ineq_constraints_by_priority(separation_data)
599+
sorted_priority_groups = separation_data.separation_priority_groups
570600
uncertainty_set_is_discrete = (
571601
config.uncertainty_set.geometry == Geometry.DISCRETE_SCENARIOS
572602
)
@@ -628,11 +658,24 @@ def perform_separation_loop(separation_data, master_data, solve_globally):
628658

629659
all_solve_call_results = ComponentMap()
630660
priority_groups_enum = enumerate(sorted_priority_groups.items())
661+
solve_desc = "global" if solve_globally else "local"
662+
solve_adverb = "Globally" if solve_globally else "Locally"
631663
for group_idx, (priority, ss_ineq_constraints) in priority_groups_enum:
632664
priority_group_solve_call_results = ComponentMap()
665+
666+
if priority is BYPASSING_SEPARATION_PRIORITY:
667+
config.progress_logger.debug(
668+
f"Bypassing {solve_desc} separation of all "
669+
f"{len(ss_ineq_constraints)} second-stage "
670+
f"inequality constraints in the group with priority {priority} "
671+
f"(group {group_idx + 1} of {len(sorted_priority_groups)}) "
672+
f"as the priority value is {priority}."
673+
)
674+
worst_case_ss_ineq_con = None
675+
continue
676+
633677
for idx, ss_ineq_con in enumerate(ss_ineq_constraints):
634678
# log progress of separation loop
635-
solve_adverb = "Globally" if solve_globally else "Locally"
636679
config.progress_logger.debug(
637680
f"{solve_adverb} separating second-stage inequality constraint "
638681
f"{get_con_name_repr(separation_data.separation_model, ss_ineq_con)} "
@@ -1293,6 +1336,12 @@ def __init__(self, model_data):
12931336
else:
12941337
self.idxs_of_master_scenarios = None
12951338

1339+
self.separation_priority_groups = group_ss_ineq_constraints_by_priority(self)
1340+
1341+
@property
1342+
def ss_ineq_cons_to_bypass(self):
1343+
return self.separation_priority_groups.get(BYPASSING_SEPARATION_PRIORITY, [])
1344+
12961345
def solve_separation(self, master_data):
12971346
"""
12981347
Solve the separation problem.

0 commit comments

Comments
 (0)