Squashed 'third_party/osqp-cpp/' content from commit 8cd904e2b4
Change-Id: Ic67c163724b33720d49822debd467a344121342e
git-subtree-dir: third_party/osqp-cpp
git-subtree-split: 8cd904e2b49c24dd41d11f8c6e0adb113dd5e26c
Signed-off-by: Austin Schuh <austin.linux@gmail.com>
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..9255ada
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,27 @@
+name: build
+on:
+ push:
+ pull_request:
+ release:
+
+jobs:
+ build:
+ name: Ubuntu latest
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Install dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install cmake libeigen3-dev
+ cmake --version
+
+ - name: Build and test
+ run: |
+ mkdir -p build
+ cd build
+ cmake ..
+ make
+ make test
+
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..16af8a8
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,96 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+cmake_minimum_required(VERSION 3.14)
+
+project(osqp-cpp)
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED True)
+
+# Options.
+option(OSQP-CPP_BUILD_TESTS
+ "Whether to build tests." ON
+)
+
+include(FetchContent)
+
+# ABSL
+if (NOT TARGET absl::strings OR NOT TARGET absl::status OR NOT TARGET absl::span)
+ message(STATUS "osqp-cpp: `absl` targets not found. Attempting to fetch contents...")
+ FetchContent_Declare(
+ abseil-cpp
+ GIT_REPOSITORY https://github.com/abseil/abseil-cpp.git
+ GIT_TAG origin/master
+ )
+ FetchContent_MakeAvailable(abseil-cpp)
+else()
+ message(STATUS "osqp-cpp: `absl` targets found.")
+endif()
+
+# EIGEN
+# Eigen is usually available as a system package, so we use find_package.
+if (NOT TARGET Eigen3::Eigen)
+ message(STATUS "osqp-cpp: `Eigen3` targets not found. Attempting to find package...")
+ find_package(Eigen3 3.3.7 REQUIRED NO_MODULE)
+else()
+ message(STATUS "osqp-cpp: `Eigen3` targets found.")
+endif()
+
+# OSQP
+if (NOT TARGET osqpstatic)
+ message(STATUS "osqp-cpp: `osqp` targets not found. Attempting to fetch contents...")
+ FetchContent_Declare(
+ osqp
+ GIT_REPOSITORY https://github.com/oxfordcontrol/osqp.git
+ GIT_TAG origin/master
+ )
+ FetchContent_MakeAvailable(osqp)
+else()
+ message(STATUS "osqp-cpp: `osqp` targets found.")
+endif()
+
+# Googletest
+if (OSQP-CPP_BUILD_TESTS)
+ enable_testing()
+ if (NOT TARGET gtest OR NOT TARGET gmock OR NOT TARGET gtest_main)
+ message(STATUS "osqp-cpp: `googletest` targets not found. Attempting to fetch contents...")
+ set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
+ FetchContent_Declare(
+ googletest
+ GIT_REPOSITORY https://github.com/google/googletest.git
+ GIT_TAG origin/main
+ )
+ FetchContent_MakeAvailable(googletest)
+ include(GoogleTest)
+ else()
+ message(STATUS "osqp-cpp: `googletest` targets found.")
+ endif()
+endif()
+
+message(STATUS "osqp-cpp: Adding osqp-cpp library...")
+add_library(osqp-cpp src/osqp++.cc)
+target_link_libraries(osqp-cpp PUBLIC absl::strings absl::status absl::statusor Eigen3::Eigen PRIVATE osqpstatic ${CMAKE_DL_LIBS})
+target_include_directories(osqp-cpp PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>")
+message(STATUS "osqp-cpp: Added osqp-cpp library.")
+
+# Build and register tests.
+if (OSQP-CPP_BUILD_TESTS)
+ message(STATUS "osqp-cpp: Adding osqp-cpp tests...")
+ add_executable(osqp_test test/osqp++_test.cc)
+ target_link_libraries(osqp_test gtest gmock gtest_main absl::status absl::span osqp-cpp)
+ gtest_discover_tests(osqp_test)
+ message(STATUS "osqp-cpp: Added osqp-cpp tests.")
+endif()
+
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..22b241c
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,29 @@
+# How to Contribute
+
+We'd love to accept your patches and contributions to this project. There are
+just a few small guidelines you need to follow.
+
+## Contributor License Agreement
+
+Contributions to this project must be accompanied by a Contributor License
+Agreement (CLA). You (or your employer) retain the copyright to your
+contribution; this simply gives us permission to use and redistribute your
+contributions as part of the project. Head over to
+<https://cla.developers.google.com/> to see your current agreements on file or
+to sign a new one.
+
+You generally only need to submit a CLA once, so if you've already submitted one
+(even if it was for a different project), you probably don't need to do it
+again.
+
+## Code reviews
+
+All submissions, including submissions by project members, require review. We
+use GitHub pull requests for this purpose. Consult
+[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
+information on using pull requests.
+
+## Community Guidelines
+
+This project follows
+[Google's Open Source Community Guidelines](https://opensource.google/conduct/).
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..7a4a3ea
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8e4c71a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,152 @@
+# osqp-cpp: A C++ wrapper for [OSQP](https://osqp.org/)
+
+A C++ wrapper for [OSQP](https://github.com/oxfordcontrol/osqp), an
+[ADMM](http://stanford.edu/~boyd/admm.html)-based solver for
+[quadratic programming](https://en.wikipedia.org/wiki/Quadratic_programming).
+
+Compared with OSQP's native C interface, the wrapper provides a more convenient
+input format using Eigen sparse matrices and handles the lifetime of the
+`OSQPWorkspace` struct. This package has similar functionality to
+[osqp-eigen](https://github.com/robotology/osqp-eigen).
+
+The full API is documented in-line in `osqp++.h`. We describe only the input
+format in this README.
+
+Note: OSQP uses looser default tolerances than other similar solvers. We
+recommend looking at the description of the convergence tolerances in Section
+3.4 of the OSQP [paper](https://arxiv.org/abs/1711.08013) and adjusting
+tolerances via the `OsqpSettings` struct as appropriate.
+
+This is not an officially supported Google product.
+
+## `OsqpInstance` format
+
+OSQP solves the convex quadratic optimization problem:
+
+```
+min_x 0.5 * x'Px + q'x
+s.t. l <= Ax <= u
+```
+
+where `P` is a symmetric positive semi-definite matrix.
+
+The inequalities are component-wise, and equalities may be enforced by setting
+`l[i] == u[i]` for some row `i`. Single-sided inequalities can be enforced by
+setting the lower or upper bounds to negative or positive infinity
+(`std::numeric_limits<double>::infinity()`), respectively.
+
+This maps to the `OsqpInstance` struct in `osqp++.h` as follows.
+
+- `objective_matrix` is `P`.
+- `objective_vector` is `q`.
+- `constraint_matrix` is `A`.
+- `lower_bounds` is `l`.
+- `upper_bounds` is `u`.
+
+## Example usage
+
+The code below formulates and solves the following 2-dimensional optimization
+problem:
+
+```
+min_(x,y) x^2 + 0.5 * x * y + y^2 + x
+s.t. x >= 1
+```
+
+```C++
+const double kInfinity = std::numeric_limits<double>::infinity();
+SparseMatrix<double> objective_matrix(2, 2);
+const Triplet<double> kTripletsP[] = {
+ {0, 0, 2.0}, {1, 0, 0.5}, {0, 1, 0.5}, {1, 1, 2.0}};
+objective_matrix.setFromTriplets(std::begin(kTripletsP),
+ std::end(kTripletsP));
+
+SparseMatrix<double> constraint_matrix(1, 2);
+const Triplet<double> kTripletsA[] = {{0, 0, 1.0}};
+constraint_matrix.setFromTriplets(std::begin(kTripletsA),
+ std::end(kTripletsA));
+
+OsqpInstance instance;
+instance.objective_matrix = objective_matrix;
+instance.objective_vector.resize(2);
+instance.objective_vector << 1.0, 0.0;
+instance.constraint_matrix = constraint_matrix;
+instance.lower_bounds.resize(1);
+instance.lower_bounds << 1.0;
+instance.upper_bounds.resize(1);
+instance.upper_bounds << kInfinity;
+
+OsqpSolver solver;
+OsqpSettings settings;
+// Edit settings if appropriate.
+auto status = solver.Init(instance, settings);
+// Assuming status.ok().
+OsqpExitCode exit_code = solver.Solve();
+// Assuming exit_code == OsqpExitCode::kOptimal.
+double optimal_objective = solver.objective_value();
+Eigen::VectorXd optimal_solution = solver.primal_solution();
+```
+
+## Installation (Unix)
+
+osqp-cpp requires CMake, a C++17 compiler, and the following packages:
+
+- [OSQP](https://github.com/oxfordcontrol/osqp) (compiled with 64-bit integers)
+- [abseil-cpp](https://github.com/abseil/abseil-cpp)
+- [Eigen](http://eigen.tuxfamily.org/index.php?title=Main_Page)
+- [googletest](https://github.com/google/googletest) (for testing only)
+
+On Debian/Ubuntu systems you may install Eigen via the `libeigen3-dev` package.
+
+osqp-cpp will attempt to automatically detect if the necessary targets exist as
+part of the same project. If the necessary `OSQP`, `abseil-cpp`, or `googletest`
+targets are not found, osqp-cpp will attempt to download the sources from their
+GitHub repositories through the use of CMake's `FetchContent` functionality. If
+the `Eigen3` targets are not found, osqp-cpp will attempt to find Eigen3 as a
+system package. To prevent osqp-cpp from unnecessarily downloading target
+dependencies, please ensure that any target dependencies that are already
+available are included before osqp-cpp.
+
+To build osqp-cpp, run the following from the `osqp-cpp` directory:
+
+```sh
+$ mkdir build; cd build
+$ cmake ..
+$ make
+$ make test
+```
+
+The interface is regularly tested only on Linux. Contributions to support and
+automatically test additional platforms are welcome.
+
+## Installation (Windows)
+
+*These instructions are maintained by the community.*
+
+Install prerequisite packages:
+
+```sh
+$ vcpkg install eigen3:x64-windows
+$ vcpkg install abseil:x64-windows
+$ vcpkg install gtest:x64-windows
+```
+
+Then, run the following from the `osqp-cpp` directory:
+
+```sh
+$ mkdir build; cd build
+$ cmake ..
+$ cmake --build .
+$ cd Debug
+```
+
+## FAQ
+
+- Is OSQP deterministic?
+ - No, not in its default configuration. Section 5.2 of the OSQP
+ [paper](https://arxiv.org/abs/1711.08013) describes that the update rule
+ for the step size rho depends on the ratio between the runtime of the
+ iterations and the runtime of the numerical factorization. Setting
+ `adaptive_rho` to `false` disables this update rule and makes OSQP
+ deterministic, but this could significantly slow down OSQP's
+ convergence.
diff --git a/include/osqp++.h b/include/osqp++.h
new file mode 100644
index 0000000..3f79fa9
--- /dev/null
+++ b/include/osqp++.h
@@ -0,0 +1,443 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef OSQP_CPP_H_
+#define OSQP_CPP_H_
+
+// A C++ wrapper for OSQP (https://osqp.org/). See README.md for an overview.
+
+#include <memory>
+#include <string>
+
+#include "absl/base/attributes.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "Eigen/Core"
+#include "Eigen/SparseCore"
+
+namespace osqp {
+
+// Must match the typedef in osqp/include/glob_opts.h (if not, it will trigger
+// a static_assert failure in osqp++.cc).
+using c_int = long long; // NOLINT
+
+// A memory-safe mirror of the OSQPData struct defined in osqp/include/types.h.
+// The number of variables and constraints is implied by the shape of
+// constraint_matrix. The format of the struct is further discussed in
+// README.md. See also osqp++_test.cc for example usage.
+struct OsqpInstance {
+ c_int num_variables() const { return constraint_matrix.cols(); }
+ c_int num_constraints() const { return constraint_matrix.rows(); }
+
+ // Only the upper triangle of the objective matrix is read. The lower triangle
+ // is ignored.
+ Eigen::SparseMatrix<double, Eigen::ColMajor, c_int> objective_matrix;
+ Eigen::VectorXd objective_vector;
+ Eigen::SparseMatrix<double, Eigen::ColMajor, c_int> constraint_matrix;
+ Eigen::VectorXd lower_bounds;
+ Eigen::VectorXd upper_bounds;
+};
+
+// This is a mirror of the OSQPSettings struct defined in
+// osqp/include/types.h and documented at
+// http://osqp.readthedocs.io/en/latest/interfaces/solver_settings.html. The
+// names are unchanged and (hence) violate Google naming conventions. The
+// default values are defined in osqp/include/constants.h. Note, OSQP's default
+// settings are looser than other QP solvers. Do choose appropriate values of
+// eps_abs and eps_rel for your application.
+struct OsqpSettings {
+ OsqpSettings(); // Sets default values.
+
+ double rho;
+ double sigma;
+ c_int scaling;
+ bool adaptive_rho;
+ c_int adaptive_rho_interval;
+ double adaptive_rho_tolerance;
+ double adaptive_rho_fraction;
+ c_int max_iter;
+ double eps_abs;
+ double eps_rel;
+ double eps_prim_inf;
+ double eps_dual_inf;
+ double alpha;
+ // linsys_solver is omitted. We don't change this.
+ double delta;
+ bool polish;
+ c_int polish_refine_iter;
+ bool verbose;
+ bool scaled_termination;
+ c_int check_termination;
+ bool warm_start;
+ double time_limit;
+};
+
+// Type-safe wrapper for OSQP's status codes that are defined at
+// osqp/include/constants.h.
+enum class OsqpExitCode {
+ kOptimal, // Optimal solution found.
+ kPrimalInfeasible, // Certificate of primal infeasibility found.
+ kDualInfeasible, // Certificate of dual infeasibility found.
+ kOptimalInaccurate, // Optimal solution found subject to reduced tolerances
+ kPrimalInfeasibleInaccurate, // Certificate of primal infeasibility found
+ // subject to reduced tolerances.
+ kDualInfeasibleInaccurate, // Certificate of dual infeasibility found
+ // subject to reduced tolerances.
+ kMaxIterations, // Maximum number of iterations reached.
+ kInterrupted, // Interrupted by signal or CTRL-C.
+ kTimeLimitReached, // Ran out of time.
+ kNonConvex, // The problem was found to be non-convex.
+ kUnknown, // Unknown problem in solver.
+};
+
+std::string ToString(OsqpExitCode exitcode);
+
+// This is a workaround to avoid including OSQP's header file. We can't directly
+// forward-declare OSQPWorkspace because it is defined as a typedef of an
+// anonymous struct.
+struct OSQPWorkspaceHelper;
+
+// This class is the main interface for calling OSQP. See example usage in
+// README.md.
+class OsqpSolver {
+ public:
+ OsqpSolver() = default;
+ // Move-only.
+ OsqpSolver(OsqpSolver&& rhs) = default;
+ OsqpSolver& operator=(OsqpSolver&& rhs) = default;
+ OsqpSolver(const OsqpSolver&) = delete;
+ OsqpSolver& operator=(const OsqpSolver&) = delete;
+
+ // Creates the internal OSQP workspace given the instance data and settings.
+ // It is valid to call Init() multiple times.
+ absl::Status Init(const OsqpInstance& instance, const OsqpSettings& settings);
+
+ // Updates the elements of matrix the objective matrix P (upper triangular).
+ // The new matrix should have the same sparsity structure.
+ //
+ // The solve will start from the previous optimal solution, which might not be
+ // a good starting point given the new objective matrix. If that's the
+ // case, one can call SetWarmStart with zero vectors to reset the state of the
+ // solver.
+ absl::Status UpdateObjectiveMatrix(
+ const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>&
+ objective_matrix);
+
+ // Updates the elements of matrix the constraint matrix A.
+ // The new matrix should have the same sparsity structure.
+ absl::Status UpdateConstraintMatrix(
+ const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>&
+ constraint_matrix);
+
+ // Combines call of UpdateObjectiveMatrix and UpdateConstraintMatrix.
+ absl::Status UpdateObjectiveAndConstraintMatrices(
+ const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>&
+ objective_matrix,
+ const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>&
+ constraint_matrix);
+
+ // Returns true if Init() has been called successfully.
+ bool IsInitialized() const { return workspace_ != nullptr; }
+
+ // Solves the instance by calling osqp_solve(). CHECK-fails if IsInitialized()
+ // is false.
+ ABSL_MUST_USE_RESULT OsqpExitCode Solve();
+
+ // The number of iterations taken. CHECK-fails if IsInitialized() is false.
+ c_int iterations() const;
+
+ // The objective value of the primal solution. CHECK-fails if IsInitialized()
+ // is false.
+ double objective_value() const;
+
+ // The primal solution, i.e., x. The Map is valid only for the lifetime of
+ // the OSQP workspace. It will be invalidated by a call to Init() or if the
+ // OsqpSolver is deleted. CHECK-fails if IsInitialized() is false.
+ // Implementation details (do not depend on these): The underlying memory is
+ // overwritten by SetPrimalWarmStart(). Modification of the problem data does
+ // not destroy the solution.
+ Eigen::Map<const Eigen::VectorXd> primal_solution() const;
+
+ // The vector of lagrange multipliers on the linear constraints. The Map is
+ // valid only for the lifetime of the OSQP workspace. It will be invalidated
+ // by a call to Init() or if the OsqpSolver is deleted. CHECK-fails if
+ // IsInitialized() is false. Implementation details (do not depend on these):
+ // The underlying memory is overwritten by SetDualWarmStart(). Modification of
+ // the problem data does not destroy the solution.
+ Eigen::Map<const Eigen::VectorXd> dual_solution() const;
+
+ // The primal infeasibility certificate. It is valid to query this only if
+ // Solve() returns kPrimalInfeasible or kPrimalInfeasibleInaccurate. The
+ // Map is valid only for the lifetime of the OSQP workspace. It will be
+ // invalidated by a call to Init() or of the OsqpSolver is deleted.
+ Eigen::Map<const Eigen::VectorXd> primal_infeasibility_certificate() const;
+
+ // TODO(ml): Implement dual_infeasibility_certificate.
+
+ // Sets a primal and dual warm-start for the next solve. Equivalent to
+ // SetPrimalWarmStart(primal_vector) and SetDualWarmStart(dual_vector).
+ // Returns:
+ // - FailedPreconditionError if IsInitialized() is false
+ // - InvalidArgumentError if the vectors do not have expected dimensions
+ // - UnknownError if the internal OSQP call fails
+ // - OkStatus on success
+ absl::Status SetWarmStart(
+ const Eigen::Ref<const Eigen::VectorXd>& primal_vector,
+ const Eigen::Ref<const Eigen::VectorXd>& dual_vector);
+
+ // Sets a warm-start for the primal iterate for the next solve. Use a vector
+ // of zeros to reset to the default initialization.
+ // - FailedPreconditionError if IsInitialized() is false
+ // - InvalidArgumentError if the vector does not have expected dimensions
+ // - UnknownError if the internal OSQP call fails
+ // - OkStatus on success
+ absl::Status SetPrimalWarmStart(
+ const Eigen::Ref<const Eigen::VectorXd>& primal_vector);
+
+ // Sets a warm-start for the dual iterate for the next solve. Use a vector
+ // of zeros to reset to the default initialization.
+ // - FailedPreconditionError if IsInitialized() is false
+ // - InvalidArgumentError if the vector does not have expected dimensions
+ // - UnknownError if the internal OSQP call fails
+ // - OkStatus on success
+ absl::Status SetDualWarmStart(
+ const Eigen::Ref<const Eigen::VectorXd>& dual_vector);
+
+ // Sets the objective vector for the next solve. Returns:
+ // - FailedPreconditionError if IsInitialized() is false
+ // - InvalidArgumentError if the vectors do not have expected dimensions
+ // - UnknownError if the internal OSQP call fails
+ // - OkStatus on success
+ absl::Status SetObjectiveVector(
+ const Eigen::Ref<const Eigen::VectorXd>& objective_vector);
+
+ // Sets the lower_bounds and upper_bounds vectors for the next solve. Returns:
+ // - FailedPreconditionError if IsInitialized() is false
+ // - InvalidArgumentError if the vectors do not have expected dimensions
+ // - InvalidArgumentError if lower_bounds[i] > upper_bounds[i] for some i
+ // - UnknownError if the internal OSQP call fails
+ // - OkStatus on success
+ absl::Status SetBounds(const Eigen::Ref<const Eigen::VectorXd>& lower_bounds,
+ const Eigen::Ref<const Eigen::VectorXd>& upper_bounds);
+
+ // Gets the current value of the rho setting, i.e., the ADMM rho step. Returns
+ // a FailedPreconditionError if IsInitialized() is false.
+ absl::StatusOr<double> GetRho() const;
+
+ // Gets the current value of the sigma setting, i.e., the ADMM sigma step.
+ // Returns a FailedPreconditionError if IsInitialized() is false.
+ absl::StatusOr<double> GetSigma() const;
+
+ // Gets the current value of the scaling setting, i.e., the number of
+ // heuristic scaling iterations. Returns a FailedPreconditionError if
+ // IsInitialized() is false.
+ absl::StatusOr<c_int> GetScaling() const;
+
+ // Gets the current value of the adaptive_rho setting, i.e., whether the rho
+ // step size is adaptively set. Returns a FailedPreconditionError if
+ // IsInitialized() is false.
+ absl::StatusOr<bool> GetAdaptiveRho() const;
+
+ // Gets the current value of the adaptive_rho_interval setting, i.e., the
+ // number of iterations between rho adaptations. Returns a
+ // FailedPreconditionError if IsInitialized() is false.
+ absl::StatusOr<c_int> GetAdaptiveRhoInterval() const;
+
+ // Gets the current value of the adaptive_rho_tolerance setting, i.e., the
+ // tolerance X for adapting rho (the new value must be X times larger, or 1/X
+ // times smaller, than the current value). Returns a FailedPreconditionError
+ // if IsInitialized() is false.
+ absl::StatusOr<double> GetAdaptiveRhoTolerance() const;
+
+ // Gets the current value of the adaptive_rho_fraction setting, i.e., in
+ // automatic mode (adaptive_rho_interval = 0), what fraction of setup time is
+ // spent on selecting rho. Returns a FailedPreconditionError if
+ // IsInitialized() is false.
+ absl::StatusOr<double> GetAdaptiveRhoFraction() const;
+
+ // Gets the current value of the max_iter setting, i.e., the maximum number of
+ // iterations. Returns a FailedPreconditionError if IsInitialized() is false.
+ absl::StatusOr<c_int> GetMaxIter() const;
+
+ // Gets the current value of the eps_abs setting, i.e., the absolute error
+ // tolerance for convergence. Returns a FailedPreconditionError if
+ // IsInitialized() is false.
+ absl::StatusOr<double> GetEpsAbs() const;
+
+ // Gets the current value of the eps_rel setting, i.e., the relative error
+ // tolerance for convergence. Returns a FailedPreconditionError if
+ // IsInitialized() is false.
+ absl::StatusOr<double> GetEpsRel() const;
+
+ // Gets the current value of the eps_prim_inf setting, i.e., the absolute
+ // error tolerance for primal infeasibility. Returns a FailedPreconditionError
+ // if IsInitialized() is false.
+ absl::StatusOr<double> GetEpsPrimInf() const;
+
+ // Gets the current value of the eps_dual_inf setting, i.e., the absolute
+ // error tolerance for dual infeasibility. Returns a FailedPreconditionError
+ // if IsInitialized() is false.
+ absl::StatusOr<double> GetEpsDualInf() const;
+
+ // Gets the current value of the alpha setting, i.e., the ADMM overrelaxation
+ // parameter. Returns a FailedPreconditionError if IsInitialized() is false.
+ absl::StatusOr<double> GetAlpha() const;
+
+ // Gets the current value of the delta setting, i.e., the polishing
+ // regularization parameter. Returns a FailedPreconditionError if
+ // IsInitialized() is false.
+ absl::StatusOr<double> GetDelta() const;
+
+ // Gets the current value of the polish setting, i.e., whether polishing is
+ // performed. Returns a FailedPreconditionError if IsInitialized() is false.
+ absl::StatusOr<bool> GetPolish() const;
+
+ // Gets the current value of the polish_refine_iter setting, i.e., the number
+ // of refinement iterations in polishing. Returns a FailedPreconditionError if
+ // IsInitialized() is false.
+ absl::StatusOr<c_int> GetPolishRefineIter() const;
+
+ // Gets the current value of the verbose setting, i.e., whether solver output
+ // is printed. Returns a FailedPreconditionError if IsInitialized() is false.
+ absl::StatusOr<bool> GetVerbose() const;
+
+ // Gets the current value of the scaled_termination setting, i.e., whether
+ // scaled termination criteria is used. Returns a FailedPreconditionError if
+ // IsInitialized() is false.
+ absl::StatusOr<bool> GetScaledTermination() const;
+
+ // Gets the current value of the check_termination setting, i.e., the interval
+ // for checking termination. Returns a FailedPreconditionError if
+ // IsInitialized() is false.
+ absl::StatusOr<c_int> GetCheckTermination() const;
+
+ // Gets the current value of the warm_start setting, i.e., if warm starting is
+ // performed. Returns a FailedPreconditionError if IsInitialized() is false.
+ absl::StatusOr<bool> GetWarmStart() const;
+
+ // Gets the current value of the time_limit setting, i.e., the time limit as
+ // expressed in seconds. Returns a FailedPreconditionError if IsInitialized()
+ // is false.
+ absl::StatusOr<double> GetTimeLimit() const;
+
+ // Updates the rho setting, i.e., the ADMM rho step. Returns:
+ // - FailedPreconditionError if IsInitialized() is false
+ // - InvalidArgumentError if rho_new <= 0.0
+ // - OkStatus on success
+ absl::Status UpdateRho(double rho_new);
+
+ // Updates the max_iter setting, i.e., the maximum number of iterations.
+ // Returns:
+ // - FailedPreconditionError if IsInitialized() is false
+ // - InvalidArgumentError if max_iter_new <= 0
+ // - OkStatus on success
+ absl::Status UpdateMaxIter(int max_iter_new);
+
+ // Updates the eps_abs setting, i.e., the absolute error tolerance for
+ // convergence. Returns:
+ // - FailedPreconditionError if IsInitialized() is false
+ // - InvalidArgumentError if eps_abs_new < 0.0
+ // - OkStatus on success
+ absl::Status UpdateEpsAbs(double eps_abs_new);
+
+ // Updates the eps_rel setting, i.e., the relative error tolerance for
+ // convergence. Returns:
+ // - FailedPreconditionError if IsInitialized() is false
+ // - InvalidArgumentError if eps_rel_new < 0.0
+ // - OkStatus on success
+ absl::Status UpdateEpsRel(double eps_rel_new);
+
+ // Updates the eps_prim_inf setting, i.e., the absolute error tolerance for
+ // primal infeasibility. Returns:
+ // - FailedPreconditionError if IsInitialized() is false
+ // - InvalidArgumentError if eps_prim_inf_new < 0.0
+ // - OkStatus on success
+ absl::Status UpdateEpsPrimInf(double eps_prim_inf_new);
+
+ // Updates the eps_dual_inf setting, i.e., the absolute error tolerance for
+ // dual infeasibility. Returns:
+ // - FailedPreconditionError if IsInitialized() is false
+ // - InvalidArgumentError if eps_dual_inf_new < 0.0
+ // - OkStatus on success
+ absl::Status UpdateEpsDualInf(double eps_dual_inf_new);
+
+ // Updates the alpha setting, i.e., the ADMM overrelaxation parameter.
+ // Returns:
+ // - FailedPreconditionError if IsInitialized() is false
+ // - InvalidArgumentError if !(0 < alpha_new < 2)
+ // - OkStatus on success
+ absl::Status UpdateAlpha(double alpha_new);
+
+ // Updates the delta setting, i.e., the polishing regularization parameter.
+ // Returns:
+ // - FailedPreconditionError if IsInitialized() is false
+ // - InvalidArgumentError if delta_new <= 0.0
+ // - OkStatus on success
+ absl::Status UpdateDelta(double delta_new);
+
+ // Updates the polish setting, i.e., whether polishing is performed. Returns:
+ // - FailedPreconditionError if IsInitialized() is false
+ // - OkStatus on success
+ absl::Status UpdatePolish(bool polish_new);
+
+ // Updates the polish_refine_iter setting, i.e., the number of refinement
+ // iterations in polishing. Returns:
+ // - FailedPreconditionError if IsInitialized() is false
+ // - InvalidArgumentError if polish_refine_iter_new <= 0.0
+ // - OkStatus on success
+ absl::Status UpdatePolishRefineIter(int polish_refine_iter_new);
+
+ // Updates the verbose setting, i.e., whether solver output is printed.
+ // Returns:
+ // - FailedPreconditionError if IsInitialized() is false
+ // - OkStatus on success
+ absl::Status UpdateVerbose(bool verbose_new);
+
+ // Updates the scaled_termination setting, i.e., whether scaled termination
+ // criteria is used. Returns:
+ // - FailedPreconditionError if IsInitialized() is false
+ // - OkStatus on success
+ absl::Status UpdateScaledTermination(bool scaled_termination_new);
+
+ // Updates the check_termination setting, i.e., the interval for checking
+ // termination. Setting to zero disables termination checking. Returns:
+ // - FailedPreconditionError if IsInitialized() is false
+ // - InvalidArgumentError if check_termination_new < 0.0
+ // - OkStatus on success
+ absl::Status UpdateCheckTermination(c_int check_termination_new);
+
+ // Updates the warm_start setting, i.e., whether warm starting is performed.
+ // Returns:
+ // - FailedPreconditionError if IsInitialized() is false
+ // - OkStatus on success
+ absl::Status UpdateWarmStart(bool warm_start_new);
+
+ // Updates the time_limit setting, i.e., the time limit as expressed in
+ // seconds. Setting the time limit to zero disables time-limiting. Returns:
+ // - FailedPreconditionError if IsInitialized() is false
+ // - InvalidArgumentError if time_limit_new < 0.0
+ // - OkStatus on success
+ absl::Status UpdateTimeLimit(double time_limit_new);
+
+ private:
+ struct OsqpDeleter {
+ void operator()(OSQPWorkspaceHelper* workspace) const;
+ };
+
+ std::unique_ptr<OSQPWorkspaceHelper, OsqpDeleter> workspace_;
+};
+
+} // namespace osqp
+
+#endif // OSQP_CPP_H_
diff --git a/src/osqp++.cc b/src/osqp++.cc
new file mode 100644
index 0000000..5fa119e
--- /dev/null
+++ b/src/osqp++.cc
@@ -0,0 +1,992 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <algorithm>
+#include <cassert>
+#include <string>
+#include <type_traits>
+
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
+#include "Eigen/Core"
+#include "Eigen/SparseCore"
+#include "ctrlc.h"
+#include "osqp.h"
+#include "osqp++.h"
+
+// Fails to compile if OSQP's typedefs change. This lets us avoid including
+// osqp.h in osqp++.h.
+static_assert(
+ std::is_same_v<osqp::c_int, ::c_int>,
+ "OSQP's c_int typedef does not match the definition in osqp++.h.");
+static_assert(std::is_same_v<c_float, double>,
+ "OSQP's c_float typedef is unexpectedly not the same as double");
+
+static_assert(sizeof(OSQPSettings) == 176,
+ "The size of OSQPSettings has changed unexpectedly. Make sure "
+ "that the map between ::OSQPSettings and osqp::OsqpSettings "
+ "remains up to date.");
+
+namespace osqp {
+
+namespace {
+
+using ::Eigen::Map;
+using ::Eigen::Ref;
+using ::Eigen::VectorXd;
+
+// The mapping between ::OSQPSettings and osqp::OsqpSettings is maintained
+// manually and may need to be updated for new releases of OSQP.
+
+void CopyFromInternalSettings(const ::OSQPSettings& osqp_settings,
+ OsqpSettings* settings) {
+ settings->rho = osqp_settings.rho;
+ settings->sigma = osqp_settings.sigma;
+ settings->scaling = osqp_settings.scaling;
+ settings->adaptive_rho = osqp_settings.adaptive_rho;
+ settings->adaptive_rho_interval = osqp_settings.adaptive_rho_interval;
+ settings->adaptive_rho_tolerance = osqp_settings.adaptive_rho_tolerance;
+ settings->adaptive_rho_fraction = osqp_settings.adaptive_rho_fraction;
+ settings->max_iter = osqp_settings.max_iter;
+ settings->eps_abs = osqp_settings.eps_abs;
+ settings->eps_rel = osqp_settings.eps_rel;
+ settings->eps_prim_inf = osqp_settings.eps_prim_inf;
+ settings->eps_dual_inf = osqp_settings.eps_dual_inf;
+ settings->alpha = osqp_settings.alpha;
+ settings->delta = osqp_settings.delta;
+ settings->polish = osqp_settings.polish;
+ settings->polish_refine_iter = osqp_settings.polish_refine_iter;
+ settings->verbose = osqp_settings.verbose;
+ settings->scaled_termination = osqp_settings.scaled_termination;
+ settings->check_termination = osqp_settings.check_termination;
+ settings->warm_start = osqp_settings.warm_start;
+ settings->time_limit = osqp_settings.time_limit;
+}
+
+::OSQPSettings ToInternalSettings(const OsqpSettings& settings) {
+ OSQPSettings osqp_settings;
+ osqp_settings.rho = settings.rho;
+ osqp_settings.sigma = settings.sigma;
+ osqp_settings.scaling = settings.scaling;
+ osqp_settings.adaptive_rho = settings.adaptive_rho;
+ osqp_settings.adaptive_rho_interval = settings.adaptive_rho_interval;
+ osqp_settings.adaptive_rho_tolerance = settings.adaptive_rho_tolerance;
+ osqp_settings.adaptive_rho_fraction = settings.adaptive_rho_fraction;
+ osqp_settings.max_iter = settings.max_iter;
+ osqp_settings.eps_abs = settings.eps_abs;
+ osqp_settings.eps_rel = settings.eps_rel;
+ osqp_settings.eps_prim_inf = settings.eps_prim_inf;
+ osqp_settings.eps_dual_inf = settings.eps_dual_inf;
+ osqp_settings.alpha = settings.alpha;
+ osqp_settings.delta = settings.delta;
+ osqp_settings.polish = settings.polish;
+ osqp_settings.polish_refine_iter = settings.polish_refine_iter;
+ osqp_settings.verbose = settings.verbose;
+ osqp_settings.scaled_termination = settings.scaled_termination;
+ osqp_settings.check_termination = settings.check_termination;
+ osqp_settings.warm_start = settings.warm_start;
+ osqp_settings.time_limit = settings.time_limit;
+ osqp_settings.linsys_solver = ::QDLDL_SOLVER;
+ return osqp_settings;
+}
+
+} // namespace
+
+OsqpSettings::OsqpSettings() {
+ ::OSQPSettings osqp_settings;
+ osqp_set_default_settings(&osqp_settings);
+ CopyFromInternalSettings(osqp_settings, this);
+}
+
+struct OSQPWorkspaceHelper : public ::OSQPWorkspace {};
+
+void OsqpSolver::OsqpDeleter::operator()(OSQPWorkspaceHelper* workspace) const {
+ osqp_cleanup(workspace);
+}
+
+// OSQP_HANDLE_EXITCODE(x) expands to 'case x: return "x"'. Using this macro
+// prevents typos in the strings.
+#define OSQP_HANDLE_EXITCODE(x) \
+ case x: \
+ return #x
+
+std::string ToString(OsqpExitCode exitcode) {
+ switch (exitcode) {
+ OSQP_HANDLE_EXITCODE(OsqpExitCode::kOptimal);
+ OSQP_HANDLE_EXITCODE(OsqpExitCode::kPrimalInfeasible);
+ OSQP_HANDLE_EXITCODE(OsqpExitCode::kDualInfeasible);
+ OSQP_HANDLE_EXITCODE(OsqpExitCode::kOptimalInaccurate);
+ OSQP_HANDLE_EXITCODE(OsqpExitCode::kPrimalInfeasibleInaccurate);
+ OSQP_HANDLE_EXITCODE(OsqpExitCode::kDualInfeasibleInaccurate);
+ OSQP_HANDLE_EXITCODE(OsqpExitCode::kMaxIterations);
+ OSQP_HANDLE_EXITCODE(OsqpExitCode::kInterrupted);
+ OSQP_HANDLE_EXITCODE(OsqpExitCode::kTimeLimitReached);
+ OSQP_HANDLE_EXITCODE(OsqpExitCode::kNonConvex);
+ OSQP_HANDLE_EXITCODE(OsqpExitCode::kUnknown);
+ }
+ return "Unknown exit code";
+}
+
+#undef OSQP_HANDLE_EXITCODE
+
+namespace {
+
+absl::Status CheckDimensions(const int left_value, const int right_value,
+ absl::string_view left_name,
+ absl::string_view right_name) {
+ if (left_value != right_value) {
+ return absl::InvalidArgumentError(
+ absl::StrCat("Dimension mismatch: ", left_name, " (= ", left_value,
+ ") must equal ", right_name, " (= ", right_value, ")."));
+ } else {
+ return absl::OkStatus();
+ }
+}
+
+} // namespace
+
+#define OSQP_CHECK_DIMENSIONS(left_value, right_value) \
+ CheckDimensions((left_value), (right_value), #left_value, #right_value)
+
+#define OSQP_RETURN_IF_ERROR(expr) \
+ { \
+ const absl::Status result = expr; \
+ if (!result.ok()) return result; \
+ }
+
+#define OSQP_CHECK(expr) assert(expr)
+
+absl::Status OsqpSolver::Init(const OsqpInstance& instance,
+ const OsqpSettings& settings) {
+ if (!instance.objective_matrix.isCompressed()) {
+ return absl::InvalidArgumentError(
+ "objective_matrix must be compressed (call makeCompressed()).");
+ }
+ if (!instance.constraint_matrix.isCompressed()) {
+ return absl::InvalidArgumentError(
+ "constraint_matrix must be compressed (call makeCompressed()).");
+ }
+ const c_int num_variables = instance.num_variables();
+ const c_int num_constraints = instance.num_constraints();
+
+ OSQP_RETURN_IF_ERROR(
+ OSQP_CHECK_DIMENSIONS(instance.objective_matrix.cols(), num_variables));
+ OSQP_RETURN_IF_ERROR(
+ OSQP_CHECK_DIMENSIONS(instance.objective_matrix.rows(), num_variables));
+ OSQP_RETURN_IF_ERROR(
+ OSQP_CHECK_DIMENSIONS(instance.objective_vector.size(), num_variables));
+ OSQP_RETURN_IF_ERROR(
+ OSQP_CHECK_DIMENSIONS(instance.lower_bounds.size(), num_constraints));
+ OSQP_RETURN_IF_ERROR(
+ OSQP_CHECK_DIMENSIONS(instance.upper_bounds.size(), num_constraints));
+
+ // Clip bounds using OSQP_INFTY. Failing to do this causes subtle convergence
+ // issues instead of producing explicit errors (e.g., the
+ // DetectsPrimalInfeasible test fails). The osqp-python interface also clips
+ // the bounds in the same way.
+ VectorXd clipped_lower_bounds = instance.lower_bounds.cwiseMax(-OSQP_INFTY);
+ VectorXd clipped_upper_bounds = instance.upper_bounds.cwiseMin(OSQP_INFTY);
+
+ // OSQP copies all the data, so it's okay to discard this struct after
+ // osqp_setup. It also does not modify the input data (note osqp_setup takes a
+ // const OSQPData*). const_cast is needed here to fill in the input data
+ // structures.
+ OSQPData data;
+ data.n = num_variables;
+ data.m = num_constraints;
+
+ // TODO(ml): This copy could be avoided if the matrix is already upper
+ // triangular.
+ Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>
+ objective_matrix_upper_triangle =
+ instance.objective_matrix.triangularView<Eigen::Upper>();
+
+ // OSQP's csc struct represents sparse matrices in compressed sparse column
+ // (CSC) format and (confusingly) triplet format. osqp_setup() assumes the
+ // input is provided in CSC format. The mapping from Eigen::SparseMatrix's
+ // outerIndexPtr(), innerIndexPtr(), and valuePtr() is direct because we
+ // require the sparse matrices to be compressed and follow the Eigen::ColMajor
+ // storage scheme. For further background, see
+ // https://eigen.tuxfamily.org/dox/group__TutorialSparse.html for the
+ // description of CSC format in Eigen and osqp/include/types.h for the
+ // definition of OSQP's csc struct.
+ ::csc objective_matrix = {
+ objective_matrix_upper_triangle.outerIndexPtr()[num_variables],
+ num_variables,
+ num_variables,
+ const_cast<c_int*>(objective_matrix_upper_triangle.outerIndexPtr()),
+ const_cast<c_int*>(objective_matrix_upper_triangle.innerIndexPtr()),
+ const_cast<double*>(objective_matrix_upper_triangle.valuePtr()),
+ -1};
+ data.P = &objective_matrix;
+
+ ::csc constraint_matrix = {
+ instance.constraint_matrix.outerIndexPtr()[num_variables],
+ num_constraints,
+ num_variables,
+ const_cast<c_int*>(instance.constraint_matrix.outerIndexPtr()),
+ const_cast<c_int*>(instance.constraint_matrix.innerIndexPtr()),
+ const_cast<double*>(instance.constraint_matrix.valuePtr()),
+ -1};
+ data.A = &constraint_matrix;
+
+ data.q = const_cast<double*>(instance.objective_vector.data());
+ data.l = clipped_lower_bounds.data();
+ data.u = clipped_upper_bounds.data();
+
+ ::OSQPSettings osqp_settings = ToInternalSettings(settings);
+
+ OSQPWorkspace* workspace = nullptr;
+ const int return_code = osqp_setup(&workspace, &data, &osqp_settings);
+ workspace_.reset(static_cast<OSQPWorkspaceHelper*>(workspace));
+ if (return_code == 0) {
+ return absl::OkStatus();
+ }
+ switch (static_cast<osqp_error_type>(return_code)) {
+ case OSQP_DATA_VALIDATION_ERROR:
+ return absl::InvalidArgumentError(
+ "Unable to initialize OSQP: data validation error.");
+ case OSQP_SETTINGS_VALIDATION_ERROR:
+ return absl::InvalidArgumentError(
+ "Unable to initialize OSQP: invalid settings.");
+ case OSQP_LINSYS_SOLVER_LOAD_ERROR:
+ // This should never happen because qdldl is statically linked in.
+ return absl::UnknownError(
+ "Unable to initialize OSQP: unable to load linear solver.");
+ case OSQP_LINSYS_SOLVER_INIT_ERROR:
+ return absl::UnknownError(
+ "Unable to initialize OSQP: unable to initialize linear solver.");
+ case OSQP_NONCVX_ERROR:
+ return absl::InvalidArgumentError(
+ "Unable to initialize OSQP: the problem appears non-convex.");
+ case OSQP_MEM_ALLOC_ERROR:
+ return absl::UnknownError(
+ "Unable to initialize OSQP: memory allocation error.");
+ case OSQP_WORKSPACE_NOT_INIT_ERROR:
+ return absl::UnknownError(
+ "Unable to initialize OSQP: workspace not initialized.");
+ }
+ return absl::UnknownError(
+ "Unable to initialize OSQP: unrecognized error code.");
+}
+
+namespace {
+
+absl::Status VerifySameSparsity(
+ const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>& new_matrix,
+ const csc* ref_matrix, size_t num_variables) {
+ if (new_matrix.nonZeros() != ref_matrix->p[num_variables]) {
+ return absl::InvalidArgumentError(
+ "The new new matrix should have the same number of non-zero "
+ "elements.");
+ }
+
+ for (size_t i = 0; i < num_variables; ++i) {
+ if (ref_matrix->p[i] != new_matrix.outerIndexPtr()[i]) {
+ return absl::InvalidArgumentError(
+ "Sparsity of the new matrix differs from the previously "
+ "defined matrix.");
+ }
+ }
+
+ for (size_t i = 0; i < new_matrix.nonZeros(); ++i) {
+ if (ref_matrix->i[i] != new_matrix.innerIndexPtr()[i]) {
+ return absl::InvalidArgumentError(
+ "Sparsity of the new matrix differs from the previously "
+ "defined matrix.");
+ }
+ }
+
+ return absl::OkStatus();
+}
+
+// Helper function for calling osqp_update_P with an upper triangular objective
+// matrix. Assumes objective_matrix_upper_triangle is always upper triangular.
+absl::Status UpdateUpperTriangularObjectiveMatrix(
+ const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>&
+ objective_matrix_upper_triangle,
+ OSQPWorkspaceHelper* workspace) {
+ const c_int num_variables = workspace->data->n;
+
+ if (objective_matrix_upper_triangle.rows() !=
+ objective_matrix_upper_triangle.cols() ||
+ objective_matrix_upper_triangle.rows() != num_variables) {
+ return absl::InvalidArgumentError(absl::StrFormat(
+ "The new objective matrix should be square with dimension equal to the "
+ "number of variables. Matrix dimensions: %d x %d, num_variables=%d",
+ objective_matrix_upper_triangle.rows(),
+ objective_matrix_upper_triangle.cols(), num_variables));
+ }
+
+ OSQP_RETURN_IF_ERROR(VerifySameSparsity(objective_matrix_upper_triangle,
+ workspace->data->P, num_variables));
+
+ c_int nnzP = objective_matrix_upper_triangle.nonZeros();
+
+ const int return_code = osqp_update_P(
+ workspace, objective_matrix_upper_triangle.valuePtr(), OSQP_NULL, nnzP);
+ if (return_code == 0) {
+ return absl::OkStatus();
+ }
+ return absl::UnknownError(
+ "Unable to update OSQP P matrix: unrecognized error code.");
+}
+
+// Helper function for calling osqp_update_P_A with an upper triangular
+// objective matrix. Assumes objective_matrix_upper_triangle is always upper
+// triangular.
+absl::Status UpdateUpperTriangularObjectiveMatrixAndConstraintMatrix(
+ const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>&
+ objective_matrix_upper_triangle,
+ const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>&
+ constraint_matrix,
+ OSQPWorkspaceHelper* workspace) {
+ const c_int num_variables = workspace->data->n;
+
+ if (objective_matrix_upper_triangle.rows() !=
+ objective_matrix_upper_triangle.cols() ||
+ objective_matrix_upper_triangle.rows() != num_variables) {
+ return absl::InvalidArgumentError(absl::StrFormat(
+ "The new objective matrix should be square with dimension equal to the "
+ "number of variables. Matrix dimensions: %d x %d, num_variables=%d",
+ objective_matrix_upper_triangle.rows(),
+ objective_matrix_upper_triangle.cols(), num_variables));
+ }
+ if (constraint_matrix.cols() != num_variables) {
+ return absl::InvalidArgumentError(absl::StrFormat(
+ "The new constraint matrix should column size equal to the "
+ "number of variables. Matrix dimensions: %d x %d, num_variables=%d",
+ constraint_matrix.rows(), constraint_matrix.cols(), num_variables));
+ }
+
+ OSQP_RETURN_IF_ERROR(VerifySameSparsity(objective_matrix_upper_triangle,
+ workspace->data->P, num_variables));
+
+ c_int nnzP = objective_matrix_upper_triangle.nonZeros();
+
+ OSQP_RETURN_IF_ERROR(
+ VerifySameSparsity(constraint_matrix, workspace->data->A, num_variables));
+
+ c_int nnzA = constraint_matrix.nonZeros();
+
+ const int return_code = osqp_update_P_A(
+ workspace, objective_matrix_upper_triangle.valuePtr(), OSQP_NULL, nnzP,
+ constraint_matrix.valuePtr(), OSQP_NULL, nnzA);
+ if (return_code == 0) {
+ return absl::OkStatus();
+ }
+ return absl::UnknownError(
+ "Unable to update OSQP P and A matrix: unrecognized error code.");
+}
+
+// Returns true if the sparse matrix 'matrix' is upper triangular.
+bool IsUpperTriangular(
+ const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>& matrix) {
+ // Iterate through all non-zero elements, and ensure that their indices are
+ // only on the upper-right triangle, including the diagonal.
+ for (int i = 0; i < matrix.outerSize(); ++i) {
+ for (Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>::InnerIterator it(
+ matrix, i);
+ it; ++it) {
+ if (it.col() < it.row()) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+} // namespace
+
+absl::Status OsqpSolver::UpdateObjectiveMatrix(
+ const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>&
+ objective_matrix) {
+ // If the objective matrix is already upper triangular, we can skip the
+ // temporary.
+ if (IsUpperTriangular(objective_matrix)) {
+ return UpdateUpperTriangularObjectiveMatrix(objective_matrix,
+ workspace_.get());
+ }
+
+ // If not upper triangular, make a temporary.
+ Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>
+ objective_matrix_upper_triangle =
+ objective_matrix.triangularView<Eigen::Upper>();
+ return UpdateUpperTriangularObjectiveMatrix(objective_matrix_upper_triangle,
+ workspace_.get());
+}
+
+absl::Status OsqpSolver::UpdateConstraintMatrix(
+ const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>&
+ constraint_matrix) {
+ const c_int num_variables = workspace_->data->n;
+
+ if (constraint_matrix.cols() != num_variables) {
+ return absl::InvalidArgumentError(absl::StrFormat(
+ "The new constraint matrix should column size equal to the "
+ "number of variables. Matrix dimensions: %d x %d, num_variables=%d",
+ constraint_matrix.rows(), constraint_matrix.cols(), num_variables));
+ }
+
+ OSQP_RETURN_IF_ERROR(VerifySameSparsity(constraint_matrix,
+ workspace_->data->A, num_variables));
+
+ c_int nnzA = constraint_matrix.nonZeros();
+
+ const int return_code = osqp_update_A(
+ workspace_.get(), constraint_matrix.valuePtr(), OSQP_NULL, nnzA);
+ if (return_code == 0) {
+ return absl::OkStatus();
+ }
+ return absl::UnknownError(
+ "Unable to update OSQP A matrix: unrecognized error code.");
+}
+
+absl::Status OsqpSolver::UpdateObjectiveAndConstraintMatrices(
+ const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>& objective_matrix,
+ const Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>&
+ constraint_matrix) {
+ // If the objective matrix is already upper triangular, we can skip the
+ // temporary.
+ if (IsUpperTriangular(objective_matrix)) {
+ return UpdateUpperTriangularObjectiveMatrixAndConstraintMatrix(
+ objective_matrix, constraint_matrix, workspace_.get());
+ }
+
+ // If not upper triangular, make a temporary.
+ Eigen::SparseMatrix<double, Eigen::ColMajor, c_int>
+ objective_matrix_upper_triangle =
+ objective_matrix.triangularView<Eigen::Upper>();
+ return UpdateUpperTriangularObjectiveMatrixAndConstraintMatrix(
+ objective_matrix_upper_triangle, constraint_matrix, workspace_.get());
+}
+
+namespace {
+OsqpExitCode StatusToExitCode(const c_int status_val) {
+ switch (status_val) {
+ case OSQP_SOLVED:
+ return OsqpExitCode::kOptimal;
+ case OSQP_SOLVED_INACCURATE:
+ return OsqpExitCode::kOptimalInaccurate;
+ case OSQP_PRIMAL_INFEASIBLE:
+ return OsqpExitCode::kPrimalInfeasible;
+ case OSQP_PRIMAL_INFEASIBLE_INACCURATE:
+ return OsqpExitCode::kPrimalInfeasibleInaccurate;
+ case OSQP_DUAL_INFEASIBLE:
+ return OsqpExitCode::kDualInfeasible;
+ case OSQP_DUAL_INFEASIBLE_INACCURATE:
+ return OsqpExitCode::kDualInfeasibleInaccurate;
+ case OSQP_MAX_ITER_REACHED:
+ return OsqpExitCode::kMaxIterations;
+ case OSQP_SIGINT:
+ return OsqpExitCode::kInterrupted;
+ case OSQP_TIME_LIMIT_REACHED:
+ return OsqpExitCode::kTimeLimitReached;
+ case OSQP_NON_CVX:
+ return OsqpExitCode::kNonConvex;
+ default:
+ return OsqpExitCode::kUnknown;
+ }
+}
+
+} // namespace
+
+OsqpExitCode OsqpSolver::Solve() {
+ OSQP_CHECK(IsInitialized());
+ if (osqp_solve(workspace_.get()) != 0) {
+ // From looking at the code, this can happen if the solve is interrupted
+ // with ctrl-c or if updating "rho" fails.
+ if (osqp_is_interrupted()) {
+ return OsqpExitCode::kInterrupted;
+ }
+ return OsqpExitCode::kUnknown;
+ }
+ return StatusToExitCode(workspace_->info->status_val);
+}
+
+c_int OsqpSolver::iterations() const {
+ OSQP_CHECK(IsInitialized());
+ return workspace_->info->iter;
+}
+
+double OsqpSolver::objective_value() const {
+ OSQP_CHECK(IsInitialized());
+ return workspace_->info->obj_val;
+}
+
+Map<const VectorXd> OsqpSolver::primal_solution() const {
+ OSQP_CHECK(IsInitialized());
+ return Map<const VectorXd>(workspace_->solution->x, workspace_->data->n);
+}
+
+Map<const VectorXd> OsqpSolver::dual_solution() const {
+ OSQP_CHECK(IsInitialized());
+ return Map<const VectorXd>(workspace_->solution->y, workspace_->data->m);
+}
+
+Map<const VectorXd> OsqpSolver::primal_infeasibility_certificate() const {
+ OSQP_CHECK(IsInitialized());
+ const OsqpExitCode exit_code = StatusToExitCode(workspace_->info->status_val);
+ OSQP_CHECK(exit_code == OsqpExitCode::kPrimalInfeasible ||
+ exit_code == OsqpExitCode::kPrimalInfeasibleInaccurate);
+ return Map<const VectorXd>(workspace_->delta_y, workspace_->data->m);
+}
+
+absl::Status OsqpSolver::SetWarmStart(const Ref<const VectorXd>& primal_vector,
+ const Ref<const VectorXd>& dual_vector) {
+ // This is identical to calling osqp_warm_start with both vectors at once.
+ OSQP_RETURN_IF_ERROR(SetPrimalWarmStart(primal_vector));
+ OSQP_RETURN_IF_ERROR(SetDualWarmStart(dual_vector));
+ return absl::OkStatus();
+}
+
+absl::Status OsqpSolver::SetPrimalWarmStart(
+ const Ref<const VectorXd>& primal_vector) {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ const c_int num_variables = workspace_->data->n;
+ OSQP_RETURN_IF_ERROR(
+ OSQP_CHECK_DIMENSIONS(primal_vector.size(), num_variables));
+
+ const int return_code =
+ osqp_warm_start_x(workspace_.get(), primal_vector.data());
+ if (return_code != 0) {
+ return absl::UnknownError("osqp_warm_start_x unexpectedly failed.");
+ }
+ return absl::OkStatus();
+}
+
+absl::Status OsqpSolver::SetDualWarmStart(
+ const Ref<const VectorXd>& dual_vector) {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ const c_int num_constraints = workspace_->data->m;
+ OSQP_RETURN_IF_ERROR(
+ OSQP_CHECK_DIMENSIONS(dual_vector.size(), num_constraints));
+
+ const int return_code =
+ osqp_warm_start_y(workspace_.get(), dual_vector.data());
+ if (return_code != 0) {
+ return absl::UnknownError("osqp_warm_start_y unexpectedly failed.");
+ }
+ return absl::OkStatus();
+}
+
+absl::Status OsqpSolver::SetObjectiveVector(
+ const Ref<const VectorXd>& objective_vector) {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ const c_int num_variables = workspace_->data->n;
+ OSQP_RETURN_IF_ERROR(
+ OSQP_CHECK_DIMENSIONS(objective_vector.size(), num_variables));
+
+ const int return_code =
+ osqp_update_lin_cost(workspace_.get(), objective_vector.data());
+ if (return_code != 0) {
+ return absl::UnknownError("osqp_update_lin_cost unexpectedly failed.");
+ }
+ return absl::OkStatus();
+}
+
+// NOTE(ml): osqp_update_lower_bound and osqp_update_upper_bound are not
+// exposed because they have confusing semantics. They immediately error if a
+// new set of bounds is inconsistent with the existing bounds on the other side.
+absl::Status OsqpSolver::SetBounds(const Ref<const VectorXd>& lower_bounds,
+ const Ref<const VectorXd>& upper_bounds) {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ const c_int num_constraints = workspace_->data->m;
+ OSQP_RETURN_IF_ERROR(
+ OSQP_CHECK_DIMENSIONS(lower_bounds.size(), num_constraints));
+ OSQP_RETURN_IF_ERROR(
+ OSQP_CHECK_DIMENSIONS(upper_bounds.size(), num_constraints));
+ // OSQP does this check internally, but we can return a better error message.
+ for (int i = 0; i < num_constraints; i++) {
+ if (lower_bounds[i] > upper_bounds[i]) {
+ return absl::InvalidArgumentError(
+ absl::StrCat("Inconsistent bounds at index ", i, ", ",
+ lower_bounds[i], " must be <= ", upper_bounds[i], "."));
+ }
+ }
+
+ const int return_code = osqp_update_bounds(
+ workspace_.get(), lower_bounds.data(), upper_bounds.data());
+ if (return_code != 0) {
+ return absl::UnknownError("osqp_update_bounds unexpectedly failed.");
+ }
+ return absl::OkStatus();
+}
+
+absl::StatusOr<double> OsqpSolver::GetRho() const {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ return workspace_->settings->rho;
+}
+
+absl::StatusOr<double> OsqpSolver::GetSigma() const {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ return workspace_->settings->sigma;
+}
+
+absl::StatusOr<c_int> OsqpSolver::GetScaling() const {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ return workspace_->settings->scaling;
+}
+
+absl::StatusOr<bool> OsqpSolver::GetAdaptiveRho() const {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ return workspace_->settings->adaptive_rho;
+}
+
+absl::StatusOr<c_int> OsqpSolver::GetAdaptiveRhoInterval() const {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ return workspace_->settings->adaptive_rho_interval;
+}
+
+absl::StatusOr<double> OsqpSolver::GetAdaptiveRhoTolerance() const {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ return workspace_->settings->adaptive_rho_tolerance;
+}
+
+absl::StatusOr<double> OsqpSolver::GetAdaptiveRhoFraction() const {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ return workspace_->settings->adaptive_rho_fraction;
+}
+
+absl::StatusOr<c_int> OsqpSolver::GetMaxIter() const {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ return workspace_->settings->max_iter;
+}
+
+absl::StatusOr<double> OsqpSolver::GetEpsAbs() const {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ return workspace_->settings->eps_abs;
+}
+
+absl::StatusOr<double> OsqpSolver::GetEpsRel() const {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ return workspace_->settings->eps_rel;
+}
+
+absl::StatusOr<double> OsqpSolver::GetEpsPrimInf() const {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ return workspace_->settings->eps_prim_inf;
+}
+
+absl::StatusOr<double> OsqpSolver::GetEpsDualInf() const {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ return workspace_->settings->eps_dual_inf;
+}
+
+absl::StatusOr<double> OsqpSolver::GetAlpha() const {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ return workspace_->settings->alpha;
+}
+
+absl::StatusOr<double> OsqpSolver::GetDelta() const {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ return workspace_->settings->delta;
+}
+
+absl::StatusOr<bool> OsqpSolver::GetPolish() const {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ return workspace_->settings->polish;
+}
+
+absl::StatusOr<c_int> OsqpSolver::GetPolishRefineIter() const {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ return workspace_->settings->polish_refine_iter;
+}
+
+absl::StatusOr<bool> OsqpSolver::GetVerbose() const {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ return workspace_->settings->verbose;
+}
+
+absl::StatusOr<bool> OsqpSolver::GetScaledTermination() const {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ return workspace_->settings->scaled_termination;
+}
+
+absl::StatusOr<c_int> OsqpSolver::GetCheckTermination() const {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ return workspace_->settings->check_termination;
+}
+
+absl::StatusOr<bool> OsqpSolver::GetWarmStart() const {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ return workspace_->settings->warm_start;
+}
+
+absl::StatusOr<double> OsqpSolver::GetTimeLimit() const {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ return workspace_->settings->time_limit;
+}
+
+absl::Status OsqpSolver::UpdateRho(const double rho_new) {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ if (rho_new <= 0) {
+ return absl::InvalidArgumentError(
+ absl::StrCat("Invalid rho value: ", rho_new));
+ }
+ if (osqp_update_rho(workspace_.get(), rho_new) != 0) {
+ return absl::UnknownError("osqp_update_rho unexpectedly failed.");
+ }
+ return absl::OkStatus();
+}
+
+absl::Status OsqpSolver::UpdateMaxIter(const int max_iter_new) {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ if (max_iter_new <= 0) {
+ return absl::InvalidArgumentError(
+ absl::StrCat("Invalid max_iter value: ", max_iter_new));
+ }
+ if (osqp_update_max_iter(workspace_.get(), max_iter_new) != 0) {
+ return absl::UnknownError("osqp_update_max_iter unexpectedly failed.");
+ }
+ return absl::OkStatus();
+}
+
+absl::Status OsqpSolver::UpdateEpsAbs(const double eps_abs_new) {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ if (eps_abs_new < 0.0) {
+ return absl::InvalidArgumentError(
+ absl::StrCat("Invalid eps_abs value: ", eps_abs_new));
+ }
+ if (osqp_update_eps_abs(workspace_.get(), eps_abs_new) != 0) {
+ return absl::UnknownError("osqp_update_eps_abs unexpectedly failed.");
+ }
+ return absl::OkStatus();
+}
+
+absl::Status OsqpSolver::UpdateEpsRel(const double eps_rel_new) {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ if (eps_rel_new < 0.0) {
+ return absl::InvalidArgumentError(
+ absl::StrCat("Invalid eps_rel value: ", eps_rel_new));
+ }
+ if (osqp_update_eps_rel(workspace_.get(), eps_rel_new) != 0) {
+ return absl::UnknownError("osqp_update_eps_rel unexpectedly failed.");
+ }
+ return absl::OkStatus();
+}
+
+absl::Status OsqpSolver::UpdateEpsPrimInf(const double eps_prim_inf_new) {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ if (eps_prim_inf_new < 0.0) {
+ return absl::InvalidArgumentError(
+ absl::StrCat("Invalid eps_prim_inf value: ", eps_prim_inf_new));
+ }
+ if (osqp_update_eps_prim_inf(workspace_.get(), eps_prim_inf_new) != 0) {
+ return absl::UnknownError("osqp_update_eps_prim_inf unexpectedly failed.");
+ }
+ return absl::OkStatus();
+}
+
+absl::Status OsqpSolver::UpdateEpsDualInf(const double eps_dual_inf_new) {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ if (eps_dual_inf_new < 0.0) {
+ return absl::InvalidArgumentError(
+ absl::StrCat("Invalid eps_dual_inf value: ", eps_dual_inf_new));
+ }
+ if (osqp_update_eps_dual_inf(workspace_.get(), eps_dual_inf_new) != 0) {
+ return absl::UnknownError("osqp_update_eps_dual_inf unexpectedly failed.");
+ }
+ return absl::OkStatus();
+}
+
+absl::Status OsqpSolver::UpdateAlpha(const double alpha_new) {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ if (alpha_new <= 0.0 || alpha_new >= 2) {
+ return absl::InvalidArgumentError(
+ absl::StrCat("Invalid alpha value: ", alpha_new));
+ }
+ if (osqp_update_alpha(workspace_.get(), alpha_new) != 0) {
+ return absl::UnknownError("osqp_update_alpha unexpectedly failed.");
+ }
+ return absl::OkStatus();
+}
+
+absl::Status OsqpSolver::UpdateDelta(const double delta_new) {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ if (delta_new <= 0.0) {
+ return absl::InvalidArgumentError(
+ absl::StrCat("Invalid delta value: ", delta_new));
+ }
+ if (osqp_update_delta(workspace_.get(), delta_new) != 0) {
+ return absl::UnknownError("osqp_update_delta unexpectedly failed.");
+ }
+ return absl::OkStatus();
+}
+
+absl::Status OsqpSolver::UpdatePolish(const bool polish_new) {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ if (osqp_update_polish(workspace_.get(), polish_new) != 0) {
+ return absl::UnknownError("osqp_update_polish unexpectedly failed.");
+ }
+ return absl::OkStatus();
+}
+
+absl::Status OsqpSolver::UpdatePolishRefineIter(
+ const int polish_refine_iter_new) {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ if (polish_refine_iter_new <= 0.0) {
+ return absl::InvalidArgumentError(absl::StrCat(
+ "Invalid polish_refine_iter value: ", polish_refine_iter_new));
+ }
+ if (osqp_update_polish_refine_iter(workspace_.get(),
+ polish_refine_iter_new) != 0) {
+ return absl::UnknownError(
+ "osqp_update_polish_refine_iter unexpectedly failed.");
+ }
+ return absl::OkStatus();
+}
+
+absl::Status OsqpSolver::UpdateVerbose(const bool verbose_new) {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ if (osqp_update_verbose(workspace_.get(), verbose_new) != 0) {
+ return absl::UnknownError("osqp_update_verbose unexpectedly failed.");
+ }
+ return absl::OkStatus();
+}
+
+absl::Status OsqpSolver::UpdateScaledTermination(
+ const bool scaled_termination_new) {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ if (osqp_update_scaled_termination(workspace_.get(),
+ scaled_termination_new) != 0) {
+ return absl::UnknownError(
+ "osqp_update_scaled_termination unexpectedly failed.");
+ }
+ return absl::OkStatus();
+}
+
+absl::Status OsqpSolver::UpdateCheckTermination(
+ const c_int check_termination_new) {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ if (check_termination_new < 0.0) {
+ return absl::InvalidArgumentError(absl::StrCat(
+ "Invalid check_termination value: ", check_termination_new));
+ }
+ if (osqp_update_check_termination(workspace_.get(), check_termination_new) !=
+ 0) {
+ return absl::UnknownError(
+ "osqp_update_check_termination unexpectedly failed.");
+ }
+ return absl::OkStatus();
+}
+
+absl::Status OsqpSolver::UpdateWarmStart(const bool warm_start_new) {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ if (osqp_update_warm_start(workspace_.get(), warm_start_new) != 0) {
+ return absl::UnknownError("osqp_update_warm_start unexpectedly failed.");
+ }
+ return absl::OkStatus();
+}
+
+absl::Status OsqpSolver::UpdateTimeLimit(const double time_limit_new) {
+ if (!IsInitialized()) {
+ return absl::FailedPreconditionError("OsqpSolver is not initialized.");
+ }
+ if (time_limit_new < 0.0) {
+ return absl::InvalidArgumentError(
+ absl::StrCat("Invalid time_limit value: ", time_limit_new));
+ }
+ if (osqp_update_time_limit(workspace_.get(), time_limit_new) != 0) {
+ return absl::UnknownError("osqp_update_time_limit unexpectedly failed.");
+ }
+ return absl::OkStatus();
+}
+
+#undef OSQP_CHECK_DIMENSIONS
+#undef OSQP_RETURN_IF_ERROR
+#undef OSQP_CHECK
+
+} // namespace osqp
diff --git a/test/osqp++_test.cc b/test/osqp++_test.cc
new file mode 100644
index 0000000..205a74e
--- /dev/null
+++ b/test/osqp++_test.cc
@@ -0,0 +1,967 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <iterator>
+#include <limits>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/types/span.h"
+#include "Eigen/SparseCore"
+#include "osqp++.h"
+
+namespace osqp {
+namespace {
+
+using ::Eigen::SparseMatrix;
+using ::Eigen::Triplet;
+using ::Eigen::VectorXd;
+using ::testing::DoubleNear;
+
+constexpr double kTolerance = 1.0e-5;
+constexpr double kInfinity = std::numeric_limits<double>::infinity();
+
+void ExpectElementsAre(Eigen::Map<const Eigen::VectorXd> vec,
+ absl::Span<const double> expected,
+ const double tolerance) {
+ EXPECT_EQ(vec.size(), expected.size());
+ for (int i = 0; i < vec.size(); ++i) {
+ EXPECT_THAT(vec[i], DoubleNear(expected[i], tolerance))
+ << "violation at index =" << i;
+ }
+}
+
+TEST(OsqpTest, DimensionMismatchObjectiveMatrix) {
+ OsqpInstance instance;
+ instance.objective_matrix = SparseMatrix<double>(1, 1);
+ instance.objective_vector.resize(1);
+ instance.objective_vector << -1.0;
+ instance.constraint_matrix = SparseMatrix<double>(2, 2);
+ instance.lower_bounds.resize(2);
+ instance.lower_bounds << 0.0, 0.0;
+ instance.upper_bounds.resize(2);
+ instance.upper_bounds << 1.0, 1.0;
+ OsqpSolver solver;
+ OsqpSettings settings;
+ EXPECT_EQ(solver.Init(instance, settings).code(),
+ absl::StatusCode::kInvalidArgument);
+ EXPECT_FALSE(solver.IsInitialized());
+}
+
+TEST(OsqpTest, DimensionMismatchObjectiveVector) {
+ OsqpInstance instance;
+ instance.objective_matrix = SparseMatrix<double>(1, 1);
+ instance.objective_vector.resize(2);
+ instance.objective_vector << 1.0, 1.0;
+ instance.constraint_matrix = SparseMatrix<double>(0, 1);
+ // instance.lower_bounds not set.
+ // instance.upper_bounds not set.
+ OsqpSolver solver;
+ OsqpSettings settings;
+ EXPECT_EQ(solver.Init(instance, settings).code(),
+ absl::StatusCode::kInvalidArgument);
+ EXPECT_FALSE(solver.IsInitialized());
+}
+
+TEST(OsqpTest, DimensionMismatchLowerBounds) {
+ OsqpInstance instance;
+ instance.objective_matrix = SparseMatrix<double>(1, 1);
+ instance.objective_vector.resize(1);
+ instance.objective_vector << 1.0;
+ instance.constraint_matrix = SparseMatrix<double>(0, 1);
+ instance.lower_bounds.resize(1);
+ instance.lower_bounds << 1.0;
+ // instance.upper_bounds not set.
+ OsqpSolver solver;
+ OsqpSettings settings;
+ EXPECT_EQ(solver.Init(instance, settings).code(),
+ absl::StatusCode::kInvalidArgument);
+ EXPECT_FALSE(solver.IsInitialized());
+}
+
+TEST(OsqpTest, DimensionMismatchUpperBounds) {
+ OsqpInstance instance;
+ instance.objective_matrix = SparseMatrix<double>(1, 1);
+ instance.objective_vector.resize(1);
+ instance.objective_vector << 1.0;
+ instance.constraint_matrix = SparseMatrix<double>(0, 1);
+ // instance.lower_bounds not set.
+ instance.upper_bounds.resize(1);
+ instance.upper_bounds << 1.0;
+ OsqpSolver solver;
+ OsqpSettings settings;
+ EXPECT_EQ(solver.Init(instance, settings).code(),
+ absl::StatusCode::kInvalidArgument);
+ EXPECT_FALSE(solver.IsInitialized());
+}
+
+// A lower bound greater than an upper bound triggers an OSQP initialization
+// error.
+TEST(OsqpTest, InvalidData) {
+ OsqpInstance instance;
+ instance.objective_matrix = SparseMatrix<double>(1, 1);
+ instance.objective_vector.resize(1);
+ instance.objective_vector << 1.0;
+ instance.constraint_matrix = SparseMatrix<double>(1, 1);
+ instance.lower_bounds.resize(1);
+ instance.lower_bounds << 1.0;
+ instance.upper_bounds.resize(1);
+ instance.upper_bounds << 0.0;
+ OsqpSolver solver;
+ OsqpSettings settings;
+ EXPECT_EQ(solver.Init(instance, settings).code(),
+ absl::StatusCode::kInvalidArgument);
+ EXPECT_FALSE(solver.IsInitialized());
+}
+
+TEST(OsqpTest, TwoDimLP) {
+ // Minimize -x subject to:
+ // x + y <= 1, x >= 0, y >= 0.
+
+ SparseMatrix<double> constraint_matrix(3, 2);
+ const Triplet<double> kTripletsA[] = {
+ {0, 0, 1.0}, {0, 1, 1.0}, {1, 0, 1.0}, {2, 1, 1.0}};
+ constraint_matrix.setFromTriplets(std::begin(kTripletsA),
+ std::end(kTripletsA));
+
+ OsqpInstance instance;
+ instance.objective_matrix = SparseMatrix<double>(2, 2);
+ instance.objective_vector.resize(2);
+ instance.objective_vector << -1.0, 0.0;
+ instance.constraint_matrix = constraint_matrix;
+ instance.lower_bounds.resize(3);
+ instance.lower_bounds << -kInfinity, 0.0, 0.0;
+ instance.upper_bounds.resize(3);
+ instance.upper_bounds << 1.0, kInfinity, kInfinity;
+
+ OsqpSolver solver;
+ OsqpSettings settings;
+ // absolute_convergence_tolerance (eps_abs) is an l_2 tolerance on the
+ // residual vector, so this is safe given we use kTolerance
+ // as an l_infty tolerance.
+ settings.eps_abs = kTolerance;
+ settings.eps_rel = 0.0;
+ EXPECT_FALSE(solver.IsInitialized());
+ ASSERT_TRUE(solver.Init(instance, settings).ok());
+ EXPECT_TRUE(solver.IsInitialized());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+
+ EXPECT_NEAR(solver.objective_value(), -1.0, kTolerance);
+ ExpectElementsAre(solver.primal_solution(), {1.0, 0.0}, kTolerance);
+ ExpectElementsAre(solver.dual_solution(), {1.0, 0.0, -1.0}, kTolerance);
+}
+
+TEST(OsqpTest, TwoDimLPWithEquality) {
+ // Minimize -x subject to:
+ // x + y <= 1, x >= 0, y == 0.5.
+
+ SparseMatrix<double> constraint_matrix(3, 2);
+ const Triplet<double> kTripletsA[] = {
+ {0, 0, 1.0}, {0, 1, 1.0}, {1, 0, 1.0}, {2, 1, 1.0}};
+ constraint_matrix.setFromTriplets(std::begin(kTripletsA),
+ std::end(kTripletsA));
+
+ OsqpInstance instance;
+ instance.objective_matrix = SparseMatrix<double>(2, 2);
+ instance.objective_vector.resize(2);
+ instance.objective_vector << -1.0, 0.0;
+ instance.constraint_matrix = constraint_matrix;
+ instance.lower_bounds.resize(3);
+ instance.lower_bounds << -kInfinity, 0.0, 0.5;
+ instance.upper_bounds.resize(3);
+ instance.upper_bounds << 1.0, kInfinity, 0.5;
+
+ OsqpSolver solver;
+ OsqpSettings settings;
+ settings.eps_abs = kTolerance;
+ settings.eps_rel = 0.0;
+ ASSERT_TRUE(solver.Init(instance, settings).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+
+ EXPECT_NEAR(solver.objective_value(), -0.5, kTolerance);
+ ExpectElementsAre(solver.primal_solution(), {0.5, 0.5}, kTolerance);
+ ExpectElementsAre(solver.dual_solution(), {1.0, 0.0, -1.0}, kTolerance);
+}
+
+TEST(OsqpTest, DetectsPrimalInfeasible) {
+ // Minimize 0 subject to:
+ // x == 2, x >= 3.
+ SparseMatrix<double> objective_matrix(1, 1);
+
+ SparseMatrix<double> constraint_matrix(2, 1);
+ const Triplet<double> kTripletsQ[] = {{0, 0, 1.0}, {1, 0, 1.0}};
+ constraint_matrix.setFromTriplets(std::begin(kTripletsQ),
+ std::end(kTripletsQ));
+
+ OsqpInstance instance;
+ instance.objective_matrix = objective_matrix;
+ instance.objective_vector.resize(1);
+ instance.objective_vector << 0.0;
+ instance.constraint_matrix = constraint_matrix;
+ instance.lower_bounds.resize(2);
+ instance.lower_bounds << 2, 3;
+ instance.upper_bounds.resize(2);
+ instance.upper_bounds << 2, kInfinity;
+
+ OsqpSolver solver;
+ OsqpSettings settings;
+ ASSERT_TRUE(solver.Init(instance, settings).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kPrimalInfeasible);
+ Eigen::Map<const VectorXd> cert = solver.primal_infeasibility_certificate();
+ // The infeasibility certificate satisfies cert[0] + cert[1] == 0,
+ // cert[0]* 2 + cert[1] * 3 < 0, and cert[1] <= 0. See the OSQP documentation
+ // for the general definition of the certificate.
+ EXPECT_THAT(cert[0] / cert[1], DoubleNear(-1.0, kTolerance));
+ EXPECT_LE(cert[1], 0.0);
+ EXPECT_LT(cert[0] * 2 + cert[1] * 3, 0.0);
+}
+
+TEST(OsqpTest, NewConstraintMatrixInTwoDimLP) {
+ // Minimize -x subject to:
+ // x + y <= 1, x >= 1, y >= 0.
+
+ SparseMatrix<double> constraint_matrix(3, 2);
+ const Triplet<double> kTripletsA[] = {
+ {0, 0, 1.0}, {0, 1, 1.0}, {1, 0, 1.0}, {2, 1, 1.0}};
+ constraint_matrix.setFromTriplets(std::begin(kTripletsA),
+ std::end(kTripletsA));
+
+ OsqpInstance instance;
+ instance.objective_matrix = SparseMatrix<double>(2, 2);
+ instance.objective_vector.resize(2);
+ instance.objective_vector << -1.0, 0.0;
+ instance.constraint_matrix = constraint_matrix;
+ instance.lower_bounds.resize(3);
+ instance.lower_bounds << -kInfinity, 1.0, 0.0;
+ instance.upper_bounds.resize(3);
+ instance.upper_bounds << 1.0, kInfinity, kInfinity;
+
+ OsqpSolver solver;
+ OsqpSettings settings;
+ // absolute_convergence_tolerance (eps_abs) is an l_2 tolerance on the
+ // residual vector, so this is safe given we use kTolerance
+ // as an l_infty tolerance.
+ settings.eps_abs = kTolerance;
+ settings.eps_rel = 0.0;
+ EXPECT_FALSE(solver.IsInitialized());
+ ASSERT_TRUE(solver.Init(instance, settings).ok());
+ EXPECT_TRUE(solver.IsInitialized());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+
+ double x_exp = 1.0;
+ double y_exp = 0.0;
+
+ EXPECT_NEAR(solver.objective_value(), -x_exp, kTolerance);
+ ExpectElementsAre(solver.primal_solution(), {x_exp, y_exp}, kTolerance);
+
+ // Change to Minimize -x subject to:
+ // 2*x + y <= 1.5, 2*x >= 1, y >= 0.25
+ SparseMatrix<double> new_constraint_matrix = constraint_matrix;
+ new_constraint_matrix.coeffRef(0, 0) = 2;
+ new_constraint_matrix.coeffRef(1, 0) = 2;
+
+ VectorXd new_lower_bounds(3);
+ new_lower_bounds << -kInfinity, 1, 0.25;
+ VectorXd new_upper_bounds(3);
+ new_upper_bounds << 1.5, kInfinity, kInfinity;
+
+ ASSERT_TRUE(solver.SetBounds(new_lower_bounds, new_upper_bounds).ok());
+
+ ASSERT_TRUE(solver.UpdateConstraintMatrix(new_constraint_matrix).ok());
+ EXPECT_TRUE(solver.IsInitialized());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+
+ x_exp = 0.624997;
+ y_exp = 0.250002;
+
+ EXPECT_NEAR(solver.objective_value(), -x_exp, 2 * kTolerance);
+ ExpectElementsAre(solver.primal_solution(), {x_exp, y_exp}, kTolerance);
+}
+
+class TwoDimensionalQpTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ // Minimize x^2 + (1/2)xy + y^2 subject to x >= 1.
+ SparseMatrix<double> objective_matrix(2, 2);
+ const Triplet<double> kTripletsP[] = {
+ {0, 0, 2.0}, {1, 0, 0.5}, {0, 1, 0.5}, {1, 1, 2.0}};
+ objective_matrix.setFromTriplets(std::begin(kTripletsP),
+ std::end(kTripletsP));
+
+ SparseMatrix<double> constraint_matrix(1, 2);
+ const Triplet<double> kTripletsA[] = {{0, 0, 1.0}};
+ constraint_matrix.setFromTriplets(std::begin(kTripletsA),
+ std::end(kTripletsA));
+
+ OsqpInstance instance;
+ instance.objective_matrix = objective_matrix;
+ instance.objective_vector.resize(2);
+ instance.objective_vector << 0.0, 0.0;
+ instance.constraint_matrix = constraint_matrix;
+ instance.lower_bounds.resize(1);
+ instance.lower_bounds << 1.0;
+ instance.upper_bounds.resize(1);
+ instance.upper_bounds << kInfinity;
+
+ OsqpSettings settings;
+ settings.eps_abs = kTolerance / 10;
+ settings.eps_rel = 0.0;
+ // check_termination is set so that the warm-started instance converges
+ // immediately.
+ settings.check_termination = 1;
+ ASSERT_TRUE(solver_.Init(instance, settings).ok());
+ }
+
+ OsqpSolver solver_;
+};
+
+TEST_F(TwoDimensionalQpTest, Solves) {
+ ASSERT_EQ(solver_.Solve(), OsqpExitCode::kOptimal);
+
+ // NOTE(ml): The convergence guarantees don't imply that the error in
+ // objective_value() is within kTolerance. To be more precise, we could
+ // compute the worst error bound in a kTolerance box around the optimal
+ // solution, but 2 * kTolerance works here.
+ EXPECT_NEAR(solver_.objective_value(), 1.0 - 0.5 * 0.25 + 0.25 * 0.25,
+ 2 * kTolerance);
+ ExpectElementsAre(solver_.primal_solution(), {1.0, -0.25}, kTolerance);
+ ExpectElementsAre(solver_.dual_solution(), {-15.0 / 8.0}, 2 * kTolerance);
+}
+
+TEST_F(TwoDimensionalQpTest, UsesJointWarmStart) {
+ VectorXd primal_warm_start(2);
+ primal_warm_start << 1.0, -0.25;
+ VectorXd dual_warm_start(1);
+ dual_warm_start << -15.0 / 8.0;
+ ASSERT_TRUE(solver_.SetWarmStart(primal_warm_start, dual_warm_start).ok());
+ ASSERT_EQ(solver_.Solve(), OsqpExitCode::kOptimal);
+ EXPECT_EQ(solver_.iterations(), 1);
+}
+
+TEST_F(TwoDimensionalQpTest, UsesSeparateWarmStart) {
+ VectorXd primal_warm_start(2);
+ primal_warm_start << 1.0, -0.25;
+ VectorXd dual_warm_start(1);
+ dual_warm_start << -15.0 / 8.0;
+ ASSERT_TRUE(solver_.SetPrimalWarmStart(primal_warm_start).ok());
+ ASSERT_TRUE(solver_.SetDualWarmStart(dual_warm_start).ok());
+ ASSERT_EQ(solver_.Solve(), OsqpExitCode::kOptimal);
+ EXPECT_EQ(solver_.iterations(), 1);
+}
+
+TEST_F(TwoDimensionalQpTest, SolvesWithNewObjectiveVector) {
+ // Changes the objective to x^2 + (1/2)xy + y^2 + x
+ VectorXd objective_vector(2);
+ objective_vector << 1.0, 0.0;
+ ASSERT_TRUE(solver_.SetObjectiveVector(objective_vector).ok());
+ ASSERT_EQ(solver_.Solve(), OsqpExitCode::kOptimal);
+
+ EXPECT_NEAR(solver_.objective_value(), 1.0 - 0.5 * 0.25 + 0.25 * 0.25 + 1.0,
+ 3 * kTolerance);
+ ExpectElementsAre(solver_.primal_solution(), {1.0, -0.25}, kTolerance);
+}
+
+TEST_F(TwoDimensionalQpTest, SolvesWithNewBounds) {
+ // Fixes x to 2.0.
+ VectorXd bound_vector(1);
+ bound_vector << 2.0;
+ ASSERT_TRUE(solver_.SetBounds(bound_vector, bound_vector).ok());
+ ASSERT_EQ(solver_.Solve(), OsqpExitCode::kOptimal);
+ EXPECT_NEAR(solver_.objective_value(), 4.0 - 0.5 + 0.5 * 0.5, 2 * kTolerance);
+ ExpectElementsAre(solver_.primal_solution(), {2.0, -0.5}, kTolerance);
+}
+
+// These tests are instantiated twice, once with a non-upper triangular
+// objective matrix, and once with an upper triangular objective matrix.
+class ParameterizedTwoDimensionalQpTest
+ : public testing::WithParamInterface<bool>,
+ public TwoDimensionalQpTest {};
+TEST_P(ParameterizedTwoDimensionalQpTest, SolvesWithNewObjectiveMatrix) {
+ // Minimize x^2 + (1/2)xy + y^2 subject to x >= 1.
+ ASSERT_EQ(solver_.Solve(), OsqpExitCode::kOptimal);
+
+ double x_exp = 1.0;
+ double y_exp = -0.25;
+
+ EXPECT_NEAR(solver_.objective_value(),
+ x_exp * x_exp + 0.5 * x_exp * y_exp + y_exp * y_exp,
+ 2 * kTolerance);
+ ExpectElementsAre(solver_.primal_solution(), {x_exp, y_exp}, kTolerance);
+ ExpectElementsAre(solver_.dual_solution(), {-15.0 / 8.0}, 2 * kTolerance);
+
+ // Minimize x^2 + (1/2)xy + (1/2)y^2 subject to x >= 1.
+ SparseMatrix<double> new_objective_matrix(2, 2);
+ const Triplet<double> kTripletsP[] = {
+ {0, 0, 2.0}, {1, 0, 0.5}, {0, 1, 0.5}, {1, 1, 1}};
+ new_objective_matrix.setFromTriplets(std::begin(kTripletsP),
+ std::end(kTripletsP));
+ if (GetParam()) {
+ new_objective_matrix = new_objective_matrix.triangularView<Eigen::Upper>();
+ }
+
+ ASSERT_TRUE(solver_.UpdateObjectiveMatrix(new_objective_matrix).ok());
+ ASSERT_EQ(solver_.Solve(), OsqpExitCode::kOptimal);
+
+ x_exp = 1.0;
+ y_exp = -0.5;
+ EXPECT_NEAR(solver_.objective_value(),
+ x_exp * x_exp + 0.5 * x_exp * y_exp + 0.5 * y_exp * y_exp,
+ 2 * kTolerance);
+ ExpectElementsAre(solver_.primal_solution(), {x_exp, y_exp}, kTolerance);
+}
+TEST_P(ParameterizedTwoDimensionalQpTest,
+ SolvesWithNewObjectiveAndConstraintMatrices) {
+ // Minimize x^2 + (1/2)xy + y^2 subject to x >= 1.
+ ASSERT_EQ(solver_.Solve(), OsqpExitCode::kOptimal);
+
+ double x_exp = 1.0;
+ double y_exp = -0.25;
+
+ EXPECT_NEAR(solver_.objective_value(),
+ x_exp * x_exp + 0.5 * x_exp * y_exp + y_exp * y_exp,
+ 2 * kTolerance);
+ ExpectElementsAre(solver_.primal_solution(), {x_exp, y_exp}, kTolerance);
+ ExpectElementsAre(solver_.dual_solution(), {-15.0 / 8.0}, 2 * kTolerance);
+
+ // Change to minimize x^2 + (1/2)xy + (1/2)y^2 subject to 0.5*x >= 1.
+ SparseMatrix<double> new_objective_matrix(2, 2);
+ const Triplet<double> kTripletsP[] = {
+ {0, 0, 2.0}, {1, 0, 0.5}, {0, 1, 0.5}, {1, 1, 1}};
+ new_objective_matrix.setFromTriplets(std::begin(kTripletsP),
+ std::end(kTripletsP));
+ if (GetParam()) {
+ new_objective_matrix = new_objective_matrix.triangularView<Eigen::Upper>();
+ }
+
+ SparseMatrix<double> new_constraint_matrix(1, 2);
+ const Triplet<double> kTripletsA[] = {{0, 0, 0.5}};
+ new_constraint_matrix.setFromTriplets(std::begin(kTripletsA),
+ std::end(kTripletsA));
+
+ const absl::Status result = solver_.UpdateObjectiveAndConstraintMatrices(
+ new_objective_matrix, new_constraint_matrix);
+ ASSERT_TRUE(result.ok());
+ ASSERT_EQ(solver_.Solve(), OsqpExitCode::kOptimal);
+
+ x_exp = 2.0;
+ y_exp = -1.0;
+ EXPECT_NEAR(solver_.objective_value(),
+ x_exp * x_exp + 0.5 * x_exp * y_exp + 0.5 * y_exp * y_exp,
+ 2 * kTolerance);
+ ExpectElementsAre(solver_.primal_solution(), {x_exp, y_exp}, kTolerance);
+}
+INSTANTIATE_TEST_SUITE_P(UseUpperTriangularObjectiveMatrix,
+ ParameterizedTwoDimensionalQpTest,
+ ::testing::ValuesIn({false, true}));
+
+TEST(OsqpTest, ErrorIfNotPsd) {
+ // Minimize -x^2.
+
+ SparseMatrix<double> objective_matrix(1, 1);
+ const Triplet<double> kTripletsP[] = {{0, 0, -2.0}};
+ objective_matrix.setFromTriplets(std::begin(kTripletsP),
+ std::end(kTripletsP));
+
+ SparseMatrix<double> constraint_matrix(0, 1);
+
+ OsqpInstance instance;
+ instance.objective_matrix = objective_matrix;
+ instance.objective_vector.resize(1);
+ instance.objective_vector << 0.0;
+ instance.constraint_matrix = constraint_matrix;
+ // instance.lower_bounds not set.
+ // instance.upper_bounds not set.
+
+ OsqpSolver solver;
+ OsqpSettings settings;
+ settings.eps_abs = kTolerance;
+ settings.eps_rel = 0.0;
+ settings.verbose = true;
+ EXPECT_EQ(solver.Init(instance, settings).code(),
+ absl::StatusCode::kInvalidArgument);
+}
+
+// This returns an OsqpInstance that corresponds to minimizing
+// (x-1)^2 + (y-1)^2 + c
+// within the bounds -100 <= x, y <= 100.
+OsqpInstance GetToyProblem() {
+ OsqpInstance instance;
+
+ instance.objective_matrix = SparseMatrix<double>(2, 2);
+ const Triplet<double> triplets[] = {{0, 0, 2.0}, {1, 1, 2.0}};
+ instance.objective_matrix.setFromTriplets(std::begin(triplets),
+ std::end(triplets));
+
+ instance.objective_vector.resize(2);
+ instance.objective_vector << -2.0, -2.0;
+
+ instance.constraint_matrix = instance.objective_matrix;
+
+ instance.lower_bounds.resize(2);
+ instance.lower_bounds << -100.0, -100.0;
+
+ instance.upper_bounds.resize(2);
+ instance.upper_bounds << 100.0, 100.0;
+
+ return instance;
+}
+
+// Returns the solution to the problem returned by GetToyProblem().
+VectorXd GetToySolution() {
+ VectorXd soln(2);
+ soln << 1.0, 1.0;
+ return soln;
+}
+
+TEST(OsqpTest, GetAndUpdateRho) {
+ OsqpSettings settings;
+ settings.rho = 3.0e-3;
+
+ OsqpSolver solver;
+ ASSERT_TRUE(solver.Init(GetToyProblem(), settings).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+
+ {
+ const auto rho = solver.GetRho();
+ ASSERT_TRUE(rho.ok());
+ EXPECT_EQ(*rho, 3.0e-3);
+ }
+
+ ASSERT_TRUE(solver.UpdateRho(4.0e-4).ok());
+ {
+ const auto rho = solver.GetRho();
+ ASSERT_TRUE(rho.ok());
+ EXPECT_EQ(*rho, 4.0e-4);
+ }
+}
+
+TEST(OsqpTest, GetSigma) {
+ OsqpSettings settings;
+ settings.sigma = 3.0e-3;
+
+ OsqpSolver solver;
+ ASSERT_TRUE(solver.Init(GetToyProblem(), settings).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+
+ const auto sigma = solver.GetSigma();
+ ASSERT_TRUE(sigma.ok());
+ EXPECT_EQ(*sigma, 3.0e-3);
+}
+
+TEST(OsqpTest, GetScaling) {
+ OsqpSettings settings;
+ settings.scaling = 23;
+
+ OsqpSolver solver;
+ ASSERT_TRUE(solver.Init(GetToyProblem(), settings).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+
+ const auto scaling = solver.GetScaling();
+ ASSERT_TRUE(scaling.ok());
+ EXPECT_EQ(*scaling, 23);
+}
+
+TEST(OsqpTest, GetAdaptiveRho) {
+ OsqpSettings settings;
+ settings.adaptive_rho = false;
+
+ OsqpSolver solver;
+ ASSERT_TRUE(solver.Init(GetToyProblem(), settings).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+
+ const auto adaptive_rho = solver.GetAdaptiveRho();
+ ASSERT_TRUE(adaptive_rho.ok());
+ EXPECT_FALSE(*adaptive_rho);
+}
+
+TEST(OsqpTest, GetAdaptiveRhoInterval) {
+ OsqpSettings settings;
+ settings.adaptive_rho_interval = 12;
+
+ OsqpSolver solver;
+ ASSERT_TRUE(solver.Init(GetToyProblem(), settings).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+
+ const auto adaptive_rho_interval = solver.GetAdaptiveRhoInterval();
+ ASSERT_TRUE(adaptive_rho_interval.ok());
+ EXPECT_EQ(*adaptive_rho_interval, 12);
+}
+
+TEST(OsqpTest, GetAdaptiveRhoTolerance) {
+ OsqpSettings settings;
+ settings.adaptive_rho_tolerance = 17.0;
+
+ OsqpSolver solver;
+ ASSERT_TRUE(solver.Init(GetToyProblem(), settings).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+
+ const auto adaptive_rho_tolerance = solver.GetAdaptiveRhoTolerance();
+ ASSERT_TRUE(adaptive_rho_tolerance.ok());
+ EXPECT_EQ(*adaptive_rho_tolerance, 17);
+}
+
+TEST(OsqpTest, GetAdaptiveRhoFraction) {
+ OsqpSettings settings;
+ settings.adaptive_rho_fraction = 3.4;
+
+ OsqpSolver solver;
+ ASSERT_TRUE(solver.Init(GetToyProblem(), settings).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+
+ const auto adaptive_rho_fraction = solver.GetAdaptiveRhoFraction();
+ ASSERT_TRUE(adaptive_rho_fraction.ok());
+ EXPECT_EQ(*adaptive_rho_fraction, 3.4);
+}
+
+TEST(OsqpTest, GetAndUpdateEpsAbs) {
+ OsqpSettings settings;
+ settings.eps_abs = 3.0e-3;
+
+ OsqpSolver solver;
+ ASSERT_TRUE(solver.Init(GetToyProblem(), settings).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+
+ {
+ const auto eps_abs = solver.GetEpsAbs();
+ ASSERT_TRUE(eps_abs.ok());
+ EXPECT_EQ(*eps_abs, 3.0e-3);
+ }
+
+ ASSERT_TRUE(solver.UpdateEpsAbs(4.0e-4).ok());
+ {
+ const auto eps_abs = solver.GetEpsAbs();
+ ASSERT_TRUE(eps_abs.ok());
+ EXPECT_EQ(*eps_abs, 4.0e-4);
+ }
+}
+
+TEST(OsqpTest, GetAndUpdateEpsRel) {
+ OsqpSettings settings;
+ settings.eps_rel = 3.0e-3;
+
+ OsqpSolver solver;
+ ASSERT_TRUE(solver.Init(GetToyProblem(), settings).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+
+ {
+ const auto eps_rel = solver.GetEpsRel();
+ ASSERT_TRUE(eps_rel.ok());
+ EXPECT_EQ(*eps_rel, 3.0e-3);
+ }
+
+ ASSERT_TRUE(solver.UpdateEpsRel(4.0e-4).ok());
+ {
+ const auto eps_rel = solver.GetEpsRel();
+ ASSERT_TRUE(eps_rel.ok());
+ EXPECT_EQ(*eps_rel, 4.0e-4);
+ }
+}
+
+TEST(OsqpTest, GetAndUpdateEpsPrimInf) {
+ OsqpSettings settings;
+ settings.eps_prim_inf = 3.0e-3;
+
+ OsqpSolver solver;
+ ASSERT_TRUE(solver.Init(GetToyProblem(), settings).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+
+ {
+ const auto eps_prim_inf = solver.GetEpsPrimInf();
+ ASSERT_TRUE(eps_prim_inf.ok());
+ EXPECT_EQ(*eps_prim_inf, 3.0e-3);
+ }
+
+ ASSERT_TRUE(solver.UpdateEpsPrimInf(4.0e-4).ok());
+ {
+ const auto eps_prim_inf = solver.GetEpsPrimInf();
+ ASSERT_TRUE(eps_prim_inf.ok());
+ EXPECT_EQ(*eps_prim_inf, 4.0e-4);
+ }
+}
+
+TEST(OsqpTest, GetAndUpdateEpsDualInf) {
+ OsqpSettings settings;
+ settings.eps_dual_inf = 3.0e-3;
+
+ OsqpSolver solver;
+ ASSERT_TRUE(solver.Init(GetToyProblem(), settings).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+
+ {
+ const auto eps_dual_inf = solver.GetEpsDualInf();
+ ASSERT_TRUE(eps_dual_inf.ok());
+ EXPECT_EQ(*eps_dual_inf, 3.0e-3);
+ }
+ ASSERT_TRUE(solver.UpdateEpsDualInf(4.0e-4).ok());
+ {
+ const auto eps_dual_inf = solver.GetEpsDualInf();
+ ASSERT_TRUE(eps_dual_inf.ok());
+ EXPECT_EQ(*eps_dual_inf, 4.0e-4);
+ }
+}
+
+TEST(OsqpTest, GetAndUpdateAlpha) {
+ OsqpSettings settings;
+ settings.alpha = 3.0e-3;
+
+ OsqpSolver solver;
+ ASSERT_TRUE(solver.Init(GetToyProblem(), settings).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+
+ {
+ const auto alpha = solver.GetAlpha();
+ ASSERT_TRUE(alpha.ok());
+ EXPECT_EQ(*alpha, 3.0e-3);
+ }
+
+ ASSERT_TRUE(solver.UpdateAlpha(4.0e-4).ok());
+ {
+ const auto alpha = solver.GetAlpha();
+ ASSERT_TRUE(alpha.ok());
+ EXPECT_EQ(*alpha, 4.0e-4);
+ }
+}
+
+TEST(OsqpTest, GetAndUpdateDelta) {
+ OsqpSettings settings;
+ settings.delta = 3.0e-3;
+
+ OsqpSolver solver;
+ ASSERT_TRUE(solver.Init(GetToyProblem(), settings).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+
+ {
+ const auto delta = solver.GetDelta();
+ ASSERT_TRUE(delta.ok());
+ EXPECT_EQ(*delta, 3.0e-3);
+ }
+
+ ASSERT_TRUE(solver.UpdateDelta(4.0e-4).ok());
+ {
+ const auto delta = solver.GetDelta();
+ ASSERT_TRUE(delta.ok());
+ EXPECT_EQ(*delta, 4.0e-4);
+ }
+}
+
+TEST(OsqpTest, GetAndUpdatePolish) {
+ OsqpSettings settings;
+ settings.polish = true;
+
+ OsqpSolver solver;
+ ASSERT_TRUE(solver.Init(GetToyProblem(), settings).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+
+ {
+ const auto polish = solver.GetPolish();
+ ASSERT_TRUE(polish.ok());
+ EXPECT_TRUE(*polish);
+ }
+
+ ASSERT_TRUE(solver.UpdatePolish(false).ok());
+ {
+ const auto polish = solver.GetPolish();
+ ASSERT_TRUE(polish.ok());
+ EXPECT_FALSE(*polish);
+ }
+}
+
+TEST(OsqpTest, GetAndUpdatePolishRefineIter) {
+ OsqpSettings settings;
+ settings.polish_refine_iter = 12;
+
+ OsqpSolver solver;
+ ASSERT_TRUE(solver.Init(GetToyProblem(), settings).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+
+ {
+ const auto polish_refine_iter = solver.GetPolishRefineIter();
+ ASSERT_TRUE(polish_refine_iter.ok());
+ EXPECT_EQ(*polish_refine_iter, 12);
+ }
+
+ ASSERT_TRUE(solver.UpdatePolishRefineIter(13).ok());
+ {
+ const auto polish_refine_iter = solver.GetPolishRefineIter();
+ ASSERT_TRUE(polish_refine_iter.ok());
+ EXPECT_EQ(*polish_refine_iter, 13);
+ }
+}
+
+TEST(OsqpTest, GetAndUpdateVerbose) {
+ OsqpSettings settings;
+ settings.verbose = false;
+
+ OsqpSolver solver;
+ ASSERT_TRUE(solver.Init(GetToyProblem(), settings).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+
+ {
+ const auto verbose = solver.GetVerbose();
+ ASSERT_TRUE(verbose.ok());
+ EXPECT_FALSE(*verbose);
+ }
+
+ ASSERT_TRUE(solver.UpdateVerbose(true).ok());
+ {
+ const auto verbose = solver.GetVerbose();
+ ASSERT_TRUE(verbose.ok());
+ EXPECT_TRUE(*verbose);
+ }
+}
+
+TEST(OsqpTest, GetAndUpdateScaledTermination) {
+ OsqpSettings settings;
+ settings.scaled_termination = true;
+
+ OsqpSolver solver;
+ ASSERT_TRUE(solver.Init(GetToyProblem(), settings).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+
+ {
+ const auto scaled_termination = solver.GetScaledTermination();
+ ASSERT_TRUE(scaled_termination.ok());
+ EXPECT_TRUE(*scaled_termination);
+ }
+
+ ASSERT_TRUE(solver.UpdateScaledTermination(false).ok());
+ {
+ const auto scaled_termination = solver.GetScaledTermination();
+ ASSERT_TRUE(scaled_termination.ok());
+ EXPECT_FALSE(*scaled_termination);
+ }
+}
+
+TEST(OsqpTest, GetAndUpdateCheckTermination) {
+ OsqpSettings settings;
+ settings.check_termination = 12;
+
+ OsqpSolver solver;
+ ASSERT_TRUE(solver.Init(GetToyProblem(), settings).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+
+ {
+ const auto check_termination = solver.GetCheckTermination();
+ ASSERT_TRUE(check_termination.ok());
+ EXPECT_EQ(*check_termination, 12);
+ }
+
+ ASSERT_TRUE(solver.UpdateCheckTermination(13).ok());
+ {
+ const auto check_termination = solver.GetCheckTermination();
+ ASSERT_TRUE(check_termination.ok());
+ EXPECT_EQ(*check_termination, 13);
+ }
+}
+
+TEST(OsqpTest, GetAndUpdateWarmStart) {
+ OsqpSettings settings;
+ settings.warm_start = false;
+
+ OsqpSolver solver;
+ ASSERT_TRUE(solver.Init(GetToyProblem(), settings).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+
+ {
+ const auto warm_start = solver.GetWarmStart();
+ ASSERT_TRUE(warm_start.ok());
+ EXPECT_FALSE(*warm_start);
+ }
+
+ ASSERT_TRUE(solver.UpdateWarmStart(true).ok());
+ {
+ const auto warm_start = solver.GetWarmStart();
+ ASSERT_TRUE(warm_start.ok());
+ EXPECT_TRUE(*warm_start);
+ }
+}
+
+TEST(OsqpTest, GetAndUpdateTimeLimit) {
+ OsqpSettings settings;
+ settings.time_limit = 20.0;
+
+ OsqpSolver solver;
+ ASSERT_TRUE(solver.Init(GetToyProblem(), settings).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+
+ {
+ const auto time_limit = solver.GetTimeLimit();
+ ASSERT_TRUE(time_limit.ok());
+ EXPECT_EQ(*time_limit, 20.0);
+ }
+
+ ASSERT_TRUE(solver.UpdateTimeLimit(35.0).ok());
+ {
+ const auto time_limit = solver.GetTimeLimit();
+ ASSERT_TRUE(time_limit.ok());
+ EXPECT_EQ(*time_limit, 35.0);
+ }
+}
+
+TEST(OsqpTest, UpdateMaxIter) {
+ OsqpSettings settings;
+ settings.eps_abs = 1.0e-6;
+ settings.max_iter = 1;
+
+ // Try solving an easy problem and verify that we only used one iteration.
+ OsqpSolver solver;
+ ASSERT_TRUE(solver.Init(GetToyProblem(), settings).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kMaxIterations);
+ EXPECT_EQ(solver.iterations(), 1);
+
+ // Now allow for more iterations and check that they are actually used.
+ ASSERT_TRUE(solver.UpdateMaxIter(2).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kMaxIterations);
+ EXPECT_EQ(solver.iterations(), 2);
+
+ // Setting a large number of iterations allows us to actually solve the
+ // problem.
+ ASSERT_TRUE(solver.UpdateMaxIter(1000).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+}
+
+TEST(OsqpTest, UpdateEpsAbs) {
+ OsqpSettings settings;
+ // Check for termination after every iteration.
+ settings.check_termination = 1;
+ // Disable eps_rel to isolate the effect of eps_abs.
+ settings.eps_rel = 0.0;
+ // Set a very loose eps_abs value.
+ settings.eps_abs = 1;
+
+ // Solve and make a note of the error in our solution.
+ OsqpSolver solver;
+ ASSERT_TRUE(solver.Init(GetToyProblem(), settings).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+ const double err1 = (solver.primal_solution() - GetToySolution()).norm();
+
+ // Now set a much tighter eps_abs and solve again.
+ ASSERT_TRUE(solver.UpdateEpsAbs(1.0e-7).ok());
+ ASSERT_EQ(solver.Solve(), OsqpExitCode::kOptimal);
+ const double err2 = (solver.primal_solution() - GetToySolution()).norm();
+
+ // We should have ended up with a significantly smaller error the second time.
+ EXPECT_LT(100 * err2, err1);
+ // And we should be nearly optimal.
+ EXPECT_LT(err2, 1.0e-5);
+}
+
+// TODO(ml): Missing tests:
+// - Dual infeasible
+// - Dimension mismatches
+
+} // namespace
+} // namespace osqp