use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ViolationSeverity {
Warning,
Critical,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConservationViolationDetail {
pub law_name: String,
pub quantity: String,
pub initial_value: f64,
pub final_value: f64,
pub absolute_change: f64,
pub relative_change: f64,
pub tolerance: f64,
pub severity: ViolationSeverity,
pub step_index: Option<usize>,
}
impl ConservationViolationDetail {
pub(crate) fn new(
law_name: impl Into<String>,
quantity: impl Into<String>,
initial_value: f64,
final_value: f64,
tolerance: f64,
step_index: Option<usize>,
) -> Self {
let absolute_change = final_value - initial_value;
let relative_change = if initial_value.abs() > 1e-300 {
(absolute_change / initial_value).abs()
} else {
f64::NAN
};
let severity = if absolute_change.abs() > 10.0 * tolerance {
ViolationSeverity::Critical
} else {
ViolationSeverity::Warning
};
Self {
law_name: law_name.into(),
quantity: quantity.into(),
initial_value,
final_value,
absolute_change,
relative_change,
tolerance,
severity,
step_index,
}
}
pub fn is_critical(&self) -> bool {
self.severity == ViolationSeverity::Critical
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConservationReport {
pub checker_name: String,
pub passed: bool,
pub violations: Vec<ConservationViolationDetail>,
pub states_checked: usize,
pub max_absolute_change: f64,
}
impl ConservationReport {
pub(crate) fn new(checker_name: impl Into<String>) -> Self {
Self {
checker_name: checker_name.into(),
passed: true,
violations: Vec::new(),
states_checked: 0,
max_absolute_change: 0.0,
}
}
pub(crate) fn add_violation(&mut self, detail: ConservationViolationDetail) {
self.max_absolute_change = self.max_absolute_change.max(detail.absolute_change.abs());
self.violations.push(detail);
self.passed = false;
}
pub(crate) fn track_change(&mut self, change: f64) {
self.max_absolute_change = self.max_absolute_change.max(change.abs());
}
pub fn has_critical_violations(&self) -> bool {
self.violations.iter().any(|v| v.is_critical())
}
pub fn critical_violations(&self) -> impl Iterator<Item = &ConservationViolationDetail> {
self.violations.iter().filter(|v| v.is_critical())
}
pub fn summary(&self) -> String {
if self.passed {
format!(
"{}: PASS ({} states checked)",
self.checker_name, self.states_checked
)
} else {
format!(
"{}: FAIL ({} violations, max |ΔQ|={:.3e})",
self.checker_name,
self.violations.len(),
self.max_absolute_change
)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum PhysicalSymmetry {
TimeTranslation,
SpatialTranslation,
Rotation,
}
impl PhysicalSymmetry {
pub fn name(self) -> &'static str {
match self {
Self::TimeTranslation => "Time Translation",
Self::SpatialTranslation => "Spatial Translation",
Self::Rotation => "Rotational",
}
}
pub fn conserved_quantity(self) -> &'static str {
match self {
Self::TimeTranslation => "energy",
Self::SpatialTranslation => "linear momentum",
Self::Rotation => "angular momentum",
}
}
}
#[derive(Debug, Clone)]
pub struct NoetherCheckResult {
pub symmetry: PhysicalSymmetry,
pub conserved: bool,
pub report: ConservationReport,
}