Example #1
0
 def test_standard_monomials(self):
     x = standard_monomials(3)
     y_actual = np.prod(x)
     y_expect = Polynomial({(1, 1, 1): 1})
     assert TestPolynomials.are_equal(y_actual, y_expect)
     x = standard_monomials(2)
     y_actual = np.sum(x)**2
     y_expect = Polynomial({(2, 0): 1, (1, 1): 2, (0, 2): 1})
     assert TestPolynomials.are_equal(y_actual, y_expect)
Example #2
0
 def test_polynomial_exponentiation(self):
     p = Polynomial({(0, ): -1, (1, ): 1})
     # square of (x-1)
     res = p**2 - Polynomial({(0, ): 1, (1, ): -2, (2, ): 1})
     res.remove_terms_with_zero_as_coefficient()
     assert res.m == 1 and set(res.c) == {0}
     # cube of (2x+5)
     p = Polynomial({(0, ): 5, (1, ): 2})
     res = p**3 - Polynomial({(0, ): 125, (1, ): 150, (2, ): 60, (3, ): 8})
     res.remove_terms_with_zero_as_coefficient()
     assert res.m == 1 and set(res.c) == {0}
 def test_unconstrained_1(self):
     alpha = np.array([[0, 0], [1, 1], [2, 2], [0, 2], [2, 0]])
     c = np.array([1, -3, 1, 4, 4])
     p = Polynomial(alpha, c)
     res0 = primal_dual_unconstrained(p, level=0)
     assert abs(res0[0] - res0[1]) <= 1e-6
     c = np.array([1, 3, 1, 4, 4])
     # ^ change the sign in a way that won't affect the signomial
     # representative
     p = Polynomial(alpha, c)
     res1 = primal_dual_unconstrained(p, level=0)
     assert abs(res1[0] - res1[1]) <= 1e-6
     assert abs(res0[0] - res0[1]) <= 1e-6
Example #4
0
 def test_addition_and_subtraction(self):
     # data for tests
     s0 = Polynomial(np.array([[0], [1], [2]]), np.array([1, 2, 3]))
     t0 = Polynomial(np.array([[4]]), np.array([5]))
     # tests
     s = s0 - s0
     s.remove_terms_with_zero_as_coefficient()
     assert s.m == 1 and set(s.c) == {0}
     s = -s0 + s0
     s.remove_terms_with_zero_as_coefficient()
     assert s.m == 1 and set(s.c) == {0}
     s = s0 + t0
     assert s.alpha_c == {(0, ): 1, (1, ): 2, (2, ): 3, (4, ): 5}
Example #5
0
 def test_polynomial_multiplication(self):
     # data for tests
     s0 = Polynomial(np.array([[0], [1], [2]]), np.array([1, 2, 3]))
     t0 = Polynomial(np.array([[1]]), np.array([1]))
     q0 = Polynomial(np.array([[5]]), np.array([0]))
     # tests
     s = s0 * t0
     s.remove_terms_with_zero_as_coefficient()
     assert s.alpha_c == {(1, ): 1, (2, ): 2, (3, ): 3, (0, ): 0}
     s = t0 * s0
     s.remove_terms_with_zero_as_coefficient()
     assert s.alpha_c == {(1, ): 1, (2, ): 2, (3, ): 3, (0, ): 0}
     s = s0 * q0
     s.remove_terms_with_zero_as_coefficient()
     assert s.alpha_c == {(0, ): 0}
Example #6
0
def sage_poly_multiplier_search(p, level=1):
    """
    Suppose we have a nonnegative polynomial p that is not SAGE. Do we have an alternative
    to proving that p is nonnegative other than moving up the usual SAGE hierarchy?
    Indeed we do. We can define a multiplier

        mult = Polynomial(alpha_hat, c_tilde)

    where the rows of alpha_hat are all "level"-wise sums of rows from s.alpha, and c_tilde is a CVXPY Variable
    defining a nonzero SAGE polynomial. Then we can check if p_mod := p * mult is SAGE for any choice of c_tilde.

    :param p: a Polynomial object
    :param level: a nonnegative integer
    :return: a CVXPY Problem that is feasible iff s * mult is SAGE for some SAGE multiplier Polynomial "mult".
    """
    p.remove_terms_with_zero_as_coefficient()
    constraints = []
    # Make the multipler polynomial (and require that it be SAGE)
    mult_alpha = hierarchy_e_k([p], k=level)
    c_tilde = cvxpy.Variable(mult_alpha.shape[0], name='c_tilde')
    mult = Polynomial(mult_alpha, c_tilde)
    mult_sr, mult_cons = mult.sig_rep
    constraints += mult_cons
    constraints += relative_c_sage(mult_sr)
    constraints.append(cvxpy.sum(c_tilde) >= 1)
    # Make "p_mod := p * mult", and require that it be SAGE.
    poly_under_test = mult * p
    poly_sr, poly_cons = poly_under_test.sig_rep
    constraints += poly_cons
    constraints += relative_c_sage(poly_sr)
    # noinspection PyTypeChecker
    obj = cvxpy.Maximize(0)
    prob = cvxpy.Problem(obj, constraints)
    return prob
