Skip to content

Commit 76977e5

Browse files
authored
Merge pull request #84 from fabinsch/topic/settings-sparse-backend
User option sparse backend
2 parents 8311c3e + 71f92a5 commit 76977e5

File tree

13 files changed

+393
-10
lines changed

13 files changed

+393
-10
lines changed

bindings/python/src/expose-results.hpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@ exposeResults(pybind11::module_ m)
4040
.def_readwrite("objValue", &Info<T>::objValue)
4141
.def_readwrite("status", &Info<T>::status)
4242
.def_readwrite("rho_updates", &Info<T>::rho_updates)
43-
.def_readwrite("mu_updates", &Info<T>::mu_updates);
43+
.def_readwrite("mu_updates", &Info<T>::mu_updates)
44+
.def_readwrite("sparse_backend",
45+
&Info<T>::sparse_backend,
46+
"Sparse backend used to solve the qp, either SparseCholesky "
47+
"or MatrixFree.");
4448

4549
::pybind11::class_<Results<T>>(m, "Results", pybind11::module_local())
4650
.def(::pybind11::init<i64, i64, i64>(),

bindings/python/src/expose-settings.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ exposeSettings(pybind11::module_ m)
2626
InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT)
2727
.export_values();
2828

29+
::pybind11::enum_<SparseBackend>(m, "SparseBackend", pybind11::module_local())
30+
.value("Automatic", SparseBackend::Automatic)
31+
.value("MatrixFree", SparseBackend::MatrixFree)
32+
.value("SparseCholesky", SparseBackend::SparseCholesky)
33+
.export_values();
34+
2935
::pybind11::class_<Settings<T>>(m, "Settings", pybind11::module_local())
3036
.def(::pybind11::init(), "Default constructor.") // constructor
3137
.def_readwrite("default_rho", &Settings<T>::default_rho)
@@ -51,6 +57,7 @@ exposeSettings(pybind11::module_ m)
5157
.def_readwrite("nb_iterative_refinement",
5258
&Settings<T>::nb_iterative_refinement)
5359
.def_readwrite("initial_guess", &Settings<T>::initial_guess)
60+
.def_readwrite("sparse_backend", &Settings<T>::sparse_backend)
5461
.def_readwrite("preconditioner_accuracy",
5562
&Settings<T>::preconditioner_accuracy)
5663
.def_readwrite("preconditioner_max_iter",

bindings/python/src/expose-solve.hpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,11 @@ solveSparseQp(pybind11::module_ m)
139139
"conditioning and speeding up the solver."),
140140
pybind11::arg_v("compute_timings", true, "compute solver's timings."),
141141
pybind11::arg_v("max_iter", nullopt, "maximum number of iteration."),
142-
pybind11::arg_v(
143-
"initial_guess",
144-
proxsuite::proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS,
145-
"maximum number of iteration."));
142+
pybind11::arg_v("initial_guess",
143+
proxsuite::proxqp::InitialGuessStatus::
144+
EQUALITY_CONSTRAINED_INITIAL_GUESS),
145+
pybind11::arg_v("sparse_backend",
146+
proxsuite::proxqp::SparseBackend::Automatic));
146147
}
147148

148149
} // namespace python

include/proxsuite/proxqp/results.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ struct Info
4949
T objValue;
5050
T pri_res;
5151
T dua_res;
52+
53+
//// sparse backend used by solver, either CholeskySparse or MatrixFree
54+
SparseBackend sparse_backend;
5255
};
5356
///
5457
/// @brief This class stores all the results of PROXQP solvers with sparse and
@@ -104,6 +107,7 @@ struct Results
104107
info.pri_res = 0.;
105108
info.dua_res = 0.;
106109
info.status = QPSolverOutput::PROXQP_NOT_RUN;
110+
info.sparse_backend = SparseBackend::Automatic;
107111
}
108112
/*!
109113
* cleanups the Result variables and set the info variables to their initial
@@ -129,6 +133,7 @@ struct Results
129133
info.pri_res = 0.;
130134
info.dua_res = 0.;
131135
info.status = QPSolverOutput::PROXQP_MAX_ITER_REACHED;
136+
info.sparse_backend = SparseBackend::Automatic;
132137
}
133138
void cold_start(optional<Settings<T>> settings = nullopt)
134139
{

include/proxsuite/proxqp/settings.hpp

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,28 @@
1414

1515
namespace proxsuite {
1616
namespace proxqp {
17+
18+
// Sparse backend specifications
19+
enum struct SparseBackend
20+
{
21+
Automatic, // the solver will select the appropriate sparse backend.
22+
SparseCholesky, // sparse cholesky backend.
23+
MatrixFree, // iterative matrix free sparse backend.
24+
};
25+
26+
inline std::ostream&
27+
operator<<(std::ostream& os, const SparseBackend& sparse_backend)
28+
{
29+
if (sparse_backend == SparseBackend::Automatic)
30+
os << "Automatic";
31+
else if (sparse_backend == SparseBackend::SparseCholesky) {
32+
os << "SparseCholesky";
33+
} else {
34+
os << "MatrixFree";
35+
}
36+
return os;
37+
}
38+
1739
///
1840
/// @brief This class defines the settings of PROXQP solvers with sparse and
1941
/// dense backends.
@@ -68,6 +90,8 @@ struct Settings
6890
T eps_primal_inf;
6991
T eps_dual_inf;
7092
bool bcl_update;
93+
94+
SparseBackend sparse_backend;
7195
/*!
7296
* Default constructor.
7397
* @param default_rho default rho parameter of result class
@@ -121,6 +145,8 @@ struct Settings
121145
* @param bcl_update if set to true, BCL strategy is used for calibrating
122146
* mu_eq and mu_in. If set to false, a strategy developped by Martinez & al is
123147
* used.
148+
* @param sparse_backend Default automatic. User can choose between sparse
149+
* cholesky or iterative matrix free sparse backend.
124150
*/
125151

