Пример #1
0
 def test_vector2norm_2(self):
     x = Variable(shape=(3, ), name='x')
     y = Variable(shape=(1, ), name='y')
     nrm = vector2norm(x - np.array([0.1, 0.2, 0.3]))
     con = [nrm <= y]
     A_expect = np.array([
         [0, 0, 0, 1,
          -1],  # linear inequality constraint in terms of epigraph variable
         [0, 0, 0, 0,
          1],  # epigraph component in second order cone constraint
         [1, 0, 0, 0,
          0],  # start of main block in second order cone constraint
         [0, 1, 0, 0, 0],
         [0, 0, 1, 0, 0]
     ])  # end of main block in second order cone constraint
     b_expect = np.zeros(shape=(5, ))
     b_expect[0] = 0
     b_expect[2] = -0.1
     b_expect[3] = -0.2
     b_expect[4] = -0.3
     A, b, K, _, _, _ = compile_constrained_system(con)
     A = np.round(A.toarray(), decimals=1)
     assert np.all(A == A_expect)
     assert np.all(b == b_expect)
     assert K == [Cone('+', 1), Cone('S', 4)]
Пример #2
0
def dualize_problem(c, A, b, Kp):
    """
    min{ c @ x : A @ x + b in K} == max{ -b @ y : c = A.T @ y, y in K^\dagger }

    Parameters
    ----------
    c : ndarray with ndim == 1
    A : csc_matrix
    b : ndarray with ndim == 1
    Kp : list of Cone

    Returns
    -------
    f : ndarray with ndim == 1
    G : csc_matrix
    h : ndarray with ndim == 1
    Kd : list of Cone

    Notes
    -----
    Temporary implementation. Might end up needing to transform A, so that the
    dual problem can be stated exclusively with primal cones.
    """
    Kd = []
    for Ki in Kp:
        if Ki.type == 'e':
            Kd.append(Cone('de', 3))  # dual exponential cone
        elif Ki.type == '0':
            Kd.append(Cone('fr', Ki.len))  # free cone
        else:
            Kd.append(Ki)  # remaining cones are self-dual
    f = -b
    G = A.T
    h = c
    return f, G, h, Kd
Пример #3
0
def sum_relent(x, y, z, aux_vars):
    # represent "sum{x[i] * ln( x[i] / y[i] )} + z <= 0" in conic form.
    # return the Variable object created for all epigraphs needed in
    # this process, as well as A_data, b, and K.
    x, y = _align_args(x, y)
    if not isinstance(z, Expression):
        z = Expression(z)
    if z.size != 1 or not z.is_affine():
        raise RuntimeError('Illegal argument to sum_relent.')
    else:
        z = z.item()  # gets the single element
    num_rows = 1 + 3 * x.size
    b = np.zeros(num_rows,)
    K = [Cone('+', 1)] + [Cone('e', 3) for _ in range(x.size)]
    A_rows, A_cols, A_vals = [], [], []
    # populate the first row
    z_id2co = [(a.id, co) for a, co in z.atoms_to_coeffs.items()]
    A_cols += [aid for aid, _ in z_id2co]
    A_vals += [-co for _, co in z_id2co]
    aux_var_ids = aux_vars.scalar_variable_ids
    A_cols += aux_var_ids
    A_vals += [-1] * len(aux_var_ids)
    A_rows += [0] * len(A_vals)
    b[0] = -z.offset
    # populate the epigraph terms
    curr_row = 1
    _fast_elemwise_data(A_rows, A_cols, A_vals, b, x, y, aux_var_ids, curr_row)
    return A_vals, np.array(A_rows), A_cols, b, K
Пример #4
0
    def test_power_cone_system(self):
        n = 4
        rng = np.random.default_rng(12345)

        # Create w and z in one array, where the last one will be z
        wz = Variable(shape=(n+1,), name='wz')

        # Make the last element negative to indicate that that element is z in the wz variable
        lamb = rng.random((n+1,))
        lamb[-1] = -1*np.sum(lamb[:-1])

        # Create simple constraints
        constraints1 = [PowCone(wz, lamb)]
        A, b, K, _, _, _ = compile_constrained_system(constraints1)
        A = A.toarray()
        assert np.allclose(A, np.identity(n+1))
        assert np.allclose(b, np.zeros((n+1,)))
        actual_annotations = {'weights': np.array(lamb[:-1]/np.sum(lamb[:-1])).tolist()}
        assert K[0] == Cone('pow', n+1, annotations=actual_annotations)

        # Increment z
        offset = np.zeros((n+1,))
        offset[-1] = 1
        wz1 = wz + offset
        constraints2 = [PowCone(wz1, lamb)]
        A, b, K, _, _, _ = compile_constrained_system(constraints2)
        A = A.toarray()
        assert np.allclose(A, np.identity(n + 1))
        expected_b = np.zeros((n + 1,))
        expected_b[-1] = 1
        assert np.allclose(b, expected_b)
        actual_annotations = {'weights': np.array(lamb[:-1] / np.sum(lamb[:-1])).tolist()}
        assert K[0] == Cone('pow', n + 1, annotations=actual_annotations)

        #Increment w
        r = rng.random((n+1,))
        r[-1] = 0
        wz2 = wz + r
        constraints3 = [PowCone(wz2, lamb)]
        A, b, K, _, _, _ = compile_constrained_system(constraints3)
        A = A.toarray()
        assert np.allclose(A, np.identity(n + 1))
        expected_b = r
        assert np.allclose(b[:-1], expected_b[:-1])
        assert K[0] == Cone('pow', n+1, annotations=actual_annotations)

        # Last test reconstructed from matrix creation
        x = Variable(shape=(n+1,), name='x')
        M = np.random.rand(n+1, n+1)
        y = M @ x
        constraints4 = [PowCone(y, lamb)]
        A, b, K, _, _, _ = compile_constrained_system(constraints4)
        A = A.toarray()
        assert np.allclose(A, M)
        assert K[0] == Cone('pow', n+1, annotations=actual_annotations)
