blob: 3f79fa995e41acccfc537b3a2b8d229b5ecbb13f [file] [log] [blame]
Austin Schuha20e8c92022-02-20 17:44:06 -08001// Copyright 2020 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#ifndef OSQP_CPP_H_
16#define OSQP_CPP_H_
17
18// A C++ wrapper for OSQP (https://osqp.org/). See README.md for an overview.
19
20#include <memory>
21#include <string>
22
23#include "absl/base/attributes.h"
24#include "absl/status/status.h"
25#include "absl/status/statusor.h"
26#include "Eigen/Core"
27#include "Eigen/SparseCore"
28
29namespace osqp {
30
31// Must match the typedef in osqp/include/glob_opts.h (if not, it will trigger
32// a static_assert failure in osqp++.cc).
33using c_int = long long; // NOLINT
34
35// A memory-safe mirror of the OSQPData struct defined in osqp/include/types.h.
36// The number of variables and constraints is implied by the shape of
37// constraint_matrix. The format of the struct is further discussed in
38// README.md. See also osqp++_test.cc for example usage.
39struct OsqpInstance {
40 c_int num_variables() const { return constraint_matrix.cols(); }
41 c_int num_constraints() const { return constraint_matrix.rows(); }
42
43 // Only the upper triangle of the objective matrix is read. The lower triangle
44 // is ignored.
45 Eigen::SparseMatrix<double, Eigen::ColMajor, c_int> objective_matrix;
46 Eigen::VectorXd objective_vector;
47 Eigen::SparseMatrix<double, Eigen::ColMajor, c_int> constraint_matrix;
48 Eigen::VectorXd lower_bounds;
49 Eigen::VectorXd upper_bounds;
50};
51
52// This is a mirror of the OSQPSettings struct defined in
53// osqp/include/types.h and documented at
54// http://osqp.readthedocs.io/en/latest/interfaces/solver_settings.html. The
55// names are unchanged and (hence) violate Google naming conventions. The
56// default values are defined in osqp/include/constants.h. Note, OSQP's default
57// settings are looser than other QP solvers. Do choose appropriate values of
58// eps_abs and eps_rel for your application.
59struct OsqpSettings {
60 OsqpSettings(); // Sets default values.
61
62 double rho;
63 double sigma;
64 c_int scaling;
65 bool adaptive_rho;
66 c_int adaptive_rho_interval;
67 double adaptive_rho_tolerance;
68 double adaptive_rho_fraction;
69 c_int max_iter;
70 double eps_abs;
71 double eps_rel;
72 double eps_prim_inf;
73 double eps_dual_inf;
74 double alpha;
75 // linsys_solver is omitted. We don't change this.
76 double delta;
77 bool polish;
78 c_int polish_refine_iter;
79 bool verbose;
80 bool scaled_termination;
81 c_int check_termination;
82 bool warm_start;
83 double time_limit;
84};
85
86// Type-safe wrapper for OSQP's status codes that are defined at
87// osqp/include/constants.h.
88enum class OsqpExitCode {
89 kOptimal, // Optimal solution found.
90 kPrimalInfeasible, // Certificate of primal infeasibility found.
91 kDualInfeasible, // Certificate of dual infeasibility found.
92 kOptimalInaccurate, // Optimal solution found subject to reduced tolerances
93 kPrimalInfeasibleInaccurate, // Certificate of primal infeasibility found
94 // subject to reduced tolerances.
95 kDualInfeasibleInaccurate, // Certificate of dual infeasibility found
96 // subject to reduced tolerances.
97 kMaxIterations, // Maximum number of iterations reached.
98 kInterrupted, // Interrupted by signal or CTRL-C.
99 kTimeLimitReached, // Ran out of time.
100 kNonConvex, // The problem was found to be non-convex.
101 kUnknown, // Unknown problem in solver.
102};
103
104std::string ToString(OsqpExitCode exitcode);
105
106// This is a workaround to avoid including OSQP's header file. We can't directly
107// forward-declare OSQPWorkspace because it is defined as a typedef of an
108// anonymous struct.
109struct OSQPWorkspaceHelper;
110
111// This class is the main interface for calling OSQP. See example usage in
112// README.md.
113class OsqpSolver {
114 public:
115 OsqpSolver() = default;
116 // Move-only.
117 OsqpSolver(OsqpSolver&& rhs) = default;
118 OsqpSolver& operator=(OsqpSolver&& rhs) = default;
119 OsqpSolver(const OsqpSolver&) = delete;
120 OsqpSolver& operator=(const OsqpSolver&) = delete;
121
122 // Creates the internal OSQP workspace given the instance data and settings.
123 // It is valid to call Init() multiple times.
124 absl::Status Init(const OsqpInstance& instance, const OsqpSettings& settings);
125
126 // Updates the elements of matrix the objective matrix P (upper triangular).
127 // The new matrix should have the same sparsity structure.
128 //
129 // The solve will start from the previous optimal solution, which might not be
130 // a good starting point given the new objective matrix. If that's the
131 // case, one can call SetWarmStart with zero vectors to reset the state of the
132 // solver.
133 absl::Status UpdateObjectiveMatrix(
134 const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>&
135 objective_matrix);
136
137 // Updates the elements of matrix the constraint matrix A.
138 // The new matrix should have the same sparsity structure.
139 absl::Status UpdateConstraintMatrix(
140 const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>&
141 constraint_matrix);
142
143 // Combines call of UpdateObjectiveMatrix and UpdateConstraintMatrix.
144 absl::Status UpdateObjectiveAndConstraintMatrices(
145 const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>&
146 objective_matrix,
147 const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>&
148 constraint_matrix);
149
150 // Returns true if Init() has been called successfully.
151 bool IsInitialized() const { return workspace_ != nullptr; }
152
153 // Solves the instance by calling osqp_solve(). CHECK-fails if IsInitialized()
154 // is false.
155 ABSL_MUST_USE_RESULT OsqpExitCode Solve();
156
157 // The number of iterations taken. CHECK-fails if IsInitialized() is false.
158 c_int iterations() const;
159
160 // The objective value of the primal solution. CHECK-fails if IsInitialized()
161 // is false.
162 double objective_value() const;
163
164 // The primal solution, i.e., x. The Map is valid only for the lifetime of
165 // the OSQP workspace. It will be invalidated by a call to Init() or if the
166 // OsqpSolver is deleted. CHECK-fails if IsInitialized() is false.
167 // Implementation details (do not depend on these): The underlying memory is
168 // overwritten by SetPrimalWarmStart(). Modification of the problem data does
169 // not destroy the solution.
170 Eigen::Map<const Eigen::VectorXd> primal_solution() const;
171
172 // The vector of lagrange multipliers on the linear constraints. The Map is
173 // valid only for the lifetime of the OSQP workspace. It will be invalidated
174 // by a call to Init() or if the OsqpSolver is deleted. CHECK-fails if
175 // IsInitialized() is false. Implementation details (do not depend on these):
176 // The underlying memory is overwritten by SetDualWarmStart(). Modification of
177 // the problem data does not destroy the solution.
178 Eigen::Map<const Eigen::VectorXd> dual_solution() const;
179
180 // The primal infeasibility certificate. It is valid to query this only if
181 // Solve() returns kPrimalInfeasible or kPrimalInfeasibleInaccurate. The
182 // Map is valid only for the lifetime of the OSQP workspace. It will be
183 // invalidated by a call to Init() or of the OsqpSolver is deleted.
184 Eigen::Map<const Eigen::VectorXd> primal_infeasibility_certificate() const;
185
186 // TODO(ml): Implement dual_infeasibility_certificate.
187
188 // Sets a primal and dual warm-start for the next solve. Equivalent to
189 // SetPrimalWarmStart(primal_vector) and SetDualWarmStart(dual_vector).
190 // Returns:
191 // - FailedPreconditionError if IsInitialized() is false
192 // - InvalidArgumentError if the vectors do not have expected dimensions
193 // - UnknownError if the internal OSQP call fails
194 // - OkStatus on success
195 absl::Status SetWarmStart(
196 const Eigen::Ref<const Eigen::VectorXd>& primal_vector,
197 const Eigen::Ref<const Eigen::VectorXd>& dual_vector);
198
199 // Sets a warm-start for the primal iterate for the next solve. Use a vector
200 // of zeros to reset to the default initialization.
201 // - FailedPreconditionError if IsInitialized() is false
202 // - InvalidArgumentError if the vector does not have expected dimensions
203 // - UnknownError if the internal OSQP call fails
204 // - OkStatus on success
205 absl::Status SetPrimalWarmStart(
206 const Eigen::Ref<const Eigen::VectorXd>& primal_vector);
207
208 // Sets a warm-start for the dual iterate for the next solve. Use a vector
209 // of zeros to reset to the default initialization.
210 // - FailedPreconditionError if IsInitialized() is false
211 // - InvalidArgumentError if the vector does not have expected dimensions
212 // - UnknownError if the internal OSQP call fails
213 // - OkStatus on success
214 absl::Status SetDualWarmStart(
215 const Eigen::Ref<const Eigen::VectorXd>& dual_vector);
216
217 // Sets the objective vector for the next solve. Returns:
218 // - FailedPreconditionError if IsInitialized() is false
219 // - InvalidArgumentError if the vectors do not have expected dimensions
220 // - UnknownError if the internal OSQP call fails
221 // - OkStatus on success
222 absl::Status SetObjectiveVector(
223 const Eigen::Ref<const Eigen::VectorXd>& objective_vector);
224
225 // Sets the lower_bounds and upper_bounds vectors for the next solve. Returns:
226 // - FailedPreconditionError if IsInitialized() is false
227 // - InvalidArgumentError if the vectors do not have expected dimensions
228 // - InvalidArgumentError if lower_bounds[i] > upper_bounds[i] for some i
229 // - UnknownError if the internal OSQP call fails
230 // - OkStatus on success
231 absl::Status SetBounds(const Eigen::Ref<const Eigen::VectorXd>& lower_bounds,
232 const Eigen::Ref<const Eigen::VectorXd>& upper_bounds);
233
234 // Gets the current value of the rho setting, i.e., the ADMM rho step. Returns
235 // a FailedPreconditionError if IsInitialized() is false.
236 absl::StatusOr<double> GetRho() const;
237
238 // Gets the current value of the sigma setting, i.e., the ADMM sigma step.
239 // Returns a FailedPreconditionError if IsInitialized() is false.
240 absl::StatusOr<double> GetSigma() const;
241
242 // Gets the current value of the scaling setting, i.e., the number of
243 // heuristic scaling iterations. Returns a FailedPreconditionError if
244 // IsInitialized() is false.
245 absl::StatusOr<c_int> GetScaling() const;
246
247 // Gets the current value of the adaptive_rho setting, i.e., whether the rho
248 // step size is adaptively set. Returns a FailedPreconditionError if
249 // IsInitialized() is false.
250 absl::StatusOr<bool> GetAdaptiveRho() const;
251
252 // Gets the current value of the adaptive_rho_interval setting, i.e., the
253 // number of iterations between rho adaptations. Returns a
254 // FailedPreconditionError if IsInitialized() is false.
255 absl::StatusOr<c_int> GetAdaptiveRhoInterval() const;
256
257 // Gets the current value of the adaptive_rho_tolerance setting, i.e., the
258 // tolerance X for adapting rho (the new value must be X times larger, or 1/X
259 // times smaller, than the current value). Returns a FailedPreconditionError
260 // if IsInitialized() is false.
261 absl::StatusOr<double> GetAdaptiveRhoTolerance() const;
262
263 // Gets the current value of the adaptive_rho_fraction setting, i.e., in
264 // automatic mode (adaptive_rho_interval = 0), what fraction of setup time is
265 // spent on selecting rho. Returns a FailedPreconditionError if
266 // IsInitialized() is false.
267 absl::StatusOr<double> GetAdaptiveRhoFraction() const;
268
269 // Gets the current value of the max_iter setting, i.e., the maximum number of
270 // iterations. Returns a FailedPreconditionError if IsInitialized() is false.
271 absl::StatusOr<c_int> GetMaxIter() const;
272
273 // Gets the current value of the eps_abs setting, i.e., the absolute error
274 // tolerance for convergence. Returns a FailedPreconditionError if
275 // IsInitialized() is false.
276 absl::StatusOr<double> GetEpsAbs() const;
277
278 // Gets the current value of the eps_rel setting, i.e., the relative error
279 // tolerance for convergence. Returns a FailedPreconditionError if
280 // IsInitialized() is false.
281 absl::StatusOr<double> GetEpsRel() const;
282
283 // Gets the current value of the eps_prim_inf setting, i.e., the absolute
284 // error tolerance for primal infeasibility. Returns a FailedPreconditionError
285 // if IsInitialized() is false.
286 absl::StatusOr<double> GetEpsPrimInf() const;
287
288 // Gets the current value of the eps_dual_inf setting, i.e., the absolute
289 // error tolerance for dual infeasibility. Returns a FailedPreconditionError
290 // if IsInitialized() is false.
291 absl::StatusOr<double> GetEpsDualInf() const;
292
293 // Gets the current value of the alpha setting, i.e., the ADMM overrelaxation
294 // parameter. Returns a FailedPreconditionError if IsInitialized() is false.
295 absl::StatusOr<double> GetAlpha() const;
296
297 // Gets the current value of the delta setting, i.e., the polishing
298 // regularization parameter. Returns a FailedPreconditionError if
299 // IsInitialized() is false.
300 absl::StatusOr<double> GetDelta() const;
301
302 // Gets the current value of the polish setting, i.e., whether polishing is
303 // performed. Returns a FailedPreconditionError if IsInitialized() is false.
304 absl::StatusOr<bool> GetPolish() const;
305
306 // Gets the current value of the polish_refine_iter setting, i.e., the number
307 // of refinement iterations in polishing. Returns a FailedPreconditionError if
308 // IsInitialized() is false.
309 absl::StatusOr<c_int> GetPolishRefineIter() const;
310
311 // Gets the current value of the verbose setting, i.e., whether solver output
312 // is printed. Returns a FailedPreconditionError if IsInitialized() is false.
313 absl::StatusOr<bool> GetVerbose() const;
314
315 // Gets the current value of the scaled_termination setting, i.e., whether
316 // scaled termination criteria is used. Returns a FailedPreconditionError if
317 // IsInitialized() is false.
318 absl::StatusOr<bool> GetScaledTermination() const;
319
320 // Gets the current value of the check_termination setting, i.e., the interval
321 // for checking termination. Returns a FailedPreconditionError if
322 // IsInitialized() is false.
323 absl::StatusOr<c_int> GetCheckTermination() const;
324
325 // Gets the current value of the warm_start setting, i.e., if warm starting is
326 // performed. Returns a FailedPreconditionError if IsInitialized() is false.
327 absl::StatusOr<bool> GetWarmStart() const;
328
329 // Gets the current value of the time_limit setting, i.e., the time limit as
330 // expressed in seconds. Returns a FailedPreconditionError if IsInitialized()
331 // is false.
332 absl::StatusOr<double> GetTimeLimit() const;
333
334 // Updates the rho setting, i.e., the ADMM rho step. Returns:
335 // - FailedPreconditionError if IsInitialized() is false
336 // - InvalidArgumentError if rho_new <= 0.0
337 // - OkStatus on success
338 absl::Status UpdateRho(double rho_new);
339
340 // Updates the max_iter setting, i.e., the maximum number of iterations.
341 // Returns:
342 // - FailedPreconditionError if IsInitialized() is false
343 // - InvalidArgumentError if max_iter_new <= 0
344 // - OkStatus on success
345 absl::Status UpdateMaxIter(int max_iter_new);
346
347 // Updates the eps_abs setting, i.e., the absolute error tolerance for
348 // convergence. Returns:
349 // - FailedPreconditionError if IsInitialized() is false
350 // - InvalidArgumentError if eps_abs_new < 0.0
351 // - OkStatus on success
352 absl::Status UpdateEpsAbs(double eps_abs_new);
353
354 // Updates the eps_rel setting, i.e., the relative error tolerance for
355 // convergence. Returns:
356 // - FailedPreconditionError if IsInitialized() is false
357 // - InvalidArgumentError if eps_rel_new < 0.0
358 // - OkStatus on success
359 absl::Status UpdateEpsRel(double eps_rel_new);
360
361 // Updates the eps_prim_inf setting, i.e., the absolute error tolerance for
362 // primal infeasibility. Returns:
363 // - FailedPreconditionError if IsInitialized() is false
364 // - InvalidArgumentError if eps_prim_inf_new < 0.0
365 // - OkStatus on success
366 absl::Status UpdateEpsPrimInf(double eps_prim_inf_new);
367
368 // Updates the eps_dual_inf setting, i.e., the absolute error tolerance for
369 // dual infeasibility. Returns:
370 // - FailedPreconditionError if IsInitialized() is false
371 // - InvalidArgumentError if eps_dual_inf_new < 0.0
372 // - OkStatus on success
373 absl::Status UpdateEpsDualInf(double eps_dual_inf_new);
374
375 // Updates the alpha setting, i.e., the ADMM overrelaxation parameter.
376 // Returns:
377 // - FailedPreconditionError if IsInitialized() is false
378 // - InvalidArgumentError if !(0 < alpha_new < 2)
379 // - OkStatus on success
380 absl::Status UpdateAlpha(double alpha_new);
381
382 // Updates the delta setting, i.e., the polishing regularization parameter.
383 // Returns:
384 // - FailedPreconditionError if IsInitialized() is false
385 // - InvalidArgumentError if delta_new <= 0.0
386 // - OkStatus on success
387 absl::Status UpdateDelta(double delta_new);
388
389 // Updates the polish setting, i.e., whether polishing is performed. Returns:
390 // - FailedPreconditionError if IsInitialized() is false
391 // - OkStatus on success
392 absl::Status UpdatePolish(bool polish_new);
393
394 // Updates the polish_refine_iter setting, i.e., the number of refinement
395 // iterations in polishing. Returns:
396 // - FailedPreconditionError if IsInitialized() is false
397 // - InvalidArgumentError if polish_refine_iter_new <= 0.0
398 // - OkStatus on success
399 absl::Status UpdatePolishRefineIter(int polish_refine_iter_new);
400
401 // Updates the verbose setting, i.e., whether solver output is printed.
402 // Returns:
403 // - FailedPreconditionError if IsInitialized() is false
404 // - OkStatus on success
405 absl::Status UpdateVerbose(bool verbose_new);
406
407 // Updates the scaled_termination setting, i.e., whether scaled termination
408 // criteria is used. Returns:
409 // - FailedPreconditionError if IsInitialized() is false
410 // - OkStatus on success
411 absl::Status UpdateScaledTermination(bool scaled_termination_new);
412
413 // Updates the check_termination setting, i.e., the interval for checking
414 // termination. Setting to zero disables termination checking. Returns:
415 // - FailedPreconditionError if IsInitialized() is false
416 // - InvalidArgumentError if check_termination_new < 0.0
417 // - OkStatus on success
418 absl::Status UpdateCheckTermination(c_int check_termination_new);
419
420 // Updates the warm_start setting, i.e., whether warm starting is performed.
421 // Returns:
422 // - FailedPreconditionError if IsInitialized() is false
423 // - OkStatus on success
424 absl::Status UpdateWarmStart(bool warm_start_new);
425
426 // Updates the time_limit setting, i.e., the time limit as expressed in
427 // seconds. Setting the time limit to zero disables time-limiting. Returns:
428 // - FailedPreconditionError if IsInitialized() is false
429 // - InvalidArgumentError if time_limit_new < 0.0
430 // - OkStatus on success
431 absl::Status UpdateTimeLimit(double time_limit_new);
432
433 private:
434 struct OsqpDeleter {
435 void operator()(OSQPWorkspaceHelper* workspace) const;
436 };
437
438 std::unique_ptr<OSQPWorkspaceHelper, OsqpDeleter> workspace_;
439};
440
441} // namespace osqp
442
443#endif // OSQP_CPP_H_