extern crate alloc;
use alloc::string::String;
use alloc::vec::Vec;
use core::f64::consts::PI;
use nalgebra::Cholesky;
use rand::prelude::*;
use rand::SeedableRng;
use rand_xoshiro::Xoshiro256PlusPlus;
use crate::analysis::mde::estimate_mde;
use crate::constants::DEFAULT_SEED;
use crate::math;
use crate::preflight::{run_core_checks, PreflightResult};
use crate::statistics::{
bootstrap_difference_covariance, bootstrap_difference_covariance_discrete, AcquisitionStream,
OnlineStats,
};
use crate::types::{Matrix9, Vector9};
use super::CalibrationSnapshot;
#[cfg(feature = "parallel")]
use rayon::prelude::*;
#[cfg(feature = "parallel")]
#[inline]
fn counter_rng_seed(base_seed: u64, counter: u64) -> u64 {
let mut z = base_seed.wrapping_add(counter.wrapping_mul(0x9e3779b97f4a7c15));
z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
z ^ (z >> 31)
}
const CONSERVATIVE_PRIOR_SCALE: f64 = 1.5;
const TARGET_EXCEEDANCE: f64 = 0.62;
const PRIOR_CALIBRATION_SAMPLES: usize = 50_000;
const MAX_CALIBRATION_ITERATIONS: usize = 20;
const CONDITION_NUMBER_THRESHOLD: f64 = 1e4;
const DIAGONAL_FLOOR: f64 = 1e-12;
pub const NU: f64 = 4.0;
#[derive(Debug, Clone)]
pub struct Calibration {
pub sigma_rate: Matrix9,
pub block_length: usize,
pub sigma_t: f64,
pub l_r: Matrix9,
pub prior_cov_marginal: Matrix9,
pub theta_ns: f64,
pub calibration_samples: usize,
pub discrete_mode: bool,
pub mde_ns: f64,
pub calibration_snapshot: CalibrationSnapshot,
pub timer_resolution_ns: f64,
pub samples_per_second: f64,
pub c_floor: f64,
pub projection_mismatch_thresh: f64,
pub theta_tick: f64,
pub theta_eff: f64,
pub theta_floor_initial: f64,
pub rng_seed: u64,
pub batch_k: u32,
pub preflight_result: PreflightResult,
}
impl Calibration {
#[allow(clippy::too_many_arguments)]
pub fn new(
sigma_rate: Matrix9,
block_length: usize,
sigma_t: f64,
l_r: Matrix9,
theta_ns: f64,
calibration_samples: usize,
discrete_mode: bool,
mde_ns: f64,
calibration_snapshot: CalibrationSnapshot,
timer_resolution_ns: f64,
samples_per_second: f64,
c_floor: f64,
projection_mismatch_thresh: f64,
theta_tick: f64,
theta_eff: f64,
theta_floor_initial: f64,
rng_seed: u64,
batch_k: u32,
) -> Self {
let r = l_r * l_r.transpose();
let prior_cov_marginal = r * (2.0 * sigma_t * sigma_t);
Self {
sigma_rate,
block_length,
sigma_t,
l_r,
prior_cov_marginal,
theta_ns,
calibration_samples,
discrete_mode,
mde_ns,
calibration_snapshot,
timer_resolution_ns,
samples_per_second,
c_floor,
projection_mismatch_thresh,
theta_tick,
theta_eff,
theta_floor_initial,
rng_seed,
batch_k,
preflight_result: PreflightResult::new(),
}
}
#[allow(clippy::too_many_arguments)]
pub fn with_preflight(
sigma_rate: Matrix9,
block_length: usize,
sigma_t: f64,
l_r: Matrix9,
theta_ns: f64,
calibration_samples: usize,
discrete_mode: bool,
mde_ns: f64,
calibration_snapshot: CalibrationSnapshot,
timer_resolution_ns: f64,
samples_per_second: f64,
c_floor: f64,
projection_mismatch_thresh: f64,
theta_tick: f64,
theta_eff: f64,
theta_floor_initial: f64,
rng_seed: u64,
batch_k: u32,
preflight_result: PreflightResult,
) -> Self {
let r = l_r * l_r.transpose();
let prior_cov_marginal = r * (2.0 * sigma_t * sigma_t);
Self {
sigma_rate,
block_length,
sigma_t,
l_r,
prior_cov_marginal,
theta_ns,
calibration_samples,
discrete_mode,
mde_ns,
calibration_snapshot,
timer_resolution_ns,
samples_per_second,
c_floor,
projection_mismatch_thresh,
theta_tick,
theta_eff,
theta_floor_initial,
rng_seed,
batch_k,
preflight_result,
}
}
pub fn n_eff(&self, n: usize) -> usize {
if self.block_length == 0 {
return n.max(1);
}
(n / self.block_length).max(1)
}
pub fn covariance_for_n(&self, n: usize) -> Matrix9 {
if n == 0 {
return self.sigma_rate; }
let n_eff = self.n_eff(n);
self.sigma_rate / (n_eff as f64)
}
pub fn covariance_for_n_raw(&self, n: usize) -> Matrix9 {
if n == 0 {
return self.sigma_rate; }
self.sigma_rate / (n as f64)
}
pub fn estimate_collection_time_secs(&self, n: usize) -> f64 {
if self.samples_per_second <= 0.0 {
return 0.0;
}
n as f64 / self.samples_per_second
}
pub fn to_summary(&self) -> crate::ffi_summary::CalibrationSummary {
crate::ffi_summary::CalibrationSummary {
block_length: self.block_length,
calibration_samples: self.calibration_samples,
discrete_mode: self.discrete_mode,
timer_resolution_ns: self.timer_resolution_ns,
theta_ns: self.theta_ns,
theta_eff: self.theta_eff,
theta_floor_initial: self.theta_floor_initial,
theta_tick: self.theta_tick,
mde_ns: self.mde_ns,
samples_per_second: self.samples_per_second,
}
}
}
#[derive(Debug, Clone)]
pub struct CalibrationConfig {
pub calibration_samples: usize,
pub bootstrap_iterations: usize,
pub timer_resolution_ns: f64,
pub theta_ns: f64,
pub alpha: f64,
pub seed: u64,
pub skip_preflight: bool,
pub force_discrete_mode: bool,
}
impl Default for CalibrationConfig {
fn default() -> Self {
Self {
calibration_samples: 5000,
bootstrap_iterations: 200, timer_resolution_ns: 1.0,
theta_ns: 100.0,
alpha: 0.01,
seed: DEFAULT_SEED,
skip_preflight: false,
force_discrete_mode: false,
}
}
}
#[derive(Debug, Clone)]
pub enum CalibrationError {
TooFewSamples {
collected: usize,
minimum: usize,
},
CovarianceEstimationFailed {
reason: String,
},
PreflightCheckFailed {
check: String,
message: String,
},
TimerTooCoarse {
resolution_ns: f64,
operation_ns: f64,
},
}
impl core::fmt::Display for CalibrationError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
CalibrationError::TooFewSamples { collected, minimum } => {
write!(
f,
"Too few samples: collected {}, need at least {}",
collected, minimum
)
}
CalibrationError::CovarianceEstimationFailed { reason } => {
write!(f, "Covariance estimation failed: {}", reason)
}
CalibrationError::PreflightCheckFailed { check, message } => {
write!(f, "Preflight check '{}' failed: {}", check, message)
}
CalibrationError::TimerTooCoarse {
resolution_ns,
operation_ns,
} => {
write!(
f,
"Timer resolution ({:.1}ns) too coarse for operation ({:.1}ns)",
resolution_ns, operation_ns
)
}
}
}
}
pub fn calibrate(
baseline_samples: &[u64],
sample_samples: &[u64],
ns_per_tick: f64,
config: &CalibrationConfig,
samples_per_second: f64,
) -> Result<Calibration, CalibrationError> {
let n = baseline_samples.len().min(sample_samples.len());
const MIN_CALIBRATION_SAMPLES: usize = 100;
if n < MIN_CALIBRATION_SAMPLES {
return Err(CalibrationError::TooFewSamples {
collected: n,
minimum: MIN_CALIBRATION_SAMPLES,
});
}
let baseline_ns: Vec<f64> = baseline_samples[..n]
.iter()
.map(|&t| t as f64 * ns_per_tick)
.collect();
let sample_ns: Vec<f64> = sample_samples[..n]
.iter()
.map(|&t| t as f64 * ns_per_tick)
.collect();
let unique_baseline = count_unique(&baseline_ns);
let unique_sample = count_unique(&sample_ns);
let min_uniqueness = (unique_baseline as f64 / n as f64).min(unique_sample as f64 / n as f64);
let discrete_mode = config.force_discrete_mode || min_uniqueness < 0.10;
let mut acquisition_stream = AcquisitionStream::with_capacity(2 * n);
acquisition_stream.push_batch_interleaved(&baseline_ns, &sample_ns);
let interleaved = acquisition_stream.to_timing_samples();
let cov_estimate = if discrete_mode {
bootstrap_difference_covariance_discrete(
&baseline_ns,
&sample_ns,
config.bootstrap_iterations,
config.seed,
)
} else {
bootstrap_difference_covariance(
&interleaved,
config.bootstrap_iterations,
config.seed,
false,
)
};
if !cov_estimate.is_stable() {
return Err(CalibrationError::CovarianceEstimationFailed {
reason: String::from("Covariance matrix is not positive definite"),
});
}
let sigma_rate = cov_estimate.matrix * (n as f64);
let mde = estimate_mde(&cov_estimate.matrix, config.alpha);
let preflight_result = if config.skip_preflight {
PreflightResult::new()
} else {
run_core_checks(
&baseline_ns,
&sample_ns,
config.timer_resolution_ns,
config.seed,
)
};
let calibration_snapshot = compute_calibration_snapshot(&baseline_ns, &sample_ns);
let c_floor = compute_c_floor_9d(&sigma_rate, config.seed);
let theta_tick = config.timer_resolution_ns;
let theta_floor_initial = (c_floor / (n as f64).sqrt()).max(theta_tick);
let theta_eff = if config.theta_ns > 0.0 {
config.theta_ns.max(theta_floor_initial)
} else {
theta_floor_initial
};
let (sigma_t, l_r) =
calibrate_t_prior_scale(&sigma_rate, theta_eff, n, discrete_mode, config.seed);
Ok(Calibration::with_preflight(
sigma_rate,
cov_estimate.block_size,
sigma_t,
l_r,
config.theta_ns,
n,
discrete_mode,
mde.mde_ns,
calibration_snapshot,
config.timer_resolution_ns,
samples_per_second,
c_floor,
cov_estimate.q_thresh,
theta_tick,
theta_eff,
theta_floor_initial,
config.seed,
1, preflight_result,
))
}
fn count_unique(values: &[f64]) -> usize {
use alloc::collections::BTreeSet;
let buckets: BTreeSet<i64> = values.iter().map(|&v| (v * 1000.0) as i64).collect();
buckets.len()
}
fn compute_calibration_snapshot(baseline_ns: &[f64], sample_ns: &[f64]) -> CalibrationSnapshot {
let mut baseline_stats = OnlineStats::new();
let mut sample_stats = OnlineStats::new();
for &t in baseline_ns {
baseline_stats.update(t);
}
for &t in sample_ns {
sample_stats.update(t);
}
CalibrationSnapshot::new(baseline_stats.finalize(), sample_stats.finalize())
}
pub fn compute_prior_cov_9d(
sigma_rate: &Matrix9,
sigma_prior: f64,
discrete_mode: bool,
) -> Matrix9 {
let r = compute_correlation_matrix(sigma_rate);
let r = apply_correlation_regularization(&r, discrete_mode);
r * (sigma_prior * sigma_prior)
}
fn compute_correlation_matrix(sigma: &Matrix9) -> Matrix9 {
let mut d_inv_sqrt = [0.0_f64; 9];
for i in 0..9 {
let var = sigma[(i, i)].max(DIAGONAL_FLOOR);
d_inv_sqrt[i] = 1.0 / math::sqrt(var);
}
let mut r = *sigma;
for i in 0..9 {
for j in 0..9 {
r[(i, j)] *= d_inv_sqrt[i] * d_inv_sqrt[j];
}
}
r
}
fn estimate_condition_number(r: &Matrix9) -> f64 {
let diag: Vec<f64> = (0..9).map(|i| r[(i, i)].abs()).collect();
let max_diag = diag.iter().cloned().fold(0.0_f64, f64::max);
let min_diag = diag.iter().cloned().fold(f64::INFINITY, f64::min);
if min_diag < DIAGONAL_FLOOR {
return f64::INFINITY;
}
max_diag / min_diag
}
fn is_fragile_regime(r: &Matrix9, discrete_mode: bool) -> bool {
if discrete_mode {
return true;
}
let cond = estimate_condition_number(r);
if cond > CONDITION_NUMBER_THRESHOLD {
return true;
}
Cholesky::new(*r).is_none()
}
fn apply_correlation_regularization(r: &Matrix9, discrete_mode: bool) -> Matrix9 {
let mut r = *r;
if is_fragile_regime(&r, discrete_mode) {
let cond = estimate_condition_number(&r);
let lambda = if cond > CONDITION_NUMBER_THRESHOLD * 10.0 {
0.2 } else if cond > CONDITION_NUMBER_THRESHOLD {
0.1 } else if discrete_mode {
0.05 } else {
0.01 };
let identity = Matrix9::identity();
r = r * (1.0 - lambda) + identity * lambda;
}
for &eps in &[1e-10, 1e-9, 1e-8, 1e-7, 1e-6] {
let r_jittered = r + Matrix9::identity() * eps;
if Cholesky::new(r_jittered).is_some() {
return r_jittered;
}
}
r + Matrix9::identity() * 1e-5
}
fn compute_median_se(sigma_rate: &Matrix9, n_cal: usize) -> f64 {
let mut ses: Vec<f64> = (0..9)
.map(|i| {
let var = sigma_rate[(i, i)].max(DIAGONAL_FLOOR);
math::sqrt(var / n_cal.max(1) as f64)
})
.collect();
ses.sort_by(|a, b| a.total_cmp(b));
ses[4] }
pub fn calibrate_t_prior_scale(
sigma_rate: &Matrix9,
theta_eff: f64,
n_cal: usize,
discrete_mode: bool,
seed: u64,
) -> (f64, Matrix9) {
let median_se = compute_median_se(sigma_rate, n_cal);
let r = compute_correlation_matrix(sigma_rate);
let r_reg = apply_correlation_regularization(&r, discrete_mode);
let l_r = match Cholesky::new(r_reg) {
Some(c) => c.l().into_owned(),
None => Matrix9::identity(),
};
let normalized_effects = precompute_t_prior_effects(&l_r, seed);
let mut lo = theta_eff * 0.05;
let mut hi = (theta_eff * 50.0).max(10.0 * median_se);
for _ in 0..MAX_CALIBRATION_ITERATIONS {
let mid = (lo + hi) / 2.0;
let threshold = theta_eff / mid;
let count = normalized_effects
.iter()
.filter(|&&m| m > threshold)
.count();
let exceedance = count as f64 / normalized_effects.len() as f64;
if (exceedance - TARGET_EXCEEDANCE).abs() < 0.01 {
return (mid, l_r); }
if exceedance > TARGET_EXCEEDANCE {
hi = mid;
} else {
lo = mid;
}
}
(theta_eff * CONSERVATIVE_PRIOR_SCALE, l_r)
}
fn precompute_t_prior_effects(l_r: &Matrix9, seed: u64) -> Vec<f64> {
use rand_distr::Gamma;
#[cfg(feature = "parallel")]
{
let l_r = *l_r;
(0..PRIOR_CALIBRATION_SAMPLES)
.into_par_iter()
.map(|i| {
let mut rng = Xoshiro256PlusPlus::seed_from_u64(counter_rng_seed(seed, i as u64));
let gamma_dist = Gamma::new(NU / 2.0, 2.0 / NU).unwrap();
let lambda: f64 = gamma_dist.sample(&mut rng);
let inv_sqrt_lambda = 1.0 / math::sqrt(lambda.max(DIAGONAL_FLOOR));
let mut z = Vector9::zeros();
for j in 0..9 {
z[j] = sample_standard_normal(&mut rng);
}
let w = l_r * z;
let max_w = w.iter().map(|x| x.abs()).fold(0.0_f64, f64::max);
max_w * inv_sqrt_lambda
})
.collect()
}
#[cfg(not(feature = "parallel"))]
{
let mut rng = Xoshiro256PlusPlus::seed_from_u64(seed);
let gamma_dist = Gamma::new(NU / 2.0, 2.0 / NU).unwrap();
let mut effects = Vec::with_capacity(PRIOR_CALIBRATION_SAMPLES);
for _ in 0..PRIOR_CALIBRATION_SAMPLES {
let lambda: f64 = gamma_dist.sample(&mut rng);
let inv_sqrt_lambda = 1.0 / math::sqrt(lambda.max(DIAGONAL_FLOOR));
let mut z = Vector9::zeros();
for i in 0..9 {
z[i] = sample_standard_normal(&mut rng);
}
let w = l_r * z;
let max_w = w.iter().map(|x| x.abs()).fold(0.0_f64, f64::max);
effects.push(max_w * inv_sqrt_lambda);
}
effects
}
}
pub fn compute_c_floor_9d(sigma_rate: &Matrix9, seed: u64) -> f64 {
let chol = match Cholesky::new(*sigma_rate) {
Some(c) => c,
None => {
let trace: f64 = (0..9).map(|i| sigma_rate[(i, i)]).sum();
return math::sqrt(trace / 9.0) * 2.5; }
};
let l = chol.l().into_owned();
#[cfg(feature = "parallel")]
let mut max_effects: Vec<f64> = (0..PRIOR_CALIBRATION_SAMPLES)
.into_par_iter()
.map(|i| {
let mut rng = Xoshiro256PlusPlus::seed_from_u64(counter_rng_seed(seed, i as u64));
let mut z = Vector9::zeros();
for j in 0..9 {
z[j] = sample_standard_normal(&mut rng);
}
let sample = l * z;
sample.iter().map(|x| x.abs()).fold(0.0_f64, f64::max)
})
.collect();
#[cfg(not(feature = "parallel"))]
let mut max_effects: Vec<f64> = {
let mut rng = Xoshiro256PlusPlus::seed_from_u64(seed);
let mut effects = Vec::with_capacity(PRIOR_CALIBRATION_SAMPLES);
for _ in 0..PRIOR_CALIBRATION_SAMPLES {
let mut z = Vector9::zeros();
for i in 0..9 {
z[i] = sample_standard_normal(&mut rng);
}
let sample = l * z;
let max_effect = sample.iter().map(|x| x.abs()).fold(0.0_f64, f64::max);
effects.push(max_effect);
}
effects
};
let idx =
((PRIOR_CALIBRATION_SAMPLES as f64 * 0.95) as usize).min(PRIOR_CALIBRATION_SAMPLES - 1);
let (_, &mut percentile_95, _) = max_effects.select_nth_unstable_by(idx, |a, b| a.total_cmp(b));
percentile_95
}
fn sample_standard_normal<R: Rng>(rng: &mut R) -> f64 {
let u1: f64 = rng.random();
let u2: f64 = rng.random();
math::sqrt(-2.0 * math::ln(u1.max(1e-12))) * math::cos(2.0 * PI * u2)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::statistics::StatsSnapshot;
fn make_test_calibration() -> Calibration {
let snapshot = CalibrationSnapshot::new(
StatsSnapshot {
count: 1000,
mean: 100.0,
variance: 25.0,
autocorr_lag1: 0.1,
},
StatsSnapshot {
count: 1000,
mean: 105.0,
variance: 30.0,
autocorr_lag1: 0.12,
},
);
let sigma_rate = Matrix9::identity() * 1000.0;
let theta_eff = 100.0;
Calibration::new(
sigma_rate,
10, 100.0, Matrix9::identity(), theta_eff, 5000, false, 5.0, snapshot, 1.0, 10000.0, 10.0, 18.48, 0.001, theta_eff, 0.1, 42, 1, )
}
#[test]
fn test_covariance_scaling() {
let cal = make_test_calibration();
let cov_1000 = cal.covariance_for_n(1000);
assert!(
(cov_1000[(0, 0)] - 10.0).abs() < 1e-10,
"expected 10.0, got {}",
cov_1000[(0, 0)]
);
let cov_2000 = cal.covariance_for_n(2000);
assert!(
(cov_2000[(0, 0)] - 5.0).abs() < 1e-10,
"expected 5.0, got {}",
cov_2000[(0, 0)]
);
}
#[test]
fn test_n_eff() {
let cal = make_test_calibration();
assert_eq!(cal.n_eff(100), 10);
assert_eq!(cal.n_eff(1000), 100);
assert_eq!(cal.n_eff(10), 1);
assert_eq!(cal.n_eff(5), 1); assert_eq!(cal.n_eff(0), 1); }
#[test]
fn test_covariance_zero_n() {
let cal = make_test_calibration();
let cov = cal.covariance_for_n(0);
assert!((cov[(0, 0)] - 1000.0).abs() < 1e-10);
}
#[test]
fn test_estimate_collection_time() {
let cal = make_test_calibration();
let time = cal.estimate_collection_time_secs(1000);
assert!((time - 0.1).abs() < 1e-10);
}
#[test]
fn test_compute_prior_cov_9d_unit_diagonal() {
let sigma_rate = Matrix9::identity();
let prior = compute_prior_cov_9d(&sigma_rate, 10.0, false);
let expected = 100.0;
for i in 0..9 {
assert!(
(prior[(i, i)] - expected).abs() < 1.0,
"Diagonal {} was {}, expected ~{}",
i,
prior[(i, i)],
expected
);
}
}
#[test]
fn test_c_floor_computation() {
let sigma_rate = Matrix9::identity() * 100.0;
let c_floor = compute_c_floor_9d(&sigma_rate, 42);
assert!(c_floor > 15.0, "c_floor {} should be > 15", c_floor);
assert!(c_floor < 40.0, "c_floor {} should be < 40", c_floor);
}
#[test]
fn test_calibration_config_default() {
let config = CalibrationConfig::default();
assert_eq!(config.calibration_samples, 5000);
assert_eq!(config.bootstrap_iterations, 200);
assert!((config.theta_ns - 100.0).abs() < 1e-10);
assert!((config.timer_resolution_ns - 1.0).abs() < 1e-10);
assert!(!config.skip_preflight);
assert!(!config.force_discrete_mode);
}
fn reference_t_prior_exceedance(l_r: &Matrix9, sigma: f64, theta: f64, seed: u64) -> f64 {
use rand_distr::Gamma;
let mut rng = Xoshiro256PlusPlus::seed_from_u64(seed);
let gamma_dist = Gamma::new(NU / 2.0, 2.0 / NU).unwrap();
let mut count = 0usize;
for _ in 0..PRIOR_CALIBRATION_SAMPLES {
let lambda: f64 = gamma_dist.sample(&mut rng);
let scale = sigma / crate::math::sqrt(lambda.max(DIAGONAL_FLOOR));
let mut z = Vector9::zeros();
for i in 0..9 {
z[i] = sample_standard_normal(&mut rng);
}
let delta = l_r * z * scale;
let max_effect = delta.iter().map(|x| x.abs()).fold(0.0_f64, f64::max);
if max_effect > theta {
count += 1;
}
}
count as f64 / PRIOR_CALIBRATION_SAMPLES as f64
}
fn optimized_t_prior_exceedance(normalized_effects: &[f64], sigma: f64, theta: f64) -> f64 {
let threshold = theta / sigma;
let count = normalized_effects
.iter()
.filter(|&&m| m > threshold)
.count();
count as f64 / normalized_effects.len() as f64
}
#[test]
fn test_t_prior_precompute_exceedance_matches_reference() {
let l_r = Matrix9::identity();
let theta = 10.0;
let seed = 12345u64;
let normalized_effects = precompute_t_prior_effects(&l_r, seed);
for sigma in [5.0, 10.0, 15.0, 20.0, 30.0] {
let optimized = optimized_t_prior_exceedance(&normalized_effects, sigma, theta);
let reference = reference_t_prior_exceedance(&l_r, sigma, theta, seed);
assert!(
(0.0..=1.0).contains(&optimized),
"Optimized exceedance {} out of range for sigma={}",
optimized,
sigma
);
assert!(
(0.0..=1.0).contains(&reference),
"Reference exceedance {} out of range for sigma={}",
reference,
sigma
);
println!(
"sigma={}: optimized={:.4}, reference={:.4}",
sigma, optimized, reference
);
}
}
#[test]
fn test_t_prior_exceedance_monotonicity() {
let l_r = Matrix9::identity();
let theta = 10.0;
let seed = 42u64;
let normalized_effects = precompute_t_prior_effects(&l_r, seed);
let mut prev_exceedance = 0.0;
for sigma in [1.0, 2.0, 5.0, 10.0, 20.0, 50.0, 100.0] {
let exceedance = optimized_t_prior_exceedance(&normalized_effects, sigma, theta);
assert!(
exceedance >= prev_exceedance,
"Exceedance should increase with sigma: sigma={}, exc={}, prev={}",
sigma,
exceedance,
prev_exceedance
);
prev_exceedance = exceedance;
}
let large_sigma_exc = optimized_t_prior_exceedance(&normalized_effects, 1000.0, theta);
assert!(
large_sigma_exc > 0.99,
"Exceedance at large sigma should be ~1, got {}",
large_sigma_exc
);
let small_sigma_exc = optimized_t_prior_exceedance(&normalized_effects, 0.1, theta);
assert!(
small_sigma_exc < 0.01,
"Exceedance at small sigma should be ~0, got {}",
small_sigma_exc
);
}
#[test]
fn test_calibrate_t_prior_scale_finds_target_exceedance() {
let sigma_rate = Matrix9::identity() * 100.0;
let theta_eff = 10.0;
let n_cal = 5000;
let discrete_mode = false;
let seed = 42u64;
let (sigma_t, l_r) =
calibrate_t_prior_scale(&sigma_rate, theta_eff, n_cal, discrete_mode, seed);
let normalized_effects = precompute_t_prior_effects(&l_r, seed);
let exceedance = optimized_t_prior_exceedance(&normalized_effects, sigma_t, theta_eff);
assert!(
(exceedance - TARGET_EXCEEDANCE).abs() < 0.05,
"Calibrated t-prior exceedance {} should be near target {}",
exceedance,
TARGET_EXCEEDANCE
);
}
#[test]
fn test_calibration_determinism() {
let sigma_rate = Matrix9::identity() * 100.0;
let theta_eff = 10.0;
let n_cal = 5000;
let discrete_mode = false;
let seed = 12345u64;
let (sigma_t_1, _) =
calibrate_t_prior_scale(&sigma_rate, theta_eff, n_cal, discrete_mode, seed);
let (sigma_t_2, _) =
calibrate_t_prior_scale(&sigma_rate, theta_eff, n_cal, discrete_mode, seed);
assert!(
(sigma_t_1 - sigma_t_2).abs() < 1e-10,
"Same seed should give same sigma_t: {} vs {}",
sigma_t_1,
sigma_t_2
);
}
#[test]
fn test_precomputed_effects_distribution() {
let l_r = Matrix9::identity();
let seed = 42u64;
let effects = precompute_t_prior_effects(&l_r, seed);
assert!(
effects.iter().all(|&m| m > 0.0),
"All effects should be positive"
);
let mean: f64 = effects.iter().sum::<f64>() / effects.len() as f64;
assert!(
mean > 1.0 && mean < 10.0,
"Mean effect {} should be in reasonable range",
mean
);
let variance: f64 =
effects.iter().map(|&m| (m - mean).powi(2)).sum::<f64>() / (effects.len() - 1) as f64;
assert!(variance > 0.1, "Effects should have non-trivial variance");
}
#[test]
#[ignore] fn bench_calibration_timing() {
use std::time::Instant;
let sigma_rate = Matrix9::identity() * 10000.0;
let theta_eff = 100.0;
let n_cal = 5000;
let discrete_mode = false;
let _ = calibrate_t_prior_scale(&sigma_rate, theta_eff, n_cal, discrete_mode, 1);
let iterations = 10;
let start = Instant::now();
for i in 0..iterations {
let _ = calibrate_t_prior_scale(&sigma_rate, theta_eff, n_cal, discrete_mode, i as u64);
}
let t_prior_time = start.elapsed();
println!(
"\n=== Calibration Performance ===\n\
\n\
T-prior calibration: {:?} per call\n\
({} iterations averaged)",
t_prior_time / iterations as u32,
iterations
);
}
}