furtif_core/traits/structures/lattice/mod.rs
1// This program is free software: you can redistribute it and/or modify
2// it under the terms of the Lesser GNU General Public License as published
3// by the Free Software Foundation, either version 3 of the License, or
4// (at your option) any later version.
5
6// This program is distributed in the hope that it will be useful,
7// but WITHOUT ANY WARRANTY; without even the implied warranty of
8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9// Lesser GNU General Public License for more details.
10
11// You should have received a copy of the Lesser GNU General Public License
12// along with this program. If not, see <https://www.gnu.org/licenses/>.
13
14// Copyright 2024 Frederic Dambreville, Jean Dezert Developers.
15
16
17use std::{ rc::Rc, sync::Arc, hash::Hash, collections::HashMap, };
18use hashed_type_def::HashedTypeDef;
19use rand::prelude::*;
20// #[cfg(not(feature = "silx-types"))] use crate::fake_slx::{u128slx, FakeSlx};
21// #[cfg(feature = "silx-types")] use silx_types::{ u128slx, IntoSlx, Float, };
22
23#[cfg(feature = "silx-types")] use silx_types::Float;
24
25use crate::{
26 types::{ u128slx, IntoSlx, },
27 structs::{
28 SafeElement, AssignmentBuilder, Assignment, hidden,
29 ASSIGNMENT_EPSILON, one_f64slx, zero_f64slx,
30 },
31 traits::CollectionFamily1,
32};
33
34/// General trait for lattices
35pub trait Lattice: Sized {
36 /// Type for encoding lattice elements:
37 /// * type `Self::Item` may contain element which are not in the lattice
38 /// * type `SafeElement<Self::Item>` contains both the element and the hash of lattice in order to assert its origin:
39 /// * `SafeElement<Self::Item>` is only produced by method `check_safe` which control if raw element is within the lattice
40 /// * `SafeElement<Self::Item>` or `SafeCollection<Self::Item,I>` contains checked elements;
41 /// * Safe elements are helper, but in last ressort, the user is responsible to ensure the data validity
42 type Item: Clone + Eq + HashedTypeDef;
43
44 /// Random lattice generator (for tests)
45 /// * `rng: &mut R` : random number generator
46 /// * `R: Rng` : type of random number generator
47 /// * Output: random lattice
48 fn rand_lattice<R: Rng>(rng: &mut R) -> Self;
49
50 /// Random element generator (for tests); elements are built safe
51 /// * `rng: &mut R` : random number generator
52 /// * `R: Rng` : type of random number generator
53 /// * Output: random element
54 fn rand_element<R: Rng>(&self, rng: &mut R) -> SafeElement<Self::Item>;
55
56 /// Reference to lattice hash
57 fn ref_lattice_hash(&self) -> &u128slx;
58
59 /// Test if element is from lattice
60 /// * this is necessary since type Self::Item may contain more potential elements than the lattice
61 /// * `element: &Self::Item` : element to be tested
62 /// * Output: boolean
63 fn contains(&self, element: &Self::Item) -> bool;
64
65 /// Reference to least element of the lattice
66 /// * Output: reference to least element
67 fn ref_bottom(&self) -> &SafeElement<Self::Item>;
68
69 /// Reference to greatest element of the lattice
70 /// * Output: reference to greatest element
71 fn ref_top(&self) -> &SafeElement<Self::Item>;
72
73 /// Unsafe greatest lower bound
74 /// * values are not tested to be within lattice.
75 /// * contract: operator should be associative
76 /// * `element_left: &Self::Item` : left operand
77 /// * `element_right: &Self::Item` : right operand
78 /// * Output: unsafe greatest lower bound
79 unsafe fn unsafe_meet(&self, element_left: &Self::Item, element_right: &Self::Item) -> Self::Item;
80
81 /// Unsafe least upper bound
82 /// * values are not tested to be within lattice.
83 /// * contract: operator should be associative!
84 /// * `element_left: &Self::Item` : left operand
85 /// * `element_right: &Self::Item` : right operand
86 /// * Output: unsafe least upper bound
87 unsafe fn unsafe_join(&self, element_left: &Self::Item, element_right: &Self::Item) -> Self::Item;
88
89 /// Parse safe element from str
90 /// * `s: &str` : string to be parsed
91 /// * Output: parsed safe element or error
92 fn from_str(&self, s: &str) -> Result<SafeElement<Self::Item>,String>;
93
94 /// Format safe element into String
95 /// * `element: &SafeElement<Self::Item>` : safe element to be formated into string
96 /// * Output: formated string or error
97 fn to_string(&self, element: &SafeElement<Self::Item>) -> Result<String,String>;
98
99 /// Lattice hash
100 /// * Output: lattice hash
101 fn lattice_hash(&self) -> u128slx { *self.ref_lattice_hash() }
102
103 /// Least element of the lattice
104 /// * Output: least element of the lattice
105 fn bottom(&self) -> SafeElement<Self::Item> { self.ref_bottom().clone() }
106
107 /// Greatest element of the lattice
108 /// * Output: greatest element of the lattice
109 fn top(&self) -> SafeElement<Self::Item> { self.ref_top().clone() }
110
111 /// Is safe element the least element of the lattice?
112 /// * `safe_element: &SafeElement<Self::Item>` : safe element
113 /// * Output: boolean or error
114 fn is_bottom(&self, safe_element: &SafeElement<Self::Item>) -> Result<bool, String> {
115 match (safe_element,self.ref_bottom()) {
116 (
117 SafeElement { code: element, lattice_hash },
118 SafeElement { code: bottom, lattice_hash: self_hash }
119 ) if lattice_hash == self_hash => Ok(element == bottom),
120 _ => Err("element is not within lattice".to_string()),
121 }
122 }
123
124 /// Is safe element the greatest element of the lattice?
125 /// * `safe_element: &SafeElement<Self::Item>` : safe element
126 /// * Output: boolean or error
127 fn is_top(&self, safe_element: &SafeElement<Self::Item>) -> Result<bool, String> {
128 match (safe_element,self.ref_top()) {
129 (
130 SafeElement { code: element, lattice_hash },
131 SafeElement { code: top, lattice_hash: self_hash }
132 ) if lattice_hash == self_hash => Ok(element == top),
133 _ => Err("element is not within lattice".to_string()),
134 }
135 }
136
137 /// Is unsafe element the least element of the lattice?
138 /// * `element: &Self::Item` : unsafe element
139 /// * Output: boolean or error
140 unsafe fn unsafe_is_bottom(&self, element: &Self::Item) -> bool {
141 element == &self.ref_bottom().code
142 }
143
144 /// Is unsafe element the greatest element of the lattice?
145 /// * `element: &Self::Item` : unsafe element
146 /// * Output: boolean or error
147 unsafe fn unsafe_is_top(&self, element: &Self::Item) -> bool {
148 element == &self.ref_top().code
149 }
150
151 /// Greatest lower bound
152 /// * `left: &SafeElement<Self::Item>` : left operand
153 /// * `right: &SafeElement<Self::Item>` : right operand
154 /// * Output: greatest lower bound or error
155 #[inline] fn meet(&self, left: &SafeElement<Self::Item>, right: &SafeElement<Self::Item>) -> Result<SafeElement<Self::Item>, String> {
156 let SafeElement { code: element_left, lattice_hash: lattice_hash_left, } = left;
157 let SafeElement { code: element_right, lattice_hash: lattice_hash_right, } = right;
158 let lattice_hash = self.ref_lattice_hash();
159 match (lattice_hash_left == lattice_hash, lattice_hash_right == lattice_hash) {
160 (true,true) => Ok(
161 SafeElement { code: unsafe { self.unsafe_meet(element_left,element_right) }, lattice_hash: *lattice_hash, }
162 ),
163 (false,true) => Err("left is not within lattice".to_string()),
164 (true,false) => Err("right is not within lattice".to_string()),
165 (false,false) => Err("entries are not within lattice".to_string()),
166 }
167 }
168
169 /// Greatest lower bound of a collection
170 /// * `it: I` : collection of safe elements
171 /// * `I` : type of collection
172 /// * Output: greatest lower bound or error
173 #[inline] fn meet_some<'a, I>(&self, it: I) -> Result<SafeElement<Self::Item>, String>
174 where I: IntoIterator<Item=&'a SafeElement<Self::Item>>, Self: 'a {
175 let SafeElement { code: mut cumul, lattice_hash: self_hash } = self.top();
176 for (idx,SafeElement { code: element, lattice_hash }) in it.into_iter().enumerate() {
177 if *lattice_hash == self_hash {
178 cumul = unsafe { self.unsafe_meet(&cumul,element) };
179 } else { return Err(format!("element at index {idx} is not within lattice")); }
180 }
181 Ok(SafeElement { code: cumul, lattice_hash: self_hash })
182 }
183
184 /// Least upper bound
185 /// * `left: &SafeElement<Self::Item>` : left operand
186 /// * `right: &SafeElement<Self::Item>` : right operand
187 /// * Output: least upper bound or error
188 fn join(&self, left: &SafeElement<Self::Item>, right: &SafeElement<Self::Item>) -> Result<SafeElement<Self::Item>, String> {
189 let SafeElement { code: element_left, lattice_hash: lattice_hash_left, } = left;
190 let SafeElement { code: element_right, lattice_hash: lattice_hash_right, } = right;
191 let lattice_hash = self.ref_lattice_hash();
192 match (lattice_hash_left == lattice_hash, lattice_hash_right == lattice_hash) {
193 (true,true) => Ok(
194 SafeElement { code: unsafe { self.unsafe_join(element_left,element_right) }, lattice_hash: *lattice_hash, }
195 ),
196 (false,true) => Err("left is not within lattice".to_string()),
197 (true,false) => Err("right is not within lattice".to_string()),
198 (false,false) => Err("entries are not within lattice".to_string()),
199 }
200 }
201
202 /// Least upper bound of a collection
203 /// * `it: I` : collection of safe elements
204 /// * `I` : type of collection
205 /// * Output: least upper bound or error
206 fn join_some<'a, I>(&self, it: I) -> Result<SafeElement<Self::Item>, String>
207 where I: IntoIterator<Item=&'a SafeElement<Self::Item>>, Self: 'a {
208 let SafeElement { code: mut cumul, lattice_hash: self_hash } = self.bottom();
209 for (idx,SafeElement { code: element, lattice_hash }) in it.into_iter().enumerate() {
210 if *lattice_hash == self_hash {
211 cumul = unsafe { self.unsafe_join(&cumul,element) };
212 } else { return Err(format!("element at index {idx} is not within lattice")); }
213 }
214 Ok(SafeElement { code: cumul, lattice_hash: self_hash })
215 }
216
217 /// Check if unsafe element is in lattice and then build safe element
218 /// * `element: T` : unsafe element
219 /// * `T` : type of unsafe element; actually any type which implement `Into<Self::Item>`
220 /// * Output: safe element or error
221 fn check_safe<T>(&self, element: T) -> Result<SafeElement<Self::Item>, String> where T: Into<Self::Item> {
222 let element: Self::Item = element.into();
223 if self.contains(&element) {
224 Ok(SafeElement { code: element, lattice_hash: self.lattice_hash(), })
225 } else { Err("lattice does not contain element".to_string()) }
226 }
227
228 /// Elements collection generator (for tests); elements are built safe
229 /// * `len: usize` : size of the collection
230 /// * `rng: &mut R` : random number generator
231 /// * `R: Rng` : type of random number generator
232 /// * `I` : type of collection
233 /// * Output: collection of random safe elements
234 fn rand_elements<R: Rng,I>(&self, len: usize, rng: &mut R) -> I::Type<SafeElement<Self::Item>> where I: CollectionFamily1 {
235 (0..len).map(|_| self.rand_element(rng)).collect()
236 }
237
238 /// Init a new assignment builder
239 /// * Output: empty assignment builder
240 fn assignment(&self,) -> AssignmentBuilder<Self::Item> where Self::Item: Eq + Ord + Hash, {
241 let elements = hidden::OrdMap::new();
242 AssignmentBuilder { elements, lattice_hash: self.lattice_hash(), length_mid: u32::MAX.slx(), length_max: u32::MAX.slx() }
243 }
244
245 /// Init a new assignment builder with capacity
246 /// * `capacity: usize` : capacity
247 /// * Output: empty assignment builder with capacity `capacity`
248 fn assignment_with_capacity(&self, capacity: usize) -> AssignmentBuilder<Self::Item> where Self::Item: Eq + Ord + Hash, {
249 let elements = hidden::OrdMap::with_capacity(capacity);
250 AssignmentBuilder { elements, lattice_hash: self.lattice_hash(), length_mid: u32::MAX.slx(), length_max: u32::MAX.slx() }
251 }
252
253 /// Init a new prunable assignment builder
254 /// * `length_mid: u32slx` : max size of pruned assignment
255 /// * `length_max: u32slx` : max size of assignment
256 /// * Output: empty assignment builder
257 fn prunable(&self, length_mid: u32, length_max: u32,) -> AssignmentBuilder<Self::Item> where Self::Item: Eq + Ord + Hash, {
258 let elements = hidden::OrdMap::new();
259 AssignmentBuilder { elements, lattice_hash: self.lattice_hash(), length_mid: length_mid.slx(), length_max: length_max.slx() }
260 }
261
262 /// Init a new prunable assignment builder with capacity
263 /// * `length_mid: u32slx` : max size of pruned assignment
264 /// * `length_max: u32slx` : max size of assignment
265 /// * `capacity: usize` : capacity
266 /// * Output: empty assignment builder with capacity `capacity`
267 fn prunable_with_capacity(&self, length_mid: u32, length_max: u32, capacity: usize)
268 -> AssignmentBuilder<Self::Item> where Self::Item: Eq + Ord + Hash, {
269 let elements = hidden::OrdMap::with_capacity(capacity);
270 AssignmentBuilder { elements, lattice_hash: self.lattice_hash(), length_mid: length_mid.slx(), length_max: length_max.slx() }
271 }
272
273 /// Unsafe test if left and right cover top, ie. union of left and right is top
274 /// * `left: &Self::Item` : left operand
275 /// * `right: &Self::Item` : right operand
276 /// * Output: boolean
277 #[inline] unsafe fn unsafe_cover(&self, left: &Self::Item, right: &Self::Item) -> bool {
278 &self.unsafe_join(left,right) == &self.ref_top().code
279 }
280
281 /// Unsafe test if left and right are disjoint
282 /// * `left: &Self::Item` : left operand
283 /// * `right: &Self::Item` : right operand
284 /// * Output: boolean
285 #[inline] unsafe fn unsafe_disjoint(&self, left: &Self::Item, right: &Self::Item) -> bool {
286 &self.unsafe_meet(left,right) == &self.ref_bottom().code
287 }
288
289 /// Unsafe test if left implies (i.e. is contained by) right
290 /// * should be equivalent to implies_meet
291 /// * `left: &Self::Item` : left operand
292 /// * `right: &Self::Item` : right operand
293 /// * Output: boolean
294 #[inline] unsafe fn unsafe_implies_join(&self, left: &Self::Item, right: &Self::Item) -> bool {
295 &self.unsafe_join(left,right) == right
296 }
297
298 /// Unsafe test if left is implied by (i.e. contains) right
299 /// * should be equivalent to implied_meet
300 /// * `left: &Self::Item` : left operand
301 /// * `right: &Self::Item` : right operand
302 /// * Output: boolean
303 #[inline] unsafe fn unsafe_implied_join(&self, left: &Self::Item, right: &Self::Item) -> bool {
304 &self.unsafe_join(left,right) == left
305 }
306
307 /// Unsafe test if left implies (i.e. is contained by) right
308 /// * should be equivalent to implies_join
309 /// * `left: &Self::Item` : left operand
310 /// * `right: &Self::Item` : right operand
311 /// * Output: boolean
312 #[inline] unsafe fn unsafe_implies_meet(&self, left: &Self::Item, right: &Self::Item) -> bool {
313 &self.unsafe_meet(left,right) == left
314 }
315
316 /// Unsafe test if left is implied by (i.e. contains) right
317 /// * should be equivalent to implied_joint
318 /// * `left: &Self::Item` : left operand
319 /// * `right: &Self::Item` : right operand
320 /// * Output: boolean
321 #[inline] unsafe fn unsafe_implied_meet(&self, left: &Self::Item, right: &Self::Item) -> bool {
322 &self.unsafe_meet(left,right) == right
323 }
324
325 /// Test if left and right cover top, ie. union of left and right is top
326 /// * `left: &SafeElement<Self::Item>` : left operand
327 /// * `right: &SafeElement<Self::Item>` : right operand
328 /// * Output: boolean or error
329 #[inline] fn cover(&self, left: &SafeElement<Self::Item>, right: &SafeElement<Self::Item>) -> Result<bool, String> {
330 Ok(&self.join(left,right)? == self.ref_top())
331 }
332
333 /// Test if left and right are disjoint
334 /// * `left: &SafeElement<Self::Item>` : left operand
335 /// * `right: &SafeElement<Self::Item>` : right operand
336 /// * Output: boolean or error
337 #[inline] fn disjoint(&self, left: &SafeElement<Self::Item>, right: &SafeElement<Self::Item>) -> Result<bool, String> {
338 Ok(&self.meet(left,right)? == self.ref_bottom())
339 }
340
341 /// Test if left implies (i.e. is contained by) right
342 /// * should be equivalent to implies_meet
343 /// * `left: &SafeElement<Self::Item>` : left operand
344 /// * `right: &SafeElement<Self::Item>` : right operand
345 /// * Output: boolean or error
346 #[inline] fn implies_join(&self, left: &SafeElement<Self::Item>, right: &SafeElement<Self::Item>) -> Result<bool, String> {
347 Ok(&self.join(left,right)? == right)
348 }
349
350 /// Test if left is implied by (i.e. contains) right
351 /// * should be equivalent to implied_meet
352 /// * `left: &SafeElement<Self::Item>` : left operand
353 /// * `right: &SafeElement<Self::Item>` : right operand
354 /// * Output: boolean or error
355 #[inline] fn implied_join(&self, left: &SafeElement<Self::Item>, right: &SafeElement<Self::Item>) -> Result<bool, String> {
356 Ok(&self.join(left,right)? == left)
357 }
358
359 /// Test if left implies (i.e. is contained by) right
360 /// * should be equivalent to implies_join
361 /// * `left: &SafeElement<Self::Item>` : left operand
362 /// * `right: &SafeElement<Self::Item>` : right operand
363 /// * Output: boolean or error
364 #[inline] fn implies_meet(&self, left: &SafeElement<Self::Item>, right: &SafeElement<Self::Item>) -> Result<bool, String> {
365 Ok(&self.meet(left,right)? == left)
366 }
367
368 /// Test if left is implied by (i.e. contains) right
369 /// * should be equivalent to implied_joint
370 /// * `left: &SafeElement<Self::Item>` : left operand
371 /// * `right: &SafeElement<Self::Item>` : right operand
372 /// * Output: boolean or error
373 #[inline] fn implied_meet(&self, left: &SafeElement<Self::Item>, right: &SafeElement<Self::Item>) -> Result<bool, String> {
374 Ok(&self.meet(left,right)? == right)
375 }
376}
377
378/// General trait for complemented lattices
379/// * such lattices have logical negation
380pub trait ComplementedLattice: Lattice {
381 /// Unsafe logical negation
382 /// * contract: not has to be bijective, be equal to its inverse, and has to check de Morgan's law
383 /// * i.e. double not is identity and not of meet is join of not
384 /// * this is unsafe: value is not tested to be within lattice
385 /// * `element: &Self::Item` : unsafe element
386 /// * Output: logical negation of unsafe element
387 unsafe fn unsafe_not(&self, element: &Self::Item) -> Self::Item;
388
389 /// Logical negation
390 /// * `safe_element: &SafeElement<Self::Item>` : safe element
391 /// * Output: logical negation of safe element
392 fn not(&self, safe_element: &SafeElement<Self::Item>) -> Result<SafeElement<Self::Item>, String> {
393 let SafeElement { code: element, lattice_hash, } = safe_element;
394 let lattice_hash = * lattice_hash;
395 if self.lattice_hash() == lattice_hash {
396 Ok(SafeElement { code: unsafe { self.unsafe_not(element) }, lattice_hash, })
397 } else { Err("element is not within lattice".to_string()) }
398 }
399
400 /// Compute the co-assignment of an assignment
401 /// * The following computation is done on the weighted elements of the assignment: `(e,w) -> (e.not(), 1 - w)`
402 /// * `assignment: &Assignment<Self::Item>` : assigment
403 /// * Output: co-assignment
404 fn co_assignment(&self, assignment: &Assignment<Self::Item>) -> Result<Assignment<Self::Item>, String>
405 where Self::Item: Ord + Hash, {
406 let Assignment { lattice_hash, .. } = *assignment;
407 let Assignment { elements, .. } = assignment;
408 if self.lattice_hash() == lattice_hash {
409 let eps = ASSIGNMENT_EPSILON.slx();
410 let one = *one_f64slx();
411 let zero = *zero_f64slx();
412 let mut co_assignment = HashMap::with_capacity(elements.len());
413 for (element, weight) in elements {
414 if *weight - one > eps { return Err(format!("assigned weight {weight} is greater than 1.0")); }
415 co_assignment.insert(unsafe { self.unsafe_not(element) }, (one - *weight).max(zero));
416 }
417 Ok(Assignment { elements: co_assignment, lattice_hash })
418 } else { Err("assignment is not within lattice".to_string()) }
419 }
420}
421
422macro_rules! impl_as_ref {
423 ($($ty: ident,)*) => {
424 $(
425 impl<L> Lattice for $ty<L> where L: Lattice, {
426 type Item = L::Item;
427 fn rand_lattice<R: Rng>(rng: &mut R) -> Self { $ty::new(L::rand_lattice(rng)) }
428 fn rand_element<R: Rng>(&self, rng: &mut R) -> SafeElement<Self::Item> {
429 let SafeElement { code, lattice_hash } = self.as_ref().rand_element(rng);
430 SafeElement { code, lattice_hash, }
431 }
432 fn ref_lattice_hash(&self) -> &u128slx { self.as_ref().ref_lattice_hash() }
433 fn contains(&self, element: &Self::Item) -> bool { self.as_ref().contains(element) }
434 fn ref_bottom(&self) -> &SafeElement<Self::Item> { self.as_ref().ref_bottom() }
435 fn ref_top(&self) -> &SafeElement<Self::Item> { self.as_ref().ref_top() }
436 unsafe fn unsafe_meet(&self, element_left: &Self::Item, element_right: &Self::Item) -> Self::Item {
437 self.as_ref().unsafe_meet(element_left, element_right)
438 }
439 unsafe fn unsafe_join(&self, element_left: &Self::Item, element_right: &Self::Item) -> Self::Item {
440 self.as_ref().unsafe_join(element_left, element_right)
441 }
442 fn from_str(&self, s: &str) -> Result<SafeElement<Self::Item>,String> {
443 self.as_ref().from_str(s)
444 }
445 fn to_string(&self, element: &SafeElement<Self::Item>,) -> Result<String,String> {
446 self.as_ref().to_string(element,)
447 }
448 }
449 )*
450 };
451}
452
453impl_as_ref!(Arc,Rc,Box,);