Example #7
0
 def test_sigrep_3(self):
     alpha = np.random.randint(low=1, high=10, size=(10, 3))
     alpha *= 2
     c = np.random.randn(10)
     p = Polynomial(alpha, c)
     # The signomial representative has the same exponents and coeffs.
     sr, sr_cons = p.sig_rep
     assert len(sr_cons) == 0
     assert p.alpha_c == sr.alpha_c
 def test_sigrep_2(self):
     c33 = cvxpy.Variable(shape=(), name='c33')
     p = Polynomial({(0, 0): 0, (1, 1): -1, (3, 3): c33})
     sr, sr_cons = p.sig_rep
     assert len(sr_cons) == 2
     var_names = set(v.name() for v in sr_cons[0].variables())
     var_names.union(set(v.name() for v in sr_cons[1].variables()))
     for v in var_names:
         assert v == 'c33' or v[:-3] == 'sig_rep_coeff'
     assert sr.alpha_c[(1, 1)] == -1
Example #9
0
def make_poly_lagrangian(f, gs, p, q, add_constant_poly=True):
    """
    Given a problem \inf{ f(x) : g(x) >= 0 for g in gs}, construct the q-fold constraints H,
    and the lagrangian
        L = f - \gamma - \sum_{h \in H} s_h * h
    where \gamma and the coefficients on Polynomials s_h are CVXPY Variables.

    :param f: a Polynomial (or a constant numeric type).
    :param gs: a nonempty list of Polynomials.
    :param p: a nonnegative integer. Defines the exponent set of the Polynomials s_h.
    :param q: a positive integer. Defines "H" as all products of q elements from gs.
    :param add_constant_poly: a boolean. If True, makes sure that "gs" contains a
    Polynomial that is identically equal to 1.

    :return: a Polynomial object with coefficients as affine expressions of CVXPY Variables.
    The coefficients will either be optimized directly (in the case of constrained_sage_primal),
    or simply used to determine appropriate dual variables (in the case of constrained_sage_dual).

    Also return a list of pairs of Polynomial objects. If the pair (p1, p2) is in this list,
    then p1 is a generalized Lagrange multiplier (which we should constrain to be nonnegative,
    somehow), and p2 represents a constraint p2(x) >= 0 in the polynomial program after taking
    products of the gs.
    """
    if not all([isinstance(g, Polynomial) for g in gs]):
        raise RuntimeError('Constraints must be Polynomial objects.')
    if add_constant_poly:
        gs.append(Polynomial({(0, ) * gs[0].n: 1}))
    if not isinstance(f, Polynomial):
        f = Polynomial({(0, ) * gs[0].n: f})
    gs = set(gs)  # remove duplicates
    hs = set([np.prod(comb) for comb in combinations_with_replacement(gs, q)])
    gamma = cvxpy.Variable(name='gamma')
    lagrangian = f - gamma
    alpha_E_p = hierarchy_e_k([f] + list(gs), k=p)
    dualized_polynomials = []
    for h in hs:
        temp_shc = cvxpy.Variable(name='shc_' + str(h),
                                  shape=(alpha_E_p.shape[0], ))
        temp_sh = Polynomial(alpha_E_p, temp_shc)
        lagrangian -= temp_sh * h
        dualized_polynomials.append((temp_sh, h))
    return lagrangian, dualized_polynomials
 def test_constrained_1(self):
     # an example from page 16 of
     # http://homepages.laas.fr/henrion/papers/gloptipoly3.pdf
     # --- which is itself borrowed from somewhere else.
     f = Polynomial({(1, 0, 0): -2, (0, 1, 0): 1, (0, 0, 1): -1})
     # Constraints over more than one variable
     g1 = Polynomial({
         (0, 0, 0): 24,
         (1, 0, 0): -20,
         (0, 1, 0): 9,
         (0, 0, 1): -13,
         (2, 0, 0): 4,
         (1, 1, 0): -4,
         (1, 0, 1): 4,
         (0, 2, 0): 2,
         (0, 1, 1): -2,
         (0, 0, 2): 2
     })
     g2 = Polynomial({
         (1, 0, 0): -1,
         (0, 1, 0): -1,
         (0, 0, 1): -1,
         (0, 0, 0): 4
     })
     g3 = Polynomial({(0, 1, 0): -3, (0, 0, 1): -1, (0, 0, 0): 6})
     # Bound constraints on x_1
     g4 = Polynomial({(1, 0, 0): 1})
     g5 = Polynomial({(1, 0, 0): -1, (0, 0, 0): 2})
     # Bound constraints on x_2
     g6 = Polynomial({(0, 1, 0): 1})
     # Bound constraints on x_3
     g7 = Polynomial({(0, 0, 1): 1})
     g8 = Polynomial({(0, 0, 1): -1, (0, 0, 0): 3})
     # Assemble!
     gs = [g1, g2, g3, g4, g5, g6, g7, g8]
     res = primal_dual_constrained(f, gs, 0, 1)
     assert abs(res[0] - (-6)) <= 1e-6
     assert abs(res[1] - (-6)) <= 1e-6
     # ^ incidentally, this is the same as gloptipoly3 !
     res1 = primal_dual_constrained(f, gs, 1, 1)
     assert abs(res1[0] - (-5.7)) <= 0.02
     assert abs(res1[0] - (-5.7)) <= 0.02
