use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Ternary {
Conserved = 1,
Approximate = 0,
Violated = -1,
}
impl Ternary {
pub fn from_i8(v: i8) -> Self {
match v {
1 => Ternary::Conserved,
0 => Ternary::Approximate,
_ => Ternary::Violated,
}
}
pub fn is_ok(self) -> bool {
self != Ternary::Violated
}
}
impl fmt::Display for Ternary {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Ternary::Conserved => write!(f, "+1 (conserved)"),
Ternary::Approximate => write!(f, " 0 (approximate)"),
Ternary::Violated => write!(f, "-1 (violated)"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ConservationLaw {
Energy,
Mass,
Information,
Custom(String),
}
impl fmt::Display for ConservationLaw {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ConservationLaw::Energy => write!(f, "energy"),
ConservationLaw::Mass => write!(f, "mass"),
ConservationLaw::Information => write!(f, "information"),
ConservationLaw::Custom(name) => write!(f, "custom({})", name),
}
}
}
#[derive(Debug, Clone)]
pub struct VerificationResult {
pub law: ConservationLaw,
pub before: f64,
pub after: f64,
pub delta: f64,
pub epsilon: f64,
pub verdict: Ternary,
}
impl VerificationResult {
pub fn check(law: ConservationLaw, before: f64, after: f64, epsilon: f64) -> Self {
let delta = (after - before).abs();
let verdict = if delta == 0.0 {
Ternary::Conserved
} else if delta <= epsilon {
Ternary::Approximate
} else {
Ternary::Violated
};
VerificationResult {
law,
before,
after,
delta,
epsilon,
verdict,
}
}
}
impl fmt::Display for VerificationResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"[{}] {} → {} (Δ={}, ε={})",
self.verdict, self.before, self.after, self.delta, self.epsilon
)
}
}
#[derive(Debug, Clone)]
pub struct ConservationMonitor {
epsilon: f64,
snapshots: Vec<(ConservationLaw, String, f64)>,
results: Vec<VerificationResult>,
}
impl ConservationMonitor {
pub fn new(epsilon: f64) -> Self {
ConservationMonitor {
epsilon,
snapshots: Vec::new(),
results: Vec::new(),
}
}
pub fn snapshot(&mut self, law: ConservationLaw, label: impl Into<String>, value: f64) {
self.snapshots.push((law, label.into(), value));
}
pub fn verify(&mut self, law: &ConservationLaw, label: &str, after_value: f64) -> Option<VerificationResult> {
let idx = self.snapshots.iter().position(|(l, lb, _)| l == law && lb == label)?;
let (law, _, before) = self.snapshots.remove(idx);
let result = VerificationResult::check(law, before, after_value, self.epsilon);
self.results.push(result.clone());
Some(result)
}
pub fn verify_all<F>(&mut self, mut after_fn: F) -> Vec<VerificationResult>
where
F: FnMut(&ConservationLaw, &str) -> f64,
{
let snapshots = std::mem::take(&mut self.snapshots);
let mut batch = Vec::new();
for (law, label, before) in snapshots {
let after = after_fn(&law, &label);
let result = VerificationResult::check(law, before, after, self.epsilon);
batch.push(result.clone());
self.results.push(result);
}
batch
}
pub fn results(&self) -> &[VerificationResult] {
&self.results
}
pub fn pending(&self) -> usize {
self.snapshots.len()
}
pub fn reset(&mut self) {
self.snapshots.clear();
self.results.clear();
}
}
#[derive(Debug, Clone)]
pub struct ConservationBudget {
drifts: Vec<(ConservationLaw, f64)>,
alert_threshold: f64,
total_verifications: u64,
total_violations: u64,
drift_history: Vec<f64>,
}
impl ConservationBudget {
pub fn new(alert_threshold: f64) -> Self {
ConservationBudget {
drifts: Vec::new(),
alert_threshold,
total_verifications: 0,
total_violations: 0,
drift_history: Vec::new(),
}
}
pub fn record(&mut self, result: &VerificationResult) {
self.total_verifications += 1;
if result.verdict == Ternary::Violated {
self.total_violations += 1;
}
self.drift_history.push(result.delta);
if let Some(entry) = self.drifts.iter_mut().find(|(l, _)| l == &result.law) {
entry.1 += result.delta;
} else {
self.drifts.push((result.law.clone(), result.delta));
}
}
pub fn record_all(&mut self, results: &[VerificationResult]) {
for r in results {
self.record(r);
}
}
pub fn drift_for(&self, law: &ConservationLaw) -> f64 {
self.drifts
.iter()
.find(|(l, _)| l == law)
.map(|&(_, d)| d)
.unwrap_or(0.0)
}
pub fn total_drift(&self) -> f64 {
self.drifts.iter().map(|(_, d)| d).sum()
}
pub fn check_alert(&self) -> Option<Alert> {
let total = self.total_drift();
if total > self.alert_threshold {
Some(Alert {
total_drift: total,
threshold: self.alert_threshold,
worst_law: self.drifts.iter().max_by(|a, b| a.1.partial_cmp(&b.1).unwrap()).map(|(l, _)| l.clone()),
})
} else {
None
}
}
pub fn verification_count(&self) -> u64 {
self.total_verifications
}
pub fn violation_count(&self) -> u64 {
self.total_violations
}
pub fn average_drift(&self) -> f64 {
if self.drift_history.is_empty() {
0.0
} else {
self.drift_history.iter().sum::<f64>() / self.drift_history.len() as f64
}
}
pub fn violation_rate(&self) -> f64 {
if self.total_verifications == 0 {
0.0
} else {
self.total_violations as f64 / self.total_verifications as f64
}
}
}
#[derive(Debug, Clone)]
pub struct Alert {
pub total_drift: f64,
pub threshold: f64,
pub worst_law: Option<ConservationLaw>,
}
impl fmt::Display for Alert {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"CONSERVATION ALERT: drift {:.6} exceeds threshold {:.6}",
self.total_drift, self.threshold
)?;
if let Some(ref law) = self.worst_law {
write!(f, " (worst: {})", law)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ternary_exact_conservation() {
let result = VerificationResult::check(
ConservationLaw::Energy,
100.0,
100.0,
1e-9,
);
assert_eq!(result.verdict, Ternary::Conserved);
assert_eq!(result.delta, 0.0);
}
#[test]
fn ternary_approximate_conservation() {
let result = VerificationResult::check(
ConservationLaw::Mass,
50.0,
50.0005,
0.001,
);
assert_eq!(result.verdict, Ternary::Approximate);
assert!(result.delta > 0.0);
assert!(result.delta <= 0.001);
}
#[test]
fn ternary_violation() {
let result = VerificationResult::check(
ConservationLaw::Information,
200.0,
195.0,
1.0,
);
assert_eq!(result.verdict, Ternary::Violated);
assert_eq!(result.delta, 5.0);
}
#[test]
fn monitor_snapshot_and_verify() {
let mut mon = ConservationMonitor::new(0.01);
mon.snapshot(ConservationLaw::Energy, "kernel_a", 42.0);
assert_eq!(mon.pending(), 1);
let result = mon.verify(&ConservationLaw::Energy, "kernel_a", 42.005).unwrap();
assert_eq!(result.verdict, Ternary::Approximate);
assert_eq!(mon.pending(), 0);
assert_eq!(mon.results().len(), 1);
}
#[test]
fn monitor_verify_missing_returns_none() {
let mut mon = ConservationMonitor::new(0.01);
assert!(mon.verify(&ConservationLaw::Energy, "nope", 0.0).is_none());
}
#[test]
fn monitor_verify_all_batch() {
let mut mon = ConservationMonitor::new(0.1);
mon.snapshot(ConservationLaw::Energy, "k1", 10.0);
mon.snapshot(ConservationLaw::Mass, "k2", 20.0);
mon.snapshot(ConservationLaw::Information, "k3", 30.0);
let results = mon.verify_all(|law, _label| match law {
ConservationLaw::Energy => 10.0,
ConservationLaw::Mass => 20.05,
ConservationLaw::Information => 30.2,
_ => 0.0,
});
assert_eq!(results.len(), 3);
assert_eq!(results[0].verdict, Ternary::Conserved);
assert_eq!(results[1].verdict, Ternary::Approximate);
assert_eq!(results[2].verdict, Ternary::Violated);
}
#[test]
fn budget_tracks_drift_and_alerts() {
let mut budget = ConservationBudget::new(5.0);
for _ in 0..3 {
let r = VerificationResult::check(ConservationLaw::Energy, 100.0, 98.0, 0.5);
budget.record(&r);
}
assert_eq!(budget.verification_count(), 3);
assert_eq!(budget.violation_count(), 3); assert!((budget.drift_for(&ConservationLaw::Energy) - 6.0).abs() < 1e-9);
assert!(budget.check_alert().is_some()); }
#[test]
fn budget_statistics() {
let mut budget = ConservationBudget::new(100.0);
let r1 = VerificationResult::check(ConservationLaw::Mass, 10.0, 10.0, 0.01);
let r2 = VerificationResult::check(ConservationLaw::Mass, 10.0, 10.02, 0.01);
let r3 = VerificationResult::check(ConservationLaw::Mass, 10.0, 10.5, 0.01);
budget.record_all(&[r1, r2, r3]);
assert_eq!(budget.verification_count(), 3);
assert_eq!(budget.violation_count(), 2); let avg = budget.average_drift();
assert!(avg > 0.0);
let rate = budget.violation_rate();
assert!((rate - 2.0 / 3.0).abs() < 1e-9);
}
#[test]
fn custom_conservation_law() {
let custom = ConservationLaw::Custom("angular_momentum".into());
let result = VerificationResult::check(custom.clone(), 7.0, 7.0, 0.0);
assert_eq!(result.verdict, Ternary::Conserved);
assert_eq!(result.law, custom);
}
}