1#[inline]
13pub fn binomial_coefficient_f64(n: usize, k: usize) -> f64 {
14 if k > n {
15 return 0.0;
16 }
17 if k == 0 || k == n {
18 return 1.0;
19 }
20 let k_eff = k.min(n - k);
21 let mut num: u128 = 1;
31 for j in 0..k_eff {
32 match num.checked_mul((n - j) as u128) {
33 Some(scaled) => num = scaled / (j as u128 + 1),
34 None => {
35 let mut out = num as f64;
39 for jj in j..k_eff {
40 out = out * (n - jj) as f64 / (jj + 1) as f64;
41 }
42 return out;
43 }
44 }
45 }
46 num as f64
47}
48
49#[inline]
50fn horner_polynomial(x: f64, coeffs: &[f64]) -> f64 {
51 coeffs.iter().rev().fold(0.0, |acc, &c| acc * x + c)
52}
53
54#[inline]
60pub fn stable_polynomial_times_exp_neg(x: f64, coeffs: &[f64]) -> f64 {
61 if coeffs.is_empty() || !x.is_finite() {
62 return 0.0;
63 }
64 const DIRECT_EXP_SWITCH: f64 = 600.0;
69 if x <= DIRECT_EXP_SWITCH {
70 return horner_polynomial(x, coeffs) * (-x).exp();
71 }
72
73 let inv_x = x.recip();
74 let mut tail = 0.0;
75 for &c in coeffs {
76 tail = tail * inv_x + c;
77 }
78 let degree = (coeffs.len() - 1) as f64;
79 let scale = (degree * x.ln() - x).exp();
80 scale * tail
81}
82
83pub fn gauss_legendre(n: usize) -> (Vec<f64>, Vec<f64>) {
94 let mut tmp: Vec<(f64, f64)> = Vec::with_capacity(n);
95 let half = n.div_ceil(2);
96 for i in 0..half {
97 let mut z = (std::f64::consts::PI * (i as f64 + 0.75) / (n as f64 + 0.5)).cos();
98 let mut pp = 0.0_f64;
99 for _ in 0..200 {
100 let mut p1 = 1.0_f64;
101 let mut p2 = 0.0_f64;
102 for j in 0..n {
103 let p3 = p2;
104 p2 = p1;
105 p1 = ((2.0 * j as f64 + 1.0) * z * p2 - j as f64 * p3) / (j as f64 + 1.0);
106 }
107 pp = n as f64 * (z * p1 - p2) / (z * z - 1.0);
108 let z_prev = z;
109 z = z_prev - p1 / pp;
110 if (z - z_prev).abs() < 1e-15 {
111 break;
112 }
113 }
114 let w = 2.0 / ((1.0 - z * z) * pp * pp);
115 if !n.is_multiple_of(2) && i == half - 1 {
117 tmp.push((0.0, w));
118 } else {
119 tmp.push((-z.abs(), w));
120 tmp.push((z.abs(), w));
121 }
122 }
123 tmp.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
124 let mut nodes = Vec::with_capacity(n);
125 let mut weights = Vec::with_capacity(n);
126 for (z, w) in tmp.into_iter().take(n) {
127 nodes.push(z);
128 weights.push(w);
129 }
130 (nodes, weights)
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn gauss_legendre_integrates_polynomials_exactly() {
139 for n in [1usize, 2, 3, 5, 8, 40, 64] {
141 let (nodes, weights) = gauss_legendre(n);
142 assert_eq!(nodes.len(), n);
143 assert_eq!(weights.len(), n);
144 assert!(nodes.windows(2).all(|w| w[0] < w[1]), "nodes ascending");
145 if !n.is_multiple_of(2) {
146 assert_eq!(nodes[n / 2], 0.0, "odd-n central node is exact zero");
147 }
148 let total: f64 = weights.iter().sum();
149 assert!((total - 2.0).abs() < 1e-13, "∫1 dx = 2, got {total}");
150 if n >= 2 {
151 let x2: f64 = nodes.iter().zip(&weights).map(|(x, w)| w * x * x).sum();
152 assert!((x2 - 2.0 / 3.0).abs() < 1e-13, "∫x² dx = 2/3, got {x2}");
153 }
154 }
155 }
156
157 #[test]
158 fn binom_k_exceeds_n_returns_zero() {
159 assert_eq!(binomial_coefficient_f64(3, 5), 0.0);
160 assert_eq!(binomial_coefficient_f64(0, 1), 0.0);
161 assert_eq!(binomial_coefficient_f64(10, 11), 0.0);
162 }
163
164 #[test]
165 fn binom_k_zero_returns_one() {
166 assert_eq!(binomial_coefficient_f64(0, 0), 1.0);
167 assert_eq!(binomial_coefficient_f64(5, 0), 1.0);
168 assert_eq!(binomial_coefficient_f64(100, 0), 1.0);
169 }
170
171 #[test]
172 fn binom_k_equals_n_returns_one() {
173 assert_eq!(binomial_coefficient_f64(1, 1), 1.0);
174 assert_eq!(binomial_coefficient_f64(5, 5), 1.0);
175 assert_eq!(binomial_coefficient_f64(20, 20), 1.0);
176 }
177
178 #[test]
179 fn binom_small_exact_values() {
180 assert_eq!(binomial_coefficient_f64(5, 2), 10.0);
181 assert_eq!(binomial_coefficient_f64(10, 3), 120.0);
182 assert_eq!(binomial_coefficient_f64(20, 10), 184_756.0);
183 assert_eq!(binomial_coefficient_f64(6, 3), 20.0);
184 }
185
186 #[test]
187 fn binom_symmetry() {
188 assert_eq!(
189 binomial_coefficient_f64(10, 3),
190 binomial_coefficient_f64(10, 7)
191 );
192 assert_eq!(
193 binomial_coefficient_f64(20, 5),
194 binomial_coefficient_f64(20, 15)
195 );
196 assert_eq!(
197 binomial_coefficient_f64(54, 24),
198 binomial_coefficient_f64(54, 30)
199 );
200 }
201
202 #[test]
203 fn binom_c54_24_is_exact() {
204 assert_eq!(binomial_coefficient_f64(54, 24), 1_402_659_561_581_460.0);
207 }
208
209 #[test]
210 fn poly_exp_empty_coeffs_returns_zero() {
211 assert_eq!(stable_polynomial_times_exp_neg(1.0, &[]), 0.0);
212 assert_eq!(stable_polynomial_times_exp_neg(0.0, &[]), 0.0);
213 assert_eq!(stable_polynomial_times_exp_neg(700.0, &[]), 0.0);
214 }
215
216 #[test]
217 fn poly_exp_nonfinite_x_returns_zero() {
218 assert_eq!(
219 stable_polynomial_times_exp_neg(f64::INFINITY, &[1.0, 2.0]),
220 0.0
221 );
222 assert_eq!(
223 stable_polynomial_times_exp_neg(f64::NEG_INFINITY, &[1.0, 2.0]),
224 0.0
225 );
226 assert_eq!(stable_polynomial_times_exp_neg(f64::NAN, &[1.0]), 0.0);
227 }
228
229 #[test]
230 fn poly_exp_constant_at_zero() {
231 assert_eq!(stable_polynomial_times_exp_neg(0.0, &[5.0]), 5.0);
233 assert_eq!(stable_polynomial_times_exp_neg(0.0, &[3.0, 1.0, 2.0]), 3.0);
234 }
235
236 #[test]
237 fn poly_exp_constant_poly_direct_path() {
238 let x = 2.0;
240 let got = stable_polynomial_times_exp_neg(x, &[3.0]);
241 let expected = 3.0 * (-x).exp();
242 assert!(
243 (got - expected).abs() < 1e-14,
244 "got={got} expected={expected}"
245 );
246 }
247
248 #[test]
249 fn poly_exp_linear_poly_direct_path() {
250 let x = 1.5;
252 let (a, b) = (2.0, 3.0);
253 let got = stable_polynomial_times_exp_neg(x, &[a, b]);
254 let expected = (a + b * x) * (-x).exp();
255 assert!(
256 (got - expected).abs() < 1e-14,
257 "got={got} expected={expected}"
258 );
259 }
260
261 #[test]
262 fn poly_exp_constant_poly_asymptotic_path() {
263 let x = 700.0_f64;
265 let got = stable_polynomial_times_exp_neg(x, &[1.0]);
266 let expected = (-x).exp();
267 let rel = (got - expected).abs() / expected;
268 assert!(rel < 1e-12, "got={got} expected={expected} rel={rel}");
269 }
270
271 #[test]
272 fn poly_exp_quadratic_asymptotic_path() {
273 let x = 620.0_f64;
279 let got = stable_polynomial_times_exp_neg(x, &[0.0, 0.0, 1.0]);
280 let expected = (2.0 * x.ln() - x).exp();
281 let rel = (got - expected).abs() / expected.abs();
282 assert!(rel < 1e-12, "got={got} expected={expected} rel={rel}");
283 }
284}