use crate::Rule;
use anyhow::{anyhow, Result};
use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TimeInterval {
pub start: f64,
pub end: f64,
}
impl TimeInterval {
pub fn new(start: f64, end: f64) -> Result<Self> {
if start > end {
return Err(anyhow!("Start time must be before end time"));
}
Ok(Self { start, end })
}
pub fn duration(&self) -> f64 {
self.end - self.start
}
pub fn contains_point(&self, point: f64) -> bool {
point >= self.start && point <= self.end
}
pub fn allen_relation(&self, other: &TimeInterval) -> AllenRelation {
let eps = 1e-10;
if (self.start - other.start).abs() < eps && (self.end - other.end).abs() < eps {
return AllenRelation::Equals;
}
if self.end < other.start - eps {
return AllenRelation::Before;
}
if self.start > other.end + eps {
return AllenRelation::After;
}
if (self.end - other.start).abs() < eps {
return AllenRelation::Meets;
}
if (self.start - other.end).abs() < eps {
return AllenRelation::MetBy;
}
if self.start > other.start + eps && self.end < other.end - eps {
return AllenRelation::During;
}
if self.start < other.start - eps && self.end > other.end + eps {
return AllenRelation::Contains;
}
if (self.start - other.start).abs() < eps && self.end < other.end - eps {
return AllenRelation::Starts;
}
if (self.start - other.start).abs() < eps && self.end > other.end + eps {
return AllenRelation::StartedBy;
}
if self.start > other.start + eps && (self.end - other.end).abs() < eps {
return AllenRelation::Finishes;
}
if self.start < other.start - eps && (self.end - other.end).abs() < eps {
return AllenRelation::FinishedBy;
}
if self.start < other.start - eps
&& self.end > other.start + eps
&& self.end < other.end - eps
{
return AllenRelation::Overlaps;
}
if self.start > other.start + eps
&& self.start < other.end - eps
&& self.end > other.end + eps
{
return AllenRelation::OverlappedBy;
}
AllenRelation::Before
}
pub fn intersection(&self, other: &TimeInterval) -> Option<TimeInterval> {
let start = self.start.max(other.start);
let end = self.end.min(other.end);
if start <= end {
TimeInterval::new(start, end).ok()
} else {
None
}
}
pub fn union(&self, other: &TimeInterval) -> Option<TimeInterval> {
let relation = self.allen_relation(other);
match relation {
AllenRelation::Before | AllenRelation::After => None,
_ => {
let start = self.start.min(other.start);
let end = self.end.max(other.end);
TimeInterval::new(start, end).ok()
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AllenRelation {
Before,
After,
Meets,
MetBy,
Overlaps,
OverlappedBy,
During,
Contains,
Starts,
StartedBy,
Finishes,
FinishedBy,
Equals,
}
impl AllenRelation {
pub fn inverse(&self) -> AllenRelation {
match self {
AllenRelation::Before => AllenRelation::After,
AllenRelation::After => AllenRelation::Before,
AllenRelation::Meets => AllenRelation::MetBy,
AllenRelation::MetBy => AllenRelation::Meets,
AllenRelation::Overlaps => AllenRelation::OverlappedBy,
AllenRelation::OverlappedBy => AllenRelation::Overlaps,
AllenRelation::During => AllenRelation::Contains,
AllenRelation::Contains => AllenRelation::During,
AllenRelation::Starts => AllenRelation::StartedBy,
AllenRelation::StartedBy => AllenRelation::Starts,
AllenRelation::Finishes => AllenRelation::FinishedBy,
AllenRelation::FinishedBy => AllenRelation::Finishes,
AllenRelation::Equals => AllenRelation::Equals,
}
}
pub fn all() -> Vec<AllenRelation> {
vec![
AllenRelation::Before,
AllenRelation::After,
AllenRelation::Meets,
AllenRelation::MetBy,
AllenRelation::Overlaps,
AllenRelation::OverlappedBy,
AllenRelation::During,
AllenRelation::Contains,
AllenRelation::Starts,
AllenRelation::StartedBy,
AllenRelation::Finishes,
AllenRelation::FinishedBy,
AllenRelation::Equals,
]
}
}
#[derive(Debug, Clone)]
pub struct TemporalConstraint {
pub relations: HashSet<AllenRelation>,
}
impl TemporalConstraint {
pub fn universal() -> Self {
Self {
relations: AllenRelation::all().into_iter().collect(),
}
}
pub fn single(relation: AllenRelation) -> Self {
let mut relations = HashSet::new();
relations.insert(relation);
Self { relations }
}
pub fn intersect(&self, other: &TemporalConstraint) -> TemporalConstraint {
TemporalConstraint {
relations: self
.relations
.intersection(&other.relations)
.copied()
.collect(),
}
}
pub fn is_empty(&self) -> bool {
self.relations.is_empty()
}
pub fn compose(&self, other: &TemporalConstraint) -> TemporalConstraint {
let mut result = HashSet::new();
for r1 in &self.relations {
for r2 in &other.relations {
result.extend(Self::compose_relations(*r1, *r2));
}
}
TemporalConstraint { relations: result }
}
fn compose_relations(r1: AllenRelation, r2: AllenRelation) -> Vec<AllenRelation> {
use AllenRelation::*;
match (r1, r2) {
(Before, Before) => vec![Before],
(Before, Meets) => vec![Before],
(Meets, Before) => vec![Before],
(Meets, Meets) => vec![Meets],
(Equals, r) => vec![r],
(r, Equals) => vec![r],
_ => AllenRelation::all(), }
}
}
#[derive(Debug, Clone)]
pub struct TemporalConstraintNetwork {
intervals: HashMap<String, TimeInterval>,
constraints: HashMap<(String, String), TemporalConstraint>,
}
impl TemporalConstraintNetwork {
pub fn new() -> Self {
Self {
intervals: HashMap::new(),
constraints: HashMap::new(),
}
}
pub fn add_interval(&mut self, name: String, interval: TimeInterval) {
self.intervals.insert(name, interval);
}
pub fn add_constraint(&mut self, from: String, to: String, relation: AllenRelation) {
let constraint = TemporalConstraint::single(relation);
self.constraints
.insert((from.clone(), to.clone()), constraint.clone());
let inverse_constraint = TemporalConstraint::single(relation.inverse());
self.constraints.insert((to, from), inverse_constraint);
}
pub fn get_constraint(&self, from: &str, to: &str) -> TemporalConstraint {
self.constraints
.get(&(from.to_string(), to.to_string()))
.cloned()
.unwrap_or_else(TemporalConstraint::universal)
}
pub fn is_consistent(&self) -> Result<bool> {
let mut network = self.clone();
let mut changed = true;
let mut iterations = 0;
const MAX_ITERATIONS: usize = 100;
while changed && iterations < MAX_ITERATIONS {
changed = false;
iterations += 1;
let interval_names: Vec<String> = network.intervals.keys().cloned().collect();
for i in &interval_names {
for j in &interval_names {
if i == j {
continue;
}
for k in &interval_names {
if k == i || k == j {
continue;
}
let c_ij = network.get_constraint(i, j);
let c_ik = network.get_constraint(i, k);
let c_kj = network.get_constraint(k, j);
let composed = c_ik.compose(&c_kj);
let new_c_ij = c_ij.intersect(&composed);
if new_c_ij.is_empty() {
return Ok(false); }
if new_c_ij.relations != c_ij.relations {
changed = true;
network
.constraints
.insert((i.clone(), j.clone()), new_c_ij.clone());
let inverse_relations: HashSet<AllenRelation> =
new_c_ij.relations.iter().map(|r| r.inverse()).collect();
network.constraints.insert(
(j.clone(), i.clone()),
TemporalConstraint {
relations: inverse_relations,
},
);
}
}
}
}
}
Ok(true)
}
pub fn get_intervals(&self) -> &HashMap<String, TimeInterval> {
&self.intervals
}
pub fn query_relation(&self, from: &str, to: &str) -> Option<HashSet<AllenRelation>> {
self.constraints
.get(&(from.to_string(), to.to_string()))
.map(|c| c.relations.clone())
}
}
impl Default for TemporalConstraintNetwork {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct TemporalRule {
pub rule: Rule,
pub temporal_constraints: HashMap<String, TimeInterval>,
}
impl TemporalRule {
pub fn new(rule: Rule) -> Self {
Self {
rule,
temporal_constraints: HashMap::new(),
}
}
pub fn add_temporal_constraint(&mut self, variable: String, interval: TimeInterval) {
self.temporal_constraints.insert(variable, interval);
}
pub fn is_applicable_at(&self, time: f64) -> bool {
self.temporal_constraints
.values()
.all(|interval| interval.contains_point(time))
}
}
#[derive(Debug, Clone)]
pub struct TemporalEvent {
pub name: String,
pub interval: TimeInterval,
pub properties: HashMap<String, String>,
}
impl TemporalEvent {
pub fn new(name: String, interval: TimeInterval) -> Self {
Self {
name,
interval,
properties: HashMap::new(),
}
}
pub fn add_property(&mut self, key: String, value: String) {
self.properties.insert(key, value);
}
pub fn relation_with(&self, other: &TemporalEvent) -> AllenRelation {
self.interval.allen_relation(&other.interval)
}
}
#[derive(Debug)]
pub struct TemporalReasoningEngine {
tcn: TemporalConstraintNetwork,
rules: Vec<TemporalRule>,
events: Vec<TemporalEvent>,
}
impl TemporalReasoningEngine {
pub fn new() -> Self {
Self {
tcn: TemporalConstraintNetwork::new(),
rules: Vec::new(),
events: Vec::new(),
}
}
pub fn add_interval(&mut self, name: String, interval: TimeInterval) {
self.tcn.add_interval(name, interval);
}
pub fn add_constraint(&mut self, from: String, to: String, relation: AllenRelation) {
self.tcn.add_constraint(from, to, relation);
}
pub fn add_temporal_rule(&mut self, rule: TemporalRule) {
self.rules.push(rule);
}
pub fn add_event(&mut self, event: TemporalEvent) {
self.events.push(event);
}
pub fn get_applicable_rules(&self, time: f64) -> Vec<&TemporalRule> {
self.rules
.iter()
.filter(|rule| rule.is_applicable_at(time))
.collect()
}
pub fn get_events_at(&self, time: f64) -> Vec<&TemporalEvent> {
self.events
.iter()
.filter(|event| event.interval.contains_point(time))
.collect()
}
pub fn find_events_with_relation(
&self,
event_name: &str,
relation: AllenRelation,
) -> Vec<&TemporalEvent> {
if let Some(target_event) = self.events.iter().find(|e| e.name == event_name) {
self.events
.iter()
.filter(|e| e.name != event_name && e.relation_with(target_event) == relation)
.collect()
} else {
Vec::new()
}
}
pub fn is_consistent(&self) -> Result<bool> {
self.tcn.is_consistent()
}
pub fn get_network(&self) -> &TemporalConstraintNetwork {
&self.tcn
}
}
impl Default for TemporalReasoningEngine {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_time_interval_creation() -> Result<(), Box<dyn std::error::Error>> {
let interval = TimeInterval::new(1.0, 5.0)?;
assert_eq!(interval.start, 1.0);
assert_eq!(interval.end, 5.0);
assert_eq!(interval.duration(), 4.0);
assert!(TimeInterval::new(5.0, 1.0).is_err());
Ok(())
}
#[test]
fn test_allen_before() -> Result<(), Box<dyn std::error::Error>> {
let i1 = TimeInterval::new(1.0, 3.0)?;
let i2 = TimeInterval::new(5.0, 7.0)?;
assert_eq!(i1.allen_relation(&i2), AllenRelation::Before);
assert_eq!(i2.allen_relation(&i1), AllenRelation::After);
Ok(())
}
#[test]
fn test_allen_meets() -> Result<(), Box<dyn std::error::Error>> {
let i1 = TimeInterval::new(1.0, 3.0)?;
let i2 = TimeInterval::new(3.0, 5.0)?;
assert_eq!(i1.allen_relation(&i2), AllenRelation::Meets);
assert_eq!(i2.allen_relation(&i1), AllenRelation::MetBy);
Ok(())
}
#[test]
fn test_allen_overlaps() -> Result<(), Box<dyn std::error::Error>> {
let i1 = TimeInterval::new(1.0, 4.0)?;
let i2 = TimeInterval::new(3.0, 6.0)?;
assert_eq!(i1.allen_relation(&i2), AllenRelation::Overlaps);
assert_eq!(i2.allen_relation(&i1), AllenRelation::OverlappedBy);
Ok(())
}
#[test]
fn test_allen_during() -> Result<(), Box<dyn std::error::Error>> {
let i1 = TimeInterval::new(2.0, 4.0)?;
let i2 = TimeInterval::new(1.0, 5.0)?;
assert_eq!(i1.allen_relation(&i2), AllenRelation::During);
assert_eq!(i2.allen_relation(&i1), AllenRelation::Contains);
Ok(())
}
#[test]
fn test_allen_equals() -> Result<(), Box<dyn std::error::Error>> {
let i1 = TimeInterval::new(1.0, 5.0)?;
let i2 = TimeInterval::new(1.0, 5.0)?;
assert_eq!(i1.allen_relation(&i2), AllenRelation::Equals);
Ok(())
}
#[test]
fn test_interval_intersection() -> Result<(), Box<dyn std::error::Error>> {
let i1 = TimeInterval::new(1.0, 5.0)?;
let i2 = TimeInterval::new(3.0, 7.0)?;
let intersection = i1.intersection(&i2).ok_or("expected Some value")?;
assert_eq!(intersection.start, 3.0);
assert_eq!(intersection.end, 5.0);
Ok(())
}
#[test]
fn test_interval_union() -> Result<(), Box<dyn std::error::Error>> {
let i1 = TimeInterval::new(1.0, 5.0)?;
let i2 = TimeInterval::new(3.0, 7.0)?;
let union = i1.union(&i2).ok_or("expected Some value")?;
assert_eq!(union.start, 1.0);
assert_eq!(union.end, 7.0);
Ok(())
}
#[test]
fn test_temporal_constraint_network() -> Result<(), Box<dyn std::error::Error>> {
let mut tcn = TemporalConstraintNetwork::new();
let morning = TimeInterval::new(8.0, 12.0)?;
let lunch = TimeInterval::new(12.0, 13.0)?;
let afternoon = TimeInterval::new(13.0, 17.0)?;
tcn.add_interval("morning".to_string(), morning);
tcn.add_interval("lunch".to_string(), lunch);
tcn.add_interval("afternoon".to_string(), afternoon);
tcn.add_constraint(
"morning".to_string(),
"lunch".to_string(),
AllenRelation::Meets,
);
tcn.add_constraint(
"lunch".to_string(),
"afternoon".to_string(),
AllenRelation::Meets,
);
assert!(tcn.is_consistent()?);
Ok(())
}
#[test]
fn test_temporal_rule() -> Result<(), Box<dyn std::error::Error>> {
let rule = Rule {
name: "test_rule".to_string(),
body: vec![],
head: vec![],
};
let mut temporal_rule = TemporalRule::new(rule);
let interval = TimeInterval::new(9.0, 17.0)?;
temporal_rule.add_temporal_constraint("working_hours".to_string(), interval);
assert!(temporal_rule.is_applicable_at(10.0));
assert!(!temporal_rule.is_applicable_at(20.0));
Ok(())
}
#[test]
fn test_temporal_event() -> Result<(), Box<dyn std::error::Error>> {
let meeting =
TemporalEvent::new("team_meeting".to_string(), TimeInterval::new(14.0, 15.0)?);
let lunch = TemporalEvent::new("lunch_break".to_string(), TimeInterval::new(12.0, 13.0)?);
assert_eq!(lunch.relation_with(&meeting), AllenRelation::Before);
Ok(())
}
#[test]
fn test_temporal_reasoning_engine() -> Result<(), Box<dyn std::error::Error>> {
let mut engine = TemporalReasoningEngine::new();
let event1 = TemporalEvent::new("event1".to_string(), TimeInterval::new(1.0, 3.0)?);
let event2 = TemporalEvent::new("event2".to_string(), TimeInterval::new(2.0, 4.0)?);
engine.add_event(event1);
engine.add_event(event2);
let events_at_2_5 = engine.get_events_at(2.5);
assert_eq!(events_at_2_5.len(), 2);
Ok(())
}
#[test]
fn test_allen_relation_inverse() {
assert_eq!(AllenRelation::Before.inverse(), AllenRelation::After);
assert_eq!(AllenRelation::Meets.inverse(), AllenRelation::MetBy);
assert_eq!(
AllenRelation::Overlaps.inverse(),
AllenRelation::OverlappedBy
);
assert_eq!(AllenRelation::Equals.inverse(), AllenRelation::Equals);
}
}