use crate::model::BuildConfig;
pub fn ratio(numerator: f64, denominator: f64) -> Option<f64> {
if denominator > 0.0 {
Some(numerator / denominator)
} else {
None
}
}
pub fn shrink_toward(observed_sum: f64, observed_weight: f64, alpha: f64, global_mean: f64) -> f64 {
(observed_sum + alpha * global_mean) / (observed_weight + alpha)
}
pub fn raw_score(
weighted_count: f64,
smoothed_success_rate: Option<f64>,
smoothed_mean_score: Option<f64>,
config: &BuildConfig,
) -> f64 {
let mut score = config.count_weight * (1.0 + weighted_count).ln();
if let Some(success) = smoothed_success_rate {
score += config.success_weight * success;
}
if let Some(mean_score) = smoothed_mean_score {
score += config.score_weight * mean_score;
}
score
}
pub fn normalize(raw_scores: &[f64]) -> Vec<f64> {
let sum: f64 = raw_scores.iter().sum();
if sum > 0.0 {
raw_scores.iter().map(|s| s / sum).collect()
} else if raw_scores.is_empty() {
Vec::new()
} else {
vec![1.0 / raw_scores.len() as f64; raw_scores.len()]
}
}
pub fn confidence(weighted_count: f64, k: f64) -> f64 {
weighted_count / (weighted_count + k)
}
pub fn entropy_bits(distribution: &[f64]) -> f64 {
distribution
.iter()
.filter(|&&p| p > 0.0)
.map(|&p| -p * p.log2())
.sum()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shrink_toward_pulls_low_sample_rate_to_global_mean() {
let smoothed = shrink_toward(1.0, 1.0, 5.0, 0.3);
assert!((smoothed - 0.4166).abs() < 1e-3);
}
#[test]
fn shrink_toward_zero_trials_falls_back_to_global_mean() {
assert_eq!(shrink_toward(0.0, 0.0, 5.0, 0.3), 0.3);
}
#[test]
fn raw_score_drops_missing_components() {
let config = BuildConfig::default();
let count_only = raw_score(10.0, None, None, &config);
assert_eq!(count_only, config.count_weight * 11f64.ln());
}
#[test]
fn normalize_sums_to_one() {
let priors = normalize(&[1.0, 3.0]);
assert!((priors.iter().sum::<f64>() - 1.0).abs() < 1e-9);
assert!((priors[1] - 0.75).abs() < 1e-9);
}
#[test]
fn normalize_falls_back_to_uniform_when_all_zero() {
let priors = normalize(&[0.0, 0.0, 0.0]);
assert_eq!(priors, vec![1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0]);
}
#[test]
fn confidence_grows_toward_one_with_more_samples() {
assert!((confidence(0.0, 20.0) - 0.0).abs() < 1e-9);
assert!(confidence(1000.0, 20.0) > 0.98);
}
#[test]
fn entropy_is_zero_for_a_single_dominant_action() {
assert_eq!(entropy_bits(&[1.0, 0.0]), 0.0);
}
#[test]
fn entropy_is_maximal_for_a_uniform_two_way_split() {
assert!((entropy_bits(&[0.5, 0.5]) - 1.0).abs() < 1e-9);
}
}