Skip to content
12 changes: 9 additions & 3 deletions pyomo/contrib/solver/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from .solvers.gurobi_direct_minlp import GurobiDirectMINLP
from .solvers.highs import Highs
from .solvers.knitro.direct import KnitroDirectSolver
from .solvers.knitro.persistent import KnitroPersistentSolver


def load():
Expand All @@ -34,9 +35,9 @@ def load():
doc="Direct (scipy-based) interface to Gurobi",
)(GurobiDirect)
SolverFactory.register(
name='gurobi_direct_minlp',
legacy_name='gurobi_direct_minlp',
doc='Direct interface to Gurobi accommodating general MINLP',
name="gurobi_direct_minlp",
legacy_name="gurobi_direct_minlp",
doc="Direct interface to Gurobi accommodating general MINLP",
)(GurobiDirectMINLP)
SolverFactory.register(
name="highs", legacy_name="highs", doc="Persistent interface to HiGHS"
Expand All @@ -46,3 +47,8 @@ def load():
legacy_name="knitro_direct",
doc="Direct interface to KNITRO solver",
)(KnitroDirectSolver)
SolverFactory.register(
name="knitro_persistent",
legacy_name="knitro_persistent",
doc="Persistent interface to KNITRO solver",
)(KnitroPersistentSolver)
8 changes: 3 additions & 5 deletions pyomo/contrib/solver/solvers/knitro/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# ___________________________________________________________________________

from abc import abstractmethod
from collections.abc import Mapping, Sequence
from collections.abc import Iterable, Mapping, Sequence
from datetime import datetime, timezone
from io import StringIO
from typing import Optional
Expand Down Expand Up @@ -49,8 +49,6 @@


class KnitroSolverBase(SolutionProvider, PackageChecker, SolverBase):
CONFIG = KnitroConfig()
config: KnitroConfig

_engine: Engine
_model_data: KnitroModelData
Expand Down Expand Up @@ -96,7 +94,7 @@ def solve(self, model: BlockData, **kwds) -> Results:
return results

def _build_config(self, **kwds) -> KnitroConfig:
return self.config(value=kwds, preserve_implicit=True) # type: ignore
return self.config(value=kwds, preserve_implicit=True)

def _validate_problem(self) -> None:
if len(self._model_data.objs) > 1:
Expand Down Expand Up @@ -194,7 +192,7 @@ def get_num_solutions(self) -> int:
def _get_vars(self) -> list[VarData]:
return self._model_data.variables

