Skip to main content

entropy_conservation/
conservation.rs

1//! Conservation law enforcement: track H over code changes,
2//! detect entropy-increasing commits, compute entropy gradient.
3
4use crate::entropy::VerificationEntropy;
5
6/// A single violation of the conservation law dH/dt ≤ 0.
7#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
8pub struct EntropyViolation {
9    /// Description of what changed.
10    pub description: String,
11    /// Entropy before the change.
12    pub h_before: f64,
13    /// Entropy after the change.
14    pub h_after: f64,
15    /// Magnitude of increase.
16    pub delta: f64,
17}
18
19/// Report on conservation-law compliance for a sequence of changes.
20#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
21pub struct ConservationReport {
22    /// Shannon entropy before changes.
23    pub before: f64,
24    /// Shannon entropy after changes.
25    pub after: f64,
26    /// Net change in entropy.
27    pub delta: f64,
28    /// Individual violations (entropy increases).
29    pub violations: Vec<EntropyViolation>,
30}
31
32impl ConservationReport {
33    /// Build a conservation report from a sequence of entropy snapshots.
34    ///
35    /// Each pair of consecutive values represents a change. Any increase
36    /// is flagged as a violation.
37    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    /// Whether the overall change obeys the conservation law.
75    pub fn is_conserved(&self) -> bool {
76        self.delta <= 1e-12
77    }
78
79    /// Severity: total magnitude of all violations.
80    pub fn total_violation(&self) -> f64 {
81        self.violations.iter().map(|v| v.delta).sum()
82    }
83}
84
85/// Given a "before" and "after" probability distribution, check conservation.
86pub 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        // Net decrease but one local violation
126        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]; // H = 2.0
133        let after = vec![0.5, 0.3, 0.1, 0.1];      // H < 2.0
134        let report = check_conservation(&before, &after).unwrap();
135        assert!(report.is_conserved());
136    }
137}