def solve(self, objective, constraints, cached_data, warm_start, verbose, solver_opts): """Returns the result of the call to the solver. Parameters ---------- objective : CVXPY objective object Raw objective passed by CVXPY. Can be convex/concave. constraints : list The list of raw constraints. Returns ------- tuple (status, optimal value, primal, equality dual, inequality dual) """ sym_data = self.get_sym_data(objective, constraints) id_map = sym_data.var_offsets N = sym_data.x_length extractor = CoeffExtractor(id_map, N) # Extract the coefficients (Ps, Q, R) = extractor.get_coeffs(objective.args[0]) P = Ps[0] q = np.asarray(Q.todense()).flatten() r = R[0] # Forming the KKT system if len(constraints) > 0: Cs = [extractor.get_coeffs(c._expr)[1:] for c in constraints] As = sp.vstack([C[0] for C in Cs]) bs = np.array([C[1] for C in Cs]).flatten() lhs = sp.bmat([[2 * P, As.transpose()], [As, None]], format='csr') rhs = np.concatenate([-q, -bs]) else: # avoiding calling vstack with empty list lhs = 2 * P rhs = -q warnings.filterwarnings('error') # Actually solving the KKT system try: sol = SLA.spsolve(lhs.tocsr(), rhs) x = np.array(sol[:N]) nu = np.array(sol[N:]) p_star = np.dot(x.transpose(), P * x + q) + r except SLA.MatrixRankWarning: x = None nu = None p_star = None warnings.resetwarnings() result_dict = {s.PRIMAL: x, s.EQ_DUAL: nu, s.VALUE: p_star} return self.format_results(result_dict, None, cached_data)
def apply(self, problem): """See docstring for MatrixStuffing.apply""" inverse_data = InverseData(problem) # Form the constraints extractor = CoeffExtractor(inverse_data) params_to_P, params_to_q, flattened_variable = self.stuffed_objective( problem, extractor) # Lower equality and inequality to Zero and NonPos. cons = [] for con in problem.constraints: if isinstance(con, Equality): con = lower_equality(con) elif isinstance(con, Inequality): con = lower_ineq_to_nonpos(con) cons.append(con) # Reorder constraints to Zero, NonPos. constr_map = group_constraints(cons) ordered_cons = constr_map[Zero] + constr_map[NonPos] inverse_data.cons_id_map = {con.id: con.id for con in ordered_cons} inverse_data.constraints = ordered_cons # Batch expressions together, then split apart. expr_list = [arg for c in ordered_cons for arg in c.args] params_to_Ab = extractor.affine(expr_list) inverse_data.minimize = type(problem.objective) == Minimize new_prob = ParamQuadProg(params_to_P, params_to_q, flattened_variable, params_to_Ab, problem.variables(), inverse_data.var_offsets, ordered_cons, problem.parameters(), inverse_data.param_id_map) return new_prob, inverse_data
def stuffed_objective(self, problem, inverse_data): extractor = CoeffExtractor(inverse_data) # Extract to c.T * x, store r C, R = extractor.get_coeffs(problem.objective.expr) c = np.asarray(C.todense()).flatten() boolean, integer = extract_mip_idx(problem.variables()) x = Variable(inverse_data.x_length, boolean=boolean, integer=integer) new_obj = c.T * x + 0 inverse_data.r = R[0] return new_obj, x
def apply(self, problem): inverse_data = InverseData(problem) # Form the constraints extractor = CoeffExtractor(inverse_data) params_to_objective, flattened_variable = self.stuffed_objective( problem, extractor) # Lower equality and inequality to Zero and NonNeg. cons = [] for con in problem.constraints: if isinstance(con, Equality): con = lower_equality(con) elif isinstance(con, Inequality): con = lower_ineq_to_nonneg(con) elif isinstance(con, NonPos): con = nonpos2nonneg(con) elif isinstance(con, SOC) and con.axis == 1: con = SOC(con.args[0], con.args[1].T, axis=0, constr_id=con.constr_id) elif isinstance(con, PowCone3D) and con.args[0].ndim > 1: x, y, z = con.args alpha = con.alpha con = PowCone3D(x.flatten(), y.flatten(), z.flatten(), alpha.flatten(), constr_id=con.constr_id) elif isinstance(con, ExpCone) and con.args[0].ndim > 1: x, y, z = con.args con = ExpCone(x.flatten(), y.flatten(), z.flatten(), constr_id=con.constr_id) cons.append(con) # Reorder constraints to Zero, NonNeg, SOC, PSD, EXP, PowCone3D constr_map = group_constraints(cons) ordered_cons = constr_map[Zero] + constr_map[NonNeg] + \ constr_map[SOC] + constr_map[PSD] + constr_map[ExpCone] + constr_map[PowCone3D] inverse_data.cons_id_map = {con.id: con.id for con in ordered_cons} inverse_data.constraints = ordered_cons # Batch expressions together, then split apart. expr_list = [arg for c in ordered_cons for arg in c.args] params_to_problem_data = extractor.affine(expr_list) inverse_data.minimize = type(problem.objective) == Minimize new_prob = ParamConeProg(params_to_objective, flattened_variable, params_to_problem_data, problem.variables(), inverse_data.var_offsets, ordered_cons, problem.parameters(), inverse_data.param_id_map) return new_prob, inverse_data
def stuffed_objective(self, problem, inverse_data): # We need to copy the problem because we are changing atoms in the # expression tree problem_copy = problems.problem.Problem( Minimize(problem.objective.expr.tree_copy()), [con.tree_copy() for con in problem.constraints]) inverse_data_of_copy = InverseData(problem_copy) extractor = CoeffExtractor(inverse_data_of_copy) # extract to x.T * P * x + q.T * x, store r P, q, r = extractor.quad_form(problem_copy.objective.expr) # concatenate all variables in one vector boolean, integer = extract_mip_idx(problem.variables()) x = Variable(inverse_data.x_length, boolean=boolean, integer=integer) new_obj = QuadForm(x, P) + q.T * x inverse_data.r = r return new_obj, x
def apply(self, problem): inverse_data = InverseData(problem) new_obj, new_var = self.stuffed_objective(problem, inverse_data) # Form the constraints extractor = CoeffExtractor(inverse_data) new_cons = [] for con in problem.constraints: arg_list = [] for arg in con.args: A, b = extractor.get_coeffs(arg) arg_list.append(reshape(A * new_var + b, arg.shape)) new_cons.append(con.copy(arg_list)) inverse_data.cons_id_map[con.id] = new_cons[-1].id # Map of old constraint id to new constraint id. inverse_data.minimize = type(problem.objective) == Minimize new_prob = problems.problem.Problem(Minimize(new_obj), new_cons) return new_prob, inverse_data
def apply(self, problem): """See docstring for MatrixStuffing.apply""" inverse_data = InverseData(problem) # Form the constraints extractor = CoeffExtractor(inverse_data) new_obj, new_var, r = self.stuffed_objective(problem, extractor) inverse_data.r = r # Lower equality and inequality to Zero and NonPos. cons = [] for con in problem.constraints: if isinstance(con, Equality): con = lower_equality(con) elif isinstance(con, Inequality): con = lower_inequality(con) cons.append(con) # Batch expressions together, then split apart. expr_list = [arg for c in cons for arg in c.args] problem_data_tensor = extractor.affine(expr_list) Afull, bfull = canon.get_matrix_and_offset_from_unparameterized_tensor( problem_data_tensor, new_var.size) if 0 not in Afull.shape and 0 not in bfull.shape: Afull = cvxtypes.constant()(Afull) bfull = cvxtypes.constant()(np.atleast_1d(bfull)) new_cons = [] offset = 0 for orig_con, con in zip(problem.constraints, cons): arg_list = [] for arg in con.args: A = Afull[offset:offset + arg.size, :] b = bfull[offset:offset + arg.size] arg_list.append(reshape(A @ new_var + b, arg.shape)) offset += arg.size new_constraint = con.copy(arg_list) new_cons.append(new_constraint) inverse_data.constraints = new_cons inverse_data.minimize = type(problem.objective) == Minimize new_prob = problems.problem.Problem(Minimize(new_obj), new_cons) return new_prob, inverse_data
def format_constr(self, problem, constr, exp_cone_order): """Extract coefficient and offset vector from constraint. Special cases PSD constraints, as SCS expects constraints to be imposed on solely the lower triangular part of the variable matrix. Moreover, it requires the off-diagonal coefficients to be scaled by sqrt(2). """ if isinstance(constr, PSD): expr = constr.expr triangularized_expr = scaled_lower_tri(expr + expr.T) / 2 extractor = CoeffExtractor(InverseData(problem)) A_prime, b_prime = extractor.affine(triangularized_expr) # SCS requests constraints to be formatted as # Ax + s = b, where s is constrained to reside in some # cone. Here, however, we are formatting the constraint # as A"x + b" = s = -Ax + b; hence, A = -A", b = b" return -1 * A_prime, b_prime else: return super(SCS, self).format_constr(problem, constr, exp_cone_order)
def apply(self, problem): inverse_data = InverseData(problem) # Form the constraints extractor = CoeffExtractor(inverse_data) new_obj, new_var, r = self.stuffed_objective(problem, extractor) inverse_data.r = r # Lower equality and inequality to Zero and NonPos. cons = [] for con in problem.constraints: if isinstance(con, Equality): con = lower_equality(con) elif isinstance(con, Inequality): con = lower_inequality(con) elif isinstance(con, SOC) and con.axis == 1: con = SOC(con.args[0], con.args[1].T, axis=0, constr_id=con.constr_id) cons.append(con) # Batch expressions together, then split apart. expr_list = [arg for con in cons for arg in con.args] Afull, bfull = extractor.affine(expr_list) new_cons = [] offset = 0 for con in cons: arg_list = [] for arg in con.args: A = Afull[offset:offset + arg.size, :] b = bfull[offset:offset + arg.size] arg_list.append(reshape(A * new_var + b, arg.shape)) offset += arg.size new_cons.append(con.copy(arg_list)) inverse_data.cons_id_map[con.id] = new_cons[-1].id # Map of old constraint id to new constraint id. inverse_data.minimize = type(problem.objective) == Minimize new_prob = problems.problem.Problem(Minimize(new_obj), new_cons) return new_prob, inverse_data
def psd_coeff_offset(problem, c): """ Returns an array "G" and vector "h" such that the given constraint is equivalent to "G * z <=_{PSD} h". :param problem: the cvxpy Problem in which "c" arises. :param c: a cvxpy Constraint defining a linear matrix inequality "B + \sum_j A[j] * z[j] >=_{PSD} 0". :return: (G, h) such that "c" holds at "z" iff "G * z <=_{PSD} b" (where the PSD cone is reshaped into a subset of R^N with N = dim ** 2). Note: It is desirable to change this mosek interface so that PSD constraints are represented by a vector in R^N with N = (dim * (dim + 1) / 2). This is possible because arguments to a linear matrix inequality are necessarily symmetric. For now we use N = dim ** 2, because it simplifies implementation and only makes a modest difference in the size of the problem seen by mosek. """ extractor = CoeffExtractor(InverseData(problem)) A_vec, b_vec = extractor.affine(c.expr) G = -A_vec h = b_vec dim = c.expr.shape[0] return G, h, dim
def apply(self, problem): """Returns a stuffed problem. The returned problem is a minimization problem in which every constraint in the problem has affine arguments that are expressed in the form A @ x + b. Parameters ---------- problem: The problem to stuff; the arguments of every constraint must be affine constraints: A list of constraints, whose arguments are affine Returns ------- Problem The stuffed problem InverseData Data for solution retrieval """ inverse_data = InverseData(problem) # Form the constraints extractor = CoeffExtractor(inverse_data) new_obj, new_var, r = self.stuffed_objective(problem, extractor) inverse_data.r = r # Lower equality and inequality to Zero and NonPos. cons = [] for con in problem.constraints: if isinstance(con, Equality): con = lower_equality(con) elif isinstance(con, Inequality): con = lower_inequality(con) elif isinstance(con, SOC) and con.axis == 1: con = SOC(con.args[0], con.args[1].T, axis=0, constr_id=con.constr_id) cons.append(con) # Batch expressions together, then split apart. expr_list = [arg for c in cons for arg in c.args] Afull, bfull = extractor.affine(expr_list) if 0 not in Afull.shape and 0 not in bfull.shape: Afull = cvxtypes.constant()(Afull) bfull = cvxtypes.constant()(bfull) new_cons = [] offset = 0 for con in cons: arg_list = [] for arg in con.args: A = Afull[offset:offset+arg.size, :] b = bfull[offset:offset+arg.size] arg_list.append(reshape(A*new_var + b, arg.shape)) offset += arg.size new_cons.append(con.copy(arg_list)) # Map old constraint id to new constraint id. inverse_data.cons_id_map[con.id] = new_cons[-1].id inverse_data.minimize = type(problem.objective) == Minimize new_prob = problems.problem.Problem(Minimize(new_obj), new_cons) return new_prob, inverse_data