Skip to main content

thermite_special/
bernoulli.rs

1//! The Bernoulli sequence as vectors: `$B_0, B_1, B_2, B_3, \ldots$`, splatted.
2//!
3//! This is the public face of the Bernoulli tables. The raw per-format tables live in
4//! [`tables::bernoulli`](crate::tables::bernoulli) and hold only the even-index numbers
5//! from `$B_2$` up, because those are the ones that need a table; [`BernoulliSequence`]
6//! puts the head and the zeros back so the whole sequence can be iterated.
7//!
8//! ```
9//! use thermite::prelude::*;
10//! use thermite_special::bernoulli::BernoulliMath;
11//!
12//! type V = Vector<f64>;
13//!
14//! let b: Vec<f64> = V::bernoulli_numbers(-0.5)   // B_1 = -1/2, the caller's choice
15//!     .take(7)
16//!     .map(|v| v.extract::<0>())
17//!     .collect();
18//!
19//! assert_eq!(b, [1.0, -0.5, 1.0 / 6.0, 0.0, -1.0 / 30.0, 0.0, 1.0 / 42.0]);
20//! ```
21//!
22//! # `$B_1$` is yours to pick
23//!
24//! It is the one Bernoulli number the two conventions disagree on (`$-1/2$` from the
25//! generating function `$x/(e^x - 1)$`, `$+1/2$` from `$x/(1 - e^{-x})$`), so the table
26//! does not carry it and this iterator takes it as an argument instead. Pass whichever
27//! your formula assumes. Nothing else in the sequence changes with the choice.
28//!
29//! # Where it stops
30//!
31//! After the last `$B_{2n}$` representable in the element type: `$B_{258}$` for `f64`
32//! (259 items) and `$B_{64}$` for `f32` (65 items). `$|B_{2n}|$` grows factorially, so
33//! there is nothing beyond it to yield. See the
34//! [table docs](crate::tables::bernoulli) for the boundary in full.
35//!
36//! The iterator is [`ExactSizeIterator`], so `len()` gives that count up front.
37
38use core::iter::FusedIterator;
39
40use crate::RealPrimalMath;
41
42pub use crate::tables::bernoulli::{BernoulliNumbers, bernoulli_b2n};
43
44/// The Bernoulli sequence `$B_0, B_1, B_2, \ldots$` as splatted vectors, including the
45/// zero-valued odd terms.
46///
47/// Built by [`BernoulliMath::bernoulli_numbers`] or [`BernoulliSequence::new`]. See the
48/// [module docs](self) for the `$B_1$` convention and where the sequence ends.
49#[derive(Debug, Clone, Copy)]
50pub struct BernoulliSequence<V: RealPrimalMath<Element: BernoulliNumbers>> {
51    /// The subscript of the next number to yield, so `idx` IS `n` in `$B_n$`, not an
52    /// index into the underlying table, which skips `$B_0$`, `$B_1$` and the odd zeros.
53    idx: usize,
54    /// The caller's `$B_1$`. Held for the whole iteration rather than consumed at step
55    /// two, which is what lets the state be a bare counter.
56    b1: V::Element,
57}
58
59impl<V: RealPrimalMath<Element: BernoulliNumbers>> BernoulliSequence<V> {
60    /// Starts at `$B_0$`, with `b1` as the value of `$B_1$`.
61    #[inline]
62    #[must_use]
63    pub const fn new(b1: V::Element) -> Self {
64        Self { idx: 0, b1 }
65    }
66}
67
68impl<V: RealPrimalMath<Element: BernoulliNumbers>> Iterator for BernoulliSequence<V> {
69    type Item = V;
70
71    #[inline]
72    fn next(&mut self) -> Option<V> {
73        let table = <V::Element as BernoulliNumbers>::B2N;
74
75        let n = self.idx;
76        if n > 2 * table.len() {
77            // Deliberately does NOT advance, so the iterator is fused and `idx` cannot
78            // run away past the end.
79            return None;
80        }
81        self.idx = n + 1;
82
83        Some(match n {
84            0 => V::ONE,
85            1 => V::splat(self.b1),
86            // Every odd Bernoulli number past B_1 is zero. The table skips them, which is
87            // why `idx` is the subscript and the table index is derived, never the reverse.
88            _ if n % 2 == 1 => V::ZERO,
89            _ => V::splat(table[n / 2 - 1]),
90        })
91    }
92
93    #[inline]
94    fn size_hint(&self) -> (usize, Option<usize>) {
95        let end = 2 * <V::Element as BernoulliNumbers>::B2N.len() + 1;
96        let remaining = end.saturating_sub(self.idx);
97        (remaining, Some(remaining))
98    }
99}
100
101impl<V: RealPrimalMath<Element: BernoulliNumbers>> ExactSizeIterator for BernoulliSequence<V> {}
102
103impl<V: RealPrimalMath<Element: BernoulliNumbers>> FusedIterator for BernoulliSequence<V> {}
104
105/// Bernoulli numbers for real primal vectors.
106///
107/// Blanket-implemented for every [`RealPrimalMath`] vector whose element carries the
108/// tables, so bringing the trait into scope is all that is needed.
109pub trait BernoulliMath: RealPrimalMath<Element: BernoulliNumbers> + Sized {
110    /// The sequence `$B_0, B_1, B_2, \ldots$` as splatted vectors, zeros included.
111    ///
112    /// `b1` is the value of `$B_1$`, which the tables deliberately do not choose for you.
113    /// See the [module docs](self).
114    #[inline]
115    #[must_use]
116    fn bernoulli_numbers(b1: Self::Element) -> BernoulliSequence<Self> {
117        BernoulliSequence::new(b1)
118    }
119}
120
121impl<V: RealPrimalMath<Element: BernoulliNumbers>> BernoulliMath for V {}
Last built: 2026-09-08 21:35:55 UTC