@@ -49,21 +49,31 @@ arma::vec solve_simplex_qp(const arma::mat& Q, const arma::vec& c, int max_iter
4949
5050 arma::vec y = x;
5151 double t_acc = 1.0 ;
52-
52+
5353 for (int iter = 0 ; iter < max_iter; iter++) {
5454 arma::vec x_prev = x;
55-
55+
5656 // Gradient step
5757 arma::vec grad = Q * y - c;
58-
58+
5959 // Projection step
6060 x = proj_simplex (y - t * grad);
61-
61+
62+ // Adaptive restart (O'Donoghue & Candes 2015, gradient scheme): when the
63+ // momentum direction disagrees with the descent direction, the
64+ // acceleration is overshooting and slowing convergence on ill-conditioned
65+ // Q (the dominant cost in SCM, where Q = X0'VX0 is highly collinear).
66+ // Resetting t_acc removes the stale momentum. This only changes the path
67+ // to the optimum, not the optimum itself.
68+ if (arma::dot (grad, x - x_prev) > 0.0 ) {
69+ t_acc = 1.0 ;
70+ }
71+
6272 // FISTA acceleration
6373 double t_acc_next = (1.0 + std::sqrt (1.0 + 4.0 * t_acc * t_acc)) / 2.0 ;
6474 y = x + ((t_acc - 1.0 ) / t_acc_next) * (x - x_prev);
6575 t_acc = t_acc_next;
66-
76+
6777 if (arma::norm (x - x_prev, 2 ) < tol) {
6878 break ;
6979 }
@@ -97,6 +107,10 @@ arma::vec solve_simplex_qp_lr(const arma::mat& B, const arma::vec& b,
97107 arma::vec x_prev = x;
98108 arma::vec grad = B.t () * (B * y - b); // O(k * N_co)
99109 x = proj_simplex (y - t * grad);
110+ // Adaptive restart (gradient scheme) -- see solve_simplex_qp for rationale.
111+ if (arma::dot (grad, x - x_prev) > 0.0 ) {
112+ t_acc = 1.0 ;
113+ }
100114 double t_acc_next = (1.0 + std::sqrt (1.0 + 4.0 * t_acc * t_acc)) / 2.0 ;
101115 y = x + ((t_acc - 1.0 ) / t_acc_next) * (x - x_prev);
102116 t_acc = t_acc_next;
0 commit comments