pacr_types/ets.rs
1//! Pillar: II. PACR field: Ω (Resource Triple).
2//!
3//! Three physically coupled resource axes that cannot be simultaneously
4//! minimised — the Energy-Time-Space trilemma:
5//!
6//! E — energy consumed (joules) conservation of energy
7//! T — execution time (seconds) Margolus–Levitin: T ≥ πℏ/2E
8//! S — memory/storage used (bytes) Bremermann / Holevo–von Neumann
9//!
10//! These three axes lie on a 2-D constraint surface, not three independent axes.
11//! Storing them together as `ResourceTriple` makes their coupling explicit and
12//! enables the Margolus–Levitin consistency check at every append.
13
14use crate::estimate::Estimate;
15use crate::landauer::LandauerCost;
16use crate::landauer::H_BAR;
17
18// ── Core type ─────────────────────────────────────────────────────────────────
19
20/// The resource constraint triple (E, T, S) for a computation event.
21///
22/// All three values use `Estimate<f64>` to carry measurement uncertainty.
23/// See [`ResourceTriple::validate_physics`] for the physical consistency checks.
24///
25/// PACR field Ω. Derived from Pillar II (conservation laws + Margolus–Levitin).
26#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
27pub struct ResourceTriple {
28 /// Actual energy consumed (joules). Must satisfy `energy.point ≥ Λ.point`.
29 pub energy: Estimate<f64>,
30 /// Actual execution duration (seconds). Must satisfy Margolus–Levitin bound.
31 pub time: Estimate<f64>,
32 /// Actual memory/storage used (bytes, as f64 for SI-unit consistency).
33 pub space: Estimate<f64>,
34}
35
36impl ResourceTriple {
37 /// Validates physical consistency of this triple.
38 ///
39 /// Returns every violation found; an empty `Vec` means the record is clean.
40 /// A physically invalid record is still storable (measurement errors happen),
41 /// but violations must be flagged for the Landauer auditor to investigate.
42 #[must_use]
43 pub fn validate_physics(&self) -> Vec<PhysicsViolation> {
44 let mut v: Vec<PhysicsViolation> = Vec::new();
45
46 if self.energy.point < 0.0 {
47 v.push(PhysicsViolation::NegativeEnergy);
48 }
49 if self.time.point <= 0.0 {
50 v.push(PhysicsViolation::NonPositiveTime);
51 }
52 if self.space.point < 0.0 {
53 v.push(PhysicsViolation::NegativeSpace);
54 }
55
56 // Margolus–Levitin: T ≥ π·ℏ / (2·E)
57 // Checked for completeness; macroscopically relevant at femtojoule scale
58 // and for future sub-quantum extensions.
59 if self.energy.point > 0.0 {
60 let t_min = std::f64::consts::PI * H_BAR / (2.0 * self.energy.point);
61 if self.time.point < t_min {
62 v.push(PhysicsViolation::MargolusLevitinViolated {
63 actual_s: self.time.point,
64 minimum_s: t_min,
65 });
66 }
67 }
68
69 v
70 }
71
72 /// Thermodynamic waste = E − Λ with conservative uncertainty propagation.
73 ///
74 /// Uncertainty is propagated as:
75 /// `waste.lower = energy.lower − λ.upper` (minimum possible waste)
76 /// `waste.upper = energy.upper − λ.lower` (maximum possible waste)
77 #[must_use]
78 pub fn thermodynamic_waste(&self, landauer: &LandauerCost) -> Estimate<f64> {
79 Estimate {
80 point: self.energy.point - landauer.point,
81 lower: self.energy.lower - landauer.upper,
82 upper: self.energy.upper - landauer.lower,
83 }
84 }
85}
86
87// ── Violation enum ────────────────────────────────────────────────────────────
88
89/// A physical-law violation detected in a [`ResourceTriple`].
90#[derive(Debug, Clone, thiserror::Error)]
91#[non_exhaustive]
92pub enum PhysicsViolation {
93 /// Energy is negative — violates conservation of energy.
94 #[error("energy is negative — violates conservation of energy")]
95 NegativeEnergy,
96
97 /// Time is non-positive — violates causality.
98 #[error("time is non-positive — violates causality")]
99 NonPositiveTime,
100
101 /// Space is negative — physically impossible.
102 #[error("space is negative — physically impossible")]
103 NegativeSpace,
104
105 /// Margolus–Levitin theorem violated: T < π·ℏ / (2·E).
106 #[error("Margolus–Levitin violated: T={actual_s:.3e} s < T_min={minimum_s:.3e} s")]
107 MargolusLevitinViolated {
108 /// Measured execution time (seconds).
109 actual_s: f64,
110 /// Minimum allowed time (seconds) at this energy.
111 minimum_s: f64,
112 },
113}
114
115// ── Unit tests ────────────────────────────────────────────────────────────────
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use crate::estimate::Estimate;
121
122 fn valid_triple() -> ResourceTriple {
123 ResourceTriple {
124 energy: Estimate::exact(1e-19),
125 time: Estimate::exact(1e-9),
126 space: Estimate::exact(128.0),
127 }
128 }
129
130 #[test]
131 fn valid_triple_has_no_violations() {
132 assert!(valid_triple().validate_physics().is_empty());
133 }
134
135 #[test]
136 fn negative_energy_is_flagged() {
137 let mut t = valid_triple();
138 t.energy = Estimate {
139 point: -1.0,
140 lower: -2.0,
141 upper: 0.0,
142 };
143 let v = t.validate_physics();
144 assert!(v
145 .iter()
146 .any(|e| matches!(e, PhysicsViolation::NegativeEnergy)));
147 }
148
149 #[test]
150 fn zero_time_is_flagged() {
151 let mut t = valid_triple();
152 t.time = Estimate::exact(0.0);
153 let v = t.validate_physics();
154 assert!(v
155 .iter()
156 .any(|e| matches!(e, PhysicsViolation::NonPositiveTime)));
157 }
158
159 #[test]
160 fn negative_space_is_flagged() {
161 let mut t = valid_triple();
162 t.space = Estimate {
163 point: -1.0,
164 lower: -2.0,
165 upper: 0.0,
166 };
167 let v = t.validate_physics();
168 assert!(v
169 .iter()
170 .any(|e| matches!(e, PhysicsViolation::NegativeSpace)));
171 }
172
173 #[test]
174 fn thermodynamic_waste_propagates_uncertainty() {
175 let triple = ResourceTriple {
176 energy: Estimate::new(1e-19, 0.8e-19, 1.2e-19).unwrap(),
177 time: Estimate::exact(1e-9),
178 space: Estimate::exact(0.0),
179 };
180 let lambda = Estimate::new(1e-20, 0.5e-20, 2.0e-20).unwrap();
181 let waste = triple.thermodynamic_waste(&lambda);
182
183 assert!((waste.point - (1e-19 - 1e-20)).abs() < 1e-30);
184 // lower = energy.lower - lambda.upper = 0.8e-19 - 2.0e-20
185 assert!((waste.lower - (0.8e-19 - 2.0e-20)).abs() < 1e-30);
186 // upper = energy.upper - lambda.lower = 1.2e-19 - 0.5e-20
187 assert!((waste.upper - (1.2e-19 - 0.5e-20)).abs() < 1e-30);
188 }
189}