class TestCos(object): def _result_with_mono_bounds(self, visitor, g, mono_g, bounds_g): mono = ComponentMap() mono[g] = mono_g bounds = ComponentMap() bounds[g] = bounds_g expr = pe.cos(g) matched, result = visitor.visit_expression(expr, mono, bounds) assert matched return result @given(expressions(), nonnegative_sin_bounds()) def test_nonincreasing_nonnegative_sin(self, visitor, g, bounds): mono = self._result_with_mono_bounds(visitor, g, M.Nonincreasing, bounds) assert mono.is_nondecreasing() @given(expressions(), nonnegative_sin_bounds()) def test_nondecreasing_nonnegative_sin(self, visitor, g, bounds): mono = self._result_with_mono_bounds(visitor, g, M.Nondecreasing, bounds) assert mono.is_nonincreasing() @given(expressions(), nonpositive_sin_bounds()) def test_nondecreasing_nonpositive_sin(self, visitor, g, bounds): mono = self._result_with_mono_bounds(visitor, g, M.Nondecreasing, bounds) assert mono.is_nondecreasing() @given(expressions(), nonpositive_sin_bounds()) def test_nonincreasing_nonpositive_sin(self, visitor, g, bounds): mono = self._result_with_mono_bounds(visitor, g, M.Nonincreasing, bounds) assert mono.is_nonincreasing()
class TestAtan(UnaryFunctionTest): @given(child=expressions()) def test_is_concave(self, visitor, child): cvx = self._rule_result(visitor, child, C.Concave, M.Unknown, I(0, None), pe.atan) assert cvx == C.Concave @pytest.mark.parametrize('bounds', [I(None, 0), I(None, None)]) @given(child=expressions()) def test_is_not_concave(self, visitor, child, bounds): cvx = self._rule_result(visitor, child, C.Concave, M.Unknown, bounds, pe.atan) assert cvx == C.Unknown @given(child=expressions()) def test_is_convex(self, visitor, child): cvx = self._rule_result(visitor, child, C.Convex, M.Unknown, I(None, 0), pe.atan) assert cvx == C.Convex @pytest.mark.parametrize('bounds', [I(0, None), I(None, None)]) @given(child=expressions()) def test_is_not_convex(self, visitor, child, bounds): cvx = self._rule_result(visitor, child, C.Convex, C.Unknown, bounds, pe.atan) assert cvx == C.Unknown
class TestAbs(UnaryFunctionTest): @pytest.mark.parametrize( 'mono,bounds', itertools.product( [M.Nondecreasing, M.Nonincreasing, M.Constant], [I(None, 0), I(0, None), I(None, None)])) @given(child=expressions()) def test_linear_child(self, visitor, child, mono, bounds): cvx = self._rule_result(visitor, child, C.Linear, mono, bounds, abs) assert cvx == C.Convex @pytest.mark.parametrize('mono,bounds,expected', [ (M.Unknown, I(0, None), C.Convex), (M.Unknown, I(None, 0), C.Concave), ]) @given(child=expressions()) def test_convex_child(self, visitor, child, mono, bounds, expected): cvx = self._rule_result(visitor, child, C.Convex, mono, bounds, abs) assert cvx == expected @pytest.mark.parametrize('mono,bounds,expected', [ (M.Unknown, I(0, None), C.Concave), (M.Unknown, I(None, 0), C.Convex), ]) @given(child=expressions()) def test_concave_child(self, visitor, child, mono, bounds, expected): cvx = self._rule_result(visitor, child, C.Concave, mono, bounds, abs) assert cvx == expected
class TestExp(UnaryFunctionTest): @pytest.mark.parametrize( 'cvx,mono,bounds', itertools.product( [C.Convex, C.Linear], [M.Nondecreasing, M.Nonincreasing, M.Constant, M.Unknown], [I(0, None), I(None, 0), I(None, None)], ) ) @given(child=expressions()) def test_convex_child(self, visitor, child, cvx, mono, bounds): cvx = self._rule_result(visitor, child, cvx, mono, bounds, pe.exp) assert cvx == C.Convex @pytest.mark.parametrize( 'cvx,mono,bounds', itertools.product( [C.Concave, C.Unknown], [M.Nondecreasing, M.Nonincreasing, M.Constant, M.Unknown], [I(0, None), I(None, 0), I(None, None)], ) ) @given(child=expressions()) def test_convex_child(self, visitor, child, cvx, mono, bounds): cvx = self._rule_result(visitor, child, cvx, mono, bounds, pe.exp) assert cvx == C.Unknown
class TestPowConstantBase(object): def _result_with_base_expo(self, visitor, base, expo, mono_expo, bounds_expo): rule = PowerRule() mono = ComponentMap() mono[base] = M.Constant mono[expo] = mono_expo bounds = ComponentMap() bounds[base] = I(base, base) bounds[expo] = bounds_expo expr = base ** expo assume(isinstance(expr, PowExpression)) matched, result = visitor.visit_expression(expr, mono, bounds) assert matched return result @pytest.mark.parametrize( 'mono_expo,bounds_expo', itertools.product( [M.Nondecreasing, M.Nonincreasing, M.Unknown], [I(None, 0), I(0, None), I(None, None)] ) ) @given( base=reals(max_value=-0.01, allow_infinity=False), expo=expressions(), ) def test_negative_base(self, visitor, base, expo, mono_expo, bounds_expo): mono = self._result_with_base_expo(visitor, base, expo, mono_expo, bounds_expo) assert mono == M.Unknown @pytest.mark.parametrize('mono_expo,bounds_expo,expected', [ (M.Nondecreasing, I(None, 0), M.Nondecreasing), (M.Nondecreasing, I(0, None), M.Unknown), (M.Nonincreasing, I(0, None), M.Nondecreasing), (M.Nonincreasing, I(None, 0), M.Unknown), ]) @given( base=reals(min_value=0.01, max_value=0.999), expo=expressions(), ) def test_base_between_0_and_1(self, visitor, base, expo, mono_expo, bounds_expo, expected): mono = self._result_with_base_expo(visitor, base, expo, mono_expo, bounds_expo) assert mono == expected @pytest.mark.parametrize('mono_expo,bounds_expo,expected', [ (M.Nondecreasing, I(0, None), M.Nondecreasing), (M.Nondecreasing, I(None, 0), M.Unknown), (M.Nonincreasing, I(None, 0), M.Nondecreasing), (M.Nonincreasing, I(0, None), M.Unknown), ]) @given( base=reals(min_value=1.01, allow_infinity=False), expo=expressions(), ) def test_base_gt_1(self, visitor, base, expo, mono_expo, bounds_expo, expected): mono = self._result_with_base_expo(visitor, base, expo, mono_expo, bounds_expo) assert mono == expected
class TestCos(UnaryFunctionTest): @given(expressions()) def test_bound_size_too_big(self, visitor, child): cvx = self._rule_result(visitor, child, C.Linear, M.Constant, I(-2, 2), pe.cos) assert cvx == C.Unknown @given(expressions()) def test_bound_opposite_sign(self, visitor, child): cvx = self._rule_result(visitor, child, C.Linear, M.Constant, I(pi/2-0.1, pi/2+0.1), pe.cos) assert cvx == C.Unknown @given(expressions()) def test_positive_cos_linear_child(self, visitor, child): cvx = self._rule_result(visitor, child, C.Linear, M.Constant, I(0, 0.5*pi), pe.cos) assert cvx == C.Concave @given(expressions()) def test_positive_cos_convex_child_but_wrong_interval(self, visitor, child): cvx = self._rule_result(visitor, child, C.Convex, M.Unknown, I(0, 0.5*pi), pe.cos) assert cvx == C.Unknown @given(expressions()) def test_positive_cos_convex_child(self, visitor, child): cvx = self._rule_result(visitor, child, C.Convex, M.Unknown, I(1.5*pi, 2*pi), pe.cos) assert cvx == C.Concave @given(expressions()) def test_positive_cos_concave_child(self, visitor, child): cvx = self._rule_result(visitor, child, C.Concave, M.Unknown, I(0, 0.5*pi), pe.cos) assert cvx == C.Concave @given(expressions()) def test_negative_cos_linear_child(self, visitor, child): cvx = self._rule_result(visitor, child, C.Linear, M.Constant, I(0.6*pi, 1.4*pi), pe.cos) assert cvx == C.Convex @given(expressions()) def test_negative_cos_concave_child_but_wrong_interval(self, visitor, child): cvx = self._rule_result(visitor, child, C.Concave, M.Unknown, I(0.6*pi, 0.9*pi), pe.cos) assert cvx == C.Unknown @given(expressions()) def test_negative_cos_concave_child(self, visitor, child): cvx = self._rule_result(visitor, child, C.Concave, M.Unknown, I(1.1*pi, 1.4*pi), pe.cos) assert cvx == C.Convex @given(expressions()) def test_negative_cos_convex_child(self, visitor, child): cvx = self._rule_result(visitor, child, C.Convex, M.Unknown, I(0.6*pi, 0.9*pi), pe.cos) assert cvx == C.Convex
class TestSum(object): def _result_with_terms(self, visitor, expr_with_monos): children = [c for c, _ in expr_with_monos] monos = [m for _, m in expr_with_monos] monotonicity = ComponentMap() for child, mono in expr_with_monos: monotonicity[child] = mono expr = SumExpression(children) matched, result = visitor.visit_expression(expr, monotonicity, None) assert matched return result @given( st.lists( st.tuples(expressions(), st.just(M.Nondecreasing)), min_size=1, ), st.lists( st.tuples(expressions(), st.one_of(st.just(M.Nondecreasing), st.just(M.Constant)))), ) def test_nondecreasing(self, visitor, a, b): mono = self._result_with_terms(visitor, a + b) assert mono.is_nondecreasing() @given( st.lists(st.tuples(expressions(), st.just(M.Nonincreasing)), min_size=1), st.lists( st.tuples(expressions(), st.one_of(st.just(M.Nonincreasing), st.just(M.Constant)))), ) def test_nonincreasing(self, visitor, a, b): mono = self._result_with_terms(visitor, a + b) assert mono.is_nonincreasing() @given( st.lists(st.tuples(expressions(), st.just(M.Constant)), min_size=1), ) def test_constant(self, visitor, a): mono = self._result_with_terms(visitor, a) assert mono.is_constant() @given( st.lists(st.tuples(expressions(), st.just(M.Nondecreasing)), min_size=1), st.lists(st.tuples( expressions(), st.just(M.Nonincreasing), ), min_size=1), ) def test_unknown(self, visitor, a, b): mono = self._result_with_terms(visitor, a + b) assert mono.is_unknown()
class TestTan(UnaryFunctionTest): @given(expressions()) def test_bound_size_too_big(self, visitor, child): cvx = self._rule_result(visitor, child, C.Linear, M.Constant, I(-2, 2), pe.tan) assert cvx == C.Unknown @given(expressions()) def test_bound_opposite_sign(self, visitor, child): cvx = self._rule_result(visitor, child, C.Linear, M.Constant, I(-0.1, 0.1), pe.tan) assert cvx == C.Unknown @given(expressions()) def test_positive_tan_convex_child(self, visitor, child): cvx = self._rule_result(visitor, child, C.Convex, M.Unknown, I(pi, 1.5*pi), pe.tan) assert cvx == C.Convex @given(expressions()) def test_positive_tan_concave_child(self, visitor, child): cvx = self._rule_result(visitor, child, C.Concave, M.Unknown, I(pi, 1.5*pi), pe.tan) assert cvx == C.Unknown @given(expressions()) def test_negative_tan_convex_child(self, visitor, child): cvx = self._rule_result(visitor, child, C.Convex, M.Unknown, I(-1.5*pi, -pi), pe.tan) assert cvx == C.Unknown @given(expressions()) def test_negative_tan_concave_child(self, visitor, child): cvx = self._rule_result(visitor, child, C.Concave, M.Unknown, I(-0.49*pi, -0.1), pe.tan) assert cvx == C.Concave
class TestLog(UnaryFunctionTest): @pytest.mark.parametrize( 'cvx,mono', itertools.product( [C.Concave, C.Linear], [M.Nondecreasing, M.Nonincreasing, M.Constant, M.Unknown], )) @given(child=expressions()) def test_concave_child(self, visitor, child, cvx, mono): cvx = self._rule_result(visitor, child, cvx, mono, I(0, None), pe.log) assert cvx == C.Concave @pytest.mark.parametrize( 'cvx,mono', itertools.product( [C.Convex, C.Unknown], [M.Nondecreasing, M.Nonincreasing, M.Constant, M.Unknown], ), ) @given(child=expressions()) def test_non_concave_child(self, visitor, child, cvx, mono): cvx = self._rule_result(visitor, child, cvx, mono, I(0, None), pe.log) assert cvx == C.Unknown
class TestSum: def _rule_result(self, visitor, children_with_cvx): children = [c for c, _ in children_with_cvx] convexity = ComponentMap() for child, cvx in children_with_cvx: convexity[child] = cvx expr = SumExpression(children) matched, result = visitor.visit_expression(expr, convexity, None, None) assert matched return result @given( st.lists(st.tuples(expressions(), st.just(C.Convex)), min_size=1), st.lists(st.tuples(expressions(), st.just(C.Linear)), ), ) def test_convex(self, visitor, a, b): cvx = self._rule_result(visitor, a + b) assert cvx.is_convex() @given( st.lists(st.tuples(expressions(), st.just(C.Concave)), min_size=1), st.lists(st.tuples(expressions(), st.just(C.Linear))), ) def test_concave(self, visitor, a, b): cvx = self._rule_result(visitor, a + b) assert cvx.is_concave() @given( st.lists(st.tuples(expressions(), st.just(C.Linear)), min_size=1), ) def test_linear(self, visitor, a): cvx = self._rule_result(visitor, a) assert cvx.is_linear() @given( st.lists(st.tuples(expressions(), st.just(C.Concave)), min_size=1), st.lists(st.tuples(expressions(), st.just(C.Convex)), min_size=1), ) def test_unknown(self, visitor, a, b): cvx = self._rule_result(visitor, a + b) assert cvx.is_unknown()
class TestProduct: def _rule_result(self, visitor, f, g, cvx_f, cvx_g, mono_f, mono_g, bounds_f, bounds_g): convexity = ComponentMap() convexity[f] = cvx_f convexity[g] = cvx_g mono = ComponentMap() mono[f] = mono_f mono[g] = mono_g bounds = ComponentMap() bounds[f] = bounds_f bounds[g] = bounds_g expr = f * g assume(isinstance(expr, ProductExpression)) matched, result = visitor.visit_expression(expr, convexity, mono, bounds) assert matched return result @pytest.mark.parametrize('cvx_f,bounds_g,expected', [ (C.Convex, I(0, None), C.Convex), (C.Convex, I(None, 0), C.Concave), (C.Concave, I(0, None), C.Concave), (C.Concave, I(None, 0), C.Convex), (C.Linear, I(0, None), C.Linear), (C.Linear, I(None, 0), C.Linear), (C.Unknown, I(0, None), C.Unknown), (C.Unknown, I(None, 0), C.Unknown), ]) @given(f=expressions(), g=constants()) def test_product_with_constant(self, visitor, f, g, cvx_f, bounds_g, expected): assume(f.is_expression_type() and not isinstance(f, MonomialTermExpression)) cvx_g = C.Linear # g is constant mono_f = M.Unknown mono_g = M.Constant bounds_f = I(None, None) assert self._rule_result(visitor, f, g, cvx_f, cvx_g, mono_f, mono_g, bounds_f, bounds_g) == expected assert self._rule_result(visitor, g, f, cvx_g, cvx_f, mono_g, mono_f, bounds_g, bounds_f) == expected @pytest.mark.parametrize('cvx_f,bounds_f,expected', [ (C.Linear, I(None, None), C.Convex), (C.Linear, I(0, None), C.Convex), (C.Linear, I(None, 0), C.Convex), (C.Convex, I(None, None), C.Unknown), (C.Convex, I(0, None), C.Convex), (C.Convex, I(None, 0), C.Unknown), (C.Concave, I(None, None), C.Unknown), (C.Concave, I(0, None), C.Unknown), (C.Concave, I(None, 0), C.Convex), ]) @given(f=expressions()) def test_product_with_itself(self, visitor, f, cvx_f, bounds_f, expected): convexity = ComponentMap() convexity[f] = cvx_f bounds = ComponentMap() bounds[f] = bounds_f expr = f * f matched, result = visitor.visit_expression(expr, convexity, None, bounds) assert matched assert result == expected @given(var=variables(), coef=reals()) def test_product_with_itself_with_coeff(self, visitor, var, coef): if coef > 0: expected = C.Convex else: expected = C.Concave rule = ProductRule() g = coef * var assume(isinstance(g, ProductExpression)) matched, result = visitor.visit_expression(ProductExpression([var, g]), None, None, None) assert result == expected matched, result = visitor.visit_expression(ProductExpression([g, var]), None, None, None) assert result == expected @given(var=variables(), vars_with_coef=st.lists(st.tuples( variables(), constants(), ), )) def test_product_linear_by_var(self, visitor, var, vars_with_coef): rule = ProductRule() mono = ComponentMap() lin = sum(v * c for v, c in vars_with_coef) mono[var] = M.Nondecreasing mono[lin] = M.Nondecreasing matched, result = visitor.visit_expression( ProductExpression([var, lin]), None, mono, None) assert result == C.Unknown matched, result = visitor.visit_expression( ProductExpression([lin, var]), None, mono, None) assert result == C.Unknown
(C.Concave, True, True, C.Unknown), (C.Unknown, True, True, C.Unknown), # g(x) <= u is the same as g(x) (C.Linear, False, True, C.Linear), (C.Convex, False, True, C.Convex), (C.Concave, False, True, C.Concave), (C.Unknown, False, True, C.Unknown), # l <= g(x) is the negation of g(x) (C.Linear, True, False, C.Linear), (C.Convex, True, False, C.Concave), (C.Concave, True, False, C.Convex), (C.Unknown, True, False, C.Unknown), ]) @given(child=expressions()) def test_constraint(visitor, child, cvx_child, bounded_below, bounded_above, expected): if bounded_below: lower_bound = 0.0 else: lower_bound = None if bounded_above: upper_bound = 1.0 else: upper_bound = None expr = Constraint('constr', lower_bound, upper_bound, children=[child]) convexity = ComponentMap()
class TestPowConstantExponent(object): def _result_with_base_expo(self, visitor, base, mono_base, bounds_base, expo): mono = ComponentMap() mono[base] = mono_base mono[expo] = M.Constant bounds = ComponentMap() bounds[base] = bounds_base bounds[expo] = I(expo, expo) expr = PowExpression([base, expo]) matched, result = visitor.visit_expression(expr, mono, bounds) assert matched return result @pytest.mark.parametrize( 'mono_base,bounds_base', itertools.product([M.Nonincreasing, M.Nondecreasing], [I(None, 0), I(0, None), I(None, None)]) ) @given(base=expressions()) def test_exponent_equals_1(self, visitor, base, mono_base, bounds_base): mono = self._result_with_base_expo(visitor, base, mono_base, bounds_base, 1.0) assert mono == mono_base @pytest.mark.parametrize( 'mono_base,bounds_base', itertools.product([M.Nonincreasing, M.Nondecreasing], [I(None, 0), I(0, None), I(None, None)]) ) @given(base=expressions()) def test_exponent_equals_0(self, visitor, base, mono_base, bounds_base): mono = self._result_with_base_expo(visitor, base, mono_base, bounds_base, 0.0) assert mono == M.Constant @pytest.mark.parametrize('mono_base,bounds_base,expected', [ (M.Nondecreasing, I(0, None), M.Nondecreasing), (M.Nonincreasing, I(None, 0), M.Nondecreasing), (M.Nondecreasing, I(None, 0), M.Nonincreasing), (M.Nonincreasing, I(0, None), M.Nonincreasing), ]) @given( base=expressions(), expo=st.integers(min_value=1, max_value=1000), ) def test_positive_even_integer(self, visitor, base, expo, mono_base, bounds_base, expected): mono = self._result_with_base_expo(visitor, base, mono_base, bounds_base, 2*expo) assert mono == expected @pytest.mark.parametrize('mono_base,bounds_base,expected', [ (M.Nondecreasing, I(0, None), M.Nonincreasing), (M.Nonincreasing, I(None, 0), M.Nonincreasing), (M.Nondecreasing, I(None, 0), M.Nondecreasing), (M.Nonincreasing, I(0, None), M.Nondecreasing), ]) @given( base=expressions(), expo=st.integers(min_value=1, max_value=1000) ) def test_negative_even_integer(self, visitor, base, expo, mono_base, bounds_base, expected): mono = self._result_with_base_expo(visitor, base, mono_base, bounds_base, -2*expo) assert mono == expected @pytest.mark.parametrize('mono_base,expected', [ (M.Nondecreasing, M.Nondecreasing), (M.Nonincreasing, M.Nonincreasing), ]) @given( base=expressions(), expo=st.integers(min_value=1, max_value=1000) ) def test_positive_odd_integer(self, visitor, base, expo, mono_base, expected): mono = self._result_with_base_expo(visitor, base, mono_base, I(None, None), 2*expo+1) assert mono == expected @pytest.mark.parametrize('mono_base,expected', [ (M.Nondecreasing, M.Nonincreasing), (M.Nonincreasing, M.Nondecreasing), ]) @given( base=expressions(), expo=st.integers(min_value=1, max_value=1000) ) def test_negative_odd_integer(self, visitor, base, expo, mono_base, expected): mono = self._result_with_base_expo( visitor, base, mono_base, I(None, None), -2*expo+1 ) assert mono == expected @pytest.mark.parametrize('mono_base', [M.Nondecreasing, M.Nondecreasing]) @given( base=expressions(), expo=reals(allow_infinity=False, min_value=-1e5, max_value=1e5), ) 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 @pytest.mark.parametrize('mono_base,expected', [ (M.Nondecreasing, M.Nondecreasing), (M.Nonincreasing, M.Nonincreasing) ]) @given( base=expressions(), expo=reals(allow_infinity=False, min_value=1e-5, max_value=1e-5) ) def test_positive_noninteger(self, visitor, base, expo, mono_base, expected): mono = self._result_with_base_expo(visitor, base, mono_base, I(0, None), expo) assert mono == expected @pytest.mark.parametrize('mono_base,expected', [ (M.Nonincreasing, M.Nondecreasing), (M.Nondecreasing, M.Nonincreasing) ]) @given( base=expressions(), expo=reals(allow_infinity=False, min_value=-1e5, max_value=-1e-5) ) def test_negative_noninteger(self, visitor, base, expo, mono_base, expected): mono = self._result_with_base_expo(visitor, base, mono_base, I(0, None), expo) assert mono == expected
assert result.is_nondecreasing() and (not result.is_constant()) def test_constant_is_constant(): rule = ConstantRule() result = rule.apply(PE(ET.Constant), None, None) assert result.is_constant() @pytest.mark.parametrize('expr_mono,expected_mono', [ (M.Nondecreasing, M.Nondecreasing), (M.Nonincreasing, M.Nonincreasing), (M.Constant, M.Constant), (M.Unknown, M.Unknown), ]) @given(child=expressions()) def test_lte_constraint(visitor, child, expr_mono, expected_mono): mono = ComponentMap() mono[child] = expr_mono expr = Constraint('cons', None, 0.0, children=[child]) matched, result = visitor.visit_expression(expr, mono, None) assert matched assert result == expected_mono @pytest.mark.parametrize('expr_mono,expected_mono', [ (M.Nondecreasing, M.Nonincreasing), (M.Nonincreasing, M.Nondecreasing), (M.Constant, M.Constant), (M.Unknown, M.Unknown), ])
class TestPowConstantBase: def _rule_result(self, visitor, base, expo, cvx_expo, mono_expo, bounds_expo): convexity = ComponentMap() convexity[expo] = cvx_expo convexity[base] = C.Linear mono = ComponentMap() mono[expo] = mono_expo mono[base] = M.Constant bounds = ComponentMap() bounds[base] = I(base, base) bounds[expo] = bounds_expo expr = PowExpression([base, expo]) matched, result = visitor.visit_expression(expr, convexity, mono, bounds) assert matched return result @pytest.mark.parametrize( 'cvx_expo,mono_expo,bounds_expo', itertools.product( [C.Convex, C.Concave, C.Linear, C.Unknown], [M.Nondecreasing, M.Nonincreasing, M.Unknown], [I(None, 0), I(0, None), I(None, None)], )) @given( base=reals(max_value=-0.01, allow_infinity=False), expo=expressions(), ) def test_negative_base(self, visitor, base, expo, cvx_expo, mono_expo, bounds_expo): cvx = self._rule_result(visitor, base, expo, cvx_expo, mono_expo, bounds_expo) assert cvx == C.Unknown @pytest.mark.parametrize( 'cvx_expo,mono_expo,bounds_expo', itertools.product( [C.Convex, C.Concave, C.Linear, C.Unknown], [M.Nondecreasing, M.Nonincreasing, M.Unknown], [I(None, 0), I(0, None), I(None, None)], )) @given( base=reals(min_value=0.001, max_value=0.999), expo=expressions(), ) def test_base_between_0_and_1(self, visitor, base, expo, cvx_expo, mono_expo, bounds_expo): if cvx_expo == C.Concave or cvx_expo == C.Linear: expected = C.Convex else: expected = C.Unknown cvx = self._rule_result(visitor, base, expo, cvx_expo, mono_expo, bounds_expo) assert cvx == expected @pytest.mark.parametrize( 'cvx_expo,mono_expo,bounds_expo', itertools.product( [C.Convex, C.Concave, C.Linear, C.Unknown], [M.Nondecreasing, M.Nonincreasing, M.Unknown], [I(None, 0), I(0, None), I(None, None)], )) @given( base=reals(min_value=1, allow_infinity=False), expo=expressions(), ) def test_base_gt_1(self, visitor, base, expo, cvx_expo, mono_expo, bounds_expo): if cvx_expo == C.Convex or cvx_expo == C.Linear: expected = C.Convex else: expected = C.Unknown cvx = self._rule_result(visitor, base, expo, cvx_expo, mono_expo, bounds_expo) assert cvx == expected
class TestPowConstantExponent(object): def _rule_result(self, visitor, base, cvx_base, mono_base, bounds_base, expo): convexity = ComponentMap() convexity[base] = cvx_base convexity[expo] = C.Linear mono = ComponentMap() mono[base] = mono_base mono[expo] = M.Constant bounds = ComponentMap() bounds[base] = bounds_base bounds[expo] = I(expo, expo) expr = PowExpression([base, expo]) matched, result = visitor.visit_expression(expr, convexity, mono, bounds) assert matched return result @pytest.mark.parametrize( 'cvx_base,mono_base,bounds_base', itertools.product( [C.Convex, C.Concave, C.Linear, C.Unknown], [M.Nondecreasing, M.Nonincreasing, M.Unknown], [I(None, 0), I(0, None), I(None, None)], )) @given(base=expressions()) def test_exponent_equals_0(self, visitor, base, cvx_base, mono_base, bounds_base): cvx = self._rule_result(visitor, base, cvx_base, mono_base, bounds_base, 0.0) assert cvx == C.Linear @pytest.mark.parametrize( 'cvx_base,mono_base,bounds_base', itertools.product( [C.Convex, C.Concave, C.Linear, C.Unknown], [M.Nondecreasing, M.Nonincreasing, M.Unknown], [I(None, 0), I(0, None), I(None, None)], )) @given(base=expressions()) def test_exponent_equals_1(self, visitor, base, cvx_base, mono_base, bounds_base): cvx = self._rule_result(visitor, base, cvx_base, mono_base, bounds_base, 1.0) assert cvx == cvx_base @pytest.mark.parametrize('cvx_base,mono_base,bounds_base,expected', [ (C.Linear, M.Nondecreasing, I(None, None), C.Convex), (C.Convex, M.Unknown, I(0, None), C.Convex), (C.Convex, M.Unknown, I(None, 0), C.Unknown), (C.Concave, M.Unknown, I(0, None), C.Unknown), (C.Concave, M.Unknown, I(None, 0), C.Convex), ]) @given( base=expressions(), expo=st.integers(min_value=1), ) def test_positive_even_integer(self, visitor, base, expo, cvx_base, mono_base, bounds_base, expected): cvx = self._rule_result(visitor, base, cvx_base, mono_base, bounds_base, 2 * expo) assert cvx == expected @pytest.mark.parametrize('cvx_base,mono_base,bounds_base,expected', [ (C.Convex, M.Unknown, I(None, 0), C.Convex), (C.Convex, M.Unknown, I(0, None), C.Concave), (C.Concave, M.Unknown, I(0, None), C.Convex), (C.Concave, M.Unknown, I(None, 0), C.Concave), ]) @given( base=expressions(), expo=st.integers(min_value=1), ) def test_negative_even_integer(self, visitor, base, expo, cvx_base, mono_base, bounds_base, expected): cvx = self._rule_result(visitor, base, cvx_base, mono_base, bounds_base, -2 * expo) assert cvx == expected @pytest.mark.parametrize('cvx_base,mono_base,bounds_base,expected', [ (C.Convex, M.Unknown, I(0, None), C.Convex), (C.Convex, M.Unknown, I(None, 0), C.Unknown), (C.Concave, M.Unknown, I(None, 0), C.Concave), (C.Concave, M.Unknown, I(0, None), C.Unknown), ]) @given( base=expressions(), expo=st.integers(min_value=1), ) def test_positive_odd_integer(self, visitor, base, expo, cvx_base, mono_base, bounds_base, expected): cvx = self._rule_result(visitor, base, cvx_base, mono_base, bounds_base, 2 * expo + 1) assert cvx == expected @pytest.mark.parametrize('cvx_base,mono_base,bounds_base,expected', [ (C.Concave, M.Unknown, I(0, None), C.Convex), (C.Concave, M.Unknown, I(None, 0), C.Unknown), (C.Convex, M.Unknown, I(None, 0), C.Concave), (C.Convex, M.Unknown, I(0, None), C.Unknown), ]) @given( base=expressions(), expo=st.integers(min_value=1), ) def test_negative_odd_integer(self, visitor, base, expo, cvx_base, mono_base, bounds_base, expected): cvx = self._rule_result(visitor, base, cvx_base, mono_base, bounds_base, -2 * expo + 1) assert cvx == expected @given( base=expressions(), expo=reals(min_value=1, allow_infinity=False), ) def test_positive_gt_1_non_integer_negative_base(self, visitor, base, expo): expo = expo + 1e-6 assume(expo != int(expo)) cvx = self._rule_result(visitor, base, C.Convex, M.Unknown, I(None, -1), expo) assert cvx == C.Unknown @given( base=expressions(), expo=reals(min_value=1, allow_infinity=False), ) def test_positive_gt_1_non_integer(self, visitor, base, expo): expo = expo + 1e-5 # make it positive assume(expo != int(expo)) cvx = self._rule_result(visitor, base, C.Convex, M.Unknown, I(0, None), expo) assert cvx == C.Convex @pytest.mark.parametrize('cvx,expected', [(C.Convex, C.Concave), (C.Concave, C.Convex)]) @given( base=expressions(), expo=reals(max_value=0, allow_infinity=False), ) 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 @given( base=expressions(), expo=reals(min_value=0, max_value=1, allow_infinity=False), ) 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
return PolynomialDegree(degree) def test_variable_is_always_1(visitor): matched, result = visitor.visit_expression(pe.Var(), None) assert matched assert result.degree == 1 def test_constant_is_always_0(visitor): matched, result = visitor.visit_expression(123.0, None) assert matched assert result.degree == 0 @given(expressions(), polynomial_degrees()) def test_objective(visitor, child, child_degree): expr = Objective('obj', children=[child]) poly = ComponentMap() poly[child] = child_degree matched, result = visitor.visit_expression(expr, poly) assert matched assert result == child_degree @given(expressions(), polynomial_degrees()) def test_constraint(visitor, child, child_degree): expr = Constraint('cons', 0.0, None, children=[child]) poly = ComponentMap() poly[child] = child_degree matched, result = visitor.visit_expression(expr, poly)