Skip to content

Commit

Permalink
Activating IPOPT_V2 with presolver (#1436)
Browse files Browse the repository at this point in the history
* Adding infrastructure to support ipopt_v2

* Moving core/util to ipopt_v2

* Moving MH initializer to ipopt_v2

* Fixing pint version issue

* Set TSA to use old IPOPT interface

* Trying to resolve Windows failures

* Working on platofrm dependent failure

* BTInitializer with presolve

* Moving last bits of core code to ipopt_v2

* Starting on idaes/models

* Removing ma57_automatic_scaling default and updating idaes/models/control

* idaes/model/properties part 1

* Remaining parts of idaes/models/proeprties

* Fixing typo

* Switching idaes/models/unit_models to ipopt_v2

* Attempt to work around HXLC issues for now

* Some clean up

* Switching modular properties initializer to solver indexed blocks

* Addressing comments

* Fixing pylint warings

* Removing unnecessary test for legacy solver wrapper
  • Loading branch information
Andrew Lee authored Jun 26, 2024
1 parent a66d4c4 commit 2e58de6
Show file tree
Hide file tree
Showing 94 changed files with 505 additions and 229 deletions.
79 changes: 79 additions & 0 deletions idaes/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import os

import pyomo.common.config
from pyomo.common.config import Bool

_log = logging.getLogger(__name__)
# Default release version if no options provided for get-extensions
Expand Down Expand Up @@ -322,6 +323,84 @@ def _new_idaes_config_block():
),
)

cfg.declare(
"ipopt_v2",
pyomo.common.config.ConfigBlock(
implicit=False,
description="Default config for 'ipopt' solver",
),
)
cfg["ipopt_v2"].declare(
"options",
pyomo.common.config.ConfigBlock(
implicit=True,
description="Default solver options for 'ipopt'",
),
)

cfg["ipopt_v2"]["options"].declare(
"nlp_scaling_method",
pyomo.common.config.ConfigValue(
domain=str,
default="gradient-based",
description="Ipopt NLP scaling method",
),
)

cfg["ipopt_v2"]["options"].declare(
"tol",
pyomo.common.config.ConfigValue(
domain=float,
default=1e-6,
description="Ipopt tol option",
),
)

cfg["ipopt_v2"]["options"].declare(
"max_iter",
pyomo.common.config.ConfigValue(
domain=int,
default=200,
description="Ipopt max_iter option",
),
)

cfg["ipopt_v2"]["options"].declare(
"linear_solver",
pyomo.common.config.ConfigValue(
domain=str,
default="ma57",
description="Linear solver to be used by IPOPT",
),
)

cfg["ipopt_v2"].declare(
"writer_config",
pyomo.common.config.ConfigBlock(
implicit=True,
description="Default writer configuration for 'ipopt'",
),
)

# TODO: Remember to update BTInitializer to use get_solver once scaling tools are deployed.
cfg["ipopt_v2"]["writer_config"].declare(
"scale_model",
pyomo.common.config.ConfigValue(
domain=Bool,
default=False, # TODO: Change to true once transition complete
description="Whether to apply model scaling in writer",
),
)

cfg["ipopt_v2"]["writer_config"].declare(
"linear_presolve",
pyomo.common.config.ConfigValue(
domain=Bool,
default=True,
description="Whether to apply linear presolve in writer",
),
)

