Skip to main content

relmath/
annotated.rs

1//! Additive annotation-domain and annotated-relation foundations.
2//!
3//! In this G4 slice, an annotation is a value combined by semiring-like
4//! operations and stored in a deterministic annotated relation. Facts are kept
5//! in a `BTreeMap`, so iteration order follows fact order. Only non-zero
6//! annotations are stored; exact support materialization forgets annotation
7//! values and keeps only the facts whose stored annotation is not zero.
8//!
9//! Unlike [`crate::provenance`], this module does not model witness sets or
10//! explanation queries. An annotation here is the stored algebraic value for a
11//! fact, not a derivation witness.
12//!
13//! Ordinary exact relations correspond to the boolean semiring, where `false`
14//! means absence, `true` means presence, `add` models union-like combination,
15//! and `mul` models composition-like chaining.
16
17use std::collections::BTreeMap;
18
19use crate::{
20    BinaryRelation, NaryRelation, NaryRelationError, UnaryRelation, traits::FiniteRelation,
21};
22
23/// Semiring-like annotation domain for additive relation semantics.
24///
25/// This trait captures the smallest G4 foundation currently needed by the
26/// crate:
27///
28/// - `zero` models absence or additive identity;
29/// - `one` models multiplicative identity;
30/// - `add` models union-like combination;
31/// - `mul` models composition-like chaining.
32///
33/// In later annotated relation layers, materializing exact support should keep
34/// tuples whose annotation is not zero and forget the annotation value itself.
35///
36/// # Examples
37///
38/// ```rust
39/// use relmath::annotated::{BooleanSemiring, Semiring};
40///
41/// fn reachable_via_two_hop_or_direct(
42///     direct: BooleanSemiring,
43///     first_leg: BooleanSemiring,
44///     second_leg: BooleanSemiring,
45/// ) -> BooleanSemiring {
46///     direct.add(&first_leg.mul(&second_leg))
47/// }
48///
49/// let reachable = reachable_via_two_hop_or_direct(
50///     BooleanSemiring::FALSE,
51///     BooleanSemiring::TRUE,
52///     BooleanSemiring::TRUE,
53/// );
54///
55/// assert!(reachable.is_one());
56/// assert!(bool::from(reachable));
57/// ```
58pub trait Semiring: Clone + Eq {
59    /// Returns the additive identity for the annotation domain.
60    #[must_use]
61    fn zero() -> Self;
62
63    /// Returns the multiplicative identity for the annotation domain.
64    #[must_use]
65    fn one() -> Self;
66
67    /// Combines two annotations with union-like or choice-like semantics.
68    #[must_use]
69    fn add(&self, rhs: &Self) -> Self;
70
71    /// Combines two annotations with chaining or composition-like semantics.
72    #[must_use]
73    fn mul(&self, rhs: &Self) -> Self;
74
75    /// Returns `true` when this annotation is the additive identity.
76    #[must_use]
77    fn is_zero(&self) -> bool {
78        self == &Self::zero()
79    }
80
81    /// Returns `true` when this annotation is the multiplicative identity.
82    #[must_use]
83    fn is_one(&self) -> bool {
84        self == &Self::one()
85    }
86}
87
88/// Deterministic annotated facts over a semiring-like annotation domain.
89///
90/// `AnnotatedRelation<F, A>` stores at most one annotation per fact. Repeated
91/// insertion of the same fact combines annotations by `Semiring::add` in
92/// insertion order. Only non-zero annotations are stored, so exact support is
93/// the set of facts whose current annotation is not zero.
94///
95/// Inserting a zero annotation for an absent fact does nothing. Inserting a
96/// zero annotation for a present fact also leaves the stored annotation
97/// unchanged because zero is the additive identity. If a custom annotation
98/// domain combines to zero, the fact is removed from the annotated relation.
99/// Unlike provenance in [`crate::provenance`], the stored value is the
100/// annotation itself rather than a witness or explanation set.
101///
102/// This first annotated relation slice focuses on deterministic storage,
103/// inspection, and exact support materialization. Annotated set algebra,
104/// annotated composition, temporal semantics, and broader uncertainty
105/// operators remain later work.
106///
107/// # Examples
108///
109/// ```rust
110/// use relmath::{
111///     BinaryRelation,
112///     annotated::{AnnotatedRelation, Semiring},
113/// };
114///
115/// #[derive(Clone, Debug, PartialEq, Eq)]
116/// struct Count(u8);
117///
118/// impl Semiring for Count {
119///     fn zero() -> Self {
120///         Self(0)
121///     }
122///
123///     fn one() -> Self {
124///         Self(1)
125///     }
126///
127///     fn add(&self, rhs: &Self) -> Self {
128///         Self(self.0.saturating_add(rhs.0))
129///     }
130///
131///     fn mul(&self, rhs: &Self) -> Self {
132///         Self(self.0.saturating_mul(rhs.0))
133///     }
134/// }
135///
136/// let mut assignments = AnnotatedRelation::new();
137///
138/// assert!(assignments.insert(("Alice", "Review"), Count(1)));
139/// assert!(assignments.insert(("Alice", "Review"), Count(2)));
140/// assert_eq!(
141///     assignments.annotation_of(&("Alice", "Review")),
142///     Some(&Count(3))
143/// );
144///
145/// let support: BinaryRelation<_, _> = assignments.to_binary_relation();
146/// assert_eq!(support.to_vec(), vec![("Alice", "Review")]);
147/// ```
148#[derive(Clone, Debug, PartialEq, Eq)]
149pub struct AnnotatedRelation<F: Ord, A: Semiring> {
150    facts: BTreeMap<F, A>,
151}
152
153impl<F: Ord, A: Semiring> AnnotatedRelation<F, A> {
154    /// Creates an empty annotated relation.
155    ///
156    /// # Examples
157    ///
158    /// ```rust
159    /// use relmath::annotated::{AnnotatedRelation, BooleanSemiring};
160    /// use relmath::FiniteRelation;
161    ///
162    /// let relation = AnnotatedRelation::<(&str, &str), BooleanSemiring>::new();
163    ///
164    /// assert!(relation.is_empty());
165    /// ```
166    #[must_use]
167    pub fn new() -> Self {
168        Self {
169            facts: BTreeMap::new(),
170        }
171    }
172
173    /// Creates an annotated relation from `(fact, annotation)` entries.
174    ///
175    /// Repeated facts combine annotations by `Semiring::add` in iterator order.
176    /// Entries with zero annotations do not create stored support.
177    ///
178    /// # Examples
179    ///
180    /// ```rust
181    /// use relmath::annotated::{AnnotatedRelation, BooleanSemiring};
182    ///
183    /// let relation = AnnotatedRelation::from_facts([
184    ///     (("alice", "read"), BooleanSemiring::TRUE),
185    ///     (("alice", "read"), BooleanSemiring::FALSE),
186    ///     (("bob", "approve"), BooleanSemiring::FALSE),
187    /// ]);
188    ///
189    /// assert!(relation.contains_fact(&("alice", "read")));
190    /// assert!(!relation.contains_fact(&("bob", "approve")));
191    /// ```
192    #[must_use]
193    pub fn from_facts<I>(facts: I) -> Self
194    where
195        I: IntoIterator<Item = (F, A)>,
196    {
197        facts.into_iter().collect()
198    }
199
200    /// Inserts one annotation for a fact.
201    ///
202    /// Returns `true` when the stored support or stored annotation changes.
203    ///
204    /// Repeated facts combine as `current.add(&annotation)`. Facts whose
205    /// resulting annotation is zero are removed from the relation.
206    ///
207    /// # Examples
208    ///
209    /// ```rust
210    /// use relmath::annotated::{AnnotatedRelation, BooleanSemiring};
211    ///
212    /// let mut relation = AnnotatedRelation::new();
213    ///
214    /// assert!(!relation.insert(("alice", "read"), BooleanSemiring::FALSE));
215    /// assert!(relation.insert(("alice", "read"), BooleanSemiring::TRUE));
216    /// assert!(!relation.insert(("alice", "read"), BooleanSemiring::FALSE));
217    /// assert!(relation.contains_fact(&("alice", "read")));
218    /// ```
219    pub fn insert(&mut self, fact: F, annotation: A) -> bool {
220        match self.facts.entry(fact) {
221            std::collections::btree_map::Entry::Vacant(entry) => {
222                if annotation.is_zero() {
223                    return false;
224                }
225
226                entry.insert(annotation);
227                true
228            }
229            std::collections::btree_map::Entry::Occupied(mut entry) => {
230                let combined = entry.get().add(&annotation);
231
232                if combined.is_zero() {
233                    entry.remove();
234                    true
235                } else if entry.get() == &combined {
236                    false
237                } else {
238                    *entry.get_mut() = combined;
239                    true
240                }
241            }
242        }
243    }
244
245    /// Returns `true` when the relation contains the given fact with a
246    /// non-zero annotation.
247    ///
248    /// # Examples
249    ///
250    /// ```rust
251    /// use relmath::annotated::{AnnotatedRelation, BooleanSemiring};
252    ///
253    /// let relation = AnnotatedRelation::from_facts([
254    ///     (("alice", "read"), BooleanSemiring::TRUE),
255    ///     (("bob", "approve"), BooleanSemiring::FALSE),
256    /// ]);
257    ///
258    /// assert!(relation.contains_fact(&("alice", "read")));
259    /// assert!(!relation.contains_fact(&("bob", "approve")));
260    /// ```
261    #[must_use]
262    pub fn contains_fact(&self, fact: &F) -> bool {
263        self.facts.contains_key(fact)
264    }
265
266    /// Returns the stored annotation for one fact.
267    ///
268    /// When a fact is absent, this returns `None`. Zero annotations are not
269    /// stored, so `None` also means there is no stored exact support for that
270    /// fact. Unlike [`crate::provenance::ProvenanceRelation::why`], this
271    /// returns the stored annotation value itself rather than a witness set.
272    ///
273    /// # Examples
274    ///
275    /// ```rust
276    /// use relmath::annotated::{AnnotatedRelation, BooleanSemiring};
277    ///
278    /// let relation = AnnotatedRelation::from_facts([
279    ///     (("alice", "read"), BooleanSemiring::TRUE),
280    ///     (("bob", "approve"), BooleanSemiring::FALSE),
281    /// ]);
282    ///
283    /// assert_eq!(
284    ///     relation.annotation_of(&("alice", "read")),
285    ///     Some(&BooleanSemiring::TRUE)
286    /// );
287    /// assert_eq!(relation.annotation_of(&("bob", "approve")), None);
288    /// ```
289    #[must_use]
290    pub fn annotation_of(&self, fact: &F) -> Option<&A> {
291        self.facts.get(fact)
292    }
293
294    /// Returns an iterator over facts and annotations in deterministic fact
295    /// order.
296    ///
297    /// # Examples
298    ///
299    /// ```rust
300    /// use relmath::annotated::{AnnotatedRelation, BooleanSemiring};
301    ///
302    /// let relation = AnnotatedRelation::from_facts([
303    ///     (("alice", "admin"), BooleanSemiring::TRUE),
304    ///     (("bob", "reviewer"), BooleanSemiring::TRUE),
305    /// ]);
306    ///
307    /// assert_eq!(
308    ///     relation
309    ///         .iter()
310    ///         .map(|(fact, annotation)| (*fact, *annotation))
311    ///         .collect::<Vec<_>>(),
312    ///     vec![
313    ///         (("alice", "admin"), BooleanSemiring::TRUE),
314    ///         (("bob", "reviewer"), BooleanSemiring::TRUE),
315    ///     ]
316    /// );
317    /// ```
318    pub fn iter(&self) -> impl Iterator<Item = (&F, &A)> {
319        self.facts.iter()
320    }
321
322    /// Returns the exact support relation of stored facts as a unary relation.
323    ///
324    /// This materialization forgets annotation values and keeps only which
325    /// facts are currently present with a non-zero annotation.
326    ///
327    /// # Examples
328    ///
329    /// ```rust
330    /// use relmath::annotated::{AnnotatedRelation, BooleanSemiring};
331    ///
332    /// let relation = AnnotatedRelation::from_facts([
333    ///     (("alice", "admin"), BooleanSemiring::TRUE),
334    ///     (("bob", "reviewer"), BooleanSemiring::TRUE),
335    ///     (("bob", "reviewer"), BooleanSemiring::FALSE),
336    /// ]);
337    ///
338    /// assert_eq!(
339    ///     relation.support().to_vec(),
340    ///     vec![("alice", "admin"), ("bob", "reviewer")]
341    /// );
342    /// ```
343    #[must_use]
344    pub fn support(&self) -> UnaryRelation<F>
345    where
346        F: Clone,
347    {
348        self.facts.keys().cloned().collect()
349    }
350}
351
352impl<F: Ord, A: Semiring> Default for AnnotatedRelation<F, A> {
353    fn default() -> Self {
354        Self::new()
355    }
356}
357
358impl<F: Ord, A: Semiring> FiniteRelation for AnnotatedRelation<F, A> {
359    fn len(&self) -> usize {
360        self.facts.len()
361    }
362}
363
364impl<F: Ord, A: Semiring> FromIterator<(F, A)> for AnnotatedRelation<F, A> {
365    fn from_iter<I: IntoIterator<Item = (F, A)>>(iter: I) -> Self {
366        let mut relation = Self::new();
367        relation.extend(iter);
368        relation
369    }
370}
371
372impl<F: Ord, A: Semiring> Extend<(F, A)> for AnnotatedRelation<F, A> {
373    fn extend<I: IntoIterator<Item = (F, A)>>(&mut self, iter: I) {
374        for (fact, annotation) in iter {
375            self.insert(fact, annotation);
376        }
377    }
378}
379
380impl<T: Ord + Clone, A: Semiring> AnnotatedRelation<T, A> {
381    /// Converts scalar facts into a unary relation support set.
382    ///
383    /// # Examples
384    ///
385    /// ```rust
386    /// use relmath::annotated::{AnnotatedRelation, BooleanSemiring};
387    ///
388    /// let concepts = AnnotatedRelation::from_facts([
389    ///     ("Closure", BooleanSemiring::TRUE),
390    ///     ("Relations", BooleanSemiring::TRUE),
391    /// ]);
392    ///
393    /// assert_eq!(
394    ///     concepts.to_unary_relation().to_vec(),
395    ///     vec!["Closure", "Relations"]
396    /// );
397    /// ```
398    #[must_use]
399    pub fn to_unary_relation(&self) -> UnaryRelation<T> {
400        self.support()
401    }
402}
403
404impl<A0: Ord + Clone, B0: Ord + Clone, A: Semiring> AnnotatedRelation<(A0, B0), A> {
405    /// Converts pair facts into a binary relation support set.
406    ///
407    /// # Examples
408    ///
409    /// ```rust
410    /// use relmath::annotated::{AnnotatedRelation, BooleanSemiring};
411    ///
412    /// let permissions = AnnotatedRelation::from_facts([
413    ///     (("Alice", "read"), BooleanSemiring::TRUE),
414    ///     (("Bob", "approve"), BooleanSemiring::TRUE),
415    /// ]);
416    ///
417    /// assert_eq!(
418    ///     permissions.to_binary_relation().to_vec(),
419    ///     vec![("Alice", "read"), ("Bob", "approve")]
420    /// );
421    /// ```
422    #[must_use]
423    pub fn to_binary_relation(&self) -> BinaryRelation<A0, B0> {
424        self.facts.keys().cloned().collect()
425    }
426}
427
428impl<T: Ord + Clone, A: Semiring> AnnotatedRelation<Vec<T>, A> {
429    /// Converts row facts into an n-ary relation with the given schema.
430    ///
431    /// The explicit schema preserves the current exact n-ary boundary where row
432    /// values do not themselves encode column names. This method reuses the
433    /// current `NaryRelation` validation rules, so duplicate or blank column
434    /// names and row-arity mismatches return `NaryRelationError`.
435    ///
436    /// # Examples
437    ///
438    /// ```rust
439    /// use relmath::annotated::{AnnotatedRelation, BooleanSemiring};
440    ///
441    /// let completion = AnnotatedRelation::from_facts([
442    ///     (vec!["Alice", "Math", "passed"], BooleanSemiring::TRUE),
443    ///     (vec!["Bob", "Physics", "passed"], BooleanSemiring::TRUE),
444    /// ]);
445    ///
446    /// let relation = completion
447    ///     .to_nary_relation(["student", "course", "status"])
448    ///     .expect("expected valid support relation");
449    ///
450    /// assert_eq!(
451    ///     relation.to_rows(),
452    ///     vec![
453    ///         vec!["Alice", "Math", "passed"],
454    ///         vec!["Bob", "Physics", "passed"],
455    ///     ]
456    /// );
457    /// ```
458    pub fn to_nary_relation<I, S>(&self, schema: I) -> Result<NaryRelation<T>, NaryRelationError>
459    where
460        I: IntoIterator<Item = S>,
461        S: Into<String>,
462    {
463        NaryRelation::from_rows(schema, self.facts.keys().cloned())
464    }
465}
466
467/// Boolean semiring for ordinary exact relation semantics.
468///
469/// `BooleanSemiring` is the first built-in annotation family in G4. It keeps
470/// the exact meaning already used by the published unary, binary, and n-ary
471/// relations:
472///
473/// - `false` is zero and represents absence;
474/// - `true` is one and represents presence or identity;
475/// - `add` uses logical OR;
476/// - `mul` uses logical AND.
477///
478/// # Examples
479///
480/// ```rust
481/// use relmath::annotated::{BooleanSemiring, Semiring};
482///
483/// let direct = BooleanSemiring::FALSE;
484/// let first_leg = BooleanSemiring::TRUE;
485/// let second_leg = BooleanSemiring::TRUE;
486///
487/// assert_eq!(direct.add(&first_leg), BooleanSemiring::TRUE);
488/// assert_eq!(first_leg.mul(&second_leg), BooleanSemiring::TRUE);
489/// assert!(BooleanSemiring::zero().is_zero());
490/// assert!(BooleanSemiring::one().is_one());
491/// ```
492#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
493pub struct BooleanSemiring(bool);
494
495impl BooleanSemiring {
496    /// The additive identity and absence value for the boolean semiring.
497    pub const FALSE: Self = Self(false);
498
499    /// The multiplicative identity and present value for the boolean semiring.
500    pub const TRUE: Self = Self(true);
501
502    /// Creates a boolean semiring value from a raw boolean.
503    ///
504    /// # Examples
505    ///
506    /// ```rust
507    /// use relmath::annotated::BooleanSemiring;
508    ///
509    /// assert_eq!(BooleanSemiring::new(false), BooleanSemiring::FALSE);
510    /// assert_eq!(BooleanSemiring::new(true), BooleanSemiring::TRUE);
511    /// ```
512    #[must_use]
513    pub const fn new(value: bool) -> Self {
514        Self(value)
515    }
516
517    /// Returns the raw boolean stored by this semiring value.
518    ///
519    /// # Examples
520    ///
521    /// ```rust
522    /// use relmath::annotated::BooleanSemiring;
523    ///
524    /// assert!(!BooleanSemiring::FALSE.value());
525    /// assert!(BooleanSemiring::TRUE.value());
526    /// ```
527    #[must_use]
528    pub const fn value(self) -> bool {
529        self.0
530    }
531}
532
533impl From<bool> for BooleanSemiring {
534    fn from(value: bool) -> Self {
535        Self::new(value)
536    }
537}
538
539impl From<BooleanSemiring> for bool {
540    fn from(value: BooleanSemiring) -> Self {
541        value.value()
542    }
543}
544
545impl Semiring for BooleanSemiring {
546    fn zero() -> Self {
547        Self::FALSE
548    }
549
550    fn one() -> Self {
551        Self::TRUE
552    }
553
554    fn add(&self, rhs: &Self) -> Self {
555        Self(self.0 || rhs.0)
556    }
557
558    fn mul(&self, rhs: &Self) -> Self {
559        Self(self.0 && rhs.0)
560    }
561}