Temporal Inference Engine

Added in version 1.5.0.

The s3dgraphy.temporal package implements Layer B of DP-32 — the TPQ / TAQ closure that converts topological stratigraphic relations (cuts, overlies, fills) into temporal direction (is_after / is_before) and computes the earliest / latest plausible date of every stratigraphic unit by BFS over the resulting DAG.

Two cooperating components

s3dgraphy.temporal.inference_engine.TemporalInferenceEngine

Topology-to-temporal mapping plus transitive reduction (NetworkX). Given a graph with cuts / overlies / fills edges, the engine returns the minimal set of is_after edges that captures the same temporal order, dropping the redundant ones.

Graph.calculate_chronology()

The high-level entry point used by chronology consumers. Internally:

  1. Seeds node-level TPQ/TAQ from PropertyNodes named absolute_time_start / absolute_time_end via the resolver registered in Property Propagation.

  2. Seeds swimlane-level TPQ/TAQ from EpochNode start_time / end_time (header) or from a PropertyNode attached to the epoch (SL_PD override — the resolver prefers this over the header).

  3. Runs BFS over the inferred temporal DAG, propagating CALCUL_START_T (max of upstream TPQs) and CALCUL_END_T (min of downstream TAQs) until fixpoint.

  4. Surfaces [chronology paradox] warnings with attribution (see Diagnostics API) when propagation forces a node’s window to be empty or inverted.

Topological-to-temporal mapping

The current canonical table lives in TemporalInferenceEngine.TOPOLOGICAL_TO_TEMPORAL:

Edge type

Temporal direction

cuts

source is more recent

is_cut_by

target is more recent

overlies

source is more recent

is_overlain_by

target is more recent

fills

source is more recent

is_filled_by

target is more recent

abuts / is_abutted_by

ambiguous (no temporal direction)

is_bonded_to / bonded_to

symmetric (no temporal direction)

is_physically_equal_to /

symmetric (no temporal direction)

equals

Edges that are already is_after (or is_before) are absorbed into the DAG without re-derivation. Symmetric and ambiguous relations are intentionally dropped — they encode physical contact, not sequence.

attribute_temporal_claim

The temporal layer relies on attribute_temporal_claim() (in s3dgraphy.diagnostics) to answer the question “who asserted this temporal seed?” whenever a paradox is detected. Resolution order:

  1. Direct has_author on the temporal PropertyNode.

  2. A sibling ExtractorNode in the containing ParadataNodeGroup and its has_author.

  3. The PropertyNode’s host swimlane (epoch).

The returned tuple (display, kind, author_uuid) is appended to the [chronology paradox] warning as [attributed to <name> (<kind>)] so the user can tell whether to audit the original document author (kind = "author") or the AI extractor that produced the bad inference (kind = "extractor").

Stratigraphic cycle detection

Graph.calculate_chronology() runs s3dgraphy.diagnostics.detect_stratigraphic_cycles() before the TPQ/TAQ propagation pass. The detector runs Tarjan SCC over is_after / cuts / overlies / fills / is_before edges and appends one [stratigraphic cycle] warning per loop, with per-node attribution. The propagation BFS already survived cycles via a visited set, but the user must be notified — AI extractors occasionally close these loops.

Worked example — temporal closure

US005  --overlies-->  US003           # construction order
US005  --has_property-->  PN("absolute_time_start" = -80)
US001  --has_property-->  PN("absolute_time_end"   = -120)
US005  --cuts-->     US001             # foundation trench cuts US001
US001  --has_first_epoch-->  EP_HELLENISTIC (end_time = -100)

For graph.calculate_chronology():

  1. Engine maps overlies → US005 more recent than US003; cuts → US005 more recent than US001.

  2. Seeds: US005 TPQ = −80; US001 TAQ = −120 (node-level); US001 TAQ also constrained by swimlane to −100.

  3. BFS: US005 inherits CALCUL_END_T no later than −80 (own TPQ becomes the floor); US003 inherits no later than US005’s end; US001 inherits no earlier than US005’s start (TPQ propagates backward via is_after).

  4. Paradox check: US001 has TPQ ≤ −80 (from cuts ▸ US005) but TAQ = −120 — the unit’s window is empty. A [chronology paradox] warning is emitted with attribution.

Why a Layer B at all?

Topological edges are what archaeologists see in the trench; temporal direction is what we infer about time. Keeping them as separate edge types in s3dgraphy (cuts vs is_after) lets the graph faithfully record both layers — the engine derives one from the other on demand, with transitive reduction so the DAG stays readable.

API Reference

Temporal Inference Engine for s3dgraphy

Converts topological stratigraphic relations (cuts, overlies, fills) into temporal relations (is_after, is_before) and performs transitive reduction to obtain the minimal set of temporal edges.

class s3dgraphy.temporal.inference_engine.TemporalInferenceEngine(connections_datamodel=None)[source]

Bases: object

Infers temporal relationships from topological stratigraphic relations.

The engine: 1. Maps topological relations to temporal directions 2. Builds a directed acyclic graph (DAG) of temporal precedence 3. Performs transitive reduction to eliminate redundant edges 4. Detects cycles (data inconsistencies)

__init__(connections_datamodel=None)[source]

Initialize the temporal inference engine.

Parameters:

connections_datamodel – Optional connections datamodel dict. If None, loads from default location.

extract_temporal_from_graph(graph) → List[Tuple[str, str]][source]

Extract temporal relations from a graph’s topological edges.

Parameters:

graph – s3dgraphy Graph object

Returns:

List of (source, target) temporal edges

where source is more recent than target

Return type:

List[Tuple[str, str]]

transitive_reduction(temporal_edges: List[Tuple[str, str]]) → List[Tuple[str, str]][source]

Perform transitive reduction on temporal edges.

Removes redundant edges from the temporal DAG. For example: - If A→B and B→C and A→C, then A→C is redundant (transitive) - Output: A→B, B→C

Parameters:

temporal_edges – List of (source, target) tuples where source is more recent than target

Returns:

Minimal set of temporal edges

Return type:

List[Tuple[str, str]]

Raises:

ValueError – If the graph contains cycles (data inconsistency)

get_temporal_order(temporal_edges: List[Tuple[str, str]]) → List[str][source]

Get topological sort of nodes by temporal order.

Parameters:

temporal_edges – List of (source, target) temporal edges

Returns:

Node IDs sorted from most recent to most ancient

Return type:

List[str]

validate_consistency(temporal_edges: List[Tuple[str, str]]) → Tuple[bool, List[List[str]]][source]

Validate temporal consistency (check for cycles).

Parameters:

temporal_edges – List of (source, target) temporal edges

Returns:

(is_valid, cycles)
  • is_valid: True if no cycles, False otherwise

  • cycles: List of cycle paths (empty if valid)

Return type:

Tuple[bool, List[List[str]]]

get_ambiguous_relations(graph) → List[Edge][source]

Get edges with ambiguous temporal direction.

Parameters:

graph – s3dgraphy Graph object

Returns:

Edges that don’t have clear temporal direction

Return type:

List[Edge]

print_inference_report(temporal_edges: List[Tuple[str, str]], minimal_edges: List[Tuple[str, str]])[source]

Print a report of temporal inference results.

Parameters:
  • temporal_edges – Original temporal edges (before reduction)

  • minimal_edges – Minimal edges (after reduction)