def test_conditional_sage_dual_1(self):
     n, m = 2, 6
     x = Variable(shape=(n, ), name='x')
     cons = [1 >= vector2norm(x)]
     gts = [lambda z: 1 - np.linalg.norm(z, 2)]
     eqs = []
     sigdom = SigDomain(n, coniclifts_cons=cons, gts=gts, eqs=eqs)
     np.random.seed(0)
     x0 = np.random.randn(n)
     x0 /= 2 * np.linalg.norm(x0)
     alpha = np.random.randn(m, n)
     c = np.array([1, 2, 3, 4, -0.5, -0.1])
     v0 = np.exp(alpha @ x0)
     v = Variable(shape=(m, ), name='projected_v0')
     t = Variable(shape=(1, ), name='epigraph_var')
     sage_constraint = sage_cones.DualSageCone(v,
                                               alpha,
                                               name='test',
                                               X=sigdom,
                                               c=c)
     epi_constraint = vector2norm(v - v0) <= t
     constraints = [sage_constraint, epi_constraint]
     prob = Problem(CL_MIN, t, constraints)
     prob.solve(solver='ECOS')
     v0 = sage_constraint.violation(norm_ord=1, rough=False)
     assert v0 < 1e-6
     v1 = sage_constraint.violation(norm_ord=np.inf, rough=True)
     assert v1 < 1e-6
     val = prob.value
     assert val < 1e-7
Beispiel #2
0
    def project(item, alpha):
        """
        Calculates the shortest distance (the projection) of a vector
        to a cone parametrized by :math:'\\alpha'

        Parameters
        ----------
        item - the point we are projecting
        alpha - the :math:'\\alpha' parameter
                for the Cone that we are projecting to

        Returns
        -------
        The distance of the projection to the Cone
        """
        from sageopt.coniclifts import MIN as CL_MIN

        item = Expression(item).ravel()
        w = Variable(shape=(item.size, ))
        t = Variable(shape=(1, ))

        cons = [vector2norm(item - w) <= t, PowCone(w, alpha)]

        prob = Problem(CL_MIN, t, cons)
        prob.solve(verbose=False)

        return prob.value
Beispiel #3
0
    def _geometric_program_1(self, solver, **kwargs):
        """
        Solve a GP with a linear objective and single posynomial constraint.

        The reference solution was computed by Wolfram Alpha.
        """
        alpha = np.array([[1, 0],
                          [0, 1],
                          [1, 1],
                          [0.5, 0],
                          [0, 0.5]])
        c = np.array([3, 2, 1, 4, 2])
        x = cl.Variable(shape=(2,), name='x')
        y = alpha @ x
        expr = cl.weighted_sum_exp(c, y)
        cons = [expr <= 1]
        obj = - x[0] - 2 * x[1]
        prob = Problem(cl.MIN, obj, cons)
        status, val = prob.solve(solver=solver, **kwargs)
        assert status == 'solved'
        assert abs(val - 10.4075826) < 1e-6
        x_star = x.value
        expect = np.array([-4.93083, -2.73838])
        assert np.allclose(x_star, expect, atol=1e-4)
        return prob
 def test_ordinary_sage_dual_3(self):
     # provide a vector "c" in the dual SAGE cone constructor.
     # generate a point with zero distance from the dual SAGE cone
     n, m = 2, 6
     np.random.seed(0)
     alpha = 10 * np.random.randn(m, n)
     x0 = np.random.randn(n) / 10
     v0 = np.exp(alpha @ x0)
     dummy_vars = Variable(shape=(2, )).scalar_variables()
     c = np.array([1, 2, 3, 4, dummy_vars[0], dummy_vars[1]])
     c = Expression(c)
     v = Variable(shape=(m, ), name='projected_v0')
     t = Variable(shape=(1, ), name='epigraph_var')
     sage_constraint = sage_cones.DualSageCone(v,
                                               alpha,
                                               X=None,
                                               name='test_con',
                                               c=c)
     epi_constraint = vector2norm(v - v0) <= t
     constraints = [sage_constraint, epi_constraint]
     prob = Problem(CL_MIN, t, constraints)
     prob.solve(solver='ECOS')
     viol = sage_constraint.violation(norm_ord=1, rough=False)
     assert viol < 1e-6
     viol = sage_constraint.violation(norm_ord=np.inf, rough=True)
     assert viol < 1e-6
     val = prob.value
     assert val < 1e-7