Пример #5
0
 def epigraph_conic_form(self):
     """
     Generate conic constraint for epigraph
         np.linalg.norm( np.array(self.args), ord=2) <= self._epigraph_variable
     The coniclifts standard for the second order cone (of length n) is
         { (t,x) : x \in R^{n-1}, t \in R, || x ||_2 <= t }.
     """
     m = len(self.args) + 1
     b = np.zeros(m, )
     A_rows, A_cols, A_vals = [0], [self._epigraph_variable.id
                                    ], [1]  # for first row
     for i, arg in enumerate(self.args):
         nonconst_terms = len(arg) - 1
         if nonconst_terms > 0:
             A_rows += nonconst_terms * [i + 1]
             for var, coeff in arg[:-1]:
                 A_cols.append(var.id)
                 A_vals.append(coeff)
         else:
             # make sure we infer correct dimensions later on
             A_rows.append(i + 1)
             A_cols.append(ScalarVariable.curr_variable_count() - 1)
             A_vals.append(0)
         b[i + 1] = arg[-1][1]
     K = [Cone('S', m)]
     A_rows = np.array(A_rows)
     return A_vals, A_rows, A_cols, b, K
Пример #6
0
 def epigraph_conic_form(self):
     """
     |x| <= epi is represented as 0 <= epi + x, 0 <= epi - x.
     """
     if self._eval_only:
         msg = """
         This Abs atom was declared for evaluation only, and cannot be used in an optimization
         model requiring a conic form.
         """
         raise RuntimeError(msg)
     b = np.zeros(2, )
     K = [Cone('+', 2)]
     A_rows = [0, 1]
     A_cols = 2 * [self._epigraph_variable.id]
     A_vals = 2 * [1]
     x = self.args[0]
     num_nonconst = len(x) - 1
     if num_nonconst > 0:
         A_rows += num_nonconst * [0]
         A_cols += [var.id for var, co in x[:-1]]
         A_vals += [co for var, co in x[:-1]]
         A_rows += num_nonconst * [1]
         A_cols += [var.id for var, co in x[:-1]]
         A_vals += [-co for var, co in x[:-1]]
     b[0] = x[-1][1]
     b[1] = -b[0]
     return A_vals, np.array(A_rows), A_cols, b, K
Пример #7
0
def elementwise_relent(x, y, z):
    """
    Return variables "z" and conic constraint data for the system
        x[i] * ln( x[i] / y[i] ) <= z[i]

    A_vals - a list of floats,
    np.array(A_rows) - a numpy array of ints,
    A_cols - a list of ints,
    b - a numpy 1darray,
    K - a list of coniclifts Cone objects (of type 'e'),
    """
    x, y = _align_args(x, y)
    num_rows = 3 * x.size
    b = np.zeros(num_rows, )
    K = [Cone('e', 3) for _ in range(x.size)]
    A_rows, A_cols, A_vals = [], [], []
    curr_row = 0
    if isinstance(z, Variable):
        aux_var_ids = z.scalar_variable_ids
        _fast_elemwise_data(A_rows, A_cols, A_vals, b, x, y, aux_var_ids,
                            curr_row)
    else:
        z = z.ravel()
        _compact_elemwise_data(A_rows, A_cols, A_vals, b, x, y, z, curr_row)
    return A_vals, np.array(A_rows), A_cols, b, K
Пример #8
0
 def test_pos_1(self):
     x = Variable(shape=(3, ), name='x')
     con = [cl_pos(affine.sum(x) - 7) <= 5]
     A_expect = np.array([[0, 0, 0, -1], [0, 0, 0, 1], [-1, -1, -1, 1]])
     A, b, K, _1, _2, _3 = compile_constrained_system(con)
     A = np.round(A.toarray(), decimals=1)
     assert np.all(A == A_expect)
     assert np.all(b == np.array([5, 0, 7]))
     assert K == [Cone('+', 1), Cone('+', 2)]
     # value propagation
     x.value = np.array([4, 4, 4])
     viol = con[0].violation()
     assert viol == 0
     x.value = np.array([4.2, 3.9, 4.0])
     viol = con[0].violation()
     assert abs(viol - 0.1) < 1e-7
Пример #9
0
 def _ordsage_conic_form(self):
     cone_data = []
     nu_keys = self._nus.keys()
     for i in self.ech.U_I:
         if i in nu_keys:
             idx_set = self.ech.expcovers[i]
             # relative entropy inequality constraint
             x = self._nus[i]
             y = np.exp(1) * self.age_vectors[i][idx_set]  # This line consumes a large amount of runtime
             z = -self.age_vectors[i][i]
             epi = self._relent_epi_vars[i]
             cd = sum_relent(x, y, z, epi)
             cone_data.append(cd)
             if not self.settings['kernel_basis']:
                 # linear equality constraints
                 mat = (self.alpha[idx_set, :] - self.alpha[i, :]).T
                 av, ar, ac, _ = comp_aff.matvec(mat, self._nus[i])
                 num_rows = mat.shape[0]
                 curr_b = np.zeros(num_rows, )
                 curr_k = [Cone('0', num_rows)]
                 cone_data.append((av, ar, ac, curr_b, curr_k))
         else:
             con = 0 <= self.age_vectors[i][i]
             con.epigraph_checked = True
             cd = con.conic_form()
             cone_data.append(cd)
     cone_data.append(self._age_vectors_sum_to_c())
     return cone_data
