blob: 5fa119eaf44b6c4848848beab6321cace91e6360 [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#include <algorithm>
16#include <cassert>
17#include <string>
18#include <type_traits>
19
20#include "absl/status/status.h"
21#include "absl/status/statusor.h"
22#include "absl/strings/str_cat.h"
23#include "absl/strings/str_format.h"
24#include "absl/strings/string_view.h"
25#include "Eigen/Core"
26#include "Eigen/SparseCore"
27#include "ctrlc.h"
28#include "osqp.h"
29#include "osqp++.h"
30
31// Fails to compile if OSQP's typedefs change. This lets us avoid including
32// osqp.h in osqp++.h.
33static_assert(
34 std::is_same_v<osqp::c_int, ::c_int>,
35 "OSQP's c_int typedef does not match the definition in osqp++.h.");
36static_assert(std::is_same_v<c_float, double>,
37 "OSQP's c_float typedef is unexpectedly not the same as double");
38
39static_assert(sizeof(OSQPSettings) == 176,
40 "The size of OSQPSettings has changed unexpectedly. Make sure "
41 "that the map between ::OSQPSettings and osqp::OsqpSettings "
42 "remains up to date.");
43
44namespace osqp {
45
46namespace {
47
48using ::Eigen::Map;
49using ::Eigen::Ref;
50using ::Eigen::VectorXd;
51
52// The mapping between ::OSQPSettings and osqp::OsqpSettings is maintained
53// manually and may need to be updated for new releases of OSQP.
54
55void CopyFromInternalSettings(const ::OSQPSettings& osqp_settings,
56 OsqpSettings* settings) {
57 settings->rho = osqp_settings.rho;
58 settings->sigma = osqp_settings.sigma;
59 settings->scaling = osqp_settings.scaling;
60 settings->adaptive_rho = osqp_settings.adaptive_rho;
61 settings->adaptive_rho_interval = osqp_settings.adaptive_rho_interval;
62 settings->adaptive_rho_tolerance = osqp_settings.adaptive_rho_tolerance;
63 settings->adaptive_rho_fraction = osqp_settings.adaptive_rho_fraction;
64 settings->max_iter = osqp_settings.max_iter;
65 settings->eps_abs = osqp_settings.eps_abs;
66 settings->eps_rel = osqp_settings.eps_rel;
67 settings->eps_prim_inf = osqp_settings.eps_prim_inf;
68 settings->eps_dual_inf = osqp_settings.eps_dual_inf;
69 settings->alpha = osqp_settings.alpha;
70 settings->delta = osqp_settings.delta;
71 settings->polish = osqp_settings.polish;
72 settings->polish_refine_iter = osqp_settings.polish_refine_iter;
73 settings->verbose = osqp_settings.verbose;
74 settings->scaled_termination = osqp_settings.scaled_termination;
75 settings->check_termination = osqp_settings.check_termination;
76 settings->warm_start = osqp_settings.warm_start;
77 settings->time_limit = osqp_settings.time_limit;
78}
79
80::OSQPSettings ToInternalSettings(const OsqpSettings& settings) {
81 OSQPSettings osqp_settings;
82 osqp_settings.rho = settings.rho;
83 osqp_settings.sigma = settings.sigma;
84 osqp_settings.scaling = settings.scaling;
85 osqp_settings.adaptive_rho = settings.adaptive_rho;
86 osqp_settings.adaptive_rho_interval = settings.adaptive_rho_interval;
87 osqp_settings.adaptive_rho_tolerance = settings.adaptive_rho_tolerance;
88 osqp_settings.adaptive_rho_fraction = settings.adaptive_rho_fraction;
89 osqp_settings.max_iter = settings.max_iter;
90 osqp_settings.eps_abs = settings.eps_abs;
91 osqp_settings.eps_rel = settings.eps_rel;
92 osqp_settings.eps_prim_inf = settings.eps_prim_inf;
93 osqp_settings.eps_dual_inf = settings.eps_dual_inf;
94 osqp_settings.alpha = settings.alpha;
95 osqp_settings.delta = settings.delta;
96 osqp_settings.polish = settings.polish;
97 osqp_settings.polish_refine_iter = settings.polish_refine_iter;
98 osqp_settings.verbose = settings.verbose;
99 osqp_settings.scaled_termination = settings.scaled_termination;
100 osqp_settings.check_termination = settings.check_termination;
101 osqp_settings.warm_start = settings.warm_start;
102 osqp_settings.time_limit = settings.time_limit;
103 osqp_settings.linsys_solver = ::QDLDL_SOLVER;
104 return osqp_settings;
105}
106
107} // namespace
108
109OsqpSettings::OsqpSettings() {
110 ::OSQPSettings osqp_settings;
111 osqp_set_default_settings(&osqp_settings);
112 CopyFromInternalSettings(osqp_settings, this);
113}
114
115struct OSQPWorkspaceHelper : public ::OSQPWorkspace {};
116
117void OsqpSolver::OsqpDeleter::operator()(OSQPWorkspaceHelper* workspace) const {
118 osqp_cleanup(workspace);
119}
120
121// OSQP_HANDLE_EXITCODE(x) expands to 'case x: return "x"'. Using this macro
122// prevents typos in the strings.
123#define OSQP_HANDLE_EXITCODE(x) \
124 case x: \
125 return #x
126
127std::string ToString(OsqpExitCode exitcode) {
128 switch (exitcode) {
129 OSQP_HANDLE_EXITCODE(OsqpExitCode::kOptimal);
130 OSQP_HANDLE_EXITCODE(OsqpExitCode::kPrimalInfeasible);
131 OSQP_HANDLE_EXITCODE(OsqpExitCode::kDualInfeasible);
132 OSQP_HANDLE_EXITCODE(OsqpExitCode::kOptimalInaccurate);
133 OSQP_HANDLE_EXITCODE(OsqpExitCode::kPrimalInfeasibleInaccurate);
134 OSQP_HANDLE_EXITCODE(OsqpExitCode::kDualInfeasibleInaccurate);
135 OSQP_HANDLE_EXITCODE(OsqpExitCode::kMaxIterations);
136 OSQP_HANDLE_EXITCODE(OsqpExitCode::kInterrupted);
137 OSQP_HANDLE_EXITCODE(OsqpExitCode::kTimeLimitReached);
138 OSQP_HANDLE_EXITCODE(OsqpExitCode::kNonConvex);
139 OSQP_HANDLE_EXITCODE(OsqpExitCode::kUnknown);
140 }
141 return "Unknown exit code";
142}
143
144#undef OSQP_HANDLE_EXITCODE
145
146namespace {
147
148absl::Status CheckDimensions(const int left_value, const int right_value,
149 absl::string_view left_name,
150 absl::string_view right_name) {
151 if (left_value != right_value) {
152 return absl::InvalidArgumentError(
153 absl::StrCat("Dimension mismatch: ", left_name, " (= ", left_value,
154 ") must equal ", right_name, " (= ", right_value, ")."));
155 } else {
156 return absl::OkStatus();
157 }
158}
159
160} // namespace
161
162#define OSQP_CHECK_DIMENSIONS(left_value, right_value) \
163 CheckDimensions((left_value), (right_value), #left_value, #right_value)
164
165#define OSQP_RETURN_IF_ERROR(expr) \
166 { \
167 const absl::Status result = expr; \
168 if (!result.ok()) return result; \
169 }
170
171#define OSQP_CHECK(expr) assert(expr)
172
173absl::Status OsqpSolver::Init(const OsqpInstance& instance,
174 const OsqpSettings& settings) {
175 if (!instance.objective_matrix.isCompressed()) {
176 return absl::InvalidArgumentError(
177 "objective_matrix must be compressed (call makeCompressed()).");
178 }
179 if (!instance.constraint_matrix.isCompressed()) {
180 return absl::InvalidArgumentError(
181 "constraint_matrix must be compressed (call makeCompressed()).");
182 }
183 const c_int num_variables = instance.num_variables();
184 const c_int num_constraints = instance.num_constraints();
185
186 OSQP_RETURN_IF_ERROR(
187 OSQP_CHECK_DIMENSIONS(instance.objective_matrix.cols(), num_variables));
188 OSQP_RETURN_IF_ERROR(
189 OSQP_CHECK_DIMENSIONS(instance.objective_matrix.rows(), num_variables));
190 OSQP_RETURN_IF_ERROR(
191 OSQP_CHECK_DIMENSIONS(instance.objective_vector.size(), num_variables));
192 OSQP_RETURN_IF_ERROR(
193 OSQP_CHECK_DIMENSIONS(instance.lower_bounds.size(), num_constraints));
194 OSQP_RETURN_IF_ERROR(
195 OSQP_CHECK_DIMENSIONS(instance.upper_bounds.size(), num_constraints));
196
197 // Clip bounds using OSQP_INFTY. Failing to do this causes subtle convergence
198 // issues instead of producing explicit errors (e.g., the
199 // DetectsPrimalInfeasible test fails). The osqp-python interface also clips
200 // the bounds in the same way.
201 VectorXd clipped_lower_bounds = instance.lower_bounds.cwiseMax(-OSQP_INFTY);
202 VectorXd clipped_upper_bounds = instance.upper_bounds.cwiseMin(OSQP_INFTY);
203
204 // OSQP copies all the data, so it's okay to discard this struct after
205 // osqp_setup. It also does not modify the input data (note osqp_setup takes a
206 // const OSQPData*). const_cast is needed here to fill in the input data
207 // structures.
208 OSQPData data;
209 data.n = num_variables;
210 data.m = num_constraints;
211
212 // TODO(ml): This copy could be avoided if the matrix is already upper
213 // triangular.
214 Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>
215 objective_matrix_upper_triangle =
216 instance.objective_matrix.triangularView<Eigen::Upper>();
217
218 // OSQP's csc struct represents sparse matrices in compressed sparse column
219 // (CSC) format and (confusingly) triplet format. osqp_setup() assumes the
220 // input is provided in CSC format. The mapping from Eigen::SparseMatrix's
221 // outerIndexPtr(), innerIndexPtr(), and valuePtr() is direct because we
222 // require the sparse matrices to be compressed and follow the Eigen::ColMajor
223 // storage scheme. For further background, see
224 // https://eigen.tuxfamily.org/dox/group__TutorialSparse.html for the
225 // description of CSC format in Eigen and osqp/include/types.h for the
226 // definition of OSQP's csc struct.
227 ::csc objective_matrix = {
228 objective_matrix_upper_triangle.outerIndexPtr()[num_variables],
229 num_variables,
230 num_variables,
231 const_cast<c_int*>(objective_matrix_upper_triangle.outerIndexPtr()),
232 const_cast<c_int*>(objective_matrix_upper_triangle.innerIndexPtr()),
233 const_cast<double*>(objective_matrix_upper_triangle.valuePtr()),
234 -1};
235 data.P = &objective_matrix;
236
237 ::csc constraint_matrix = {
238 instance.constraint_matrix.outerIndexPtr()[num_variables],
239 num_constraints,
240 num_variables,
241 const_cast<c_int*>(instance.constraint_matrix.outerIndexPtr()),
242 const_cast<c_int*>(instance.constraint_matrix.innerIndexPtr()),
243 const_cast<double*>(instance.constraint_matrix.valuePtr()),
244 -1};
245 data.A = &constraint_matrix;
246
247 data.q = const_cast<double*>(instance.objective_vector.data());
248 data.l = clipped_lower_bounds.data();
249 data.u = clipped_upper_bounds.data();
250
251 ::OSQPSettings osqp_settings = ToInternalSettings(settings);
252
253 OSQPWorkspace* workspace = nullptr;
254 const int return_code = osqp_setup(&workspace, &data, &osqp_settings);
255 workspace_.reset(static_cast<OSQPWorkspaceHelper*>(workspace));
256 if (return_code == 0) {
257 return absl::OkStatus();
258 }
259 switch (static_cast<osqp_error_type>(return_code)) {
260 case OSQP_DATA_VALIDATION_ERROR:
261 return absl::InvalidArgumentError(
262 "Unable to initialize OSQP: data validation error.");
263 case OSQP_SETTINGS_VALIDATION_ERROR:
264 return absl::InvalidArgumentError(
265 "Unable to initialize OSQP: invalid settings.");
266 case OSQP_LINSYS_SOLVER_LOAD_ERROR:
267 // This should never happen because qdldl is statically linked in.
268 return absl::UnknownError(
269 "Unable to initialize OSQP: unable to load linear solver.");
270 case OSQP_LINSYS_SOLVER_INIT_ERROR:
271 return absl::UnknownError(
272 "Unable to initialize OSQP: unable to initialize linear solver.");
273 case OSQP_NONCVX_ERROR:
274 return absl::InvalidArgumentError(
275 "Unable to initialize OSQP: the problem appears non-convex.");
276 case OSQP_MEM_ALLOC_ERROR:
277 return absl::UnknownError(
278 "Unable to initialize OSQP: memory allocation error.");
279 case OSQP_WORKSPACE_NOT_INIT_ERROR:
280 return absl::UnknownError(
281 "Unable to initialize OSQP: workspace not initialized.");
282 }
283 return absl::UnknownError(
284 "Unable to initialize OSQP: unrecognized error code.");
285}
286
287namespace {
288
289absl::Status VerifySameSparsity(
290 const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>& new_matrix,
291 const csc* ref_matrix, size_t num_variables) {
292 if (new_matrix.nonZeros() != ref_matrix->p[num_variables]) {
293 return absl::InvalidArgumentError(
294 "The new new matrix should have the same number of non-zero "
295 "elements.");
296 }
297
298 for (size_t i = 0; i < num_variables; ++i) {
299 if (ref_matrix->p[i] != new_matrix.outerIndexPtr()[i]) {
300 return absl::InvalidArgumentError(
301 "Sparsity of the new matrix differs from the previously "
302 "defined matrix.");
303 }
304 }
305
306 for (size_t i = 0; i < new_matrix.nonZeros(); ++i) {
307 if (ref_matrix->i[i] != new_matrix.innerIndexPtr()[i]) {
308 return absl::InvalidArgumentError(
309 "Sparsity of the new matrix differs from the previously "
310 "defined matrix.");
311 }
312 }
313
314 return absl::OkStatus();
315}
316
317// Helper function for calling osqp_update_P with an upper triangular objective
318// matrix. Assumes objective_matrix_upper_triangle is always upper triangular.
319absl::Status UpdateUpperTriangularObjectiveMatrix(
320 const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>&
321 objective_matrix_upper_triangle,
322 OSQPWorkspaceHelper* workspace) {
323 const c_int num_variables = workspace->data->n;
324
325 if (objective_matrix_upper_triangle.rows() !=
326 objective_matrix_upper_triangle.cols() ||
327 objective_matrix_upper_triangle.rows() != num_variables) {
328 return absl::InvalidArgumentError(absl::StrFormat(
329 "The new objective matrix should be square with dimension equal to the "
330 "number of variables. Matrix dimensions: %d x %d, num_variables=%d",
331 objective_matrix_upper_triangle.rows(),
332 objective_matrix_upper_triangle.cols(), num_variables));
333 }
334
335 OSQP_RETURN_IF_ERROR(VerifySameSparsity(objective_matrix_upper_triangle,
336 workspace->data->P, num_variables));
337
338 c_int nnzP = objective_matrix_upper_triangle.nonZeros();
339
340 const int return_code = osqp_update_P(
341 workspace, objective_matrix_upper_triangle.valuePtr(), OSQP_NULL, nnzP);
342 if (return_code == 0) {
343 return absl::OkStatus();
344 }
345 return absl::UnknownError(
346 "Unable to update OSQP P matrix: unrecognized error code.");
347}
348
349// Helper function for calling osqp_update_P_A with an upper triangular
350// objective matrix. Assumes objective_matrix_upper_triangle is always upper
351// triangular.
352absl::Status UpdateUpperTriangularObjectiveMatrixAndConstraintMatrix(
353 const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>&
354 objective_matrix_upper_triangle,
355 const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>&
356 constraint_matrix,
357 OSQPWorkspaceHelper* workspace) {
358 const c_int num_variables = workspace->data->n;
359
360 if (objective_matrix_upper_triangle.rows() !=
361 objective_matrix_upper_triangle.cols() ||
362 objective_matrix_upper_triangle.rows() != num_variables) {
363 return absl::InvalidArgumentError(absl::StrFormat(
364 "The new objective matrix should be square with dimension equal to the "
365 "number of variables. Matrix dimensions: %d x %d, num_variables=%d",
366 objective_matrix_upper_triangle.rows(),
367 objective_matrix_upper_triangle.cols(), num_variables));
368 }
369 if (constraint_matrix.cols() != num_variables) {
370 return absl::InvalidArgumentError(absl::StrFormat(
371 "The new constraint matrix should column size equal to the "
372 "number of variables. Matrix dimensions: %d x %d, num_variables=%d",
373 constraint_matrix.rows(), constraint_matrix.cols(), num_variables));
374 }
375
376 OSQP_RETURN_IF_ERROR(VerifySameSparsity(objective_matrix_upper_triangle,
377 workspace->data->P, num_variables));
378
379 c_int nnzP = objective_matrix_upper_triangle.nonZeros();
380
381 OSQP_RETURN_IF_ERROR(
382 VerifySameSparsity(constraint_matrix, workspace->data->A, num_variables));
383
384 c_int nnzA = constraint_matrix.nonZeros();
385
386 const int return_code = osqp_update_P_A(
387 workspace, objective_matrix_upper_triangle.valuePtr(), OSQP_NULL, nnzP,
388 constraint_matrix.valuePtr(), OSQP_NULL, nnzA);
389 if (return_code == 0) {
390 return absl::OkStatus();
391 }
392 return absl::UnknownError(
393 "Unable to update OSQP P and A matrix: unrecognized error code.");
394}
395
396// Returns true if the sparse matrix 'matrix' is upper triangular.
397bool IsUpperTriangular(
398 const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>& matrix) {
399 // Iterate through all non-zero elements, and ensure that their indices are
400 // only on the upper-right triangle, including the diagonal.
401 for (int i = 0; i < matrix.outerSize(); ++i) {
402 for (Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>::InnerIterator it(
403 matrix, i);
404 it; ++it) {
405 if (it.col() < it.row()) {
406 return false;
407 }
408 }
409 }
410 return true;
411}
412
413} // namespace
414
415absl::Status OsqpSolver::UpdateObjectiveMatrix(
416 const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>&
417 objective_matrix) {
418 // If the objective matrix is already upper triangular, we can skip the
419 // temporary.
420 if (IsUpperTriangular(objective_matrix)) {
421 return UpdateUpperTriangularObjectiveMatrix(objective_matrix,
422 workspace_.get());
423 }
424
425 // If not upper triangular, make a temporary.
426 Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>
427 objective_matrix_upper_triangle =
428 objective_matrix.triangularView<Eigen::Upper>();
429 return UpdateUpperTriangularObjectiveMatrix(objective_matrix_upper_triangle,
430 workspace_.get());
431}
432
433absl::Status OsqpSolver::UpdateConstraintMatrix(
434 const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>&
435 constraint_matrix) {
436 const c_int num_variables = workspace_->data->n;
437
438 if (constraint_matrix.cols() != num_variables) {
439 return absl::InvalidArgumentError(absl::StrFormat(
440 "The new constraint matrix should column size equal to the "
441 "number of variables. Matrix dimensions: %d x %d, num_variables=%d",
442 constraint_matrix.rows(), constraint_matrix.cols(), num_variables));
443 }
444
445 OSQP_RETURN_IF_ERROR(VerifySameSparsity(constraint_matrix,
446 workspace_->data->A, num_variables));
447
448 c_int nnzA = constraint_matrix.nonZeros();
449
450 const int return_code = osqp_update_A(
451 workspace_.get(), constraint_matrix.valuePtr(), OSQP_NULL, nnzA);
452 if (return_code == 0) {
453 return absl::OkStatus();
454 }
455 return absl::UnknownError(
456 "Unable to update OSQP A matrix: unrecognized error code.");
457}
458
459absl::Status OsqpSolver::UpdateObjectiveAndConstraintMatrices(
460 const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>& objective_matrix,
461 const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>&
462 constraint_matrix) {
463 // If the objective matrix is already upper triangular, we can skip the
464 // temporary.
465 if (IsUpperTriangular(objective_matrix)) {
466 return UpdateUpperTriangularObjectiveMatrixAndConstraintMatrix(
467 objective_matrix, constraint_matrix, workspace_.get());
468 }
469
470 // If not upper triangular, make a temporary.
471 Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>
472 objective_matrix_upper_triangle =
473 objective_matrix.triangularView<Eigen::Upper>();
474 return UpdateUpperTriangularObjectiveMatrixAndConstraintMatrix(
475 objective_matrix_upper_triangle, constraint_matrix, workspace_.get());
476}
477
478namespace {
479OsqpExitCode StatusToExitCode(const c_int status_val) {
480 switch (status_val) {
481 case OSQP_SOLVED:
482 return OsqpExitCode::kOptimal;
483 case OSQP_SOLVED_INACCURATE:
484 return OsqpExitCode::kOptimalInaccurate;
485 case OSQP_PRIMAL_INFEASIBLE:
486 return OsqpExitCode::kPrimalInfeasible;
487 case OSQP_PRIMAL_INFEASIBLE_INACCURATE:
488 return OsqpExitCode::kPrimalInfeasibleInaccurate;
489 case OSQP_DUAL_INFEASIBLE:
490 return OsqpExitCode::kDualInfeasible;
491 case OSQP_DUAL_INFEASIBLE_INACCURATE:
492 return OsqpExitCode::kDualInfeasibleInaccurate;
493 case OSQP_MAX_ITER_REACHED:
494 return OsqpExitCode::kMaxIterations;
495 case OSQP_SIGINT:
496 return OsqpExitCode::kInterrupted;
497 case OSQP_TIME_LIMIT_REACHED:
498 return OsqpExitCode::kTimeLimitReached;
499 case OSQP_NON_CVX:
500 return OsqpExitCode::kNonConvex;
501 default:
502 return OsqpExitCode::kUnknown;
503 }
504}
505
506} // namespace
507
508OsqpExitCode OsqpSolver::Solve() {
509 OSQP_CHECK(IsInitialized());
510 if (osqp_solve(workspace_.get()) != 0) {
511 // From looking at the code, this can happen if the solve is interrupted
512 // with ctrl-c or if updating "rho" fails.
513 if (osqp_is_interrupted()) {
514 return OsqpExitCode::kInterrupted;
515 }
516 return OsqpExitCode::kUnknown;
517 }
518 return StatusToExitCode(workspace_->info->status_val);
519}
520
521c_int OsqpSolver::iterations() const {
522 OSQP_CHECK(IsInitialized());
523 return workspace_->info->iter;
524}
525
526double OsqpSolver::objective_value() const {
527 OSQP_CHECK(IsInitialized());
528 return workspace_->info->obj_val;
529}
530
531Map<const VectorXd> OsqpSolver::primal_solution() const {
532 OSQP_CHECK(IsInitialized());
533 return Map<const VectorXd>(workspace_->solution->x, workspace_->data->n);
534}
535
536Map<const VectorXd> OsqpSolver::dual_solution() const {
537 OSQP_CHECK(IsInitialized());
538 return Map<const VectorXd>(workspace_->solution->y, workspace_->data->m);
539}
540
541Map<const VectorXd> OsqpSolver::primal_infeasibility_certificate() const {
542 OSQP_CHECK(IsInitialized());
543 const OsqpExitCode exit_code = StatusToExitCode(workspace_->info->status_val);
544 OSQP_CHECK(exit_code == OsqpExitCode::kPrimalInfeasible ||
545 exit_code == OsqpExitCode::kPrimalInfeasibleInaccurate);
546 return Map<const VectorXd>(workspace_->delta_y, workspace_->data->m);
547}
548
549absl::Status OsqpSolver::SetWarmStart(const Ref<const VectorXd>& primal_vector,
550 const Ref<const VectorXd>& dual_vector) {
551 // This is identical to calling osqp_warm_start with both vectors at once.
552 OSQP_RETURN_IF_ERROR(SetPrimalWarmStart(primal_vector));
553 OSQP_RETURN_IF_ERROR(SetDualWarmStart(dual_vector));
554 return absl::OkStatus();
555}
556
557absl::Status OsqpSolver::SetPrimalWarmStart(
558 const Ref<const VectorXd>& primal_vector) {
559 if (!IsInitialized()) {
560 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
561 }
562 const c_int num_variables = workspace_->data->n;
563 OSQP_RETURN_IF_ERROR(
564 OSQP_CHECK_DIMENSIONS(primal_vector.size(), num_variables));
565
566 const int return_code =
567 osqp_warm_start_x(workspace_.get(), primal_vector.data());
568 if (return_code != 0) {
569 return absl::UnknownError("osqp_warm_start_x unexpectedly failed.");
570 }
571 return absl::OkStatus();
572}
573
574absl::Status OsqpSolver::SetDualWarmStart(
575 const Ref<const VectorXd>& dual_vector) {
576 if (!IsInitialized()) {
577 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
578 }
579 const c_int num_constraints = workspace_->data->m;
580 OSQP_RETURN_IF_ERROR(
581 OSQP_CHECK_DIMENSIONS(dual_vector.size(), num_constraints));
582
583 const int return_code =
584 osqp_warm_start_y(workspace_.get(), dual_vector.data());
585 if (return_code != 0) {
586 return absl::UnknownError("osqp_warm_start_y unexpectedly failed.");
587 }
588 return absl::OkStatus();
589}
590
591absl::Status OsqpSolver::SetObjectiveVector(
592 const Ref<const VectorXd>& objective_vector) {
593 if (!IsInitialized()) {
594 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
595 }
596 const c_int num_variables = workspace_->data->n;
597 OSQP_RETURN_IF_ERROR(
598 OSQP_CHECK_DIMENSIONS(objective_vector.size(), num_variables));
599
600 const int return_code =
601 osqp_update_lin_cost(workspace_.get(), objective_vector.data());
602 if (return_code != 0) {
603 return absl::UnknownError("osqp_update_lin_cost unexpectedly failed.");
604 }
605 return absl::OkStatus();
606}
607
608// NOTE(ml): osqp_update_lower_bound and osqp_update_upper_bound are not
609// exposed because they have confusing semantics. They immediately error if a
610// new set of bounds is inconsistent with the existing bounds on the other side.
611absl::Status OsqpSolver::SetBounds(const Ref<const VectorXd>& lower_bounds,
612 const Ref<const VectorXd>& upper_bounds) {
613 if (!IsInitialized()) {
614 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
615 }
616 const c_int num_constraints = workspace_->data->m;
617 OSQP_RETURN_IF_ERROR(
618 OSQP_CHECK_DIMENSIONS(lower_bounds.size(), num_constraints));
619 OSQP_RETURN_IF_ERROR(
620 OSQP_CHECK_DIMENSIONS(upper_bounds.size(), num_constraints));
621 // OSQP does this check internally, but we can return a better error message.
622 for (int i = 0; i < num_constraints; i++) {
623 if (lower_bounds[i] > upper_bounds[i]) {
624 return absl::InvalidArgumentError(
625 absl::StrCat("Inconsistent bounds at index ", i, ", ",
626 lower_bounds[i], " must be <= ", upper_bounds[i], "."));
627 }
628 }
629
630 const int return_code = osqp_update_bounds(
631 workspace_.get(), lower_bounds.data(), upper_bounds.data());
632 if (return_code != 0) {
633 return absl::UnknownError("osqp_update_bounds unexpectedly failed.");
634 }
635 return absl::OkStatus();
636}
637
638absl::StatusOr<double> OsqpSolver::GetRho() const {
639 if (!IsInitialized()) {
640 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
641 }
642 return workspace_->settings->rho;
643}
644
645absl::StatusOr<double> OsqpSolver::GetSigma() const {
646 if (!IsInitialized()) {
647 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
648 }
649 return workspace_->settings->sigma;
650}
651
652absl::StatusOr<c_int> OsqpSolver::GetScaling() const {
653 if (!IsInitialized()) {
654 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
655 }
656 return workspace_->settings->scaling;
657}
658
659absl::StatusOr<bool> OsqpSolver::GetAdaptiveRho() const {
660 if (!IsInitialized()) {
661 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
662 }
663 return workspace_->settings->adaptive_rho;
664}
665
666absl::StatusOr<c_int> OsqpSolver::GetAdaptiveRhoInterval() const {
667 if (!IsInitialized()) {
668 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
669 }
670 return workspace_->settings->adaptive_rho_interval;
671}
672
673absl::StatusOr<double> OsqpSolver::GetAdaptiveRhoTolerance() const {
674 if (!IsInitialized()) {
675 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
676 }
677 return workspace_->settings->adaptive_rho_tolerance;
678}
679
680absl::StatusOr<double> OsqpSolver::GetAdaptiveRhoFraction() const {
681 if (!IsInitialized()) {
682 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
683 }
684 return workspace_->settings->adaptive_rho_fraction;
685}
686
687absl::StatusOr<c_int> OsqpSolver::GetMaxIter() const {
688 if (!IsInitialized()) {
689 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
690 }
691 return workspace_->settings->max_iter;
692}
693
694absl::StatusOr<double> OsqpSolver::GetEpsAbs() const {
695 if (!IsInitialized()) {
696 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
697 }
698 return workspace_->settings->eps_abs;
699}
700
701absl::StatusOr<double> OsqpSolver::GetEpsRel() const {
702 if (!IsInitialized()) {
703 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
704 }
705 return workspace_->settings->eps_rel;
706}
707
708absl::StatusOr<double> OsqpSolver::GetEpsPrimInf() const {
709 if (!IsInitialized()) {
710 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
711 }
712 return workspace_->settings->eps_prim_inf;
713}
714
715absl::StatusOr<double> OsqpSolver::GetEpsDualInf() const {
716 if (!IsInitialized()) {
717 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
718 }
719 return workspace_->settings->eps_dual_inf;
720}
721
722absl::StatusOr<double> OsqpSolver::GetAlpha() const {
723 if (!IsInitialized()) {
724 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
725 }
726 return workspace_->settings->alpha;
727}
728
729absl::StatusOr<double> OsqpSolver::GetDelta() const {
730 if (!IsInitialized()) {
731 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
732 }
733 return workspace_->settings->delta;
734}
735
736absl::StatusOr<bool> OsqpSolver::GetPolish() const {
737 if (!IsInitialized()) {
738 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
739 }
740 return workspace_->settings->polish;
741}
742
743absl::StatusOr<c_int> OsqpSolver::GetPolishRefineIter() const {
744 if (!IsInitialized()) {
745 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
746 }
747 return workspace_->settings->polish_refine_iter;
748}
749
750absl::StatusOr<bool> OsqpSolver::GetVerbose() const {
751 if (!IsInitialized()) {
752 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
753 }
754 return workspace_->settings->verbose;
755}
756
757absl::StatusOr<bool> OsqpSolver::GetScaledTermination() const {
758 if (!IsInitialized()) {
759 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
760 }
761 return workspace_->settings->scaled_termination;
762}
763
764absl::StatusOr<c_int> OsqpSolver::GetCheckTermination() const {
765 if (!IsInitialized()) {
766 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
767 }
768 return workspace_->settings->check_termination;
769}
770
771absl::StatusOr<bool> OsqpSolver::GetWarmStart() const {
772 if (!IsInitialized()) {
773 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
774 }
775 return workspace_->settings->warm_start;
776}
777
778absl::StatusOr<double> OsqpSolver::GetTimeLimit() const {
779 if (!IsInitialized()) {
780 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
781 }
782 return workspace_->settings->time_limit;
783}
784
785absl::Status OsqpSolver::UpdateRho(const double rho_new) {
786 if (!IsInitialized()) {
787 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
788 }
789 if (rho_new <= 0) {
790 return absl::InvalidArgumentError(
791 absl::StrCat("Invalid rho value: ", rho_new));
792 }
793 if (osqp_update_rho(workspace_.get(), rho_new) != 0) {
794 return absl::UnknownError("osqp_update_rho unexpectedly failed.");
795 }
796 return absl::OkStatus();
797}
798
799absl::Status OsqpSolver::UpdateMaxIter(const int max_iter_new) {
800 if (!IsInitialized()) {
801 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
802 }
803 if (max_iter_new <= 0) {
804 return absl::InvalidArgumentError(
805 absl::StrCat("Invalid max_iter value: ", max_iter_new));
806 }
807 if (osqp_update_max_iter(workspace_.get(), max_iter_new) != 0) {
808 return absl::UnknownError("osqp_update_max_iter unexpectedly failed.");
809 }
810 return absl::OkStatus();
811}
812
813absl::Status OsqpSolver::UpdateEpsAbs(const double eps_abs_new) {
814 if (!IsInitialized()) {
815 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
816 }
817 if (eps_abs_new < 0.0) {
818 return absl::InvalidArgumentError(
819 absl::StrCat("Invalid eps_abs value: ", eps_abs_new));
820 }
821 if (osqp_update_eps_abs(workspace_.get(), eps_abs_new) != 0) {
822 return absl::UnknownError("osqp_update_eps_abs unexpectedly failed.");
823 }
824 return absl::OkStatus();
825}
826
827absl::Status OsqpSolver::UpdateEpsRel(const double eps_rel_new) {
828 if (!IsInitialized()) {
829 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
830 }
831 if (eps_rel_new < 0.0) {
832 return absl::InvalidArgumentError(
833 absl::StrCat("Invalid eps_rel value: ", eps_rel_new));
834 }
835 if (osqp_update_eps_rel(workspace_.get(), eps_rel_new) != 0) {
836 return absl::UnknownError("osqp_update_eps_rel unexpectedly failed.");
837 }
838 return absl::OkStatus();
839}
840
841absl::Status OsqpSolver::UpdateEpsPrimInf(const double eps_prim_inf_new) {
842 if (!IsInitialized()) {
843 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
844 }
845 if (eps_prim_inf_new < 0.0) {
846 return absl::InvalidArgumentError(
847 absl::StrCat("Invalid eps_prim_inf value: ", eps_prim_inf_new));
848 }
849 if (osqp_update_eps_prim_inf(workspace_.get(), eps_prim_inf_new) != 0) {
850 return absl::UnknownError("osqp_update_eps_prim_inf unexpectedly failed.");
851 }
852 return absl::OkStatus();
853}
854
855absl::Status OsqpSolver::UpdateEpsDualInf(const double eps_dual_inf_new) {
856 if (!IsInitialized()) {
857 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
858 }
859 if (eps_dual_inf_new < 0.0) {
860 return absl::InvalidArgumentError(
861 absl::StrCat("Invalid eps_dual_inf value: ", eps_dual_inf_new));
862 }
863 if (osqp_update_eps_dual_inf(workspace_.get(), eps_dual_inf_new) != 0) {
864 return absl::UnknownError("osqp_update_eps_dual_inf unexpectedly failed.");
865 }
866 return absl::OkStatus();
867}
868
869absl::Status OsqpSolver::UpdateAlpha(const double alpha_new) {
870 if (!IsInitialized()) {
871 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
872 }
873 if (alpha_new <= 0.0 || alpha_new >= 2) {
874 return absl::InvalidArgumentError(
875 absl::StrCat("Invalid alpha value: ", alpha_new));
876 }
877 if (osqp_update_alpha(workspace_.get(), alpha_new) != 0) {
878 return absl::UnknownError("osqp_update_alpha unexpectedly failed.");
879 }
880 return absl::OkStatus();
881}
882
883absl::Status OsqpSolver::UpdateDelta(const double delta_new) {
884 if (!IsInitialized()) {
885 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
886 }
887 if (delta_new <= 0.0) {
888 return absl::InvalidArgumentError(
889 absl::StrCat("Invalid delta value: ", delta_new));
890 }
891 if (osqp_update_delta(workspace_.get(), delta_new) != 0) {
892 return absl::UnknownError("osqp_update_delta unexpectedly failed.");
893 }
894 return absl::OkStatus();
895}
896
897absl::Status OsqpSolver::UpdatePolish(const bool polish_new) {
898 if (!IsInitialized()) {
899 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
900 }
901 if (osqp_update_polish(workspace_.get(), polish_new) != 0) {
902 return absl::UnknownError("osqp_update_polish unexpectedly failed.");
903 }
904 return absl::OkStatus();
905}
906
907absl::Status OsqpSolver::UpdatePolishRefineIter(
908 const int polish_refine_iter_new) {
909 if (!IsInitialized()) {
910 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
911 }
912 if (polish_refine_iter_new <= 0.0) {
913 return absl::InvalidArgumentError(absl::StrCat(
914 "Invalid polish_refine_iter value: ", polish_refine_iter_new));
915 }
916 if (osqp_update_polish_refine_iter(workspace_.get(),
917 polish_refine_iter_new) != 0) {
918 return absl::UnknownError(
919 "osqp_update_polish_refine_iter unexpectedly failed.");
920 }
921 return absl::OkStatus();
922}
923
924absl::Status OsqpSolver::UpdateVerbose(const bool verbose_new) {
925 if (!IsInitialized()) {
926 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
927 }
928 if (osqp_update_verbose(workspace_.get(), verbose_new) != 0) {
929 return absl::UnknownError("osqp_update_verbose unexpectedly failed.");
930 }
931 return absl::OkStatus();
932}
933
934absl::Status OsqpSolver::UpdateScaledTermination(
935 const bool scaled_termination_new) {
936 if (!IsInitialized()) {
937 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
938 }
939 if (osqp_update_scaled_termination(workspace_.get(),
940 scaled_termination_new) != 0) {
941 return absl::UnknownError(
942 "osqp_update_scaled_termination unexpectedly failed.");
943 }
944 return absl::OkStatus();
945}
946
947absl::Status OsqpSolver::UpdateCheckTermination(
948 const c_int check_termination_new) {
949 if (!IsInitialized()) {
950 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
951 }
952 if (check_termination_new < 0.0) {
953 return absl::InvalidArgumentError(absl::StrCat(
954 "Invalid check_termination value: ", check_termination_new));
955 }
956 if (osqp_update_check_termination(workspace_.get(), check_termination_new) !=
957 0) {
958 return absl::UnknownError(
959 "osqp_update_check_termination unexpectedly failed.");
960 }
961 return absl::OkStatus();
962}
963
964absl::Status OsqpSolver::UpdateWarmStart(const bool warm_start_new) {
965 if (!IsInitialized()) {
966 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
967 }
968 if (osqp_update_warm_start(workspace_.get(), warm_start_new) != 0) {
969 return absl::UnknownError("osqp_update_warm_start unexpectedly failed.");
970 }
971 return absl::OkStatus();
972}
973
974absl::Status OsqpSolver::UpdateTimeLimit(const double time_limit_new) {
975 if (!IsInitialized()) {
976 return absl::FailedPreconditionError("OsqpSolver is not initialized.");
977 }
978 if (time_limit_new < 0.0) {
979 return absl::InvalidArgumentError(
980 absl::StrCat("Invalid time_limit value: ", time_limit_new));
981 }
982 if (osqp_update_time_limit(workspace_.get(), time_limit_new) != 0) {
983 return absl::UnknownError("osqp_update_time_limit unexpectedly failed.");
984 }
985 return absl::OkStatus();
986}
987
988#undef OSQP_CHECK_DIMENSIONS
989#undef OSQP_RETURN_IF_ERROR
990#undef OSQP_CHECK
991
992} // namespace osqp