harness_space/lattice.rs
1//! # Lattice Module
2//! Defines a generic lattice data structure and associated operations.
3//! This module provides `Lattice<T>`, a structure capable of representing
4//! any partially ordered set where `T` is the type of elements in the lattice.
5//!
6//! ## Features
7//! - Creation of lattices and addition of elements and relations (`a ≤ b`).
8//! - Automatic computation of transitive closure to represent all implied relations.
9//! - Checking for relations (`leq`).
10//! - Finding minimal and maximal elements.
11//! - Computation of join (least upper bound) and meet (greatest lower bound) of elements.
12//! - Exporting the lattice structure to a DOT file format for visualization (e.g., with Graphviz).
13//!
14//! ## Type Constraints
15//! - The element type `T` must implement `std::hash::Hash`, `std::cmp::Eq`, and `std::clone::Clone`
16//! for basic lattice operations.
17//! - For generating DOT file output using `save_to_dot_file`, `T` must also implement
18//! `std::fmt::Display` (for node labels) and `std::cmp::Ord` (for consistent output ordering).
19//!
20//! ## Example Usage
21//! ```
22//! use harness_space::{lattice::Lattice, prelude::*};
23//!
24//! // Create a new lattice for integers
25//! let mut lattice: Lattice<i32> = Lattice::new();
26//!
27//! // Add elements and relations (e.g., a simple chain 1 ≤ 2 ≤ 3)
28//! lattice.add_relation(1, 2);
29//! lattice.add_relation(2, 3);
30//!
31//! // Check relations
32//! assert!(lattice.leq(&1, &3).unwrap());
33//! assert!(!lattice.leq(&3, &1).unwrap());
34//!
35//! // Find minimal and maximal elements
36//! let minimal = lattice.minimal_elements();
37//! assert!(minimal.contains(&1) && minimal.len() == 1);
38//! let maximal = lattice.maximal_elements();
39//! assert!(maximal.contains(&3) && maximal.len() == 1);
40//!
41//! // Compute join and meet (for a diamond lattice, for example)
42//! let mut diamond: Lattice<char> = Lattice::new();
43//! diamond.add_relation('d', 'b'); // d is bottom
44//! diamond.add_relation('d', 'c');
45//! diamond.add_relation('b', 'a'); // a is top
46//! diamond.add_relation('c', 'a');
47//!
48//! assert_eq!(diamond.join('b', 'c'), Some('a'));
49//! assert_eq!(diamond.meet('b', 'c'), Some('d'));
50//!
51//! // Save to a DOT file (requires T: Display + Ord)
52//! // if let Err(e) = diamond.save_to_dot_file("diamond_lattice.dot") {
53//! // eprintln!("Failed to save: {}", e);
54//! // }
55//! ```
56
57use std::{
58 collections::{HashMap, HashSet},
59 fs::File,
60 hash::Hash,
61 io::{Result as IoResult, Write as IoWrite},
62};
63
64use crate::set::{Collection, Poset};
65
66/// A node in a lattice representing an element and its relationships.
67///
68/// Each node stores an element of type `T` and maintains sets of its direct
69/// successors (elements greater than this one) and direct predecessors
70/// (elements less than this one) in the partial order.
71#[derive(Debug, Clone)]
72pub struct LatticeNode<T> {
73 /// The element stored in this node.
74 element: T,
75 /// Direct successors (elements that are greater than this one according to the
76 /// lattice's partial order).
77 successors: HashSet<T>,
78 /// Direct predecessors (elements that are less than this one according to the
79 /// lattice's partial order).
80 predecessors: HashSet<T>,
81}
82
83/// A general lattice structure that can represent any partially ordered set
84/// with join and meet operations.
85///
86/// A lattice is a partially ordered set in which any two elements have a unique
87/// supremum (also called a least upper bound or join) and a unique infimum
88/// (also called a greatest lower bound or meet).
89/// This implementation stores elements of type `T` and their relationships.
90/// The type `T` must implement `Hash`, `Eq`, and `Clone`.
91/// For DOT file generation, `T` must also implement `Display` and `Ord`.
92#[derive(Debug, Default, Clone)]
93pub struct Lattice<T> {
94 /// Map of elements to their nodes. Each key is an element in the lattice,
95 /// and its value is the `LatticeNode` containing the element's relationships.
96 nodes: HashMap<T, LatticeNode<T>>,
97}
98
99impl<T: Hash + Eq + Clone> Lattice<T> {
100 /// Creates a new empty lattice.
101 ///
102 /// # Examples
103 ///
104 /// ```
105 /// use harness_space::{lattice::Lattice, prelude::*};
106 /// let lattice: Lattice<i32> = Lattice::new();
107 /// ```
108 pub fn new() -> Self { Self { nodes: HashMap::new() } }
109
110 /// Adds a new element to the lattice.
111 ///
112 /// If the element already exists in the lattice, this method does nothing.
113 ///
114 /// # Arguments
115 ///
116 /// * `element`: The element to add to the lattice.
117 ///
118 /// # Examples
119 ///
120 /// ```
121 /// use harness_space::{lattice::Lattice, prelude::*};
122 /// let mut lattice = Lattice::new();
123 /// lattice.add_element(1);
124 /// ```
125 pub fn add_element(&mut self, element: T) {
126 if !self.nodes.contains_key(&element) {
127 self.nodes.insert(element.clone(), LatticeNode {
128 element,
129 successors: HashSet::new(),
130 predecessors: HashSet::new(),
131 });
132 }
133 }
134
135 /// Adds a relation `a ≤ b` to the lattice.
136 ///
137 /// This indicates that element `a` is less than or equal to element `b`
138 /// in the partial order. If `a` or `b` are not already in the lattice,
139 /// they are added.
140 ///
141 /// After adding the direct relation, this method also updates the transitive
142 /// closure of the lattice to ensure all indirect relationships are captured.
143 ///
144 /// # Arguments
145 ///
146 /// * `a`: The smaller element in the relation.
147 /// * `b`: The greater element in the relation.
148 ///
149 /// # Examples
150 ///
151 /// ```
152 /// use harness_space::{lattice::Lattice, prelude::*};
153 /// let mut lattice = Lattice::new();
154 /// lattice.add_relation(1, 2); // 1 ≤ 2
155 /// ```
156 pub fn add_relation(&mut self, a: T, b: T) {
157 self.add_element(a.clone());
158 self.add_element(b.clone());
159
160 // Add direct relation
161 if let Some(node_a) = self.nodes.get_mut(&a) {
162 node_a.successors.insert(b.clone());
163 }
164 if let Some(node_b) = self.nodes.get_mut(&b) {
165 node_b.predecessors.insert(a);
166 }
167
168 // Transitive closure
169 self.compute_transitive_closure();
170 }
171
172 /// Computes the transitive closure of the lattice
173 fn compute_transitive_closure(&mut self) {
174 let mut changed = true;
175 while changed {
176 changed = false;
177 let mut updates = Vec::new();
178
179 // Collect all updates first
180 for node in self.nodes.values() {
181 for succ in &node.successors {
182 if let Some(succ_node) = self.nodes.get(succ) {
183 for succ_succ in &succ_node.successors {
184 updates.push((node.element.clone(), succ_succ.clone()));
185 }
186 }
187 }
188 }
189
190 // Apply updates
191 for (a, b) in updates {
192 if let Some(node_a) = self.nodes.get_mut(&a) {
193 if node_a.successors.insert(b.clone()) {
194 changed = true;
195 }
196 }
197 if let Some(node_b) = self.nodes.get_mut(&b) {
198 if node_b.predecessors.insert(a) {
199 changed = true;
200 }
201 }
202 }
203 }
204 }
205}
206
207impl<T: Hash + Eq + Clone> Collection for Lattice<T> {
208 type Item = T;
209
210 fn contains(&self, point: &Self::Item) -> bool { self.nodes.contains_key(point) }
211
212 fn is_empty(&self) -> bool { self.nodes.is_empty() }
213}
214
215impl<T: Hash + Eq + Clone> Poset for Lattice<T> {
216 /// Checks if `a ≤ b` in the lattice.
217 ///
218 /// This method determines if element `a` is less than or equal to element `b`
219 /// according to the partial order defined in the lattice. This relies on the
220 /// precomputed transitive closure.
221 ///
222 /// # Arguments
223 ///
224 /// * `a`: The first element.
225 /// * `b`: The second element.
226 ///
227 /// # Returns
228 ///
229 /// `true` if `a ≤ b`, `false` otherwise. Returns `false` if either `a` or `b`
230 /// is not in the lattice.
231 ///
232 /// # Examples
233 ///
234 /// ```
235 /// use harness_space::{lattice::Lattice, prelude::*};
236 /// let mut lattice = Lattice::new();
237 /// lattice.add_relation(1, 2);
238 /// lattice.add_relation(2, 3);
239 /// assert!(lattice.leq(&1, &3).unwrap()); // Transitive: 1 ≤ 2 and 2 ≤ 3 => 1 ≤ 3
240 /// assert!(!lattice.leq(&3, &1).unwrap());
241 /// ```
242 fn leq(&self, a: &T, b: &T) -> Option<bool> {
243 if !self.nodes.contains_key(a) || !self.nodes.contains_key(b) {
244 return None;
245 }
246 if a == b {
247 return Some(true);
248 }
249 let node_a = self.nodes.get(a).unwrap();
250 Some(node_a.successors.contains(b))
251 }
252
253 /// Returns all minimal elements in the lattice.
254 ///
255 /// Minimal elements are those that have no predecessors in the partial order.
256 ///
257 /// # Returns
258 ///
259 /// A `HashSet` containing all minimal elements of the lattice.
260 /// If the lattice is empty, an empty set is returned.
261 ///
262 /// # Examples
263 ///
264 /// ```
265 /// use harness_space::{lattice::Lattice, prelude::*};
266 /// let mut lattice = Lattice::new();
267 /// lattice.add_relation(1, 2);
268 /// lattice.add_relation(1, 3);
269 /// let minimal = lattice.minimal_elements();
270 /// assert!(minimal.contains(&1) && minimal.len() == 1);
271 /// ```
272 fn minimal_elements(&self) -> HashSet<T> {
273 self
274 .nodes
275 .iter()
276 .filter(|(_, node)| node.predecessors.is_empty())
277 .map(|(element, _)| element.clone())
278 .collect()
279 }
280
281 /// Returns all maximal elements in the lattice.
282 ///
283 /// Maximal elements are those that have no successors in the partial order.
284 ///
285 /// # Returns
286 ///
287 /// A `HashSet` containing all maximal elements of the lattice.
288 /// If the lattice is empty, an empty set is returned.
289 ///
290 /// # Examples
291 ///
292 /// ```
293 /// use harness_space::{lattice::Lattice, prelude::*};
294 /// let mut lattice = Lattice::new();
295 /// lattice.add_relation(1, 3);
296 /// lattice.add_relation(2, 3);
297 /// let maximal = lattice.maximal_elements();
298 /// assert!(maximal.contains(&3) && maximal.len() == 1);
299 /// ```
300 fn maximal_elements(&self) -> HashSet<T> {
301 self
302 .nodes
303 .iter()
304 .filter(|(_, node)| node.successors.is_empty())
305 .map(|(element, _)| element.clone())
306 .collect()
307 }
308
309 /// Computes the join (least upper bound) of two elements `a` and `b`.
310 ///
311 /// The join of `a` and `b` is an element `x` such that `a ≤ x` and `b ≤ x`,
312 /// and for any other element `y` with `a ≤ y` and `b ≤ y`, it holds that `x ≤ y`.
313 ///
314 /// # Arguments
315 ///
316 /// * `a`: The first element.
317 /// * `b`: The second element.
318 ///
319 /// # Returns
320 ///
321 /// An `Option<T>` containing the unique join of `a` and `b` if it exists.
322 /// Returns `None` if:
323 /// * Either `a` or `b` is not in the lattice.
324 /// * `a` and `b` have no common upper bounds.
325 /// * `a` and `b` have multiple minimal common upper bounds (i.e., the join is not unique).
326 ///
327 /// # Examples
328 ///
329 /// ```
330 /// use harness_space::{lattice::Lattice, prelude::*};
331 /// let mut lattice = Lattice::new(); // Diamond lattice
332 /// lattice.add_relation(4, 2);
333 /// lattice.add_relation(4, 3);
334 /// lattice.add_relation(2, 1);
335 /// lattice.add_relation(3, 1);
336 /// // Here, 4 is bottom, 1 is top.
337 /// assert_eq!(lattice.join(2, 3), Some(1));
338 /// assert_eq!(lattice.join(4, 2), Some(2));
339 /// ```
340 fn join(&self, a: T, b: T) -> Option<T> {
341 if !self.nodes.contains_key(&a) || !self.nodes.contains_key(&b) {
342 return None; // Elements must be in the lattice
343 }
344
345 let node_a = self.nodes.get(&a).unwrap();
346 let node_b = self.nodes.get(&b).unwrap();
347
348 let mut upper_bounds_a = node_a.successors.iter().cloned().collect::<HashSet<T>>();
349 upper_bounds_a.insert(a.clone());
350
351 let mut upper_bounds_b = node_b.successors.iter().cloned().collect::<HashSet<T>>();
352 upper_bounds_b.insert(b.clone());
353
354 let common_upper_bounds: HashSet<T> =
355 upper_bounds_a.intersection(&upper_bounds_b).cloned().collect();
356
357 if common_upper_bounds.is_empty() {
358 return None;
359 }
360
361 let minimal_common_upper_bounds: Vec<T> = common_upper_bounds
362 .iter()
363 .filter(|&x| common_upper_bounds.iter().all(|y| x == y || !self.leq(y, x).unwrap_or(false)))
364 .cloned()
365 .collect();
366
367 if minimal_common_upper_bounds.len() == 1 {
368 Some(minimal_common_upper_bounds[0].clone())
369 } else {
370 None
371 }
372 }
373
374 /// Computes the meet (greatest lower bound) of two elements `a` and `b`.
375 ///
376 /// The meet of `a` and `b` is an element `x` such that `x ≤ a` and `x ≤ b`,
377 /// and for any other element `y` with `y ≤ a` and `y ≤ b`, it holds that `y ≤ x`.
378 ///
379 /// # Arguments
380 ///
381 /// * `a`: The first element.
382 /// * `b`: The second element.
383 ///
384 /// # Returns
385 ///
386 /// An `Option<T>` containing the unique meet of `a` and `b` if it exists.
387 /// Returns `None` if:
388 /// * Either `a` or `b` is not in the lattice.
389 /// * `a` and `b` have no common lower bounds.
390 /// * `a` and `b` have multiple maximal common lower bounds (i.e., the meet is not unique).
391 ///
392 /// # Examples
393 ///
394 /// ```
395 /// use harness_space::{lattice::Lattice, prelude::*};
396 /// let mut lattice = Lattice::new(); // Diamond lattice
397 /// lattice.add_relation(4, 2);
398 /// lattice.add_relation(4, 3);
399 /// lattice.add_relation(2, 1);
400 /// lattice.add_relation(3, 1);
401 /// // Here, 4 is bottom, 1 is top.
402 /// assert_eq!(lattice.meet(2, 3), Some(4));
403 /// assert_eq!(lattice.meet(1, 2), Some(2));
404 /// ```
405 fn meet(&self, a: T, b: T) -> Option<T> {
406 if !self.nodes.contains_key(&a) || !self.nodes.contains_key(&b) {
407 return None; // Elements must be in the lattice
408 }
409
410 let node_a = self.nodes.get(&a).unwrap();
411 let node_b = self.nodes.get(&b).unwrap();
412
413 let mut lower_bounds_a = node_a.predecessors.iter().cloned().collect::<HashSet<T>>();
414 lower_bounds_a.insert(a.clone());
415
416 let mut lower_bounds_b = node_b.predecessors.iter().cloned().collect::<HashSet<T>>();
417 lower_bounds_b.insert(b.clone());
418
419 let common_lower_bounds: HashSet<T> =
420 lower_bounds_a.intersection(&lower_bounds_b).cloned().collect();
421
422 if common_lower_bounds.is_empty() {
423 return None;
424 }
425
426 let maximal_common_lower_bounds: Vec<T> = common_lower_bounds
427 .iter()
428 .filter(|&x| common_lower_bounds.iter().all(|y| x == y || !self.leq(x, y).unwrap_or(false)))
429 .cloned()
430 .collect();
431
432 if maximal_common_lower_bounds.len() == 1 {
433 Some(maximal_common_lower_bounds[0].clone())
434 } else {
435 None
436 }
437 }
438
439 /// Returns the set of elements that are less than or equal to `a`.
440 ///
441 /// This method returns a `HashSet` containing all elements in the lattice
442 /// that are less than or equal to `a`. If `a` is not in the lattice,
443 /// the method returns an empty set.
444 ///
445 /// # Arguments
446 ///
447 /// * `a`: The element to get the lower bounds of.
448 ///
449 /// # Returns
450 ///
451 /// A `HashSet` containing all elements in the lattice that are less than or equal to `a`.
452 fn downset(&self, a: T) -> HashSet<T> {
453 self
454 .nodes
455 .iter()
456 .filter(|(_, node)| self.leq(&node.element, &a).unwrap_or(false))
457 .map(|(element, _)| element.clone())
458 .collect()
459 }
460
461 /// Returns the set of elements that are greater than or equal to `a`.
462 ///
463 /// This method returns a `HashSet` containing all elements in the lattice
464 /// that are greater than or equal to `a`. If `a` is not in the lattice,
465 /// the method returns an empty set.
466 ///
467 /// # Arguments
468 ///
469 /// * `a`: The element to get the upper bounds of.
470 ///
471 /// # Returns
472 ///
473 /// A `HashSet` containing all elements in the lattice that are greater than or equal to `a`.
474 fn upset(&self, a: T) -> HashSet<T> {
475 self
476 .nodes
477 .iter()
478 .filter(|(_, node)| self.leq(&a, &node.element).unwrap_or(false))
479 .map(|(element, _)| element.clone())
480 .collect()
481 }
482
483 /// Returns the set of elements that are successors of `a`.
484 ///
485 /// This method returns a `HashSet` containing all elements in the lattice
486 /// that are direct successors of `a`. If `a` is not in the lattice,
487 /// the method returns an empty set.
488 fn successors(&self, a: T) -> HashSet<T> {
489 self.nodes.get(&a).map_or_else(HashSet::new, |node_a| {
490 // Filter to only include direct successors
491 let all_successors = &node_a.successors;
492 all_successors
493 .iter()
494 .filter(|&b| {
495 // A successor b is direct if there's no other element c where a < c < b
496 !all_successors.iter().any(|c| {
497 c != b && self.nodes.get(c).is_some_and(|node_c| node_c.successors.contains(b))
498 })
499 })
500 .cloned()
501 .collect()
502 })
503 }
504
505 /// Returns the set of elements that are predecessors of `a`.
506 ///
507 /// This method returns a `HashSet` containing all elements in the lattice
508 /// that are direct predecessors of `a`. If `a` is not in the lattice,
509 /// the method returns an empty set.
510 fn predecessors(&self, a: T) -> HashSet<T> {
511 self.nodes.get(&a).map_or_else(HashSet::new, |node_a| {
512 // Filter to only include direct predecessors
513 let all_predecessors = &node_a.predecessors;
514 all_predecessors
515 .iter()
516 .filter(|&b| {
517 // A predecessor b is direct if there's no other element c where b < c < a
518 !all_predecessors.iter().any(|c| {
519 c != b && self.nodes.get(c).is_some_and(|node_c| node_c.predecessors.contains(b))
520 })
521 })
522 .cloned()
523 .collect()
524 })
525 }
526}
527
528// Helper function to escape strings for DOT format
529fn escape_dot_label(label: &str) -> String { label.replace('"', "\\\"") }
530
531// Implementation block for methods requiring Display and Ord for T
532impl<T: Hash + Eq + Clone + std::fmt::Display + Ord> Lattice<T> {
533 /// Saves the lattice representation in DOT format to the specified file.
534 ///
535 /// This method generates a string in the DOT language (used by Graphviz)
536 /// representing the Hasse diagram of the lattice and writes it to the given file.
537 /// The diagram shows only the covering relations (immediate successor/predecessor).
538 /// Elements are displayed using their `Display` implementation.
539 /// The layout is bottom-to-top (`rankdir="BT"`).
540 ///
541 /// Requires `T` to implement `std::fmt::Display` and `std::cmp::Ord`
542 /// for consistent node labeling and ordering in the output.
543 ///
544 /// # Arguments
545 ///
546 /// * `filename`: The path to the file where the DOT representation will be saved. If the file
547 /// exists, it will be overwritten.
548 ///
549 /// # Returns
550 ///
551 /// An `IoResult<()>` which is `Ok(())` on successful write, or an `Err`
552 /// containing an `std::io::Error` if any I/O error occurs (e.g., file
553 /// creation fails).
554 ///
555 /// # Panics
556 ///
557 /// This method does not explicitly panic, but file operations can panic
558 /// under certain unrecoverable conditions (though `File::create` and `writeln!`
559 /// typically return `Result`).
560 ///
561 /// # Examples
562 ///
563 /// ```no_run
564 /// use harness_space::lattice::Lattice;
565 /// let mut lattice = Lattice::new();
566 /// lattice.add_relation("a", "b");
567 /// lattice.add_relation("b", "c");
568 /// if let Err(e) = lattice.save_to_dot_file("my_lattice.dot") {
569 /// eprintln!("Failed to save lattice: {}", e);
570 /// }
571 /// ```
572 ///
573 /// The resulting `my_lattice.dot` file would look something like:
574 /// ```dot
575 /// digraph Lattice {
576 /// rankdir="BT";
577 /// node [shape=plaintext];
578 /// "a";
579 /// "b";
580 /// "c";
581 ///
582 /// "a" -> "b";
583 /// "b" -> "c";
584 /// }
585 /// ```
586 pub fn save_to_dot_file(&self, filename: &str) -> IoResult<()> {
587 let mut file = File::create(filename)?;
588
589 if self.nodes.is_empty() {
590 return writeln!(file, "digraph Lattice {{\n label=\"Empty Lattice\";\n}}");
591 }
592
593 writeln!(file, "digraph Lattice {{")?;
594 writeln!(file, " rankdir=\"BT\";")?;
595 writeln!(file, " node [shape=plaintext];")?;
596
597 let mut sorted_node_keys: Vec<&T> = self.nodes.keys().collect();
598 // T: Ord is required for sorted_node_keys.sort()
599 // &T will be sorted based on the Ord impl of T.
600 sorted_node_keys.sort();
601
602 // Define nodes
603 for node_key_ptr in &sorted_node_keys {
604 let node_key = *node_key_ptr; // node_key is &T
605 // node_key.to_string() requires T: std::fmt::Display
606 writeln!(file, " \"{}\";", escape_dot_label(&node_key.to_string()))?;
607 }
608 writeln!(file)?; // Blank line for readability
609
610 // Define edges (covering relations)
611 for source_key_ptr in &sorted_node_keys {
612 let source_key = *source_key_ptr; // source_key is &T
613 if let Some(node) = self.nodes.get(source_key) {
614 let mut sorted_successors: Vec<&T> = node.successors.iter().collect();
615 sorted_successors.sort(); // T: Ord required for &T to sort
616
617 for succ_key in sorted_successors {
618 // succ_key is &T
619 let mut is_immediate = true;
620 let mut inner_sorted_successors_for_check: Vec<&T> = node.successors.iter().collect();
621 inner_sorted_successors_for_check.sort(); // T: Ord required
622
623 for intermediate_key in inner_sorted_successors_for_check {
624 if intermediate_key == succ_key {
625 continue;
626 }
627 if let Some(intermediate_node_w) = self.nodes.get(intermediate_key) {
628 if intermediate_node_w.successors.contains(succ_key) {
629 is_immediate = false;
630 break;
631 }
632 }
633 }
634
635 if is_immediate {
636 writeln!(
637 file,
638 " \"{}\" -> \"{}\";",
639 escape_dot_label(&source_key.to_string()), // T: Display
640 escape_dot_label(&succ_key.to_string()) // T: Display
641 )?;
642 }
643 }
644 }
645 }
646 writeln!(file, "}}")
647 }
648}
649
650#[cfg(test)]
651mod tests {
652 use super::*;
653
654 fn m_lattice() -> Lattice<i32> {
655 let mut m_lattice: Lattice<i32> = Lattice::new();
656 // M-shape for non-unique meet if 5,6 considered uppers for join(1,3)
657 // 5 6
658 // / \ / \
659 // 1 2 3
660 // join(1,3) -> should be None if 5 and 6 are incomparable & minimal uppers
661 m_lattice.add_element(1);
662 m_lattice.add_element(2);
663 m_lattice.add_element(3);
664 m_lattice.add_element(5);
665 m_lattice.add_element(6);
666
667 m_lattice.add_relation(1, 5);
668 m_lattice.add_relation(2, 5);
669 m_lattice.add_relation(2, 6);
670 m_lattice.add_relation(3, 6);
671 m_lattice
672 }
673
674 fn diamond_lattice() -> Lattice<i32> {
675 // Create a diamond lattice:
676 // 1
677 // / \
678 // 2 3
679 // \ /
680 // 4
681 let mut diamond_lattice: Lattice<i32> = Lattice::new();
682 diamond_lattice.add_element(1);
683 diamond_lattice.add_element(2);
684 diamond_lattice.add_element(3);
685 diamond_lattice.add_element(4);
686
687 diamond_lattice.add_relation(1, 2);
688 diamond_lattice.add_relation(1, 3);
689 diamond_lattice.add_relation(2, 4);
690 diamond_lattice.add_relation(3, 4);
691
692 diamond_lattice
693 }
694
695 #[test]
696 fn test_basic_lattice() {
697 let mut lattice = Lattice::new();
698 lattice.add_relation(1, 2);
699 lattice.add_relation(2, 3);
700
701 assert!(lattice.leq(&1, &2).unwrap_or(false));
702 assert!(lattice.leq(&2, &3).unwrap_or(false));
703 assert!(lattice.leq(&1, &3).unwrap_or(false));
704 assert!(!lattice.leq(&2, &1).unwrap_or(false));
705
706 let minimal = lattice.minimal_elements();
707 assert_eq!(minimal.len(), 1);
708 assert!(minimal.contains(&1));
709
710 let maximal = lattice.maximal_elements();
711 assert_eq!(maximal.len(), 1);
712 assert!(maximal.contains(&3));
713 }
714
715 #[test]
716 fn test_diamond_lattice() {
717 let lattice = diamond_lattice();
718 assert!(lattice.leq(&1, &4).unwrap_or(false));
719 assert!(!lattice.leq(&2, &3).unwrap_or(false));
720 assert!(!lattice.leq(&3, &2).unwrap_or(false));
721
722 let minimal = lattice.minimal_elements();
723 assert_eq!(minimal.len(), 1);
724 assert!(minimal.contains(&1));
725
726 let maximal = lattice.maximal_elements();
727 assert_eq!(maximal.len(), 1);
728 assert!(maximal.contains(&4));
729 }
730
731 #[test]
732 fn test_lattice_operations_diamond() {
733 let mut lattice = Lattice::new();
734 lattice.add_relation(1, 2);
735 lattice.add_relation(1, 3);
736 lattice.add_relation(2, 4);
737 lattice.add_relation(3, 4);
738
739 // Test join
740 println!("join(2, 3): {:?}", lattice.join(2, 3));
741 assert_eq!(lattice.join(2, 3), Some(4));
742
743 println!("join(1, 4): {:?}", lattice.join(1, 4));
744 assert_eq!(lattice.join(1, 4), Some(4));
745
746 println!("join(1, 2): {:?}", lattice.join(1, 2));
747 assert_eq!(lattice.join(1, 2), Some(2));
748
749 println!("join(1, 1): {:?}", lattice.join(1, 1));
750 assert_eq!(lattice.join(1, 1), Some(1));
751
752 // Test meet
753 println!("meet(2, 3): {:?}", lattice.meet(2, 3));
754 assert_eq!(lattice.meet(2, 3), Some(1));
755
756 println!("meet(1, 4): {:?}", lattice.meet(1, 4));
757 assert_eq!(lattice.meet(1, 4), Some(1));
758
759 println!("meet(2, 4): {:?}", lattice.meet(2, 4));
760 assert_eq!(lattice.meet(2, 4), Some(2));
761
762 println!("meet(4, 4): {:?}", lattice.meet(4, 4));
763 assert_eq!(lattice.meet(4, 4), Some(4));
764 }
765
766 #[test]
767 fn test_lattice_operations_non_lattice_examples() {
768 let m_lattice = m_lattice();
769
770 println!("join(1, 2) for M-shape: {:?}", m_lattice.join(1, 2));
771 assert_eq!(m_lattice.join(1, 2), Some(5));
772
773 println!("join(2, 3) for M-shape: {:?}", m_lattice.join(2, 3));
774 assert_eq!(m_lattice.join(2, 3), Some(6));
775
776 println!("join(1, 3) for M-shape: {:?}", m_lattice.join(1, 3));
777 assert_eq!(m_lattice.join(1, 3), None);
778
779 // Example with two minimal upper bounds:
780 // c d
781 // / \ /
782 // a b
783 let mut non_join_lattice: Lattice<&str> = Lattice::new();
784 non_join_lattice.add_relation("a", "c");
785 non_join_lattice.add_relation("a", "d");
786 non_join_lattice.add_relation("b", "c");
787 non_join_lattice.add_relation("b", "d");
788
789 assert_eq!(non_join_lattice.join("a", "b"), None);
790 assert_eq!(non_join_lattice.meet("c", "d"), None);
791
792 // Example with two maximal lower bounds:
793 // a b
794 // / \ / \
795 // c d
796 let mut non_meet_lattice: Lattice<&str> = Lattice::new();
797 non_meet_lattice.add_relation("c", "a");
798 non_meet_lattice.add_relation("d", "a");
799 non_meet_lattice.add_relation("c", "b");
800 non_meet_lattice.add_relation("d", "b");
801
802 assert_eq!(non_meet_lattice.meet("a", "b"), None);
803 assert_eq!(non_meet_lattice.join("c", "d"), None);
804 }
805
806 #[test]
807 fn test_graphviz_output() {
808 let temp_dir = tempfile::tempdir().unwrap();
809 let temp_path = temp_dir.path().join("test_m_shape_lattice.dot");
810 let filename = temp_path.to_str().unwrap();
811 println!("--- M-shape Example - Saving to {filename} ---");
812 m_lattice().save_to_dot_file(filename).expect("Failed to save M-shape lattice");
813
814 // Check if the file was created
815 assert!(temp_path.exists());
816
817 // Clean up the temporary directory
818 drop(temp_dir);
819 }
820
821 #[test]
822 #[ignore = "Manual test to see output of M-shape lattice"]
823 fn test_graphviz_output_manual() {
824 let filename = "test_m_shape_lattice.dot";
825 println!("--- M-shape Example - Saving to {filename} ---");
826 m_lattice().save_to_dot_file(filename).expect("Failed to save M-shape lattice");
827 }
828
829 #[test]
830 fn test_downset() {
831 let lattice = diamond_lattice();
832 let downset = lattice.downset(4);
833 assert_eq!(downset, HashSet::from([1, 2, 3, 4]));
834
835 let downset = lattice.downset(2);
836 assert_eq!(downset, HashSet::from([1, 2]));
837
838 let downset = lattice.downset(1);
839 assert_eq!(downset, HashSet::from([1]));
840 }
841
842 #[test]
843 fn test_upset() {
844 let lattice = diamond_lattice();
845 let upset = lattice.upset(4);
846 assert_eq!(upset, HashSet::from([4]));
847
848 let upset = lattice.upset(2);
849 assert_eq!(upset, HashSet::from([2, 4]));
850
851 let upset = lattice.upset(1);
852 assert_eq!(upset, HashSet::from([1, 2, 3, 4]));
853 }
854
855 #[test]
856 fn test_successors() {
857 let lattice = diamond_lattice();
858 let successors = lattice.successors(1);
859 assert_eq!(successors, HashSet::from([2, 3]));
860 }
861
862 #[test]
863 fn test_predecessors() {
864 let lattice = diamond_lattice();
865 let predecessors = lattice.predecessors(4);
866 assert_eq!(predecessors, HashSet::from([2, 3]));
867 }
868}