-
Notifications
You must be signed in to change notification settings - Fork 546
BUG FIXES: Solver Refactor for IDAES Integration #3214
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
Changes from 12 commits
a89ef83
617cc59
9b00edd
ac64a5a
ca79360
a99277d
eb83c2e
b3024f5
9eb3da0
a96cd10
5cd0e65
71709fd
cd33b43
7d9d490
8f90da8
468d40a
d0a2b87
643865c
1456e56
03830c0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,11 +14,11 @@ | |
from typing import Sequence, Dict, Optional, Mapping, NoReturn, List, Tuple | ||
import os | ||
|
||
from pyomo.core.base.constraint import _GeneralConstraintData | ||
from pyomo.core.base.var import _GeneralVarData | ||
from pyomo.core.base.constraint import Constraint, _GeneralConstraintData | ||
from pyomo.core.base.var import Var, _GeneralVarData | ||
from pyomo.core.base.param import _ParamData | ||
from pyomo.core.base.block import _BlockData | ||
from pyomo.core.base.objective import _GeneralObjectiveData | ||
from pyomo.core.base.objective import Objective, _GeneralObjectiveData | ||
from pyomo.common.config import document_kwargs_from_configdict, ConfigValue | ||
from pyomo.common.errors import ApplicationError | ||
from pyomo.common.deprecation import deprecation_warning | ||
|
@@ -348,9 +348,20 @@ class LegacySolverWrapper: | |
interface. Necessary for backwards compatibility. | ||
""" | ||
|
||
def __init__(self, solver_io=None, **kwargs): | ||
if solver_io is not None: | ||
def __init__(self, **kwargs): | ||
if 'solver_io' in kwargs: | ||
raise NotImplementedError('Still working on this') | ||
# There is no reason for a user to be trying to mix both old | ||
# and new options. That is silly. So we will yell at them. | ||
if 'options' in kwargs and 'solver_options' in kwargs: | ||
raise ApplicationError( | ||
"Both 'options' and 'solver_options' were requested. " | ||
"Please use one or the other, not both." | ||
) | ||
elif 'options' in kwargs: | ||
self.options = kwargs.pop('options') | ||
elif 'solver_options' in kwargs: | ||
self.solver_options = kwargs.pop('solver_options') | ||
super().__init__(**kwargs) | ||
|
||
# | ||
|
@@ -376,6 +387,8 @@ def _map_config( | |
keepfiles=NOTSET, | ||
solnfile=NOTSET, | ||
options=NOTSET, | ||
solver_options=NOTSET, | ||
writer_config=NOTSET, | ||
): | ||
"""Map between legacy and new interface configuration options""" | ||
self.config = self.config() | ||
|
@@ -393,8 +406,29 @@ def _map_config( | |
self.config.time_limit = timelimit | ||
if report_timing is not NOTSET: | ||
self.config.report_timing = report_timing | ||
if options is not NOTSET: | ||
if hasattr(self, 'options'): | ||
self.config.solver_options.set_value(self.options) | ||
mrmundt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if hasattr(self, 'solver_options'): | ||
self.config.solver_options.set_value(self.solver_options) | ||
if (options is not NOTSET) and (solver_options is not NOTSET): | ||
# There is no reason for a user to be trying to mix both old | ||
# and new options. That is silly. So we will yell at them. | ||
# Example that would raise an error: | ||
# solver.solve(model, options={'foo' : 'bar'}, solver_options={'foo' : 'not_bar'}) | ||
raise ApplicationError( | ||
"Both 'options' and 'solver_options' were declared " | ||
"in the 'solve' call. Please use one or the other, " | ||
mrmundt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"not both." | ||
) | ||
elif options is not NOTSET: | ||
# This block is trying to mimic the existing logic in the legacy | ||
# interface that allows users to pass initialized options to | ||
# the solver object and override them in the solve call. | ||
self.config.solver_options.set_value(options) | ||
elif solver_options is not NOTSET: | ||
self.config.solver_options.set_value(solver_options) | ||
if writer_config is not NOTSET: | ||
self.config.writer_config.set_value(writer_config) | ||
# This is a new flag in the interface. To preserve backwards compatibility, | ||
# its default is set to "False" | ||
if raise_exception_on_nonoptimal_result is not NOTSET: | ||
|
@@ -435,9 +469,13 @@ def _map_results(self, model, results): | |
] | ||
legacy_soln.status = legacy_solution_status_map[results.solution_status] | ||
legacy_results.solver.termination_message = str(results.termination_condition) | ||
legacy_results.problem.number_of_constraints = model.nconstraints() | ||
legacy_results.problem.number_of_variables = model.nvariables() | ||
number_of_objectives = model.nobjectives() | ||
legacy_results.problem.number_of_constraints = len( | ||
list(model.component_map(ctype=Constraint)) | ||
) | ||
legacy_results.problem.number_of_variables = len( | ||
list(model.component_map(ctype=Var)) | ||
) | ||
number_of_objectives = len(list(model.component_map(ctype=Objective))) | ||
mrmundt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
legacy_results.problem.number_of_objectives = number_of_objectives | ||
if number_of_objectives == 1: | ||
obj = get_objective(model) | ||
|
@@ -464,7 +502,15 @@ def _solution_handler( | |
"""Method to handle the preferred action for the solution""" | ||
symbol_map = SymbolMap() | ||
symbol_map.default_labeler = NumericLabeler('x') | ||
model.solutions.add_symbol_map(symbol_map) | ||
try: | ||
model.solutions.add_symbol_map(symbol_map) | ||
except AttributeError: | ||
# Something wacky happens in IDAES due to the usage of ScalarBlock | ||
# instead of PyomoModel. This is an attempt to fix that. | ||
from pyomo.core.base.PyomoModel import ModelSolutions | ||
|
||
setattr(model, 'solutions', ModelSolutions(model)) | ||
model.solutions.add_symbol_map(symbol_map) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems weird to include here. Is this pointing to an edge case that was missed in #110 that should be fixed directly in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Absolutely, yes, it should be. It was something that Andrew ran into while trying to get this working for IDAES, since they inherit from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is really wacky - and probably not something that should be added to Block (in the new world order, there should never be a reason to attach a symbolmap to the model) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How would you like to handle this? It causes issues in IDAES if it's not present because they use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added a commit to @andrewlee94's branch on IDAES to resolve this: andrewlee94/idaes-pse@532d23b |
||
legacy_results._smap_id = id(symbol_map) | ||
delete_legacy_soln = True | ||
if load_solutions: | ||
|
@@ -508,7 +554,10 @@ def solve( | |
options: Optional[Dict] = None, | ||
keepfiles: bool = False, | ||
symbolic_solver_labels: bool = False, | ||
# These are for forward-compatibility | ||
raise_exception_on_nonoptimal_result: bool = False, | ||
solver_options: Optional[Dict] = None, | ||
writer_config: Optional[Dict] = None, | ||
): | ||
""" | ||
Solve method: maps new solve method style to backwards compatible version. | ||
|
@@ -534,6 +583,8 @@ def solve( | |
'keepfiles', | ||
'solnfile', | ||
'options', | ||
'solver_options', | ||
'writer_config', | ||
) | ||
loc = locals() | ||
filtered_args = {k: loc[k] for k in map_args if loc.get(k, None) is not None} | ||
|
@@ -559,7 +610,10 @@ def available(self, exception_flag=True): | |
""" | ||
ans = super().available() | ||
if exception_flag and not ans: | ||
raise ApplicationError(f'Solver {self.__class__} is not available ({ans}).') | ||
raise ApplicationError( | ||
f'Solver "{self.name}" is not available. ' | ||
f'The returned status is: {ans}.' | ||
) | ||
return bool(ans) | ||
|
||
def license_is_valid(self) -> bool: | ||
|
Uh oh!
There was an error while loading. Please reload this page.