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
def test_diagflat(self): x = np.array([[1, 2], [3, 4]]) x_cl = Variable(shape=x.shape) x_cl.value = x expected = np.diagflat(x) actual = aff.diagflat(x_cl) assert np.allclose(expected, actual.value)
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
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 _ordsage_init_variables(self): for i in self.ech.U_I: num_cover = self.ech.expcover_counts[i] if num_cover > 0: var_name = 'nu^{(' + str(i) + ')}_' + self.name if self.settings['kernel_basis']: var_name = '_pre_' + var_name idx_set = self.ech.expcovers[i] mat = (self.alpha[idx_set, :] - self.alpha[i, :]).T nu_i_basis = kernel_basis(mat) self._nu_bases[i] = nu_i_basis pre_nu_i = Variable(shape=(nu_i_basis.shape[1],), name=var_name) self._pre_nus[i] = pre_nu_i self._nus[i] = self._nu_bases[i] @ pre_nu_i else: var_name = 'nu^{(' + str(i) + ')}_' + self.name nu_i = Variable(shape=(num_cover,), name=var_name) self._nus[i] = nu_i var_name = '_relent_epi_^{(' + str(i) + ')}_' + self.name self._relent_epi_vars[i] = Variable(shape=(num_cover,), name=var_name) c_len = self._c_len_check_infeas(num_cover, i) var_name = 'c^{(' + str(i) + ')}_{' + self.name + '}' self._c_vars[i] = Variable(shape=(c_len,), name=var_name) if self.settings['kernel_basis']: self._variables += list(self._pre_nus.values()) else: self._variables += list(self._nus.values()) self._variables += list(self._c_vars.values()) self._variables += list(self._relent_epi_vars.values()) pass
def test_linear_equality_1(self): n, m = 3, 2 np.random.seed(0) x0 = np.random.randn(n, ).round(decimals=5) A = np.random.randn(m, n).round(decimals=5) b0 = A @ x0 x = Variable(shape=(n, ), name='x') constraint = A @ x == b0 # Test basic constraint attributes assert constraint.epigraph_checked # equality constraints are automatically checked. my_vars = constraint.variables() assert len(my_vars) == 1 and my_vars[0].name == x.name # Test ability to correctly compute violations x.value = x0 viol = constraint.violation() assert viol < 1e-15 viol = constraint.violation(norm_ord=1) assert viol < 1e-15 x.value = np.zeros(n, ) viol = constraint.violation() assert abs(viol - np.linalg.norm(b0, ord=2)) < 1e-15 viol = constraint.violation(norm_ord=np.inf) assert abs(viol - np.linalg.norm(b0, ord=np.inf)) < 1e-15
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
def test_triu(self): A = np.random.randn(5, 5).round(decimals=3) A_cl = Variable(shape=A.shape) A_cl.value = A temp = aff.triu(A) expr0 = aff.sum(temp) expr1 = aff.sum(np.triu(A_cl)) assert Expression.are_equivalent(expr0, expr1.value)
def test_tile(self): x = np.array([0, 1, 2]) x_cl = Variable(shape=x.shape) x_cl.value = x A = aff.tile(x_cl, 2) assert np.allclose(np.tile(x, 2), A.value) expr0 = aff.sum(A) expr1 = aff.sum(x) * 2 assert Expression.are_equivalent(expr0.value, expr1)
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
def test_kron(self): I = np.eye(2) X = Variable(shape=(2, 2)) expr0 = aff.kron(I, X) expr1 = aff.kron(X, I) X0 = np.random.randn(2, 2).round(decimals=3) X.value = X0 assert np.allclose(expr0.value, np.kron(I, X0)) assert np.allclose(expr1.value, np.kron(X0, I))
def test_block(self): A = np.eye(2) * 2 A_cl = Variable(shape=A.shape) A_cl.value = A B = np.eye(3) * 3 expected = np.block([[A, np.zeros((2, 3))], [np.ones((3, 2)), B]]) actual = aff.block([[A_cl, np.zeros((2, 3))], [np.ones((3, 2)), B]]) assert np.allclose(expected, actual.value) A_cl.value = 1 + A # or 2*A, or really anything different from A itself. assert not np.allclose(expected, actual.value)
def test_ordinary_sage_primal_1(self): n, m = 2, 5 np.random.seed(0) alpha = np.random.randn(m, n) c = Variable(shape=(m, ), name='test_c') constraint = sage_cones.PrimalSageCone(c, alpha, X=None, name='test') c0 = np.ones(shape=(m, )) c.value = c0 viol_default = constraint.violation() assert viol_default == 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)
def test_array_split(self): x = np.arange(8.0) x_cl = Variable(shape=x.shape) x_cl.value = x expected = np.array_split(x, 3) actual = aff.array_split(x_cl, 3) assert np.all( [np.allclose(expected[i], actual[i].value) for i in range(3)]) x_cl.value = 1 + x assert not np.all( [np.allclose(expected[i], actual[i].value) for i in range(3)])
def test_dsplit(self): x = np.arange(16.0).reshape(2, 2, 4) x_cl = Variable(shape=x.shape) x_cl.value = x expected = np.dsplit(x, 2) actual = aff.dsplit(x_cl, 2) assert np.all( [np.allclose(expected[i], actual[i].value) for i in range(2)]) x_cl.value = 1 + x assert not np.all( [np.allclose(expected[i], actual[i].value) for i in range(2)])
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_dot(self): x = Variable(shape=(4, )) a = np.array([1, 2, 3, 4]) expr0 = aff.dot(x, a) expr1 = aff.dot(a, x) x0 = np.random.rand(4).round(decimals=4) expect = np.dot(a, x0) x.value = x0 actual0 = expr0.value actual1 = expr1.value assert actual0 == expect assert actual1 == expect assert Expression.are_equivalent(expr0, expr1)
def test_outer(self): x = Variable(shape=(3, )) x0 = np.random.randn(3).round(decimals=3) x.value = x0 a = np.array([1, 2, 3, 4]) expr0 = aff.outer(a, x) assert np.allclose(expr0.value, np.outer(a, x0)) expr1 = aff.outer(x, a) assert np.allclose(expr1.value, np.outer(x0, a)) b = np.array([9, 8]) expr2 = aff.outer(b, x) assert np.allclose(expr2.value, np.outer(b, x0)) expr3 = aff.outer(x, b) assert np.allclose(expr3.value, np.outer(x0, b))
def _initialize_variables(self): self._variables = self.v.variables() if self._m > 1: for i in self.ech.U_I: var_name = 'mu[' + str(i) + ']_{' + self.name + '}' self._lifted_mu_vars[i] = Variable(shape=(self._lifted_n,), name=var_name) self._variables.append(self._lifted_mu_vars[i]) self.mu_vars[i] = self._lifted_mu_vars[i][:self._n] num_cover = self.ech.expcover_counts[i] if num_cover > 0 and not self.settings['compact_dual']: var_name = '_relent_epi_[' + str(i) + ']_{' + self.name + '}' epi = Variable(shape=(num_cover,), name=var_name) self._relent_epi_vars[i] = epi self._variables.append(epi) pass
def test_inner(self): # test with scalar inputs x = Variable() a = 2.0 expr0 = aff.inner(a, x) expr1 = aff.inner(x, a) assert Expression.are_equivalent(expr0, expr1) # test with multidimensional arrays a = np.arange(24).reshape((2, 3, 4)) x = Variable(shape=(4, )) x.value = np.arange(4) expr = aff.inner(a, x) expect = np.inner(a, np.arange(4)) actual = expr.value assert np.allclose(expect, actual)
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_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 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_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
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
def __init__(self, args): args = args.as_expr().ravel() self._args = tuple(self.parse_arg(v) for v in args) self._id = Vector2Norm._VECTOR_2_NORM_COUNTER_ Vector2Norm._VECTOR_2_NORM_COUNTER_ += 1 v = Variable(shape=(), name='_vec2norm_epi[' + str(self.id) + ']_') self._epigraph_variable = v[()].scalar_variables()[0] self._evaluator = Vector2Norm._vector2norm_evaluator
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 test_trace_and_diag(self): x = Variable(shape=(5, )) A = np.random.randn(5, 5).round(decimals=3) for i in range(5): A[i, i] = 0 temp = A + aff.diag(x) expr0 = aff.trace(temp) expr1 = aff.sum(x) assert Expression.are_equivalent(expr0, expr1)
def _condsage_init_variables(self): for i in self.ech.U_I: num_cover = self.ech.expcover_counts[i] if num_cover > 0: var_name = 'nu^{(' + str(i) + ')}_' + self.name self._nus[i] = Variable(shape=(num_cover,), name=var_name) var_name = '_relent_epi_^{(' + str(i) + ')}_' + self.name self._relent_epi_vars[i] = Variable(shape=(num_cover,), name=var_name) var_name = 'eta^{(' + str(i) + ')}_{' + self.name + '}' self._eta_vars[i] = Variable(shape=(self.X.b.size,), name=var_name) c_len = self._c_len_check_infeas(num_cover, i) var_name = 'c^{(' + str(i) + ')}_{' + self.name + '}' self._c_vars[i] = Variable(shape=(c_len,), name=var_name) self._variables += list(self._nus.values()) self._variables += list(self._c_vars.values()) self._variables += list(self._relent_epi_vars.values()) self._variables += list(self._eta_vars.values()) pass