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}