def _monotonicity_constant_exponent(base, expo, mono_base, mono_expo, bounds_base, bounds_expo): # pylint: disable=too-many-return-statements, too-many-arguments if almosteq(expo, 1): return mono_base elif almosteq(expo, 0): return Monotonicity.Constant is_integer = almosteq(expo, int(expo)) is_even = almosteq(expo % 2, 0) if is_integer and is_even: return _monotonicity_even_exponent( base, expo, mono_base, mono_expo, bounds_base, bounds_expo, ) elif is_integer: # is odd return _monotonicity_odd_exponent( base, expo, mono_base, mono_expo, bounds_base, bounds_expo, ) return _monotonicity_noninteger_exponent( base, expo, mono_base, mono_expo, bounds_base, bounds_expo, )
def test_noninteger_negative_base(self, visitor, base, expo, mono_base): assume(not almosteq(expo, 0)) assume(not almosteq(expo, int(expo))) mono = self._result_with_base_expo( visitor, base, mono_base, I(None, 0), expo ) assert mono == M.Unknown
def __eq__(self, other): if isinstance(other, (int, float)): return self.__eq__(Interval(other, other)) if not isinstance(other, Interval): return False # pylint: disable=protected-access return (almosteq(self._lower, other._lower) and almosteq(self._upper, other._upper))
def _constant_expo_pow_convexity(expo, cvx_base, bounds_base): is_integer = almosteq(expo, int(expo)) is_even = almosteq(expo % 2, 0) if almosteq(expo, 0): return Convexity.Linear elif almosteq(expo, 1): return cvx_base elif is_integer and is_even: return _integer_even_expo_pow_convexity(expo, cvx_base, bounds_base) elif is_integer: return _integer_odd_expo_pow_convexity(expo, cvx_base, bounds_base) return _noninteger_expo_pow_convexity(expo, cvx_base, bounds_base)
def expr_equal(expr1, expr2): stack = [(expr1, expr2)] while len(stack) > 0: e1, e2 = stack.pop() if type(e1) != type(e2): return False if _is_leaf_node(e1): if isinstance(e1, Number): if not almosteq(e1, e2): return False if isinstance(e1, NumericConstant): if not almosteq(e1.value, e2.value): return False if isinstance(e1, _VarData): if id(e1) != id(e2): return False else: if len(e1._args_) != len(e2._args_): return False if isinstance(e1, LinearExpression): if len(e1.linear_vars) != len(e2.linear_vars): return False for v1, v2 in zip(e1.linear_vars, e2.linear_vars): if id(v1) != id(v2): return False for c1, c2 in zip(e2.linear_coefs, e2.linear_coefs): if not almosteq(c1, c2): return False if not almosteq(e1.constant, e2.constant): return False if isinstance(e1, UnaryFunctionExpression): if e1.name != e2.name: return False # all checks passed, check args for a1, a2 in zip(e1._args_, e2._args_): stack.append((a1, a2)) return True
def test_positive_lt_0_non_integer(self, visitor, base, expo, cvx, expected): expo = expo - 1e-5 # make it negative assume(not almosteq(expo, int(expo))) cvx = self._rule_result(visitor, base, cvx, M.Unknown, I(0, None), expo) assert cvx == expected
def apply(self, expr, ctx): num, den = expr.children if not self._valid_expression(num, den): return None a1, x, b1 = self._linear_components(num) a2, y, b2 = self._linear_components(den) if x is not None and y is not None: if x is not y: return None if x is not None: x_bound = ctx.bounds(x) elif y is not None: x_bound = ctx.bounds(y) else: raise RuntimeError('no variable in numerator or denonimator') dd_num = -2 * a2 * (a1 * b2 - a2 * b1) dd_den = a2 * x_bound + b2 if almosteq(dd_num, 0): return Convexity.Linear dd_bound = dd_den * (1 / dd_num) if dd_bound.is_nonpositive(): return Convexity.Concave elif dd_bound.is_nonnegative(): return Convexity.Convex # inconclusive return None
def _integer_positive_odd_expo_pow_convexity(expo, cvx_base, bounds_base): if almosteq(expo, 1): # pragma: no cover return cvx_base # we won't reach this because of the check in apply. elif cvx_base.is_convex() and bounds_base.is_nonnegative(): return Convexity.Convex elif cvx_base.is_concave() and bounds_base.is_nonpositive(): return Convexity.Concave return Convexity.Unknown
def _tan(self): if almostgte(self.size(), pi): return Interval(None, None) lower = self._lower % pi upper = lower + (self._upper - self._lower) new_lower = min_(tan(lower, RM.RD), tan(upper, RM.RD)) new_upper = max_(tan(lower, RM.RU), tan(upper, RM.RU)) if almosteq(lower, 0.5 * pi): new_lower = None if almosteq(upper, 0.5 * pi): new_upper = None if new_lower is not None and new_upper is not None: new = Interval(new_lower, new_upper) if 0.5 * pi in new or pi in new: return Interval(None, None) return Interval(new_lower, new_upper)
def _collect_expression_types(quadratic, linear): """Collect different expression types from quadratic and linear expression. Given an expression a0 x0^2 + a1 x1^2 + ...+ b0 x0 + b1 x1 + b2 x2 + ... Returns the quadratic univariate expressions like `a0 x0^2`, the bilinear expressions `c x1 x2`, and the linear expressions `bn xn`. :param quadratic: the quadratic expression :param linear: the linear expression :return: A tuple (univariate_expr, bilinear_terms, linear_terms) """ variables_coef = ComponentMap() for coef, var in zip(linear.linear_coefs, linear.linear_vars): variables_coef[var] = \ _VariableCoefficients(quadratic=0.0, linear=coef) non_univariate_terms = [] for term in quadratic.terms: if term.var1 is term.var2: var_coef = variables_coef.get(term.var1, None) if var_coef is None: non_univariate_terms.append(term) else: linear_coef = var_coef.linear var_coef = _VariableCoefficients(quadratic=term.coefficient, linear=linear_coef) variables_coef[term.var1] = var_coef else: non_univariate_terms.append(term) linear_terms = [(v, vc.linear) for v, vc in variables_coef.items() if almosteq(vc.quadratic, 0.0)] univariate_terms = [(v, vc.quadratic, vc.linear) for v, vc in variables_coef.items() if not almosteq(vc.quadratic, 0.0)] return univariate_terms, non_univariate_terms, linear_terms
def __pow__(self, pow_): # Rules and tests from Boost::interval if isinstance(pow_, Interval): if not almosteq(pow_.lower_bound, pow_.upper_bound): return Interval(None, None) pow_ = pow_.lower_bound return self.__pow__(pow_) if not almosteq(pow_, int(pow_)): return Interval(None, None) pow_ = int(pow_) if almosteq(pow_, 0.0): return Interval(1, 1) if pow_ < 0: return 1.0 / self.__pow__(-pow_) is_odd = (abs(pow_) % 2) == 1 if self.upper_bound < 0: # [-2, -1] yl = power(-self.upper_bound, pow_, RM.RD) yu = power(-self.lower_bound, pow_, RM.RU) if is_odd: return Interval(-yu, -yl) else: return Interval(yl, yu) elif self.lower_bound < 0: # [-1, 1] if is_odd: yl = power(-self.lower_bound, pow_, RM.RD) yu = power(self.upper_bound, pow_, RM.RU) return Interval(-yl, yu) else: return Interval( 0, power(max(-self.lower_bound, self.upper_bound), pow_, RM.RU)) else: # [1, 2] return Interval(power(self.lower_bound, pow_, RM.RD), power(self.upper_bound, pow_, RM.RU))
def apply(self, expr, bounds): assert len(expr.args) == 2 _, expo = expr.args if type(expo) not in nonpyomo_leaf_types: if not expo.is_constant(): return Interval(None, None) expo = expo.value is_even = almosteq(expo % 2, 0) is_positive = expo > 0 if is_even and is_positive: return Interval(0, None) return Interval(None, None)
def intersect(self, other, rel_eps=None, abs_eps=None): """Intersect this interval with another.""" if not isinstance(other, Interval): raise ValueError('intersect with non Interval value') new_lower = max(self._lower, other._lower) # pylint: disable=protected-access new_upper = min(self._upper, other._upper) # pylint: disable=protected-access if new_upper < new_lower: if almosteq(new_lower, new_upper, rel_eps=rel_eps, abs_eps=abs_eps): new_lower = new_upper else: raise EmptyIntervalError() return Interval(new_lower, new_upper)
def test_linear_with_constant_over_variable(coef, const): assume(coef != 0.0) assume(coef < MAX_NUM and const < MAX_NUM) rule = FractionalRule() x = PE(ET.Variable) num = PE(ET.Linear, [x], coefficients=[coef], constant_term=const) ctx = FractionalContext({ x: I(0, None), }) result = rule.apply(PE(ET.Division, [num, x]), ctx) if almosteq(const, 0): expected = Convexity.Linear elif const > 0: expected = Convexity.Convex elif const < 0: expected = Convexity.Concave
def _monotonicity_constant_base(base, _expo, _mono_base, mono_expo, _bounds_base, bounds_expo): # pylint: disable=too-many-return-statements, too-many-arguments if base < 0: return Monotonicity.Unknown elif almosteq(base, 0): return Monotonicity.Constant elif 0 < base < 1: if mono_expo.is_nondecreasing() and bounds_expo.is_nonpositive(): return Monotonicity.Nondecreasing elif mono_expo.is_nonincreasing() and bounds_expo.is_nonnegative(): return Monotonicity.Nondecreasing return Monotonicity.Unknown elif almostgte(base, 1): if mono_expo.is_nondecreasing() and bounds_expo.is_nonnegative(): return Monotonicity.Nondecreasing elif mono_expo.is_nonincreasing() and bounds_expo.is_nonpositive(): return Monotonicity.Nondecreasing return Monotonicity.Unknown return Monotonicity.Unknown # pragma: no cover
def apply(self, expr, bounds): base, expo = expr.args if type(expo) not in nonpyomo_leaf_types: if not expo.is_constant(): return None expo = expo.value if not almosteq(expo, 2): return None expr_bound = bounds[expr] # the bound of a square number is never negative, but check anyway to # avoid unexpected crashes. if not expr_bound.is_nonnegative(): return None sqrt_bound = expr_bound.sqrt() return [ Interval(-sqrt_bound.upper_bound, sqrt_bound.upper_bound), None, ]
def hash(self, f): f = make_number(f) if self.root is None: self.root = self._make_node(f) return self.root.hash curr_node = self.root while True: if almosteq(f, curr_node.num): return curr_node.hash elif almostlte(f, curr_node.num): if curr_node.left is None: new_node = self._make_node(f) curr_node.left = new_node return new_node.hash else: curr_node = curr_node.left else: if curr_node.right is None: new_node = self._make_node(f) curr_node.right = new_node return new_node.hash else: curr_node = curr_node.right
def is_zero(self): """Check if the interval is [0, 0].""" if self._is_zero is None: self._is_zero = \ almosteq(self._lower, 0) and almosteq(self._upper, 0) return self._is_zero
def test_positive_0_1_non_integer(self, visitor, base, expo): assume(not almosteq(expo, int(expo))) cvx = self._rule_result(visitor, base, C.Concave, M.Unknown, I(0, None), expo) assert cvx == C.Concave