Skip to content

Commit 90d81af

Browse files
authored
Merge pull request #110 from Bambade/add_duality_gap
Add duality gap measure
2 parents 0e3f6b5 + 1ff7959 commit 90d81af

File tree

5 files changed

+144
-39
lines changed

5 files changed

+144
-39
lines changed

include/proxsuite/proxqp/dense/solver.hpp

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,9 @@ qp_solve( //
985985
T primal_feasibility_in_lhs(0);
986986
T dual_feasibility_lhs(0);
987987

988+
T duality_gap(0);
989+
T rhs_duality_gap(0);
990+
988991
for (i64 iter = 0; iter < qpsettings.max_iter; ++iter) {
989992

990993
// compute primal residual
@@ -1002,13 +1005,17 @@ qp_solve( //
10021005

10031006
global_dual_residual(qpresults,
10041007
qpwork,
1008+
qpmodel,
10051009
ruiz,
10061010
dual_feasibility_lhs,
10071011
dual_feasibility_rhs_0,
10081012
dual_feasibility_rhs_1,
1009-
dual_feasibility_rhs_3);
1013+
dual_feasibility_rhs_3,
1014+
rhs_duality_gap,
1015+
duality_gap);
10101016
qpresults.info.pri_res = primal_feasibility_lhs;
10111017
qpresults.info.dua_res = dual_feasibility_lhs;
1018+
qpresults.info.duality_gap = duality_gap;
10121019

10131020
T new_bcl_mu_in(qpresults.info.mu_in);
10141021
T new_bcl_mu_eq(qpresults.info.mu_eq);
@@ -1059,8 +1066,9 @@ qp_solve( //
10591066
std::cout << std::scientific << std::setw(2) << std::setprecision(2)
10601067
<< "| primal residual=" << qpresults.info.pri_res
10611068
<< "| dual residual=" << qpresults.info.dua_res
1062-
<< " | mu_in=" << qpresults.info.mu_in
1063-
<< " | rho=" << qpresults.info.rho << std::endl;
1069+
<< "| duality gap=" << qpresults.info.duality_gap
1070+
<< "| mu_in=" << qpresults.info.mu_in
1071+
<< "| rho=" << qpresults.info.rho << std::endl;
10641072
ruiz.scale_primal_in_place(VectorViewMut<T>{ from_eigen, qpresults.x });
10651073
ruiz.scale_dual_in_place_eq(VectorViewMut<T>{ from_eigen, qpresults.y });
10661074
ruiz.scale_dual_in_place_in(VectorViewMut<T>{ from_eigen, qpresults.z });
@@ -1079,8 +1087,12 @@ qp_solve( //
10791087
qpresults.info.rho = rho_new;
10801088
}
10811089
if (is_dual_feasible) {
1082-
qpresults.info.status = QPSolverOutput::PROXQP_SOLVED;
1083-
break;
1090+
if (qpresults.info.duality_gap <=
1091+
qpsettings.eps_abs +
1092+
(qpsettings.eps_abs + qpsettings.eps_rel) * rhs_duality_gap) {
1093+
qpresults.info.status = QPSolverOutput::PROXQP_SOLVED;
1094+
break;
1095+
}
10841096
}
10851097
}
10861098
qpresults.info.iter_ext += 1; // We start a new external loop update
@@ -1138,12 +1150,16 @@ qp_solve( //
11381150

11391151
global_dual_residual(qpresults,
11401152
qpwork,
1153+
qpmodel,
11411154
ruiz,
11421155
dual_feasibility_lhs_new,
11431156
dual_feasibility_rhs_0,
11441157
dual_feasibility_rhs_1,
1145-
dual_feasibility_rhs_3);
1158+
dual_feasibility_rhs_3,
1159+
rhs_duality_gap,
1160+
duality_gap);
11461161
qpresults.info.dua_res = dual_feasibility_lhs_new;
1162+
qpresults.info.duality_gap = duality_gap;
11471163

11481164
is_dual_feasible =
11491165
dual_feasibility_lhs_new <=
@@ -1154,8 +1170,12 @@ qp_solve( //
11541170
std::max(dual_feasibility_rhs_1, qpwork.dual_feasibility_rhs_2)));
11551171

11561172
if (is_dual_feasible) {
1157-
qpresults.info.status = QPSolverOutput::PROXQP_SOLVED;
1158-
break;
1173+
if (qpresults.info.duality_gap <=
1174+
qpsettings.eps_abs +
1175+
(qpsettings.eps_abs + qpsettings.eps_rel) * rhs_duality_gap) {
1176+
qpresults.info.status = QPSolverOutput::PROXQP_SOLVED;
1177+
break;
1178+
}
11591179
}
11601180
}
11611181
if (qpsettings.bcl_update) {
@@ -1190,12 +1210,16 @@ qp_solve( //
11901210

11911211
global_dual_residual(qpresults,
11921212
qpwork,
1213+
qpmodel,
11931214
ruiz,
11941215
dual_feasibility_lhs_new,
11951216
dual_feasibility_rhs_0,
11961217
dual_feasibility_rhs_1,
1197-
dual_feasibility_rhs_3);
1218+
dual_feasibility_rhs_3,
1219+
rhs_duality_gap,
1220+
duality_gap);
11981221
qpresults.info.dua_res = dual_feasibility_lhs_new;
1222+
qpresults.info.duality_gap = duality_gap;
11991223

12001224
if (primal_feasibility_lhs_new >= primal_feasibility_lhs &&
12011225
dual_feasibility_lhs_new >= dual_feasibility_lhs &&

include/proxsuite/proxqp/dense/utils.hpp

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -343,27 +343,44 @@ global_dual_residual_infeasibility(
343343
*/
344344
template<typename T>
345345
void
346-
global_dual_residual(const Results<T>& qpresults,
346+
global_dual_residual(Results<T>& qpresults,
347347
Workspace<T>& qpwork,
348+
const Model<T>& qpmodel,
348349
const preconditioner::RuizEquilibration<T>& ruiz,
349350
T& dual_feasibility_lhs,
350351
T& dual_feasibility_rhs_0,
351352
T& dual_feasibility_rhs_1,
352-
T& dual_feasibility_rhs_3)
353+
T& dual_feasibility_rhs_3,
354+
T& rhs_duality_gap,
355+
T& duality_gap)
353356
{
354357
// dual_feasibility_lhs = norm(dual_residual_scaled)
355358
// dual_feasibility_rhs_0 = norm(unscaled(Hx))
356359
// dual_feasibility_rhs_1 = norm(unscaled(ATy))
357360
// dual_feasibility_rhs_3 = norm(unscaled(CTz))
358361
//
359362
// dual_residual_scaled = scaled(Hx + g + ATy + CTz)
363+
const isize max_dim =
364+
std::max(qpmodel.dim, std::max(qpmodel.n_eq, qpmodel.n_in));
365+
const T sqrt_max_dim(std::sqrt(max_dim)); // for normalizing scalar products
366+
360367
qpwork.dual_residual_scaled = qpwork.g_scaled;
361368
qpwork.CTz.noalias() =
362369
qpwork.H_scaled.template selfadjointView<Eigen::Lower>() * qpresults.x;
363370
qpwork.dual_residual_scaled += qpwork.CTz;
364371
ruiz.unscale_dual_residual_in_place(
365-
VectorViewMut<T>{ from_eigen, qpwork.CTz });
372+
VectorViewMut<T>{ from_eigen, qpwork.CTz }); // contains unscaled Hx
366373
dual_feasibility_rhs_0 = infty_norm(qpwork.CTz);
374+
375+
ruiz.unscale_primal_in_place(VectorViewMut<T>{ from_eigen, qpresults.x });
376+
duality_gap = (qpmodel.g).dot(qpresults.x);
377+
rhs_duality_gap = std::abs(duality_gap);
378+
T xHx = (qpwork.CTz).dot(qpresults.x);
379+
duality_gap += xHx; // contains now xHx+g.Tx
380+
rhs_duality_gap = std::max(rhs_duality_gap, std::abs(xHx));
381+
382+
ruiz.scale_primal_in_place(VectorViewMut<T>{ from_eigen, qpresults.x });
383+
367384
qpwork.CTz.noalias() = qpwork.A_scaled.transpose() * qpresults.y;
368385
qpwork.dual_residual_scaled += qpwork.CTz;
369386
ruiz.unscale_dual_residual_in_place(
@@ -383,6 +400,21 @@ global_dual_residual(const Results<T>& qpresults,
383400

384401
ruiz.scale_dual_residual_in_place(
385402
VectorViewMut<T>{ from_eigen, qpwork.dual_residual_scaled });
403+
404+
ruiz.unscale_dual_in_place_eq(VectorViewMut<T>{ from_eigen, qpresults.y });
405+
T by = (qpmodel.b).dot(qpresults.y);
406+
rhs_duality_gap = std::max(rhs_duality_gap, std::abs(by));
407+
duality_gap += by;
408+
ruiz.scale_dual_in_place_eq(VectorViewMut<T>{ from_eigen, qpresults.y });
409+
ruiz.unscale_dual_in_place_in(VectorViewMut<T>{ from_eigen, qpresults.z });
410+
T zu = positive_part(qpresults.z).dot(qpmodel.u);
411+
rhs_duality_gap = std::max(rhs_duality_gap, std::abs(zu));
412+
duality_gap += zu;
413+
T zl = negative_part(qpresults.z).dot(qpmodel.l);
414+
rhs_duality_gap = std::max(rhs_duality_gap, std::abs(zl));
415+
duality_gap += zl;
416+
ruiz.scale_dual_in_place_in(VectorViewMut<T>{ from_eigen, qpresults.z });
417+
duality_gap /= sqrt_max_dim; // in order to get an a-dimensional duality gap
386418
}
387419

388420
} // namespace dense

include/proxsuite/proxqp/results.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ struct Info
4949
T objValue;
5050
T pri_res;
5151
T dua_res;
52-
52+
T duality_gap;
5353
//// sparse backend used by solver, either CholeskySparse or MatrixFree
5454
SparseBackend sparse_backend;
5555
};
@@ -106,6 +106,7 @@ struct Results
106106
info.objValue = 0.;
107107
info.pri_res = 0.;
108108
info.dua_res = 0.;
109+
info.duality_gap = 0.;
109110
info.status = QPSolverOutput::PROXQP_NOT_RUN;
110111
info.sparse_backend = SparseBackend::Automatic;
111112
}
@@ -132,6 +133,7 @@ struct Results
132133
info.rho_updates = 0;
133134
info.pri_res = 0.;
134135
info.dua_res = 0.;
136+
info.duality_gap = 0.;
135137
info.status = QPSolverOutput::PROXQP_MAX_ITER_REACHED;
136138
info.sparse_backend = SparseBackend::Automatic;
137139
}

