Skip to main content

relmath/core/
binary.rs

1use std::collections::BTreeSet;
2
3use crate::{UnaryRelation, traits::FiniteRelation};
4
5/// A finite binary relation.
6///
7/// The starter implementation stores pairs in a `BTreeSet` so iteration order
8/// is deterministic.
9///
10/// Composition direction is:
11///
12/// `r.compose(&s)` = `{ (a, c) | exists b. (a, b) in r and (b, c) in s }`
13///
14/// # Examples
15///
16/// ```rust
17/// use relmath::{BinaryRelation, UnaryRelation};
18///
19/// let user_role = BinaryRelation::from_pairs([
20///     ("ann", "admin"),
21///     ("bob", "reviewer"),
22/// ]);
23///
24/// let extra_role = BinaryRelation::from_pairs([
25///     ("bob", "reviewer"),
26///     ("cara", "guest"),
27/// ]);
28///
29/// let role_permission = BinaryRelation::from_pairs([
30///     ("admin", "read"),
31///     ("reviewer", "approve"),
32///     ("guest", "view"),
33/// ]);
34///
35/// assert_eq!(
36///     user_role.union(&extra_role).to_vec(),
37///     vec![("ann", "admin"), ("bob", "reviewer"), ("cara", "guest")]
38/// );
39/// assert_eq!(
40///     user_role.intersection(&extra_role).to_vec(),
41///     vec![("bob", "reviewer")]
42/// );
43/// assert_eq!(user_role.difference(&extra_role).to_vec(), vec![("ann", "admin")]);
44/// assert_eq!(
45///     user_role.converse().to_vec(),
46///     vec![("admin", "ann"), ("reviewer", "bob")]
47/// );
48///
49/// let effective_permission = user_role.union(&extra_role).compose(&role_permission);
50/// assert!(effective_permission.contains(&"ann", &"read"));
51/// assert_eq!(
52///     effective_permission.iter().copied().collect::<Vec<_>>(),
53///     vec![("ann", "read"), ("bob", "approve"), ("cara", "view")]
54/// );
55/// assert_eq!(
56///     effective_permission.domain().to_vec(),
57///     vec!["ann", "bob", "cara"]
58/// );
59/// assert_eq!(
60///     effective_permission.range().to_vec(),
61///     vec!["approve", "read", "view"]
62/// );
63/// assert_eq!(
64///     effective_permission
65///         .restrict_domain(&UnaryRelation::from_values(["ann", "cara"]))
66///         .to_vec(),
67///     vec![("ann", "read"), ("cara", "view")]
68/// );
69/// assert_eq!(
70///     effective_permission
71///         .restrict_range(&UnaryRelation::from_values(["read", "view"]))
72///         .to_vec(),
73///     vec![("ann", "read"), ("cara", "view")]
74/// );
75/// assert_eq!(
76///     effective_permission.image(&UnaryRelation::singleton("bob")).to_vec(),
77///     vec!["approve"]
78/// );
79/// assert_eq!(
80///     effective_permission
81///         .preimage(&UnaryRelation::from_values(["read", "view"]))
82///         .to_vec(),
83///     vec!["ann", "cara"]
84/// );
85/// ```
86#[derive(Clone, Debug, PartialEq, Eq)]
87pub struct BinaryRelation<A: Ord, B: Ord> {
88    pairs: BTreeSet<(A, B)>,
89}
90
91impl<A: Ord, B: Ord> BinaryRelation<A, B> {
92    /// Creates an empty binary relation.
93    #[must_use]
94    pub fn new() -> Self {
95        Self {
96            pairs: BTreeSet::new(),
97        }
98    }
99
100    /// Creates a binary relation from the provided pairs.
101    #[must_use]
102    pub fn from_pairs<I>(pairs: I) -> Self
103    where
104        I: IntoIterator<Item = (A, B)>,
105    {
106        pairs.into_iter().collect()
107    }
108
109    /// Inserts a pair into the relation.
110    ///
111    /// Returns `true` when the pair was not already present.
112    pub fn insert(&mut self, left: A, right: B) -> bool {
113        self.pairs.insert((left, right))
114    }
115
116    /// Returns `true` when the relation contains the given pair.
117    #[must_use]
118    pub fn contains(&self, left: &A, right: &B) -> bool {
119        self.pairs.iter().any(|(candidate_left, candidate_right)| {
120            candidate_left == left && candidate_right == right
121        })
122    }
123
124    /// Returns an iterator over the stored pairs in deterministic order.
125    pub fn iter(&self) -> impl Iterator<Item = &(A, B)> {
126        self.pairs.iter()
127    }
128
129    /// Returns the domain of the relation.
130    #[must_use]
131    pub fn domain(&self) -> UnaryRelation<A>
132    where
133        A: Clone,
134    {
135        self.pairs.iter().map(|(left, _)| left.clone()).collect()
136    }
137
138    /// Returns the range of the relation.
139    #[must_use]
140    pub fn range(&self) -> UnaryRelation<B>
141    where
142        B: Clone,
143    {
144        self.pairs.iter().map(|(_, right)| right.clone()).collect()
145    }
146
147    /// Returns the converse relation.
148    #[must_use]
149    pub fn converse(&self) -> BinaryRelation<B, A>
150    where
151        A: Clone,
152        B: Clone,
153    {
154        self.pairs
155            .iter()
156            .map(|(left, right)| (right.clone(), left.clone()))
157            .collect()
158    }
159
160    /// Returns the union of `self` and `other`.
161    #[must_use]
162    pub fn union(&self, other: &Self) -> Self
163    where
164        A: Clone,
165        B: Clone,
166    {
167        self.pairs.union(&other.pairs).cloned().collect()
168    }
169
170    /// Returns the intersection of `self` and `other`.
171    #[must_use]
172    pub fn intersection(&self, other: &Self) -> Self
173    where
174        A: Clone,
175        B: Clone,
176    {
177        self.pairs.intersection(&other.pairs).cloned().collect()
178    }
179
180    /// Returns the set difference `self \ other`.
181    #[must_use]
182    pub fn difference(&self, other: &Self) -> Self
183    where
184        A: Clone,
185        B: Clone,
186    {
187        self.pairs.difference(&other.pairs).cloned().collect()
188    }
189
190    /// Restricts the domain of the relation to `allowed`.
191    #[must_use]
192    pub fn restrict_domain(&self, allowed: &UnaryRelation<A>) -> Self
193    where
194        A: Clone,
195        B: Clone,
196    {
197        self.pairs
198            .iter()
199            .filter(|(left, _)| allowed.contains(left))
200            .cloned()
201            .collect()
202    }
203
204    /// Restricts the range of the relation to `allowed`.
205    #[must_use]
206    pub fn restrict_range(&self, allowed: &UnaryRelation<B>) -> Self
207    where
208        A: Clone,
209        B: Clone,
210    {
211        self.pairs
212            .iter()
213            .filter(|(_, right)| allowed.contains(right))
214            .cloned()
215            .collect()
216    }
217
218    /// Computes the image of `sources` through the relation.
219    ///
220    /// Returns `{ b | exists a in sources. (a, b) in self }`.
221    #[must_use]
222    pub fn image(&self, sources: &UnaryRelation<A>) -> UnaryRelation<B>
223    where
224        B: Clone,
225    {
226        self.pairs
227            .iter()
228            .filter(|(left, _)| sources.contains(left))
229            .map(|(_, right)| right.clone())
230            .collect()
231    }
232
233    /// Computes the preimage of `targets` through the relation.
234    ///
235    /// Returns `{ a | exists b in targets. (a, b) in self }`.
236    #[must_use]
237    pub fn preimage(&self, targets: &UnaryRelation<B>) -> UnaryRelation<A>
238    where
239        A: Clone,
240    {
241        self.pairs
242            .iter()
243            .filter(|(_, right)| targets.contains(right))
244            .map(|(left, _)| left.clone())
245            .collect()
246    }
247
248    /// Composes `self` with `rhs`.
249    ///
250    /// The result contains `(a, c)` whenever there exists `b` such that
251    /// `(a, b)` is in `self` and `(b, c)` is in `rhs`.
252    ///
253    /// # Examples
254    ///
255    /// ```rust
256    /// use relmath::BinaryRelation;
257    ///
258    /// let user_role = BinaryRelation::from_pairs([
259    ///     ("ann", "admin"),
260    ///     ("bob", "reviewer"),
261    /// ]);
262    /// let role_permission = BinaryRelation::from_pairs([
263    ///     ("admin", "read"),
264    ///     ("reviewer", "approve"),
265    /// ]);
266    ///
267    /// let effective_permission = user_role.compose(&role_permission);
268    ///
269    /// assert_eq!(
270    ///     effective_permission.to_vec(),
271    ///     vec![("ann", "read"), ("bob", "approve")]
272    /// );
273    /// ```
274    #[must_use]
275    pub fn compose<C>(&self, rhs: &BinaryRelation<B, C>) -> BinaryRelation<A, C>
276    where
277        A: Clone,
278        B: Clone,
279        C: Clone + Ord,
280    {
281        let mut result = BTreeSet::new();
282
283        for (left, middle_left) in &self.pairs {
284            for (middle_right, right) in &rhs.pairs {
285                if middle_left == middle_right {
286                    result.insert((left.clone(), right.clone()));
287                }
288            }
289        }
290
291        BinaryRelation { pairs: result }
292    }
293
294    /// Converts the relation into a sorted vector of pairs.
295    #[must_use]
296    pub fn to_vec(&self) -> Vec<(A, B)>
297    where
298        A: Clone,
299        B: Clone,
300    {
301        self.pairs.iter().cloned().collect()
302    }
303}
304
305impl<A: Ord, B: Ord> Default for BinaryRelation<A, B> {
306    fn default() -> Self {
307        Self::new()
308    }
309}
310
311impl<T: Ord> BinaryRelation<T, T> {
312    /// Returns the carrier induced by the relation: domain union range.
313    #[must_use]
314    pub fn carrier(&self) -> UnaryRelation<T>
315    where
316        T: Clone,
317    {
318        self.domain().union(&self.range())
319    }
320
321    /// Returns the identity relation on `carrier`.
322    ///
323    /// # Examples
324    ///
325    /// ```rust
326    /// use relmath::{BinaryRelation, UnaryRelation};
327    ///
328    /// let carrier = UnaryRelation::from_values(["Draft", "Review"]);
329    ///
330    /// assert_eq!(
331    ///     BinaryRelation::identity(&carrier).to_vec(),
332    ///     vec![("Draft", "Draft"), ("Review", "Review")]
333    /// );
334    /// ```
335    #[must_use]
336    pub fn identity(carrier: &UnaryRelation<T>) -> Self
337    where
338        T: Clone,
339    {
340        carrier
341            .iter()
342            .map(|value| (value.clone(), value.clone()))
343            .collect()
344    }
345
346    /// Computes the transitive closure of the relation.
347    ///
348    /// # Examples
349    ///
350    /// ```rust
351    /// use relmath::BinaryRelation;
352    ///
353    /// let step = BinaryRelation::from_pairs([
354    ///     ("Draft", "Review"),
355    ///     ("Review", "Approved"),
356    /// ]);
357    ///
358    /// assert_eq!(
359    ///     step.transitive_closure().to_vec(),
360    ///     vec![
361    ///         ("Draft", "Approved"),
362    ///         ("Draft", "Review"),
363    ///         ("Review", "Approved"),
364    ///     ]
365    /// );
366    /// ```
367    #[must_use]
368    pub fn transitive_closure(&self) -> Self
369    where
370        T: Clone,
371    {
372        let mut closure = self.clone();
373        let mut changed = true;
374
375        while changed {
376            changed = false;
377            let snapshot = closure.to_vec();
378
379            for (left, middle_left) in &snapshot {
380                for (middle_right, right) in &snapshot {
381                    if middle_left == middle_right
382                        && closure.pairs.insert((left.clone(), right.clone()))
383                    {
384                        changed = true;
385                    }
386                }
387            }
388        }
389
390        closure
391    }
392
393    /// Computes the reflexive-transitive closure on the given `carrier`.
394    ///
395    /// Values that appear in `carrier` but not in any pair still gain their
396    /// identity edge in the result.
397    ///
398    /// # Examples
399    ///
400    /// ```rust
401    /// use relmath::{BinaryRelation, UnaryRelation};
402    ///
403    /// let step = BinaryRelation::from_pairs([("Draft", "Review")]);
404    /// let states = UnaryRelation::from_values(["Draft", "Review", "Archived"]);
405    /// let reachable = step.reflexive_transitive_closure(&states);
406    ///
407    /// assert!(reachable.contains(&"Archived", &"Archived"));
408    /// assert!(reachable.contains(&"Draft", &"Review"));
409    /// ```
410    #[must_use]
411    pub fn reflexive_transitive_closure(&self, carrier: &UnaryRelation<T>) -> Self
412    where
413        T: Clone,
414    {
415        self.transitive_closure().union(&Self::identity(carrier))
416    }
417
418    /// Returns `true` when the relation is reflexive on `carrier`.
419    #[must_use]
420    pub fn is_reflexive(&self, carrier: &UnaryRelation<T>) -> bool {
421        carrier.iter().all(|value| self.contains(value, value))
422    }
423
424    /// Returns `true` when the relation is irreflexive on `carrier`.
425    #[must_use]
426    pub fn is_irreflexive(&self, carrier: &UnaryRelation<T>) -> bool {
427        carrier.iter().all(|value| !self.contains(value, value))
428    }
429
430    /// Returns `true` when the relation is symmetric.
431    #[must_use]
432    pub fn is_symmetric(&self) -> bool {
433        self.pairs
434            .iter()
435            .all(|(left, right)| self.contains(right, left))
436    }
437
438    /// Returns `true` when the relation is antisymmetric.
439    #[must_use]
440    pub fn is_antisymmetric(&self) -> bool {
441        self.pairs
442            .iter()
443            .all(|(left, right)| left == right || !self.contains(right, left))
444    }
445
446    /// Returns `true` when the relation is transitive.
447    #[must_use]
448    pub fn is_transitive(&self) -> bool
449    where
450        T: Clone,
451    {
452        let snapshot = self.to_vec();
453
454        snapshot.iter().all(|(left, middle_left)| {
455            snapshot.iter().all(|(middle_right, right)| {
456                middle_left != middle_right || self.contains(left, right)
457            })
458        })
459    }
460
461    /// Returns `true` when the relation is an equivalence relation on `carrier`.
462    ///
463    /// # Examples
464    ///
465    /// ```rust
466    /// use relmath::{BinaryRelation, UnaryRelation};
467    ///
468    /// let aliases = BinaryRelation::from_pairs([
469    ///     ("A. Smith", "A. Smith"),
470    ///     ("A. Smith", "Alice Smith"),
471    ///     ("Alice Smith", "A. Smith"),
472    ///     ("Alice Smith", "Alice Smith"),
473    /// ]);
474    /// let carrier = UnaryRelation::from_values(["A. Smith", "Alice Smith"]);
475    ///
476    /// assert!(aliases.is_equivalence(&carrier));
477    /// ```
478    #[must_use]
479    pub fn is_equivalence(&self, carrier: &UnaryRelation<T>) -> bool
480    where
481        T: Clone,
482    {
483        self.is_reflexive(carrier) && self.is_symmetric() && self.is_transitive()
484    }
485
486    /// Returns `true` when the relation is a partial order on `carrier`.
487    ///
488    /// # Examples
489    ///
490    /// ```rust
491    /// use relmath::{BinaryRelation, UnaryRelation};
492    ///
493    /// let divides = BinaryRelation::from_pairs([
494    ///     (1_u8, 1_u8),
495    ///     (1, 2),
496    ///     (1, 4),
497    ///     (2, 2),
498    ///     (2, 4),
499    ///     (4, 4),
500    /// ]);
501    /// let carrier = UnaryRelation::from_values([1_u8, 2_u8, 4_u8]);
502    ///
503    /// assert!(divides.is_partial_order(&carrier));
504    /// ```
505    #[must_use]
506    pub fn is_partial_order(&self, carrier: &UnaryRelation<T>) -> bool
507    where
508        T: Clone,
509    {
510        self.is_reflexive(carrier) && self.is_antisymmetric() && self.is_transitive()
511    }
512}
513
514impl<A: Ord, B: Ord> FiniteRelation for BinaryRelation<A, B> {
515    fn len(&self) -> usize {
516        self.pairs.len()
517    }
518}
519
520impl<A: Ord, B: Ord> FromIterator<(A, B)> for BinaryRelation<A, B> {
521    fn from_iter<I: IntoIterator<Item = (A, B)>>(iter: I) -> Self {
522        Self {
523            pairs: iter.into_iter().collect(),
524        }
525    }
526}
527
528impl<A: Ord, B: Ord> Extend<(A, B)> for BinaryRelation<A, B> {
529    fn extend<I: IntoIterator<Item = (A, B)>>(&mut self, iter: I) {
530        self.pairs.extend(iter);
531    }
532}
533
534impl<A: Ord, B: Ord> IntoIterator for BinaryRelation<A, B> {
535    type Item = (A, B);
536    type IntoIter = std::collections::btree_set::IntoIter<(A, B)>;
537
538    fn into_iter(self) -> Self::IntoIter {
539        self.pairs.into_iter()
540    }
541}
542
543impl<'a, A: Ord, B: Ord> IntoIterator for &'a BinaryRelation<A, B> {
544    type Item = &'a (A, B);
545    type IntoIter = std::collections::btree_set::Iter<'a, (A, B)>;
546
547    fn into_iter(self) -> Self::IntoIter {
548        self.pairs.iter()
549    }
550}