def __init__(self, substitute=None, descend_into_named_expressions=True, remove_named_expressions=True): if substitute is None: substitute = {} # Note: preserving the attribute names from the previous # implementation of the expression walker. self.substitute = substitute self.enter_named_expr = descend_into_named_expressions self.rm_named_expr = remove_named_expressions kwds = {} if hasattr(self, 'visiting_potential_leaf'): deprecation_warning( "ExpressionReplacementVisitor: this walker has been ported " "to derive from StreamBasedExpressionVisitor. " "visiting_potential_leaf() has been replaced by beforeChild()" "(note to implementers: the sense of the bool return value " "has been inverted).", version='6.2') def beforeChild(node, child, child_idx): is_leaf, ans = self.visiting_potential_leaf(child) return not is_leaf, ans kwds['beforeChild'] = beforeChild if hasattr(self, 'visit'): raise DeveloperError( "ExpressionReplacementVisitor: this walker has been ported " "to derive from StreamBasedExpressionVisitor. " "overriding visit() has no effect (and is likely to generate " "invalid expression trees)") super().__init__(**kwds)
def _deprecation_docstring(obj, msg, version, remove_in): if version is None: # or version in ('','tbd','TBD'): raise DeveloperError("@deprecated missing initial version") return ( '%s %s\n %s\n' % (_doc_flag, version, _default_msg(obj, msg, None, remove_in)) )
def deprecated(msg=None, logger='pyomo.core', version=None, remove_in=None): """Indicate that a function, method or class is deprecated. This decorator will cause a warning to be logged when the wrapped function or method is called, or when the deprecated class is constructed. This decorator also updates the target object's docstring to indicate that it is deprecated. Args: msg (str): a custom deprecation message (default: "This {function|class} has been deprecated and may be removed in a future release.") logger (str): the logger to use for emitting the warning (default: "pyomo.core") version (str): [required] the version in which the decorated object was deprecated. General practice is to set version to '' or 'TBD' during development and update it to the actual release as part of the release process. remove_in (str): the version in which the decorated object will be removed from the code. """ if version is None: # or version in ('','tbd','TBD'): raise DeveloperError("@deprecated missing initial version") def wrap(func): message = _default_msg(msg, version, remove_in, func) @functools.wraps(func, assigned=('__module__', '__name__')) def wrapper(*args, **kwargs): deprecation_warning(message, logger) return func(*args, **kwargs) if func.__doc__ is None: wrapper.__doc__ = textwrap.fill('DEPRECATION WARNING: %s' % (message, ), width=70) else: wrapper.__doc__ = textwrap.fill( 'DEPRECATION WARNING: %s' % (message,), width=70) + '\n\n' + \ textwrap.fill(textwrap.dedent(func.__doc__.strip())) return wrapper return wrap
def set_value(self, expr): """Set the expression on this constraint.""" # Clear any previously-cached normalized constraint self._lower = self._upper = self._body = self._expr = None _expr_type = expr.__class__ if hasattr(expr, 'is_relational'): if not expr.is_relational(): 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))) self._expr = expr elif _expr_type is tuple: # or expr_type is list: for arg in expr: if arg is None or arg.__class__ in native_numeric_types \ or isinstance(arg, NumericValue): continue raise ValueError( "Constraint '%s' does not have a proper value. " "Constraint expressions expressed as tuples must " "contain native numeric types or Pyomo NumericValue " "objects. Tuple %s contained invalid type, %s" % (self.name, expr, arg.__class__.__name__)) if len(expr) == 2: # # Form equality expression # if expr[0] is None or expr[1] is None: raise ValueError( "Constraint '%s' does not have a proper value. " "Equality Constraints expressed as 2-tuples " "cannot contain None [received %s]" % ( self.name, expr, )) self._expr = logical_expr.EqualityExpression(expr) elif len(expr) == 3: # # Form (ranged) inequality expression # if expr[0] is None: self._expr = logical_expr.InequalityExpression( expr[1:], False) elif expr[2] is None: self._expr = logical_expr.InequalityExpression( expr[:2], False) else: self._expr = logical_expr.RangedExpression(expr, False) else: raise ValueError( "Constraint '%s' does not have a proper value. " "Found 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))) # # Ignore an 'empty' constraint # elif _expr_type is type: del self.parent_component()[self.index()] if expr is Constraint.Skip: return elif expr is Constraint.Infeasible: # TODO: create a trivial infeasible constraint. This # could be useful in the case of GDP where certain # disjuncts are trivially infeasible, but we would still # like to express the disjunction. #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))) raise ValueError(msg) # # Normalize the incoming expressions, if we can # args = self._expr.args if self._expr.__class__ is logical_expr.InequalityExpression: if self._expr.strict: raise ValueError("Constraint '%s' encountered a strict " "inequality expression ('>' or '< '). All" " constraints must be formulated using " "using '<=', '>=', or '=='." % (self.name, )) if args[1] is None or args[1].__class__ in native_numeric_types \ or not args[1].is_potentially_variable(): self._body = args[0] self._upper = args[1] elif args[0] is None or args[0].__class__ in native_numeric_types \ or not args[0].is_potentially_variable(): self._lower = args[0] self._body = args[1] else: self._body = args[0] - args[1] self._upper = 0 elif self._expr.__class__ is logical_expr.EqualityExpression: if args[0] is None or args[1] is None: # Error check: ensure equality does not have infinite RHS raise ValueError("Equality constraint '%s' defined with " "non-finite term (%sHS == None)." % (self.name, 'L' if args[0] is None else 'R')) if args[0].__class__ in native_numeric_types or \ not args[0].is_potentially_variable(): self._lower = self._upper = args[0] self._body = args[1] elif args[1].__class__ in native_numeric_types or \ not args[1].is_potentially_variable(): self._lower = self._upper = args[1] self._body = args[0] else: self._lower = self._upper = 0 self._body = args[0] - args[1] # The following logic is caught below when checking for # invalid non-finite bounds: # # if self._lower.__class__ in native_numeric_types and \ # not math.isfinite(self._lower): # raise ValueError( # "Equality constraint '%s' defined with " # "non-finite term." % (self.name)) elif self._expr.__class__ is logical_expr.RangedExpression: if any(self._expr.strict): raise ValueError("Constraint '%s' encountered a strict " "inequality expression ('>' or '< '). All" " constraints must be formulated using " "using '<=', '>=', or '=='." % (self.name, )) if all((arg is None or arg.__class__ in native_numeric_types or not arg.is_potentially_variable()) for arg in (args[0], args[2])): self._lower, self._body, self._upper = args else: # Defensive programming: we currently only support three # relational expression types. This will only be hit if # someone defines a fourth... raise DeveloperError( "Unrecognized relational expression type: %s" % (self._expr.__class__.__name__, )) # We have historically mapped incoming inf to None if self._lower.__class__ in native_numeric_types: if self._lower == -_inf: self._lower = None elif not math.isfinite(self._lower): raise ValueError( "Constraint '%s' created with an invalid non-finite " "lower bound (%s)." % (self.name, self._lower)) if self._upper.__class__ in native_numeric_types: if self._upper == _inf: self._upper = None elif not math.isfinite(self._upper): raise ValueError( "Constraint '%s' created with an invalid non-finite " "upper bound (%s)." % (self.name, self._upper))
def power(xl, xu, yl, yu): """ Compute bounds on x**y. """ if xl > 0: """ If x is always positive, things are simple. We only need to worry about the sign of y. """ if yl < 0 and yu > 0: lb = min(xu**yl, xl**yu) ub = max(xl**yl, xu**yu) elif yl >= 0: lb = xl**yl ub = xu**yu elif yu <= 0: lb = xu**yl ub = xl**yu else: raise DeveloperError() elif xl == 0: # this section is only needed so we do not encounter math domain errors; # The logic is essentially the same as above (xl > 0) if xu == 0 and yl < 0: _lba = math.inf else: _lba = xu**yl if yu < 0: _lbb = math.inf else: _lbb = xl**yu lb = min(_lba, _lbb) if yl < 0: _uba = math.inf else: _uba = xl**yl if xu == 0 and yu < 0: _ubb = math.inf else: _ubb = xu**yu ub = max(_uba, _ubb) elif yl == yu and yl == round(yl): # the exponent is an integer, so x can be negative """ The logic here depends on several things: 1) The sign of x 2) The sign of y 3) Whether y is even or odd. There are also special cases to avoid math domain errors. """ y = yl if xu <= 0: if y < 0: if y % 2 == 0: lb = xl**y if xu == 0: ub = math.inf else: ub = xu**y else: if xu == 0: lb = -math.inf else: lb = xu**y ub = xl**y else: if y % 2 == 0: lb = xu**y ub = xl**y else: lb = xl**y ub = xu**y else: if y < 0: if y % 2 == 0: lb = min(xl**y, xu**y) ub = math.inf else: lb = -math.inf ub = math.inf else: if y % 2 == 0: lb = 0 ub = max(xl**y, xu**y) else: lb = xl**y ub = xu**y elif yl == yu: # the exponent is allowed to be fractional, so x must be positive xl = 0 lb, ub = power(xl, xu, yl, yu) else: msg = 'encountered an exponent where the base is allowed to be negative ' msg += 'and the exponent is allowed to be fractional and is not fixed. ' msg += 'Assuming the lower bound of the base to be 0.' warnings.warn(msg) logger.warning(msg) xl = 0 lb, ub = power(xl, xu, yl, yu) return lb, ub
def power(xl, xu, yl, yu): """ Compute bounds on x**y. """ if xl > 0: """ If x is always positive, things are simple. We only need to worry about the sign of y. """ if yl < 0 and yu > 0: lb = min(xu**yl, xl**yu) ub = max(xl**yl, xu**yu) elif yl >= 0: lb = min(xl**yl, xl**yu) ub = max(xu**yl, xu**yu) elif yu <= 0: lb = min(xu**yl, xu**yu) ub = max(xl**yl, xl**yu) else: raise DeveloperError() elif xl == 0: if yl >= 0: lb = min(xl**yl, xl**yu) ub = max(xu**yl, xu**yu) else: lb = -inf ub = inf elif yl == yu and yl == round(yl): # the exponent is an integer, so x can be negative """ The logic here depends on several things: 1) The sign of x 2) The sign of y 3) Whether y is even or odd. There are also special cases to avoid math domain errors. """ y = yl if xu <= 0: if y < 0: if y % 2 == 0: lb = xl**y if xu == 0: ub = inf else: ub = xu**y else: if xu == 0: lb = -inf ub = inf else: lb = xu**y ub = xl**y else: if y % 2 == 0: lb = xu**y ub = xl**y else: lb = xl**y ub = xu**y else: if y < 0: if y % 2 == 0: lb = min(xl**y, xu**y) ub = inf else: lb = -inf ub = inf else: if y % 2 == 0: lb = 0 ub = max(xl**y, xu**y) else: lb = xl**y ub = xu**y elif yl == yu: # the exponent has to be fractional, so x must be positive if xu < 0: msg = 'Cannot raise a negative number to the power of {0}.\n'.format( yl) msg += 'The upper bound of a variable raised to the power of {0} is {1}'.format( yl, xu) raise InfeasibleConstraintException(msg) xl = 0 lb, ub = power(xl, xu, yl, yu) else: lb = -inf ub = inf return lb, ub
def _generate_relational_expression(etype, lhs, rhs): rhs_is_relational = False lhs_is_relational = False constant_lhs = True constant_rhs = True if lhs is not None and lhs.__class__ not in native_numeric_types: lhs = _process_arg(lhs) # Note: _process_arg can return a native type if lhs is not None and lhs.__class__ not in native_numeric_types: lhs_is_relational = lhs.is_relational() constant_lhs = False if rhs is not None and rhs.__class__ not in native_numeric_types: rhs = _process_arg(rhs) # Note: _process_arg can return a native type if rhs is not None and rhs.__class__ not in native_numeric_types: rhs_is_relational = rhs.is_relational() constant_rhs = False if constant_lhs and constant_rhs: if etype == _eq: return lhs == rhs elif etype == _le: return lhs <= rhs elif etype == _lt: return lhs < rhs else: raise ValueError("Unknown relational expression type '%s'" % etype) if etype == _eq: if lhs_is_relational or rhs_is_relational: raise TypeError( "Cannot create an EqualityExpression where one of the " "sub-expressions is a relational expression:\n" " %s\n {==}\n %s" % ( lhs, rhs, )) return EqualityExpression((lhs, rhs)) else: if etype == _le: strict = False elif etype == _lt: strict = True else: raise DeveloperError("Unknown relational expression type '%s'" % (etype, )) if lhs_is_relational: if lhs.__class__ is InequalityExpression: if rhs_is_relational: raise TypeError( "Cannot create an InequalityExpression where both " "sub-expressions are relational expressions:\n" " %s\n {%s}\n %s" % ( lhs, "<" if strict else "<=", rhs, )) return RangedExpression(lhs._args_ + (rhs, ), (lhs._strict, strict)) else: raise TypeError( "Cannot create an InequalityExpression where one of the " "sub-expressions is an equality or ranged expression:\n" " %s\n {%s}\n %s" % ( lhs, "<" if strict else "<=", rhs, )) elif rhs_is_relational: if rhs.__class__ is InequalityExpression: return RangedExpression((lhs, ) + rhs._args_, (strict, rhs._strict)) else: raise TypeError( "Cannot create an InequalityExpression where one of the " "sub-expressions is an equality or ranged expression:\n" " %s\n {%s}\n %s" % ( lhs, "<" if strict else "<=", rhs, )) else: return InequalityExpression((lhs, rhs), strict)