예제 #1
0
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.size, (2, 1))
        self.assertEqual(y.size, (1, 1))
        self.assertEqual(x.curvature, s.AFFINE)
        self.assertEqual(x.canonical_form[0].size, (2, 1))
        self.assertEqual(x.canonical_form[1], [])

        self.assertEqual(repr(self.x), "Variable(2, 1)")
        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)
        # mat = coeffs[self.A.id][1]
        # self.assertEqual(mat.shape, (2,4))
        # self.assertEqual(mat[0,2], 1)

    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, 1) 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 = NonNegative()
        with self.assertRaises(Exception) as cm:
            x.value = -2
        self.assertEqual(str(cm.exception), "Invalid sign for NonNegative value.")

        # Small negative values are rounded to 0.
        x.value = -1e-8
        self.assertEqual(x.value, 0)

    # Test tranposing variables.
    def test_transpose_variable(self):
        var = self.a.T
        self.assertEqual(var.name(), "a")
        self.assertEqual(var.size, (1, 1))

        self.a.save_value(2)
        self.assertEqual(var.value, 2)

        var = self.x.T
        self.assertEqual(var.name(), "x.T")
        self.assertEqual(var.size, (1, 2))

        self.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.size, (2, 3))

        # coeffs = var.canonical_form[0].coefficients()
        # mat = coeffs.values()[0][0]
        # self.assertEqual(mat.size, (2,6))
        # self.assertEqual(mat[1,3], 1)

        index = var[1, 0]
        self.assertEqual(index.name(), "C.T[1, 0]")
        self.assertEqual(index.size, (1, 1))

        var = self.x.T.T
        self.assertEqual(var.name(), "x.T.T")
        self.assertEqual(var.size, (2, 1))

    # Test the Constant class.
    def test_constants(self):
        c = Constant(2)
        self.assertEqual(c.name(), str(2))

        c = Constant(2)
        self.assertEqual(c.value, 2)
        self.assertEqual(c.size, (1, 1))
        self.assertEqual(c.curvature, s.CONSTANT)
        self.assertEqual(c.sign, s.POSITIVE)
        self.assertEqual(Constant(-2).sign, s.NEGATIVE)
        self.assertEqual(Constant(0).sign, s.ZERO)
        self.assertEqual(c.canonical_form[0].size, (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.size, (1, 2))
        self.assertEqual(c.sign, s.POSITIVE)
        self.assertEqual((-c).sign, s.NEGATIVE)
        self.assertEqual((0*c).sign, s.ZERO)
        c = Constant([[2], [-2]])
        self.assertEqual(c.sign, s.UNKNOWN)

        # Test sign of a complex expression.
        c = Constant([1, 2])
        A = Constant([[1, 1], [1, 1]])
        exp = c.T*A*c
        self.assertEqual(exp.sign, s.POSITIVE)
        self.assertEqual((c.T*c).sign, s.POSITIVE)
        exp = c.T.T
        self.assertEqual(exp.sign, s.POSITIVE)
        exp = c.T*self.A
        self.assertEqual(exp.sign, s.UNKNOWN)

        # Test repr.
        self.assertEqual(repr(c), "Constant(CONSTANT, POSITIVE, (2, 1))")

    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).size, (1, 1))

    # Test the Parameter class.
    def test_parameters(self):
        p = Parameter(name='p')
        self.assertEqual(p.name(), "p")
        self.assertEqual(p.size, (1, 1))

        p = Parameter(4, 3, sign="positive")
        with self.assertRaises(Exception) as cm:
            p.value = 1
        self.assertEqual(str(cm.exception), "Invalid dimensions (1, 1) for Parameter value.")

        val = -np.ones((4, 3))
        val[0, 0] = 2

        p = Parameter(4, 3, sign="positive")
        with self.assertRaises(Exception) as cm:
            p.value = val
        self.assertEqual(str(cm.exception), "Invalid sign for Parameter value.")

        p = Parameter(4, 3, sign="negative")
        with self.assertRaises(Exception) as cm:
            p.value = val
        self.assertEqual(str(cm.exception), "Invalid sign for Parameter value.")

        # No error for unknown sign.
        p = Parameter(4, 3)
        p.value = val

        # Initialize a parameter with a value.
        p = Parameter(value=10)
        self.assertEqual(p.value, 10)

        # Test assigning None.
        p.value = 10
        p.value = None
        assert p.value is None

        with self.assertRaises(Exception) as cm:
            p = Parameter(2, 1, sign="negative", value=[2, 1])
        self.assertEqual(str(cm.exception), "Invalid sign for Parameter value.")

        with self.assertRaises(Exception) as cm:
            p = Parameter(4, 3, sign="positive", value=[1, 2])
        self.assertEqual(str(cm.exception), "Invalid dimensions (2, 1) for Parameter value.")

        # Test repr.
        p = Parameter(4, 3, sign="negative")
        self.assertEqual(repr(p), 'Parameter(4, 3, sign="NEGATIVE")')

    # 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].size, (2, 1))
        self.assertEqual(exp.canonical_form[1], [])
        # self.assertEqual(exp.name(), self.x.name() + " + " + c.name())
        self.assertEqual(exp.size, (2, 1))

        z = Variable(2, name='z')
        exp = exp + z + self.x

        with self.assertRaises(Exception) as cm:
            (self.x + self.y)
        self.assertEqual(str(cm.exception), "Incompatible dimensions (2, 1) (3, 1)")

        # Matrices
        exp = self.A + self.B
        self.assertEqual(exp.curvature, s.AFFINE)
        self.assertEqual(exp.size, (2, 2))

        with self.assertRaises(Exception) as cm:
            (self.A + self.C)
        self.assertEqual(str(cm.exception), "Incompatible dimensions (2, 2) (3, 2)")

        with self.assertRaises(Exception) as cm:
            AddExpression([self.A, self.C])
        self.assertEqual(str(cm.exception), "Incompatible dimensions (2, 2) (3, 2)")

        # 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, 1))")

    # 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].size, (2, 1))
        self.assertEqual(exp.canonical_form[1], [])
        # self.assertEqual(exp.name(), self.x.name() + " - " + Constant([2,2]).name())
        self.assertEqual(exp.size, (2, 1))

        z = Variable(2, name='z')
        exp = exp - z - self.x

        with self.assertRaises(Exception) as cm:
            (self.x - self.y)
        self.assertEqual(str(cm.exception), "Incompatible dimensions (2, 1) (3, 1)")

        # Matrices
        exp = self.A - self.B
        self.assertEqual(exp.curvature, s.AFFINE)
        self.assertEqual(exp.size, (2, 2))

        with self.assertRaises(Exception) as cm:
            (self.A - self.C)
        self.assertEqual(str(cm.exception), "Incompatible dimensions (2, 2) (3, 2)")

        # Test repr.
        self.assertEqual(repr(self.x - c), "Expression(AFFINE, UNKNOWN, (2, 1))")

    # 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].size, (1, 1))
        self.assertEqual(exp.canonical_form[1], [])
        # self.assertEqual(exp.name(), c.name() + " * " + self.x.name())
        self.assertEqual(exp.size, (1, 1))

        with self.assertRaises(Exception) as cm:
            ([2, 2, 3]*self.x)
        self.assertEqual(str(cm.exception), "Incompatible dimensions (3, 1) (2, 1)")

        # Matrices
        with self.assertRaises(Exception) as cm:
            Constant([[2, 1], [2, 2]]) * self.C
        self.assertEqual(str(cm.exception), "Incompatible dimensions (2, 2) (3, 2)")

        # Affine times affine is okay
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            q = self.A * 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 * self.B) * 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) * self.B
        self.assertEqual(exp.curvature, s.AFFINE)
        self.assertEqual(exp.size, (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)

        # Scalar constants on the right should be moved left.
        expr = self.C*2
        self.assertEqual(expr.args[0].value, 2)

        # Scalar variables on the left should be moved right.
        expr = self.a*[2, 1]
        self.assertItemsAlmostEqual(expr.args[0].value, [2, 1])

    def test_matmul_expression(self):
        """Test matmul function, corresponding to .__matmul__( operator.
        """
        if PY35:
            # 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.canonical_form[0].size, (1, 1))
            self.assertEqual(exp.canonical_form[1], [])
            # self.assertEqual(exp.name(), c.name() + " .__matmul__( " + self.x.name())
            self.assertEqual(exp.size, (1, 1))

            with self.assertRaises(Exception) as cm:
                self.x.__matmul__(2)
            self.assertEqual(str(cm.exception),
                             "Scalar operands are not allowed, use '*' instead")
            with self.assertRaises(Exception) as cm:
                (self.x.__matmul__(np.array([2, 2, 3])))
            self.assertEqual(str(cm.exception), "Incompatible dimensions (2, 1) (3, 1)")

            # Matrices
            with self.assertRaises(Exception) as cm:
                Constant([[2, 1], [2, 2]]) .__matmul__(self.C)
            self.assertEqual(str(cm.exception), "Incompatible dimensions (2, 2) (3, 2)")

            # 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.size, (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)
        else:
            pass

    # 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].size, (2, 1))
        self.assertEqual(exp.canonical_form[1], [])
        # self.assertEqual(exp.name(), c.name() + " * " + self.x.name())
        self.assertEqual(exp.size, (2, 1))

        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.size, (1, 1))
        self.assertEqual(exp.sign, s.NEGATIVE)

        # Parameters.
        p = Parameter(sign="positive")
        exp = 2/p
        p.value = 2
        self.assertEqual(exp.value, 1)

        rho = Parameter(sign="positive")
        rho.value = 1

        self.assertEqual(rho.sign, s.POSITIVE)
        self.assertEqual(Constant(2).sign, s.POSITIVE)
        self.assertEqual((Constant(2)/Constant(2)).sign, s.POSITIVE)
        self.assertEqual((Constant(2)*rho).sign, s.POSITIVE)
        self.assertEqual((rho/2).sign, s.POSITIVE)

    # Test the NegExpression class.
    def test_neg_expression(self):
        # Vectors
        exp = -self.x
        self.assertEqual(exp.curvature, s.AFFINE)
        assert exp.is_affine()
        self.assertEqual(exp.sign, s.UNKNOWN)
        assert not exp.is_positive()
        self.assertEqual(exp.canonical_form[0].size, (2, 1))
        self.assertEqual(exp.canonical_form[1], [])
        # self.assertEqual(exp.name(), "-%s" % self.x.name())
        self.assertEqual(exp.size, self.x.size)

        # Matrices
        exp = -self.C
        self.assertEqual(exp.curvature, s.AFFINE)
        self.assertEqual(exp.size, (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_negative()
        self.assertEqual(exp.canonical_form[0].size, (2, 1))
        self.assertEqual(exp.canonical_form[1], [])
        # self.assertEqual(exp.name(), self.x.name() + " + " + Constant(2).name())
        self.assertEqual(exp.size, (2, 1))

        self.assertEqual((4 - self.x).size, (2, 1))
        self.assertEqual((4 * self.x).size, (2, 1))
        self.assertEqual((4 <= self.x).size, (2, 1))
        self.assertEqual((4 == self.x).size, (2, 1))
        self.assertEqual((self.x >= 4).size, (2, 1))

        # Matrices
        exp = (self.A + 2) + 4
        self.assertEqual(exp.curvature, s.AFFINE)
        self.assertEqual((3 * self.A).size, (2, 2))

        self.assertEqual(exp.size, (2, 2))

    # Test indexing expression.
    def test_index_expression(self):
        # Tuple of integers as key.
        exp = self.x[1, 0]
        # self.assertEqual(exp.name(), "x[1,0]")
        self.assertEqual(exp.curvature, s.AFFINE)
        assert exp.is_affine()
        self.assertEqual(exp.size, (1, 1))
        # coeff = exp.canonical_form[0].coefficients()[self.x][0]
        # self.assertEqual(coeff[0,1], 1)
        self.assertEqual(exp.value, None)

        exp = self.x[1, 0].T
        # self.assertEqual(exp.name(), "x[1,0]")
        self.assertEqual(exp.curvature, s.AFFINE)
        self.assertEqual(exp.size, (1, 1))

        with self.assertRaises(Exception) as cm:
            (self.x[2, 0])
        self.assertEqual(str(cm.exception), "Index/slice out of bounds.")

        # Slicing
        exp = self.C[0:2, 1]
        # self.assertEqual(exp.name(), "C[0:2,1]")
        self.assertEqual(exp.size, (2, 1))
        exp = self.C[0:, 0:2]
        # self.assertEqual(exp.name(), "C[0:,0:2]")
        self.assertEqual(exp.size, (3, 2))
        exp = self.C[0::2, 0::2]
        # self.assertEqual(exp.name(), "C[0::2,0::2]")
        self.assertEqual(exp.size, (2, 1))
        exp = self.C[:3, :1:2]
        # self.assertEqual(exp.name(), "C[0:3,0]")
        self.assertEqual(exp.size, (3, 1))
        exp = self.C[0:, 0]
        # self.assertEqual(exp.name(), "C[0:,0]")
        self.assertEqual(exp.size, (3, 1))

        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.size, (1, 1))
        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.size, (3, 2))
        self.assertEqual(exp[0, 1].value, 7)

        # Slice of transpose
        exp = self.C.T[0:2, 1]
        self.assertEqual(exp.size, (2, 1))

        # Arithmetic expression indexing
        exp = (self.x + self.z)[1, 0]
        # 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.size, (1, 1))

        exp = (self.x + self.a)[1, 0]
        # self.assertEqual(exp.name(), "x[1,0] + a")
        self.assertEqual(exp.curvature, s.AFFINE)
        self.assertEqual(exp.size, (1, 1))

        exp = (self.x - self.z)[1, 0]
        # self.assertEqual(exp.name(), "x[1,0] - z[1,0]")
        self.assertEqual(exp.curvature, s.AFFINE)
        self.assertEqual(exp.size, (1, 1))

        exp = (self.x - self.a)[1, 0]
        # self.assertEqual(exp.name(), "x[1,0] - a")
        self.assertEqual(exp.curvature, s.AFFINE)
        self.assertEqual(exp.size, (1, 1))

        exp = (-self.x)[1, 0]
        # self.assertEqual(exp.name(), "-x[1,0]")
        self.assertEqual(exp.curvature, s.AFFINE)
        self.assertEqual(exp.size, (1, 1))

        c = Constant([[1, 2], [3, 4]])
        exp = (c*self.x)[1, 0]
        # self.assertEqual(exp.name(), "[[2], [4]] * x[0:,0]")
        self.assertEqual(exp.curvature, s.AFFINE)
        self.assertEqual(exp.size, (1, 1))

        c = Constant([[1, 2], [3, 4]])
        exp = (c*self.a)[1, 0]
        # self.assertEqual(exp.name(), "2 * a")
        self.assertEqual(exp.curvature, s.AFFINE)
        self.assertEqual(exp.size, (1, 1))

    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.size, (1, 1))
        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.size, (2, 1))
        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.size, (4, 1))
        self.assertEqual(exp.curvature, s.CONSTANT)

        x = Variable(4)
        Problem(Minimize(0), [x[::-1] == c]).solve()
        self.assertItemsAlmostEqual(x.value, [4, 3, 2, 1])
        self.assertEqual(x[::-1].size, (4, 1))

        x = Variable(2)
        self.assertEqual(x[::-1].size, (2, 1))

        x = Variable(100, name="x")
        self.assertEqual("x[:-1, 0]", str(x[:-1]))

        c = Constant([[1, 2], [3, 4]])
        expr = c[0, 2:0:-1]
        self.assertEqual(expr.size, (1, 1))
        self.assertAlmostEqual(expr.value, 3)

        expr = c[0, 2::-1]
        self.assertEqual(expr.size, (1, 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.size, (2, 1))
        self.assertEqual(expr.sign, s.POSITIVE)
        self.assertItemsAlmostEqual(A[A <= 2], expr.value)

        expr = C[A % 2 == 0]
        self.assertEqual(expr.size, (6, 1))
        self.assertEqual(expr.sign, s.POSITIVE)
        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.size, (2, 1))
        self.assertEqual(expr.sign, s.POSITIVE)
        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.size, (2, 1))
        self.assertEqual(expr.sign, s.POSITIVE)
        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.size, (3, 2))
        self.assertEqual(expr.sign, s.POSITIVE)
        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.size, (1, 3))
        self.assertEqual(expr.sign, s.POSITIVE)
        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.size, (3, 1))
        self.assertEqual(expr.sign, s.POSITIVE)
        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.size, (2, 4))
        self.assertEqual(expr.sign, s.POSITIVE)
        self.assertItemsAlmostEqual(A[[1, 2]], expr.value)

        # List for rows, index for columns.
        expr = C[[0, 2], 3]
        self.assertEqual(expr.size, (2, 1))
        self.assertEqual(expr.sign, s.POSITIVE)
        self.assertItemsAlmostEqual(A[[0, 2], 3], expr.value)

        # Index for row, list for columns.
        expr = C[1, [0, 2]]
        self.assertEqual(expr.size, (2, 1))
        self.assertEqual(expr.sign, s.POSITIVE)
        self.assertItemsAlmostEqual(A[1, [0, 2]], expr.value)

        # List for rows, slice for columns.
        expr = C[[0, 2], 1:3]
        self.assertEqual(expr.size, (2, 2))
        self.assertEqual(expr.sign, s.POSITIVE)
        self.assertItemsAlmostEqual(A[[0, 2], 1:3], expr.value)

        # Slice for row, list for columns.
        expr = C[1:-1, [0, 2]]
        self.assertEqual(expr.size, (1, 2))
        self.assertEqual(expr.sign, s.POSITIVE)
        self.assertItemsAlmostEqual(A[1:-1, [0, 2]], expr.value)

        # Lists for rows and columns.
        expr = C[[0, 1], [1, 3]]
        self.assertEqual(expr.size, (2, 1))
        self.assertEqual(expr.sign, s.POSITIVE)
        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.size, (2, 1))
        self.assertEqual(expr.sign, s.POSITIVE)
        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.size, (2, 1))
        self.assertEqual(expr.sign, s.POSITIVE)
        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.size, (3, 4))
        self.assertEqual(y.name(), "x")

        x = Semidef(5, name="x")
        y = x.copy()
        self.assertEqual(y.size, (5, 5))

    def test_param_copy(self):
        """Test the copy function for Parameters.
        """
        x = Parameter(3, 4, name="x", sign="positive")
        y = x.copy()
        self.assertEqual(y.size, (3, 4))
        self.assertEqual(y.name(), "x")
        self.assertEqual(y.sign, "POSITIVE")

    def test_constant_copy(self):
        """Test the copy function for Constants.
        """
        x = Constant(2)
        y = x.copy()
        self.assertEqual(y.size, (1, 1))
        self.assertEqual(y.value, 2)
