Source code for pyomo.contrib.appsi.fbbt

# ____________________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2026 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.contrib.appsi.base import PersistentBase
from pyomo.common.config import (
    ConfigDict,
    ConfigValue,
    NonNegativeFloat,
    NonNegativeInt,
)
from .cmodel import cmodel, cmodel_available
from typing import List, Optional
from pyomo.core.base.var import VarData
from pyomo.core.base.param import ParamData
from pyomo.core.base.constraint import ConstraintData
from pyomo.core.base.sos import SOSConstraintData
from pyomo.core.base.objective import ObjectiveData, minimize, maximize
from pyomo.core.base.block import BlockData
from pyomo.core.base import SymbolMap, TextLabeler
from pyomo.common.errors import InfeasibleConstraintException


[docs] class IntervalConfig(ConfigDict): """ Configuration options for the FBBT IntervalTightener Attributes ---------- feasibility_tol: float integer_tol: float improvement_tol: float max_iter: int """
[docs] def __init__( self, description=None, doc=None, implicit=False, implicit_domain=None, visibility=0, ): super(IntervalConfig, self).__init__( description=description, doc=doc, implicit=implicit, implicit_domain=implicit_domain, visibility=visibility, ) self.feasibility_tol: float = self.declare( 'feasibility_tol', ConfigValue(domain=NonNegativeFloat, default=1e-8) ) self.integer_tol: float = self.declare( 'integer_tol', ConfigValue(domain=NonNegativeFloat, default=1e-5) ) self.improvement_tol: float = self.declare( 'improvement_tol', ConfigValue(domain=NonNegativeFloat, default=1e-4) ) self.max_iter: int = self.declare( 'max_iter', ConfigValue(domain=NonNegativeInt, default=10) ) self.deactivate_satisfied_constraints: bool = self.declare( 'deactivate_satisfied_constraints', ConfigValue(domain=bool, default=False) )
[docs] class IntervalTightener(PersistentBase):
[docs] def __init__(self): super(IntervalTightener, self).__init__() self._config = IntervalConfig() self._cmodel = None self._var_map = dict() self._con_map = dict() self._param_map = dict() self._rvar_map = dict() self._rcon_map = dict() self._pyomo_expr_types = cmodel.PyomoExprTypes() self._symbolic_solver_labels: bool = False self._symbol_map = SymbolMap() self._var_labeler = None self._con_labeler = None self._param_labeler = None self._obj_labeler = None self._objective = None
@property def config(self): return self._config @config.setter def config(self, val: IntervalConfig): self._config = val def set_instance(self, model, symbolic_solver_labels: Optional[bool] = None): saved_config = self.config saved_update_config = self.update_config self.__init__() self.config = saved_config self.update_config = saved_update_config self._expr_types = cmodel.PyomoExprTypes() if symbolic_solver_labels is not None: self._symbolic_solver_labels = symbolic_solver_labels if self._symbolic_solver_labels: self._var_labeler = TextLabeler() self._con_labeler = TextLabeler() self._param_labeler = TextLabeler() self._obj_labeler = TextLabeler() self._model = model self._cmodel = cmodel.FBBTModel() self.add_block(model) if self._objective is None: self.set_objective(None) def _add_variables(self, variables: List[VarData]): if self._symbolic_solver_labels: set_name = True symbol_map = self._symbol_map labeler = self._var_labeler else: set_name = False symbol_map = None labeler = None cmodel.process_pyomo_vars( self._pyomo_expr_types, variables, self._var_map, self._param_map, self._vars, self._rvar_map, set_name, symbol_map, labeler, False, ) def _add_params(self, params: List[ParamData]): cparams = cmodel.create_params(len(params)) for ndx, p in enumerate(params): cp = cparams[ndx] cp.value = p.value self._param_map[id(p)] = cp if self._symbolic_solver_labels: for ndx, p in enumerate(params): cp = cparams[ndx] cp.name = self._symbol_map.getSymbol(p, self._param_labeler) def _add_constraints(self, cons: List[ConstraintData]): cmodel.process_fbbt_constraints( self._cmodel, self._pyomo_expr_types, cons, self._var_map, self._param_map, self._active_constraints, self._con_map, self._rcon_map, ) if self._symbolic_solver_labels: for c, cc in self._con_map.items(): cc.name = self._symbol_map.getSymbol(c, self._con_labeler) def _add_sos_constraints(self, cons: List[SOSConstraintData]): if len(cons) != 0: raise NotImplementedError( 'IntervalTightener does not support SOS constraints' ) def _remove_constraints(self, cons: List[ConstraintData]): if self._symbolic_solver_labels: for c in cons: self._symbol_map.removeSymbol(c) for c in cons: cc = self._con_map.pop(c) self._cmodel.remove_constraint(cc) del self._rcon_map[cc] def _remove_sos_constraints(self, cons: List[SOSConstraintData]): if len(cons) != 0: raise NotImplementedError( 'IntervalTightener does not support SOS constraints' ) def _remove_variables(self, variables: List[VarData]): if self._symbolic_solver_labels: for v in variables: self._symbol_map.removeSymbol(v) for v in variables: cvar = self._var_map.pop(id(v)) del self._rvar_map[cvar] def _remove_params(self, params: List[ParamData]): if self._symbolic_solver_labels: for p in params: self._symbol_map.removeSymbol(p) for p in params: del self._param_map[id(p)] def _update_variables(self, variables: List[VarData]): cmodel.process_pyomo_vars( self._pyomo_expr_types, variables, self._var_map, self._param_map, self._vars, self._rvar_map, False, None, None, True, ) def update_params(self): for p_id, p in self._params.items(): cp = self._param_map[p_id] cp.value = p.value def set_objective(self, obj: ObjectiveData): if self._symbolic_solver_labels: if self._objective is not None: self._symbol_map.removeSymbol(self._objective) super().set_objective(obj) def _set_objective(self, obj: ObjectiveData): if obj is None: ce = cmodel.Constant(0) sense = 0 else: ce = cmodel.appsi_expr_from_pyomo_expr( obj.expr, self._var_map, self._param_map, self._pyomo_expr_types ) if obj.sense is minimize: sense = 0 else: sense = 1 cobj = cmodel.FBBTObjective(ce) cobj.sense = sense self._cmodel.objective = cobj self._objective = obj if self._symbolic_solver_labels and obj is not None: cobj.name = self._symbol_map.getSymbol(obj, self._obj_labeler) def _update_pyomo_var_bounds(self): for cv, v in self._rvar_map.items(): cv_lb = cv.get_lb() cv_ub = cv.get_ub() if -cmodel.inf < cv_lb: v.setlb(cv_lb) v_id = id(v) _v, _lb, _ub, _fixed, _domain, _value = self._vars[v_id] self._vars[v_id] = (_v, cv_lb, _ub, _fixed, _domain, _value) if cv_ub < cmodel.inf: v.setub(cv_ub) v_id = id(v) _v, _lb, _ub, _fixed, _domain, _value = self._vars[v_id] self._vars[v_id] = (_v, _lb, cv_ub, _fixed, _domain, _value) def _deactivate_satisfied_cons(self): cons_to_deactivate = list() if self.config.deactivate_satisfied_constraints: for c, cc in self._con_map.items(): if not cc.active: cons_to_deactivate.append(c) self.remove_constraints(cons_to_deactivate) for c in cons_to_deactivate: c.deactivate() def perform_fbbt( self, model: BlockData, symbolic_solver_labels: Optional[bool] = None ): if model is not self._model: self.set_instance(model, symbolic_solver_labels=symbolic_solver_labels) else: if ( symbolic_solver_labels is not None and symbolic_solver_labels != self._symbolic_solver_labels ): raise RuntimeError( 'symbolic_solver_labels can only be changed through the set_instance method. ' 'Please either use set_instance or create a new instance of IntervalTightener.' ) self.update() try: n_iter = self._cmodel.perform_fbbt( self.config.feasibility_tol, self.config.integer_tol, self.config.improvement_tol, self.config.max_iter, self.config.deactivate_satisfied_constraints, ) finally: # we want to make sure the pyomo model and cmodel stay in sync # even if an exception is raised and caught self._update_pyomo_var_bounds() self._deactivate_satisfied_cons() return n_iter def perform_fbbt_with_seed(self, model: BlockData, seed_var: VarData): if model is not self._model: self.set_instance(model) else: self.update() try: n_iter = self._cmodel.perform_fbbt_with_seed( self._var_map[id(seed_var)], self.config.feasibility_tol, self.config.integer_tol, self.config.improvement_tol, self.config.max_iter, self.config.deactivate_satisfied_constraints, ) finally: # we want to make sure the pyomo model and cmodel stay in sync # even if an exception is raised and caught self._update_pyomo_var_bounds() self._deactivate_satisfied_cons() return n_iter