126152
Settings(
@@ -161,7 +187,8 @@ struct Settings
161187
T preconditioner_accuracy = 1.e-3,
162188
T eps_primal_inf = 1.E-4,
163189
T eps_dual_inf = 1.E-4,
164-
bool bcl_update = true)
190+
bool bcl_update = true,
191+
SparseBackend sparse_backend = SparseBackend::Automatic)
165192
: default_rho(default_rho)
166193
, default_mu_eq(default_mu_eq)
167194
, default_mu_in(default_mu_in)
@@ -196,6 +223,7 @@ struct Settings
196223
, eps_primal_inf(eps_primal_inf)
197224
, eps_dual_inf(eps_dual_inf)
198225
, bcl_update(bcl_update)
226+
, sparse_backend(sparse_backend)
199227
{
200228
}
201229
/*
@@ -231,7 +259,8 @@ struct Settings
231259
T preconditioner_accuracy_ = 1.e-3,
232260
T eps_primal_inf_ = 1.E-4,
233261
T eps_dual_inf_ = 1.E-4,
234-
bool bcl_update_ = true
262+
bool bcl_update_ = true,
263+
SparseBackend sparse_backend_ = SparseBackend::Automatic
235264
){
236265
alpha_bcl = alpha_bcl_;
237266
beta_bcl = beta_bcl_ ;
@@ -264,6 +293,8 @@ struct Settings
264293
eps_primal_inf = eps_primal_inf_;
265294
eps_dual_inf = eps_dual_inf_;
266295
bcl_update = bcl_update_;
296+
sparse_backend = sparse_backend_;
297+
267298
268299
}
269300
*/

