entropy_conservation/
conservation.rs1use crate::entropy::VerificationEntropy;
5
6#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
8pub struct EntropyViolation {
9 pub description: String,
11 pub h_before: f64,
13 pub h_after: f64,
15 pub delta: f64,
17}
18
19#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
21pub struct ConservationReport {
22 pub before: f64,
24 pub after: f64,
26 pub delta: f64,
28 pub violations: Vec<EntropyViolation>,
30}
31
32impl ConservationReport {
33 pub fn from_snapshots(snapshots: &[VerificationEntropy]) -> Self {
38 if snapshots.is_empty() {
39 return Self {
40 before: 0.0,
41 after: 0.0,
42 delta: 0.0,
43 violations: vec![],
44 };
45 }
46
47 let before = snapshots[0].shannon;
48 let after = snapshots.last().unwrap().shannon;
49 let delta = after - before;
50
51 let mut violations = Vec::new();
52 for window in snapshots.windows(2) {
53 let h_before = window[0].shannon;
54 let h_after = window[1].shannon;
55 let d = h_after - h_before;
56 if d > 1e-12 {
57 violations.push(EntropyViolation {
58 description: format!("entropy increased by {:.6} bits", d),
59 h_before,
60 h_after,
61 delta: d,
62 });
63 }
64 }
65
66 Self {
67 before,
68 after,
69 delta,
70 violations,
71 }
72 }
73
74 pub fn is_conserved(&self) -> bool {
76 self.delta <= 1e-12
77 }
78
79 pub fn total_violation(&self) -> f64 {
81 self.violations.iter().map(|v| v.delta).sum()
82 }
83}
84
85pub fn check_conservation(p_before: &[f64], p_after: &[f64]) -> Result<ConservationReport, crate::EntropyError> {
87 let ve_before = VerificationEntropy::from_probabilities(p_before)?;
88 let ve_after = VerificationEntropy::from_probabilities(p_after)?;
89 Ok(ConservationReport::from_snapshots(&[ve_before, ve_after]))
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 fn make_ve(shannon: f64) -> VerificationEntropy {
97 VerificationEntropy {
98 shannon,
99 renyi: Default::default(),
100 tsallis: 0.0,
101 }
102 }
103
104 #[test]
105 fn decreasing_entropy_is_conserved() {
106 let snapshots = vec![make_ve(2.0), make_ve(1.8), make_ve(1.5), make_ve(1.2)];
107 let report = ConservationReport::from_snapshots(&snapshots);
108 assert!(report.is_conserved());
109 assert!(report.violations.is_empty());
110 }
111
112 #[test]
113 fn increasing_entropy_flags_violations() {
114 let snapshots = vec![make_ve(1.0), make_ve(1.5), make_ve(1.2), make_ve(2.0)];
115 let report = ConservationReport::from_snapshots(&snapshots);
116 assert!(!report.is_conserved());
117 assert!(!report.violations.is_empty());
118 assert_eq!(report.violations.len(), 2, "two increases in the sequence");
119 }
120
121 #[test]
122 fn net_decrease_with_local_increases() {
123 let snapshots = vec![make_ve(2.0), make_ve(2.3), make_ve(1.8)];
124 let report = ConservationReport::from_snapshots(&snapshots);
125 assert!(report.is_conserved(), "net delta is negative");
127 assert_eq!(report.violations.len(), 1);
128 }
129
130 #[test]
131 fn check_conservation_api() {
132 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();
135 assert!(report.is_conserved());
136 }
137}