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