def test_numpy_matrix(self): interface = intf.get_matrix_interface(np.matrix) # const_to_matrix mat = interface.const_to_matrix([1, 2, 3]) self.assertEquals(interface.size(mat), (3, 1)) mat = interface.const_to_matrix([[1], [2], [3]]) self.assertEquals(mat[0, 0], 1) # identity mat = interface.identity(4) cvxopt_dense = intf.get_matrix_interface(cvxopt.matrix) cmp_mat = interface.const_to_matrix(cvxopt_dense.identity(4)) self.assertEquals(interface.size(mat), interface.size(cmp_mat)) assert not (mat - cmp_mat).any() # scalar_matrix mat = interface.scalar_matrix(2, 4, 3) self.assertEquals(interface.size(mat), (4, 3)) self.assertEquals(interface.index(mat, (1, 2)), 2) # reshape mat = interface.const_to_matrix([[1, 2, 3], [3, 4, 5]]) mat = interface.reshape(mat, (6, 1)) self.assertEquals(interface.index(mat, (4, 0)), 4) # index mat = interface.const_to_matrix([[1, 2, 3, 4], [3, 4, 5, 6]]) self.assertEquals(interface.index(mat, (0, 1)), 3) mat = interface.index(mat, (slice(1, 4, 2), slice(0, 2, None))) assert not (mat - np.matrix("2 4; 4 6")).any() # Sign self.sign_for_intf(interface)
def test_ndarray(self): interface = intf.get_matrix_interface(np.ndarray) # const_to_matrix mat = interface.const_to_matrix([1,2,3]) self.assertEquals(interface.size(mat), (3,1)) mat = interface.const_to_matrix([1,2]) self.assertEquals(interface.size(mat), (2,1)) # CVXOPT sparse conversion tmp = intf.get_matrix_interface(cvxopt.spmatrix).const_to_matrix([1,2,3]) mat = interface.const_to_matrix(tmp) assert (mat == interface.const_to_matrix([1,2,3])).all() # identity mat = interface.identity(4) cvxopt_dense = intf.get_matrix_interface(cvxopt.matrix) cmp_mat = interface.const_to_matrix(cvxopt_dense.identity(4)) self.assertEquals(interface.size(mat), interface.size(cmp_mat)) assert (mat == cmp_mat).all() # scalar_matrix mat = interface.scalar_matrix(2,4,3) self.assertEquals(interface.size(mat), (4,3)) self.assertEquals(interface.index(mat, (1,2)), 2) # reshape mat = interface.const_to_matrix([[1,2,3],[3,4,5]]) mat = interface.reshape(mat, (6,1)) self.assertEquals(interface.index(mat, (4,0)), 4) # index mat = interface.const_to_matrix([[1,2,3,4],[3,4,5,6]]) self.assertEquals( interface.index(mat, (0,1)), 3) mat = interface.index(mat, (slice(1,4,2), slice(0,2,None))) self.assertEquals(list(mat.flatten('C')), [2,4,4,6]) # Scalars and matrices. scalar = interface.const_to_matrix(2) mat = interface.const_to_matrix([1,2,3]) assert (scalar*mat == interface.const_to_matrix([2,4,6])).all() assert (scalar - mat == interface.const_to_matrix([1,0,-1])).all()
def test_conversion_between_intf(self): """Test conversion between every pair of interfaces. """ interfaces = [intf.get_matrix_interface(cvxopt.matrix), intf.get_matrix_interface(cvxopt.spmatrix), intf.get_matrix_interface(np.ndarray), intf.get_matrix_interface(np.matrix), intf.get_matrix_interface(sp.csc_matrix)] cmp_mat = [[1,2,3,4],[3,4,5,6],[-1,0,2,4]] for i in range(len(interfaces)): for j in range(i+1, len(interfaces)): intf1 = interfaces[i] mat1 = intf1.const_to_matrix(cmp_mat) intf2 = interfaces[j] mat2 = intf2.const_to_matrix(cmp_mat) for col in range(len(cmp_mat)): for row in range(len(cmp_mat[0])): key = (slice(row, row+1, None), slice(col, col+1, None)) self.assertEqual(intf1.index(mat1, key), intf2.index(mat2, key)) # Convert between the interfaces. self.assertEqual(cmp_mat[col][row], intf1.index(intf1.const_to_matrix(mat2), key)) self.assertEqual(intf2.index(intf2.const_to_matrix(mat1), key), cmp_mat[col][row])
def test_numpy_matrix(self): interface = intf.get_matrix_interface(np.matrix) # const_to_matrix mat = interface.const_to_matrix([1, 2, 3]) self.assertEqual(interface.size(mat), (3, 1)) mat = interface.const_to_matrix([[1], [2], [3]]) self.assertEqual(mat[0, 0], 1) # identity # mat = interface.identity(4) # cvxopt_dense = intf.get_matrix_interface(cvxopt.matrix) # cmp_mat = interface.const_to_matrix(cvxopt_dense.identity(4)) # self.assertEqual(interface.size(mat), interface.size(cmp_mat)) # assert not (mat - cmp_mat).any() # scalar_matrix mat = interface.scalar_matrix(2, 4, 3) self.assertEqual(interface.size(mat), (4, 3)) self.assertEqual(interface.index(mat, (1, 2)), 2) # reshape mat = interface.const_to_matrix([[1, 2, 3], [3, 4, 5]]) mat = interface.reshape(mat, (6, 1)) self.assertEqual(interface.index(mat, (4, 0)), 4) mat = interface.const_to_matrix(1, convert_scalars=True) self.assertEqual(type(interface.reshape(mat, (1, 1))), type(mat)) # index mat = interface.const_to_matrix([[1, 2, 3, 4], [3, 4, 5, 6]]) self.assertEqual(interface.index(mat, (0, 1)), 3) mat = interface.index(mat, (slice(1, 4, 2), slice(0, 2, None))) assert not (mat - np.matrix("2 4; 4 6")).any() # Sign self.sign_for_intf(interface)
def test_cvxopt_sparse(self): interface = intf.get_matrix_interface(cvxopt.spmatrix) # const_to_matrix mat = interface.const_to_matrix([1, 2, 3]) self.assertEquals(interface.size(mat), (3, 1)) # identity mat = interface.identity(4) cmp_mat = interface.const_to_matrix(np.eye(4)) self.assertEquals(interface.size(mat), interface.size(cmp_mat)) assert not mat - cmp_mat assert intf.is_sparse(mat) # scalar_matrix mat = interface.scalar_matrix(2, 4, 3) self.assertEquals(interface.size(mat), (4, 3)) self.assertEquals(interface.index(mat, (1, 2)), 2) # reshape mat = interface.const_to_matrix([[1, 2, 3], [3, 4, 5]]) mat = interface.reshape(mat, (6, 1)) self.assertEquals(interface.index(mat, (4, 0)), 4) # Test scalars. scalar = interface.scalar_matrix(1, 1, 1) self.assertEquals(type(scalar), cvxopt.spmatrix) scalar = interface.scalar_matrix(1, 1, 3) self.assertEquals(scalar.size, (1, 3)) # index mat = interface.const_to_matrix([[1, 2, 3, 4], [3, 4, 5, 6]]) self.assertEquals(interface.index(mat, (0, 1)), 3) mat = interface.index(mat, (slice(1, 4, 2), slice(0, 2, None))) self.assertEquals(list(mat), [2, 4, 4, 6]) # Sign self.sign_for_intf(interface)
def test_mul_elemwise(self): """Tests problems with mul_elemwise. """ c = [[1, -1], [2, -2]] expr = mul_elemwise(c, self.A) obj = Minimize(normInf(expr)) p = Problem(obj, [self.A == 5]) result = p.solve() self.assertAlmostEqual(result, 10) self.assertItemsAlmostEqual(expr.value, [5, -5] + [10, -10]) # Test with a sparse matrix. import cvxopt interface = intf.get_matrix_interface(cvxopt.spmatrix) c = interface.const_to_matrix([1,2]) expr = mul_elemwise(c, self.x) obj = Minimize(normInf(expr)) p = Problem(obj, [self.x == 5]) result = p.solve() self.assertAlmostEqual(result, 10) self.assertItemsAlmostEqual(expr.value, [5, 10]) # Test promotion. c = [[1, -1], [2, -2]] expr = mul_elemwise(c, self.a) obj = Minimize(normInf(expr)) p = Problem(obj, [self.a == 5]) result = p.solve() self.assertAlmostEqual(result, 10) self.assertItemsAlmostEqual(expr.value, [5, -5] + [10, -10])
def test_cvxopt_dense(self): interface = intf.get_matrix_interface(cvxopt.matrix) # const_to_matrix mat = interface.const_to_matrix([1, 2, 3]) self.assertEquals(interface.size(mat), (3, 1)) sp_mat = sp.coo_matrix(([1, 2], ([3, 4], [2, 1])), (5, 5)) mat = interface.const_to_matrix(sp_mat) self.assertEquals(interface.size(mat), (5, 5)) # identity mat = interface.identity(4) cmp_mat = interface.const_to_matrix(np.eye(4)) self.assertEquals(type(mat), type(cmp_mat)) self.assertEquals(interface.size(mat), interface.size(cmp_mat)) assert not mat - cmp_mat # scalar_matrix mat = interface.scalar_matrix(2, 4, 3) self.assertEquals(interface.size(mat), (4, 3)) self.assertEquals(interface.index(mat, (1, 2)), 2) # reshape mat = interface.const_to_matrix([[1, 2, 3], [3, 4, 5]]) mat = interface.reshape(mat, (6, 1)) self.assertEquals(interface.index(mat, (4, 0)), 4) # index mat = interface.const_to_matrix([[1, 2, 3, 4], [3, 4, 5, 6]]) self.assertEquals(interface.index(mat, (0, 1)), 3) mat = interface.index(mat, (slice(1, 4, 2), slice(0, 2, None))) self.assertEquals(list(mat), [2, 4, 4, 6]) # Sign self.sign_for_intf(interface)
def test_scipy_sparse(self): interface = intf.get_matrix_interface(sp.csc_matrix) # const_to_matrix mat = interface.const_to_matrix([1,2,3]) self.assertEquals(interface.size(mat), (3,1)) C = cvxopt.spmatrix([1,1,1,1,1],[0,1,2,0,0,],[0,0,0,1,2]) mat = interface.const_to_matrix(C) self.assertEquals(interface.size(mat), (3, 3)) # identity mat = interface.identity(4) cmp_mat = interface.const_to_matrix(np.eye(4)) self.assertEquals(interface.size(mat), interface.size(cmp_mat)) assert (mat - cmp_mat).nnz == 0 # scalar_matrix mat = interface.scalar_matrix(2,4,3) self.assertEquals(interface.size(mat), (4,3)) self.assertEquals(interface.index(mat, (1,2)), 2) # reshape mat = interface.const_to_matrix([[1,2,3],[3,4,5]]) mat = interface.reshape(mat, (6,1)) self.assertEquals(interface.index(mat, (4,0)), 4) # Test scalars. scalar = interface.scalar_matrix(1, 1, 1) self.assertEquals(type(scalar), np.ndarray) scalar = interface.scalar_matrix(1, 1, 3) self.assertEquals(scalar.shape, (1,3)) # index mat = interface.const_to_matrix([[1,2,3,4],[3,4,5,6]]) self.assertEquals( interface.index(mat, (0,1)), 3) mat = interface.index(mat, (slice(1,4,2), slice(0,2,None))) assert not (mat - np.matrix("2 4; 4 6")).any()
def quad_form(x, P): """ Alias for :math:`x^T P x`. """ x, P = map(Expression.cast_to_const, (x, P)) # Check dimensions. n = P.size[0] if P.size[1] != n or x.size != (n,1): raise Exception("Invalid dimensions for arguments.") if x.is_constant(): return x.T * P * x elif P.is_constant(): np_intf = intf.get_matrix_interface(np.ndarray) P = np_intf.const_to_matrix(P.value) # Force symmetry P = (P + P.T) / 2.0 scale, M1, M2 = _decomp_quad(P) ret = 0 if M1.size > 0: ret += scale * sum_squares(Constant(M1.T) * x) if M2.size > 0: ret -= scale * sum_squares(Constant(M2.T) * x) return ret else: raise Exception("At least one argument to quad_form must be constant.")
def test_cvxopt_sparse(self): interface = intf.get_matrix_interface(cvxopt.spmatrix) # const_to_matrix mat = interface.const_to_matrix([1, 2, 3]) self.assertEquals(interface.size(mat), (3, 1)) # identity mat = interface.identity(4) cmp_mat = interface.const_to_matrix(np.eye(4)) self.assertEquals(interface.size(mat), interface.size(cmp_mat)) assert not mat - cmp_mat assert intf.is_sparse(mat) # scalar_matrix mat = interface.scalar_matrix(2, 4, 3) self.assertEquals(interface.size(mat), (4, 3)) self.assertEquals(interface.index(mat, (1, 2)), 2) # reshape mat = interface.const_to_matrix([[1, 2, 3], [3, 4, 5]]) mat = interface.reshape(mat, (6, 1)) self.assertEquals(interface.index(mat, (4, 0)), 4) mat = interface.const_to_matrix(1, convert_scalars=True) self.assertEquals(type(interface.reshape(mat, (1, 1))), type(mat)) # Test scalars. scalar = interface.scalar_matrix(1, 1, 1) self.assertEquals(type(scalar), cvxopt.spmatrix) scalar = interface.scalar_matrix(1, 1, 3) self.assertEquals(scalar.size, (1, 3)) # index mat = interface.const_to_matrix([[1, 2, 3, 4], [3, 4, 5, 6]]) self.assertEquals(interface.index(mat, (0, 1)), 3) mat = interface.index(mat, (slice(1, 4, 2), slice(0, 2, None))) self.assertEquals(list(mat), [2, 4, 4, 6]) # Sign self.sign_for_intf(interface)
def test_ndarray(self): interface = intf.get_matrix_interface(np.ndarray) # const_to_matrix mat = interface.const_to_matrix([1, 2, 3]) self.assertEquals(interface.size(mat), (3, 1)) mat = interface.const_to_matrix([1, 2]) self.assertEquals(interface.size(mat), (2, 1)) # CVXOPT sparse conversion tmp = intf.get_matrix_interface(cvxopt.spmatrix).const_to_matrix( [1, 2, 3]) mat = interface.const_to_matrix(tmp) assert (mat == interface.const_to_matrix([1, 2, 3])).all() # identity mat = interface.identity(4) cvxopt_dense = intf.get_matrix_interface(cvxopt.matrix) cmp_mat = interface.const_to_matrix(cvxopt_dense.identity(4)) self.assertEquals(interface.size(mat), interface.size(cmp_mat)) assert (mat == cmp_mat).all() # scalar_matrix mat = interface.scalar_matrix(2, 4, 3) self.assertEquals(interface.size(mat), (4, 3)) self.assertEquals(interface.index(mat, (1, 2)), 2) # reshape mat = interface.const_to_matrix([[1, 2, 3], [3, 4, 5]]) mat = interface.reshape(mat, (6, 1)) self.assertEquals(interface.index(mat, (4, 0)), 4) mat = interface.const_to_matrix(1, convert_scalars=True) self.assertEquals(type(interface.reshape(mat, (1, 1))), type(mat)) # index mat = interface.const_to_matrix([[1, 2, 3, 4], [3, 4, 5, 6]]) self.assertEquals(interface.index(mat, (0, 1)), 3) mat = interface.index(mat, (slice(1, 4, 2), slice(0, 2, None))) self.assertEquals(list(mat.flatten('C')), [2, 4, 4, 6]) # Scalars and matrices. scalar = interface.const_to_matrix(2) mat = interface.const_to_matrix([1, 2, 3]) assert (scalar * mat == interface.const_to_matrix([2, 4, 6])).all() assert (scalar - mat == interface.const_to_matrix([1, 0, -1])).all() # Sign self.sign_for_intf(interface) # Size. assert interface.size(np.array([1, 2, 3])) == (3, 1)
def test_conversion_between_intf(self): """Test conversion between every pair of interfaces. """ interfaces = [intf.get_matrix_interface(np.ndarray), intf.get_matrix_interface(np.matrix), intf.get_matrix_interface(sp.csc_matrix)] cmp_mat = [[1, 2, 3, 4], [3, 4, 5, 6], [-1, 0, 2, 4]] for i in range(len(interfaces)): for j in range(i+1, len(interfaces)): intf1 = interfaces[i] mat1 = intf1.const_to_matrix(cmp_mat) intf2 = interfaces[j] mat2 = intf2.const_to_matrix(cmp_mat) for col in range(len(cmp_mat)): for row in range(len(cmp_mat[0])): key = (slice(row, row+1, None), slice(col, col+1, None)) self.assertEqual(intf1.index(mat1, key), intf2.index(mat2, key)) # Convert between the interfaces. self.assertEqual(cmp_mat[col][row], intf1.index(intf1.const_to_matrix(mat2), key)) self.assertEqual(intf2.index(intf2.const_to_matrix(mat1), key), cmp_mat[col][row])
def test_scipy_sparse(self): """Test cvxopt sparse interface. """ interface = intf.get_matrix_interface(sp.csc_matrix) # const_to_matrix mat = interface.const_to_matrix([1, 2, 3]) self.assertEqual(interface.shape(mat), (3, 1)) # C = cvxopt.spmatrix([1, 1, 1, 1, 1], [0, 1, 2, 0, 0, ], [0, 0, 0, 1, 2]) # mat = interface.const_to_matrix(C) # self.assertEqual(interface.shape(mat), (3, 3)) # identity mat = interface.identity(4) cmp_mat = interface.const_to_matrix(np.eye(4)) self.assertEqual(interface.shape(mat), interface.shape(cmp_mat)) assert (mat - cmp_mat).nnz == 0 # scalar_matrix mat = interface.scalar_matrix(2, (4, 3)) self.assertEqual(interface.shape(mat), (4, 3)) self.assertEqual(interface.index(mat, (1, 2)), 2) # reshape mat = interface.const_to_matrix([[1, 2, 3], [3, 4, 5]]) mat = interface.reshape(mat, (6, 1)) self.assertEqual(interface.index(mat, (4, 0)), 4) # Test scalars. scalar = interface.scalar_matrix(1, (1, 1)) self.assertEqual(type(scalar), np.ndarray) scalar = interface.scalar_matrix(1, (1, 3)) self.assertEqual(scalar.shape, (1, 3)) # index mat = interface.const_to_matrix([[1, 2, 3, 4], [3, 4, 5, 6]]) self.assertEqual(interface.index(mat, (0, 1)), 3) mat = interface.index(mat, (slice(1, 4, 2), slice(0, 2, None))) assert not (mat - np.array([[2, 4], [4, 6]])).any() # scalar value mat = sp.eye(1) self.assertEqual(intf.scalar_value(mat), 1.0) # Sign self.sign_for_intf(interface) # Complex # define sparse matrix [[0, 1j],[-1j,0]] row = np.array([0, 1]) col = np.array([1, 0]) data = np.array([1j, -1j]) A = sp.csr_matrix((data, (row, col)), shape=(2, 2)) mat = interface.const_to_matrix(A) self.assertEquals(mat[0, 1], 1j) self.assertEquals(mat[1, 0], -1j)
def quad_form(x, P): """ Alias for :math:`x^T P x`. """ x, P = map(Expression.cast_to_const, (x, P)) # Check dimensions. n = P.size[0] if P.size[1] != n or x.size != (n, 1): raise Exception("Invalid dimensions for arguments.") if x.is_constant(): return x.T * P * x elif P.is_constant(): np_intf = intf.get_matrix_interface(np.ndarray) P = np_intf.const_to_matrix(P.value) sgn, scale, M = _decomp_quad(P) return sgn * scale * square(norm(Constant(M.T) * x)) else: raise Exception("At least one argument to quad_form must be constant.")
def quad_form(x, P): """ Alias for :math:`x^T P x`. """ x, P = list(map(Expression.cast_to_const, (x, P))) # Check dimensions. n = P.size[0] if P.size[1] != n or x.size != (n,1): raise Exception("Invalid dimensions for arguments.") if x.is_constant(): return x.T * P * x elif P.is_constant(): np_intf = intf.get_matrix_interface(np.ndarray) P = np_intf.const_to_matrix(P.value) sgn, scale, M = _decomp_quad(P) return sgn * scale * square(norm(Constant(M.T) * x)) else: raise Exception("At least one argument to quad_form must be constant.")
def test_scipy_sparse(self): interface = intf.get_matrix_interface(sp.csc_matrix) # const_to_matrix mat = interface.const_to_matrix([1, 2, 3]) self.assertEquals(interface.size(mat), (3, 1)) C = cvxopt.spmatrix([1, 1, 1, 1, 1], [ 0, 1, 2, 0, 0, ], [0, 0, 0, 1, 2]) mat = interface.const_to_matrix(C) self.assertEquals(interface.size(mat), (3, 3)) # identity mat = interface.identity(4) cmp_mat = interface.const_to_matrix(np.eye(4)) self.assertEquals(interface.size(mat), interface.size(cmp_mat)) assert (mat - cmp_mat).nnz == 0 # scalar_matrix mat = interface.scalar_matrix(2, 4, 3) self.assertEquals(interface.size(mat), (4, 3)) self.assertEquals(interface.index(mat, (1, 2)), 2) # reshape mat = interface.const_to_matrix([[1, 2, 3], [3, 4, 5]]) mat = interface.reshape(mat, (6, 1)) self.assertEquals(interface.index(mat, (4, 0)), 4) mat = interface.const_to_matrix(1, convert_scalars=True) self.assertEquals(type(interface.reshape(mat, (1, 1))), type(mat)) # Test scalars. scalar = interface.scalar_matrix(1, 1, 1) self.assertEquals(type(scalar), np.ndarray) scalar = interface.scalar_matrix(1, 1, 3) self.assertEquals(scalar.shape, (1, 3)) # index mat = interface.const_to_matrix([[1, 2, 3, 4], [3, 4, 5, 6]]) self.assertEquals(interface.index(mat, (0, 1)), 3) mat = interface.index(mat, (slice(1, 4, 2), slice(0, 2, None))) assert not (mat - np.matrix("2 4; 4 6")).any() # scalar value mat = sp.eye(1) self.assertEqual(intf.scalar_value(mat), 1.0) # Sign self.sign_for_intf(interface)
def quad_form(x, P): """ Alias for :math:`x^T P x`. """ x, P = map(Expression.cast_to_const, (x, P)) # Check dimensions. n = P.size[0] if P.size[1] != n or x.size != (n,1): raise Exception("Invalid dimensions for arguments.") if x.is_constant(): return x.T * P * x elif P.is_constant(): np_intf = intf.get_matrix_interface(np.ndarray) P = np_intf.const_to_matrix(P.value) # P must be symmetric. if not np.allclose(P, P.T): msg = "P is not symmetric." raise CvxPyDomainError(msg) sgn, scale, M = _decomp_quad(P) return sgn * scale * square(norm(Constant(M.T) * x)) else: raise Exception("At least one argument to quad_form must be constant.")
def test_numpy_matrix(self) -> None: interface = intf.get_matrix_interface(np.matrix) # const_to_matrix mat = interface.const_to_matrix([1, 2, 3]) self.assertEqual(interface.shape(mat), (3, 1)) mat = interface.const_to_matrix([[1], [2], [3]]) self.assertEqual(mat[0, 0], 1) mat = interface.scalar_matrix(2, (4, 3)) self.assertEqual(interface.shape(mat), (4, 3)) self.assertEqual(interface.index(mat, (1, 2)), 2) # reshape mat = interface.const_to_matrix([[1, 2, 3], [3, 4, 5]]) mat = interface.reshape(mat, (6, 1)) self.assertEqual(interface.index(mat, (4, 0)), 4) mat = interface.const_to_matrix(1, convert_scalars=True) self.assertEqual(type(interface.reshape(mat, (1, 1))), type(mat)) # index mat = interface.const_to_matrix([[1, 2, 3, 4], [3, 4, 5, 6]]) self.assertEqual(interface.index(mat, (0, 1)), 3) mat = interface.index(mat, (slice(1, 4, 2), slice(0, 2, None))) self.assertFalse((mat - np.array([[2, 4], [4, 6]])).any()) # Sign self.sign_for_intf(interface)
def test_cvxopt_dense(self): interface = intf.get_matrix_interface(cvxopt.matrix) # const_to_matrix mat = interface.const_to_matrix([1, 2, 3]) self.assertEquals(interface.size(mat), (3, 1)) # identity mat = interface.identity(4) cmp_mat = interface.const_to_matrix(np.eye(4)) self.assertEquals(type(mat), type(cmp_mat)) self.assertEquals(interface.size(mat), interface.size(cmp_mat)) assert not mat - cmp_mat # scalar_matrix mat = interface.scalar_matrix(2, 4, 3) self.assertEquals(interface.size(mat), (4, 3)) self.assertEquals(interface.index(mat, (1, 2)), 2) # reshape mat = interface.const_to_matrix([[1, 2, 3], [3, 4, 5]]) mat = interface.reshape(mat, (6, 1)) self.assertEquals(interface.index(mat, (4, 0)), 4) # index mat = interface.const_to_matrix([[1, 2, 3, 4], [3, 4, 5, 6]]) self.assertEquals(interface.index(mat, (0, 1)), 3) mat = interface.index(mat, (slice(1, 4, 2), slice(0, 2, None))) self.assertEquals(list(mat), [2, 4, 4, 6])
class Problem(u.Canonical): """A convex optimization problem. Attributes ---------- objective : Minimize or Maximize The expression to minimize or maximize. constraints : list The constraints on the problem variables. """ # The solve methods available. REGISTERED_SOLVE_METHODS = {} # Interfaces for interacting with matrices. _SPARSE_INTF = intf.DEFAULT_SPARSE_INTERFACE _DENSE_INTF = intf.DEFAULT_INTERFACE _CVXOPT_DENSE_INTF = intf.get_matrix_interface(cvxopt.matrix) _CVXOPT_SPARSE_INTF = intf.get_matrix_interface(cvxopt.spmatrix) def __init__(self, objective, constraints=None): if constraints is None: constraints = [] self.objective = objective self.constraints = constraints self._value = None self._status = None @property def value(self): """The value from the last time the problem was solved. Returns ------- float or None """ return self._value @property def status(self): """The status from the last time the problem was solved. Returns ------- str """ return self._status def is_dcp(self): """Does the problem satisfy DCP rules? """ return all(exp.is_dcp() for exp in self.constraints + [self.objective]) def _filter_constraints(self, constraints): """Separate the constraints by type. Parameters ---------- constraints : list A list of constraints. Returns ------- dict A map of type key to an ordered set of constraints. """ constr_map = { s.EQ: [], s.LEQ: [], s.SOC: [], s.SOC_EW: [], s.SDP: [], s.EXP: [] } for c in constraints: if isinstance(c, lo.LinEqConstr): constr_map[s.EQ].append(c) elif isinstance(c, lo.LinLeqConstr): constr_map[s.LEQ].append(c) elif isinstance(c, SOC): constr_map[s.SOC].append(c) elif isinstance(c, SDP): constr_map[s.SDP].append(c) elif isinstance(c, ExpCone): constr_map[s.EXP].append(c) return constr_map def canonicalize(self): """Computes the graph implementation of the problem. Returns ------- tuple (affine objective, constraints dict) """ constraints = [] obj, constr = self.objective.canonical_form constraints += constr unique_constraints = list(unique(self.constraints, key=lambda c: c.id)) for constr in unique_constraints: constraints += constr.canonical_form[1] constr_map = self._filter_constraints(constraints) return (obj, constr_map) def _format_for_solver(self, constr_map, solver): """Formats the problem for the solver. Parameters ---------- constr_map : dict A map of constraint type to a list of constraints. solver: str The solver being targetted. Returns ------- dict The dimensions of the cones. """ dims = {} dims["f"] = sum(c.size[0] * c.size[1] for c in constr_map[s.EQ]) dims["l"] = sum(c.size[0] * c.size[1] for c in constr_map[s.LEQ]) # Formats SOC, SOC_EW, and SDP constraints for the solver. nonlin = constr_map[s.SOC] + constr_map[s.SDP] for constr in nonlin: for ineq_constr in constr.format(): constr_map[s.LEQ].append(ineq_constr) # Elemwise SOC constraints have an SOC constraint # for each element in their arguments. dims["q"] = [] for constr in constr_map[s.SOC]: for cone_size in constr.size: dims["q"].append(cone_size[0]) dims["s"] = [c.size[0] for c in constr_map[s.SDP]] # Format exponential cone constraints. if solver == s.CVXOPT: for constr in constr_map[s.EXP]: constr_map[s.EQ] += constr.format(s.CVXOPT) elif solver == s.SCS: for constr in constr_map[s.EXP]: constr_map[s.LEQ] += constr.format(s.SCS) dims["ep"] = sum(c.size[0] * c.size[1] for c in constr_map[s.EXP]) # Remove redundant constraints. for key in [s.EQ, s.LEQ]: constraints = unique(constr_map[key], key=lambda c: c.constr_id) constr_map[key] = list(constraints) return dims @staticmethod def _constraints_count(constr_map): """Returns the number of internal constraints. """ return sum([len(cset) for cset in constr_map.values()]) def _choose_solver(self, constr_map): """Determines the appropriate solver. Parameters ---------- constr_map: dict A dict of the canonicalized constraints. Returns ------- str The solver that will be used. """ # If no constraints, use ECOS. if self._constraints_count(constr_map) == 0: return s.ECOS # If SDP, defaults to CVXOPT. elif constr_map[s.SDP]: return s.CVXOPT # If EXP cone without SDP, defaults to SCS. elif constr_map[s.EXP]: return s.SCS # Otherwise use ECOS. else: return s.ECOS def _validate_solver(self, constr_map, solver): """Raises an exception if the solver cannot solve the problem. Parameters ---------- constr_map: dict A dict of the canonicalized constraints. solver : str The solver to be used. """ if (constr_map[s.SDP] and not solver in s.SDP_CAPABLE) or \ (constr_map[s.EXP] and not solver in s.EXP_CAPABLE) or \ (self._constraints_count(constr_map) == 0 and solver == s.SCS): raise Exception("The solver %s cannot solve the problem." % solver) def variables(self): """Returns a list of the variables in the problem. """ vars_ = self.objective.variables() for constr in self.constraints: vars_ += constr.variables() # Remove duplicates. return list(set(vars_)) def parameters(self): """Returns a list of the parameters in the problem. """ params = self.objective.parameters() for constr in self.constraints: params += constr.parameters() # Remove duplicates. return list(set(params)) def solve(self, *args, **kwargs): """Solves the problem using the specified method. Parameters ---------- method : function The solve method to use. solver : str, optional The solver to use. verbose : bool, optional Overrides the default of hiding solver output. solver_specific_opts : dict, optional A dict of options that will be passed to the specific solver. In general, these options will override any default settings imposed by cvxpy. Returns ------- float The optimal value for the problem, or a string indicating why the problem could not be solved. """ func_name = kwargs.pop("method", None) if func_name is not None: func = Problem.REGISTERED_SOLVE_METHODS[func_name] return func(self, *args, **kwargs) else: return self._solve(*args, **kwargs) @classmethod def register_solve(cls, name, func): """Adds a solve method to the Problem class. Parameters ---------- name : str The keyword for the method. func : function The function that executes the solve method. """ cls.REGISTERED_SOLVE_METHODS[name] = func def get_problem_data(self, solver): """Returns the problem data used in the call to the solver. Parameters ---------- solver : str The solver the problem data is for. Returns ------- tuple arguments to solver """ objective, constr_map = self.canonicalize() # Raise an error if the solver cannot handle the problem. self._validate_solver(constr_map, solver) dims = self._format_for_solver(constr_map, solver) all_ineq = constr_map[s.EQ] + constr_map[s.LEQ] var_offsets, var_sizes, x_length = self._get_var_offsets( objective, all_ineq) if solver == s.ECOS and not (constr_map[s.SDP] or constr_map[s.EXP]): args, offset = self._ecos_problem_data(objective, constr_map, dims, var_offsets, x_length) elif solver == s.CVXOPT and not constr_map[s.EXP]: args, offset = self._cvxopt_problem_data(objective, constr_map, dims, var_offsets, x_length) elif solver == s.SCS: args, offset = self._scs_problem_data(objective, constr_map, dims, var_offsets, x_length) else: raise Exception("Cannot return problem data for the solver %s." % solver) return args def _solve(self, solver=None, ignore_dcp=False, verbose=False, solver_specific_opts=None): """Solves a DCP compliant optimization problem. Saves the values of primal and dual variables in the variable and constraint objects, respectively. Parameters ---------- solver : str, optional The solver to use. Defaults to ECOS. ignore_dcp : bool, optional Overrides the default of raising an exception if the problem is not DCP. verbose : bool, optional Overrides the default of hiding solver output. solver_specific_opts : dict, optional A dict of options that will be passed to the specific solver. In general, these options will override any default settings imposed by cvxpy. Returns ------- float The optimal value for the problem, or a string indicating why the problem could not be solved. """ # Safely set default as empty dict. if solver_specific_opts is None: solver_specific_opts = {} if not self.is_dcp(): if ignore_dcp: print("Problem does not follow DCP rules. " "Solving a convex relaxation.") else: raise Exception("Problem does not follow DCP rules.") objective, constr_map = self.canonicalize() # Choose a default solver if none specified. if solver is None: solver = self._choose_solver(constr_map) else: # Raise an error if the solver cannot handle the problem. self._validate_solver(constr_map, solver) dims = self._format_for_solver(constr_map, solver) all_ineq = constr_map[s.EQ] + constr_map[s.LEQ] var_offsets, var_sizes, x_length = self._get_var_offsets( objective, all_ineq) if solver == s.CVXOPT: result = self._cvxopt_solve(objective, constr_map, dims, var_offsets, x_length, verbose, solver_specific_opts) elif solver == s.SCS: result = self._scs_solve(objective, constr_map, dims, var_offsets, x_length, verbose, solver_specific_opts) elif solver == s.ECOS: result = self._ecos_solve(objective, constr_map, dims, var_offsets, x_length, verbose, solver_specific_opts) else: raise Exception("Unknown solver.") status, value, x, y, z = result if status == s.OPTIMAL: self._save_values(x, self.variables(), var_offsets) self._save_dual_values(y, constr_map[s.EQ], EqConstraint) self._save_dual_values(z, constr_map[s.LEQ], LeqConstraint) self._value = value else: self._handle_failure(status) self._status = status return self.value def _ecos_problem_data(self, objective, constr_map, dims, var_offsets, x_length): """Returns the problem data for the call to ECOS. Parameters ---------- objective: Expression The canonicalized objective. constr_map: dict A dict of the canonicalized constraints. dims: dict A dict with information about the types of constraints. var_offsets: dict A dict mapping variable id to offset in the stacked variable x. x_length: int The height of x. Returns ------- tuple ((c, G, h, dims, A, b), offset) """ c, obj_offset = self._get_obj(objective, var_offsets, x_length, self._DENSE_INTF, self._DENSE_INTF) # Convert obj_offset to a scalar. obj_offset = self._DENSE_INTF.scalar_value(obj_offset) A, b = self._constr_matrix(constr_map[s.EQ], var_offsets, x_length, self._SPARSE_INTF, self._DENSE_INTF) G, h = self._constr_matrix(constr_map[s.LEQ], var_offsets, x_length, self._SPARSE_INTF, self._DENSE_INTF) # Convert c,h,b to 1D arrays. c, h, b = map(intf.from_2D_to_1D, [c.T, h, b]) # Return the arguments that would be passed to ECOS. return ((c, G, h, dims, A, b), obj_offset) def _ecos_solve(self, objective, constr_map, dims, var_offsets, x_length, verbose, opts): """Calls the ECOS solver and returns the result. Parameters ---------- objective: Expression The canonicalized objective. constr_map: dict A dict of the canonicalized constraints. dims: dict A dict with information about the types of constraints. var_offsets: dict A dict mapping variable id to offset in the stacked variable x. x_length: int The height of x. verbose: bool Should the solver show output? opts: dict List of user-specific options for ECOS Returns ------- tuple (status, optimal objective, optimal x, optimal equality constraint dual, optimal inequality constraint dual) """ prob_data = self._ecos_problem_data(objective, constr_map, dims, var_offsets, x_length) obj_offset = prob_data[1] results = ecos.solve(*prob_data[0], verbose=verbose) status = s.SOLVER_STATUS[s.ECOS][results['info']['exitFlag']] if status == s.OPTIMAL: primal_val = results['info']['pcost'] value = self.objective._primal_to_result(primal_val - obj_offset) return (status, value, results['x'], results['y'], results['z']) else: return (status, None, None, None, None) def _cvxopt_problem_data(self, objective, constr_map, dims, var_offsets, x_length): """Returns the problem data for the call to CVXOPT. Assumes no exponential cone constraints. Parameters ---------- objective: Expression The canonicalized objective. constr_map: dict A dict of the canonicalized constraints. dims: dict A dict with information about the types of constraints. var_offsets: dict A dict mapping variable id to offset in the stacked variable x. x_length: int The height of x. Returns ------- tuple ((c, G, h, dims, A, b), offset) """ c, obj_offset = self._get_obj(objective, var_offsets, x_length, self._CVXOPT_DENSE_INTF, self._CVXOPT_DENSE_INTF) # Convert obj_offset to a scalar. obj_offset = self._CVXOPT_DENSE_INTF.scalar_value(obj_offset) A, b = self._constr_matrix(constr_map[s.EQ], var_offsets, x_length, self._CVXOPT_SPARSE_INTF, self._CVXOPT_DENSE_INTF) G, h = self._constr_matrix(constr_map[s.LEQ], var_offsets, x_length, self._CVXOPT_SPARSE_INTF, self._CVXOPT_DENSE_INTF) # Return the arguments that would be passed to CVXOPT. return ((c.T, G, h, dims, A, b), obj_offset) def _cvxopt_solve(self, objective, constr_map, dims, var_offsets, x_length, verbose, opts): """Calls the CVXOPT conelp or cpl solver and returns the result. Parameters ---------- objective: Expression The canonicalized objective. constr_map: dict A dict of the canonicalized constraints. dims: dict A dict with information about the types of constraints. sorted_vars: list An ordered list of the problem variables. var_offsets: dict A dict mapping variable id to offset in the stacked variable x. x_length: int The height of x. verbose: bool Should the solver show output? opts: dict List of user-specific options for CVXOPT; will be inserted into cvxopt.solvers.options. Returns ------- tuple (status, optimal objective, optimal x, optimal equality constraint dual, optimal inequality constraint dual) """ prob_data = self._cvxopt_problem_data(objective, constr_map, dims, var_offsets, x_length) c, G, h, dims, A, b = prob_data[0] obj_offset = prob_data[1] # Save original cvxopt solver options. old_options = cvxopt.solvers.options # Silence cvxopt if verbose is False. cvxopt.solvers.options['show_progress'] = verbose # Always do one step of iterative refinement after solving KKT system. cvxopt.solvers.options['refinement'] = 1 # Apply any user-specific options for key, value in opts.items(): cvxopt.solvers.options[key] = value # Target cvxopt clp if nonlinear constraints exist if constr_map[s.EXP]: # Get the nonlinear constraints. F = self._merge_nonlin(constr_map[s.EXP], var_offsets, x_length) # Get custom kktsolver. kktsolver = get_kktsolver(G, dims, A, F) results = cvxopt.solvers.cpl(c, F, G, h, dims, A, b, kktsolver=kktsolver) else: # Get custom kktsolver. kktsolver = get_kktsolver(G, dims, A) results = cvxopt.solvers.conelp(c, G, h, dims, A, b, kktsolver=kktsolver) # Restore original cvxopt solver options. cvxopt.solvers.options = old_options status = s.SOLVER_STATUS[s.CVXOPT][results['status']] if status == s.OPTIMAL: primal_val = results['primal objective'] value = self.objective._primal_to_result(primal_val - obj_offset) if constr_map[s.EXP]: ineq_dual = results['zl'] else: ineq_dual = results['z'] return (status, value, results['x'], results['y'], ineq_dual) else: return (status, None, None, None, None) def _scs_problem_data(self, objective, constr_map, dims, var_offsets, x_length): """Returns the problem data for the call to SCS. Parameters ---------- objective: Expression The canonicalized objective. constr_map: dict A dict of the canonicalized constraints. dims: dict A dict with information about the types of constraints. var_offsets: dict A dict mapping variable id to offset in the stacked variable x. x_length: int The height of x. Returns ------- tuple ((data, dims), offset) """ c, obj_offset = self._get_obj(objective, var_offsets, x_length, self._DENSE_INTF, self._DENSE_INTF) # Convert obj_offset to a scalar. obj_offset = self._DENSE_INTF.scalar_value(obj_offset) A, b = self._constr_matrix(constr_map[s.EQ] + constr_map[s.LEQ], var_offsets, x_length, self._SPARSE_INTF, self._DENSE_INTF) # Convert c, b to 1D arrays. c, b = map(intf.from_2D_to_1D, [c.T, b]) data = {"c": c} data["A"] = A data["b"] = b return ((data, dims), obj_offset) def _scs_solve(self, objective, constr_map, dims, var_offsets, x_length, verbose, opts): """Calls the SCS solver and returns the result. Parameters ---------- objective: LinExpr The canonicalized objective. constr_map: dict A dict of the canonicalized constraints. dims: dict A dict with information about the types of constraints. var_offsets: dict A dict mapping variable id to offset in the stacked variable x. x_length: int The height of x. verbose: bool Should the solver show output? opts: dict A dict of the solver parameters passed to scs Returns ------- tuple (status, optimal objective, optimal x, optimal equality constraint dual, optimal inequality constraint dual) """ prob_data = self._scs_problem_data(objective, constr_map, dims, var_offsets, x_length) obj_offset = prob_data[1] # Set the options to be VERBOSE plus any user-specific options. opts = dict({"VERBOSE": verbose}.items() + opts.items()) use_indirect = opts["USE_INDIRECT"] if "USE_INDIRECT" in opts else False results = scs.solve(*prob_data[0], opts=opts, USE_INDIRECT=use_indirect) status = s.SOLVER_STATUS[s.SCS][results["info"]["status"]] if status == s.OPTIMAL: primal_val = results["info"]["pobj"] value = self.objective._primal_to_result(primal_val - obj_offset) eq_dual = results["y"][0:dims["f"]] ineq_dual = results["y"][dims["f"]:] return (status, value, results["x"], eq_dual, ineq_dual) else: return (status, None, None, None, None) def _handle_failure(self, status): """Updates value fields based on the cause of solver failure. Parameters ---------- status: str The status of the solver. """ # Set all primal and dual variable values to None. for var_ in self.variables(): var_.save_value(None) for constr in self.constraints: constr.save_value(None) # Set the problem value. if status == s.INFEASIBLE: self._value = self.objective._primal_to_result(np.inf) elif status == s.UNBOUNDED: self._value = self.objective._primal_to_result(-np.inf) else: # Solver error self._value = None def _get_var_offsets(self, objective, constraints): """Maps each variable to a horizontal offset. Parameters ---------- objective : Expression The canonicalized objective. constraints : list The canonicalized constraints. Returns ------- tuple (map of variable to offset, length of variable vector) """ vars_ = lu.get_expr_vars(objective) for constr in constraints: vars_ += lu.get_expr_vars(constr.expr) var_offsets = OrderedDict() var_sizes = {} # Ensure the variables are always in the same # order for the same problem. var_names = list(set(vars_)) var_names.sort(key=lambda (var_id, var_size): var_id) # Map var ids to offsets. vert_offset = 0 for var_id, var_size in var_names: var_sizes[var_id] = var_size var_offsets[var_id] = vert_offset vert_offset += var_size[0] * var_size[1] return (var_offsets, var_sizes, vert_offset) def _save_dual_values(self, result_vec, constraints, constr_type): """Saves the values of the dual variables. Parameters ---------- results_vec : array_like A vector containing the dual variable values. constraints : list A list of the LinEqConstr/LinLeqConstr in the problem. constr_type : type EqConstr or LeqConstr """ constr_offsets = {} offset = 0 for constr in constraints: constr_offsets[constr.constr_id] = offset offset += constr.size[0] * constr.size[1] active_constraints = [] for constr in self.constraints: # Ignore constraints of the wrong type. if type(constr) == constr_type: active_constraints.append(constr) self._save_values(result_vec, active_constraints, constr_offsets) def _save_values(self, result_vec, objects, offset_map): """Saves the values of the optimal primal/dual variables. Parameters ---------- results_vec : array_like A vector containing the variable values. objects : list The variables or constraints where the values will be stored. offset_map : dict A map of object id to offset in the results vector. """ if len(result_vec) > 0: # Cast to desired matrix type. result_vec = self._DENSE_INTF.const_to_matrix(result_vec) for obj in objects: rows, cols = obj.size if obj.id in offset_map: offset = offset_map[obj.id] # Handle scalars if (rows, cols) == (1, 1): value = intf.index(result_vec, (offset, 0)) else: value = self._DENSE_INTF.zeros(rows, cols) self._DENSE_INTF.block_add( value, result_vec[offset:offset + rows * cols], 0, 0, rows, cols) offset += rows * cols else: # The variable was multiplied by zero. value = self._DENSE_INTF.zeros(rows, cols) obj.save_value(value) def _get_obj(self, objective, var_offsets, x_length, matrix_intf, vec_intf): """Wraps _constr_matrix so it can be called for the objective. """ dummy_constr = lu.create_eq(objective) return self._constr_matrix([dummy_constr], var_offsets, x_length, matrix_intf, vec_intf) def _constr_matrix(self, constraints, var_offsets, x_length, matrix_intf, vec_intf): """Returns a matrix and vector representing a list of constraints. In the matrix, each constraint is given a block of rows. Each variable coefficient is inserted as a block with upper left corner at matrix[variable offset, constraint offset]. The constant term in the constraint is added to the vector. Parameters ---------- constraints : list A list of constraints. var_offsets : dict A dict of variable id to horizontal offset. x_length : int The length of the x vector. matrix_intf : interface The matrix interface to use for creating the constraints matrix. vec_intf : interface The matrix interface to use for creating the constant vector. Returns ------- tuple A (matrix, vector) tuple. """ rows = sum([c.size[0] * c.size[1] for c in constraints]) cols = x_length V, I, J = [], [], [] const_vec = vec_intf.zeros(rows, 1) vert_offset = 0 for constr in constraints: coeffs = op2mat.get_coefficients(constr.expr) for id_, size, block in coeffs: vert_start = vert_offset vert_end = vert_start + constr.size[0] * constr.size[1] if id_ is lo.CONSTANT_ID: # Flatten the block. block = self._DENSE_INTF.const_to_matrix(block) block_size = intf.size(block) block = self._DENSE_INTF.reshape( block, (block_size[0] * block_size[1], 1)) const_vec[vert_start:vert_end, :] += block else: horiz_offset = var_offsets[id_] if intf.is_scalar(block): block = intf.scalar_value(block) V.append(block) I.append(vert_start) J.append(horiz_offset) else: # Block is a numpy matrix or # scipy CSC sparse matrix. if not intf.is_sparse(block): block = self._SPARSE_INTF.const_to_matrix(block) block = block.tocoo() V.extend(block.data) I.extend(block.row + vert_start) J.extend(block.col + horiz_offset) vert_offset += constr.size[0] * constr.size[1] # Create the constraints matrix. if len(V) > 0: matrix = sp.coo_matrix((V, (I, J)), (rows, cols)) # Convert the constraints matrix to the correct type. matrix = matrix_intf.const_to_matrix(matrix, convert_scalars=True) else: # Empty matrix. matrix = matrix_intf.zeros(rows, cols) return (matrix, -const_vec) def _merge_nonlin(self, nl_constr, var_offsets, x_length): """ TODO: ensure that this works with numpy data structs... """ rows = sum([constr.size[0] * constr.size[1] for constr in nl_constr]) cols = x_length big_x = self._CVXOPT_DENSE_INTF.zeros(cols, 1) for constr in nl_constr: constr.place_x0(big_x, var_offsets, self._CVXOPT_DENSE_INTF) def F(x=None, z=None): if x is None: return rows, big_x big_f = self._CVXOPT_DENSE_INTF.zeros(rows, 1) big_Df = self._CVXOPT_SPARSE_INTF.zeros(rows, cols) if z: big_H = self._CVXOPT_SPARSE_INTF.zeros(cols, cols) offset = 0 for constr in nl_constr: constr_entries = constr.size[0] * constr.size[1] local_x = constr.extract_variables(x, var_offsets, self._CVXOPT_DENSE_INTF) if z: f, Df, H = constr.f(local_x, z[offset:offset + constr_entries]) else: result = constr.f(local_x) if result: f, Df = result else: return None big_f[offset:offset + constr_entries] = f constr.place_Df(big_Df, Df, var_offsets, offset, self._CVXOPT_SPARSE_INTF) if z: constr.place_H(big_H, H, var_offsets, self._CVXOPT_SPARSE_INTF) offset += constr_entries if z is None: return big_f, big_Df return big_f, big_Df, big_H return F def __str__(self): return repr(self) def __repr__(self): return "Problem(%s, %s)" % (repr(self.objective), repr( self.constraints))
class ExpCone(NonlinearConstraint): """A reformulated exponential cone constraint. Operates elementwise on x, y, z. Original cone: K = {(x,y,z) | y > 0, ye^(x/y) <= z} U {(x,y,z) | x <= 0, y = 0, z >= 0} Reformulated cone: K = {(x,y,z) | y, z > 0, y * log(y) + x <= y * log(z)} U {(x,y,z) | x <= 0, y = 0, z >= 0} Attributes ---------- x: Variable x in the exponential cone. y: Variable y in the exponential cone. z: Variable z in the exponential cone. """ CVXOPT_DENSE_INTF = intf.get_matrix_interface(cvxopt.matrix) CVXOPT_SPARSE_INTF = intf.get_matrix_interface(cvxopt.spmatrix) def __init__(self, x, y, z): self.x = x self.y = y self.z = z self.size = (int(self.x.size[0]), int(self.x.size[1])) super(ExpCone, self).__init__(self._solver_hook, [self.x, self.y, self.z]) def __str__(self): return "ExpCone(%s, %s, %s)" % (self.x, self.y, self.z) def format(self, eq_constr, leq_constr, dims, solver): """Formats EXP constraints for the solver. Parameters ---------- eq_constr : list A list of the equality constraints in the canonical problem. leq_constr : list A list of the inequality constraints in the canonical problem. dims : dict A dict with the dimensions of the conic constraints. solver : str The solver being called. """ if solver.name() == s.CVXOPT: eq_constr += self.__CVXOPT_format[0] elif solver.name() == s.SCS: leq_constr += self.__SCS_format[1] elif solver.name() == s.ECOS: leq_constr += self.__ECOS_format[1] else: raise SolverError("Solver does not support exponential cone.") # Update dims. dims[s.EXP_DIM] += self.size[0] * self.size[1] @pu.lazyprop def __ECOS_format(self): return ([], format_elemwise([self.x, self.z, self.y])) @pu.lazyprop def __SCS_format(self): return ([], format_elemwise([self.x, self.y, self.z])) @pu.lazyprop def __CVXOPT_format(self): constraints = [] for i, var in enumerate(self.vars_): if not var.type is VARIABLE: lone_var = lu.create_var(var.size) constraints.append(lu.create_eq(lone_var, var)) self.vars_[i] = lone_var return (constraints, []) def _solver_hook(self, vars_=None, scaling=None): """A function used by CVXOPT's nonlinear solver. Based on f(x,y,z) = y * log(y) + x - y * log(z). Parameters ---------- vars_: A cvxopt dense matrix with values for (x,y,z). scaling: A scaling for the Hessian. Returns ------- _solver_hook() returns the constraint size and a feasible point. _solver_hook(x) returns the function value and gradient at x. _solver_hook(x, z) returns the function value, gradient, and (z scaled) Hessian at x. """ entries = self.size[0] * self.size[1] if vars_ is None: x_init = entries * [0.0] y_init = entries * [0.5] z_init = entries * [1.0] return self.size[0], cvxopt.matrix(x_init + y_init + z_init) # Unpack vars_ x = vars_[0:entries] y = vars_[entries:2 * entries] z = vars_[2 * entries:] # Out of domain. # TODO what if y == 0.0? if min(y) <= 0.0 or min(z) <= 0.0: return None # Evaluate the function. f = self.CVXOPT_DENSE_INTF.zeros(entries, 1) for i in range(entries): f[i] = x[i] - y[i] * math.log(z[i]) + y[i] * math.log(y[i]) # Compute the gradient. Df = self.CVXOPT_DENSE_INTF.zeros(entries, 3 * entries) for i in range(entries): Df[i, i] = 1.0 Df[i, entries + i] = math.log(y[i]) - math.log(z[i]) + 1.0 Df[i, 2 * entries + i] = -y[i] / z[i] if scaling is None: return f, Df # Compute the Hessian. big_H = self.CVXOPT_SPARSE_INTF.zeros(3 * entries, 3 * entries) for i in range(entries): H = cvxopt.matrix([ [0.0, 0.0, 0.0], [0.0, 1.0 / y[i], -1.0 / z[i]], [0.0, -1.0 / z[i], y[i] / (z[i]**2)], ]) big_H[i:3 * entries:entries, i:3 * entries:entries] = scaling[i] * H return f, Df, big_H