Esempio n. 1
0
    def print_progress(self, leaf):
        """
        Print progress at each iteration
        """
        if self.upper_glob == np.inf:
            gap = "    --- "
        else:
            gap = "%8.2f%%" % \
                ((self.upper_glob - self.lower_glob)/abs(self.lower_glob)*100)

        if leaf.status == osqp.constant('OSQP_PRIMAL_INFEASIBLE') or \
                leaf.status == osqp.constant('OSQP_DUAL_INFEASIBLE'):
            obj = np.inf
        else:
            obj = leaf.lower

        if leaf.intinf is None:
            intinf = "  ---"
        else:
            intinf = "%5d" % leaf.intinf

        print("%4d\t%4d\t  %10.2e\t%4d\t%s\t  %10.2e\t%10.2e\t%s\t%5d" %
              (self.iter_num, len(self.leaves), obj, leaf.depth, intinf,
               self.lower_glob, self.upper_glob, gap, leaf.num_iter),
              end='')

        if leaf.status == osqp.constant('OSQP_MAX_ITER_REACHED'):
            print("!")
        else:
            print("")
Esempio n. 2
0
    def bound_and_branch(self, leaf):
        """
        Analize result from leaf solution and bound
        """

        # Update total number of OSQP ADMM iteration
        self.osqp_iter += leaf.num_iter

        # Update total time to solve OSQP problems
        self.osqp_solve_time += leaf.osqp_solve_time

        # 1) If infeasible or unbounded, then return (prune)
        if leaf.status == osqp.constant('OSQP_PRIMAL_INFEASIBLE') or \
                leaf.status == osqp.constant('OSQP_DUAL_INFEASIBLE'):
            return

        # 2) If lower bound is greater than upper bound, then return (prune)
        if leaf.lower > self.upper_glob:
            return

        # 3) If integer feasible, then
        #   - update best solution
        #   - update best upper bound
        #   - prune all leaves with lower bound greater than best upper bound
        if (self.is_int_feas(leaf.x, leaf)):
            # Update best solution so far
            self.x = leaf.x
            # Update upper bound
            self.upper_glob = leaf.lower
            # Prune all nodes
            self.prune()
            return

        # 4) If fractional, get integer solution using heuristic.
        #    If integer solution satisfies linear constraints, then:
        #    - compute objective value at integer x
        #    If objective value improves the upper bound
        #       - update best upper bound
        #       - prune all leaves with lower bound greater than current one
        x_int = self.get_integer_solution(leaf.x)
        if self.satisfies_lin_constraints(x_int, self.data.l, self.data.u):
            obj_int = self.data.compute_obj_val(x_int)
            if obj_int < self.upper_glob:
                self.upper_glob = obj_int
                self.x = x_int
                self.prune()

        # 5) If we got here, branch current leaf producing two children
        self.branch(leaf)

        # 6) Update lower bound with minimum between lower bounds
        self.lower_glob = min([leaf.lower for leaf in self.leaves])
Esempio n. 3
0
    def solve(self):
        """
        Find lower bound of the relaxed problem corresponding to this node
        """

        # Update l and u in the solver instance
        self.solver.update(l=self.l, u=self.u)

        # Warm start solver with currently stored solution
        self.solver.warm_start(x=self.x, y=self.y)

        # Solve current problem
        results = self.solver.solve()

        # Store solver status
        self.status = results.info.status_val

        # DEBUG: Problems that hit max_iter are infeasible
        # if self.status == osqp.constant('OSQP_MAX_ITER_REACHED'):
        #     self.status = osqp.constant('OSQP_PRIMAL_INFEASIBLE')

        # Store number of iterations
        self.num_iter = results.info.iter

        # Store solve time
        self.osqp_solve_time = results.info.run_time

        # Store solver solution
        self.x = results.x
        self.y = results.y

        # Enforce integer variables to be exactly within the bounds
        if self.status == osqp.constant('OSQP_SOLVED') or \
                self.status == osqp.constant('OSQP_MAX_ITER_REACHED'):
            #  import ipdb; ipdb.set_trace()
            n_int = self.data.n_int
            i_idx = self.data.i_idx
            self.x[i_idx] = \
                np.minimum(np.maximum(self.x[i_idx],
                                      self.l[-n_int:]),
                           self.u[-n_int:])
            #  if any(self.x[i_idx] < self.l[-n_int:]):
            #      import ipdb; ipdb.set_trace()
            #  if any(self.x[i_idx] > self.u[-n_int:]):
            #      import ipdb; ipdb.set_trace()

            # Update objective value of relaxed problem (lower bound)
            self.lower = self.data.compute_obj_val(self.x)
