algebra/
join_semilattice.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9//! Concrete **join-semilattice** types and building blocks.
10//!
11//! This module provides a small toolkit of lattice wrappers
12//! convenient for CRDTs and other monotone data structures.
13//!
14//! # Overview
15//!
16//! - [`Max<T>`] / [`Min<T>`]: wrapper types where `join` is `max`
17//!   or `min` on the inner value. Useful for timestamps, counters,
18//!   or high/low-water marks.
19//!
20//! - [`Any`] / [`All`]: boolean lattices where `join` is `||` (OR)
21//!   or `&&` (AND). Useful for error flags, presence checks,
22//!   or invariants.
23//!
24//! - [`LatticeMap<K, V>`]: pointwise map lattice over `HashMap<K, V>`
25//!   where `V` is a lattice; `join` unions keys and joins overlapping
26//!   values. Ideal for per-replica state maps.
27//!
28//! # Example
29//!
30//! ```rust
31//! use algebra::JoinSemilattice;
32//! use algebra::Max;
33//!
34//! let x: Max<_> = 1.into();
35//! let y: Max<_> = 3.into();
36//!
37//! // join = max
38//! let z = x.join(&y);
39//! assert_eq!(z.0, 3);
40//! ```
41//!
42//! These primitives are intentionally small and generic. Higher-level
43//! CRDTs build on them to express their replica states as lattices with
44//! well-defined, convergent merge operations.
45
46use std::collections::HashMap;
47use std::hash::Hash;
48
49use serde::Deserialize;
50use serde::Serialize;
51
52use super::BoundedJoinSemilattice;
53use super::JoinSemilattice;
54
55// Max<T>: join = max
56
57/// Newtype wrapper for an `Ord` type where `join` is `max`.
58///
59/// - `join = max(a, b)`
60/// - `bottom = T::MIN` (when T: Bounded)
61///
62/// # Example
63/// ```
64/// use algebra::JoinSemilattice;
65/// use algebra::Max;
66///
67/// let a = Max(5);
68/// let b = Max(10);
69/// assert_eq!(a.join(&b), Max(10));
70/// ```
71#[derive(
72    Clone,
73    Copy,
74    Debug,
75    PartialEq,
76    Eq,
77    PartialOrd,
78    Ord,
79    Hash,
80    Serialize,
81    Deserialize,
82    typeuri::Named
83)]
84pub struct Max<T>(pub T);
85
86impl<T: Ord + Clone> JoinSemilattice for Max<T> {
87    fn join(&self, other: &Self) -> Self {
88        if self.0 >= other.0 {
89            self.clone()
90        } else {
91            other.clone()
92        }
93    }
94}
95
96impl<T: Ord + Clone + num_traits::Bounded> BoundedJoinSemilattice for Max<T> {
97    fn bottom() -> Self {
98        Max(num_traits::Bounded::min_value())
99    }
100}
101
102impl<T> From<T> for Max<T> {
103    fn from(value: T) -> Self {
104        Max(value)
105    }
106}
107
108impl<T: Ord + Clone + num_traits::Bounded> Default for Max<T> {
109    fn default() -> Self {
110        Self::bottom()
111    }
112}
113
114impl<T> Max<T> {
115    /// Get the inner value.
116    pub fn get(&self) -> &T {
117        &self.0
118    }
119}
120
121// Min<T>: join = min
122
123/// Newtype wrapper for an `Ord` type where `join` is `min`.
124///
125/// - `join = min(a, b)`
126/// - `bottom = T::MAX` (when T: Bounded)
127///
128/// # Example
129/// ```
130/// use algebra::JoinSemilattice;
131/// use algebra::Min;
132///
133/// let a = Min(5);
134/// let b = Min(10);
135/// assert_eq!(a.join(&b), Min(5));
136/// ```
137#[derive(
138    Clone,
139    Copy,
140    Debug,
141    PartialEq,
142    Eq,
143    PartialOrd,
144    Ord,
145    Hash,
146    Serialize,
147    Deserialize,
148    typeuri::Named
149)]
150pub struct Min<T>(pub T);
151
152impl<T: Ord + Clone> JoinSemilattice for Min<T> {
153    fn join(&self, other: &Self) -> Self {
154        if self.0 <= other.0 {
155            self.clone()
156        } else {
157            other.clone()
158        }
159    }
160}
161
162impl<T: Ord + Clone + num_traits::Bounded> BoundedJoinSemilattice for Min<T> {
163    fn bottom() -> Self {
164        Min(num_traits::Bounded::max_value())
165    }
166}
167
168impl<T> From<T> for Min<T> {
169    fn from(value: T) -> Self {
170        Min(value)
171    }
172}
173
174impl<T: Ord + Clone + num_traits::Bounded> Default for Min<T> {
175    fn default() -> Self {
176        Self::bottom()
177    }
178}
179
180impl<T> Min<T> {
181    /// Get the inner value.
182    pub fn get(&self) -> &T {
183        &self.0
184    }
185}
186
187// Any: join = OR (disjunction)
188
189/// Newtype wrapper for `bool` where `join` is logical OR.
190///
191/// - `join = a || b`
192/// - `bottom = false`
193///
194/// # Example
195/// ```
196/// use algebra::Any;
197/// use algebra::JoinSemilattice;
198///
199/// let a = Any(false);
200/// let b = Any(true);
201/// assert_eq!(a.join(&b), Any(true));
202/// ```
203#[derive(
204    Clone,
205    Copy,
206    Debug,
207    PartialEq,
208    Eq,
209    PartialOrd,
210    Ord,
211    Hash,
212    Serialize,
213    Deserialize,
214    typeuri::Named
215)]
216pub struct Any(pub bool);
217
218impl JoinSemilattice for Any {
219    fn join(&self, other: &Self) -> Self {
220        Any(self.0 || other.0)
221    }
222}
223
224impl BoundedJoinSemilattice for Any {
225    fn bottom() -> Self {
226        Any(false)
227    }
228}
229
230impl From<bool> for Any {
231    fn from(value: bool) -> Self {
232        Any(value)
233    }
234}
235
236// All: join = AND (conjunction, dual order)
237
238/// Newtype wrapper for `bool` where `join` is logical AND.
239///
240/// `All(bool)` forms a join-semilattice under the **dual** boolean order
241/// (true < false):
242///
243/// - `All(a).join(&All(b)) == All(a && b)`
244/// - Bottom element is `All(true)`
245///
246/// This is useful for combining boolean conditions where "all must
247/// be true", such as validation checks, preconditions, or invariants.
248///
249/// Note: The dual order may seem counterintuitive, but it makes `AND`
250/// the join operation (least upper bound). This mirrors how [`Min`] uses
251/// the dual order to make `min` the join.
252///
253/// # Example
254///
255/// ```
256/// use algebra::All;
257/// use algebra::JoinSemilattice;
258///
259/// let a = All(true);
260/// let b = All(false);
261/// assert_eq!(a.join(&b), All(false));
262/// ```
263#[derive(
264    Clone,
265    Copy,
266    Debug,
267    PartialEq,
268    Eq,
269    Hash,
270    Serialize,
271    Deserialize,
272    typeuri::Named
273)]
274pub struct All(pub bool);
275
276impl JoinSemilattice for All {
277    fn join(&self, other: &Self) -> Self {
278        All(self.0 && other.0)
279    }
280}
281
282impl BoundedJoinSemilattice for All {
283    fn bottom() -> Self {
284        All(true)
285    }
286}
287
288impl From<bool> for All {
289    fn from(value: bool) -> Self {
290        All(value)
291    }
292}
293
294// LatticeMap<K, V>
295
296/// Pointwise map lattice over `HashMap`.
297///
298/// This is a reusable building block for CRDT states that look like
299/// "map from IDs to lattice values".
300///
301/// Keys are optional; values form a join-semilattice. The induced
302/// lattice order is:
303///
304///   m1 ≤ m2  iff  for all k, m1[k] ≤ m2[k]
305///
306/// Operationally, `join` is:
307/// - keys: union of the key sets
308/// - values: pointwise `join` on overlapping keys
309///
310/// Bottom is the empty map.
311///
312/// # Key growth and deletion
313///
314/// Under this lattice, the **set of keys grows monotonically** under
315/// `join` (it is the union of key sets). As a result, `LatticeMap`
316/// does **not** directly support deletion semantics (there is no
317/// "remove" operation that is monotone w.r.t. this order).
318///
319/// Different applications want different deletion semantics and may
320/// require causal context, so `LatticeMap` intentionally leaves
321/// deletion policy to composition.
322///
323/// To model deletions, encode them in the *value* lattice `V`
324/// (tombstones), or use a dedicated set/map CRDT depending on the
325/// desired semantics. For example, one simple tombstone pattern is:
326///
327/// ```rust,ignore
328/// use algebra::{LatticeMap, LWW};
329///
330/// // Treat `None` as "deleted" at the application layer.
331/// // Conflicts resolve via LWW; a delete can be represented by
332/// // writing `None`.
333/// type Tombstoned<V> = LWW<Option<V>>;
334/// type Map<K, V> = LatticeMap<K, Tombstoned<V>>;
335/// ```
336///
337/// (If this pattern becomes common, we can add a small helper wrapper
338/// type later, but the policy is intentionally left to composition.)
339///
340/// # Example: Watermark tracking
341///
342/// Track the low watermark across multiple ranks where each rank
343/// reports its progress monotonically:
344///
345/// ```
346/// use algebra::JoinSemilattice;
347/// use algebra::LatticeMap;
348/// use algebra::Min;
349///
350/// // Rank 0 reports progress: 100
351/// let mut state1: LatticeMap<u32, Min<u64>> = LatticeMap::new();
352/// state1.insert(0, Min(100));
353///
354/// // Rank 1 reports progress: 200
355/// let mut state2: LatticeMap<u32, Min<u64>> = LatticeMap::new();
356/// state2.insert(1, Min(200));
357///
358/// // Merge: union keys, pointwise min on values
359/// let merged = state1.join(&state2);
360///
361/// // Low watermark is the min across all ranks
362/// let watermark = merged.iter().map(|(_, v)| v.0).min().unwrap();
363/// assert_eq!(watermark, 100); // min(rank0=100, rank1=200)
364/// ```
365///
366/// # Commutativity and Idempotence
367///
368/// Unlike a simple HashMap with last-write-wins merge, `LatticeMap`
369/// provides true commutativity:
370///
371/// ```
372/// use algebra::JoinSemilattice;
373/// use algebra::LatticeMap;
374/// use algebra::Min;
375///
376/// let mut a: LatticeMap<u32, Min<i64>> = LatticeMap::new();
377/// a.insert(0, Min(10));
378///
379/// let mut b: LatticeMap<u32, Min<i64>> = LatticeMap::new();
380/// b.insert(0, Min(20));
381///
382/// // Join is commutative: a ⊔ b = b ⊔ a
383/// assert_eq!(a.join(&b), b.join(&a));
384///
385/// // Join is idempotent: a ⊔ a = a
386/// assert_eq!(a.join(&a), a);
387/// ```
388#[derive(Clone, Debug, Serialize, Deserialize)]
389#[serde(bound(
390    serialize = "K: Eq + Hash + Serialize, V: Serialize",
391    deserialize = "K: Eq + Hash + Deserialize<'de>, V: Deserialize<'de>"
392))]
393pub struct LatticeMap<K, V> {
394    inner: HashMap<K, V>,
395}
396
397// Manual impl to keep bounds minimal and aligned with HashMap
398// equality: HashMap<K, V>: PartialEq requires K: Eq + Hash and V:
399// PartialEq.
400impl<K, V> PartialEq for LatticeMap<K, V>
401where
402    K: Eq + Hash,
403    V: PartialEq,
404{
405    fn eq(&self, other: &Self) -> bool {
406        self.inner == other.inner
407    }
408}
409
410impl<K, V> Eq for LatticeMap<K, V>
411where
412    K: Eq + Hash,
413    V: Eq,
414{
415}
416
417impl<K, V> LatticeMap<K, V>
418where
419    K: Eq + Hash,
420{
421    /// Create an empty map lattice.
422    pub fn new() -> Self {
423        Self {
424            inner: HashMap::new(),
425        }
426    }
427
428    /// Insert or replace a value for a key.
429    pub fn insert(&mut self, k: K, v: V) {
430        self.inner.insert(k, v);
431    }
432
433    /// Get a reference to the value for this key, if present.
434    pub fn get(&self, k: &K) -> Option<&V> {
435        self.inner.get(k)
436    }
437
438    /// Iterate over `(key, value)` pairs.
439    pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
440        self.inner.iter()
441    }
442
443    /// Access the underlying `HashMap`.
444    pub fn as_inner(&self) -> &HashMap<K, V> {
445        &self.inner
446    }
447
448    /// Consume the wrapper and return the underlying `HashMap`.
449    pub fn into_inner(self) -> HashMap<K, V> {
450        self.inner
451    }
452
453    /// Number of entries.
454    pub fn len(&self) -> usize {
455        self.inner.len()
456    }
457
458    /// Is the map empty?
459    pub fn is_empty(&self) -> bool {
460        self.inner.is_empty()
461    }
462}
463
464impl<K, V> Default for LatticeMap<K, V>
465where
466    K: Eq + Hash,
467{
468    fn default() -> Self {
469        Self::new()
470    }
471}
472
473impl<K, V> JoinSemilattice for LatticeMap<K, V>
474where
475    K: Eq + Hash + Clone,
476    V: JoinSemilattice + Clone,
477{
478    fn join(&self, other: &Self) -> Self {
479        let mut out = self.inner.clone();
480
481        for (k, v_other) in &other.inner {
482            out.entry(k.clone())
483                .and_modify(|v_here| {
484                    *v_here = v_here.join(v_other);
485                })
486                .or_insert_with(|| v_other.clone());
487        }
488
489        LatticeMap { inner: out }
490    }
491}
492
493impl<K, V> BoundedJoinSemilattice for LatticeMap<K, V>
494where
495    K: Eq + Hash + Clone,
496    V: BoundedJoinSemilattice + Clone,
497{
498    fn bottom() -> Self {
499        LatticeMap {
500            inner: HashMap::new(),
501        }
502    }
503}
504
505use std::collections::HashSet;
506
507/// `HashSet<T>`: join = union
508///
509/// A `HashSet<T>` forms a join-semilattice under set union:
510/// - join = union (∪)
511/// - bottom = empty set (∅)
512///
513/// This makes `HashSet` ideal for tracking "sets of things" in
514/// distributed systems where we want to accumulate all observed values
515/// across replicas.
516impl<T: Eq + Hash + Clone> JoinSemilattice for HashSet<T> {
517    fn join(&self, other: &Self) -> Self {
518        self.union(other).cloned().collect()
519    }
520}
521
522impl<T: Eq + Hash + Clone> BoundedJoinSemilattice for HashSet<T> {
523    fn bottom() -> Self {
524        HashSet::new()
525    }
526}
527
528use std::collections::BTreeSet;
529
530/// `BTreeSet<T>`: join = union
531///
532/// Same semantics as `HashSet`, but uses `Ord` instead of `Hash`.
533impl<T: Ord + Clone> JoinSemilattice for BTreeSet<T> {
534    fn join(&self, other: &Self) -> Self {
535        self.union(other).cloned().collect()
536    }
537}
538
539impl<T: Ord + Clone> BoundedJoinSemilattice for BTreeSet<T> {
540    fn bottom() -> Self {
541        BTreeSet::new()
542    }
543}
544
545/// `Option<L>`: lifted lattice
546///
547/// `Option<L>` lifts a lattice `L` by adding a new bottom element (`None`).
548/// - `None` is the bottom element
549/// - `Some(a).join(Some(b))` = `Some(a.join(b))`
550/// - `None.join(x)` = `x.join(None)` = `x`
551///
552/// This is useful for representing "optional" lattice values where
553/// absence is distinct from any present value.
554impl<L: JoinSemilattice + Clone> JoinSemilattice for Option<L> {
555    fn join(&self, other: &Self) -> Self {
556        match (self, other) {
557            (None, x) | (x, None) => x.clone(),
558            (Some(a), Some(b)) => Some(a.join(b)),
559        }
560    }
561}
562
563impl<L: JoinSemilattice + Clone> BoundedJoinSemilattice for Option<L> {
564    fn bottom() -> Self {
565        None
566    }
567}
568
569/// `()`: trivial unit lattice
570///
571/// The unit type forms a trivial lattice with a single element.
572impl JoinSemilattice for () {
573    fn join(&self, _other: &Self) -> Self {}
574}
575
576impl BoundedJoinSemilattice for () {
577    fn bottom() -> Self {}
578}
579
580// Tests
581
582#[cfg(test)]
583mod tests {
584    use super::*;
585
586    // Max tests
587
588    #[test]
589    fn max_join_is_maximum() {
590        let a = Max(5);
591        let b = Max(10);
592        assert_eq!(a.join(&b), Max(10));
593        assert_eq!(b.join(&a), Max(10));
594    }
595
596    #[test]
597    fn max_bottom_is_min_value() {
598        assert_eq!(Max::<i32>::bottom(), Max(i32::MIN));
599        assert_eq!(Max::<u64>::bottom(), Max(u64::MIN));
600    }
601
602    #[test]
603    fn max_is_idempotent() {
604        let x = Max(42);
605        assert_eq!(x.join(&x), x);
606    }
607
608    #[test]
609    fn max_is_commutative() {
610        let a = Max(3);
611        let b = Max(7);
612        assert_eq!(a.join(&b), b.join(&a));
613    }
614
615    #[test]
616    fn max_is_associative() {
617        let a = Max(1);
618        let b = Max(5);
619        let c = Max(3);
620        assert_eq!(a.join(&b).join(&c), a.join(&b.join(&c)));
621    }
622
623    #[test]
624    fn max_bottom_is_identity() {
625        let x = Max(100);
626        assert_eq!(Max::bottom().join(&x), x);
627        assert_eq!(x.join(&Max::bottom()), x);
628    }
629
630    // Min tests
631
632    #[test]
633    fn min_join_is_minimum() {
634        let a = Min(5);
635        let b = Min(10);
636        assert_eq!(a.join(&b), Min(5));
637        assert_eq!(b.join(&a), Min(5));
638    }
639
640    #[test]
641    fn min_bottom_is_max_value() {
642        assert_eq!(Min::<i32>::bottom(), Min(i32::MAX));
643        assert_eq!(Min::<u64>::bottom(), Min(u64::MAX));
644    }
645
646    #[test]
647    fn min_is_idempotent() {
648        let x = Min(42);
649        assert_eq!(x.join(&x), x);
650    }
651
652    #[test]
653    fn min_is_commutative() {
654        let a = Min(3);
655        let b = Min(7);
656        assert_eq!(a.join(&b), b.join(&a));
657    }
658
659    #[test]
660    fn min_is_associative() {
661        let a = Min(1);
662        let b = Min(5);
663        let c = Min(3);
664        assert_eq!(a.join(&b).join(&c), a.join(&b.join(&c)));
665    }
666
667    #[test]
668    fn min_bottom_is_identity() {
669        let x = Min(100);
670        assert_eq!(Min::bottom().join(&x), x);
671        assert_eq!(x.join(&Min::bottom()), x);
672    }
673
674    // Any tests
675
676    #[test]
677    fn any_join_is_or() {
678        assert_eq!(Any(false).join(&Any(false)), Any(false));
679        assert_eq!(Any(false).join(&Any(true)), Any(true));
680        assert_eq!(Any(true).join(&Any(false)), Any(true));
681        assert_eq!(Any(true).join(&Any(true)), Any(true));
682    }
683
684    #[test]
685    fn any_bottom_is_false() {
686        assert_eq!(Any::bottom(), Any(false));
687        assert_eq!(Any(false).join(&Any(true)), Any(true));
688        assert_eq!(Any(true).join(&Any(false)), Any(true));
689    }
690
691    #[test]
692    fn any_is_idempotent() {
693        assert_eq!(Any(false).join(&Any(false)), Any(false));
694        assert_eq!(Any(true).join(&Any(true)), Any(true));
695    }
696
697    // All tests
698
699    #[test]
700    fn all_join_is_and() {
701        assert_eq!(All(false).join(&All(false)), All(false));
702        assert_eq!(All(false).join(&All(true)), All(false));
703        assert_eq!(All(true).join(&All(false)), All(false));
704        assert_eq!(All(true).join(&All(true)), All(true));
705    }
706
707    #[test]
708    fn all_bottom_is_true() {
709        assert_eq!(All::bottom(), All(true));
710        assert_eq!(All(true).join(&All(false)), All(false));
711        assert_eq!(All(false).join(&All(true)), All(false));
712    }
713
714    #[test]
715    fn all_is_idempotent() {
716        assert_eq!(All(false).join(&All(false)), All(false));
717        assert_eq!(All(true).join(&All(true)), All(true));
718    }
719
720    // From<T> tests
721
722    #[test]
723    fn max_from_value() {
724        let x: Max<i32> = 42.into();
725        assert_eq!(x, Max(42));
726        assert_eq!(Max::from(100u64), Max(100u64));
727    }
728
729    #[test]
730    fn min_from_value() {
731        let x: Min<i32> = 42.into();
732        assert_eq!(x, Min(42));
733        assert_eq!(Min::from(100u64), Min(100u64));
734    }
735
736    #[test]
737    fn any_from_bool() {
738        let t: Any = true.into();
739        let f: Any = false.into();
740        assert_eq!(t, Any(true));
741        assert_eq!(f, Any(false));
742    }
743
744    #[test]
745    fn all_from_bool() {
746        let t: All = true.into();
747        let f: All = false.into();
748        assert_eq!(t, All(true));
749        assert_eq!(f, All(false));
750    }
751
752    // Default tests
753
754    #[test]
755    fn max_default_is_bottom() {
756        assert_eq!(Max::<i32>::default(), Max::<i32>::bottom());
757        assert_eq!(Max::<u64>::default(), Max(u64::MIN));
758    }
759
760    #[test]
761    fn min_default_is_bottom() {
762        assert_eq!(Min::<i32>::default(), Min::<i32>::bottom());
763        assert_eq!(Min::<u64>::default(), Min(u64::MAX));
764    }
765
766    // Serialization round-trip tests
767
768    #[test]
769    fn max_serde_roundtrip() {
770        let original = Max(42i64);
771        let encoded = bincode::serialize(&original).unwrap();
772        let decoded: Max<i64> = bincode::deserialize(&encoded).unwrap();
773        assert_eq!(original, decoded);
774
775        // Also test with bottom value
776        let bottom = Max::<i64>::bottom();
777        let encoded = bincode::serialize(&bottom).unwrap();
778        let decoded: Max<i64> = bincode::deserialize(&encoded).unwrap();
779        assert_eq!(bottom, decoded);
780    }
781
782    #[test]
783    fn min_serde_roundtrip() {
784        let original = Min(42i64);
785        let encoded = bincode::serialize(&original).unwrap();
786        let decoded: Min<i64> = bincode::deserialize(&encoded).unwrap();
787        assert_eq!(original, decoded);
788
789        // Also test with bottom value
790        let bottom = Min::<i64>::bottom();
791        let encoded = bincode::serialize(&bottom).unwrap();
792        let decoded: Min<i64> = bincode::deserialize(&encoded).unwrap();
793        assert_eq!(bottom, decoded);
794    }
795
796    #[test]
797    fn any_serde_roundtrip() {
798        for value in [true, false] {
799            let original = Any(value);
800            let encoded = bincode::serialize(&original).unwrap();
801            let decoded: Any = bincode::deserialize(&encoded).unwrap();
802            assert_eq!(original, decoded);
803        }
804
805        // Also test bottom
806        let bottom = Any::bottom();
807        let encoded = bincode::serialize(&bottom).unwrap();
808        let decoded: Any = bincode::deserialize(&encoded).unwrap();
809        assert_eq!(bottom, decoded);
810    }
811
812    #[test]
813    fn all_serde_roundtrip() {
814        for value in [true, false] {
815            let original = All(value);
816            let encoded = bincode::serialize(&original).unwrap();
817            let decoded: All = bincode::deserialize(&encoded).unwrap();
818            assert_eq!(original, decoded);
819        }
820
821        // Also test bottom
822        let bottom = All::bottom();
823        let encoded = bincode::serialize(&bottom).unwrap();
824        let decoded: All = bincode::deserialize(&encoded).unwrap();
825        assert_eq!(bottom, decoded);
826    }
827
828    // LatticeMap tests
829
830    #[test]
831    fn lattice_map_new_is_empty() {
832        let m: LatticeMap<i32, Max<i32>> = LatticeMap::new();
833        assert!(m.is_empty());
834        assert_eq!(m.len(), 0);
835    }
836
837    #[test]
838    fn lattice_map_bottom_is_empty() {
839        let m: LatticeMap<i32, Max<i32>> = BoundedJoinSemilattice::bottom();
840        assert!(m.is_empty());
841        assert_eq!(m.len(), 0);
842    }
843
844    #[test]
845    fn lattice_map_insert_and_get() {
846        let mut m: LatticeMap<&str, Max<i32>> = LatticeMap::new();
847        m.insert("foo", Max(42));
848        assert_eq!(m.get(&"foo"), Some(&Max(42)));
849        assert_eq!(m.get(&"bar"), None);
850        assert_eq!(m.len(), 1);
851        assert!(!m.is_empty());
852    }
853
854    #[test]
855    fn lattice_map_join_is_pointwise() {
856        // Values are Max<i32>, so join = max on each entry.
857        let mut m1: LatticeMap<&str, Max<i32>> = LatticeMap::new();
858        m1.insert("a", Max(1));
859        m1.insert("b", Max(10));
860
861        let mut m2: LatticeMap<&str, Max<i32>> = LatticeMap::new();
862        m2.insert("b", Max(7));
863        m2.insert("c", Max(3));
864
865        let j = m1.join(&m2);
866
867        // Keys: union of {a,b} and {b,c} = {a,b,c}. Values: pointwise max.
868        assert_eq!(j.get(&"a"), Some(&Max(1)));
869        assert_eq!(j.get(&"b"), Some(&Max(10))); // max(10, 7)
870        assert_eq!(j.get(&"c"), Some(&Max(3)));
871    }
872
873    #[test]
874    fn lattice_map_join_is_commutative() {
875        let mut m1: LatticeMap<u32, Min<i64>> = LatticeMap::new();
876        m1.insert(0, Min(10));
877
878        let mut m2: LatticeMap<u32, Min<i64>> = LatticeMap::new();
879        m2.insert(0, Min(20));
880
881        assert_eq!(m1.join(&m2), m2.join(&m1));
882    }
883
884    #[test]
885    fn lattice_map_join_is_idempotent() {
886        let mut m: LatticeMap<u32, Max<i64>> = LatticeMap::new();
887        m.insert(1, Max(100));
888        m.insert(2, Max(200));
889
890        assert_eq!(m.join(&m), m);
891    }
892
893    #[test]
894    fn lattice_map_watermark_example() {
895        // Simulate distributed watermark tracking
896        let mut state1: LatticeMap<u32, Min<u64>> = LatticeMap::new();
897        state1.insert(0, Min(100)); // Rank 0 at 100
898        state1.insert(1, Min(200)); // Rank 1 at 200
899
900        let mut state2: LatticeMap<u32, Min<u64>> = LatticeMap::new();
901        state2.insert(0, Min(150)); // Rank 0 updated to 150
902        state2.insert(2, Min(50)); // Rank 2 at 50
903
904        // Merge states
905        let merged = state1.join(&state2);
906
907        // Check pointwise min
908        assert_eq!(merged.get(&0), Some(&Min(100))); // min(100, 150)
909        assert_eq!(merged.get(&1), Some(&Min(200))); // only in state1
910        assert_eq!(merged.get(&2), Some(&Min(50))); // only in state2
911
912        // Overall low watermark
913        let watermark = merged.iter().map(|(_, v)| v.0).min().unwrap();
914        assert_eq!(watermark, 50);
915    }
916
917    #[test]
918    fn lattice_map_iter_works() {
919        let mut m: LatticeMap<&str, Max<i32>> = LatticeMap::new();
920        m.insert("a", Max(1));
921        m.insert("b", Max(2));
922
923        let items: HashMap<_, _> = m.iter().map(|(k, v)| (*k, v.0)).collect();
924        assert_eq!(items.get(&"a"), Some(&1));
925        assert_eq!(items.get(&"b"), Some(&2));
926        assert_eq!(items.len(), 2);
927    }
928
929    #[test]
930    fn lattice_map_serde_roundtrip() {
931        let mut original: LatticeMap<u32, Min<i64>> = LatticeMap::new();
932        original.insert(1, Min(10));
933        original.insert(2, Min(20));
934
935        let encoded = bincode::serialize(&original).unwrap();
936        let decoded: LatticeMap<u32, Min<i64>> = bincode::deserialize(&encoded).unwrap();
937
938        assert_eq!(original, decoded);
939    }
940
941    // HashSet tests
942
943    #[test]
944    fn hashset_join_is_union() {
945        let mut a = HashSet::new();
946        a.insert(1);
947        a.insert(2);
948
949        let mut b = HashSet::new();
950        b.insert(2);
951        b.insert(3);
952
953        let c = a.join(&b);
954        assert_eq!(c.len(), 3);
955        assert!(c.contains(&1));
956        assert!(c.contains(&2));
957        assert!(c.contains(&3));
958    }
959
960    #[test]
961    fn hashset_bottom_is_empty() {
962        let bottom: HashSet<i32> = HashSet::bottom();
963        assert!(bottom.is_empty());
964
965        let mut a = HashSet::new();
966        a.insert(42);
967
968        assert_eq!(bottom.join(&a), a);
969        assert_eq!(a.join(&bottom), a);
970    }
971
972    #[test]
973    fn hashset_is_idempotent() {
974        let mut a = HashSet::new();
975        a.insert(1);
976        a.insert(2);
977
978        assert_eq!(a.join(&a), a);
979    }
980
981    #[test]
982    fn hashset_is_commutative() {
983        let mut a = HashSet::new();
984        a.insert(1);
985        a.insert(2);
986
987        let mut b = HashSet::new();
988        b.insert(2);
989        b.insert(3);
990
991        assert_eq!(a.join(&b), b.join(&a));
992    }
993
994    #[test]
995    fn hashset_is_associative() {
996        let mut a = HashSet::new();
997        a.insert(1);
998
999        let mut b = HashSet::new();
1000        b.insert(2);
1001
1002        let mut c = HashSet::new();
1003        c.insert(3);
1004
1005        assert_eq!(a.join(&b).join(&c), a.join(&b.join(&c)));
1006    }
1007
1008    #[test]
1009    fn hashset_semigroup_blanket_impl() {
1010        // Semigroup::combine is blanket-impl'd from JoinSemilattice::join
1011        // so combine(a, b) == join(a, b) == union
1012        let mut a: HashSet<&str> = HashSet::new();
1013        a.insert("foo");
1014
1015        let mut b: HashSet<&str> = HashSet::new();
1016        b.insert("bar");
1017
1018        // Test via join (Semigroup::combine delegates to join)
1019        let c = a.join(&b);
1020        assert_eq!(c.len(), 2);
1021        assert!(c.contains(&"foo"));
1022        assert!(c.contains(&"bar"));
1023    }
1024
1025    #[test]
1026    fn hashset_monoid_blanket_impl() {
1027        // Monoid::empty is blanket-impl'd from BoundedJoinSemilattice::bottom
1028        // so empty() == bottom() == empty set
1029        let empty: HashSet<i32> = HashSet::bottom();
1030        assert!(empty.is_empty());
1031
1032        let mut a = HashSet::new();
1033        a.insert(42);
1034
1035        // Test identity: bottom.join(a) == a == a.join(bottom)
1036        assert_eq!(empty.join(&a), a);
1037        assert_eq!(a.join(&empty), a);
1038    }
1039
1040    // BTreeSet tests
1041
1042    #[test]
1043    fn btreeset_join_is_union() {
1044        let a: BTreeSet<i32> = [1, 2].into_iter().collect();
1045        let b: BTreeSet<i32> = [2, 3].into_iter().collect();
1046
1047        let c = a.join(&b);
1048        assert_eq!(c.len(), 3);
1049        assert!(c.contains(&1));
1050        assert!(c.contains(&2));
1051        assert!(c.contains(&3));
1052    }
1053
1054    #[test]
1055    fn btreeset_bottom_is_empty() {
1056        let bottom: BTreeSet<i32> = BTreeSet::bottom();
1057        assert!(bottom.is_empty());
1058
1059        let a: BTreeSet<i32> = [42].into_iter().collect();
1060        assert_eq!(bottom.join(&a), a);
1061        assert_eq!(a.join(&bottom), a);
1062    }
1063
1064    #[test]
1065    fn btreeset_is_idempotent() {
1066        let a: BTreeSet<i32> = [1, 2].into_iter().collect();
1067        assert_eq!(a.join(&a), a);
1068    }
1069
1070    #[test]
1071    fn btreeset_is_commutative() {
1072        let a: BTreeSet<i32> = [1, 2].into_iter().collect();
1073        let b: BTreeSet<i32> = [2, 3].into_iter().collect();
1074        assert_eq!(a.join(&b), b.join(&a));
1075    }
1076
1077    // Option tests
1078
1079    #[test]
1080    fn option_join_lifts_inner() {
1081        let a: Option<Max<i32>> = Some(Max(5));
1082        let b: Option<Max<i32>> = Some(Max(10));
1083
1084        assert_eq!(a.join(&b), Some(Max(10)));
1085    }
1086
1087    #[test]
1088    fn option_join_with_none() {
1089        let a: Option<Max<i32>> = Some(Max(5));
1090        let none: Option<Max<i32>> = None;
1091
1092        // None acts as identity for join
1093        assert_eq!(a.join(&none), a);
1094        assert_eq!(none.join(&a), a);
1095        assert_eq!(none.join(&none), None);
1096    }
1097
1098    #[test]
1099    fn option_bottom_is_none() {
1100        let bottom: Option<Max<i32>> = Option::bottom();
1101        assert_eq!(bottom, None);
1102    }
1103
1104    #[test]
1105    fn option_with_hashset() {
1106        let a: Option<HashSet<i32>> = Some([1, 2].into_iter().collect());
1107        let b: Option<HashSet<i32>> = Some([2, 3].into_iter().collect());
1108
1109        let c = a.join(&b);
1110        let expected: HashSet<i32> = [1, 2, 3].into_iter().collect();
1111        assert_eq!(c, Some(expected));
1112    }
1113
1114    #[test]
1115    fn option_is_idempotent() {
1116        let a: Option<Max<i32>> = Some(Max(5));
1117        assert_eq!(a.join(&a), a);
1118
1119        let none: Option<Max<i32>> = None;
1120        assert_eq!(none.join(&none), none);
1121    }
1122
1123    #[test]
1124    fn option_is_commutative() {
1125        let a: Option<Max<i32>> = Some(Max(5));
1126        let b: Option<Max<i32>> = Some(Max(10));
1127        assert_eq!(a.join(&b), b.join(&a));
1128    }
1129
1130    // Unit tests
1131
1132    #[test]
1133    fn unit_join_is_trivial() {
1134        assert_eq!(().join(&()), ());
1135    }
1136
1137    #[test]
1138    fn unit_bottom_is_unit() {
1139        assert_eq!(<()>::bottom(), ());
1140    }
1141
1142    #[test]
1143    fn unit_is_idempotent() {
1144        assert_eq!(().join(&()), ());
1145    }
1146}