qp_solver_osqp.cpp
Go to the documentation of this file.
1 /*********************************************************************
2  *
3  * Software License Agreement
4  *
5  * Copyright (c) 2020,
6  * TU Dortmund - Institute of Control Theory and Systems Engineering.
7  * All rights reserved.
8  *
9  * This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program. If not, see <https://www.gnu.org/licenses/>.
21  *
22  * Authors: Christoph Rösmann
23  *********************************************************************/
24 
25 #ifdef OSQP
26 
28 
29 #include <corbo-core/console.h>
30 
31 namespace corbo {
32 
33 SolverOsqp::SolverOsqp()
34 {
35  // Problem settings
36  _settings = std::unique_ptr<OSQPSettings>(new OSQPSettings);
37 
38  // Define Solver settings as default
39  osqp_set_default_settings(_settings.get());
40 
41  // disable verbosity by default
42  _settings->verbose = 0;
43 }
44 
45 SolverOsqp::~SolverOsqp()
46 {
47  // Cleanup
48  if (_work) osqp_cleanup(_work);
49 }
50 
52 {
53  if (_initialized) return true;
54 
55  assert(CORBO_INF_DBL >= OSQP_INFTY);
56 
57  _initialized = true;
58  return true;
59 }
60 
63  bool zero_x_warmstart)
64 {
65  PRINT_ERROR("This method is currently not implemented.");
66  return SolverStatus::Error;
67 }
68 
70  Eigen::Ref<Eigen::VectorXd> ubA, bool new_structure, bool zero_x_warmstart, bool update_P, bool update_q,
71  bool update_A, bool update_bounds)
72 {
73  if (!_initialized)
74  {
75  if (!initialize()) return SolverStatus::Error;
76  }
77 
78  // TODO(roesmann): we copy now the solution vectors, but just for rapid prototyping (to avoid this complete SQP cleanup mess etc.). We will change
79  // this later
80 
81  // P and A must be incompressed mode
82  if (!P.isCompressed()) P.makeCompressed();
83  if (!A.isCompressed()) A.makeCompressed();
84 
85  csc* P_csc = csc_matrix(P.rows(), P.cols(), P.nonZeros(), P.valuePtr(), P.innerIndexPtr(), P.outerIndexPtr());
86  csc* A_csc = csc_matrix(A.rows(), A.cols(), A.nonZeros(), A.valuePtr(), A.innerIndexPtr(), A.outerIndexPtr());
87 
88  // Populate data
89  std::unique_ptr<OSQPData> data;
90  if (new_structure || _force_new_structure || !_settings->warm_start || !_work)
91  {
92  // cleanup previous structure
93  if (_work) osqp_cleanup(_work);
94 
95  // TODO(roesmann): whenever I move "_data = std::unique_ptr<OSQPData>(new OSQPData);" to initialize()
96  // I get a segfault / issue in the assembler code of OSQP....
97  data = std::unique_ptr<OSQPData>(new OSQPData);
98  data->n = P.rows();
99  data->m = A.rows();
100  data->P = P_csc;
101  data->q = q.data();
102  data->A = A_csc;
103  data->l = lbA.data();
104  data->u = ubA.data();
105 
106  // Setup workspace
107  c_int exitflag = osqp_setup(&_work, data.get(), _settings.get()); // allocates memory!
108 
109  if (!_work || exitflag != 0)
110  {
111  PRINT_ERROR("Osqp setup failed.");
112  return SolverStatus::Error;
113  }
114 
115  _force_new_structure = false;
116  }
117  else
118  {
119  if (!_work)
120  {
121  PRINT_ERROR_NAMED("No previous workspace found. Cannot solve...");
122  return SolverStatus::Error;
123  }
124 
125  if (zero_x_warmstart)
126  {
127  if (_zero.size() != _work->data->n) _zero.setZero(_work->data->n);
129  }
130 
131  int dim_constr = _work->data->m;
132 
133  if (update_P && update_A && dim_constr > 0)
134  {
135  if (osqp_update_P_A(_work, P.valuePtr(), OSQP_NULL, P.nonZeros(), A.valuePtr(), OSQP_NULL, A.nonZeros()) != 0)
136  {
137  PRINT_ERROR("SolverOSQP: Cannot update P and A due to dimensions mismatch. Maybe a new sparsity pattern?");
138  return SolverStatus::Error;
139  }
140  }
141  else if (update_P) // P must be upper triangular!
142  {
143  if (osqp_update_P(_work, P.valuePtr(), OSQP_NULL, P.nonZeros()) != 0)
144  {
145  PRINT_ERROR("SolverOSQP: Cannot update P due to dimensions mismatch. Maybe a new sparsity pattern?");
146  return SolverStatus::Error;
147  }
148  }
149  else if (update_A && dim_constr > 0)
150  {
151  if (osqp_update_A(_work, A.valuePtr(), OSQP_NULL, A.nonZeros()) != 0)
152  {
153  PRINT_ERROR_NAMED("SolverOSQP: Cannot update A due to dimensions mismatch. Maybe a new sparsity pattern?");
154  return SolverStatus::Error;
155  }
156  }
157 
158  if (update_q)
159  {
160  if (osqp_update_lin_cost(_work, q.data()) != 0)
161  {
162  PRINT_ERROR_NAMED("Cannot update q due to dimensions mismatch.");
163  return SolverStatus::Error;
164  }
165  }
166 
167  if (update_bounds && dim_constr > 0)
168  {
169  if (osqp_update_bounds(_work, lbA.data(), ubA.data()) != 0)
170  {
171  PRINT_ERROR_NAMED("Cannot update lbA abd ubA due to dimensions mismatch.");
172  return SolverStatus::Error;
173  }
174  }
175  }
176 
177  // Solve Problem
178  // c_int exit_flag =
179  osqp_solve(_work);
180 
181  // WARNING the lagrange multipliers in OSQP are defiend for L = f(x) + lambda * c(x)
182 
183  // if (exit_flag == 0)
184  // {
185  // }
186  // else
187  // {
188  // }
189 
190  if (data)
191  {
192  if (data->P) c_free(data->P);
193  if (data->A) c_free(data->A);
194  }
195 
196  // TODO(roesmann): osqp supports warm-starting and update of just the P matrix (hessian), so we should later go for that
197 
198  // TODO(roesmann): we could cache and efficiently update the local sparse identity hessian, since
199  // both row and column vectors are just 0,1,2,3,... and the value vector 1,1,1,1,1
200  return convertOsqpExitFlagToSolverStatus(_work->info->status_val);
201 }
202 
204 {
205  assert(_work);
206  return Eigen::Map<Eigen::VectorXd>(_work->solution->x, _work->data->n);
207 }
208 
210 {
211  assert(_work);
212  return Eigen::Map<Eigen::VectorXd>(_work->solution->y, _work->data->m);
213 }
214 
216 {
217  if (!_work)
218  {
219  PRINT_ERROR_NAMED("No previous workspace found. Cannot update primal solution...");
220  return;
221  }
222  if (x.size() == _work->data->n)
223  osqp_warm_start_x(_work, x.data());
224  else
225  PRINT_ERROR_NAMED("Dimensions mismatch");
226 }
228 {
229  if (!_work)
230  {
231  PRINT_ERROR_NAMED("No previous workspace found. Cannot update dual solution...");
232  return;
233  }
234  if (y.size() == _work->data->m)
235  {
236  if (y.size() > 0) osqp_warm_start_y(_work, y.data());
237  }
238  else
239  PRINT_ERROR_NAMED("Dimensions mismatch");
240 }
241 
242 SolverStatus SolverOsqp::convertOsqpExitFlagToSolverStatus(c_int status) const
243 {
244  switch (status)
245  {
246  case OSQP_SOLVED:
247  case OSQP_SOLVED_INACCURATE:
248  {
250  }
251  case OSQP_MAX_ITER_REACHED:
252  case OSQP_TIME_LIMIT_REACHED:
253  case OSQP_SIGINT:
254  {
256  }
257  case OSQP_PRIMAL_INFEASIBLE:
258  case OSQP_DUAL_INFEASIBLE:
259  case OSQP_NON_CVX:
260  {
262  }
263  case OSQP_UNSOLVED:
264  {
265  return SolverStatus::Error;
266  }
267  default:
268  {
269  }
270  };
271  // OSQP_DUAL_INFEASIBLE_INACCURATE
272  // OSQP_PRIMAL_INFEASIBLE_INACCURATE
273  return SolverStatus::Error;
274 }
275 
276 void SolverOsqp::clear()
277 {
278  if (_work)
279  {
280  osqp_cleanup(_work);
281  _work = nullptr;
282  }
283  _force_new_structure = true; // we could also check for _work in solve()
284  _initialized = false;
285 }
286 
287 #ifdef MESSAGE_SUPPORT
288 void SolverOsqp::toMessage(corbo::messages::SolverOsqp& message) const
289 {
290  message.set_max_iter(_settings->max_iter);
291  message.set_eps_abs(_settings->eps_abs);
292  message.set_eps_rel(_settings->eps_rel);
293  message.set_eps_prim_inf(_settings->eps_prim_inf);
294  message.set_eps_dual_inf(_settings->eps_dual_inf);
295  message.set_alpha(_settings->alpha);
296  message.set_verbose(_settings->verbose);
297  message.set_scaled_termination(_settings->scaled_termination);
298  message.set_check_termination(_settings->check_termination);
299  message.set_warm_start(_settings->warm_start);
300  message.set_time_limit(_settings->time_limit);
301 
302  message.set_rho(_settings->rho);
303  message.set_sigma(_settings->sigma);
304  message.set_scaling(_settings->scaling);
305  message.set_adaptive_rho(_settings->adaptive_rho);
306 
307  message.set_delta(_settings->delta);
308  message.set_polish(_settings->polish);
309  message.set_polish_refine_iter(_settings->polish_refine_iter);
310 
311  switch (_settings->linsys_solver)
312  {
313  case QDLDL_SOLVER:
314  {
315  message.set_linear_solver(messages::SolverOsqp_LinearSolver_QDLDL);
316  break;
317  }
318  case MKL_PARDISO_SOLVER:
319  {
320  message.set_linear_solver(messages::SolverOsqp_LinearSolver_MKL_PARADISO);
321  break;
322  }
323  }
324 }
325 
326 void SolverOsqp::fromMessage(const corbo::messages::SolverOsqp& message, std::stringstream* issues)
327 {
328  _settings->max_iter = message.max_iter();
329  _settings->eps_abs = message.eps_abs();
330  _settings->eps_rel = message.eps_rel();
331  _settings->eps_prim_inf = message.eps_prim_inf();
332  _settings->eps_dual_inf = message.eps_dual_inf();
333  _settings->alpha = message.alpha();
334  _settings->verbose = message.verbose();
335  _settings->scaled_termination = message.scaled_termination();
336  _settings->check_termination = message.check_termination();
337  _settings->warm_start = message.warm_start();
338  _settings->time_limit = message.time_limit();
339 
340  _settings->rho = message.rho();
341  _settings->sigma = message.sigma();
342  _settings->scaling = message.scaling();
343  _settings->adaptive_rho = message.adaptive_rho();
344 
345  _settings->delta = message.delta();
346  _settings->polish = message.polish();
347  _settings->polish_refine_iter = message.polish_refine_iter();
348 
349  switch (message.linear_solver())
350  {
351  case messages::SolverOsqp_LinearSolver_QDLDL:
352  {
353  _settings->linsys_solver = QDLDL_SOLVER;
354  break;
355  }
356  case messages::SolverOsqp_LinearSolver_MKL_PARADISO:
357  {
358  _settings->linsys_solver = MKL_PARDISO_SOLVER;
359  break;
360  }
361  }
362 }
363 #endif
364 
365 } // namespace corbo
366 
367 #endif // OSQP
#define PRINT_ERROR_NAMED(msg)
Definition: console.h:260
SolverStatus solve(SparseMatrix &P, Eigen::Ref< Eigen::VectorXd > q, SparseMatrix &A, Eigen::Ref< Eigen::VectorXd > lbA, Eigen::Ref< Eigen::VectorXd > ubA, Eigen::Ref< Eigen::VectorXd > lb, Eigen::Ref< Eigen::VectorXd > ub, bool new_structure=true, bool zero_x_warmstart=false) override
Solve full QP.
Scalar * x
void clear() override
clear internal caches
Scalar * y
void updatePrimalSolutionWarmStart(const Eigen::Ref< const Eigen::VectorXd > &x) override
Eigen::SparseMatrix< double, Eigen::ColMajor, long long > SparseMatrix
A matrix or vector expression mapping an existing array of data.
Definition: Map.h:94
MatrixType A(a, *n, *n, *lda)
Eigen::Ref< Eigen::VectorXd > getDualSolution() override
virtual bool initialize()
Initialize the qp solver.
constexpr const double CORBO_INF_DBL
Representation for infinity (double version)
EIGEN_DEVICE_FUNC const Scalar & q
Eigen::Ref< Eigen::VectorXd > getPrimalSolution() override
A matrix or vector expression mapping an existing expression.
Definition: Ref.h:192
void updateDualSolutionWarmStart(const Eigen::Ref< const Eigen::VectorXd > &y) override
#define PRINT_ERROR(msg)
Print msg-stream as error msg.
Definition: console.h:173


control_box_rst
Author(s): Christoph Rösmann
autogenerated on Mon Feb 28 2022 22:07:14