Beispiel #5
0
    def violation(self, norm_ord=np.inf, rough=False):
        """
        Return a measure of violation for the constraint that ``self.v`` belongs to
        :math:`C_{\\mathrm{SAGE}}(\\alpha, X)^{\\dagger}`.

        Parameters
        ----------
        norm_ord : int
            The value of ``ord`` passed to numpy ``norm`` functions, when reducing
            vector-valued residuals into a scalar residual.

        rough : bool
            Setting ``rough=False`` computes violation by solving an optimization
            problem. Setting ``rough=True`` computes violation by taking norms of
            residuals of appropriate elementwise equations and inequalities involving
            ``self.v`` and auxiliary variables.

        Notes
        -----
        When ``rough=False``, the optimization-based violation is computed by projecting
        the vector ``self.v`` onto a new copy of a dual SAGE constraint, and then returning
        the L2-norm between ``self.v`` and that projection. This optimization step essentially
        re-solves for all auxiliary variables used by this constraint.
        """
        v = self.v.value
        viols = []
        for i in self.ech.U_I:
            selector = self.ech.expcovers[i]
            num_cover = self.ech.expcover_counts[i]
            if num_cover > 0:
                expr1 = np.tile(v[i], num_cover).ravel()
                expr2 = v[selector].ravel()
                lowerbounds = special_functions.rel_entr(expr1, expr2)
                mat = -(self.alpha[selector, :] - self.alpha[i, :])
                mu_i = self._lifted_mu_vars[i].value
                # compute rough violation for this dual AGE cone
                residual = mat @ mu_i[:self._n] - lowerbounds
                residual[residual >= 0] = 0
                curr_viol = np.linalg.norm(residual, ord=norm_ord)
                if (self.X is not None) and (not np.isnan(curr_viol)):
                    AbK_val = self.X.A @ mu_i + v[i] * self.X.b
                    AbK_viol = PrimalProductCone.project(AbK_val, self.X.K)
                    curr_viol += AbK_viol
                # as applicable, solve an optimization problem to compute the violation.
                if (curr_viol > 0 or np.isnan(curr_viol)) and not rough:
                    temp_var = Variable(shape=(self._lifted_n,), name='temp_var')
                    cons = [mat @ temp_var[:self._n] >= lowerbounds]
                    if self.X is not None:
                        con = PrimalProductCone(self.X.A @ temp_var + v[i] * self.X.b, self.X.K)
                        cons.append(con)
                    prob = Problem(CL_MIN, Expression([0]), cons)
                    status, value = prob.solve(verbose=False)
                    if status in {CL_SOLVED, CL_INACCURATE} and abs(value) < 1e-7:
                        curr_viol = 0
                viols.append(curr_viol)
            else:
                viols.append(0)
        viol = max(viols)
        return viol
Beispiel #6
0
 def project(item, K):
     from sageopt.coniclifts import MIN as CL_MIN
     item = Expression(item).ravel()
     x = Variable(shape=(item.size, ))
     t = Variable(shape=(1, ))
     cons = [vector2norm(item - x) <= t, PrimalProductCone(x, K)]
     prob = Problem(CL_MIN, t, cons)
     prob.solve(verbose=False)
     return prob.value
