Skip to content

Updates to PyROS Solver Timing System #3198

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 24 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
aac6e2f
Standardize PyROS subordinate solver calls
shermanjasonaf Mar 17, 2024
d9f2251
Account for user settings in subsolver time limit adjustment
shermanjasonaf Mar 17, 2024
2593fce
Fix test error message string
shermanjasonaf Mar 17, 2024
679dc7e
Add support for SCIP time limit adjustment
shermanjasonaf Mar 17, 2024
fcb2819
Simplify time limit adjustment reversion
shermanjasonaf Mar 17, 2024
0c8afa5
Update solver test availability and license check
shermanjasonaf Mar 17, 2024
ec830e6
Move PyROS timer start to before argument validation
shermanjasonaf Mar 17, 2024
348a896
Fix typos
shermanjasonaf Mar 17, 2024
0efb798
Merge branch 'main' into fix-pyros-subsolver-time-limits
shermanjasonaf Mar 17, 2024
8aa9dff
Merge branch 'fix-pyros-subsolver-time-limits' of github.com:shermanj…
shermanjasonaf Mar 17, 2024
dbe0529
Update version number, changelog
shermanjasonaf Mar 18, 2024
2bcb6ee
Merge branch 'main' into fix-pyros-subsolver-time-limits
shermanjasonaf Mar 19, 2024
1e618e5
Merge branch 'main' into fix-pyros-subsolver-time-limits
shermanjasonaf Mar 27, 2024
bd475d1
Fix docstring typo
shermanjasonaf Mar 28, 2024
7e1f714
Merge branch 'fix-pyros-subsolver-time-limits' of github.com:shermanj…
shermanjasonaf Mar 28, 2024
ef1464c
Restore PyROS intro and disclaimer logging
shermanjasonaf Mar 28, 2024
d9ca987
Update PyROS solver logging docs example
shermanjasonaf Mar 28, 2024
3926a3b
Merge branch 'main' into fix-pyros-subsolver-time-limits
shermanjasonaf Apr 2, 2024
ca32f5c
Merge branch 'main' into fix-pyros-subsolver-time-limits
blnicho Apr 2, 2024
7b46ea5
Make `symbolic_solver_labels` configurable
shermanjasonaf Apr 5, 2024
f766b33
Make log example reflective of `symbolic_solver_labels`
shermanjasonaf Apr 5, 2024
e2c4617
Merge branch 'fix-pyros-subsolver-time-limits' of github.com:shermanj…
shermanjasonaf Apr 5, 2024
2186651
Merge branch 'Pyomo:main' into fix-pyros-subsolver-time-limits
shermanjasonaf Apr 8, 2024
0935515
Merge branch 'main' into fix-pyros-subsolver-time-limits
mrmundt Apr 9, 2024
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
7 changes: 4 additions & 3 deletions doc/OnlineDocs/contributed_packages/pyros.rst
Original file line number Diff line number Diff line change
Expand Up @@ -903,10 +903,10 @@ Observe that the log contains the following information:
:linenos:

==============================================================================
PyROS: The Pyomo Robust Optimization Solver, v1.2.9.
Pyomo version: 6.7.0
PyROS: The Pyomo Robust Optimization Solver, v1.2.11.
Pyomo version: 6.7.2
Commit hash: unknown
Invoked at UTC 2023-12-16T00:00:00.000000
Invoked at UTC 2024-03-28T00:00:00.000000

Developed by: Natalie M. Isenberg (1), Jason A. F. Sherman (1),
John D. Siirola (2), Chrysanthos E. Gounaris (1)
Expand All @@ -926,6 +926,7 @@ Observe that the log contains the following information:
keepfiles=False
tee=False
load_solution=True
symbolic_solver_labels=False
objective_focus=<ObjectiveType.worst_case: 1>
nominal_uncertain_param_vals=[0.13248000000000001, 4.97, 4.97, 1800]
decision_rule_order=1
Expand Down
11 changes: 11 additions & 0 deletions pyomo/contrib/pyros/CHANGELOG.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@
PyROS CHANGELOG
===============

-------------------------------------------------------------------------------
PyROS 1.2.11 17 Mar 2024
-------------------------------------------------------------------------------
- Standardize calls to subordinate solvers across all PyROS subproblem types
- Account for user-specified subsolver time limits when automatically
adjusting subsolver time limits
- Add support for automatic adjustment of SCIP subsolver time limit
- Move start point of main PyROS solver timer to just before argument
validation begins


-------------------------------------------------------------------------------
PyROS 1.2.10 07 Feb 2024
-------------------------------------------------------------------------------
Expand Down
15 changes: 15 additions & 0 deletions pyomo/contrib/pyros/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,21 @@ def pyros_config():
),
),
)
CONFIG.declare(
'symbolic_solver_labels',
ConfigValue(
default=False,
domain=bool,
description=(
"""
True to ensure the component names given to the
subordinate solvers for every subproblem reflect
the names of the corresponding Pyomo modeling components,
False otherwise.
"""
),
),
)

