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)]
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
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
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)
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
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
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
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
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
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
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)]
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
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
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
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)]
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
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)
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
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
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
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]))
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
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
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, )), [])]
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
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)]
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
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
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