sim_incremental_core/dataflow/
lattice.rs1use crate::ValueFingerprint;
4use std::{error::Error, fmt};
5
6pub trait StateSize {
8 fn state_size(&self) -> usize;
10}
11
12pub trait JoinSemilattice: Clone + Eq + StateSize {
17 fn bottom(&self) -> Self;
19
20 fn join(&self, other: &Self) -> Self;
22
23 fn less_equal(&self, other: &Self) -> bool;
25}
26
27pub trait TransferPolicy<S> {
32 fn fingerprint(&self) -> ValueFingerprint;
34
35 fn policy_size(&self) -> usize;
37
38 fn transfer(&self, state: &S) -> S;
40}
41
42#[derive(Clone, Copy, Debug, Eq, PartialEq)]
44pub enum DataflowLaw {
45 JoinIdempotent,
47 JoinCommutative,
49 JoinAssociative,
51 Bottom,
53 PartialOrderConsistent,
55 TransferDeterministic,
57 TransferMonotone,
59 TransferProgress,
61}
62
63#[derive(Clone, Debug, Eq, PartialEq)]
65pub struct LawViolation {
66 law: DataflowLaw,
67 witnesses: Box<[usize]>,
68}
69
70impl LawViolation {
71 fn new(law: DataflowLaw, witnesses: impl Into<Box<[usize]>>) -> Self {
72 Self {
73 law,
74 witnesses: witnesses.into(),
75 }
76 }
77
78 #[must_use]
80 pub const fn law(&self) -> DataflowLaw {
81 self.law
82 }
83
84 #[must_use]
86 pub fn witnesses(&self) -> &[usize] {
87 &self.witnesses
88 }
89}
90
91impl fmt::Display for LawViolation {
92 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
93 write!(
94 formatter,
95 "dataflow admission failed {:?} at sample indices {:?}",
96 self.law, self.witnesses
97 )
98 }
99}
100
101impl Error for LawViolation {}
102
103#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
105pub struct LawSuite;
106
107impl LawSuite {
108 pub fn check_lattice<S: JoinSemilattice>(samples: &[S]) -> Result<(), LawViolation> {
110 let Some(first) = samples.first() else {
111 return Err(LawViolation::new(DataflowLaw::Bottom, Vec::new()));
112 };
113 let bottom = first.bottom();
114 for (a_index, a) in samples.iter().enumerate() {
115 if a.join(a) != *a {
116 return Err(LawViolation::new(DataflowLaw::JoinIdempotent, [a_index]));
117 }
118 if !bottom.less_equal(a) || bottom.join(a) != *a || a.join(&bottom) != *a {
119 return Err(LawViolation::new(DataflowLaw::Bottom, [a_index]));
120 }
121 if !a.less_equal(a) {
122 return Err(LawViolation::new(
123 DataflowLaw::PartialOrderConsistent,
124 [a_index],
125 ));
126 }
127 for (b_index, b) in samples.iter().enumerate() {
128 if a.join(b) != b.join(a) {
129 return Err(LawViolation::new(
130 DataflowLaw::JoinCommutative,
131 [a_index, b_index],
132 ));
133 }
134 let join_order = a.join(b) == *b;
135 if a.less_equal(b) != join_order || (a.less_equal(b) && b.less_equal(a) && a != b) {
136 return Err(LawViolation::new(
137 DataflowLaw::PartialOrderConsistent,
138 [a_index, b_index],
139 ));
140 }
141 for (c_index, c) in samples.iter().enumerate() {
142 if a.join(b).join(c) != a.join(&b.join(c)) {
143 return Err(LawViolation::new(
144 DataflowLaw::JoinAssociative,
145 [a_index, b_index, c_index],
146 ));
147 }
148 }
149 }
150 }
151 Ok(())
152 }
153
154 pub fn check_transfer<S, P>(policy: &P, samples: &[S]) -> Result<(), LawViolation>
156 where
157 S: JoinSemilattice,
158 P: TransferPolicy<S>,
159 {
160 for (a_index, a) in samples.iter().enumerate() {
161 let output = policy.transfer(a);
162 if output != policy.transfer(a) {
163 return Err(LawViolation::new(
164 DataflowLaw::TransferDeterministic,
165 [a_index],
166 ));
167 }
168 if !a.less_equal(&output) {
169 return Err(LawViolation::new(DataflowLaw::TransferProgress, [a_index]));
170 }
171 for (b_index, b) in samples.iter().enumerate() {
172 if a.less_equal(b) && !output.less_equal(&policy.transfer(b)) {
173 return Err(LawViolation::new(
174 DataflowLaw::TransferMonotone,
175 [a_index, b_index],
176 ));
177 }
178 }
179 }
180 Ok(())
181 }
182}
183
184#[derive(Clone, Debug)]
186pub struct AdmittedTransfer<P> {
187 policy: P,
188 fingerprint: ValueFingerprint,
189 policy_size: usize,
190}
191
192impl<P> AdmittedTransfer<P> {
193 pub fn admit<S>(policy: P, samples: &[S]) -> Result<Self, LawViolation>
195 where
196 S: JoinSemilattice,
197 P: TransferPolicy<S>,
198 {
199 LawSuite::check_lattice(samples)?;
200 LawSuite::check_transfer(&policy, samples)?;
201 let fingerprint = policy.fingerprint();
202 let policy_size = policy.policy_size();
203 Ok(Self {
204 policy,
205 fingerprint,
206 policy_size,
207 })
208 }
209
210 #[must_use]
212 pub const fn fingerprint(&self) -> ValueFingerprint {
213 self.fingerprint
214 }
215
216 #[must_use]
218 pub const fn policy_size(&self) -> usize {
219 self.policy_size
220 }
221
222 pub fn transfer<S>(&self, state: &S) -> S
224 where
225 P: TransferPolicy<S>,
226 {
227 self.policy.transfer(state)
228 }
229
230 #[must_use]
232 pub const fn policy(&self) -> &P {
233 &self.policy
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 impl StateSize for u8 {
242 fn state_size(&self) -> usize {
243 size_of::<Self>()
244 }
245 }
246
247 impl JoinSemilattice for u8 {
248 fn bottom(&self) -> Self {
249 0
250 }
251
252 fn join(&self, other: &Self) -> Self {
253 *self | *other
254 }
255
256 fn less_equal(&self, other: &Self) -> bool {
257 self & other == *self
258 }
259 }
260
261 #[derive(Clone, Debug)]
262 struct AddFacts(u8);
263
264 impl TransferPolicy<u8> for AddFacts {
265 fn fingerprint(&self) -> ValueFingerprint {
266 ValueFingerprint::new(u64::from(self.0))
267 }
268
269 fn policy_size(&self) -> usize {
270 size_of::<Self>()
271 }
272
273 fn transfer(&self, state: &u8) -> u8 {
274 state | self.0
275 }
276 }
277
278 #[test]
279 fn public_law_suite_admits_sound_policy_and_accounts_identity() {
280 let samples = [0, 1, 2, 3];
281 LawSuite::check_lattice(&samples).unwrap();
282 LawSuite::check_transfer(&AddFacts(2), &samples).unwrap();
283
284 let admitted = AdmittedTransfer::admit(AddFacts(2), &samples).unwrap();
285 assert_eq!(admitted.fingerprint(), ValueFingerprint::new(2));
286 assert_eq!(admitted.policy_size(), 1);
287 assert_eq!(admitted.transfer(&1), 3);
288 assert_eq!(3_u8.state_size(), 1);
289 }
290
291 #[derive(Clone, Debug)]
292 struct NonMonotone;
293
294 impl TransferPolicy<u8> for NonMonotone {
295 fn fingerprint(&self) -> ValueFingerprint {
296 ValueFingerprint::new(99)
297 }
298
299 fn policy_size(&self) -> usize {
300 0
301 }
302
303 fn transfer(&self, state: &u8) -> u8 {
304 if *state == 0 { 3 } else { *state }
305 }
306 }
307
308 #[test]
309 fn non_monotone_transfer_is_refused_at_admission_with_named_law() {
310 let refusal = AdmittedTransfer::admit(NonMonotone, &[0, 1, 2, 3]).unwrap_err();
311 assert_eq!(refusal.law(), DataflowLaw::TransferMonotone);
312 assert_eq!(refusal.witnesses(), &[0, 1]);
313 }
314}