def solve_QP(self, problem, solver): self.assertTrue(Qp2SymbolicQp().accepts(problem)) canon_p, canon_inverse = Qp2SymbolicQp().apply(problem) self.assertTrue(QpMatrixStuffing().accepts(canon_p)) stuffed_p, stuffed_inverse = QpMatrixStuffing().apply(canon_p) qp_solution = QpSolver(solver).solve(stuffed_p, False, False, {}) stuffed_solution = QpMatrixStuffing().invert(qp_solution, stuffed_inverse) return Qp2SymbolicQp().invert(stuffed_solution, canon_inverse)
def construct_solving_chain(problem, candidates, gp: bool = False, enforce_dpp: bool = False) -> "SolvingChain": """Build a reduction chain from a problem to an installed solver. Note that if the supplied problem has 0 variables, then the solver parameter will be ignored. 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. enforce_dpp : bool, optional When True, a DPPError will be thrown when trying to parse a non-DPP problem (instead of just a warning). Defaults to False. Returns ------- SolvingChain A SolvingChain that can be used to solve the problem. Raises ------ SolverError Raised if no suitable solver exists among the installed solvers, or if the target solver is not installed. """ if len(problem.variables()) == 0: return SolvingChain(reductions=[ConstantSolver()]) reductions = _reductions_for_problem_class(problem, candidates, gp) dpp_context = 'dcp' if not gp else 'dgp' dpp_error_msg = ( "You are solving a parameterized problem that is not DPP. " "Because the problem is not DPP, subsequent solves will not be " "faster than the first one. For more information, see the " "documentation on Discplined Parametrized Programming, at\n" "\thttps://www.cvxpy.org/tutorial/advanced/index.html#" "disciplined-parametrized-programming") if not problem.is_dpp(dpp_context): if not enforce_dpp: warnings.warn(dpp_error_msg) reductions = [EvalParams()] + reductions else: raise DPPError(dpp_error_msg) elif any(param.is_complex() for param in problem.parameters()): reductions = [EvalParams()] + reductions # Conclude with matrix stuffing; choose one of the following paths: # (1) QpMatrixStuffing --> [a QpSolver], # (2) ConeMatrixStuffing --> [a ConicSolver] if _solve_as_qp(problem, candidates): # Canonicalize as a QP solver = candidates['qp_solvers'][0] solver_instance = slv_def.SOLVER_MAP_QP[solver] reductions += [QpMatrixStuffing(), solver_instance] return SolvingChain(reductions=reductions) # Canonicalize as a cone program 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) constr_types = set() # ^ We use constr_types to infer an incomplete list of cones that # the solver will need after canonicalization. for c in problem.constraints: constr_types.add(type(c)) ex_cos = [ct for ct in constr_types if ct in EXOTIC_CONES] # ^ The way we populate "ex_cos" will need to change if and when # we have atoms that require exotic cones. for co in ex_cos: sim_cos = EXOTIC_CONES[co] # get the set of required simple cones constr_types.update(sim_cos) constr_types.remove(co) # We now go over individual elementary cones support by CVXPY ( # SOC, ExpCone, NonNeg, Zero, PSD, PowCone3D) and check if # they've appeared in constr_types or if the problem has an atom # requiring that cone. cones = [] atoms = problem.atoms() if SOC in constr_types or any(atom in SOC_ATOMS for atom in atoms): cones.append(SOC) if ExpCone in constr_types or any(atom in EXP_ATOMS for atom in atoms): cones.append(ExpCone) if any(t in constr_types for t in [Inequality, NonPos, NonNeg]) \ or any(atom in NONPOS_ATOMS for atom in atoms): cones.append(NonNeg) if Equality in constr_types or Zero in constr_types: cones.append(Zero) if PSD in constr_types \ or any(atom in PSD_ATOMS for atom in atoms) \ or any(v.is_psd() or v.is_nsd() for v in problem.variables()): cones.append(PSD) if PowCone3D in constr_types: # if we add in atoms that specifically use the 3D power cone # (rather than the ND power cone), then we'll need to check # for those atoms here as well. cones.append(PowCone3D) # Here, we make use of the observation that canonicalization only # increases the number of constraints in our problem. has_constr = len(cones) > 0 or len(problem.constraints) > 0 for solver in candidates['conic_solvers']: solver_instance = slv_def.SOLVER_MAP_CONIC[solver] if (all(c in solver_instance.SUPPORTED_CONSTRAINTS for c in cones) and (has_constr or not solver_instance.REQUIRES_CONSTR)): if ex_cos: reductions.append(Exotic2Common()) reductions += [ConeMatrixStuffing(), solver_instance] return SolvingChain(reductions=reductions) raise SolverError("Either candidate conic solvers (%s) do not support the " "cones output by the problem (%s), or there are not " "enough constraints in the problem." % ( candidates['conic_solvers'], ", ".join([cone.__name__ for cone in cones])))
def construct_solving_chain(problem, candidates, gp=False, enforce_dpp=False): """Build a reduction chain from a problem to an installed solver. Note that if the supplied problem has 0 variables, then the solver parameter will be ignored. 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. enforce_dpp : bool, optional When True, a DPPError will be thrown when trying to parse a non-DPP problem (instead of just a warning). Defaults to False. Returns ------- SolvingChain A SolvingChain that can be used to solve the problem. Raises ------ SolverError Raised if no suitable solver exists among the installed solvers, or if the target solver is not installed. """ if len(problem.variables()) == 0: return SolvingChain(reductions=[ConstantSolver()]) reductions = _reductions_for_problem_class(problem, candidates, gp) dpp_context = 'dcp' if not gp else 'dgp' dpp_error_msg = ( "You are solving a parameterized problem that is not DPP. " "Because the problem is not DPP, subsequent solves will not be " "faster than the first one. For more information, see the " "documentation on Discplined Parametrized Programming, at\n" "\thttps://www.cvxpy.org/tutorial/advanced/index.html#" "disciplined-parametrized-programming") if not problem.is_dpp(dpp_context): if not enforce_dpp: warnings.warn(dpp_error_msg) reductions = [EvalParams()] + reductions else: raise DPPError(dpp_error_msg) elif any(param.is_complex() for param in problem.parameters()): reductions = [EvalParams()] + reductions # Conclude with matrix stuffing; choose one of the following paths: # (1) QpMatrixStuffing --> [a QpSolver], # (2) ConeMatrixStuffing --> [a ConicSolver] if _solve_as_qp(problem, candidates): # Canonicalize as a QP solver = sorted(candidates['qp_solvers'], key=lambda s: slv_def.QP_SOLVERS.index(s))[0] solver_instance = slv_def.SOLVER_MAP_QP[solver] reductions += [QpMatrixStuffing(), solver_instance] return SolvingChain(reductions=reductions) # Canonicalize as a cone program 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) # Our choice of solver depends upon which atoms are present in the # problem. The types of atoms to check for are SOC atoms, PSD atoms, # and exponential atoms. atoms = problem.atoms() cones = [] if (any(atom in SOC_ATOMS for atom in atoms) or any(type(c) == SOC for c in problem.constraints)): cones.append(SOC) if (any(atom in EXP_ATOMS for atom in atoms) or any(type(c) == ExpCone for c in problem.constraints)): cones.append(ExpCone) if (any(atom in NONPOS_ATOMS for atom in atoms) or any( type(c) in [Inequality, NonPos, NonNeg] for c in problem.constraints)): cones.append(NonNeg) if (any(type(c) in [Equality, Zero] for c in problem.constraints)): cones.append(Zero) if (any(atom in PSD_ATOMS for atom in atoms) or any(type(c) == PSD for c in problem.constraints) or any(v.is_psd() or v.is_nsd() for v in problem.variables())): cones.append(PSD) # Here, we make use of the observation that canonicalization only # increases the number of constraints in our problem. has_constr = len(cones) > 0 or len(problem.constraints) > 0 for solver in sorted(candidates['conic_solvers'], key=lambda s: slv_def.CONIC_SOLVERS.index(s)): solver_instance = slv_def.SOLVER_MAP_CONIC[solver] if (all(c in solver_instance.SUPPORTED_CONSTRAINTS for c in cones) and (has_constr or not solver_instance.REQUIRES_CONSTR)): reductions += [ConeMatrixStuffing(), solver_instance] return SolvingChain(reductions=reductions) raise SolverError("Either candidate conic solvers (%s) do not support the " "cones output by the problem (%s), or there are not " "enough constraints in the problem." % (candidates['conic_solvers'], ", ".join( [cone.__name__ for cone in cones])))