def simulate_chain(in_prob, affine, **solve_kwargs): # get a ParamConeProg object reductions = [Dcp2Cone(), CvxAttr2Constr(), ConeMatrixStuffing()] chain = Chain(None, reductions) cone_prog, inv_prob2cone = chain.apply(in_prob) # apply the Slacks reduction, reconstruct a high-level problem, # solve the problem, invert the reduction. cone_prog = ConicSolver().format_constraints(cone_prog, exp_cone_order=[0, 1, 2]) data, inv_data = a2d.Slacks.apply(cone_prog, affine) G, h, f, K_dir, K_aff = data[s.A], data[s.B], data[ s.C], data['K_dir'], data['K_aff'] G = sp.sparse.csc_matrix(G) y = cp.Variable(shape=(G.shape[1], )) objective = cp.Minimize(f @ y) aff_con = TestSlacks.set_affine_constraints(G, h, y, K_aff) dir_con = TestSlacks.set_direct_constraints(y, K_dir) int_con = TestSlacks.set_integer_constraints(y, data) constraints = aff_con + dir_con + int_con slack_prob = cp.Problem(objective, constraints) slack_prob.solve(**solve_kwargs) slack_prims = { a2d.FREE: y[:cone_prog.x.size].value } # nothing else need be populated. slack_sol = cp.Solution(slack_prob.status, slack_prob.value, slack_prims, None, dict()) cone_sol = a2d.Slacks.invert(slack_sol, inv_data) # pass solution up the solving chain in_prob_sol = chain.invert(cone_sol, inv_prob2cone) in_prob.unpack(in_prob_sol)
def simulate_chain(in_prob): # Get a ParamConeProg object reductions = [Dcp2Cone(), CvxAttr2Constr(), ConeMatrixStuffing()] chain = Chain(None, reductions) cone_prog, inv_prob2cone = chain.apply(in_prob) # Dualize the problem, reconstruct a high-level cvxpy problem for the dual. # Solve the problem, invert the dualize reduction. solver = ConicSolver() cone_prog = solver.format_constraints(cone_prog, exp_cone_order=[0, 1, 2]) data, inv_data = a2d.Dualize.apply(cone_prog) A, b, c, K_dir = data[s.A], data[s.B], data[s.C], data['K_dir'] y = cp.Variable(shape=(A.shape[1], )) constraints = [A @ y == b] i = K_dir[a2d.FREE] dual_prims = {a2d.FREE: y[:i], a2d.SOC: []} if K_dir[a2d.NONNEG]: dim = K_dir[a2d.NONNEG] dual_prims[a2d.NONNEG] = y[i:i + dim] constraints.append(y[i:i + dim] >= 0) i += dim for dim in K_dir[a2d.SOC]: dual_prims[a2d.SOC].append(y[i:i + dim]) constraints.append(SOC(y[i], y[i + 1:i + dim])) i += dim if K_dir[a2d.DUAL_EXP]: dual_prims[a2d.DUAL_EXP] = y[i:] y_de = cp.reshape(y[i:], ((y.size - i) // 3, 3), order='C') # fill rows first constraints.append( ExpCone(-y_de[:, 1], -y_de[:, 0], np.exp(1) * y_de[:, 2])) objective = cp.Maximize(c @ y) dual_prob = cp.Problem(objective, constraints) dual_prob.solve(solver='SCS', eps=1e-8) dual_prims[a2d.FREE] = dual_prims[a2d.FREE].value if K_dir[a2d.NONNEG]: dual_prims[a2d.NONNEG] = dual_prims[a2d.NONNEG].value dual_prims[a2d.SOC] = [expr.value for expr in dual_prims[a2d.SOC]] if K_dir[a2d.DUAL_EXP]: dual_prims[a2d.DUAL_EXP] = dual_prims[a2d.DUAL_EXP].value dual_duals = {s.EQ_DUAL: constraints[0].dual_value} dual_sol = cp.Solution(dual_prob.status, dual_prob.value, dual_prims, dual_duals, dict()) cone_sol = a2d.Dualize.invert(dual_sol, inv_data) # Pass the solution back up the solving chain. in_prob_sol = chain.invert(cone_sol, inv_prob2cone) in_prob.unpack(in_prob_sol)
def construct_intermediate_chain(problem, candidates, gp: bool = False): """ Builds a chain that rewrites a problem into an intermediate representation suitable for numeric reductions. Parameters ---------- problem : Problem The problem for which to build a chain. candidates : dict Dictionary of candidate solvers divided in qp_solvers and conic_solvers. gp : bool If True, the problem is parsed as a Disciplined Geometric Program instead of as a Disciplined Convex Program. Returns ------- Chain A Chain that can be used to convert the problem to an intermediate form. Raises ------ DCPError Raised if the problem is not DCP and `gp` is False. DGPError Raised if the problem is not DGP and `gp` is True. """ reductions = [] if len(problem.variables()) == 0: return Chain(reductions=reductions) # TODO Handle boolean constraints. if complex2real.accepts(problem): reductions += [complex2real.Complex2Real()] if gp: reductions += [Dgp2Dcp()] if not gp and not problem.is_dcp(): append = build_non_disciplined_error_msg(problem, 'DCP') if problem.is_dgp(): append += ("\nHowever, the problem does follow DGP rules. " "Consider calling solve() with `gp=True`.") elif problem.is_dqcp(): append += ("\nHowever, the problem does follow DQCP rules. " "Consider calling solve() with `qcp=True`.") raise DCPError("Problem does not follow DCP rules. Specifically:\n" + append) elif gp and not problem.is_dgp(): append = build_non_disciplined_error_msg(problem, 'DGP') if problem.is_dcp(): append += ("\nHowever, the problem does follow DCP rules. " "Consider calling solve() with `gp=False`.") elif problem.is_dqcp(): append += ("\nHowever, the problem does follow DQCP rules. " "Consider calling solve() with `qcp=True`.") raise DGPError("Problem does not follow DGP rules." + append) # Dcp2Cone and Qp2SymbolicQp require problems to minimize their objectives. if type(problem.objective) == Maximize: reductions += [FlipObjective()] # First, attempt to canonicalize the problem to a linearly constrained QP. if candidates['qp_solvers'] and qp2symbolic_qp.accepts(problem): reductions += [CvxAttr2Constr(), Qp2SymbolicQp()] return Chain(reductions=reductions) # Canonicalize it to conic problem. if not candidates['conic_solvers']: raise SolverError("Problem could not be reduced to a QP, and no " "conic solvers exist among candidate solvers " "(%s)." % candidates) reductions += [Dcp2Cone(), CvxAttr2Constr()] return Chain(reductions=reductions)
def _solve(self, solver=None, warm_start=True, verbose=False, gp=False, qcp=False, requires_grad=False, enforce_dpp=False, **kwargs): """Solves a DCP compliant optimization problem. Saves the values of primal and dual variables in the variable and constraint objects, respectively. Arguments --------- solver : str, optional The solver to use. Defaults to ECOS. warm_start : bool, optional Should the previous solver result be used to warm start? verbose : bool, optional Overrides the default of hiding solver output. gp : bool, optional If True, parses the problem as a disciplined geometric program. qcp : bool, optional If True, parses the problem as a disciplined quasiconvex program. requires_grad : bool, optional Makes it possible to compute gradients with respect to parameters by calling `backward()` after solving, or to compute perturbations to the variables by calling `derivative()`. When True, the solver must be SCS, and dqcp must be False. A DPPError is thrown when problem is not DPP. enforce_dpp : bool, optional When True, a DPPError will be thrown when trying to solve a non-DPP problem (instead of just a warning). Defaults to False. kwargs : dict, optional A dict of options that will be passed to the specific solver. In general, these options will override any default settings imposed by cvxpy. Returns ------- float The optimal value for the problem, or a string indicating why the problem could not be solved. """ for parameter in self.parameters(): if parameter.value is None: raise error.ParameterError( "A Parameter (whose name is '%s') does not have a value " "associated with it; all Parameter objects must have " "values before solving a problem." % parameter.name()) if requires_grad: dpp_context = 'dgp' if gp else 'dcp' if qcp: raise ValueError("Cannot compute gradients of DQCP problems.") elif not self.is_dpp(dpp_context): raise error.DPPError("Problem is not DPP (when requires_grad " "is True, problem must be DPP).") elif solver is not None and solver not in [s.SCS, s.DIFFCP]: raise ValueError("When requires_grad is True, the only " "supported solver is SCS " "(received %s)." % solver) elif s.DIFFCP not in slv_def.INSTALLED_SOLVERS: raise ImportError( "The Python package diffcp must be installed to " "differentiate through problems. Please follow the " "installation instructions at " "https://github.com/cvxgrp/diffcp") else: solver = s.DIFFCP else: if gp and qcp: raise ValueError("At most one of `gp` and `qcp` can be True.") if qcp and not self.is_dcp(): if not self.is_dqcp(): raise error.DQCPError("The problem is not DQCP.") reductions = [dqcp2dcp.Dqcp2Dcp()] if type(self.objective) == Maximize: reductions = [FlipObjective()] + reductions chain = Chain(problem=self, reductions=reductions) soln = bisection.bisect(chain.reduce(), solver=solver, verbose=verbose, **kwargs) self.unpack(chain.retrieve(soln)) return self.value data, solving_chain, inverse_data = self.get_problem_data( solver, gp, enforce_dpp) solution = solving_chain.solve_via_data(self, data, warm_start, verbose, kwargs) self.unpack_results(solution, solving_chain, inverse_data) return self.value
def _solve(self, solver=None, warm_start=True, verbose=False, parallel=False, gp=False, qcp=False, **kwargs): """Solves a DCP compliant optimization problem. Saves the values of primal and dual variables in the variable and constraint objects, respectively. Parameters ---------- solver : str, optional The solver to use. Defaults to ECOS. warm_start : bool, optional Should the previous solver result be used to warm start? verbose : bool, optional Overrides the default of hiding solver output. parallel : bool, optional If problem is separable, solve in parallel. gp : bool, optional If True, parses the problem as a disciplined geometric program. qcp : bool, optional If True, parses the problem as a disciplined quasiconvex program. kwargs : dict, optional A dict of options that will be passed to the specific solver. In general, these options will override any default settings imposed by cvxpy. Returns ------- float The optimal value for the problem, or a string indicating why the problem could not be solved. """ if gp and qcp: raise ValueError("At most one of `gp` and `qcp` can be True.") if qcp and not self.is_dcp(): if not self.is_dqcp(): raise error.DQCPError("The problem is not DQCP.") reductions = [dqcp2dcp.Dqcp2Dcp()] if type(self.objective) == Maximize: reductions = [FlipObjective()] + reductions chain = Chain(problem=self, reductions=reductions) soln = bisection.bisect(chain.reduce(), solver=solver, verbose=verbose, **kwargs) self.unpack(chain.retrieve(soln)) return self.value if parallel: from cvxpy.transforms.separable_problems import get_separable_problems self._separable_problems = (get_separable_problems(self)) if len(self._separable_problems) > 1: return self._parallel_solve(solver, warm_start, verbose, **kwargs) self._construct_chains(solver=solver, gp=gp) data, solving_inverse_data = self._solving_chain.apply( self._intermediate_problem) solution = self._solving_chain.solve_via_data(self, data, warm_start, verbose, kwargs) full_chain = self._solving_chain.prepend(self._intermediate_chain) inverse_data = self._intermediate_inverse_data + solving_inverse_data self.unpack_results(solution, full_chain, inverse_data) return self.value