#[must_use]
#[inline]
pub fn ensemble_spread_to_confidence(std_dev: f64, confidence_z: f64) -> f64 {
std_dev.abs() * confidence_z
}
#[must_use]
pub fn observation_error_to_likelihood(residual: f64, std_dev: f64) -> f64 {
if std_dev <= 0.0 {
return 0.0;
}
let z = residual / std_dev;
let two_pi_sqrt = std::f64::consts::TAU.sqrt();
(-0.5 * z * z).exp() / (std_dev * two_pi_sqrt)
}
#[must_use]
pub fn yields_to_mean_variance(yields: &[f64]) -> (f64, f64) {
if yields.is_empty() {
return (0.0, 0.0);
}
let n = yields.len() as f64;
let mean = yields.iter().sum::<f64>() / n;
let variance = if yields.len() > 1 {
yields.iter().map(|&y| (y - mean).powi(2)).sum::<f64>() / (n - 1.0)
} else {
0.0
};
(mean, variance)
}
#[must_use]
pub fn replicates_to_t_statistic(
mean_a: f64,
std_a: f64,
n_a: usize,
mean_b: f64,
std_b: f64,
n_b: usize,
) -> f64 {
if n_a == 0 || n_b == 0 {
return 0.0;
}
let se = ((std_a * std_a / n_a as f64) + (std_b * std_b / n_b as f64)).sqrt();
if se < 1e-15 {
return 0.0;
}
(mean_a - mean_b) / se
}
#[must_use]
#[inline]
pub fn uncertainty_to_gaussian(measured_value: f64, uncertainty_k: f64) -> (f64, f64) {
let std_dev = uncertainty_k.abs() / 1.96;
(measured_value, std_dev)
}
#[must_use]
#[inline]
pub fn sensor_noise_to_std_dev(psd: f64, bandwidth_hz: f64) -> f64 {
if psd <= 0.0 || bandwidth_hz <= 0.0 {
return 0.0;
}
(psd * bandwidth_hz).sqrt()
}
#[must_use]
pub fn item_variances_to_alpha(k: usize, sum_item_variance: f64, total_variance: f64) -> f64 {
if k < 2 || total_variance < 1e-15 {
return 0.0;
}
let kf = k as f64;
(kf / (kf - 1.0)) * (1.0 - sum_item_variance / total_variance)
}
#[must_use]
#[inline]
pub fn z_rates_to_d_prime(z_hit: f64, z_false_alarm: f64) -> f64 {
z_hit - z_false_alarm
}
#[must_use]
pub fn trait_scores_to_correlation(a: &[f64], b: &[f64]) -> (f64, f64, f64, f64, f64) {
if a.is_empty() || a.len() != b.len() {
return (0.0, 0.0, 0.0, 0.0, 0.0);
}
let n = a.len() as f64;
let mean_a = a.iter().sum::<f64>() / n;
let mean_b = b.iter().sum::<f64>() / n;
let mut cov = 0.0;
let mut var_a = 0.0;
let mut var_b = 0.0;
for i in 0..a.len() {
let da = a[i] - mean_a;
let db = b[i] - mean_b;
cov += da * db;
var_a += da * da;
var_b += db * db;
}
if n > 1.0 {
let denom = n - 1.0;
(mean_a, mean_b, cov / denom, var_a / denom, var_b / denom)
} else {
(mean_a, mean_b, 0.0, 0.0, 0.0)
}
}
#[must_use]
pub fn learning_trials_to_power_law(trials: &[(u32, f64)]) -> (f64, f64) {
if trials.len() < 2 {
return (0.0, 0.0);
}
let n = trials.len() as f64;
let mut sum_x = 0.0;
let mut sum_y = 0.0;
let mut sum_xy = 0.0;
let mut sum_xx = 0.0;
for &(trial, time) in trials {
if trial == 0 || time <= 0.0 {
continue;
}
let x = (trial as f64).ln();
let y = time.ln();
sum_x += x;
sum_y += y;
sum_xy += x * y;
sum_xx += x * x;
}
let denom = n * sum_xx - sum_x * sum_x;
if denom.abs() < 1e-15 {
return (0.0, 0.0);
}
let slope = (n * sum_xy - sum_x * sum_y) / denom;
let intercept = (sum_y - slope * sum_x) / n;
let a = intercept.exp();
let b = -slope; (a, b)
}
#[must_use]
pub fn response_times_to_hick_params(data: &[(usize, f64)]) -> (f64, f64) {
if data.len() < 2 {
return (0.0, 0.0);
}
let n = data.len() as f64;
let mut sum_x = 0.0;
let mut sum_y = 0.0;
let mut sum_xy = 0.0;
let mut sum_xx = 0.0;
for &(choices, rt) in data {
if choices == 0 {
continue;
}
let x = (choices as f64).log2();
sum_x += x;
sum_y += rt;
sum_xy += x * rt;
sum_xx += x * x;
}
let denom = n * sum_xx - sum_x * sum_x;
if denom.abs() < 1e-15 {
return (0.0, 0.0);
}
let b = (n * sum_xy - sum_x * sum_y) / denom;
let a = (sum_y - b * sum_x) / n;
(a, b)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn confidence_95() {
let half_width = ensemble_spread_to_confidence(10.0, 1.96);
assert!((half_width - 19.6).abs() < 0.01);
}
#[test]
fn likelihood_at_mean() {
let w = observation_error_to_likelihood(0.0, 1.0);
assert!((w - 0.3989).abs() < 0.001);
}
#[test]
fn likelihood_zero_std() {
assert!((observation_error_to_likelihood(1.0, 0.0)).abs() < f64::EPSILON);
}
#[test]
fn yields_basic() {
let (mean, var) = yields_to_mean_variance(&[10.0, 12.0, 11.0, 13.0]);
assert!((mean - 11.5).abs() < 0.01);
assert!(var > 0.0);
}
#[test]
fn yields_empty() {
let (m, v) = yields_to_mean_variance(&[]);
assert!((m).abs() < f64::EPSILON);
assert!((v).abs() < f64::EPSILON);
}
#[test]
fn t_statistic_equal_means() {
let t = replicates_to_t_statistic(10.0, 1.0, 30, 10.0, 1.0, 30);
assert!(t.abs() < 0.01);
}
#[test]
fn t_statistic_different_means() {
let t = replicates_to_t_statistic(10.0, 1.0, 30, 12.0, 1.0, 30);
assert!(t.abs() > 1.0);
}
#[test]
fn uncertainty_gaussian() {
let (mean, std) = uncertainty_to_gaussian(300.0, 2.0);
assert!((mean - 300.0).abs() < f64::EPSILON);
assert!((std - 2.0 / 1.96).abs() < 0.01);
}
#[test]
fn sensor_noise() {
let std = sensor_noise_to_std_dev(1e-6, 1000.0);
assert!((std - 0.0316).abs() < 0.001);
}
#[test]
fn sensor_noise_zero() {
assert!((sensor_noise_to_std_dev(0.0, 1000.0)).abs() < f64::EPSILON);
}
#[test]
fn alpha_high_reliability() {
let alpha = item_variances_to_alpha(3, 0.1, 10.0);
assert!((alpha - 1.485).abs() < 0.001);
}
#[test]
fn alpha_single_item() {
assert!((item_variances_to_alpha(1, 1.0, 2.0)).abs() < f64::EPSILON);
}
#[test]
fn d_prime_from_z() {
let d = z_rates_to_d_prime(1.28, -1.28);
assert!((d - 2.56).abs() < 0.01);
}
#[test]
fn trait_correlation_basic() {
let a = [1.0, 2.0, 3.0, 4.0, 5.0];
let b = [2.0, 4.0, 6.0, 8.0, 10.0];
let (ma, mb, cov, va, vb) = trait_scores_to_correlation(&a, &b);
assert!((ma - 3.0).abs() < 1e-10);
assert!((mb - 6.0).abs() < 1e-10);
assert!(cov > 0.0); assert!(va > 0.0);
assert!(vb > 0.0);
}
#[test]
fn trait_correlation_empty() {
let (m, _, _, _, _) = trait_scores_to_correlation(&[], &[]);
assert!(m.abs() < f64::EPSILON);
}
#[test]
fn learning_power_law_fit() {
let trials: Vec<(u32, f64)> = (1..=10)
.map(|n| (n, 10.0 * (n as f64).powf(-0.3)))
.collect();
let (a, b) = learning_trials_to_power_law(&trials);
assert!((a - 10.0).abs() < 0.5);
assert!((b - 0.3).abs() < 0.05);
}
#[test]
fn hick_params_fit() {
let data: Vec<(usize, f64)> = vec![
(2, 0.2 + 0.1 * 1.0),
(4, 0.2 + 0.1 * 2.0),
(8, 0.2 + 0.1 * 3.0),
];
let (a, b) = response_times_to_hick_params(&data);
assert!((a - 0.2).abs() < 0.01);
assert!((b - 0.1).abs() < 0.01);
}
}