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("")
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])
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)
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
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
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
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