pub fn log_sum_exp(log_values: &[f64]) -> f64 {
if log_values.is_empty() {
return f64::NEG_INFINITY;
}
let max_val = log_values
.iter()
.fold(f64::NEG_INFINITY, |acc, &x| acc.max(x));
if max_val.is_infinite() && max_val < 0.0 {
return f64::NEG_INFINITY;
}
let sum_exp: f64 = log_values.iter().map(|&x| (x - max_val).exp()).sum();
if sum_exp == 0.0 {
f64::NEG_INFINITY
} else {
max_val + sum_exp.ln()
}
}
pub fn weighted_log_sum_exp(log_values: &[f64], weights: &[f64]) -> f64 {
assert_eq!(log_values.len(), weights.len());
if log_values.is_empty() {
return f64::NEG_INFINITY;
}
let max_val = log_values
.iter()
.fold(f64::NEG_INFINITY, |acc, &x| acc.max(x));
if max_val.is_infinite() && max_val < 0.0 {
return f64::NEG_INFINITY;
}
let weighted_sum: f64 = log_values
.iter()
.zip(weights.iter())
.map(|(&x, &w)| w * (x - max_val).exp())
.sum();
if weighted_sum == 0.0 {
f64::NEG_INFINITY
} else {
max_val + weighted_sum.ln()
}
}
pub fn normalize_log_probs(log_probs: &[f64]) -> Vec<f64> {
let log_sum = log_sum_exp(log_probs);
log_probs.iter().map(|&lp| (lp - log_sum).exp()).collect()
}
pub fn log1p_exp(x: f64) -> f64 {
if x > 33.3 {
x
} else if x > -37.0 {
x.exp().ln_1p()
} else {
x.exp()
}
}
pub fn safe_ln(x: f64) -> f64 {
if x <= 0.0 || !x.is_finite() {
f64::NEG_INFINITY
} else {
x.ln()
}
}
pub fn log_gamma(x: f64) -> f64 {
if x <= 0.0 || !x.is_finite() {
f64::NAN
} else {
libm::lgamma(x)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_log_sum_exp_stability() {
assert!((log_sum_exp(&[700.0, 701.0, 699.0]) - 701.4076059644444).abs() < 1e-9);
assert_eq!(log_sum_exp(&[5.0]), 5.0);
let small_vals = vec![-700.0, -701.0, -699.0];
let result = log_sum_exp(&small_vals);
assert!(result.is_finite());
assert_eq!(log_sum_exp(&[0.0, -1000.0]), 0.0);
assert_eq!(log_sum_exp(&[]), f64::NEG_INFINITY);
assert_eq!(
log_sum_exp(&[f64::NEG_INFINITY, f64::NEG_INFINITY]),
f64::NEG_INFINITY
);
}
#[test]
fn test_normalize_log_probs() {
let log_probs = vec![-1.0, -2.0, -3.0];
let probs = normalize_log_probs(&log_probs);
assert!((probs.iter().sum::<f64>() - 1.0).abs() < 1e-10);
assert!((probs[0] - 0.6652409557748219).abs() < 1e-9);
assert!((probs[1] - 0.24472847105479764).abs() < 1e-9);
assert!((probs[2] - 0.09003057317038043).abs() < 1e-9);
assert!((probs[0] / probs[1] - std::f64::consts::E).abs() < 1e-9);
}
#[test]
fn test_log1p_exp_stability() {
assert!((log1p_exp(0.0) - std::f64::consts::LN_2).abs() < 1e-9); assert!((log1p_exp(2.0) - 2.1269280110429727).abs() < 1e-9); assert!((log1p_exp(50.0) - 50.0).abs() < 1e-10);
assert!((log1p_exp(-50.0) - 1.9287498479639178e-22).abs() < 1e-31);
assert_eq!(log1p_exp(-1000.0), 0.0);
}
#[test]
fn test_weighted_log_sum_exp_more_edges() {
let logs = vec![-1000.0, 0.0, -10.0];
let weights = vec![0.0, 1.0, 0.0];
let res = weighted_log_sum_exp(&logs, &weights);
assert!((res - 0.0).abs() < 1e-12);
let weights2 = vec![0.5, 0.5, 0.0];
let res2 = weighted_log_sum_exp(&logs, &weights2);
assert!(res2.is_finite());
}
#[test]
fn test_safe_ln_edges() {
assert_eq!(safe_ln(-1.0), f64::NEG_INFINITY);
assert_eq!(safe_ln(f64::INFINITY), f64::NEG_INFINITY);
assert_eq!(safe_ln(1.0), 0.0);
}
}