Skip to content

Commit bda7c8f

Browse files
authored
Merge pull request #114 from jcarpent/devel
Enforce robustness computation of duality gap
2 parents 10f88ae + df460b5 commit bda7c8f

18 files changed

+962
-1040
lines changed

include/proxsuite/helpers/common.hpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,38 @@ struct infinite_bound
2424
}
2525
};
2626

27+
#define PROXSUITE_DEDUCE_RET(...) \
28+
noexcept(noexcept(__VA_ARGS__)) \
29+
->typename std::remove_const<decltype(__VA_ARGS__)>::type \
30+
{ \
31+
return __VA_ARGS__; \
32+
} \
33+
static_assert(true, ".")
34+
35+
/// @brief \brief Returns the part of the expression which is lower than value
36+
template<typename T, typename Scalar>
37+
auto
38+
at_most(T const& expr, const Scalar value) PROXSUITE_DEDUCE_RET(
39+
(expr.array() < value).select(expr, T::Constant(expr.rows(), value)));
40+
41+
/// @brief \brief Returns the part of the expression which is greater than value
42+
template<typename T, typename Scalar>
43+
auto
44+
at_least(T const& expr, const Scalar value) PROXSUITE_DEDUCE_RET(
45+
(expr.array() > value).select(expr, T::Constant(expr.rows(), value)));
46+
47+
/// @brief \brief Returns the positive part of an expression
48+
template<typename T>
49+
auto
50+
positive_part(T const& expr)
51+
PROXSUITE_DEDUCE_RET((expr.array() > 0).select(expr, T::Zero(expr.rows())));
52+
53+
/// @brief \brief Returns the negative part of an expression
54+
template<typename T>
55+
auto
56+
negative_part(T const& expr)
57+
PROXSUITE_DEDUCE_RET((expr.array() < 0).select(expr, T::Zero(expr.rows())));
58+
2759
} // helpers
2860
} // proxsuite
2961

include/proxsuite/proxqp/dense/solver.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -459,8 +459,8 @@ compute_inner_loop_saddle_point(const Model<T>& qpmodel,
459459
{
460460

461461
qpwork.active_part_z =
462-
positive_part(qpwork.primal_residual_in_scaled_up) +
463-
negative_part(qpwork.primal_residual_in_scaled_low) -
462+
helpers::positive_part(qpwork.primal_residual_in_scaled_up) +
463+
helpers::negative_part(qpwork.primal_residual_in_scaled_low) -
464464
qpresults.z * qpresults.info.mu_in; // contains now : [Cx-u+z_prev*mu_in]+
465465
// + [Cx-l+z_prev*mu_in]- - z*mu_in
466466

include/proxsuite/proxqp/dense/utils.hpp

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,20 @@
77
#ifndef PROXSUITE_PROXQP_DENSE_UTILS_HPP
88
#define PROXSUITE_PROXQP_DENSE_UTILS_HPP
99

10+
#include <iostream>
11+
#include <fstream>
12+
#include <cmath>
13+
#include <type_traits>
14+
15+
#include "proxsuite/helpers/common.hpp"
1016
#include "proxsuite/proxqp/dense/views.hpp"
1117
#include "proxsuite/proxqp/dense/workspace.hpp"
1218
#include <proxsuite/proxqp/dense/model.hpp>
1319
#include <proxsuite/proxqp/results.hpp>
1420
#include <proxsuite/proxqp/utils/prints.hpp>
1521
#include <proxsuite/proxqp/settings.hpp>
1622
#include <proxsuite/proxqp/dense/preconditioner/ruiz.hpp>
17-
#include <iostream>
18-
#include <fstream>
19-
#include <cmath>
20-
#include <type_traits>
23+
2124
//#include <fmt/format.h>
2225
//#include <fmt/ostream.h>
2326

@@ -110,22 +113,6 @@ save_data(const std::string& filename, const ::Eigen::MatrixBase<Derived>& mat)
110113
}
111114
}
112115

