LCOV - code coverage report
Current view: top level - src/include/panoc-alm/inner/directions - specialized-lbfgs.hpp (source / functions) Hit Total Coverage
Test: ecee3ec3a495b05c61f077aa7a236b7e00601437 Lines: 63 65 96.9 %
Date: 2021-11-04 22:49:09 Functions: 5 5 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : #pragma once
       2             : 
       3             : #include <panoc-alm/inner/detail/panoc-helpers.hpp>
       4             : #include <panoc-alm/inner/directions/decl/specialized-lbfgs.hpp>
       5             : #include <panoc-alm/inner/directions/lbfgs.hpp>
       6             : 
       7             : #include <cmath>
       8             : 
       9             : namespace pa {
      10             : 
      11           4 : inline void SpecializedLBFGS::initialize(crvec x₀, crvec grad₀) {
      12           4 :     idx  = 0;
      13           4 :     full = false;
      14           4 :     x(0) = x₀;
      15           4 :     g(0) = grad₀;
      16           4 : }
      17             : 
      18             : /// Standard L-BFGS update without changing the step size γ.
      19          13 : inline bool SpecializedLBFGS::standard_update(crvec xₖ, crvec xₖ₊₁,
      20             :                                               crvec pₖ, crvec pₖ₊₁,
      21             :                                               crvec gradₖ₊₁) {
      22          13 :     const auto s = xₖ₊₁ - xₖ;
      23          13 :     const auto y = pₖ - pₖ₊₁;
      24             : 
      25          13 :     real_t yᵀs = y.dot(s);
      26          13 :     real_t sᵀs = s.squaredNorm();
      27          13 :     real_t pᵀp = pₖ₊₁.squaredNorm();
      28          13 :     real_t ρ   = 1 / yᵀs;
      29             : 
      30          13 :     if (not LBFGS::update_valid(params, yᵀs, sᵀs, pᵀp))
      31           0 :         return false;
      32             : 
      33             :     // Store the new s and y vectors
      34          13 :     this->s(idx) = s;
      35          13 :     this->y(idx) = y;
      36          13 :     this->ρ(idx) = ρ;
      37             : 
      38             :     // Store x and the gradient
      39          13 :     this->x(succ(idx)) = xₖ₊₁;
      40          13 :     this->g(succ(idx)) = gradₖ₊₁;
      41             : 
      42             :     // Increment the index in the circular buffer
      43          13 :     idx = succ(idx);
      44          13 :     full |= idx == 0;
      45             : 
      46          13 :     return true;
      47          13 : }
      48             : 
      49             : /// L-BFGS update when changing the step size γ, recomputing everything.
      50           3 : inline bool SpecializedLBFGS::full_update(crvec xₖ, crvec xₖ₊₁,
      51             :                                           crvec pₖ_old_γ, crvec pₖ₊₁,
      52             :                                           crvec gradₖ₊₁, const Box &C,
      53             :                                           real_t γ) {
      54           3 :     auto &&sₖ = xₖ₊₁ - xₖ;
      55           3 :     auto &&yₖ = this->w(); // temporary workspace
      56             :     // Old pₖ is no longer valid, recompute with new γ
      57             :     (void)pₖ_old_γ;
      58           3 :     auto &&pₖ = this->p();
      59           3 :     pₖ        = detail::projected_gradient_step(C, γ, x(idx), g(idx));
      60           3 :     yₖ        = pₖ - pₖ₊₁;
      61             : 
      62           3 :     assert(x(idx) == xₖ);
      63             : 
      64           3 :     real_t yᵀs = yₖ.dot(sₖ);
      65           3 :     real_t sᵀs = sₖ.squaredNorm();
      66           3 :     real_t pᵀp = pₖ₊₁.squaredNorm();
      67           3 :     real_t ρₖ  = 1 / yᵀs;
      68             : 
      69           3 :     if (not LBFGS::update_valid(params, yᵀs, sᵀs, pᵀp))
      70           0 :         return false;
      71             : 
      72             :     // Recompute all residuals with new γ
      73             :     // yₖ = pₖ - pₖ₊₁
      74             :     // pₖ = Π(-γ∇ψ(xₖ), C - xₖ)
      75           3 :     size_t endidx = full ? idx : pred(0);
      76           8 :     for (size_t i = pred(idx); i != endidx; i = pred(i)) {
      77           5 :         this->y(i) = -pₖ /* i+1 */;
      78           5 :         pₖ = detail::projected_gradient_step(C, γ, this->x(i), this->g(i));
      79           5 :         this->y(i) += pₖ /* i */;
      80           5 :     }
      81             :     // Store the new s and y vectors
      82           3 :     this->s(idx) = sₖ;
      83           3 :     this->y(idx) = yₖ;
      84           3 :     this->ρ(idx) = ρₖ;
      85             : 
      86             :     // Store x and the gradient
      87           3 :     this->x(succ(idx)) = xₖ₊₁;
      88           3 :     this->g(succ(idx)) = gradₖ₊₁;
      89             : 
      90             :     // Increment the index in the circular buffer
      91           3 :     idx = succ(idx);
      92           3 :     full |= idx == 0;
      93             : 
      94           3 :     return true;
      95           3 : }
      96             : 
      97          16 : inline bool SpecializedLBFGS::update(crvec xₖ, crvec xₖ₊₁,
      98             :                                      crvec pₖ, crvec pₖ₊₁,
      99             :                                      crvec gradₖ₊₁, const Box &C,
     100             :                                      real_t γ) {
     101          32 :     bool ret = (γ == old_γ || old_γ == 0)
     102          13 :                    ? standard_update(xₖ, xₖ₊₁, pₖ, pₖ₊₁, gradₖ₊₁)
     103           3 :                    : full_update(xₖ, xₖ₊₁, pₖ, pₖ₊₁, gradₖ₊₁, C, γ);
     104          16 :     old_γ    = γ;
     105          16 :     return ret;
     106          16 : }
     107             : 
     108             : template <class Vec>
     109             : void SpecializedLBFGS::apply(Vec &&q) {
     110             :     // TODO: dry, reuse standard LBFGS::apply
     111             :     auto update1 = [&](size_t i) {
     112             :         α(i) = ρ(i) * (s(i).dot(q));
     113             :         q -= α(i) * y(i);
     114             :     };
     115             :     if (idx)
     116             :         for (size_t i = idx; i-- > 0;)
     117             :             update1(i);
     118             :     if (full)
     119             :         for (size_t i = history(); i-- > idx;)
     120             :             update1(i);
     121             : 
     122             :     // q = H₀ * q; // TODO: diagonal matrix H₀?
     123             : 
     124             :     auto update2 = [&](size_t i) {
     125             :         real_t β = ρ(i) * (y(i).dot(q));
     126             :         q += (α(i) - β) * s(i);
     127             :     };
     128             :     if (full)
     129             :         for (size_t i = idx; i < history(); ++i)
     130             :             update2(i);
     131             :     for (size_t i = 0; i < idx; ++i)
     132             :         update2(i);
     133             : }
     134             : 
     135           4 : inline void SpecializedLBFGS::resize(size_t n, size_t history) {
     136           4 :     sto.resize(n + 1, history * 4 + 2);
     137           4 :     sto.fill(std::numeric_limits<real_t>::quiet_NaN());
     138           4 :     idx  = 0;
     139           4 :     full = false;
     140           4 : }
     141             : 
     142             : inline void SpecializedLBFGS::reset() {
     143             :     x(0) = x(idx);
     144             :     g(0) = x(idx);
     145             :     idx  = 0;
     146             :     full = false;
     147             : }
     148             : 
     149             : } // namespace pa
     150             : 
     151             : #include <panoc-alm/inner/directions/decl/panoc-direction-update.hpp>
     152             : 
     153             : namespace pa {
     154             : 
     155             : template <>
     156             : struct PANOCDirection<SpecializedLBFGS> {
     157             : 
     158             :     static void initialize(SpecializedLBFGS &lbfgs, crvec x₀,
     159             :                            crvec x̂₀, crvec p₀, crvec grad₀) {
     160             :         lbfgs.initialize(x₀, grad₀);
     161             :         (void)x̂₀;
     162             :         (void)p₀;
     163             :     }
     164             : 
     165             :     static bool update(SpecializedLBFGS &lbfgs, crvec xₖ, crvec xₖ₊₁,
     166             :                        crvec pₖ, crvec pₖ₊₁, crvec gradₖ₊₁,
     167             :                        const Box &C, real_t γ) {
     168             :         return lbfgs.update(xₖ, xₖ₊₁, pₖ, pₖ₊₁, gradₖ₊₁, C, γ);
     169             :     }
     170             : 
     171             :     static bool apply(SpecializedLBFGS &lbfgs, crvec xₖ, crvec x̂ₖ,
     172             :                       crvec pₖ, real_t γ, rvec qₖ) {
     173             :         (void)xₖ;
     174             :         (void)x̂ₖ;
     175             :         (void)γ; // TODO: add this parameter to SLBFGS
     176             :         qₖ = pₖ;
     177             :         lbfgs.apply(qₖ);
     178             :         return true;
     179             :     }
     180             : 
     181             :     static void changed_γ(SpecializedLBFGS &lbfgs, real_t γₖ, real_t old_γₖ) {
     182             :         (void)lbfgs;
     183             :         (void)γₖ;
     184             :         (void)old_γₖ;
     185             :     }
     186             : };
     187             : 
     188             : } // namespace pa

Generated by: LCOV version 1.15