def test_real(self): """Test real. """ A = np.ones((2, 2)) expr = Constant(A) + 1j * Constant(A) expr = cvx.real(expr) assert expr.is_real() assert not expr.is_complex() assert not expr.is_imag() self.assertItemsAlmostEqual(expr.value, A) x = Variable(complex=True) expr = cvx.imag(x) + cvx.real(x) assert expr.is_real()
def test_neg_indices(self): """Test negative indices. """ c = Constant([[1, 2], [3, 4]]) exp = c[-1, -1] self.assertEquals(exp.value, 4) self.assertEquals(exp.size, (1, 1)) self.assertEquals(exp.curvature, u.Curvature.CONSTANT_KEY) c = Constant([1, 2, 3, 4]) exp = c[1:-1] self.assertItemsAlmostEqual(exp.value, [2, 3]) self.assertEquals(exp.size, (2, 1)) self.assertEquals(exp.curvature, u.Curvature.CONSTANT_KEY)
def quad_form_canon(expr, args): # TODO this doesn't work with parameters! scale, M1, M2 = decomp_quad(args[1].value) # Special case where P == 0. if M1.size == M2.size == 0: return Constant(0), [] if M1.size > 0: expr = sum_squares(Constant(M1.T) @ args[0]) if M2.size > 0: scale = -scale expr = sum_squares(Constant(M2.T) @ args[0]) obj, constr = quad_over_lin_canon(expr, expr.args) return scale * obj, constr
def test_add_expression(self): # Vectors c = Constant([2,2]) exp = self.x + c self.assertEqual(exp.curvature, u.Curvature.AFFINE_KEY) self.assertEqual(exp.sign, u.Sign.UNKNOWN_KEY) 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, u.Curvature.AFFINE_KEY) 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)
def norm_inf_canon(expr, args): x = args[0] axis = expr.axis shape = expr.shape t = Variable(shape) if axis is None: # shape = (1, 1) promoted_t = promote(t, x.shape) elif axis == 0: # shape = (1, n) promoted_t = Constant(np.ones( (x.shape[0], 1))) * reshape(t, (1, x.shape[1])) else: # shape = (m, 1) promoted_t = reshape(t, (x.shape[0], 1)) * Constant( np.ones((1, x.shape[1]))) return t, [x <= promoted_t, x + promoted_t >= 0]
def test_sub_expression(self): # Vectors c = Constant([2, 2]) exp = self.x - c self.assertEqual(exp.curvature, u.Curvature.AFFINE_KEY) self.assertEqual(exp.sign, u.Sign.UNKNOWN_KEY) 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, u.Curvature.AFFINE_KEY) 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)")
def test_constant_atoms(atom_info, objective_type) -> None: atom, size, args, obj_val = atom_info for indexer in get_indices(size): for solver in SOLVERS_TO_TRY: # Atoms with Constant arguments. prob_val = obj_val[indexer].value const_args = [Constant(arg) for arg in args] problem = Problem(objective_type(atom(*const_args)[indexer])) run_atom(atom, problem, prob_val, solver) # Atoms with Variable arguments. variables = [] constraints = [] for idx, expr in enumerate(args): variables.append(Variable(intf.shape(expr))) constraints.append(variables[-1] == expr) objective = objective_type(atom(*variables)[indexer]) new_obj_val = prob_val if objective_type == cp.Maximize: objective = -objective new_obj_val = -new_obj_val problem = Problem(objective, constraints) run_atom(atom, problem, new_obj_val, solver) # Atoms with Parameter arguments. parameters = [] for expr in args: parameters.append(Parameter(intf.shape(expr))) parameters[-1].value = intf.DEFAULT_INTF.const_to_matrix(expr) objective = objective_type(atom(*parameters)[indexer]) run_atom(atom, Problem(objective), prob_val, solver)
def test_constant_copy(self): """Test the copy function for Constants. """ x = Constant(2) y = x.copy() self.assertEquals(y.size, (1, 1)) self.assertEquals(y.value, 2)
def canonicalize(self): """Represent the atom as an affine objective and conic constraints. """ # Constant atoms are treated as a leaf. if self.is_constant(): # Parameterized expressions are evaluated later. if self.parameters(): param = CallbackParam(lambda: self.value, self.shape) return param.canonical_form # Non-parameterized expressions are evaluated immediately. else: return Constant(self.value).canonical_form else: arg_objs = [] constraints = [] for arg in self.args: obj, constr = arg.canonical_form arg_objs.append(obj) constraints += constr # Special info required by the graph implementation. data = self.get_data() graph_obj, graph_constr = self.graph_implementation(arg_objs, self.shape, data) return graph_obj, constraints + graph_constr
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_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,))")
def test_div_expression(self): # Vectors exp = self.x / 2 self.assertEqual(exp.curvature, u.Curvature.AFFINE_KEY) self.assertEqual(exp.sign, u.Sign.UNKNOWN_KEY) 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, u.Curvature.CONSTANT_KEY) self.assertEqual(exp.size, (1, 1)) self.assertEqual(exp.sign, u.Sign.NEGATIVE_KEY) # Parameters. p = Parameter(sign="positive") exp = 2 / p p.value = 2 self.assertEquals(exp.value, 1)
def separable_canon(expr, real_args, imag_args, real2imag): """Canonicalize linear functions that are seprable in real and imaginary parts. """ if all(val is None for val in imag_args): outputs = (expr.copy(real_args), None) elif all(val is None for val in real_args): outputs = (None, expr.copy(imag_args)) else: # Mixed real_args and imaginaries. for idx, real_val in enumerate(real_args): if real_val is None: real_args[idx] = Constant(np.zeros(imag_args[idx].shape)) elif imag_args[idx] is None: imag_args[idx] = Constant(np.zeros(real_args[idx].shape)) outputs = (expr.copy(real_args), expr.copy(imag_args)) return outputs
def test_atom(): for atom_list, objective_type in atoms: for atom, size, args, obj_val in atom_list: for row in range(size[0]): for col in range(size[1]): for solver in SOLVERS_TO_TRY: # Atoms with Constant arguments. const_args = [Constant(arg) for arg in args] yield (run_atom, atom, Problem( objective_type(atom(*const_args)[row, col])), obj_val[row, col].value, solver) # Atoms with Variable arguments. variables = [] constraints = [] for idx, expr in enumerate(args): variables.append(Variable(*intf.size(expr))) constraints.append(variables[-1] == expr) objective = objective_type(atom(*variables)[row, col]) yield (run_atom, atom, Problem(objective, constraints), obj_val[row, col].value, solver) # Atoms with Parameter arguments. parameters = [] for expr in args: parameters.append(Parameter(*intf.size(expr))) parameters[ -1].value = intf.DEFAULT_INTF.const_to_matrix( expr) objective = objective_type(atom(*parameters)[row, col]) yield (run_atom, atom, Problem(objective), obj_val[row, col].value, solver)
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)
def apply(self, problem): if not attributes_present(problem.variables(), CONVEX_ATTRIBUTES): return problem, () # For each unique variable, add constraints. id2new_var = {} id2new_obj = {} id2old_var = {} constr = [] for var in problem.variables(): if var.id not in id2new_var: id2old_var[var.id] = var new_var = False new_attr = var.attributes.copy() for key in CONVEX_ATTRIBUTES: if new_attr[key]: new_var = True new_attr[key] = False if attributes_present([var], SYMMETRIC_ATTRIBUTES): n = var.shape[0] shape = (n*(n+1)//2, 1) upper_tri = Variable(shape, var_id=var.id, **new_attr) upper_tri.set_variable_of_provenance(var) id2new_var[var.id] = upper_tri fill_coeff = Constant(upper_tri_to_full(n)) full_mat = fill_coeff @ upper_tri obj = reshape(full_mat, (n, n)) elif var.attributes['diag']: diag_var = Variable(var.shape[0], var_id=var.id, **new_attr) diag_var.set_variable_of_provenance(var) id2new_var[var.id] = diag_var obj = diag(diag_var) elif new_var: obj = Variable(var.shape, var_id=var.id, **new_attr) obj.set_variable_of_provenance(var) id2new_var[var.id] = obj else: obj = var id2new_var[var.id] = obj id2new_obj[id(var)] = obj if var.is_pos() or var.is_nonneg(): constr.append(obj >= 0) elif var.is_neg() or var.is_nonpos(): constr.append(obj <= 0) elif var.is_psd(): constr.append(obj >> 0) elif var.attributes['NSD']: constr.append(obj << 0) # Create new problem. obj = problem.objective.tree_copy(id_objects=id2new_obj) cons_id_map = {} for cons in problem.constraints: constr.append(cons.tree_copy(id_objects=id2new_obj)) cons_id_map[cons.id] = constr[-1].id inverse_data = (id2new_var, id2old_var, cons_id_map) return cvxtypes.problem()(obj, constr), inverse_data
def max_canon(expr, args): x = args[0] shape = expr.shape axis = expr.axis t = Variable(shape) if axis is None: # shape = (1, 1) promoted_t = promote(t, x.shape) elif axis == 0: # shape = (1, n) promoted_t = Constant(np.ones((x.shape[0], 1))) * reshape( t, (1, x.shape[1])) else: # shape = (m, 1) promoted_t = reshape(t, (x.shape[0], 1)) * Constant( np.ones((1, x.shape[1]))) constraints = [x <= promoted_t] return t, constraints
def exp_cone(self): """Test exponential cone problems. """ for solver in self.solvers: # Basic. p = Problem(Minimize(self.b), [exp(self.a) <= self.b, self.a >= 1]) pmod = Problem(Minimize(self.b), [ExpCone(self.a, Constant(1), self.b), self.a >= 1]) self.assertTrue(ConeMatrixStuffing().accepts(pmod)) p_new = ConeMatrixStuffing().apply(pmod) if not solver.accepts(p_new[0]): return result = p.solve(solver.name()) sltn = solver.solve(p_new[0], False, False, {}) self.assertAlmostEqual(sltn.opt_val, result, places=1) inv_sltn = ConeMatrixStuffing().invert(sltn, p_new[1]) self.assertAlmostEqual(inv_sltn.opt_val, result, places=1) for var in pmod.variables(): self.assertItemsAlmostEqual(inv_sltn.primal_vars[var.id], var.value, places=1) # More complex. # TODO CVXOPT fails here. if solver.name() == 'CVXOPT': return p = Problem(Minimize(self.b), [ exp(self.a / 2 + self.c) <= self.b + 5, self.a >= 1, self.c >= 5 ]) pmod = Problem(Minimize(self.b), [ ExpCone(self.a / 2 + self.c, Constant(1), self.b + 5), self.a >= 1, self.c >= 5 ]) self.assertTrue(ConeMatrixStuffing().accepts(pmod)) result = p.solve(solver.name()) p_new = ConeMatrixStuffing().apply(pmod) sltn = solver.solve(p_new[0], False, False, {}) self.assertAlmostEqual(sltn.opt_val, result, places=0) inv_sltn = ConeMatrixStuffing().invert(sltn, p_new[1]) self.assertAlmostEqual(inv_sltn.opt_val, result, places=0) for var in pmod.variables(): self.assertItemsAlmostEqual(inv_sltn.primal_vars[var.id], var.value, places=0)
def test_mul_expression(self): # Vectors c = Constant([[2], [2]]) exp = c * self.x self.assertEqual(exp.curvature, u.Curvature.AFFINE_KEY) self.assertEqual((c[0] * self.x).sign, u.Sign.UNKNOWN_KEY) 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)") with self.assertRaises(Exception) as cm: (self.A * self.B) self.assertEqual(str(cm.exception), "Cannot multiply two non-constants.") # Constant expressions T = Constant([[1, 2, 3], [3, 5, 5]]) exp = (T + T) * self.B self.assertEqual(exp.curvature, u.Curvature.AFFINE_KEY) 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, u.Sign.UNKNOWN_KEY) # 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_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, u.Curvature.CONSTANT_KEY) self.assertEqual(c.sign, u.Sign.POSITIVE_KEY) self.assertEqual(Constant(-2).sign, u.Sign.NEGATIVE_KEY) self.assertEqual(Constant(0).sign, u.Sign.ZERO_KEY) 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._dcp_attr.sign.neg_mat.shape, (1, 2)) # 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, u.Sign.POSITIVE_KEY) self.assertEqual((c.T * c).sign, u.Sign.POSITIVE_KEY) exp = c.T.T self.assertEqual(exp._dcp_attr.sign.pos_mat.shape, (2, 1)) exp = c.T * self.A self.assertEqual(exp._dcp_attr.sign.pos_mat.shape, (1, 2))
def lp_6() -> SolverTestHelper: """Test LP with no constraints""" x = cp.Variable() from cvxpy.expressions.constants import Constant objective = cp.Maximize(Constant(0.23) * x) obj_pair = (objective, np.inf) var_pairs = [(x, None)] sth = SolverTestHelper(obj_pair, var_pairs, []) return sth
def mi_lp_4() -> SolverTestHelper: """Test MI without constraints""" x = cp.Variable(boolean=True) from cvxpy.expressions.constants import Constant objective = cp.Maximize(Constant(0.23) * x) obj_pair = (objective, 0.23) var_pairs = [(x, 1)] sth = SolverTestHelper(obj_pair, var_pairs, []) return sth
def setUp(self): self.x = Variable(2, name='x') self.y = Variable(2, name='y') self.A = Variable(3, 2, name='A') self.B = Variable(5, 2, name='B') self.C = Constant([[1, 2], [1, 2]]) self.intf = intf.DEFAULT_INTERFACE
def log_canon(expr, args): x = args[0] shape = expr.shape t = Variable(shape) ones = Constant(np.ones(shape)) # TODO(akshayka): ExpCone requires each of its inputs to be a Variable; # is this something that we want to change? constraints = [ExpCone(t, ones, x)] return t, constraints
def test_vector_lp(self): for solver in self.solvers: c = Constant(numpy.array([1, 2])) p = Problem(Minimize(c.T * self.x), [self.x >= c]) result = p.solve(solver.name()) self.assertTrue(ConeMatrixStuffing().accepts(p)) p_new = ConeMatrixStuffing().apply(p) # result_new = p_new[0].solve(solver.name()) # self.assertAlmostEqual(result, result_new) self.assertTrue(solver.accepts(p_new[0])) sltn = solver.solve(p_new[0], False, False, {}) self.assertAlmostEqual(sltn.opt_val, result) inv_sltn = ConeMatrixStuffing().invert(sltn, p_new[1]) p_new1 = ConeMatrixStuffing().apply(p) self.assertTrue(solver.accepts(p_new1[0])) sltn = solver.solve(p_new1[0], False, False, {}) self.assertAlmostEqual(sltn.opt_val, result) inv_sltn = ConeMatrixStuffing().invert(sltn, p_new1[1]) self.assertAlmostEqual(inv_sltn.opt_val, result) self.assertItemsAlmostEqual(inv_sltn.primal_vars[self.x.id], self.x.value) A = Constant(numpy.array([[3, 5], [1, 2]]).T).value Imat = Constant([[1, 0], [0, 1]]) p = Problem(Minimize(c.T * self.x + self.a), [ A * self.x >= [-1, 1], 4 * Imat * self.z == self.x, self.z >= [2, 2], self.a >= 2 ]) self.assertTrue(ConeMatrixStuffing().accepts(p)) result = p.solve(solver.name()) p_new = ConeMatrixStuffing().apply(p) result_new = p_new[0].solve(solver.name()) self.assertAlmostEqual(result, result_new) self.assertTrue(solver.accepts(p_new[0])) sltn = solver.solve(p_new[0], False, False, {}) self.assertAlmostEqual(sltn.opt_val, result, places=1) inv_sltn = ConeMatrixStuffing().invert(sltn, p_new[1]) self.assertAlmostEqual(inv_sltn.opt_val, result, places=1) for var in p.variables(): self.assertItemsAlmostEqual(inv_sltn.primal_vars[var.id], var.value, places=1)
def _grad(self, values): """Gives the (sub/super)gradient of the atom w.r.t. each argument. Matrix expressions are vectorized, so the gradient is a matrix. Args: values: A list of numeric values for the arguments. Returns: A list of SciPy CSC sparse matrices or None. """ # TODO should be a simple function in cvxcore for this. # Make a fake lin op tree for the function. fake_args = [] var_offsets = {} offset = 0 for idx, arg in enumerate(self.args): if arg.is_constant(): fake_args += [Constant(arg.value).canonical_form[0]] else: fake_args += [lu.create_var(arg.shape, idx)] var_offsets[idx] = offset offset += arg.size var_length = offset fake_expr, _ = self.graph_implementation(fake_args, self.shape, self.get_data()) param_to_size = {lo.CONSTANT_ID: 1} param_to_col = {lo.CONSTANT_ID: 0} # Get the matrix representation of the function. canon_mat = canonInterface.get_problem_matrix( [fake_expr], var_length, var_offsets, param_to_size, param_to_col, self.size, ) # HACK TODO TODO convert tensors back to vectors. # COO = (V[lo.CONSTANT_ID][0], (J[lo.CONSTANT_ID][0], I[lo.CONSTANT_ID][0])) shape = (var_length + 1, self.size) stacked_grad = canon_mat.reshape(shape).tocsc()[:-1, :] # Break up into per argument matrices. grad_list = [] start = 0 for arg in self.args: if arg.is_constant(): grad_shape = (arg.size, shape[1]) if grad_shape == (1, 1): grad_list += [0] else: grad_list += [sp.coo_matrix(grad_shape, dtype='float64')] else: stop = start + arg.size grad_list += [stacked_grad[start:stop, :]] start = stop return grad_list
def entr_canon(expr, args): x = args[0] shape = expr.shape t = Variable(shape) # -x\log(x) >= t <=> x\exp(t/x) <= 1 # TODO(akshayka): ExpCone requires each of its inputs to be a Variable; # is this something that we want to change? ones = Constant(np.ones(shape)) constraints = [ExpCone(t, x, ones)] return t, constraints
def linearize(expr): """Returns an affine approximation to the expression computed at the variable/parameter values. Gives an elementwise lower (upper) bound for convex (concave) expressions that is tight at the current variable/parameter values. No guarantees for non-DCP expressions. If f and g are convex, the objective f - g can be (heuristically) minimized using the implementation below of the convex-concave method: .. code :: python for iters in range(N): Problem(Minimize(f - linearize(g))).solve() Returns None if cannot be linearized. Args: expr: An expression. Returns: An affine expression or None. """ expr = Constant.cast_to_const(expr) if expr.is_affine(): return expr else: tangent = expr.value if tangent is None: raise ValueError( "Cannot linearize non-affine expression with missing variable values." ) grad_map = expr.grad for var in expr.variables(): if grad_map[var] is None: return None elif var.is_matrix(): flattened = Constant(grad_map[var]).T * vec(var - var.value) tangent = tangent + reshape(flattened, expr.shape) else: tangent = tangent + Constant( grad_map[var]).T * (var - var.value) return tangent
def logistic_canon(expr, args): x = args[0] shape = expr.shape # log(1 + exp(x)) <= t <=> exp(-t) + exp(x - t) <= 1 t0 = Variable(shape) t1, constr1 = exp_canon(expr, [-t0]) t2, constr2 = exp_canon(expr, [x - t0]) ones = Constant(np.ones(shape)) constraints = constr1 + constr2 + [t1 + t2 <= ones] return t0, constraints
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)