How Semantica Handles Temporal Reasoning: Deterministic Allen Interval Algebra Implementation

Semantica implements temporal reasoning as a deterministic, pure-Python engine based on Allen's interval algebra, using immutable TemporalInterval objects and the TemporalReasoningEngine class to perform relation detection, merging, gap analysis, and retroactive impact classification without any LLM dependencies.

The semantica-agi/semantica repository provides a robust knowledge graph infrastructure where temporal reasoning operates through mathematical interval algebra rather than probabilistic inference. All temporal operations are confined to the TemporalReasoningEngine class, which guarantees reproducible behavior across downstream pipelines by eliminating non-deterministic components.

Core Architecture and Design Philosophy

Semantica's temporal reasoning engine is architected as a stateless, deterministic processor located in semantica/kg/temporal_reasoning.py and publicly re-exported through semantica/reasoning/temporal_reasoning.py. The engine operates exclusively on immutable TemporalInterval objects and the IntervalRelation enum, ensuring thread-safe operations and predictable results.

The implementation follows Allen's interval algebra, a mathematical framework that defines all possible temporal relationships between two intervals (BEFORE, OVERLAPS, CONTAINS, etc.). Because the engine contains no LLM calls, it produces fully deterministic outputs suitable for audit trails and reproducible knowledge graph construction.

Data Structures and Allen's Interval Relations

The foundation of Semantica's temporal capabilities rests on three core data structures defined across semantica/kg/temporal_model.py and the reasoning modules:

  • TemporalInterval: Immutable objects representing time spans with UTC-aware datetime start and end points.
  • IntervalRelation: An enum encoding all thirteen possible Allen relations (BEFORE, MEETS, OVERLAPS, FINISHES, CONTAINS, etc.).
  • TemporalBound.OPEN: A sentinel value representing open-ended intervals that extend indefinitely into the future or past.

The engine validates all intervals through the _validate_interval method, which guarantees that start ≤ end for any bounded interval. For granularity-based operations, the _expand_end method adjusts interval endpoints to the last instant of the chosen temporal granularity (second through year).

Temporal Operations and Capabilities

The TemporalReasoningEngine exposes eight primary capability categories, each implemented as specific methods with deterministic algorithms.

Relation Detection and Boolean Checks

The relation(a, b) method computes the precise Allen relationship between two intervals, returning an IntervalRelation enum value. For convenience, the engine provides optimized boolean accessors:

  • overlaps(a, b): Returns True if intervals share any common instant.
  • contains(outer, inner): Returns True if the outer interval fully encompasses the inner interval.

Active-At Queries with Granularity Support

The active_at(interval, timestamp, granularity=None) method determines whether a specific timestamp falls within an interval. When provided, the granularity parameter (accepting values from "second" to "year") normalizes both the interval and timestamp before comparison, enabling fuzzy temporal membership checks.

Merging, Gap Analysis, and Coverage Metrics

For batch interval processing, the engine provides:

  • merge_intervals(intervals): Normalizes a collection of potentially overlapping intervals into a minimal set of non-overlapping, chronologically ordered intervals.
  • gap_analysis(intervals, domain_start, domain_end): Identifies temporal gaps within a specified domain that remain uncovered by the provided intervals.
  • coverage_percentage(intervals, domain_start, domain_end): Calculates the exact proportion of a temporal domain covered by the interval set, returning a float between 0.0 and 1.0.

Timeline Construction and Retroactive Impact

For knowledge graph applications:

  • timeline_of(entity_id, graph): Extracts a chronologically ordered sequence of fact-level events (add, modify, remove) for a specific entity or relationship from the graph structure.
  • retroactive_coverage: Classifies existing bi-temporal facts within the graph as affected, partial, or unaffected by a proposed revision, enabling impact analysis before applying changes.

Normalization Utilities

The engine ensures temporal consistency through normalize_timestamp and normalize_interval methods, which align arbitrary datetime objects to specified granularities. All normalization operations preserve UTC awareness and handle the TemporalBound.OPEN sentinel appropriately.

