use crate::entropy::VerificationEntropy;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct EntropyViolation {
pub description: String,
pub h_before: f64,
pub h_after: f64,
pub delta: f64,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ConservationReport {
pub before: f64,
pub after: f64,
pub delta: f64,
pub violations: Vec<EntropyViolation>,
}
impl ConservationReport {
pub fn from_snapshots(snapshots: &[VerificationEntropy]) -> Self {
if snapshots.is_empty() {
return Self {
before: 0.0,
after: 0.0,
delta: 0.0,
violations: vec![],
};
}
let before = snapshots[0].shannon;
let after = snapshots.last().unwrap().shannon;
let delta = after - before;
let mut violations = Vec::new();
for window in snapshots.windows(2) {
let h_before = window[0].shannon;
let h_after = window[1].shannon;
let d = h_after - h_before;
if d > 1e-12 {
violations.push(EntropyViolation {
description: format!("entropy increased by {:.6} bits", d),
h_before,
h_after,
delta: d,
});
}
}
Self {
before,
after,
delta,
violations,
}
}
pub fn is_conserved(&self) -> bool {
self.delta <= 1e-12
}
pub fn total_violation(&self) -> f64 {
self.violations.iter().map(|v| v.delta).sum()
}
}
pub fn check_conservation(p_before: &[f64], p_after: &[f64]) -> Result<ConservationReport, crate::EntropyError> {
let ve_before = VerificationEntropy::from_probabilities(p_before)?;
let ve_after = VerificationEntropy::from_probabilities(p_after)?;
Ok(ConservationReport::from_snapshots(&[ve_before, ve_after]))
}
#[cfg(test)]
mod tests {
use super::*;
fn make_ve(shannon: f64) -> VerificationEntropy {
VerificationEntropy {
shannon,
renyi: Default::default(),
tsallis: 0.0,
}
}
#[test]
fn decreasing_entropy_is_conserved() {
let snapshots = vec![make_ve(2.0), make_ve(1.8), make_ve(1.5), make_ve(1.2)];
let report = ConservationReport::from_snapshots(&snapshots);
assert!(report.is_conserved());
assert!(report.violations.is_empty());
}
#[test]
fn increasing_entropy_flags_violations() {
let snapshots = vec![make_ve(1.0), make_ve(1.5), make_ve(1.2), make_ve(2.0)];
let report = ConservationReport::from_snapshots(&snapshots);
assert!(!report.is_conserved());
assert!(!report.violations.is_empty());
assert_eq!(report.violations.len(), 2, "two increases in the sequence");
}
#[test]
fn net_decrease_with_local_increases() {
let snapshots = vec![make_ve(2.0), make_ve(2.3), make_ve(1.8)];
let report = ConservationReport::from_snapshots(&snapshots);
assert!(report.is_conserved(), "net delta is negative");
assert_eq!(report.violations.len(), 1);
}
#[test]
fn check_conservation_api() {
let before = vec![0.25, 0.25, 0.25, 0.25]; let after = vec![0.5, 0.3, 0.1, 0.1]; let report = check_conservation(&before, &after).unwrap();
assert!(report.is_conserved());
}
}