Пример #10
0
 def epigraph_conic_form(self):
     """
     Refer to coniclifts/standards/cone_standards.txt to see that
     "(x, y) : e^x <= y" is represented as "(x, y, 1) \in K_{exp}".
     :return:
     """
     b = np.zeros(3, )
     K = [Cone('e', 3)]
     A_rows, A_cols, A_vals = [], [], []
     x = self.args[0]
     # first coordinate
     num_nonconst = len(x) - 1
     if num_nonconst > 0:
         A_rows += num_nonconst * [0]
         A_cols = [var.id for var, co in x[:-1]]
         A_vals = [co for var, co in x[:-1]]
     else:
         # infer correct dimensions later on
         A_rows.append(0)
         A_cols.append(ScalarVariable.curr_variable_count() - 1)
         A_vals.append(0)
     b[0] = x[-1][1]
     # second coordinate
     A_rows.append(1),
     A_cols.append(self._epigraph_variable.id)
     A_vals.append(1)
     # third coordinate (zeros for A, but included to infer correct dims later on)
     A_rows.append(2)
     A_cols.append(ScalarVariable.curr_variable_count() - 1)
     A_vals.append(0)
     b[2] = 1
     return A_vals, np.array(A_rows), A_cols, b, K
Пример #11
0
    def conic_form(self):
        """
        Returns a sparse matrix representation of the Power
        cone object. It represents of the object as :math:'Ax+b \\in K'
        where K is a cone object

        Returns
        -------
        A_vals - list (of floats)
        A_rows - numpy 1darray (of integers)
        A_cols - list (of integers)
        b - numpy 1darray (of floats)
        K - list (of coniclifts Cone objects)
        """
        A_rows, A_cols, A_vals = [], [], []
        K = [
            Cone('pow',
                 self.w.size,
                 annotations={'weights': self.alpha.tolist()})
        ]
        b = np.zeros(shape=(self.w.size, ))

        # Loop through w and add every variable
        for i, se in enumerate(self.w_low.flat):
            if len(se.atoms_to_coeffs) == 0:
                b[i] = se.offset
                A_rows.append(i)
                A_cols.append(ScalarVariable.curr_variable_count() - 1)
                # make sure scipy infers correct dimensions later on.
                A_vals.append(0)
            else:
                b[i] = se.offset
                A_rows += [i] * len(se.atoms_to_coeffs)
                col_idx_to_coeff = [(a.id, c)
                                    for a, c in se.atoms_to_coeffs.items()]
                A_cols += [atom_id for (atom_id, _) in col_idx_to_coeff]
                A_vals += [c for (_, c) in col_idx_to_coeff]

        # Make final row of matrix the information about z
        i = self.w_low.size
        # Loop through w and add every variable
        for se in self.z_low.flat:
            if len(se.atoms_to_coeffs) == 0:
                b[i] = se.offset
                A_rows.append(i)
                A_cols.append(ScalarVariable.curr_variable_count() - 1)
                # make sure scipy infers correct dimensions later on.
                A_vals.append(0)
            else:
                b[i] = se.offset
                A_rows += [i] * len(se.atoms_to_coeffs)
                col_idx_to_coeff = [(a.id, c)
                                    for a, c in se.atoms_to_coeffs.items()]
                A_cols += [atom_id for (atom_id, _) in col_idx_to_coeff]
                A_vals += [c for (_, c) in col_idx_to_coeff]

        return [(A_vals, np.array(A_rows), A_cols, b, K)]
Пример #12
0
 def test_abs_1(self):
     x = Variable(shape=(2, ), name='x')
     one_norm = affine.sum(cl_abs(x))
     con = [one_norm <= 5]
     A_expect = np.array([[0, 0, -1, -1], [1, 0, 1, 0], [-1, 0, 1, 0],
                          [0, 1, 0, 1], [0, -1, 0, 1]])
     A, b, K, _1, _2, _3 = compile_constrained_system(con)
     A = np.round(A.toarray(), decimals=1)
     assert np.all(A == A_expect)
     assert np.all(b == np.array([5, 0, 0, 0, 0]))
     assert K == [Cone('+', 1), Cone('+', 2), Cone('+', 2)]
     # value propagation
     x.value = np.array([1, -2])
     viol = con[0].violation()
     assert viol == 0
     x.value = np.array([-3, 3])
     viol = con[0].violation()
     assert viol == 1
Пример #13
0
 def _age_vectors_sum_to_c(self):
     nonconst_locs = np.ones(self._m, dtype=bool)
     nonconst_locs[self.ech.N_I] = False
     aux_c_vars = list(self.age_vectors.values())
     aux_c_vars = aff.vstack(aux_c_vars).T
     aux_c_vars = aux_c_vars[nonconst_locs, :]
     main_c_var = self.c[nonconst_locs]
     A_vals, A_rows, A_cols, b = comp_aff.columns_sum_leq_vec(aux_c_vars, main_c_var)
     conetype = '0' if self.settings['sum_age_force_equality'] else '+'
     K = [Cone(conetype, b.size)]
     return A_vals, A_rows, A_cols, b, K