Beispiel #7
0
 def project(item, alpha, X):
     if np.all(item >= 0):
         return 0
     c = Variable(shape=(item.size,))
     t = Variable(shape=(1,))
     cons = [
         vector2norm(item - c) <= t,
         PrimalSageCone(c, alpha, X, 'temp_con')
     ]
     prob = Problem(CL_MIN, t, cons)
     prob.solve(verbose=False)
     return prob.value
 def test_separate_cone_constraints_2(self):
     num_vars = 5
     x = Variable(shape=(num_vars, ))
     cons = [vector2norm(x) <= 1]
     prob = Problem(CL_MIN, Expression([0]), cons)
     A0, b0, K0 = prob.A, prob.b, prob.K
     assert A0.shape == (num_vars + 2, num_vars + 1)
     assert len(K0) == 2
     assert K0[0].type == '+' and K0[0].len == 1
     assert K0[1].type == 'S' and K0[1].len == num_vars + 1
     A1, b1, K1, sepK1 = separate_cone_constraints(A0,
                                                   b0,
                                                   K0,
                                                   dont_sep={'+'})
     A1 = A1.toarray()
     assert A1.shape == (num_vars + 2, 2 * (num_vars + 1))
     assert len(K1) == 2
     assert K1[0].type == '+' and K1[0].len == 1
     assert K1[1].type == '0' and K1[1].len == num_vars + 1
     assert len(sepK1) == 1
     assert sepK1[0].type == 'S' and sepK1[0].len == num_vars + 1
     A0 = A0.toarray()
     temp = np.vstack(
         (np.zeros(shape=(1, num_vars + 1)), np.eye(num_vars + 1)))
     expect_A1 = np.hstack((A0, -temp))
     assert np.allclose(expect_A1, A1)
 def test_separate_cone_constraints_3(self):
     alpha = np.array([[1, 0], [0, 1], [1, 1], [0.5, 0], [0, 0.5]])
     c = np.array([3, 2, 1, 4, 2])
     x = Variable(shape=(2, ), name='x')
     expr = weighted_sum_exp(c, alpha @ x)
     cons = [expr <= 1]
     obj = -x[0] - 2 * x[1]
     prob = Problem(CL_MIN, obj, cons)
     A0, b0, K0 = prob.A, prob.b, prob.K
     assert A0.shape == (16, 7)
     assert len(K0) == 6
     assert K0[0].type == '+' and K0[0].len == 1
     for i in [1, 2, 3, 4, 5]:
         assert K0[i].type == 'e'
     A1, b1, K1, sepK1 = separate_cone_constraints(A0,
                                                   b0,
                                                   K0,
                                                   dont_sep={'+'})
     A1 = A1.toarray()
     A0 = A0.toarray()
     temp = np.vstack((np.zeros(shape=(1, 15)), np.eye(15)))
     expect_A1 = np.hstack((A0, -temp))
     assert np.allclose(A1, expect_A1)
     assert len(K1) == len(K0)
     assert K1[0].type == '+' and K1[0].len == 1
     for i in [1, 2, 3, 4, 5]:
         assert K1[i].type == '0' and K1[i].len == 3
     assert len(sepK1) == 5
     for co in sepK1:
         assert co.type == 'e'
     pass
 def test_separate_cone_constraints_1(self):
     num_ineqs = 10
     num_vars = 5
     G = np.random.randn(num_ineqs, num_vars).round(decimals=3)
     x = Variable(shape=(num_vars, ))
     h = np.random.randn(num_ineqs).round(decimals=3)
     cons = [G @ x >= h]
     prob = Problem(CL_MIN, Expression([0]), cons)
     A0, b0, K0 = prob.A, prob.b, prob.K
     # main test (separate everything other than the zero cone)
     A1, b1, K1, sepK1 = separate_cone_constraints(A0, b0, K0)
     A1 = A1.toarray()
     assert A1.shape == (num_ineqs, num_vars + num_ineqs)
     expect_A1 = np.hstack((G, -np.eye(num_ineqs)))
     assert np.allclose(A1, expect_A1)
     assert len(K1) == 1
     assert K1[0].type == '0'
     assert len(sepK1) == 1
     assert sepK1[0].type == '+'
     assert np.allclose(b0, b1)
     assert np.allclose(b0, -h)
     # trivial test (don't separate anything, including some cones not in the set)
     A2, b2, K2, sepK2 = separate_cone_constraints(
         A0, b0, K0, dont_sep={'+', '0', 'S', 'e'})
     A2 = A2.toarray()
     A0 = A0.toarray()
     assert np.allclose(A0, A2)
     assert np.allclose(b0, b2)
     pass
