Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 15 additions & 1 deletion pyomo/contrib/doe/doe.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,21 @@ def run_doe(self, model=None, results_file=None):

# Solve the full model, which has now been initialized with the square solve
if self.use_grey_box:
res = self.grey_box_solver.solve(model, tee=self.grey_box_tee)
grey_box_solver_options = None
if hasattr(self.grey_box_solver, 'config') and hasattr(
self.grey_box_solver.config, 'options'
):
grey_box_solver_options = dict(
self.grey_box_solver.config.options.items()
)
if grey_box_solver_options:
res = self.grey_box_solver.solve(
model,
tee=self.grey_box_tee,
solver_options=grey_box_solver_options,
)
else:
res = self.grey_box_solver.solve(model, tee=self.grey_box_tee)
else:
res = self.solver.solve(model, tee=self.tee)

Expand Down
54 changes: 46 additions & 8 deletions pyomo/contrib/mindtpy/algorithm_base_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,13 +275,18 @@ def model_is_valid(self):
'Your model is a NLP (nonlinear program). '
'Using NLP solver %s to solve.' % config.nlp_solver
)
nlp_args = dict(config.nlp_solver_args)
update_solver_timelimit(
self.nlp_opt, config.nlp_solver, self.timing, config
self.nlp_opt,
config.nlp_solver,
self.timing,
config,
solve_args=nlp_args,
)
self.nlp_opt.solve(
self.original_model,
tee=config.nlp_solver_tee,
**config.nlp_solver_args,
**nlp_args,
)
return False
else:
Expand Down Expand Up @@ -834,7 +839,13 @@ def init_rNLP(self, add_oa_cuts=True):
MindtPy = self.rnlp.MindtPy_utils
TransformationFactory('core.relax_integer_vars').apply_to(self.rnlp)
nlp_args = dict(config.nlp_solver_args)
update_solver_timelimit(self.nlp_opt, config.nlp_solver, self.timing, config)
update_solver_timelimit(
self.nlp_opt,
config.nlp_solver,
self.timing,
config,
solve_args=nlp_args,
)
with SuppressInfeasibleWarning():
results = self.nlp_opt.solve(
self.rnlp,
Expand Down Expand Up @@ -1109,7 +1120,13 @@ def solve_subproblem(self):
return self.fixed_nlp, results
# Solve the NLP
nlp_args = dict(config.nlp_solver_args)
update_solver_timelimit(self.nlp_opt, config.nlp_solver, self.timing, config)
update_solver_timelimit(
self.nlp_opt,
config.nlp_solver,
self.timing,
config,
solve_args=nlp_args,
)
with SuppressInfeasibleWarning():
with time_code(self.timing, 'fixed subproblem'):
results = self.nlp_opt.solve(
Expand Down Expand Up @@ -1381,7 +1398,11 @@ def solve_feasibility_subproblem(self):
MindtPy.feas_obj.activate()
nlp_args = dict(config.nlp_solver_args)
update_solver_timelimit(
self.feasibility_nlp_opt, config.nlp_solver, self.timing, config
self.feasibility_nlp_opt,
config.nlp_solver,
self.timing,
config,
solve_args=nlp_args,
)
try:
TransformationFactory('contrib.deactivate_trivial_constraints').apply_to(
Expand Down Expand Up @@ -2395,7 +2416,13 @@ def solve_fp_subproblem(self):
return fp_nlp, results
# Solve the NLP
nlp_args = dict(config.nlp_solver_args)
update_solver_timelimit(self.nlp_opt, config.nlp_solver, self.timing, config)
update_solver_timelimit(
self.nlp_opt,
config.nlp_solver,
self.timing,
config,
solve_args=nlp_args,
)
with SuppressInfeasibleWarning():
with time_code(self.timing, 'fp subproblem'):
results = self.nlp_opt.solve(
Expand Down Expand Up @@ -2662,10 +2689,21 @@ def initialize_subsolvers(self):
set_solver_mipgap(self.mip_opt, config.mip_solver, config)

set_solver_constraint_violation_tolerance(
self.nlp_opt, config.nlp_solver, config
self.nlp_opt,
config.nlp_solver,
config,
solve_args=(
config.nlp_solver_args if config.nlp_solver == 'cyipopt' else None
),
)
set_solver_constraint_violation_tolerance(
self.feasibility_nlp_opt, config.nlp_solver, config, warm_start=False
self.feasibility_nlp_opt,
config.nlp_solver,
config,
warm_start=False,
solve_args=(
config.nlp_solver_args if config.nlp_solver == 'cyipopt' else None
),
)

self.set_appsi_solver_update_config()
Expand Down
45 changes: 45 additions & 0 deletions pyomo/contrib/mindtpy/tests/test_cyipopt_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from types import SimpleNamespace
from unittest import mock

import pyomo.common.unittest as unittest

from pyomo.contrib.mindtpy import util


class TestCyIpoptSolveOptions(unittest.TestCase):
def test_update_solver_timelimit_uses_solve_time_solver_options(self):
solver = SimpleNamespace(config=SimpleNamespace(options={}), options={})
config = SimpleNamespace(time_limit=10)
solve_args = {'solver_options': {'print_level': 0}}

with mock.patch.object(util, 'get_main_elapsed_time', return_value=3.2):
util.update_solver_timelimit(
solver, 'cyipopt', timing=object(), config=config, solve_args=solve_args
)

self.assertEqual(
solve_args['solver_options'],
{'print_level': 0, 'max_cpu_time': 7.0},
)
self.assertEqual(solver.config.options, {})

def test_constraint_tolerance_uses_existing_legacy_options_keyword(self):
solver = SimpleNamespace(config=SimpleNamespace(options={}), options={})
config = SimpleNamespace(zero_tolerance=1e-7)
solve_args = {'options': {'max_iter': 5}}

util.set_solver_constraint_violation_tolerance(
solver, 'cyipopt', config, solve_args=solve_args
)

self.assertEqual(
solve_args['options'],
{'max_iter': 5, 'constr_viol_tol': 1e-7},
)
self.assertEqual(solver.config.options, {})

def test_solver_option_merge_rejects_mixed_option_keywords(self):
with self.assertRaisesRegex(ValueError, "Both 'options' and 'solver_options'"):
util._update_solve_solver_options(
{'options': {}, 'solver_options': {}}, {'max_cpu_time': 1.0}
)
36 changes: 32 additions & 4 deletions pyomo/contrib/mindtpy/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,24 @@
numpy = attempt_import('numpy')[0]


def _update_solve_solver_options(solve_args, new_options):
if 'options' in solve_args and 'solver_options' in solve_args:
raise ValueError(
"Both 'options' and 'solver_options' were requested. "
"Please use one or the other, not both."
)

if 'solver_options' in solve_args:
option_kwd = 'solver_options'
elif 'options' in solve_args:
option_kwd = 'options'
else:
option_kwd = 'solver_options'

solve_args[option_kwd] = dict(solve_args.get(option_kwd, {}))
solve_args[option_kwd].update(new_options)


def calc_jacobians(constraint_list, differentiate_mode):
"""Generates a map of jacobians for the variables in the model.

Expand Down Expand Up @@ -496,7 +514,7 @@ def generate_norm1_norm_constraint(model, setpoint_model, config, discrete_only=
)


def update_solver_timelimit(opt, solver_name, timing, config):
def update_solver_timelimit(opt, solver_name, timing, config, solve_args=None):
"""Updates the time limit for subsolvers.

Parameters
Expand Down Expand Up @@ -524,7 +542,12 @@ def update_solver_timelimit(opt, solver_name, timing, config):
elif solver_name == 'appsi_highs':
opt.config.time_limit = remaining
elif solver_name == 'cyipopt':
opt.config.options['max_cpu_time'] = float(remaining)
if solve_args is None:
opt.config.options['max_cpu_time'] = float(remaining)
else:
_update_solve_solver_options(
solve_args, {'max_cpu_time': float(remaining)}
)
elif solver_name == 'glpk':
opt.options['tmlim'] = remaining
elif solver_name == 'baron':
Expand Down Expand Up @@ -565,7 +588,7 @@ def set_solver_mipgap(opt, solver_name, config):


def set_solver_constraint_violation_tolerance(
opt, solver_name, config, warm_start=True
opt, solver_name, config, warm_start=True, solve_args=None
):
"""Set constraint violation tolerance for solvers.

Expand All @@ -583,7 +606,12 @@ def set_solver_constraint_violation_tolerance(
elif solver_name in {'ipopt', 'appsi_ipopt'}:
opt.options['constr_viol_tol'] = config.zero_tolerance
elif solver_name == 'cyipopt':
opt.config.options['constr_viol_tol'] = config.zero_tolerance
if solve_args is None:
opt.config.options['constr_viol_tol'] = config.zero_tolerance
else:
_update_solve_solver_options(
solve_args, {'constr_viol_tol': config.zero_tolerance}
)
elif solver_name == 'gams':
if config.nlp_solver_args['solver'] in {
'ipopt',
Expand Down
25 changes: 24 additions & 1 deletion pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
from pyomo.opt.results.solution import Solution

logger = logging.getLogger(__name__)
_options_not_set = object()

# This maps the cyipopt STATUS_MESSAGES back to string representations
# of the Ipopt ApplicationReturnStatus enum
Expand Down Expand Up @@ -311,8 +312,30 @@ def _int(x):

return tuple(_int(_) for _ in cyipopt_interface.cyipopt.__version__.split("."))

def _get_solve_options(
self, options=_options_not_set, solver_options=_options_not_set
):
if options is not _options_not_set and solver_options is not _options_not_set:
raise ValueError(
"Both 'options' and 'solver_options' were requested. "
"Please use one or the other, not both."
)

solve_options = dict(self.config.options.items())
if options is not _options_not_set:
solve_options.update(options)
elif solver_options is not _options_not_set:
solve_options.update(solver_options)

return solve_options

def solve(self, model, **kwds):
options = kwds.pop('options', _options_not_set)
solver_options = kwds.pop('solver_options', _options_not_set)
config = self.config(kwds, preserve_implicit=True)
solve_options = self._get_solve_options(
options=options, solver_options=solver_options
)

if not isinstance(model, Block):
raise ValueError(
Expand Down Expand Up @@ -377,7 +400,7 @@ def solve(self, model, **kwds):
except AttributeError:
# Fall back to pre-1.0.0 API
add_option = cyipopt_solver.addOption
for k, v in config.options.items():
for k, v in solve_options.items():
add_option(k, v)

timer = TicTocTimer()
Expand Down
Loading
Loading