Пример #14
0
 def test_relent_1(self):
     # compilation and evaluation
     x = Variable(shape=(2, ), name='x')
     y = Variable(shape=(2, ), name='y')
     re = relent(2 * x, np.exp(1) * y)
     con = [re <= 10, 3 <= x, x <= 5]
     # compilation
     A, b, K, _, _, _ = compile_constrained_system(con)
     A_expect = np.array([
         [0., 0., 0., 0., -1.,
          -1.],  # linear inequality on epigraph for relent constr
         [1., 0., 0., 0., 0., 0.],  # bound constraints on x
         [0., 1., 0., 0., 0., 0.],  #
         [-1., 0., 0., 0., 0., 0.],  # more bound constraints on x
         [0., -1., 0., 0., 0., 0.],  #
         [0., 0., 0., 0., -1., 0.],  # first exponential cone
         [0., 0., 2.72, 0., 0., 0.],  #
         [2., 0., 0., 0., 0., 0.],  #
         [0., 0., 0., 0., 0., -1.],  # second exponential cone
         [0., 0., 0., 2.72, 0., 0.],  #
         [0., 2., 0., 0., 0., 0.]
     ])  #
     A = np.round(A.toarray(), decimals=2)
     assert np.all(A == A_expect)
     assert np.all(
         b == np.array([10., -3., -3., 5., 5., 0., 0., 0., 0., 0., 0.]))
     assert K == [
         Cone('+', 1),
         Cone('+', 2),
         Cone('+', 2),
         Cone('e', 3),
         Cone('e', 3)
     ]
     # value propagation
     x0 = np.array([1, 2])
     x.value = x0
     y0 = np.array([3, 4])
     y.value = y0
     actual = re.value
     expect = np.sum(rel_entr(2 * x0, np.exp(1) * y0))
     assert abs(actual - expect) < 1e-7
Пример #15
0
 def test_relent_2(self):
     # compilation with the elementwise option
     x = Variable(shape=(2, ), name='x')
     y = Variable(shape=(2, ), name='y')
     re = affine.sum(relent(2 * x, np.exp(1) * y, elementwise=True))
     con = [re <= 1]
     A, b, K, _, _, _ = compile_constrained_system(con)
     A_expect = np.array([
         [0., 0., 0., 0., -1.,
          -1.],  # linear inequality on epigraph for relent constr
         [0., 0., 0., 0., -1., 0.],  # first exponential cone
         [0., 0., 2.72, 0., 0., 0.],  #
         [2., 0., 0., 0., 0., 0.],  #
         [0., 0., 0., 0., 0., -1.],  # second exponential cone
         [0., 0., 0., 2.72, 0., 0.],  #
         [0., 2., 0., 0., 0., 0.]
     ])  #
     A = np.round(A.toarray(), decimals=2)
     assert np.all(A == A_expect)
     assert np.all(b == np.array([1., 0., 0., 0., 0., 0., 0.]))
     assert K == [Cone('+', 1), Cone('e', 3), Cone('e', 3)]
Пример #16
0
 def conic_form(self):
     if self._m > 1:
         nontrivial_I = list(set(self.ech.U_I + self.ech.P_I))
         con = self.v[nontrivial_I] >= 0
         # TODO: figure out when above constraint is implied by exponential cone constraints.
         con.epigraph_checked = True
         cd = con.conic_form()
         cone_data = [cd]
         for i in self.ech.U_I:
             idx_set = self.ech.covers[i]
             num_cover = self.ech.cover_counts[i]
             if num_cover == 0:
                 continue
             expr = np.tile(self.v[i], num_cover).view(Expression)
             mat = self.alpha[i, :] - self.alpha[idx_set, :]
             vecvar = self._lifted_mu_vars[i][:self._n]
             # TODO: extend DualSageCone to use the "kernel_basis_matrix" option that's already
             # available in the primal cone for ordinary SAGE (X=R^n).
             # Here, we'd need a matrix "reduced_mat" where range(reduced_mat) = range(mat),
             # but reduced_mat has linearly independent columns. If mat already has linearly
             # independent columns then we use reduced_mat = mat. Otherwise we can get a linearly
             # independent subset of columns by doing row reduction on mat.T and checking where
             # the pivots are.
             if self.settings['compact_dual']:
                 epi = mat @ vecvar
                 cd = elementwise_relent(expr, self.v[idx_set], epi)
                 cone_data.append(cd)
             else:
                 # relative entropy constraints
                 epi = self._relent_epi_vars[i]
                 cd = elementwise_relent(expr, self.v[idx_set], epi)
                 cone_data.append(cd)
                 # Linear inequalities
                 av, ar, ac, _ = comp_aff.matvec_minus_vec(mat, vecvar, epi)
                 num_rows = mat.shape[0]
                 curr_b = np.zeros(num_rows)
                 curr_k = [Cone('+', num_rows)]
                 cone_data.append((av, ar, ac, curr_b, curr_k))
             # membership in cone induced by self.AbK
             if self.X:
                 A, b, K = self.X.A, self.X.b, self.X.K
                 vecvar = self._lifted_mu_vars[i]
                 singlevar = self.v[i]
                 av, ar, ac, curr_b = comp_aff.matvec_plus_vec_times_scalar(
                     A, vecvar, b, singlevar)
                 cone_data.append((av, ar, ac, curr_b, K))
         return cone_data
     else:
         con = self.v >= 0
         con.epigraph_checked = True
         cd = con.conic_form()
         cone_data = [cd]
         return cone_data
