Skip to main content

qualia_core_db/modalities/
interval_reasoning.rs

1// Enhanced Interval Reasoning Engine
2// Advanced temporal algebra operations, interval constraint satisfaction, and temporal planning
3
4use crate::NQuin;
5use std::collections::HashMap;
6
7/// Temporal interval with start and end points
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct TemporalInterval {
10    pub id: u64,
11    pub start: i64, // Unix timestamp or relative time
12    pub end: i64,   // Unix timestamp or relative time
13    pub duration: i64,
14}
15
16impl TemporalInterval {
17    /// Create a new interval
18    pub fn new(id: u64, start: i64, end: i64) -> Self {
19        assert!(start <= end, "Interval start must be <= end");
20        let duration = end - start;
21        Self {
22            id,
23            start,
24            end,
25            duration,
26        }
27    }
28
29    /// Check if interval contains a point
30    pub fn contains(&self, point: i64) -> bool {
31        point >= self.start && point <= self.end
32    }
33
34    /// Check if interval overlaps with another
35    pub fn overlaps(&self, other: &TemporalInterval) -> bool {
36        self.start <= other.end && other.start <= self.end
37    }
38
39    /// Get intersection with another interval
40    pub fn intersection(&self, other: &TemporalInterval) -> Option<TemporalInterval> {
41        if !self.overlaps(other) {
42            return None;
43        }
44
45        let start = self.start.max(other.start);
46        let end = self.end.min(other.end);
47        Some(TemporalInterval::new(0, start, end))
48    }
49
50    /// Get union with another interval
51    pub fn union(&self, other: &TemporalInterval) -> TemporalInterval {
52        let start = self.start.min(other.start);
53        let end = self.end.max(other.end);
54        TemporalInterval::new(0, start, end)
55    }
56
57    /// Get gap between intervals
58    pub fn gap(&self, other: &TemporalInterval) -> Option<i64> {
59        if self.overlaps(other) {
60            return None;
61        }
62
63        if self.end < other.start {
64            Some(other.start - self.end)
65        } else {
66            Some(self.start - other.end)
67        }
68    }
69}
70
71/// Allen's Interval Algebra relations
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum AllenRelation {
74    Before,       // A before B
75    After,        // A after B
76    Meets,        // A meets B
77    MetBy,        // A met by B
78    Overlaps,     // A overlaps B
79    OverlappedBy, // A overlapped by B
80    Starts,       // A starts B
81    StartedBy,    // A started by B
82    During,       // A during B
83    Contains,     // A contains B
84    Ends,         // A ends B
85    EndedBy,      // A ended by B
86    Equal,        // A equal B
87}
88
89/// Interval constraint satisfaction problem
90pub struct IntervalCSP {
91    pub intervals: HashMap<u64, TemporalInterval>,
92    pub constraints: HashMap<(u64, u64), Vec<AllenRelation>>,
93    pub solution: Option<HashMap<u64, TemporalInterval>>,
94}
95
96impl IntervalCSP {
97    /// Create a new interval CSP
98    pub fn new() -> Self {
99        Self {
100            intervals: HashMap::new(),
101            constraints: HashMap::new(),
102            solution: None,
103        }
104    }
105
106    /// Add an interval to the CSP
107    pub fn add_interval(&mut self, interval: TemporalInterval) {
108        self.intervals.insert(interval.id, interval);
109    }
110
111    /// Add a constraint between two intervals
112    pub fn add_constraint(&mut self, id1: u64, id2: u64, relation: AllenRelation) {
113        self.constraints
114            .entry((id1, id2))
115            .or_insert_with(Vec::new)
116            .push(relation);
117    }
118
119    /// Check if two intervals satisfy a given relation
120    pub fn satisfies_relation(
121        &self,
122        interval1: &TemporalInterval,
123        interval2: &TemporalInterval,
124        relation: &AllenRelation,
125    ) -> bool {
126        match relation {
127            AllenRelation::Before => interval1.end < interval2.start,
128            AllenRelation::After => interval1.start > interval2.end,
129            AllenRelation::Meets => interval1.end == interval2.start,
130            AllenRelation::MetBy => interval1.start == interval2.end,
131            AllenRelation::Overlaps => {
132                interval1.start < interval2.start
133                    && interval2.start < interval1.end
134                    && interval1.end < interval2.end
135            }
136            AllenRelation::OverlappedBy => {
137                interval2.start < interval1.start
138                    && interval1.start < interval2.end
139                    && interval2.end < interval1.end
140            }
141            AllenRelation::Starts => {
142                interval1.start == interval2.start && interval1.end < interval2.end
143            }
144            AllenRelation::StartedBy => {
145                interval2.start == interval1.start && interval2.end < interval1.end
146            }
147            AllenRelation::During => {
148                interval1.start > interval2.start && interval1.end < interval2.end
149            }
150            AllenRelation::Contains => {
151                interval2.start > interval1.start && interval2.end < interval1.end
152            }
153            AllenRelation::Ends => {
154                interval1.start > interval2.start && interval1.end == interval2.end
155            }
156            AllenRelation::EndedBy => {
157                interval2.start > interval1.start && interval2.end == interval1.end
158            }
159            AllenRelation::Equal => {
160                interval1.start == interval2.start && interval1.end == interval2.end
161            }
162        }
163    }
164
165    /// Solve the CSP using simple backtracking
166    pub fn solve(&mut self) -> bool {
167        let mut solution = HashMap::new();
168
169        // Try to assign intervals that satisfy all constraints
170        for (id, interval) in &self.intervals {
171            solution.insert(*id, interval.clone());
172        }
173
174        // Check all constraints
175        for ((id1, id2), relations) in &self.constraints {
176            if let (Some(interval1), Some(interval2)) = (solution.get(id1), solution.get(id2)) {
177                let satisfied = relations
178                    .iter()
179                    .any(|relation| self.satisfies_relation(interval1, interval2, relation));
180
181                if !satisfied {
182                    return false;
183                }
184            }
185        }
186
187        self.solution = Some(solution);
188        true
189    }
190
191    /// Get the solution if it exists
192    pub fn get_solution(&self) -> Option<&HashMap<u64, TemporalInterval>> {
193        self.solution.as_ref()
194    }
195}
196
197/// Temporal planning system
198pub struct TemporalPlanner {
199    pub tasks: HashMap<u64, Task>,
200    pub schedule: Option<Vec<ScheduledTask>>,
201}
202
203#[derive(Debug, Clone)]
204pub struct Task {
205    pub id: u64,
206    pub name: String,
207    pub duration: i64,
208    pub dependencies: Vec<u64>,
209    pub constraints: Vec<TaskConstraint>,
210}
211
212#[derive(Debug, Clone)]
213pub enum TaskConstraint {
214    MustStartAfter(i64),
215    MustEndBefore(i64),
216    MustStartBefore(i64),
217    MustEndAfter(i64),
218    FixedStart(i64),
219    FixedEnd(i64),
220}
221
222#[derive(Debug, Clone)]
223pub struct ScheduledTask {
224    pub task: Task,
225    pub interval: TemporalInterval,
226}
227
228impl TemporalPlanner {
229    /// Create a new temporal planner
230    pub fn new() -> Self {
231        Self {
232            tasks: HashMap::new(),
233            schedule: None,
234        }
235    }
236
237    /// Add a task to the planner
238    pub fn add_task(&mut self, task: Task) {
239        self.tasks.insert(task.id, task);
240    }
241
242    /// Generate a schedule using simple greedy algorithm
243    pub fn generate_schedule(&mut self) -> Result<(), String> {
244        let mut scheduled: Vec<ScheduledTask> = Vec::new();
245        let mut task_queue: Vec<&Task> = self.tasks.values().collect();
246
247        // Sort tasks by dependencies (simple topological sort)
248        task_queue.sort_by(|a, b| a.dependencies.len().cmp(&b.dependencies.len()));
249
250        let mut current_time = 0i64;
251
252        for task in task_queue {
253            // Check if all dependencies are satisfied
254            let mut dependencies_satisfied = true;
255            let mut earliest_start = current_time;
256
257            for &dep_id in &task.dependencies {
258                if let Some(scheduled_dep) = scheduled.iter().find(|st| st.task.id == dep_id) {
259                    earliest_start = earliest_start.max(scheduled_dep.interval.end);
260                } else {
261                    dependencies_satisfied = false;
262                    break;
263                }
264            }
265
266            if !dependencies_satisfied {
267                return Err(format!("Task {} has unsatisfied dependencies", task.id));
268            }
269
270            // Apply constraints
271            let mut start_time = earliest_start;
272            let end_time = start_time + task.duration;
273
274            for constraint in &task.constraints {
275                match constraint {
276                    TaskConstraint::MustStartAfter(time) => start_time = start_time.max(*time),
277                    TaskConstraint::MustEndBefore(time) => {
278                        if end_time > *time {
279                            return Err(format!("Task {} must end before {}", task.id, time));
280                        }
281                    }
282                    TaskConstraint::MustStartBefore(time) => {
283                        if start_time > *time {
284                            return Err(format!("Task {} must start before {}", task.id, time));
285                        }
286                    }
287                    TaskConstraint::MustEndAfter(time) => {
288                        if end_time < *time {
289                            start_time = *time - task.duration;
290                        }
291                    }
292                    TaskConstraint::FixedStart(time) => start_time = *time,
293                    TaskConstraint::FixedEnd(time) => start_time = *time - task.duration,
294                }
295            }
296
297            // Create interval and add to schedule
298            let interval = TemporalInterval::new(task.id, start_time, start_time + task.duration);
299            scheduled.push(ScheduledTask {
300                task: task.clone(),
301                interval,
302            });
303
304            current_time = start_time + task.duration;
305        }
306
307        self.schedule = Some(scheduled);
308        Ok(())
309    }
310
311    /// Get the generated schedule
312    pub fn get_schedule(&self) -> Option<&Vec<ScheduledTask>> {
313        self.schedule.as_ref()
314    }
315
316    /// Check if schedule is valid
317    pub fn validate_schedule(&self) -> bool {
318        if let Some(schedule) = &self.schedule {
319            // Check for overlaps in tasks that shouldn't overlap
320            for (i, task1) in schedule.iter().enumerate() {
321                for task2 in schedule.iter().skip(i + 1) {
322                    // Tasks with dependencies can overlap if designed to do so
323                    if task1.interval.overlaps(&task2.interval) {
324                        // Check if this is allowed (simplified validation)
325                        if !task1.task.dependencies.contains(&task2.task.id)
326                            && !task2.task.dependencies.contains(&task1.task.id)
327                        {
328                            return false;
329                        }
330                    }
331                }
332            }
333            true
334        } else {
335            false
336        }
337    }
338}
339
340/// Advanced temporal algebra operations
341pub struct TemporalAlgebra;
342
343impl TemporalAlgebra {
344    /// Compute transitive closure of temporal relations
345    pub fn transitive_closure(
346        intervals: &HashMap<u64, TemporalInterval>,
347    ) -> HashMap<(u64, u64), AllenRelation> {
348        let mut relations = HashMap::new();
349
350        for (id1, interval1) in intervals {
351            for (id2, interval2) in intervals {
352                if id1 != id2 {
353                    let relation = Self::determine_relation(interval1, interval2);
354                    relations.insert((*id1, *id2), relation);
355                }
356            }
357        }
358
359        relations
360    }
361
362    /// Determine the Allen relation between two intervals
363    pub fn determine_relation(
364        interval1: &TemporalInterval,
365        interval2: &TemporalInterval,
366    ) -> AllenRelation {
367        if interval1.end < interval2.start {
368            AllenRelation::Before
369        } else if interval1.start > interval2.end {
370            AllenRelation::After
371        } else if interval1.end == interval2.start {
372            AllenRelation::Meets
373        } else if interval1.start == interval2.end {
374            AllenRelation::MetBy
375        } else if interval1.start < interval2.start
376            && interval2.start < interval1.end
377            && interval1.end < interval2.end
378        {
379            AllenRelation::Overlaps
380        } else if interval2.start < interval1.start
381            && interval1.start < interval2.end
382            && interval2.end < interval1.end
383        {
384            AllenRelation::OverlappedBy
385        } else if interval1.start == interval2.start && interval1.end < interval2.end {
386            AllenRelation::Starts
387        } else if interval2.start == interval1.start && interval2.end < interval1.end {
388            AllenRelation::StartedBy
389        } else if interval1.start > interval2.start && interval1.end < interval2.end {
390            AllenRelation::During
391        } else if interval2.start > interval1.start && interval2.end < interval1.end {
392            AllenRelation::Contains
393        } else if interval1.start > interval2.start && interval1.end == interval2.end {
394            AllenRelation::Ends
395        } else if interval2.start > interval1.start && interval2.end == interval1.end {
396            AllenRelation::EndedBy
397        } else {
398            AllenRelation::Equal
399        }
400    }
401
402    /// Compose temporal relations
403    pub fn compose_relations(rel1: AllenRelation, rel2: AllenRelation) -> Vec<AllenRelation> {
404        // Simplified composition table
405        match (rel1, rel2) {
406            (AllenRelation::Before, AllenRelation::Before) => vec![AllenRelation::Before],
407            (AllenRelation::Before, AllenRelation::Meets) => vec![AllenRelation::Before],
408            (AllenRelation::Meets, AllenRelation::Before) => vec![AllenRelation::Before],
409            (AllenRelation::During, AllenRelation::During) => vec![AllenRelation::During],
410            (AllenRelation::Starts, AllenRelation::Starts) => vec![AllenRelation::Starts],
411            _ => vec![rel1], // Default case
412        }
413    }
414}
415
416/// Convert interval reasoning results to NQuin
417pub fn interval_to_quin(interval: &TemporalInterval, context: u64) -> NQuin {
418    let mut quin = NQuin {
419        subject: interval.id,
420        predicate: crate::q_hash("has_temporal_interval"),
421        object: ((interval.start as u64) << 32) | (interval.duration as u64 & 0xFFFFFFFF),
422        context,
423        metadata: interval.end as u64,
424        parity: 0,
425    };
426
427    quin.parity = quin.subject ^ quin.predicate ^ quin.object ^ quin.context;
428    quin
429}
430
431/// Convert schedule to NQuin collection
432pub fn schedule_to_quins(schedule: &[ScheduledTask], context: u64) -> Vec<NQuin> {
433    let mut quins = Vec::new();
434
435    for scheduled_task in schedule {
436        let interval_quin = interval_to_quin(&scheduled_task.interval, context);
437        quins.push(interval_quin);
438
439        // Store task metadata
440        let task_quin = NQuin {
441            subject: scheduled_task.task.id,
442            predicate: crate::q_hash("has_task_metadata"),
443            object: scheduled_task.task.duration as u64,
444            context,
445            metadata: crate::q_hash(&scheduled_task.task.name),
446            parity: 0,
447        };
448        quins.push(task_quin);
449    }
450
451    quins
452}
453
454#[cfg(test)]
455mod tests {
456    use super::*;
457
458    #[test]
459    fn test_temporal_interval_creation() {
460        let interval = TemporalInterval::new(1, 100, 200);
461        assert_eq!(interval.start, 100);
462        assert_eq!(interval.end, 200);
463        assert_eq!(interval.duration, 100);
464    }
465
466    #[test]
467    fn test_interval_operations() {
468        let interval1 = TemporalInterval::new(1, 100, 200);
469        let interval2 = TemporalInterval::new(2, 150, 250);
470
471        assert!(interval1.overlaps(&interval2));
472
473        let intersection = interval1.intersection(&interval2).unwrap();
474        assert_eq!(intersection.start, 150);
475        assert_eq!(intersection.end, 200);
476
477        let union = interval1.union(&interval2);
478        assert_eq!(union.start, 100);
479        assert_eq!(union.end, 250);
480    }
481
482    #[test]
483    fn test_allen_relations() {
484        let interval1 = TemporalInterval::new(1, 100, 200);
485        let interval2 = TemporalInterval::new(2, 50, 150);
486        let interval3 = TemporalInterval::new(3, 200, 300);
487
488        assert_eq!(
489            TemporalAlgebra::determine_relation(&interval1, &interval2),
490            AllenRelation::OverlappedBy
491        );
492        assert_eq!(
493            TemporalAlgebra::determine_relation(&interval1, &interval3),
494            AllenRelation::Meets
495        );
496    }
497
498    #[test]
499    fn test_interval_csp() {
500        let mut csp = IntervalCSP::new();
501
502        let interval1 = TemporalInterval::new(1, 100, 200);
503        let interval2 = TemporalInterval::new(2, 200, 300);
504
505        csp.add_interval(interval1);
506        csp.add_interval(interval2);
507        csp.add_constraint(1, 2, AllenRelation::Meets);
508
509        assert!(csp.solve());
510        assert!(csp.get_solution().is_some());
511    }
512
513    #[test]
514    fn test_temporal_planner() {
515        let mut planner = TemporalPlanner::new();
516
517        let task1 = Task {
518            id: 1,
519            name: "Task 1".to_string(),
520            duration: 100,
521            dependencies: vec![],
522            constraints: vec![TaskConstraint::FixedStart(100)],
523        };
524
525        let task2 = Task {
526            id: 2,
527            name: "Task 2".to_string(),
528            duration: 50,
529            dependencies: vec![1],
530            constraints: vec![],
531        };
532
533        planner.add_task(task1);
534        planner.add_task(task2);
535
536        assert!(planner.generate_schedule().is_ok());
537        assert!(planner.validate_schedule());
538    }
539
540    #[test]
541    fn test_interval_to_quin() {
542        let interval = TemporalInterval::new(42, 1000, 1500);
543        let quin = interval_to_quin(&interval, 123);
544
545        assert_eq!(quin.subject, 42);
546        assert_eq!(quin.context, 123);
547        assert_eq!(quin.metadata, 1500); // end time stored in metadata
548    }
549}