def _get_items(self, item_type: type[ItemType]) -> Sequence[ItemType]:
def _get_items(self, item_type: type[ItemType]) -> Iterable[ItemType]:
maps = {
VarData: self._model_data.variables,
ConstraintData: self._model_data.cons,
Expand Down
54 changes: 40 additions & 14 deletions pyomo/contrib/solver/solvers/knitro/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
# ___________________________________________________________________________

from pyomo.common.config import Bool, ConfigValue
from pyomo.contrib.solver.common.config import SolverConfig
from pyomo.contrib.solver.common.config import PersistentSolverConfig, SolverConfig


class KnitroConfig(SolverConfig):
"""Configuration for the direct Knitro solver interface."""

def __init__(
self,
description=None,
Expand All @@ -30,31 +32,55 @@ def __init__(
visibility=visibility,
)

self.rebuild_model_on_remove_var: bool = self.declare(
"rebuild_model_on_remove_var",
self.restore_variable_values_after_solve: bool = self.declare(
"restore_variable_values_after_solve",
ConfigValue(
domain=Bool,
default=False,
doc=(
"KNITRO solver does not allow variable removal. We can "
"either make the variable a continuous free variable or "
"rebuild the whole model when variable removal is "
"attempted. When `rebuild_model_on_remove_var` is set to "
"True, the model will be rebuilt."
"To evaluate non-linear constraints, KNITRO solver sets "
"explicit values on variables. This option controls "
"whether to restore the original variable values after "
"solving."
),
),
)

self.restore_variable_values_after_solve: bool = self.declare(
"restore_variable_values_after_solve",

class KnitroPersistentConfig(KnitroConfig, PersistentSolverConfig):
"""Configuration for the persistent Knitro solver interface.

Extends KnitroConfig with persistent solver capabilities including
auto_updates configuration.
"""

def __init__(
self,
description=None,
doc=None,
implicit=False,
implicit_domain=None,
visibility=0,
) -> None:
super().__init__(
description=description,
doc=doc,
implicit=implicit,
implicit_domain=implicit_domain,
visibility=visibility,
)

self.rebuild_model_on_remove_var: bool = self.declare(
"rebuild_model_on_remove_var",
ConfigValue(
domain=Bool,
default=False,
doc=(
"To evaluate non-linear constraints, KNITRO solver sets "
"explicit values on variables. This option controls "
"whether to restore the original variable values after "
"solving."
"KNITRO solver does not allow variable removal. We can "
"either make the variable a continuous free variable or "
"rebuild the whole model when variable removal is "
"attempted. When `rebuild_model_on_remove_var` is set to "
"True, the model will be rebuilt."
),
),
)
3 changes: 3 additions & 0 deletions pyomo/contrib/solver/solvers/knitro/direct.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@


class KnitroDirectSolver(KnitroSolverBase):
CONFIG = KnitroConfig()
config: KnitroConfig

def _presolve(
self, model: BlockData, config: KnitroConfig, timer: HierarchicalTimer
) -> None:
Expand Down
133 changes: 133 additions & 0 deletions pyomo/contrib/solver/solvers/knitro/persistent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# ___________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2025
# National Technology and Engineering Solutions of Sandia, LLC
# Under the terms of Contract DE-NA0003525 with National Technology and
# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
# rights in this software.
# This software is distributed under the 3-clause BSD License.
# ___________________________________________________________________________

from pyomo.common.timing import HierarchicalTimer
from pyomo.contrib.solver.common.base import PersistentSolverBase
from pyomo.contrib.solver.solvers.knitro.base import KnitroSolverBase
from pyomo.contrib.solver.solvers.knitro.config import KnitroPersistentConfig
from pyomo.contrib.solver.solvers.knitro.utils import KnitroModelData
from pyomo.core.base.block import BlockData
from pyomo.core.base.constraint import ConstraintData
from pyomo.core.base.objective import ObjectiveData
from pyomo.core.base.param import ParamData
from pyomo.core.base.var import VarData


class KnitroPersistentSolver(KnitroSolverBase, PersistentSolverBase):
CONFIG = KnitroPersistentConfig()
config: KnitroPersistentConfig

_model: BlockData | None
_staged_model_data: KnitroModelData

def __init__(self, **kwds) -> None:
PersistentSolverBase.__init__(self, **kwds)
KnitroSolverBase.__init__(self, **kwds)
self._model = None
self._staged_model_data = KnitroModelData()

def _presolve(
self, model: BlockData, config: KnitroPersistentConfig, timer: HierarchicalTimer
) -> None:
if self._model is not model:
self.set_instance(model)
self._staged_model_data.clear()

if self._staged_model_data:
self._update()

def _solve(self, config: KnitroPersistentConfig, timer: HierarchicalTimer) -> None:
self._engine.set_outlev()
if config.threads is not None:
self._engine.set_num_threads(config.threads)
if config.time_limit is not None:
self._engine.set_time_limit(config.time_limit)

timer.start("load_options")
self._engine.set_options(**config.solver_options)
timer.stop("load_options")

timer.start("solve")
self._engine.solve()
timer.stop("solve")

def set_instance(self, model: BlockData):
if self._model is model:
return
self._model = model
self._model_data.set_block(model)
self._engine.renew()
self._engine.add_vars(self._model_data.variables)
self._engine.add_cons(self._model_data.cons)
if self._model_data.objs:
self._engine.set_obj(self._model_data.objs[0])

def add_block(self, block: BlockData):
self._staged_model_data.add_block(block, clear_objs=True)

def add_variables(self, variables: list[VarData]):
self._staged_model_data.add_vars(variables)

def add_constraints(self, cons: list[ConstraintData]):
self._staged_model_data.add_cons(cons, existing_vars=self._model_data.variables)

def set_objective(self, obj: ObjectiveData):
self._staged_model_data.objs.clear()
self._staged_model_data.objs.append(obj)

def _update(self):
self._model_data.add_vars(self._staged_model_data.variables)
self._model_data.add_cons(self._staged_model_data.cons)

self._engine.add_vars(self._staged_model_data.variables)
self._engine.add_cons(self._staged_model_data.cons)

if self._staged_model_data.objs:
self._model_data.objs.clear()
self._model_data.objs.extend(self._staged_model_data.objs)
self._engine.set_obj(self._model_data.objs[0])

self._staged_model_data.clear()

def remove_variables(self, variables: list[VarData]) -> None:
raise NotImplementedError(
"KnitroPersistentSolver does not support removing variables."
)

def remove_constraints(self, cons: list[ConstraintData]) -> None:
raise NotImplementedError(
"KnitroPersistentSolver does not support removing constraints."
)

def update_variables(self, variables: list[VarData]) -> None:
raise NotImplementedError(
"KnitroPersistentSolver does not support updating variables."
)

def update_parameters(self) -> None:
raise NotImplementedError(
"KnitroPersistentSolver does not support updating parameters."
)

def add_parameters(self, params: list[ParamData]) -> None:
raise NotImplementedError(
"KnitroPersistentSolver does not support adding parameters."
)

def remove_parameters(self, params: list[ParamData]) -> None:
raise NotImplementedError(
"KnitroPersistentSolver does not support removing parameters."
)

def remove_block(self, block: BlockData) -> None:
raise NotImplementedError(
"KnitroPersistentSolver does not support removing blocks."
)
Loading
Loading