def test_is_constant(self): v = variable() self.assertEqual(v.is_constant(), False) self.assertEqual(is_constant(v), False) self.assertEqual(v.fixed, False) self.assertEqual(v.value, None) v.value = 1.0 self.assertEqual(v.is_constant(), False) self.assertEqual(is_constant(v), False) self.assertEqual(v.fixed, False) self.assertEqual(v.value, 1.0) v.fix() self.assertEqual(v.is_constant(), False) self.assertEqual(is_constant(v), False) self.assertEqual(v.fixed, True) self.assertEqual(v.value, 1.0) v.value = None self.assertEqual(v.is_constant(), False) self.assertEqual(is_constant(v), False) self.assertEqual(v.fixed, True) self.assertEqual(v.value, None) v.free() self.assertEqual(v.is_constant(), False) self.assertEqual(is_constant(v), False) self.assertEqual(v.fixed, False) self.assertEqual(v.value, None)
def test_is_constant(self): f = functional_value() self.assertEqual(f.is_constant(), False) self.assertEqual(is_constant(f), False) f.fn = lambda: 2 self.assertEqual(f.is_constant(), False) self.assertEqual(is_constant(f), False)
def test_is_constant(self): p = parameter() self.assertEqual(p.is_constant(), False) self.assertEqual(is_constant(p), False) p.value = 1.0 self.assertEqual(p.is_constant(), False) self.assertEqual(is_constant(p), False)
def test_is_constant(self): e = self._ctype_factory() self.assertEqual(e.is_constant(), False) self.assertEqual(is_constant(e), False) e.expr = 1 self.assertEqual(e.is_constant(), False) self.assertEqual(is_constant(e), False) p = parameter() self.assertEqual(p.is_constant(), False) self.assertEqual(is_constant(p), False) p.value = 2 e.expr = p + 1 self.assertEqual(e.is_constant(), False) self.assertEqual(is_constant(e), False)
def test_is_constant(self): v = variable() e = noclone(v) self.assertEqual(e.is_constant(), False) self.assertEqual(is_constant(e), False) v.fix(1) self.assertEqual(e.is_constant(), False) self.assertEqual(is_constant(e), False) p = parameter() e = noclone(p) self.assertEqual(p.is_constant(), False) self.assertEqual(is_constant(p), False) self.assertEqual(is_constant(noclone(1)), True)
def _diff_abs(node, val_dict, der_dict): """ Reverse automatic differentiation on the abs function. This will raise an exception at 0. Parameters ---------- node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression val_dict: ComponentMap der_dict: ComponentMap """ assert len(node.args) == 1 arg = node.args[0] der = der_dict[node] val = val_dict[arg] if not is_constant(val): raise DifferentiationException( 'Cannot perform symbolic differentiation of abs(x). Please use numeric differentiation' ) if val == 0: raise DifferentiationException('Cannot differentiate abs(x) at x=0') elif val < 0: der_dict[arg] -= der else: der_dict[arg] += der
def _diff_abs(node, val_dict, der_dict): """ Reverse automatic differentiation on the abs function. This will raise an exception at 0. Parameters ---------- node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression val_dict: ComponentMap der_dict: ComponentMap """ assert len(node.args) == 1 arg = node.args[0] der = der_dict[node] val = val_dict[arg] if is_constant(val) and val == 0: raise DifferentiationException('Cannot differentiate abs(x) at x=0') der_dict[arg] += der * val / abs(val)
def _build_relaxation(self): clb, cub = _get_bounds(self._c) slb, sub = _get_bounds(self._s) vmsq_1_lb, vmsq_1_ub = _get_bounds(self._vmsq_1) vmsq_2_lb, vmsq_2_ub = _get_bounds(self._vmsq_2) if None in { clb, cub, slb, sub, vmsq_1_lb, vmsq_1_ub, vmsq_2_lb, vmsq_2_ub }: return None if vmsq_1_lb == vmsq_1_ub and vmsq_2_lb == vmsq_2_ub: if clb == cub or slb == sub: rhs = [vmsq_1_lb * vmsq_2_lb] else: rhs = [math.sqrt(vmsq_1_lb * vmsq_2_lb)] elif vmsq_1_lb == vmsq_1_ub: if clb == cub or slb == sub: rhs = [vmsq_1_lb * self._vmsq_2] else: m = (math.sqrt(vmsq_2_ub) - math.sqrt(vmsq_2_lb)) / (vmsq_2_ub - vmsq_2_lb) b = math.sqrt(vmsq_2_ub) - m * vmsq_2_ub rhs = [math.sqrt(vmsq_1_lb) * (m * self._vmsq_2 + b)] elif vmsq_2_lb == vmsq_2_ub: if clb == cub or slb == sub: rhs = [vmsq_2_lb * self._vmsq_1] else: m = (math.sqrt(vmsq_1_ub) - math.sqrt(vmsq_1_lb)) / (vmsq_1_ub - vmsq_1_lb) b = math.sqrt(vmsq_1_ub) - m * vmsq_1_ub rhs = [math.sqrt(vmsq_2_lb) * (m * self._vmsq_1 + b)] else: if clb == cub or slb == sub: rhs = [ vmsq_1_lb * self._vmsq_2 + self._vmsq_1 * vmsq_2_lb - vmsq_1_lb * vmsq_2_lb, vmsq_1_ub * self._vmsq_2 + self._vmsq_1 * vmsq_2_ub - vmsq_1_ub * vmsq_2_ub ] else: cm = [ sqrt(vmsq_1_lb) / (sqrt(vmsq_2_lb) + sqrt(vmsq_2_ub)), sqrt(vmsq_1_ub) / (sqrt(vmsq_2_lb) + sqrt(vmsq_2_ub)) ] bm = [ sqrt(vmsq_2_lb) / (sqrt(vmsq_1_lb) + sqrt(vmsq_1_ub)), sqrt(vmsq_2_ub) / (sqrt(vmsq_1_lb) + sqrt(vmsq_1_ub)) ] am = [ sqrt(vmsq_1_lb * vmsq_2_lb) - bm[0] * vmsq_1_lb - cm[0] * vmsq_2_lb, sqrt(vmsq_1_ub * vmsq_2_ub) - bm[1] * vmsq_1_ub - cm[1] * vmsq_2_ub ] rhs = list() for i in [0, 1]: rhs.append(am[i] + bm[i] * self._vmsq_1 + cm[i] * self._vmsq_2) if clb == cub and slb == sub: lhs = [clb**2 + slb**2] elif clb == cub: m = (sub**2 - slb**2) / (sub - slb) b = sub**2 - m * sub lhs = [clb**2 + m * self._s + b] elif slb == sub: m = (cub**2 - clb**2) / (cub - clb) b = cub**2 - m * cub lhs = [m * self._c + b + slb**2] else: if sqrt(cub**2 + sub**2) + sqrt(clb**2 + slb**2) - sqrt( cub**2 + slb**2) - sqrt(clb**2 + sub**2) <= 0: cn = [(sqrt(clb**2 + sub**2) - sqrt(clb**2 + slb**2)) / (sub - slb), (sqrt(cub**2 + sub**2) - sqrt(cub**2 + slb**2)) / (sub - slb)] bn = [(sqrt(cub**2 + slb**2) - sqrt(clb**2 + slb**2)) / (cub - clb), (sqrt(cub**2 + sub**2) - sqrt(clb**2 + sub**2)) / (cub - clb)] an = [ sqrt(clb**2 + slb**2) - bn[0] * clb - cn[0] * slb, sqrt(cub**2 + sub**2) - bn[1] * cub - cn[1] * sub ] else: cn = [(sqrt(clb**2 + sub**2) - sqrt(clb**2 + slb**2)) / (sub - slb), (sqrt(cub**2 + sub**2) - sqrt(cub**2 + slb**2)) / (sub - slb)] bn = [(sqrt(cub**2 + sub**2) - sqrt(clb**2 + sub**2)) / (cub - clb), (sqrt(cub**2 + slb**2) - sqrt(clb**2 + slb**2)) / (cub - clb)] an = [ sqrt(clb**2 + slb**2) - bn[0] * clb - cn[0] * slb, sqrt(cub**2 + sub**2) - bn[1] * cub - cn[1] * sub ] lhs = list() for i in [0, 1]: lhs.append(an[i] + bn[i] * self._c + cn[i] * self._s) self.underestimators = pe.ConstraintList() for _lhs in lhs: for _rhs in rhs: if is_constant(_lhs) and is_constant(_rhs): continue self.underestimators.add(_lhs >= _rhs)
def is_constant(self): """A boolean indicating whether this expression is constant.""" return is_constant(self._expr)
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 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.""" 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 _relax_leaf_to_root_PowExpression(node, values, aux_var_map, degree_map, parent_block, relaxation_side_map, counter): arg1, arg2 = values degree1 = degree_map[arg1] degree2 = degree_map[arg2] if degree2 == 0: if degree1 == 0: res = arg1 ** arg2 degree_map[res] = 0 return res if not is_constant(arg2): logger.warning('Only constant exponents are supported: ' + str(arg1**arg2) + '\nReplacing ' + str(arg2) + ' with its value.') arg2 = pe.value(arg2) if arg2 == 1: return arg1 elif arg2 == 0: res = 1 degree_map[res] = 0 return res elif arg2 == 2: return _relax_quadratic(arg1=arg1, aux_var_map=aux_var_map, relaxation_side=relaxation_side_map[node], degree_map=degree_map, parent_block=parent_block, counter=counter) elif arg2 >= 0: if arg2 == round(arg2): if arg2 % 2 == 0 or compute_bounds_on_expr(arg1)[0] >= 0: return _relax_convex_pow(arg1=arg1, arg2=arg2, aux_var_map=aux_var_map, relaxation_side=relaxation_side_map[node], degree_map=degree_map, parent_block=parent_block, counter=counter) elif compute_bounds_on_expr(arg1)[1] <= 0: return _relax_concave_pow(arg1=arg1, arg2=arg2, aux_var_map=aux_var_map, relaxation_side=relaxation_side_map[node], degree_map=degree_map, parent_block=parent_block, counter=counter) else: # reformulate arg1 ** arg2 as arg1 * arg1 ** (arg2 - 1) _new_relaxation_side_map = ComponentMap() _reformulated = arg1 * arg1 ** (arg2 - 1) _new_relaxation_side_map[_reformulated] = relaxation_side_map[node] res = _relax_expr(expr=_reformulated, aux_var_map=aux_var_map, parent_block=parent_block, relaxation_side_map=_new_relaxation_side_map, counter=counter, degree_map=degree_map) degree_map[res] = 1 return res else: if arg2 < 1: return _relax_concave_pow(arg1=arg1, arg2=arg2, aux_var_map=aux_var_map, relaxation_side=relaxation_side_map[node], degree_map=degree_map, parent_block=parent_block, counter=counter) else: return _relax_convex_pow(arg1=arg1, arg2=arg2, aux_var_map=aux_var_map, relaxation_side=relaxation_side_map[node], degree_map=degree_map, parent_block=parent_block, counter=counter) else: if arg2 == round(arg2): if compute_bounds_on_expr(arg1)[0] >= 0: return _relax_convex_pow(arg1=arg1, arg2=arg2, aux_var_map=aux_var_map, relaxation_side=relaxation_side_map[node], degree_map=degree_map, parent_block=parent_block, counter=counter) elif compute_bounds_on_expr(arg1)[1] <= 0: if arg2 % 2 == 0: return _relax_convex_pow(arg1=arg1, arg2=arg2, aux_var_map=aux_var_map, relaxation_side=relaxation_side_map[node], degree_map=degree_map, parent_block=parent_block, counter=counter) else: return _relax_concave_pow(arg1=arg1, arg2=arg2, aux_var_map=aux_var_map, relaxation_side=relaxation_side_map[node], degree_map=degree_map, parent_block=parent_block, counter=counter) else: # reformulate arg1 ** arg2 as 1 / arg1 ** (-arg2) _new_relaxation_side_map = ComponentMap() _reformulated = 1 / (arg1 ** (-arg2)) _new_relaxation_side_map[_reformulated] = relaxation_side_map[node] res = _relax_expr(expr=_reformulated, aux_var_map=aux_var_map, parent_block=parent_block, relaxation_side_map=_new_relaxation_side_map, counter=counter, degree_map=degree_map) degree_map[res] = 1 return res else: assert compute_bounds_on_expr(arg1)[0] >= 0 return _relax_convex_pow(arg1=arg1, arg2=arg2, aux_var_map=aux_var_map, relaxation_side=relaxation_side_map[node], degree_map=degree_map, parent_block=parent_block, counter=counter) elif degree1 == 0: if not is_constant(arg1): logger.warning('Found {0} raised to a variable power. However, {0} does not appear to be constant (maybe ' 'it is or depends on a mutable Param?). Replacing {0} with its value.'.format(str(arg1))) arg1 = pe.value(arg1) if arg1 < 0: raise ValueError('Cannot raise a negative base to a variable exponent: ' + str(arg1**arg2)) return _relax_convex_pow(arg1=arg1, arg2=arg2, aux_var_map=aux_var_map, relaxation_side=relaxation_side_map[node], degree_map=degree_map, parent_block=parent_block, counter=counter, swap=True) else: if (id(arg1), id(arg2), 'pow') in aux_var_map: _aux_var, relaxation = aux_var_map[id(arg1), id(arg2), 'pow'] if relaxation_side_map[node] != relaxation.relaxation_side: relaxation.relaxation_side = RelaxationSide.BOTH return _aux_var else: assert compute_bounds_on_expr(arg1)[0] >= 0 _new_relaxation_side_map = ComponentMap() _reformulated = pe.exp(arg2 * pe.log(arg1)) _new_relaxation_side_map[_reformulated] = relaxation_side_map[node] res = _relax_expr(expr=_reformulated, aux_var_map=aux_var_map, parent_block=parent_block, relaxation_side_map=_new_relaxation_side_map, counter=counter, degree_map=degree_map) degree_map[res] = 1 return res
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