Пример #17
0
    def test_LP_systems(self):
        n = 4
        m = 5
        x = Variable(shape=(n, 1), name='x')

        G = np.random.randn(m, n)
        h = G @ np.abs(np.random.randn(n, 1))
        constraints = [G @ x == h,
                       x >= 0]
        # Reference case : the constraints are over x, and we are interested in no variables other than x.
        A0, b0, K0, var_mapping0, _, _ = compile_constrained_system(constraints)
        A0 = A0.toarray()
        #   A0 should be the (m+n)-by-n matrix formed by stacking -G on top of the identity.
        #   b0 should be the (m+n)-length vector formed by concatenating h with the zero vector.
        #   Should see K0 == [('0',m), ('+',n)]
        #   var_mapping0 should be a length-1 dictionary with var_mapping0['x'] == np.arange(n).reshape((n,1)).
        assert np.all(b0 == np.hstack([h.ravel(), np.zeros(n)]))
        assert K0 == [Cone('0', m), Cone('+', n)]
        assert var_maps_equal(var_mapping0, {'x': np.arange(0, n).reshape((n, 1))})
        expected_A0 = np.vstack((-G, np.eye(n)))
        assert np.all(A0 == expected_A0)
Пример #18
0
 def conic_form(self):
     from sageopt.coniclifts.base import ScalarVariable
     # This function assumes that self.expr is affine (i.e. that any necessary epigraph
     # variables have been substituted into the nonlinear expression).
     #
     # The vector "K" returned by this function may only include entries for
     # the zero cone and R_+.
     #
     # Note: signs on coefficients are inverted in this function. This happens
     # because flipping signs on A and b won't affect the zero cone, and
     # it correctly converts affine constraints of the form "expression <= 0"
     # to the form "-expression >= 0". We want this latter form because our
     # primitive cones are the zero cone and R_+.
     if not self.epigraph_checked:
         raise RuntimeError(
             'Cannot canonicalize without check for epigraph substitution.')
     m = self.expr.size
     b = np.empty(shape=(m, ))
     if self.operator == '==':
         K = [Cone('0', m)]
     elif self.operator == '<=':
         K = [Cone('+', m)]
     else:
         raise RuntimeError('Unknown operator.')
     A_rows, A_cols, A_vals = [], [], []
     for i, se in enumerate(self.expr.flat):
         if len(se.atoms_to_coeffs) == 0:
             b[i] = -se.offset
             A_rows.append(i)
             A_cols.append(int(ScalarVariable.curr_variable_count()) - 1)
             A_vals.append(
                 0)  # make sure scipy infers correct dimensions later on.
         else:
             b[i] = -se.offset
             A_rows += [i] * len(se.atoms_to_coeffs)
             col_idx_to_coeff = [(a.id, c)
                                 for a, c in se.atoms_to_coeffs.items()]
             A_cols += [atom_id for (atom_id, _) in col_idx_to_coeff]
             A_vals += [-c for (_, c) in col_idx_to_coeff]
     return A_vals, np.array(A_rows), A_cols, b, K
Пример #19
0
 def test_vector2norm_1(self):
     x = Variable(shape=(3, ), name='x')
     nrm = vector2norm(x)
     con = [nrm <= 1]
     A_expect = np.array([
         [0, 0, 0,
          -1],  # linear inequality constraint in terms of epigraph variable
         [0, 0, 0, 1],  # epigraph component in second order cone constraint
         [1, 0, 0,
          0],  # start of main block in second order cone constraint
         [0, 1, 0, 0],
         [0, 0, 1, 0]
     ])  # end of main block in second order cone constraint
     A, b, K, _1, _2, _3 = compile_constrained_system(con)
     A = np.round(A.toarray(), decimals=1)
     assert np.all(A == A_expect)
     assert np.all(b == np.array([1, 0, 0, 0, 0]))
     assert K == [Cone('+', 1), Cone('S', 4)]
     # value propagation
     x.value = np.zeros(3)
     viol = con[0].violation()
     assert viol == 0
Пример #20
0
 def _primal_apply(c, A, b, K):
     inv_data = {'n': A.shape[1]}
     A, b, K, sep_K = separate_cone_constraints(A,
                                                b,
                                                K,
                                                dont_sep={'0', '+'})
     c = np.hstack([c, np.zeros(shape=(A.shape[1] - len(c)))])
     type_selectors = build_cone_type_selectors(K)
     # Below: inequality constraints that the "user" intended to give to MOSEK.
     A_ineq = A[type_selectors['+'], :]
     b_ineq = b[type_selectors['+']]
     # Below: equality constraints that the "user" intended to give to MOSEK
     A_z = A[type_selectors['0'], :]
     b_z = b[type_selectors['0']]
     # Finally: the matrix "A" and vector "u_c" that appear in the MOSEK documentation as the standard form for
     # an SDP that includes vectorized variables. We use "b" instead of "u_c". It's value in the MOSEK Task will
     # later be specified with MOSEK's "putconboundlist" function.
     A = -sp.vstack([A_ineq, A_z], format='csc')
     b = np.hstack([b_ineq, b_z])
     K = [Cone('+', A_ineq.shape[0]), Cone('0', A_z.shape[0])]
     # Return values
     data = {'A': A, 'b': b, 'K': K, 'sep_K': sep_K, 'c': c}
     return data, inv_data
