|
| 1 | +// SPDX-FileCopyrightText: 2026 The PPVM Authors |
| 2 | +// SPDX-License-Identifier: Apache-2.0 |
| 3 | + |
| 4 | +//! Taylor-partition selection for the `quspin-expm`-backed `exp(t·A)·b` |
| 5 | +//! engine (driven from [`crate::mf_expm`]): the `(m, s)` selection tables |
| 6 | +//! [`THETA`] / [`THETA_LOOSE`] from Al-Mohy & Higham (2011), used to pick |
| 7 | +//! the partition handed to `quspin-expm`'s `from_parts`. |
| 8 | +
|
| 9 | +/// `θ_m` table from Al-Mohy & Higham (2011), Table A.3, for double |
| 10 | +/// precision (unit roundoff `u = 2^{-53}`). |
| 11 | +/// |
| 12 | +/// `θ_m` bounds `‖A‖₁` such that the degree-`m` Taylor polynomial |
| 13 | +/// approximates `exp(A)` to within `u`. We pick `(m, s)` with |
| 14 | +/// `s ≥ ⌈‖tA‖₁ / θ_m⌉` and minimise `m·s` (total SpMV count). |
| 15 | +pub(crate) const THETA: &[(u32, f64)] = &[ |
| 16 | + (1, 2.29e-16), |
| 17 | + (2, 2.58e-8), |
| 18 | + (3, 1.39e-5), |
| 19 | + (4, 3.40e-4), |
| 20 | + (5, 2.40e-3), |
| 21 | + (6, 9.07e-3), |
| 22 | + (7, 2.38e-2), |
| 23 | + (8, 5.00e-2), |
| 24 | + (9, 8.96e-2), |
| 25 | + (10, 1.44e-1), |
| 26 | + (11, 2.14e-1), |
| 27 | + (12, 3.00e-1), |
| 28 | + (13, 4.00e-1), |
| 29 | + (14, 5.14e-1), |
| 30 | + (15, 6.41e-1), |
| 31 | + (16, 7.81e-1), |
| 32 | + (17, 9.31e-1), |
| 33 | + (18, 1.09), |
| 34 | + (19, 1.26), |
| 35 | + (20, 1.44), |
| 36 | + (21, 1.62), |
| 37 | + (22, 1.82), |
| 38 | + (23, 2.01), |
| 39 | + (24, 2.22), |
| 40 | + (25, 2.43), |
| 41 | + (26, 2.64), |
| 42 | + (27, 2.86), |
| 43 | + (28, 3.08), |
| 44 | + (29, 3.31), |
| 45 | + (30, 3.54), |
| 46 | +]; |
| 47 | + |
| 48 | +/// `θ_m` table for a relaxed backward-error tolerance `tol = 1e-6`, computed |
| 49 | +/// with the same Al-Mohy & Higham (2011) construction as [`THETA`] (the |
| 50 | +/// backward-error series `h_{m+1}(x) = log(e^{-x} T_m(x))`; validated by |
| 51 | +/// reproducing the `u = 2^{-53}` table above to ~2 significant figures). |
| 52 | +/// |
| 53 | +/// The predictor-corrector truncates the Pauli basis at `drop_tol` (typically |
| 54 | +/// 1e-3), so computing `exp` to double-precision backward error (~1e-16) is |
| 55 | +/// ~10 orders more accurate than the state it acts on. Using `tol = 1e-6` |
| 56 | +/// (still ~1000x tighter than the truncation) admits a lower-degree Taylor |
| 57 | +/// polynomial for the same `‖tA‖`, cutting the SpMV count (e.g. 23 -> 13 at |
| 58 | +/// `‖tA‖ ≈ 2`) with no measurable effect on the truncated result. |
| 59 | +pub(crate) const THETA_LOOSE: &[(u32, f64)] = &[ |
| 60 | + (1, 2.000e-06), |
| 61 | + (2, 2.447e-03), |
| 62 | + (3, 2.863e-02), |
| 63 | + (4, 1.025e-01), |
| 64 | + (5, 2.262e-01), |
| 65 | + (6, 3.911e-01), |
| 66 | + (7, 5.866e-01), |
| 67 | + (8, 8.045e-01), |
| 68 | + (9, 1.039), |
| 69 | + (10, 1.285), |
| 70 | + (11, 1.539), |
| 71 | + (12, 1.801), |
| 72 | + (13, 2.067), |
| 73 | + (14, 2.337), |
| 74 | + (15, 2.610), |
| 75 | + (16, 2.885), |
| 76 | + (17, 3.162), |
| 77 | + (18, 3.441), |
| 78 | + (19, 3.721), |
| 79 | + (20, 4.001), |
| 80 | + (21, 4.282), |
| 81 | + (22, 4.564), |
| 82 | + (23, 4.847), |
| 83 | + (24, 5.129), |
| 84 | + (25, 5.412), |
| 85 | + (26, 5.696), |
| 86 | + (27, 5.979), |
| 87 | + (28, 6.263), |
| 88 | + (29, 6.546), |
| 89 | + (30, 6.830), |
| 90 | +]; |
| 91 | + |
| 92 | +/// Pick `(m, s)` minimising `s·m` subject to `s ≥ ⌈t_norm / θ_m⌉, s ≥ 1`, |
| 93 | +/// using the `θ_m` table `theta`. Restricted to the table's `m` range; for |
| 94 | +/// larger norms `s` simply grows linearly. |
| 95 | +fn select_ms_with(t_norm: f64, theta: &[(u32, f64)]) -> (u32, u32) { |
| 96 | + if t_norm <= 0.0 { |
| 97 | + return (1, 1); |
| 98 | + } |
| 99 | + let mut best_m = 1u32; |
| 100 | + let mut best_s = 1u32; |
| 101 | + let mut best_cost = u64::MAX; |
| 102 | + for &(m, th) in theta { |
| 103 | + let s_f = (t_norm / th).ceil(); |
| 104 | + let s = if s_f >= 1.0 { s_f as u32 } else { 1 }; |
| 105 | + let cost = (m as u64) * (s as u64); |
| 106 | + if cost < best_cost { |
| 107 | + best_cost = cost; |
| 108 | + best_m = m; |
| 109 | + best_s = s; |
| 110 | + } |
| 111 | + } |
| 112 | + (best_m, best_s) |
| 113 | +} |
| 114 | + |
| 115 | +/// `(m, s)` selection at double-precision backward error ([`THETA`]). |
| 116 | +pub(crate) fn select_ms(t_norm: f64) -> (u32, u32) { |
| 117 | + select_ms_with(t_norm, THETA) |
| 118 | +} |
| 119 | + |
| 120 | +/// `(m, s)` selection at the relaxed `tol = 1e-6` backward error |
| 121 | +/// ([`THETA_LOOSE`]) — fewer SpMVs, used on the truncated PC expm path. |
| 122 | +pub(crate) fn select_ms_loose(t_norm: f64) -> (u32, u32) { |
| 123 | + select_ms_with(t_norm, THETA_LOOSE) |
| 124 | +} |
| 125 | + |
| 126 | +#[cfg(test)] |
| 127 | +mod tests { |
| 128 | + use super::*; |
| 129 | + |
| 130 | + #[test] |
| 131 | + fn ms_selection_sane() { |
| 132 | + // tiny norm → small m, s = 1 |
| 133 | + let (m, s) = select_ms(1e-9); |
| 134 | + assert!(m <= 5, "expected small m for tiny norm, got m={m}"); |
| 135 | + assert_eq!(s, 1); |
| 136 | + |
| 137 | + // moderate norm → m·s should be ~10-50 |
| 138 | + let (m, s) = select_ms(1.0); |
| 139 | + assert!((m * s) <= 50, "moderate norm cost too high: m={m} s={s}"); |
| 140 | + |
| 141 | + // large norm → s grows |
| 142 | + let (_m, s) = select_ms(100.0); |
| 143 | + assert!(s >= 20, "large norm should require many steps, got s={s}"); |
| 144 | + } |
| 145 | +} |
0 commit comments