Example #11
0
def hierarchy_c_h_array(s_h, h, lagrangian):
    """
    Assume (s_h * h).alpha is a subset of lagrangian.alpha.

    :param s_h: a SAGE multiplier Polynomial for the constrained hierarchy
    :param h: the constraint Polynomial
    :param lagrangian: the Polynomial f - \gamma - \sum_{h \in H} s_h * h.

    :return: a matrix c_h so that if "v" is a dual variable to the constraint
    "lagrangian is a SAGE polynomial", then the constraint "s_h is SAGE"
    dualizes to "c_h * v \in C_{SAGE}^{POLY}(s_h)^{\star}".
    """
    c_h = np.zeros((s_h.alpha.shape[0], lagrangian.alpha.shape[0]))
    for i, row in enumerate(s_h.alpha):
        temp_poly = Polynomial({tuple(row): 1}) * h
        c_h[i, :] = relative_coeff_vector(temp_poly, lagrangian.alpha)
    return c_h
 def test_unconstrained_2(self):
     # an example from
     # https://arxiv.org/abs/1808.08431
     p = Polynomial({
         (0, 0): 1,
         (2, 6): 3,
         (6, 2): 2,
         (2, 2): 6,
         (1, 2): -1,
         (2, 1): 2,
         (3, 3): -3
     })
     res0 = primal_dual_unconstrained(p, level=0)
     assert abs(res0[0] - res0[1]) <= 1e-6
     res1 = primal_dual_unconstrained(p, level=1)
     assert abs(res1[0] - res1[1]) <= 1e-5
     assert res1[0] - res0[0] > 1e-2
Example #13
0
 def test_scalar_multiplication(self):
     # data for tests
     alpha0 = np.array([[0], [1], [2]])
     c0 = np.array([1, 2, 3])
     s0 = Polynomial(alpha0, c0)
     # Tests
     s = 2 * s0
     # noinspection PyTypeChecker
     assert set(s.c) == set(2 * s0.c)
     s = s0 * 2
     # noinspection PyTypeChecker
     assert set(s.c) == set(2 * s0.c)
     s = 1 * s0
     assert s.alpha_c == s0.alpha_c
     s = 0 * s0
     s.remove_terms_with_zero_as_coefficient()
     assert s.m == 1 and set(s.c) == {0}
 def test_unconstrained_3(self):
     # an example from
     # http://homepages.laas.fr/henrion/papers/gloptipoly3.pdf
     p = Polynomial({
         (0, 0): 0,
         (2, 0): 4,
         (1, 1): 1,
         (0, 2): -4,
         (4, 0): -2.1,
         (0, 4): 4,
         (6, 0): 1.0 / 3.0
     })
     # We'll see that level=0 has a decent bound, and level=1 is nearly
     # optimal. ECOS is unable to solver level=2 due to conditioning problems,
     # but MOSEK solves level=2 without any trouble; the solution when level=2
     # is globally optimal.
     res0 = primal_dual_unconstrained(p, level=0)
     assert abs(res0[0] - res0[1]) <= 1e-6
     res1 = primal_dual_unconstrained(p, level=1)
     assert abs(res1[0] - res1[1]) <= 1e-6
