Skip to content
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

Add more judicious enforcement of PyROS Solver time limit #2660

Merged
merged 25 commits into from
Jan 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d8cd120
Add more judicious enforcement of PyROS time limit
shermanjasonaf Dec 7, 2022
28e55f8
Merge branch 'main' into fix-pyros-timing
shermanjasonaf Dec 12, 2022
3d0cf2f
Resolve failing unit test attribute errors
shermanjasonaf Dec 12, 2022
017299f
Copy solver object in PyROS solver argument parsing
shermanjasonaf Dec 12, 2022
13414de
Update version number, changelog
shermanjasonaf Dec 12, 2022
5281fd9
Add logger warning if subsolver time limit not adjusted
shermanjasonaf Dec 19, 2022
f806144
Format subsolver time limit warning message
shermanjasonaf Dec 19, 2022
d620558
Resolve issues with gams solver time limit adjustment
shermanjasonaf Dec 22, 2022
ceca30d
More comprehensive PyROS time limit unit tests
shermanjasonaf Dec 22, 2022
a56c476
Add `options` attribute to test time delay solver
shermanjasonaf Dec 22, 2022
121518f
Dont clone subsolvers, add time limit reversion routine
shermanjasonaf Dec 22, 2022
45cafe0
Resolve error with solver option key
shermanjasonaf Dec 22, 2022
5ed6fc2
More comprehensive solver settings check tests
shermanjasonaf Dec 22, 2022
d5b61fb
Tweak docstrings, add assertion
shermanjasonaf Dec 22, 2022
e8ec4b5
Merge branch 'main' into fix-pyros-timing
shermanjasonaf Dec 22, 2022
8173e00
Clarify solver options attribute deletion
shermanjasonaf Dec 22, 2022
bad0998
Fixes to `TimeDelaySolver` for unit tests
shermanjasonaf Dec 22, 2022
7beb29c
Instantiate `TimeDelaySolver` directly for tests
shermanjasonaf Dec 23, 2022
ee1ab76
Merge branch 'main' into fix-pyros-timing
shermanjasonaf Jan 4, 2023
03d078c
Merge branch 'main' into fix-pyros-timing
shermanjasonaf Jan 9, 2023
f6c73c0
Merge branch 'main' into fix-pyros-timing
shermanjasonaf Jan 11, 2023
6c802b9
Merge branch 'main' into fix-pyros-timing
shermanjasonaf Jan 11, 2023
f5ecf5f
Merge branch 'main' into fix-pyros-timing
shermanjasonaf Jan 13, 2023
ff34d7a
Use more efficient SolverFactory class refs
shermanjasonaf Jan 13, 2023
2d97f27
Add comment on subsolver time limit adjustment
shermanjasonaf Jan 13, 2023
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
6 changes: 6 additions & 0 deletions pyomo/contrib/pyros/CHANGELOG.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
PyROS CHANGELOG
===============

-------------------------------------------------------------------------------
PyROS 1.2.6 07 Dec 2022
-------------------------------------------------------------------------------
- Add more judicious enforcement of PyROS time limit.


-------------------------------------------------------------------------------
PyROS 1.2.5 06 Dec 2022
-------------------------------------------------------------------------------
Expand Down
82 changes: 81 additions & 1 deletion pyomo/contrib/pyros/master_problem_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@
Objective, Constraint,
ConstraintList, SortComponents)
from pyomo.opt import TerminationCondition as tc
from pyomo.opt import SolverResults
from pyomo.core.expr import value
from pyomo.core.base.set_types import NonNegativeIntegers, NonNegativeReals
from pyomo.contrib.pyros.util import (selective_clone,
ObjectiveType,
pyrosTerminationCondition,
process_termination_condition_master_problem,
adjust_solver_time_settings,
revert_solver_max_time_adjustment,
get_main_elapsed_time,
output_logger)
from pyomo.contrib.pyros.solve_data import (MasterProblemData,
MasterResult)
Expand Down Expand Up @@ -241,6 +245,11 @@ def solve_master_feasibility_problem(model_data, config):
else:
solver = config.local_solver

orig_setting, custom_setting_present = adjust_solver_time_settings(
model_data.timing,
solver,
config,
)
try:
results = solver.solve(model, tee=config.tee, load_solutions=False)
except ApplicationError:
Expand All @@ -253,6 +262,13 @@ def solve_master_feasibility_problem(model_data, config):
f"{model_data.iteration}"
)
raise
finally:
revert_solver_max_time_adjustment(
solver,
orig_setting,
custom_setting_present,
config,
)

