Source code for pyomo.contrib.preprocessing.plugins.bounds_to_vars

#  ___________________________________________________________________________
#
#  Pyomo: Python Optimization Modeling Objects
#  Copyright (c) 2008-2022
#  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.
#  ___________________________________________________________________________

"""Transformation to convert explicit bounds to variable bounds."""

from __future__ import division

from math import fabs
import math

from pyomo.core.base.transformation import TransformationFactory
from pyomo.common.config import (ConfigBlock, ConfigValue, NonNegativeFloat,
                                 add_docstring_list)
from pyomo.core.base.constraint import Constraint
from pyomo.core.expr.numvalue import value
from pyomo.core.plugins.transform.hierarchy import IsomorphicTransformation
from pyomo.repn import generate_standard_repn


[docs]@TransformationFactory.register('contrib.constraints_to_var_bounds', doc="Change constraints to be a bound on the variable.") class ConstraintToVarBoundTransform(IsomorphicTransformation): """Change constraints to be a bound on the variable. Looks for constraints of form: :math:`k*v + c_1 \\leq c_2`. Changes variable lower bound on :math:`v` to match :math:`(c_2 - c_1)/k` if it results in a tighter bound. Also does the same thing for lower bounds. Keyword arguments below are specified for the ``apply_to`` and ``create_using`` functions. """ CONFIG = ConfigBlock("ConstraintToVarBounds") CONFIG.declare("tolerance", ConfigValue( default=1E-13, domain=NonNegativeFloat, description="tolerance on bound equality (:math:`LB = UB`)" )) CONFIG.declare("detect_fixed", ConfigValue( default=True, domain=bool, description="If True, fix variable when " ":math:`| LB - UB | \\leq tolerance`." )) __doc__ = add_docstring_list(__doc__, CONFIG) def _apply_to(self, model, **kwds): config = self.CONFIG(kwds) for constr in model.component_data_objects( ctype=Constraint, active=True, descend_into=True): # Check if the constraint is k * x + c1 <= c2 or c2 <= k * x + c1 repn = generate_standard_repn(constr.body) if not repn.is_linear() or len(repn.linear_vars) != 1: # Skip nonlinear constraints, trivial constraints, and those # that involve more than one variable. continue else: var = repn.linear_vars[0] const = repn.constant coef = float(repn.linear_coefs[0]) if coef == 0: # Skip trivial constraints continue elif coef > 0: if constr.has_ub(): new_ub = (constr.ub - const) / coef var_ub = float('inf') if var.ub is None else var.ub var.setub(min(var_ub, new_ub)) if constr.has_lb(): new_lb = (constr.lb - const) / coef var_lb = float('-inf') if var.lb is None else var.lb var.setlb(max(var_lb, new_lb)) elif coef < 0: if constr.has_ub(): new_lb = (constr.ub - const) / coef var_lb = float('-inf') if var.lb is None else var.lb var.setlb(max(var_lb, new_lb)) if constr.has_lb(): new_ub = (constr.lb - const) / coef var_ub = float('inf') if var.ub is None else var.ub var.setub(min(var_ub, new_ub)) if var.is_integer(): # Make sure that the lb and ub are integral. Use safe # construction if near to integer. if var.has_lb(): var.setlb(int(min(math.ceil(var.lb - config.tolerance), math.ceil(var.lb)))) if var.has_ub(): var.setub(int(max(math.floor(var.ub + config.tolerance), math.floor(var.ub)))) if var is not None and var.value is not None: _adjust_var_value_if_not_feasible(var) if config.detect_fixed and var.has_lb() and var.has_ub(): lb, ub = var.bounds if lb == ub: var.fix(lb) elif fabs(lb - ub) <= config.tolerance: # If the bounds are note exactly equal, set the # value to the midpoint var.fix((lb + ub) / 2) constr.deactivate()
def _adjust_var_value_if_not_feasible(var): # Sometimes deactivating the constraint will remove a # variable from all active constraints, so that it won't be # updated during the optimization. Therefore, we need to # shift the value of var as necessary in order to keep it # within its implied bounds, as the constraint we are # deactivating is not an invalid constraint, but rather we # are moving its implied bound directly onto the variable. var_value = var.value if var.has_lb(): var_value = max(var_value, var.lb) if var.has_ub(): var_value = min(var_value, var.ub) if var.is_integer(): var.set_value(int(var_value)) else: var.set_value(var_value)