Beispiel #1
0
    def test_psd_constraints(self):
        """ Test positive semi-definite constraints
        """
        C = Variable((3, 3))
        obj = Maximize(C[0, 2])
        constraints = [
            diag(C) == 1, C[0, 1] == 0.6, C[1, 2] == -0.3, C == C.T, C >> 0
        ]
        prob = Problem(obj, constraints)
        self.assertTrue(FlipObjective().accepts(prob))
        p_min = FlipObjective().apply(prob)
        self.assertTrue(ConeMatrixStuffing().accepts(p_min[0]))

        C = Variable((2, 2))
        obj = Maximize(C[0, 1])
        constraints = [C == 1, C >> [[2, 0], [0, 2]]]
        prob = Problem(obj, constraints)
        self.assertTrue(FlipObjective().accepts(prob))
        p_min = FlipObjective().apply(prob)
        self.assertTrue(ConeMatrixStuffing().accepts(p_min[0]))

        C = Variable((2, 2), symmetric=True)
        obj = Minimize(C[0, 0])
        constraints = [C << [[2, 0], [0, 2]]]
        prob, _ = CvxAttr2Constr().apply(Problem(obj, constraints))
        self.assertTrue(ConeMatrixStuffing().accepts(prob))
Beispiel #2
0
 def test_nonneg_constraints_backend(self) -> None:
     x = Variable(shape=(2, ), name='x')
     objective = Maximize(-4 * x[0] - 5 * x[1])
     constr_expr = hstack(
         [3 - (2 * x[0] + x[1]), 3 - (x[0] + 2 * x[1]), x[0], x[1]])
     constraints = [NonNeg(constr_expr)]
     prob = Problem(objective, constraints)
     self.assertFalse(ConeMatrixStuffing().accepts(prob))
     self.assertTrue(FlipObjective().accepts(prob))
     p_min = FlipObjective().apply(prob)
     self.assertTrue(ConeMatrixStuffing().accepts(p_min[0]))
Beispiel #3
0
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)
Beispiel #4
0
    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
Beispiel #5
0
    def test_scalar_lp(self):
        """Test scalar LP problems.
        """
        for solver in self.solvers:
            p = Problem(Minimize(3 * self.a), [self.a >= 2])
            self.assertTrue(ConeMatrixStuffing().accepts(p))
            result = p.solve(solver.name())
            p_new = ConeMatrixStuffing().apply(p)
            result_new = p_new[0].solve(solver.name())
            self.assertAlmostEqual(result, result_new)
            sltn = solver.solve(p_new[0], False, False, {})
            self.assertAlmostEqual(sltn.opt_val, result)
            inv_sltn = ConeMatrixStuffing().invert(sltn, p_new[1])
            self.assertAlmostEqual(inv_sltn.opt_val, result)
            self.assertAlmostEqual(inv_sltn.primal_vars[self.a.id],
                                   self.a.value)

            # TODO: Maximize
            p = Problem(Minimize(-3 * self.a + self.b),
                        [self.a <= 2, self.b == self.a, self.b <= 5])
            result = p.solve(solver.name())
            self.assertTrue(ConeMatrixStuffing().accepts(p))
            p_new = ConeMatrixStuffing().apply(p)
            sltn = solver.solve(p_new[0], False, False, {})
            self.assertAlmostEqual(sltn.opt_val, result)
            inv_sltn = ConeMatrixStuffing().invert(sltn, p_new[1])
            self.assertAlmostEqual(inv_sltn.opt_val, result)
            self.assertAlmostEqual(inv_sltn.primal_vars[self.a.id],
                                   self.a.value)
            self.assertAlmostEqual(inv_sltn.primal_vars[self.b.id],
                                   self.b.value)

            # With a constant in the objective.
            p = Problem(Minimize(3 * self.a - self.b + 100), [
                self.a >= 2, self.b + 5 * self.c - 2 == self.a,
                self.b <= 5 + self.c
            ])
            self.assertTrue(ConeMatrixStuffing().accepts(p))
            result = p.solve(solver.name())
            p_new = ConeMatrixStuffing().apply(p)
            sltn = solver.solve(p_new[0], False, False, {})
            self.assertAlmostEqual(sltn.opt_val, result - 100)
            inv_sltn = ConeMatrixStuffing().invert(sltn, p_new[1])
            self.assertAlmostEqual(inv_sltn.opt_val, result)
            self.assertAlmostEqual(inv_sltn.primal_vars[self.a.id],
                                   self.a.value)
            self.assertAlmostEqual(inv_sltn.primal_vars[self.b.id],
                                   self.b.value)

            # Unbounded problems.
            # TODO: Maximize
            p = Problem(Minimize(-self.a), [self.a >= 2])
            self.assertTrue(ConeMatrixStuffing().accepts(p))
            try:
                result = p.solve(solver.name())
            except SolverError:  # Gurobi fails on this one
                return
            p_new = ConeMatrixStuffing().apply(p)
            self.assertTrue(solver.accepts(p_new[0]))
            sltn = solver.solve(p_new[0], False, False, {})
            self.assertAlmostEqual(sltn.opt_val, result)
            inv_sltn = ConeMatrixStuffing().invert(sltn, p_new[1])
            self.assertAlmostEqual(inv_sltn.opt_val, result)

            # Infeasible problems.
            p = Problem(Maximize(self.a), [self.a >= 2, self.a <= 1])
            result = p.solve(solver.name())
            self.assertTrue(FlipObjective().accepts(p))
            p_min = FlipObjective().apply(p)
            self.assertTrue(ConeMatrixStuffing().accepts(p_min[0]))
            p_new = ConeMatrixStuffing().apply(p_min[0])
            result_new = p_new[0].solve(solver.name())
            self.assertAlmostEqual(result, -result_new)
            self.assertTrue(solver.accepts(p_new[0]))
            sltn = solver.solve(p_new[0], False, False, {})
            self.assertAlmostEqual(sltn.opt_val, -result)
            inv_sltn = ConeMatrixStuffing().invert(sltn, p_new[1])
            self.assertAlmostEqual(inv_sltn.opt_val, -result)
            inv_flipped_sltn = FlipObjective().invert(inv_sltn, p_min[1])
            self.assertAlmostEqual(inv_flipped_sltn.opt_val, result)
Beispiel #6
0
def _reductions_for_problem_class(problem,
                                  candidates,
                                  gp: bool = False) -> List[Any]:
    """
    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
    -------
    list of Reduction objects
        A list of reductions 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 = []
    # 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()]

    if _solve_as_qp(problem, candidates):
        reductions += [CvxAttr2Constr(), qp2symbolic_qp.Qp2SymbolicQp()]
    else:
        # 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)
        else:
            reductions += [Dcp2Cone(), CvxAttr2Constr()]

    constr_types = {type(c) for c in problem.constraints}
    if FiniteSet in constr_types:
        reductions += [Valinvec2mixedint()]

    return reductions
Beispiel #7
0
    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