Пример #21
0
    def test_SDP_system_1(self):
        x = Variable(shape=(2, 2), name='x', var_properties=['symmetric'])

        D = np.random.randn(2, 2)
        D += D.T
        D /= 2.0
        B = np.random.rand(1, 2)
        C = np.diag([3, 0.5])
        constraints = [affine.trace(D @ x) == 5,
                       B @ x @ B.T >= 1,
                       B @ x @ B.T >> 1,  # a 1-by-1 LMI
                       C @ x @ C.T >> -2]
        A, b, K, _, _, _ = compile_constrained_system(constraints)
        A = A.toarray()
        assert K == [Cone('0', 1), Cone('+', 1), Cone('P', 1), Cone('P', 3)]
        expect_row_0 = -np.array([D[0, 0], 2 * D[1, 0], D[1, 1]])
        assert np.allclose(expect_row_0, A[0, :])
        temp = B.T @ B
        expect_row_1and2 = np.array([temp[0, 0], 2 * temp[0, 1], temp[1, 1]])
        assert np.allclose(expect_row_1and2, A[1, :])
        assert np.allclose(expect_row_1and2, A[2, :])
        expect_rows_3to6 = np.diag([C[0, 0] ** 2, C[0, 0] * C[1, 1], C[1, 1] ** 2])
        assert np.allclose(expect_rows_3to6, A[3:, :])
        assert np.all(b == np.array([5, -1, -1, 2, 2, 2]))
Пример #22
0
 def _age_vectors_sum_to_c(self):
     nonconst_locs = np.ones(self._m, dtype=bool)
     # The line of code below had to be disabled when we used special
     # "covers" for polynomials. TODO: figure out what about the polynomial
     # covers makes these constraints necessary here when they weren't
     # necessary before.
     #
     #   nonconst_locs[self.ech.N_I] = False
     aux_c_vars = list(self.age_vectors.values())
     aux_c_vars = aff.column_stack(aux_c_vars)
     aux_c_vars = aux_c_vars[nonconst_locs, :]
     main_c_var = self.c[nonconst_locs]
     A_vals, A_rows, A_cols, b = comp_aff.columns_sum_leq_vec(
         aux_c_vars, main_c_var, mat_offsets=True)
     conetype = '0' if self.settings['sum_age_force_equality'] else '+'
     K = [Cone(conetype, b.size)]
     return A_vals, A_rows, A_cols, b, K
Пример #23
0
 def conic_form(self):
     if self._m > 1:
         nontrivial_I = list(set(self.ech.U_I + self.ech.P_I))
         con = self.v[nontrivial_I] >= 0
         # TODO: figure out when above constraint is implied by exponential cone constraints.
         con.epigraph_checked = True
         cd = con.conic_form()
         cone_data = [cd]
         for i in self.ech.U_I:
             idx_set = self.ech.expcovers[i]
             num_cover = self.ech.expcover_counts[i]
             if num_cover == 0:
                 continue
             expr = np.tile(self.v[i], num_cover).view(Expression)
             mat = self.alpha[i, :] - self.alpha[idx_set, :]
             vecvar = self._lifted_mu_vars[i][:self._n]
             if self.settings['compact_dual']:
                 epi = mat @ vecvar
                 cd = elementwise_relent(expr, self.v[idx_set], epi)
                 cone_data.append(cd)
             else:
                 # relative entropy constraints
                 epi = self._relent_epi_vars[i]
                 cd = elementwise_relent(expr, self.v[idx_set], epi)
                 cone_data.append(cd)
                 # Linear inequalities
                 av, ar, ac, _ = comp_aff.matvec_minus_vec(mat, vecvar, epi)
                 num_rows = mat.shape[0]
                 curr_b = np.zeros(num_rows)
                 curr_k = [Cone('+', num_rows)]
                 cone_data.append((av, ar, ac, curr_b, curr_k))
             # membership in cone induced by self.AbK
             if self.X is not None:
                 A, b, K = self.X.A, self.X.b, self.X.K
                 vecvar = self._lifted_mu_vars[i]
                 singlevar = self.v[i]
                 av, ar, ac, curr_b = comp_aff.matvec_plus_vec_times_scalar(A, vecvar, b, singlevar)
                 cone_data.append((av, ar, ac, curr_b, K))
         return cone_data
     else:
         con = self.v >= 0
         con.epigraph_checked = True
         cd = con.conic_form()
         cone_data = [cd]
         return cone_data