Working with Temporal Intervals: Code Examples

The following example demonstrates relation detection, active-at queries, interval merging, and coverage calculation using the public API:

from datetime import datetime, timezone
from semantica.reasoning.temporal_reasoning import (
    TemporalInterval,
    TemporalReasoningEngine,
    IntervalRelation,
    TemporalBound,
)

engine = TemporalReasoningEngine()

# Define two intervals

a = TemporalInterval(
    start=datetime(2023, 1, 1, tzinfo=timezone.utc),
    end=datetime(2023, 1, 10, tzinfo=timezone.utc),
)
b = TemporalInterval(
    start=datetime(2023, 1, 5, tzinfo=timezone.utc),
    end=TemporalBound.OPEN,               # Open-ended interval

)

# Relation between a and b

rel = engine.relation(a, b)
print(rel)                     # → IntervalRelation.OVERLAPS

# Check if a timestamp is active within an interval (day granularity)

ts = "2023-01-05T12:34:00Z"
print(engine.active_at(a, ts, granularity="day"))  # → True

# Merge overlapping intervals

intervals = [a, b, TemporalInterval(
    start=datetime(2023, 1, 12, tzinfo=timezone.utc),
    end=datetime(2023, 1, 20, tzinfo=timezone.utc),
)]
merged = engine.merge_intervals(intervals)
print(merged)  # → two merged intervals (one open-ended)

# Compute coverage of a domain

domain_start = "2023-01-01"
domain_end = "2023-01-31"
coverage = engine.coverage_percentage(merged, domain_start, domain_end)
print(f"Coverage: {coverage:.2%}")  # → e.g., 71.4%

Implementation Details and File Structure

The temporal reasoning system spans four key locations in the Semantica codebase:

All datetime handling strictly enforces UTC awareness to prevent timezone ambiguity, while the TemporalBound.OPEN sentinel enables representation of unbounded intervals without resorting to arbitrary maximum dates.

Summary

  • Semantica implements deterministic temporal reasoning through Allen's interval algebra, ensuring reproducible results without LLM dependencies.
  • The TemporalReasoningEngine class in semantica/kg/temporal_reasoning.py provides immutable, UTC-aware interval operations.
  • Core capabilities include relation detection, active-at queries, interval merging, gap analysis, and retroactive impact classification.
  • Open-ended intervals are handled via the TemporalBound.OPEN sentinel rather than arbitrary date limits.
  • All temporal validation and granularity expansion occur through internal methods _validate_interval and _expand_end, guaranteeing mathematical consistency.

Frequently Asked Questions

How does Semantica ensure deterministic temporal reasoning?

Semantica eliminates non-determinism by implementing temporal reasoning as pure-Python mathematical operations without any LLM calls or stochastic components. The TemporalReasoningEngine processes immutable TemporalInterval objects using Allen's interval algebra, producing identical outputs for identical inputs across all executions, as verified in tests/test_395_temporal_semantics_comprehensive.py.

What file contains the core temporal reasoning implementation?

The primary implementation resides in semantica/kg/temporal_reasoning.py, which defines the TemporalReasoningEngine class and all interval algebra logic. Consumers typically import from semantica/reasoning/temporal_reasoning.py, which serves as the public façade re-exporting these components alongside definitions from semantica/kg/temporal_model.py.

How does Semantica handle open-ended or infinite time intervals?

Semantica represents unbounded intervals using the TemporalBound.OPEN sentinel value rather than arbitrary maximum datetime values. This sentinel can be assigned to either the start or end of a TemporalInterval, allowing the engine to properly handle queries against intervals that extend indefinitely into the past or future through the relation and active_at methods.

Can Semantica normalize timestamps to different granularities?

Yes, the engine supports temporal normalization from second-level precision up to year-level granularity through the normalize_timestamp and normalize_interval methods. When using active_at with a granularity parameter (e.g., "day" or "month"), the engine internally calls _expand_end to adjust interval endpoints to the last instant of the specified granularity before performing comparisons.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →