Skip to content

Commit fb1341c

Browse files
authored
Merge pull request #3198 from shermanjasonaf/fix-pyros-subsolver-time-limits
Updates to PyROS Solver Timing System
2 parents 4a5bf8a + 0935515 commit fb1341c

File tree

8 files changed

+258
-209
lines changed

8 files changed

+258
-209
lines changed

doc/OnlineDocs/contributed_packages/pyros.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -903,10 +903,10 @@ Observe that the log contains the following information:
903903
:linenos:
904904
905905
==============================================================================
906-
PyROS: The Pyomo Robust Optimization Solver, v1.2.9.
907-
Pyomo version: 6.7.0
906+
PyROS: The Pyomo Robust Optimization Solver, v1.2.11.
907+
Pyomo version: 6.7.2
908908
Commit hash: unknown
909-
Invoked at UTC 2023-12-16T00:00:00.000000
909+
Invoked at UTC 2024-03-28T00:00:00.000000
910910
911911
Developed by: Natalie M. Isenberg (1), Jason A. F. Sherman (1),
912912
John D. Siirola (2), Chrysanthos E. Gounaris (1)
@@ -926,6 +926,7 @@ Observe that the log contains the following information:
926926
keepfiles=False
927927
tee=False
928928
load_solution=True
929+
symbolic_solver_labels=False
929930
objective_focus=<ObjectiveType.worst_case: 1>
930931
nominal_uncertain_param_vals=[0.13248000000000001, 4.97, 4.97, 1800]
931932
decision_rule_order=1

pyomo/contrib/pyros/CHANGELOG.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22
PyROS CHANGELOG
33
===============
44

5+
-------------------------------------------------------------------------------
6+
PyROS 1.2.11 17 Mar 2024
7+
-------------------------------------------------------------------------------
8+
- Standardize calls to subordinate solvers across all PyROS subproblem types
9+
- Account for user-specified subsolver time limits when automatically
10+
adjusting subsolver time limits
11+
- Add support for automatic adjustment of SCIP subsolver time limit
12+
- Move start point of main PyROS solver timer to just before argument
13+
validation begins
14+
15+
516
-------------------------------------------------------------------------------
617
PyROS 1.2.10 07 Feb 2024
718
-------------------------------------------------------------------------------

pyomo/contrib/pyros/config.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,21 @@ def pyros_config():
503503
),
504504
),
505505
)
506+
CONFIG.declare(
507+
'symbolic_solver_labels',
508+
ConfigValue(
509+
default=False,
510+
domain=bool,
511+
description=(
512+
"""
513+
True to ensure the component names given to the
514+
subordinate solvers for every subproblem reflect
515+
the names of the corresponding Pyomo modeling components,
516+
False otherwise.
517+
"""
518+
),
519+
),
520+
)
506521

507522
# ================================================
508523
# === Required User Inputs

pyomo/contrib/pyros/master_problem_methods.py

