Skip to main content

thermite_special/specialized/generic/
trigamma.rs

1use thermite::{
2    element::FloatElementWithBits,
3    mask::GenericMask,
4    math::{CoreMathWithPolicy as _, TranscendentalMathWithPolicy as _, policy::Policy},
5    prelude::*,
6};
7
8use crate::specialized::SpecializedSpecialMath;
9use crate::tables::gamma::Trigamma;
10
11/// Shared trigamma (`psi_1`) implementation for all real element types.
12///
13/// `psi_1(x) = d/dx psi(x)`, the second derivative of `ln Gamma`. Structurally much
14/// cheaper than `digamma`: the reduction to the fitted range is a *single* step
15/// rather than a masked walk, because the `[1, 2]` rational and the `x > 4` rational
16/// between them already cover everything from 1 upward.
17///
18/// The poles at zero and the negative integers evaluate to `+inf`, which is the
19/// correct two-sided limit (`psi_1` has a double pole there, so unlike `psi` the two
20/// one-sided limits agree) and falls out of the reflection term for free.
21#[inline(always)]
22pub fn trigamma_impl<P, E, V>(x_in: V, t: &Trigamma<E>) -> V
23where
24    P: Policy,
25    E: FloatElementWithBits,
26    V: FloatVectorWithBits<Element = E> + SpecializedSpecialMath<E>,
27{
28    let x0 = x_in.flush_denormals_p::<P>();
29
30    // --- Reflection for x <= 0: psi_1(x) = -psi_1(1 - x) + pi^2 / sin^2(pi*x) ---
31    let reflect = x0.cmp_le(V::ZERO);
32    let mut refl = V::ZERO;
33    let mut x = x0;
34
35    if const { P::POLICY.avoid_branching } || reflect.any() {
36        // Boost takes sin_pi of whichever of {x, 1 - x} is smaller in magnitude; on
37        // this path that is always x, since |x| < |1 - x| for every x <= 0.
38        let s = x0.sin_pi_p::<P>();
39
40        // sin_pi is exactly zero at the negative integers, so those lanes divide by
41        // zero and yield +inf - the intended pole value, no special case needed.
42        refl = (V::PI_SQUARED / (s * s)).zz(reflect);
43        x = reflect.select(V::ONE - x0, x);
44    }
45
46    // --- One recurrence step for 0 < x < 1: psi_1(x) = 1/x^2 + psi_1(x + 1) ---
47    // One step suffices: the rational below is fit from 1 up, and the reflected lanes
48    // already satisfy x >= 1.
49    let mut acc = V::ZERO;
50    let below_one = x.cmp_lt(V::ONE);
51
52    if const { P::POLICY.avoid_branching } || below_one.any() {
53        acc = (x * x).approx_reciprocal_p::<P>().zz(below_one);
54        x = x.add_c(below_one, V::ONE);
55    }
56
57    // --- Three minimax rational regions ---
58    let small = x.cmp_le(V::TWO);
59    let mid = x.cmp_le(V::splat(E::from_int(4))) & !small;
60    let large = !(small | mid);
61
62    // A single reciprocal covers both uses: it is the trailing 1/x^2 (small) or 1/x
63    // (mid, large) scale factor, and on the x > 2 lanes it is *also* the polynomial
64    // argument y = 1/x. Note the small lanes take 1/(x*x) directly rather than
65    // squaring a reciprocal, so they keep the accuracy of a single rounding.
66    let y = small.select(x * x, x).approx_reciprocal_p::<P>();
67
68    let mut num = V::EMPTY;
69    let mut den = V::EMPTY;
70    let mut base = V::ONE;
71
72    if const { P::POLICY.avoid_branching } || small.any() {
73        num = x.poly_n_p::<P, _>(&t.p_1_2);
74        den = x.poly_n_p::<P, _>(&t.q_1_2);
75        base = small.select(V::splat(t.offset), V::ONE);
76    }
77
78    if const { P::POLICY.avoid_branching } || mid.any() {
79        num = mid.select(y.poly_n_p::<P, _>(&t.p_2_4), num);
80        den = mid.select(y.poly_n_p::<P, _>(&t.q_2_4), den);
81    }
82
83    if const { P::POLICY.avoid_branching } || large.any() {
84        num = large.select(y.poly_n_p::<P, _>(&t.p_4_inf), num);
85        den = large.select(y.poly_n_p::<P, _>(&t.q_4_inf), den);
86    }
87
88    // Selecting the numerator and denominator before dividing keeps this to one
89    // division for all three regions - and means an unselected region's overflow can
90    // never turn into an inf/inf NaN in a lane that survives.
91    let main = (base + num / den) * y;
92
93    reflect.select(refl - main, acc + main)
94}
Last built: 2026-09-08 21:35:55 UTC