cfg.declare(
"ipopt_l1",
pyomo.common.config.ConfigBlock(
Expand Down
59 changes: 50 additions & 9 deletions idaes/core/initialization/block_triangularization.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
Initializer class for implementing Block Triangularization initialization
"""
from pyomo.environ import SolverFactory
from pyomo.common.config import ConfigDict, ConfigValue
from pyomo.common.config import Bool, ConfigDict, ConfigValue
from pyomo.contrib.incidence_analysis import (
IncidenceGraphInterface,
solve_strongly_connected_components,
Expand All @@ -25,7 +25,6 @@
InitializationStatus,
)
from idaes.core.util.exceptions import InitializationError
from idaes.core.solvers import get_solver

__author__ = "Andrew Lee"

Expand All @@ -47,7 +46,7 @@ class BlockTriangularizationInitializer(InitializerBase):
CONFIG.declare(
"block_solver",
ConfigValue(
default="ipopt",
default="ipopt_v2",
description="Solver to use for NxN blocks",
),
)
Expand All @@ -59,13 +58,52 @@ class BlockTriangularizationInitializer(InitializerBase):
doc="Dict of options to use to set solver.options.",
),
)
CONFIG.block_solver_options.declare(
"tol",
ConfigValue(
default=1e-8,
domain=float,
description="Convergence tolerance for block solver",
),
)
CONFIG.block_solver_options.declare(
"max_iter",
ConfigValue(
default=200,
domain=int,
description="Iteration limit for block solver",
),
)
CONFIG.declare(
"block_solver_writer_config",
ConfigDict(
implicit=True,
description="Dict of writer_config arguments to pass to block solver",
),
)
CONFIG.block_solver_writer_config.declare(
"linear_presolve",
ConfigValue(
default=True,
domain=Bool,
description="Whether to use linear presolver with block solver",
),
)
CONFIG.block_solver_writer_config.declare(
"scale_model",
ConfigValue(
default=False,
domain=Bool,
description="Whether to apply model scaling with block solver",
),
)
CONFIG.declare(
"block_solver_call_options",
ConfigDict(
implicit=True,
description="Dict of arguments to pass to solver.solve call",
doc="Dict of arguments to be passed as part of the solver.solve "
"call, such as tee=True/",
"call, such as tee=True.",
),
)
CONFIG.declare(
Expand Down Expand Up @@ -111,11 +149,14 @@ def initialization_routine(self, model):
"""
Call Block Triangularization solver on model.
"""
if self.config.block_solver is not None:
solver = SolverFactory(self.config.block_solver)
solver.options.update(self.config.block_solver_options)
else:
solver = get_solver(options=self.config.block_solver_options)
# TODO: For now, go directly through solver factory as default solver
# options cause failures. Most of these appear to be due to scaling,
# so hopefully we can fix these later.
solver = SolverFactory(
self.config.block_solver,
options=self.config.block_solver_options,
writer_config=self.config.block_solver_writer_config,
)

if model.is_indexed():
for d in model.values():
Expand Down
15 changes: 13 additions & 2 deletions idaes/core/initialization/initializer_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,7 @@ class ModularInitializerBase(InitializerBase):
CONFIG.declare(
"solver",
ConfigValue(
default=None, # TODO: Can we add a square problem solver as the default here?
default="ipopt_v2", # TODO: Can we add a square problem solver as the default here?
# At the moment there is an issue with the scipy solvers not supporting the tee argument.
description="Solver to use for initialization",
),
Expand All @@ -563,6 +563,13 @@ class ModularInitializerBase(InitializerBase):
description="Dict of options to pass to solver",
),
)
CONFIG.declare(
"writer_config",
ConfigDict(
implicit=True,
description="Dict of writer_config arguments to pass to solver",
),
)
CONFIG.declare(
"default_submodel_initializer",
ConfigValue(
Expand Down Expand Up @@ -820,6 +827,10 @@ def cleanup(self, model, plugin_initializer_args, sub_initializers):

def _get_solver(self):
if self._solver is None:
self._solver = get_solver(self.config.solver, self.config.solver_options)
self._solver = get_solver(
self.config.solver,
solver_options=self.config.solver_options,
writer_config=self.config.writer_config,
)

return self._solver
3 changes: 2 additions & 1 deletion idaes/core/initialization/tests/test_initializer_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -706,8 +706,9 @@ def test_base_attributed(self):
assert initializer.config.default_submodel_initializer is None

assert initializer._solver is None
assert initializer.config.solver is None
assert initializer.config.solver == "ipopt_v2"
assert initializer.config.solver_options == {}
assert initializer.config.writer_config == {}

@pytest.mark.unit
def test_get_submodel_initializer_specific_model(self):
Expand Down
27 changes: 20 additions & 7 deletions idaes/core/solvers/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,22 @@
# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md
# for full copyright and license information.
#################################################################################
# TODO: Missing doc strings
# pylint: disable=missing-module-docstring
# pylint: disable=missing-class-docstring
"""
Wrapper for Pyomo solvers to allow us to define default solver options
"""

from copy import deepcopy

from pyomo.environ import SolverFactory

import idaes


class SolverWrapper(object):
"""
Wrapper for Pyomo solvers to allow us to define default solver options
"""

def __init__(self, name, register=True):
if name is None:
name = "default"
Expand All @@ -43,20 +50,26 @@ def __call__(self, *args, **kwargs):
name = self.name
solver = self.solver
if name in idaes.cfg and (
idaes.cfg.use_idaes_solver_config
or name == "default"
or not self.registered
idaes.cfg.use_idaes_solver_config or not self.registered
):
for k, v in idaes.cfg[name].items():
if k not in kwargs:
kwargs[k] = v
kwargs[k] = deepcopy(v)
elif k == "options":
# options is in ConfigBlock and in kwargs, treat "options"
# special so individual options can have defaults not just
# the whole options block
for opk, opv in v.items():
if opk not in kwargs["options"]:
kwargs["options"][opk] = opv
elif k == "writer_config":
# writer_config is in ConfigBlock and in kwargs, treat "writer_config"
# special so individual options can have defaults not just
# the whole options block
for opk, opv in v.items():
if opk not in kwargs["writer_config"]:
kwargs["writer_config"][opk] = opv

return solver(*args, **kwargs)


Expand Down
27 changes: 24 additions & 3 deletions idaes/core/solvers/get_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,46 @@


# Author: Andrew Lee
def get_solver(solver=None, options=None):
def get_solver(
solver=None,
solver_options: dict = None,
writer_config: dict = None,
options: dict = None,
):
"""
General method for getting a solver object which defaults to the standard
IDAES solver (defined in the IDAES configuration).
Args:
solver: string name for desired solver. Default=None, use default solver
options: dict of solver options to use, overwrites any settings
solver_options: dict of solver options to use, overwrites any settings
provided by IDAES configuration. Default = None, use default
solver options.
writer_config: dict of configuration options for solver writer, overwrites
ny settings provided by IDAES configuration. Default = None, use
default solver options.
options: DEPRECATED. Alias of solver_options.
Returns:
A Pyomo solver object
"""
if solver_options is not None:
if options is not None:
raise ValueError(
"Cannot provide both the 'options' and 'solver_options' argument. "
"'options' has been deprecated in favor of 'solver_options'."
)
options = solver_options

if solver is None:
solver = "default"
solver_obj = idaes.core.solvers.SolverWrapper(solver, register=False)()

if options is not None:
solver_obj.options.update(options)
for k, v in options.items():
solver_obj.options[k] = v
if writer_config is not None:
for k, v in writer_config.items():
solver_obj.config.writer_config[k] = v

return solver_obj
Loading

0 comments on commit 2e58de6

Please sign in to comment.