Esempio n. 4
0
    def __init__(self, data, l, u, solver, depth=0, lower=None,
                 x0=None, y0=None):
        """
        Initialize node class
        """

        # Assign data structure
        self.data = data

        # Set l and u for relaxed QP problem
        self.l = l
        self.u = u

        # Assign solver
        self.solver = solver

        # Set depth
        self.depth = depth

        # Set bound
        if lower is None:
            self.lower = -np.inf
        else:
            self.lower = lower

        # Frac_idx, intin
        self.frac_idx = None
        self.intinf = None

        # Number of integer infeasible variables
        self.intinf = None

        # Number of OSQP ADMM iterations
        self.num_iter = 0

        # Time to solve the QP
        self.osqp_solve_time = 0

        # Warm-start variables which are also the relaxed solutions
        if x0 is None:
            x0 = np.zeros(self.data.n)
        if y0 is None:
            y0 = np.zeros(self.data.m + self.data.n_int)
        self.x = x0
        self.y = y0

        # Set QP solver return status
        self.status = osqp.constant('OSQP_UNSOLVED')

        # Add next variable elements
        self.nextvar_idx = None   # Index of next variable within solution x
        # Index of constraint to change for branching
        # on next variable
        self.constr_idx = None
Esempio n. 5
0
def osqp_solve_qp(P,
                  q,
                  G=None,
                  h=None,
                  A=None,
                  b=None,
                  initvals=None,
                  verbose=False,
                  eps_abs=1e-4,
                  eps_rel=1e-4,
                  polish=True):
    """
    Solve a Quadratic Program defined as:

    .. math::

        \\begin{split}\\begin{array}{ll}
        \\mbox{minimize} &
            \\frac{1}{2} x^T P x + q^T x \\\\
        \\mbox{subject to}
            & G x \\leq h                \\\\
            & A x = h
        \\end{array}\\end{split}

    using `OSQP <https://github.com/oxfordcontrol/osqp>`_.

    Parameters
    ----------
    P : scipy.sparse.csc_matrix
        Symmetric quadratic-cost matrix.
    q : numpy.array
        Quadratic cost vector.
    G : scipy.sparse.csc_matrix
        Linear inequality constraint matrix.
    h : numpy.array
        Linear inequality constraint vector.
    A : scipy.sparse.csc_matrix, optional
        Linear equality constraint matrix.
    b : numpy.array, optional
        Linear equality constraint vector.
    initvals : numpy.array, optional
        Warm-start guess vector.
    verbose : bool, optional
        Set to `True` to print out extra information.
    eps_abs : scalar, optional
        Absolute convergence tolerance of the solver. Lower values yield more
        precise solutions at the cost of computation time.
    eps_rel : scalar, optional
        Relative convergence tolerance of the solver. Lower values yield more
        precise solutions at the cost of computation time.
    polish : bool, optional
        Perform `polishing <https://osqp.org/docs/solver/#polishing>`_, an
        additional step where the solver tries to improve the accuracy of the
        solution. Default is ``True``.

    Returns
    -------
    x : array, shape=(n,)
        Solution to the QP, if found, otherwise ``None``.

    Note
    ----
    OSQP requires `P` to be symmetric, and won't check for errors otherwise.
    Check out for this point if you e.g. `get nan values
    <https://github.com/oxfordcontrol/osqp/issues/10>`_ in your solutions.

    Note
    ----
    As of OSQP v0.6.1, the default values for both absolute and relative
    tolerances are set to ``1e-3``, which results in low solver times but
    imprecise solutions compared to the other QP solvers. We lower them to
    ``1e-5`` so that OSQP behaves closer to the norm in terms of numerical
    accuracy.
    """
    if type(P) is ndarray:
        warn(conversion_warning("P"))
        P = csc_matrix(P)
    solver = OSQP()
    kwargs = {
        'eps_abs': eps_abs,
        'eps_rel': eps_rel,
        'polish': polish,
        'verbose': verbose
    }
    if A is None and G is None:
        solver.setup(P=P, q=q, **kwargs)
    elif A is not None:
        if type(A) is ndarray:
            warn(conversion_warning("A"))
            A = csc_matrix(A)
        if G is None:
            solver.setup(P=P, q=q, A=A, l=b, u=b, **kwargs)
        else:  # G is not None
            l = -inf * ones(len(h))
            qp_A = vstack([G, A]).tocsc()
            qp_l = hstack([l, b])
            qp_u = hstack([h, b])
            solver.setup(P=P, q=q, A=qp_A, l=qp_l, u=qp_u, **kwargs)
    else:  # A is None
        if type(G) is ndarray:
            warn(conversion_warning("G"))
            G = csc_matrix(G)
        l = -inf * ones(len(h))
        solver.setup(P=P, q=q, A=G, l=l, u=h, **kwargs)
    if initvals is not None:
        solver.warm_start(x=initvals)
    res = solver.solve()
    if hasattr(solver, 'constant'):
        success_status = solver.constant('OSQP_SOLVED')
    else:  # more recent versions of OSQP
        success_status = osqp.constant('OSQP_SOLVED')
    if res.info.status_val != success_status:
        print("OSQP exited with status '%s'" % res.info.status)
    return res.x
