def to_string(self, ostream=None, verbose=None, precedence=0, labeler=None): """Convert this expression into a string.""" if ostream is None: ostream = sys.stdout _verbose = pyomo.core.kernel.expr_common.TO_STRING_VERBOSE if \ verbose is None else verbose if _verbose: ostream.write(self._to_string_label()) ostream.write("{") if self._expr is None: ostream.write("Undefined") elif isinstance(self._expr, NumericValue): self._expr.to_string(ostream=ostream, verbose=verbose, precedence=precedence, labeler=labeler) else: as_numeric(self._expr).to_string(ostream=ostream, verbose=verbose, precedence=precedence, labeler=labeler) if _verbose: ostream.write("}")
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._potentially_variable(): raise ValueError("Constraint lower bounds must be " "expressions restricted to data.") self._ub = ub
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._potentially_variable(): raise ValueError("Constraint right-hand side must be " "expressions restricted to data.") self._lb = rhs self._ub = rhs self._equality = True
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: # 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) # assigning to the rhs property # will set the equality flag to True if arg1 is None or (not arg1._potentially_variable()): self.rhs = arg1 self.body = arg0 elif arg0 is None or (not arg0._potentially_variable()): self.rhs = arg0 self.body = arg1 else: with EXPR.bypass_clone_check(): self.rhs = ZeroConstant self.body = arg0 self.body -= arg1 # # Form inequality expression # elif len(expr) == 3: arg0 = expr[0] if arg0 is not None: arg0 = as_numeric(arg0) if arg0._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._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.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 summation(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 summation(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.generate_relational_expression.chainedInequality is not None: raise TypeError(EXPR.chainedInequalityErrorMessage()) # # Process relational expressions # (i.e. explicit '==', '<', and '<=') # if relational_expr: if _expr_type is EXPR._EqualityExpression: # Equality expression: only 2 arguments! _args = expr._args # Explicitly dereference the original arglist (otherwise # this runs afoul of the getrefcount logic) expr._args = [] # assigning to the rhs property # will set the equality flag to True if not _args[1]._potentially_variable(): self.rhs = _args[1] self.body = _args[0] elif not _args[0]._potentially_variable(): self.rhs = _args[0] self.body = _args[1] else: with EXPR.bypass_clone_check(): self.rhs = ZeroConstant self.body = _args[0] self.body -= _args[1] else: # Inequality expression: 2 or 3 arguments if expr._strict: try: _strict = any(expr._strict) except: _strict = True if _strict: # # We can relax this when: # (a) we have a need for this # (b) we have problem writer that # explicitly handles this # (c) we make sure that all problem writers # that don't handle this make it known # to the user through an error or # warning # raise ValueError( "Constraint '%s' encountered a strict " "inequality expression ('>' or '<'). All" " constraints must be formulated using " "using '<=', '>=', or '=='." % (self.name)) _args = expr._args # Explicitly dereference the original arglist (otherwise # this runs afoul of the getrefcount logic) expr._args = [] if len(_args) == 3: if _args[0]._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 _args[2]._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.lb = _args[0] self.body = _args[1] self.ub = _args[2] else: if not _args[1]._potentially_variable(): self.lb = None self.body = _args[0] self.ub = _args[1] elif not _args[0]._potentially_variable(): self.lb = _args[0] self.body = _args[1] self.ub = None else: with EXPR.bypass_clone_check(): self.lb = None self.body = _args[0] self.body -= _args[1] self.ub = ZeroConstant # # Replace numeric bound values with a NumericConstant object, # and reset the values to 'None' if they are 'infinite' # if (self.lb is not None) and is_constant(self.lb): val = self.lb() if not pyutilib.math.is_finite(val): if val > 0: raise ValueError( "Constraint '%s' created with a +Inf lower " "bound." % (self.name)) self.lb = None if (self.ub is not None) and is_constant(self.ub): val = self.ub() if not pyutilib.math.is_finite(val): if val < 0: raise ValueError( "Constraint '%s' created with a -Inf upper " "bound." % (self.name)) self.ub = None # # 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 body(self, body): if body is not None: body = as_numeric(body) self._body = body
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]) # 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 ]) model_ctypes = model.collect_ctypes(active=True) 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)) # 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 body = StringIO() con_body.to_string(body, labeler=var_label) cName = symbolMap.getSymbol(con, con_labeler) if con.equality: constraint_names.append('%s' % cName) ConstraintIO.write('%s.. %s =e= %s ;\n' % ( constraint_names[-1], body.getvalue(), _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), body.getvalue() )) if con.has_ub(): constraint_names.append('%s_hi' % cName) ConstraintIO.write('%s.. %s =l= %s ;\n' % ( constraint_names[-1], body.getvalue(), _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) body = StringIO() obj.expr.to_string(body, labeler=var_label) constraint_names.append(oName) ConstraintIO.write('%s.. GAMS_OBJECTIVE =e= %s ;\n' % ( oName, body.getvalue() )) # 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 expr(self, expr): self._expr = as_numeric(expr) if (expr is not None) else None