diff --git a/pyomo/contrib/appsi/plugins.py b/pyomo/contrib/appsi/plugins.py index 5333158239e..d976a758ac8 100644 --- a/pyomo/contrib/appsi/plugins.py +++ b/pyomo/contrib/appsi/plugins.py @@ -1,6 +1,6 @@ from pyomo.common.extensions import ExtensionBuilderFactory from .base import SolverFactory -from .solvers import Gurobi, Ipopt, Cbc, Cplex, Highs +from .solvers import Gurobi, Ipopt, Cbc, Cplex, Highs, Scip from .build import AppsiBuilder @@ -21,3 +21,6 @@ def load(): SolverFactory.register( name='appsi_highs', doc='Automated persistent interface to Highs' )(Highs) + SolverFactory.register( + name='appsi_scip', doc='Python interface to SCIP' + )(Scip) diff --git a/pyomo/contrib/appsi/solvers/__init__.py b/pyomo/contrib/appsi/solvers/__init__.py index df58a0cb245..0cc9d906f7d 100644 --- a/pyomo/contrib/appsi/solvers/__init__.py +++ b/pyomo/contrib/appsi/solvers/__init__.py @@ -3,3 +3,4 @@ from .cbc import Cbc from .cplex import Cplex from .highs import Highs +from .scip import Scip diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index a3aae2a9213..8e1241f3b14 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -138,6 +138,10 @@ def cbc_options(self): def cbc_options(self, val: Dict): self._solver_options = val + @property + def options(self): + return self._solver_options + @property def update_config(self): return self._writer.update_config diff --git a/pyomo/contrib/appsi/solvers/scip.py b/pyomo/contrib/appsi/solvers/scip.py index 3b7d7f392a5..3f870d68e16 100644 --- a/pyomo/contrib/appsi/solvers/scip.py +++ b/pyomo/contrib/appsi/solvers/scip.py @@ -7,14 +7,14 @@ ) from pyomo.core.base.constraint import Constraint from pyomo.core.base.block import _BlockData -from pyomo.core.base.var import _GeneralVarData, ScalarVar +from pyomo.core.base.var import _GeneralVarData, ScalarVar, Var from pyomo.core.base.param import _ParamData, ScalarParam from pyomo.core.base.expression import _GeneralExpressionData, ScalarExpression from pyomo.core.base.set import Binary, Integers from pyomo.core.expr import numeric_expr from pyomo.common.timing import HierarchicalTimer from pyomo.core.staleflag import StaleFlagManager -from pyomo.common.collections import ComponentMap +from pyomo.common.collections import ComponentMap, ComponentSet from pyomo.core.expr.visitor import StreamBasedExpressionVisitor, polynomial_degree from pyomo.core.base import SymbolMap, NumericLabeler, TextLabeler from pyomo.contrib.appsi.utils.get_objective import get_objective @@ -24,11 +24,13 @@ from pyomo.common.config import ConfigValue, NonNegativeInt import sys import logging +from typing import Optional try: import pyscipopt from pyscipopt import Model scip_available = True except: + Model = None scip_available = False @@ -42,12 +44,16 @@ def __init__( scip_model: Model, symbol_map: SymbolMap, labeler, + only_child_vars: bool, + accepted_vars: Optional[ComponentSet] = None, ): super().__init__() self.var_map = var_map self.scip_model = scip_model self._symbol_map = symbol_map self._labeler = labeler + self._only_child_vars = only_child_vars + self._accepted_vars = accepted_vars self._handlers = h = dict() h[_GeneralVarData] = self._handle_var @@ -87,6 +93,9 @@ def _handle_var(self, node: _GeneralVarData, data): if node in self.var_map: return self.var_map[node] + if self._only_child_vars and node not in self._accepted_vars: + raise RuntimeError('Constraint or objective uses variables that live outside the block being solved') + if node.is_fixed(): return node.value @@ -160,12 +169,19 @@ def exitNode(self, node, data): return self._handlers[type(node)](node, data) -def create_scip_model(pyomo_model: _BlockData, symbol_map, labeler): +def create_scip_model(pyomo_model: _BlockData, symbol_map, labeler, only_child_vars: bool): pm = pyomo_model m = Model() + if only_child_vars: + accepted_vars = ComponentSet() + for v in pm.component_data_objects(Var, descend_into=True): + accepted_vars.add(v) + else: + accepted_vars = None + var_map = ComponentMap() - visitor = PyomoToScipVisitor(var_map, m, symbol_map=symbol_map, labeler=labeler) + visitor = PyomoToScipVisitor(var_map, m, symbol_map=symbol_map, labeler=labeler, only_child_vars=only_child_vars, accepted_vars=accepted_vars) for con in pm.component_data_objects(Constraint, descend_into=True, active=True): scip_body = visitor.walk_expression(con.body) @@ -227,11 +243,12 @@ def __init__(self): class Scip(Solver): - def __init__(self) -> None: + def __init__(self, only_child_vars: bool = False) -> None: super().__init__() self._config = ScipConfig() self._options = dict() self._symbol_map = None + self._only_child_vars = only_child_vars def available(self): if scip_available: @@ -347,7 +364,7 @@ def solve( labeler = NumericLabeler('x') timer.start('create SCIP model') - scip_model, var_map = create_scip_model(model, symbol_map=self._symbol_map, labeler=labeler) + scip_model, var_map = create_scip_model(model, symbol_map=self._symbol_map, labeler=labeler, only_child_vars=self._only_child_vars) timer.stop('create SCIP model') self._set_options(scip_model, var_map) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 33f6877aaf8..398a7dd8e74 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -6,7 +6,7 @@ parameterized = parameterized.parameterized from pyomo.contrib.appsi.base import TerminationCondition, Results, PersistentSolver from pyomo.contrib.appsi.cmodel import cmodel_available -from pyomo.contrib.appsi.solvers import Gurobi, Ipopt, Cplex, Cbc, Highs +from pyomo.contrib.appsi.solvers import Gurobi, Ipopt, Cplex, Cbc, Highs, Scip from typing import Type from pyomo.core.expr.numeric_expr import LinearExpression import os @@ -25,11 +25,12 @@ ('cplex', Cplex), ('cbc', Cbc), ('highs', Highs), + ('scip', Scip), ] -mip_solvers = [('gurobi', Gurobi), ('cplex', Cplex), ('cbc', Cbc), ('highs', Highs)] -nlp_solvers = [('ipopt', Ipopt)] -qcp_solvers = [('gurobi', Gurobi), ('ipopt', Ipopt), ('cplex', Cplex)] -miqcqp_solvers = [('gurobi', Gurobi), ('cplex', Cplex)] +mip_solvers = [('gurobi', Gurobi), ('cplex', Cplex), ('cbc', Cbc), ('highs', Highs), ('scip', Scip)] +nlp_solvers = [('ipopt', Ipopt), ('scip', Scip)] +qcp_solvers = [('gurobi', Gurobi), ('ipopt', Ipopt), ('cplex', Cplex), ('scip', Scip)] +miqcqp_solvers = [('gurobi', Gurobi), ('cplex', Cplex), ('scip', Scip)] only_child_vars_options = [True, False]