include/proxsuite/proxqp/sparse/solver.hpp

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,8 @@ qp_solve(Results<T>& results,
724724
break;
725725
}
726726
}
727+
T rhs_duality_gap(0);
728+
727729
for (isize iter = 0; iter < settings.max_iter; ++iter) {
728730

729731
results.info.iter_ext += 1;
@@ -777,7 +779,8 @@ qp_solve(Results<T>& results,
777779
VEG_BIND( // ?
778780
auto,
779781
(primal_feasibility_lhs, dual_feasibility_lhs),
780-
detail::unscaled_primal_dual_residual(primal_residual_eq_scaled,
782+
detail::unscaled_primal_dual_residual(results,
783+
primal_residual_eq_scaled,
781784
primal_residual_in_scaled_lo,
782785
primal_residual_in_scaled_up,
783786
dual_residual_scaled,
@@ -786,12 +789,13 @@ qp_solve(Results<T>& results,
786789
dual_feasibility_rhs_0,
787790
dual_feasibility_rhs_1,
788791
dual_feasibility_rhs_3,
792+
rhs_duality_gap,
789793
precond,
790794
data,
791795
qp_scaled.as_const(),
792-
detail::vec(x_e),
793-
detail::vec(y_e),
794-
detail::vec(z_e),
796+
detail::vec_mut(x_e),
797+
detail::vec_mut(y_e),
798+
detail::vec_mut(z_e),
795799
stack));
796800
/*put in debug mode
797801
if (settings.verbose) {
@@ -824,8 +828,9 @@ qp_solve(Results<T>& results,
824828
std::cout << std::scientific << std::setw(2) << std::setprecision(2)
825829
<< "| primal residual=" << primal_feasibility_lhs
826830
<< "| dual residual=" << dual_feasibility_lhs
827-
<< " | mu_in=" << results.info.mu_in
828-
<< " | rho=" << results.info.rho << std::endl;
831+
<< "| dualality gap=" << results.info.duality_gap
832+
<< "| mu_in=" << results.info.mu_in
833+
<< "| rho=" << results.info.rho << std::endl;
829834
results.info.pri_res = primal_feasibility_lhs;
830835
results.info.dua_res = dual_feasibility_lhs;
831836
precond.scale_primal_in_place(VectorViewMut<T>{ from_eigen, x_e });
@@ -834,10 +839,14 @@ qp_solve(Results<T>& results,
834839
}
835840
if (is_primal_feasible(primal_feasibility_lhs) &&
836841
is_dual_feasible(dual_feasibility_lhs)) {
837-
results.info.pri_res = primal_feasibility_lhs;
838-
results.info.dua_res = dual_feasibility_lhs;
839-
results.info.status = QPSolverOutput::PROXQP_SOLVED;
840-
break;
842+
if (results.info.duality_gap <=
843+
settings.eps_abs +
844+
(settings.eps_rel + settings.eps_abs) * rhs_duality_gap) {
845+
results.info.pri_res = primal_feasibility_lhs;
846+
results.info.dua_res = dual_feasibility_lhs;
847+
results.info.status = QPSolverOutput::PROXQP_SOLVED;
848+
break;
849+
}
841850
}
842851

843852
LDLT_TEMP_VEC_UNINIT(T, x_prev_e, n, stack);
@@ -1202,7 +1211,8 @@ qp_solve(Results<T>& results,
12021211
VEG_BIND(
12031212
auto,
12041213
(primal_feasibility_lhs_new, dual_feasibility_lhs_new),
1205-
detail::unscaled_primal_dual_residual(primal_residual_eq_scaled,
1214+
detail::unscaled_primal_dual_residual(results,
1215+
primal_residual_eq_scaled,
12061216
primal_residual_in_scaled_lo,
12071217
primal_residual_in_scaled_up,
12081218
dual_residual_scaled,
@@ -1211,20 +1221,25 @@ qp_solve(Results<T>& results,
12111221
dual_feasibility_rhs_0,
12121222
dual_feasibility_rhs_1,
12131223
dual_feasibility_rhs_3,
1224+
rhs_duality_gap,
12141225
precond,
12151226
data,
12161227
qp_scaled.as_const(),
1217-
detail::vec(x_e),
1218-
detail::vec(y_e),
1219-
detail::vec(z_e),
1228+
detail::vec_mut(x_e),
1229+
detail::vec_mut(y_e),
1230+
detail::vec_mut(z_e),
12201231
stack));
12211232

12221233
if (is_primal_feasible(primal_feasibility_lhs_new) &&
12231234
is_dual_feasible(dual_feasibility_lhs_new)) {
1224-
results.info.pri_res = primal_feasibility_lhs_new;
1225-
results.info.dua_res = dual_feasibility_lhs_new;
1226-
results.info.status = QPSolverOutput::PROXQP_SOLVED;
1227-
break;
1235+
if (results.info.duality_gap <=
1236+
settings.eps_abs +
1237+
(settings.eps_abs + settings.eps_rel) * rhs_duality_gap) {
1238+
results.info.pri_res = primal_feasibility_lhs_new;
1239+
results.info.dua_res = dual_feasibility_lhs_new;
1240+
results.info.status = QPSolverOutput::PROXQP_SOLVED;
1241+
break;
1242+
}
12281243
}
12291244

12301245
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
@@ -1259,7 +1274,8 @@ qp_solve(Results<T>& results,
12591274
VEG_BIND(
12601275
auto,
12611276
(_, dual_feasibility_lhs_new_2),
1262-
detail::unscaled_primal_dual_residual(primal_residual_eq_scaled,
1277+
detail::unscaled_primal_dual_residual(results,
1278+
primal_residual_eq_scaled,
12631279
primal_residual_in_scaled_lo,
12641280
primal_residual_in_scaled_up,
12651281
dual_residual_scaled,
@@ -1268,12 +1284,13 @@ qp_solve(Results<T>& results,
12681284
dual_feasibility_rhs_0,
12691285
dual_feasibility_rhs_1,
12701286
dual_feasibility_rhs_3,
1287+
rhs_duality_gap,
12711288
precond,
12721289
data,
12731290
qp_scaled.as_const(),
1274-
detail::vec(x_e),
1275-
detail::vec(y_e),
1276-
detail::vec(z_e),
1291+
detail::vec_mut(x_e),
1292+
detail::vec_mut(y_e),
1293+
detail::vec_mut(z_e),
12771294
stack));
12781295
proxsuite::linalg::veg::unused(_);
12791296

0 commit comments

Comments
 (0)