LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
ctmc_gmres.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_API_MC_CTMC_GMRES_H
6#define LINE_API_MC_CTMC_GMRES_H
7
8/**
9 * @file
10 * @ingroup api_mc
11 * Restarted GMRES with an ILUT preconditioner, for the linear systems a
12 * generator produces.
13 *
14 * Templated port of matlab/src/api/mc/ctmc_gmres.m and
15 * jar/src/main/java/jline/api/mc/Ctmc_gmres.java. This is the iterative
16 * counterpart of the direct solve in ctmc_solve, meant for generators whose LU
17 * fill-in exceeds the memory available; no CTMC-specific processing happens
18 * here, so the same routine serves the stochastic complement and the
19 * aggregation methods.
20 *
21 * Two preparation steps are not optional on a generator, and both are ported.
22 * Rows are equilibrated to unit max norm, so the O(1) normalization row does
23 * not mix with rows carrying rates of a wholly different magnitude. The states
24 * are then reordered by reverse Cuthill-McKee: in the natural ordering of a
25 * birth-death chain the unpivoted incomplete elimination has growth factor
26 * (mu/lambda)^n, which overflows within a few thousand states, and a
27 * fill-reducing ordering rather than pivoting is what removes it.
28 *
29 * The preconditioner is ILUT(p, tau) of Saad (1994): the row is expanded into a
30 * dense workspace, entries below tau times the mean magnitude of the original
31 * row are dropped as they are produced, and the p largest survivors are kept in
32 * each of the L and U parts. A vanishing pivot is replaced by a value of the
33 * order of the row threshold, keeping its sign, which is Saad's remedy and
34 * avoids abandoning the factorization for one rate-free state. When the
35 * factorization breaks down entirely the preconditioner degrades to Jacobi.
36 *
37 * MATLAB-VS-JAVA DISAGREEMENT, resolved in favour of Java. MATLAB calls the
38 * built-in gmres with (L,U), which applies the preconditioner on the LEFT, so
39 * its RELRES is the preconditioned residual norm(M\\‍(b-Ax))/norm(M\\b) and
40 * depends on the preconditioner. The JAR expands the Krylov space of A M^-1
41 * instead, and reports the true residual norm(b-Ax)/norm(b). The solution both
42 * converge to is the same; the reported relres is not, and a tolerance on the
43 * true residual is the one a caller can act on, so this port follows the JAR.
44 * FLAG keeps the MATLAB convention: 0 converged, 1 iteration limit, 3
45 * stagnation or divergence or a non-finite iterate.
46 *
47 * GATED ON TRANSCENDENTAL ARITHMETIC. GMRES stops on a residual tolerance and
48 * its Arnoldi step normalizes by a Euclidean norm, so square roots appear in
49 * every iteration and there is no exact answer to converge to: at Rational the
50 * iteration would run to the iteration limit with exploding denominators. The
51 * exact solve of the same system is ctmc_solve, which is what a Rational
52 * caller should use.
53 */
54
55#include <algorithm>
56#include <cmath>
57#include <cstddef>
58#include <functional>
59#include <queue>
60#include <vector>
61
62#include "line/num/number.h"
63#include "line/util/error.h"
64#include "line/util/matrix.h"
65
66namespace line {
67namespace mc {
68
69/**
70 * Order above which the direct sparse factorization is abandoned in favour of
71 * the Krylov path. The same number the other three codebases use
72 * (`ctmc_solve.m`, `Ctmc_solve.GMRES_MIN_STATES`, `ctmc.py`), so a model
73 * switches methods at the same size in all four.
74 */
75constexpr std::size_t GMRES_MIN_STATES = 6000;
76
77template <class T>
79 std::vector<T> x; ///< solution
80 int flag; ///< 0 converged, 1 iteration limit, 3 stagnation/divergence
81 T relres; ///< true relative residual norm(b - A x) / norm(b)
82 long iter; ///< total inner iterations
83};
84
85namespace detail {
86
87/** Relative threshold below which ILUT discards a fill-in entry. */
88constexpr double GMRES_ILUT_DROP_TOL = 1e-4;
89/** Fill allowed per row, as a multiple of the average nnz per row. */
90constexpr double GMRES_ILUT_FILL_FACTOR = 10.0;
91/** Arnoldi breakdown threshold, relative to the unorthogonalized vector. */
92constexpr double GMRES_BREAKDOWN_TOL = 1e-14;
93constexpr double GMRES_DEFAULT_TOL = 1e-12;
94constexpr long GMRES_DEFAULT_RESTART = 50;
95
96/**
97 * Finiteness without depending on which of std:: or boost:: supplies isfinite
98 * for T: |v| < |v| + 1 holds for every finite value, fails for an infinity
99 * (inf + 1 is inf) and fails for a NaN (every comparison does).
100 */
101template <class T>
102inline bool num_isfinite(const T& v) {
103 const T a = num_abs(T(v));
104 return a < T(a + num_traits<T>::from_int(1));
105}
106
107/**
108 * Compressed sparse row form, with each row's column indices increasing and the
109 * position of the diagonal recorded. The incomplete factorization is a
110 * row-oriented elimination and needs exactly this layout.
111 */
112template <class T>
113struct CsrMatrix {
114 std::size_t n = 0;
115 std::vector<std::size_t> rowPtr, colIdx;
116 std::vector<T> val;
117 std::vector<long> diagPtr;
118
119 void build_diag() {
120 diagPtr.assign(n, -1);
121 for (std::size_t i = 0; i < n; ++i)
122 for (std::size_t p = rowPtr[i]; p < rowPtr[i + 1]; ++p)
123 if (colIdx[p] == i) {
124 diagPtr[i] = static_cast<long>(p);
125 break;
126 }
127 }
128
129 static CsrMatrix of(const Matrix<T>& A) {
130 CsrMatrix c;
131 c.n = A.rows();
132 const T zero = num_traits<T>::from_int(0);
133 c.rowPtr.assign(c.n + 1, 0);
134 for (std::size_t i = 0; i < c.n; ++i) {
135 std::size_t cnt = 0;
136 for (std::size_t j = 0; j < A.cols(); ++j)
137 if (A(i, j) != zero) ++cnt;
138 c.rowPtr[i + 1] = c.rowPtr[i] + cnt;
139 }
140 c.colIdx.resize(c.rowPtr[c.n]);
141 c.val.resize(c.rowPtr[c.n]);
142 std::size_t p = 0;
143 for (std::size_t i = 0; i < c.n; ++i)
144 for (std::size_t j = 0; j < A.cols(); ++j)
145 if (A(i, j) != zero) {
146 c.colIdx[p] = j;
147 c.val[p] = A(i, j);
148 ++p;
149 }
150 c.build_diag();
151 return c;
152 }
153
154 void mult(const std::vector<T>& v, std::vector<T>& out) const {
155 for (std::size_t i = 0; i < n; ++i) {
156 T s = num_traits<T>::from_int(0);
157 for (std::size_t p = rowPtr[i]; p < rowPtr[i + 1]; ++p) s += val[p] * v[colIdx[p]];
158 out[i] = s;
159 }
160 }
161
162 /** Scales every row to unit max norm; returns the divisors applied. */
163 std::vector<T> equilibrate() {
164 std::vector<T> scale(n, num_traits<T>::from_int(1));
165 for (std::size_t i = 0; i < n; ++i) {
166 T m = num_traits<T>::from_int(0);
167 for (std::size_t p = rowPtr[i]; p < rowPtr[i + 1]; ++p) {
168 const T a = num_abs(T(val[p]));
169 if (a > m) m = a;
170 }
171 if (m == num_traits<T>::from_int(0)) continue;
172 scale[i] = m;
173 if (m == num_traits<T>::from_int(1)) continue;
174 for (std::size_t p = rowPtr[i]; p < rowPtr[i + 1]; ++p) val[p] /= m;
175 }
176 return scale;
177 }
178
179 /** Reordering so that entry (i,j) becomes (iperm[i], iperm[j]). */
180 CsrMatrix permute_symmetric(const std::vector<std::size_t>& perm,
181 const std::vector<std::size_t>& iperm) const {
182 CsrMatrix c;
183 c.n = n;
184 c.rowPtr.assign(n + 1, 0);
185 for (std::size_t i = 0; i < n; ++i) {
186 const std::size_t oi = perm[i];
187 c.rowPtr[i + 1] = c.rowPtr[i] + (rowPtr[oi + 1] - rowPtr[oi]);
188 }
189 c.colIdx.resize(rowPtr[n]);
190 c.val.resize(rowPtr[n]);
191 std::vector<std::pair<std::size_t, T>> row;
192 for (std::size_t i = 0; i < n; ++i) {
193 const std::size_t oi = perm[i];
194 row.clear();
195 for (std::size_t p = rowPtr[oi]; p < rowPtr[oi + 1]; ++p)
196 row.push_back(std::make_pair(iperm[colIdx[p]], val[p]));
197 std::sort(row.begin(), row.end(),
198 [](const std::pair<std::size_t, T>& a, const std::pair<std::size_t, T>& b) {
199 return a.first < b.first;
200 });
201 std::size_t base = c.rowPtr[i];
202 for (std::size_t k = 0; k < row.size(); ++k) {
203 c.colIdx[base + k] = row[k].first;
204 c.val[base + k] = row[k].second;
205 }
206 }
207 c.build_diag();
208 return c;
209 }
210};
211
212/**
213 * Reverse Cuthill-McKee ordering of the symmetrized pattern. Level structures
214 * are grown from the lowest-degree unvisited node of each component, newly
215 * discovered neighbours are appended in order of increasing degree, and the
216 * result is reversed.
217 */
218template <class T>
219std::vector<std::size_t> rcm_order(const CsrMatrix<T>& a) {
220 const std::size_t n = a.n;
221 std::vector<std::size_t> deg(n, 0);
222 for (std::size_t i = 0; i < n; ++i)
223 for (std::size_t p = a.rowPtr[i]; p < a.rowPtr[i + 1]; ++p) {
224 const std::size_t j = a.colIdx[p];
225 if (j == i) continue;
226 ++deg[i];
227 ++deg[j];
228 }
229 std::vector<std::size_t> adjPtr(n + 1, 0);
230 for (std::size_t i = 0; i < n; ++i) adjPtr[i + 1] = adjPtr[i] + deg[i];
231 std::vector<std::size_t> adj(adjPtr[n]), fill(adjPtr.begin(), adjPtr.begin() + n);
232 for (std::size_t i = 0; i < n; ++i)
233 for (std::size_t p = a.rowPtr[i]; p < a.rowPtr[i + 1]; ++p) {
234 const std::size_t j = a.colIdx[p];
235 if (j == i) continue;
236 adj[fill[i]++] = j;
237 adj[fill[j]++] = i;
238 }
239 // Duplicate neighbours, from a pattern with both (i,j) and (j,i), only bias
240 // the degree used for tie-breaking, which is harmless.
241
242 std::vector<char> seen(n, 0);
243 std::vector<std::size_t> result(n), queue(n);
244 std::size_t count = 0;
245 while (count < n) {
246 std::size_t start = n;
247 for (std::size_t i = 0; i < n; ++i)
248 if (!seen[i] && (start == n || deg[i] < deg[start])) start = i;
249 std::size_t head = count, tail = count;
250 queue[tail++] = start;
251 seen[start] = 1;
252 while (head < tail) {
253 const std::size_t v = queue[head++];
254 result[count++] = v;
255 const std::size_t from = tail;
256 for (std::size_t p = adjPtr[v]; p < adjPtr[v + 1]; ++p) {
257 const std::size_t u = adj[p];
258 if (!seen[u]) {
259 seen[u] = 1;
260 queue[tail++] = u;
261 }
262 }
263 std::stable_sort(queue.begin() + from, queue.begin() + tail,
264 [&deg](std::size_t x, std::size_t y) { return deg[x] < deg[y]; });
265 }
266 }
267 std::vector<std::size_t> perm(n);
268 for (std::size_t i = 0; i < n; ++i) perm[i] = result[n - 1 - i];
269 return perm;
270}
271
272/** Threshold incomplete LU factorization, ILUT(p, tau) of Saad (1994). */
273template <class T>
274struct Ilut {
275 bool ok = false;
276 std::size_t n = 0;
277 std::vector<std::size_t> lPtr, lCol, uPtr, uCol;
278 std::vector<T> lVal, uVal, dInv;
279
280 /** out = U^-1 L^-1 v, with L unit lower triangular. */
281 void solve(const std::vector<T>& v, std::vector<T>& out) const {
282 for (std::size_t i = 0; i < n; ++i) {
283 T s = v[i];
284 for (std::size_t p = lPtr[i]; p < lPtr[i + 1]; ++p) s -= lVal[p] * out[lCol[p]];
285 out[i] = s;
286 }
287 for (std::size_t i = n; i-- > 0;) {
288 T s = out[i];
289 for (std::size_t p = uPtr[i]; p < uPtr[i + 1]; ++p) s -= uVal[p] * out[uCol[p]];
290 out[i] = s * dInv[i];
291 }
292 }
293
294 /** Keeps the entries of largest magnitude, by partial selection. */
295 static std::size_t keep_largest(std::vector<std::size_t>& cols, const std::vector<T>& w,
296 std::size_t len, std::size_t keep) {
297 if (len <= keep) return len;
298 for (std::size_t i = 0; i < keep; ++i) {
299 std::size_t best = i;
300 for (std::size_t j = i + 1; j < len; ++j)
301 if (num_abs(T(w[cols[j]])) > num_abs(T(w[cols[best]]))) best = j;
302 std::swap(cols[i], cols[best]);
303 }
304 return keep;
305 }
306
307 static Ilut factorize(const CsrMatrix<T>& a, double dropTol, double fillFactor) {
308 Ilut f;
309 const std::size_t n = a.n;
310 f.n = n;
311 const T zero = num_traits<T>::from_int(0);
312 const std::size_t nnz = a.rowPtr[n];
313 std::size_t lfil = static_cast<std::size_t>(
314 std::ceil(fillFactor * static_cast<double>(nnz) / static_cast<double>(n == 0 ? 1 : n)));
315 if (lfil < 1) lfil = 1;
316 const T dropT = num_traits<T>::from_double(dropTol);
317
318 f.lPtr.assign(n + 1, 0);
319 f.uPtr.assign(n + 1, 0);
320 f.dInv.assign(n, zero);
321
322 std::vector<T> w(n, zero);
323 std::vector<long> wPos(n, -1);
324 std::vector<std::size_t> wIdx(n), rowsL(n), rowsU(n);
325 std::size_t wCount = 0;
326 std::priority_queue<std::size_t, std::vector<std::size_t>, std::greater<std::size_t>> pending;
327
328 for (std::size_t i = 0; i < n; ++i) {
329 T tnorm = zero;
330 const std::size_t rowLen = a.rowPtr[i + 1] - a.rowPtr[i];
331 for (std::size_t p = a.rowPtr[i]; p < a.rowPtr[i + 1]; ++p) {
332 const std::size_t j = a.colIdx[p];
333 w[j] = a.val[p];
334 wPos[j] = static_cast<long>(wCount);
335 wIdx[wCount++] = j;
336 tnorm += num_abs(T(a.val[p]));
337 if (j < i) pending.push(j);
338 }
339 if (rowLen == 0 || tnorm == zero) {
340 // empty-row breakdown rationale: see _kb/03-api-layer.md (cpp port notes: mc)
341 return f; // ok stays false
342 }
343 const T tau = dropT * tnorm / num_traits<T>::from_int(static_cast<long>(rowLen));
344
345 while (!pending.empty()) {
346 const std::size_t k = pending.top();
347 pending.pop();
348 const T mult = w[k] * f.dInv[k];
349 if (!(num_abs(T(mult)) > tau)) {
350 w[k] = zero;
351 continue;
352 }
353 w[k] = mult;
354 for (std::size_t p = f.uPtr[k]; p < f.uPtr[k + 1]; ++p) {
355 const std::size_t j = f.uCol[p];
356 const T upd = mult * f.uVal[p];
357 if (wPos[j] >= 0) {
358 w[j] -= upd;
359 } else {
360 if (!(num_abs(T(upd)) > tau)) continue;
361 w[j] = -upd;
362 wPos[j] = static_cast<long>(wCount);
363 wIdx[wCount++] = j;
364 if (j < i) pending.push(j);
365 }
366 }
367 }
368
369 std::size_t nl = 0, nu = 0;
370 T diag = w[i];
371 for (std::size_t t = 0; t < wCount; ++t) {
372 const std::size_t j = wIdx[t];
373 if (j == i) continue;
374 if (!(num_abs(T(w[j])) > tau)) continue;
375 if (j < i)
376 rowsL[nl++] = j;
377 else
378 rowsU[nu++] = j;
379 }
380 nl = keep_largest(rowsL, w, nl, lfil);
381 nu = keep_largest(rowsU, w, nu, lfil);
382 std::sort(rowsL.begin(), rowsL.begin() + nl);
383 std::sort(rowsU.begin(), rowsU.begin() + nu);
384
385 for (std::size_t t = 0; t < nl; ++t) {
386 f.lCol.push_back(rowsL[t]);
387 f.lVal.push_back(w[rowsL[t]]);
388 }
389 for (std::size_t t = 0; t < nu; ++t) {
390 f.uCol.push_back(rowsU[t]);
391 f.uVal.push_back(w[rowsU[t]]);
392 }
393 f.lPtr[i + 1] = f.lCol.size();
394 f.uPtr[i + 1] = f.uCol.size();
395
396 // Saad's pivot remedy: see _kb/03-api-layer.md (cpp port notes: mc)
397 if (!(num_abs(T(diag)) > tau) || !(diag == diag)) {
398 const T substitute = tau > zero ? tau : num_traits<T>::from_double(1e-8);
399 diag = diag < zero ? T(-substitute) : substitute;
400 }
401 f.dInv[i] = num_traits<T>::from_int(1) / diag;
402 if (!num_isfinite(f.dInv[i])) return f; // ok stays false
403
404 for (std::size_t t = 0; t < wCount; ++t) {
405 w[wIdx[t]] = zero;
406 wPos[wIdx[t]] = -1;
407 }
408 wCount = 0;
409 while (!pending.empty()) pending.pop();
410 }
411 f.ok = true;
412 return f;
413 }
414};
415
416/** ILUT if it factorized, Jacobi otherwise. */
417template <class T>
418struct Precond {
419 Ilut<T> lu;
420 std::vector<T> dinv; ///< non-empty when the Jacobi fallback is in use
421
422 static Precond of(const CsrMatrix<T>& a) {
423 Precond m;
424 m.lu = Ilut<T>::factorize(a, GMRES_ILUT_DROP_TOL, GMRES_ILUT_FILL_FACTOR);
425 if (m.lu.ok) return m;
426 const T zero = num_traits<T>::from_int(0);
427 m.dinv.assign(a.n, num_traits<T>::from_int(1));
428 for (std::size_t i = 0; i < a.n; ++i) {
429 const T d = a.diagPtr[i] < 0 ? zero : a.val[static_cast<std::size_t>(a.diagPtr[i])];
430 m.dinv[i] = (d == zero) ? num_traits<T>::from_int(1) : T(num_traits<T>::from_int(1) / d);
431 }
432 return m;
433 }
434
435 void apply(const std::vector<T>& v, std::vector<T>& out) const {
436 if (!dinv.empty()) {
437 for (std::size_t i = 0; i < dinv.size(); ++i) out[i] = dinv[i] * v[i];
438 return;
439 }
440 lu.solve(v, out);
441 }
442};
443
444/** Equilibrated, reordered and preconditioned form of a coefficient matrix. */
445template <class T>
446struct GmresPrepared {
447 std::size_t n = 0;
448 CsrMatrix<T> csr;
449 std::vector<std::size_t> perm;
450 std::vector<T> rowScale;
451 Precond<T> M;
452
453 explicit GmresPrepared(const Matrix<T>& A) {
454 n = A.rows();
455 if (A.cols() != n) throw InputError("ctmc_gmres: matrix is not square");
456 CsrMatrix<T> c = CsrMatrix<T>::of(A);
457 rowScale = c.equilibrate();
458 perm = rcm_order(c);
459 std::vector<std::size_t> iperm(n);
460 for (std::size_t i = 0; i < n; ++i) iperm[perm[i]] = i;
461 csr = c.permute_symmetric(perm, iperm);
462 M = Precond<T>::of(csr);
463 }
464};
465
466template <class T>
467T vec_norm2(const std::vector<T>& v) {
468 T s = num_traits<T>::from_int(0);
469 for (const T& x : v) s += x * x;
470 using std::sqrt;
471 return sqrt(s);
472}
473
474template <class T>
475T vec_dot(const std::vector<T>& a, const std::vector<T>& b) {
476 T s = num_traits<T>::from_int(0);
477 for (std::size_t i = 0; i < a.size(); ++i) s += a[i] * b[i];
478 return s;
479}
480
481/** Right-preconditioned restarted GMRES on an already prepared system. */
482template <class T>
483GmresResult<T> gmres_solve(const GmresPrepared<T>& prep, const std::vector<T>& rhsIn,
484 const std::vector<T>& x0In, double tol, long restart, long maxit) {
485 const std::size_t n = prep.n;
486 const T zero = num_traits<T>::from_int(0);
487 const T one = num_traits<T>::from_int(1);
488 if (tol <= 0.0) tol = GMRES_DEFAULT_TOL;
489 if (restart <= 0) restart = std::min(static_cast<long>(n), GMRES_DEFAULT_RESTART);
490 restart = std::min(restart, static_cast<long>(n));
491 if (maxit <= 0)
492 maxit = static_cast<long>(std::ceil(static_cast<double>(n) / static_cast<double>(restart)));
493 maxit = std::max(1L, std::min(maxit, static_cast<long>(n)));
494 const std::size_t m = static_cast<std::size_t>(restart);
495 const T tolT = num_traits<T>::from_double(tol);
496
497 // Row scaling, then RCM permutation, on both the right-hand side and the
498 // initial guess.
499 std::vector<T> rhs(n), x(n);
500 for (std::size_t i = 0; i < n; ++i) rhs[i] = rhsIn[prep.perm[i]] / prep.rowScale[prep.perm[i]];
501 for (std::size_t i = 0; i < n; ++i) x[i] = x0In[prep.perm[i]];
502
503 T bnorm = vec_norm2(rhs);
504 if (bnorm == zero) bnorm = one;
505
506 std::vector<T> r(n), w(n), z(n), corr(n);
507 prep.csr.mult(x, r);
508 for (std::size_t i = 0; i < n; ++i) r[i] = rhs[i] - r[i];
509 T beta = vec_norm2(r);
510
511 GmresResult<T> out;
512 out.flag = 1;
513 out.iter = 0;
514 out.relres = beta / bnorm;
515 if (out.relres <= tolT) {
516 out.flag = 0;
517 out.x.assign(n, zero);
518 for (std::size_t i = 0; i < n; ++i) out.x[prep.perm[i]] = x[i];
519 return out;
520 }
521
522 std::vector<std::vector<T>> V(m + 1, std::vector<T>(n, zero));
523 std::vector<std::vector<T>> H(m + 1, std::vector<T>(m, zero));
524 std::vector<T> cs(m, zero), sn(m, zero), g(m + 1, zero), y(m, zero);
525 const T breakTol = num_traits<T>::from_double(GMRES_BREAKDOWN_TOL);
526 const T initres = out.relres;
527 bool havePrev = false;
528 T prevrelres = zero;
529
530 for (long cycle = 0; cycle < maxit; ++cycle) {
531 beta = vec_norm2(r);
532 if (beta == zero) {
533 out.flag = 0;
534 out.relres = zero;
535 break;
536 }
537 for (std::size_t i = 0; i < n; ++i) V[0][i] = r[i] / beta;
538 std::fill(g.begin(), g.end(), zero);
539 g[0] = beta;
540
541 std::size_t k = 0;
542 for (std::size_t j = 0; j < m; ++j) {
543 // right-preconditioning rationale: see _kb/03-api-layer.md (cpp port notes: mc)
544 prep.M.apply(V[j], z);
545 prep.csr.mult(z, w);
546 ++out.iter;
547
548 const T wnorm0 = vec_norm2(w);
549 // Gram-Schmidt reorthogonalization rationale: see _kb/03-api-layer.md (cpp port notes: mc)
550 for (int pass = 0; pass < 2; ++pass)
551 for (std::size_t i = 0; i <= j; ++i) {
552 const T hij = vec_dot(V[i], w);
553 H[i][j] += hij;
554 for (std::size_t q = 0; q < n; ++q) w[q] -= hij * V[i][q];
555 }
556 const T hnext = vec_norm2(w);
557 H[j + 1][j] = hnext;
558
559 k = j + 1;
560 const bool breakdown = !(hnext > breakTol * wnorm0);
561 if (!breakdown)
562 for (std::size_t q = 0; q < n; ++q) V[j + 1][q] = w[q] / hnext;
563
564 // Accumulated Givens rotations on the new Hessenberg column, then a
565 // fresh rotation annihilating its subdiagonal entry.
566 for (std::size_t i = 0; i < j; ++i) {
567 const T t1 = cs[i] * H[i][j] + sn[i] * H[i + 1][j];
568 H[i + 1][j] = -sn[i] * H[i][j] + cs[i] * H[i + 1][j];
569 H[i][j] = t1;
570 }
571 using std::sqrt;
572 const T denom = sqrt(T(H[j][j] * H[j][j] + H[j + 1][j] * H[j + 1][j]));
573 if (denom == zero) {
574 cs[j] = one;
575 sn[j] = zero;
576 } else {
577 cs[j] = H[j][j] / denom;
578 sn[j] = H[j + 1][j] / denom;
579 }
580 H[j][j] = cs[j] * H[j][j] + sn[j] * H[j + 1][j];
581 H[j + 1][j] = zero;
582 g[j + 1] = -sn[j] * g[j];
583 g[j] = cs[j] * g[j];
584
585 out.relres = num_abs(T(g[j + 1])) / bnorm;
586 if (out.relres <= tolT || breakdown) break;
587 }
588
589 // Least-squares solution on the rotated Hessenberg system, mapped back
590 // through the preconditioner.
591 for (std::size_t i = k; i-- > 0;) {
592 T s = g[i];
593 for (std::size_t q = i + 1; q < k; ++q) s -= H[i][q] * y[q];
594 y[i] = (H[i][i] == zero) ? zero : T(s / H[i][i]);
595 }
596 std::fill(corr.begin(), corr.end(), zero);
597 for (std::size_t i = 0; i < k; ++i)
598 for (std::size_t q = 0; q < n; ++q) corr[q] += y[i] * V[i][q];
599 prep.M.apply(corr, z);
600 for (std::size_t q = 0; q < n; ++q) x[q] += z[q];
601
602 prep.csr.mult(x, r);
603 for (std::size_t q = 0; q < n; ++q) r[q] = rhs[q] - r[q];
604 out.relres = vec_norm2(r) / bnorm;
605
606 for (std::size_t i = 0; i <= m; ++i) std::fill(H[i].begin(), H[i].end(), zero);
607
608 if (out.relres <= tolT) {
609 out.flag = 0;
610 break;
611 }
612 // divergence-detection rationale: see _kb/03-api-layer.md (cpp port notes: mc)
613 if (!(out.relres < num_traits<T>::from_int(100) * initres)) {
614 out.flag = 3;
615 break;
616 }
617 // Stagnation: a cycle that fails to reduce the residual will not do so
618 // on the next one either.
619 if (havePrev && out.relres >= prevrelres * (one - num_traits<T>::from_double(1e-12))) {
620 out.flag = 3;
621 break;
622 }
623 prevrelres = out.relres;
624 havePrev = true;
625 }
626
627 out.x.assign(n, zero);
628 for (std::size_t i = 0; i < n; ++i) out.x[prep.perm[i]] = x[i];
629 for (std::size_t i = 0; i < n; ++i)
630 if (!num_isfinite(out.x[i])) {
631 out.flag = 3;
632 out.relres = num_traits<T>::from_double(1e300);
633 return out;
634 }
635 if (out.relres <= tolT) out.flag = 0;
636 return out;
637}
638
639} // namespace detail
640
641/**
642 * @brief Restarted GMRES with an ILUT preconditioner, for the linear systems
643 * a generator produces.
644 *
645 * @param A coefficient matrix, already assembled
646 * @param b right-hand side
647 * @param tol relative residual tolerance (default 1e-12)
648 * @param restart restart length; <= 0 selects min(n, 50)
649 * @param maxit outer cycles; <= 0 selects ceil(n / restart)
650 * @param x0 initial guess; empty selects the uniform vector ones(n)/n
651 */
652template <class T>
653GmresResult<T> ctmc_gmres(const Matrix<T>& A, const std::vector<T>& b, double tol = 1e-12,
654 long restart = 0, long maxit = 0, const std::vector<T>& x0 = std::vector<T>()) {
656 "ctmc_gmres requires transcendental arithmetic: the Arnoldi step normalizes by a "
657 "Euclidean norm and the iteration stops on a residual tolerance, so there is no "
658 "exact result to converge to; use ctmc_solve for an exact solve");
659 const std::size_t n = A.rows();
660 if (A.cols() != n) throw InputError("ctmc_gmres: matrix is not square");
661 if (b.size() != n) throw InputError("ctmc_gmres: right-hand side has the wrong length");
662 if (!x0.empty() && x0.size() != n) throw InputError("ctmc_gmres: initial guess has the wrong length");
663 std::vector<T> guess = x0;
664 if (guess.empty())
665 guess.assign(n, num_traits<T>::from_int(1) / num_traits<T>::from_int(static_cast<long>(n)));
666 const detail::GmresPrepared<T> prep(A);
667 return detail::gmres_solve(prep, b, guess, tol, restart, maxit);
668}
669
670} // namespace mc
671} // namespace line
672
673#endif // LINE_API_MC_CTMC_GMRES_H
InputError(const std::string &what)
Definition error.h:39
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
The exception types the port throws.
Dense matrix and non-owning view.
constexpr std::size_t GMRES_MIN_STATES
Order above which the direct sparse factorization is abandoned in favour of the Krylov path.
Definition ctmc_gmres.h:75
GmresResult< T > ctmc_gmres(const Matrix< T > &A, const std::vector< T > &b, double tol=1e-12, long restart=0, long maxit=0, const std::vector< T > &x0=std::vector< T >())
Restarted GMRES with an ILUT preconditioner, for the linear systems a generator produces.
Definition ctmc_gmres.h:653
T num_abs(const T &v)
Definition number.h:172
Number-type abstraction for the templated API port.
long iter
total inner iterations
Definition ctmc_gmres.h:82
int flag
0 converged, 1 iteration limit, 3 stagnation/divergence
Definition ctmc_gmres.h:80
T relres
true relative residual norm(b - A x) / norm(b)
Definition ctmc_gmres.h:81
std::vector< T > x
solution
Definition ctmc_gmres.h:79