Skip to content

Commit

Permalink
Merge branch 'main' into mis
Browse files Browse the repository at this point in the history
  • Loading branch information
mrmundt authored Apr 23, 2024
2 parents 2b75576 + fb1341c commit 0f26a0a
Show file tree
Hide file tree
Showing 10 changed files with 306 additions and 210 deletions.
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
15 changes: 14 additions & 1 deletion pyomo/contrib/preprocessing/plugins/var_aggregator.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@


from pyomo.common.collections import ComponentMap, ComponentSet
from pyomo.core.base import Block, Constraint, VarList, Objective, TransformationFactory
from pyomo.core.base import (
Block,
Constraint,
VarList,
Objective,
Reals,
TransformationFactory,
)
from pyomo.core.expr import ExpressionReplacementVisitor
from pyomo.core.expr.numvalue import value
from pyomo.core.plugins.transform.hierarchy import IsomorphicTransformation
Expand Down Expand Up @@ -248,6 +255,12 @@ def _apply_to(self, model, detect_fixed_vars=True):
# the variables in its equality set.
z_agg.setlb(max_if_not_None(v.lb for v in eq_set if v.has_lb()))
z_agg.setub(min_if_not_None(v.ub for v in eq_set if v.has_ub()))
# Set the domain of the aggregate variable to the intersection of
# the domains of the variables in its equality set
domain = Reals
for v in eq_set:
domain = domain & v.domain
z_agg.domain = domain

# Set the fixed status of the aggregate var
fixed_vars = [v for v in eq_set if v.fixed]
Expand Down
34 changes: 34 additions & 0 deletions pyomo/contrib/preprocessing/tests/test_var_aggregator.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@
max_if_not_None,
min_if_not_None,
)
from pyomo.core.expr.compare import assertExpressionsEqual
from pyomo.environ import (
Binary,
ConcreteModel,
Constraint,
ConstraintList,
maximize,
Objective,
RangeSet,
Reals,
SolverFactory,
TransformationFactory,
Var,
Expand Down Expand Up @@ -210,6 +214,36 @@ def test_var_update(self):
self.assertEqual(m.x.value, 0)
self.assertEqual(m.y.value, 0)

def test_binary_inequality(self):
m = ConcreteModel()
m.x = Var(domain=Binary)
m.y = Var(domain=Binary)
m.c = Constraint(expr=m.x == m.y)
m.o = Objective(expr=0.5 * m.x + m.y, sense=maximize)
TransformationFactory('contrib.aggregate_vars').apply_to(m)
var_to_z = m._var_aggregator_info.var_to_z
z = var_to_z[m.x]
self.assertIs(var_to_z[m.y], z)
self.assertEqual(z.domain, Binary)
self.assertEqual(z.lb, 0)
self.assertEqual(z.ub, 1)
assertExpressionsEqual(self, m.o.expr, 0.5 * z + z)

def test_equality_different_domains(self):
m = ConcreteModel()
m.x = Var(domain=Reals, bounds=(1, 2))
m.y = Var(domain=Binary)
m.c = Constraint(expr=m.x == m.y)
m.o = Objective(expr=0.5 * m.x + m.y, sense=maximize)
TransformationFactory('contrib.aggregate_vars').apply_to(m)
var_to_z = m._var_aggregator_info.var_to_z
z = var_to_z[m.x]
self.assertIs(var_to_z[m.y], z)
self.assertEqual(z.lb, 1)
self.assertEqual(z.ub, 1)
self.assertEqual(z.domain, Binary)
assertExpressionsEqual(self, m.o.expr, 0.5 * z + z)


if __name__ == '__main__':
unittest.main()
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
Loading

0 comments on commit 0f26a0a

Please sign in to comment.