Skip to main content

dsfb_tmtr/
simulation.rs

1use serde::Serialize;
2
3use crate::causal::{
4    build_causal_graph, summarize_causal_graph, CausalGraph, CausalMetricsSummary,
5};
6use crate::config::SimulationConfig;
7use crate::metrics::{
8    build_prediction_tubes, notebook_ready_summary, summarize_scenario, NotebookReadySummary,
9    PredictionTubePoint, ScenarioSummaryRow,
10};
11use crate::observer::{simulate_observers, ObserverSeries};
12use crate::scenario::{scenario_suite, ScenarioDefinition};
13use crate::tmtr::{apply_tmtr, CorrectionEvent, RecursionStats};
14
15#[derive(Debug, Clone, Serialize)]
16pub struct ModeArtifacts {
17    pub mode: String,
18    pub observers: Vec<ObserverSeries>,
19    pub correction_events: Vec<CorrectionEvent>,
20    pub prediction_tubes: Vec<PredictionTubePoint>,
21    pub causal_graph: CausalGraph,
22    pub causal_metrics: CausalMetricsSummary,
23    pub recursion_stats: RecursionStats,
24}
25
26#[derive(Debug, Clone, Serialize)]
27pub struct ScenarioArtifacts {
28    pub definition: ScenarioDefinition,
29    pub truth: Vec<f64>,
30    pub baseline: ModeArtifacts,
31    pub tmtr: ModeArtifacts,
32    pub summary: ScenarioSummaryRow,
33}
34
35#[derive(Debug, Clone, Serialize)]
36pub struct SimulationRun {
37    pub config: SimulationConfig,
38    pub config_hash: String,
39    pub scenarios: Vec<ScenarioArtifacts>,
40    pub notebook_summary: NotebookReadySummary,
41}
42
43impl SimulationRun {
44    pub fn stable_signature(&self) -> anyhow::Result<String> {
45        #[derive(Serialize)]
46        struct StableView<'a> {
47            config: &'a SimulationConfig,
48            config_hash: &'a str,
49            scenarios: &'a [ScenarioArtifacts],
50        }
51        Ok(serde_json::to_string(&StableView {
52            config: &self.config,
53            config_hash: &self.config_hash,
54            scenarios: &self.scenarios,
55        })?)
56    }
57}
58
59pub fn run_simulation(config: &SimulationConfig) -> anyhow::Result<SimulationRun> {
60    let scenarios = scenario_suite(config)
61        .into_iter()
62        .map(|definition| run_single_scenario(config, definition))
63        .collect::<anyhow::Result<Vec<_>>>()?;
64    let notebook_summary =
65        notebook_ready_summary(&config.output_root, &collect_summaries(&scenarios));
66    Ok(SimulationRun {
67        config: config.clone(),
68        config_hash: config.stable_hash()?,
69        scenarios,
70        notebook_summary,
71    })
72}
73
74fn run_single_scenario(
75    config: &SimulationConfig,
76    definition: ScenarioDefinition,
77) -> anyhow::Result<ScenarioArtifacts> {
78    let truth = definition.truth_series();
79    let (specs, baseline_observers) = simulate_observers(&definition, &truth);
80    let baseline_primary = baseline_observers[0].clone();
81    let baseline_tubes = build_prediction_tubes(&definition, "baseline", &baseline_primary, &truth);
82    let baseline_graph = build_causal_graph(
83        &definition.name,
84        "baseline",
85        &baseline_observers,
86        &[],
87        config.min_trust_gap,
88    );
89    let baseline_causal = summarize_causal_graph(&baseline_graph, definition.delta);
90
91    let tmtr_result = apply_tmtr(&definition, config, &specs, &baseline_observers, &truth);
92    let tmtr_primary = tmtr_result.observers[0].clone();
93    let tmtr_tubes = build_prediction_tubes(&definition, "tmtr", &tmtr_primary, &truth);
94    let tmtr_graph = build_causal_graph(
95        &definition.name,
96        "tmtr",
97        &tmtr_result.observers,
98        &tmtr_result.correction_events,
99        config.min_trust_gap,
100    );
101    let tmtr_causal = summarize_causal_graph(&tmtr_graph, definition.delta);
102
103    let summary = summarize_scenario(
104        &definition,
105        &baseline_primary,
106        &tmtr_primary,
107        &baseline_tubes,
108        &tmtr_tubes,
109        &baseline_causal,
110        &tmtr_causal,
111        &tmtr_result.recursion_stats,
112    );
113
114    Ok(ScenarioArtifacts {
115        definition,
116        truth,
117        baseline: ModeArtifacts {
118            mode: "baseline".to_string(),
119            observers: baseline_observers,
120            correction_events: Vec::new(),
121            prediction_tubes: baseline_tubes,
122            causal_graph: baseline_graph,
123            causal_metrics: baseline_causal,
124            recursion_stats: RecursionStats {
125                total_correction_events: 0,
126                max_recursion_depth: 0,
127                mean_recursion_depth: 0.0,
128                convergence_iterations: 0,
129                average_correction_magnitude: 0.0,
130                average_correction_trust_weight: 0.0,
131                monotonicity_violations: 0,
132            },
133        },
134        tmtr: ModeArtifacts {
135            mode: "tmtr".to_string(),
136            observers: tmtr_result.observers,
137            correction_events: tmtr_result.correction_events,
138            prediction_tubes: tmtr_tubes,
139            causal_graph: tmtr_graph,
140            causal_metrics: tmtr_causal,
141            recursion_stats: tmtr_result.recursion_stats,
142        },
143        summary,
144    })
145}
146
147fn collect_summaries(scenarios: &[ScenarioArtifacts]) -> Vec<ScenarioSummaryRow> {
148    scenarios
149        .iter()
150        .map(|scenario| scenario.summary.clone())
151        .collect()
152}
153
154#[cfg(test)]
155mod tests {
156    use crate::config::SimulationConfig;
157    use crate::simulation::run_simulation;
158
159    #[test]
160    fn deterministic_signature_is_stable() {
161        let config = SimulationConfig {
162            n_steps: 240,
163            ..SimulationConfig::default()
164        };
165        let first = run_simulation(&config).expect("first simulation");
166        let second = run_simulation(&config).expect("second simulation");
167        assert_eq!(first.config_hash, second.config_hash);
168        assert_eq!(
169            first.stable_signature().expect("first signature"),
170            second.stable_signature().expect("second signature")
171        );
172    }
173}