use std::f64::consts::PI;
pub fn density(x: f64, lambda: f64, sigma: f64) -> f64 {
if lambda <= 0.0 {
return 0.0;
}
let s2 = sigma * sigma;
let sqrt_l = lambda.sqrt();
let a = s2 * (1.0 - sqrt_l) * (1.0 - sqrt_l);
let b = s2 * (1.0 + sqrt_l) * (1.0 + sqrt_l);
if x < a || x > b {
return 0.0;
}
if (x - a).abs() < 1e-15 && (b - a).abs() < 1e-15 {
return f64::INFINITY;
}
let diff_b = b - x;
let diff_x = x - a;
if diff_b < 0.0 || diff_x < 0.0 {
return 0.0;
}
let norm = lambda.min(1.0);
(diff_b * diff_x).sqrt() / (2.0 * PI * s2 * norm * x)
}
pub fn moments(lambda: f64, max_k: usize, output: &mut [f64]) {
if max_k == 0 || output.is_empty() {
return;
}
let n = max_k.min(output.len());
let mut cumulants = vec![0.0_f64; n];
for i in 0..n {
cumulants[i] = lambda.powi(i as i32);
}
crate::moments::cumulant_to_moment(&cumulants, output);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::moments;
#[test]
fn test_mp_density_at_peak() {
let rho = density(1.0, 1.0, 1.0);
let expected = (3.0_f64).sqrt() / (2.0 * PI);
assert!((rho - expected).abs() < 1e-10, "ρ(1) = {} (expected {})", rho, expected);
assert!((density(-0.1, 1.0, 1.0) - 0.0).abs() < 1e-15);
assert!((density(4.1, 1.0, 1.0) - 0.0).abs() < 1e-15);
assert!((density(4.0, 1.0, 1.0) - 0.0).abs() < 1e-10,
"density at b=4: {}", density(4.0, 1.0, 1.0));
let _ = density(0.0, 1.0, 1.0);
}
#[test]
fn test_mp_density_integral_lambda_1() {
let lambda: f64 = 1.0;
let sqrt_l = lambda.sqrt();
let a = (1.0 - sqrt_l) * (1.0 - sqrt_l);
let b = (1.0 + sqrt_l) * (1.0 + sqrt_l);
let n_bins = 10000_usize;
let dx = (b - a) / n_bins as f64;
let integral: f64 = (0..n_bins)
.map(|i| {
let x = a + (i as f64 + 0.5) * dx;
density(x, lambda, 1.0) * dx
})
.sum();
assert!((integral - 1.0).abs() < 0.01, "integral = {} (expected 1)", integral);
}
#[test]
fn test_mp_density_integral_lambda_half() {
let lambda: f64 = 0.5;
let sqrt_l = lambda.sqrt();
let a = (1.0 - sqrt_l) * (1.0 - sqrt_l);
let b = (1.0 + sqrt_l) * (1.0 + sqrt_l);
let n_bins = 100000_usize;
let dx = (b - a) / n_bins as f64;
let integral: f64 = (0..n_bins)
.map(|i| {
let x = a + (i as f64 + 0.5) * dx;
density(x, lambda, 1.0) * dx
})
.sum();
assert!(
(integral - 1.0).abs() < 0.01,
"integral(λ=0.5) = {} (expected ~1)",
integral
);
}
#[test]
fn test_mp_density_integral_lambda_2() {
let lambda: f64 = 2.0;
let sqrt_l = lambda.sqrt();
let a = (1.0 - sqrt_l) * (1.0 - sqrt_l);
let b = (1.0 + sqrt_l) * (1.0 + sqrt_l);
let n_bins = 100000_usize;
let dx = (b - a) / n_bins as f64;
let integral: f64 = (0..n_bins)
.map(|i| {
let x = a + (i as f64 + 0.5) * dx;
density(x, lambda, 1.0) * dx
})
.sum();
assert!(
(integral - 1.0).abs() < 0.01,
"integral(λ=2) = {} (expected ~1)",
integral
);
}
#[test]
fn test_mp_density_lambda_half_sanity() {
let lambda: f64 = 0.5;
let sqrt_l = lambda.sqrt();
let a = (1.0 - sqrt_l) * (1.0 - sqrt_l);
let b = (1.0 + sqrt_l) * (1.0 + sqrt_l);
let mid = (a + b) / 2.0;
let rho_mid = density(mid, lambda, 1.0);
assert!(rho_mid > 0.0 && rho_mid < 100.0, "density at midpoint out of range: {}", rho_mid);
assert!((density(a - 0.01, lambda, 1.0) - 0.0).abs() < 1e-15);
assert!((density(b + 0.01, lambda, 1.0) - 0.0).abs() < 1e-15);
}
#[test]
fn test_mp_moments_lambda_1() {
let mut m = vec![0.0_f64; 6];
moments(1.0, 6, &mut m);
let expected = [1.0, 2.0, 5.0, 14.0, 42.0, 132.0];
for (i, &exp) in expected.iter().enumerate() {
assert!(
(m[i] - exp).abs() < 1e-10,
"m_{} = {} (expected {})",
i + 1,
m[i],
exp
);
}
}
#[test]
fn test_mp_cumulants() {
let lambda = 0.7;
let mut mp_mom = vec![0.0_f64; 5];
moments(lambda, 5, &mut mp_mom);
let mut cumulants = vec![0.0_f64; 5];
moments::moment_to_cumulant(&mp_mom, &mut cumulants);
assert!((cumulants[0] - 1.0).abs() < 1e-8, "κ₁ = {}", cumulants[0]);
assert!(
(cumulants[1] - lambda).abs() < 1e-8,
"κ₂ = {} (expected {})",
cumulants[1],
lambda
);
assert!(
(cumulants[2] - lambda * lambda).abs() < 1e-8,
"κ₃ = {} (expected {})",
cumulants[2],
lambda * lambda
);
}
#[test]
fn test_mp_moments_lambda_half() {
let lambda = 0.5;
let mut m = vec![0.0_f64; 4];
moments(lambda, 4, &mut m);
assert!((m[0] - 1.0).abs() < 1e-10, "m₁ = {}", m[0]);
assert!(
(m[1] - (1.0 + lambda)).abs() < 1e-8,
"m₂ = {} (expected {})",
m[1],
1.0 + lambda
);
}
#[test]
fn test_mp_moment_cumulant_roundtrip() {
let lambda = 0.3;
let mut mp_mom = vec![0.0_f64; 6];
moments(lambda, 6, &mut mp_mom);
let mut cumulants = vec![0.0_f64; 6];
moments::moment_to_cumulant(&mp_mom, &mut cumulants);
let mut mp_mom2 = vec![0.0_f64; 6];
moments::cumulant_to_moment(&cumulants, &mut mp_mom2);
for i in 0..6 {
assert!(
(mp_mom2[i] - mp_mom[i]).abs() < 1e-10,
"i={}: {} vs {}",
i,
mp_mom2[i],
mp_mom[i]
);
}
}
}