Lines changed: 28 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from pyomo.core.expr import value
2828
from pyomo.core.base.set_types import NonNegativeIntegers, NonNegativeReals
2929
from pyomo.contrib.pyros.util import (
30+
call_solver,
3031
selective_clone,
3132
ObjectiveType,
3233
pyrosTerminationCondition,
@@ -239,31 +240,18 @@ def solve_master_feasibility_problem(model_data, config):
239240
else:
240241
solver = config.local_solver
241242

242-
timer = TicTocTimer()
243-
orig_setting, custom_setting_present = adjust_solver_time_settings(
244-
model_data.timing, solver, config
245-
)
246-
model_data.timing.start_timer("main.master_feasibility")
247-
timer.tic(msg=None)
248-
try:
249-
results = solver.solve(model, tee=config.tee, load_solutions=False)
250-
except ApplicationError:
251-
# account for possible external subsolver errors
252-
# (such as segmentation faults, function evaluation
253-
# errors, etc.)
254-
config.progress_logger.error(
243+
results = call_solver(
244+
model=model,
245+
solver=solver,
246+
config=config,
247+
timing_obj=model_data.timing,
248+
timer_name="main.master_feasibility",
249+
err_msg=(
255250
f"Optimizer {repr(solver)} encountered exception "
256251
"attempting to solve master feasibility problem in iteration "
257252
f"{model_data.iteration}."
258-
)
259-
raise
260-
else:
261-
setattr(results.solver, TIC_TOC_SOLVE_TIME_ATTR, timer.toc(msg=None))
262-
model_data.timing.stop_timer("main.master_feasibility")
263-
finally:
264-
revert_solver_max_time_adjustment(
265-
solver, orig_setting, custom_setting_present, config
266-
)
253+
),
254+
)
267255

268256
feasible_terminations = {
269257
tc.optimal,
@@ -482,28 +470,18 @@ def minimize_dr_vars(model_data, config):
482470
config.progress_logger.debug(f" Initial DR norm: {value(polishing_obj)}")
483471

484472
# === Solve the polishing model
485-
timer = TicTocTimer()
486-
orig_setting, custom_setting_present = adjust_solver_time_settings(
487-
model_data.timing, solver, config
488-
)
489-
model_data.timing.start_timer("main.dr_polishing")
490-
timer.tic(msg=None)
491-
try:
492-
results = solver.solve(polishing_model, tee=config.tee, load_solutions=False)
493-
except ApplicationError:
494-
config.progress_logger.error(
473+
results = call_solver(
474+
model=polishing_model,
475+
solver=solver,
476+
config=config,
477+
timing_obj=model_data.timing,
478+
timer_name="main.dr_polishing",
479+
err_msg=(
495480
f"Optimizer {repr(solver)} encountered an exception "
496481
"attempting to solve decision rule polishing problem "
497482
f"in iteration {model_data.iteration}"
498-
)
499-
raise
500-
else:
501-
setattr(results.solver, TIC_TOC_SOLVE_TIME_ATTR, timer.toc(msg=None))
502-
model_data.timing.stop_timer("main.dr_polishing")
503-
finally:
504-
revert_solver_max_time_adjustment(
505-
solver, orig_setting, custom_setting_present, config
506-
)
483+
),
484+
)
507485

508486
# interested in the time and termination status for debugging
509487
# purposes
@@ -726,43 +704,25 @@ def solver_call_master(model_data, config, solver, solve_data):
726704
solve_mode = "global" if config.solve_master_globally else "local"
727705
config.progress_logger.debug("Solving master problem")
728706

729-
timer = TicTocTimer()
730707
for idx, opt in enumerate(solvers):
731708
if idx > 0:
732709
config.progress_logger.warning(
733710
f"Invoking backup solver {opt!r} "
734711
f"(solver {idx + 1} of {len(solvers)}) for "
735712
f"master problem of iteration {model_data.iteration}."
736713
)
737-
orig_setting, custom_setting_present = adjust_solver_time_settings(
738-
model_data.timing, opt, config
739-
)
740-
model_data.timing.start_timer("main.master")
741-
timer.tic(msg=None)
742-
try:
743-
results = opt.solve(
744-
nlp_model,
745-
tee=config.tee,
746-
load_solutions=False,
747-
symbolic_solver_labels=True,
748-
)
749-
except ApplicationError:
750-
# account for possible external subsolver errors
751-
# (such as segmentation faults, function evaluation
752-
# errors, etc.)
753-
config.progress_logger.error(
714+
results = call_solver(
715+
model=nlp_model,
716+
solver=opt,
717+
config=config,
718+
timing_obj=model_data.timing,
719+
timer_name="main.master",
720+
err_msg=(
754721
f"Optimizer {repr(opt)} ({idx + 1} of {len(solvers)}) "
755722
"encountered exception attempting to "
756723
f"solve master problem in iteration {model_data.iteration}"
757-
)
758-
raise
759-
else:
760-
setattr(results.solver, TIC_TOC_SOLVE_TIME_ATTR, timer.toc(msg=None))
761-
model_data.timing.stop_timer("main.master")
762-
finally:
763-
revert_solver_max_time_adjustment(
764-
solver, orig_setting, custom_setting_present, config
765-
)
724+
),
725+
)
766726

767727
optimal_termination = check_optimal_termination(results)
768728
infeasible = results.solver.termination_condition == tc.infeasible

pyomo/contrib/pyros/pyros.py

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,14 @@
1212
# pyros.py: Generalized Robust Cutting-Set Algorithm for Pyomo
1313
import logging
1414
from pyomo.common.config import document_kwargs_from_configdict
15-
from pyomo.common.collections import Bunch
1615
from pyomo.core.base.block import Block
1716
from pyomo.core.expr import value
1817
from pyomo.core.base.var import Var
1918
from pyomo.core.base.objective import Objective
2019
from pyomo.contrib.pyros.util import time_code
2120
from pyomo.common.modeling import unique_component_name
2221
from pyomo.opt import SolverFactory
23-
from pyomo.contrib.pyros.config import pyros_config
22+
from pyomo.contrib.pyros.config import pyros_config, logger_domain
2423
from pyomo.contrib.pyros.util import (
2524
recast_to_min_obj,
2625
add_decision_rule_constraints,
@@ -44,7 +43,7 @@
4443
from datetime import datetime
4544

4645

47-
__version__ = "1.2.10"
46+
__version__ = "1.2.11"
4847

4948

5049
default_pyros_solver_logger = setup_pyros_logger()
@@ -330,32 +329,41 @@ def solve(
330329
Summary of PyROS termination outcome.
331330
332331
"""
333-
kwds.update(
334-
dict(
335-
first_stage_variables=first_stage_variables,
336-
second_stage_variables=second_stage_variables,
337-
uncertain_params=uncertain_params,
338-
uncertainty_set=uncertainty_set,
339-
local_solver=local_solver,
340-
global_solver=global_solver,
341-
)
342-
)
343-
config, state_vars = self._resolve_and_validate_pyros_args(model, **kwds)
344-
345-
# === Create data containers
346332
model_data = ROSolveResults()
347-
model_data.timing = Bunch()
348-
349-
# === Start timer, run the algorithm
350333
model_data.timing = TimingData()
351334
with time_code(
352335
timing_data_obj=model_data.timing,
353336
code_block_name="main",
354337
is_main_timer=True,
355338
):
356-
# output intro and disclaimer
357-
self._log_intro(logger=config.progress_logger, level=logging.INFO)
358-
self._log_disclaimer(logger=config.progress_logger, level=logging.INFO)
339+
kwds.update(
340+
dict(
341+
first_stage_variables=first_stage_variables,
342+
second_stage_variables=second_stage_variables,
343+
uncertain_params=uncertain_params,
344+
uncertainty_set=uncertainty_set,
345+
local_solver=local_solver,
346+
global_solver=global_solver,
347+
)
348+
)
349+
350+
# we want to log the intro and disclaimer in
351+
# advance of assembling the config.
352+
# this helps clarify to the user that any
353+
# messages logged during assembly of the config
354+
# were, in fact, logged after PyROS was initiated
355+
progress_logger = logger_domain(
356+
kwds.get(
357+
"progress_logger",
358+
kwds.get("options", dict()).get(
359+
"progress_logger", default_pyros_solver_logger
360+
),
361+
)
362+
)
363+
self._log_intro(logger=progress_logger, level=logging.INFO)
364+
self._log_disclaimer(logger=progress_logger, level=logging.INFO)
365+
366+
config, state_vars = self._resolve_and_validate_pyros_args(model, **kwds)
359367
self._log_config(
360368
logger=config.progress_logger,
361369
config=config,

pyomo/contrib/pyros/separation_problem_methods.py

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from pyomo.core.base import Var, Param
1919
from pyomo.common.collections import ComponentSet, ComponentMap
2020
from pyomo.common.dependencies import numpy as np
21-
from pyomo.contrib.pyros.util import ObjectiveType, get_time_from_solver
2221
from pyomo.contrib.pyros.solve_data import (
2322
DiscreteSeparationSolveCallResults,
2423
SeparationSolveCallResults,
@@ -37,9 +36,11 @@
3736
from pyomo.contrib.pyros.util import ABS_CON_CHECK_FEAS_TOL
3837
from pyomo.common.timing import TicTocTimer
3938
from pyomo.contrib.pyros.util import (
40-
TIC_TOC_SOLVE_TIME_ATTR,
4139
adjust_solver_time_settings,
40+
call_solver,
41+
ObjectiveType,
4242
revert_solver_max_time_adjustment,
43+
TIC_TOC_SOLVE_TIME_ATTR,
4344
)
4445
import os
4546
from copy import deepcopy
@@ -1070,14 +1071,14 @@ def solver_call_separation(
10701071

10711072
separation_obj.activate()
10721073

1074+
solve_mode_adverb = "globally" if solve_globally else "locally"
10731075
solve_call_results = SeparationSolveCallResults(
10741076
solved_globally=solve_globally,
10751077
time_out=False,
10761078
results_list=[],
10771079
found_violation=False,
10781080
subsolver_error=False,
10791081
)
1080-
timer = TicTocTimer()
10811082
for idx, opt in enumerate(solvers):
10821083
if idx > 0:
10831084
config.progress_logger.warning(
@@ -1086,37 +1087,19 @@ def solver_call_separation(
10861087
f"separation of performance constraint {con_name_repr} "
10871088
f"in iteration {model_data.iteration}."
10881089
)
1089-
orig_setting, custom_setting_present = adjust_solver_time_settings(
1090-
model_data.timing, opt, config
1091-
)
1092-
model_data.timing.start_timer(f"main.{solve_mode}_separation")
1093-
timer.tic(msg=None)
1094-
try:
1095-
results = opt.solve(
1096-
nlp_model,
1097-
tee=config.tee,
1098-
load_solutions=False,
1099-
symbolic_solver_labels=True,
1100-
)
1101-
except ApplicationError:
1102-
# account for possible external subsolver errors
1103-
# (such as segmentation faults, function evaluation
1104-
# errors, etc.)
1105-
adverb = "globally" if solve_globally else "locally"
1106-
config.progress_logger.error(
1090+
results = call_solver(
1091+
model=nlp_model,
1092+
solver=opt,
1093+
config=config,
1094+
timing_obj=model_data.timing,
1095+
timer_name=f"main.{solve_mode}_separation",
1096+
err_msg=(
11071097
f"Optimizer {repr(opt)} ({idx + 1} of {len(solvers)}) "
11081098
f"encountered exception attempting "
1109-
f"to {adverb} solve separation problem for constraint "
1099+
f"to {solve_mode_adverb} solve separation problem for constraint "
11101100
f"{con_name_repr} in iteration {model_data.iteration}."
1111-
)
1112-
raise
1113-
else:
1114-
setattr(results.solver, TIC_TOC_SOLVE_TIME_ATTR, timer.toc(msg=None))
1115-
model_data.timing.stop_timer(f"main.{solve_mode}_separation")
1116-
finally:
1117-
revert_solver_max_time_adjustment(
1118-
opt, orig_setting, custom_setting_present, config
1119-
)
1101+
),
1102+
)
11201103

11211104
# record termination condition for this particular solver
11221105
solver_status_dict[str(opt)] = results.solver.termination_condition

0 commit comments

Comments
 (0)