1use crate::conservation::ConservationLaw;
2use crate::drift::DriftDetector;
3use crate::error::Result;
4use crate::report::Report;
5use crate::symmetry::Symmetry;
6
7#[derive(Debug, Clone)]
9pub struct MonitorSnapshot {
10 pub time: f64,
11 pub values: Vec<f64>,
12 pub violations: Vec<usize>,
13}
14
15#[derive(Debug, Clone)]
17pub struct ConservationMonitor {
18 pub laws: Vec<ConservationLaw>,
19 pub history: Vec<MonitorSnapshot>,
20 pub drift_detectors: Vec<DriftDetector>,
21}
22
23impl ConservationMonitor {
24 pub fn new() -> Self {
26 Self {
27 laws: Vec::new(),
28 history: Vec::new(),
29 drift_detectors: Vec::new(),
30 }
31 }
32
33 pub fn newtonian(tolerance: f64) -> Self {
35 let mut m = Self::new();
36 m.laws.push(ConservationLaw::new(
37 Symmetry::TimeTranslation,
38 0.0,
39 tolerance,
40 ));
41 m.laws.push(ConservationLaw::new(
42 Symmetry::SpatialTranslation,
43 0.0,
44 tolerance,
45 ));
46 m.laws.push(ConservationLaw::new(Symmetry::Rotation, 0.0, tolerance));
47 m.drift_detectors = m
48 .laws
49 .iter()
50 .enumerate()
51 .map(|(i, law)| DriftDetector::new(i, law.tolerance * 10.0))
52 .collect();
53 m
54 }
55
56 pub fn hamiltonian(tolerance: f64) -> Self {
58 let mut m = Self::new();
59 m.laws.push(ConservationLaw::new(
60 Symmetry::TimeTranslation,
61 0.0,
62 tolerance,
63 ));
64 m.drift_detectors = m
65 .laws
66 .iter()
67 .enumerate()
68 .map(|(i, law)| DriftDetector::new(i, law.tolerance * 10.0))
69 .collect();
70 m
71 }
72
73 pub fn custom(laws: Vec<ConservationLaw>) -> Self {
75 let drift_detectors = laws
76 .iter()
77 .enumerate()
78 .map(|(i, law)| DriftDetector::new(i, law.tolerance * 10.0))
79 .collect();
80 Self {
81 laws,
82 history: Vec::new(),
83 drift_detectors,
84 }
85 }
86
87 pub fn expected_value_count(&self) -> usize {
89 self.laws.iter().map(|l| l.symmetry.component_count()).sum()
90 }
91
92 pub fn add_law(&mut self, law: ConservationLaw) {
94 let idx = self.laws.len();
95 self.laws.push(law);
96 self.drift_detectors
97 .push(DriftDetector::new(idx, self.laws.last().unwrap().tolerance * 10.0));
98 }
99
100 pub fn tick(&mut self, time: f64, values: &[f64]) -> Result<()> {
105 let expected = self.expected_value_count();
106 if values.len() != expected {
107 return Err(crate::error::Error::ValueCountMismatch {
108 expected,
109 got: values.len(),
110 });
111 }
112
113 let mut violations = Vec::new();
114 let mut offset = 0;
115 for (i, law) in self.laws.iter().enumerate() {
116 let count = law.symmetry.component_count();
117 if !law.is_conserved(values[offset]) {
119 violations.push(i);
120 }
121 if let Some(detector) = self.drift_detectors.get_mut(i) {
123 detector.record_drift(time, law.drift(values[offset]));
124 }
125 offset += count;
126 }
127
128 self.history.push(MonitorSnapshot {
129 time,
130 values: values.to_vec(),
131 violations: violations.clone(),
132 });
133
134 Ok(())
135 }
136
137 pub fn health(&self) -> f64 {
139 if self.history.is_empty() {
140 return 1.0;
141 }
142 let clean = self.history.iter().filter(|s| s.violations.is_empty()).count();
143 clean as f64 / self.history.len() as f64
144 }
145
146 pub fn total_violations(&self) -> usize {
148 self.history.iter().map(|s| s.violations.len()).sum()
149 }
150
151 pub fn report(&self) -> Report {
153 let violated_law_indices: Vec<usize> = self
154 .history
155 .iter()
156 .flat_map(|s| s.violations.iter().copied())
157 .collect::<std::collections::HashSet<_>>()
158 .into_iter()
159 .collect();
160
161 let mut violations = Vec::new();
162 for &idx in &violated_law_indices {
163 if let Some(law) = self.laws.get(idx) {
164 let times: Vec<f64> = self
165 .history
166 .iter()
167 .filter(|s| s.violations.contains(&idx))
168 .map(|s| s.time)
169 .collect();
170 let breaking_scale = self
171 .drift_detectors
172 .get(idx)
173 .and_then(|d| d.breaking_scale);
174 violations.push(crate::report::Violation {
175 law_name: law.name.clone(),
176 law_index: idx,
177 first_violation: times.first().copied(),
178 last_violation: times.last().copied(),
179 violation_count: times.len(),
180 breaking_scale,
181 });
182 }
183 }
184
185 Report {
186 total_ticks: self.history.len(),
187 health: self.health(),
188 total_violations: self.total_violations(),
189 violations,
190 }
191 }
192}
193
194impl Default for ConservationMonitor {
195 fn default() -> Self {
196 Self::new()
197 }
198}