Skip to main content

quoracle/
expr.rs

1//! Expression algebra for defining quorum systems.
2//!
3//! An [`Expr`] describes which sets of nodes form a quorum:
4//! - [`Node`]: a single node
5//! - [`Or`] (`a + b`): any one child is a quorum
6//! - [`And`] (`a * b`): every child is needed
7//! - [`Choose`] ([`choose`]): any `k` of the children
8//!
9//! `Or`, `And`, and `Choose` are always non-empty and `Choose` always has
10//! `1 <= k <= children.len()`. Their fields are private so those
11//! invariants cannot be broken after construction.
12
13use crate::error::{Error, Result};
14use hashbrown::{HashMap, HashSet};
15use itertools::Itertools;
16use std::fmt::{self, Debug, Display};
17use std::hash::Hash;
18use std::ops::{Add, Mul};
19use std::time::Duration;
20
21/// Trait for types that can be used as node identifiers
22/// in quorum expressions.
23pub trait Element:
24    Ord + Clone + Hash + Debug + Display + Send + Sync + 'static
25{
26}
27
28impl<T> Element for T where
29    T: Ord + Clone + Hash + Debug + Display + Send + Sync + 'static
30{
31}
32
33/// A node in a quorum system.
34///
35/// Nodes are identified by `x`: equality, ordering, and hashing only look
36/// at `x`. Capacities are in requests per unit time; the defaults are a
37/// capacity of 1.0 and a latency of 1 second.
38#[derive(Debug, Clone)]
39pub struct Node<T: Element> {
40    x: T,
41    read_capacity: f64,
42    write_capacity: f64,
43    latency: Duration,
44}
45
46impl<T: Element> PartialEq for Node<T> {
47    fn eq(&self, other: &Self) -> bool {
48        self.x == other.x
49    }
50}
51
52impl<T: Element> Eq for Node<T> {}
53
54impl<T: Element> Hash for Node<T> {
55    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
56        self.x.hash(state);
57    }
58}
59
60impl<T: Element> PartialOrd for Node<T> {
61    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
62        Some(self.cmp(other))
63    }
64}
65
66impl<T: Element> Ord for Node<T> {
67    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
68        self.x.cmp(&other.x)
69    }
70}
71
72impl<T: Element> Display for Node<T> {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        write!(f, "{}", self.x)
75    }
76}
77
78fn check_capacity(name: &str, c: f64) -> Result<f64> {
79    if c.is_finite() && c > 0.0 {
80        Ok(c)
81    } else {
82        Err(Error::InvalidExpression(format!(
83            "{name} must be finite and > 0, got {c}"
84        )))
85    }
86}
87
88impl<T: Element> Node<T> {
89    /// Create a node with capacity 1.0 and latency 1 second.
90    #[must_use]
91    pub fn new(x: T) -> Self {
92        Self {
93            x,
94            read_capacity: 1.0,
95            write_capacity: 1.0,
96            latency: Duration::from_secs(1),
97        }
98    }
99
100    /// Set one capacity for both reads and writes.
101    ///
102    /// # Errors
103    /// Returns [`Error::InvalidExpression`] unless `capacity` is finite
104    /// and positive.
105    pub fn with_capacity(self, capacity: f64) -> Result<Self> {
106        self.with_read_write_capacity(capacity, capacity)
107    }
108
109    /// Set separate read and write capacities.
110    ///
111    /// # Errors
112    /// Returns [`Error::InvalidExpression`] unless both capacities are
113    /// finite and positive.
114    pub fn with_read_write_capacity(
115        mut self,
116        read: f64,
117        write: f64,
118    ) -> Result<Self> {
119        self.read_capacity = check_capacity("read capacity", read)?;
120        self.write_capacity = check_capacity("write capacity", write)?;
121        Ok(self)
122    }
123
124    /// Set the latency for this node.
125    #[must_use]
126    pub fn with_latency(mut self, latency: Duration) -> Self {
127        self.latency = latency;
128        self
129    }
130
131    /// The node identifier.
132    #[must_use]
133    pub fn x(&self) -> &T {
134        &self.x
135    }
136
137    /// Read capacity (requests per unit time).
138    #[must_use]
139    pub fn read_capacity(&self) -> f64 {
140        self.read_capacity
141    }
142
143    /// Write capacity (requests per unit time).
144    #[must_use]
145    pub fn write_capacity(&self) -> f64 {
146        self.write_capacity
147    }
148
149    /// Latency of a request to this node.
150    #[must_use]
151    pub fn latency(&self) -> Duration {
152        self.latency
153    }
154}
155
156/// An expression describing which sets of nodes form a quorum.
157#[derive(Debug, Clone, PartialEq)]
158pub enum Expr<T: Element> {
159    /// A single node
160    Node(Node<T>),
161    /// At least one child expression must be satisfied
162    Or(Or<T>),
163    /// All child expressions must be satisfied
164    And(And<T>),
165    /// At least k child expressions must be satisfied
166    Choose(Choose<T>),
167}
168
169/// OR combinator: at least one child must be satisfied.
170#[derive(Debug, Clone, PartialEq)]
171pub struct Or<T: Element> {
172    children: Vec<Expr<T>>,
173}
174
175/// AND combinator: all children must be satisfied.
176#[derive(Debug, Clone, PartialEq)]
177pub struct And<T: Element> {
178    children: Vec<Expr<T>>,
179}
180
181/// CHOOSE combinator: at least `k` children must be satisfied.
182#[derive(Debug, Clone, PartialEq)]
183pub struct Choose<T: Element> {
184    k: usize,
185    children: Vec<Expr<T>>,
186}
187
188fn non_empty<T: Element>(
189    what: &str,
190    children: Vec<Expr<T>>,
191) -> Result<Vec<Expr<T>>> {
192    if children.is_empty() {
193        Err(Error::InvalidExpression(format!(
194            "{what} cannot be constructed with an empty list"
195        )))
196    } else {
197        Ok(children)
198    }
199}
200
201fn check_k(k: usize, n: usize) -> Result<()> {
202    if k == 0 || k > n {
203        Err(Error::InvalidExpression(format!(
204            "k must be in the range [1, {n}], got {k}"
205        )))
206    } else {
207        Ok(())
208    }
209}
210
211impl<T: Element> Or<T> {
212    /// Create an OR expression.
213    ///
214    /// # Errors
215    /// Returns [`Error::InvalidExpression`] if `children` is empty.
216    pub fn new(children: Vec<Expr<T>>) -> Result<Self> {
217        Ok(Self { children: non_empty("Or", children)? })
218    }
219
220    /// The child expressions.
221    #[must_use]
222    pub fn children(&self) -> &[Expr<T>] {
223        &self.children
224    }
225}
226
227impl<T: Element> And<T> {
228    /// Create an AND expression.
229    ///
230    /// # Errors
231    /// Returns [`Error::InvalidExpression`] if `children` is empty.
232    pub fn new(children: Vec<Expr<T>>) -> Result<Self> {
233        Ok(Self { children: non_empty("And", children)? })
234    }
235
236    /// The child expressions.
237    #[must_use]
238    pub fn children(&self) -> &[Expr<T>] {
239        &self.children
240    }
241}
242
243impl<T: Element> Choose<T> {
244    /// Create a CHOOSE expression.
245    ///
246    /// # Errors
247    /// Returns [`Error::InvalidExpression`] unless
248    /// `1 <= k <= children.len()`.
249    pub fn new(k: usize, children: Vec<Expr<T>>) -> Result<Self> {
250        check_k(k, children.len())?;
251        Ok(Self { k, children })
252    }
253
254    /// How many children must be satisfied.
255    #[must_use]
256    pub fn k(&self) -> usize {
257        self.k
258    }
259
260    /// The child expressions.
261    #[must_use]
262    pub fn children(&self) -> &[Expr<T>] {
263        &self.children
264    }
265}
266
267// -- Display implementations --
268
269fn write_joined<T: Element>(
270    f: &mut fmt::Formatter<'_>,
271    children: &[Expr<T>],
272    sep: &str,
273) -> fmt::Result {
274    for (i, child) in children.iter().enumerate() {
275        if i > 0 {
276            f.write_str(sep)?;
277        }
278        write!(f, "{child}")?;
279    }
280    Ok(())
281}
282
283impl<T: Element> Display for Expr<T> {
284    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285        match self {
286            Expr::Node(n) => write!(f, "{n}"),
287            Expr::Or(o) => write!(f, "{o}"),
288            Expr::And(a) => write!(f, "{a}"),
289            Expr::Choose(c) => write!(f, "{c}"),
290        }
291    }
292}
293
294impl<T: Element> Display for Or<T> {
295    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296        f.write_str("(")?;
297        write_joined(f, &self.children, " + ")?;
298        f.write_str(")")
299    }
300}
301
302impl<T: Element> Display for And<T> {
303    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304        f.write_str("(")?;
305        write_joined(f, &self.children, " * ")?;
306        f.write_str(")")
307    }
308}
309
310impl<T: Element> Display for Choose<T> {
311    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312        write!(f, "choose{}(", self.k)?;
313        write_joined(f, &self.children, ", ")?;
314        f.write_str(")")
315    }
316}
317
318// -- Core expression methods --
319
320impl<T: Element> Expr<T> {
321    /// Iterate over the quorums of this expression.
322    ///
323    /// The same quorum can be produced more than once (for example by
324    /// `a + a`), and non-minimal quorums are included (for example
325    /// `{a, b}` from `a + a * b`).
326    pub fn quorums(&self) -> Box<dyn Iterator<Item = HashSet<T>> + '_> {
327        match self {
328            Expr::Node(node) => {
329                let mut s = HashSet::with_capacity(1);
330                s.insert(node.x.clone());
331                Box::new(std::iter::once(s))
332            }
333            Expr::Or(or) => {
334                Box::new(or.children.iter().flat_map(Expr::quorums))
335            }
336            Expr::And(and) => Box::new(and_quorums(&and.children)),
337            Expr::Choose(ch) => Box::new(choose_quorums(ch.k, &ch.children)),
338        }
339    }
340
341    /// Whether `xs` contains a quorum of this expression.
342    #[must_use]
343    pub fn is_quorum(&self, xs: &HashSet<T>) -> bool {
344        match self {
345            Expr::Node(node) => xs.contains(&node.x),
346            Expr::Or(or) => or.children.iter().any(|e| e.is_quorum(xs)),
347            Expr::And(and) => and.children.iter().all(|e| e.is_quorum(xs)),
348            Expr::Choose(ch) => {
349                ch.children.iter().filter(|e| e.is_quorum(xs)).count() >= ch.k
350            }
351        }
352    }
353
354    /// The identifiers of all nodes in this expression.
355    #[must_use]
356    pub fn elements(&self) -> HashSet<T> {
357        self.nodes().into_iter().map(|n| n.x).collect()
358    }
359
360    /// All nodes in this expression.
361    ///
362    /// If the same identifier appears more than once with different
363    /// capacities or latency, the first occurrence (in left-to-right order)
364    /// wins.
365    #[must_use]
366    pub fn nodes(&self) -> HashSet<Node<T>> {
367        let mut out = HashSet::new();
368        self.collect_nodes(&mut out);
369        out
370    }
371
372    fn collect_nodes(&self, out: &mut HashSet<Node<T>>) {
373        match self {
374            Expr::Node(node) => {
375                if !out.contains(node) {
376                    out.insert(node.clone());
377                }
378            }
379            Expr::Or(Or { children })
380            | Expr::And(And { children })
381            | Expr::Choose(Choose { children, .. }) => {
382                for c in children {
383                    c.collect_nodes(out);
384                }
385            }
386        }
387    }
388
389    /// The dual expression: `Or` and `And` swap, and `choose(k, n)` becomes
390    /// `choose(n - k + 1, n)`.
391    ///
392    /// Every quorum of an expression intersects every quorum of its dual.
393    #[must_use]
394    pub fn dual(&self) -> Self {
395        match self {
396            Expr::Node(_) => self.clone(),
397            Expr::Or(or) => Expr::And(And {
398                children: or.children.iter().map(Expr::dual).collect(),
399            }),
400            Expr::And(and) => Expr::Or(Or {
401                children: and.children.iter().map(Expr::dual).collect(),
402            }),
403            Expr::Choose(ch) => Expr::Choose(Choose {
404                k: ch.children.len() - ch.k + 1,
405                children: ch.children.iter().map(Expr::dual).collect(),
406            }),
407        }
408    }
409
410    /// Whether every node identifier appears at most once.
411    #[must_use]
412    pub fn dup_free(&self) -> bool {
413        self.elements().len() == self.num_leaves()
414    }
415
416    /// The resilience: the largest number of nodes that can fail while
417    /// some quorum is still fully alive.
418    ///
419    /// This equals `min_hitting_set(quorums) - 1`. Duplicate-free
420    /// expressions are solved in closed form. Otherwise the hitting set is
421    /// solved exactly as an integer program over the minimal quorums.
422    #[must_use]
423    pub fn resilience(&self) -> usize {
424        let min_failures = if self.dup_free() {
425            self.dup_free_min_failures()
426        } else {
427            min_hitting_set(self)
428        };
429        min_failures.saturating_sub(1)
430    }
431
432    fn num_leaves(&self) -> usize {
433        match self {
434            Expr::Node(_) => 1,
435            Expr::Or(Or { children })
436            | Expr::And(And { children })
437            | Expr::Choose(Choose { children, .. }) => {
438                children.iter().map(Expr::num_leaves).sum()
439            }
440        }
441    }
442
443    /// For duplicate-free expressions, the minimum number of node failures
444    /// that leaves no quorum alive.
445    fn dup_free_min_failures(&self) -> usize {
446        match self {
447            Expr::Node(_) => 1,
448            Expr::Or(or) => {
449                or.children.iter().map(Expr::dup_free_min_failures).sum()
450            }
451            Expr::And(and) => and
452                .children
453                .iter()
454                .map(Expr::dup_free_min_failures)
455                .min()
456                .unwrap_or(0),
457            Expr::Choose(ch) => {
458                let mut subfailures: Vec<usize> = ch
459                    .children
460                    .iter()
461                    .map(Expr::dup_free_min_failures)
462                    .collect();
463                subfailures.sort_unstable();
464                subfailures.iter().take(ch.children.len() - ch.k + 1).sum()
465            }
466        }
467    }
468}
469
470/// Cartesian product of child quorums, unioned.
471fn and_quorums<T: Element>(
472    children: &[Expr<T>],
473) -> impl Iterator<Item = HashSet<T>> + '_ {
474    let child_quorums: Vec<Vec<HashSet<T>>> =
475        children.iter().map(|e| e.quorums().collect()).collect();
476    union_product(child_quorums)
477}
478
479/// For each k-subset of children, the cartesian product of their quorums.
480fn choose_quorums<T: Element>(
481    k: usize,
482    children: &[Expr<T>],
483) -> impl Iterator<Item = HashSet<T>> + '_ {
484    let child_quorums: Vec<Vec<HashSet<T>>> =
485        children.iter().map(|e| e.quorums().collect()).collect();
486    (0..children.len()).combinations(k).flat_map(move |combo| {
487        union_product(combo.iter().map(|&i| child_quorums[i].clone()).collect())
488    })
489}
490
491fn union_product<T: Element>(
492    parts: Vec<Vec<HashSet<T>>>,
493) -> impl Iterator<Item = HashSet<T>> {
494    parts.into_iter().multi_cartesian_product().map(|subquorums| {
495        subquorums.into_iter().fold(HashSet::new(), |mut acc, q| {
496            acc.extend(q);
497            acc
498        })
499    })
500}
501
502/// Remove duplicate and non-minimal sets: keep only sets that are not
503/// supersets of another set in the collection.
504pub(crate) fn minimize<T: Element>(
505    mut sets: Vec<HashSet<T>>,
506) -> Vec<HashSet<T>> {
507    sets.sort_by_key(HashSet::len);
508    let mut minimal: Vec<HashSet<T>> = Vec::new();
509    for s in sets {
510        if !minimal.iter().any(|m| s.is_superset(m)) {
511            minimal.push(s);
512        }
513    }
514    minimal
515}
516
517/// Exact minimum hitting set of an expression's quorums.
518fn min_hitting_set<T: Element>(e: &Expr<T>) -> usize {
519    min_hitting_set_of(minimize(e.quorums().collect()))
520}
521
522/// Size of the smallest set that intersects every set in `sets`
523/// (0 if `sets` is empty).
524///
525/// Exact branch-and-bound: some element of the first un-hit set must be in
526/// any hitting set, so branching on those elements is exhaustive.
527// ponytail: exponential worst case (hitting set is NP-hard); fine for the
528// tens of nodes quorum systems have. Use an ILP solver if that changes.
529fn min_hitting_set_of<T: Element>(sets: Vec<HashSet<T>>) -> usize {
530    let mut index: HashMap<T, usize> = HashMap::new();
531    let sets: Vec<Vec<usize>> = sets
532        .into_iter()
533        .map(|q| {
534            q.into_iter()
535                .map(|x| {
536                    let n = index.len();
537                    *index.entry(x).or_insert(n)
538                })
539                .collect()
540        })
541        .collect();
542    let mut best = index.len();
543    let mut chosen = vec![false; index.len()];
544    hitting_set_search(&sets, &mut chosen, 0, &mut best);
545    best
546}
547
548fn hitting_set_search(
549    sets: &[Vec<usize>],
550    chosen: &mut [bool],
551    size: usize,
552    best: &mut usize,
553) {
554    if size >= *best {
555        return;
556    }
557    let Some(unhit) = sets.iter().find(|s| !s.iter().any(|&i| chosen[i]))
558    else {
559        *best = size;
560        return;
561    };
562    for &i in unhit {
563        chosen[i] = true;
564        hitting_set_search(sets, chosen, size + 1, best);
565        chosen[i] = false;
566    }
567}
568
569// -- Operator overloading --
570
571/// `Expr + Expr` produces an Or expression, flattening nested Or
572/// children.
573impl<T: Element> Add for Expr<T> {
574    type Output = Expr<T>;
575
576    fn add(self, rhs: Self) -> Self::Output {
577        let mut children = Vec::new();
578        for e in [self, rhs] {
579            match e {
580                Expr::Or(or) => children.extend(or.children),
581                other => children.push(other),
582            }
583        }
584        Expr::Or(Or { children })
585    }
586}
587
588/// `Expr * Expr` produces an And expression, flattening nested And
589/// children.
590impl<T: Element> Mul for Expr<T> {
591    type Output = Expr<T>;
592
593    fn mul(self, rhs: Self) -> Self::Output {
594        let mut children = Vec::new();
595        for e in [self, rhs] {
596            match e {
597                Expr::And(and) => children.extend(and.children),
598                other => children.push(other),
599            }
600        }
601        Expr::And(And { children })
602    }
603}
604
605impl<T: Element> From<Node<T>> for Expr<T> {
606    fn from(node: Node<T>) -> Self {
607        Expr::Node(node)
608    }
609}
610
611impl<T: Element> From<Or<T>> for Expr<T> {
612    fn from(e: Or<T>) -> Self {
613        Expr::Or(e)
614    }
615}
616
617impl<T: Element> From<And<T>> for Expr<T> {
618    fn from(e: And<T>) -> Self {
619        Expr::And(e)
620    }
621}
622
623impl<T: Element> From<Choose<T>> for Expr<T> {
624    fn from(e: Choose<T>) -> Self {
625        Expr::Choose(e)
626    }
627}
628
629// -- Helper functions --
630
631/// Create a choose expression. Returns `Or` when k == 1, `And`
632/// when k == n, and `Choose` otherwise.
633///
634/// # Errors
635/// Returns [`Error::InvalidExpression`] if `exprs` is empty or `k` is
636/// out of range `[1, len]`.
637pub fn choose<T: Element>(k: usize, exprs: Vec<Expr<T>>) -> Result<Expr<T>> {
638    let exprs = non_empty("choose", exprs)?;
639    check_k(k, exprs.len())?;
640    Ok(if k == 1 {
641        Expr::Or(Or { children: exprs })
642    } else if k == exprs.len() {
643        Expr::And(And { children: exprs })
644    } else {
645        Expr::Choose(Choose { k, children: exprs })
646    })
647}
648
649/// Create a majority quorum expression. Requires
650/// `floor(n/2) + 1` children to be satisfied.
651///
652/// # Errors
653/// Returns [`Error::InvalidExpression`] if `exprs` is empty.
654pub fn majority<T: Element>(exprs: Vec<Expr<T>>) -> Result<Expr<T>> {
655    let k = exprs.len() / 2 + 1;
656    choose(k, exprs)
657}
658
659#[cfg(test)]
660#[expect(clippy::unwrap_used)]
661mod tests {
662    use super::*;
663    use hashbrown::HashSet;
664
665    fn n(x: &str) -> Expr<String> {
666        Expr::Node(Node::new(x.to_string()))
667    }
668
669    fn set(items: &[&str]) -> HashSet<String> {
670        items.iter().map(|s| (*s).to_string()).collect()
671    }
672
673    fn quorum_set(e: &Expr<String>) -> HashSet<Vec<String>> {
674        e.quorums()
675            .map(|q| {
676                let mut v: Vec<String> = q.into_iter().collect();
677                v.sort();
678                v
679            })
680            .collect()
681    }
682
683    fn sorted_set(items: &[&str]) -> Vec<String> {
684        let mut v: Vec<String> =
685            items.iter().map(|s| (*s).to_string()).collect();
686        v.sort();
687        v.dedup();
688        v
689    }
690
691    fn assert_quorums(e: &Expr<String>, expected: &[&[&str]]) {
692        let got = quorum_set(e);
693        let want: HashSet<Vec<String>> =
694            expected.iter().map(|s| sorted_set(s)).collect();
695        assert_eq!(got, want, "quorums mismatch");
696    }
697
698    // -- quorums tests --
699
700    #[test]
701    fn test_quorums_or() {
702        let e = n("a") + n("b") + n("c");
703        assert_quorums(&e, &[&["a"], &["b"], &["c"]]);
704    }
705
706    #[test]
707    fn test_quorums_and() {
708        let e = n("a") * n("b") * n("c");
709        assert_quorums(&e, &[&["a", "b", "c"]]);
710    }
711
712    #[test]
713    fn test_quorums_mixed() {
714        let e = n("a") + n("b") * n("c");
715        assert_quorums(&e, &[&["a"], &["b", "c"]]);
716    }
717
718    #[test]
719    fn test_quorums_dup_and() {
720        let e = n("a") * n("a") * n("a");
721        assert_quorums(&e, &[&["a"]]);
722    }
723
724    #[test]
725    fn test_quorums_dup_or() {
726        let e = n("a") + n("a") + n("a");
727        assert_quorums(&e, &[&["a"]]);
728    }
729
730    #[test]
731    fn test_quorums_node_times_or() {
732        let e = n("a") * (n("a") + n("b"));
733        assert_quorums(&e, &[&["a"], &["a", "b"]]);
734    }
735
736    #[test]
737    fn test_quorums_choose_1() {
738        let e = choose(1, vec![n("a"), n("b"), n("c")]).unwrap();
739        assert_quorums(&e, &[&["a"], &["b"], &["c"]]);
740    }
741
742    #[test]
743    fn test_quorums_choose_2() {
744        let e = choose(2, vec![n("a"), n("b"), n("c")]).unwrap();
745        assert_quorums(&e, &[&["a", "b"], &["a", "c"], &["b", "c"]]);
746    }
747
748    #[test]
749    fn test_quorums_choose_3() {
750        let e = choose(3, vec![n("a"), n("b"), n("c")]).unwrap();
751        assert_quorums(&e, &[&["a", "b", "c"]]);
752    }
753
754    #[test]
755    fn test_quorums_cross_product() {
756        let e = (n("a") + n("b")) * (n("c") + n("d"));
757        assert_quorums(
758            &e,
759            &[&["a", "c"], &["a", "d"], &["b", "c"], &["b", "d"]],
760        );
761    }
762
763    #[test]
764    fn test_quorums_cross_product_dup() {
765        let e = (n("a") + n("b")) * (n("a") + n("c"));
766        assert_quorums(&e, &[&["a"], &["a", "c"], &["a", "b"], &["b", "c"]]);
767    }
768
769    #[test]
770    fn test_quorums_nested_choose() {
771        let e = choose(
772            2,
773            vec![
774                choose(2, vec![n("a"), n("b"), n("c")]).unwrap(),
775                choose(2, vec![n("d"), n("e"), n("f")]).unwrap(),
776                choose(2, vec![n("a"), n("c"), n("e")]).unwrap(),
777            ],
778        )
779        .unwrap();
780
781        // The Python test lists many quorums, but since sets
782        // deduplicate, we just check the total count matches
783        // and spot-check some quorums.
784        let qs = quorum_set(&e);
785        // Verify some specific quorums are present
786        assert!(qs.contains(&sorted_set(&["a", "b", "d", "e"])));
787        assert!(qs.contains(&sorted_set(&["b", "c", "d", "f"])));
788        assert!(qs.contains(&sorted_set(&["a", "c", "e", "f"])));
789    }
790
791    // -- is_quorum tests --
792
793    #[test]
794    fn test_is_quorum_or() {
795        let expr = n("a") + n("b") + n("c");
796        assert!(expr.is_quorum(&set(&["a"])));
797        assert!(expr.is_quorum(&set(&["b"])));
798        assert!(expr.is_quorum(&set(&["c"])));
799        assert!(expr.is_quorum(&set(&["a", "b"])));
800        assert!(expr.is_quorum(&set(&["a", "c"])));
801        assert!(expr.is_quorum(&set(&["b", "c"])));
802        assert!(expr.is_quorum(&set(&["a", "b", "c"])));
803        assert!(expr.is_quorum(&set(&["a", "x"])));
804        assert!(!expr.is_quorum(&set(&[])));
805        assert!(!expr.is_quorum(&set(&["x"])));
806    }
807
808    #[test]
809    fn test_is_quorum_and() {
810        let expr = n("a") * n("b") * n("c");
811        assert!(expr.is_quorum(&set(&["a", "b", "c"])));
812        assert!(expr.is_quorum(&set(&["a", "b", "c", "x"])));
813        assert!(!expr.is_quorum(&set(&[])));
814        assert!(!expr.is_quorum(&set(&["a"])));
815        assert!(!expr.is_quorum(&set(&["b"])));
816        assert!(!expr.is_quorum(&set(&["c"])));
817        assert!(!expr.is_quorum(&set(&["a", "b"])));
818        assert!(!expr.is_quorum(&set(&["a", "c"])));
819        assert!(!expr.is_quorum(&set(&["b", "c"])));
820        assert!(!expr.is_quorum(&set(&["x"])));
821        assert!(!expr.is_quorum(&set(&["a", "x"])));
822    }
823
824    #[test]
825    fn test_is_quorum_choose() {
826        let expr = choose(2, vec![n("a"), n("b"), n("c")]).unwrap();
827        assert!(expr.is_quorum(&set(&["a", "b"])));
828        assert!(expr.is_quorum(&set(&["a", "c"])));
829        assert!(expr.is_quorum(&set(&["b", "c"])));
830        assert!(expr.is_quorum(&set(&["a", "b", "c"])));
831        assert!(expr.is_quorum(&set(&["a", "b", "c", "x"])));
832        assert!(!expr.is_quorum(&set(&["a"])));
833        assert!(!expr.is_quorum(&set(&["b"])));
834        assert!(!expr.is_quorum(&set(&["c"])));
835        assert!(!expr.is_quorum(&set(&["x"])));
836    }
837
838    #[test]
839    fn test_is_quorum_cross_product() {
840        let expr = (n("a") + n("b")) * (n("c") + n("d"));
841        assert!(expr.is_quorum(&set(&["a", "c"])));
842        assert!(expr.is_quorum(&set(&["a", "d"])));
843        assert!(expr.is_quorum(&set(&["b", "c"])));
844        assert!(expr.is_quorum(&set(&["b", "d"])));
845        assert!(expr.is_quorum(&set(&["a", "b", "d"])));
846        assert!(expr.is_quorum(&set(&["b", "c", "d"])));
847        assert!(expr.is_quorum(&set(&["a", "c", "d"])));
848        assert!(expr.is_quorum(&set(&["a", "b", "c", "d"])));
849        assert!(!expr.is_quorum(&set(&["a"])));
850        assert!(!expr.is_quorum(&set(&["b"])));
851        assert!(!expr.is_quorum(&set(&["c"])));
852        assert!(!expr.is_quorum(&set(&["d"])));
853        assert!(!expr.is_quorum(&set(&["a", "b"])));
854        assert!(!expr.is_quorum(&set(&["c", "d"])));
855        assert!(!expr.is_quorum(&set(&["a", "b", "x"])));
856    }
857
858    // -- resilience tests --
859
860    #[test]
861    fn test_resilience_single() {
862        assert_eq!(n("a").resilience(), 0);
863    }
864
865    #[test]
866    fn test_resilience_or() {
867        assert_eq!((n("a") + n("b")).resilience(), 1);
868        assert_eq!((n("a") + n("b") + n("c")).resilience(), 2);
869        assert_eq!((n("a") + n("b") + n("c") + n("d")).resilience(), 3);
870    }
871
872    #[test]
873    fn test_resilience_and() {
874        assert_eq!((n("a") * n("b")).resilience(), 0);
875        assert_eq!((n("a") * n("b") * n("c")).resilience(), 0);
876        assert_eq!((n("a") * n("b") * n("c") * n("d")).resilience(), 0);
877    }
878
879    #[test]
880    fn test_resilience_mixed() {
881        assert_eq!(((n("a") + n("b")) * (n("c") + n("d"))).resilience(), 1);
882        assert_eq!(
883            ((n("a") + n("b") + n("c")) * (n("d") + n("e") + n("f")))
884                .resilience(),
885            2
886        );
887    }
888
889    #[test]
890    fn test_resilience_dup() {
891        // These have duplicate elements, so they use LP
892        assert_eq!(
893            ((n("a") + n("b") + n("c")) * (n("a") + n("e") + n("f")))
894                .resilience(),
895            2
896        );
897        assert_eq!(
898            ((n("a") + n("a") + n("c")) * (n("d") + n("e") + n("f")))
899                .resilience(),
900            1
901        );
902        assert_eq!(
903            ((n("a") + n("a") + n("a")) * (n("d") + n("e") + n("f")))
904                .resilience(),
905            0
906        );
907        assert_eq!(
908            (n("a") * n("b")
909                + n("b") * n("c")
910                + n("a") * n("d")
911                + n("a") * n("d") * n("e"))
912            .resilience(),
913            1
914        );
915    }
916
917    #[test]
918    fn test_resilience_choose() {
919        let ch2_3 = choose(2, vec![n("a"), n("b"), n("c")]).unwrap();
920        assert_eq!(ch2_3.resilience(), 1);
921
922        let ch2_5 =
923            choose(2, vec![n("a"), n("b"), n("c"), n("d"), n("e")]).unwrap();
924        assert_eq!(ch2_5.resilience(), 3);
925
926        let ch3_5 =
927            choose(3, vec![n("a"), n("b"), n("c"), n("d"), n("e")]).unwrap();
928        assert_eq!(ch3_5.resilience(), 2);
929
930        let ch4_5 =
931            choose(4, vec![n("a"), n("b"), n("c"), n("d"), n("e")]).unwrap();
932        assert_eq!(ch4_5.resilience(), 1);
933    }
934
935    #[test]
936    fn test_resilience_choose_compound() {
937        let e1 =
938            choose(2, vec![n("a") + n("b") + n("c"), n("d") + n("e"), n("f")])
939                .unwrap();
940        assert_eq!(e1.resilience(), 2);
941
942        let e2 =
943            choose(2, vec![n("a") * n("b"), n("a") * n("c"), n("d")]).unwrap();
944        assert_eq!(e2.resilience(), 0);
945
946        let e3 =
947            choose(2, vec![n("a") + n("b"), n("a") + n("c"), n("a") + n("d")])
948                .unwrap();
949        assert_eq!(e3.resilience(), 2);
950    }
951
952    // -- dual tests --
953
954    fn assert_dual(x: &Expr<String>, y: &Expr<String>) {
955        let x_dual = x.dual();
956        let x_qs = quorum_set(&x_dual);
957        let y_qs = quorum_set(y);
958        assert_eq!(x_qs, y_qs, "dual mismatch");
959    }
960
961    #[test]
962    fn test_dual_node() {
963        assert_dual(&n("a"), &n("a"));
964    }
965
966    #[test]
967    fn test_dual_or_and() {
968        assert_dual(&(n("a") + n("b")), &(n("a") * n("b")));
969    }
970
971    #[test]
972    fn test_dual_dup() {
973        assert_dual(&(n("a") + n("a")), &(n("a") * n("a")));
974    }
975
976    #[test]
977    fn test_dual_compound() {
978        assert_dual(
979            &((n("a") + n("b")) * (n("c") + n("d"))),
980            &((n("a") * n("b")) + (n("c") * n("d"))),
981        );
982        assert_dual(
983            &((n("a") + n("b")) * (n("a") + n("d"))),
984            &((n("a") * n("b")) + (n("a") * n("d"))),
985        );
986        assert_dual(
987            &((n("a") + n("b")) * (n("a") + n("a"))),
988            &((n("a") * n("b")) + (n("a") * n("a"))),
989        );
990        assert_dual(
991            &((n("a") + n("a")) * (n("a") + n("a"))),
992            &((n("a") * n("a")) + (n("a") * n("a"))),
993        );
994    }
995
996    #[test]
997    fn test_dual_nested() {
998        assert_dual(
999            &((n("a") + (n("a") * n("b"))) + ((n("c") * n("d")) + n("a"))),
1000            &((n("a") * (n("a") + n("b"))) * ((n("c") + n("d")) * n("a"))),
1001        );
1002    }
1003
1004    #[test]
1005    fn test_dual_choose() {
1006        let ch2_3 = choose(2, vec![n("a"), n("b"), n("c")]).unwrap();
1007        let ch2_3b = choose(2, vec![n("a"), n("b"), n("c")]).unwrap();
1008        assert_dual(&ch2_3, &ch2_3b);
1009
1010        let ch2_ab_cd_e =
1011            choose(2, vec![n("a") + n("b"), n("c") + n("d"), n("e")]).unwrap();
1012        let ch2_ab_cd_e_dual =
1013            choose(2, vec![n("a") * n("b"), n("c") * n("d"), n("e")]).unwrap();
1014        assert_dual(&ch2_ab_cd_e, &ch2_ab_cd_e_dual);
1015
1016        let ch3_5 =
1017            choose(3, vec![n("a"), n("b"), n("c"), n("d"), n("e")]).unwrap();
1018        let ch3_5b =
1019            choose(3, vec![n("a"), n("b"), n("c"), n("d"), n("e")]).unwrap();
1020        assert_dual(&ch3_5, &ch3_5b);
1021
1022        let ch2_5 =
1023            choose(2, vec![n("a"), n("b"), n("c"), n("d"), n("e")]).unwrap();
1024        let ch4_5 =
1025            choose(4, vec![n("a"), n("b"), n("c"), n("d"), n("e")]).unwrap();
1026        assert_dual(&ch2_5, &ch4_5);
1027        assert_dual(&ch4_5, &ch2_5);
1028    }
1029
1030    // -- dup_free tests --
1031
1032    #[test]
1033    fn test_dup_free() {
1034        assert!(n("a").dup_free());
1035        assert!((n("a") + n("b")).dup_free());
1036        assert!((n("a") * n("b")).dup_free());
1037        assert!((n("a") * n("b") + n("c")).dup_free());
1038
1039        let ch = choose(2, vec![n("a"), n("b"), n("c")]).unwrap();
1040        assert!(ch.dup_free());
1041
1042        let ch2 =
1043            choose(2, vec![n("a") * n("b"), n("c"), n("d") + n("e") + n("f")])
1044                .unwrap();
1045        assert!(ch2.dup_free());
1046
1047        let ch3 =
1048            choose(3, vec![n("a"), n("b"), n("c"), n("d"), n("e")]).unwrap();
1049        assert!(ch3.dup_free());
1050
1051        assert!(((n("a") + n("b")) * (n("c") + (n("d") * n("e")))).dup_free());
1052    }
1053
1054    #[test]
1055    fn test_not_dup_free() {
1056        assert!(!(n("a") + n("a")).dup_free());
1057        assert!(!(n("a") * n("a")).dup_free());
1058        assert!(!(n("a") * (n("b") + n("a"))).dup_free());
1059
1060        let ch = choose(2, vec![n("a"), n("b"), n("a")]).unwrap();
1061        assert!(!ch.dup_free());
1062
1063        let ch2 =
1064            choose(3, vec![n("a"), n("b"), n("c"), n("d"), n("a")]).unwrap();
1065        assert!(!ch2.dup_free());
1066
1067        assert!(!((n("a") + n("b")) * (n("c") + (n("d") * n("a")))).dup_free());
1068    }
1069
1070    // -- choose/majority helper tests --
1071
1072    #[test]
1073    fn test_choose_returns_or_for_k1() {
1074        let e = choose(1, vec![n("a"), n("b"), n("c")]).unwrap();
1075        assert!(matches!(e, Expr::Or(_)));
1076    }
1077
1078    #[test]
1079    fn test_choose_returns_and_for_k_eq_n() {
1080        let e = choose(3, vec![n("a"), n("b"), n("c")]).unwrap();
1081        assert!(matches!(e, Expr::And(_)));
1082    }
1083
1084    #[test]
1085    fn test_choose_returns_choose_for_middle_k() {
1086        let e = choose(2, vec![n("a"), n("b"), n("c")]).unwrap();
1087        assert!(matches!(e, Expr::Choose(_)));
1088    }
1089
1090    #[test]
1091    fn test_choose_errors() {
1092        assert!(choose::<String>(0, vec![]).is_err());
1093        assert!(choose(0, vec![n("a")]).is_err());
1094        assert!(choose(2, vec![n("a")]).is_err());
1095    }
1096
1097    #[test]
1098    fn test_majority() {
1099        let e = majority(vec![n("a"), n("b"), n("c")]).unwrap();
1100        assert_quorums(&e, &[&["a", "b"], &["a", "c"], &["b", "c"]]);
1101    }
1102
1103    // -- min hitting set (exact) --
1104
1105    fn mhs(vecs: &[&[&str]]) -> usize {
1106        min_hitting_set_of(vecs.iter().map(|v| set(v)).collect())
1107    }
1108
1109    #[test]
1110    fn test_min_hitting_set() {
1111        assert_eq!(mhs(&[]), 0);
1112        assert_eq!(mhs(&[&["a"]]), 1);
1113        assert_eq!(mhs(&[&["a"], &["b"]]), 2);
1114        assert_eq!(mhs(&[&["a", "b"], &["b", "c"]]), 1);
1115        assert_eq!(mhs(&[&["a"], &["b"], &["c"]]), 3);
1116        assert_eq!(mhs(&[&["a", "b", "c"]]), 1);
1117        assert_eq!(
1118            mhs(&[&["a", "c"], &["a", "d"], &["b", "c"], &["b", "d"]]),
1119            2
1120        );
1121        assert_eq!(mhs(&[&["a", "b"], &["a", "c"], &["b", "c"]]), 2);
1122        assert_eq!(
1123            mhs(&[&["a", "b"], &["b", "c"], &["a", "d"], &["a", "d", "e"]]),
1124            2
1125        );
1126        // All pairs of 5 elements: must pick 4.
1127        let xs = ["a", "b", "c", "d", "e"];
1128        let pairs: Vec<HashSet<String>> =
1129            xs.iter().array_combinations().map(|[p, q]| set(&[p, q])).collect();
1130        assert_eq!(min_hitting_set_of(pairs), 4);
1131    }
1132
1133    // -- constructors, accessors, display --
1134
1135    #[test]
1136    fn test_node_builders_and_accessors() {
1137        let a = Node::new("a")
1138            .with_capacity(4.0)
1139            .unwrap()
1140            .with_latency(Duration::from_millis(5));
1141        assert_eq!(*a.x(), "a");
1142        assert_eq!(a.read_capacity().to_bits(), 4.0_f64.to_bits());
1143        assert_eq!(a.write_capacity().to_bits(), 4.0_f64.to_bits());
1144        assert_eq!(a.latency(), Duration::from_millis(5));
1145        let b = Node::new("b").with_read_write_capacity(2.0, 3.0).unwrap();
1146        assert!(b.read_capacity() < b.write_capacity());
1147        for bad in [0.0, -1.0, f64::NAN, f64::INFINITY] {
1148            assert!(Node::new("a").with_capacity(bad).is_err());
1149            assert!(Node::new("a").with_read_write_capacity(1.0, bad).is_err());
1150        }
1151        // Identity is by `x` only.
1152        assert_eq!(a, Node::new("a"));
1153        assert!(Node::new("a") < Node::new("b"));
1154        assert_eq!(
1155            Node::new("a").partial_cmp(&Node::new("b")),
1156            Some(std::cmp::Ordering::Less)
1157        );
1158    }
1159
1160    #[test]
1161    fn test_combinator_constructors() {
1162        assert!(Or::<String>::new(vec![]).is_err());
1163        assert!(And::<String>::new(vec![]).is_err());
1164        assert!(Choose::<String>::new(1, vec![]).is_err());
1165        assert!(Choose::new(0, vec![n("a")]).is_err());
1166        assert!(Choose::new(2, vec![n("a")]).is_err());
1167
1168        let or = Or::new(vec![n("a"), n("b")]).unwrap();
1169        assert_eq!(or.children().len(), 2);
1170        let and = And::new(vec![n("a"), n("b")]).unwrap();
1171        assert_eq!(and.children().len(), 2);
1172        let ch = Choose::new(2, vec![n("a"), n("b"), n("c")]).unwrap();
1173        assert_eq!(ch.k(), 2);
1174        assert_eq!(ch.children().len(), 3);
1175
1176        let e: Expr<String> = ch.into();
1177        assert_eq!(e.to_string(), "choose2(a, b, c)");
1178        assert_eq!(Expr::from(or).to_string(), "(a + b)");
1179        assert_eq!(Expr::from(and).to_string(), "(a * b)");
1180        assert_eq!(Expr::from(Node::new("z".to_string())).to_string(), "z");
1181        assert_eq!(e.dual().to_string(), "choose2(a, b, c)");
1182    }
1183
1184    #[test]
1185    fn test_first_node_occurrence_wins() {
1186        let fast = Node::new("a".to_string()).with_latency(Duration::ZERO);
1187        let e = Expr::Node(fast) + n("a");
1188        let nodes = e.nodes();
1189        assert_eq!(nodes.len(), 1);
1190        assert!(nodes.iter().all(|x| x.latency() == Duration::ZERO));
1191    }
1192
1193    #[test]
1194    fn test_majority_errors() {
1195        assert!(majority::<String>(vec![]).is_err());
1196    }
1197}