def construct_solving_chain(problem, solver=None, gp=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. solver : string The name of the solver with which to terminate the chain. If no solver is supplied (i.e., if solver is None), then the targeted solver may be any of those that are installed. If the problem is variable-free, then this parameter is ignored. gp : bool If True, the problem is parsed as a Disciplined Geometric Program instead of as a Disciplined Convex Program. Returns ------- SolvingChain A SolvingChain that can be used to solve the problem. 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. SolverError Raised if no suitable solver exists among the installed solvers, or if the target solver is not installed. """ if solver is not None: if solver not in slv_def.INSTALLED_SOLVERS: raise SolverError("The solver %s is not installed." % solver) candidates = [solver] else: candidates = slv_def.INSTALLED_SOLVERS reductions = [] if problem.parameters(): reductions += [EvalParams()] if len(problem.variables()) == 0: reductions += [ConstantSolver()] return SolvingChain(reductions=reductions) if Complex2Real().accepts(problem): reductions += [Complex2Real()] if gp: reductions += [Dgp2Dcp()] if solver is not None and solver not in slv_def.CONIC_SOLVERS: raise SolverError( "When `gp=True`, `solver` must be a conic solver " "(received '%s'); try calling `solve()` with `solver=cvxpy.ECOS`." % solver) elif solver is None: candidates = slv_def.INSTALLED_CONIC_SOLVERS if not gp and not problem.is_dcp(): append = "" if problem.is_dgp(): append = (" However, the problem does follow DGP rules. " "Consider calling this function with `gp=True`.") raise DCPError("Problem does not follow DCP rules." + append) elif gp and not problem.is_dgp(): append = "" if problem.is_dcp(): append = (" However, the problem does follow DCP rules. " "Consider calling this function with `gp=False`.") raise DGPError("Problem does not follow DGP rules." + append) # Dcp2Cone and Qp2SymbolicQp require problems to minimize their objectives. if type(problem.objective) == Maximize: reductions.append(FlipObjective()) # Conclude the chain with one of the following: # (1) Qp2SymbolicQp --> QpMatrixStuffing --> [a QpSolver], # (2) Dcp2Cone --> ConeMatrixStuffing --> [a ConicSolver] # # First, attempt to canonicalize the problem to a linearly constrained QP. candidate_qp_solvers = [s for s in slv_def.QP_SOLVERS if s in candidates] # Consider only MIQP solvers if problem is integer if problem.is_mixed_integer(): candidate_qp_solvers = [ s for s in candidate_qp_solvers if slv_def.SOLVER_MAP_QP[s].MIP_CAPABLE ] if candidate_qp_solvers and Qp2SymbolicQp().accepts(problem): solver = sorted(candidate_qp_solvers, key=lambda s: slv_def.QP_SOLVERS.index(s))[0] solver_instance = slv_def.SOLVER_MAP_QP[solver] reductions += [ CvxAttr2Constr(), Qp2SymbolicQp(), QpMatrixStuffing(), solver_instance ] return SolvingChain(reductions=reductions) candidate_conic_solvers = [ s for s in slv_def.CONIC_SOLVERS if s in candidates ] if problem.is_mixed_integer(): candidate_conic_solvers = \ [s for s in candidate_conic_solvers if slv_def.SOLVER_MAP_CONIC[s].MIP_CAPABLE] if not candidate_conic_solvers and \ not candidate_qp_solvers: raise SolverError("Problem is mixed-integer, but candidate " "QP/Conic solvers (%s) are not MIP-capable." % [candidate_qp_solvers, candidate_conic_solvers]) if not candidate_conic_solvers: raise SolverError("Problem could not be reduced to a QP, and no " "conic solvers exist among candidate solvers " "(%s)." % candidates) # Attempt to canonicalize the problem to a cone program. # 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 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(candidate_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 += [ Dcp2Cone(), CvxAttr2Constr(), 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." % (candidate_conic_solvers, ", ".join([cone.__name__ for cone in cones])))
def construct_solving_chain(problem, candidates): """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. 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. """ reductions = [] if problem.parameters(): reductions += [EvalParams()] if len(problem.variables()) == 0: reductions += [ConstantSolver()] return SolvingChain(reductions=reductions) # Conclude the chain with one of the following: # (1) QpMatrixStuffing --> [a QpSolver], # (2) ConeMatrixStuffing --> [a ConicSolver] # First, attempt to canonicalize the problem to a linearly constrained QP. if candidates['qp_solvers'] and QpMatrixStuffing.accepts(problem): 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) 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 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])))
def construct_solving_chain(problem, candidates, gp=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. 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) if gp: # Log-log convex programs need a specialized DPP ruleset (so that # the corresponding DCP program ends up being DPP), which we do not yet # have; for now, just evaluate the parameters. reductions += [EvalParams()] elif not problem.is_dpp(): warnings.warn( "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") reductions += [EvalParams()] elif any(param.is_complex() for param in problem.parameters()): reductions += [EvalParams()] # 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] for c in problem.constraints)): cones.append(NonPos) 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])))
def construct_solving_chain(problem, solver=None): """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. solver : string The name of the solver with which to terminate the chain. If no solver is supplied (i.e., if solver is None), then the targeted solver may be any of those that are installed. If the problem is variable-free, then this parameter is ignored. Returns ------- SolvingChain A SolvingChain that can be used to solve the problem. Raises ------ DCPError Raised if the problem is not DCP. SolverError Raised if no suitable solver exists among the installed solvers, or if the target solver is not installed. """ if solver is not None: if solver not in INSTALLED_SOLVERS: raise SolverError("The solver %s is not installed." % solver) candidates = [solver] else: candidates = INSTALLED_SOLVERS reductions = [] # Evaluate parameters and short-circuit the solver if the problem # is constant. if problem.parameters(): reductions += [EvalParams()] if len(problem.variables()) == 0: reductions += [ConstantSolver()] return SolvingChain(reductions=reductions) if Complex2Real().accepts(problem): reductions += [Complex2Real()] # Presently, we have but two reduction chains: # (1) Qp2SymbolicQp --> QpMatrixStuffing --> [a QpSolver], # (2) Dcp2Cone --> ConeMatrixStuffing --> [a ConicSolver] # Both of these chains require that the problem is DCP. if not problem.is_dcp(): raise DCPError("Problem does not follow DCP rules.") # Both reduction chains exclusively accept minimization problems. if type(problem.objective) == Maximize: reductions.append(FlipObjective()) # Attempt to canonicalize the problem to a linearly constrained QP. candidate_qp_solvers = [s for s in QP_SOLVERS if s in candidates] # Consider only MIQP solvers if problem is integer if problem.is_mixed_integer(): candidate_qp_solvers = \ [s for s in candidate_qp_solvers if SOLVER_MAP_QP[s].MIP_CAPABLE] if candidate_qp_solvers and Qp2SymbolicQp().accepts(problem): solver = sorted(candidate_qp_solvers, key=lambda s: QP_SOLVERS.index(s))[0] solver_instance = SOLVER_MAP_QP[solver] reductions += [ CvxAttr2Constr(), Qp2SymbolicQp(), QpMatrixStuffing(), solver_instance ] return SolvingChain(reductions=reductions) candidate_conic_solvers = [s for s in CONIC_SOLVERS if s in candidates] if problem.is_mixed_integer(): candidate_conic_solvers = \ [s for s in candidate_conic_solvers if SOLVER_MAP_CONIC[s].MIP_CAPABLE] if not candidate_conic_solvers and \ not candidate_qp_solvers: raise SolverError("Problem is mixed-integer, but candidate " "QP/Conic solvers (%s) are not MIP-capable." % [candidate_qp_solvers, candidate_conic_solvers]) if not candidate_conic_solvers: raise SolverError("Problem could not be reduced to a QP, and no " "conic solvers exist among candidate solvers " "(%s)." % candidates) # Attempt to canonicalize the problem to a cone program. # 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 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(candidate_conic_solvers, key=lambda s: CONIC_SOLVERS.index(s)): solver_instance = 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 += [ Dcp2Cone(), CvxAttr2Constr(), 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." % (candidate_conic_solvers, ", ".join([cone.__name__ for cone in cones])))