113-
#define LDLT_DEDUCE_RET(...) \
114-
noexcept(noexcept(__VA_ARGS__)) \
115-
->typename std::remove_const<decltype(__VA_ARGS__)>::type \
116-
{ \
117-
return __VA_ARGS__; \
118-
} \
119-
static_assert(true, ".")
120-
template<typename T>
121-
auto
122-
positive_part(T const& expr)
123-
LDLT_DEDUCE_RET((expr.array() > 0).select(expr, T::Zero(expr.rows())));
124-
template<typename T>
125-
auto
126-
negative_part(T const& expr)
127-
LDLT_DEDUCE_RET((expr.array() < 0).select(expr, T::Zero(expr.rows())));
128-
129116
/*!
130117
* Derives the global primal residual of the QP problem.
131118
*
@@ -180,8 +167,8 @@ global_primal_residual(const Model<T>& qpmodel,
180167
primal_feasibility_in_rhs_0 = infty_norm(qpwork.primal_residual_in_scaled_up);
181168

182169
qpwork.primal_residual_in_scaled_low =
183-
positive_part(qpwork.primal_residual_in_scaled_up - qpmodel.u) +
184-
negative_part(qpwork.primal_residual_in_scaled_up - qpmodel.l);
170+
helpers::positive_part(qpwork.primal_residual_in_scaled_up - qpmodel.u) +
171+
helpers::negative_part(qpwork.primal_residual_in_scaled_up - qpmodel.l);
185172
qpwork.primal_residual_eq_scaled -= qpmodel.b;
186173

187174
primal_feasibility_in_lhs = infty_norm(qpwork.primal_residual_in_scaled_low);
@@ -236,8 +223,8 @@ global_primal_residual_infeasibility(
236223
ruiz.unscale_dual_residual_in_place(ATdy);
237224
ruiz.unscale_dual_residual_in_place(CTdz);
238225
T eq_inf = dy.to_eigen().dot(qpwork.b_scaled);
239-
T in_inf = positive_part(dz.to_eigen()).dot(qpwork.u_scaled) -
240-
positive_part(-dz.to_eigen()).dot(qpwork.l_scaled);
226+
T in_inf = helpers::positive_part(dz.to_eigen()).dot(qpwork.u_scaled) -
227+
helpers::negative_part(dz.to_eigen()).dot(qpwork.l_scaled);
241228
ruiz.unscale_dual_in_place_eq(dy);
242229
ruiz.unscale_dual_in_place_in(dz);
243230

@@ -375,7 +362,7 @@ global_dual_residual(Results<T>& qpresults,
375362
ruiz.unscale_primal_in_place(VectorViewMut<T>{ from_eigen, qpresults.x });
376363
duality_gap = (qpmodel.g).dot(qpresults.x);
377364
rhs_duality_gap = std::abs(duality_gap);
378-
T xHx = (qpwork.CTz).dot(qpresults.x);
365+
const T xHx = (qpwork.CTz).dot(qpresults.x);
379366
duality_gap += xHx; // contains now xHx+g.Tx
380367
rhs_duality_gap = std::max(rhs_duality_gap, std::abs(xHx));
381368

@@ -402,19 +389,29 @@ global_dual_residual(Results<T>& qpresults,
402389
VectorViewMut<T>{ from_eigen, qpwork.dual_residual_scaled });
403390

404391
ruiz.unscale_dual_in_place_eq(VectorViewMut<T>{ from_eigen, qpresults.y });
405-
T by = (qpmodel.b).dot(qpresults.y);
392+
const T by = (qpmodel.b).dot(qpresults.y);
406393
rhs_duality_gap = std::max(rhs_duality_gap, std::abs(by));
407394
duality_gap += by;
408395
ruiz.scale_dual_in_place_eq(VectorViewMut<T>{ from_eigen, qpresults.y });
396+
409397
ruiz.unscale_dual_in_place_in(VectorViewMut<T>{ from_eigen, qpresults.z });
410-
T zu = positive_part(qpresults.z).dot(qpmodel.u);
398+
399+
const T zu =
400+
helpers::positive_part(qpresults.z)
401+
.dot(helpers::at_most(qpmodel.u, helpers::infinite_bound<T>::value()));
411402
rhs_duality_gap = std::max(rhs_duality_gap, std::abs(zu));
412403
duality_gap += zu;
413-
T zl = negative_part(qpresults.z).dot(qpmodel.l);
404+
405+
const T zl =
406+
helpers::negative_part(qpresults.z)
407+
.dot(helpers::at_least(qpmodel.l, -helpers::infinite_bound<T>::value()));
414408
rhs_duality_gap = std::max(rhs_duality_gap, std::abs(zl));
415409
duality_gap += zl;
410+
416411
ruiz.scale_dual_in_place_in(VectorViewMut<T>{ from_eigen, qpresults.z });
412+
417413
duality_gap /= sqrt_max_dim; // in order to get an a-dimensional duality gap
414+
rhs_duality_gap /= sqrt_max_dim;
418415
}
419416

420417
} // namespace dense

include/proxsuite/proxqp/dense/wrapper.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ object Qp.solve(); // solve the problem
5858
// Verify solution accuracy
5959
T pri_res = std::max(
6060
(qp.A * Qp.results.x - qp.b).lpNorm<Eigen::Infinity>(),
61-
(proxqp::dense::positive_part(qp.C * Qp.results.x -
62-
qp.u) + proxqp::dense::negative_part(qp.C * Qp.results.x - qp.l))
61+
(helpers::positive_part(qp.C * Qp.results.x -
62+
qp.u) + helpers::negative_part(qp.C * Qp.results.x - qp.l))
6363
.lpNorm<Eigen::Infinity>());
6464
T dua_res = (qp.H * Qp.results.x + qp.g + qp.A.transpose() *
6565
Qp.results.y + qp.C.transpose() * Qp.results.z) .lpNorm<Eigen::Infinity>();

include/proxsuite/proxqp/sparse/solver.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,8 +1136,8 @@ qp_solve(Results<T>& results,
11361136
primal_residual_in_scaled_up += alpha * Cdx;
11371137

11381138
T err_in = std::max({
1139-
(infty_norm(detail::negative_part(primal_residual_in_scaled_lo) +
1140-
detail::positive_part(primal_residual_in_scaled_up) -
1139+
(infty_norm(helpers::negative_part(primal_residual_in_scaled_lo) +
1140+
helpers::positive_part(primal_residual_in_scaled_up) -
11411141
results.info.mu_in * z_e)),
11421142
(infty_norm(primal_residual_eq_scaled)),
11431143
(infty_norm(dual_residual_scaled)),

include/proxsuite/proxqp/sparse/utils.hpp

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
#ifndef PROXSUITE_PROXQP_SPARSE_UTILS_HPP
77
#define PROXSUITE_PROXQP_SPARSE_UTILS_HPP
88

9+
#include <iostream>
10+
#include <Eigen/IterativeLinearSolvers>
11+
#include <unsupported/Eigen/IterativeSolvers>
12+
13+
#include "proxsuite/helpers/common.hpp"
914
#include <proxsuite/linalg/dense/core.hpp>
1015
#include <proxsuite/linalg/sparse/core.hpp>
1116
#include <proxsuite/linalg/sparse/factorize.hpp>
@@ -21,10 +26,6 @@
2126
#include "proxsuite/proxqp/sparse/preconditioner/ruiz.hpp"
2227
#include "proxsuite/proxqp/sparse/preconditioner/identity.hpp"
2328

24-
#include <iostream>
25-
#include <Eigen/IterativeLinearSolvers>
26-
#include <unsupported/Eigen/IterativeSolvers>
27-
2829
namespace proxsuite {
2930
namespace proxqp {
3031
namespace sparse {
@@ -98,15 +99,8 @@ print_setup_header(const Settings<T>& settings,
9899
<< std::endl;
99100
}
100101
}
102+
101103
namespace detail {
102-
template<typename T>
103-
auto
104-
positive_part(T const& expr)
105-
VEG_DEDUCE_RET((expr.array() > 0).select(expr, T::Zero(expr.rows())));
106-
template<typename T>
107-
auto
108-
negative_part(T const& expr)
109-
VEG_DEDUCE_RET((expr.array() < 0).select(expr, T::Zero(expr.rows())));
110104

111105
template<typename T, typename I>
112106
VEG_NO_INLINE void
@@ -500,8 +494,8 @@ global_primal_residual_infeasibility(VectorViewMut<T> ATdy,
500494
ruiz.unscale_dual_residual_in_place(ATdy);
501495
ruiz.unscale_dual_residual_in_place(CTdz);
502496
T eq_inf = dy.to_eigen().dot(qp_scaled.b.to_eigen());
503-
T in_inf = positive_part(dz.to_eigen()).dot(qp_scaled.u.to_eigen()) -
504-
positive_part(-dz.to_eigen()).dot(qp_scaled.l.to_eigen());
497+
T in_inf = helpers::positive_part(dz.to_eigen()).dot(qp_scaled.u.to_eigen()) -
498+
helpers::negative_part(dz.to_eigen()).dot(qp_scaled.l.to_eigen());
505499
ruiz.unscale_dual_in_place_eq(dy);
506500
ruiz.unscale_dual_in_place_in(dz);
507501

@@ -657,27 +651,36 @@ unscaled_primal_dual_residual(
657651
precond.unscale_primal_in_place({ proxqp::from_eigen, x_e });
658652
results.info.duality_gap = x_e.dot(data.g); // contains gTx
659653
rhs_duality_gap = std::abs(results.info.duality_gap);
660-
T xHx = (tmp).dot(x_e);
654+
655+
const T xHx = (tmp).dot(x_e);
661656
results.info.duality_gap += xHx;
662657
rhs_duality_gap = std::max(rhs_duality_gap, std::abs(xHx));
663658
tmp += data.g; // contains now Hx+g
664659
precond.scale_primal_in_place({ proxqp::from_eigen, x_e });
665660

666661
precond.unscale_dual_in_place_eq({ proxsuite::proxqp::from_eigen, y_e });
667-
T by = (data.b).dot(y_e);
662+
const T by = (data.b).dot(y_e);
668663
results.info.duality_gap += by;
669664
rhs_duality_gap = std::max(rhs_duality_gap, std::abs(by));
670665
precond.scale_dual_in_place_eq({ proxsuite::proxqp::from_eigen, y_e });
666+
671667
precond.unscale_dual_in_place_in({ proxsuite::proxqp::from_eigen, z_e });
672-
T zl = (data.l).dot(detail::negative_part(z_e));
668+
669+
const T zl = helpers::negative_part(z_e).dot(
670+
helpers::at_least(data.l, -helpers::infinite_bound<T>::value()));
673671
results.info.duality_gap += zl;
674672
rhs_duality_gap = std::max(rhs_duality_gap, std::abs(zl));
675-
T zu = (data.u).dot(detail::positive_part(z_e));
673+
674+
const T zu = helpers::positive_part(z_e).dot(
675+
helpers::at_most(data.u, helpers::infinite_bound<T>::value()));
676676
results.info.duality_gap += zu;
677677
rhs_duality_gap = std::max(rhs_duality_gap, std::abs(zu));
678+
678679
precond.scale_dual_in_place_in({ proxsuite::proxqp::from_eigen, z_e });
680+
679681
results.info.duality_gap /=
680682
sqrt_max_dim; // in order to get an a-dimensional duality gap
683+
rhs_duality_gap /= sqrt_max_dim;
681684
}
682685

683686
{
@@ -719,8 +722,8 @@ unscaled_primal_dual_residual(
719722
auto l = data.l;
720723
auto u = data.u;
721724
primal_residual_in_scaled_lo =
722-
positive_part(primal_residual_in_scaled_up - u) +
723-
negative_part(primal_residual_in_scaled_up - l);
725+
helpers::positive_part(primal_residual_in_scaled_up - u) +
726+
helpers::negative_part(primal_residual_in_scaled_up - l);
724727

725728
primal_residual_eq_scaled -= b;
726729
T primal_feasibility_eq_lhs = infty_norm(primal_residual_eq_scaled);

include/proxsuite/proxqp/sparse/wrapper.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ ::proxsuite::proxqp::test::rand::vector_rand<T>(n_in); auto u = (l.array() +
6868
// Verify solution accuracy
6969
T pri_res = std::max(
7070
(qp.A * Qp.results.x - qp.b).lpNorm<Eigen::Infinity>(),
71-
(proxqp::dense::positive_part(qp.C * Qp.results.x -
72-
qp.u) + proxqp::dense::negative_part(qp.C * Qp.results.x - qp.l))
71+
(helpers::positive_part(qp.C * Qp.results.x -
72+
qp.u) + helpers::negative_part(qp.C * Qp.results.x - qp.l))
7373
.lpNorm<Eigen::Infinity>());
7474
T dua_res = (qp.H * Qp.results.x + qp.g + qp.A.transpose() *
7575
Qp.results.y + qp.C.transpose() * Qp.results.z) .lpNorm<Eigen::Infinity>();

include/proxsuite/proxqp/utils/prints.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ print_preambule()
3131
{
3232
print_line();
3333
std::cout
34-
<< " ProxQP - Primal Dual Proximal QP "
34+
<< " ProxQP - Primal-Dual Proximal QP "
3535
"Solver\n"
3636
<< " (c) Antoine Bambade, Sarah El Kazdadi, Fabian Schramm, Adrien "
37-
"Taylor, "
37+
"Taylor, and "
3838
"Justin Carpentier\n"
3939
<< " Inria Paris 2022 \n"
4040
<< std::endl;

test/src/cvxpy.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ DOCTEST_TEST_CASE("3 dim test case from cvxpy, check feasibility")
4242
Results<T> results = dense::solve<T>(
4343
H, g, nullopt, nullopt, C, l, u, nullopt, nullopt, nullopt, eps_abs, 0);
4444

45-
T pri_res = (dense::positive_part(C * results.x - u) +
46-
dense::negative_part(C * results.x - l))
45+
T pri_res = (helpers::positive_part(C * results.x - u) +
46+
helpers::negative_part(C * results.x - l))
4747
.lpNorm<Eigen::Infinity>();
4848
T dua_res =
4949
(H * results.x + g + C.transpose() * results.z).lpNorm<Eigen::Infinity>();
@@ -82,8 +82,8 @@ DOCTEST_TEST_CASE("simple test case from cvxpy, check feasibility")
8282
Results<T> results = dense::solve<T>(
8383
H, g, nullopt, nullopt, C, l, u, nullopt, nullopt, nullopt, eps_abs, 0);
8484

85-
T pri_res = (dense::positive_part(C * results.x - u) +
86-
dense::negative_part(C * results.x - l))
85+
T pri_res = (helpers::positive_part(C * results.x - u) +
86+
helpers::negative_part(C * results.x - l))
8787
.lpNorm<Eigen::Infinity>();
8888
T dua_res =
8989
(H * results.x + g + C.transpose() * results.z).lpNorm<Eigen::Infinity>();
@@ -140,8 +140,8 @@ DOCTEST_TEST_CASE("simple test case from cvxpy, init with solution, check that "
140140
z << 0.0;
141141
qp.solve(x, nullopt, z);
142142

143-
T pri_res = (dense::positive_part(C * qp.results.x - u) +
144-
dense::negative_part(C * qp.results.x - l))
143+
T pri_res = (helpers::positive_part(C * qp.results.x - u) +
144+
helpers::negative_part(C * qp.results.x - l))
145145
.lpNorm<Eigen::Infinity>();
146146
T dua_res = (H * qp.results.x + g + C.transpose() * qp.results.z)
147147
.lpNorm<Eigen::Infinity>();

test/src/dense_maros_meszaros.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,8 @@ TEST_CASE("dense maros meszaros using the api")
136136

137137
T prim_eq = proxqp::dense::infty_norm(A * x - b);
138138
T prim_in =
139-
proxqp::dense::infty_norm(proxqp::dense::positive_part(C * x - u) +
140-
proxqp::dense::negative_part(C * x - l));
139+
proxqp::dense::infty_norm(helpers::positive_part(C * x - u) +
140+
helpers::negative_part(C * x - l));
141141
std::cout << "primal residual " << std::max(prim_eq, prim_in)
142142
<< std::endl;
143143
std::cout << "dual residual "

0 commit comments

Comments
 (0)