Changeset 2860


Ignore:
Timestamp:
Jul 27, 2010 5:02:03 PM (9 years ago)
Author:
prsteel
Message:

Nonnegativity transformation, and associated tests.

Location:
coopr.pyomo/trunk/coopr/pyomo
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • coopr.pyomo/trunk/coopr/pyomo/tests/unit/test_transform.py

    r2422 r2860  
    1414import pyutilib.services
    1515from pyutilib.component.core import Plugin
     16from pyutilib.services import registered_executable
     17from coopr.pyomo.transform import *
    1618
    1719class Test(unittest.TestCase):
     
    2426           os.unlink("unknown.lp")
    2527        pyutilib.services.TempfileManager.clear_tempfiles()
     28
     29    @staticmethod
     30    def nonnegativeBounds(var):
     31        # Either the bounds or domain must enforce nonnegativity
     32        if var.lb is not None and var.lb >= 0:
     33            return True
     34        elif var.domain is not None and var.domain.bounds()[0] >= 0:
     35            return True
     36        else:
     37            return False
    2638
    2739    def test_relax_integrality1(self):
     
    5870        self.failUnlessEqual(apply_transformation('foo', self.model),None)
    5971
     72    def test_nonnegativity_transformation_1(self):
     73        self.model.A = RangeSet(1,4)
     74        self.model.a = Var()
     75        #
     76        # TODO: Use the commented definition of b once the domain bug in
     77        # Var objects related to RangeSet domains has been resolved.
     78        #
     79        #self.model.b = Var(within=self.model.A)
     80        self.model.b = Var()
     81        self.model.c = Var(within=NonNegativeIntegers)
     82        self.model.d = Var(within=Integers, bounds=(-2,3))
     83        self.model.e = Var(within=Boolean)
     84        self.model.f = Var(domain=Boolean)
     85
     86        transform = NonNegativeTransformation()
     87        print "------------------ Instance -------------------"
     88        instance=self.model.create()
     89        #transformed = apply_transformation("nonnegative_transform", instance)
     90        print "------------------ Transformation -------------------"
     91        transformed = transform(instance)
     92
     93        # Check that all variables have nonnegative bounds or domains
     94        for c in ('a', 'b', 'c', 'd', 'e', 'f'):
     95            var = transformed.__getattribute__(c)
     96            for ndx in var._index:
     97                self.failUnless(self.nonnegativeBounds(var[ndx]))
     98
     99        # Check that discrete variables are still discrete, and continuous
     100        # continuous
     101        print transformed.a.domain
     102        for ndx in transformed.a._index:
     103            self.failUnless(isinstance(transformed.a[ndx].domain, RealSet))
     104        for ndx in transformed.b._index:
     105            #self.failUnless(isinstance(transformed.b[ndx].domain, IntegerSet))
     106            self.failUnless(isinstance(transformed.b[ndx].domain, RealSet))
     107        for ndx in transformed.c._index:
     108            self.failUnless(isinstance(transformed.c[ndx].domain, IntegerSet))
     109        for ndx in transformed.d._index:
     110            self.failUnless(isinstance(transformed.d[ndx].domain, IntegerSet))
     111        for ndx in transformed.e._index:
     112            self.failUnless(isinstance(transformed.e[ndx].domain, BooleanSet))
     113        for ndx in transformed.f._index:
     114            self.failUnless(isinstance(transformed.f[ndx].domain, BooleanSet))
     115
     116    def test_nonnegativity_transformation_2(self):
     117        self.model.S = RangeSet(0,10)
     118        self.model.T = Set(initialize=["foo", "bar"])
     119
     120        # Unindexed, singly indexed, and doubly indexed variables with
     121        # explicit bounds
     122        self.model.x1 = Var(bounds=(-3, 3))
     123        self.model.y1 = Var(self.model.S, bounds=(-3, 3))
     124        self.model.z1 = Var(self.model.S, self.model.T, bounds=(-3, 3))
     125
     126        # Unindexed, singly indexed, and doubly indexed variables with
     127        # rule-defined bounds
     128        def boundsRule(*args):
     129            return (-4, 4)
     130        self.model.x2 = Var(bounds=boundsRule)
     131        self.model.y2 = Var(self.model.S, bounds=boundsRule)
     132        self.model.z2 = Var(self.model.S, self.model.T, bounds=boundsRule)
     133
     134       
     135        # Unindexed, singly indexed, and doubly indexed variables with
     136        # explicit domains
     137        self.model.x3 = Var(domain=NegativeReals)
     138        self.model.y3 = Var(self.model.S, domain = NegativeIntegers)
     139        self.model.z3 = Var(self.model.S, self.model.T, domain = Reals)
     140
     141        # Unindexed, singly indexed, and doubly indexed variables with
     142        # rule-defined domains
     143        def domainRule(*args):
     144            if len(args) == 1 or args[0] == 0:
     145                return NonNegativeReals
     146            elif args[0] == 1:
     147                return NonNegativeIntegers
     148            elif args[0] == 2:
     149                return NonPositiveReals
     150            elif args[0] == 3:
     151                return NonPositiveIntegers
     152            elif args[0] == 4:
     153                return NegativeReals
     154            elif args[0] == 5:
     155                return NegativeIntegers
     156            elif args[0] == 6:
     157                return PositiveReals
     158            elif args[0] == 7:
     159                return PositiveIntegers
     160            elif args[0] == 8:
     161                return Reals
     162            elif args[0] == 9:
     163                return Integers
     164            elif args[0] == 10:
     165                return Binary
     166
     167        self.model.x4 = Var(domain=domainRule)
     168        self.model.y4 = Var(self.model.S, domain=domainRule)
     169        self.model.z4 = Var(self.model.S, self.model.T, domain=domainRule)
     170
     171        transform = NonNegativeTransformation()
     172        instance=self.model.create()
     173        #transformed = apply_transformation("nonnegative_transform", instance)
     174        transformed = transform(instance)
     175
     176        # Make sure everything is nonnegative
     177        for c in ('x', 'y', 'z'):
     178            for n in ('1', '2', '3', '4'):
     179                var = transformed.__getattribute__(c+n)
     180                for ndx in var._index:
     181                    self.failUnless(self.nonnegativeBounds(var[ndx]))
     182
     183    @unittest.skipIf(registered_executable('glpsol') is None, "The 'glpsol' executable is not available")
     184    def test_nonnegative_transform_3(self):
     185        self.model.S = RangeSet(0,10)
     186        self.model.T = Set(initialize=["foo", "bar"])
     187
     188        # Unindexed, singly indexed, and doubly indexed variables with
     189        # explicit bounds
     190        self.model.x1 = Var(bounds=(-3, 3))
     191        self.model.y1 = Var(self.model.S, bounds=(-3, 3))
     192        self.model.z1 = Var(self.model.S, self.model.T, bounds=(-3, 3))
     193
     194        # Unindexed, singly indexed, and doubly indexed variables with
     195        # rule-defined bounds
     196        def boundsRule(*args):
     197            return (-4, 4)
     198        self.model.x2 = Var(bounds=boundsRule)
     199        self.model.y2 = Var(self.model.S, bounds=boundsRule)
     200        self.model.z2 = Var(self.model.S, self.model.T, bounds=boundsRule)
     201
     202       
     203        # Unindexed, singly indexed, and doubly indexed variables with
     204        # explicit domains
     205        self.model.x3 = Var(domain=NegativeReals, bounds=(-10, 10))
     206        self.model.y3 = Var(self.model.S, domain = NegativeIntegers, bounds=(-10, 10))
     207        self.model.z3 = Var(self.model.S, self.model.T, domain = Reals, bounds=(-10, 10))
     208
     209        # Unindexed, singly indexed, and doubly indexed variables with
     210        # rule-defined domains
     211        def domainRule(*args):
     212            if len(args) == 1:
     213                arg = 0
     214            elif isinstance(args[0], tuple):
     215                arg = args[0][0]
     216            else:
     217                arg = args[0]
     218
     219            if len(args) == 1 or arg == 0:
     220                return NonNegativeReals
     221            elif arg == 1:
     222                return NonNegativeIntegers
     223            elif arg == 2:
     224                return NonPositiveReals
     225            elif arg == 3:
     226                return NonPositiveIntegers
     227            elif arg == 4:
     228                return NegativeReals
     229            elif arg == 5:
     230                return NegativeIntegers
     231            elif arg == 6:
     232                return PositiveReals
     233            elif arg == 7:
     234                return PositiveIntegers
     235            elif arg == 8:
     236                return Reals
     237            elif arg == 9:
     238                return Integers
     239            elif arg == 10:
     240                return Binary
     241            else:
     242                return Reals
     243
     244        self.model.x4 = Var(domain=domainRule, bounds=(-10, 10))
     245        self.model.y4 = Var(self.model.S, domain=domainRule, bounds=(-10, 10))
     246        self.model.z4 = Var(self.model.S, self.model.T, domain=domainRule, bounds=(-10, 10))
     247
     248        def objRule(model):
     249            return sum(5*summation(model.__getattribute__(c+n)) \
     250                       for c in ('x', 'y', 'z') for n in ('1', '2', '3', '4'))
     251           
     252        self.model.obj = Objective(rule=objRule)
     253
     254        transform = NonNegativeTransformation()
     255        instance=self.model.create()
     256        #transformed = apply_transformation("nonnegative_transform", instance)
     257        transformed = transform(instance)
     258       
     259        from coopr.opt.base import SolverFactory
     260
     261        solver = SolverFactory("glpk")
     262
     263        instance_sol = solver.solve(instance)
     264        transformed_sol = solver.solve(transformed)
     265
     266        self.failUnlessEqual(
     267            instance_sol["Solution"][0]["Objective"]["f"]["value"],
     268            transformed_sol["Solution"][0]["Objective"]["f"]["value"]
     269            )
     270                             
     271
    60272if __name__ == "__main__":
    61273   unittest.main()
    62 
  • coopr.pyomo/trunk/coopr/pyomo/transform/__init__.py

    r2807 r2860  
    99
    1010
     11# Transformation heirarchy
    1112from transformation import *
    1213from linear_transformation import *
     
    1718from nonisomorphic_transformation import *
    1819
     20# Transformations
    1921import relax_integrality
    2022import eliminate_fixed_vars
    2123#import standard_form
    22 import equality_transform
     24from equality_transform import *
     25from nonnegative_transform import *
    2326import util
  • coopr.pyomo/trunk/coopr/pyomo/transform/nonnegative_transform.py

    r2819 r2860  
    88from coopr.pyomo.base.util import multisum
    99from util import collectAbstractComponents
    10 from coopr.pyomo.util import multisum
     10from coopr.pyomo.base.util import multisum
     11from coopr.pyomo.transform import *
     12from coopr.pyomo.base.var import _VarElement, _VarBase, _VarValue
     13from coopr.pyomo.base.expr import _SumExpression, _ProductExpression, \
     14     _AbsExpression, _PowExpression
     15from coopr.pyomo.base.numvalue import create_name
    1116
    1217__all__ = ["NonNegativeTransformation"]
     
    1924    """
    2025
    21     alias("abstract_nonnegative_transform")
     26    alias("nonnegative_transform")
    2227
    2328    def __init__(self, **kwds):
    24         kwds["name"] = kwds.pop("name", "abstract_nonnegative_transform")
     29        kwds["name"] = kwds.pop("name", "nonnegative_transform")
    2530        Plugin.__init__(self, **kwds)
     31
     32        self.realSets = (Reals, PositiveReals, NonNegativeReals, NegativeReals,
     33                         NonPositiveReals, PercentFraction, RealSet)
     34        # Intentionally leave out Binary, Boolean, BinarySet, and BooleanSet;
     35        # we check for those explicitly
     36        self.discreteSets = (IntegerSet, Integers, PositiveIntegers,
     37                             NonPositiveIntegers, NegativeIntegers,
     38                             NonNegativeIntegers)
     39
    2640
    2741    def apply(self, model, **kwds):
     
    5872        neg_suffix = kwds.pop("neg_suffix", "_minus")
    5973
    60         cp = model.clone()
    61 
    62         def boundsConstraintRule(lb, ub, attr, vars, model):
    63             """
    64             Produces 'lb < x^+ - x^- < ub' style constraints. Designed to
    65             be made a closer through functools.partial, across lb, ub, attr,
    66             and vars. vars is a {varname: coefficient} dictionary. attr is the
    67             base variable name; that is, X[1] would be referenced by
    68 
    69               model.__getattr__('X')[1]
    70 
    71             and so attr='X', and 1 is a key of vars.
    72            
    73             """
    74             return (lb,
    75                     sum(c * model.__getattr__(attr)[v] \
    76                         for (v,c) in vars.items())
    77                     ub)
    78 
    79         def noConstraint(*args):
    80             return None
    81 
    82         def domainRule(domains, ndx, model):
    83             """
    84             domains should be a map from variable indices to domains. Designed
    85             to be a closure.
    86             """
    87             return domains[ndx]
    88 
    89         def sumRule(attr, vars, model)
    90             """
    91             Returns a sum expression.
    92             """
    93             return sum(c*model.__getattr__(attr)[v] for (c, v) vars.items())
    94 
    95         components = collectAbstractComponents(cp)
     74        nonneg = model.clone()
     75        components = collectAbstractComponents(nonneg)
    9676
    9777        # Map from variable base names to a {index, rule} map
    98         constraintRules = {}
     78        constraint_rules = {}
    9979
    10080        # Map from variable base names to a rule defining the domains for that
    10181        # variable
    102         domainRules = {}
     82        domain_rules = {}
    10383
    10484        # Map from variable base names to its set of indices
    105         varIndices = {}
    106 
    107         # Map from fully qualified variable names to replacement expressions
    108         modifiedVars = {}
     85        var_indices = {}
     86
     87        # Map from fully qualified variable names to replacement expressions.
     88        # For now, it is actually a map from a variable name to a closure that
     89        # must later be evaulated with a model containing the replacement
     90        # variables.
     91        var_map = {}
    10992
    11093        #
     
    11295        # variable
    11396        #
    114         for varName in components["Var"]:
    115             var = model.__getattr__(varName)
    116 
    117             # Individual bounds, domain, and indices
    118             bounds = {}
    119             domain = {}
     97        for var_name in components["Var"]:
     98            var = nonneg.__getattribute__(var_name)
     99
     100            # Individual bounds and domains
     101            orig_bounds = {}
     102            orig_domain = {}
     103
     104            # New indices
    120105            indices = set()
    121106
    122             # The new constraints. Maps constraint names to a function
    123             # that returns a bounds tuple. The function requires one
    124             # argument, the model.
    125             constaints = {}
    126 
    127             # Get the individual index bounds
     107            # Map from constraint names to a constraint rule.
     108            constraints = {}
     109
     110            # Map from variable indices to a domain
     111            domains = {}
     112
    128113            for ndx in var._index:
    129                 bounds[ndx] = (var[ndx].lb.value, var[ndx].ub.value)
    130 
    131             # Get the individual index domains
    132             for ndx in var._index:
    133                 domain[ndx] = var[ndx].domain
    134 
    135             # The constraints to apply to the model. Each key is a constraint
    136             # name, and each value is the rule defining that constraint.
    137             constraints = {}
    138 
    139             # A map from each index to its domain
    140             domains = {}
    141            
    142             for ndx in var._index:
    143                 if (bounds[ndx] != (None, None) and bounds[ndx][0] < 0) \
    144                    or (domain[ndx] in (NonPositiveReals, NegativeReals,
    145                                        NonPositiveIntegers,
    146                                        NegativeIntegers)):
    147                     # Need to convert variable
    148 
    149                     posVarSuffix = "%s%s" % (str(ndx), pos_suffix)
    150                     negVarSuffix = "%s%s" % (str(ndx), neg_suffix)
    151 
    152                     # Add new indices
    153                     indices.add(posVarSuffix)
    154                     indices.add(negVarSuffix)
    155 
    156                     posVarName = "%s[%s]" % (str(varName), posVarSuffix)
    157                     negVarName = "%s[%s]" % (str(varName), negVarSuffix)
    158 
    159                     # Add to set of modified variables
    160                     vname = "%s[%s]" % (str(varName), str(ndx))
    161                     modifiedVars[vname] = partial(sumRule,
    162                                                   varName,
    163                                                   {posVarSuffix:1,
    164                                                    negVarSuffix:-1})
    165 
    166                     # Enforce bounds as constraints
    167                     if bounds[ndx] != (None, None):
    168                         constraints["%s_%s" % (varName, "bounds")] = partial(
    169                             boundsConstraintRule,
    170                             bounds[0],
    171                             bounds[1],
    172                             varName,
    173                             {posVarSuffix:1, negVarSuffix:-1})
    174 
    175                     # Enforce bounds of domain as constraints
    176                     if var.domain is not None and var.domain.bounds in not None:
    177                         constraints["%s_%s" % (varName, "domain")] = partial(
    178                             boundsConstraintRule,
    179                             domain.bounds[0],
    180                             domain.bounds[1],
    181                             varName,
    182                             {posVarSuffix:1, negVarSuffix:-1})
    183 
    184                     # Enforce discrete or continuous variables
    185                     #
    186                     # TODO: Make this cleaner, possibly by enforcing an
    187                     # inheritance hierarching on domain sets.
    188                     if var.domain is not None:
    189                         if domain in realSets
    190                             domains[posVarSuffix] = Reals
    191                             domains[negVarSuffix] = Reals
    192                         elif domain in discreteSets
    193                             domains[posVarSuffix] = Integers
    194                             domains[negVarSuffix] = Integers
    195                         elif domain in (Binary, Boolean):
    196                             # Technically encompassed by bounds and integer
    197                             # domain, but worthy of special note since some
    198                             # formats optimize over binary variables
    199                             domains[posVarSuffix] = Binary
    200                             domains[negVarSuffix] = Binary
    201                         else:
    202                             print ("Domain '%s' not recognized, defaulting " + \
    203                                    "to 'Reals'") % (str(domain))
    204                             domains[posVarSuffix] = Reals
    205                             domains[negVarSuffix] = Reals
     114                # Fully qualified variable name
     115                vname = create_name(str(var_name), ndx)
     116
     117                # We convert each index to a string to avoid difficult issues
     118                # regarding appending a suffix to tuples.
     119                #
     120                # If the index is None, this casts the index to a string,
     121                # which doesn't match up with how Pyomo treats None indices
     122                # internally. Replace with "" to be consistent.
     123                if ndx is None:
     124                    v_ndx = ""
    206125                else:
    207                     # No conversion necessary
    208                     indices.add(ndx)
    209                     domains[ndx] = var.domain
    210 
    211             constraintRules[varName] = constraints
    212             domainRules[varName]     = partial(domainRule, domains)
    213             varIndices[varName]      = indices
    214 
    215         # The new model
    216         nonneg = Model()
     126                    v_ndx = str(ndx)
     127
     128                # Get the variable bounds
     129                lb = var[ndx].lb
     130                ub = var[ndx].ub
     131                if lb is not None:
     132                    lb = lb.value
     133                if ub is not None:
     134                    ub = ub.value
     135                orig_bounds[ndx] = (lb, ub)
     136
     137                # Get the variable domain
     138                if var[ndx].domain is not None:
     139                    orig_domain[ndx] = var[ndx].domain
     140                else:
     141                    orig_domain[ndx] = var.domain
     142
     143                # Determine the replacement expression. Either a new single
     144                # variable with the same attributes, or a sum of two new
     145                # variables.
     146                #
     147                # If both the bounds and domain allow for negative values,
     148                # replace the variable with the sum of nonnegative ones.
     149
     150                bounds_neg = (orig_bounds[ndx] == (None, None) or
     151                              orig_bounds[ndx][0] < 0)
     152                domain_neg = (orig_domain[ndx] is None or
     153                              orig_domain[ndx].bounds()[0] < 0)
     154                if bounds_neg and domain_neg:
     155
     156                    # Make two new variables.
     157                    posVarSuffix = "%s%s" % (v_ndx, pos_suffix)
     158                    negVarSuffix = "%s%s" % (v_ndx, neg_suffix)
     159
     160                    new_indices = (posVarSuffix, negVarSuffix)
     161
     162                    # Replace the original variable with a sum expression
     163                    expr_dict = {posVarSuffix: 1, negVarSuffix: -1}
     164                else:
     165                    # Add the new index
     166                    new_indices = (v_ndx,)
     167
     168                    # Replace the original variable with a sum expression
     169                    expr_dict = {v_ndx: 1}
     170
     171                # Add the new indices
     172                for x in new_indices:
     173                    indices.add(x)
     174
     175                # Replace the original variable with an expression
     176                var_map[vname] = self._partial(self.sumRule,
     177                                          var_name,
     178                                          expr_dict)
     179
     180                # Enforce bounds as constraints
     181                if orig_bounds[ndx] != (None, None):
     182                    cname = "%s_%s" % (vname, "bounds")
     183                    tmp = orig_bounds[ndx]
     184                    constraints[cname] = self._partial(
     185                        self.boundsConstraintRule,
     186                        tmp[0],
     187                        tmp[1],
     188                        var_name,
     189                        expr_dict)
     190
     191                # Enforce the bounds of the domain as constraints
     192                if orig_domain[ndx] != None:
     193                    cname = "%s_%s" % (vname, "domain_bounds")
     194                    tmp = orig_domain[ndx].bounds()
     195                    constraints[cname] = self._partial(
     196                        self.boundsConstraintRule,
     197                        tmp[0],
     198                        tmp[1],
     199                        var_name,
     200                        expr_dict)
     201
     202                # Domain will either be NonNegativeReals, NonNegativeIntegers,
     203                # or Binary. We consider Binary because some solvers may
     204                # optimize over binary variables.
     205                if isinstance(orig_domain[ndx], RealSet):
     206                    for x in new_indices:
     207                        domains[x] = NonNegativeReals
     208                elif isinstance(orig_domain[ndx], IntegerSet):
     209                    for x in new_indices:
     210                        domains[x] = NonNegativeIntegers
     211                elif isinstance(orig_domain[ndx], BooleanSet):
     212                    for x in new_indices:
     213                        domains[x] = Binary
     214                else:
     215                    print ("Warning: domain '%s' not recognized, " + \
     216                           "defaulting to 'Reals'") % (str(var.domain))
     217                    for x in new_indices:
     218                        domains[x] = Reals
     219
     220            constraint_rules[var_name] = constraints
     221            domain_rules[var_name] = self._partial(self.exprMapRule, domains)
     222            var_indices[var_name] = indices
     223
     224        # Remove all existing variables.
     225        toRemove = []
     226        for (attr_name, attr) in nonneg.__dict__.items():
     227            if isinstance(attr, _VarBase):
     228                toRemove.append(attr_name)
     229        for attr_name in toRemove:
     230            nonneg.__delattr__(attr_name)
    217231
    218232        # Add the sets defining the variables, then the variables
    219         for (k, v) in varIndices.items():
     233        for (k, v) in var_indices.items():
    220234            sname = "%s_indices" % k
    221235            nonneg.__setattr__(sname, Set(initialize=v))
    222             nonneg.__setattr__(k, Var(nonneg.__getattr__(sname),
    223                                       domain = domainRules[k]))
     236            nonneg.__setattr__(k, Var(nonneg.__getattribute__(sname),
     237                                      domain = domain_rules[k],
     238                                      bounds = (0, None)))
     239
     240        # Construct the model to get the variables and their indices
     241        # recognized in the model
     242        nonneg = nonneg.create()
     243
     244        # Safe to evaluate the modifiedVars mapping
     245        for var in var_map:
     246            var_map[var] = var_map[var](nonneg)
    224247
    225248        # Map from constraint base names to maps from indices to expressions
    226249        constraintExprs = {}
    227        
     250
    228251        #
    229252        # Convert all modified variables in all constraints in the original
     
    231254        #
    232255        for conName in components["Constraint"]:
    233             con = model.__getattr__(conName)
     256            con = nonneg.__getattribute__(conName)
    234257
    235258            # Map from constraint indices to a corrected expression
     
    237260
    238261            for (ndx, cdata) in con._data.items():
    239                 lower = _walk_expr(cdata.lower, modifiedVars)
    240                 body  = _walk_expr(cdata.body,  modifiedVars)
    241                 upper = _walk_expr(cdata.upper, modifiedVars)
     262                lower = self._walk_expr(cdata.lower, var_map)
     263                body  = self._walk_expr(cdata.body,  var_map)
     264                upper = self._walk_expr(cdata.upper, var_map)
    242265                exprMap[ndx] = (lower, body, upper)
    243266
     
    252275        # problem
    253276        #
    254         for objName in components["Constraint"]:
    255             obj = model.__getattr__(objName)
    256 
    257             # Map from constraint indices to a corrected expression
     277        for objName in components["Objective"]:
     278            obj = nonneg.__getattribute__(objName)
     279
     280            # Map from objective indices to a corrected expression
    258281            exprMap = {}
    259282
    260283            for (ndx, odata) in obj._data.items():
    261                 exprMap[ndx] = _walk_expr(odata.expr, modifiedVars)
     284                exprMap[ndx] = self._walk_expr(odata.expr, var_map)
    262285
    263286            # Add to list of expression maps
    264             objectiveExprs[conName] = exprMap
    265 
    266         # Make the constraints and objectives
    267 
    268            
    269 
    270     @staticmethod
    271     def _walk_expr(self, expr, varMap):
     287            objectiveExprs[objName] = exprMap
     288
     289
     290        # Make the modified original constraints
     291        for (conName, ruleMap) in constraintExprs.items():
     292            # Make the set of indices
     293            sname = conName + "_indices"
     294            nonneg.__setattr__(sname, Set(initialize=ruleMap.keys()))
     295
     296            # Define the constraint
     297            nonneg.__setattr__(conName,
     298                               Constraint(nonneg.__getattribute__(sname),
     299                                          rule=self._partial(exprMapRule, ruleMap)))
     300
     301        # Make the bounds constraints
     302        for (varName, ruleMap) in constraint_rules.items():
     303            conName = varName + "_constraints"
     304            # Make the set of indices
     305            sname = conName + "_indices"
     306            nonneg.__setattr__(sname, Set(initialize=ruleMap.keys()))
     307
     308            # Define the constraint
     309            nonneg.__setattr__(
     310                conName,
     311                Constraint(nonneg.__getattribute__(sname),
     312                           rule=self._partial(self.delayedExprMapRule,
     313                                              ruleMap)))
     314
     315        # Make the objectives
     316        for (objName, ruleMap) in objectiveExprs.items():
     317            # Make the set of indices
     318            sname = objName + "_indices"
     319            nonneg.__setattr__(sname, Set(initialize=ruleMap.keys()))
     320
     321            # Define the constraint
     322            nonneg.__setattr__(objName,
     323                               Objective(nonneg.__getattribute__(sname),
     324                                         rule=self._partial(self.exprMapRule, ruleMap)))
     325
     326        return nonneg.create()
     327
     328
     329    @staticmethod
     330    def _walk_expr(expr, varMap):
    272331        """
    273332        Walks an expression tree, making the replacements defined in varMap
    274333        """
    275334
    276         # Attempt to replace a variable
     335        # Attempt to replace a singleton variable
    277336        if isinstance(expr, _VarElement):
    278337            if expr.name in varMap:
     
    281340                return expr
    282341
     342        # Attempt to replace an indexed variable
     343        if isinstance(expr, _VarValue):
     344            if expr.name in varMap:
     345                return varMap[expr.name]
     346            else:
     347                return expr
     348
    283349        # Iterate through the numerator and denominator of a product term
    284350        if isinstance(expr, _ProductExpression):
    285351            i = 0
    286352            while i < len(expr._numerator):
    287                 expr._numerator[i] = _walk_expr(expr._numerator[i], varmap)
     353                expr._numerator[i] = NonNegativeTransformation._walk_expr(
     354                    expr._numerator[i],
     355                    varMap)
    288356                i += 1
    289357
    290358            i = 0
    291359            while i < len(expr._denominator):
    292                 expr._denominator[i] = _walk_expr(expr.denominator[i], varmap)
     360                expr._denominator[i] = NonNegativeTransformation._walk_expr(
     361                    expr.denominator[i],
     362                    varMap)
    293363                i += 1
    294364
     
    298368            i = 0
    299369            while i < len(expr._args):
    300                 expr._args[i] = _walk_expr(expr._args[i], varMap)
     370                expr._args[i] = NonNegativeTransformation._walk_expr(
     371                    expr._args[i],
     372                    varMap)
    301373                i += 1
    302374
     
    304376
    305377        return expr
     378
     379    @staticmethod
     380    def boundsConstraintRule(lb, ub, attr, vars, model):
     381        """
     382        Produces 'lb < x^+ - x^- < ub' style constraints. Designed to
     383        be made a closer through functools.partial, across lb, ub, attr,
     384        and vars. vars is a {varname: coefficient} dictionary. attr is the
     385        base variable name; that is, X[1] would be referenced by
     386
     387          model.__getattribute__('X')[1]
     388
     389        and so attr='X', and 1 is a key of vars.
     390
     391        """
     392        return (lb,
     393                sum(c * model.__getattribute__(attr)[v] \
     394                    for (v,c) in vars.items()),
     395                ub)
     396
     397    @staticmethod
     398    def noConstraint(*args):
     399        return None
     400
     401    @staticmethod
     402    def sumRule(attr, vars, model):
     403        """
     404        Returns a sum expression.
     405        """
     406        return sum(c*model.__getattribute__(attr)[v] for (v, c) in vars.items())
     407
     408    @staticmethod
     409    def exprMapRule(ruleMap, ndx, model):
     410        """ Rule intended to return expressions from a lookup table """
     411        return ruleMap[ndx]
     412
     413    @staticmethod
     414    def delayedExprMapRule(ruleMap, ndx, model):
     415        """
     416        Rule intended to return expressions from a lookup table. Each entry
     417        in the lookup table is a functor that needs to be evaluated before
     418        returning.
     419        """
     420        return ruleMap[ndx](model)
     421
     422    @staticmethod
     423    def _partial(*args, **kwargs):
     424        """
     425        copy.deepcopy balks at copying anonymous functions. This overrides
     426        the default behavior of functools.partial to make deepcopy return
     427        the function itself, rather than attempting to copy it.
     428        """
     429        func = partial(*args, **kwargs)
     430
     431        def _partial_deepcopy(memo={}):
     432            return func
     433
     434        func.__deepcopy__ = _partial_deepcopy
     435        return func
Note: See TracChangeset for help on using the changeset viewer.