include/proxsuite/proxqp/sparse/helpers.hpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,19 @@ qp_setup(QpView<T, I> qp,
222222
break;
223223
}
224224
}
225+
// if user chose Automatic as sparse backend, store in results which backend
226+
// of SparseCholesky or MatrixFree had been used
227+
if (settings.sparse_backend == SparseBackend::Automatic) {
228+
if (work.internal.do_ldlt) {
229+
results.info.sparse_backend = SparseBackend::SparseCholesky;
230+
} else {
231+
results.info.sparse_backend = SparseBackend::MatrixFree;
232+
}
233+
}
234+
// if user selected a specfic sparse backend, store it in results
235+
else {
236+
results.info.sparse_backend = settings.sparse_backend;
237+
}
225238
}
226239
/*!
227240
* Checks whether matrix b has the same sparsity structure as matrix a.

include/proxsuite/proxqp/sparse/utils.hpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,13 @@ print_setup_header(const Settings<T>& settings,
4949
// Print Settings
5050
std::cout << "settings: " << std::endl;
5151
std::cout << " backend = sparse," << std::endl;
52+
std::cout << " sparse_backend = " << settings.sparse_backend;
53+
if (settings.sparse_backend == SparseBackend::Automatic) {
54+
std::cout << " -> " << results.info.sparse_backend;
55+
}
56+
std::cout << "," << std::endl;
5257
std::cout << " eps_abs = " << settings.eps_abs
53-
<< " eps_rel = " << settings.eps_rel << std::endl;
58+
<< ", eps_rel = " << settings.eps_rel << std::endl;
5459
std::cout << " eps_prim_inf = " << settings.eps_primal_inf
5560
<< ", eps_dual_inf = " << settings.eps_dual_inf << "," << std::endl;
5661

include/proxsuite/proxqp/sparse/workspace.hpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,14 @@ struct Workspace
490490

491491
// if ldlt is too sparse
492492
// do_ldlt = !overflow && lnnz < (10000000);
493-
do_ldlt = !overflow && lnnz < 10000000;
493+
if (settings.sparse_backend == SparseBackend::Automatic) {
494+
do_ldlt = !overflow && lnnz < 10000000;
495+
} else if (settings.sparse_backend == SparseBackend::SparseCholesky) {
496+
do_ldlt = true;
497+
} else {
498+
do_ldlt = false;
499+
}
500+
494501
} else {
495502
T* kktx = data.kkt_values.ptr_mut();
496503
usize pos = 0;

include/proxsuite/proxqp/sparse/wrapper.hpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -700,7 +700,9 @@ solve(
700700
bool compute_timings = true,
701701
optional<isize> max_iter = nullopt,
702702
proxsuite::proxqp::InitialGuessStatus initial_guess =
703-
proxsuite::proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS)
703+
proxsuite::proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS,
704+
proxsuite::proxqp::SparseBackend sparse_backend =
705+
proxsuite::proxqp::SparseBackend::Automatic)
704706
{
705707

706708
isize n(0);
@@ -732,6 +734,7 @@ solve(
732734
Qp.settings.max_iter = verbose.value();
733735
}
734736
Qp.settings.compute_timings = compute_timings;
737+
Qp.settings.sparse_backend = sparse_backend;
735738
Qp.init(H, g, A, b, C, l, u, compute_preconditioner, rho, mu_eq, mu_in);
736739
Qp.solve(x, y, z);
737740

test/src/sparse_qp_solve.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,78 @@ DOCTEST_TEST_CASE(
216216
}
217217
}
218218

219+
DOCTEST_TEST_CASE(
220+
"sparse random strongly convex qp with equality and "
221+
"inequality constraints: test setting specific sparse backend")
222+
{
223+
224+
std::cout
225+
<< "---testing sparse random strongly convex qp with equality and "
226+
"inequality constraints: test setting specific sparse backend ---"
227+
<< std::endl;
228+
for (auto const& dims : { // proxsuite::linalg::veg::tuplify(50, 0, 0),
229+
// proxsuite::linalg::veg::tuplify(50, 25, 0),
230+
// proxsuite::linalg::veg::tuplify(10, 0, 10),
231+
// proxsuite::linalg::veg::tuplify(50, 0, 25),
232+
// proxsuite::linalg::veg::tuplify(50, 10, 25),
233+
proxsuite::linalg::veg::tuplify(10, 3, 2) }) {
234+
VEG_BIND(auto const&, (n, n_eq, n_in), dims);
235+
236+
double eps_abs = 1.e-9;
237+
T sparsity_factor = 0.15;
238+
T strong_convexity_factor = 0.01;
239+
::proxsuite::proxqp::utils::rand::set_seed(1);
240+
proxqp::sparse::SparseModel<T> qp = utils::sparse_strongly_convex_qp(
241+
n, n_eq, n_in, sparsity_factor, strong_convexity_factor);
242+
proxsuite::proxqp::InitialGuessStatus initial_guess =
243+
proxsuite::proxqp::InitialGuessStatus::NO_INITIAL_GUESS;
244+
proxsuite::proxqp::SparseBackend sparse_backend =
245+
proxsuite::proxqp::SparseBackend::MatrixFree;
246+
proxsuite::proxqp::Results<T> results =
247+
proxsuite::proxqp::sparse::solve<T, I>(qp.H,
248+
qp.g,
249+
qp.A,
250+
qp.b,
251+
qp.C,
252+
qp.l,
253+
qp.u,
254+
nullopt,
255+
nullopt,
256+
nullopt,
257+
eps_abs,
258+
nullopt,
259+
nullopt,
260+
nullopt,
261+
nullopt,
262+
nullopt,
263+
true,
264+
true,
265+
nullopt,
266+
initial_guess,
267+
sparse_backend);
268+
T dua_res = proxqp::dense::infty_norm(
269+
qp.H.selfadjointView<Eigen::Upper>() * results.x + qp.g +
270+
qp.A.transpose() * results.y + qp.C.transpose() * results.z);
271+
T pri_res =
272+
std::max(proxqp::dense::infty_norm(qp.A * results.x - qp.b),
273+
proxqp::dense::infty_norm(
274+
sparse::detail::positive_part(qp.C * results.x - qp.u) +
275+
sparse::detail::negative_part(qp.C * results.x - qp.l)));
276+
DOCTEST_CHECK(pri_res <= eps_abs);
277+
DOCTEST_CHECK(dua_res <= eps_abs);
278+
DOCTEST_CHECK(results.info.sparse_backend == SparseBackend::MatrixFree);
279+
280+
std::cout << "------using API solving qp with dim: " << n
281+
<< " neq: " << n_eq << " nin: " << n_in << std::endl;
282+
std::cout << "primal residual: " << pri_res << std::endl;
283+
std::cout << "dual residual: " << dua_res << std::endl;
284+
std::cout << "total number of iteration: " << results.info.iter
285+
<< std::endl;
286+
std::cout << "setup timing " << results.info.setup_time << " solve time "
287+
<< results.info.solve_time << std::endl;
288+
}
289+
}
290+
219291
DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and "
220292
"inequality constraints: test warm starting")
221293
{

0 commit comments

Comments
 (0)