blob: 3f79fa995e41acccfc537b3a2b8d229b5ecbb13f [file] [log] [blame]
// 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_