use crate::conservation::ConservationLaw;
use crate::drift::DriftDetector;
use crate::error::Result;
use crate::report::Report;
use crate::symmetry::Symmetry;
#[derive(Debug, Clone)]
pub struct MonitorSnapshot {
pub time: f64,
pub values: Vec<f64>,
pub violations: Vec<usize>,
}
#[derive(Debug, Clone)]
pub struct ConservationMonitor {
pub laws: Vec<ConservationLaw>,
pub history: Vec<MonitorSnapshot>,
pub drift_detectors: Vec<DriftDetector>,
}
impl ConservationMonitor {
pub fn new() -> Self {
Self {
laws: Vec::new(),
history: Vec::new(),
drift_detectors: Vec::new(),
}
}
pub fn newtonian(tolerance: f64) -> Self {
let mut m = Self::new();
m.laws.push(ConservationLaw::new(
Symmetry::TimeTranslation,
0.0,
tolerance,
));
m.laws.push(ConservationLaw::new(
Symmetry::SpatialTranslation,
0.0,
tolerance,
));
m.laws.push(ConservationLaw::new(Symmetry::Rotation, 0.0, tolerance));
m.drift_detectors = m
.laws
.iter()
.enumerate()
.map(|(i, law)| DriftDetector::new(i, law.tolerance * 10.0))
.collect();
m
}
pub fn hamiltonian(tolerance: f64) -> Self {
let mut m = Self::new();
m.laws.push(ConservationLaw::new(
Symmetry::TimeTranslation,
0.0,
tolerance,
));
m.drift_detectors = m
.laws
.iter()
.enumerate()
.map(|(i, law)| DriftDetector::new(i, law.tolerance * 10.0))
.collect();
m
}
pub fn custom(laws: Vec<ConservationLaw>) -> Self {
let drift_detectors = laws
.iter()
.enumerate()
.map(|(i, law)| DriftDetector::new(i, law.tolerance * 10.0))
.collect();
Self {
laws,
history: Vec::new(),
drift_detectors,
}
}
pub fn expected_value_count(&self) -> usize {
self.laws.iter().map(|l| l.symmetry.component_count()).sum()
}
pub fn add_law(&mut self, law: ConservationLaw) {
let idx = self.laws.len();
self.laws.push(law);
self.drift_detectors
.push(DriftDetector::new(idx, self.laws.last().unwrap().tolerance * 10.0));
}
pub fn tick(&mut self, time: f64, values: &[f64]) -> Result<()> {
let expected = self.expected_value_count();
if values.len() != expected {
return Err(crate::error::Error::ValueCountMismatch {
expected,
got: values.len(),
});
}
let mut violations = Vec::new();
let mut offset = 0;
for (i, law) in self.laws.iter().enumerate() {
let count = law.symmetry.component_count();
if !law.is_conserved(values[offset]) {
violations.push(i);
}
if let Some(detector) = self.drift_detectors.get_mut(i) {
detector.record_drift(time, law.drift(values[offset]));
}
offset += count;
}
self.history.push(MonitorSnapshot {
time,
values: values.to_vec(),
violations: violations.clone(),
});
Ok(())
}
pub fn health(&self) -> f64 {
if self.history.is_empty() {
return 1.0;
}
let clean = self.history.iter().filter(|s| s.violations.is_empty()).count();
clean as f64 / self.history.len() as f64
}
pub fn total_violations(&self) -> usize {
self.history.iter().map(|s| s.violations.len()).sum()
}
pub fn report(&self) -> Report {
let violated_law_indices: Vec<usize> = self
.history
.iter()
.flat_map(|s| s.violations.iter().copied())
.collect::<std::collections::HashSet<_>>()
.into_iter()
.collect();
let mut violations = Vec::new();
for &idx in &violated_law_indices {
if let Some(law) = self.laws.get(idx) {
let times: Vec<f64> = self
.history
.iter()
.filter(|s| s.violations.contains(&idx))
.map(|s| s.time)
.collect();
let breaking_scale = self
.drift_detectors
.get(idx)
.and_then(|d| d.breaking_scale);
violations.push(crate::report::Violation {
law_name: law.name.clone(),
law_index: idx,
first_violation: times.first().copied(),
last_violation: times.last().copied(),
violation_count: times.len(),
breaking_scale,
});
}
}
Report {
total_ticks: self.history.len(),
health: self.health(),
total_violations: self.total_violations(),
violations,
}
}
}
impl Default for ConservationMonitor {
fn default() -> Self {
Self::new()
}
}