Beispiel #11
0
 def _presolve_trivial_ord_age(self, i, covers):
     """
     Set covers[i][:] = False if the i-th (ordinary) AGE cone is trivial.
     """
     mat = self.alpha[covers[i], :] - self.alpha[i, :]
     x = Variable(shape=(mat.shape[1], ), name='temp_x')
     objective = Expression([0])
     cons = [mat @ x <= -1]
     prob = Problem(CL_MIN, objective, cons)
     # If prob is feasible, then there exists a sequence x_t where
     # max(mat @ x_t) diverges to -\infty as t increases. Using this
     # sequence we can satisfy the constraints for the i-th dual
     # AGE cone no matter the value of the vector "v" that needs to belong
     # to the dual AGE cone.
     prob.solve(verbose=False, solver=self.settings['reduction_solver'])
     if prob.status in {CL_SOLVED, CL_INACCURATE
                        } and abs(prob.value) < 1e-7:
         covers[i][:] = False
Beispiel #12
0
    def test_simple_sage_1(self):
        """
        Solve a simple SAGE relaxation for a signomial minimization problem.

        Do this without resorting to "Signomial" objects.
        """
        alpha = np.array([[0, 0],
                          [1, 0],
                          [0, 1],
                          [1, 1],
                          [0.5, 0],
                          [0, 0.5]])
        gamma = cl.Variable(shape=(), name='gamma')
        c = cl.Expression([0 - gamma, 3, 2, 1, -4, -2])
        expected_val = -1.8333331773244161

        # with presolve
        cl.presolve_trivial_age_cones(True)
        con = cl.PrimalSageCone(c, alpha, None, 'test_con_name')
        obj = gamma
        prob = Problem(cl.MAX, obj, [con])
        status, val = prob.solve(solver='ECOS', verbose=False)
        assert abs(val - expected_val) < 1e-6
        v = con.violation()
        assert v < 1e-6

        # without presolve
        cl.presolve_trivial_age_cones(False)
        con = cl.PrimalSageCone(c, alpha, None, 'test_con_name')
        obj = gamma
        prob = Problem(cl.MAX, obj, [con])
        status, val = prob.solve(solver='ECOS', verbose=False)
        assert abs(val - expected_val) < 1e-6
        v = con.violation()
        assert v < 1e-6