feasible_terminations = {
tc.optimal, tc.locallyOptimal, tc.globallyOptimal, tc.feasible
Expand Down Expand Up @@ -400,6 +416,11 @@ def minimize_dr_vars(model_data, config):

# === Solve the polishing model
timer = TicTocTimer()
orig_setting, custom_setting_present = adjust_solver_time_settings(
model_data.timing,
solver,
config,
)
timer.tic(msg=None)
try:
results = solver.solve(
Expand All @@ -420,6 +441,13 @@ def minimize_dr_vars(model_data, config):
TIC_TOC_SOLVE_TIME_ATTR,
timer.toc(msg=None),
)
finally:
revert_solver_max_time_adjustment(
solver,
orig_setting,
custom_setting_present,
config,
)

# === Process solution by termination condition
acceptable = {
Expand Down Expand Up @@ -564,6 +592,11 @@ def solver_call_master(model_data, config, solver, solve_data):

timer = TicTocTimer()
for opt in backup_solvers:
orig_setting, custom_setting_present = adjust_solver_time_settings(
model_data.timing,
opt,
config,
)
timer.tic(msg=None)
try:
results = opt.solve(
Expand All @@ -587,6 +620,13 @@ def solver_call_master(model_data, config, solver, solve_data):
TIC_TOC_SOLVE_TIME_ATTR,
timer.toc(msg=None),
)
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 Expand Up @@ -619,7 +659,6 @@ def solver_call_master(model_data, config, solver, solve_data):
v.value
for v in nlp_model.scenarios[0, 0].util.first_stage_variables
)

if config.objective_focus is ObjectiveType.nominal:
master_soln.ssv_vals = list(
v.value
Expand Down Expand Up @@ -647,6 +686,20 @@ def solver_call_master(model_data, config, solver, solve_data):
master_soln.results = results
master_soln.master_model = nlp_model

# if PyROS time limit exceeded, exit loop and return solution
elapsed = get_main_elapsed_time(model_data.timing)
if config.time_limit:
if elapsed >= config.time_limit:
try_backup = False
master_soln.master_subsolver_results = (
None,
pyrosTerminationCondition.time_out
)
master_soln.pyros_termination_condition = (
pyrosTerminationCondition.time_out
)
output_logger(config=config, time_out=True, elapsed=elapsed)

if not try_backup:
return master_soln

Expand Down Expand Up @@ -691,6 +744,33 @@ def solve_master(model_data, config):
results = solve_master_feasibility_problem(model_data, config)
master_soln.feasibility_problem_results = results

# if pyros time limit reached, load time out status
# to master results and return to caller
elapsed = get_main_elapsed_time(model_data.timing)
if config.time_limit:
if elapsed >= config.time_limit:
# load master model
master_soln.master_model = model_data.master_model
master_soln.nominal_block = model_data.master_model.scenarios[0, 0]

# empty results object, with master solve time of zero
master_soln.results = SolverResults()
setattr(master_soln.results.solver, TIC_TOC_SOLVE_TIME_ATTR, 0)

# PyROS time out status
master_soln.pyros_termination_condition = (
pyrosTerminationCondition.time_out
)
master_soln.master_subsolver_results = (
None,
pyrosTerminationCondition.time_out
)

# log time out message
output_logger(config=config, time_out=True, elapsed=elapsed)

return master_soln

solver = config.global_solver if config.solve_master_globally else config.local_solver

return solver_call_master(model_data=model_data, config=config, solver=solver,
Expand Down
4 changes: 2 additions & 2 deletions pyomo/contrib/pyros/pyros.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
from pyomo.core.base import Constraint


__version__ = "1.2.5"
__version__ = "1.2.6"


def NonNegIntOrMinusOne(obj):
Expand Down Expand Up @@ -79,7 +79,7 @@ class SolverResolvable(object):
def __call__(self, obj):
'''
if obj is a string, return the Solver object for that solver name
if obj is a Solver object, return the Solver
if obj is a Solver object, return a copy of the Solver
if obj is a list, and each element of list is solver resolvable, return list of solvers
'''
if isinstance(obj, str):
Expand Down
57 changes: 32 additions & 25 deletions pyomo/contrib/pyros/pyros_algorithm_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,27 +178,25 @@ def ROSolver_iterative_solve(model_data, config):
output_logger(config=config, robust_infeasible=True)
elif master_soln.pyros_termination_condition is pyrosTerminationCondition.subsolver_error:
term_cond = pyrosTerminationCondition.subsolver_error
elif master_soln.pyros_termination_condition is pyrosTerminationCondition.time_out:
term_cond = pyrosTerminationCondition.time_out
else:
term_cond = None
if term_cond == pyrosTerminationCondition.subsolver_error or \
term_cond == pyrosTerminationCondition.robust_infeasible:
update_grcs_solve_data(pyros_soln=model_data, k=k, term_cond=term_cond,
nominal_data=nominal_data,
timing_data=timing_data,
separation_data=separation_data,
master_soln=master_soln)
if term_cond in {
pyrosTerminationCondition.subsolver_error,
pyrosTerminationCondition.time_out,
pyrosTerminationCondition.robust_infeasible,
}:
update_grcs_solve_data(
pyros_soln=model_data,
k=k,
term_cond=term_cond,
nominal_data=nominal_data,
timing_data=timing_data,
separation_data=separation_data,
master_soln=master_soln,
)
return model_data, []
# === Check if time limit reached
elapsed = get_main_elapsed_time(model_data.timing)
if config.time_limit:
if elapsed >= config.time_limit:
output_logger(config=config, time_out=True, elapsed=elapsed)
update_grcs_solve_data(pyros_soln=model_data, k=k, term_cond=pyrosTerminationCondition.time_out,
nominal_data=nominal_data,
timing_data=timing_data,
separation_data=separation_data,
master_soln=master_soln)
return model_data, []

# === Save nominal information
if k == 0:
Expand All @@ -212,7 +210,6 @@ def ROSolver_iterative_solve(model_data, config):
nominal_data.nom_second_stage_cost = master_soln.second_stage_objective
nominal_data.nom_obj = value(master_data.master_model.obj)


if (
# === Decision rule polishing (do not polish on first iteration if no ssv or if decision_rule_order = 0)
(config.decision_rule_order != 0 and len(config.second_stage_variables) > 0 and k != 0)
Expand All @@ -234,6 +231,22 @@ def ROSolver_iterative_solve(model_data, config):
vals.append(dvar.value)
dr_var_lists_polished.append(vals)

# === Check if time limit reached
elapsed = get_main_elapsed_time(model_data.timing)
if config.time_limit:
if elapsed >= config.time_limit:
output_logger(config=config, time_out=True, elapsed=elapsed)
update_grcs_solve_data(
pyros_soln=model_data,
k=k,
term_cond=pyrosTerminationCondition.time_out,
nominal_data=nominal_data,
timing_data=timing_data,
separation_data=separation_data,
master_soln=master_soln,
)
return model_data, []

# === Set up for the separation problem
separation_data.opt_fsv_vals = [v.value for v in master_soln.master_model.scenarios[0,0].util.first_stage_variables]
separation_data.opt_ssv_vals = master_soln.ssv_vals
Expand Down Expand Up @@ -337,9 +350,3 @@ def ROSolver_iterative_solve(model_data, config):

# === In this case we still return the final solution objects for the last iteration
return model_data, separation_solns






18 changes: 17 additions & 1 deletion pyomo/contrib/pyros/separation_problem_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@
from pyomo.common.errors import ApplicationError
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
from pyomo.contrib.pyros.util import (
TIC_TOC_SOLVE_TIME_ATTR,
adjust_solver_time_settings,
revert_solver_max_time_adjustment,
)
import os
from copy import deepcopy

Expand Down Expand Up @@ -564,6 +568,11 @@ def solver_call_separation(model_data, config, solver, solve_data, is_global):

timer = TicTocTimer()
for opt in backup_solvers:
orig_setting, custom_setting_present = adjust_solver_time_settings(
model_data.timing,
opt,
config,
)
timer.tic(msg=None)
try:
results = opt.solve(
Expand All @@ -587,6 +596,13 @@ def solver_call_separation(model_data, config, solver, solve_data, is_global):
TIC_TOC_SOLVE_TIME_ATTR,
timer.toc(msg=None),
)
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