LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
mg1.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_LIB_SMC_MG1_H
6#define LINE_LIB_SMC_MG1_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * The M/G/1-type and GI/M/1-type fundamental-matrix solvers of MAMSolver /
12 * SMCSolver, ported from `matlab/lib/thirdparty/MG1files`:
13 * `stat.m`, `MG1_EG.m`, `MG1_Decay.m`, `GIM1_Caudal.m`, `MG1_Shifts.m`,
14 * `MG1_CR.m`, `MG1_FI.m` and `GIM1_R.m`.
15 *
16 * These are the third-party numerics the ETAQA aggregation sits on: `MG1_CR`
17 * returns the minimal nonnegative G of an M/G/1-type chain and `GIM1_R` the
18 * minimal nonnegative R of a GI/M/1-type one, and without them
19 * `solver_mam_bmap_map_1` and `solver_mam_map_bmap_1` have nothing to
20 * aggregate. The port follows the MATLAB line by line, including the block
21 * index arithmetic, the stopping tests and the constants, so that a divergence
22 * against the reference is a bug here and not a design difference.
23 *
24 * BLOCK LAYOUT. The reference passes a block sequence as one wide matrix
25 * `A = [A0 A1 A2 ... Amax]`, m rows by m*(max+1) columns. This port carries the
26 * same sequence as a `std::vector<Matrix<double>>` of m x m blocks, which is
27 * the identical object with the index arithmetic done once, in `blocks_of` /
28 * `hcat`, instead of at every use. The GI/M/1 side stacks its blocks
29 * VERTICALLY in the reference; `GIM1_R_ETAQA` is what transposes that stack
30 * into the horizontal one, so everything below is horizontal.
31 *
32 * DOUBLE ONLY, and not by preference. `MG1_Decay` and `GIM1_Caudal` bisect on
33 * the Perron-Frobenius eigenvalue of A(z), which needs LAPACK; `MG1_CR`
34 * evaluates its polynomials at complex roots of unity through an FFT, whose
35 * twiddle factors are cos/sin; `MG1_pi_ETAQA` drops a column chosen by a
36 * numerical rank test, which needs an SVD. None of the three has a
37 * multiprecision or exact counterpart in this tree, so the whole family is
38 * declared on `Matrix<double>` and the solvers that call it refuse at any
39 * other arithmetic rather than down-converting behind the caller's back.
40 *
41 * TWO REFERENCE BRANCHES ARE REFUSED BY NAME rather than transcribed, because
42 * they cannot run in MATLAB either. `MG1_Shifts` writes
43 * rowhatA(1,maxd*i:end) = uT*A(:,maxd*i:end)
44 * in three places (the drift < 1 'tau' branch and the drift > 1 'one' branch),
45 * where `i` is not a loop variable at that point: it is either MATLAB's
46 * imaginary unit, which makes the index complex and errors, or the leftover
47 * value 1 from the `beta` loop above, which addresses column `maxd` of a matrix
48 * whose blocks start every m columns. Either way the line does not compute
49 * "the last block", which is what the surrounding code needs. ETAQA reaches
50 * neither branch: it shifts a positive recurrent chain (drift < 1) with the
51 * default ShiftType 'one', which is the branch that is correct and is ported.
52 */
53
54#include <algorithm>
55#include <cmath>
56#include <complex>
57#include <cstddef>
58#include <limits>
59#include <string>
60#include <vector>
61
63#include "line/num/number.h"
64#include "line/util/eig.h"
65#include "line/util/error.h"
66#include "line/util/fft.h"
67#include "line/util/linalg.h"
68#include "line/util/lstsq.h"
69#include "line/util/lu.h"
70#include "line/util/matrix.h"
71
72namespace line {
73namespace smc {
74
75using Blocks = std::vector<Matrix<double>>;
76
77// ---------------------------------------------------------------------------
78// Small matrix helpers, named after the MATLAB they stand for
79// ---------------------------------------------------------------------------
80
81/** A + B. Templated so the point-wise CR step can add complex blocks. */
82template <class T>
83Matrix<T> madd(const Matrix<T>& A, const Matrix<T>& B) {
84 if (A.rows() != B.rows() || A.cols() != B.cols())
85 throw InputError("smc: matrix addition with mismatched shapes");
87 for (std::size_t i = 0; i < A.rows(); ++i)
88 for (std::size_t j = 0; j < A.cols(); ++j) C(i, j) = A(i, j) + B(i, j);
89 return C;
90}
91
92/** A - B. */
93template <class T>
94Matrix<T> msub(const Matrix<T>& A, const Matrix<T>& B) {
95 if (A.rows() != B.rows() || A.cols() != B.cols())
96 throw InputError("smc: matrix subtraction with mismatched shapes");
98 for (std::size_t i = 0; i < A.rows(); ++i)
99 for (std::size_t j = 0; j < A.cols(); ++j) C(i, j) = A(i, j) - B(i, j);
100 return C;
101}
102
103/** c * A. */
104inline Matrix<double> mscale(const Matrix<double>& A, double c) {
105 Matrix<double> C(A.rows(), A.cols(), 0.0);
106 for (std::size_t i = 0; i < A.rows(); ++i)
107 for (std::size_t j = 0; j < A.cols(); ++j) C(i, j) = c * A(i, j);
108 return C;
109}
110
111/** sum(A,2), the row sums, as a column held in a vector. */
112inline std::vector<double> rowsums(const Matrix<double>& A) {
113 std::vector<double> s(A.rows(), 0.0);
114 for (std::size_t i = 0; i < A.rows(); ++i)
115 for (std::size_t j = 0; j < A.cols(); ++j) s[i] += A(i, j);
116 return s;
117}
118
119/** norm(A,inf), the largest absolute row sum. */
120inline double inf_norm(const Matrix<double>& A) {
121 double best = 0.0;
122 for (std::size_t i = 0; i < A.rows(); ++i) {
123 double s = 0.0;
124 for (std::size_t j = 0; j < A.cols(); ++j) s += std::fabs(A(i, j));
125 if (s > best) best = s;
126 }
127 return best;
128}
129
130/** norm(A,inf) over a whole block sequence stacked vertically. */
131inline double inf_norm(const Blocks& blk, std::size_t from) {
132 double best = 0.0;
133 for (std::size_t i = from; i < blk.size(); ++i) best = std::max(best, inf_norm(blk[i]));
134 return best;
135}
136
137/** max(max(abs(A-B))). */
138inline double max_abs_diff(const Matrix<double>& A, const Matrix<double>& B) {
139 double best = 0.0;
140 for (std::size_t i = 0; i < A.rows(); ++i)
141 for (std::size_t j = 0; j < A.cols(); ++j)
142 best = std::max(best, std::fabs(A(i, j) - B(i, j)));
143 return best;
144}
145
146/** max(sum(A)), the largest column sum WITHOUT absolute values, as in MATLAB. */
147inline double max_col_sum(const Matrix<double>& A) {
148 double best = -std::numeric_limits<double>::infinity();
149 for (std::size_t j = 0; j < A.cols(); ++j) {
150 double s = 0.0;
151 for (std::size_t i = 0; i < A.rows(); ++i) s += A(i, j);
152 if (s > best) best = s;
153 }
154 return best;
155}
156
157/** Splits the wide `[A0 A1 ... Amax]` into its m x m blocks. */
158inline Blocks blocks_of(const Matrix<double>& A, std::size_t m) {
159 if (m == 0 || A.cols() % m != 0)
160 throw InputError("smc: the block sequence has an incorrect number of columns");
161 const std::size_t nb = A.cols() / m;
162 Blocks out(nb, Matrix<double>(A.rows(), m, 0.0));
163 for (std::size_t b = 0; b < nb; ++b)
164 for (std::size_t i = 0; i < A.rows(); ++i)
165 for (std::size_t j = 0; j < m; ++j) out[b](i, j) = A(i, b * m + j);
166 return out;
167}
168
169/** Re-assembles a block sequence into the wide `[A0 A1 ... Amax]`. */
170inline Matrix<double> hcat(const Blocks& blk) {
171 if (blk.empty()) return Matrix<double>();
172 const std::size_t r = blk[0].rows(), c = blk[0].cols();
173 Matrix<double> A(r, c * blk.size(), 0.0);
174 for (std::size_t b = 0; b < blk.size(); ++b)
175 for (std::size_t i = 0; i < r; ++i)
176 for (std::size_t j = 0; j < c; ++j) A(i, b * c + j) = blk[b](i, j);
177 return A;
178}
179
180/** Stacks a block sequence vertically, `[A0; A1; ...; Amax]`. */
181inline Matrix<double> vcat(const Blocks& blk) {
182 if (blk.empty()) return Matrix<double>();
183 const std::size_t r = blk[0].rows(), c = blk[0].cols();
184 Matrix<double> A(r * blk.size(), c, 0.0);
185 for (std::size_t b = 0; b < blk.size(); ++b)
186 for (std::size_t i = 0; i < r; ++i)
187 for (std::size_t j = 0; j < c; ++j) A(b * r + i, j) = blk[b](i, j);
188 return A;
189}
190
191/** Splits a vertical stack into its blocks of `r` rows. */
192inline Blocks vblocks_of(const Matrix<double>& A, std::size_t r) {
193 if (r == 0 || A.rows() % r != 0)
194 throw InputError("smc: the stacked block sequence has an incorrect number of rows");
195 const std::size_t nb = A.rows() / r;
196 Blocks out(nb, Matrix<double>(r, A.cols(), 0.0));
197 for (std::size_t b = 0; b < nb; ++b)
198 for (std::size_t i = 0; i < r; ++i)
199 for (std::size_t j = 0; j < A.cols(); ++j) out[b](i, j) = A(b * r + i, j);
200 return out;
201}
202
203// ---------------------------------------------------------------------------
204// stat.m
205// ---------------------------------------------------------------------------
206
207/**
208 * Stationary distribution of a stochastic matrix: the left eigenvector for
209 * eigenvalue 1, nonnegative and summing to one.
210 *
211 * Port of `stat.m`, including its shape: `[A - I, e]` is S x (S+1), so the
212 * reference's `y / B` is a least-squares solve of a consistent overdetermined
213 * system and not a square solve. The normalization is IN the system (the
214 * appended column of ones against the appended 1 on the right), which is why
215 * the result needs no rescaling afterwards.
216 */
217inline std::vector<double> stat(const Matrix<double>& A) {
218 const std::size_t S = A.rows();
219 if (A.cols() != S) throw InputError("stat: matrix is not square");
220 // x [A - I, e] = [0 ... 0 1] <=> [A - I, e]^T x^T = [0 ... 0 1]^T
221 Matrix<double> Bt(S + 1, S, 0.0);
222 for (std::size_t i = 0; i < S; ++i)
223 for (std::size_t j = 0; j < S; ++j) Bt(j, i) = A(i, j) - (i == j ? 1.0 : 0.0);
224 for (std::size_t i = 0; i < S; ++i) Bt(S, i) = 1.0;
225 std::vector<double> y(S + 1, 0.0);
226 y[S] = 1.0;
227 return lstsq(Bt, y, detail::lstsq_tolerance(Bt)).x;
228}
229
230/** theta A, the row vector times matrix product used throughout. */
231inline std::vector<double> rowvec_times(const std::vector<double>& v, const Matrix<double>& A) {
232 return vecmul(v, A);
233}
234
235/** The inner product of a row vector with a column held as a vector. */
236inline double dot(const std::vector<double>& a, const std::vector<double>& b) {
237 if (a.size() != b.size()) throw InputError("smc: inner product with mismatched lengths");
238 double s = 0.0;
239 for (std::size_t i = 0; i < a.size(); ++i) s += a[i] * b[i];
240 return s;
241}
242
243/** The drift of an M/G/1-type sequence, and the invariant vector it uses. */
244struct Drift {
245 double value = 0.0;
246 std::vector<double> theta; ///< stat(A0 + A1 + ... + Amax)
247};
248
249/**
250 * `drift = theta * beta` with `beta = (Amax)e + (Amax+Amax-1)e + ...`, the
251 * expected level increment per transition of the phase process. Repeated
252 * verbatim in MG1_EG, MG1_Shifts, MG1_pi_ETAQA and GIM1_R, so it lives here.
253 */
254inline Drift mg1_drift(const Blocks& A) {
255 const std::size_t dega = A.size() - 1;
256 Matrix<double> sumA = A[dega];
257 std::vector<double> beta = rowsums(sumA);
258 for (std::size_t i = dega; i-- > 1;) {
259 sumA = madd(sumA, A[i]);
260 const std::vector<double> rs = rowsums(sumA);
261 for (std::size_t k = 0; k < beta.size(); ++k) beta[k] += rs[k];
262 }
263 sumA = madd(sumA, A[0]);
264 Drift d;
265 d.theta = stat(sumA);
266 d.value = dot(d.theta, beta);
267 return d;
268}
269
270// ---------------------------------------------------------------------------
271// MG1_Decay.m and GIM1_Caudal.m
272// ---------------------------------------------------------------------------
273
274/**
275 * `max(eig(M))` with MATLAB's semantics on a complex spectrum: the element of
276 * largest modulus, ties broken by the larger phase angle. For the nonnegative
277 * A(z) both callers evaluate, this is the Perron-Frobenius eigenvalue and is
278 * real; the comparisons below then take its real part, which is what MATLAB's
279 * relational operators do on a complex value.
280 */
281inline std::complex<double> max_eig(const Matrix<double>& M) {
282 const std::vector<std::complex<double>> ev = eig_values(M);
283 if (ev.empty()) throw NumericError("smc: empty spectrum");
284 std::complex<double> best = ev[0];
285 for (const std::complex<double>& z : ev) {
286 const double mz = std::abs(z), mb = std::abs(best);
287 if (mz > mb || (mz == mb && std::arg(z) > std::arg(best))) best = z;
288 }
289 return best;
290}
291
292/** A(z) = A0 + A1 z + ... + Amax z^max, by Horner as the reference writes it. */
293inline Matrix<double> poly_at(const Blocks& A, double z) {
294 Matrix<double> temp = A.back();
295 for (std::size_t i = A.size() - 1; i-- > 0;) temp = madd(mscale(temp, z), A[i]);
296 return temp;
297}
298
299/**
300 * Decay rate of a recurrent M/G/1-type chain: the unique z > 1 with
301 * PF(A(z)) = z. Port of `MG1_Decay.m`; the eigenvector output the reference
302 * offers is not returned, because the only caller that wants it is the
303 * 'tau' shift, which is refused (see the header note).
304 */
305inline double mg1_decay(const Blocks& A) {
306 double eta = 1.0, new_eta = 0.0;
307 while (new_eta - eta < 0.0) {
308 eta += 1.0;
309 new_eta = max_eig(poly_at(A, eta)).real();
310 }
311 double eta_min = eta - 1.0, eta_max = eta;
312 eta = eta_min + 0.5;
313 while (eta_max - eta_min > 1e-15) {
314 new_eta = max_eig(poly_at(A, eta)).real();
315 if (new_eta < eta) {
316 eta_min = eta;
317 } else {
318 eta_max = eta;
319 }
320 eta = (eta_min + eta_max) / 2.0;
321 }
322 return eta;
323}
324
325/**
326 * Caudal characteristic of a GI/M/1-type chain: the spectral radius of R, the
327 * unique z in (0,1) with PF(A(z)) = z. Port of `GIM1_Caudal.m`.
328 */
329inline double gim1_caudal(const Blocks& A) {
330 double eta_min = 0.0, eta_max = 1.0, eta = 0.5;
331 while (eta_max - eta_min > 1e-15) {
332 const double new_eta = max_eig(poly_at(A, eta)).real();
333 if (new_eta > eta) {
334 eta_min = eta;
335 } else {
336 eta_max = eta;
337 }
338 eta = (eta_min + eta_max) / 2.0;
339 }
340 return eta;
341}
342
343// ---------------------------------------------------------------------------
344// MG1_Shifts.m
345// ---------------------------------------------------------------------------
346
347/** What `MG1_Shifts` returns: the shifted sequence and the drift it measured. */
350 double drift = 0.0;
351 double tau = 1.0;
352 std::vector<double> v;
353};
354
355/**
356 * Shift technique for the M/G/1-type sequence. Port of `MG1_Shifts.m`,
357 * ShiftType 'one', which is the default and the only type ETAQA uses.
358 *
359 * For a positive recurrent chain (drift < 1) the eigenvalue 1 of A(z) is
360 * shifted to zero by subtracting `(A0+...+Ai)e u^T` from block i with
361 * `u^T = e^T/m`; cyclic reduction then converges linearly in the SECOND
362 * largest root instead of stalling on the unit one, which is the entire point
363 * of running CR on the shifted sequence and undoing the shift on G afterwards.
364 *
365 * 'tau' and 'dbl', and 'one' at drift > 1, are refused: see the header.
366 */
367inline ShiftResult mg1_shifts(const Blocks& Ain, const std::string& shift_type) {
368 if (shift_type != "one")
369 throw UnsupportedError(
370 "MG1_Shifts: ShiftType '" + shift_type +
371 "' is not ported. Its reference branch writes rowhatA(1,maxd*i:end) with `i` "
372 "undefined at that point, so it addresses column maxd of a sequence whose blocks "
373 "start every m columns and does not compute the last block it needs; the port "
374 "refuses rather than transcribe a line that cannot run. ETAQA uses ShiftType 'one'");
375
376 Blocks A = Ain;
377 const std::size_t m = A[0].rows();
378 const Drift d = mg1_drift(A);
379
380 if (!(d.value < 1.0))
381 throw UnsupportedError(
382 "MG1_Shifts: the drift > 1 branch of ShiftType 'one' is not ported, for the same "
383 "reason as ShiftType 'tau': it shifts one to infinity through the same defective "
384 "rowhatA(1,maxd*i:end) line. A transient M/G/1-type chain is outside what ETAQA "
385 "solves here");
386
387 // Shift one to zero: A1 <- A1 - I, then hatA_i = A_i - (A0+...+Ai) e u^T.
388 for (std::size_t i = 0; i < m; ++i) A[1](i, i) -= 1.0;
389 std::vector<double> col(m, 0.0);
390 Blocks hatA(A.size(), Matrix<double>(m, m, 0.0));
391 for (std::size_t b = 0; b < A.size(); ++b) {
392 const std::vector<double> rs = rowsums(A[b]);
393 for (std::size_t i = 0; i < m; ++i) col[i] += rs[i];
394 for (std::size_t i = 0; i < m; ++i)
395 for (std::size_t j = 0; j < m; ++j)
396 hatA[b](i, j) = A[b](i, j) - col[i] / static_cast<double>(m);
397 }
398 for (std::size_t i = 0; i < m; ++i) hatA[1](i, i) += 1.0;
399
400 ShiftResult out;
401 out.hatA = hatA;
402 out.drift = d.value;
403 out.v.assign(m, 0.0);
404 return out;
405}
406
407// ---------------------------------------------------------------------------
408// MG1_EG.m
409// ---------------------------------------------------------------------------
410
411/**
412 * G in closed form when A0 has rank one. Port of `MG1_EG.m`.
413 *
414 * `found` is false when the shortcut does not apply, which is the reference's
415 * empty return. A rank-one A0 means every down-transition forgets the phase it
416 * came from, so G is the same rank-one matrix `e beta` in the recurrent case;
417 * this is not an approximation and it is why an M/M/1-shaped input never
418 * enters cyclic reduction at all.
419 */
420inline Matrix<double> mg1_eg(const Blocks& Ain, bool& found) {
421 found = false;
422 Blocks A = Ain;
423 const std::size_t m = A[0].rows();
424 const std::size_t dega = A.size() - 1;
425 const Drift d = mg1_drift(A);
426
427 if (matrix_rank(A[0]) != 1) return Matrix<double>();
428
429 if (d.value < 1.0) {
430 // A0 = alpha beta: G = e beta with beta the normalized first nonzero row.
431 const std::vector<double> rs = rowsums(A[0]);
432 std::size_t first = m;
433 for (std::size_t i = 0; i < m; ++i)
434 if (rs[i] > 0.0) {
435 first = i;
436 break;
437 }
438 if (first == m) return Matrix<double>();
439 Matrix<double> G(m, m, 0.0);
440 for (std::size_t i = 0; i < m; ++i)
441 for (std::size_t j = 0; j < m; ++j) G(i, j) = A[0](first, j) / rs[first];
442 found = true;
443 return G;
444 }
445 if (d.value > 1.0) {
446 // Transient chain: G through the Ramaswami dual and its caudal value.
447 Blocks At(A.size(), Matrix<double>(m, m, 0.0));
448 for (std::size_t b = 0; b < A.size(); ++b)
449 for (std::size_t i = 0; i < m; ++i)
450 for (std::size_t j = 0; j < m; ++j)
451 At[b](i, j) = A[b](j, i) * d.theta[j] / d.theta[i];
452 const double etahat = gim1_caudal(At);
453 Matrix<double> temp = At[dega];
454 for (std::size_t i = dega; i-- > 1;) temp = madd(mscale(temp, etahat), At[i]);
455 Matrix<double> M = matmul(At[0], inverse(msub(eye<double>(m), temp)));
456 Matrix<double> G(m, m, 0.0);
457 for (std::size_t i = 0; i < m; ++i)
458 for (std::size_t j = 0; j < m; ++j) G(i, j) = M(j, i) * d.theta[j] / d.theta[i];
459 found = true;
460 return G;
461 }
462 return Matrix<double>();
463}
464
465// ---------------------------------------------------------------------------
466// MG1_CR.m
467// ---------------------------------------------------------------------------
468
469/** Options of `MG1_CR`, with the reference's defaults. */
471 std::string mode = "ShiftPWCR"; ///< 'ShiftPWCR' or 'PWCR'
472 std::string shift_type = "one";
473 std::size_t max_num_it = 50;
474 std::size_t max_num_root = 2048;
475 double epsilon = 1e-16;
476};
477
478namespace cr_detail {
479
480/** Blockwise DFT along the block index, MATLAB's fft over the block sequence. */
481inline std::vector<Matrix<std::complex<double>>> block_dft(const Blocks& blk, std::size_t n,
482 std::size_t use, bool inverse) {
483 const std::size_t m = blk[0].rows();
484 std::vector<Matrix<std::complex<double>>> out(
485 n, Matrix<std::complex<double>>(m, m, std::complex<double>(0.0, 0.0)));
486 std::vector<std::complex<double>> buf(n);
487 for (std::size_t i = 0; i < m; ++i)
488 for (std::size_t j = 0; j < m; ++j) {
489 for (std::size_t k = 0; k < n; ++k)
490 buf[k] = (k < use && k < blk.size()) ? std::complex<double>(blk[k](i, j), 0.0)
491 : std::complex<double>(0.0, 0.0);
492 dft(buf, inverse);
493 for (std::size_t k = 0; k < n; ++k) out[k](i, j) = buf[k];
494 }
495 return out;
496}
497
498/** The inverse of the above, keeping the real part as `real(ifft(...))` does. */
499inline Blocks block_idft_real(const std::vector<Matrix<std::complex<double>>>& F) {
500 const std::size_t n = F.size(), m = F[0].rows();
501 Blocks out(n, Matrix<double>(m, m, 0.0));
502 std::vector<std::complex<double>> buf(n);
503 for (std::size_t i = 0; i < m; ++i)
504 for (std::size_t j = 0; j < m; ++j) {
505 for (std::size_t k = 0; k < n; ++k) buf[k] = F[k](i, j);
506 dft(buf, true);
507 for (std::size_t k = 0; k < n; ++k) out[k](i, j) = buf[k].real();
508 }
509 return out;
510}
511
512inline Matrix<std::complex<double>> cmul(const Matrix<std::complex<double>>& A,
513 const Matrix<std::complex<double>>& B) {
514 return matmul(A, B);
515}
516
517inline Matrix<std::complex<double>> cinv_i_minus(const Matrix<std::complex<double>>& A) {
518 const std::size_t m = A.rows();
519 Matrix<std::complex<double>> M(m, m, std::complex<double>(0.0, 0.0));
520 for (std::size_t i = 0; i < m; ++i)
521 for (std::size_t j = 0; j < m; ++j)
522 M(i, j) = (i == j ? std::complex<double>(1.0, 0.0) : std::complex<double>(0.0, 0.0)) -
523 A(i, j);
524 return inverse(M);
525}
526
527/** The tail norm the reference measures, over blocks deg/2 .. deg-1. */
528inline double tail_norm(const Blocks& blk) {
529 const std::size_t deg = blk.size();
530 // MATLAB's `for i=deg/2:deg-1` starts at a HALF-INTEGER when deg is odd,
531 // and a half-integer index never matches, so the loop body runs from
532 // ceil(deg/2). Reproduced, or a degree-1 sequence would be measured here
533 // where the reference measures nothing.
534 const std::size_t start = (deg + 1) / 2;
535 double best = 0.0;
536 for (std::size_t i = start; i + 1 <= deg && i < deg; ++i) best = std::max(best, inf_norm(blk[i]));
537 return best;
538}
539
540/** The even-subscript blocks A0, A2, A4, ... of a sequence. */
541inline Blocks even_blocks(const Blocks& b) {
542 Blocks out;
543 for (std::size_t i = 0; i < b.size(); i += 2) out.push_back(b[i]);
544 return out;
545}
546
547/** The odd-subscript blocks A1, A3, A5, ... of a sequence. */
548inline Blocks odd_blocks(const Blocks& b) {
549 Blocks out;
550 for (std::size_t i = 1; i < b.size(); i += 2) out.push_back(b[i]);
551 return out;
552}
553
554} // namespace cr_detail
555
556/**
557 * Cyclic reduction for M/G/1-type Markov chains [Bini, Meini]. Port of
558 * `MG1_CR.m`, default mode 'ShiftPWCR' with ShiftType 'one'.
559 *
560 * WHAT THE ALGORITHM DOES, since the transcription is otherwise opaque. One
561 * step of cyclic reduction eliminates every odd level of the chain and leaves
562 * a chain of the same M/G/1-type shape on the even ones, so the level
563 * distance halves per iteration and the iteration converges quadratically.
564 * Doing that on the block sequences directly is a polynomial composition; the
565 * reference instead evaluates the four sequences at the (nj+1)-th roots of
566 * unity, does the composition POINT-WISE (a small dense inverse per root), and
567 * interpolates back with an inverse transform -- the "point-wise" in PWCR. The
568 * number of roots doubles until the interpolated tail is below (nj+1) eps,
569 * which is the reference's own accuracy control and the reason MaxNumRoot
570 * exists.
571 *
572 * Everything runs on the TRANSPOSED blocks, as the reference does after
573 * `D=D'`, and the final G is transposed back.
574 */
575inline Matrix<double> mg1_cr(const Blocks& Ain, const Mg1CrOptions& opts = Mg1CrOptions()) {
576 if (Ain.empty()) throw InputError("MG1_CR: empty block sequence");
577 const std::size_t m = Ain[0].rows();
578 if (opts.mode != "ShiftPWCR" && opts.mode != "PWCR")
579 throw UnsupportedError("MG1_CR: Mode '" + opts.mode +
580 "' is not supported; the reference offers 'PWCR' and 'ShiftPWCR'");
581
582 bool eg_found = false;
583 const Matrix<double> Geg = mg1_eg(Ain, eg_found);
584 if (eg_found) return Geg;
585
586 Blocks A = Ain;
587 double drift = 0.0;
588 if (opts.mode == "ShiftPWCR") {
589 const ShiftResult sh = mg1_shifts(A, opts.shift_type);
590 A = sh.hatA;
591 drift = sh.drift;
592 }
593
594 // D = A', padded with zero blocks to 2^(1+floor(log2(maxd)))+1 of them.
595 const std::size_t maxd = A.size() - 1;
596 if (maxd == 0) throw InputError("MG1_CR: the sequence needs at least two blocks");
597 std::size_t target = 1;
598 while (target < maxd) target <<= 1; // 2^ceil(log2(maxd))
599 if (target == maxd) target <<= 1; // 2^(1+floor(log2(maxd))) when maxd is a power of two
600 target += 1;
601 Blocks D(target, Matrix<double>(m, m, 0.0));
602 for (std::size_t b = 0; b <= maxd; ++b) D[b] = A[b].transpose();
603
604 Blocks Aeven = cr_detail::even_blocks(D);
605 Blocks Aodd = cr_detail::odd_blocks(D);
606 Blocks Ahatodd(Aeven.begin() + 1, Aeven.end());
607 Ahatodd.push_back(D.back());
608 Blocks Ahateven = Aodd;
609
610 Matrix<double> Rj = D[1];
611 for (std::size_t i = 2; i < D.size(); ++i) Rj = madd(Rj, D[i]);
612 Rj = matmul(D[0], inverse(msub(eye<double>(m), Rj)));
613
614 Matrix<double> G(m, m, 0.0);
615 Blocks Anew, Ahatnew;
616 std::size_t numit = 0;
617 while (numit < opts.max_num_it) {
618 ++numit;
619 std::size_t nj = Aodd.size() - 1;
620 double nAnew = 0.0, nAhatnew = 0.0;
621
622 if (nj > 0) {
623 const std::size_t n = nj + 1;
624 const std::vector<Matrix<std::complex<double>>> T1 =
625 cr_detail::block_dft(Aodd, n, n, false);
626 const std::vector<Matrix<std::complex<double>>> T2 =
627 cr_detail::block_dft(Aeven, n, n, false);
628 const std::vector<Matrix<std::complex<double>>> T3 =
629 cr_detail::block_dft(Ahatodd, n, n, false);
630 const std::vector<Matrix<std::complex<double>>> T4 =
631 cr_detail::block_dft(Ahateven, n, n, false);
632 std::vector<Matrix<std::complex<double>>> Ah(n), An(n);
633 const double pi = 3.14159265358979323846;
634 for (std::size_t c = 0; c < n; ++c) {
635 const Matrix<std::complex<double>> W = cr_detail::cinv_i_minus(T1[c]);
636 Ah[c] = madd(T4[c], cr_detail::cmul(cr_detail::cmul(T2[c], W), T3[c]));
637 const double ang = -2.0 * pi * static_cast<double>(c) / static_cast<double>(n);
638 const std::complex<double> w(std::cos(ang), std::sin(ang));
639 Matrix<std::complex<double>> first(m, m, std::complex<double>(0.0, 0.0));
640 for (std::size_t i = 0; i < m; ++i)
641 for (std::size_t j = 0; j < m; ++j) first(i, j) = w * T1[c](i, j);
642 An[c] = madd(first, cr_detail::cmul(cr_detail::cmul(T2[c], W), T2[c]));
643 }
644 Ahatnew = cr_detail::block_idft_real(Ah);
645 Anew = cr_detail::block_idft_real(An);
646 } else {
647 const Matrix<double> temp =
648 matmul(Aeven[0], inverse(msub(eye<double>(m), Aodd[0])));
649 Ahatnew.assign(1, madd(Ahateven[0], matmul(temp, Ahatodd[0])));
650 Anew.clear();
651 Anew.push_back(matmul(temp, Aeven[0]));
652 Anew.push_back(Aodd[0]);
653 }
654
655 nAnew = cr_detail::tail_norm(Anew);
656 nAhatnew = cr_detail::tail_norm(Ahatnew);
657
658 // Double the number of roots until the interpolated tail is negligible.
659 while ((nAnew > static_cast<double>(nj + 1) * opts.epsilon ||
660 nAhatnew > static_cast<double>(nj + 1) * opts.epsilon) &&
661 nj + 1 < opts.max_num_root) {
662 nj = 2 * (nj + 1) - 1;
663 const std::size_t n = nj + 1;
664 const std::size_t stopv = std::min(n, Aodd.size());
665 const std::vector<Matrix<std::complex<double>>> T1 =
666 cr_detail::block_dft(Aodd, n, stopv, false);
667 const std::vector<Matrix<std::complex<double>>> T2 =
668 cr_detail::block_dft(Aeven, n, stopv, false);
669 const std::vector<Matrix<std::complex<double>>> T3 =
670 cr_detail::block_dft(Ahatodd, n, stopv, false);
671 const std::vector<Matrix<std::complex<double>>> T4 =
672 cr_detail::block_dft(Ahateven, n, stopv, false);
673 std::vector<Matrix<std::complex<double>>> Ah(n), An(n);
674 const double pi = 3.14159265358979323846;
675 for (std::size_t c = 0; c < n; ++c) {
676 const Matrix<std::complex<double>> W = cr_detail::cinv_i_minus(T1[c]);
677 Ah[c] = madd(T4[c], cr_detail::cmul(cr_detail::cmul(T2[c], W), T3[c]));
678 const double ang = -2.0 * pi * static_cast<double>(c) / static_cast<double>(n);
679 const std::complex<double> w(std::cos(ang), std::sin(ang));
680 Matrix<std::complex<double>> first(m, m, std::complex<double>(0.0, 0.0));
681 for (std::size_t i = 0; i < m; ++i)
682 for (std::size_t j = 0; j < m; ++j) first(i, j) = w * T1[c](i, j);
683 An[c] = madd(first, cr_detail::cmul(cr_detail::cmul(T2[c], W), T2[c]));
684 }
685 Ahatnew = cr_detail::block_idft_real(Ah);
686 Anew = cr_detail::block_idft_real(An);
687 nAnew = cr_detail::tail_norm(Anew);
688 nAhatnew = cr_detail::tail_norm(Ahatnew);
689 }
690
691 if (nj > 1) {
692 const std::size_t keep = (nj + 1) / 2;
693 Anew.resize(std::min(keep, Anew.size()));
694 Ahatnew.resize(std::min(keep, Ahatnew.size()));
695 }
696
697 Aeven = cr_detail::even_blocks(Anew);
698 Aodd = cr_detail::odd_blocks(Anew);
699 Ahateven = cr_detail::even_blocks(Ahatnew);
700 Ahatodd = cr_detail::odd_blocks(Ahatnew);
701
702 if (opts.mode == "PWCR") {
703 Matrix<double> Rnewj = Anew.size() > 1 ? Anew[1] : Matrix<double>(m, m, 0.0);
704 for (std::size_t i = 2; i < Anew.size(); ++i) Rnewj = madd(Rnewj, Anew[i]);
705 Rnewj = matmul(Anew[0], inverse(msub(eye<double>(m), Rnewj)));
706 const Matrix<double> U =
707 Anew.size() > 1
708 ? msub(eye<double>(m),
709 matmul(Anew[0], inverse(msub(eye<double>(m), Anew[1]))))
710 : eye<double>(m);
711 if (max_abs_diff(Rj, Rnewj) < opts.epsilon || max_col_sum(U) < opts.epsilon) {
712 G = Ahatnew[0];
713 for (std::size_t i = 1; i < Ahatnew.size(); ++i)
714 G = madd(G, matmul(Rnewj, Ahatnew[i]));
715 G = matmul(D[0], inverse(msub(eye<double>(m), G)));
716 break;
717 }
718 Rj = Rnewj;
719 double tail_sum = 0.0;
720 for (std::size_t i = 1; i < Ahatnew.size(); ++i) tail_sum += Ahatnew[i].sum();
721 const double sv = svd_values(Anew[0]).empty() ? 0.0 : svd_values(Anew[0])[0];
722 const Matrix<double> V =
723 msub(eye<double>(m), matmul(D[0], inverse(msub(eye<double>(m), Ahatnew[0]))));
724 if (sv < opts.epsilon || tail_sum < opts.epsilon || max_col_sum(V) < opts.epsilon) {
725 G = matmul(D[0], inverse(msub(eye<double>(m), Ahatnew[0])));
726 break;
727 }
728 } else {
729 const Matrix<double> Gold = G;
730 G = matmul(D[0], inverse(msub(eye<double>(m), Ahatnew[0])));
731 if (inf_norm(msub(G, Gold)) < opts.epsilon || inf_norm(Ahatnew, 1) < opts.epsilon)
732 break;
733 }
734 }
735 if (numit == opts.max_num_it && !Ahatnew.empty())
736 G = matmul(D[0], inverse(msub(eye<double>(m), Ahatnew[0])));
737
738 G = G.transpose();
739
740 // Undo the shift: shifting one to zero removed the rank-one term e u^T
741 // from G, so it goes back on.
742 if (opts.mode == "ShiftPWCR" && drift < 1.0)
743 for (std::size_t i = 0; i < m; ++i)
744 for (std::size_t j = 0; j < m; ++j) G(i, j) += 1.0 / static_cast<double>(m);
745 return G;
746}
747
748// ---------------------------------------------------------------------------
749// MG1_FI.m
750// ---------------------------------------------------------------------------
751
752/** Options of `MG1_FI`, with the reference's defaults. */
754 std::string mode = "U-Based"; ///< 'Natural', 'Traditional', 'U-Based', or 'Shift<Mode>'
755 std::string shift_type = "one";
756 std::size_t max_num_it = 10000;
757 double tol = 1e-14;
758};
759
760/**
761 * Functional iterations for M/G/1-type Markov chains [Neuts]. Port of
762 * `MG1_FI.m` for the three modes and the shift variants; the reference's
763 * `NonZeroBlocks` option is not exposed, because it changes only which
764 * products are skipped when some A_i vanish and converges to the same G.
765 *
766 * 'U-Based' is the default and the one `GIM1_R(...,'FI')` uses: it solves
767 * G = (I - sum_{j>=1} A_j G^{j-1})^{-1} A0, which is the U-based iteration and
768 * converges monotonically from below to the minimal nonnegative solution.
769 */
770inline Matrix<double> mg1_fi(const Blocks& Ain, const Mg1FiOptions& opts = Mg1FiOptions()) {
771 const std::size_t m = Ain[0].rows();
772 const std::size_t maxd = Ain.size() - 1;
773
774 bool eg_found = false;
775 const Matrix<double> Geg = mg1_eg(Ain, eg_found);
776 if (eg_found) return Geg;
777
778 Blocks A = Ain;
779 const bool shifted = opts.mode.find("Shift") != std::string::npos;
780 double drift = 0.0;
781 if (shifted) {
782 const ShiftResult sh = mg1_shifts(A, opts.shift_type);
783 A = sh.hatA;
784 drift = sh.drift;
785 }
786
787 const bool natural = opts.mode.find("Natural") != std::string::npos;
788 const bool traditional = opts.mode.find("Traditional") != std::string::npos;
789 const bool ubased = opts.mode.find("U-Based") != std::string::npos;
790 if (!natural && !traditional && !ubased)
791 throw UnsupportedError("MG1_FI: Mode '" + opts.mode + "' is not supported");
792
793 Matrix<double> G(m, m, 0.0);
794 double check = 1.0;
795 std::size_t numit = 0;
796 while (check > opts.tol && numit < opts.max_num_it) {
797 const Matrix<double> Gold = G;
798 if (natural) {
799 G = A[maxd];
800 for (std::size_t j = maxd; j-- > 0;) G = madd(A[j], matmul(G, Gold));
801 } else if (traditional) {
802 G = A[maxd];
803 for (std::size_t j = maxd; j-- > 2;) G = madd(A[j], matmul(G, Gold));
804 G = madd(A[0], matmul(G, matmul(Gold, Gold)));
805 G = matmul(inverse(msub(eye<double>(m), A[1])), G);
806 } else {
807 G = A[maxd];
808 for (std::size_t j = maxd; j-- > 1;) G = madd(A[j], matmul(G, Gold));
809 G = matmul(inverse(msub(eye<double>(m), G)), A[0]);
810 }
811 check = inf_norm(msub(G, Gold));
812 ++numit;
813 }
814
815 if (shifted && drift < 1.0)
816 for (std::size_t i = 0; i < m; ++i)
817 for (std::size_t j = 0; j < m; ++j) G(i, j) += 1.0 / static_cast<double>(m);
818 return G;
819}
820
821// ---------------------------------------------------------------------------
822// GIM1_R.m
823// ---------------------------------------------------------------------------
824
825/**
826 * R of a GI/M/1-type Markov chain, through the G of its dual. Port of
827 * `GIM1_R.m` for Dual 'A', 'R' and 'B' and Algor 'FI' and 'CR'.
828 *
829 * THE DUAL IS THE WHOLE IDEA. There is no cyclic reduction for R directly, so
830 * the chain is transposed into an M/G/1-type one whose G carries the same
831 * information: the Ramaswami dual `diag(theta)^-1 A_i' diag(theta)` for a
832 * transient chain, and the Bright dual, which additionally rescales block i by
833 * eta^(i-1) with eta the caudal characteristic, for a positive recurrent one.
834 * 'A' picks between them by the drift, which is what makes it the fastest
835 * default. R is then read back off G by the inverse similarity, times eta in
836 * the Bright case.
837 */
838inline Matrix<double> gim1_r(const Blocks& Ain, const std::string& dual,
839 const std::string& algor) {
840 const std::size_t m = Ain[0].rows();
841 const std::size_t dega = Ain.size() - 1;
842 Blocks A = Ain;
843
844 // drift > 1: positive recurrent GI/M/1; drift < 1: transient.
845 const Drift d = mg1_drift(A);
846 std::vector<double> theta = d.theta;
847 const bool ram = (dual == "R") || (dual == "A" && d.value <= 1.0);
848 double eta = 1.0;
849
850 if (ram) {
851 for (std::size_t b = 0; b <= dega; ++b) {
852 Matrix<double> Bb(m, m, 0.0);
853 for (std::size_t i = 0; i < m; ++i)
854 for (std::size_t j = 0; j < m; ++j) Bb(i, j) = A[b](j, i) * theta[j] / theta[i];
855 A[b] = Bb;
856 }
857 } else if (dual == "B" || dual == "A") {
858 eta = (d.value > 1.0) ? gim1_caudal(Ain) : mg1_decay(Ain);
859 // theta of A0 + A1 eta + ... + Amax eta^max, shifted to be stochastic.
860 Matrix<double> sumAeta = mscale(Ain[dega], std::pow(eta, static_cast<double>(dega)));
861 for (std::size_t i = dega; i-- > 0;)
862 sumAeta = madd(sumAeta, mscale(Ain[i], std::pow(eta, static_cast<double>(i))));
863 Matrix<double> shifted = sumAeta;
864 for (std::size_t i = 0; i < m; ++i) shifted(i, i) += (1.0 - eta);
865 theta = stat(shifted);
866 for (std::size_t b = 0; b <= dega; ++b) {
867 Matrix<double> Bb(m, m, 0.0);
868 const double s = std::pow(eta, static_cast<double>(b) - 1.0);
869 for (std::size_t i = 0; i < m; ++i)
870 for (std::size_t j = 0; j < m; ++j)
871 Bb(i, j) = s * A[b](j, i) * theta[j] / theta[i];
872 A[b] = Bb;
873 }
874 } else {
875 throw InputError("GIM1_R: Dual '" + dual + "' is not one of 'A', 'B', 'R'");
876 }
877
879 if (algor == "FI") {
880 G = mg1_fi(A);
881 } else if (algor == "CR") {
882 G = mg1_cr(A);
883 } else {
884 throw UnsupportedError(
885 "GIM1_R: Algor '" + algor +
886 "' is not ported; MG1_NI, MG1_RR and MG1_IS have no C++ counterpart. "
887 "'FI' (the one GIM1_R_ETAQA asks for) and 'CR' are available");
888 }
889
890 Matrix<double> R(m, m, 0.0);
891 for (std::size_t i = 0; i < m; ++i)
892 for (std::size_t j = 0; j < m; ++j) R(i, j) = G(j, i) * theta[j] / theta[i];
893 if (!ram)
894 for (std::size_t i = 0; i < m; ++i)
895 for (std::size_t j = 0; j < m; ++j) R(i, j) *= eta;
896 return R;
897}
898
899} // namespace smc
900} // namespace line
901
902#endif // LINE_LIB_SMC_MG1_H
InputError(const std::string &what)
Definition error.h:39
std::size_t size() const
Definition matrix.h:91
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
bool empty() const
Definition matrix.h:92
Matrix transpose() const
Definition matrix.h:110
NumericError(const std::string &what)
Definition error.h:45
UnsupportedError(const std::string &what)
Definition error.h:51
std::complex<double> as a number type for the generic linear algebra.
Eigenvalues and singular values, backed by LAPACK.
The exception types the port throws.
Discrete Fourier transform of arbitrary length, in double complex.
Dense linear algebra over the templated number type: products, identity, inverse, and powers.
Least squares for a rectangular system, exact-capable.
LU factorization with partial pivoting, templated on the number type.
Dense matrix and non-owning view.
double max_col_sum(const Matrix< double > &A)
max(sum(A)), the largest column sum WITHOUT absolute values, as in MATLAB.
Definition mg1.h:147
Blocks vblocks_of(const Matrix< double > &A, std::size_t r)
Splits a vertical stack into its blocks of r rows.
Definition mg1.h:192
Matrix< double > hcat(const Blocks &blk)
Re-assembles a block sequence into the wide [A0 A1 ... Amax].
Definition mg1.h:170
ShiftResult mg1_shifts(const Blocks &Ain, const std::string &shift_type)
Shift technique for the M/G/1-type sequence.
Definition mg1.h:367
double mg1_decay(const Blocks &A)
Decay rate of a recurrent M/G/1-type chain: the unique z > 1 with PF(A(z)) = z.
Definition mg1.h:305
Blocks blocks_of(const Matrix< double > &A, std::size_t m)
Splits the wide [A0 A1 ... Amax] into its m x m blocks.
Definition mg1.h:158
Matrix< double > poly_at(const Blocks &A, double z)
A(z) = A0 + A1 z + ... + Amax z^max, by Horner as the reference writes it.
Definition mg1.h:293
Matrix< T > msub(const Matrix< T > &A, const Matrix< T > &B)
A - B.
Definition mg1.h:94
double inf_norm(const Matrix< double > &A)
norm(A,inf), the largest absolute row sum.
Definition mg1.h:120
Matrix< double > mg1_eg(const Blocks &Ain, bool &found)
G in closed form when A0 has rank one.
Definition mg1.h:420
Matrix< double > gim1_r(const Blocks &Ain, const std::string &dual, const std::string &algor)
R of a GI/M/1-type Markov chain, through the G of its dual.
Definition mg1.h:838
std::vector< double > stat(const Matrix< double > &A)
Stationary distribution of a stochastic matrix: the left eigenvector for eigenvalue 1,...
Definition mg1.h:217
Matrix< double > mg1_cr(const Blocks &Ain, const Mg1CrOptions &opts=Mg1CrOptions())
Cyclic reduction for M/G/1-type Markov chains [Bini, Meini].
Definition mg1.h:575
Matrix< double > mg1_fi(const Blocks &Ain, const Mg1FiOptions &opts=Mg1FiOptions())
Functional iterations for M/G/1-type Markov chains [Neuts].
Definition mg1.h:770
std::vector< double > rowsums(const Matrix< double > &A)
sum(A,2), the row sums, as a column held in a vector.
Definition mg1.h:112
Drift mg1_drift(const Blocks &A)
drift = theta * beta with beta = (Amax)e + (Amax+Amax-1)e + ..., the expected level increment per tra...
Definition mg1.h:254
std::complex< double > max_eig(const Matrix< double > &M)
max(eig(M)) with MATLAB's semantics on a complex spectrum: the element of largest modulus,...
Definition mg1.h:281
double dot(const std::vector< double > &a, const std::vector< double > &b)
The inner product of a row vector with a column held as a vector.
Definition mg1.h:236
std::vector< Matrix< double > > Blocks
Definition mg1.h:75
Matrix< T > madd(const Matrix< T > &A, const Matrix< T > &B)
A + B.
Definition mg1.h:83
std::vector< double > rowvec_times(const std::vector< double > &v, const Matrix< double > &A)
theta A, the row vector times matrix product used throughout.
Definition mg1.h:231
double gim1_caudal(const Blocks &A)
Caudal characteristic of a GI/M/1-type chain: the spectral radius of R, the unique z in (0,...
Definition mg1.h:329
double max_abs_diff(const Matrix< double > &A, const Matrix< double > &B)
max(max(abs(A-B))).
Definition mg1.h:138
Matrix< double > mscale(const Matrix< double > &A, double c)
c * A.
Definition mg1.h:104
Matrix< double > vcat(const Blocks &blk)
Stacks a block sequence vertically, [A0; A1; ...; Amax].
Definition mg1.h:181
LstsqResult< T > lstsq(const Matrix< T > &A, const std::vector< T > &b, const T &tol)
Least-squares solution of A x = b, minimum-norm when A is rank deficient.
Definition lstsq.h:152
std::vector< T > vecmul(const std::vector< T > &v, const Matrix< T > &A)
Row vector times matrix, v A.
Definition linalg.h:50
Matrix< T > inverse(const Matrix< T > &A)
Inverse by LU with one factorization and n back substitutions.
Definition linalg.h:72
Matrix< T > matmul(const Matrix< T > &A, const Matrix< T > &B)
Matrix product A B.
Definition linalg.h:36
std::vector< std::complex< double > > eig_values(const Matrix< double > &A)
Eigenvalues of a general real square matrix, in LAPACK's order.
Definition eig.h:59
std::size_t matrix_rank(const Matrix< double > &A)
Numerical rank at the standard max(m,n) eps sigma_1 threshold.
Definition eig.h:329
std::vector< double > svd_values(const Matrix< double > &A)
Singular values in descending order.
Definition eig.h:128
Matrix< T > eye(std::size_t n)
Identity of order n.
Definition linalg.h:28
void dft(std::vector< std::complex< double > > &a, bool inverse)
In-place DFT of a.
Definition fft.h:120
Number-type abstraction for the templated API port.
The drift of an M/G/1-type sequence, and the invariant vector it uses.
Definition mg1.h:244
double value
Definition mg1.h:245
std::vector< double > theta
stat(A0 + A1 + ... + Amax)
Definition mg1.h:246
Options of MG1_CR, with the reference's defaults.
Definition mg1.h:470
std::string shift_type
Definition mg1.h:472
std::string mode
'ShiftPWCR' or 'PWCR'
Definition mg1.h:471
std::size_t max_num_root
Definition mg1.h:474
std::size_t max_num_it
Definition mg1.h:473
Options of MG1_FI, with the reference's defaults.
Definition mg1.h:753
std::string shift_type
Definition mg1.h:755
std::string mode
'Natural', 'Traditional', 'U-Based', or 'Shift<Mode>'
Definition mg1.h:754
std::size_t max_num_it
Definition mg1.h:756
What MG1_Shifts returns: the shifted sequence and the drift it measured.
Definition mg1.h:348
std::vector< double > v
Definition mg1.h:352