1pub mod bigint;
63pub mod bits;
64pub mod fft;
65pub mod geometry;
66pub mod gf2;
67pub mod gf2m;
68pub mod linalg;
69pub mod log_domain;
70pub mod nearest;
71pub mod nlse;
72pub mod quadrature;
73pub mod search;
74pub mod sequences;
75pub mod special;
76pub mod stats;
77
78pub use bits::{bits_to_index, gray, gray_inverse, index_to_bits};
79pub use comnum_core::units::{amp2db, db2amp, db2lin, db2np, dbm2w, lin2db, np2db, w2dbm};
80pub use log_domain::{Jacobian, ln_1p_exp, log_sum_exp, log_sum_exp_iter, max_star};
81pub use quadrature::{gauss_hermite, gauss_legendre, gaussian_nodes, simpson, simpson_weight, try_simpson};
82pub use search::{argmax, bisect, gcd, golden_max};
83pub use special::{
84 bessel_i0e, bessel_j0, bessel_k, beta_inc, beta_inv, erf, erfc, erfcinv, expint_e1, fresnel, gamma_p, ln_bessel_k,
85 ln_binomial_pmf, ln_gamma, normal_pdf, qfunc, qfuncinv,
86};
87
88pub use comnum_core::C64;
89
90pub const PLANCK: f64 = 6.626_070_15e-34;
92pub const BOLTZMANN: f64 = 1.380_649e-23;
94pub const ELEMENTARY_CHARGE: f64 = 1.602_176_634e-19;
96pub const SPEED_OF_LIGHT: f64 = 299_792_458.0;
98pub const ZERO_CELSIUS: f64 = 273.15;
100
101pub(crate) const SCALE_FLOOR: f64 = 1e-300;
104
105#[inline]
107pub fn cis(theta: f64) -> C64 {
108 let (s, c) = theta.sin_cos();
109 C64::new(c, s)
110}
111
112#[inline]
114pub fn sinc(x: f64) -> f64 {
115 if x == 0.0 { 1.0 } else { (std::f64::consts::PI * x).sin() / (std::f64::consts::PI * x) }
116}
117
118#[inline]
121pub fn effective_length(alpha: f64, length: f64) -> f64 {
122 if alpha * length < 1e-12 { length } else { (1.0 - (-alpha * length).exp()) / alpha }
123}
124
125#[inline]
133pub fn nm2hz(wavelength_nm: f64) -> f64 {
134 SPEED_OF_LIGHT / (wavelength_nm * 1e-9)
135}
136
137pub fn power(x: &[C64]) -> f64 {
139 if x.is_empty() {
140 return 0.0;
141 }
142 energy(x) / x.len() as f64
143}
144
145pub fn energy(x: &[C64]) -> f64 {
147 x.iter().map(C64::norm_sqr).sum()
148}
149
150pub fn diric(x: f64, n: usize) -> f64 {
164 let half = 0.5 * x;
165 let den = half.sin();
166 let nf = n as f64;
167 if den.abs() < 1e-12 {
168 let k = (half / std::f64::consts::PI).round() as i64;
170 return if (k * (n as i64 - 1)).rem_euclid(2) == 0 { 1.0 } else { -1.0 };
171 }
172 (nf * half).sin() / (nf * den)
173}
174
175pub fn papr_db(x: &[C64]) -> f64 {
185 let peak = x.iter().map(C64::norm_sqr).fold(0.0, f64::max);
186 let mean = power(x);
187 if mean > 0.0 { lin2db(peak / mean) } else { f64::NEG_INFINITY }
188}
189
190pub fn entropy(p: &[f64]) -> f64 {
203 p.iter().filter(|q| **q > 0.0).map(|q| -q * q.log2()).sum()
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn ln_bessel_k_returns_its_limits_instead_of_looping() {
212 assert_eq!(ln_bessel_k(0.0, f64::INFINITY), f64::NEG_INFINITY);
213 assert_eq!(ln_bessel_k(2.5, f64::INFINITY), f64::NEG_INFINITY);
214 assert_eq!(ln_bessel_k(f64::INFINITY, 1.0), f64::INFINITY);
215 assert_eq!(bessel_k(1.0, f64::INFINITY), 0.0);
216 assert!(ln_bessel_k(f64::NAN, 1.0).is_nan());
217 assert!(ln_bessel_k(1.0, f64::NAN).is_nan());
218 assert!(ln_bessel_k(1.0, 0.0).is_nan());
219 let lead = |nu: f64, x: f64| -std::f64::consts::LN_2 + ln_gamma(nu) + nu * (std::f64::consts::LN_2 - x.ln());
222 assert!((ln_bessel_k(2.0, 1e-300) / lead(2.0, 1e-300) - 1.0).abs() < 1e-12);
223 assert_eq!(ln_bessel_k(2.0, 1e-310), lead(2.0, 1e-310));
224 assert!(ln_bessel_k(0.01, 1e-320).is_nan());
225 }
226
227 #[test]
228 #[allow(clippy::unreadable_literal, reason = "the values of SciPy, digit for digit")]
229 fn gamma_e1_and_erf_against_scipy() {
230 for (x, v) in
232 [(0.5, 0.5723649429247), (3.7, 1.428072326665388), (10.0, 12.801827480081469), (100.5, 361.43554046777757)]
233 {
234 assert!((ln_gamma(x) - v).abs() < 1e-13 * v.abs().max(1.0), "lgamma {x}");
235 }
236 assert!(ln_gamma(1.0).abs() < 1e-15 && ln_gamma(2.0).abs() < 1e-15);
237 for (a, x, v) in [
238 (2.5, 1.0, 0.15085496391539038),
239 (10.0, 6.2, 0.09837933549011063),
240 (10.0, 20.0, 0.9950045876916924),
241 (20.0, 5.0, 3.452135820914455e-7),
242 (0.5, 30.0, 0.9999999999999906),
243 ] {
244 assert!((gamma_p(a, x) / v - 1.0).abs() < 1e-12, "P({a}, {x}) = {} vs {v}", gamma_p(a, x));
245 }
246 for (x, v) in [
247 (1e-3, 6.331539364136149),
248 (0.3, 0.9056766516758468),
249 (2.5, 0.024914917870269736),
250 (50.0, 3.783264029550459e-24),
251 ] {
252 assert!((expint_e1(x) / v - 1.0).abs() < 1e-13, "E1({x})");
253 }
254 for (x, v) in
255 [(0.1, 0.1124629160182849), (1.9, 0.9927904292352575), (2.1, 0.997020533343667), (3.5, 0.9999992569016276)]
256 {
257 assert!((erf(x) - v).abs() < 1e-9, "erf({x})");
258 assert_eq!(erf(-x), -erf(x));
259 }
260 }
261
262 #[test]
263 fn bessel_i0e_matches_scipy() {
264 let table = [
266 (0.0, 1.0),
267 (0.5, 0.645_035_270_449_150_1),
268 (1.0, 0.465_759_607_593_640_43),
269 (5.0, 0.183_540_812_609_328_34),
270 (10.0, 0.127_833_337_163_428_6),
271 (30.0, 0.073_145_946_482_237_3),
272 (59.9, 0.051_654_795_368_473_484),
273 (60.1, 0.051_568_411_420_294_53),
274 (100.0, 0.039_944_379_299_096_68),
275 (1e3, 0.012_617_240_455_891_257),
276 (1e5, 0.001_261_567_837_976_776_6),
277 ];
278 for (x, want) in table {
279 let got = bessel_i0e(x);
280 assert!((got / want - 1.0).abs() < 1e-13, "{x}: {got} vs {want}");
281 assert_eq!(bessel_i0e(-x), got);
282 }
283 }
284
285 #[test]
286 #[allow(clippy::unreadable_literal, reason = "the values of SciPy, digit for digit")]
287 fn bessel_j0_against_scipy() {
288 for (x, v) in [
290 (0.0, 1.0),
291 (0.0628, 0.9990142830026578),
292 (1.0, 0.7651976865579665),
293 (2.404825557695773, 0.0),
294 (5.0, -0.1775967713143383),
295 (10.0, -0.2459357644513483),
296 (30.0, -0.08636798358104023),
297 (-3.0, -0.2600519549019335),
298 ] {
299 assert!((bessel_j0(x) - v).abs() < 1e-14, "J0({x}) = {} vs {v}", bessel_j0(x));
300 }
301 }
302
303 #[test]
304 #[allow(clippy::unreadable_literal, reason = "the values of SciPy, digit for digit")]
305 fn fresnel_against_scipy() {
306 let table = [
308 (0.1, 0.09999753262708506, 0.0005235895476122108),
309 (0.5, 0.4923442258714464, 0.06473243285999929),
310 (1.0, 0.779893400376823, 0.4382591473903547),
311 (1.4999, 0.4453535730062187, 0.6975432066559986),
312 (1.5, 0.44526117603982146, 0.697504960082093),
313 (2.0, 0.48825340607534073, 0.34341567836369824),
314 (3.7, 0.5419456621544874, 0.5749803498874729),
315 (10.0, 0.49989869420551575, 0.46816997858488224),
316 (31.4, 0.5006397897427959, 0.51011704529959),
317 (-2.5, -0.45741300964177706, -0.6191817558195929),
318 ];
319 for (x, c, s) in table {
320 let (fc, fs) = fresnel(x);
321 assert!((fc - c).abs() < 1e-14 && (fs - s).abs() < 1e-14, "{x}: ({fc}, {fs}) vs ({c}, {s})");
322 }
323 assert_eq!(fresnel(0.0), (0.0, 0.0));
324 let (c, s) = fresnel(1e4);
326 assert!((c - 0.5).abs() < 1e-4 && (s - 0.5).abs() < 1e-4);
327 }
328
329 #[test]
330 #[allow(clippy::unreadable_literal, clippy::excessive_precision, reason = "the values of mpmath, digit for digit")]
331 fn erfc_reference_values() {
332 for (x, v) in [
335 (0.0, 1.0),
336 (1e-10, 0.99999999988716208329),
337 (0.1, 0.8875370839817151078),
338 (0.3, 0.67137324054087257236),
339 (0.46875, 0.50738652678206200841),
340 (0.5, 0.47950012218695346232),
341 (0.75, 0.2888443663464848684),
342 (1.0, 0.15729920705028513066),
343 (1.5, 0.033894853524689272933),
344 (2.0, 0.0046777349810472658379),
345 (3.0, 0.000022090496998585441373),
346 (4.0, 1.5417257900280018852e-8),
347 (4.5, 1.9661604415428874763e-10),
348 (5.0, 1.5374597944280348502e-12),
349 (6.0, 2.1519736712498913117e-17),
350 (8.0, 1.122429717298292708e-29),
351 (10.0, 2.088487583762544757e-45),
352 (15.0, 7.2129941724512066666e-100),
353 (20.0, 5.3958656116079009289e-176),
354 (26.0, 5.6631924088561428465e-296),
355 ] {
356 assert!((erfc(x) / v - 1.0).abs() < 1e-15, "erfc({x}) = {} against {v}", erfc(x));
357 assert!((erfc(-x) - (2.0 - v)).abs() < 1e-15, "erfc(-{x})");
358 }
359 for (x, v) in [(-0.3, 1.3286267594591274276), (-1.0, 1.8427007929497148693), (-3.0, 1.9999779095030014146)] {
360 assert!((erfc(x) / v - 1.0).abs() < 1e-15, "erfc({x})");
361 }
362 assert!((erfc(27.0) / 5.237048923789255685e-319 - 1.0).abs() < 1e-3);
364 assert_eq!((erfc(27.5), erfc(f64::INFINITY), erfc(f64::NEG_INFINITY)), (0.0, 0.0, 2.0));
365 assert!(erfc(f64::NAN).is_nan());
366 assert!((erfcinv(erfc(1.3)) - 1.3).abs() < 1e-9);
367 assert!((qfunc(4.753_424_308_822_899) / 1.000_000_000_000_001_3e-6 - 1.0).abs() < 1e-14);
370 }
371
372 #[test]
373 fn decibels_round_trip() {
374 assert!((lin2db(db2lin(7.3)) - 7.3).abs() < 1e-12);
375 assert!((w2dbm(dbm2w(-3.0)) + 3.0).abs() < 1e-12);
376 }
377}