Beispiel #13
0
 def case_1():
     alpha = np.array([[1, 0], [0, 1], [1, 1], [0.5, 0], [0, 0.5]])
     c = np.array([3, 2, 1, 4, 2])
     x = cl.Variable(shape=(2, ), name='x')
     y = alpha @ x
     expr = cl.weighted_sum_exp(c, y)
     cons = [expr <= 1]
     obj = -x[0] - 2 * x[1]
     prob = Problem(cl.MIN, obj, cons)
     status = 'solved'
     value = 10.4075826  # up to 1e-6
     x_star = np.array([-4.93083, -2.73838])  # up to 1e-4
     return prob, status, value, x_star
 def test_ordinary_sage_dual_1(self):
     # generate a point which has positive distance from the dual SAGE cone
     n, m = 2, 6
     np.random.seed(0)
     alpha = 10 * np.random.randn(m, n)
     v0 = 10 * np.abs(np.random.randn(m)) + 0.01
     v0[0] = -v0[0]
     v = Variable(shape=(m, ), name='projected_v0')
     t = Variable(shape=(1, ), name='epigraph_var')
     sage_constraint = sage_cones.DualSageCone(v,
                                               alpha,
                                               X=None,
                                               name='test_con')
     epi_constraint = vector2norm(v - v0) <= t
     constraints = [sage_constraint, epi_constraint]
     prob = Problem(CL_MIN, t, constraints)
     prob.solve(solver='ECOS')
     viol = sage_constraint.violation(norm_ord=1, rough=False)
     assert viol < 1e-6
     viol = sage_constraint.violation(norm_ord=np.inf, rough=True)
     assert viol < 1e-6
     val = prob.value
     assert val + 1e-6 >= abs(v0[0])
 def test_ordinary_sage_dual_2(self):
     # generate a point with zero distance from the dual SAGE cone
     n, m = 2, 6
     np.random.seed(0)
     alpha = 10 * np.random.randn(m, n)
     x0 = np.random.randn(n) / 10
     v0 = np.exp(alpha @ x0)
     v = Variable(shape=(m, ), name='projected_v0')
     t = Variable(shape=(1, ), name='epigraph_var')
     sage_constraint = sage_cones.DualSageCone(v,
                                               alpha,
                                               X=None,
                                               name='test_con')
     epi_constraint = vector2norm(v - v0) <= t
     constraints = [sage_constraint, epi_constraint]
     prob = Problem(CL_MIN, t, constraints)
     prob.solve(solver='ECOS')
     viol = sage_constraint.violation(norm_ord=1, rough=False)
     assert viol < 1e-6
     viol = sage_constraint.violation(norm_ord=np.inf, rough=True)
     assert viol < 1e-6
     val = prob.value
     assert val < 1e-7
    def test_ordinary_sage_primal_2(self):
        n, m = 2, 6
        np.random.seed(0)
        alpha = 1 * np.random.randn(m - 1, n)
        conv_comb = np.random.rand(m - 1)
        conv_comb /= np.sum(conv_comb)
        alpha_last = alpha.T @ conv_comb
        alpha = np.row_stack([alpha, alpha_last])
        c0 = np.array([1, 2, 3, 4, -0.5, -0.1])
        c = Variable(shape=(m, ), name='projected_c0')
        t = Variable(shape=(1, ), name='epigraph_var')
        sage_constraint = sage_cones.PrimalSageCone(c,
                                                    alpha,
                                                    X=None,
                                                    name='test')
        epi_constraint = vector2norm(c - c0) <= t
        constraints = [sage_constraint, epi_constraint]
        prob = Problem(CL_MIN, t, constraints)
        prob.solve(solver='ECOS')

        # constraint violations
        v0 = sage_constraint.violation(norm_ord=1, rough=False)
        assert v0 < 1e-6
        v1 = sage_constraint.violation(norm_ord=np.inf, rough=True)
        assert v1 < 1e-6

        # certificates
        w4 = sage_constraint.age_witnesses[4].value
        c4 = sage_constraint.age_vectors[4].value
        drop4 = np.array([True, True, True, True, False, True])
        level4 = np.sum(rel_entr(w4[drop4], np.exp(1) * c4[drop4])) - c4[4]
        assert level4 < 1e-6
        w5 = sage_constraint.age_witnesses[5].value
        c5 = sage_constraint.age_vectors[5].value
        drop5 = np.array([True, True, True, True, True, False])
        level5 = np.sum(rel_entr(w5[drop5], np.exp(1) * c5[drop5])) - c5[5]
        assert level5 < 1e-6
Beispiel #17
0
    def _presolve_trivial_cond_age(self, i, covers, threshold=-100):
        """
        Overwrite covers[i][:] = False if it LOOKS LIKE the i-th X-AGE cone is trivial.

        If X is a cone (which happens if self.AbK[1] is the zero vector), then this method
        for checking triviality is necessary and sufficient.

        If X is not a cone, then this method might incorrectly identify an AGE cone as trivial.
        Setting "threshold" to a large negative number decreases the chance that we ignore
        a useful AGE cone.

        Note: If X is compact, then no X-AGE cone is trivial.
        """
        mat = self.alpha[covers[i], :] - self.alpha[i, :]
        x = Variable(shape=(mat.shape[1], ), name='temp_x')
        t = Variable(shape=(1, ), name='temp_t')
        objective = t
        A, b, K = self.AbK
        cons = [mat @ x <= t, PrimalProductCone(A @ x + b, K)]
        prob = Problem(CL_MIN, objective, cons)
        prob.solve(verbose=False, solver=self.settings['reduction_solver'])
        if prob.status in {CL_SOLVED, CL_INACCURATE
                           } and prob.value < threshold:
            covers[i][:] = False
