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: 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: Variable((2, 0)) self.assertEqual(str(cm.exception), "Invalid dimensions (2, 0).") with self.assertRaises(Exception) as cm: Variable((2, .5)) self.assertEqual(str(cm.exception), "Invalid dimensions (2, 0.5).") with self.assertRaises(Exception) as cm: Variable(2, 1) self.assertEqual(str(cm.exception), "Variable name 1 must be a string.") 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.array([[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.vstack([ np.hstack([np.real(A), -np.imag(A)]), np.hstack([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 arithmetic. p = Parameter(shape=(2, 2), PSD=True) self.assertTrue((2 * p).is_psd()) self.assertTrue((p + p).is_psd()) self.assertTrue((-p).is_nsd()) self.assertTrue(((-2) * (-p)).is_psd()) # 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 = 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_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 = cp.real(v) assert expr.is_hermitian() expr = cp.imag(v) assert expr.is_hermitian() expr = cp.conj(v) assert expr.is_hermitian() expr = cp.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): (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): (self.A + self.C) # Incompatible dimensions with self.assertRaises(ValueError): 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): (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): (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): ([2, 2, 3] * self.x) # Matrices: Incompatible dimensions with self.assertRaises(ValueError): 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) # 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 # 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.assertRegexpMatches(str(cm.exception), "Incompatible shapes for division.*") c = Constant([3.0, 4.0, 12.0]) self.assertItemsAlmostEqual((c / Constant([1.0, 2.0, 3.0])).value, np.array([3.0, 2.0, 4.0])) # 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 cvxpy sum function. """ self.a.value = 1 expr = cp.sum(self.a) self.assertEqual(expr.value, 1) self.x.value = [1, 2] expr = cp.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 = cp.maximum(1, 3 * self.y) self.assertEqual(expr.is_pwl(), True) expr = cp.abs(self.y) self.assertEqual(expr.is_pwl(), True) expr = cp.pnorm(3 * self.y, 1) self.assertEqual(expr.is_pwl(), True) expr = cp.pnorm(3 * self.y**2, 1) self.assertEqual(expr.is_pwl(), False)
class TestExpressions(BaseTest): """ Unit tests for the expression/expression module. """ def setUp(self) -> None: 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) -> None: 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: 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: Variable((2, 0)) self.assertEqual(str(cm.exception), "Invalid dimensions (2, 0).") with self.assertRaises(Exception) as cm: Variable((2, .5)) self.assertEqual(str(cm.exception), "Invalid dimensions (2, 0.5).") with self.assertRaises(Exception) as cm: Variable(2, 1) self.assertEqual(str(cm.exception), "Variable name 1 must be a string.") def test_assign_var_value(self) -> None: """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) -> None: 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.array([[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) -> None: 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_constant_psd_nsd(self): n = 5 np.random.randn(0) U = np.random.randn(n, n) U = U @ U.T (evals, U) = np.linalg.eigh(U) # U is now an orthogonal matrix # Try four indefinite matrices with different eigenvalue # spread around the origin. v1 = np.array([3, 2, 1, 1e-8, -1]) P = Constant(U @ np.diag(v1) @ U.T) self.assertFalse(P.is_psd()) self.assertFalse(P.is_nsd()) v2 = np.array([3, 2, 2, 1e-6, -1]) P = Constant(U @ np.diag(v2) @ U.T) self.assertFalse(P.is_psd()) self.assertFalse(P.is_nsd()) v3 = np.array([3, 2, 2, 1e-4, -1e-6]) P = Constant(U @ np.diag(v3) @ U.T) self.assertFalse(P.is_psd()) self.assertFalse(P.is_nsd()) v4 = np.array([-1, 3, 0, 0, 0]) P = Constant(U @ np.diag(v4) @ U.T) self.assertFalse(P.is_psd()) self.assertFalse(P.is_nsd()) # Try a test case given in GitHub issue 1451. # (Should be equivalent to v4 above). P = Constant(np.array([[1, 2], [2, 1]])) x = Variable(shape=(2, )) expr = cp.quad_form(x, P) self.assertFalse(expr.is_dcp()) self.assertFalse((-expr).is_dcp()) self.assertFalse(gershgorin_psd_check(P.value, tol=0.99)) # Useful Gershgorin disc check P = Constant(np.array([[2, 1], [1, 2]])) self.assertTrue(gershgorin_psd_check(P.value, tol=0.0)) # Verify good behavior for large eigenvalues P = Constant(np.diag(9 * [1e-4] + [-1e4])) self.assertFalse(P.is_psd()) self.assertFalse(P.is_nsd()) # Check a case when the matrix is in fact PSD. P = Constant(np.ones(shape=(5, 5))) self.assertTrue(P.is_psd()) self.assertFalse(P.is_nsd()) # Check with sparse inputs P = Constant(sp.eye(10)) self.assertTrue(gershgorin_psd_check(P.value, s.EIGVAL_TOL)) self.assertTrue(P.is_psd()) self.assertTrue((-P).is_nsd()) Q = -s.EIGVAL_TOL / 2 * P self.assertTrue(gershgorin_psd_check(Q.value, s.EIGVAL_TOL)) Q = -1.1 * s.EIGVAL_TOL * P self.assertFalse(gershgorin_psd_check(Q.value, s.EIGVAL_TOL)) self.assertFalse(Q.is_psd()) def test_1D_array(self) -> None: """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) -> None: # 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) -> None: # 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.vstack([ np.hstack([np.real(A), -np.imag(A)]), np.hstack([np.imag(A), np.real(A)]) ]) p = Parameter(shape=(2 * n, 2 * n), PSD=True) p.value = A self.assertItemsAlmostEqual(p.value, A) # Test arithmetic. p = Parameter(shape=(2, 2), PSD=True) self.assertTrue((2 * p).is_psd()) self.assertTrue((p + p).is_psd()) self.assertTrue((-p).is_nsd()) self.assertTrue(((-2) * (-p)).is_psd()) # Test invalid PSD and NSD parameters n = 5 P = Parameter(shape=(n, n), PSD=True) N = Parameter(shape=(n, n), NSD=True) np.random.randn(0) U = np.random.randn(n, n) U = U @ U.T (evals, U) = np.linalg.eigh(U) # U is now an orthogonal matrix v1 = np.array([3, 2, 1, 1e-8, -1]) v2 = np.array([3, 2, 2, 1e-6, -1]) v3 = np.array([3, 2, 2, 1e-4, -1e-6]) v4 = np.array([-1, 3, 0, 0, 0]) vs = [v1, v2, v3, v4] for vi in vs: with self.assertRaises(Exception) as cm: P.value = U @ np.diag(vi) @ U.T self.assertEqual(str(cm.exception), "Parameter value must be positive semidefinite.") with self.assertRaises(Exception) as cm: N.value = -U @ np.diag(vi) @ U.T self.assertEqual(str(cm.exception), "Parameter value must be negative semidefinite.") # Test the Parameter class on bad inputs. def test_parameters_failures(self) -> None: 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) -> 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_hermitian(self) -> None: """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 = cp.real(v) assert expr.is_hermitian() expr = cp.imag(v) assert expr.is_hermitian() expr = cp.conj(v) assert expr.is_hermitian() expr = cp.promote(Variable(), (2, 2)) assert expr.is_hermitian() def test_round_attr(self) -> None: """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) -> None: # 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): (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): (self.A + self.C) # Incompatible dimensions with self.assertRaises(ValueError): 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) -> None: # 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): (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): (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) -> None: # 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): ([2, 2, 3] @ self.x) # Matrices: Incompatible dimensions with self.assertRaises(ValueError): 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) # By default, warnings are raised if we access matmul from *. c = Constant([[2], [2]]) with warnings.catch_warnings(record=True) as w: c * self.x self.assertEqual(2, len(w)) self.assertEqual(w[0].category, UserWarning) self.assertEqual(w[1].category, DeprecationWarning) # repeat, to make sure warnings continue to be displayed c * self.x self.assertEqual(4, len(w)) self.assertEqual(w[2].category, UserWarning) self.assertEqual(w[3].category, DeprecationWarning) # suppress one of the two warnings warnings.simplefilter('ignore', DeprecationWarning) c * self.x self.assertEqual(5, len(w)) # suppress both warnings warnings.simplefilter('ignore', UserWarning) c * self.x self.assertEqual(len(w), 5) # verify that an error can be raised. warnings.simplefilter("error", UserWarning) with self.assertRaises(UserWarning): c * self.x 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 # Test the DivExpresion class. def test_div_expression(self) -> None: # 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.assertRegex(str(cm.exception), "Incompatible shapes for division.*") c = Constant([3.0, 4.0, 12.0]) self.assertItemsAlmostEqual((c / Constant([1.0, 2.0, 3.0])).value, np.array([3.0, 2.0, 4.0])) # 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) # Broadcasting. x = cp.Variable((3, 3)) c = np.arange(1, 4)[:, None] expr = x / c self.assertEqual((3, 3), expr.shape) x.value = np.ones((3, 3)) A = np.ones((3, 3)) / c self.assertItemsAlmostEqual(A, expr.value) with self.assertRaises(Exception) as cm: (x / c[:, 0]) print(cm.exception) self.assertRegex(str(cm.exception), "Incompatible shapes for division.*") # Test the NegExpression class. def test_neg_expression(self) -> None: # 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) -> None: # 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) -> None: # 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) -> None: """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) -> None: """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) -> None: """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(solver=cp.SCS) 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) -> None: """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) -> None: """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) -> None: 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) -> None: """Test cvxpy sum function. """ self.a.value = 1 expr = cp.sum(self.a) self.assertEqual(expr.value, 1) self.x.value = [1, 2] expr = cp.sum(self.x) self.assertEqual(expr.value, 3) def test_var_copy(self) -> None: """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) -> None: """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) -> None: """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) -> None: """Test is_pwl() """ A = np.ones((2, 3)) b = np.ones(2) expr = A @ self.y - b self.assertEqual(expr.is_pwl(), True) expr = cp.maximum(1, 3 * self.y) self.assertEqual(expr.is_pwl(), True) expr = cp.abs(self.y) self.assertEqual(expr.is_pwl(), True) expr = cp.pnorm(3 * self.y, 1) self.assertEqual(expr.is_pwl(), True) expr = cp.pnorm(3 * self.y**2, 1) self.assertEqual(expr.is_pwl(), False) def test_broadcast_mul(self) -> None: """Test multiply broadcasting. """ y = Parameter((3, 1)) z = Variable((1, 3)) y.value = np.arange(3)[:, None] z.value = (np.arange(3) - 1)[None, :] expr = cp.multiply(y, z) self.assertItemsAlmostEqual(expr.value, y.value * z.value) prob = cp.Problem(cp.Minimize(cp.sum(expr)), [z == z.value]) prob.solve(solver=cp.SCS) self.assertItemsAlmostEqual(expr.value, y.value * z.value) np.random.seed(0) m, n = 3, 4 A = np.random.rand(m, n) col_scale = Variable(n) with self.assertRaises(ValueError) as cm: cp.multiply(A, col_scale) self.assertEqual(str(cm.exception), "Cannot broadcast dimensions (3, 4) (4,)") col_scale = Variable([1, n]) C = cp.multiply(A, col_scale) self.assertEqual(C.shape, (m, n)) row_scale = Variable([m, 1]) R = cp.multiply(A, row_scale) self.assertEqual(R.shape, (m, n)) def test_broadcast_add(self) -> None: """Test addition broadcasting. """ y = Parameter((3, 1)) z = Variable((1, 3)) y.value = np.arange(3)[:, None] z.value = (np.arange(3) - 1)[None, :] expr = y + z self.assertItemsAlmostEqual(expr.value, y.value + z.value) prob = cp.Problem(cp.Minimize(cp.sum(expr)), [z == z.value]) prob.solve(solver=cp.SCS) self.assertItemsAlmostEqual(expr.value, y.value + z.value) np.random.seed(0) m, n = 3, 4 A = np.random.rand(m, n) col_scale = Variable(n) with self.assertRaises(ValueError) as cm: A + col_scale self.assertEqual(str(cm.exception), "Cannot broadcast dimensions (3, 4) (4,)") col_scale = Variable([1, n]) C = A + col_scale self.assertEqual(C.shape, (m, n)) row_scale = Variable([m, 1]) R = A + row_scale self.assertEqual(R.shape, (m, n)) def test_log_log_curvature(self) -> None: """Test that the curvature string is populated for log-log expressions. """ x = Variable(pos=True) monomial = x * x * x assert monomial.curvature == s.LOG_LOG_AFFINE posynomial = x * x * x + x assert posynomial.curvature == s.LOG_LOG_CONVEX llcv = 1 / (x * x * x + x) assert llcv.curvature == s.LOG_LOG_CONCAVE