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}