Esempio n. 6
0
class OSQPSolver(object):

    m = osqp.OSQP()
    STATUS_MAP = {osqp.constant('OSQP_SOLVED'): s.OPTIMAL,
                  osqp.constant('OSQP_MAX_ITER_REACHED'): s.MAX_ITER_REACHED,
                  osqp.constant('OSQP_PRIMAL_INFEASIBLE'): s.PRIMAL_INFEASIBLE,
                  osqp.constant('OSQP_DUAL_INFEASIBLE'): s.DUAL_INFEASIBLE}

    def __init__(self, settings={}):
        '''
        Initialize solver object by setting require settings
        '''
        self._settings = settings

    @property
    def settings(self):
        """Solver settings"""
        return self._settings

    def solve(self, example):
        '''
        Solve problem

        Args:
            problem: problem structure with QP matrices

        Returns:
            Results structure
        '''
        problem = example.qp_problem
        settings = self._settings.copy()
        high_accuracy = settings.pop('high_accuracy', None)

        # Setup OSQP
        m = osqp.OSQP()
        m.setup(problem['P'], problem['q'], problem['A'], problem['l'],
                problem['u'],
                **settings)

        # Solve
        results = m.solve()
        status = self.STATUS_MAP.get(results.info.status_val, s.SOLVER_ERROR)

        if status in s.SOLUTION_PRESENT:
            if not is_qp_solution_optimal(problem,
                                          results.x,
                                          results.y,
                                          high_accuracy=high_accuracy):
                status = s.SOLVER_ERROR

        # Verify solver time
        if settings.get('time_limit') is not None:
            if results.info.run_time > settings.get('time_limit'):
                status = s.TIME_LIMIT

        return_results = Results(status,
                                 results.info.obj_val,
                                 results.x,
                                 results.y,
                                 results.info.run_time,
                                 results.info.iter)

        return_results.status_polish = results.info.status_polish
        return_results.setup_time = results.info.setup_time
        return_results.solve_time = results.info.solve_time
        return_results.update_time = results.info.update_time
        return_results.rho_updates = results.info.rho_updates

        return return_results
Esempio n. 7
0
def osqp_solve_qp(P, q, G=None, h=None, A=None, b=None, initvals=None):
    """
    Solve a Quadratic Program defined as:

        minimize
            (1/2) * x.T * P * x + q.T * x

        subject to
            G * x <= h
            A * x == b

    using OSQP <https://github.com/oxfordcontrol/osqp>.

    Parameters
    ----------
    P : scipy.sparse.csc_matrix
        Symmetric quadratic-cost matrix.
    q : numpy.array
        Quadratic cost vector.
    G : scipy.sparse.csc_matrix
        Linear inequality constraint matrix.
    h : numpy.array
        Linear inequality constraint vector.
    A : scipy.sparse.csc_matrix, optional
        Linear equality constraint matrix.
    b : numpy.array, optional
        Linear equality constraint vector.
    initvals : numpy.array, optional
        Warm-start guess vector.

    Returns
    -------
    x : array, shape=(n,)
        Solution to the QP, if found, otherwise ``None``.

    Note
    ----
    OSQP requires `P` to be symmetric, and won't check for errors otherwise.
    Check out for this point if you e.g. `get nan values
    <https://github.com/oxfordcontrol/osqp/issues/10>`_ in your solutions.
    """
    global __verbose__
    if type(P) is ndarray:
        warn(conversion_warning("P"))
        P = csc_matrix(P)
    solver = OSQP()
    if A is None and G is None:
        solver.setup(P=P, q=q, verbose=__verbose__)
    elif A is not None:
        if type(A) is ndarray:
            warn(conversion_warning("A"))
            A = csc_matrix(A)
        if G is None:
            solver.setup(P=P, q=q, A=A, l=b, u=b, verbose=__verbose__)
        else:  # G is not None
            l = -inf * ones(len(h))
            qp_A = vstack([G, A]).tocsc()
            qp_l = hstack([l, b])
            qp_u = hstack([h, b])
            solver.setup(P=P, q=q, A=qp_A, l=qp_l, u=qp_u, verbose=__verbose__)
    else:  # A is None
        if type(G) is ndarray:
            warn(conversion_warning("G"))
            G = csc_matrix(G)
        l = -inf * ones(len(h))
        solver.setup(P=P, q=q, A=G, l=l, u=h, verbose=__verbose__)
    if initvals is not None:
        solver.warm_start(x=initvals)
    res = solver.solve()
    if hasattr(solver, 'constant'):
        success_status = solver.constant('OSQP_SOLVED')
    else:  # more recent versions of OSQP
        success_status = osqp.constant('OSQP_SOLVED')
    if res.info.status_val != success_status:
        print("OSQP exited with status '%s'" % res.info.status)
    return res.x