Skip to main content

dsfb_tmtr/
config.rs

1use std::fs;
2use std::path::{Path, PathBuf};
3
4use anyhow::{Context, Result};
5use clap::{Parser, ValueEnum};
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ValueEnum)]
9#[serde(rename_all = "snake_case")]
10pub enum ScenarioSelection {
11    All,
12    DisturbanceRecovery,
13    ForwardPrediction,
14    HierarchyConsistency,
15    AerospaceNavigation,
16    RoboticsSensorOcclusion,
17    IndustrialFaultRefinement,
18    NeuralMultimodalDelay,
19}
20
21impl Default for ScenarioSelection {
22    fn default() -> Self {
23        Self::All
24    }
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ValueEnum)]
28#[serde(rename_all = "snake_case")]
29pub enum KernelKind {
30    Uniform,
31    Exponential,
32    ResonanceGated,
33}
34
35impl Default for KernelKind {
36    fn default() -> Self {
37        Self::ResonanceGated
38    }
39}
40
41#[derive(Debug, Clone, Parser)]
42pub struct Cli {
43    #[arg(long)]
44    pub config: Option<PathBuf>,
45    #[arg(long, value_enum)]
46    pub scenario: Option<ScenarioSelection>,
47    #[arg(long)]
48    pub output_root: Option<PathBuf>,
49    #[arg(long)]
50    pub n_steps: Option<usize>,
51    #[arg(long)]
52    pub delta: Option<usize>,
53    #[arg(long)]
54    pub prediction_horizon: Option<usize>,
55    #[arg(long)]
56    pub max_iterations: Option<usize>,
57    #[arg(long)]
58    pub max_recursion_depth: Option<usize>,
59    #[arg(long)]
60    pub convergence_tolerance: Option<f64>,
61    #[arg(long)]
62    pub trust_threshold: Option<f64>,
63    #[arg(long)]
64    pub min_trust_gap: Option<f64>,
65    #[arg(long, value_enum)]
66    pub kernel: Option<KernelKind>,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct SimulationConfig {
71    pub scenario: ScenarioSelection,
72    pub output_root: String,
73    pub n_steps: usize,
74    pub delta: usize,
75    pub prediction_horizon: usize,
76    pub max_iterations: usize,
77    pub max_recursion_depth: usize,
78    pub convergence_tolerance: f64,
79    pub trust_threshold: f64,
80    pub min_trust_gap: f64,
81    pub kernel: KernelKind,
82}
83
84impl Default for SimulationConfig {
85    fn default() -> Self {
86        Self {
87            scenario: ScenarioSelection::All,
88            output_root: default_output_root().display().to_string(),
89            n_steps: 1000,
90            delta: 48,
91            prediction_horizon: 32,
92            max_iterations: 6,
93            max_recursion_depth: 2,
94            convergence_tolerance: 1e-3,
95            trust_threshold: 0.62,
96            min_trust_gap: 0.04,
97            kernel: KernelKind::ResonanceGated,
98        }
99    }
100}
101
102impl SimulationConfig {
103    pub fn from_cli(cli: Cli) -> Result<Self> {
104        let mut config = if let Some(path) = cli.config.as_ref() {
105            Self::from_json_file(path)?
106        } else {
107            Self::default()
108        };
109
110        if let Some(scenario) = cli.scenario {
111            config.scenario = scenario;
112        }
113        if let Some(path) = cli.output_root {
114            config.output_root = path.display().to_string();
115        }
116        if let Some(n_steps) = cli.n_steps {
117            config.n_steps = n_steps.max(64);
118        }
119        if let Some(delta) = cli.delta {
120            config.delta = delta.max(4);
121        }
122        if let Some(prediction_horizon) = cli.prediction_horizon {
123            config.prediction_horizon = prediction_horizon.max(4);
124        }
125        if let Some(max_iterations) = cli.max_iterations {
126            config.max_iterations = max_iterations.max(1);
127        }
128        if let Some(max_recursion_depth) = cli.max_recursion_depth {
129            config.max_recursion_depth = max_recursion_depth.max(1);
130        }
131        if let Some(convergence_tolerance) = cli.convergence_tolerance {
132            config.convergence_tolerance = convergence_tolerance.max(1e-9);
133        }
134        if let Some(trust_threshold) = cli.trust_threshold {
135            config.trust_threshold = trust_threshold.clamp(0.0, 1.0);
136        }
137        if let Some(min_trust_gap) = cli.min_trust_gap {
138            config.min_trust_gap = min_trust_gap.max(0.0);
139        }
140        if let Some(kernel) = cli.kernel {
141            config.kernel = kernel;
142        }
143        config.prediction_horizon = config
144            .prediction_horizon
145            .min(config.n_steps.saturating_sub(1));
146        config.delta = config.delta.min(config.n_steps.saturating_sub(2));
147        Ok(config)
148    }
149
150    pub fn from_json_file(path: &Path) -> Result<Self> {
151        let raw = fs::read_to_string(path)
152            .with_context(|| format!("failed to read config file {}", path.display()))?;
153        let mut config: Self = serde_json::from_str(&raw)
154            .with_context(|| format!("failed to parse config file {}", path.display()))?;
155        if config.output_root.is_empty() {
156            config.output_root = default_output_root().display().to_string();
157        }
158        Ok(config)
159    }
160
161    pub fn output_root_path(&self) -> PathBuf {
162        PathBuf::from(&self.output_root)
163    }
164
165    pub fn stable_hash(&self) -> Result<String> {
166        let json = serde_json::to_string(self).context("failed to serialize config for hashing")?;
167        Ok(stable_hash_bytes(json.as_bytes()))
168    }
169}
170
171pub fn default_output_root() -> PathBuf {
172    Path::new(env!("CARGO_MANIFEST_DIR"))
173        .join("..")
174        .join("..")
175        .join("output-dsfb-tmtr")
176}
177
178pub fn stable_hash_bytes(bytes: &[u8]) -> String {
179    let mut hash: u64 = 0xcbf29ce484222325;
180    for byte in bytes {
181        hash ^= u64::from(*byte);
182        hash = hash.wrapping_mul(0x100000001b3);
183    }
184    format!("{hash:016x}")
185}