relmath/temporal.rs
1//! Deterministic temporal interval and valid-time foundations.
2//!
3//! This first G5 slice introduces half-open intervals `[start, end)` over
4//! endpoint domains with total ordering. Intervals use deterministic
5//! lexicographic `(start, end)` ordering when stored in ordered collections,
6//! which makes later coalescing behavior easy to audit.
7//!
8//! Half-open semantics mean:
9//!
10//! - `start` is included;
11//! - `end` is excluded;
12//! - directly adjacent intervals such as `[1, 3)` and `[3, 5)` do not
13//! overlap, but they can be coalesced;
14//! - invalid intervals with `start >= end` are rejected explicitly.
15//!
16//! On top of the interval substrate, this module also provides a first
17//! deterministic valid-time relation foundation:
18//!
19//! - `ValidTimeSupport<T>` stores the canonical interval support for one fact;
20//! - `ValidTimeRelation<F, T>` maps exact facts to deterministic valid-time
21//! support and materializes exact support back into the published unary,
22//! binary, and n-ary relation surface.
23//! - `ValidTimeRelation::snapshot_at` and `ValidTimeRelation::restrict_to`
24//! provide the first narrow temporal operations over stored facts.
25//!
26//! This module remains intentionally narrower than a full temporal algebra.
27//! Temporal joins, temporal composition, transaction time, and bitemporal APIs
28//! are later work.
29
30use std::{collections::BTreeMap, fmt};
31
32use crate::{
33 BinaryRelation, NaryRelation, NaryRelationError, UnaryRelation, traits::FiniteRelation,
34};
35
36/// Validation errors for deterministic half-open intervals.
37///
38/// # Examples
39///
40/// ```rust
41/// use relmath::temporal::{Interval, IntervalError};
42///
43/// assert_eq!(
44/// Interval::new(4, 4),
45/// Err(IntervalError::InvalidBounds { start: 4, end: 4 })
46/// );
47/// ```
48#[derive(Clone, Debug, PartialEq, Eq)]
49pub enum IntervalError<T> {
50 /// The interval is invalid because `start >= end`.
51 InvalidBounds {
52 /// The requested inclusive lower bound.
53 start: T,
54 /// The requested exclusive upper bound.
55 end: T,
56 },
57}
58
59impl<T: fmt::Debug> fmt::Display for IntervalError<T> {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 match self {
62 Self::InvalidBounds { start, end } => {
63 write!(
64 f,
65 "invalid half-open interval: expected start < end, got start={start:?}, end={end:?}"
66 )
67 }
68 }
69 }
70}
71
72impl<T: fmt::Debug> std::error::Error for IntervalError<T> {}
73
74/// Deterministic half-open interval over an ordered endpoint domain.
75///
76/// `Interval<T>` represents `[start, end)`: `start` is included and `end` is
77/// excluded. The interval is valid only when `start < end`.
78///
79/// Intervals derive `Ord`, so ordered collections such as `BTreeSet` keep
80/// them in lexicographic `(start, end)` order. That is the deterministic order
81/// later temporal coalescing code should use.
82///
83/// # Examples
84///
85/// ```rust
86/// use std::collections::BTreeSet;
87///
88/// use relmath::temporal::Interval;
89///
90/// let windows = BTreeSet::from([
91/// Interval::new(3, 5).expect("expected valid interval"),
92/// Interval::new(1, 4).expect("expected valid interval"),
93/// Interval::new(1, 3).expect("expected valid interval"),
94/// ]);
95///
96/// assert_eq!(
97/// windows.into_iter().collect::<Vec<_>>(),
98/// vec![
99/// Interval::new(1, 3).expect("expected valid interval"),
100/// Interval::new(1, 4).expect("expected valid interval"),
101/// Interval::new(3, 5).expect("expected valid interval"),
102/// ]
103/// );
104/// ```
105#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
106pub struct Interval<T: Ord> {
107 start: T,
108 end: T,
109}
110
111impl<T: Ord> Interval<T> {
112 /// Creates one valid half-open interval `[start, end)`.
113 ///
114 /// Returns an explicit error when `start >= end`.
115 ///
116 /// # Examples
117 ///
118 /// ```rust
119 /// use relmath::temporal::{Interval, IntervalError};
120 ///
121 /// let valid = Interval::new(2, 5).expect("expected valid interval");
122 /// assert_eq!(valid.start(), &2);
123 /// assert_eq!(valid.end(), &5);
124 ///
125 /// assert_eq!(
126 /// Interval::new(5, 2),
127 /// Err(IntervalError::InvalidBounds { start: 5, end: 2 })
128 /// );
129 /// ```
130 pub fn new(start: T, end: T) -> Result<Self, IntervalError<T>> {
131 if start < end {
132 Ok(Self { start, end })
133 } else {
134 Err(IntervalError::InvalidBounds { start, end })
135 }
136 }
137
138 /// Returns the inclusive lower bound of the interval.
139 #[must_use]
140 pub fn start(&self) -> &T {
141 &self.start
142 }
143
144 /// Returns the exclusive upper bound of the interval.
145 #[must_use]
146 pub fn end(&self) -> &T {
147 &self.end
148 }
149
150 /// Returns `true` when `point` lies inside `[start, end)`.
151 ///
152 /// # Examples
153 ///
154 /// ```rust
155 /// use relmath::temporal::Interval;
156 ///
157 /// let window = Interval::new(10, 12).expect("expected valid interval");
158 ///
159 /// assert!(window.contains(&10));
160 /// assert!(window.contains(&11));
161 /// assert!(!window.contains(&12));
162 /// ```
163 #[must_use]
164 pub fn contains(&self, point: &T) -> bool {
165 &self.start <= point && point < &self.end
166 }
167
168 /// Returns `true` when the intervals overlap with non-empty intersection.
169 ///
170 /// Directly adjacent intervals do not overlap under half-open semantics.
171 ///
172 /// # Examples
173 ///
174 /// ```rust
175 /// use relmath::temporal::Interval;
176 ///
177 /// let left = Interval::new(1, 3).expect("expected valid interval");
178 /// let overlap = Interval::new(2, 4).expect("expected valid interval");
179 /// let adjacent = Interval::new(3, 5).expect("expected valid interval");
180 ///
181 /// assert!(left.overlaps(&overlap));
182 /// assert!(!left.overlaps(&adjacent));
183 /// ```
184 #[must_use]
185 pub fn overlaps(&self, other: &Self) -> bool {
186 self.start < other.end && other.start < self.end
187 }
188
189 /// Returns `true` when the intervals touch at exactly one boundary.
190 ///
191 /// Adjacent intervals are not overlapping, but later valid-time layers may
192 /// still coalesce them into one canonical support fragment.
193 #[must_use]
194 pub fn is_adjacent_to(&self, other: &Self) -> bool {
195 self.end == other.start || other.end == self.start
196 }
197
198 /// Returns `true` when the intervals can be coalesced.
199 ///
200 /// This is true for overlapping or directly adjacent intervals.
201 #[must_use]
202 pub fn can_coalesce_with(&self, other: &Self) -> bool {
203 self.overlaps(other) || self.is_adjacent_to(other)
204 }
205}
206
207impl<T: Ord + Clone> Interval<T> {
208 /// Returns the non-empty intersection of two intervals.
209 ///
210 /// When the intervals are disjoint or only touch at one boundary, this
211 /// returns `None`.
212 ///
213 /// # Examples
214 ///
215 /// ```rust
216 /// use relmath::temporal::Interval;
217 ///
218 /// let left = Interval::new(1, 4).expect("expected valid interval");
219 /// let right = Interval::new(3, 6).expect("expected valid interval");
220 ///
221 /// assert_eq!(
222 /// left.intersection(&right),
223 /// Some(Interval::new(3, 4).expect("expected valid interval"))
224 /// );
225 /// ```
226 #[must_use]
227 pub fn intersection(&self, other: &Self) -> Option<Self> {
228 let start = if self.start >= other.start {
229 self.start.clone()
230 } else {
231 other.start.clone()
232 };
233 let end = if self.end <= other.end {
234 self.end.clone()
235 } else {
236 other.end.clone()
237 };
238
239 Self::new(start, end).ok()
240 }
241
242 /// Restricts this interval to the overlap with `constraint`.
243 ///
244 /// This is an interval-level helper for later temporal `restrict_to`
245 /// behavior on valid-time relations.
246 #[must_use]
247 pub fn restrict_to(&self, constraint: &Self) -> Option<Self> {
248 self.intersection(constraint)
249 }
250
251 /// Coalesces two overlapping or directly adjacent intervals.
252 ///
253 /// Returns `None` when the intervals are strictly disjoint.
254 ///
255 /// # Examples
256 ///
257 /// ```rust
258 /// use relmath::temporal::Interval;
259 ///
260 /// let morning = Interval::new(9, 12).expect("expected valid interval");
261 /// let afternoon = Interval::new(12, 15).expect("expected valid interval");
262 ///
263 /// assert_eq!(
264 /// morning.coalesce(&afternoon),
265 /// Some(Interval::new(9, 15).expect("expected valid interval"))
266 /// );
267 /// ```
268 #[must_use]
269 pub fn coalesce(&self, other: &Self) -> Option<Self> {
270 if !self.can_coalesce_with(other) {
271 return None;
272 }
273
274 let start = if self.start <= other.start {
275 self.start.clone()
276 } else {
277 other.start.clone()
278 };
279 let end = if self.end >= other.end {
280 self.end.clone()
281 } else {
282 other.end.clone()
283 };
284
285 Some(Self { start, end })
286 }
287}
288
289fn canonicalize_intervals<T: Ord + Clone>(mut intervals: Vec<Interval<T>>) -> Vec<Interval<T>> {
290 intervals.sort();
291
292 let mut canonical: Vec<Interval<T>> = Vec::with_capacity(intervals.len());
293 for interval in intervals {
294 if let Some(last) = canonical.last_mut() {
295 if let Some(coalesced) = last.coalesce(&interval) {
296 *last = coalesced;
297 continue;
298 }
299 }
300
301 canonical.push(interval);
302 }
303
304 canonical
305}
306
307/// Deterministic canonical valid-time support for one fact.
308///
309/// `ValidTimeSupport<T>` stores half-open intervals in canonical
310/// lexicographic order. Overlapping or directly adjacent intervals are
311/// coalesced, so user-visible support never contains redundant fragments.
312/// Support can then be queried for overlap with one interval or restricted to
313/// one interval window without losing deterministic canonical order.
314///
315/// This is the interval support attached to one stored fact. It is distinct
316/// from the exact support of a relation, which forgets time and keeps only
317/// which facts are present.
318///
319/// # Examples
320///
321/// ```rust
322/// use relmath::temporal::{Interval, ValidTimeSupport};
323///
324/// let support = ValidTimeSupport::from_intervals([
325/// Interval::new(3, 5).expect("expected valid interval"),
326/// Interval::new(1, 3).expect("expected valid interval"),
327/// Interval::new(6, 7).expect("expected valid interval"),
328/// Interval::new(4, 6).expect("expected valid interval"),
329/// ]);
330///
331/// assert_eq!(
332/// support.to_vec(),
333/// vec![Interval::new(1, 7).expect("expected valid interval")]
334/// );
335/// assert!(support.contains(&4));
336/// assert!(!support.contains(&7));
337/// ```
338#[derive(Clone, Debug, PartialEq, Eq)]
339pub struct ValidTimeSupport<T: Ord + Clone> {
340 intervals: Vec<Interval<T>>,
341}
342
343impl<T: Ord + Clone> ValidTimeSupport<T> {
344 /// Creates empty valid-time support.
345 ///
346 /// A `ValidTimeRelation` does not store empty support for a fact, so
347 /// absent facts return `None` rather than an empty support value. This
348 /// constructor is still useful for standalone inspection and testing.
349 ///
350 /// # Examples
351 ///
352 /// ```rust
353 /// use relmath::temporal::ValidTimeSupport;
354 ///
355 /// let empty = ValidTimeSupport::<i32>::new();
356 ///
357 /// assert!(empty.is_empty());
358 /// assert_eq!(empty.len(), 0);
359 /// ```
360 #[must_use]
361 pub fn new() -> Self {
362 Self {
363 intervals: Vec::new(),
364 }
365 }
366
367 /// Creates canonical valid-time support from intervals.
368 ///
369 /// The resulting support is sorted and coalesced deterministically.
370 ///
371 /// # Examples
372 ///
373 /// ```rust
374 /// use relmath::temporal::{Interval, ValidTimeSupport};
375 ///
376 /// let support = ValidTimeSupport::from_intervals([
377 /// Interval::new(2, 4).expect("expected valid interval"),
378 /// Interval::new(1, 2).expect("expected valid interval"),
379 /// ]);
380 ///
381 /// assert_eq!(
382 /// support.to_vec(),
383 /// vec![Interval::new(1, 4).expect("expected valid interval")]
384 /// );
385 /// ```
386 #[must_use]
387 pub fn from_intervals<I>(intervals: I) -> Self
388 where
389 I: IntoIterator<Item = Interval<T>>,
390 {
391 intervals.into_iter().collect()
392 }
393
394 /// Returns the number of canonical support intervals.
395 ///
396 /// For one stored fact, this counts canonical support fragments rather than
397 /// time points or inserted raw intervals.
398 #[must_use]
399 pub fn len(&self) -> usize {
400 self.intervals.len()
401 }
402
403 /// Returns `true` when the support contains no intervals.
404 #[must_use]
405 pub fn is_empty(&self) -> bool {
406 self.intervals.is_empty()
407 }
408
409 /// Returns `true` when `point` is covered by some support interval.
410 ///
411 /// # Examples
412 ///
413 /// ```rust
414 /// use relmath::temporal::{Interval, ValidTimeSupport};
415 ///
416 /// let support =
417 /// ValidTimeSupport::from_intervals([Interval::new(2, 4).expect("expected valid interval")]);
418 ///
419 /// assert!(support.contains(&2));
420 /// assert!(support.contains(&3));
421 /// assert!(!support.contains(&4));
422 /// ```
423 #[must_use]
424 pub fn contains(&self, point: &T) -> bool {
425 self.intervals
426 .iter()
427 .any(|interval| interval.contains(point))
428 }
429
430 /// Returns `true` when some support interval overlaps `constraint`.
431 ///
432 /// Direct boundary-only contact does not count as overlap under half-open
433 /// semantics.
434 ///
435 /// # Examples
436 ///
437 /// ```rust
438 /// use relmath::temporal::{Interval, ValidTimeSupport};
439 ///
440 /// let support = ValidTimeSupport::from_intervals([
441 /// Interval::new(1, 3).expect("expected valid interval"),
442 /// Interval::new(5, 7).expect("expected valid interval"),
443 /// ]);
444 ///
445 /// assert!(support.overlaps(&Interval::new(2, 6).expect("expected valid interval")));
446 /// assert!(!support.overlaps(&Interval::new(3, 5).expect("expected valid interval")));
447 /// ```
448 #[must_use]
449 pub fn overlaps(&self, constraint: &Interval<T>) -> bool {
450 self.intervals
451 .iter()
452 .any(|interval| interval.overlaps(constraint))
453 }
454
455 /// Restricts this support to the overlap with `constraint`.
456 ///
457 /// The returned support remains canonical: overlapping or directly adjacent
458 /// fragments are coalesced deterministically. When no interval overlaps the
459 /// constraint, this returns empty support.
460 ///
461 /// # Examples
462 ///
463 /// ```rust
464 /// use relmath::temporal::{Interval, ValidTimeSupport};
465 ///
466 /// let support = ValidTimeSupport::from_intervals([
467 /// Interval::new(1, 3).expect("expected valid interval"),
468 /// Interval::new(5, 7).expect("expected valid interval"),
469 /// ]);
470 ///
471 /// assert_eq!(
472 /// support
473 /// .restrict_to(&Interval::new(2, 6).expect("expected valid interval"))
474 /// .to_vec(),
475 /// vec![
476 /// Interval::new(2, 3).expect("expected valid interval"),
477 /// Interval::new(5, 6).expect("expected valid interval"),
478 /// ]
479 /// );
480 /// assert!(
481 /// support
482 /// .restrict_to(&Interval::new(3, 5).expect("expected valid interval"))
483 /// .is_empty()
484 /// );
485 /// ```
486 #[must_use]
487 pub fn restrict_to(&self, constraint: &Interval<T>) -> Self {
488 self.intervals
489 .iter()
490 .filter_map(|interval| interval.restrict_to(constraint))
491 .collect()
492 }
493
494 /// Returns an iterator over canonical support intervals.
495 ///
496 /// # Examples
497 ///
498 /// ```rust
499 /// use relmath::temporal::{Interval, ValidTimeSupport};
500 ///
501 /// let support = ValidTimeSupport::from_intervals([
502 /// Interval::new(4, 6).expect("expected valid interval"),
503 /// Interval::new(1, 3).expect("expected valid interval"),
504 /// ]);
505 ///
506 /// assert_eq!(
507 /// support.iter().cloned().collect::<Vec<_>>(),
508 /// vec![
509 /// Interval::new(1, 3).expect("expected valid interval"),
510 /// Interval::new(4, 6).expect("expected valid interval"),
511 /// ]
512 /// );
513 /// ```
514 pub fn iter(&self) -> impl Iterator<Item = &Interval<T>> {
515 self.intervals.iter()
516 }
517
518 /// Returns the canonical interval list as a vector.
519 #[must_use]
520 pub fn to_vec(&self) -> Vec<Interval<T>> {
521 self.intervals.clone()
522 }
523
524 fn insert_interval(&mut self, interval: Interval<T>) -> bool {
525 let mut updated = self.intervals.clone();
526 updated.push(interval);
527 updated = canonicalize_intervals(updated);
528
529 if updated == self.intervals {
530 false
531 } else {
532 self.intervals = updated;
533 true
534 }
535 }
536}
537
538impl<T: Ord + Clone> Default for ValidTimeSupport<T> {
539 fn default() -> Self {
540 Self::new()
541 }
542}
543
544impl<T: Ord + Clone> FromIterator<Interval<T>> for ValidTimeSupport<T> {
545 fn from_iter<I: IntoIterator<Item = Interval<T>>>(iter: I) -> Self {
546 let intervals = canonicalize_intervals(iter.into_iter().collect());
547 Self { intervals }
548 }
549}
550
551impl<T: Ord + Clone> Extend<Interval<T>> for ValidTimeSupport<T> {
552 fn extend<I: IntoIterator<Item = Interval<T>>>(&mut self, iter: I) {
553 let mut updated = self.intervals.clone();
554 updated.extend(iter);
555 self.intervals = canonicalize_intervals(updated);
556 }
557}
558
559/// Deterministic valid-time support attached to exact facts.
560///
561/// `ValidTimeRelation<F, T>` keeps exact facts in deterministic fact order and
562/// stores canonical valid-time support for each fact. Repeated insertion of a
563/// fact with overlapping or directly adjacent intervals coalesces that fact's
564/// support. The first temporal operations on top of this surface are
565/// [`Self::snapshot_at`] and [`Self::restrict_to`].
566///
567/// In this first G5 slice, only facts with non-empty valid-time support are
568/// stored. Exact support materialization forgets time and keeps only which
569/// facts are present at all.
570///
571/// # Examples
572///
573/// ```rust
574/// use relmath::temporal::{Interval, ValidTimeRelation};
575///
576/// let assignments = ValidTimeRelation::from_facts([
577/// (("alice", "review"), Interval::new(1, 3).expect("expected valid interval")),
578/// (("alice", "review"), Interval::new(3, 5).expect("expected valid interval")),
579/// (("bob", "approve"), Interval::new(2, 4).expect("expected valid interval")),
580/// ]);
581///
582/// assert_eq!(
583/// assignments
584/// .valid_time_of(&("alice", "review"))
585/// .expect("expected support")
586/// .to_vec(),
587/// vec![Interval::new(1, 5).expect("expected valid interval")]
588/// );
589/// assert!(assignments.is_active_at(&("alice", "review"), &4));
590/// assert!(!assignments.is_active_at(&("alice", "review"), &5));
591/// ```
592#[derive(Clone, Debug, PartialEq, Eq)]
593pub struct ValidTimeRelation<F: Ord, T: Ord + Clone> {
594 facts: BTreeMap<F, ValidTimeSupport<T>>,
595}
596
597impl<F: Ord, T: Ord + Clone> ValidTimeRelation<F, T> {
598 /// Creates an empty valid-time relation.
599 ///
600 /// # Examples
601 ///
602 /// ```rust
603 /// use relmath::{FiniteRelation, temporal::ValidTimeRelation};
604 ///
605 /// let relation = ValidTimeRelation::<(&str, &str), i32>::new();
606 ///
607 /// assert!(relation.is_empty());
608 /// assert!(relation.snapshot_at(&0).is_empty());
609 /// ```
610 #[must_use]
611 pub fn new() -> Self {
612 Self {
613 facts: BTreeMap::new(),
614 }
615 }
616
617 /// Creates a valid-time relation from `(fact, interval)` entries.
618 ///
619 /// Repeated facts combine by deterministic canonical coalescing of valid
620 /// time support.
621 ///
622 /// # Examples
623 ///
624 /// ```rust
625 /// use relmath::temporal::{Interval, ValidTimeRelation};
626 ///
627 /// let relation = ValidTimeRelation::from_facts([
628 /// ("alice", Interval::new(1, 3).expect("expected valid interval")),
629 /// ("alice", Interval::new(3, 5).expect("expected valid interval")),
630 /// ]);
631 ///
632 /// assert_eq!(
633 /// relation
634 /// .valid_time_of(&"alice")
635 /// .expect("expected support")
636 /// .to_vec(),
637 /// vec![Interval::new(1, 5).expect("expected valid interval")]
638 /// );
639 /// ```
640 #[must_use]
641 pub fn from_facts<I>(facts: I) -> Self
642 where
643 I: IntoIterator<Item = (F, Interval<T>)>,
644 {
645 facts.into_iter().collect()
646 }
647
648 /// Inserts one valid interval for a fact.
649 ///
650 /// Returns `true` when the fact's stored valid-time support changes.
651 ///
652 /// # Examples
653 ///
654 /// ```rust
655 /// use relmath::temporal::{Interval, ValidTimeRelation};
656 ///
657 /// let mut relation = ValidTimeRelation::new();
658 ///
659 /// assert!(relation.insert("alice", Interval::new(1, 3).expect("expected valid interval")));
660 /// assert!(!relation.insert("alice", Interval::new(2, 3).expect("already covered")));
661 /// ```
662 pub fn insert(&mut self, fact: F, interval: Interval<T>) -> bool {
663 match self.facts.entry(fact) {
664 std::collections::btree_map::Entry::Vacant(entry) => {
665 entry.insert(ValidTimeSupport::from_intervals([interval]));
666 true
667 }
668 std::collections::btree_map::Entry::Occupied(mut entry) => {
669 entry.get_mut().insert_interval(interval)
670 }
671 }
672 }
673
674 /// Inserts one interval for a fact from raw bounds.
675 ///
676 /// Returns an explicit error when `start >= end`.
677 ///
678 /// # Examples
679 ///
680 /// ```rust
681 /// use relmath::temporal::{IntervalError, ValidTimeRelation};
682 ///
683 /// let mut relation = ValidTimeRelation::new();
684 ///
685 /// assert!(relation.insert_bounds("alice", 1, 3).expect("expected valid bounds"));
686 /// assert_eq!(
687 /// relation.insert_bounds("alice", 3, 3),
688 /// Err(IntervalError::InvalidBounds { start: 3, end: 3 })
689 /// );
690 /// ```
691 pub fn insert_bounds(&mut self, fact: F, start: T, end: T) -> Result<bool, IntervalError<T>> {
692 Ok(self.insert(fact, Interval::new(start, end)?))
693 }
694
695 /// Returns `true` when the relation contains the given fact with non-empty
696 /// valid-time support.
697 ///
698 /// # Examples
699 ///
700 /// ```rust
701 /// use relmath::temporal::{Interval, ValidTimeRelation};
702 ///
703 /// let relation = ValidTimeRelation::from_facts([(
704 /// "alice",
705 /// Interval::new(1, 3).expect("expected valid interval"),
706 /// )]);
707 ///
708 /// assert!(relation.contains_fact(&"alice"));
709 /// assert!(!relation.contains_fact(&"bob"));
710 /// ```
711 #[must_use]
712 pub fn contains_fact(&self, fact: &F) -> bool {
713 self.facts.contains_key(fact)
714 }
715
716 /// Returns the canonical valid-time support for one fact.
717 ///
718 /// When a fact is absent, this returns `None`. In this first G5 slice, the
719 /// relation does not store empty support as a sentinel value, so `None`
720 /// also means the fact has no stored valid-time support.
721 ///
722 /// # Examples
723 ///
724 /// ```rust
725 /// use relmath::temporal::{Interval, ValidTimeRelation};
726 ///
727 /// let relation = ValidTimeRelation::from_facts([(
728 /// "alice",
729 /// Interval::new(1, 3).expect("expected valid interval"),
730 /// )]);
731 ///
732 /// assert_eq!(
733 /// relation.valid_time_of(&"alice").expect("expected support").to_vec(),
734 /// vec![Interval::new(1, 3).expect("expected valid interval")]
735 /// );
736 /// assert!(relation.valid_time_of(&"bob").is_none());
737 /// ```
738 #[must_use]
739 pub fn valid_time_of(&self, fact: &F) -> Option<&ValidTimeSupport<T>> {
740 self.facts.get(fact)
741 }
742
743 /// Returns `true` when `fact` is active at `point`.
744 ///
745 /// Absent facts are never active.
746 ///
747 /// # Examples
748 ///
749 /// ```rust
750 /// use relmath::temporal::{Interval, ValidTimeRelation};
751 ///
752 /// let relation = ValidTimeRelation::from_facts([(
753 /// "alice",
754 /// Interval::new(1, 3).expect("expected valid interval"),
755 /// )]);
756 ///
757 /// assert!(relation.is_active_at(&"alice", &1));
758 /// assert!(!relation.is_active_at(&"alice", &3));
759 /// assert!(!relation.is_active_at(&"bob", &1));
760 /// ```
761 #[must_use]
762 pub fn is_active_at(&self, fact: &F, point: &T) -> bool {
763 self.facts
764 .get(fact)
765 .is_some_and(|support| support.contains(point))
766 }
767
768 /// Returns an iterator over facts and valid-time support in deterministic
769 /// fact order.
770 ///
771 /// # Examples
772 ///
773 /// ```rust
774 /// use relmath::temporal::{Interval, ValidTimeRelation};
775 ///
776 /// let relation = ValidTimeRelation::from_facts([
777 /// ("bob", Interval::new(2, 4).expect("expected valid interval")),
778 /// ("alice", Interval::new(1, 3).expect("expected valid interval")),
779 /// ]);
780 ///
781 /// assert_eq!(
782 /// relation
783 /// .iter()
784 /// .map(|(fact, support)| (*fact, support.to_vec()))
785 /// .collect::<Vec<_>>(),
786 /// vec![
787 /// ("alice", vec![Interval::new(1, 3).expect("expected valid interval")]),
788 /// ("bob", vec![Interval::new(2, 4).expect("expected valid interval")]),
789 /// ]
790 /// );
791 /// ```
792 pub fn iter(&self) -> impl Iterator<Item = (&F, &ValidTimeSupport<T>)> {
793 self.facts.iter()
794 }
795
796 /// Returns the exact support relation of stored facts as a unary relation.
797 ///
798 /// This materialization forgets time and keeps only which facts have
799 /// non-empty valid-time support.
800 ///
801 /// # Examples
802 ///
803 /// ```rust
804 /// use relmath::temporal::{Interval, ValidTimeRelation};
805 ///
806 /// let relation = ValidTimeRelation::from_facts([
807 /// ("Closure", Interval::new(1, 4).expect("expected valid interval")),
808 /// ("Relations", Interval::new(2, 5).expect("expected valid interval")),
809 /// ]);
810 ///
811 /// assert_eq!(relation.support().to_vec(), vec!["Closure", "Relations"]);
812 /// ```
813 #[must_use]
814 pub fn support(&self) -> UnaryRelation<F>
815 where
816 F: Clone,
817 {
818 self.facts.keys().cloned().collect()
819 }
820
821 /// Returns the exact snapshot of facts active at `point`.
822 ///
823 /// The result is a unary relation over stored facts. Facts are present in
824 /// the snapshot exactly when their valid-time support contains `point`.
825 /// When no facts are active, this returns an empty unary relation.
826 ///
827 /// # Examples
828 ///
829 /// ```rust
830 /// use relmath::{FiniteRelation, temporal::{Interval, ValidTimeRelation}};
831 ///
832 /// let assignments = ValidTimeRelation::from_facts([
833 /// (("alice", "review"), Interval::new(1, 3).expect("expected valid interval")),
834 /// (("alice", "review"), Interval::new(3, 5).expect("expected valid interval")),
835 /// (("bob", "approve"), Interval::new(2, 4).expect("expected valid interval")),
836 /// ]);
837 ///
838 /// assert_eq!(
839 /// assignments.snapshot_at(&3).to_vec(),
840 /// vec![("alice", "review"), ("bob", "approve")]
841 /// );
842 /// assert!(assignments.snapshot_at(&5).is_empty());
843 /// ```
844 #[must_use]
845 pub fn snapshot_at(&self, point: &T) -> UnaryRelation<F>
846 where
847 F: Clone,
848 {
849 self.facts
850 .iter()
851 .filter(|(_, support)| support.contains(point))
852 .map(|(fact, _)| fact.clone())
853 .collect()
854 }
855
856 /// Restricts every fact's valid-time support to `constraint`.
857 ///
858 /// Facts whose support has no overlap with `constraint` are omitted from
859 /// the returned relation. Remaining support fragments stay in deterministic
860 /// fact order and canonical interval order.
861 ///
862 /// # Examples
863 ///
864 /// ```rust
865 /// use relmath::temporal::{Interval, ValidTimeRelation};
866 ///
867 /// let assignments = ValidTimeRelation::from_facts([
868 /// (("alice", "review"), Interval::new(1, 3).expect("expected valid interval")),
869 /// (("alice", "review"), Interval::new(5, 7).expect("expected valid interval")),
870 /// (("bob", "approve"), Interval::new(2, 4).expect("expected valid interval")),
871 /// (("carol", "audit"), Interval::new(7, 9).expect("expected valid interval")),
872 /// ]);
873 ///
874 /// let audit_window = Interval::new(2, 6).expect("expected valid interval");
875 /// let restricted = assignments.restrict_to(&audit_window);
876 ///
877 /// assert_eq!(
878 /// restricted
879 /// .valid_time_of(&("alice", "review"))
880 /// .expect("expected support")
881 /// .to_vec(),
882 /// vec![
883 /// Interval::new(2, 3).expect("expected valid interval"),
884 /// Interval::new(5, 6).expect("expected valid interval"),
885 /// ]
886 /// );
887 /// assert!(!restricted.contains_fact(&("carol", "audit")));
888 /// ```
889 #[must_use]
890 pub fn restrict_to(&self, constraint: &Interval<T>) -> Self
891 where
892 F: Clone,
893 {
894 let facts = self
895 .facts
896 .iter()
897 .filter_map(|(fact, support)| {
898 let restricted = support.restrict_to(constraint);
899
900 if restricted.is_empty() {
901 None
902 } else {
903 Some((fact.clone(), restricted))
904 }
905 })
906 .collect();
907
908 Self { facts }
909 }
910}
911
912impl<F: Ord, T: Ord + Clone> Default for ValidTimeRelation<F, T> {
913 fn default() -> Self {
914 Self::new()
915 }
916}
917
918impl<F: Ord, T: Ord + Clone> FiniteRelation for ValidTimeRelation<F, T> {
919 fn len(&self) -> usize {
920 self.facts.len()
921 }
922}
923
924impl<F: Ord, T: Ord + Clone> FromIterator<(F, Interval<T>)> for ValidTimeRelation<F, T> {
925 fn from_iter<I: IntoIterator<Item = (F, Interval<T>)>>(iter: I) -> Self {
926 let mut relation = Self::new();
927 relation.extend(iter);
928 relation
929 }
930}
931
932impl<F: Ord, T: Ord + Clone> Extend<(F, Interval<T>)> for ValidTimeRelation<F, T> {
933 fn extend<I: IntoIterator<Item = (F, Interval<T>)>>(&mut self, iter: I) {
934 for (fact, interval) in iter {
935 self.insert(fact, interval);
936 }
937 }
938}
939
940impl<Value: Ord + Clone, Time: Ord + Clone> ValidTimeRelation<Value, Time> {
941 /// Converts scalar facts into a unary relation support set.
942 ///
943 /// # Examples
944 ///
945 /// ```rust
946 /// use relmath::temporal::{Interval, ValidTimeRelation};
947 ///
948 /// let relation = ValidTimeRelation::from_facts([(
949 /// "Closure",
950 /// Interval::new(1, 4).expect("expected valid interval"),
951 /// )]);
952 ///
953 /// assert_eq!(relation.to_unary_relation().to_vec(), vec!["Closure"]);
954 /// ```
955 #[must_use]
956 pub fn to_unary_relation(&self) -> UnaryRelation<Value> {
957 self.support()
958 }
959}
960
961impl<A: Ord + Clone, B: Ord + Clone, Time: Ord + Clone> ValidTimeRelation<(A, B), Time> {
962 /// Converts pair facts into a binary relation support set.
963 ///
964 /// # Examples
965 ///
966 /// ```rust
967 /// use relmath::temporal::{Interval, ValidTimeRelation};
968 ///
969 /// let relation = ValidTimeRelation::from_facts([(
970 /// ("alice", "review"),
971 /// Interval::new(1, 3).expect("expected valid interval"),
972 /// )]);
973 ///
974 /// assert_eq!(relation.to_binary_relation().to_vec(), vec![("alice", "review")]);
975 /// ```
976 #[must_use]
977 pub fn to_binary_relation(&self) -> BinaryRelation<A, B> {
978 self.facts.keys().cloned().collect()
979 }
980}
981
982impl<Value: Ord + Clone, Time: Ord + Clone> ValidTimeRelation<Vec<Value>, Time> {
983 /// Converts row facts into an n-ary relation with the given schema.
984 ///
985 /// This method reuses the current `NaryRelation` validation rules, so
986 /// duplicate or blank column names and row-arity mismatches return
987 /// `NaryRelationError`.
988 ///
989 /// # Examples
990 ///
991 /// ```rust
992 /// use relmath::temporal::{Interval, ValidTimeRelation};
993 ///
994 /// let rows = ValidTimeRelation::from_facts([(
995 /// vec!["Alice", "Math", "passed"],
996 /// Interval::new(1, 3).expect("expected valid interval"),
997 /// )]);
998 ///
999 /// let relation = rows
1000 /// .to_nary_relation(["student", "course", "status"])
1001 /// .expect("expected valid n-ary relation");
1002 ///
1003 /// assert_eq!(relation.to_rows(), vec![vec!["Alice", "Math", "passed"]]);
1004 /// ```
1005 pub fn to_nary_relation<I, S>(
1006 &self,
1007 schema: I,
1008 ) -> Result<NaryRelation<Value>, NaryRelationError>
1009 where
1010 I: IntoIterator<Item = S>,
1011 S: Into<String>,
1012 {
1013 NaryRelation::from_rows(schema, self.facts.keys().cloned())
1014 }
1015}