LBFGS++ 0.4.0
A header-only C++ library for L-BFGS and L-BFGS-B algorithms.
Loading...
Searching...
No Matches
BFGSMat.h
1// Copyright (C) 2020-2026 Yixuan Qiu <yixuan.qiu@cos.name>
2// Under MIT license
3
4#ifndef LBFGSPP_BFGS_MAT_H
5#define LBFGSPP_BFGS_MAT_H
6
7#include <vector>
8#include <Eigen/Core>
9#include <Eigen/LU>
10#include "BKLDLT.h"
11
13
14namespace LBFGSpp {
15
16//
17// An *implicit* representation of the BFGS approximation to the Hessian matrix
18//
19// B = theta * I - W * M * W' -- approximation to Hessian matrix, see [2]
20// H = inv(B) -- approximation to inverse Hessian matrix, see [2]
21//
22// Reference:
23// [1] D. C. Liu and J. Nocedal (1989). On the limited memory BFGS method for large scale optimization.
24// [2] R. H. Byrd, P. Lu, and J. Nocedal (1995). A limited memory algorithm for bound constrained optimization.
25//
26template <typename Scalar, bool LBFGSB = false>
27class BFGSMat
28{
29private:
30 using Vector = Eigen::Matrix<Scalar, Eigen::Dynamic, 1>;
31 using Matrix = Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>;
32 using RefConstVec = Eigen::Ref<const Vector>;
33 using IndexSet = std::vector<int>;
34
35 int m_m; // Maximum number of correction vectors
36 Scalar m_theta; // theta * I is the initial approximation to the Hessian matrix
37 Matrix m_s; // History of the s vectors
38 Matrix m_y; // History of the y vectors
39 Vector m_ys; // History of the s'y values
40 Vector m_alpha; // Temporary values used in computing H * v
41 int m_ncorr; // Number of correction vectors in the history, m_ncorr <= m
42 int m_ptr; // A Pointer to locate the most recent history, 1 <= m_ptr <= m
43 // Details: s and y vectors are stored in cyclic order.
44 // For example, if the current s-vector is stored in m_s[, m-1],
45 // then in the next iteration m_s[, 0] will be overwritten.
46 // m_s[, m_ptr-1] points to the most recent history (if ncorr > 0),
47 // and m_s[, m_ptr % m] points to the location that will be
48 // overwritten next time.
49
50 //========== The following members are only used in L-BFGS-B algorithm ==========//
51 Matrix m_permMinv; // Permutated M inverse
52 BKLDLT<Scalar> m_permMsolver; // Represents the permutated M matrix
53
54public:
55 // Constructor
56 BFGSMat() {}
57
58 // Reset internal variables
59 // n: dimension of the vector to be optimized
60 // m: maximum number of corrections to approximate the Hessian matrix
61 inline void reset(int n, int m)
62 {
63 m_m = m;
64 m_theta = Scalar(1);
65 m_s.resize(n, m);
66 m_y.resize(n, m);
67 m_ys.resize(m);
68 m_alpha.resize(m);
69 m_ncorr = 0;
70 m_ptr = m; // This makes sure that m_ptr % m == 0 in the first step
71
72 if (LBFGSB)
73 {
74 m_permMinv.resize(2 * m, 2 * m);
75 m_permMinv.setZero();
76 m_permMinv.diagonal().setOnes();
77 }
78 }
79
80 // Add correction vectors to the BFGS matrix
81 inline void add_correction(const RefConstVec& s, const RefConstVec& y)
82 {
83 const int loc = m_ptr % m_m;
84
85 m_s.col(loc).noalias() = s;
86 m_y.col(loc).noalias() = y;
87
88 // ys = y's = 1/rho
89 const Scalar ys = m_s.col(loc).dot(m_y.col(loc));
90 m_ys[loc] = ys;
91
92 m_theta = m_y.col(loc).squaredNorm() / ys;
93
94 if (m_ncorr < m_m)
95 m_ncorr++;
96
97 m_ptr = loc + 1;
98
99 if (LBFGSB)
100 {
101 // Minv = [-D L']
102 // [ L theta*S'S]
103
104 // Copy -D
105 // Let S=[s[0], ..., s[m-1]], Y=[y[0], ..., y[m-1]]
106 // D = [s[0]'y[0], ..., s[m-1]'y[m-1]]
107 m_permMinv(loc, loc) = -ys;
108
109 // Update S'S
110 // We only store S'S in Minv, and multiply theta when LU decomposition is performed
111 Vector Ss = m_s.leftCols(m_ncorr).transpose() * m_s.col(loc);
112 m_permMinv.block(m_m + loc, m_m, 1, m_ncorr).noalias() = Ss.transpose();
113 m_permMinv.block(m_m, m_m + loc, m_ncorr, 1).noalias() = Ss;
114
115 // Compute L
116 // L = [ 0 ]
117 // [ s[1]'y[0] 0 ]
118 // [ s[2]'y[0] s[2]'y[1] ]
119 // ...
120 // [s[m-1]'y[0] ... ... ... ... ... s[m-1]'y[m-2] 0]
121 //
122 // L_next = [ 0 ]
123 // [s[2]'y[1] 0 ]
124 // [s[3]'y[1] s[3]'y[2] ]
125 // ...
126 // [s[m]'y[1] ... ... ... ... ... s[m]'y[m-1] 0]
127 const int len = m_ncorr - 1;
128 // First zero out the column of oldest y
129 if (m_ncorr >= m_m)
130 m_permMinv.block(m_m, loc, m_m, 1).setZero();
131 // Compute the row associated with new s
132 // The current row is loc
133 // End with column (loc + m - 1) % m
134 // Length is len
135 int yloc = (loc + m_m - 1) % m_m;
136 for (int i = 0; i < len; i++)
137 {
138 m_permMinv(m_m + loc, yloc) = m_s.col(loc).dot(m_y.col(yloc));
139 yloc = (yloc + m_m - 1) % m_m;
140 }
141
142 // Matrix LDLT factorization
143 m_permMinv.block(m_m, m_m, m_m, m_m) *= m_theta;
144 m_permMsolver.compute(m_permMinv);
145 m_permMinv.block(m_m, m_m, m_m, m_m) /= m_theta;
146 }
147 }
148
149 // Explicitly form the B matrix
150 inline Matrix get_Bmat() const
151 {
152 // Initial approximation theta * I
153 const int n = m_s.rows();
154 Matrix B = m_theta * Matrix::Identity(n, n);
155 if (m_ncorr < 1)
156 return B;
157
158 // Construct W matrix, W = [Y, theta * S]
159 // Y = [y0, y1, ..., yc]
160 // S = [s0, s1, ..., sc]
161 // We first set W = [Y, S], since later we still need Y and S matrices
162 // After computing Minv, we rescale the S part in W
163 Matrix W(n, 2 * m_ncorr);
164 // r = m_ptr - 1 points to the most recent element,
165 // (r + 1) % m_ncorr points to the oldest element
166 int j = m_ptr % m_ncorr;
167 for (int i = 0; i < m_ncorr; i++)
168 {
169 W.col(i).noalias() = m_y.col(j);
170 W.col(m_ncorr + i).noalias() = m_s.col(j);
171 j = (j + 1) % m_m;
172 }
173 // Now Y = W[:, :c], S = W[:, c:]
174
175 // Construct Minv matrix, Minv = [-D L' ]
176 // [ L theta * S'S]
177
178 // D = diag(y0's0, ..., yc'sc)
179 Matrix Minv(2 * m_ncorr, 2 * m_ncorr);
180 Minv.topLeftCorner(m_ncorr, m_ncorr).setZero();
181 Vector ys = W.leftCols(m_ncorr).cwiseProduct(W.rightCols(m_ncorr)).colwise().sum().transpose();
182 Minv.diagonal().head(m_ncorr).noalias() = -ys;
183 // L = [ 0 ]
184 // [ s[1]'y[0] 0 ]
185 // [ s[2]'y[0] s[2]'y[1] ]
186 // ...
187 // [s[c-1]'y[0] ... ... ... ... ... s[c-1]'y[c-2] 0]
188 Minv.bottomLeftCorner(m_ncorr, m_ncorr).setZero();
189 for (int i = 0; i < m_ncorr - 1; i++)
190 {
191 // Number of terms for this column
192 const int nterm = m_ncorr - i - 1;
193 // S[:, -nterm:]'Y[:, j]
194 Minv.col(i).tail(nterm).noalias() = W.rightCols(nterm).transpose() * W.col(i);
195 }
196 // The symmetric block
197 Minv.topRightCorner(m_ncorr, m_ncorr).noalias() = Minv.bottomLeftCorner(m_ncorr, m_ncorr).transpose();
198 // theta * S'S
199 Minv.bottomRightCorner(m_ncorr, m_ncorr).noalias() = m_theta * W.rightCols(m_ncorr).transpose() * W.rightCols(m_ncorr);
200
201 // Set the true W matrix
202 W.rightCols(m_ncorr).array() *= m_theta;
203
204 // Compute B = theta * I - W * M * W'
205 Eigen::PartialPivLU<Matrix> M_solver(Minv);
206 B.noalias() -= W * M_solver.solve(W.transpose());
207 return B;
208 }
209
210 // Explicitly form the H matrix
211 inline Matrix get_Hmat() const
212 {
213 // Initial approximation 1/theta * I
214 const int n = m_s.rows();
215 Matrix H = (Scalar(1) / m_theta) * Matrix::Identity(n, n);
216 if (m_ncorr < 1)
217 return H;
218
219 // Construct W matrix, W = [1/theta * Y, S]
220 // Y = [y0, y1, ..., yc]
221 // S = [s0, s1, ..., sc]
222 // We first set W = [Y, S], since later we still need Y and S matrices
223 // After computing M, we rescale the Y part in W
224 Matrix W(n, 2 * m_ncorr);
225 // p = m_ptr - 1 points to the most recent element,
226 // (p + 1) % m_ncorr points to the oldest element
227 int j = m_ptr % m_ncorr;
228 for (int i = 0; i < m_ncorr; i++)
229 {
230 W.col(i).noalias() = m_y.col(j);
231 W.col(m_ncorr + i).noalias() = m_s.col(j);
232 j = (j + 1) % m_m;
233 }
234 // Now Y = W[:, :c], S = W[:, c:]
235
236 // Construct M matrix, M = [ 0 -inv(R) ]
237 // [ -inv(R)' inv(R)'(D + 1/theta * Y'Y)inv(R) ]
238 // D = diag(y0's0, ..., yc'sc)
239 Matrix M(2 * m_ncorr, 2 * m_ncorr);
240 // First use M[:c, :c] to store R
241 // R = [s[0]'y[0] s[0]'y[1] ... s[0]'y[c-1] ]
242 // [ 0 s[1]'y[1] ... s[1]'y[c-1] ]
243 // ...
244 // [ 0 0 ... s[c-1]'y[c-1] ]
245 for (int i = 0; i < m_ncorr; i++)
246 {
247 M.col(i).head(i + 1).noalias() = W.middleCols(m_ncorr, i + 1).transpose() * W.col(i);
248 }
249 // Compute inv(R)
250 Matrix Rinv = M.topLeftCorner(m_ncorr, m_ncorr).template triangularView<Eigen::Upper>().solve(Matrix::Identity(m_ncorr, m_ncorr));
251 // Zero out the top left block
252 M.topLeftCorner(m_ncorr, m_ncorr).setZero();
253 // Set the top right block
254 M.topRightCorner(m_ncorr, m_ncorr).noalias() = -Rinv;
255 // The symmetric block
256 M.bottomLeftCorner(m_ncorr, m_ncorr).noalias() = -Rinv.transpose();
257 // 1/theta * Y'Y
258 Matrix block = (Scalar(1) / m_theta) * W.leftCols(m_ncorr).transpose() * W.leftCols(m_ncorr);
259 // D + 1/theta * Y'Y
260 Vector ys = W.leftCols(m_ncorr).cwiseProduct(W.rightCols(m_ncorr)).colwise().sum().transpose();
261 block.diagonal().array() += ys.array();
262 // The bottom right block
263 M.bottomRightCorner(m_ncorr, m_ncorr).noalias() = Rinv.transpose() * block * Rinv;
264
265 // Set the true W matrix
266 W.leftCols(m_ncorr).array() *= (Scalar(1) / m_theta);
267
268 // Compute H = 1/theta * I + W * M * W'
269 H.noalias() += W * M * W.transpose();
270 return H;
271 }
272
273 // Recursive formula to compute a * H * v, where a is a scalar, and v is [n x 1]
274 // H0 = (1/theta) * I is the initial approximation to H
275 // Algorithm 7.4 of Nocedal, J., & Wright, S. (2006). Numerical optimization.
276 inline void apply_Hv(const Vector& v, const Scalar& a, Vector& res)
277 {
278 res.resize(v.size());
279
280 // L-BFGS two-loop recursion
281
282 // Loop 1
283 res.noalias() = a * v;
284 int j = m_ptr % m_m;
285 for (int i = 0; i < m_ncorr; i++)
286 {
287 j = (j + m_m - 1) % m_m;
288 m_alpha[j] = m_s.col(j).dot(res) / m_ys[j];
289 res.noalias() -= m_alpha[j] * m_y.col(j);
290 }
291
292 // Apply initial H0
293 res /= m_theta;
294
295 // Loop 2
296 for (int i = 0; i < m_ncorr; i++)
297 {
298 const Scalar beta = m_y.col(j).dot(res) / m_ys[j];
299 res.noalias() += (m_alpha[j] - beta) * m_s.col(j);
300 j = (j + 1) % m_m;
301 }
302 }
303
304 //========== The following functions are only used in L-BFGS-B algorithm ==========//
305
306 // Return the value of theta
307 inline Scalar theta() const { return m_theta; }
308
309 // Return current number of correction vectors
310 inline int num_corrections() const { return m_ncorr; }
311
312 // W = [Y, theta * S]
313 // W [n x (2*ncorr)], v [n x 1], res [(2*ncorr) x 1]
314 // res preserves the ordering of Y and S columns
315 inline void apply_Wtv(const Vector& v, Vector& res) const
316 {
317 res.resize(2 * m_ncorr);
318 res.head(m_ncorr).noalias() = m_y.leftCols(m_ncorr).transpose() * v;
319 res.tail(m_ncorr).noalias() = m_theta * m_s.leftCols(m_ncorr).transpose() * v;
320 }
321
322 // The b-th row of the W matrix
323 // Preserves the ordering of Y and S columns
324 // Return as a column vector
325 inline Vector Wb(int b) const
326 {
327 Vector res(2 * m_ncorr);
328 for (int j = 0; j < m_ncorr; j++)
329 {
330 res[j] = m_y(b, j);
331 res[m_ncorr + j] = m_s(b, j);
332 }
333 res.tail(m_ncorr) *= m_theta;
334 return res;
335 }
336
337 // Extract rows of W
338 inline Matrix Wb(const IndexSet& b) const
339 {
340 const int nb = b.size();
341 const int* bptr = b.data();
342 Matrix res(nb, 2 * m_ncorr);
343
344 for (int j = 0; j < m_ncorr; j++)
345 {
346 const Scalar* Yptr = &m_y(0, j);
347 const Scalar* Sptr = &m_s(0, j);
348 Scalar* resYptr = res.data() + j * nb;
349 Scalar* resSptr = resYptr + m_ncorr * nb;
350 for (int i = 0; i < nb; i++)
351 {
352 const int row = bptr[i];
353 resYptr[i] = Yptr[row];
354 resSptr[i] = Sptr[row];
355 }
356 }
357 return res;
358 }
359
360 // M is [(2*ncorr) x (2*ncorr)], v is [(2*ncorr) x 1]
361 inline void apply_Mv(const Vector& v, Vector& res) const
362 {
363 res.resize(2 * m_ncorr);
364 if (m_ncorr < 1)
365 return;
366
367 Vector vpadding = Vector::Zero(2 * m_m);
368 vpadding.head(m_ncorr).noalias() = v.head(m_ncorr);
369 vpadding.segment(m_m, m_ncorr).noalias() = v.tail(m_ncorr);
370
371 // Solve linear equation
372 m_permMsolver.solve_inplace(vpadding);
373
374 res.head(m_ncorr).noalias() = vpadding.head(m_ncorr);
375 res.tail(m_ncorr).noalias() = vpadding.segment(m_m, m_ncorr);
376 }
377
378 // Compute W'Pv
379 // W [n x (2*ncorr)], v [nP x 1], res [(2*ncorr) x 1]
380 // res preserves the ordering of Y and S columns
381 // Returns false if the result is known to be zero
382 inline bool apply_WtPv(const IndexSet& P_set, const Vector& v, Vector& res, bool test_zero = false) const
383 {
384 const int* Pptr = P_set.data();
385 const Scalar* vptr = v.data();
386 int nP = P_set.size();
387
388 // Remove zeros in v to save computation
389 IndexSet P_reduced;
390 std::vector<Scalar> v_reduced;
391 if (test_zero)
392 {
393 P_reduced.reserve(nP);
394 for (int i = 0; i < nP; i++)
395 {
396 if (vptr[i] != Scalar(0))
397 {
398 P_reduced.push_back(Pptr[i]);
399 v_reduced.push_back(vptr[i]);
400 }
401 }
402 Pptr = P_reduced.data();
403 vptr = v_reduced.data();
404 nP = P_reduced.size();
405 }
406
407 res.resize(2 * m_ncorr);
408 if (m_ncorr < 1 || nP < 1)
409 {
410 res.setZero();
411 return false;
412 }
413
414 for (int j = 0; j < m_ncorr; j++)
415 {
416 Scalar resy = Scalar(0), ress = Scalar(0);
417 const Scalar* yptr = &m_y(0, j);
418 const Scalar* sptr = &m_s(0, j);
419 for (int i = 0; i < nP; i++)
420 {
421 const int row = Pptr[i];
422 resy += yptr[row] * vptr[i];
423 ress += sptr[row] * vptr[i];
424 }
425 res[j] = resy;
426 res[m_ncorr + j] = ress;
427 }
428 res.tail(m_ncorr) *= m_theta;
429 return true;
430 }
431
432 // Compute s * P'WMv
433 // Assume that v[2*ncorr x 1] has the same ordering (permutation) as W and M
434 // Returns false if the result is known to be zero
435 inline bool apply_PtWMv(const IndexSet& P_set, const Vector& v, Vector& res, const Scalar& scale) const
436 {
437 const int nP = P_set.size();
438 res.resize(nP);
439 res.setZero();
440 if (m_ncorr < 1 || nP < 1)
441 return false;
442
443 Vector Mv;
444 apply_Mv(v, Mv);
445 // WP * Mv
446 Mv.tail(m_ncorr) *= m_theta;
447 for (int j = 0; j < m_ncorr; j++)
448 {
449 const Scalar* yptr = &m_y(0, j);
450 const Scalar* sptr = &m_s(0, j);
451 const Scalar Mvy = Mv[j], Mvs = Mv[m_ncorr + j];
452 for (int i = 0; i < nP; i++)
453 {
454 const int row = P_set[i];
455 res[i] += Mvy * yptr[row] + Mvs * sptr[row];
456 }
457 }
458 res *= scale;
459 return true;
460 }
461 // If the P'W matrix has been explicitly formed, do a direct matrix multiplication
462 inline bool apply_PtWMv(const Matrix& WP, const Vector& v, Vector& res, const Scalar& scale) const
463 {
464 const int nP = WP.rows();
465 res.resize(nP);
466 if (m_ncorr < 1 || nP < 1)
467 {
468 res.setZero();
469 return false;
470 }
471
472 Vector Mv;
473 apply_Mv(v, Mv);
474 // WP * Mv
475 Mv.tail(m_ncorr) *= m_theta;
476 res.noalias() = scale * (WP * Mv);
477 return true;
478 }
479
480 // Compute F'BAb = -(F'W)M(W'AA'd)
481 // W'd is known, and AA'+FF'=I, so W'AA'd = W'd - W'FF'd
482 // Usually d contains many zeros, so we fist compute number of nonzero elements in A set and F set,
483 // denoted as nnz_act and nnz_fv, respectively
484 // If nnz_act is smaller, compute W'AA'd = WA' (A'd) directly
485 // If nnz_fv is smaller, compute W'AA'd = W'd - WF' * (F'd)
486 inline void compute_FtBAb(
487 const Matrix& WF, const IndexSet& fv_set, const IndexSet& newact_set, const Vector& Wd, const Vector& drt,
488 Vector& res) const
489 {
490 const int nact = newact_set.size();
491 const int nfree = WF.rows();
492 res.resize(nfree);
493 if (m_ncorr < 1 || nact < 1 || nfree < 1)
494 {
495 res.setZero();
496 return;
497 }
498
499 // W'AA'd
500 Vector rhs(2 * m_ncorr);
501 if (nact <= nfree)
502 {
503 // Construct A'd
504 Vector Ad(nfree);
505 for (int i = 0; i < nact; i++)
506 Ad[i] = drt[newact_set[i]];
507 apply_WtPv(newact_set, Ad, rhs);
508 }
509 else
510 {
511 // Construct F'd
512 Vector Fd(nfree);
513 for (int i = 0; i < nfree; i++)
514 Fd[i] = drt[fv_set[i]];
515 // Compute W'AA'd = W'd - WF' * (F'd)
516 rhs.noalias() = WF.transpose() * Fd;
517 rhs.tail(m_ncorr) *= m_theta;
518 rhs.noalias() = Wd - rhs;
519 }
520
521 apply_PtWMv(WF, rhs, res, Scalar(-1));
522 }
523
524 // Compute inv(P'BP) * v
525 // P represents an index set
526 // inv(P'BP) * v = v / theta + WP * inv(inv(M) - WP' * WP / theta) * WP' * v / theta^2
527 //
528 // v is [nP x 1]
529 inline void solve_PtBP(const Matrix& WP, const Vector& v, Vector& res) const
530 {
531 const int nP = WP.rows();
532 res.resize(nP);
533 if (m_ncorr < 1 || nP < 1)
534 {
535 res.noalias() = v / m_theta;
536 return;
537 }
538
539 // Compute the matrix in the middle (only the lower triangular part is needed)
540 // Remember that W = [Y, theta * S], but we do not store theta in WP
541 Matrix mid(2 * m_ncorr, 2 * m_ncorr);
542 // [0:(ncorr - 1), 0:(ncorr - 1)]
543 for (int j = 0; j < m_ncorr; j++)
544 {
545 mid.col(j).segment(j, m_ncorr - j).noalias() = m_permMinv.col(j).segment(j, m_ncorr - j) -
546 WP.block(0, j, nP, m_ncorr - j).transpose() * WP.col(j) / m_theta;
547 }
548 // [ncorr:(2 * ncorr - 1), 0:(ncorr - 1)]
549 mid.block(m_ncorr, 0, m_ncorr, m_ncorr).noalias() = m_permMinv.block(m_m, 0, m_ncorr, m_ncorr) -
550 WP.rightCols(m_ncorr).transpose() * WP.leftCols(m_ncorr);
551 // [ncorr:(2 * ncorr - 1), ncorr:(2 * ncorr - 1)]
552 for (int j = 0; j < m_ncorr; j++)
553 {
554 mid.col(m_ncorr + j).segment(m_ncorr + j, m_ncorr - j).noalias() = m_theta *
555 (m_permMinv.col(m_m + j).segment(m_m + j, m_ncorr - j) - WP.rightCols(m_ncorr - j).transpose() * WP.col(m_ncorr + j));
556 }
557 // Factorization
558 BKLDLT<Scalar> midsolver(mid);
559 // Compute the final result
560 Vector WPv = WP.transpose() * v;
561 WPv.tail(m_ncorr) *= m_theta;
562 midsolver.solve_inplace(WPv);
563 WPv.tail(m_ncorr) *= m_theta;
564 res.noalias() = v / m_theta + (WP * WPv) / (m_theta * m_theta);
565 }
566
567 // Compute P'BQv, where P and Q are two mutually exclusive index selection operators
568 // P'BQv = -WP * M * WQ' * v
569 // Returns false if the result is known to be zero
570 inline bool apply_PtBQv(const Matrix& WP, const IndexSet& Q_set, const Vector& v, Vector& res, bool test_zero = false) const
571 {
572 const int nP = WP.rows();
573 const int nQ = Q_set.size();
574 res.resize(nP);
575 if (m_ncorr < 1 || nP < 1 || nQ < 1)
576 {
577 res.setZero();
578 return false;
579 }
580
581 Vector WQtv;
582 bool nonzero = apply_WtPv(Q_set, v, WQtv, test_zero);
583 if (!nonzero)
584 {
585 res.setZero();
586 return false;
587 }
588
589 Vector MWQtv;
590 apply_Mv(WQtv, MWQtv);
591 MWQtv.tail(m_ncorr) *= m_theta;
592 res.noalias() = -WP * MWQtv;
593 return true;
594 }
595 // If the Q'W matrix has been explicitly formed, do a direct matrix multiplication
596 inline bool apply_PtBQv(const Matrix& WP, const Matrix& WQ, const Vector& v, Vector& res) const
597 {
598 const int nP = WP.rows();
599 const int nQ = WQ.rows();
600 res.resize(nP);
601 if (m_ncorr < 1 || nP < 1 || nQ < 1)
602 {
603 res.setZero();
604 return false;
605 }
606
607 // Remember that W = [Y, theta * S], so we need to multiply theta to the second half
608 Vector WQtv = WQ.transpose() * v;
609 WQtv.tail(m_ncorr) *= m_theta;
610 Vector MWQtv;
611 apply_Mv(WQtv, MWQtv);
612 MWQtv.tail(m_ncorr) *= m_theta;
613 res.noalias() = -WP * MWQtv;
614 return true;
615 }
616};
617
618} // namespace LBFGSpp
619
621
622#endif // LBFGSPP_BFGS_MAT_H