예제 #1
0
 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)
예제 #2
0
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])))
예제 #3
0
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])))