def test_assign_var_value(self): """Test assigning a value to a variable. """ # Scalar variable. a = Variable() a.value = 1 self.assertEqual(a.value, 1) with self.assertRaises(Exception) as cm: a.value = [2, 1] self.assertEqual(str(cm.exception), "Invalid dimensions (2,) for Variable value.") # Test assigning None. a.value = 1 a.value = None assert a.value is None # Vector variable. x = Variable(2) x.value = [2, 1] self.assertItemsAlmostEqual(x.value, [2, 1]) # Matrix variable. A = Variable((3, 2)) A.value = np.ones((3, 2)) self.assertItemsAlmostEqual(A.value, np.ones((3, 2))) # Test assigning negative val to nonnegative variable. x = Variable(nonneg=True) with self.assertRaises(Exception) as cm: x.value = -2 self.assertEqual(str(cm.exception), "Variable value must be nonnegative.")
def maximum_canon(expr, args): shape = expr.shape t = Variable(shape) constraints = [t >= elem for elem in args] return t, constraints
def setUp(self): self.a = Variable(name='a') self.b = Variable(name='b') self.c = Variable(name='c') self.x = Variable(2, name='x') self.y = Variable(3, name='y') self.z = Variable(2, name='z') self.w = Variable(5, name='w') self.A = Variable((2, 2), name='A') self.B = Variable((2, 2), name='B') self.C = Variable((3, 2), name='C') self.slope = Variable(1, name='slope') self.offset = Variable(1, name='offset') self.quadratic_coeff = Variable(1, name='quadratic_coeff') T = 100 self.position = Variable((2, T), name='position') self.velocity = Variable((2, T), name='velocity') self.force = Variable((2, T - 1), name='force') self.xs = Variable(80, name='xs') self.xsr = Variable(200, name='xsr') self.xef = Variable(80, name='xef') # Check for all installed QP solvers self.solvers = [x for x in QP_SOLVERS if x in INSTALLED_SOLVERS] if 'MOSEK' in INSTALLED_SOLVERS: self.solvers.append('MOSEK')
class TestConstraints(BaseTest): """ Unit tests for the expression/expression module. """ def setUp(self): self.a = Variable(name='a') self.b = Variable(name='b') self.x = Variable(2, name='x') self.y = Variable(3, name='y') self.z = Variable(2, name='z') self.A = Variable((2, 2), name='A') self.B = Variable((2, 2), name='B') self.C = Variable((3, 2), name='C') # def test_constr_str(self): # """Test string representations of the constraints. # """ # constr = self.x <= self.x # self.assertEqual(repr(constr), "NonPos(%s, %s)" % (repr(self.x), repr(self.x))) # constr = self.x <= 2*self.x # self.assertEqual(repr(constr), "NonPos(%s, %s)" % (repr(self.x), repr(2*self.x))) # constr = 2*self.x >= self.x # self.assertEqual(repr(constr), "NonPos(%s, %s)" % (repr(self.x), repr(2*self.x))) def test_equality(self): """Test the Equality class. """ constr = self.x == self.z self.assertEqual(constr.name(), "x == z") self.assertEqual(constr.shape, (2, )) # self.assertItemsEqual(constr.variables().keys(), [self.x.id, self.z.id]) # Test value and dual_value. assert constr.dual_value is None with self.assertRaises(ValueError): constr.value() self.x.save_value(2) self.z.save_value(2) assert constr.value() self.x.save_value(3) assert not constr.value() self.x.value = np.array([2, 1]) self.z.value = np.array([2, 2]) assert not constr.value() self.assertItemsAlmostEqual(constr.violation(), [0, 1]) self.assertItemsAlmostEqual(constr.residual, [0, 1]) self.z.value = np.array([2, 1]) assert constr.value() self.assertItemsAlmostEqual(constr.violation(), [0, 0]) self.assertItemsAlmostEqual(constr.residual, [0, 0]) # Incompatible dimensions with self.assertRaises(ValueError): (self.x == self.y) # Test copy with args=None copy = constr.copy() self.assertTrue(type(copy) is type(constr)) # A new object is constructed, so copy.args == constr.args but copy.args # is not constr.args. self.assertEqual(copy.args, constr.args) self.assertFalse(copy.args is constr.args) # Test copy with new args copy = constr.copy(args=[self.A]) self.assertTrue(type(copy) is type(constr)) self.assertTrue(copy.args[0] is self.A) def test_inequality(self): """Test the Inequality class. """ constr = self.x <= self.z self.assertEqual(constr.name(), "x <= z") self.assertEqual(constr.shape, (2, )) # Test value and dual_value. assert constr.dual_value is None with self.assertRaises(ValueError): constr.value() self.x.save_value(1) self.z.save_value(2) assert constr.value() self.x.save_value(3) assert not constr.value() # self.assertItemsEqual(constr.variables().keys(), [self.x.id, self.z.id]) self.x.value = np.array([2, 1]) self.z.value = np.array([2, 0]) assert not constr.value() self.assertItemsAlmostEqual(constr.violation(), [0, 1]) self.assertItemsAlmostEqual(constr.residual, [0, 1]) self.z.value = np.array([2, 2]) assert constr.value() self.assertItemsAlmostEqual(constr.violation(), [0, 0]) self.assertItemsAlmostEqual(constr.residual, [0, 0]) # Incompatible dimensions with self.assertRaises(Exception): (self.x <= self.y) # Test copy with args=None copy = constr.copy() self.assertTrue(type(copy) is type(constr)) # A new object is constructed, so copy.args == constr.args but copy.args # is not constr.args. self.assertEqual(copy.args, constr.args) self.assertFalse(copy.args is constr.args) # Test copy with new args copy = constr.copy(args=[self.A]) self.assertTrue(type(copy) is type(constr)) self.assertTrue(copy.args[0] is self.A) def test_psd_constraint(self): """Test the PSD constraint <<. """ constr = self.A >> self.B self.assertEqual(constr.name(), "A + -B >> 0") self.assertEqual(constr.shape, (2, 2)) # Test value and dual_value. assert constr.dual_value is None with self.assertRaises(ValueError): constr.value() self.A.save_value(np.array([[2, -1], [1, 2]])) self.B.save_value(np.array([[1, 0], [0, 1]])) assert constr.value() self.assertAlmostEqual(constr.violation(), 0) self.assertAlmostEqual(constr.residual, 0) self.B.save_value(np.array([[3, 0], [0, 3]])) assert not constr.value() self.assertAlmostEqual(constr.violation(), 1) self.assertAlmostEqual(constr.residual, 1) with self.assertRaises(Exception) as cm: (self.x >> 0) self.assertEqual(str(cm.exception), "Non-square matrix in positive definite constraint.") # Test copy with args=None copy = constr.copy() self.assertTrue(type(copy) is type(constr)) # A new object is constructed, so copy.args == constr.args but copy.args # is not constr.args. self.assertEqual(copy.args, constr.args) self.assertFalse(copy.args is constr.args) # Test copy with new args copy = constr.copy(args=[self.B]) self.assertTrue(type(copy) is type(constr)) self.assertTrue(copy.args[0] is self.B) def test_nsd_constraint(self): """Test the PSD constraint <<. """ constr = self.A << self.B self.assertEqual(constr.name(), "B + -A >> 0") self.assertEqual(constr.shape, (2, 2)) # Test value and dual_value. assert constr.dual_value is None with self.assertRaises(ValueError): constr.value() self.B.save_value(np.array([[2, -1], [1, 2]])) self.A.save_value(np.array([[1, 0], [0, 1]])) assert constr.value() self.A.save_value(np.array([[3, 0], [0, 3]])) assert not constr.value() with self.assertRaises(Exception) as cm: self.x << 0 self.assertEqual(str(cm.exception), "Non-square matrix in positive definite constraint.") def test_geq(self): """Test the >= operator. """ constr = self.z >= self.x self.assertEqual(constr.name(), "x <= z") self.assertEqual(constr.shape, (2, )) # Incompatible dimensions with self.assertRaises(ValueError): (self.y >= self.x) # Test the SOC class. def test_soc_constraint(self): exp = self.x + self.z scalar_exp = self.a + self.b constr = SOC(scalar_exp, exp) self.assertEqual(constr.size, 3) def test_chained_constraints(self): """Tests that chaining constraints raises an error. """ error_str = ("Cannot evaluate the truth value of a constraint or " "chain constraints, e.g., 1 >= x >= 0.") with self.assertRaises(Exception) as cm: (self.z <= self.x <= 1) self.assertEqual(str(cm.exception), error_str) with self.assertRaises(Exception) as cm: (self.x == self.z == 1) self.assertEqual(str(cm.exception), error_str) if PY2: with self.assertRaises(Exception) as cm: (self.z <= self.x).__nonzero__() self.assertEqual(str(cm.exception), error_str) else: with self.assertRaises(Exception) as cm: (self.z <= self.x).__bool__() self.assertEqual(str(cm.exception), error_str)
def setUp(self): self.a = Variable(name='a') self.b = Variable(name='b') self.x = Variable(2, name='x') self.y = Variable(3, name='y') self.z = Variable(2, name='z') self.A = Variable((2, 2), name='A') self.B = Variable((2, 2), name='B') self.C = Variable((3, 2), name='C')
def test_round_attr(self): """Test rounding for attributes. """ # Nonpos v = Variable(1, nonpos=True) self.assertAlmostEqual(v.project(1), 0) v = Variable(2, nonpos=True) self.assertItemsAlmostEqual(v.project(np.array([1, -1])), [0, -1]) # Nonneg v = Variable(1, nonneg=True) self.assertAlmostEqual(v.project(-1), 0) v = Variable(2, nonneg=True) self.assertItemsAlmostEqual(v.project(np.array([1, -1])), [1, 0]) # Boolean v = Variable((2, 2), boolean=True) self.assertItemsAlmostEqual(v.project(np.array([[1, -1], [1, 0]]).T), [1, 0, 1, 0]) # Integer v = Variable((2, 2), integer=True) self.assertItemsAlmostEqual(v.project(np.array([[1, -1.6], [1, 0]]).T), [1, -2, 1, 0]) # Symmetric v = Variable((2, 2), symmetric=True) self.assertItemsAlmostEqual(v.project(np.array([[1, -1], [1, 0]])), [1, 0, 0, 0]) # PSD v = Variable((2, 2), PSD=True) self.assertItemsAlmostEqual(v.project(np.array([[1, -1], [1, -1]])), [1, 0, 0, 0]) # NSD v = Variable((2, 2), NSD=True) self.assertItemsAlmostEqual(v.project(np.array([[1, -1], [1, -1]])), [0, 0, 0, -1]) # diag v = Variable((2, 2), diag=True) self.assertItemsAlmostEqual( v.project(np.array([[1, -1], [1, 0]])).todense(), [1, 0, 0, 0]) # Hermitian v = Variable((2, 2), hermitian=True) self.assertItemsAlmostEqual(v.project(np.array([[1, -1j], [1, 0]])), [1, 0.5 + 0.5j, 0.5 - 0.5j, 0]) A = Constant(np.array([[1.0]])) self.assertEqual(A.is_psd(), True) self.assertEqual(A.is_nsd(), False) A = Constant(np.array([[-1.0]])) self.assertEqual(A.is_psd(), False) self.assertEqual(A.is_nsd(), True) A = Constant(np.array([[0.0]])) self.assertEqual(A.is_psd(), True) self.assertEqual(A.is_nsd(), True)
class TestMatrices(unittest.TestCase): """ Unit tests for testing different forms of matrices as constants. """ def assertExpression(self, expr, shape): """Asserts that expr is an Expression with dimension shape. """ assert isinstance(expr, Expression) or isinstance(expr, Constraint) self.assertEqual(expr.shape, shape) def setUp(self): self.a = Variable(name='a') self.b = Variable(name='b') self.c = Variable(name='c') self.x = Variable(2, name='x') self.y = Variable(3, name='y') self.z = Variable(2, name='z') self.A = Variable((2, 2), name='A') self.B = Variable((2, 2), name='B') self.C = Variable((3, 2), name='C') # Test numpy arrays def test_numpy_arrays(self): # Vector v = numpy.arange(2) self.assertExpression(self.x + v, (2, )) self.assertExpression(v + self.x, (2, )) self.assertExpression(self.x - v, (2, )) self.assertExpression(v - self.x, (2, )) self.assertExpression(self.x <= v, (2, )) self.assertExpression(v <= self.x, (2, )) self.assertExpression(self.x == v, (2, )) self.assertExpression(v == self.x, (2, )) # Matrix A = numpy.arange(8).reshape((4, 2)) self.assertExpression(A * self.x, (4, )) if PY35: self.assertExpression(self.x.__rmatmul__(A), (4, )) # PSD inequalities. A = numpy.ones((2, 2)) self.assertExpression(A << self.A, (2, 2)) self.assertExpression(A >> self.A, (2, 2)) # Test numpy matrices def test_numpy_matrices(self): # Vector v = numpy.arange(2) self.assertExpression(self.x + v, (2, )) self.assertExpression(v + v + self.x, (2, )) self.assertExpression(self.x - v, (2, )) self.assertExpression(v - v - self.x, (2, )) self.assertExpression(self.x <= v, (2, )) self.assertExpression(v <= self.x, (2, )) self.assertExpression(self.x == v, (2, )) self.assertExpression(v == self.x, (2, )) # Matrix A = numpy.arange(8).reshape((4, 2)) self.assertExpression(A * self.x, (4, )) self.assertExpression((A.T.dot(A)) * self.x, (2, )) if PY35: self.assertExpression(self.x.__rmatmul__(A), (4, )) # PSD inequalities. A = numpy.ones((2, 2)) self.assertExpression(A << self.A, (2, 2)) self.assertExpression(A >> self.A, (2, 2)) def test_numpy_scalars(self): """Test numpy scalars.""" v = numpy.float64(2.0) self.assertExpression(self.x + v, (2, )) self.assertExpression(v + self.x, (2, )) self.assertExpression(v * self.x, (2, )) self.assertExpression(self.x - v, (2, )) self.assertExpression(v - v - self.x, (2, )) self.assertExpression(self.x <= v, (2, )) self.assertExpression(v <= self.x, (2, )) self.assertExpression(self.x == v, (2, )) self.assertExpression(v == self.x, (2, )) # PSD inequalities. self.assertExpression(v << self.A, (2, 2)) self.assertExpression(v >> self.A, (2, 2)) # def test_cvxopt_matrices(self): # """Test cvxopt dense matrices. # """ # # Vector # v = cvxopt.matrix( numpy.arange(2).reshape((2,1)) ) # self.assertExpression(self.x + v, (2,1)) # self.assertExpression(v + v + self.x, (2,1)) # self.assertExpression(self.x - v, (2,1)) # self.assertExpression(v - v - self.x, (2,1)) # self.assertExpression(self.x <= v, (2,1)) # self.assertExpression(v <= self.x, (2,1)) # self.assertExpression(self.x == v, (2,1)) # self.assertExpression(v == self.x, (2,1)) # # Matrix # A = cvxopt.matrix( numpy.arange(8).reshape((4,2)) ) # self.assertExpression(A*self.x, (4,1)) # self.assertExpression( (A.T*A) * self.x, (2,1)) # # Test cvxopt sparse matrices. # def test_cvxopt_sparse(self): # m = 100 # n = 20 # mu = cvxopt.exp(cvxopt.normal(m)) # F = cvxopt.normal(m, n) # D = cvxopt.spdiag(cvxopt.uniform(m)) # x = Variable(m) # exp = square(norm2(D*x)) def test_scipy_sparse(self): """Test scipy sparse matrices.""" # Constants. A = numpy.arange(8).reshape((4, 2)) A = sp.csc_matrix(A) A = sp.eye(2).tocsc() key = (slice(0, 1, None), slice(None, None, None)) Aidx = intf.index(A, (slice(0, 2, None), slice(None, None, None))) Aidx = intf.index(Aidx, key) self.assertEqual(Aidx.shape, (1, 2)) self.assertEqual(Aidx[0, 0], 1) self.assertEqual(Aidx[0, 1], 0) # Linear ops. var = Variable((4, 2)) A = numpy.arange(8).reshape((4, 2)) A = sp.csc_matrix(A) B = sp.hstack([A, A]) self.assertExpression(var + A, (4, 2)) self.assertExpression(A + var, (4, 2)) self.assertExpression(B * var, (4, 2)) self.assertExpression(var - A, (4, 2)) self.assertExpression(A - A - var, (4, 2)) if PY35: self.assertExpression(var.__rmatmul__(B), (4, 2))
def partial_optimize(prob, opt_vars=None, dont_opt_vars=None, solver=None): """Partially optimizes the given problem over the specified variables. Either opt_vars or dont_opt_vars must be given. If both are given, they must contain all the variables in the problem. Partial optimize is useful for two-stage optimization and graph implementations. For example, we can write .. code :: python x = Variable(n) t = Variable(n) abs_x = partial_optimize(Problem(Minimize(sum(t)), [-t <= x, x <= t]), opt_vars=[t]) to define the entrywise absolute value of x. Parameters ---------- prob : Problem The problem to partially optimize. opt_vars : list, optional The variables to optimize over. dont_opt_vars : list, optional The variables to not optimize over. solver : str, optional The default solver to use for value and grad. Returns ------- Expression An expression representing the partial optimization. Convex for minimization objectives and concave for maximization objectives. """ # One of the two arguments must be specified. if opt_vars is None and dont_opt_vars is None: raise ValueError( "partial_optimize called with neither opt_vars nor dont_opt_vars." ) # If opt_vars is not specified, it's the complement of dont_opt_vars. elif opt_vars is None: ids = [id(var) for var in dont_opt_vars] opt_vars = [var for var in prob.variables() if not id(var) in ids] # If dont_opt_vars is not specified, it's the complement of opt_vars. elif dont_opt_vars is None: ids = [id(var) for var in opt_vars] dont_opt_vars = [var for var in prob.variables() if not id(var) in ids] elif opt_vars is not None and dont_opt_vars is not None: ids = [id(var) for var in opt_vars + dont_opt_vars] for var in prob.variables(): if id(var) not in ids: raise ValueError( ("If opt_vars and new_opt_vars are both specified, " "they must contain all variables in the problem.") ) # Replace the opt_vars in prob with new variables. id_to_new_var = {id(var): Variable(var.shape, **var.attributes) for var in opt_vars} new_obj = prob.objective.tree_copy(id_to_new_var) new_constrs = [con.tree_copy(id_to_new_var) for con in prob.constraints] new_var_prob = Problem(new_obj, new_constrs) return PartialProblem(new_var_prob, opt_vars, dont_opt_vars, solver)
class TestConstraints(BaseTest): """ Unit tests for the expression/expression module. """ def setUp(self) -> None: self.a = Variable(name='a') self.b = Variable(name='b') self.x = Variable(2, name='x') self.y = Variable(3, name='y') self.z = Variable(2, name='z') self.A = Variable((2, 2), name='A') self.B = Variable((2, 2), name='B') self.C = Variable((3, 2), name='C') def test_equality(self) -> None: """Test the Equality class. """ constr = self.x == self.z self.assertEqual(constr.name(), "x == z") self.assertEqual(constr.shape, (2,)) # Test value and dual_value. assert constr.dual_value is None with self.assertRaises(ValueError): constr.value() self.x.save_value(2) self.z.save_value(2) assert constr.value() self.x.save_value(3) assert not constr.value() self.x.value = np.array([2, 1]) self.z.value = np.array([2, 2]) assert not constr.value() self.assertItemsAlmostEqual(constr.violation(), [0, 1]) self.assertItemsAlmostEqual(constr.residual, [0, 1]) self.z.value = np.array([2, 1]) assert constr.value() self.assertItemsAlmostEqual(constr.violation(), [0, 0]) self.assertItemsAlmostEqual(constr.residual, [0, 0]) # Incompatible dimensions with self.assertRaises(ValueError): (self.x == self.y) # Test copy with args=None copy = constr.copy() self.assertTrue(type(copy) is type(constr)) # A new object is constructed, so copy.args == constr.args but copy.args # is not constr.args. self.assertEqual(copy.args, constr.args) self.assertFalse(copy.args is constr.args) # Test copy with new args copy = constr.copy(args=[self.A]) self.assertTrue(type(copy) is type(constr)) self.assertTrue(copy.args[0] is self.A) def test_inequality(self) -> None: """Test the Inequality class. """ constr = self.x <= self.z self.assertEqual(constr.name(), "x <= z") self.assertEqual(constr.shape, (2,)) # Test value and dual_value. assert constr.dual_value is None with self.assertRaises(ValueError): constr.value() self.x.save_value(1) self.z.save_value(2) assert constr.value() self.x.save_value(3) assert not constr.value() # self.assertItemsEqual(constr.variables().keys(), [self.x.id, self.z.id]) self.x.value = np.array([2, 1]) self.z.value = np.array([2, 0]) assert not constr.value() self.assertItemsAlmostEqual(constr.violation(), [0, 1]) self.assertItemsAlmostEqual(constr.residual, [0, 1]) self.z.value = np.array([2, 2]) assert constr.value() self.assertItemsAlmostEqual(constr.violation(), [0, 0]) self.assertItemsAlmostEqual(constr.residual, [0, 0]) # Incompatible dimensions with self.assertRaises(Exception): (self.x <= self.y) # Test copy with args=None copy = constr.copy() self.assertTrue(type(copy) is type(constr)) # A new object is constructed, so copy.args == constr.args but copy.args # is not constr.args. self.assertEqual(copy.args, constr.args) self.assertFalse(copy.args is constr.args) # Test copy with new args copy = constr.copy(args=[self.A]) self.assertTrue(type(copy) is type(constr)) self.assertTrue(copy.args[0] is self.A) def test_psd_constraint(self) -> None: """Test the PSD constraint <<. """ constr = self.A >> self.B self.assertEqual(constr.name(), "A + -B >> 0") self.assertEqual(constr.shape, (2, 2)) # Test value and dual_value. assert constr.dual_value is None with self.assertRaises(ValueError): constr.value() self.A.save_value(np.array([[2, -1], [1, 2]])) self.B.save_value(np.array([[1, 0], [0, 1]])) assert constr.value() self.assertAlmostEqual(constr.violation(), 0) self.assertAlmostEqual(constr.residual, 0) self.B.save_value(np.array([[3, 0], [0, 3]])) assert not constr.value() self.assertAlmostEqual(constr.violation(), 1) self.assertAlmostEqual(constr.residual, 1) with self.assertRaises(Exception) as cm: (self.x >> 0) self.assertEqual(str(cm.exception), "Non-square matrix in positive definite constraint.") # Test copy with args=None copy = constr.copy() self.assertTrue(type(copy) is type(constr)) # A new object is constructed, so copy.args == constr.args but copy.args # is not constr.args. self.assertEqual(copy.args, constr.args) self.assertFalse(copy.args is constr.args) # Test copy with new args copy = constr.copy(args=[self.B]) self.assertTrue(type(copy) is type(constr)) self.assertTrue(copy.args[0] is self.B) def test_nsd_constraint(self) -> None: """Test the PSD constraint <<. """ constr = self.A << self.B self.assertEqual(constr.name(), "B + -A >> 0") self.assertEqual(constr.shape, (2, 2)) # Test value and dual_value. assert constr.dual_value is None with self.assertRaises(ValueError): constr.value() self.B.save_value(np.array([[2, -1], [1, 2]])) self.A.save_value(np.array([[1, 0], [0, 1]])) assert constr.value() self.A.save_value(np.array([[3, 0], [0, 3]])) assert not constr.value() with self.assertRaises(Exception) as cm: self.x << 0 self.assertEqual(str(cm.exception), "Non-square matrix in positive definite constraint.") def test_geq(self) -> None: """Test the >= operator. """ constr = self.z >= self.x self.assertEqual(constr.name(), "x <= z") self.assertEqual(constr.shape, (2,)) # Incompatible dimensions with self.assertRaises(ValueError): (self.y >= self.x) # Test the SOC class. def test_soc_constraint(self) -> None: exp = self.x + self.z scalar_exp = self.a + self.b constr = SOC(scalar_exp, exp) self.assertEqual(constr.size, 3) # Test invalid dimensions. error_str = ("Argument dimensions (1,) and (1, 4), with axis=0, " "are incompatible.") with self.assertRaises(Exception) as cm: SOC(Variable(1), Variable((1, 4))) self.assertEqual(str(cm.exception), error_str) def test_pow3d_constraint(self) -> None: n = 3 np.random.seed(0) alpha = 0.275 x, y, z = Variable(n), Variable(n), Variable(n) con = PowCone3D(x, y, z, alpha) # check violation against feasible values x0, y0 = 0.1 + np.random.rand(n), 0.1 + np.random.rand(n) z0 = x0**alpha * y0**(1-alpha) z0[1] *= -1 x.value, y.value, z.value = x0, y0, z0 viol = con.residual() self.assertLessEqual(viol, 1e-7) # check violation against infeasible values x1 = x0.copy() x1[0] *= -0.9 x.value = x1 viol = con.residual() self.assertGreaterEqual(viol, 0.99*abs(x1[0])) # check invalid constraint data with self.assertRaises(ValueError): con = PowCone3D(x, y, z, 1.001) with self.assertRaises(ValueError): con = PowCone3D(x, y, z, -0.00001) def test_pownd_constraint(self) -> None: n = 4 W, z = Variable(n), Variable() np.random.seed(0) alpha = 0.5 + np.random.rand(n) alpha /= np.sum(alpha) with self.assertRaises(ValueError): # entries don't sum to one con = PowConeND(W, z, alpha+0.01) with self.assertRaises(ValueError): # shapes don't match exactly con = PowConeND(W, z, alpha.reshape((n, 1))) with self.assertRaises(ValueError): # wrong axis con = PowConeND(reshape_atom(W, (n, 1)), z, alpha.reshape((n, 1)), axis=1) # Compute a violation con = PowConeND(W, z, alpha) W0 = 0.1 + np.random.rand(n) z0 = np.prod(np.power(W0, alpha))+0.05 W.value, z.value = W0, z0 viol = con.violation() self.assertGreaterEqual(viol, 0.01) self.assertLessEqual(viol, 0.06) def test_chained_constraints(self) -> None: """Tests that chaining constraints raises an error. """ error_str = ("Cannot evaluate the truth value of a constraint or " "chain constraints, e.g., 1 >= x >= 0.") with self.assertRaises(Exception) as cm: (self.z <= self.x <= 1) self.assertEqual(str(cm.exception), error_str) with self.assertRaises(Exception) as cm: (self.x == self.z == 1) self.assertEqual(str(cm.exception), error_str) with self.assertRaises(Exception) as cm: (self.z <= self.x).__bool__() self.assertEqual(str(cm.exception), error_str) def test_nonpos(self) -> None: """Tests the NonPos constraint for correctness. """ n = 3 x = cp.Variable(n) c = np.arange(n) prob = cp.Problem(cp.Maximize(cp.sum(x)), [cp.NonPos(x - c)]) # Solve through cone program path. prob.solve(solver=cp.ECOS) self.assertItemsAlmostEqual(x.value, c) # Solve through QP path. prob.solve(solver=cp.OSQP) self.assertItemsAlmostEqual(x.value, c) def test_nonpos_dual(self) -> None: """Test dual variables work for NonPos. """ n = 3 x = cp.Variable(n) c = np.arange(n) prob = cp.Problem(cp.Maximize(cp.sum(x)), [(x - c) <= 0]) prob.solve(solver=cp.ECOS) dual = prob.constraints[0].dual_value prob = cp.Problem(cp.Maximize(cp.sum(x)), [cp.NonPos(x - c)]) # Solve through cone program path. prob.solve(solver=cp.ECOS) self.assertItemsAlmostEqual(prob.constraints[0].dual_value, dual) # Solve through QP path. prob.solve(solver=cp.OSQP) self.assertItemsAlmostEqual(prob.constraints[0].dual_value, dual)
def test_symmetric(self) -> None: """Test symmetric variables. """ with self.assertRaises(Exception) as cm: v = Variable((4, 3), symmetric=True) self.assertEqual(str(cm.exception), "Invalid dimensions (4, 3). Must be a square matrix.") v = Variable((2, 2), symmetric=True) assert v.is_symmetric() v = Variable((2, 2), PSD=True) assert v.is_symmetric() v = Variable((2, 2), NSD=True) assert v.is_symmetric() v = Variable((2, 2), diag=True) assert v.is_symmetric() assert self.a.is_symmetric() assert not self.A.is_symmetric() v = Variable((2, 2), symmetric=True) expr = v + v assert expr.is_symmetric() expr = -v assert expr.is_symmetric() expr = v.T assert expr.is_symmetric() expr = cp.real(v) assert expr.is_symmetric() expr = cp.imag(v) assert expr.is_symmetric() expr = cp.conj(v) assert expr.is_symmetric() expr = cp.promote(Variable(), (2, 2)) assert expr.is_symmetric()
def test_matmul_expression(self) -> None: """Test matmul function, corresponding to .__matmul__( operator. """ # Vectors c = Constant([[2], [2]]) exp = c.__matmul__(self.x) self.assertEqual(exp.curvature, s.AFFINE) self.assertEqual(exp.sign, s.UNKNOWN) # self.assertEqual(exp.name(), c.name() + " .__matmul__( " + self.x.name()) self.assertEqual(exp.shape, (1,)) with self.assertRaises(Exception) as cm: self.x.__matmul__(2) self.assertEqual(str(cm.exception), "Scalar operands are not allowed, use '*' instead") # Incompatible dimensions with self.assertRaises(ValueError) as cm: (self.x.__matmul__(np.array([2, 2, 3]))) # Incompatible dimensions with self.assertRaises(Exception) as cm: Constant([[2, 1], [2, 2]]) .__matmul__(self.C) # Affine times affine is okay with warnings.catch_warnings(): warnings.simplefilter("ignore") q = self.A .__matmul__(self.B) self.assertTrue(q.is_quadratic()) # # Nonaffine times nonconstant raises error # with warnings.catch_warnings(): # warnings.simplefilter("ignore") # with self.assertRaises(Exception) as cm: # (self.A.__matmul__(self.B).__matmul__(self.A)) # self.assertEqual(str(cm.exception), "Cannot multiply UNKNOWN and AFFINE.") # Constant expressions T = Constant([[1, 2, 3], [3, 5, 5]]) exp = (T + T) .__matmul__(self.B) self.assertEqual(exp.curvature, s.AFFINE) self.assertEqual(exp.shape, (3, 2)) # Expression that would break sign multiplication without promotion. c = Constant([[2], [2], [-2]]) exp = [[1], [2]] + c.__matmul__(self.C) self.assertEqual(exp.sign, s.UNKNOWN) # Testing shape. a = Parameter((1,)) x = Variable(shape=(1,)) expr = a.__matmul__(x) self.assertEqual(expr.shape, ()) # Testing shape. a = Parameter((1,)) x = Variable(shape=(1,)) expr = a.__matmul__(x) self.assertEqual(expr.shape, ()) A = Parameter((4, 4)) z = Variable((4, 1)) expr = A.__matmul__(z) self.assertEqual(expr.shape, (4, 1)) v = Variable((1, 1)) col_scalar = Parameter((1, 1)) assert v.shape == col_scalar.shape == col_scalar.T.shape
def setUp(self) -> None: self.X = Variable((2, 2), PSD=True) self.Y = Variable((2, 2)) self.F = np.array([[1, 0], [0, -1]])
def setUp(self): self.X = Variable((2, 2), PSD=True) self.Y = Variable((2, 2)) self.F = np.matrix([[1, 0], [0, -1]])
def apply(self, problem): if not attributes_present(problem.variables(), CONVEX_ATTRIBUTES): return problem, () # For each unique variable, add constraints. id2new_var = {} id2new_obj = {} id2old_var = {} constr = [] for var in problem.variables(): if var.id not in id2new_var: id2old_var[var.id] = var new_var = False new_attr = var.attributes.copy() for key in CONVEX_ATTRIBUTES: if new_attr[key]: new_var = True new_attr[key] = False if attributes_present([var], SYMMETRIC_ATTRIBUTES): n = var.shape[0] shape = (n * (n + 1) // 2, 1) upper_tri = Variable(shape, var_id=var.id, **new_attr) upper_tri.set_variable_of_provenance(var) id2new_var[var.id] = upper_tri fill_coeff = Constant(upper_tri_to_full(n)) full_mat = fill_coeff @ upper_tri obj = reshape(full_mat, (n, n)) elif var.attributes['diag']: diag_var = Variable(var.shape[0], var_id=var.id, **new_attr) diag_var.set_variable_of_provenance(var) id2new_var[var.id] = diag_var obj = diag(diag_var) elif new_var: obj = Variable(var.shape, var_id=var.id, **new_attr) obj.set_variable_of_provenance(var) id2new_var[var.id] = obj else: obj = var id2new_var[var.id] = obj id2new_obj[id(var)] = obj if var.is_pos() or var.is_nonneg(): constr.append(obj >= 0) elif var.is_neg() or var.is_nonpos(): constr.append(obj <= 0) elif var.is_psd(): constr.append(obj >> 0) elif var.attributes['NSD']: constr.append(obj << 0) # Create new problem. obj = problem.objective.tree_copy(id_objects=id2new_obj) cons_id_map = {} for cons in problem.constraints: constr.append(cons.tree_copy(id_objects=id2new_obj)) cons_id_map[cons.id] = constr[-1].id inverse_data = (id2new_var, id2old_var, cons_id_map) return cvxtypes.problem()(obj, constr), inverse_data
class TestExpressions(BaseTest): """ Unit tests for the expression/expression module. """ def setUp(self): self.a = Variable(name='a') self.x = Variable(2, name='x') self.y = Variable(3, name='y') self.z = Variable(2, name='z') self.A = Variable((2, 2), name='A') self.B = Variable((2, 2), name='B') self.C = Variable((3, 2), name='C') self.intf = intf.DEFAULT_INTF # Test the Variable class. def test_variable(self): x = Variable(2) y = Variable(2) assert y.name() != x.name() x = Variable(2, name='x') y = Variable() self.assertEqual(x.name(), 'x') self.assertEqual(x.shape, (2, )) self.assertEqual(y.shape, tuple()) self.assertEqual(x.curvature, s.AFFINE) # self.assertEqual(x.canonical_form[0].shape, (2, 1)) # self.assertEqual(x.canonical_form[1], []) self.assertEqual(repr(self.x), "Variable((2,))") self.assertEqual(repr(self.A), "Variable((2, 2))") # # Scalar variable # coeff = self.a.coefficients() # self.assertEqual(coeff[self.a.id], [1]) # # Vector variable. # coeffs = x.coefficients() # self.assertItemsEqual(coeffs.keys(), [x.id]) # vec = coeffs[x.id][0] # self.assertEqual(vec.shape, (2,2)) # self.assertEqual(vec[0,0], 1) # # Matrix variable. # coeffs = self.A.coefficients() # self.assertItemsEqual(coeffs.keys(), [self.A.id]) # self.assertEqual(len(coeffs[self.A.id]), 2) or 0 in self.shape # mat = coeffs[self.A.id][1] # self.assertEqual(mat.shape, (2,4)) # self.assertEqual(mat[0,2], 1) with self.assertRaises(Exception) as cm: p = Variable((2, 2), diag=True, symmetric=True) self.assertEqual( str(cm.exception), "Cannot set more than one special attribute in Variable.") with self.assertRaises(Exception) as cm: p = Variable((2, 0)) self.assertEqual(str(cm.exception), "Invalid dimensions (2, 0).") with self.assertRaises(Exception) as cm: p = Variable((2, .5)) self.assertEqual(str(cm.exception), "Invalid dimensions (2, 0.5).") def test_assign_var_value(self): """Test assigning a value to a variable. """ # Scalar variable. a = Variable() a.value = 1 self.assertEqual(a.value, 1) with self.assertRaises(Exception) as cm: a.value = [2, 1] self.assertEqual(str(cm.exception), "Invalid dimensions (2,) for Variable value.") # Test assigning None. a.value = 1 a.value = None assert a.value is None # Vector variable. x = Variable(2) x.value = [2, 1] self.assertItemsAlmostEqual(x.value, [2, 1]) # Matrix variable. A = Variable((3, 2)) A.value = np.ones((3, 2)) self.assertItemsAlmostEqual(A.value, np.ones((3, 2))) # Test assigning negative val to nonnegative variable. x = Variable(nonneg=True) with self.assertRaises(Exception) as cm: x.value = -2 self.assertEqual(str(cm.exception), "Variable value must be nonnegative.") # Test tranposing variables. def test_transpose_variable(self): var = self.a.T self.assertEqual(var.name(), "a") self.assertEqual(var.shape, tuple()) self.a.save_value(2) self.assertEqual(var.value, 2) var = self.x self.assertEqual(var.name(), "x") self.assertEqual(var.shape, (2, )) x = Variable((2, 1), name='x') var = x.T self.assertEqual(var.name(), "x.T") self.assertEqual(var.shape, (1, 2)) x.save_value(np.matrix([1, 2]).T) self.assertEqual(var.value[0, 0], 1) self.assertEqual(var.value[0, 1], 2) var = self.C.T self.assertEqual(var.name(), "C.T") self.assertEqual(var.shape, (2, 3)) # coeffs = var.canonical_form[0].coefficients() # mat = coeffs.values()[0][0] # self.assertEqual(mat.shape, (2,6)) # self.assertEqual(mat[1,3], 1) index = var[1, 0] self.assertEqual(index.name(), "C.T[1, 0]") self.assertEqual(index.shape, tuple()) var = x.T.T self.assertEqual(var.name(), "x.T.T") self.assertEqual(var.shape, (2, 1)) # Test the Constant class. def test_constants(self): c = Constant(2.0) self.assertEqual(c.name(), str(2.0)) c = Constant(2) self.assertEqual(c.value, 2) self.assertEqual(c.shape, tuple()) self.assertEqual(c.curvature, s.CONSTANT) self.assertEqual(c.sign, s.NONNEG) self.assertEqual(Constant(-2).sign, s.NONPOS) self.assertEqual(Constant(0).sign, s.ZERO) # self.assertEqual(c.canonical_form[0].shape, (1, 1)) # self.assertEqual(c.canonical_form[1], []) # coeffs = c.coefficients() # self.assertEqual(coeffs.keys(), [s.CONSTANT]) # self.assertEqual(coeffs[s.CONSTANT], [2]) # Test the sign. c = Constant([[2], [2]]) self.assertEqual(c.shape, (1, 2)) self.assertEqual(c.sign, s.NONNEG) self.assertEqual((-c).sign, s.NONPOS) self.assertEqual((0 * c).sign, s.ZERO) c = Constant([[2], [-2]]) self.assertEqual(c.sign, s.UNKNOWN) c = Constant(np.zeros((2, 1))) self.assertEqual(c.shape, (2, 1)) # Test sign of a complex expression. c = Constant([1, 2]) self.assertEqual(c.shape, (2, )) A = Constant([[1, 1], [1, 1]]) exp = c.T * A * c self.assertEqual(exp.sign, s.NONNEG) self.assertEqual((c.T * c).sign, s.NONNEG) exp = c.T.T self.assertEqual(exp.sign, s.NONNEG) exp = c.T * self.A self.assertEqual(exp.sign, s.UNKNOWN) # Test repr. self.assertEqual(repr(c), "Constant(CONSTANT, NONNEGATIVE, (2,))") def test_1D_array(self): """Test NumPy 1D arrays as constants. """ c = np.array([1, 2]) p = Parameter(2) p.value = [1, 1] self.assertEqual((c * p).value, 3) self.assertEqual((c * self.x).shape, tuple()) # Test Parameter class on good inputs. def test_parameters_successes(self): # Parameter names and dimensions p = Parameter(name='p') self.assertEqual(p.name(), "p") self.assertEqual(p.shape, tuple()) # Entry-wise constraints on parameter values. val = -np.ones((4, 3)) val[0, 0] = 2 p = Parameter((4, 3)) p.value = val # Initialize a parameter with a value; later, set it to None. p = Parameter(value=10) self.assertEqual(p.value, 10) p.value = 10 p.value = None self.assertEqual(p.value, None) # Test parameter representation. p = Parameter((4, 3), nonpos=True) self.assertEqual(repr(p), 'Parameter((4, 3), nonpos=True)') # Test valid diagonal parameter. p = Parameter((2, 2), diag=True) p.value = sp.csc_matrix(np.eye(2)) self.assertItemsAlmostEqual(p.value.todense(), np.eye(2), places=10) def test_psd_nsd_parameters(self): # Test valid rank-deficeint PSD parameter. np.random.seed(42) a = np.random.normal(size=(100, 95)) a2 = a.dot(a.T) # This must be a PSD matrix. p = Parameter((100, 100), PSD=True) p.value = a2 self.assertItemsAlmostEqual(p.value, a2, places=10) # Test positive definite matrix with non-distinct eigenvalues m, n = 10, 5 A = np.random.randn( m, n) + 1j * np.random.randn(m, n) # a random complex matrix A = np.dot(A.T.conj(), A) # a random Hermitian positive definite matrix A = np.bmat([[np.real(A), -np.imag(A)], [np.imag(A), np.real(A)]]) p = Parameter(shape=(2 * n, 2 * n), PSD=True) p.value = A self.assertItemsAlmostEqual(p.value, A) # Test invalid PSD parameter with self.assertRaises(Exception) as cm: p = Parameter((2, 2), PSD=True, value=[[1, 0], [0, -1]]) self.assertEqual(str(cm.exception), "Parameter value must be positive semidefinite.") # Test invalid NSD parameter with self.assertRaises(Exception) as cm: p = Parameter((2, 2), NSD=True, value=[[1, 0], [0, -1]]) self.assertEqual(str(cm.exception), "Parameter value must be negative semidefinite.") # Test the Parameter class on bad inputs. def test_parameters_failures(self): p = Parameter(name='p') self.assertEqual(p.name(), "p") self.assertEqual(p.shape, tuple()) p = Parameter((4, 3), nonneg=True) with self.assertRaises(Exception) as cm: p.value = 1 self.assertEqual(str(cm.exception), "Invalid dimensions () for Parameter value.") val = -np.ones((4, 3)) val[0, 0] = 2 p = Parameter((4, 3), nonneg=True) with self.assertRaises(Exception) as cm: p.value = val self.assertEqual(str(cm.exception), "Parameter value must be nonnegative.") p = Parameter((4, 3), nonpos=True) with self.assertRaises(Exception) as cm: p.value = val self.assertEqual(str(cm.exception), "Parameter value must be nonpositive.") with self.assertRaises(Exception) as cm: p = Parameter(2, 1, nonpos=True, value=[2, 1]) self.assertEqual(str(cm.exception), "Parameter value must be nonpositive.") with self.assertRaises(Exception) as cm: p = Parameter((4, 3), nonneg=True, value=[1, 2]) self.assertEqual(str(cm.exception), "Invalid dimensions (2,) for Parameter value.") with self.assertRaises(Exception) as cm: p = Parameter((2, 2), diag=True, symmetric=True) self.assertEqual( str(cm.exception), "Cannot set more than one special attribute in Parameter.") # Boolean with self.assertRaises(Exception) as cm: p = Parameter((2, 2), boolean=True, value=[[1, 1], [1, -1]]) self.assertEqual(str(cm.exception), "Parameter value must be boolean.") # Integer with self.assertRaises(Exception) as cm: p = Parameter((2, 2), integer=True, value=[[1, 1.5], [1, -1]]) self.assertEqual(str(cm.exception), "Parameter value must be integer.") # Diag. with self.assertRaises(Exception) as cm: p = Parameter((2, 2), diag=True, value=[[1, 1], [1, -1]]) self.assertEqual(str(cm.exception), "Parameter value must be diagonal.") # Symmetric. with self.assertRaises(Exception) as cm: p = Parameter((2, 2), symmetric=True, value=[[1, 1], [-1, -1]]) self.assertEqual(str(cm.exception), "Parameter value must be symmetric.") def test_symmetric(self): """Test symmetric variables. """ with self.assertRaises(Exception) as cm: v = Variable((4, 3), symmetric=True) self.assertEqual( str(cm.exception), "Invalid dimensions (4, 3). Must be a square matrix.") v = Variable((2, 2), symmetric=True) assert v.is_symmetric() v = Variable((2, 2), PSD=True) assert v.is_symmetric() v = Variable((2, 2), NSD=True) assert v.is_symmetric() v = Variable((2, 2), diag=True) assert v.is_symmetric() assert self.a.is_symmetric() assert not self.A.is_symmetric() v = Variable((2, 2), symmetric=True) expr = v + v assert expr.is_symmetric() expr = -v assert expr.is_symmetric() expr = v.T assert expr.is_symmetric() expr = real(v) assert expr.is_symmetric() expr = imag(v) assert expr.is_symmetric() expr = conj(v) assert expr.is_symmetric() expr = promote(Variable(), (2, 2)) assert expr.is_symmetric() def test_hermitian(self): """Test Hermitian variables. """ with self.assertRaises(Exception) as cm: v = Variable((4, 3), hermitian=True) self.assertEqual( str(cm.exception), "Invalid dimensions (4, 3). Must be a square matrix.") v = Variable((2, 2), hermitian=True) assert v.is_hermitian() # v = Variable((2,2), PSD=True) # assert v.is_symmetric() # v = Variable((2,2), NSD=True) # assert v.is_symmetric() v = Variable((2, 2), diag=True) assert v.is_hermitian() v = Variable((2, 2), hermitian=True) expr = v + v assert expr.is_hermitian() expr = -v assert expr.is_hermitian() expr = v.T assert expr.is_hermitian() expr = real(v) assert expr.is_hermitian() expr = imag(v) assert expr.is_hermitian() expr = conj(v) assert expr.is_hermitian() expr = promote(Variable(), (2, 2)) assert expr.is_hermitian() def test_round_attr(self): """Test rounding for attributes. """ # Nonpos v = Variable(1, nonpos=True) self.assertAlmostEqual(v.project(1), 0) v = Variable(2, nonpos=True) self.assertItemsAlmostEqual(v.project(np.array([1, -1])), [0, -1]) # Nonneg v = Variable(1, nonneg=True) self.assertAlmostEqual(v.project(-1), 0) v = Variable(2, nonneg=True) self.assertItemsAlmostEqual(v.project(np.array([1, -1])), [1, 0]) # Boolean v = Variable((2, 2), boolean=True) self.assertItemsAlmostEqual(v.project(np.array([[1, -1], [1, 0]]).T), [1, 0, 1, 0]) # Integer v = Variable((2, 2), integer=True) self.assertItemsAlmostEqual(v.project(np.array([[1, -1.6], [1, 0]]).T), [1, -2, 1, 0]) # Symmetric v = Variable((2, 2), symmetric=True) self.assertItemsAlmostEqual(v.project(np.array([[1, -1], [1, 0]])), [1, 0, 0, 0]) # PSD v = Variable((2, 2), PSD=True) self.assertItemsAlmostEqual(v.project(np.array([[1, -1], [1, -1]])), [1, 0, 0, 0]) # NSD v = Variable((2, 2), NSD=True) self.assertItemsAlmostEqual(v.project(np.array([[1, -1], [1, -1]])), [0, 0, 0, -1]) # diag v = Variable((2, 2), diag=True) self.assertItemsAlmostEqual( v.project(np.array([[1, -1], [1, 0]])).todense(), [1, 0, 0, 0]) # Hermitian v = Variable((2, 2), hermitian=True) self.assertItemsAlmostEqual(v.project(np.array([[1, -1j], [1, 0]])), [1, 0.5 + 0.5j, 0.5 - 0.5j, 0]) A = Constant(np.array([[1.0]])) self.assertEqual(A.is_psd(), True) self.assertEqual(A.is_nsd(), False) A = Constant(np.array([[-1.0]])) self.assertEqual(A.is_psd(), False) self.assertEqual(A.is_nsd(), True) A = Constant(np.array([[0.0]])) self.assertEqual(A.is_psd(), True) self.assertEqual(A.is_nsd(), True) # Test the AddExpresion class. def test_add_expression(self): # Vectors c = Constant([2, 2]) exp = self.x + c self.assertEqual(exp.curvature, s.AFFINE) self.assertEqual(exp.sign, s.UNKNOWN) # self.assertEqual(exp.canonical_form[0].shape, (2, 1)) # self.assertEqual(exp.canonical_form[1], []) # self.assertEqual(exp.name(), self.x.name() + " + " + c.name()) self.assertEqual(exp.shape, (2, )) z = Variable(2, name='z') exp = exp + z + self.x # Incompatible dimensions with self.assertRaises(ValueError) as cm: (self.x + self.y) # Matrices exp = self.A + self.B self.assertEqual(exp.curvature, s.AFFINE) self.assertEqual(exp.shape, (2, 2)) # Incompatible dimensions with self.assertRaises(ValueError) as cm: (self.A + self.C) # Incompatible dimensions with self.assertRaises(ValueError) as cm: AddExpression([self.A, self.C]) # Test that sum is flattened. exp = self.x + c + self.x self.assertEqual(len(exp.args), 3) # Test repr. self.assertEqual(repr(exp), "Expression(AFFINE, UNKNOWN, (2,))") # Test the SubExpresion class. def test_sub_expression(self): # Vectors c = Constant([2, 2]) exp = self.x - c self.assertEqual(exp.curvature, s.AFFINE) self.assertEqual(exp.sign, s.UNKNOWN) # self.assertEqual(exp.canonical_form[0].shape, (2, 1)) # self.assertEqual(exp.canonical_form[1], []) # self.assertEqual(exp.name(), self.x.name() + " - " + Constant([2,2]).name()) self.assertEqual(exp.shape, (2, )) z = Variable(2, name='z') exp = exp - z - self.x # Incompatible dimensions with self.assertRaises(ValueError) as cm: (self.x - self.y) # Matrices exp = self.A - self.B self.assertEqual(exp.curvature, s.AFFINE) self.assertEqual(exp.shape, (2, 2)) # Incompatible dimensions with self.assertRaises(ValueError) as cm: (self.A - self.C) # Test repr. self.assertEqual(repr(self.x - c), "Expression(AFFINE, UNKNOWN, (2,))") # Test the MulExpresion class. def test_mul_expression(self): # Vectors c = Constant([[2], [2]]) exp = c * self.x self.assertEqual(exp.curvature, s.AFFINE) self.assertEqual((c[0] * self.x).sign, s.UNKNOWN) # self.assertEqual(exp.canonical_form[0].shape, (1, 1)) # self.assertEqual(exp.canonical_form[1], []) # self.assertEqual(exp.name(), c.name() + " * " + self.x.name()) self.assertEqual(exp.shape, (1, )) # Incompatible dimensions with self.assertRaises(ValueError) as cm: ([2, 2, 3] * self.x) # Matrices: Incompatible dimensions with self.assertRaises(ValueError) as cm: Constant([[2, 1], [2, 2]]) * self.C # Affine times affine is okay with warnings.catch_warnings(): warnings.simplefilter("ignore") q = self.A * self.B self.assertTrue(q.is_quadratic()) # Constant expressions T = Constant([[1, 2, 3], [3, 5, 5]]) exp = (T + T) * self.B self.assertEqual(exp.curvature, s.AFFINE) self.assertEqual(exp.shape, (3, 2)) # Expression that would break sign multiplication without promotion. c = Constant([[2], [2], [-2]]) exp = [[1], [2]] + c * self.C self.assertEqual(exp.sign, s.UNKNOWN) def test_matmul_expression(self): """Test matmul function, corresponding to .__matmul__( operator. """ # Vectors c = Constant([[2], [2]]) exp = c.__matmul__(self.x) self.assertEqual(exp.curvature, s.AFFINE) self.assertEqual(exp.sign, s.UNKNOWN) # self.assertEqual(exp.name(), c.name() + " .__matmul__( " + self.x.name()) self.assertEqual(exp.shape, (1, )) with self.assertRaises(Exception) as cm: self.x.__matmul__(2) self.assertEqual(str(cm.exception), "Scalar operands are not allowed, use '*' instead") # Incompatible dimensions with self.assertRaises(ValueError) as cm: (self.x.__matmul__(np.array([2, 2, 3]))) # Incompatible dimensions with self.assertRaises(Exception) as cm: Constant([[2, 1], [2, 2]]).__matmul__(self.C) # Affine times affine is okay with warnings.catch_warnings(): warnings.simplefilter("ignore") q = self.A.__matmul__(self.B) self.assertTrue(q.is_quadratic()) # # Nonaffine times nonconstant raises error # with warnings.catch_warnings(): # warnings.simplefilter("ignore") # with self.assertRaises(Exception) as cm: # (self.A.__matmul__(self.B).__matmul__(self.A)) # self.assertEqual(str(cm.exception), "Cannot multiply UNKNOWN and AFFINE.") # Constant expressions T = Constant([[1, 2, 3], [3, 5, 5]]) exp = (T + T).__matmul__(self.B) self.assertEqual(exp.curvature, s.AFFINE) self.assertEqual(exp.shape, (3, 2)) # Expression that would break sign multiplication without promotion. c = Constant([[2], [2], [-2]]) exp = [[1], [2]] + c.__matmul__(self.C) self.assertEqual(exp.sign, s.UNKNOWN) # Test the DivExpresion class. def test_div_expression(self): # Vectors exp = self.x / 2 self.assertEqual(exp.curvature, s.AFFINE) self.assertEqual(exp.sign, s.UNKNOWN) # self.assertEqual(exp.canonical_form[0].shape, (2, 1)) # self.assertEqual(exp.canonical_form[1], []) # self.assertEqual(exp.name(), c.name() + " * " + self.x.name()) self.assertEqual(exp.shape, (2, )) with self.assertRaises(Exception) as cm: (self.x / [2, 2, 3]) print(cm.exception) self.assertEqual(str(cm.exception), "Can only divide by a scalar constant.") # Constant expressions. c = Constant(2) exp = c / (3 - 5) self.assertEqual(exp.curvature, s.CONSTANT) self.assertEqual(exp.shape, tuple()) self.assertEqual(exp.sign, s.NONPOS) # Parameters. p = Parameter(nonneg=True) exp = 2 / p p.value = 2 self.assertEqual(exp.value, 1) rho = Parameter(nonneg=True) rho.value = 1 self.assertEqual(rho.sign, s.NONNEG) self.assertEqual(Constant(2).sign, s.NONNEG) self.assertEqual((Constant(2) / Constant(2)).sign, s.NONNEG) self.assertEqual((Constant(2) * rho).sign, s.NONNEG) self.assertEqual((rho / 2).sign, s.NONNEG) # Test the NegExpression class. def test_neg_expression(self): # Vectors exp = -self.x self.assertEqual(exp.curvature, s.AFFINE) self.assertEqual(exp.shape, (2, )) assert exp.is_affine() self.assertEqual(exp.sign, s.UNKNOWN) assert not exp.is_nonneg() # self.assertEqual(exp.canonical_form[0].shape, (2, 1)) # self.assertEqual(exp.canonical_form[1], []) # self.assertEqual(exp.name(), "-%s" % self.x.name()) self.assertEqual(exp.shape, self.x.shape) # Matrices exp = -self.C self.assertEqual(exp.curvature, s.AFFINE) self.assertEqual(exp.shape, (3, 2)) # Test promotion of scalar constants. def test_scalar_const_promotion(self): # Vectors exp = self.x + 2 self.assertEqual(exp.curvature, s.AFFINE) assert exp.is_affine() self.assertEqual(exp.sign, s.UNKNOWN) assert not exp.is_nonpos() # self.assertEqual(exp.canonical_form[0].shape, (2, 1)) # self.assertEqual(exp.canonical_form[1], []) # self.assertEqual(exp.name(), self.x.name() + " + " + Constant(2).name()) self.assertEqual(exp.shape, (2, )) self.assertEqual((4 - self.x).shape, (2, )) self.assertEqual((4 * self.x).shape, (2, )) self.assertEqual((4 <= self.x).shape, (2, )) self.assertEqual((4 == self.x).shape, (2, )) self.assertEqual((self.x >= 4).shape, (2, )) # Matrices exp = (self.A + 2) + 4 self.assertEqual(exp.curvature, s.AFFINE) self.assertEqual((3 * self.A).shape, (2, 2)) self.assertEqual(exp.shape, (2, 2)) # Test indexing expression. def test_index_expression(self): # Tuple of integers as key. exp = self.x[1] # self.assertEqual(exp.name(), "x[1,0]") self.assertEqual(exp.curvature, s.AFFINE) assert exp.is_affine() self.assertEqual(exp.shape, tuple()) # coeff = exp.canonical_form[0].coefficients()[self.x][0] # self.assertEqual(coeff[0,1], 1) self.assertEqual(exp.value, None) exp = self.x[1].T # self.assertEqual(exp.name(), "x[1,0]") self.assertEqual(exp.curvature, s.AFFINE) self.assertEqual(exp.shape, tuple()) with self.assertRaises(Exception) as cm: (self.x[2, 0]) self.assertEqual(str(cm.exception), "Too many indices for expression.") with self.assertRaises(Exception) as cm: (self.x[2]) self.assertEqual(str(cm.exception), "Index 2 is out of bounds for axis 0 with size 2.") # Slicing exp = self.C[0:2, 1] # self.assertEqual(exp.name(), "C[0:2,1]") self.assertEqual(exp.shape, (2, )) exp = self.C[0:, 0:2] # self.assertEqual(exp.name(), "C[0:,0:2]") self.assertEqual(exp.shape, (3, 2)) exp = self.C[0::2, 0::2] # self.assertEqual(exp.name(), "C[0::2,0::2]") self.assertEqual(exp.shape, (2, 1)) exp = self.C[:3, :1:2] # self.assertEqual(exp.name(), "C[0:3,0]") self.assertEqual(exp.shape, (3, 1)) exp = self.C[0:, 0] # self.assertEqual(exp.name(), "C[0:,0]") self.assertEqual(exp.shape, (3, )) c = Constant([[1, -2], [0, 4]]) exp = c[1, 1] self.assertEqual(exp.curvature, s.CONSTANT) self.assertEqual(exp.sign, s.UNKNOWN) self.assertEqual(c[0, 1].sign, s.UNKNOWN) self.assertEqual(c[1, 0].sign, s.UNKNOWN) self.assertEqual(exp.shape, tuple()) self.assertEqual(exp.value, 4) c = Constant([[1, -2, 3], [0, 4, 5], [7, 8, 9]]) exp = c[0:3, 0:4:2] self.assertEqual(exp.curvature, s.CONSTANT) assert exp.is_constant() self.assertEqual(exp.shape, (3, 2)) self.assertEqual(exp[0, 1].value, 7) # Slice of transpose exp = self.C.T[0:2, 1:2] self.assertEqual(exp.shape, (2, 1)) # Arithmetic expression indexing exp = (self.x + self.z)[1] # self.assertEqual(exp.name(), "x[1,0] + z[1,0]") self.assertEqual(exp.curvature, s.AFFINE) self.assertEqual(exp.sign, s.UNKNOWN) self.assertEqual(exp.shape, tuple()) exp = (self.x + self.a)[1:2] # self.assertEqual(exp.name(), "x[1,0] + a") self.assertEqual(exp.curvature, s.AFFINE) self.assertEqual(exp.shape, (1, )) exp = (self.x - self.z)[1:2] # self.assertEqual(exp.name(), "x[1,0] - z[1,0]") self.assertEqual(exp.curvature, s.AFFINE) self.assertEqual(exp.shape, (1, )) exp = (self.x - self.a)[1] # self.assertEqual(exp.name(), "x[1,0] - a") self.assertEqual(exp.curvature, s.AFFINE) self.assertEqual(exp.shape, tuple()) exp = (-self.x)[1] # self.assertEqual(exp.name(), "-x[1,0]") self.assertEqual(exp.curvature, s.AFFINE) self.assertEqual(exp.shape, tuple()) c = Constant([[1, 2], [3, 4]]) exp = (c * self.x)[1] # self.assertEqual(exp.name(), "[[2], [4]] * x[0:,0]") self.assertEqual(exp.curvature, s.AFFINE) self.assertEqual(exp.shape, tuple()) c = Constant([[1, 2], [3, 4]]) exp = (c * self.a)[1, 0:1] # self.assertEqual(exp.name(), "2 * a") self.assertEqual(exp.curvature, s.AFFINE) self.assertEqual(exp.shape, (1, )) def test_none_idx(self): """Test None as index. """ expr = self.a[None, None] self.assertEqual(expr.shape, (1, 1)) expr = self.x[:, None] self.assertEqual(expr.shape, (2, 1)) expr = self.x[None, :] self.assertEqual(expr.shape, (1, 2)) expr = Constant([1, 2])[None, :] self.assertEqual(expr.shape, (1, 2)) self.assertItemsAlmostEqual(expr.value, [1, 2]) def test_out_of_bounds(self): """Test out of bounds indices. """ with self.assertRaises(Exception) as cm: self.x[100] self.assertEqual(str(cm.exception), "Index 100 is out of bounds for axis 0 with size 2.") with self.assertRaises(Exception) as cm: self.x[-100] self.assertEqual( str(cm.exception), "Index -100 is out of bounds for axis 0 with size 2.") exp = self.x[:-100] self.assertEqual(exp.size, (0, )) self.assertItemsAlmostEqual(exp.value, np.array([])) exp = self.C[100:2] self.assertEqual(exp.shape, (0, 2)) exp = self.C[:, -199:2] self.assertEqual(exp.shape, (3, 2)) exp = self.C[:, -199:-3] self.assertEqual(exp.shape, (3, 0)) def test_neg_indices(self): """Test negative indices. """ c = Constant([[1, 2], [3, 4]]) exp = c[-1, -1] self.assertEqual(exp.value, 4) self.assertEqual(exp.shape, tuple()) self.assertEqual(exp.curvature, s.CONSTANT) c = Constant([1, 2, 3, 4]) exp = c[1:-1] self.assertItemsAlmostEqual(exp.value, [2, 3]) self.assertEqual(exp.shape, (2, )) self.assertEqual(exp.curvature, s.CONSTANT) c = Constant([1, 2, 3, 4]) exp = c[::-1] self.assertItemsAlmostEqual(exp.value, [4, 3, 2, 1]) self.assertEqual(exp.shape, (4, )) self.assertEqual(exp.curvature, s.CONSTANT) x = Variable(4) self.assertEqual(x[::-1].shape, (4, )) Problem(Minimize(0), [x[::-1] == c]).solve() self.assertItemsAlmostEqual(x.value, [4, 3, 2, 1]) x = Variable(2) self.assertEqual(x[::-1].shape, (2, )) x = Variable(100, name="x") self.assertEqual("x[0:99]", str(x[:-1])) c = Constant([[1, 2], [3, 4]]) expr = c[0, 2:0:-1] self.assertEqual(expr.shape, (1, )) self.assertAlmostEqual(expr.value, 3) expr = c[0, 2::-1] self.assertEqual(expr.shape, (2, )) self.assertItemsAlmostEqual(expr.value, [3, 1]) def test_logical_indices(self): """Test indexing with boolean arrays. """ A = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]) C = Constant(A) # Boolean array. expr = C[A <= 2] self.assertEqual(expr.shape, (2, )) self.assertEqual(expr.sign, s.NONNEG) self.assertItemsAlmostEqual(A[A <= 2], expr.value) expr = C[A % 2 == 0] self.assertEqual(expr.shape, (6, )) self.assertEqual(expr.sign, s.NONNEG) self.assertItemsAlmostEqual(A[A % 2 == 0], expr.value) # Boolean array for rows, index for columns. expr = C[np.array([True, False, True]), 3] self.assertEqual(expr.shape, (2, )) self.assertEqual(expr.sign, s.NONNEG) self.assertItemsAlmostEqual(A[np.array([True, False, True]), 3], expr.value) # Index for row, boolean array for columns. expr = C[1, np.array([True, False, False, True])] self.assertEqual(expr.shape, (2, )) self.assertEqual(expr.sign, s.NONNEG) self.assertItemsAlmostEqual(A[1, np.array([True, False, False, True])], expr.value) # Boolean array for rows, slice for columns. expr = C[np.array([True, True, True]), 1:3] self.assertEqual(expr.shape, (3, 2)) self.assertEqual(expr.sign, s.NONNEG) self.assertItemsAlmostEqual(A[np.array([True, True, True]), 1:3], expr.value) # Slice for row, boolean array for columns. expr = C[1:-1, np.array([True, False, True, True])] self.assertEqual(expr.shape, (1, 3)) self.assertEqual(expr.sign, s.NONNEG) self.assertItemsAlmostEqual( A[1:-1, np.array([True, False, True, True])], expr.value) # Boolean arrays for rows and columns. # Not sure what this does. expr = C[np.array([True, True, True]), np.array([True, False, True, True])] self.assertEqual(expr.shape, (3, )) self.assertEqual(expr.sign, s.NONNEG) self.assertItemsAlmostEqual( A[np.array([True, True, True]), np.array([True, False, True, True])], expr.value) def test_selector_list_indices(self): """Test indexing with lists/ndarrays of indices. """ A = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]) C = Constant(A) # List for rows. expr = C[[1, 2]] self.assertEqual(expr.shape, (2, 4)) self.assertEqual(expr.sign, s.NONNEG) self.assertItemsAlmostEqual(A[[1, 2]], expr.value) # List for rows, index for columns. expr = C[[0, 2], 3] self.assertEqual(expr.shape, (2, )) self.assertEqual(expr.sign, s.NONNEG) self.assertItemsAlmostEqual(A[[0, 2], 3], expr.value) # Index for row, list for columns. expr = C[1, [0, 2]] self.assertEqual(expr.shape, (2, )) self.assertEqual(expr.sign, s.NONNEG) self.assertItemsAlmostEqual(A[1, [0, 2]], expr.value) # List for rows, slice for columns. expr = C[[0, 2], 1:3] self.assertEqual(expr.shape, (2, 2)) self.assertEqual(expr.sign, s.NONNEG) self.assertItemsAlmostEqual(A[[0, 2], 1:3], expr.value) # Slice for row, list for columns. expr = C[1:-1, [0, 2]] self.assertEqual(expr.shape, (1, 2)) self.assertEqual(expr.sign, s.NONNEG) self.assertItemsAlmostEqual(A[1:-1, [0, 2]], expr.value) # Lists for rows and columns. expr = C[[0, 1], [1, 3]] self.assertEqual(expr.shape, (2, )) self.assertEqual(expr.sign, s.NONNEG) self.assertItemsAlmostEqual(A[[0, 1], [1, 3]], expr.value) # Ndarray for rows, list for columns. expr = C[np.array([0, 1]), [1, 3]] self.assertEqual(expr.shape, (2, )) self.assertEqual(expr.sign, s.NONNEG) self.assertItemsAlmostEqual(A[np.array([0, 1]), [1, 3]], expr.value) # Ndarrays for rows and columns. expr = C[np.array([0, 1]), np.array([1, 3])] self.assertEqual(expr.shape, (2, )) self.assertEqual(expr.sign, s.NONNEG) self.assertItemsAlmostEqual(A[np.array([0, 1]), np.array([1, 3])], expr.value) def test_powers(self): exp = self.x**2 self.assertEqual(exp.curvature, s.CONVEX) exp = self.x**0.5 self.assertEqual(exp.curvature, s.CONCAVE) exp = self.x**-1 self.assertEqual(exp.curvature, s.CONVEX) def test_sum(self): """Test built-in sum. Not good usage. """ self.a.value = 1 expr = sum(self.a) self.assertEqual(expr.value, 1) self.x.value = [1, 2] expr = sum(self.x) self.assertEqual(expr.value, 3) def test_var_copy(self): """Test the copy function for variable types. """ x = Variable((3, 4), name="x") y = x.copy() self.assertEqual(y.shape, (3, 4)) self.assertEqual(y.name(), "x") x = Variable((5, 5), PSD=True, name="x") y = x.copy() self.assertEqual(y.shape, (5, 5)) def test_param_copy(self): """Test the copy function for Parameters. """ x = Parameter((3, 4), name="x", nonneg=True) y = x.copy() self.assertEqual(y.shape, (3, 4)) self.assertEqual(y.name(), "x") self.assertEqual(y.sign, "NONNEGATIVE") def test_constant_copy(self): """Test the copy function for Constants. """ x = Constant(2) y = x.copy() self.assertEqual(y.shape, tuple()) self.assertEqual(y.value, 2) def test_is_pwl(self): """Test is_pwl() """ A = np.ones((2, 3)) b = np.ones(2) expr = A * self.y - b self.assertEqual(expr.is_pwl(), True) expr = maximum(1, 3 * self.y) self.assertEqual(expr.is_pwl(), True) expr = abs(self.y) self.assertEqual(expr.is_pwl(), True) expr = pnorm(3 * self.y, 1) self.assertEqual(expr.is_pwl(), True) expr = pnorm(3 * self.y**2, 1) self.assertEqual(expr.is_pwl(), False)
def test_variable(self): """Test the Variable class. """ x = Variable(2, complex=False) y = Variable(2, complex=True) z = Variable(2, imag=True) assert not x.is_complex() assert not x.is_imag() assert y.is_complex() assert not y.is_imag() assert z.is_complex() assert z.is_imag() with self.assertRaises(Exception) as cm: x.value = np.array([1j, 0.]) self.assertEqual(str(cm.exception), "Variable value must be real.") y.value = np.array([1., 0.]) y.value = np.array([1j, 0.]) with self.assertRaises(Exception) as cm: z.value = np.array([1., 0.]) self.assertEqual(str(cm.exception), "Variable value must be imaginary.")
def test_hermitian(self): """Test Hermitian variables. """ with self.assertRaises(Exception) as cm: v = Variable((4, 3), hermitian=True) self.assertEqual( str(cm.exception), "Invalid dimensions (4, 3). Must be a square matrix.") v = Variable((2, 2), hermitian=True) assert v.is_hermitian() # v = Variable((2,2), PSD=True) # assert v.is_symmetric() # v = Variable((2,2), NSD=True) # assert v.is_symmetric() v = Variable((2, 2), diag=True) assert v.is_hermitian() v = Variable((2, 2), hermitian=True) expr = v + v assert expr.is_hermitian() expr = -v assert expr.is_hermitian() expr = v.T assert expr.is_hermitian() expr = real(v) assert expr.is_hermitian() expr = imag(v) assert expr.is_hermitian() expr = conj(v) assert expr.is_hermitian() expr = promote(Variable(), (2, 2)) assert expr.is_hermitian()
def exp_canon(expr, args): x = promote(args[0], expr.shape) t = Variable(expr.shape) ones = Constant(np.ones(expr.shape)) constraints = [ExpCone(x, ones, t)] return t, constraints
def test_variable(self): x = Variable(2) y = Variable(2) assert y.name() != x.name() x = Variable(2, name='x') y = Variable() self.assertEqual(x.name(), 'x') self.assertEqual(x.shape, (2, )) self.assertEqual(y.shape, tuple()) self.assertEqual(x.curvature, s.AFFINE) # self.assertEqual(x.canonical_form[0].shape, (2, 1)) # self.assertEqual(x.canonical_form[1], []) self.assertEqual(repr(self.x), "Variable((2,))") self.assertEqual(repr(self.A), "Variable((2, 2))") # # Scalar variable # coeff = self.a.coefficients() # self.assertEqual(coeff[self.a.id], [1]) # # Vector variable. # coeffs = x.coefficients() # self.assertItemsEqual(coeffs.keys(), [x.id]) # vec = coeffs[x.id][0] # self.assertEqual(vec.shape, (2,2)) # self.assertEqual(vec[0,0], 1) # # Matrix variable. # coeffs = self.A.coefficients() # self.assertItemsEqual(coeffs.keys(), [self.A.id]) # self.assertEqual(len(coeffs[self.A.id]), 2) or 0 in self.shape # mat = coeffs[self.A.id][1] # self.assertEqual(mat.shape, (2,4)) # self.assertEqual(mat[0,2], 1) with self.assertRaises(Exception) as cm: p = Variable((2, 2), diag=True, symmetric=True) self.assertEqual( str(cm.exception), "Cannot set more than one special attribute in Variable.") with self.assertRaises(Exception) as cm: p = Variable((2, 0)) self.assertEqual(str(cm.exception), "Invalid dimensions (2, 0).") with self.assertRaises(Exception) as cm: p = Variable((2, .5)) self.assertEqual(str(cm.exception), "Invalid dimensions (2, 0.5).")
def setUp(self) -> None: self.x = Variable(name='x') self.y = Variable(3, name='y') self.z = Variable(name='z')