use crate::ai::AiConfig;
use anyhow::Result;
use scirs2_core::random::{Random, RngExt};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use std::time::{SystemTime, UNIX_EPOCH};
pub struct TemporalReasoner {
config: TemporalConfig,
temporal_kb: TemporalKnowledgeBase,
inference_engine: Box<dyn TemporalInferenceEngine>,
event_detector: Box<dyn EventDetector>,
#[allow(dead_code)]
constraint_solver: Box<dyn TemporalConstraintSolver>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemporalConfig {
pub enable_inference: bool,
pub enable_event_detection: bool,
pub temporal_resolution: TemporalResolution,
pub max_inference_depth: usize,
pub inference_confidence_threshold: f32,
pub enable_constraint_solving: bool,
pub supported_relations: Vec<TemporalRelation>,
}
impl Default for TemporalConfig {
fn default() -> Self {
Self {
enable_inference: true,
enable_event_detection: true,
temporal_resolution: TemporalResolution::Day,
max_inference_depth: 5,
inference_confidence_threshold: 0.7,
enable_constraint_solving: true,
supported_relations: vec![
TemporalRelation::Before,
TemporalRelation::After,
TemporalRelation::During,
TemporalRelation::Overlaps,
TemporalRelation::Meets,
TemporalRelation::Starts,
TemporalRelation::Finishes,
TemporalRelation::Equals,
],
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TemporalResolution {
Millisecond,
Second,
Minute,
Hour,
Day,
Week,
Month,
Year,
Decade,
Century,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum TemporalRelation {
Before,
After,
During,
Contains,
Overlaps,
OverlappedBy,
Meets,
MetBy,
Starts,
StartedBy,
Finishes,
FinishedBy,
Equals,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemporalQuery {
pub query_type: TemporalQueryType,
pub entities: Vec<String>,
pub constraints: Vec<TemporalConstraint>,
pub time_window: Option<TimeInterval>,
pub include_inferred: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TemporalQueryType {
ValidAt { time: Timestamp },
ValidDuring { interval: TimeInterval },
TemporalRelations { entity1: String, entity2: String },
EventSequence { pattern: Vec<EventPattern> },
Aggregation {
function: AggregationFunction,
grouping: TemporalGrouping,
},
ChangeDetection { entity: String, property: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemporalResult {
pub query_id: String,
pub results: Vec<TemporalFact>,
pub inference_trace: Option<Vec<InferenceStep>>,
pub execution_time: std::time::Duration,
pub confidence: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemporalFact {
pub subject: String,
pub predicate: String,
pub object: String,
pub validity: TimeInterval,
pub confidence: f32,
pub source: FactSource,
pub annotations: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FactSource {
Asserted,
Inferred { rule: String, premises: Vec<String> },
TemporalInference { reasoning_type: String },
EventDetection { detector: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimeInterval {
pub start: Timestamp,
pub end: Timestamp,
pub interval_type: IntervalType,
}
impl TimeInterval {
pub fn contains(&self, timestamp: Timestamp) -> bool {
match self.interval_type {
IntervalType::Closed => timestamp >= self.start && timestamp <= self.end,
IntervalType::Open => timestamp > self.start && timestamp < self.end,
IntervalType::LeftOpen => timestamp > self.start && timestamp <= self.end,
IntervalType::RightOpen => timestamp >= self.start && timestamp < self.end,
}
}
pub fn overlaps(&self, other: &TimeInterval) -> bool {
self.start < other.end && other.start < self.end
}
pub fn relation_to(&self, other: &TimeInterval) -> TemporalRelation {
if self.end < other.start {
TemporalRelation::Before
} else if self.start > other.end {
TemporalRelation::After
} else if self.start == other.start && self.end == other.end {
TemporalRelation::Equals
} else if self.start >= other.start && self.end <= other.end {
TemporalRelation::During
} else if self.start <= other.start && self.end >= other.end {
TemporalRelation::Contains
} else if self.end == other.start {
TemporalRelation::Meets
} else if self.start == other.end {
TemporalRelation::MetBy
} else if self.start == other.start && self.end < other.end {
TemporalRelation::Starts
} else if self.start == other.start && self.end > other.end {
TemporalRelation::StartedBy
} else if self.end == other.end && self.start > other.start {
TemporalRelation::Finishes
} else if self.end == other.end && self.start < other.start {
TemporalRelation::FinishedBy
} else if self.overlaps(other) && self.start < other.start {
TemporalRelation::Overlaps
} else {
TemporalRelation::OverlappedBy
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum IntervalType {
Closed,
Open,
LeftOpen,
RightOpen,
}
pub type Timestamp = u64;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemporalConstraint {
pub constraint_type: ConstraintType,
pub entity: String,
pub relation: TemporalRelation,
pub reference: TemporalReference,
pub strength: ConstraintStrength,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ConstraintType {
Hard,
Soft { weight: f32 },
Conditional { condition: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TemporalReference {
Absolute(Timestamp),
Interval(TimeInterval),
Relative { entity: String, offset: Option<i64> },
Now,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ConstraintStrength {
Required,
Strong,
Medium,
Weak,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventPattern {
pub event_type: String,
pub entities: Vec<String>,
pub constraints: Vec<TemporalConstraint>,
pub optional: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AggregationFunction {
Count,
Sum,
Average,
Min,
Max,
Duration,
Frequency,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TemporalGrouping {
ByHour,
ByDay,
ByWeek,
ByMonth,
ByYear,
ByInterval { duration: u64 },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InferenceStep {
pub step: usize,
pub rule: String,
pub inputs: Vec<TemporalFact>,
pub output: TemporalFact,
pub confidence: f32,
}
pub struct TemporalKnowledgeBase {
facts_by_time: BTreeMap<Timestamp, Vec<TemporalFact>>,
facts_by_entity: HashMap<String, Vec<TemporalFact>>,
#[allow(dead_code)]
temporal_rules: Vec<TemporalRule>,
event_definitions: HashMap<String, EventDefinition>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemporalRule {
pub id: String,
pub name: String,
pub premises: Vec<TemporalPattern>,
pub conclusion: TemporalPattern,
pub confidence: f32,
pub temporal_constraints: Vec<TemporalConstraint>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemporalPattern {
pub variables: HashMap<String, String>,
pub temporal_conditions: Vec<TemporalCondition>,
pub confidence: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemporalCondition {
pub subject: String,
pub predicate: String,
pub object: String,
pub validity: TemporalValidity,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TemporalValidity {
Always,
During(TimeInterval),
At(Timestamp),
Relative {
reference: String,
relation: TemporalRelation,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventDefinition {
pub event_type: String,
pub patterns: Vec<EventDetectionPattern>,
pub duration_constraints: Option<TimeInterval>,
pub participants: Vec<ParticipantRole>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventDetectionPattern {
pub conditions: Vec<TemporalCondition>,
pub ordering: Vec<TemporalOrdering>,
pub confidence: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemporalOrdering {
pub first: String,
pub second: String,
pub relation: TemporalRelation,
pub bounds: Option<TimeInterval>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ParticipantRole {
pub role: String,
pub entity_type: String,
pub required: bool,
}
pub trait TemporalInferenceEngine: Send + Sync {
fn infer(&self, kb: &TemporalKnowledgeBase, query: &TemporalQuery)
-> Result<Vec<TemporalFact>>;
fn apply_rules(
&self,
facts: &[TemporalFact],
rules: &[TemporalRule],
) -> Result<Vec<TemporalFact>>;
}
pub trait EventDetector: Send + Sync {
fn detect_events(
&self,
facts: &[TemporalFact],
event_definitions: &[EventDefinition],
) -> Result<Vec<DetectedEvent>>;
fn get_patterns(&self) -> Vec<EventDetectionPattern>;
}
pub trait TemporalConstraintSolver: Send + Sync {
fn solve_constraints(&self, constraints: &[TemporalConstraint]) -> Result<ConstraintSolution>;
fn check_satisfaction(
&self,
constraints: &[TemporalConstraint],
assignments: &HashMap<String, Timestamp>,
) -> Result<bool>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DetectedEvent {
pub event_type: String,
pub interval: TimeInterval,
pub participants: HashMap<String, String>,
pub supporting_facts: Vec<TemporalFact>,
pub confidence: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConstraintSolution {
pub assignments: HashMap<String, Timestamp>,
pub satisfaction_score: f32,
pub unsatisfied: Vec<String>,
}
impl TemporalReasoner {
pub fn new(_config: &AiConfig) -> Result<Self> {
let temporal_config = TemporalConfig::default();
Ok(Self {
config: temporal_config,
temporal_kb: TemporalKnowledgeBase::new(),
inference_engine: Box::new(DefaultTemporalInferenceEngine::new()),
event_detector: Box::new(DefaultEventDetector::new()),
constraint_solver: Box::new(DefaultConstraintSolver::new()),
})
}
pub async fn reason(&self, query: &TemporalQuery) -> Result<TemporalResult> {
let start_time = std::time::Instant::now();
let mut inference_steps = Vec::new();
let mut facts = self.retrieve_facts(query)?;
if self.config.enable_inference && query.include_inferred {
let inferred_facts = self.inference_engine.infer(&self.temporal_kb, query)?;
for (idx, inferred_fact) in inferred_facts.iter().enumerate() {
if let FactSource::Inferred { rule, premises } = &inferred_fact.source {
let step = InferenceStep {
step: idx + 1,
rule: rule.clone(),
inputs: premises
.iter()
.filter_map(|premise_id| {
facts
.iter()
.find(|f| {
format!("{}:{}:{}", f.subject, f.predicate, f.object)
== *premise_id
})
.cloned()
})
.collect(),
output: inferred_fact.clone(),
confidence: inferred_fact.confidence,
};
inference_steps.push(step);
}
}
facts.extend(inferred_facts);
}
if self.config.enable_event_detection {
let events = self.event_detector.detect_events(
&facts,
&self
.temporal_kb
.event_definitions
.values()
.cloned()
.collect::<Vec<_>>(),
)?;
for event in events {
let event_fact = self.event_to_fact(event)?;
facts.push(event_fact);
}
}
let filtered_facts = self.filter_and_rank_facts(facts, query)?;
let overall_confidence = if filtered_facts.is_empty() {
0.0
} else {
let sum: f32 = filtered_facts.iter().map(|f| f.confidence).sum();
let count = filtered_facts.len() as f32;
(sum / count).min(1.0) };
let execution_time = start_time.elapsed();
Ok(TemporalResult {
query_id: format!("query_{}", {
let mut rng = Random::default();
rng.random::<u32>()
}),
results: filtered_facts,
inference_trace: if inference_steps.is_empty() {
None
} else {
Some(inference_steps)
},
execution_time,
confidence: overall_confidence,
})
}
pub fn add_fact(&mut self, fact: TemporalFact) -> Result<()> {
self.temporal_kb
.facts_by_time
.entry(fact.validity.start)
.or_default()
.push(fact.clone());
self.temporal_kb
.facts_by_entity
.entry(fact.subject.clone())
.or_default()
.push(fact.clone());
self.temporal_kb
.facts_by_entity
.entry(fact.object.clone())
.or_default()
.push(fact);
Ok(())
}
fn retrieve_facts(&self, query: &TemporalQuery) -> Result<Vec<TemporalFact>> {
let mut facts = Vec::new();
match &query.query_type {
TemporalQueryType::ValidAt { time } => {
for time_facts in self.temporal_kb.facts_by_time.values() {
for fact in time_facts {
if fact.validity.contains(*time) {
facts.push(fact.clone());
}
}
}
}
TemporalQueryType::ValidDuring { interval } => {
for time_facts in self.temporal_kb.facts_by_time.values() {
for fact in time_facts {
if fact.validity.overlaps(interval) {
facts.push(fact.clone());
}
}
}
}
_ => {
for time_facts in self.temporal_kb.facts_by_time.values() {
facts.extend(time_facts.clone());
}
}
}
Ok(facts)
}
fn event_to_fact(&self, event: DetectedEvent) -> Result<TemporalFact> {
Ok(TemporalFact {
subject: format!("event:{}", event.event_type),
predicate: "hasEventType".to_string(),
object: event.event_type,
validity: event.interval,
confidence: event.confidence,
source: FactSource::EventDetection {
detector: "default".to_string(),
},
annotations: HashMap::new(),
})
}
fn filter_and_rank_facts(
&self,
mut facts: Vec<TemporalFact>,
query: &TemporalQuery,
) -> Result<Vec<TemporalFact>> {
if !query.entities.is_empty() {
facts.retain(|fact| {
query.entities.contains(&fact.subject) || query.entities.contains(&fact.object)
});
}
if let Some(window) = &query.time_window {
facts.retain(|fact| fact.validity.overlaps(window));
}
facts.sort_by(|a, b| {
b.confidence
.partial_cmp(&a.confidence)
.unwrap_or(std::cmp::Ordering::Equal)
});
Ok(facts)
}
}
impl TemporalKnowledgeBase {
fn new() -> Self {
Self {
facts_by_time: BTreeMap::new(),
facts_by_entity: HashMap::new(),
temporal_rules: Vec::new(),
event_definitions: HashMap::new(),
}
}
}
struct DefaultTemporalInferenceEngine;
impl DefaultTemporalInferenceEngine {
fn new() -> Self {
Self
}
}
impl TemporalInferenceEngine for DefaultTemporalInferenceEngine {
fn infer(
&self,
_kb: &TemporalKnowledgeBase,
_query: &TemporalQuery,
) -> Result<Vec<TemporalFact>> {
Ok(Vec::new())
}
fn apply_rules(
&self,
_facts: &[TemporalFact],
_rules: &[TemporalRule],
) -> Result<Vec<TemporalFact>> {
Ok(Vec::new())
}
}
struct DefaultEventDetector;
impl DefaultEventDetector {
fn new() -> Self {
Self
}
}
impl EventDetector for DefaultEventDetector {
fn detect_events(
&self,
_facts: &[TemporalFact],
_event_definitions: &[EventDefinition],
) -> Result<Vec<DetectedEvent>> {
Ok(Vec::new())
}
fn get_patterns(&self) -> Vec<EventDetectionPattern> {
Vec::new()
}
}
struct DefaultConstraintSolver;
impl DefaultConstraintSolver {
fn new() -> Self {
Self
}
}
impl TemporalConstraintSolver for DefaultConstraintSolver {
fn solve_constraints(&self, _constraints: &[TemporalConstraint]) -> Result<ConstraintSolution> {
Ok(ConstraintSolution {
assignments: HashMap::new(),
satisfaction_score: 1.0,
unsatisfied: Vec::new(),
})
}
fn check_satisfaction(
&self,
_constraints: &[TemporalConstraint],
_assignments: &HashMap<String, Timestamp>,
) -> Result<bool> {
Ok(true)
}
}
pub fn current_timestamp() -> Timestamp {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("SystemTime should be after UNIX_EPOCH")
.as_secs()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ai::AiConfig;
#[test]
fn test_temporal_reasoner_creation() {
let config = AiConfig::default();
let reasoner = TemporalReasoner::new(&config);
assert!(reasoner.is_ok());
}
#[test]
fn test_time_interval_operations() {
let interval1 = TimeInterval {
start: 100,
end: 200,
interval_type: IntervalType::Closed,
};
let interval2 = TimeInterval {
start: 150,
end: 250,
interval_type: IntervalType::Closed,
};
assert!(interval1.overlaps(&interval2));
assert_eq!(
interval1.relation_to(&interval2),
TemporalRelation::Overlaps
);
}
#[test]
fn test_temporal_fact_creation() {
let fact = TemporalFact {
subject: "http://example.org/person1".to_string(),
predicate: "worksFor".to_string(),
object: "http://example.org/company1".to_string(),
validity: TimeInterval {
start: 1000,
end: 2000,
interval_type: IntervalType::Closed,
},
confidence: 0.9,
source: FactSource::Asserted,
annotations: HashMap::new(),
};
assert_eq!(fact.confidence, 0.9);
assert!(fact.validity.contains(1500));
assert!(!fact.validity.contains(2500));
}
#[tokio::test]
async fn test_temporal_query() {
let config = AiConfig::default();
let reasoner = TemporalReasoner::new(&config).expect("construction should succeed");
let query = TemporalQuery {
query_type: TemporalQueryType::ValidAt {
time: current_timestamp(),
},
entities: vec!["http://example.org/person1".to_string()],
constraints: Vec::new(),
time_window: None,
include_inferred: false,
};
let result = reasoner
.reason(&query)
.await
.expect("async operation should succeed");
assert!(!result.query_id.is_empty());
}
}