Skip to main content

cea_core/
calibration.rs

1const MIN_SAMPLE_EPSILON: f64 = 1e-12;
2const CONFIDENCE_SHRINKAGE_Z: f64 = 1.281_551_565_545;
3const SAMPLE_GROWTH_SCALE: f64 = 8.0;
4
5pub const MIN_SAMPLES_PER_SIGNATURE: usize = 3;
6
7#[derive(Debug, Clone, Copy)]
8pub struct ConfidenceEnvelope {
9    pub raw_confidence: f64,
10    pub coverage_fraction: f64,
11    pub sample_units: f64,
12    pub signature_count: usize,
13}
14
15impl ConfidenceEnvelope {
16    pub fn bounded_sample_factor(self, min_samples_per_signature: usize) -> f64 {
17        sample_sufficiency_factor(
18            self.sample_units,
19            self.signature_count,
20            min_samples_per_signature,
21        )
22        .clamp(0.0, 1.0)
23    }
24
25    pub fn bounded_confidence(self, min_samples_per_signature: usize) -> f64 {
26        advisory_confidence(
27            self.raw_confidence,
28            self.coverage_fraction,
29            self.sample_units,
30            self.signature_count,
31            min_samples_per_signature,
32        )
33    }
34}
35
36pub fn sample_sufficiency_factor(
37    sample_units: f64,
38    signature_count: usize,
39    min_samples_per_signature: usize,
40) -> f64 {
41    if signature_count == 0 || min_samples_per_signature == 0 {
42        return 0.0;
43    }
44
45    let required = (signature_count * min_samples_per_signature) as f64;
46    if required <= MIN_SAMPLE_EPSILON {
47        return 0.0;
48    }
49
50    (sample_units / required).clamp(0.0, 1.0)
51}
52
53pub fn advisory_confidence(
54    base_confidence: f64,
55    coverage_fraction: f64,
56    sample_units: f64,
57    signature_count: usize,
58    min_samples_per_signature: usize,
59) -> f64 {
60    let bounded_confidence = base_confidence.clamp(0.0, 1.0);
61    let bounded_coverage = coverage_fraction.clamp(0.0, 1.0);
62    let sufficiency =
63        sample_sufficiency_factor(sample_units, signature_count, min_samples_per_signature);
64    let effective_samples = effective_sample_size(sample_units);
65    let sample_weight = sample_factor(effective_samples);
66
67    (bounded_confidence.min(bounded_coverage) * sample_weight * sufficiency).clamp(0.0, 1.0)
68}
69
70pub fn posterior_mean(alpha: f64, beta: f64) -> f64 {
71    let total = (alpha + beta).max(MIN_SAMPLE_EPSILON);
72    (alpha / total).clamp(0.0, 1.0)
73}
74
75pub fn posterior_variance(alpha: f64, beta: f64) -> f64 {
76    let total = (alpha + beta).max(MIN_SAMPLE_EPSILON);
77    (alpha * beta) / ((total * total) * (total + 1.0)).max(MIN_SAMPLE_EPSILON)
78}
79
80pub fn conservative_reliability(alpha: f64, beta: f64) -> f64 {
81    let mean = posterior_mean(alpha, beta);
82    let std_dev = posterior_variance(alpha, beta).sqrt();
83    let lower = mean - (CONFIDENCE_SHRINKAGE_Z * std_dev);
84    lower.clamp(0.0, 1.0)
85}
86
87pub fn effective_sample_size(total_samples: f64) -> f64 {
88    (total_samples - 2.0).max(0.0)
89}
90
91pub fn sample_factor(effective_sample_size: f64) -> f64 {
92    (1.0 - (-effective_sample_size / SAMPLE_GROWTH_SCALE).exp()).clamp(0.0, 1.0)
93}