Пример #24
0
 def conic_form(self):
     self_dual_cones = {'+', 'S', 'P'}
     start_row = 0
     y_mod = []
     for co in self.K:
         stop_row = start_row + co.len
         if co.type in self_dual_cones:
             y_mod.append(self.y[start_row:stop_row])
         elif co.type == 'e':
             temp_y = np.array([
                 -self.y[start_row + 2],
                 np.exp(1) * self.y[start_row + 1], -self.y[start_row]
             ])
             y_mod.append(temp_y)
         elif co.type != '0':
             raise RuntimeError('Unexpected cone type "%s".' % str(co.type))
         start_row = stop_row
     y_mod = np.hstack(y_mod)
     y_mod = Expression(y_mod)
     # Now we can pretend all nonzero cones are self-dual.
     A_vals, A_rows, A_cols = [], [], []
     cur_K = [Cone(co.type, co.len) for co in self.K if co.type != '0']
     cur_K_size = sum([co.len for co in cur_K])
     if cur_K_size > 0:
         b = np.zeros(shape=(cur_K_size, ))
         for i, se in enumerate(y_mod):
             if len(se.atoms_to_coeffs) == 0:
                 b[i] = se.offset
                 A_rows.append(i)
                 A_cols.append(
                     int(ScalarVariable.curr_variable_count()) - 1)
                 A_vals.append(
                     0
                 )  # make sure scipy infers correct dimensions later on.
             else:
                 b[i] = se.offset
                 A_rows += [i] * len(se.atoms_to_coeffs)
                 col_idx_to_coeff = [(a.id, c)
                                     for a, c in se.atoms_to_coeffs.items()]
                 A_cols += [atom_id for (atom_id, _) in col_idx_to_coeff]
                 A_vals += [c for (_, c) in col_idx_to_coeff]
         return [(A_vals, np.array(A_rows), A_cols, b, cur_K)]
     else:
         return [([], np.zeros(shape=(0, ),
                               dtype=int), [], np.zeros(shape=(0, )), [])]
Пример #25
0
 def epigraph_conic_form(self):
     """
     max(0,x) <= epi is represented as 0 <= epi,  x <= epi.
     """
     b = np.zeros(2, )
     K = [Cone('+', 2)]
     A_rows = [0, 1]
     A_cols = [self._epigraph_variable.id, self._epigraph_variable.id]
     A_vals = [1, 1]
     x = self.args[0]
     num_nonconst = len(x) - 1
     if num_nonconst > 0:
         A_rows += num_nonconst * [1]
         A_cols += [var.id for var, co in x[:-1]]
         A_vals += [-co for var, co in x[:-1]]
     # b[0] = 0; already set.
     b[1] = -x[-1][1]
     return A_vals, np.array(A_rows), A_cols, b, K
Пример #26
0
 def conic_form(self):
     from sageopt.coniclifts.base import Expression, ScalarVariable
     expr = np.triu(self.arg).view(Expression)
     expr = expr[np.triu_indices(expr.shape[0])]
     K = [Cone('P', expr.size)]
     b = np.empty(shape=(expr.size, ))
     A_rows, A_cols, A_vals = [], [], []
     for i, se in enumerate(expr):
         b[i] = se.offset
         if len(se.atoms_to_coeffs) == 0:
             A_rows.append(i)
             A_cols.append(ScalarVariable.curr_variable_count())
             A_vals.append(
                 0)  # make sure scipy infers correct dimensions later on.
         else:
             A_rows += [i] * len(se.atoms_to_coeffs)
             cols_and_coeff = [(a.id, c)
                               for a, c in se.atoms_to_coeffs.items()]
             A_cols += [atom_id for (atom_id, _) in cols_and_coeff]
             A_vals += [c for (_, c) in cols_and_coeff]
     return [(A_vals, np.array(A_rows), A_cols, b, K)]
Пример #27
0
 def _condsage_conic_form(self):
     cone_data = []
     lifted_alpha = self.alpha
     if self._lifted_n > self._n:
         zero_block = np.zeros(shape=(self._m, self._lifted_n - self._n))
         lifted_alpha = np.hstack((lifted_alpha, zero_block))
     for i in self.ech.U_I:
         if i in self._nus:
             idx_set = self.ech.covers[i]
             # relative entropy inequality constraint
             x = self._nus[i]
             y = self.age_vectors[i][idx_set]
             z = -self.age_vectors[i][i] + self._eta_vars[i] @ self.X.b
             epi = self._relent_epi_vars[i]
             cd = sum_relent(x, y, z, epi, y_scale=np.exp(1))
             cone_data.append(cd)
             # linear equality constraints
             mat1 = (lifted_alpha[idx_set, :] - lifted_alpha[i, :]).T
             mat2 = -self.X.A.T
             var1 = self._nus[i]
             var2 = self._eta_vars[i]
             av, ar, ac, _ = comp_aff.matvec_plus_matvec(
                 mat1, var1, mat2, var2)
             num_rows = mat1.shape[0]
             curr_b = np.zeros(num_rows, )
             curr_k = [Cone('0', num_rows)]
             cone_data.append((av, ar, ac, curr_b, curr_k))
             # domain for "eta"
             con = DualProductCone(self._eta_vars[i], self.X.K)
             cone_data.extend(con.conic_form())
         else:
             con = 0 <= self.age_vectors[i][i]
             con.epigraph_checked = True
             cd = con.conic_form()
             cone_data.append(cd)
     cone_data.append(self._age_vectors_sum_to_c())
     return cone_data
Пример #28
0
    def epigraph_conic_form(self):
        """
        Generate conic constraint for epigraph
            self.args[0] * ln( self.args[0] / self.args[1] ) <= self._epigraph_variable.

        :return:
        """
        b = np.zeros(3,)
        K = [Cone('e', 3)]
        # ^ initializations
        A_rows, A_cols, A_vals = [0], [self._epigraph_variable.id], [-1]
        # ^ first row
        x = self.args[0]
        num_nonconst = len(x) - 1
        if num_nonconst > 0:
            A_rows += num_nonconst * [2]
            A_cols += [var.id for var, co in x[:-1]]
            A_vals += [co for var, co in x[:-1]]
        else:
            A_rows.append(2)
            A_cols.append(ScalarVariable.curr_variable_count() - 1)
            A_vals.append(0)
        b[2] = x[-1][1]
        # ^ third row
        y = self.args[1]
        num_nonconst = len(y) - 1
        if num_nonconst > 0:
            A_rows += num_nonconst * [1]
            A_cols += [var.id for var, co in y[:-1]]
            A_vals += [co for var, co in y[:-1]]
        else:
            A_rows.append(1)
            A_cols.append(ScalarVariable.curr_variable_count() - 1)
            A_vals.append(0)
        b[1] = y[-1][1]
        # ^ second row
        return A_vals, np.array(A_rows), A_cols, b, K