Beispiel #18
0
    def _default_exp_covers(self):
        expcovers = dict()
        for i in self.U_I:
            cov = np.ones(shape=(self.m,), dtype=bool)
            cov[self.N_I] = False
            cov[i] = False
            expcovers[i] = cov
        if self.AbK is None or self.settings['heuristic_reduction']:
            row_sums = np.sum(self.alpha, 1)
            if np.all(self.alpha >= 0) and np.min(row_sums) == 0:
                # Then apply the reduction.
                zero_loc = np.nonzero(row_sums == 0)[0][0]
                for i in self.U_I:
                    if i == zero_loc:
                        continue
                    curr_cover = expcovers[i]
                    curr_row = self.alpha[i, :]
                    for j in range(self.m):
                        if curr_cover[j] and j != zero_loc and curr_row @ self.alpha[j, :] == 0:
                            curr_cover[j] = False
                            """
                            The above operation is without loss of generality for ordinary SAGE
                            constraints. For conditional SAGE constraints, the operation may or
                            may-not be without loss of generality. As a basic check, the above
                            operation is w.l.o.g. even for conditional SAGE constraints, as long
                            as the "conditioning" satisfies the following property:

                                Suppose "y" a geometric-form solution which is feasible w.r.t.
                                conditioning. Then "y" remains feasible (w.r.t. conditioning)
                                when we assign "y[k] = 0".

                            The comments below explain in greater detail.

                            Observation
                            -----------
                            By being in this part of the code, there must exist a "k" where

                                 alpha[i,k] == 0 and alpha[j,k] > 0.

                            Also, we have alpha >= 0. These facts tell us that the expression

                                (alpha[j2,:] - alpha[i,:]) @ mu[:, i] (*)

                            is (1) non-decreasing in mu[k,i] for all 0 <= j2 < m, and (2)
                            strictly increasing in mu[k,i] when j2 == j. Therefore by
                            sending mu[i,k] to -\infty, we do not increase (*) for any
                            0 <= j2 < m, and in fact (*) goes to -\infty for j2 == j.

                            Consequence 1
                            -------------
                            If mu[:,i] is only subject to constraints of the form

                                v[i]*log(v[j2]/v[i]) >= (alpha[j2,:] - alpha[i,:]) @ mu[:, i]

                            with 0 <= j2 < m, then the particular constraint with j2 == j
                            is never active at any optimal solution. For ordinary SAGE cones,
                            this means the j-th term of alpha isn't used in the i-th AGE cone.

                            Consequence 2
                            -------------
                            For conditional SAGE cones, there is another constraint:

                                 A @ mu[:, i] + v[i] * b \in K.      (**)

                            However, as long as (**) allows us to send mu[k,i] to -\infty
                            without affecting feasibility, then the we arrive at the same
                            conclusion: the j-th term of alpha isn't used in the i-th AGE cone.
                            """
        if self.AbK is None:
            for i in self.U_I:
                if np.count_nonzero(expcovers[i]) == 1:
                    expcovers[i][:] = False
        if self.settings['presolve_trivial_age_cones']:
            if self.AbK is None:
                for i in self.U_I:
                    if np.any(expcovers[i]):
                        mat = self.alpha[expcovers[i], :] - self.alpha[i, :]
                        x = Variable(shape=(mat.shape[1],), name='temp_x')
                        objective = Expression([0])
                        cons = [mat @ x <= -1]
                        prob = Problem(CL_MIN, objective, cons)
                        prob.solve(verbose=False,
                                   solver=self.settings['reduction_solver'])
                        if prob.status in {CL_SOLVED, CL_INACCURATE} and abs(prob.value) < 1e-7:
                            expcovers[i][:] = False
            else:
                for i in self.U_I:
                    if np.any(expcovers[i]):
                        mat = self.alpha[expcovers[i], :] - self.alpha[i, :]
                        x = Variable(shape=(mat.shape[1],), name='temp_x')
                        t = Variable(shape=(1,), name='temp_t')
                        objective = t
                        A, b, K = self.AbK
                        cons = [mat @ x <= t, PrimalProductCone(A @ x + b, K)]
                        prob = Problem(CL_MIN, objective, cons)
                        prob.solve(verbose=False,
                                   solver=self.settings['reduction_solver'])
                        if prob.status in {CL_SOLVED, CL_INACCURATE} and prob.value < -100:
                            expcovers[i][:] = False
        return expcovers