Source code for pyomo.core.plugins.transform.equality_transform
# ___________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2024
# 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.core import TransformationFactory, Var, NonNegativeReals
from pyomo.core.base.misc import create_name
from pyomo.core.plugins.transform.hierarchy import IsomorphicTransformation
from pyomo.core.plugins.transform.util import collectAbstractComponents
[docs]
@TransformationFactory.register(
"core.add_slack_vars",
doc="Create an equivalent model by introducing slack variables to eliminate inequality constraints.",
)
class EqualityTransform(IsomorphicTransformation):
"""
Creates a new, equivalent model by introducing slack and excess variables
to eliminate inequality constraints.
"""
[docs]
def __init__(self, **kwds):
kwds["name"] = kwds.pop("name", "add_slack_vars")
super(EqualityTransform, self).__init__(**kwds)
def _create_using(self, model, **kwds):
"""
Eliminate inequality constraints.
Required arguments:
model The model to transform.
Optional keyword arguments:
slack_root The root name of auxiliary slack variables.
Default is 'auxiliary_slack'.
excess_root The root name of auxiliary slack variables.
Default is 'auxiliary_excess'.
lb_suffix The suffix applied to converted upper bound constraints
Default is '_lower_bound'.
ub_suffix The suffix applied to converted lower bound constraints
Default is '_upper_bound'.
"""
# Optional naming schemes
slack_suffix = kwds.pop("slack_suffix", "slack")
excess_suffix = kwds.pop("excess_suffix", "excess")
lb_suffix = kwds.pop("lb_suffix", "lb")
ub_suffix = kwds.pop("ub_suffix", "ub")
equality = model.clone()
components = collectAbstractComponents(equality)
#
# Fix all Constraint objects
#
for con_name in components["Constraint"]:
con = equality.__getattribute__(con_name)
#
# Get all ConstraintData objects
#
# We need to get the keys ahead of time because we are modifying
# con._data on-the-fly.
#
indices = con._data.keys()
for ndx, cdata in [(ndx, con._data[ndx]) for ndx in indices]:
qualified_con_name = create_name(con_name, ndx)
# Do nothing with equality constraints
if cdata.equality:
continue
# Add an excess variable if the lower bound exists
if cdata.lower is not None:
# Make the excess variable
excess_name = "%s_%s" % (qualified_con_name, excess_suffix)
equality.__setattr__(excess_name, Var(within=NonNegativeReals))
# Make a new lower bound constraint
lb_name = "%s_%s" % (create_name("", ndx), lb_suffix)
excess = equality.__getattribute__(excess_name)
new_expr = cdata.lower == cdata.body - excess
con.add(lb_name, new_expr)
# Add a slack variable if the lower bound exists
if cdata.upper is not None:
# Make the excess variable
slack_name = "%s_%s" % (qualified_con_name, slack_suffix)
equality.__setattr__(slack_name, Var(within=NonNegativeReals))
# Make a new upper bound constraint
ub_name = "%s_%s" % (create_name("", ndx), ub_suffix)
slack = equality.__getattribute__(slack_name)
new_expr = cdata.upper == cdata.body + slack
con.add(ub_name, new_expr)
# Since we explicitly `continue` for equality constraints, we
# can safely remove the old ConstraintData object
del con._data[ndx]
return equality.create()