1#  ___________________________________________________________________________
2#
3#  Pyomo: Python Optimization Modeling Objects
4#  Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC
5#  Under the terms of Contract DE-NA0003525 with National Technology and
6#  Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
7#  rights in this software.
8#  This software is distributed under the 3-clause BSD License.
9#  ___________________________________________________________________________
10
11from pyomo.core import TransformationFactory, Var, NonNegativeReals
12from pyomo.core.base.misc import create_name
13
14from pyomo.core.plugins.transform.hierarchy import IsomorphicTransformation
15from pyomo.core.plugins.transform.util import collectAbstractComponents
16
17
18@TransformationFactory.register("core.add_slack_vars", doc="Create an equivalent model by introducing slack variables to eliminate inequality constraints.")
19class EqualityTransform(IsomorphicTransformation):
20    """
21    Creates a new, equivalent model by introducing slack and excess variables
22    to eliminate inequality constraints.
23    """
24
25    def __init__(self, **kwds):
26        kwds["name"] = kwds.pop("name", "add_slack_vars")
27        super(EqualityTransform, self).__init__(**kwds)
28
29    def _create_using(self, model, **kwds):
30        """
31        Eliminate inequality constraints.
32
33        Required arguments:
34
35          model The model to transform.
36
37        Optional keyword arguments:
38
39          slack_root  The root name of auxiliary slack variables.
40                      Default is 'auxiliary_slack'.
41          excess_root The root name of auxiliary slack variables.
42                      Default is 'auxiliary_excess'.
43          lb_suffix   The suffix applied to converted upper bound constraints
44                      Default is '_lower_bound'.
45          ub_suffix   The suffix applied to converted lower bound constraints
46                      Default is '_upper_bound'.
47        """
48
49        # Optional naming schemes
50        slack_suffix = kwds.pop("slack_suffix", "slack")
51        excess_suffix = kwds.pop("excess_suffix", "excess")
52        lb_suffix = kwds.pop("lb_suffix", "lb")
53        ub_suffix = kwds.pop("ub_suffix", "ub")
54
55        equality = model.clone()
56        components = collectAbstractComponents(equality)
57
58        #
59        # Fix all Constraint objects
60        #
61        for con_name in components["Constraint"]:
62            con = equality.__getattribute__(con_name)
63
64            #
65            # Get all _ConstraintData objects
66            #
67            # We need to get the keys ahead of time because we are modifying
68            # con._data on-the-fly.
69            #
70            indices = con._data.keys()
71            for (ndx, cdata) in [(ndx, con._data[ndx]) for ndx in indices]:
72
73                qualified_con_name = create_name(con_name, ndx)
74
75                # Do nothing with equality constraints
76                if cdata.equality:
77                    continue
78
79                # Add an excess variable if the lower bound exists
80                if cdata.lower is not None:
81
82                    # Make the excess variable
83                    excess_name = "%s_%s" % (qualified_con_name, excess_suffix)
84                    equality.__setattr__(excess_name,
85                                         Var(within=NonNegativeReals))
86
87                    # Make a new lower bound constraint
88                    lb_name = "%s_%s" % (create_name("", ndx), lb_suffix)
89                    excess = equality.__getattribute__(excess_name)
90                    new_expr = (cdata.lower == cdata.body - excess)
91                    con.add(lb_name, new_expr)
92
93                # Add a slack variable if the lower bound exists
94                if cdata.upper is not None:
95
96                    # Make the excess variable
97                    slack_name = "%s_%s" % (qualified_con_name, slack_suffix)
98                    equality.__setattr__(slack_name,
99                                         Var(within=NonNegativeReals))
100
101                    # Make a new upper bound constraint
102                    ub_name = "%s_%s" % (create_name("", ndx), ub_suffix)
103                    slack = equality.__getattribute__(slack_name)
104                    new_expr = (cdata.upper == cdata.body + slack)
105                    con.add(ub_name, new_expr)
106
107                # Since we explicitly `continue` for equality constraints, we
108                # can safely remove the old _ConstraintData object
109                del con._data[ndx]
110
111        return equality.create()
112