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