pub mod bigint;
pub mod bits;
pub mod fft;
pub mod geometry;
pub mod gf2;
pub mod gf2m;
pub mod linalg;
pub mod log_domain;
pub mod nearest;
pub mod nlse;
pub mod quadrature;
pub mod search;
pub mod sequences;
pub mod special;
pub mod stats;
pub use bits::{bits_to_index, gray, gray_inverse, index_to_bits};
pub use comnum_core::units::{amp2db, db2amp, db2lin, db2np, dbm2w, lin2db, np2db, w2dbm};
pub use log_domain::{Jacobian, ln_1p_exp, log_sum_exp, log_sum_exp_iter, max_star};
pub use quadrature::{gauss_hermite, gauss_legendre, gaussian_nodes, simpson, simpson_weight, try_simpson};
pub use search::{argmax, bisect, gcd, golden_max};
pub use special::{
bessel_i0e, bessel_j0, bessel_k, beta_inc, beta_inv, erf, erfc, erfcinv, expint_e1, fresnel, gamma_p, ln_bessel_k,
ln_binomial_pmf, ln_gamma, normal_pdf, qfunc, qfuncinv,
};
pub use comnum_core::C64;
pub const PLANCK: f64 = 6.626_070_15e-34;
pub const BOLTZMANN: f64 = 1.380_649e-23;
pub const ELEMENTARY_CHARGE: f64 = 1.602_176_634e-19;
pub const SPEED_OF_LIGHT: f64 = 299_792_458.0;
pub const ZERO_CELSIUS: f64 = 273.15;
pub(crate) const SCALE_FLOOR: f64 = 1e-300;
#[inline]
pub fn cis(theta: f64) -> C64 {
let (s, c) = theta.sin_cos();
C64::new(c, s)
}
#[inline]
pub fn sinc(x: f64) -> f64 {
if x == 0.0 { 1.0 } else { (std::f64::consts::PI * x).sin() / (std::f64::consts::PI * x) }
}
#[inline]
pub fn effective_length(alpha: f64, length: f64) -> f64 {
if alpha * length < 1e-12 { length } else { (1.0 - (-alpha * length).exp()) / alpha }
}
#[inline]
pub fn nm2hz(wavelength_nm: f64) -> f64 {
SPEED_OF_LIGHT / (wavelength_nm * 1e-9)
}
pub fn power(x: &[C64]) -> f64 {
if x.is_empty() {
return 0.0;
}
energy(x) / x.len() as f64
}
pub fn energy(x: &[C64]) -> f64 {
x.iter().map(C64::norm_sqr).sum()
}
pub fn diric(x: f64, n: usize) -> f64 {
let half = 0.5 * x;
let den = half.sin();
let nf = n as f64;
if den.abs() < 1e-12 {
let k = (half / std::f64::consts::PI).round() as i64;
return if (k * (n as i64 - 1)).rem_euclid(2) == 0 { 1.0 } else { -1.0 };
}
(nf * half).sin() / (nf * den)
}
pub fn papr_db(x: &[C64]) -> f64 {
let peak = x.iter().map(C64::norm_sqr).fold(0.0, f64::max);
let mean = power(x);
if mean > 0.0 { lin2db(peak / mean) } else { f64::NEG_INFINITY }
}
pub fn entropy(p: &[f64]) -> f64 {
p.iter().filter(|q| **q > 0.0).map(|q| -q * q.log2()).sum()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ln_bessel_k_returns_its_limits_instead_of_looping() {
assert_eq!(ln_bessel_k(0.0, f64::INFINITY), f64::NEG_INFINITY);
assert_eq!(ln_bessel_k(2.5, f64::INFINITY), f64::NEG_INFINITY);
assert_eq!(ln_bessel_k(f64::INFINITY, 1.0), f64::INFINITY);
assert_eq!(bessel_k(1.0, f64::INFINITY), 0.0);
assert!(ln_bessel_k(f64::NAN, 1.0).is_nan());
assert!(ln_bessel_k(1.0, f64::NAN).is_nan());
assert!(ln_bessel_k(1.0, 0.0).is_nan());
let lead = |nu: f64, x: f64| -std::f64::consts::LN_2 + ln_gamma(nu) + nu * (std::f64::consts::LN_2 - x.ln());
assert!((ln_bessel_k(2.0, 1e-300) / lead(2.0, 1e-300) - 1.0).abs() < 1e-12);
assert_eq!(ln_bessel_k(2.0, 1e-310), lead(2.0, 1e-310));
assert!(ln_bessel_k(0.01, 1e-320).is_nan());
}
#[test]
#[allow(clippy::unreadable_literal, reason = "the values of SciPy, digit for digit")]
fn gamma_e1_and_erf_against_scipy() {
for (x, v) in
[(0.5, 0.5723649429247), (3.7, 1.428072326665388), (10.0, 12.801827480081469), (100.5, 361.43554046777757)]
{
assert!((ln_gamma(x) - v).abs() < 1e-13 * v.abs().max(1.0), "lgamma {x}");
}
assert!(ln_gamma(1.0).abs() < 1e-15 && ln_gamma(2.0).abs() < 1e-15);
for (a, x, v) in [
(2.5, 1.0, 0.15085496391539038),
(10.0, 6.2, 0.09837933549011063),
(10.0, 20.0, 0.9950045876916924),
(20.0, 5.0, 3.452135820914455e-7),
(0.5, 30.0, 0.9999999999999906),
] {
assert!((gamma_p(a, x) / v - 1.0).abs() < 1e-12, "P({a}, {x}) = {} vs {v}", gamma_p(a, x));
}
for (x, v) in [
(1e-3, 6.331539364136149),
(0.3, 0.9056766516758468),
(2.5, 0.024914917870269736),
(50.0, 3.783264029550459e-24),
] {
assert!((expint_e1(x) / v - 1.0).abs() < 1e-13, "E1({x})");
}
for (x, v) in
[(0.1, 0.1124629160182849), (1.9, 0.9927904292352575), (2.1, 0.997020533343667), (3.5, 0.9999992569016276)]
{
assert!((erf(x) - v).abs() < 1e-9, "erf({x})");
assert_eq!(erf(-x), -erf(x));
}
}
#[test]
fn bessel_i0e_matches_scipy() {
let table = [
(0.0, 1.0),
(0.5, 0.645_035_270_449_150_1),
(1.0, 0.465_759_607_593_640_43),
(5.0, 0.183_540_812_609_328_34),
(10.0, 0.127_833_337_163_428_6),
(30.0, 0.073_145_946_482_237_3),
(59.9, 0.051_654_795_368_473_484),
(60.1, 0.051_568_411_420_294_53),
(100.0, 0.039_944_379_299_096_68),
(1e3, 0.012_617_240_455_891_257),
(1e5, 0.001_261_567_837_976_776_6),
];
for (x, want) in table {
let got = bessel_i0e(x);
assert!((got / want - 1.0).abs() < 1e-13, "{x}: {got} vs {want}");
assert_eq!(bessel_i0e(-x), got);
}
}
#[test]
#[allow(clippy::unreadable_literal, reason = "the values of SciPy, digit for digit")]
fn bessel_j0_against_scipy() {
for (x, v) in [
(0.0, 1.0),
(0.0628, 0.9990142830026578),
(1.0, 0.7651976865579665),
(2.404825557695773, 0.0),
(5.0, -0.1775967713143383),
(10.0, -0.2459357644513483),
(30.0, -0.08636798358104023),
(-3.0, -0.2600519549019335),
] {
assert!((bessel_j0(x) - v).abs() < 1e-14, "J0({x}) = {} vs {v}", bessel_j0(x));
}
}
#[test]
#[allow(clippy::unreadable_literal, reason = "the values of SciPy, digit for digit")]
fn fresnel_against_scipy() {
let table = [
(0.1, 0.09999753262708506, 0.0005235895476122108),
(0.5, 0.4923442258714464, 0.06473243285999929),
(1.0, 0.779893400376823, 0.4382591473903547),
(1.4999, 0.4453535730062187, 0.6975432066559986),
(1.5, 0.44526117603982146, 0.697504960082093),
(2.0, 0.48825340607534073, 0.34341567836369824),
(3.7, 0.5419456621544874, 0.5749803498874729),
(10.0, 0.49989869420551575, 0.46816997858488224),
(31.4, 0.5006397897427959, 0.51011704529959),
(-2.5, -0.45741300964177706, -0.6191817558195929),
];
for (x, c, s) in table {
let (fc, fs) = fresnel(x);
assert!((fc - c).abs() < 1e-14 && (fs - s).abs() < 1e-14, "{x}: ({fc}, {fs}) vs ({c}, {s})");
}
assert_eq!(fresnel(0.0), (0.0, 0.0));
let (c, s) = fresnel(1e4);
assert!((c - 0.5).abs() < 1e-4 && (s - 0.5).abs() < 1e-4);
}
#[test]
#[allow(clippy::unreadable_literal, clippy::excessive_precision, reason = "the values of mpmath, digit for digit")]
fn erfc_reference_values() {
for (x, v) in [
(0.0, 1.0),
(1e-10, 0.99999999988716208329),
(0.1, 0.8875370839817151078),
(0.3, 0.67137324054087257236),
(0.46875, 0.50738652678206200841),
(0.5, 0.47950012218695346232),
(0.75, 0.2888443663464848684),
(1.0, 0.15729920705028513066),
(1.5, 0.033894853524689272933),
(2.0, 0.0046777349810472658379),
(3.0, 0.000022090496998585441373),
(4.0, 1.5417257900280018852e-8),
(4.5, 1.9661604415428874763e-10),
(5.0, 1.5374597944280348502e-12),
(6.0, 2.1519736712498913117e-17),
(8.0, 1.122429717298292708e-29),
(10.0, 2.088487583762544757e-45),
(15.0, 7.2129941724512066666e-100),
(20.0, 5.3958656116079009289e-176),
(26.0, 5.6631924088561428465e-296),
] {
assert!((erfc(x) / v - 1.0).abs() < 1e-15, "erfc({x}) = {} against {v}", erfc(x));
assert!((erfc(-x) - (2.0 - v)).abs() < 1e-15, "erfc(-{x})");
}
for (x, v) in [(-0.3, 1.3286267594591274276), (-1.0, 1.8427007929497148693), (-3.0, 1.9999779095030014146)] {
assert!((erfc(x) / v - 1.0).abs() < 1e-15, "erfc({x})");
}
assert!((erfc(27.0) / 5.237048923789255685e-319 - 1.0).abs() < 1e-3);
assert_eq!((erfc(27.5), erfc(f64::INFINITY), erfc(f64::NEG_INFINITY)), (0.0, 0.0, 2.0));
assert!(erfc(f64::NAN).is_nan());
assert!((erfcinv(erfc(1.3)) - 1.3).abs() < 1e-9);
assert!((qfunc(4.753_424_308_822_899) / 1.000_000_000_000_001_3e-6 - 1.0).abs() < 1e-14);
}
#[test]
fn decibels_round_trip() {
assert!((lin2db(db2lin(7.3)) - 7.3).abs() < 1e-12);
assert!((w2dbm(dbm2w(-3.0)) + 3.0).abs() < 1e-12);
}
}