# ================================================
# === Required User Inputs
Expand Down
96 changes: 28 additions & 68 deletions pyomo/contrib/pyros/master_problem_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from pyomo.core.expr import value
from pyomo.core.base.set_types import NonNegativeIntegers, NonNegativeReals
from pyomo.contrib.pyros.util import (
call_solver,
selective_clone,
ObjectiveType,
pyrosTerminationCondition,
Expand Down Expand Up @@ -239,31 +240,18 @@ def solve_master_feasibility_problem(model_data, config):
else:
solver = config.local_solver

timer = TicTocTimer()
orig_setting, custom_setting_present = adjust_solver_time_settings(
model_data.timing, solver, config
)
model_data.timing.start_timer("main.master_feasibility")
timer.tic(msg=None)
try:
results = solver.solve(model, tee=config.tee, load_solutions=False)
except ApplicationError:
# account for possible external subsolver errors
# (such as segmentation faults, function evaluation
# errors, etc.)
config.progress_logger.error(
results = call_solver(
model=model,
solver=solver,
config=config,
timing_obj=model_data.timing,
timer_name="main.master_feasibility",
err_msg=(
f"Optimizer {repr(solver)} encountered exception "
"attempting to solve master feasibility problem in iteration "
f"{model_data.iteration}."
)
raise
else:
setattr(results.solver, TIC_TOC_SOLVE_TIME_ATTR, timer.toc(msg=None))
model_data.timing.stop_timer("main.master_feasibility")
finally:
revert_solver_max_time_adjustment(
solver, orig_setting, custom_setting_present, config
)
),
)

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

# === Solve the polishing model
timer = TicTocTimer()
orig_setting, custom_setting_present = adjust_solver_time_settings(
model_data.timing, solver, config
)
model_data.timing.start_timer("main.dr_polishing")
timer.tic(msg=None)
try:
results = solver.solve(polishing_model, tee=config.tee, load_solutions=False)
except ApplicationError:
config.progress_logger.error(
results = call_solver(
model=polishing_model,
solver=solver,
config=config,
timing_obj=model_data.timing,
timer_name="main.dr_polishing",
err_msg=(
f"Optimizer {repr(solver)} encountered an exception "
"attempting to solve decision rule polishing problem "
f"in iteration {model_data.iteration}"
)
raise
else:
setattr(results.solver, TIC_TOC_SOLVE_TIME_ATTR, timer.toc(msg=None))
model_data.timing.stop_timer("main.dr_polishing")
finally:
revert_solver_max_time_adjustment(
solver, orig_setting, custom_setting_present, config
)
),
)

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

timer = TicTocTimer()
for idx, opt in enumerate(solvers):
if idx > 0:
config.progress_logger.warning(
f"Invoking backup solver {opt!r} "
f"(solver {idx + 1} of {len(solvers)}) for "
f"master problem of iteration {model_data.iteration}."
)
orig_setting, custom_setting_present = adjust_solver_time_settings(
model_data.timing, opt, config
)
model_data.timing.start_timer("main.master")
timer.tic(msg=None)
try:
results = opt.solve(
nlp_model,
tee=config.tee,
load_solutions=False,
symbolic_solver_labels=True,
)
except ApplicationError:
# account for possible external subsolver errors
# (such as segmentation faults, function evaluation
# errors, etc.)
config.progress_logger.error(
results = call_solver(
model=nlp_model,
solver=opt,
config=config,
timing_obj=model_data.timing,
timer_name="main.master",
err_msg=(
f"Optimizer {repr(opt)} ({idx + 1} of {len(solvers)}) "
"encountered exception attempting to "
f"solve master problem in iteration {model_data.iteration}"
)
raise
else:
setattr(results.solver, TIC_TOC_SOLVE_TIME_ATTR, timer.toc(msg=None))
model_data.timing.stop_timer("main.master")
finally:
revert_solver_max_time_adjustment(
solver, orig_setting, custom_setting_present, config
)
),
)

optimal_termination = check_optimal_termination(results)
infeasible = results.solver.termination_condition == tc.infeasible
Expand Down
52 changes: 30 additions & 22 deletions pyomo/contrib/pyros/pyros.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,14 @@
# pyros.py: Generalized Robust Cutting-Set Algorithm for Pyomo
import logging
from pyomo.common.config import document_kwargs_from_configdict
from pyomo.common.collections import Bunch
from pyomo.core.base.block import Block
from pyomo.core.expr import value
from pyomo.core.base.var import Var
from pyomo.core.base.objective import Objective
from pyomo.contrib.pyros.util import time_code
from pyomo.common.modeling import unique_component_name
from pyomo.opt import SolverFactory
from pyomo.contrib.pyros.config import pyros_config
from pyomo.contrib.pyros.config import pyros_config, logger_domain
from pyomo.contrib.pyros.util import (
recast_to_min_obj,
add_decision_rule_constraints,
Expand All @@ -44,7 +43,7 @@
from datetime import datetime


__version__ = "1.2.10"
__version__ = "1.2.11"


default_pyros_solver_logger = setup_pyros_logger()
Expand Down Expand Up @@ -330,32 +329,41 @@ def solve(
Summary of PyROS termination outcome.

"""
kwds.update(
dict(
first_stage_variables=first_stage_variables,
second_stage_variables=second_stage_variables,
uncertain_params=uncertain_params,
uncertainty_set=uncertainty_set,
local_solver=local_solver,
global_solver=global_solver,
)
)
config, state_vars = self._resolve_and_validate_pyros_args(model, **kwds)

# === Create data containers
model_data = ROSolveResults()
model_data.timing = Bunch()

# === Start timer, run the algorithm
model_data.timing = TimingData()
with time_code(
timing_data_obj=model_data.timing,
code_block_name="main",
is_main_timer=True,
):
# output intro and disclaimer
self._log_intro(logger=config.progress_logger, level=logging.INFO)
self._log_disclaimer(logger=config.progress_logger, level=logging.INFO)
kwds.update(
dict(
first_stage_variables=first_stage_variables,
second_stage_variables=second_stage_variables,
uncertain_params=uncertain_params,
uncertainty_set=uncertainty_set,
local_solver=local_solver,
global_solver=global_solver,
)
)

# we want to log the intro and disclaimer in
# advance of assembling the config.
# this helps clarify to the user that any
# messages logged during assembly of the config
# were, in fact, logged after PyROS was initiated
progress_logger = logger_domain(
kwds.get(
"progress_logger",
kwds.get("options", dict()).get(
"progress_logger", default_pyros_solver_logger
),
)
)
self._log_intro(logger=progress_logger, level=logging.INFO)
self._log_disclaimer(logger=progress_logger, level=logging.INFO)

config, state_vars = self._resolve_and_validate_pyros_args(model, **kwds)
self._log_config(
logger=config.progress_logger,
config=config,
Expand Down
45 changes: 14 additions & 31 deletions pyomo/contrib/pyros/separation_problem_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
from pyomo.core.base import Var, Param
from pyomo.common.collections import ComponentSet, ComponentMap
from pyomo.common.dependencies import numpy as np
from pyomo.contrib.pyros.util import ObjectiveType, get_time_from_solver
from pyomo.contrib.pyros.solve_data import (
DiscreteSeparationSolveCallResults,
SeparationSolveCallResults,
Expand All @@ -37,9 +36,11 @@
from pyomo.contrib.pyros.util import ABS_CON_CHECK_FEAS_TOL
from pyomo.common.timing import TicTocTimer
from pyomo.contrib.pyros.util import (
TIC_TOC_SOLVE_TIME_ATTR,
adjust_solver_time_settings,
call_solver,
ObjectiveType,
revert_solver_max_time_adjustment,
TIC_TOC_SOLVE_TIME_ATTR,
)
import os
from copy import deepcopy
Expand Down Expand Up @@ -1070,14 +1071,14 @@ def solver_call_separation(

separation_obj.activate()

solve_mode_adverb = "globally" if solve_globally else "locally"
solve_call_results = SeparationSolveCallResults(
solved_globally=solve_globally,
time_out=False,
results_list=[],
found_violation=False,
subsolver_error=False,
)
timer = TicTocTimer()
for idx, opt in enumerate(solvers):
if idx > 0:
config.progress_logger.warning(
Expand All @@ -1086,37 +1087,19 @@ def solver_call_separation(
f"separation of performance constraint {con_name_repr} "
f"in iteration {model_data.iteration}."
)
orig_setting, custom_setting_present = adjust_solver_time_settings(
model_data.timing, opt, config
)
model_data.timing.start_timer(f"main.{solve_mode}_separation")
timer.tic(msg=None)
try:
results = opt.solve(
nlp_model,
tee=config.tee,
load_solutions=False,
symbolic_solver_labels=True,
)
except ApplicationError:
# account for possible external subsolver errors
# (such as segmentation faults, function evaluation
# errors, etc.)
adverb = "globally" if solve_globally else "locally"
config.progress_logger.error(
results = call_solver(
model=nlp_model,
solver=opt,
config=config,
timing_obj=model_data.timing,
timer_name=f"main.{solve_mode}_separation",
err_msg=(
f"Optimizer {repr(opt)} ({idx + 1} of {len(solvers)}) "
f"encountered exception attempting "
f"to {adverb} solve separation problem for constraint "
f"to {solve_mode_adverb} solve separation problem for constraint "
f"{con_name_repr} in iteration {model_data.iteration}."
)
raise
else:
setattr(results.solver, TIC_TOC_SOLVE_TIME_ATTR, timer.toc(msg=None))
model_data.timing.stop_timer(f"main.{solve_mode}_separation")
finally:
revert_solver_max_time_adjustment(
opt, orig_setting, custom_setting_present, config
)
),
)

# record termination condition for this particular solver
solver_status_dict[str(opt)] = results.solver.termination_condition
Expand Down
Loading