예제 #2
0
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.size, (2, 1))
        self.assertEqual(y.size, (1, 1))
        self.assertEqual(x.curvature, s.AFFINE)
        self.assertEqual(x.canonical_form[0].size, (2, 1))
        self.assertEqual(x.canonical_form[1], [])

        self.assertEqual(repr(self.x), "Variable(2, 1)")
        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)
        # mat = coeffs[self.A.id][1]
        # self.assertEqual(mat.shape, (2,4))
        # self.assertEqual(mat[0,2], 1)

    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, 1) 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 = NonNegative()
        with self.assertRaises(Exception) as cm:
            x.value = -2
        self.assertEqual(str(cm.exception),
                         "Invalid sign for NonNegative value.")

    # Test tranposing variables.
    def test_transpose_variable(self):
        var = self.a.T
        self.assertEqual(var.name(), "a")
        self.assertEqual(var.size, (1, 1))

        self.a.save_value(2)
        self.assertEqual(var.value, 2)

        var = self.x.T
        self.assertEqual(var.name(), "x.T")
        self.assertEqual(var.size, (1, 2))

        self.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.size, (2, 3))

        # coeffs = var.canonical_form[0].coefficients()
        # mat = coeffs.values()[0][0]
        # self.assertEqual(mat.size, (2,6))
        # self.assertEqual(mat[1,3], 1)

        index = var[1, 0]
        self.assertEqual(index.name(), "C.T[1, 0]")
        self.assertEqual(index.size, (1, 1))

        var = self.x.T.T
        self.assertEqual(var.name(), "x.T.T")
        self.assertEqual(var.size, (2, 1))

    # Test the Constant class.
    def test_constants(self):
        c = Constant(2)
        self.assertEqual(c.name(), str(2))

        c = Constant(2)
        self.assertEqual(c.value, 2)
        self.assertEqual(c.size, (1, 1))
        self.assertEqual(c.curvature, s.CONSTANT)
        self.assertEqual(c.sign, s.POSITIVE)
        self.assertEqual(Constant(-2).sign, s.NEGATIVE)
        self.assertEqual(Constant(0).sign, s.ZERO)
        self.assertEqual(c.canonical_form[0].size, (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.size, (1, 2))
        self.assertEqual(c.sign, s.POSITIVE)
        self.assertEqual((-c).sign, s.NEGATIVE)
        self.assertEqual((0 * c).sign, s.ZERO)
        c = Constant([[2], [-2]])
        self.assertEqual(c.sign, s.UNKNOWN)

        # Test sign of a complex expression.
        c = Constant([1, 2])
        A = Constant([[1, 1], [1, 1]])
        exp = c.T * A * c
        self.assertEqual(exp.sign, s.POSITIVE)
        self.assertEqual((c.T * c).sign, s.POSITIVE)
        exp = c.T.T
        self.assertEqual(exp.sign, s.POSITIVE)
        exp = c.T * self.A
        self.assertEqual(exp.sign, s.UNKNOWN)

        # Test repr.
        self.assertEqual(repr(c), "Constant(CONSTANT, POSITIVE, (2, 1))")

    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).size, (1, 1))
        self.x.save_value(np.array([1, 4]))
        self.assertEqual((c.T * self.x).value, 9)

    # Test the Parameter class.
    def test_parameters(self):
        p = Parameter(name='p')
        self.assertEqual(p.name(), "p")
        self.assertEqual(p.size, (1, 1))

        p = Parameter(4, 3, sign="positive")
        with self.assertRaises(Exception) as cm:
            p.value = 1
        self.assertEqual(str(cm.exception),
                         "Invalid dimensions (1, 1) for Parameter value.")

        val = -np.ones((4, 3))
        val[0, 0] = 2

        p = Parameter(4, 3, sign="positive")
        with self.assertRaises(Exception) as cm:
            p.value = val
        self.assertEqual(str(cm.exception),
                         "Invalid sign for Parameter value.")

        p = Parameter(4, 3, sign="negative")
        with self.assertRaises(Exception) as cm:
            p.value = val
        self.assertEqual(str(cm.exception),
                         "Invalid sign for Parameter value.")

        # No error for unknown sign.
        p = Parameter(4, 3)
        p.value = val

        # Initialize a parameter with a value.
        p = Parameter(value=10)
        self.assertEqual(p.value, 10)

        # Test assigning None.
        p.value = 10
        p.value = None
        assert p.value is None

        with self.assertRaises(Exception) as cm:
            p = Parameter(2, 1, sign="negative", value=[2, 1])
        self.assertEqual(str(cm.exception),
                         "Invalid sign for Parameter value.")

        with self.assertRaises(Exception) as cm:
            p = Parameter(4, 3, sign="positive", value=[1, 2])
        self.assertEqual(str(cm.exception),
                         "Invalid dimensions (2, 1) for Parameter value.")

        # Test repr.
        p = Parameter(4, 3, sign="negative")
        self.assertEqual(repr(p), 'Parameter(4, 3, sign="NEGATIVE")')

    # 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].size, (2, 1))
        self.assertEqual(exp.canonical_form[1], [])
        # self.assertEqual(exp.name(), self.x.name() + " + " + c.name())
        self.assertEqual(exp.size, (2, 1))

        z = Variable(2, name='z')
        exp = exp + z + self.x

        with self.assertRaises(Exception) as cm:
            (self.x + self.y)
        self.assertEqual(str(cm.exception),
                         "Incompatible dimensions (2, 1) (3, 1)")

        # Matrices
        exp = self.A + self.B
        self.assertEqual(exp.curvature, s.AFFINE)
        self.assertEqual(exp.size, (2, 2))

        with self.assertRaises(Exception) as cm:
            (self.A + self.C)
        self.assertEqual(str(cm.exception),
                         "Incompatible dimensions (2, 2) (3, 2)")

        with self.assertRaises(Exception) as cm:
            AddExpression([self.A, self.C])
        self.assertEqual(str(cm.exception),
                         "Incompatible dimensions (2, 2) (3, 2)")

        # 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, 1))")

    # 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].size, (2, 1))
        self.assertEqual(exp.canonical_form[1], [])
        # self.assertEqual(exp.name(), self.x.name() + " - " + Constant([2,2]).name())
        self.assertEqual(exp.size, (2, 1))

        z = Variable(2, name='z')
        exp = exp - z - self.x

        with self.assertRaises(Exception) as cm:
            (self.x - self.y)
        self.assertEqual(str(cm.exception),
                         "Incompatible dimensions (2, 1) (3, 1)")

        # Matrices
        exp = self.A - self.B
        self.assertEqual(exp.curvature, s.AFFINE)
        self.assertEqual(exp.size, (2, 2))

        with self.assertRaises(Exception) as cm:
            (self.A - self.C)
        self.assertEqual(str(cm.exception),
                         "Incompatible dimensions (2, 2) (3, 2)")

        # Test repr.
        self.assertEqual(repr(self.x - c),
                         "Expression(AFFINE, UNKNOWN, (2, 1))")

    # 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].size, (1, 1))
        self.assertEqual(exp.canonical_form[1], [])
        # self.assertEqual(exp.name(), c.name() + " * " + self.x.name())
        self.assertEqual(exp.size, (1, 1))

        with self.assertRaises(Exception) as cm:
            ([2, 2, 3] * self.x)
        self.assertEqual(str(cm.exception),
                         "Incompatible dimensions (3, 1) (2, 1)")

        # Matrices
        with self.assertRaises(Exception) as cm:
            Constant([[2, 1], [2, 2]]) * self.C
        self.assertEqual(str(cm.exception),
                         "Incompatible dimensions (2, 2) (3, 2)")

        # Affine times affine is okay
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            q = self.A * 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 * self.B) * 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) * self.B
        self.assertEqual(exp.curvature, s.AFFINE)
        self.assertEqual(exp.size, (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)

        # Scalar constants on the right should be moved left.
        expr = self.C * 2
        self.assertEqual(expr.args[0].value, 2)

        # Scalar variables on the left should be moved right.
        expr = self.a * [2, 1]
        self.assertItemsAlmostEqual(expr.args[0].value, [2, 1])

    def test_matmul_expression(self):
        """Test matmul function, corresponding to .__matmul__( operator.
        """
        if PY35:
            # 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.canonical_form[0].size, (1, 1))
            self.assertEqual(exp.canonical_form[1], [])
            # self.assertEqual(exp.name(), c.name() + " .__matmul__( " + self.x.name())
            self.assertEqual(exp.size, (1, 1))

            with self.assertRaises(Exception) as cm:
                self.x.__matmul__(2)
            self.assertEqual(
                str(cm.exception),
                "Scalar operands are not allowed, use '*' instead")
            with self.assertRaises(Exception) as cm:
                (self.x.__matmul__(np.array([2, 2, 3])))
            self.assertEqual(str(cm.exception),
                             "Incompatible dimensions (2, 1) (3, 1)")

            # Matrices
            with self.assertRaises(Exception) as cm:
                Constant([[2, 1], [2, 2]]).__matmul__(self.C)
            self.assertEqual(str(cm.exception),
                             "Incompatible dimensions (2, 2) (3, 2)")

            # 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.size, (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)
        else:
            pass

    # 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].size, (2, 1))
        self.assertEqual(exp.canonical_form[1], [])
        # self.assertEqual(exp.name(), c.name() + " * " + self.x.name())
        self.assertEqual(exp.size, (2, 1))

        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.size, (1, 1))
        self.assertEqual(exp.sign, s.NEGATIVE)

        # Parameters.
        p = Parameter(sign="positive")
        exp = 2 / p
        p.value = 2
        self.assertEqual(exp.value, 1)

        rho = Parameter(sign="positive")
        rho.value = 1

        self.assertEqual(rho.sign, s.POSITIVE)
        self.assertEqual(Constant(2).sign, s.POSITIVE)
        self.assertEqual((Constant(2) / Constant(2)).sign, s.POSITIVE)
        self.assertEqual((Constant(2) * rho).sign, s.POSITIVE)
        self.assertEqual((rho / 2).sign, s.POSITIVE)

    # Test the NegExpression class.
    def test_neg_expression(self):
        # Vectors
        exp = -self.x
        self.assertEqual(exp.curvature, s.AFFINE)
        assert exp.is_affine()
        self.assertEqual(exp.sign, s.UNKNOWN)
        assert not exp.is_positive()
        self.assertEqual(exp.canonical_form[0].size, (2, 1))
        self.assertEqual(exp.canonical_form[1], [])
        # self.assertEqual(exp.name(), "-%s" % self.x.name())
        self.assertEqual(exp.size, self.x.size)

        # Matrices
        exp = -self.C
        self.assertEqual(exp.curvature, s.AFFINE)
        self.assertEqual(exp.size, (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_negative()
        self.assertEqual(exp.canonical_form[0].size, (2, 1))
        self.assertEqual(exp.canonical_form[1], [])
        # self.assertEqual(exp.name(), self.x.name() + " + " + Constant(2).name())
        self.assertEqual(exp.size, (2, 1))

        self.assertEqual((4 - self.x).size, (2, 1))
        self.assertEqual((4 * self.x).size, (2, 1))
        self.assertEqual((4 <= self.x).size, (2, 1))
        self.assertEqual((4 == self.x).size, (2, 1))
        self.assertEqual((self.x >= 4).size, (2, 1))

        # Matrices
        exp = (self.A + 2) + 4
        self.assertEqual(exp.curvature, s.AFFINE)
        self.assertEqual((3 * self.A).size, (2, 2))

        self.assertEqual(exp.size, (2, 2))

    # Test indexing expression.
    def test_index_expression(self):
        # Tuple of integers as key.
        exp = self.x[1, 0]
        # self.assertEqual(exp.name(), "x[1,0]")
        self.assertEqual(exp.curvature, s.AFFINE)
        assert exp.is_affine()
        self.assertEqual(exp.size, (1, 1))
        # coeff = exp.canonical_form[0].coefficients()[self.x][0]
        # self.assertEqual(coeff[0,1], 1)
        self.assertEqual(exp.value, None)

        exp = self.x[1, 0].T
        # self.assertEqual(exp.name(), "x[1,0]")
        self.assertEqual(exp.curvature, s.AFFINE)
        self.assertEqual(exp.size, (1, 1))

        with self.assertRaises(Exception) as cm:
            (self.x[2, 0])
        self.assertEqual(str(cm.exception), "Index/slice out of bounds.")

        # Slicing
        exp = self.C[0:2, 1]
        # self.assertEqual(exp.name(), "C[0:2,1]")
        self.assertEqual(exp.size, (2, 1))
        exp = self.C[0:, 0:2]
        # self.assertEqual(exp.name(), "C[0:,0:2]")
        self.assertEqual(exp.size, (3, 2))
        exp = self.C[0::2, 0::2]
        # self.assertEqual(exp.name(), "C[0::2,0::2]")
        self.assertEqual(exp.size, (2, 1))
        exp = self.C[:3, :1:2]
        # self.assertEqual(exp.name(), "C[0:3,0]")
        self.assertEqual(exp.size, (3, 1))
        exp = self.C[0:, 0]
        # self.assertEqual(exp.name(), "C[0:,0]")
        self.assertEqual(exp.size, (3, 1))

        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.size, (1, 1))
        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.size, (3, 2))
        self.assertEqual(exp[0, 1].value, 7)

        # Slice of transpose
        exp = self.C.T[0:2, 1]
        self.assertEqual(exp.size, (2, 1))

        # Arithmetic expression indexing
        exp = (self.x + self.z)[1, 0]
        # 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.size, (1, 1))

        exp = (self.x + self.a)[1, 0]
        # self.assertEqual(exp.name(), "x[1,0] + a")
        self.assertEqual(exp.curvature, s.AFFINE)
        self.assertEqual(exp.size, (1, 1))

        exp = (self.x - self.z)[1, 0]
        # self.assertEqual(exp.name(), "x[1,0] - z[1,0]")
        self.assertEqual(exp.curvature, s.AFFINE)
        self.assertEqual(exp.size, (1, 1))

        exp = (self.x - self.a)[1, 0]
        # self.assertEqual(exp.name(), "x[1,0] - a")
        self.assertEqual(exp.curvature, s.AFFINE)
        self.assertEqual(exp.size, (1, 1))

        exp = (-self.x)[1, 0]
        # self.assertEqual(exp.name(), "-x[1,0]")
        self.assertEqual(exp.curvature, s.AFFINE)
        self.assertEqual(exp.size, (1, 1))

        c = Constant([[1, 2], [3, 4]])
        exp = (c * self.x)[1, 0]
        # self.assertEqual(exp.name(), "[[2], [4]] * x[0:,0]")
        self.assertEqual(exp.curvature, s.AFFINE)
        self.assertEqual(exp.size, (1, 1))

        c = Constant([[1, 2], [3, 4]])
        exp = (c * self.a)[1, 0]
        # self.assertEqual(exp.name(), "2 * a")
        self.assertEqual(exp.curvature, s.AFFINE)
        self.assertEqual(exp.size, (1, 1))

    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.size, (1, 1))
        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.size, (2, 1))
        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.size, (4, 1))
        self.assertEqual(exp.curvature, s.CONSTANT)

        x = Variable(4)
        Problem(Minimize(0), [x[::-1] == c]).solve()
        self.assertItemsAlmostEqual(x.value, [4, 3, 2, 1])
        self.assertEqual(x[::-1].size, (4, 1))

        x = Variable(2)
        self.assertEqual(x[::-1].size, (2, 1))

        x = Variable(100, name="x")
        self.assertEqual("x[:-1, 0]", str(x[:-1]))

        c = Constant([[1, 2], [3, 4]])
        expr = c[0, 2:0:-1]
        self.assertEqual(expr.size, (1, 1))
        self.assertAlmostEqual(expr.value, 3)

        expr = c[0, 2::-1]
        self.assertEqual(expr.size, (1, 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.size, (2, 1))
        self.assertEqual(expr.sign, s.POSITIVE)
        self.assertItemsAlmostEqual(A[A <= 2], expr.value)

        expr = C[A % 2 == 0]
        self.assertEqual(expr.size, (6, 1))
        self.assertEqual(expr.sign, s.POSITIVE)
        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.size, (2, 1))
        self.assertEqual(expr.sign, s.POSITIVE)
        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.size, (2, 1))
        self.assertEqual(expr.sign, s.POSITIVE)
        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.size, (3, 2))
        self.assertEqual(expr.sign, s.POSITIVE)
        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.size, (1, 3))
        self.assertEqual(expr.sign, s.POSITIVE)
        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.size, (3, 1))
        self.assertEqual(expr.sign, s.POSITIVE)
        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.size, (2, 4))
        self.assertEqual(expr.sign, s.POSITIVE)
        self.assertItemsAlmostEqual(A[[1, 2]], expr.value)

        # List for rows, index for columns.
        expr = C[[0, 2], 3]
        self.assertEqual(expr.size, (2, 1))
        self.assertEqual(expr.sign, s.POSITIVE)
        self.assertItemsAlmostEqual(A[[0, 2], 3], expr.value)

        # Index for row, list for columns.
        expr = C[1, [0, 2]]
        self.assertEqual(expr.size, (2, 1))
        self.assertEqual(expr.sign, s.POSITIVE)
        self.assertItemsAlmostEqual(A[1, [0, 2]], expr.value)

        # List for rows, slice for columns.
        expr = C[[0, 2], 1:3]
        self.assertEqual(expr.size, (2, 2))
        self.assertEqual(expr.sign, s.POSITIVE)
        self.assertItemsAlmostEqual(A[[0, 2], 1:3], expr.value)

        # Slice for row, list for columns.
        expr = C[1:-1, [0, 2]]
        self.assertEqual(expr.size, (1, 2))
        self.assertEqual(expr.sign, s.POSITIVE)
        self.assertItemsAlmostEqual(A[1:-1, [0, 2]], expr.value)

        # Lists for rows and columns.
        expr = C[[0, 1], [1, 3]]
        self.assertEqual(expr.size, (2, 1))
        self.assertEqual(expr.sign, s.POSITIVE)
        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.size, (2, 1))
        self.assertEqual(expr.sign, s.POSITIVE)
        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.size, (2, 1))
        self.assertEqual(expr.sign, s.POSITIVE)
        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.size, (3, 4))
        self.assertEqual(y.name(), "x")

        x = Semidef(5, name="x")
        y = x.copy()
        self.assertEqual(y.size, (5, 5))

    def test_param_copy(self):
        """Test the copy function for Parameters.
        """
        x = Parameter(3, 4, name="x", sign="positive")
        y = x.copy()
        self.assertEqual(y.size, (3, 4))
        self.assertEqual(y.name(), "x")
        self.assertEqual(y.sign, "POSITIVE")

    def test_constant_copy(self):
        """Test the copy function for Constants.
        """
        x = Constant(2)
        y = x.copy()
        self.assertEqual(y.size, (1, 1))
        self.assertEqual(y.value, 2)

    def test_is_pwl(self):
        """Test is_pwl()
        """
        A = np.random.randn(2, 3)
        b = np.random.randn(2)

        expr = A * self.y - b
        self.assertEqual(expr.is_pwl(), True)

        expr = max_elemwise(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)