Пример #29
0
def separate_cone_constraints(A, b, K, dont_sep=None):
    """
    Given an affine representation {x : A @ x + b \in K}, construct a direct
    representation {x : \exists y with  G @ [x,y] + h \in K1, y \in K2},
    where ``K1`` consists exclusively of cones types in ``dont_sep``.

    Parameters
    ----------
    A : scipy.sparse.csc_matrix
        Has ``m`` rows and ``n`` columns.
    b : ndarray
        Of size ``m``.
    K : List[coniclifts.cones.Cone]
        Satisfies ``sum([co.len for co in K]) == m``.
    dont_sep : set of coniclifts.cones.Cone
        Cones that may be retained in the "affine part" of the feasible set's
        direct representation.

    Returns
    -------

    Examples
    --------

    EX1: Suppose the conic system (A,b,K) can be expressed as {x : B @ x == d, G @ x + h >=0, x >=0 }, where G
    is a non-square matrix. Then by calling this function with dont_sep=None, we obtain the conic system
       { [x,y] : A1 @ [x, y] == b0 } \cap {[x, y] : x >= 0, y >= 0}
    where A1 = [[B, 0], [G, -I]] and b0 = [d, -h]. This conic system is equivalent to (A,b,K) once we
    project onto the x coordinates.

    This case is interesting because it shows that a cone constraint "x >= 0" can be separated from
    from the matrix system, even though "x" appears in th inequality constraint "G @ x + d >= 0".


    EX2: Suppose the conic system (A,b,K) can be expressed as {x : B @ x == d, L <= x <= U }.
    Then by calling this function with dont_sep=None, we obtain the conic system
    { [x,y,z] : A1 @ [x,y,z] == b0 } \cap {[x,y,z] : y >= 0, z >= 0 }
    where A1 = [[B, 0, 0], [I, -I, 0], [-I, 0, -I]] and b0 = [b, L, -U]. This conic system is equivalent
    to (A,b,K) once we project out the [y,z] components.

    NOTE: here we ended up introducing slack variables for both inequalities in the variable "x", rather
    than writing {[x,w] : B @ x == b + B @ L, U - L - x - w == 0} \cap {[x,w] : x >= 0, w >= 0 }. This
    Second option is still a valid formulation, but it is not one that this function would generate.


    EX3: Suppose the conic system (A,b,K) can be expressed as  { x : B @ x == d, G @ x <=_{K_prime} h },
    where every entry of G is nonzero, A is not square, and K_prime does not contain the zero cone.
    Then dont_sep={'0'} yields
    { [x, y] : A1 @ [x, y] == b0 } \cap { [x, y] : y \in K_{prime} }
    for A1 = [[B, 0], [-G, -I]] and b0 = [d, -h].

    Notes
    -----
    dont_sep=None is equivalent to dont_sep={'0'}.

    Common use-case: Passing dont_sep={'0','+'}. This ensures that any constraints on nonlinear cones
            (i.e. exp, psd, soc, etc...) are stated simply as "x[indices_j] \in K_j", where
            "indices_{j1}" and "indices_{j2}" are disjoint whenever j1 \neq j2. This is the main
            modeling requirement for MOSEK's standard form.

    Alternative use-case: Passing dont_sep={'+','0','e','S'} for a conic system involving semidefinite
            constraints. This will force all of the "SDP-ness" of the conic system into disjoint slack variables,
            while aspects of the conic system regarding other cones (e.g. second order cones, exponential cones,
            nonnegative orthants, etc...) are left alone.

    """
    K = copy.copy(K)
    if dont_sep is None:
        dont_sep = set('0')
    allowed = {'0'}.union(
        dont_sep)  # the zero cone is never replaced by slack variables
    running_row_idx = 0
    running_new_var_idx = 0
    slacks_K = []
    aug_data = [[], [], []
                ]  # the constituent lists will store values, rows, and columns
    for i in range(len(K)):
        co_type, co_len = K[i].type, K[i].len
        if co_type not in allowed:
            new_col_idxs = np.arange(running_new_var_idx,
                                     running_new_var_idx + co_len)
            running_new_var_idx += co_len
            aug_data[2].append(new_col_idxs)
            new_slack_co = Cone(co_type, co_len,
                                {'col mapping': A.shape[1] + new_col_idxs})
            slacks_K.append(new_slack_co)
            aug_data[1].append(
                np.arange(running_row_idx, running_row_idx + co_len))
            K[i] = Cone('0', co_len)
        running_row_idx += co_len
    if running_new_var_idx > 0:
        aug_data[2] = np.hstack(aug_data[2])
        aug_data[1] = np.hstack(aug_data[1])
        aug_data[0] = -1 * np.ones(len(aug_data[1]))
        augmenting_matrix = sp.csc_matrix(
            (aug_data[0], (aug_data[1], aug_data[2])),
            shape=(A.shape[0], running_new_var_idx))
        A = sp.hstack([A, augmenting_matrix], format='csc')
    return A, b, K, slacks_K