|
12 | 12 |
|
13 | 13 | namespace vmecpp { |
14 | 14 |
|
| 15 | +namespace { |
| 16 | + |
| 17 | +// Growth of the homogeneous solutions of the T_l recurrence over a forward |
| 18 | +// pass, sqrt(B/A)^kL, above which the recurrence is run backward instead; a |
| 19 | +// forward pass amplifies the rounding of its inputs by at most this factor. |
| 20 | +constexpr double kMaxForwardGrowth = 10.0; |
| 21 | + |
| 22 | +// The zero seed of a backward pass contaminates T_l by (A/B)^{(top - l)/2} of |
| 23 | +// T_top; the pass starts far enough above kL to bring that below this at kL. |
| 24 | +constexpr double kMaxSeedContamination = 1.0e-17; |
| 25 | + |
| 26 | +// T_l = int_{-1}^{1} t^l / sqrt(A t^2 + 2 d t + B) dt for l = 0, ..., kL at |
| 27 | +// tangential grid point kl from the three-term recurrence |
| 28 | +// (l + 1) A T_{l+1} + (2 l + 1) d T_l + l B T_{l-1} = sqrtc2 - (-1)^l sqrta2 |
| 29 | +// with T_0 given. The characteristic roots of the homogeneous recurrence are a |
| 30 | +// complex pair of modulus sqrt(B/A), so a forward pass amplifies the rounding |
| 31 | +// of T_0 and of the right-hand sides by sqrt(B/A)^l and is used while that |
| 32 | +// stays below kMaxForwardGrowth; otherwise the recurrence runs backward from a |
| 33 | +// zero seed, whose contamination decays by sqrt(A/B) per step. |
| 34 | +void ComputeTl(double A, double B, double d, double sqrtc2, double sqrta2, |
| 35 | + double T0, int kL, int kl, std::vector<Eigen::VectorXd>& m_T) { |
| 36 | + const auto rhs = [sqrtc2, sqrta2](int l) { |
| 37 | + return sqrtc2 + (l % 2 == 0 ? -sqrta2 : sqrta2); |
| 38 | + }; |
| 39 | + m_T[0][kl] = T0; |
| 40 | + const double log_ratio = std::log(B / A); |
| 41 | + if (kL * log_ratio <= 2.0 * std::log(kMaxForwardGrowth)) { |
| 42 | + double T_prev = 0.0; // T_{-1} |
| 43 | + for (int l = 0; l < kL; ++l) { |
| 44 | + const double T_next = |
| 45 | + (rhs(l) - (2 * l + 1) * d * m_T[l][kl] - l * B * T_prev) / |
| 46 | + ((l + 1) * A); |
| 47 | + T_prev = m_T[l][kl]; |
| 48 | + m_T[l + 1][kl] = T_next; |
| 49 | + } |
| 50 | + return; |
| 51 | + } |
| 52 | + const int tail = static_cast<int>( |
| 53 | + std::ceil(-2.0 * std::log(kMaxSeedContamination) / log_ratio)); |
| 54 | + double T_hi = 0.0; // T_{l+1} |
| 55 | + double T_cur = 0.0; // T_l |
| 56 | + for (int l = kL + tail; l >= 1; --l) { |
| 57 | + const double T_lo = |
| 58 | + (rhs(l) - (2 * l + 1) * d * T_cur - (l + 1) * A * T_hi) / (l * B); |
| 59 | + T_hi = T_cur; |
| 60 | + T_cur = T_lo; |
| 61 | + if (1 <= l - 1 && l - 1 <= kL) { |
| 62 | + m_T[l - 1][kl] = T_lo; |
| 63 | + } |
| 64 | + } |
| 65 | +} |
| 66 | + |
| 67 | +} // namespace |
| 68 | + |
15 | 69 | SingularIntegrals::SingularIntegrals(const Sizes* s, |
16 | 70 | const FourierBasisFastToroidal* fb, |
17 | 71 | const TangentialPartitioning* tp, |
@@ -220,117 +274,9 @@ void SingularIntegrals::prepareUpdate( |
220 | 274 | (sqrtam * sqrta2[kl] - am[kl] + d[kl])) / |
221 | 275 | sqrtam; |
222 | 276 |
|
223 | | - // Fill all Tlp[0..L] and Tlm[0..L] by picking the numerically stable |
224 | | - // direction of the three-term recurrence on a per-(+/-), per-kl basis. |
225 | | - // |
226 | | - // The characteristic roots of the homogeneous recurrence satisfy |
227 | | - // A*r^2 + 2*d*r + B = 0 -> |r1 r2| = B/A. |
228 | | - // For T^+: (A, B) = (ap, am), so |r1 r2| = am/ap. |
229 | | - // For T^-: (A, B) = (am, ap), so |r1 r2| = ap/am. |
230 | | - // If B > A (at least one |r| > 1), forward iteration is unstable and |
231 | | - // backward (Miller's algorithm) is used instead; otherwise forward is fine. |
232 | | - // |
233 | | - // T^{\pm}_0 is analytic (above); T^{\pm}_{-1} = 0. Forward produces |
234 | | - // T_{l+1} from T_l and T_{l-1}; backward produces T_{l-1} from T_l and |
235 | | - // T_{l+1} via the same recurrence solved in reverse. For backward, |
236 | | - // iteration starts from a zero seed far above the required L; the result |
237 | | - // is then normalized to match the analytic T^{\pm}_0. |
238 | | - // |
239 | | - // rhs(l+1) = sqrtc2 + (-1)^{l+1}*sqrta2 (same for T^+ and T^-). |
240 | 277 | const int kL = mf + nf; |
241 | | - // The spurious solution is damped by (A/B)^kTailExtra per pass. |
242 | | - // For the worst realistic ratio (A/B ~ 0.5) suppression is ~0.5^50 ~ 1e-16. |
243 | | - const int kTailExtra = 50; |
244 | | - const int kLtail = kL + kTailExtra; |
245 | | - |
246 | | - // Only switch to backward when the forward spurious-mode growth |
247 | | - // (|r1 r2| = B/A) would actually exceed double precision over kL steps. |
248 | | - // Threshold: forward is considered stable as long as (B/A)^kL < 1e10, |
249 | | - // i.e. spurious amplitude stays within ~1e10 of the particular solution. |
250 | | - // Near-degenerate kl (|r1|~|r2|~1) fall in the forward branch, where |
251 | | - // zero-seed Miller is known to misconverge (spurious modes never damp). |
252 | | - // Formula: kL * ln(B/A) < ln(1e10) -> B/A < exp(ln(1e10)/kL). |
253 | | - constexpr double kLogGrowthThreshold = 10.0 * 2.30258509299; // ln(1e10) |
254 | | - const double logRatioP = |
255 | | - (am[kl] > ap[kl] && ap[kl] > 0.0) ? std::log(am[kl] / ap[kl]) : 0.0; |
256 | | - const bool useBackwardP = |
257 | | - static_cast<double>(kL) * logRatioP > kLogGrowthThreshold; |
258 | | - const double logRatioM = |
259 | | - (ap[kl] > am[kl] && am[kl] > 0.0) ? std::log(ap[kl] / am[kl]) : 0.0; |
260 | | - const bool useBackwardM = |
261 | | - static_cast<double>(kL) * logRatioM > kLogGrowthThreshold; |
262 | | - |
263 | | - // --- T^+: A = ap, B = am --- |
264 | | - Tlp[0][kl] = T0p; |
265 | | - if (useBackwardP) { |
266 | | - // forward unstable -> use backward recurrence. |
267 | | - double T_hi = 0.0; |
268 | | - double T_cur = 1.0e-300; |
269 | | - for (int l = kLtail; l >= 1; --l) { |
270 | | - const double rhs = sqrtc2[kl] + (l % 2 == 0 ? -1.0 : 1.0) * sqrta2[kl]; |
271 | | - const double T_lo = |
272 | | - (rhs - (2 * l + 1) * d[kl] * T_cur - (l + 1) * ap[kl] * T_hi) / |
273 | | - (l * am[kl]); |
274 | | - T_hi = T_cur; |
275 | | - T_cur = T_lo; |
276 | | - if (l - 1 <= kL) { |
277 | | - Tlp[l - 1][kl] = T_lo; |
278 | | - } |
279 | | - } |
280 | | - const double scaleP = T0p / Tlp[0][kl]; |
281 | | - for (int l = 0; l <= kL; ++l) { |
282 | | - Tlp[l][kl] *= scaleP; |
283 | | - } |
284 | | - } else { |
285 | | - // forward stable. |
286 | | - double T_prev = 0.0; // T^+_{-1} |
287 | | - int sgn = 1; |
288 | | - for (int fl = 0; fl < kL; ++fl) { |
289 | | - sgn = -sgn; |
290 | | - const double rhs = sqrtc2[kl] + sgn * sqrta2[kl]; |
291 | | - const double T_next = |
292 | | - (rhs - (2 * fl + 1) * d[kl] * Tlp[fl][kl] - fl * am[kl] * T_prev) / |
293 | | - (ap[kl] * (fl + 1)); |
294 | | - T_prev = Tlp[fl][kl]; |
295 | | - Tlp[fl + 1][kl] = T_next; |
296 | | - } |
297 | | - } |
298 | | - |
299 | | - // --- T^-: A = am, B = ap --- |
300 | | - Tlm[0][kl] = T0m; |
301 | | - if (useBackwardM) { |
302 | | - // forward unstable -> use backward recurrence. |
303 | | - double T_hi = 0.0; |
304 | | - double T_cur = 1.0e-300; |
305 | | - for (int l = kLtail; l >= 1; --l) { |
306 | | - const double rhs = sqrtc2[kl] + (l % 2 == 0 ? -1.0 : 1.0) * sqrta2[kl]; |
307 | | - const double T_lo = |
308 | | - (rhs - (2 * l + 1) * d[kl] * T_cur - (l + 1) * am[kl] * T_hi) / |
309 | | - (l * ap[kl]); |
310 | | - T_hi = T_cur; |
311 | | - T_cur = T_lo; |
312 | | - if (l - 1 <= kL) { |
313 | | - Tlm[l - 1][kl] = T_lo; |
314 | | - } |
315 | | - } |
316 | | - const double scaleM = T0m / Tlm[0][kl]; |
317 | | - for (int l = 0; l <= kL; ++l) { |
318 | | - Tlm[l][kl] *= scaleM; |
319 | | - } |
320 | | - } else { |
321 | | - // forward stable. |
322 | | - double T_prev = 0.0; // T^-_{-1} |
323 | | - int sgn = 1; |
324 | | - for (int fl = 0; fl < kL; ++fl) { |
325 | | - sgn = -sgn; |
326 | | - const double rhs = sqrtc2[kl] + sgn * sqrta2[kl]; |
327 | | - const double T_next = |
328 | | - (rhs - (2 * fl + 1) * d[kl] * Tlm[fl][kl] - fl * ap[kl] * T_prev) / |
329 | | - (am[kl] * (fl + 1)); |
330 | | - T_prev = Tlm[fl][kl]; |
331 | | - Tlm[fl + 1][kl] = T_next; |
332 | | - } |
333 | | - } |
| 278 | + ComputeTl(ap[kl], am[kl], d[kl], sqrtc2[kl], sqrta2[kl], T0p, kL, kl, Tlp); |
| 279 | + ComputeTl(am[kl], ap[kl], d[kl], sqrtc2[kl], sqrta2[kl], T0m, kL, kl, Tlm); |
334 | 280 | } // kl |
335 | 281 | } // prepareUpdate |
336 | 282 |
|
|
0 commit comments