Tempo.Network.Solver (Tempo v0.15.0)

Copy Markdown View Source

Consistency checking and bound tightening for a chronological network, by solving its Simple Temporal Problem.

The network normalises (Tempo.Network.Normalize) to a directed weighted graph — one node per boundary plus the origin z₀ — whose all-pairs shortest paths are the minimal network: the tightest bound on b₁ − b₂ is the shortest-path weight b₁ → b₂ (Dechter, Meiri & Pearl 1991; the paper's Floyd 1962). The network is consistent iff the graph has no negative cycle.

  • consistent?/1 — does any valid assignment of dates exist?

  • tighten/1 — the narrowest start, end, and duration each period can take given every constraint together.

Both run in O(n³) on the boundary count (Floyd–Warshall), which is interactive for the hundreds of periods these chronologies contain.

Summary

Functions

Whether two periods are certainly contemporary — true only when every valid chronology has them overlapping. See contemporaneity/3.

Is the network consistent — does it admit at least one valid assignment of dates?

Classify whether two periods overlap in time, given the whole network.

Whether two periods are possibly contemporary — true when at least one valid chronology has them overlapping. See contemporaneity/3.

Tighten every period's start, end, and duration to the narrowest bounds the network implies.

Explain a tightened bound as a trace — the chain of constraints that forces it.

Functions

certainly_contemporary?(network, p1, p2)

@spec certainly_contemporary?(Tempo.Network.t(), term(), term()) :: boolean()

Whether two periods are certainly contemporary — true only when every valid chronology has them overlapping. See contemporaneity/3.

consistent?(network)

@spec consistent?(Tempo.Network.t()) :: boolean()

Is the network consistent — does it admit at least one valid assignment of dates?

Arguments

Returns

  • true when consistent, false when the constraints contradict (the graph has a negative cycle).

Examples

iex> Tempo.Network.new()
...> |> Tempo.Network.add_period(:k, start: ~o"1200Y", end: ~o"1180Y")
...> |> Tempo.Network.Solver.consistent?()
false

contemporaneity(network, p1, p2)

@spec contemporaneity(Tempo.Network.t(), term(), term()) ::
  :certain | :possible | :impossible | {:error, :inconsistent}

Classify whether two periods overlap in time, given the whole network.

Returns :certain when the constraints force the periods to be contemporary, :possible when they merely allow it, and :impossible when they forbid it. The verdict is read from the minimal (tightened) network, so it accounts for every constraint — not just the two periods' own bounds — in the spirit of Geeraerts, Levy & Pluquet, Models and Algorithms for Chronology (TIME 2017), Props. 7 and 10.

Two periods that merely touch (one ends exactly where the other begins) count as contemporary, matching add_relation(:contemporary, …) — the endpoints are treated as closed here.

Arguments

Returns

  • :certain when every valid chronology has the periods overlapping;

  • :possible when some but not all valid chronologies do;

  • :impossible when none do; or

  • {:error, :inconsistent} when the network has no valid chronology.

Examples

iex> network =
...>   Tempo.Network.new()
...>   |> Tempo.Network.add_period(:k1, start: {:not_before, 1200}, duration: {:at_most, 15})
...>   |> Tempo.Network.add_period(:k2, end: {:not_after, 1300}, duration: {30, 100})
...>   |> Tempo.Network.add_period(:s1, duration: {20, 100})
...>   |> Tempo.Network.add_period(:s2, duration: {20, 100})
...>   |> Tempo.Network.add_sequence([:k1, :k2])
...>   |> Tempo.Network.add_sequence([:s1, :s2])
...>   |> Tempo.Network.add_relation(:starts_during, :s1, :k1)
...>   |> Tempo.Network.add_relation(:ends_during, :s2, :k2)
iex> Tempo.Network.Solver.contemporaneity(network, :k1, :s2)
:impossible

possibly_contemporary?(network, p1, p2)

@spec possibly_contemporary?(Tempo.Network.t(), term(), term()) :: boolean()

Whether two periods are possibly contemporary — true when at least one valid chronology has them overlapping. See contemporaneity/3.

tighten(network)

@spec tighten(Tempo.Network.t()) :: {:ok, Tempo.Network.t()} | {:error, :inconsistent}

Tighten every period's start, end, and duration to the narrowest bounds the network implies.

Arguments

Returns

  • {:ok, network} with each period's bounds replaced by the computed tightest bounds (a bound that the constraints leave unbounded becomes nil); or

  • {:error, :inconsistent} when no valid assignment exists.

Examples

iex> {:ok, tightened} =
...>   Tempo.Network.new()
...>   |> Tempo.Network.add_period(:k1, start: ~o"1200Y", duration: {:at_least, ~o"P20Y"})
...>   |> Tempo.Network.add_period(:k2, duration: {:at_least, ~o"P35Y"})
...>   |> Tempo.Network.add_sequence([:k1, :k2])
...>   |> Tempo.Network.Solver.tighten()
iex> tightened.periods[:k2].earliest_end
~o"1255Y"

trace(network, boundary, options \\ [])

@spec trace(Tempo.Network.t(), {:start | :end, term()}, keyword()) ::
  {:ok, map()} | {:error, :unbounded | :inconsistent}

Explain a tightened bound as a trace — the chain of constraints that forces it.

Reconstructs the shortest path in the constraint graph that produces the :earliest or :latest value of a boundary, mirroring the paper's Fig. 6c. Each step names the constraint responsible and the bound derived so far, and :prose renders the whole chain as a sentence.

Arguments

Options

  • :bound is :earliest (the default) or :latest.

Returns

  • {:ok, %{value: t:Tempo.t/0, steps: list, prose: String.t()}};

  • {:error, :unbounded} when the constraints leave the bound open; or

  • {:error, :inconsistent} when the network has no valid assignment.

Examples

iex> {:ok, trace} =
...>   Tempo.Network.new()
...>   |> Tempo.Network.add_period(:k1, start: {:not_before, ~o"1200Y"}, duration: {:at_least, ~o"P20Y"})
...>   |> Tempo.Network.add_period(:k2, duration: {:at_least, ~o"P35Y"})
...>   |> Tempo.Network.add_sequence([:k1, :k2])
...>   |> Tempo.Network.Solver.trace({:end, :k2})
iex> trace.value
~o"1255Y"