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