def test_none(self): val = None try: as_numeric(val) self.fail("Expected ValueError") except: pass
def test_string(self): val = 'foo' try: as_numeric(val) self.fail("Expected ValueError") except: pass
def set_value(self, cc): """ Add a complementarity condition with a specified index. """ if cc.__class__ is ComplementarityTuple: # # The ComplementarityTuple has a fixed length, so we initialize # the _args component and return # self._args = (as_numeric(cc.arg0), as_numeric(cc.arg1)) # elif cc.__class__ is tuple: if len(cc) != 2: raise ValueError( "Invalid tuple for Complementarity %s (expected 2-tuple):" "\n\t%s" % (self.name, cc)) self._args = tuple(as_numeric(x) for x in cc) elif cc is Complementarity.Skip: del self.parent_component()[self.index()] elif cc.__class__ is list: # # Call set_value() recursively to apply the error same error # checks. # return self.set_value(tuple(cc)) else: raise ValueError("Unexpected value for Complementarity %s:\n\t%s" % (self.name, cc))
def test_error1(self): class A(object): pass val = A() try: as_numeric(val) self.fail("Expected TypeError") except TypeError: pass
def test_bool(self): val = False try: as_numeric(val) self.fail("Expected ValueError") except: pass val = True try: as_numeric(val) self.fail("Expected ValueError") except: pass
def test_unknownType(self): ref = MyBogusType(42) try: val = as_numeric(ref) self.fail("Expected TypeError") except TypeError: pass
def test_unknownNumericType(self): ref = MyBogusNumericType(42) val = as_numeric(ref) self.assertEqual(val().val, 42.0) #self.assertEqual(val().val, 42) from pyomo.core.base.numvalue import native_numeric_types, native_types self.assertIn(MyBogusNumericType, native_numeric_types) self.assertIn(MyBogusNumericType, native_types) native_numeric_types.remove(MyBogusNumericType) native_types.remove(MyBogusNumericType)
def body(self): """Access the body of a constraint expression.""" if self._body is not None: body = self._body else: # The incoming RangedInequality had a potentially variable # bound. The "body" is fine, but the bounds may not be # (although the responsibility for those checks lies with the # lower/upper properties) body = self._expr.arg(1) return as_numeric(body)
def ub(self, ub): if self.equality: raise ValueError( "The ub property can not be set " "when the equality property is True.") if ub is not None: tmp = as_numeric(ub) if tmp.is_potentially_variable(): raise ValueError( "Constraint lower bounds must be " "expressions restricted to data.") self._ub = ub
def upper(self): """Access the upper bound of a constraint expression.""" bound = self._ub() # Historically, constraint.upper was guaranteed to return a type # derived from Pyomo NumericValue (or None). Replicate that # functionality, although clients should in almost all cases # move to using ConstraintData.ub instead of accessing # lower/body/upper to avoid the unnecessary creation (and # inevitable destruction) of the NumericConstant wrappers. if bound is None: return None return as_numeric(bound)
def add(self, index, cc): """ Add a complementarity condition with a specified index. """ if cc.__class__ is ComplementarityTuple: # # The ComplementarityTuple has a fixed length, so we initialize # the _args component and return # self[index]._args = ( as_numeric(cc.arg0), as_numeric(cc.arg1) ) return self[index] # if cc.__class__ is tuple: if cc is Complementarity.Skip: return elif len(cc) != 2: raise ValueError( "Invalid tuple for Complementarity %s (expected 2-tuple):" "\n\t%s" % (self.name, cc) ) elif cc.__class__ is list: # # Call add() recursively to apply the error same error # checks. # return self.add(index, tuple(cc)) elif cc is None: raise ValueError(""" Invalid complementarity condition. The complementarity condition is None instead of a 2-tuple. Please modify your rule to return Complementarity.Skip instead of None. Error thrown for Complementarity "%s" """ % ( self.name, ) ) else: raise ValueError( "Unexpected argument declaring Complementarity %s:\n\t%s" % (self.name, cc) ) # self[index]._args = tuple( as_numeric(x) for x in cc ) return self[index]
def add(self, index, cc): """ Add a complementarity condition with a specified index. """ if cc.__class__ is ComplementarityTuple: # # The ComplementarityTuple has a fixed length, so we initialize # the _args component and return # self[index]._args = (as_numeric(cc.arg0), as_numeric(cc.arg1)) return self[index] # if cc.__class__ is tuple: if cc is Complementarity.Skip: return elif len(cc) != 2: raise ValueError( "Invalid tuple for Complementarity %s (expected 2-tuple):" "\n\t%s" % (self.name, cc)) elif cc.__class__ is list: # # Call add() recursively to apply the error same error # checks. # return self.add(index, tuple(cc)) elif cc is None: raise ValueError(""" Invalid complementarity condition. The complementarity condition is None instead of a 2-tuple. Please modify your rule to return Complementarity.Skip instead of None. Error thrown for Complementarity "%s" """ % (self.name, )) else: raise ValueError( "Unexpected argument declaring Complementarity %s:\n\t%s" % (self.name, cc)) # self[index]._args = tuple(as_numeric(x) for x in cc) return self[index]
def substitute_template_with_value(expr): """A simple substituter to expand expression for current template This substituter will replace all _GetItemExpression / IndexTemplate nodes with the actual _ComponentData based on the current value of the IndexTemplate(s) """ if type(expr) is IndexTemplate: return as_numeric(expr()) else: return resolve_template(expr)
def _fix_vars(self, expr, model): """ Walk through the S-expression, fixing variables. """ # TODO - Change this to use a visitor pattern! if expr._args is None: return expr _args = [] for i in range(len(expr._args)): if isinstance(expr._args[i],ExpressionBase): _args.append( self._fix_vars(expr._args[i], model) ) elif (isinstance(expr._args[i],Var) or isinstance(expr._args[i],_VarData)) and expr._args[i].fixed: if expr._args[i].value != 0.0: _args.append( as_numeric(expr._args[i].value) ) else: _args.append( expr._args[i] ) expr._args = _args return expr
def rhs(self, rhs): if rhs is None: # None has a different meaning depending on the # context (lb or ub), so there is no way to # interpret this raise ValueError( "Constraint right-hand side can not " "be assigned a value of None.") else: tmp = as_numeric(rhs) if tmp.is_potentially_variable(): raise ValueError( "Constraint right-hand side must be " "expressions restricted to data.") self._lb = rhs self._ub = rhs self._equality = True
def test_float(self): val = 1.1 nval = as_numeric(val) self.assertEqual(val, nval) self.assertEqual(nval / 2, 0.55)
def test_long(self): val = long(1e10) nval = as_numeric(val) self.assertEqual(1.0e10, nval) #self.assertEqual(val, as_numeric(val)) self.assertEqual(nval/2, 5.0e9)
def test_const1(self): val = NumericConstant(1.0) self.assertEqual(1.0, as_numeric(val))
def expr(self, expr): self._equality = False if expr is None: self.body = None self.lb = None self.ub = None return _expr_type = expr.__class__ if _expr_type is tuple: # # Form equality expression # if len(expr) == 2: arg0 = expr[0] arg1 = expr[1] # assigning to the rhs property # will set the equality flag to True if not is_potentially_variable(arg1): self.rhs = arg1 self.body = arg0 elif not is_potentially_variable(arg0): self.rhs = arg0 self.body = arg1 else: self.rhs = ZeroConstant self.body = arg0 self.body -= arg1 # # Form inequality expression # elif len(expr) == 3: arg0 = expr[0] if arg0 is not None: if not is_numeric_data(arg0): raise ValueError( "Constraint '%s' found a 3-tuple (lower," " expression, upper) but the lower " "value was not numeric data or an " "expression restricted to storage of " "numeric data." % (self.name)) arg1 = expr[1] if arg1 is not None: arg1 = as_numeric(arg1) arg2 = expr[2] if arg2 is not None: if not is_numeric_data(arg2): raise ValueError( "Constraint '%s' found a 3-tuple (lower," " expression, upper) but the upper " "value was not numeric data or an " "expression restricted to storage of " "numeric data." % (self.name)) self.lb = arg0 self.body = arg1 self.ub = arg2 else: raise ValueError( "Constraint '%s' assigned a tuple " "of length %d. Expecting a tuple of " "length 2 or 3:\n" "Equality: (body, rhs)\n" "Inequality: (lb, body, ub)" % (self.name, len(expr))) relational_expr = False else: try: relational_expr = expr.is_relational() if not relational_expr: raise ValueError( "Constraint '%s' does not have a proper " "value. Found '%s'\nExpecting a tuple or " "equation. Examples:" "\n sum_product(model.costs) == model.income" "\n (0, model.price[item], 50)" % (self.name, str(expr))) except AttributeError: msg = ("Constraint '%s' does not have a proper " "value. Found '%s'\nExpecting a tuple or " "equation. Examples:" "\n sum_product(model.costs) == model.income" "\n (0, model.price[item], 50)" % (self.name, str(expr))) if type(expr) is bool: msg += ("\nNote: constant Boolean expressions " "are not valid constraint expressions. " "Some apparently non-constant compound " "inequalities (e.g. 'expr >= 0 <= 1') " "can return boolean values; the proper " "form for compound inequalities is " "always 'lb <= expr <= ub'.") raise ValueError(msg) # # Special check for chainedInequality errors like "if var < # 1:" within rules. Catching them here allows us to provide # the user with better (and more immediate) debugging # information. We don't want to check earlier because we # want to provide a specific debugging message if the # construction rule returned True/False; for example, if the # user did ( var < 1 > 0 ) (which also results in a non-None # chainedInequality value) # if logical_expr._using_chained_inequality and \ (logical_expr._chainedInequality.prev is not None): raise TypeError(logical_expr._chainedInequality.error_message()) # # Process relational expressions # (i.e. explicit '==', '<', and '<=') # if relational_expr: if _expr_type is logical_expr.EqualityExpression: # assigning to the rhs property # will set the equality flag to True if not is_potentially_variable(expr.arg(1)): self.rhs = expr.arg(1) self.body = expr.arg(0) elif not is_potentially_variable(expr.arg(0)): self.rhs = expr.arg(0) self.body = expr.arg(1) else: self.rhs = ZeroConstant self.body = expr.arg(0) self.body -= expr.arg(1) elif _expr_type is logical_expr.InequalityExpression: if expr._strict: raise ValueError( "Constraint '%s' encountered a strict " "inequality expression ('>' or '<'). All" " constraints must be formulated using " "using '<=', '>=', or '=='." % (self.name)) if not is_potentially_variable(expr.arg(1)): self.lb = None self.body = expr.arg(0) self.ub = expr.arg(1) elif not is_potentially_variable(expr.arg(0)): self.lb = expr.arg(0) self.body = expr.arg(1) self.ub = None else: self.lb = None self.body = expr.arg(0) self.body -= expr.arg(1) self.ub = ZeroConstant else: # RangedExpression if any(expr._strict): raise ValueError( "Constraint '%s' encountered a strict " "inequality expression ('>' or '<'). All" " constraints must be formulated using " "using '<=', '>=', or '=='." % (self.name)) if not is_numeric_data(expr.arg(0)): raise ValueError( "Constraint '%s' found a double-sided " "inequality expression (lower <= " "expression <= upper) but the lower " "bound was not numeric data or an " "expression restricted to storage of " "numeric data." % (self.name)) if not is_numeric_data(expr.arg(2)): raise ValueError( "Constraint '%s' found a double-sided "\ "inequality expression (lower <= " "expression <= upper) but the upper " "bound was not numeric data or an " "expression restricted to storage of " "numeric data." % (self.name)) self.lb = expr.arg(0) self.body = expr.arg(1) self.ub = expr.arg(2) # # Error check, to ensure that we don't have an equality # constraint with 'infinite' RHS # assert not (self.equality and (self.lb is None)) assert (not self.equality) or (self.lb is self.ub)
def expr(self, expr): self._equality = False if expr is None: self.body = None self.lb = None self.ub = None return _expr_type = expr.__class__ if _expr_type is tuple: # # Form equality expression # if len(expr) == 2: arg0 = expr[0] arg1 = expr[1] # assigning to the rhs property # will set the equality flag to True if not is_potentially_variable(arg1): self.rhs = arg1 self.body = arg0 elif not is_potentially_variable(arg0): self.rhs = arg0 self.body = arg1 else: self.rhs = ZeroConstant self.body = arg0 self.body -= arg1 # # Form inequality expression # elif len(expr) == 3: arg0 = expr[0] if arg0 is not None: if not is_numeric_data(arg0): raise ValueError( "Constraint '%s' found a 3-tuple (lower," " expression, upper) but the lower " "value was not numeric data or an " "expression restricted to storage of " "numeric data." % (self.name)) arg1 = expr[1] if arg1 is not None: arg1 = as_numeric(arg1) arg2 = expr[2] if arg2 is not None: if not is_numeric_data(arg2): raise ValueError( "Constraint '%s' found a 3-tuple (lower," " expression, upper) but the upper " "value was not numeric data or an " "expression restricted to storage of " "numeric data." % (self.name)) elif arg1 is not None and is_numeric_data(arg1): # Special case (reflect behavior of AML): if the # upper bound is None and the "body" is only data, # then shift the body to the UB and the LB to the # body arg0, arg1, arg2 = arg2, arg0, arg1 self.lb = arg0 self.body = arg1 self.ub = arg2 else: raise ValueError("Constraint '%s' assigned a tuple " "of length %d. Expecting a tuple of " "length 2 or 3:\n" "Equality: (body, rhs)\n" "Inequality: (lb, body, ub)" % (self.name, len(expr))) relational_expr = False else: try: relational_expr = expr.is_relational() if not relational_expr: raise ValueError( "Constraint '%s' does not have a proper " "value. Found '%s'\nExpecting a tuple or " "equation. Examples:" "\n sum_product(model.costs) == model.income" "\n (0, model.price[item], 50)" % (self.name, str(expr))) except AttributeError: msg = ("Constraint '%s' does not have a proper " "value. Found '%s'\nExpecting a tuple or " "equation. Examples:" "\n sum_product(model.costs) == model.income" "\n (0, model.price[item], 50)" % (self.name, str(expr))) if type(expr) is bool: msg += ("\nNote: constant Boolean expressions " "are not valid constraint expressions. " "Some apparently non-constant compound " "inequalities (e.g. 'expr >= 0 <= 1') " "can return boolean values; the proper " "form for compound inequalities is " "always 'lb <= expr <= ub'.") raise ValueError(msg) # # Process relational expressions # (i.e. explicit '==', '<', and '<=') # if relational_expr: if _expr_type is logical_expr.EqualityExpression: # assigning to the rhs property # will set the equality flag to True if not is_potentially_variable(expr.arg(1)): self.rhs = expr.arg(1) self.body = expr.arg(0) elif not is_potentially_variable(expr.arg(0)): self.rhs = expr.arg(0) self.body = expr.arg(1) else: self.rhs = ZeroConstant self.body = expr.arg(0) self.body -= expr.arg(1) elif _expr_type is logical_expr.InequalityExpression: if expr._strict: raise ValueError("Constraint '%s' encountered a strict " "inequality expression ('>' or '<'). All" " constraints must be formulated using " "using '<=', '>=', or '=='." % (self.name)) if not is_potentially_variable(expr.arg(1)): self.lb = None self.body = expr.arg(0) self.ub = expr.arg(1) elif not is_potentially_variable(expr.arg(0)): self.lb = expr.arg(0) self.body = expr.arg(1) self.ub = None else: self.lb = None self.body = expr.arg(0) self.body -= expr.arg(1) self.ub = ZeroConstant else: # RangedExpression if any(expr._strict): raise ValueError("Constraint '%s' encountered a strict " "inequality expression ('>' or '<'). All" " constraints must be formulated using " "using '<=', '>=', or '=='." % (self.name)) if not is_numeric_data(expr.arg(0)): raise ValueError("Constraint '%s' found a double-sided " "inequality expression (lower <= " "expression <= upper) but the lower " "bound was not numeric data or an " "expression restricted to storage of " "numeric data." % (self.name)) if not is_numeric_data(expr.arg(2)): raise ValueError( "Constraint '%s' found a double-sided "\ "inequality expression (lower <= " "expression <= upper) but the upper " "bound was not numeric data or an " "expression restricted to storage of " "numeric data." % (self.name)) self.lb = expr.arg(0) self.body = expr.arg(1) self.ub = expr.arg(2) # # Error check, to ensure that we don't have an equality # constraint with 'infinite' RHS # assert not (self.equality and (self.lb is None)) assert (not self.equality) or (self.lb is self.ub)
def test_float(self): val = 1.1 nval = as_numeric(val) self.assertEqual(val, nval) self.assertEqual(nval/2, 0.55)
def test_int(self): val = 1 nval = as_numeric(val) self.assertEqual(1.0, nval) #self.assertEqual(val, nval) self.assertEqual(nval/2, 0.5)
def _write_model(self, model, output_file, solver_capability, var_list, var_label, symbolMap, con_labeler, sort, skip_trivial_constraints, warmstart, solver, mtype, add_options, put_results): constraint_names = [] ConstraintIO = StringIO() linear = True linear_degree = set([0,1]) # Make sure there are no strange ActiveComponents. The expression # walker will handle strange things in constraints later. model_ctypes = model.collect_ctypes(active=True) invalids = set() for t in (model_ctypes - valid_active_ctypes_minlp): if issubclass(t, ActiveComponent): invalids.add(t) if len(invalids): invalids = [t.__name__ for t in invalids] raise RuntimeError( "Unallowable active component(s) %s.\nThe GAMS writer cannot " "export models with this component type." % ", ".join(invalids)) tc = StorageTreeChecker(model) # Walk through the model and generate the constraint definition # for all active constraints. Any Vars / Expressions that are # encountered will be added to the var_list due to the labeler # defined above. for con in model.component_data_objects(Constraint, active=True, sort=sort): if not con.has_lb() and not con.has_ub(): assert not con.equality continue # non-binding, so skip con_body = as_numeric(con.body) if skip_trivial_constraints and con_body.is_fixed(): continue if linear: if con_body.polynomial_degree() not in linear_degree: linear = False cName = symbolMap.getSymbol(con, con_labeler) if con.equality: constraint_names.append('%s' % cName) ConstraintIO.write('%s.. %s =e= %s ;\n' % ( constraint_names[-1], expression_to_string(con_body, tc, smap=symbolMap), _get_bound(con.upper) )) else: if con.has_lb(): constraint_names.append('%s_lo' % cName) ConstraintIO.write('%s.. %s =l= %s ;\n' % ( constraint_names[-1], _get_bound(con.lower), expression_to_string(con_body, tc, smap=symbolMap) )) if con.has_ub(): constraint_names.append('%s_hi' % cName) ConstraintIO.write('%s.. %s =l= %s ;\n' % ( constraint_names[-1], expression_to_string(con_body, tc, smap=symbolMap), _get_bound(con.upper) )) obj = list(model.component_data_objects(Objective, active=True, sort=sort)) if len(obj) != 1: raise RuntimeError( "GAMS writer requires exactly one active objective (found %s)" % (len(obj))) obj = obj[0] if linear: if obj.expr.polynomial_degree() not in linear_degree: linear = False oName = symbolMap.getSymbol(obj, con_labeler) constraint_names.append(oName) ConstraintIO.write('%s.. GAMS_OBJECTIVE =e= %s ;\n' % ( oName, expression_to_string(obj.expr, tc, smap=symbolMap) )) # Categorize the variables that we found categorized_vars = Categorizer(var_list, symbolMap) # Write the GAMS model # $offdigit ignores extra precise digits instead of erroring output_file.write("$offdigit\n\n") output_file.write("EQUATIONS\n\t") output_file.write("\n\t".join(constraint_names)) if categorized_vars.binary: output_file.write(";\n\nBINARY VARIABLES\n\t") output_file.write("\n\t".join(categorized_vars.binary)) if categorized_vars.ints: output_file.write(";\n\nINTEGER VARIABLES") output_file.write("\n\t") output_file.write("\n\t".join(categorized_vars.ints)) if categorized_vars.positive: output_file.write(";\n\nPOSITIVE VARIABLES\n\t") output_file.write("\n\t".join(categorized_vars.positive)) output_file.write(";\n\nVARIABLES\n\tGAMS_OBJECTIVE\n\t") output_file.write("\n\t".join(categorized_vars.reals)) output_file.write(";\n\n") for line in ConstraintIO.getvalue().splitlines(): if len(line) > 80000: line = split_long_line(line) output_file.write(line + "\n") output_file.write("\n") warn_int_bounds = False for category, var_name in categorized_vars: var = symbolMap.getObject(var_name) tc(var) if category == 'positive': if var.has_ub(): output_file.write("%s.up = %s;\n" % (var_name, _get_bound(var.ub))) elif category == 'ints': if not var.has_lb(): warn_int_bounds = True # GAMS doesn't allow -INF lower bound for ints logger.warning("Lower bound for integer variable %s set " "to -1.0E+100." % var.name) output_file.write("%s.lo = -1.0E+100;\n" % (var_name)) elif value(var.lb) != 0: output_file.write("%s.lo = %s;\n" % (var_name, _get_bound(var.lb))) if not var.has_ub(): warn_int_bounds = True # GAMS has an option value called IntVarUp that is the # default upper integer bound, which it applies if the # integer's upper bound is INF. This option maxes out at # 2147483647, so we can go higher by setting the bound. logger.warning("Upper bound for integer variable %s set " "to +1.0E+100." % var.name) output_file.write("%s.up = +1.0E+100;\n" % (var_name)) else: output_file.write("%s.up = %s;\n" % (var_name, _get_bound(var.ub))) elif category == 'binary': if var.has_lb() and value(var.lb) != 0: output_file.write("%s.lo = %s;\n" % (var_name, _get_bound(var.lb))) if var.has_ub() and value(var.ub) != 1: output_file.write("%s.up = %s;\n" % (var_name, _get_bound(var.ub))) elif category == 'reals': if var.has_lb(): output_file.write("%s.lo = %s;\n" % (var_name, _get_bound(var.lb))) if var.has_ub(): output_file.write("%s.up = %s;\n" % (var_name, _get_bound(var.ub))) else: raise KeyError('Category %s not supported' % category) if warmstart and var.value is not None: output_file.write("%s.l = %s;\n" % (var_name, var.value)) if warn_int_bounds: logger.warning( "GAMS requires finite bounds for integer variables. 1.0E100 " "is as extreme as GAMS will define, and should be enough to " "appear unbounded. If the solver cannot handle this bound, " "explicitly set a smaller bound on the pyomo model, or try a " "different GAMS solver.") model_name = "GAMS_MODEL" output_file.write("\nMODEL %s /all/ ;\n" % model_name) if mtype is None: mtype = ('lp','nlp','mip','minlp')[ (0 if linear else 1) + (2 if (categorized_vars.binary or categorized_vars.ints) else 0)] if solver is not None: if mtype.upper() not in valid_solvers[solver.upper()]: raise ValueError("GAMS writer passed solver (%s) " "unsuitable for model type (%s)" % (solver, mtype)) output_file.write("option %s=%s;\n" % (mtype, solver)) if add_options is not None: output_file.write("\n* START USER ADDITIONAL OPTIONS\n") for line in add_options: output_file.write('\n' + line) output_file.write("\n\n* END USER ADDITIONAL OPTIONS\n\n") output_file.write( "SOLVE %s USING %s %simizing GAMS_OBJECTIVE;\n\n" % ( model_name, mtype, 'min' if obj.sense == minimize else 'max')) # Set variables to store certain statuses and attributes stat_vars = ['MODELSTAT', 'SOLVESTAT', 'OBJEST', 'OBJVAL', 'NUMVAR', 'NUMEQU', 'NUMDVAR', 'NUMNZ', 'ETSOLVE'] output_file.write("Scalars MODELSTAT 'model status', " "SOLVESTAT 'solve status';\n") output_file.write("MODELSTAT = %s.modelstat;\n" % model_name) output_file.write("SOLVESTAT = %s.solvestat;\n\n" % model_name) output_file.write("Scalar OBJEST 'best objective', " "OBJVAL 'objective value';\n") output_file.write("OBJEST = %s.objest;\n" % model_name) output_file.write("OBJVAL = %s.objval;\n\n" % model_name) output_file.write("Scalar NUMVAR 'number of variables';\n") output_file.write("NUMVAR = %s.numvar\n\n" % model_name) output_file.write("Scalar NUMEQU 'number of equations';\n") output_file.write("NUMEQU = %s.numequ\n\n" % model_name) output_file.write("Scalar NUMDVAR 'number of discrete variables';\n") output_file.write("NUMDVAR = %s.numdvar\n\n" % model_name) output_file.write("Scalar NUMNZ 'number of nonzeros';\n") output_file.write("NUMNZ = %s.numnz\n\n" % model_name) output_file.write("Scalar ETSOLVE 'time to execute solve statement';\n") output_file.write("ETSOLVE = %s.etsolve\n\n" % model_name) if put_results is not None: results = put_results + '.dat' output_file.write("\nfile results /'%s'/;" % results) output_file.write("\nresults.nd=15;") output_file.write("\nresults.nw=21;") output_file.write("\nput results;") output_file.write("\nput 'SYMBOL : LEVEL : MARGINAL' /;") for var in var_list: output_file.write("\nput %s %s.l %s.m /;" % (var, var, var)) for con in constraint_names: output_file.write("\nput %s %s.l %s.m /;" % (con, con, con)) output_file.write("\nput GAMS_OBJECTIVE GAMS_OBJECTIVE.l " "GAMS_OBJECTIVE.m;\n") statresults = put_results + 'stat.dat' output_file.write("\nfile statresults /'%s'/;" % statresults) output_file.write("\nstatresults.nd=15;") output_file.write("\nstatresults.nw=21;") output_file.write("\nput statresults;") output_file.write("\nput 'SYMBOL : VALUE' /;") for stat in stat_vars: output_file.write("\nput '%s' %s /;\n" % (stat, stat))
def set_value(self, expr): """Set the expression on this constraint.""" _expr_type = expr.__class__ if hasattr(expr, 'is_relational'): relational_expr = expr.is_relational() if not relational_expr: raise ValueError( "Constraint '%s' does not have a proper " "value. Found '%s'\nExpecting a tuple or " "equation. Examples:" "\n sum(model.costs) == model.income" "\n (0, model.price[item], 50)" % (self.name, str(expr))) elif _expr_type is tuple: # or expr_type is list: # # Form equality expression # if len(expr) == 2: arg0 = expr[0] if arg0 is not None: arg0 = as_numeric(arg0) arg1 = expr[1] if arg1 is not None: arg1 = as_numeric(arg1) self._equality = True if arg1 is None or (not arg1.is_potentially_variable()): self._lower = self._upper = arg1 self._body = arg0 elif arg0 is None or (not arg0.is_potentially_variable()): self._lower = self._upper = arg0 self._body = arg1 else: self._lower = self._upper = ZeroConstant self._body = arg0 - arg1 # # Form inequality expression # elif len(expr) == 3: arg0 = expr[0] if arg0 is not None: arg0 = as_numeric(arg0) if arg0.is_potentially_variable(): raise ValueError( "Constraint '%s' found a 3-tuple (lower," " expression, upper) but the lower " "value was not data or an expression " "restricted to storage of data." % (self.name)) arg1 = expr[1] if arg1 is not None: arg1 = as_numeric(arg1) arg2 = expr[2] if arg2 is not None: arg2 = as_numeric(arg2) if arg2.is_potentially_variable(): raise ValueError( "Constraint '%s' found a 3-tuple (lower," " expression, upper) but the upper " "value was not data or an expression " "restricted to storage of data." % (self.name)) self._lower = arg0 self._body = arg1 self._upper = arg2 else: raise ValueError( "Constructor rule for constraint '%s' returned " "a tuple of length %d. Expecting a tuple of " "length 2 or 3:\n" "Equality: (left, right)\n" "Inequality: (lower, expression, upper)" % (self.name, len(expr))) relational_expr = False # # Ignore an 'empty' constraint # elif _expr_type is type: self._body = None self._lower = None self._upper = None self._equality = False if expr is Constraint.Skip: del self.parent_component()[self.index()] return elif expr is Constraint.Infeasible: del self.parent_component()[self.index()] raise ValueError( "Constraint '%s' is always infeasible" % (self.name,) ) else: raise ValueError( "Constraint '%s' does not have a proper " "value. Found '%s'\nExpecting a tuple or " "equation. Examples:" "\n sum(model.costs) == model.income" "\n (0, model.price[item], 50)" % (self.name, str(expr))) elif expr is None: raise ValueError(_rule_returned_none_error % (self.name,)) elif _expr_type is bool: raise ValueError( "Invalid constraint expression. The constraint " "expression resolved to a trivial Boolean (%s) " "instead of a Pyomo object. Please modify your " "rule to return Constraint.%s instead of %s." "\n\nError thrown for Constraint '%s'" % (expr, "Feasible" if expr else "Infeasible", expr, self.name)) else: msg = ("Constraint '%s' does not have a proper " "value. Found '%s'\nExpecting a tuple or " "equation. Examples:" "\n sum(model.costs) == model.income" "\n (0, model.price[item], 50)" % (self.name, str(expr))) if type(expr) is bool: msg += ("\nNote: constant Boolean expressions " "are not valid constraint expressions. " "Some apparently non-constant compound " "inequalities (e.g. 'expr >= 0 <= 1') " "can return boolean values; the proper " "form for compound inequalities is " "always 'lb <= expr <= ub'.") raise ValueError(msg) # # Process relational expressions # (i.e. explicit '==', '<', and '<=') # if relational_expr: if _expr_type is logical_expr.EqualityExpression: # Equality expression: only 2 arguments! self._equality = True if expr.arg(1).__class__ in native_numeric_types or not expr.arg(1).is_potentially_variable(): self._lower = self._upper = as_numeric(expr.arg(1)) self._body = expr.arg(0) elif expr.arg(0).__class__ in native_numeric_types or not expr.arg(0).is_potentially_variable(): self._lower = self._upper = as_numeric(expr.arg(0)) self._body = expr.arg(1) else: self._lower = self._upper = ZeroConstant self._body = expr.arg(0) - expr.arg(1) elif _expr_type is logical_expr.InequalityExpression: if expr._strict: raise ValueError( "Constraint '%s' encountered a strict " "inequality expression ('>' or '<'). All" " constraints must be formulated using " "using '<=', '>=', or '=='." % (self.name)) arg0 = as_numeric(expr.arg(0)) arg1 = as_numeric(expr.arg(1)) if not arg1.is_potentially_variable(): self._lower = None self._body = arg0 self._upper = arg1 elif not arg0.is_potentially_variable(): self._lower = arg0 self._body = arg1 self._upper = None else: self._lower = None self._body = arg0 self._body -= arg1 self._upper = ZeroConstant else: # RangedExpression if any(expr._strict): raise ValueError( "Constraint '%s' encountered a strict " "inequality expression ('>' or '<'). All" " constraints must be formulated using " "using '<=', '>=', or '=='." % (self.name)) #if expr.arg(0).is_potentially_variable(): # raise ValueError( # "Constraint '%s' found a double-sided " # "inequality expression (lower <= " # "expression <= upper) but the lower " # "bound was not data or an expression " # "restricted to storage of data." # % (self.name)) #if expr.arg(2).is_potentially_variable(): # raise ValueError( # "Constraint '%s' found a double-sided "\ # "inequality expression (lower <= " # "expression <= upper) but the upper " # "bound was not data or an expression " # "restricted to storage of data." # % (self.name)) self._lower = as_numeric(expr.arg(0)) self._body = expr.arg(1) self._upper = as_numeric(expr.arg(2)) # # Reset the values to 'None' if they are 'infinite' # if (self._lower is not None) and is_constant(self._lower): val = self._lower if self._lower.__class__ in native_numeric_types else self._lower() if not math.isfinite(val): if val > 0: raise ValueError( "Constraint '%s' created with a +Inf lower " "bound." % (self.name)) self._lower = None elif bool(val > 0) == bool(val <= 0): raise ValueError( "Constraint '%s' created with a non-numeric " "lower bound." % (self.name)) if (self._upper is not None) and is_constant(self._upper): val = self._upper if self._upper.__class__ in native_numeric_types else self._upper() if not math.isfinite(val): if val < 0: raise ValueError( "Constraint '%s' created with a -Inf upper " "bound." % (self.name)) self._upper = None elif bool(val > 0) == bool(val <= 0): raise ValueError( "Constraint '%s' created with a non-numeric " "upper bound." % (self.name)) # # Error check, to ensure that we don't have a constraint that # doesn't depend on any variables / parameters. # # Error check, to ensure that we don't have an equality # constraint with 'infinite' RHS # if self._equality: if self._lower is None: raise ValueError( "Equality constraint '%s' defined with " "non-finite term." % (self.name)) assert self._lower is self._upper
def set_value(self, expr): """Set the expression on this constraint.""" _expr_type = expr.__class__ if hasattr(expr, 'is_relational'): relational_expr = expr.is_relational() if not relational_expr: raise ValueError("Constraint '%s' does not have a proper " "value. Found '%s'\nExpecting a tuple or " "equation. Examples:" "\n sum(model.costs) == model.income" "\n (0, model.price[item], 50)" % (self.name, str(expr))) elif _expr_type is tuple: # or expr_type is list: # # Form equality expression # if len(expr) == 2: arg0 = expr[0] if arg0 is not None: arg0 = as_numeric(arg0) arg1 = expr[1] if arg1 is not None: arg1 = as_numeric(arg1) self._equality = True if arg1 is None or (not arg1.is_potentially_variable()): self._lower = self._upper = arg1 self._body = arg0 elif arg0 is None or (not arg0.is_potentially_variable()): self._lower = self._upper = arg0 self._body = arg1 else: self._lower = self._upper = ZeroConstant self._body = arg0 - arg1 # # Form inequality expression # elif len(expr) == 3: arg0 = expr[0] if arg0 is not None: arg0 = as_numeric(arg0) if arg0.is_potentially_variable(): raise ValueError( "Constraint '%s' found a 3-tuple (lower," " expression, upper) but the lower " "value was not data or an expression " "restricted to storage of data." % (self.name)) arg1 = expr[1] if arg1 is not None: arg1 = as_numeric(arg1) arg2 = expr[2] if arg2 is not None: arg2 = as_numeric(arg2) if arg2.is_potentially_variable(): raise ValueError( "Constraint '%s' found a 3-tuple (lower," " expression, upper) but the upper " "value was not data or an expression " "restricted to storage of data." % (self.name)) self._lower = arg0 self._body = arg1 self._upper = arg2 else: raise ValueError( "Constructor rule for constraint '%s' returned " "a tuple of length %d. Expecting a tuple of " "length 2 or 3:\n" "Equality: (left, right)\n" "Inequality: (lower, expression, upper)" % (self.name, len(expr))) relational_expr = False # # Ignore an 'empty' constraint # elif _expr_type is type: self._body = None self._lower = None self._upper = None self._equality = False if expr is Constraint.Skip: del self.parent_component()[self.index()] return elif expr is Constraint.Infeasible: del self.parent_component()[self.index()] raise ValueError("Constraint '%s' is always infeasible" % (self.name, )) else: raise ValueError("Constraint '%s' does not have a proper " "value. Found '%s'\nExpecting a tuple or " "equation. Examples:" "\n sum(model.costs) == model.income" "\n (0, model.price[item], 50)" % (self.name, str(expr))) elif expr is None: raise ValueError(_rule_returned_none_error % (self.name, )) elif _expr_type is bool: # # There are cases where a user thinks they are generating # a valid 2-sided inequality, but Python's internal # systems for handling chained inequalities is doing # something very different and resolving it to True / # False. In this case, chainedInequality will be # non-None, but the expression will be a bool. For # example, model.a < 1 > 0. # if logical_expr._using_chained_inequality \ and logical_expr._chainedInequality.prev is not None: buf = StringIO() logical_expr._chainedInequality.prev.pprint(buf) # # We are about to raise an exception, so it's OK to # reset chainedInequality # logical_expr._chainedInequality.prev = None raise ValueError( "Invalid chained (2-sided) inequality detected. " "The expression is resolving to %s instead of a " "Pyomo Expression object. This can occur when " "the middle term of a chained inequality is a " "constant or immutable parameter, for example, " "'model.a <= 1 >= 0'. The proper form for " "2-sided inequalities is '0 <= model.a <= 1'." "\n\nError thrown for Constraint '%s'" "\n\nUnresolved (dangling) inequality " "expression: %s" % (expr, self.name, buf)) else: raise ValueError( "Invalid constraint expression. The constraint " "expression resolved to a trivial Boolean (%s) " "instead of a Pyomo object. Please modify your " "rule to return Constraint.%s instead of %s." "\n\nError thrown for Constraint '%s'" % (expr, "Feasible" if expr else "Infeasible", expr, self.name)) else: msg = ("Constraint '%s' does not have a proper " "value. Found '%s'\nExpecting a tuple or " "equation. Examples:" "\n sum(model.costs) == model.income" "\n (0, model.price[item], 50)" % (self.name, str(expr))) if type(expr) is bool: msg += ("\nNote: constant Boolean expressions " "are not valid constraint expressions. " "Some apparently non-constant compound " "inequalities (e.g. 'expr >= 0 <= 1') " "can return boolean values; the proper " "form for compound inequalities is " "always 'lb <= expr <= ub'.") raise ValueError(msg) # # Special check for chainedInequality errors like "if var < # 1:" within rules. Catching them here allows us to provide # the user with better (and more immediate) debugging # information. We don't want to check earlier because we # want to provide a specific debugging message if the # construction rule returned True/False; for example, if the # user did ( var < 1 > 0 ) (which also results in a non-None # chainedInequality value) # if logical_expr._using_chained_inequality \ and logical_expr._chainedInequality.prev is not None: raise TypeError(logical_expr._chainedInequality.error_message()) # # Process relational expressions # (i.e. explicit '==', '<', and '<=') # if relational_expr: if _expr_type is logical_expr.EqualityExpression: # Equality expression: only 2 arguments! self._equality = True if expr.arg( 1).__class__ in native_numeric_types or not expr.arg( 1).is_potentially_variable(): self._lower = self._upper = as_numeric(expr.arg(1)) self._body = expr.arg(0) elif expr.arg( 0).__class__ in native_numeric_types or not expr.arg( 0).is_potentially_variable(): self._lower = self._upper = as_numeric(expr.arg(0)) self._body = expr.arg(1) else: self._lower = self._upper = ZeroConstant self._body = expr.arg(0) - expr.arg(1) elif _expr_type is logical_expr.InequalityExpression: if expr._strict: raise ValueError("Constraint '%s' encountered a strict " "inequality expression ('>' or '<'). All" " constraints must be formulated using " "using '<=', '>=', or '=='." % (self.name)) if not expr.arg(1).is_potentially_variable(): self._lower = None self._body = expr.arg(0) self._upper = as_numeric(expr.arg(1)) elif not expr.arg(0).is_potentially_variable(): self._lower = as_numeric(expr.arg(0)) self._body = expr.arg(1) self._upper = None else: self._lower = None self._body = expr.arg(0) self._body -= expr.arg(1) self._upper = ZeroConstant else: # RangedExpression if any(expr._strict): raise ValueError("Constraint '%s' encountered a strict " "inequality expression ('>' or '<'). All" " constraints must be formulated using " "using '<=', '>=', or '=='." % (self.name)) #if expr.arg(0).is_potentially_variable(): # raise ValueError( # "Constraint '%s' found a double-sided " # "inequality expression (lower <= " # "expression <= upper) but the lower " # "bound was not data or an expression " # "restricted to storage of data." # % (self.name)) #if expr.arg(2).is_potentially_variable(): # raise ValueError( # "Constraint '%s' found a double-sided "\ # "inequality expression (lower <= " # "expression <= upper) but the upper " # "bound was not data or an expression " # "restricted to storage of data." # % (self.name)) self._lower = as_numeric(expr.arg(0)) self._body = expr.arg(1) self._upper = as_numeric(expr.arg(2)) # # Reset the values to 'None' if they are 'infinite' # if (self._lower is not None) and is_constant(self._lower): val = self._lower if self._lower.__class__ in native_numeric_types else self._lower( ) if not pyutilib.math.is_finite(val): if val > 0: raise ValueError( "Constraint '%s' created with a +Inf lower " "bound." % (self.name)) self._lower = None elif bool(val > 0) == bool(val <= 0): raise ValueError("Constraint '%s' created with a non-numeric " "lower bound." % (self.name)) if (self._upper is not None) and is_constant(self._upper): val = self._upper if self._upper.__class__ in native_numeric_types else self._upper( ) if not pyutilib.math.is_finite(val): if val < 0: raise ValueError( "Constraint '%s' created with a -Inf upper " "bound." % (self.name)) self._upper = None elif bool(val > 0) == bool(val <= 0): raise ValueError("Constraint '%s' created with a non-numeric " "upper bound." % (self.name)) # # Error check, to ensure that we don't have a constraint that # doesn't depend on any variables / parameters. # # Error check, to ensure that we don't have an equality # constraint with 'infinite' RHS # if self._equality: if self._lower is None: raise ValueError("Equality constraint '%s' defined with " "non-finite term." % (self.name)) assert self._lower is self._upper
def _write_model(self, model, output_file, solver_capability, var_list, var_label, symbolMap, con_labeler, sort, skip_trivial_constraints, warmstart, solver, mtype, add_options, put_results): constraint_names = [] ConstraintIO = StringIO() linear = True linear_degree = set([0, 1]) model_ctypes = model.collect_ctypes(active=True) if False: # # WEH - Disabling this check. For now, we're allowing # variables defined on non-block objects. # # Sanity check: all active components better be things we know # how to deal with, plus Suffix if solving valid_ctypes = set([ Block, Constraint, Expression, Objective, Param, Set, RangeSet, Var, Suffix, Connector ]) if not model_ctypes.issubset(valid_ctypes): invalids = [t.__name__ for t in (model_ctypes - valid_ctypes)] raise RuntimeError( "Unallowable component(s) %s.\nThe GAMS writer cannot " "export models with this component type" % ", ".join(invalids)) # HACK: Temporary check for Connectors in active constriants. # This should be removed after the writer is moved to an # explicit GAMS-specific expression walker for generating the # constraint strings. has_Connectors = Connector in model_ctypes # Walk through the model and generate the constraint definition # for all active constraints. Any Vars / Expressions that are # encountered will be added to the var_list due to the labeler # defined above. for con in model.component_data_objects(Constraint, active=True, sort=sort): if (not con.has_lb()) and \ (not con.has_ub()): assert not con.equality continue # non-binding, so skip # HACK: Temporary check for Connectors in active constriants. if has_Connectors: raise RuntimeError("Cannot handle connectors right now.") #_check_for_connectors(con) con_body = as_numeric(con.body) if skip_trivial_constraints and con_body.is_fixed(): continue if linear: if con_body.polynomial_degree() not in linear_degree: linear = False cName = symbolMap.getSymbol(con, con_labeler) if con.equality: constraint_names.append('%s' % cName) ConstraintIO.write( '%s.. %s =e= %s ;\n' % (constraint_names[-1], expression_to_string( con_body, smap=symbolMap), _get_bound(con.upper))) else: if con.has_lb(): constraint_names.append('%s_lo' % cName) ConstraintIO.write( '%s.. %s =l= %s ;\n' % (constraint_names[-1], _get_bound(con.lower), expression_to_string(con_body, smap=symbolMap))) if con.has_ub(): constraint_names.append('%s_hi' % cName) ConstraintIO.write( '%s.. %s =l= %s ;\n' % (constraint_names[-1], expression_to_string( con_body, smap=symbolMap), _get_bound(con.upper))) obj = list( model.component_data_objects(Objective, active=True, sort=sort)) if len(obj) != 1: raise RuntimeError( "GAMS writer requires exactly one active objective (found %s)" % (len(obj))) obj = obj[0] if linear: if obj.expr.polynomial_degree() not in linear_degree: linear = False oName = symbolMap.getSymbol(obj, con_labeler) constraint_names.append(oName) ConstraintIO.write( '%s.. GAMS_OBJECTIVE =e= %s ;\n' % (oName, expression_to_string(obj.expr, smap=symbolMap))) # Categorize the variables that we found categorized_vars = Categorizer(var_list, symbolMap) # Write the GAMS model # $offdigit ignores extra precise digits instead of erroring output_file.write("$offdigit\n\n") output_file.write("EQUATIONS\n\t") output_file.write("\n\t".join(constraint_names)) if categorized_vars.binary: output_file.write(";\n\nBINARY VARIABLES\n\t") output_file.write("\n\t".join(categorized_vars.binary)) if categorized_vars.ints: output_file.write(";\n\nINTEGER VARIABLES") output_file.write("\n\t") output_file.write("\n\t".join(categorized_vars.ints)) if categorized_vars.positive: output_file.write(";\n\nPOSITIVE VARIABLES\n\t") output_file.write("\n\t".join(categorized_vars.positive)) output_file.write(";\n\nVARIABLES\n\tGAMS_OBJECTIVE\n\t") output_file.write("\n\t".join(categorized_vars.reals)) output_file.write(";\n\n") for line in ConstraintIO.getvalue().splitlines(): #if '**' in line: # # Investigate power functions for an integer exponent, in which # # case replace with power(x, int) function to improve domain # # issues. Skip first term since it's always "con_name.." # line = replace_power(line) + ';' if len(line) > 80000: line = split_long_line(line) output_file.write(line + "\n") output_file.write("\n") warn_int_bounds = False for category, var_name in categorized_vars: var = symbolMap.getObject(var_name) if category == 'positive': if var.has_ub(): output_file.write("%s.up = %s;\n" % (var_name, _get_bound(var.ub))) elif category == 'ints': if not var.has_lb(): warn_int_bounds = True # GAMS doesn't allow -INF lower bound for ints logger.warning("Lower bound for integer variable %s set " "to -1.0E+100." % var.name) output_file.write("%s.lo = -1.0E+100;\n" % (var_name)) elif value(var.lb) != 0: output_file.write("%s.lo = %s;\n" % (var_name, _get_bound(var.lb))) if not var.has_ub(): warn_int_bounds = True # GAMS has an option value called IntVarUp that is the # default upper integer bound, which it applies if the # integer's upper bound is INF. This option maxes out at # 2147483647, so we can go higher by setting the bound. logger.warning("Upper bound for integer variable %s set " "to +1.0E+100." % var.name) output_file.write("%s.up = +1.0E+100;\n" % (var_name)) else: output_file.write("%s.up = %s;\n" % (var_name, _get_bound(var.ub))) elif category == 'binary': if var.has_lb() and value(var.lb) != 0: output_file.write("%s.lo = %s;\n" % (var_name, _get_bound(var.lb))) if var.has_ub() and value(var.ub) != 1: output_file.write("%s.up = %s;\n" % (var_name, _get_bound(var.ub))) elif category == 'reals': if var.has_lb(): output_file.write("%s.lo = %s;\n" % (var_name, _get_bound(var.lb))) if var.has_ub(): output_file.write("%s.up = %s;\n" % (var_name, _get_bound(var.ub))) else: raise KeyError('Category %s not supported' % category) if warmstart and var.value is not None: output_file.write("%s.l = %s;\n" % (var_name, var.value)) if var.is_fixed(): # This probably doesn't run, since all fixed vars are by default # replaced with their value and not assigned a symbol. # But leave this here in case we change handling of fixed vars assert var.value is not None, "Cannot fix variable at None" output_file.write("%s.fx = %s;\n" % (var_name, var.value)) if warn_int_bounds: logger.warning( "GAMS requires finite bounds for integer variables. 1.0E100 " "is as extreme as GAMS will define, and should be enough to " "appear unbounded. If the solver cannot handle this bound, " "explicitly set a smaller bound on the pyomo model, or try a " "different GAMS solver.") model_name = "GAMS_MODEL" output_file.write("\nMODEL %s /all/ ;\n" % model_name) if mtype is None: mtype = ( 'lp', 'nlp', 'mip', 'minlp' )[(0 if linear else 1) + (2 if (categorized_vars.binary or categorized_vars.ints) else 0)] if solver is not None: if mtype.upper() not in valid_solvers[solver.upper()]: raise ValueError("ProblemWriter_gams passed solver (%s) " "unsuitable for model type (%s)" % (solver, mtype)) output_file.write("option %s=%s;\n" % (mtype, solver)) if add_options is not None: output_file.write("\n* START USER ADDITIONAL OPTIONS\n") for line in add_options: output_file.write('\n' + line) output_file.write("\n\n* END USER ADDITIONAL OPTIONS\n\n") output_file.write( "SOLVE %s USING %s %simizing GAMS_OBJECTIVE;\n\n" % (model_name, mtype, 'min' if obj.sense == minimize else 'max')) # Set variables to store certain statuses and attributes stat_vars = [ 'MODELSTAT', 'SOLVESTAT', 'OBJEST', 'OBJVAL', 'NUMVAR', 'NUMEQU', 'NUMDVAR', 'NUMNZ', 'ETSOLVE' ] output_file.write("Scalars MODELSTAT 'model status', " "SOLVESTAT 'solve status';\n") output_file.write("MODELSTAT = %s.modelstat;\n" % model_name) output_file.write("SOLVESTAT = %s.solvestat;\n\n" % model_name) output_file.write("Scalar OBJEST 'best objective', " "OBJVAL 'objective value';\n") output_file.write("OBJEST = %s.objest;\n" % model_name) output_file.write("OBJVAL = %s.objval;\n\n" % model_name) output_file.write("Scalar NUMVAR 'number of variables';\n") output_file.write("NUMVAR = %s.numvar\n\n" % model_name) output_file.write("Scalar NUMEQU 'number of equations';\n") output_file.write("NUMEQU = %s.numequ\n\n" % model_name) output_file.write("Scalar NUMDVAR 'number of discrete variables';\n") output_file.write("NUMDVAR = %s.numdvar\n\n" % model_name) output_file.write("Scalar NUMNZ 'number of nonzeros';\n") output_file.write("NUMNZ = %s.numnz\n\n" % model_name) output_file.write( "Scalar ETSOLVE 'time to execute solve statement';\n") output_file.write("ETSOLVE = %s.etsolve\n\n" % model_name) if put_results is not None: results = put_results + '.dat' output_file.write("\nfile results /'%s'/;" % results) output_file.write("\nresults.nd=15;") output_file.write("\nresults.nw=21;") output_file.write("\nput results;") output_file.write("\nput 'SYMBOL : LEVEL : MARGINAL' /;") for var in var_list: output_file.write("\nput %s %s.l %s.m /;" % (var, var, var)) for con in constraint_names: output_file.write("\nput %s %s.l %s.m /;" % (con, con, con)) output_file.write("\nput GAMS_OBJECTIVE GAMS_OBJECTIVE.l " "GAMS_OBJECTIVE.m;\n") statresults = put_results + 'stat.dat' output_file.write("\nfile statresults /'%s'/;" % statresults) output_file.write("\nstatresults.nd=15;") output_file.write("\nstatresults.nw=21;") output_file.write("\nput statresults;") output_file.write("\nput 'SYMBOL : VALUE' /;") for stat in stat_vars: output_file.write("\nput '%s' %s /;\n" % (stat, stat))
def set_value(self, expr): """Set the expression on this constraint.""" if expr is None: self._body = None self._lower = None self._upper = None self._equality = False return _expr_type = expr.__class__ if _expr_type is tuple: # or expr_type is list: # # Form equality expression # if len(expr) == 2: arg0 = expr[0] if arg0 is not None: arg0 = as_numeric(arg0) arg1 = expr[1] if arg1 is not None: arg1 = as_numeric(arg1) self._equality = True if arg1 is None or (not arg1.is_potentially_variable()): self._lower = self._upper = arg1 self._body = arg0 elif arg0 is None or (not arg0.is_potentially_variable()): self._lower = self._upper = arg0 self._body = arg1 else: self._lower = self._upper = ZeroConstant self._body = arg0 - arg1 # # Form inequality expression # elif len(expr) == 3: arg0 = expr[0] if arg0 is not None: arg0 = as_numeric(arg0) if arg0.is_potentially_variable(): raise ValueError( "Constraint '%s' found a 3-tuple (lower," " expression, upper) but the lower " "value was not data or an expression " "restricted to storage of data." % (self.name)) arg1 = expr[1] if arg1 is not None: arg1 = as_numeric(arg1) arg2 = expr[2] if arg2 is not None: arg2 = as_numeric(arg2) if arg2.is_potentially_variable(): raise ValueError( "Constraint '%s' found a 3-tuple (lower," " expression, upper) but the upper " "value was not data or an expression " "restricted to storage of data." % (self.name)) self._lower = arg0 self._body = arg1 self._upper = arg2 else: raise ValueError( "Constructor rule for constraint '%s' returned " "a tuple of length %d. Expecting a tuple of " "length 2 or 3:\n" "Equality: (left, right)\n" "Inequality: (lower, expression, upper)" % (self.name, len(expr))) relational_expr = False else: try: relational_expr = expr.is_relational() if not relational_expr: raise ValueError("Constraint '%s' does not have a proper " "value. Found '%s'\nExpecting a tuple or " "equation. Examples:" "\n sum(model.costs) == model.income" "\n (0, model.price[item], 50)" % (self.name, str(expr))) except AttributeError: msg = ("Constraint '%s' does not have a proper " "value. Found '%s'\nExpecting a tuple or " "equation. Examples:" "\n sum(model.costs) == model.income" "\n (0, model.price[item], 50)" % (self.name, str(expr))) if type(expr) is bool: msg += ("\nNote: constant Boolean expressions " "are not valid constraint expressions. " "Some apparently non-constant compound " "inequalities (e.g. 'expr >= 0 <= 1') " "can return boolean values; the proper " "form for compound inequalities is " "always 'lb <= expr <= ub'.") raise ValueError(msg) # # Special check for chainedInequality errors like "if var < # 1:" within rules. Catching them here allows us to provide # the user with better (and more immediate) debugging # information. We don't want to check earlier because we # want to provide a specific debugging message if the # construction rule returned True/False; for example, if the # user did ( var < 1 > 0 ) (which also results in a non-None # chainedInequality value) # if EXPR._using_chained_inequality and EXPR._chainedInequality.prev is not None: raise TypeError(EXPR._chainedInequality.error_message()) # # Process relational expressions # (i.e. explicit '==', '<', and '<=') # if relational_expr: if _expr_type is EXPR.EqualityExpression: # Equality expression: only 2 arguments! self._equality = True if expr.arg( 1).__class__ in native_numeric_types or not expr.arg( 1).is_potentially_variable(): self._lower = self._upper = expr.arg(1) self._body = expr.arg(0) elif expr.arg( 0).__class__ in native_numeric_types or not expr.arg( 0).is_potentially_variable(): self._lower = self._upper = expr.arg(0) self._body = expr.arg(1) else: self._lower = self._upper = ZeroConstant self._body = expr.arg(0) - expr.arg(1) elif _expr_type is EXPR.InequalityExpression: if expr._strict: raise ValueError("Constraint '%s' encountered a strict " "inequality expression ('>' or '<'). All" " constraints must be formulated using " "using '<=', '>=', or '=='." % (self.name)) if not expr.arg(1).is_potentially_variable(): self._lower = None self._body = expr.arg(0) self._upper = expr.arg(1) elif not expr.arg(0).is_potentially_variable(): self._lower = expr.arg(0) self._body = expr.arg(1) self._upper = None else: self._lower = None self._body = expr.arg(0) self._body -= expr.arg(1) self._upper = ZeroConstant else: # RangedExpression if any(expr._strict): raise ValueError("Constraint '%s' encountered a strict " "inequality expression ('>' or '<'). All" " constraints must be formulated using " "using '<=', '>=', or '=='." % (self.name)) #if expr.arg(0).is_potentially_variable(): # raise ValueError( # "Constraint '%s' found a double-sided " # "inequality expression (lower <= " # "expression <= upper) but the lower " # "bound was not data or an expression " # "restricted to storage of data." # % (self.name)) #if expr.arg(2).is_potentially_variable(): # raise ValueError( # "Constraint '%s' found a double-sided "\ # "inequality expression (lower <= " # "expression <= upper) but the upper " # "bound was not data or an expression " # "restricted to storage of data." # % (self.name)) self._lower = expr.arg(0) self._body = expr.arg(1) self._upper = expr.arg(2) # # Reset the values to 'None' if they are 'infinite' # if (self._lower is not None) and is_constant(self._lower): val = self._lower if self._lower.__class__ in native_numeric_types else self._lower( ) if not pyutilib.math.is_finite(val): if val > 0: raise ValueError( "Constraint '%s' created with a +Inf lower " "bound." % (self.name)) self._lower = None elif bool(val > 0) == bool(val <= 0): raise ValueError("Constraint '%s' created with a non-numeric " "lower bound." % (self.name)) if (self._upper is not None) and is_constant(self._upper): val = self._upper if self._upper.__class__ in native_numeric_types else self._upper( ) if not pyutilib.math.is_finite(val): if val < 0: raise ValueError( "Constraint '%s' created with a -Inf upper " "bound." % (self.name)) self._upper = None elif bool(val > 0) == bool(val <= 0): raise ValueError("Constraint '%s' created with a non-numeric " "upper bound." % (self.name)) # # Error check, to ensure that we don't have a constraint that # doesn't depend on any variables / parameters. # # Error check, to ensure that we don't have an equality # constraint with 'infinite' RHS # if self._equality: if self._lower is None: raise ValueError("Equality constraint '%s' defined with " "non-finite term." % (self.name)) assert self._lower is self._upper
def test_int(self): val = 1 nval = as_numeric(val) self.assertEqual(1.0, nval) #self.assertEqual(val, nval) self.assertEqual(nval / 2, 0.5)
def body(self, body): if body is not None: body = as_numeric(body) self._body = body
def test_long(self): val = long(1e10) nval = as_numeric(val) self.assertEqual(1.0e10, nval) #self.assertEqual(val, as_numeric(val)) self.assertEqual(nval / 2, 5.0e9)
def _write_model(self, model, output_file, solver_capability, var_list, var_label, symbolMap, con_labeler, sort, skip_trivial_constraints, warmstart, solver, mtype, add_options, put_results): constraint_names = [] ConstraintIO = StringIO() linear = True linear_degree = set([0, 1]) # Make sure there are no strange ActiveComponents. The expression # walker will handle strange things in constraints later. model_ctypes = model.collect_ctypes(active=True) invalids = set() for t in (model_ctypes - valid_active_ctypes_minlp): if issubclass(t, ActiveComponent): invalids.add(t) if len(invalids): invalids = [t.__name__ for t in invalids] raise RuntimeError( "Unallowable active component(s) %s.\nThe GAMS writer cannot " "export models with this component type." % ", ".join(invalids)) tc = StorageTreeChecker(model) # Walk through the model and generate the constraint definition # for all active constraints. Any Vars / Expressions that are # encountered will be added to the var_list due to the labeler # defined above. for con in model.component_data_objects(Constraint, active=True, sort=sort): if not con.has_lb() and not con.has_ub(): assert not con.equality continue # non-binding, so skip con_body = as_numeric(con.body) if skip_trivial_constraints and con_body.is_fixed(): continue if linear: if con_body.polynomial_degree() not in linear_degree: linear = False cName = symbolMap.getSymbol(con, con_labeler) if con.equality: constraint_names.append('%s' % cName) ConstraintIO.write( '%s.. %s =e= %s ;\n' % (constraint_names[-1], expression_to_string( con_body, tc, smap=symbolMap), _get_bound(con.upper))) else: if con.has_lb(): constraint_names.append('%s_lo' % cName) ConstraintIO.write( '%s.. %s =l= %s ;\n' % (constraint_names[-1], _get_bound(con.lower), expression_to_string(con_body, tc, smap=symbolMap))) if con.has_ub(): constraint_names.append('%s_hi' % cName) ConstraintIO.write( '%s.. %s =l= %s ;\n' % (constraint_names[-1], expression_to_string(con_body, tc, smap=symbolMap), _get_bound(con.upper))) obj = list( model.component_data_objects(Objective, active=True, sort=sort)) if len(obj) != 1: raise RuntimeError( "GAMS writer requires exactly one active objective (found %s)" % (len(obj))) obj = obj[0] if linear: if obj.expr.polynomial_degree() not in linear_degree: linear = False oName = symbolMap.getSymbol(obj, con_labeler) constraint_names.append(oName) ConstraintIO.write( '%s.. GAMS_OBJECTIVE =e= %s ;\n' % (oName, expression_to_string(obj.expr, tc, smap=symbolMap))) # Categorize the variables that we found categorized_vars = Categorizer(var_list, symbolMap) # Write the GAMS model # $offdigit ignores extra precise digits instead of erroring output_file.write("$offdigit\n\n") output_file.write("EQUATIONS\n\t") output_file.write("\n\t".join(constraint_names)) if categorized_vars.binary: output_file.write(";\n\nBINARY VARIABLES\n\t") output_file.write("\n\t".join(categorized_vars.binary)) if categorized_vars.ints: output_file.write(";\n\nINTEGER VARIABLES") output_file.write("\n\t") output_file.write("\n\t".join(categorized_vars.ints)) if categorized_vars.positive: output_file.write(";\n\nPOSITIVE VARIABLES\n\t") output_file.write("\n\t".join(categorized_vars.positive)) output_file.write(";\n\nVARIABLES\n\tGAMS_OBJECTIVE\n\t") output_file.write("\n\t".join(categorized_vars.reals)) output_file.write(";\n\n") for line in ConstraintIO.getvalue().splitlines(): if len(line) > 80000: line = split_long_line(line) output_file.write(line + "\n") output_file.write("\n") warn_int_bounds = False for category, var_name in categorized_vars: var = symbolMap.getObject(var_name) tc(var) if category == 'positive': if var.has_ub(): output_file.write("%s.up = %s;\n" % (var_name, _get_bound(var.ub))) elif category == 'ints': if not var.has_lb(): warn_int_bounds = True # GAMS doesn't allow -INF lower bound for ints logger.warning("Lower bound for integer variable %s set " "to -1.0E+100." % var.name) output_file.write("%s.lo = -1.0E+100;\n" % (var_name)) elif value(var.lb) != 0: output_file.write("%s.lo = %s;\n" % (var_name, _get_bound(var.lb))) if not var.has_ub(): warn_int_bounds = True # GAMS has an option value called IntVarUp that is the # default upper integer bound, which it applies if the # integer's upper bound is INF. This option maxes out at # 2147483647, so we can go higher by setting the bound. logger.warning("Upper bound for integer variable %s set " "to +1.0E+100." % var.name) output_file.write("%s.up = +1.0E+100;\n" % (var_name)) else: output_file.write("%s.up = %s;\n" % (var_name, _get_bound(var.ub))) elif category == 'binary': if var.has_lb() and value(var.lb) != 0: output_file.write("%s.lo = %s;\n" % (var_name, _get_bound(var.lb))) if var.has_ub() and value(var.ub) != 1: output_file.write("%s.up = %s;\n" % (var_name, _get_bound(var.ub))) elif category == 'reals': if var.has_lb(): output_file.write("%s.lo = %s;\n" % (var_name, _get_bound(var.lb))) if var.has_ub(): output_file.write("%s.up = %s;\n" % (var_name, _get_bound(var.ub))) else: raise KeyError('Category %s not supported' % category) if warmstart and var.value is not None: output_file.write("%s.l = %s;\n" % (var_name, var.value)) if warn_int_bounds: logger.warning( "GAMS requires finite bounds for integer variables. 1.0E100 " "is as extreme as GAMS will define, and should be enough to " "appear unbounded. If the solver cannot handle this bound, " "explicitly set a smaller bound on the pyomo model, or try a " "different GAMS solver.") model_name = "GAMS_MODEL" output_file.write("\nMODEL %s /all/ ;\n" % model_name) if mtype is None: mtype = ( 'lp', 'nlp', 'mip', 'minlp' )[(0 if linear else 1) + (2 if (categorized_vars.binary or categorized_vars.ints) else 0)] if solver is not None: if mtype.upper() not in valid_solvers[solver.upper()]: raise ValueError("GAMS writer passed solver (%s) " "unsuitable for model type (%s)" % (solver, mtype)) output_file.write("option %s=%s;\n" % (mtype, solver)) if add_options is not None: output_file.write("\n* START USER ADDITIONAL OPTIONS\n") for line in add_options: output_file.write('\n' + line) output_file.write("\n\n* END USER ADDITIONAL OPTIONS\n\n") output_file.write( "SOLVE %s USING %s %simizing GAMS_OBJECTIVE;\n\n" % (model_name, mtype, 'min' if obj.sense == minimize else 'max')) # Set variables to store certain statuses and attributes stat_vars = [ 'MODELSTAT', 'SOLVESTAT', 'OBJEST', 'OBJVAL', 'NUMVAR', 'NUMEQU', 'NUMDVAR', 'NUMNZ', 'ETSOLVE' ] output_file.write("Scalars MODELSTAT 'model status', " "SOLVESTAT 'solve status';\n") output_file.write("MODELSTAT = %s.modelstat;\n" % model_name) output_file.write("SOLVESTAT = %s.solvestat;\n\n" % model_name) output_file.write("Scalar OBJEST 'best objective', " "OBJVAL 'objective value';\n") output_file.write("OBJEST = %s.objest;\n" % model_name) output_file.write("OBJVAL = %s.objval;\n\n" % model_name) output_file.write("Scalar NUMVAR 'number of variables';\n") output_file.write("NUMVAR = %s.numvar\n\n" % model_name) output_file.write("Scalar NUMEQU 'number of equations';\n") output_file.write("NUMEQU = %s.numequ\n\n" % model_name) output_file.write("Scalar NUMDVAR 'number of discrete variables';\n") output_file.write("NUMDVAR = %s.numdvar\n\n" % model_name) output_file.write("Scalar NUMNZ 'number of nonzeros';\n") output_file.write("NUMNZ = %s.numnz\n\n" % model_name) output_file.write( "Scalar ETSOLVE 'time to execute solve statement';\n") output_file.write("ETSOLVE = %s.etsolve\n\n" % model_name) if put_results is not None: results = put_results + '.dat' output_file.write("\nfile results /'%s'/;" % results) output_file.write("\nresults.nd=15;") output_file.write("\nresults.nw=21;") output_file.write("\nput results;") output_file.write("\nput 'SYMBOL : LEVEL : MARGINAL' /;") for var in var_list: output_file.write("\nput %s %s.l %s.m /;" % (var, var, var)) for con in constraint_names: output_file.write("\nput %s %s.l %s.m /;" % (con, con, con)) output_file.write("\nput GAMS_OBJECTIVE GAMS_OBJECTIVE.l " "GAMS_OBJECTIVE.m;\n") statresults = put_results + 'stat.dat' output_file.write("\nfile statresults /'%s'/;" % statresults) output_file.write("\nstatresults.nd=15;") output_file.write("\nstatresults.nw=21;") output_file.write("\nput statresults;") output_file.write("\nput 'SYMBOL : VALUE' /;") for stat in stat_vars: output_file.write("\nput '%s' %s /;\n" % (stat, stat))
def set_value(self, expr): """Set the expression on this constraint.""" if expr is None: self._body = None self._lower = None self._upper = None self._equality = False return _expr_type = expr.__class__ if _expr_type is tuple: # or expr_type is list: # # Form equality expression # if len(expr) == 2: arg0 = expr[0] if arg0 is not None: arg0 = as_numeric(arg0) arg1 = expr[1] if arg1 is not None: arg1 = as_numeric(arg1) self._equality = True if arg1 is None or (not arg1.is_potentially_variable()): self._lower = self._upper = arg1 self._body = arg0 elif arg0 is None or (not arg0.is_potentially_variable()): self._lower = self._upper = arg0 self._body = arg1 else: self._lower = self._upper = ZeroConstant self._body = arg0 - arg1 # # Form inequality expression # elif len(expr) == 3: arg0 = expr[0] if arg0 is not None: arg0 = as_numeric(arg0) if arg0.is_potentially_variable(): raise ValueError( "Constraint '%s' found a 3-tuple (lower," " expression, upper) but the lower " "value was not data or an expression " "restricted to storage of data." % (self.name)) arg1 = expr[1] if arg1 is not None: arg1 = as_numeric(arg1) arg2 = expr[2] if arg2 is not None: arg2 = as_numeric(arg2) if arg2.is_potentially_variable(): raise ValueError( "Constraint '%s' found a 3-tuple (lower," " expression, upper) but the upper " "value was not data or an expression " "restricted to storage of data." % (self.name)) self._lower = arg0 self._body = arg1 self._upper = arg2 else: raise ValueError( "Constructor rule for constraint '%s' returned " "a tuple of length %d. Expecting a tuple of " "length 2 or 3:\n" "Equality: (left, right)\n" "Inequality: (lower, expression, upper)" % (self.name, len(expr))) relational_expr = False else: try: relational_expr = expr.is_relational() if not relational_expr: raise ValueError( "Constraint '%s' does not have a proper " "value. Found '%s'\nExpecting a tuple or " "equation. Examples:" "\n sum(model.costs) == model.income" "\n (0, model.price[item], 50)" % (self.name, str(expr))) except AttributeError: msg = ("Constraint '%s' does not have a proper " "value. Found '%s'\nExpecting a tuple or " "equation. Examples:" "\n sum(model.costs) == model.income" "\n (0, model.price[item], 50)" % (self.name, str(expr))) if type(expr) is bool: msg += ("\nNote: constant Boolean expressions " "are not valid constraint expressions. " "Some apparently non-constant compound " "inequalities (e.g. 'expr >= 0 <= 1') " "can return boolean values; the proper " "form for compound inequalities is " "always 'lb <= expr <= ub'.") raise ValueError(msg) # # Special check for chainedInequality errors like "if var < # 1:" within rules. Catching them here allows us to provide # the user with better (and more immediate) debugging # information. We don't want to check earlier because we # want to provide a specific debugging message if the # construction rule returned True/False; for example, if the # user did ( var < 1 > 0 ) (which also results in a non-None # chainedInequality value) # if logical_expr._using_chained_inequality and logical_expr._chainedInequality.prev is not None: raise TypeError(logical_expr._chainedInequality.error_message()) # # Process relational expressions # (i.e. explicit '==', '<', and '<=') # if relational_expr: if _expr_type is logical_expr.EqualityExpression: # Equality expression: only 2 arguments! self._equality = True if expr.arg(1).__class__ in native_numeric_types or not expr.arg(1).is_potentially_variable(): self._lower = self._upper = as_numeric(expr.arg(1)) self._body = expr.arg(0) elif expr.arg(0).__class__ in native_numeric_types or not expr.arg(0).is_potentially_variable(): self._lower = self._upper = as_numeric(expr.arg(0)) self._body = expr.arg(1) else: self._lower = self._upper = ZeroConstant self._body = expr.arg(0) - expr.arg(1) elif _expr_type is logical_expr.InequalityExpression: if expr._strict: raise ValueError( "Constraint '%s' encountered a strict " "inequality expression ('>' or '<'). All" " constraints must be formulated using " "using '<=', '>=', or '=='." % (self.name)) if not expr.arg(1).is_potentially_variable(): self._lower = None self._body = expr.arg(0) self._upper = as_numeric(expr.arg(1)) elif not expr.arg(0).is_potentially_variable(): self._lower = as_numeric(expr.arg(0)) self._body = expr.arg(1) self._upper = None else: self._lower = None self._body = expr.arg(0) self._body -= expr.arg(1) self._upper = ZeroConstant else: # RangedExpression if any(expr._strict): raise ValueError( "Constraint '%s' encountered a strict " "inequality expression ('>' or '<'). All" " constraints must be formulated using " "using '<=', '>=', or '=='." % (self.name)) #if expr.arg(0).is_potentially_variable(): # raise ValueError( # "Constraint '%s' found a double-sided " # "inequality expression (lower <= " # "expression <= upper) but the lower " # "bound was not data or an expression " # "restricted to storage of data." # % (self.name)) #if expr.arg(2).is_potentially_variable(): # raise ValueError( # "Constraint '%s' found a double-sided "\ # "inequality expression (lower <= " # "expression <= upper) but the upper " # "bound was not data or an expression " # "restricted to storage of data." # % (self.name)) self._lower = as_numeric(expr.arg(0)) self._body = expr.arg(1) self._upper = as_numeric(expr.arg(2)) # # Reset the values to 'None' if they are 'infinite' # if (self._lower is not None) and is_constant(self._lower): val = self._lower if self._lower.__class__ in native_numeric_types else self._lower() if not pyutilib.math.is_finite(val): if val > 0: raise ValueError( "Constraint '%s' created with a +Inf lower " "bound." % (self.name)) self._lower = None elif bool(val > 0) == bool(val <= 0): raise ValueError( "Constraint '%s' created with a non-numeric " "lower bound." % (self.name)) if (self._upper is not None) and is_constant(self._upper): val = self._upper if self._upper.__class__ in native_numeric_types else self._upper() if not pyutilib.math.is_finite(val): if val < 0: raise ValueError( "Constraint '%s' created with a -Inf upper " "bound." % (self.name)) self._upper = None elif bool(val > 0) == bool(val <= 0): raise ValueError( "Constraint '%s' created with a non-numeric " "upper bound." % (self.name)) # # Error check, to ensure that we don't have a constraint that # doesn't depend on any variables / parameters. # # Error check, to ensure that we don't have an equality # constraint with 'infinite' RHS # if self._equality: if self._lower is None: raise ValueError( "Equality constraint '%s' defined with " "non-finite term." % (self.name)) assert self._lower is self._upper
def expr(self, expr): self._expr = as_numeric(expr) if (expr is not None) else None