1use std::collections::HashMap;
7use thiserror::Error;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct TimePoint {
16 pub t: i64,
17}
18
19impl TimePoint {
20 pub fn new(t: i64) -> Self {
22 Self { t }
23 }
24}
25
26impl From<i64> for TimePoint {
27 fn from(t: i64) -> Self {
28 Self { t }
29 }
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct TimeInterval {
39 pub start: TimePoint,
40 pub end: TimePoint,
41}
42
43impl TimeInterval {
44 pub fn new(start: TimePoint, end: TimePoint) -> Result<Self, TemporalError> {
46 if start > end {
47 return Err(TemporalError::InvalidInterval {
48 start: start.t,
49 end: end.t,
50 });
51 }
52 Ok(Self { start, end })
53 }
54
55 pub fn duration_ms(&self) -> u64 {
57 (self.end.t - self.start.t) as u64
58 }
59
60 pub fn overlaps(&self, other: &TimeInterval) -> bool {
65 self.start <= other.end && other.start <= self.end
66 }
67
68 pub fn contains_point(&self, t: &TimePoint) -> bool {
70 self.start <= *t && *t <= self.end
71 }
72
73 pub fn before(&self, other: &TimeInterval) -> bool {
75 self.end < other.start
76 }
77
78 pub fn after(&self, other: &TimeInterval) -> bool {
80 other.end < self.start
81 }
82
83 pub fn meets(&self, other: &TimeInterval) -> bool {
85 self.end == other.start
86 }
87
88 pub fn during(&self, other: &TimeInterval) -> bool {
90 self.start >= other.start && self.end <= other.end
91 }
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
100pub enum AllenRelation {
101 Precedes,
103 Meets,
105 Overlaps,
107 FinishedBy,
109 Contains,
111 Starts,
113 Equals,
115 StartedBy,
117 During,
119 Finishes,
121 OverlappedBy,
123 MetBy,
125 PrecededBy,
127}
128
129impl AllenRelation {
130 pub fn classify(a: &TimeInterval, b: &TimeInterval) -> AllenRelation {
132 let as_ = a.start.t;
133 let ae = a.end.t;
134 let bs = b.start.t;
135 let be = b.end.t;
136
137 if ae < bs {
138 AllenRelation::Precedes
139 } else if ae == bs {
140 AllenRelation::Meets
141 } else if as_ < bs && ae < be {
142 AllenRelation::Overlaps
143 } else if as_ < bs && ae == be {
144 AllenRelation::FinishedBy
145 } else if as_ < bs && ae > be {
146 AllenRelation::Contains
147 } else if as_ == bs && ae < be {
148 AllenRelation::Starts
149 } else if as_ == bs && ae == be {
150 AllenRelation::Equals
151 } else if as_ == bs && ae > be {
152 AllenRelation::StartedBy
153 } else if as_ > bs && ae < be {
154 AllenRelation::During
155 } else if as_ > bs && ae == be {
156 AllenRelation::Finishes
157 } else if as_ > bs && ae > be && as_ < be {
158 AllenRelation::OverlappedBy
159 } else if as_ == be {
160 AllenRelation::MetBy
161 } else {
162 AllenRelation::PrecededBy
164 }
165 }
166
167 pub fn is_overlapping_kind(self) -> bool {
170 !matches!(
171 self,
172 AllenRelation::Precedes
173 | AllenRelation::PrecededBy
174 | AllenRelation::Meets
175 | AllenRelation::MetBy
176 )
177 }
178}
179
180#[derive(Debug, Clone, PartialEq, Eq)]
186pub struct TemporalEvent {
187 pub id: String,
188 pub interval: TimeInterval,
189 pub tags: Vec<String>,
190 pub payload: String,
191}
192
193impl TemporalEvent {
194 pub fn new(
196 id: impl Into<String>,
197 interval: TimeInterval,
198 tags: Vec<String>,
199 payload: impl Into<String>,
200 ) -> Self {
201 Self {
202 id: id.into(),
203 interval,
204 tags,
205 payload: payload.into(),
206 }
207 }
208}
209
210#[derive(Debug, Clone, PartialEq, Eq)]
216pub enum TemporalConstraint {
217 Before { a: String, b: String },
219 After { a: String, b: String },
221 Overlapping { a: String, b: String },
223 During { inner: String, outer: String },
225 Within { event: String, window: TimeInterval },
227}
228
229#[derive(Debug, Clone)]
235pub struct ConstraintViolation {
236 pub constraint: String,
238 pub event_a: String,
240 pub event_b: String,
242 pub relation: AllenRelation,
244}
245
246#[derive(Debug, Error, Clone, PartialEq, Eq)]
252pub enum TemporalError {
253 #[error("event not found: {0}")]
254 EventNotFound(String),
255
256 #[error("duplicate event id: {0}")]
257 DuplicateEventId(String),
258
259 #[error("maximum event capacity reached")]
260 MaxEventsReached,
261
262 #[error("invalid interval: start ({start}) > end ({end})")]
263 InvalidInterval { start: i64, end: i64 },
264}
265
266#[derive(Debug, Clone)]
272pub struct TemporalStats {
273 pub event_count: usize,
274 pub constraint_count: usize,
275 pub timeline_span_ms: u64,
278 pub avg_event_duration_ms: f64,
280}
281
282#[derive(Debug)]
293pub struct TemporalReasoningEngine {
294 events: HashMap<String, TemporalEvent>,
295 constraints: Vec<TemporalConstraint>,
296 max_events: usize,
297}
298
299impl TemporalReasoningEngine {
300 pub fn new(max_events: usize) -> Self {
302 Self {
303 events: HashMap::new(),
304 constraints: Vec::new(),
305 max_events,
306 }
307 }
308
309 pub fn add_event(&mut self, event: TemporalEvent) -> Result<(), TemporalError> {
316 if self.events.contains_key(&event.id) {
317 return Err(TemporalError::DuplicateEventId(event.id.clone()));
318 }
319 if self.events.len() >= self.max_events {
320 return Err(TemporalError::MaxEventsReached);
321 }
322 self.events.insert(event.id.clone(), event);
323 Ok(())
324 }
325
326 pub fn remove_event(&mut self, id: &str) -> bool {
328 self.events.remove(id).is_some()
329 }
330
331 pub fn get_event(&self, id: &str) -> Option<&TemporalEvent> {
333 self.events.get(id)
334 }
335
336 pub fn events_in_window(&self, window: &TimeInterval) -> Vec<&TemporalEvent> {
343 let mut result: Vec<&TemporalEvent> = self
344 .events
345 .values()
346 .filter(|e| e.interval.overlaps(window))
347 .collect();
348 result.sort_by(|a, b| {
349 a.interval
350 .start
351 .cmp(&b.interval.start)
352 .then_with(|| a.interval.end.cmp(&b.interval.end))
353 });
354 result
355 }
356
357 pub fn events_with_tag(&self, tag: &str) -> Vec<&TemporalEvent> {
359 let mut result: Vec<&TemporalEvent> = self
360 .events
361 .values()
362 .filter(|e| e.tags.iter().any(|t| t == tag))
363 .collect();
364 result.sort_by(|a, b| {
365 a.interval
366 .start
367 .cmp(&b.interval.start)
368 .then_with(|| a.interval.end.cmp(&b.interval.end))
369 });
370 result
371 }
372
373 pub fn timeline(&self) -> Vec<&TemporalEvent> {
375 let mut events: Vec<&TemporalEvent> = self.events.values().collect();
376 events.sort_by(|a, b| {
377 a.interval
378 .start
379 .cmp(&b.interval.start)
380 .then_with(|| a.interval.end.cmp(&b.interval.end))
381 });
382 events
383 }
384
385 pub fn concurrent_events(&self, id: &str) -> Vec<&TemporalEvent> {
388 let target = match self.events.get(id) {
389 Some(e) => e,
390 None => return Vec::new(),
391 };
392 let mut result: Vec<&TemporalEvent> = self
393 .events
394 .values()
395 .filter(|e| e.id != id && e.interval.overlaps(&target.interval))
396 .collect();
397 result.sort_by(|a, b| {
398 a.interval
399 .start
400 .cmp(&b.interval.start)
401 .then_with(|| a.interval.end.cmp(&b.interval.end))
402 });
403 result
404 }
405
406 pub fn allen_relation(&self, a: &str, b: &str) -> Option<AllenRelation> {
413 let ea = self.events.get(a)?;
414 let eb = self.events.get(b)?;
415 Some(AllenRelation::classify(&ea.interval, &eb.interval))
416 }
417
418 pub fn add_constraint(&mut self, c: TemporalConstraint) {
426 self.constraints.push(c);
427 }
428
429 pub fn check_constraints(&self) -> Vec<ConstraintViolation> {
431 let mut violations = Vec::new();
432 for constraint in &self.constraints {
433 match constraint {
434 TemporalConstraint::Before { a, b } => {
435 self.check_before(a, b, &mut violations);
436 }
437 TemporalConstraint::After { a, b } => {
438 self.check_after(a, b, &mut violations);
439 }
440 TemporalConstraint::Overlapping { a, b } => {
441 self.check_overlapping(a, b, &mut violations);
442 }
443 TemporalConstraint::During { inner, outer } => {
444 self.check_during(inner, outer, &mut violations);
445 }
446 TemporalConstraint::Within { event, window } => {
447 self.check_within(event, window, &mut violations);
448 }
449 }
450 }
451 violations
452 }
453
454 pub fn event_chains(&self) -> Vec<Vec<&TemporalEvent>> {
464 let ids: Vec<&str> = self.events.keys().map(String::as_str).collect();
466 let n = ids.len();
467 if n == 0 {
468 return Vec::new();
469 }
470
471 let idx: HashMap<&str, usize> = ids.iter().enumerate().map(|(i, &id)| (id, i)).collect();
473
474 let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
476 for i in 0..n {
477 let ei = self.events.get(ids[i]).expect("known key");
478 for j in (i + 1)..n {
479 let ej = self.events.get(ids[j]).expect("known key");
480 if ei.interval.overlaps(&ej.interval) {
481 adj[i].push(j);
482 adj[j].push(i);
483 }
484 }
485 }
486
487 let mut visited = vec![false; n];
489 let mut components: Vec<Vec<usize>> = Vec::new();
490
491 for start in 0..n {
492 if visited[start] {
493 continue;
494 }
495 let mut component = Vec::new();
496 let mut queue = std::collections::VecDeque::new();
497 queue.push_back(start);
498 visited[start] = true;
499 while let Some(cur) = queue.pop_front() {
500 component.push(cur);
501 for &nb in &adj[cur] {
502 if !visited[nb] {
503 visited[nb] = true;
504 queue.push_back(nb);
505 }
506 }
507 }
508 components.push(component);
509 }
510
511 let sort_key = |idx_val: usize| -> (i64, i64) {
514 let e = self.events.get(ids[idx_val]).expect("known key");
515 (e.interval.start.t, e.interval.end.t)
516 };
517
518 let mut chains: Vec<Vec<&TemporalEvent>> = components
519 .into_iter()
520 .map(|mut comp| {
521 comp.sort_by_key(|&i| sort_key(i));
522 comp.iter()
523 .map(|&i| self.events.get(ids[i]).expect("known key"))
524 .collect()
525 })
526 .collect();
527
528 chains.sort_by(|a, b| {
529 let ka = a
530 .first()
531 .map(|e| (e.interval.start.t, e.interval.end.t))
532 .unwrap_or((i64::MAX, i64::MAX));
533 let kb = b
534 .first()
535 .map(|e| (e.interval.start.t, e.interval.end.t))
536 .unwrap_or((i64::MAX, i64::MAX));
537 ka.cmp(&kb)
538 });
539
540 let _ = idx;
542
543 chains
544 }
545
546 pub fn stats(&self) -> TemporalStats {
552 let event_count = self.events.len();
553 let constraint_count = self.constraints.len();
554
555 if event_count == 0 {
556 return TemporalStats {
557 event_count: 0,
558 constraint_count,
559 timeline_span_ms: 0,
560 avg_event_duration_ms: 0.0,
561 };
562 }
563
564 let mut earliest = i64::MAX;
565 let mut latest = i64::MIN;
566 let mut total_duration: u64 = 0;
567
568 for e in self.events.values() {
569 if e.interval.start.t < earliest {
570 earliest = e.interval.start.t;
571 }
572 if e.interval.end.t > latest {
573 latest = e.interval.end.t;
574 }
575 total_duration += e.interval.duration_ms();
576 }
577
578 let timeline_span_ms = if latest >= earliest {
579 (latest - earliest) as u64
580 } else {
581 0
582 };
583
584 let avg_event_duration_ms = total_duration as f64 / event_count as f64;
585
586 TemporalStats {
587 event_count,
588 constraint_count,
589 timeline_span_ms,
590 avg_event_duration_ms,
591 }
592 }
593
594 fn check_before(&self, a: &str, b: &str, violations: &mut Vec<ConstraintViolation>) {
599 let (ea, eb) = match (self.events.get(a), self.events.get(b)) {
600 (Some(x), Some(y)) => (x, y),
601 _ => return,
602 };
603 let rel = AllenRelation::classify(&ea.interval, &eb.interval);
604 if rel != AllenRelation::Precedes {
606 violations.push(ConstraintViolation {
607 constraint: format!("Before({a}, {b})"),
608 event_a: a.to_owned(),
609 event_b: b.to_owned(),
610 relation: rel,
611 });
612 }
613 }
614
615 fn check_after(&self, a: &str, b: &str, violations: &mut Vec<ConstraintViolation>) {
616 let (ea, eb) = match (self.events.get(a), self.events.get(b)) {
617 (Some(x), Some(y)) => (x, y),
618 _ => return,
619 };
620 let rel = AllenRelation::classify(&ea.interval, &eb.interval);
621 if rel != AllenRelation::PrecededBy {
623 violations.push(ConstraintViolation {
624 constraint: format!("After({a}, {b})"),
625 event_a: a.to_owned(),
626 event_b: b.to_owned(),
627 relation: rel,
628 });
629 }
630 }
631
632 fn check_overlapping(&self, a: &str, b: &str, violations: &mut Vec<ConstraintViolation>) {
633 let (ea, eb) = match (self.events.get(a), self.events.get(b)) {
634 (Some(x), Some(y)) => (x, y),
635 _ => return,
636 };
637 let rel = AllenRelation::classify(&ea.interval, &eb.interval);
638 if !ea.interval.overlaps(&eb.interval) {
639 violations.push(ConstraintViolation {
640 constraint: format!("Overlapping({a}, {b})"),
641 event_a: a.to_owned(),
642 event_b: b.to_owned(),
643 relation: rel,
644 });
645 }
646 }
647
648 fn check_during(&self, inner: &str, outer: &str, violations: &mut Vec<ConstraintViolation>) {
649 let (ei, eo) = match (self.events.get(inner), self.events.get(outer)) {
650 (Some(x), Some(y)) => (x, y),
651 _ => return,
652 };
653 let rel = AllenRelation::classify(&ei.interval, &eo.interval);
654 let satisfied = matches!(
656 rel,
657 AllenRelation::During
658 | AllenRelation::Starts
659 | AllenRelation::Finishes
660 | AllenRelation::Equals
661 );
662 if !satisfied {
663 violations.push(ConstraintViolation {
664 constraint: format!("During(inner={inner}, outer={outer})"),
665 event_a: inner.to_owned(),
666 event_b: outer.to_owned(),
667 relation: rel,
668 });
669 }
670 }
671
672 fn check_within(
673 &self,
674 event_id: &str,
675 window: &TimeInterval,
676 violations: &mut Vec<ConstraintViolation>,
677 ) {
678 let ev = match self.events.get(event_id) {
679 Some(e) => e,
680 None => return,
681 };
682 let window_event = TemporalEvent::new("__window__", window.clone(), Vec::new(), "");
684 let rel = AllenRelation::classify(&ev.interval, &window_event.interval);
685 if !ev.interval.overlaps(window) {
686 violations.push(ConstraintViolation {
687 constraint: format!("Within({event_id}, window)"),
688 event_a: event_id.to_owned(),
689 event_b: "__window__".to_owned(),
690 relation: rel,
691 });
692 }
693 }
694}
695
696#[cfg(test)]
701mod tests {
702 use crate::temporal_reasoning::{
703 AllenRelation, ConstraintViolation, TemporalConstraint, TemporalError, TemporalEvent,
704 TemporalReasoningEngine, TimeInterval, TimePoint,
705 };
706
707 fn tp(t: i64) -> TimePoint {
712 TimePoint::new(t)
713 }
714
715 fn iv(s: i64, e: i64) -> TimeInterval {
716 TimeInterval::new(tp(s), tp(e)).expect("valid interval in test helper")
717 }
718
719 fn ev(id: &str, s: i64, e: i64) -> TemporalEvent {
720 TemporalEvent::new(id, iv(s, e), Vec::new(), "")
721 }
722
723 fn ev_tagged(id: &str, s: i64, e: i64, tags: &[&str]) -> TemporalEvent {
724 TemporalEvent::new(
725 id,
726 iv(s, e),
727 tags.iter().map(|t| t.to_string()).collect(),
728 "",
729 )
730 }
731
732 #[test]
737 fn test_time_point_ordering() {
738 assert!(tp(10) < tp(20));
739 assert!(tp(20) > tp(10));
740 assert_eq!(tp(5), tp(5));
741 }
742
743 #[test]
744 fn test_time_point_from_i64() {
745 let p: TimePoint = 42_i64.into();
746 assert_eq!(p.t, 42);
747 }
748
749 #[test]
754 fn test_interval_valid() {
755 let i = iv(0, 100);
756 assert_eq!(i.duration_ms(), 100);
757 }
758
759 #[test]
760 fn test_interval_invalid_returns_error() {
761 let result = TimeInterval::new(tp(100), tp(0));
762 assert_eq!(
763 result,
764 Err(TemporalError::InvalidInterval { start: 100, end: 0 })
765 );
766 }
767
768 #[test]
769 fn test_interval_zero_duration() {
770 let i = iv(50, 50);
771 assert_eq!(i.duration_ms(), 0);
772 }
773
774 #[test]
779 fn test_contains_point() {
780 let i = iv(10, 20);
781 assert!(i.contains_point(&tp(10)));
782 assert!(i.contains_point(&tp(15)));
783 assert!(i.contains_point(&tp(20)));
784 assert!(!i.contains_point(&tp(9)));
785 assert!(!i.contains_point(&tp(21)));
786 }
787
788 #[test]
789 fn test_before_after() {
790 let a = iv(0, 10);
791 let b = iv(20, 30);
792 assert!(a.before(&b));
793 assert!(b.after(&a));
794 assert!(!b.before(&a));
795 assert!(!a.after(&b));
796 }
797
798 #[test]
799 fn test_meets() {
800 let a = iv(0, 10);
801 let b = iv(10, 20);
802 assert!(a.meets(&b));
803 assert!(!b.meets(&a));
804 }
805
806 #[test]
807 fn test_during() {
808 let outer = iv(0, 100);
809 let inner = iv(20, 80);
810 assert!(inner.during(&outer));
811 assert!(!outer.during(&inner));
812 }
813
814 #[test]
815 fn test_overlaps_various() {
816 assert!(iv(0, 10).overlaps(&iv(5, 15)));
817 assert!(iv(5, 15).overlaps(&iv(0, 10)));
818 assert!(iv(0, 10).overlaps(&iv(10, 20))); assert!(!iv(0, 10).overlaps(&iv(11, 20)));
820 }
821
822 #[test]
827 fn test_allen_precedes() {
828 let rel = AllenRelation::classify(&iv(0, 5), &iv(10, 20));
829 assert_eq!(rel, AllenRelation::Precedes);
830 }
831
832 #[test]
833 fn test_allen_preceded_by() {
834 let rel = AllenRelation::classify(&iv(10, 20), &iv(0, 5));
835 assert_eq!(rel, AllenRelation::PrecededBy);
836 }
837
838 #[test]
839 fn test_allen_meets() {
840 let rel = AllenRelation::classify(&iv(0, 10), &iv(10, 20));
841 assert_eq!(rel, AllenRelation::Meets);
842 }
843
844 #[test]
845 fn test_allen_met_by() {
846 let rel = AllenRelation::classify(&iv(10, 20), &iv(0, 10));
847 assert_eq!(rel, AllenRelation::MetBy);
848 }
849
850 #[test]
851 fn test_allen_overlaps() {
852 let rel = AllenRelation::classify(&iv(0, 15), &iv(10, 25));
853 assert_eq!(rel, AllenRelation::Overlaps);
854 }
855
856 #[test]
857 fn test_allen_overlapped_by() {
858 let rel = AllenRelation::classify(&iv(10, 25), &iv(0, 15));
859 assert_eq!(rel, AllenRelation::OverlappedBy);
860 }
861
862 #[test]
863 fn test_allen_starts() {
864 let rel = AllenRelation::classify(&iv(0, 10), &iv(0, 20));
865 assert_eq!(rel, AllenRelation::Starts);
866 }
867
868 #[test]
869 fn test_allen_started_by() {
870 let rel = AllenRelation::classify(&iv(0, 20), &iv(0, 10));
871 assert_eq!(rel, AllenRelation::StartedBy);
872 }
873
874 #[test]
875 fn test_allen_finishes() {
876 let rel = AllenRelation::classify(&iv(10, 20), &iv(0, 20));
877 assert_eq!(rel, AllenRelation::Finishes);
878 }
879
880 #[test]
881 fn test_allen_finished_by() {
882 let rel = AllenRelation::classify(&iv(0, 20), &iv(10, 20));
883 assert_eq!(rel, AllenRelation::FinishedBy);
884 }
885
886 #[test]
887 fn test_allen_during() {
888 let rel = AllenRelation::classify(&iv(10, 15), &iv(0, 20));
889 assert_eq!(rel, AllenRelation::During);
890 }
891
892 #[test]
893 fn test_allen_contains() {
894 let rel = AllenRelation::classify(&iv(0, 20), &iv(10, 15));
895 assert_eq!(rel, AllenRelation::Contains);
896 }
897
898 #[test]
899 fn test_allen_equals() {
900 let rel = AllenRelation::classify(&iv(5, 15), &iv(5, 15));
901 assert_eq!(rel, AllenRelation::Equals);
902 }
903
904 #[test]
905 fn test_allen_overlapping_kind_filter() {
906 for rel in [
908 AllenRelation::Overlaps,
909 AllenRelation::FinishedBy,
910 AllenRelation::Contains,
911 AllenRelation::Starts,
912 AllenRelation::Equals,
913 AllenRelation::StartedBy,
914 AllenRelation::During,
915 AllenRelation::Finishes,
916 AllenRelation::OverlappedBy,
917 ] {
918 assert!(rel.is_overlapping_kind(), "{rel:?} should be overlapping");
919 }
920 for rel in [
922 AllenRelation::Precedes,
923 AllenRelation::PrecededBy,
924 AllenRelation::Meets,
925 AllenRelation::MetBy,
926 ] {
927 assert!(
928 !rel.is_overlapping_kind(),
929 "{rel:?} should not be overlapping"
930 );
931 }
932 }
933
934 #[test]
939 fn test_add_and_get_event() {
940 let mut engine = TemporalReasoningEngine::new(100);
941 engine
942 .add_event(ev("e1", 0, 100))
943 .expect("test: should succeed");
944 let e = engine.get_event("e1").expect("test: should succeed");
945 assert_eq!(e.id, "e1");
946 }
947
948 #[test]
949 fn test_duplicate_event_error() {
950 let mut engine = TemporalReasoningEngine::new(100);
951 engine
952 .add_event(ev("e1", 0, 100))
953 .expect("test: should succeed");
954 let res = engine.add_event(ev("e1", 50, 150));
955 assert_eq!(res, Err(TemporalError::DuplicateEventId("e1".to_owned())));
956 }
957
958 #[test]
959 fn test_max_events_error() {
960 let mut engine = TemporalReasoningEngine::new(2);
961 engine
962 .add_event(ev("e1", 0, 10))
963 .expect("test: should succeed");
964 engine
965 .add_event(ev("e2", 20, 30))
966 .expect("test: should succeed");
967 let res = engine.add_event(ev("e3", 40, 50));
968 assert_eq!(res, Err(TemporalError::MaxEventsReached));
969 }
970
971 #[test]
972 fn test_remove_event() {
973 let mut engine = TemporalReasoningEngine::new(100);
974 engine
975 .add_event(ev("e1", 0, 100))
976 .expect("test: should succeed");
977 assert!(engine.remove_event("e1"));
978 assert!(!engine.remove_event("e1"));
979 assert!(engine.get_event("e1").is_none());
980 }
981
982 #[test]
987 fn test_events_in_window() {
988 let mut engine = TemporalReasoningEngine::new(100);
989 engine
990 .add_event(ev("a", 0, 10))
991 .expect("test: should succeed"); engine
993 .add_event(ev("b", 5, 15))
994 .expect("test: should succeed"); engine
996 .add_event(ev("c", 12, 20))
997 .expect("test: should succeed"); engine
999 .add_event(ev("d", 19, 30))
1000 .expect("test: should succeed"); engine
1002 .add_event(ev("e", 25, 35))
1003 .expect("test: should succeed"); let window = iv(11, 22);
1005 let result = engine.events_in_window(&window);
1006 let ids: Vec<&str> = result.iter().map(|e| e.id.as_str()).collect();
1007 assert!(ids.contains(&"b"), "b should overlap window [11,22]");
1008 assert!(ids.contains(&"c"), "c should be inside window");
1009 assert!(ids.contains(&"d"), "d should overlap window end");
1010 assert!(!ids.contains(&"a"), "a should not overlap window");
1011 assert!(!ids.contains(&"e"), "e should not overlap window");
1012 for i in 1..result.len() {
1014 assert!(result[i - 1].interval.start <= result[i].interval.start);
1015 }
1016 }
1017
1018 #[test]
1019 fn test_events_with_tag() {
1020 let mut engine = TemporalReasoningEngine::new(100);
1021 engine
1022 .add_event(ev_tagged("a", 0, 10, &["foo", "bar"]))
1023 .expect("test: should succeed");
1024 engine
1025 .add_event(ev_tagged("b", 5, 15, &["bar"]))
1026 .expect("test: should succeed");
1027 engine
1028 .add_event(ev("c", 20, 30))
1029 .expect("test: should succeed");
1030
1031 let foo = engine.events_with_tag("foo");
1032 assert_eq!(foo.len(), 1);
1033 assert_eq!(foo[0].id, "a");
1034
1035 let bar = engine.events_with_tag("bar");
1036 assert_eq!(bar.len(), 2);
1037 assert_eq!(bar[0].id, "a");
1038 assert_eq!(bar[1].id, "b");
1039 }
1040
1041 #[test]
1042 fn test_timeline_sorted() {
1043 let mut engine = TemporalReasoningEngine::new(100);
1044 engine
1045 .add_event(ev("c", 50, 70))
1046 .expect("test: should succeed");
1047 engine
1048 .add_event(ev("a", 0, 20))
1049 .expect("test: should succeed");
1050 engine
1051 .add_event(ev("b", 10, 40))
1052 .expect("test: should succeed");
1053 let tl = engine.timeline();
1054 assert_eq!(tl[0].id, "a");
1055 assert_eq!(tl[1].id, "b");
1056 assert_eq!(tl[2].id, "c");
1057 }
1058
1059 #[test]
1060 fn test_concurrent_events() {
1061 let mut engine = TemporalReasoningEngine::new(100);
1062 engine
1063 .add_event(ev("a", 0, 50))
1064 .expect("test: should succeed");
1065 engine
1066 .add_event(ev("b", 30, 80))
1067 .expect("test: should succeed");
1068 engine
1069 .add_event(ev("c", 60, 100))
1070 .expect("test: should succeed");
1071 engine
1072 .add_event(ev("d", 200, 300))
1073 .expect("test: should succeed");
1074
1075 let conc = engine.concurrent_events("a");
1076 let ids: Vec<&str> = conc.iter().map(|e| e.id.as_str()).collect();
1077 assert!(ids.contains(&"b"));
1078 assert!(!ids.contains(&"c"));
1079 assert!(!ids.contains(&"d"));
1080 assert!(!ids.contains(&"a")); }
1082
1083 #[test]
1084 fn test_concurrent_events_unknown_id() {
1085 let engine = TemporalReasoningEngine::new(100);
1086 assert!(engine.concurrent_events("nonexistent").is_empty());
1087 }
1088
1089 #[test]
1090 fn test_allen_relation_via_engine() {
1091 let mut engine = TemporalReasoningEngine::new(100);
1092 engine
1093 .add_event(ev("a", 0, 10))
1094 .expect("test: should succeed");
1095 engine
1096 .add_event(ev("b", 20, 30))
1097 .expect("test: should succeed");
1098 assert_eq!(
1099 engine.allen_relation("a", "b"),
1100 Some(AllenRelation::Precedes)
1101 );
1102 assert_eq!(engine.allen_relation("a", "missing"), None);
1103 }
1104
1105 #[test]
1110 fn test_constraint_before_satisfied() {
1111 let mut engine = TemporalReasoningEngine::new(100);
1112 engine
1113 .add_event(ev("a", 0, 10))
1114 .expect("test: should succeed");
1115 engine
1116 .add_event(ev("b", 20, 30))
1117 .expect("test: should succeed");
1118 engine.add_constraint(TemporalConstraint::Before {
1119 a: "a".into(),
1120 b: "b".into(),
1121 });
1122 assert!(engine.check_constraints().is_empty());
1123 }
1124
1125 #[test]
1126 fn test_constraint_before_violated() {
1127 let mut engine = TemporalReasoningEngine::new(100);
1128 engine
1129 .add_event(ev("a", 0, 20))
1130 .expect("test: should succeed"); engine
1132 .add_event(ev("b", 10, 30))
1133 .expect("test: should succeed");
1134 engine.add_constraint(TemporalConstraint::Before {
1135 a: "a".into(),
1136 b: "b".into(),
1137 });
1138 let violations = engine.check_constraints();
1139 assert!(!violations.is_empty());
1140 assert_eq!(violations[0].event_a, "a");
1141 }
1142
1143 #[test]
1144 fn test_constraint_after_satisfied() {
1145 let mut engine = TemporalReasoningEngine::new(100);
1146 engine
1147 .add_event(ev("a", 50, 100))
1148 .expect("test: should succeed");
1149 engine
1150 .add_event(ev("b", 0, 30))
1151 .expect("test: should succeed");
1152 engine.add_constraint(TemporalConstraint::After {
1153 a: "a".into(),
1154 b: "b".into(),
1155 });
1156 assert!(engine.check_constraints().is_empty());
1157 }
1158
1159 #[test]
1160 fn test_constraint_after_violated() {
1161 let mut engine = TemporalReasoningEngine::new(100);
1162 engine
1163 .add_event(ev("a", 0, 20))
1164 .expect("test: should succeed");
1165 engine
1166 .add_event(ev("b", 0, 30))
1167 .expect("test: should succeed");
1168 engine.add_constraint(TemporalConstraint::After {
1169 a: "a".into(),
1170 b: "b".into(),
1171 });
1172 let violations = engine.check_constraints();
1173 assert!(!violations.is_empty());
1174 }
1175
1176 #[test]
1177 fn test_constraint_overlapping_satisfied() {
1178 let mut engine = TemporalReasoningEngine::new(100);
1179 engine
1180 .add_event(ev("a", 0, 20))
1181 .expect("test: should succeed");
1182 engine
1183 .add_event(ev("b", 10, 30))
1184 .expect("test: should succeed");
1185 engine.add_constraint(TemporalConstraint::Overlapping {
1186 a: "a".into(),
1187 b: "b".into(),
1188 });
1189 assert!(engine.check_constraints().is_empty());
1190 }
1191
1192 #[test]
1193 fn test_constraint_overlapping_violated() {
1194 let mut engine = TemporalReasoningEngine::new(100);
1195 engine
1196 .add_event(ev("a", 0, 5))
1197 .expect("test: should succeed");
1198 engine
1199 .add_event(ev("b", 10, 20))
1200 .expect("test: should succeed");
1201 engine.add_constraint(TemporalConstraint::Overlapping {
1202 a: "a".into(),
1203 b: "b".into(),
1204 });
1205 let violations = engine.check_constraints();
1206 assert!(!violations.is_empty());
1207 }
1208
1209 #[test]
1210 fn test_constraint_during_satisfied() {
1211 let mut engine = TemporalReasoningEngine::new(100);
1212 engine
1213 .add_event(ev("inner", 10, 20))
1214 .expect("test: should succeed");
1215 engine
1216 .add_event(ev("outer", 0, 50))
1217 .expect("test: should succeed");
1218 engine.add_constraint(TemporalConstraint::During {
1219 inner: "inner".into(),
1220 outer: "outer".into(),
1221 });
1222 assert!(engine.check_constraints().is_empty());
1223 }
1224
1225 #[test]
1226 fn test_constraint_during_violated() {
1227 let mut engine = TemporalReasoningEngine::new(100);
1228 engine
1229 .add_event(ev("inner", 0, 100))
1230 .expect("test: should succeed");
1231 engine
1232 .add_event(ev("outer", 10, 50))
1233 .expect("test: should succeed");
1234 engine.add_constraint(TemporalConstraint::During {
1235 inner: "inner".into(),
1236 outer: "outer".into(),
1237 });
1238 let violations = engine.check_constraints();
1239 assert!(!violations.is_empty());
1240 }
1241
1242 #[test]
1243 fn test_constraint_within_satisfied() {
1244 let mut engine = TemporalReasoningEngine::new(100);
1245 engine
1246 .add_event(ev("e", 10, 20))
1247 .expect("test: should succeed");
1248 let window = iv(0, 50);
1249 engine.add_constraint(TemporalConstraint::Within {
1250 event: "e".into(),
1251 window,
1252 });
1253 assert!(engine.check_constraints().is_empty());
1254 }
1255
1256 #[test]
1257 fn test_constraint_within_violated() {
1258 let mut engine = TemporalReasoningEngine::new(100);
1259 engine
1260 .add_event(ev("e", 100, 200))
1261 .expect("test: should succeed");
1262 let window = iv(0, 50);
1263 engine.add_constraint(TemporalConstraint::Within {
1264 event: "e".into(),
1265 window,
1266 });
1267 let violations = engine.check_constraints();
1268 assert!(!violations.is_empty());
1269 assert_eq!(violations[0].event_a, "e");
1270 }
1271
1272 #[test]
1273 fn test_multiple_constraints_mixed() {
1274 let mut engine = TemporalReasoningEngine::new(100);
1275 engine
1276 .add_event(ev("a", 0, 10))
1277 .expect("test: should succeed");
1278 engine
1279 .add_event(ev("b", 20, 30))
1280 .expect("test: should succeed");
1281 engine
1282 .add_event(ev("c", 5, 15))
1283 .expect("test: should succeed");
1284 engine.add_constraint(TemporalConstraint::Before {
1286 a: "a".into(),
1287 b: "b".into(),
1288 });
1289 engine.add_constraint(TemporalConstraint::Before {
1291 a: "a".into(),
1292 b: "c".into(),
1293 });
1294 let violations = engine.check_constraints();
1295 assert_eq!(violations.len(), 1);
1296 assert_eq!(violations[0].event_b, "c");
1297 }
1298
1299 #[test]
1304 fn test_event_chains_empty() {
1305 let engine = TemporalReasoningEngine::new(100);
1306 assert!(engine.event_chains().is_empty());
1307 }
1308
1309 #[test]
1310 fn test_event_chains_single() {
1311 let mut engine = TemporalReasoningEngine::new(100);
1312 engine
1313 .add_event(ev("a", 0, 10))
1314 .expect("test: should succeed");
1315 let chains = engine.event_chains();
1316 assert_eq!(chains.len(), 1);
1317 assert_eq!(chains[0].len(), 1);
1318 }
1319
1320 #[test]
1321 fn test_event_chains_two_isolated() {
1322 let mut engine = TemporalReasoningEngine::new(100);
1323 engine
1324 .add_event(ev("a", 0, 10))
1325 .expect("test: should succeed");
1326 engine
1327 .add_event(ev("b", 100, 200))
1328 .expect("test: should succeed");
1329 let chains = engine.event_chains();
1330 assert_eq!(chains.len(), 2);
1331 }
1332
1333 #[test]
1334 fn test_event_chains_transitive() {
1335 let mut engine = TemporalReasoningEngine::new(100);
1336 engine
1338 .add_event(ev("a", 0, 20))
1339 .expect("test: should succeed");
1340 engine
1341 .add_event(ev("b", 15, 35))
1342 .expect("test: should succeed");
1343 engine
1344 .add_event(ev("c", 30, 50))
1345 .expect("test: should succeed");
1346 engine
1347 .add_event(ev("d", 200, 300))
1348 .expect("test: should succeed"); let chains = engine.event_chains();
1350 assert_eq!(chains.len(), 2);
1352 let big = chains
1353 .iter()
1354 .find(|ch| ch.len() == 3)
1355 .expect("test: should succeed");
1356 let ids: Vec<&str> = big.iter().map(|e| e.id.as_str()).collect();
1357 assert!(ids.contains(&"a") && ids.contains(&"b") && ids.contains(&"c"));
1358 }
1359
1360 #[test]
1365 fn test_stats_empty() {
1366 let engine = TemporalReasoningEngine::new(100);
1367 let s = engine.stats();
1368 assert_eq!(s.event_count, 0);
1369 assert_eq!(s.constraint_count, 0);
1370 assert_eq!(s.timeline_span_ms, 0);
1371 assert_eq!(s.avg_event_duration_ms, 0.0);
1372 }
1373
1374 #[test]
1375 fn test_stats_with_events() {
1376 let mut engine = TemporalReasoningEngine::new(100);
1377 engine
1378 .add_event(ev("a", 0, 100))
1379 .expect("test: should succeed");
1380 engine
1381 .add_event(ev("b", 50, 200))
1382 .expect("test: should succeed");
1383 engine.add_constraint(TemporalConstraint::Overlapping {
1384 a: "a".into(),
1385 b: "b".into(),
1386 });
1387 let s = engine.stats();
1388 assert_eq!(s.event_count, 2);
1389 assert_eq!(s.constraint_count, 1);
1390 assert_eq!(s.timeline_span_ms, 200); assert!((s.avg_event_duration_ms - 125.0).abs() < f64::EPSILON);
1393 }
1394
1395 #[test]
1400 fn test_violation_contains_relation() {
1401 let mut engine = TemporalReasoningEngine::new(100);
1402 engine
1403 .add_event(ev("x", 0, 50))
1404 .expect("test: should succeed");
1405 engine
1406 .add_event(ev("y", 40, 80))
1407 .expect("test: should succeed");
1408 engine.add_constraint(TemporalConstraint::Before {
1409 a: "x".into(),
1410 b: "y".into(),
1411 });
1412 let violations = engine.check_constraints();
1413 assert_eq!(violations.len(), 1);
1414 let v: &ConstraintViolation = &violations[0];
1415 assert_eq!(v.relation, AllenRelation::Overlaps);
1416 assert!(v.constraint.contains("Before"));
1417 }
1418}