Skip to main content

dsfb_tmtr/
tmtr.rs

1use serde::Serialize;
2
3use crate::config::SimulationConfig;
4use crate::kernel::evaluate_kernel;
5use crate::observer::{ObserverSeries, ObserverSpec};
6use crate::scenario::ScenarioDefinition;
7
8#[derive(Debug, Clone, Serialize)]
9pub struct CorrectionEvent {
10    pub scenario: String,
11    pub source_level: usize,
12    pub target_level: usize,
13    pub anchor_time: usize,
14    pub corrected_time: usize,
15    pub delta_window: usize,
16    pub trust_weight: f64,
17    pub kernel_weight: f64,
18    pub compatibility: f64,
19    pub correction_magnitude: f64,
20    pub recursion_depth: usize,
21    pub iteration: usize,
22}
23
24#[derive(Debug, Clone, Serialize)]
25pub struct RecursionStats {
26    pub total_correction_events: usize,
27    pub max_recursion_depth: usize,
28    pub mean_recursion_depth: f64,
29    pub convergence_iterations: usize,
30    pub average_correction_magnitude: f64,
31    pub average_correction_trust_weight: f64,
32    pub monotonicity_violations: usize,
33}
34
35#[derive(Debug, Clone, Serialize)]
36pub struct TmtrResult {
37    pub observers: Vec<ObserverSeries>,
38    pub correction_events: Vec<CorrectionEvent>,
39    pub recursion_stats: RecursionStats,
40}
41
42pub fn apply_tmtr(
43    definition: &ScenarioDefinition,
44    config: &SimulationConfig,
45    specs: &[ObserverSpec],
46    baseline: &[ObserverSeries],
47    truth: &[f64],
48) -> TmtrResult {
49    let mut observers = baseline.to_vec();
50    let mut events = Vec::new();
51    let mut convergence_iterations = 0;
52
53    for iteration in 0..config.max_iterations {
54        convergence_iterations = iteration + 1;
55        let mut iteration_change = 0.0;
56        let mut depth_reached = vec![0usize; observers.len()];
57
58        for source_index in (1..observers.len()).rev() {
59            let target_index = source_index - 1;
60            let recursion_depth = 1 + depth_reached[source_index];
61            if recursion_depth > config.max_recursion_depth {
62                continue;
63            }
64
65            let mut pair_changed = false;
66            let source = observers[source_index].clone();
67            let target = &mut observers[target_index];
68            let eta = definition.eta[target_index];
69            let delta = definition.delta.min(config.delta);
70
71            for anchor_time in 0..truth.len() {
72                let source_trust = source.trust[anchor_time];
73                let target_trust = target.trust[anchor_time];
74                if !source.available[anchor_time] {
75                    continue;
76                }
77                if source_trust < config.trust_threshold
78                    || source_trust <= target_trust + config.min_trust_gap
79                {
80                    continue;
81                }
82
83                let window_start = anchor_time.saturating_sub(delta);
84                for corrected_time in window_start..=anchor_time {
85                    let evaluation = evaluate_kernel(
86                        config.kernel,
87                        &source,
88                        target,
89                        corrected_time,
90                        anchor_time,
91                        delta,
92                        definition.resonance_threshold,
93                    );
94                    let retro_weight =
95                        1.0 - (anchor_time - corrected_time) as f64 / (delta.max(1) as f64 + 1.0);
96                    let correction =
97                        eta * source_trust * retro_weight.max(0.05) * evaluation.signal;
98                    if correction.abs() < config.convergence_tolerance * 0.1 {
99                        continue;
100                    }
101                    target.estimate[corrected_time] += correction;
102                    iteration_change += correction.abs();
103                    pair_changed = true;
104                    events.push(CorrectionEvent {
105                        scenario: definition.name.clone(),
106                        source_level: source.level,
107                        target_level: target.level,
108                        anchor_time,
109                        corrected_time,
110                        delta_window: delta,
111                        trust_weight: source_trust,
112                        kernel_weight: evaluation.weight,
113                        compatibility: evaluation.compatibility,
114                        correction_magnitude: correction,
115                        recursion_depth,
116                        iteration: iteration + 1,
117                    });
118                }
119            }
120
121            if pair_changed {
122                depth_reached[target_index] = recursion_depth;
123                target.recompute_after_estimate_update(truth, &specs[target_index]);
124            }
125        }
126
127        if should_stop(iteration_change, config.convergence_tolerance) {
128            break;
129        }
130    }
131
132    let total_events = events.len();
133    let depth_sum = events
134        .iter()
135        .map(|event| event.recursion_depth as f64)
136        .sum::<f64>();
137    let magnitude_sum = events
138        .iter()
139        .map(|event| event.correction_magnitude.abs())
140        .sum::<f64>();
141    let trust_sum = events.iter().map(|event| event.trust_weight).sum::<f64>();
142    let max_depth = events
143        .iter()
144        .map(|event| event.recursion_depth)
145        .max()
146        .unwrap_or(0);
147
148    TmtrResult {
149        observers,
150        correction_events: events,
151        recursion_stats: RecursionStats {
152            total_correction_events: total_events,
153            max_recursion_depth: max_depth,
154            mean_recursion_depth: if total_events == 0 {
155                0.0
156            } else {
157                depth_sum / total_events as f64
158            },
159            convergence_iterations,
160            average_correction_magnitude: if total_events == 0 {
161                0.0
162            } else {
163                magnitude_sum / total_events as f64
164            },
165            average_correction_trust_weight: if total_events == 0 {
166                0.0
167            } else {
168                trust_sum / total_events as f64
169            },
170            monotonicity_violations: 0,
171        },
172    }
173}
174
175pub fn should_stop(total_change: f64, tolerance: f64) -> bool {
176    total_change <= tolerance
177}
178
179#[cfg(test)]
180mod tests {
181    use super::should_stop;
182
183    #[test]
184    fn bounded_recursion_stop_condition_triggers() {
185        assert!(should_stop(0.0005, 0.001));
186        assert!(!should_stop(0.01, 0.001));
187    }
188}