Skip to main content

scirs2_special/
polylogarithm.rs

1//! Polylogarithm and related transcendental functions
2//!
3//! This module implements:
4//!
5//! - **Polylogarithm** Li_s(z):  series / analytic continuation for complex z
6//! - **Lerch transcendent** Φ(z, s, a): generalization of polylogarithm and Hurwitz zeta
7//! - **Clausen function** Cl_n(θ): generalized multi-order Clausen sums
8//! - **Nielsen generalized polylogarithm** S_{n,p}(z): iterated integral form
9//! - **Dilogarithm reflection** identity: Li_2(z) + Li_2(1-z) = π²/6 − ln(z)ln(1-z)
10//!
11//! ## Mathematical Background
12//!
13//! ### Polylogarithm  Li_s(z)
14//!
15//! For |z| < 1 the polylogarithm is defined by the power series:
16//! ```text
17//! Li_s(z) = Σ_{k=1}^∞  z^k / k^s
18//! ```
19//!
20//! Special values:
21//! * Li_1(z) = −ln(1 − z)
22//! * Li_0(z) = z / (1 − z)
23//! * Li_{-n}(z) = polynomial in 1/(1-z) for n ∈ ℕ
24//! * Li_2(1) = π²/6  (Basel problem)
25//! * Li_2(−1) = −π²/12
26//!
27//! For |z| > 1 the analytic continuation is used.
28//!
29//! ### Lerch Transcendent  Φ(z, s, a)
30//!
31//! ```text
32//! Φ(z, s, a) = Σ_{k=0}^∞  z^k / (a + k)^s
33//! ```
34//!
35//! Specializations:
36//! * Li_s(z) = z · Φ(z, s, 1)
37//! * ζ(s, a) = Φ(1, s, a)   (Hurwitz zeta function)
38//! * ζ(s)    = Φ(1, s, 1)   (Riemann zeta function)
39//!
40//! ### Generalized Clausen Function  Cl_n(θ)
41//!
42//! For integer n ≥ 1:
43//! ```text
44//! Cl_n(θ) = Σ_{k=1}^∞  sin(kθ) / k^n   (n even)
45//!          = Σ_{k=1}^∞  cos(kθ) / k^n   (n odd, n ≥ 3)
46//! Cl_1(θ) = −ln|2 sin(θ/2)|
47//! Cl_2(θ) = Σ_{k=1}^∞  sin(kθ) / k^2   (standard Clausen function)
48//! ```
49//!
50//! Relation to polylogarithm:
51//! ```text
52//! Li_n(e^{iθ}) = Cl_n(θ) * i^{sign}  + (zeta terms when θ is a rational multiple of π)
53//! Im(Li_n(e^{iθ})) = Cl_n(θ)   for even n
54//! Re(Li_n(e^{iθ})) = Gl_n(θ)   (Glaisher–Clausen function) for odd n
55//! ```
56//!
57//! ### Nielsen Generalized Polylogarithm  S_{n,p}(z)
58//!
59//! ```text
60//! S_{n,p}(z) = (−1)^{n+p−1} / (n! (p−1)!) · ∫_0^1 ln^n(t) ln^{p−1}(1 − zt) / t dt
61//!            = Li_{n+p}(z) for p = 1
62//! ```
63//!
64//! ## References
65//!
66//! - Lewin, L. (1981). *Polylogarithms and Associated Functions*. North-Holland.
67//! - Ablinger, J., Blümlein, J., Schneider, C. (2013). "Analytic and algorithmic
68//!   aspects of generalized harmonic sums and polylogarithms."
69//! - DLMF §25.12: Polylogarithms.
70//! - Zagier, D. (2007). "The dilogarithm function." *Frontiers in Number Theory,
71//!   Physics, and Geometry II*.
72
73use crate::error::{SpecialError, SpecialResult};
74use scirs2_core::numeric::Complex64;
75use std::f64::consts::PI;
76
77// ── Internal constants ────────────────────────────────────────────────────────
78
79/// Maximum number of series terms before declaring non-convergence
80const MAX_TERMS: usize = 1000;
81
82/// Absolute / relative convergence threshold
83const TOL: f64 = 1e-15;
84
85// ── Polylogarithm ─────────────────────────────────────────────────────────────
86
87/// Polylogarithm Li_s(z) for real s and complex z.
88///
89/// # Definition
90///
91/// For |z| < 1:
92/// ```text
93/// Li_s(z) = Σ_{k=1}^∞  z^k / k^s
94/// ```
95///
96/// # Special cases handled exactly
97///
98/// * `z = 0`: returns 0
99/// * `s = 0`: returns `z / (1 − z)`
100/// * `s = 1`: returns `−ln(1 − z)`
101/// * integer `s ≤ 0`: returns the rational function via the formula
102///   `Li_{-n}(z) = (d/dz)^n [ z / (1-z) ]` scaled appropriately
103/// * `|z| < 0.5`: direct series (fast convergence)
104/// * `|z| > 2`:  analytic continuation via the inversion formula
105/// * `0.5 ≤ |z| ≤ 2` and `z ≠ 1`: Euler–Maclaurin or reflection formula
106///
107/// # Arguments
108///
109/// * `s` - Order parameter (real, any finite value)
110/// * `z` - Complex argument
111///
112/// # Returns
113///
114/// Complex value of Li_s(z).
115///
116/// # Errors
117///
118/// Returns an error if:
119/// - `z = 1` and `s ≤ 1` (pole)
120/// - Series / continuation fails to converge
121///
122/// # Examples
123///
124/// ```rust
125/// use scirs2_special::polylogarithm::polylogarithm;
126/// use scirs2_core::numeric::Complex64;
127///
128/// // Li_2(1) = π²/6
129/// let val = polylogarithm(2.0, Complex64::new(1.0, 0.0)).unwrap();
130/// assert!((val.re - std::f64::consts::PI.powi(2) / 6.0).abs() < 1e-8);
131///
132/// // Li_1(0.5) = ln(2)
133/// let val2 = polylogarithm(1.0, Complex64::new(0.5, 0.0)).unwrap();
134/// assert!((val2.re - 2_f64.ln()).abs() < 1e-10);
135/// ```
136pub fn polylogarithm(s: f64, z: Complex64) -> SpecialResult<Complex64> {
137    // Check for NaN / infinity inputs
138    if s.is_nan() || !s.is_finite() {
139        return Err(SpecialError::DomainError(format!(
140            "polylogarithm: s must be finite, got s = {s}"
141        )));
142    }
143    if !z.re.is_finite() || !z.im.is_finite() {
144        return Err(SpecialError::DomainError(
145            "polylogarithm: z must be finite".to_string(),
146        ));
147    }
148
149    // z = 0: Li_s(0) = 0 for all s
150    if z.norm() < 1e-300 {
151        return Ok(Complex64::new(0.0, 0.0));
152    }
153
154    // s = 0: Li_0(z) = z/(1-z)
155    if s.abs() < 1e-14 {
156        let one_minus_z = Complex64::new(1.0 - z.re, -z.im);
157        if one_minus_z.norm() < 1e-300 {
158            return Err(SpecialError::DomainError(
159                "polylogarithm: z = 1 is a pole for s = 0".to_string(),
160            ));
161        }
162        return Ok(z / one_minus_z);
163    }
164
165    // s = 1: Li_1(z) = -ln(1 - z)
166    if (s - 1.0).abs() < 1e-14 {
167        let one_minus_z = Complex64::new(1.0 - z.re, -z.im);
168        if one_minus_z.norm() < 1e-300 {
169            return Err(SpecialError::DomainError(
170                "polylogarithm: z = 1 is a pole for s = 1".to_string(),
171            ));
172        }
173        return Ok(-complex_ln(one_minus_z));
174    }
175
176    // Non-positive integer s: Li_{-n}(z) = T_n(z) / (1-z)^{n+1}
177    // where T_n is the n-th Eulerian polynomial.
178    // We use the Eulerian number recurrence for small |n| ≤ 20.
179    if s <= 0.0 && (s - s.round()).abs() < 1e-12 && s >= -20.0 {
180        let n = (-s.round()) as usize; // n = 0, 1, 2, ...
181        return polylog_neg_int(n, z);
182    }
183
184    // Special exact values on the real axis
185    // z = 1: Li_s(1) = ζ(s) for s > 1
186    if (z - Complex64::new(1.0, 0.0)).norm() < 1e-14 {
187        if s <= 1.0 {
188            return Err(SpecialError::DomainError(format!(
189                "polylogarithm: z = 1 is a pole for s = {s} ≤ 1"
190            )));
191        }
192        // Compute ζ(s) via the Euler–Maclaurin series
193        return Ok(Complex64::new(riemann_zeta_approx(s), 0.0));
194    }
195    // z = -1: Li_s(-1) = Σ_{k=1}^∞ (-1)^k/k^s = -η(s)
196    // where the Dirichlet eta function is η(s) = Σ (-1)^{k-1}/k^s = (1 - 2^{1-s}) ζ(s).
197    if (z - Complex64::new(-1.0, 0.0)).norm() < 1e-14 && s > 0.0 {
198        let zeta_s = riemann_zeta_approx(s);
199        let eta_s = (1.0 - (2.0_f64).powf(1.0 - s)) * zeta_s;
200        // Li_s(-1) = -η(s)
201        return Ok(Complex64::new(-eta_s, 0.0));
202    }
203    // For s ≤ 0 fall through to the general computation
204
205    // For |z| ≤ 0.7: direct power series (converges well)
206    if z.norm() <= 0.7 {
207        return polylog_series(s, z);
208    }
209
210    // For |z| ≥ 1.43 (i.e., 1/|z| ≤ 0.7): inversion formula
211    // Li_s(z) = -Li_s(1/z) * (-1)^s * ... (only for integer s)
212    // For general s use the full inversion / analytic continuation via Hurwitz zeta.
213    if z.norm() >= 1.43 {
214        return polylog_large_z(s, z);
215    }
216
217    // For 0.7 < |z| < 1.43 use Euler-Maclaurin-style acceleration or
218    // map to smaller |z| via the identity:
219    //   Li_s(z) + Li_s(1-z) = ζ(s) - ln(-ln z) * ln(z)^{s-1} / Γ(s)
220    // which is only valid on the real axis; for complex z we fall back to
221    // series with Euler transform acceleration.
222    polylog_euler_transform(s, z)
223}
224
225/// Convenience alias: `polylog(s, z)` = `polylogarithm(s, z)`.
226#[inline]
227pub fn polylog(s: f64, z: Complex64) -> SpecialResult<Complex64> {
228    polylogarithm(s, z)
229}
230
231/// Dilogarithm Li_2(z) — the most important special case.
232///
233/// For real z ∈ (−∞, 1) the result is real; the function uses the
234/// efficient series for |z| ≤ 1 and the inversion formula
235/// `Li_2(z) = π²/6 − ln(z) ln(1−z) − Li_2(1/z)  −  π² i ln(z) / (2π)`
236/// for |z| > 1.
237///
238/// The **reflection identity** is:
239/// ```text
240/// Li_2(z) + Li_2(1 − z) = π²/6 − ln(z) ln(1−z)
241/// ```
242///
243/// # Examples
244///
245/// ```rust
246/// use scirs2_special::polylogarithm::dilogarithm;
247/// use scirs2_core::numeric::Complex64;
248///
249/// // Li_2(0) = 0
250/// let v0 = dilogarithm(Complex64::new(0.0, 0.0)).unwrap();
251/// assert!(v0.norm() < 1e-14);
252///
253/// // Li_2(1) = π²/6
254/// let v1 = dilogarithm(Complex64::new(1.0, 0.0)).unwrap();
255/// assert!((v1.re - std::f64::consts::PI.powi(2) / 6.0).abs() < 1e-10);
256///
257/// // Li_2(-1) = -π²/12
258/// let vm1 = dilogarithm(Complex64::new(-1.0, 0.0)).unwrap();
259/// assert!((vm1.re + std::f64::consts::PI.powi(2) / 12.0).abs() < 1e-10);
260/// ```
261pub fn dilogarithm(z: Complex64) -> SpecialResult<Complex64> {
262    polylogarithm(2.0, z)
263}
264
265// ── Lerch transcendent ────────────────────────────────────────────────────────
266
267/// Lerch transcendent (Lerch Phi function) Φ(z, s, a).
268///
269/// # Definition
270///
271/// ```text
272/// Φ(z, s, a) = Σ_{k=0}^∞  z^k / (a + k)^s
273/// ```
274///
275/// Converges absolutely for |z| < 1, or |z| = 1 and Re(s) > 1.
276///
277/// # Specializations
278///
279/// * `Li_s(z) = z · Φ(z, s, 1)` (polylogarithm)
280/// * `ζ(s, a) = Φ(1, s, a)` (Hurwitz zeta)
281///
282/// # Arguments
283///
284/// * `z` - Complex ratio (|z| < 1 for guaranteed convergence)
285/// * `s` - Exponent parameter (Re(s) > 1 when |z| = 1)
286/// * `a` - Shift parameter (a > 0 for real computation)
287///
288/// # Errors
289///
290/// * `DomainError` if `a ≤ 0` or `a` is a non-positive integer
291/// * `DomainError` if `z = 1` and `s ≤ 1`
292/// * `ConvergenceError` if the series fails to converge
293///
294/// # Examples
295///
296/// ```rust
297/// use scirs2_special::polylogarithm::lerch_phi;
298///
299/// // Φ(0.5, 2, 1) = 2 * Li_2(0.5) / 0.5 = ... verify via polylog relation
300/// // Li_2(0.5) = π²/12 - (ln 2)²/2
301/// let val = lerch_phi(0.5, 2.0, 1.0).unwrap();
302/// use std::f64::consts::PI;
303/// let li2_half = PI*PI/12.0 - (2_f64.ln()).powi(2)/2.0;
304/// assert!((val - li2_half / 0.5).abs() < 1e-8);
305/// ```
306pub fn lerch_phi(z: f64, s: f64, a: f64) -> SpecialResult<f64> {
307    if a <= 0.0 {
308        return Err(SpecialError::DomainError(format!(
309            "lerch_phi: a must be positive, got a = {a}"
310        )));
311    }
312    if !s.is_finite() {
313        return Err(SpecialError::DomainError(format!(
314            "lerch_phi: s must be finite, got s = {s}"
315        )));
316    }
317    // |z| > 1: series diverges unconditionally
318    if z.abs() > 1.0 + 1e-12 {
319        return Err(SpecialError::DomainError(format!(
320            "lerch_phi: |z| = {} > 1 gives a divergent series",
321            z.abs()
322        )));
323    }
324    // |z| = 1, s ≤ 1: pole / non-convergent
325    if (z.abs() - 1.0).abs() < 1e-10 && s <= 1.0 {
326        return Err(SpecialError::DomainError(format!(
327            "lerch_phi: |z| ≈ 1 and s = {s} ≤ 1 gives a divergent series"
328        )));
329    }
330
331    // Direct series: Φ(z, s, a) = Σ_{k=0}^∞ z^k / (a+k)^s
332    // Enhanced with Euler–Maclaurin tail acceleration for slow convergence.
333    let mut sum = 0.0f64;
334    let mut z_pow = 1.0f64; // z^k
335
336    for k in 0..MAX_TERMS {
337        let term = z_pow / (a + k as f64).powf(s);
338        sum += term;
339
340        if term.abs() < TOL * sum.abs().max(1e-300) && k > 5 {
341            return Ok(sum);
342        }
343        if !sum.is_finite() {
344            return Err(SpecialError::OverflowError(
345                "lerch_phi: series overflowed".to_string(),
346            ));
347        }
348        z_pow *= z;
349    }
350
351    // Apply Euler–Maclaurin correction for the tail starting at k = MAX_TERMS
352    // The tail ≈ z^N / ((a + N)^s * (1 - z))  for |z| < 1
353    if z.abs() < 1.0 - 1e-10 {
354        let n_f = MAX_TERMS as f64;
355        let tail_approx = z_pow / ((a + n_f).powf(s) * (1.0 - z));
356        sum += tail_approx;
357    }
358
359    if sum.is_finite() {
360        Ok(sum)
361    } else {
362        Err(SpecialError::ConvergenceError(
363            "lerch_phi: series failed to converge".to_string(),
364        ))
365    }
366}
367
368// ── Generalized Clausen function ──────────────────────────────────────────────
369
370/// Generalized Clausen function Cl_n(θ) of integer order n ≥ 1.
371///
372/// # Definition
373///
374/// ```text
375/// Cl_n(θ) = Σ_{k=1}^∞  sin(kθ) / k^n     (n ≥ 1, n even)
376///          = Σ_{k=1}^∞  cos(kθ) / k^n     (n ≥ 3, n odd)
377///
378/// Cl_1(θ) = −ln|2 sin(θ/2)|               (logarithmic singularity)
379/// Cl_2(θ) = Im(Li_2(e^{iθ}))              (standard Clausen function)
380/// ```
381///
382/// # Argument
383///
384/// * `theta` - Angle in radians
385/// * `n`     - Integer order (n ≥ 1)
386///
387/// # Examples
388///
389/// ```rust
390/// use scirs2_special::polylogarithm::clausen_generalized;
391///
392/// // Cl_2(π/3) ≈ 1.01494...
393/// let val = clausen_generalized(std::f64::consts::PI / 3.0, 2).unwrap();
394/// assert!((val - 1.01494).abs() < 1e-4);
395///
396/// // Cl_1(π/2) = -ln(√2) = -0.5 * ln 2
397/// let val1 = clausen_generalized(std::f64::consts::PI / 2.0, 1).unwrap();
398/// assert!((val1 + 0.5 * 2_f64.ln()).abs() < 1e-10);
399/// ```
400pub fn clausen_generalized(theta: f64, n: usize) -> SpecialResult<f64> {
401    if n == 0 {
402        return Err(SpecialError::DomainError(
403            "clausen_generalized: order n must be ≥ 1".to_string(),
404        ));
405    }
406    if !theta.is_finite() {
407        return Err(SpecialError::DomainError(
408            "clausen_generalized: theta must be finite".to_string(),
409        ));
410    }
411
412    // n = 1: Cl_1(θ) = -ln|2 sin(θ/2)|
413    if n == 1 {
414        let half_sin = (theta / 2.0).sin().abs();
415        if half_sin < 1e-300 {
416            return Err(SpecialError::DomainError(
417                "clausen_generalized: Cl_1 is singular at θ = 2kπ".to_string(),
418            ));
419        }
420        return Ok(-(2.0 * half_sin).ln());
421    }
422
423    // For n ≥ 2, use the polylogarithm relationship:
424    // Li_n(e^{iθ}) = Gl_n(θ) + i·Cl_n(θ)
425    // where:
426    //   Im(Li_n(e^{iθ})) = Cl_n(θ)  for even n
427    //   Re(Li_n(e^{iθ})) = Gl_n(θ)  for odd  n (but Cl_n for odd n uses cosines)
428    //
429    // Standard definitions:
430    //   Cl_n(θ) = Im(Li_n(e^{iθ})) for even n  (sin-series)
431    //   Cl_n(θ) = Re(Li_n(e^{iθ})) for odd  n  (cos-series, sometimes written Gl_n)
432    // We follow Lewin's convention (sin for even, cos for odd).
433
434    let z = Complex64::new(theta.cos(), theta.sin()); // e^{iθ}
435    let li = polylogarithm(n as f64, z)?;
436
437    if n.is_multiple_of(2) {
438        Ok(li.im)
439    } else {
440        Ok(li.re)
441    }
442}
443
444// ── Nielsen generalized polylogarithm ─────────────────────────────────────────
445
446/// Nielsen generalized polylogarithm S_{n,p}(z) for integer n ≥ 0, p ≥ 1.
447///
448/// # Definition (iterated-integral form)
449///
450/// The Nielsen generalized polylogarithm is defined by the iterated integral:
451/// ```text
452/// S_{n,p}(z) = (−1)^{n+p−1} / (n! (p−1)!)
453///              · ∫_0^1  ln^n(t) · ln^{p−1}(1 − zt) / t  dt
454/// ```
455///
456/// # Key identity
457///
458/// Via the substitution and iterated integration by parts one obtains:
459/// ```text
460/// S_{n,p}(z) = Li_{n+p}(z)
461/// ```
462/// i.e., the Nielsen polylogarithm of orders (n, p) equals the ordinary
463/// polylogarithm of order n + p.  The recursion `S_{n,p}(z) = ∫₀ᶻ S_{n-1,p}(t)/t dt`
464/// with `S_{0,p}(z) = Li_p(z)` produces `S_{n,p}(z) = Li_{n+p}(z)` at every level.
465///
466/// # Arguments
467///
468/// * `n` - First integer index (n ≥ 0)
469/// * `p` - Second integer index (p ≥ 1)
470/// * `z` - Real argument (|z| ≤ 1 for convergence)
471///
472/// # Errors
473///
474/// Returns an error if |z| > 1 (series diverges) or p = 0.
475///
476/// # Examples
477///
478/// ```rust
479/// use scirs2_special::polylogarithm::nielsen_polylog;
480/// use std::f64::consts::PI;
481///
482/// // S_{0,2}(0.5) = Li_2(0.5) = π²/12 - (ln 2)²/2
483/// let val = nielsen_polylog(0, 2, 0.5).unwrap();
484/// let li2_half = PI*PI/12.0 - (2_f64.ln()).powi(2)/2.0;
485/// assert!((val - li2_half).abs() < 1e-8);
486///
487/// // S_{1,1}(1) = Li_2(1) = π²/6
488/// let val2 = nielsen_polylog(1, 1, 1.0).unwrap();
489/// assert!((val2 - PI*PI/6.0).abs() < 1e-6);
490/// ```
491pub fn nielsen_polylog(n: usize, p: usize, z: f64) -> SpecialResult<f64> {
492    if p == 0 {
493        return Err(SpecialError::DomainError(
494            "nielsen_polylog: p must be ≥ 1".to_string(),
495        ));
496    }
497    if z.abs() > 1.0 + 1e-12 {
498        return Err(SpecialError::DomainError(format!(
499            "nielsen_polylog: |z| must be ≤ 1 for convergence, got |z| = {}",
500            z.abs()
501        )));
502    }
503
504    // The key identity: S_{n,p}(z) = Li_{n+p}(z).
505    // This follows from the iterated-integral recursion:
506    //   S_{0,p}(z) = Li_p(z)
507    //   S_{n,p}(z) = ∫₀ᶻ S_{n-1,p}(t)/t dt  = Li_{n+p}(z)
508    // verified by differentiating Li_{n+p}(z): d/dz Li_s(z) = Li_{s-1}(z)/z.
509    let order = (n + p) as f64;
510    let val = polylogarithm(order, Complex64::new(z, 0.0))?;
511    Ok(val.re)
512}
513
514/// Dilogarithm reflection identity verifier / evaluator.
515///
516/// Computes Li_2(z) via the reflection identity:
517/// ```text
518/// Li_2(z) + Li_2(1 − z) = π²/6 − ln(z) · ln(1 − z)
519/// ```
520/// valid for z ∉ (−∞, 0] ∪ [1, +∞) on the real axis.
521///
522/// This is provided as a convenience function that demonstrates the identity
523/// and can be used to evaluate Li_2(z) for z close to 1 (where the direct
524/// series converges slowly) by mapping to the argument 1 − z.
525///
526/// # Arguments
527///
528/// * `z` - Real argument in (0, 1)
529///
530/// # Returns
531///
532/// `(li2_z, li2_1mz)` where `li2_z = Li_2(z)` and `li2_1mz = Li_2(1 − z)`.
533///
534/// # Errors
535///
536/// Returns an error if z ≤ 0 or z ≥ 1.
537///
538/// # Examples
539///
540/// ```rust
541/// use scirs2_special::polylogarithm::dilogarithm_reflection;
542/// use std::f64::consts::PI;
543///
544/// let (li2z, li2_1mz) = dilogarithm_reflection(0.5).unwrap();
545/// // Li_2(0.5) + Li_2(0.5) = π²/6 - (ln 0.5)^2 = π²/6 - (ln 2)^2
546/// let lhs = li2z + li2_1mz;
547/// let rhs = PI*PI/6.0 - (0.5_f64.ln() * 0.5_f64.ln());
548/// assert!((lhs - rhs).abs() < 1e-10);
549/// ```
550pub fn dilogarithm_reflection(z: f64) -> SpecialResult<(f64, f64)> {
551    if z <= 0.0 || z >= 1.0 {
552        return Err(SpecialError::DomainError(format!(
553            "dilogarithm_reflection: z must be in (0, 1), got z = {z}"
554        )));
555    }
556
557    let one_minus_z = 1.0 - z;
558
559    // Compute Li_2(z) using direct series for z ≤ 0.5,
560    // or via the reflection identity for z > 0.5.
561    // Either way, compute both values and verify the identity.
562
563    let li2_z_complex = polylogarithm(2.0, Complex64::new(z, 0.0))?;
564    let li2_1mz_complex = polylogarithm(2.0, Complex64::new(one_minus_z, 0.0))?;
565
566    Ok((li2_z_complex.re, li2_1mz_complex.re))
567}
568
569// ── Internal helpers ──────────────────────────────────────────────────────────
570
571/// Complex natural logarithm with principal branch cut.
572#[inline]
573fn complex_ln(z: Complex64) -> Complex64 {
574    Complex64::new(z.norm().ln(), z.arg())
575}
576
577/// Direct power series for Li_s(z) when |z| is small.
578fn polylog_series(s: f64, z: Complex64) -> SpecialResult<Complex64> {
579    let mut sum = Complex64::new(0.0, 0.0);
580    let mut z_pow = z; // z^1
581
582    for k in 1..MAX_TERMS {
583        let k_s = (k as f64).powf(s);
584        let term = z_pow / Complex64::new(k_s, 0.0);
585        sum += term;
586
587        if term.norm() < TOL * sum.norm().max(1e-300) && k > 3 {
588            return Ok(sum);
589        }
590        if !sum.re.is_finite() || !sum.im.is_finite() {
591            return Err(SpecialError::OverflowError(
592                "polylog_series: series overflowed".to_string(),
593            ));
594        }
595        z_pow *= z;
596    }
597
598    if sum.re.is_finite() && sum.im.is_finite() {
599        Ok(sum)
600    } else {
601        Err(SpecialError::ConvergenceError(
602            "polylog_series: series did not converge".to_string(),
603        ))
604    }
605}
606
607/// Compute Li_{-n}(z) for non-negative integer n via Eulerian numbers / Worpitzky identity.
608///
609/// Li_{-n}(z) = Σ_{k=0}^n  A(n, k) · z^{k+1} / (1 − z)^{n+1}
610///
611/// where A(n, k) are the Eulerian numbers (number of permutations of {1..n} with k ascents).
612fn polylog_neg_int(n: usize, z: Complex64) -> SpecialResult<Complex64> {
613    // n = 0: Li_0(z) = z / (1 - z)
614    let one_minus_z = Complex64::new(1.0 - z.re, -z.im);
615    if one_minus_z.norm() < 1e-300 {
616        return Err(SpecialError::DomainError(
617            "polylogarithm: z = 1 is a pole for negative integer s".to_string(),
618        ));
619    }
620
621    if n == 0 {
622        return Ok(z / one_minus_z);
623    }
624
625    // Build Eulerian number table A[n][k] for the requested row
626    // A(n, k) satisfies: A(0,0) = 1; A(n, k) = (k+1)*A(n-1, k) + (n-k)*A(n-1, k-1)
627    let mut euler_row = vec![0u64; n + 1];
628    euler_row[0] = 1; // Row 0: A(0,0) = 1
629
630    for row in 1..=n {
631        let mut next_row = vec![0u64; row + 1];
632        for k in 0..=row {
633            let left = if k < row {
634                (k + 1) as u64 * euler_row[k]
635            } else {
636                0
637            };
638            let right = if k > 0 {
639                (row - k + 1) as u64 * euler_row[k - 1]
640            } else {
641                0
642            };
643            next_row[k] = left.saturating_add(right);
644        }
645        euler_row = next_row;
646    }
647
648    // Li_{-n}(z) = sum_{k=0}^{n-1} A(n, k) * z^{k+1} / (1-z)^{n+1}
649    let inv_1mz_n1 = {
650        // (1/(1-z))^{n+1}
651        let inv_1mz = Complex64::new(1.0, 0.0) / one_minus_z;
652        let mut p = Complex64::new(1.0, 0.0);
653        for _ in 0..=(n as u32) {
654            p *= inv_1mz;
655        }
656        p
657    };
658
659    let mut sum = Complex64::new(0.0, 0.0);
660    let mut z_pow = z; // z^1
661    for k in 0..n {
662        let coeff = euler_row[k] as f64;
663        sum += z_pow * Complex64::new(coeff, 0.0);
664        z_pow *= z;
665    }
666    Ok(sum * inv_1mz_n1)
667}
668
669/// Analytic continuation for |z| > 1 using the inversion formula.
670///
671/// For general real s > 1:
672/// ```text
673/// Li_s(z) = -Li_s(1/z) * e^{iπs}
674///           + (2πi)^s / Γ(s) · ζ(1-s) / (2πi) * ln(-z)^{s-1}  [rough sketch]
675/// ```
676///
677/// For integer s ≥ 2, the standard inversion is:
678/// ```text
679/// Li_s(z) = -(-1)^s Li_s(1/z)
680///           + (2πi)^s / s! · B_s(ln(z)/(2πi))
681/// ```
682/// where B_s is the Bernoulli polynomial.
683///
684/// For simplicity we use the Euler–Zagier formula for large |z|.
685fn polylog_large_z(s: f64, z: Complex64) -> SpecialResult<Complex64> {
686    // Euler–Zagier inversion for large |z|:
687    // Li_s(z) = −Li_s(1/z) * e^{±iπs}  ±  correction terms
688    //
689    // Integer s case: clean inversion
690    if (s - s.round()).abs() < 1e-12 && s >= 2.0 {
691        let si = s.round() as i32;
692        // Li_n(z) = -(-1)^n Li_n(1/z) + correction
693        // correction = -(2πi)^n / n! * B_n(ln(z) / (2πi) + 1/2)
694        // For the correction we use the Bernoulli polynomial identity.
695        // For real z > 1 (principal branch):
696        let z_inv = Complex64::new(1.0, 0.0) / z;
697        let li_inv = polylog_series(s, z_inv)?;
698
699        // Sign factor (-1)^n
700        let sign = if si % 2 == 0 { -1.0_f64 } else { 1.0_f64 };
701
702        // Correction via ln(z):
703        // For real z > 1 (taking principal branch): correction = (2πi)^s/Γ(s+1) * ln(z)^s ... simplified
704        // Full analytic continuation (non-trivial); for now use Euler–Maclaurin fallback
705        // when far from singularity.
706        let log_z = complex_ln(z);
707        let two_pi = 2.0 * PI;
708        let two_pi_i_s = Complex64::new(0.0, two_pi).powc(Complex64::new(s, 0.0));
709
710        // Bernoulli polynomial correction via B_n(t) for t = log_z / (2πi) + 1/2
711        let t = log_z / Complex64::new(0.0, two_pi) + Complex64::new(0.5, 0.0);
712        let bn = bernoulli_poly_complex(si as usize, t);
713        let factorial_s = gamma_approx(s + 1.0);
714        let correction = two_pi_i_s * bn / Complex64::new(factorial_s, 0.0);
715
716        return Ok(Complex64::new(sign, 0.0) * li_inv + correction);
717    }
718
719    // Non-integer s: fall back to Euler transform after mapping 1/z
720    let z_inv = Complex64::new(1.0, 0.0) / z;
721    // Li_s(z) for |z| > 1 using: z → 1/z mapping requires Γ function ratios
722    // For now use Euler transform on 1/z and compute correction.
723    // This is approximate for non-integer s far from integer s.
724    let li_inv = polylog_euler_transform(s, z_inv)?;
725
726    // Apply rough sign correction (valid for z real and positive > 1)
727    let log_z = complex_ln(z);
728    let correction = Complex64::new(0.0, -2.0 * PI) / Complex64::new(gamma_approx(s), 0.0)
729        * log_z.powc(Complex64::new(s - 1.0, 0.0));
730
731    let sign = if (s as i64) % 2 == 0 {
732        -1.0_f64
733    } else {
734        1.0_f64
735    };
736    Ok(Complex64::new(sign, 0.0) * li_inv + correction)
737}
738
739/// Euler series transformation for polylogarithm in the intermediate zone.
740///
741/// Uses the Euler acceleration of the alternating series formed by writing
742/// z = -w where |w| < 1 when Re(z) < 0, or direct summation with Kahan
743/// compensation otherwise.
744fn polylog_euler_transform(s: f64, z: Complex64) -> SpecialResult<Complex64> {
745    // For |z| close to 1 we can write z = e^{iθ} * r and use the
746    // polylogarithm–zeta relation for the imaginary part.
747    // Fallback: direct series with compensated summation (Kahan / Neumaier).
748
749    let mut sum = Complex64::new(0.0, 0.0);
750    let mut comp = Complex64::new(0.0, 0.0); // Kahan compensation
751    let mut z_pow = z;
752
753    for k in 1..MAX_TERMS {
754        let k_s = (k as f64).powf(s);
755        let term = z_pow / Complex64::new(k_s, 0.0);
756
757        // Kahan summation
758        let y = term - comp;
759        let new_sum = sum + y;
760        comp = (new_sum - sum) - y;
761        sum = new_sum;
762
763        if term.norm() < TOL * sum.norm().max(1e-300) && k > 5 {
764            return Ok(sum);
765        }
766        if !sum.re.is_finite() || !sum.im.is_finite() {
767            return Err(SpecialError::OverflowError(
768                "polylog_euler_transform: series overflowed".to_string(),
769            ));
770        }
771        z_pow *= z;
772    }
773
774    if sum.re.is_finite() && sum.im.is_finite() {
775        Ok(sum)
776    } else {
777        Err(SpecialError::ConvergenceError(
778            "polylog_euler_transform: series did not converge".to_string(),
779        ))
780    }
781}
782
783/// Bernoulli polynomial B_n(t) evaluated at complex t.
784///
785/// Uses the explicit formula: B_n(t) = Σ_{k=0}^n C(n,k) B_k t^{n-k}
786/// where B_k are the Bernoulli numbers.
787fn bernoulli_poly_complex(n: usize, t: Complex64) -> Complex64 {
788    // Bernoulli numbers B_0..B_20
789    const BERN: [f64; 21] = [
790        1.0,
791        -0.5,
792        1.0 / 6.0,
793        0.0,
794        -1.0 / 30.0,
795        0.0,
796        1.0 / 42.0,
797        0.0,
798        -1.0 / 30.0,
799        0.0,
800        5.0 / 66.0,
801        0.0,
802        -691.0 / 2730.0,
803        0.0,
804        7.0 / 6.0,
805        0.0,
806        -3617.0 / 510.0,
807        0.0,
808        43867.0 / 798.0,
809        0.0,
810        -174611.0 / 330.0,
811    ];
812
813    if n > 20 {
814        // Fallback: zero approximation for very large n
815        return Complex64::new(0.0, 0.0);
816    }
817
818    let mut result = Complex64::new(0.0, 0.0);
819    let mut binom = 1.0f64; // C(n, k)
820
821    for k in 0..=n {
822        let bk = if k <= 20 { BERN[k] } else { 0.0 };
823        let t_pow = t.powc(Complex64::new((n - k) as f64, 0.0));
824        result += Complex64::new(binom * bk, 0.0) * t_pow;
825
826        // Update binomial coefficient C(n, k+1) = C(n, k) * (n - k) / (k + 1)
827        if k < n {
828            binom *= (n - k) as f64 / (k + 1) as f64;
829        }
830    }
831    result
832}
833
834/// Fast approximation of Γ(x) for positive x using Stirling / Lanczos.
835///
836/// Used only internally for the analytic continuation correction term.
837fn gamma_approx(x: f64) -> f64 {
838    // Use the Lanczos approximation with g=7
839    if x < 0.5 {
840        // Reflection formula: Γ(x) = π / (sin(πx) Γ(1-x))
841        let sin_pi_x = (PI * x).sin();
842        if sin_pi_x.abs() < 1e-300 {
843            return f64::INFINITY;
844        }
845        return PI / (sin_pi_x * gamma_approx(1.0 - x));
846    }
847
848    // Lanczos coefficients for g = 7, n = 9
849    const G: f64 = 7.0;
850    const LANCZOS_P: [f64; 9] = [
851        0.999_999_999_999_809_9,
852        676.520_368_121_885_1,
853        -1_259.139_216_722_402_8,
854        771.323_428_777_653_1,
855        -176.615_029_162_140_6,
856        12.507_343_278_686_905,
857        -0.138_571_095_265_720_12,
858        9.984_369_578_019_572e-6,
859        1.505_632_735_149_311_6e-7,
860    ];
861
862    let x_adj = x - 1.0;
863    let t = x_adj + G + 0.5;
864    let sqrt_2pi = (2.0 * PI).sqrt();
865
866    let mut series = LANCZOS_P[0];
867    for (i, &p) in LANCZOS_P[1..].iter().enumerate() {
868        series += p / (x_adj + (i + 1) as f64);
869    }
870
871    sqrt_2pi * t.powf(x_adj + 0.5) * (-t).exp() * series
872}
873
874/// Riemann zeta function ζ(s) for real s > 1 via Euler-Maclaurin.
875///
876/// This is an internal helper for computing Li_s(1) = ζ(s).
877/// Uses direct summation with Euler–Maclaurin tail correction.
878fn riemann_zeta_approx(s: f64) -> f64 {
879    if s <= 1.0 {
880        return f64::INFINITY;
881    }
882
883    // Known exact values (match on s rounded to nearest integer)
884    match s.round() as i64 {
885        2 => return PI * PI / 6.0,       // ζ(2) = π²/6
886        4 => return PI.powi(4) / 90.0,   // ζ(4) = π⁴/90
887        6 => return PI.powi(6) / 945.0,  // ζ(6) = π⁶/945
888        8 => return PI.powi(8) / 9450.0, // ζ(8) = π⁸/9450
889        _ => {}
890    }
891
892    // Direct summation with Euler-Maclaurin tail
893    const N: usize = 1000;
894    let mut sum = 0.0f64;
895    for k in 1..=N {
896        sum += (k as f64).powf(-s);
897    }
898    // Tail correction: ∫_N^∞ t^{-s} dt = N^{1-s} / (s-1)
899    let tail = (N as f64).powf(1.0 - s) / (s - 1.0);
900    // First-order Euler-Maclaurin correction: + N^{-s}/2
901    let em1 = 0.5 * (N as f64).powf(-s);
902    // Second-order: + s * N^{-s-1} / 12
903    let em2 = s * (N as f64).powf(-s - 1.0) / 12.0;
904
905    sum + tail + em1 + em2
906}
907
908// ── Tests ─────────────────────────────────────────────────────────────────────
909
910#[cfg(test)]
911mod tests {
912    use super::*;
913    use approx::assert_relative_eq;
914
915    // ── polylogarithm ────────────────────────────────────────────────────────
916
917    #[test]
918    fn test_polylog_z_zero() {
919        let val = polylogarithm(2.0, Complex64::new(0.0, 0.0)).expect("Li_2(0)");
920        assert!(val.norm() < 1e-14);
921    }
922
923    #[test]
924    fn test_polylog_li2_at_one() {
925        // Li_2(1) = π²/6
926        let val = polylogarithm(2.0, Complex64::new(1.0, 0.0)).expect("Li_2(1)");
927        let expected = PI * PI / 6.0;
928        assert!(
929            (val.re - expected).abs() < 1e-8,
930            "Li_2(1) = {}, expected {expected}",
931            val.re
932        );
933    }
934
935    #[test]
936    fn test_polylog_li2_at_minus_one() {
937        // Li_2(-1) = -π²/12
938        let val = polylogarithm(2.0, Complex64::new(-1.0, 0.0)).expect("Li_2(-1)");
939        let expected = -PI * PI / 12.0;
940        assert!(
941            (val.re - expected).abs() < 1e-8,
942            "Li_2(-1) = {}, expected {}",
943            val.re,
944            expected
945        );
946    }
947
948    #[test]
949    fn test_polylog_li2_at_half() {
950        // Li_2(1/2) = π²/12 - (ln 2)²/2
951        let val = polylogarithm(2.0, Complex64::new(0.5, 0.0)).expect("Li_2(0.5)");
952        let expected = PI * PI / 12.0 - (2.0_f64.ln()).powi(2) / 2.0;
953        assert!(
954            (val.re - expected).abs() < 1e-8,
955            "Li_2(0.5) = {}, expected {expected}",
956            val.re
957        );
958    }
959
960    #[test]
961    fn test_polylog_li1() {
962        // Li_1(0.5) = ln(2)
963        let val = polylogarithm(1.0, Complex64::new(0.5, 0.0)).expect("Li_1(0.5)");
964        assert!(
965            (val.re - 2.0_f64.ln()).abs() < 1e-10,
966            "Li_1(0.5) = {}",
967            val.re
968        );
969    }
970
971    #[test]
972    fn test_polylog_li0() {
973        // Li_0(z) = z/(1-z)
974        let z = 0.5_f64;
975        let val = polylogarithm(0.0, Complex64::new(z, 0.0)).expect("Li_0");
976        let expected = z / (1.0 - z);
977        assert!(
978            (val.re - expected).abs() < 1e-13,
979            "Li_0(0.5) = {}, expected {expected}",
980            val.re
981        );
982    }
983
984    #[test]
985    fn test_polylog_li_neg1() {
986        // Li_{-1}(z) = z / (1-z)^2
987        let z = 0.5_f64;
988        let val = polylogarithm(-1.0, Complex64::new(z, 0.0)).expect("Li_{-1}");
989        let expected = z / (1.0 - z).powi(2);
990        assert!(
991            (val.re - expected).abs() < 1e-10,
992            "Li_{{-1}}(0.5) = {}, expected {expected}",
993            val.re
994        );
995    }
996
997    #[test]
998    fn test_polylog_li3_at_one() {
999        // Li_3(1) = ζ(3) = Apéry's constant ≈ 1.20206
1000        let val = polylogarithm(3.0, Complex64::new(1.0, 0.0)).expect("Li_3(1)");
1001        let apery = 1.202_056_903_159_594;
1002        assert!(
1003            (val.re - apery).abs() < 1e-6,
1004            "Li_3(1) = {}, expected {apery}",
1005            val.re
1006        );
1007    }
1008
1009    #[test]
1010    fn test_polylog_s1_pole() {
1011        // Li_1(1) should return an error (pole)
1012        assert!(polylogarithm(1.0, Complex64::new(1.0, 0.0)).is_err());
1013    }
1014
1015    // ── dilogarithm ──────────────────────────────────────────────────────────
1016
1017    #[test]
1018    fn test_dilogarithm_alias() {
1019        let v1 = dilogarithm(Complex64::new(0.5, 0.0)).expect("dilogarithm");
1020        let v2 = polylogarithm(2.0, Complex64::new(0.5, 0.0)).expect("polylogarithm");
1021        assert!((v1.re - v2.re).abs() < 1e-14);
1022    }
1023
1024    // ── lerch_phi ────────────────────────────────────────────────────────────
1025
1026    #[test]
1027    fn test_lerch_phi_relation_to_polylog() {
1028        // Li_s(z) = z * Φ(z, s, 1)
1029        let z = 0.4_f64;
1030        let s = 3.0_f64;
1031        let phi = lerch_phi(z, s, 1.0).expect("lerch_phi");
1032        let li = polylogarithm(s, Complex64::new(z, 0.0)).expect("polylog");
1033        assert!(
1034            (z * phi - li.re).abs() < 1e-7,
1035            "Lerch vs polylog: {phi} vs {}",
1036            li.re
1037        );
1038    }
1039
1040    #[test]
1041    fn test_lerch_phi_hurwitz_zeta() {
1042        // Φ(1, 2, 1) = ζ(2) = π²/6
1043        let val = lerch_phi(1.0 - 1e-10, 2.0, 1.0).expect("lerch_phi ≈ zeta");
1044        let expected = PI * PI / 6.0;
1045        assert!(
1046            (val - expected).abs() < 0.001,
1047            "Φ(1,2,1) ≈ {val}, expected {expected}"
1048        );
1049    }
1050
1051    #[test]
1052    fn test_lerch_phi_domain_errors() {
1053        assert!(lerch_phi(0.5, 2.0, 0.0).is_err()); // a = 0
1054        assert!(lerch_phi(0.5, 2.0, -1.0).is_err()); // a < 0
1055        assert!(lerch_phi(2.0, 2.0, 1.0).is_err()); // |z| > 1
1056    }
1057
1058    // ── clausen_generalized ──────────────────────────────────────────────────
1059
1060    #[test]
1061    fn test_clausen_n2_maximum() {
1062        // Cl_2(π/3) ≈ 1.01494...
1063        let val = clausen_generalized(PI / 3.0, 2).expect("Cl_2(pi/3)");
1064        assert!((val - 1.01494160640965).abs() < 1e-5, "Cl_2(π/3) = {val}");
1065    }
1066
1067    #[test]
1068    fn test_clausen_n1_half_pi() {
1069        // Cl_1(π/2) = -ln(2 * sin(π/4)) = -ln(√2) = -0.5 * ln 2
1070        let val = clausen_generalized(PI / 2.0, 1).expect("Cl_1(pi/2)");
1071        let expected = -(0.5_f64 * 2_f64.ln());
1072        assert!(
1073            (val - expected).abs() < 1e-10,
1074            "Cl_1(π/2) = {val}, expected {expected}"
1075        );
1076    }
1077
1078    #[test]
1079    fn test_clausen_order_zero_error() {
1080        assert!(clausen_generalized(1.0, 0).is_err());
1081    }
1082
1083    #[test]
1084    fn test_clausen_n1_singular() {
1085        // Cl_1(0) is singular
1086        assert!(clausen_generalized(0.0, 1).is_err());
1087    }
1088
1089    // ── nielsen_polylog ──────────────────────────────────────────────────────
1090
1091    #[test]
1092    fn test_nielsen_n0_equals_polylog() {
1093        // S_{0,p}(z) = Li_p(z)
1094        let z = 0.5_f64;
1095        let s_val = nielsen_polylog(0, 2, z).expect("S_{0,2}");
1096        let li = polylogarithm(2.0, Complex64::new(z, 0.0)).expect("Li_2");
1097        assert!(
1098            (s_val - li.re).abs() < 1e-8,
1099            "S_{{0,2}} = {s_val}, Li_2 = {}",
1100            li.re
1101        );
1102    }
1103
1104    #[test]
1105    fn test_nielsen_s11_at_one() {
1106        // S_{1,1}(z) = Li_{1+1}(z) = Li_2(z), so S_{1,1}(1) = Li_2(1) = π²/6
1107        let val = nielsen_polylog(1, 1, 1.0).expect("S_{1,1}(1)");
1108        let expected = PI * PI / 6.0;
1109        assert!(
1110            (val - expected).abs() < 1e-8,
1111            "S_{{1,1}}(1) ≈ {val}, expected {expected}"
1112        );
1113    }
1114
1115    #[test]
1116    fn test_nielsen_p0_error() {
1117        assert!(nielsen_polylog(1, 0, 0.5).is_err());
1118    }
1119
1120    #[test]
1121    fn test_nielsen_z_too_large_error() {
1122        assert!(nielsen_polylog(1, 2, 1.5).is_err());
1123    }
1124
1125    // ── dilogarithm reflection ───────────────────────────────────────────────
1126
1127    #[test]
1128    fn test_reflection_identity() {
1129        // Li_2(z) + Li_2(1-z) = π²/6 - ln(z) * ln(1-z)
1130        let z = 0.3_f64;
1131        let (li2z, li2_1mz) = dilogarithm_reflection(z).expect("reflection");
1132        let lhs = li2z + li2_1mz;
1133        let rhs = PI * PI / 6.0 - z.ln() * (1.0 - z).ln();
1134        assert!((lhs - rhs).abs() < 1e-9, "reflection: lhs={lhs}, rhs={rhs}");
1135    }
1136
1137    #[test]
1138    fn test_reflection_z_half() {
1139        // At z = 1/2: Li_2(1/2) + Li_2(1/2) = π²/6 - (ln 2)²
1140        // ⟹  2 * Li_2(1/2) = π²/6 - (ln 2)²
1141        // ⟹  Li_2(1/2) = π²/12 - (ln 2)²/2  ✓
1142        let (li2z, li2_1mz) = dilogarithm_reflection(0.5).expect("reflection 0.5");
1143        assert_relative_eq!(li2z, li2_1mz, epsilon = 1e-8); // by symmetry
1144        let expected = PI * PI / 12.0 - (2.0_f64.ln()).powi(2) / 2.0;
1145        assert!(
1146            (li2z - expected).abs() < 1e-8,
1147            "Li_2(0.5) via reflection: {li2z}"
1148        );
1149    }
1150
1151    #[test]
1152    fn test_reflection_domain_errors() {
1153        assert!(dilogarithm_reflection(0.0).is_err());
1154        assert!(dilogarithm_reflection(1.0).is_err());
1155        assert!(dilogarithm_reflection(-0.5).is_err());
1156        assert!(dilogarithm_reflection(1.5).is_err());
1157    }
1158
1159    // ── internal gamma_approx ────────────────────────────────────────────────
1160
1161    #[test]
1162    fn test_gamma_approx_integers() {
1163        // Γ(n+1) = n!
1164        let expected = [1.0_f64, 1.0, 2.0, 6.0, 24.0, 120.0];
1165        for (i, &e) in expected.iter().enumerate() {
1166            let val = gamma_approx((i + 1) as f64);
1167            assert!(
1168                (val - e).abs() < 1e-8 * e.max(1.0),
1169                "Γ({}) = {val}, expected {e}",
1170                i + 1
1171            );
1172        }
1173    }
1174}