Example #15
0
def constrained_sage_poly_dual(f, gs, p=0, q=1):
    """
    Compute the dual f_{SAGE}^{(p, q)} bound for

        inf f(x) : g(x) >= 0 for g \in gs.

    :param f: a Polynomial.
    :param gs: a list of Polynomials.
    :param p: a nonnegative integer,
    :param q: a positive integer.

    :return: a CVXPY Problem that defines the dual formulation for f_{SAGE}^{(p, q)}.
    """
    lagrangian, dualized_polynomials = make_poly_lagrangian(
        f, gs, p=p, q=q, add_constant_poly=(q != 1))
    # In primal form, the Lagrangian is constrained to be a SAGE polynomial.
    # Introduce a dual variable "v" for this constraint.
    v = cvxpy.Variable(shape=(lagrangian.m, 1))
    constraints = relative_c_poly_sage_star(lagrangian, v)
    a = relative_coeff_vector(Polynomial({(0, ) * f.n: 1}), lagrangian.alpha)
    a = a.reshape(a.size, 1)
    constraints.append(a.T * v == 1)
    # The generalized Lagrange multipliers "s_h" are presumed to be SAGE polynomials.
    # For each such multiplier, introduce an appropriate dual variable "v_h", along
    # with constraints over that dual variable.
    for s_h, h in dualized_polynomials:
        v_h = cvxpy.Variable(name='v_h_' + str(s_h), shape=(s_h.m, 1))
        constraints += relative_c_poly_sage_star(s_h, v_h)
        c_h = hierarchy_c_h_array(s_h, h, lagrangian)
        constraints.append(c_h * v == v_h)
    # Define the dual objective function.
    obj_vec = relative_coeff_vector(f, lagrangian.alpha)
    obj = cvxpy.Minimize(obj_vec * v)
    # Return the CVXPY Problem.
    prob = cvxpy.Problem(obj, constraints)
    return prob
 def test_sigrep_1(self):
     p = Polynomial({(0, 0): -1, (1, 2): 1, (2, 2): 10})
     gamma = cvxpy.Variable(shape=(), name='gamma')
     p -= gamma
     sr, sr_cons = p.sig_rep
     # Even though there is a Variable in p.c, no auxiliary
     # variables should have been introduced by defining this
     # signomial representative.
     assert len(sr_cons) == 0
     count_nonconstants = 0
     for i, ci in enumerate(sr.c):
         if isinstance(ci, Expression):
             assert len(ci.variables()) == 1
             count_nonconstants += 1
             assert ci.variables()[0].name() == 'gamma'
             gamma.value = 42
             assert ci.value == -43
         elif sr.alpha[i, 0] == 1 and sr.alpha[i, 1] == 2:
             assert ci == -1
         elif sr.alpha[i, 0] == 2 and sr.alpha[i, 1] == 2:
             assert ci == 10
         else:
             assert False
     assert count_nonconstants == 1
Example #17
0
 def test_sigrep_2(self):
     p = Polynomial({(0, 0): 0, (1, 1): -1, (3, 3): 5})
     # One non-even lattice point changes sign, another stays the same
     sr, sr_cons = p.sig_rep
     assert len(sr_cons) == 0
     assert sr.alpha_c == {(0, 0): 0, (1, 1): -1, (3, 3): -5}
Example #18
0
 def test_sigrep_1(self):
     p = Polynomial({(0, 0): -1, (1, 2): 1, (2, 2): 10})
     # One non-even lattice point (the only one) changes sign.
     sr, sr_cons = p.sig_rep
     assert len(sr_cons) == 0
     assert sr.alpha_c == {(0, 0): -1, (1, 2): -1, (2, 2): 10}