Skip to content

Commit 29080c7

Browse files
feat(stdlib): Allen's interval algebra (D11 from DB-theory inventory) (#272)
## Summary Adds `stdlib/Allen.eph`: Allen's interval algebra (Allen 1983) — the canonical qualitative-temporal-reasoning framework, 13 mutually-exclusive base relations on intervals. The DB-theory inventory (this session's audit) ranked this as the cleanest of the 5 "ship-this-month quick wins": fully self-contained, zero prereqs, zero L1/L2/L3/L4 dependency. ## What lands - `type Relation` — 13 variants (Before/After, Meets/MetBy, Overlaps/OverlappedBy, Starts/StartedBy, During/Contains, Finishes/FinishedBy, Equals). - `type Interval = Interval(i64, i64)` — half-open `[start, end)`. - `make / start / end / duration / relate / inverse / is_disjoint / has_proper_overlap`. - `relate(&Interval, &Interval) -> Relation` — total O(1) 13-way classifier following Allen 1983 §2. ## Pedigree - Allen, J. F. "Maintaining knowledge about temporal intervals." CACM 26(11): 832-843. November 1983. - Snodgrass (1995) "The TSQL2 Temporal Query Language" applied this to relational databases → SQL:2011 valid-time / transaction-time period support. ## Future work (separate PRs) 1. Composition table (Allen 1983 §3 — 13×13 = 169-entry table); needs `stdlib/Set.eph`. 2. Coq mechanisation `formal/Allen.v`: `inverse(inverse(r)) = r` + orientation lemma (`relate(a,b) = inverse(relate(b,a))`). 3. Bitemporal extension (D12 — valid-time × transaction-time, Snodgrass 1995 §4). ## Test plan - [ ] Confirm typechecker accepts Allen.eph (the stdlib `.eph` files are not currently in any automated test harness — flagged separately in this session's TEST-NEEDS audit). - [ ] Future PR: add `formal/Allen.v` proving the two foundational laws. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 27f7b1f commit 29080c7

1 file changed

Lines changed: 217 additions & 0 deletions

File tree

stdlib/Allen.eph

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
3+
//
4+
// Ephapax stdlib — Allen's interval algebra (1983).
5+
//
6+
// Citation:
7+
// Allen, J. F. "Maintaining knowledge about temporal intervals."
8+
// Communications of the ACM 26 (11): 832-843. November 1983.
9+
//
10+
// Allen's interval algebra is the canonical qualitative-temporal-
11+
// reasoning framework. Given two intervals A and B, there are exactly
12+
// 13 mutually-exclusive base relations describing their possible
13+
// configurations (7 plus 6 inverses, with `equals` being self-
14+
// inverse).
15+
//
16+
// Pedigree:
17+
// Snodgrass (1995) "The TSQL2 Temporal Query Language" applied this
18+
// algebra to relational databases, motivating the SQL:2011 valid-
19+
// time / transaction-time period support.
20+
//
21+
// Fit in ephapax:
22+
// This is a fully self-contained foundation primitive — no L1/L2/
23+
// L3/L4 dependency, no proof obligations attached at landing time.
24+
// Composition table proofs can follow as a separate Coq file in
25+
// `formal/Allen.v` if/when bitemporal types (D12) land.
26+
//
27+
// Companion stdlib modules to expect later:
28+
// - `stdlib/Bitemporal.eph` (D12 — valid-time × transaction-time)
29+
// - `stdlib/Provenance.eph` (D09 — why/where/how provenance)
30+
31+
module Allen
32+
33+
// --- The 13 base relations ---
34+
//
35+
// Every pair of intervals (A, B) on a totally-ordered time domain
36+
// stands in exactly one of these relations. Mutual exclusion +
37+
// joint exhaustiveness is the canonical Allen result.
38+
//
39+
// Pictorial cheat-sheet (A = top line, B = bottom line):
40+
//
41+
// Before(A, B) A: === B: ===
42+
// Meets(A, B) A: === B: === (A.end == B.start)
43+
// Overlaps(A, B) A: ===== B: ===== (A.end strictly inside B)
44+
// Starts(A, B) A: == B: ===== (A.start == B.start, A.end < B.end)
45+
// During(A, B) A: == B: ===== (strictly inside)
46+
// Finishes(A, B) A: == B: ===== (A.end == B.end, A.start > B.start)
47+
// Equals(A, B) A: ===== B: ===== (identical)
48+
// (and their 6 inverses)
49+
//
50+
type Relation =
51+
| Before
52+
| After
53+
| Meets
54+
| MetBy
55+
| Overlaps
56+
| OverlappedBy
57+
| Starts
58+
| StartedBy
59+
| During
60+
| Contains
61+
| Finishes
62+
| FinishedBy
63+
| Equals
64+
65+
// --- The interval type ---
66+
//
67+
// An interval is a half-open `[start, end)` pair on i64. We use i64
68+
// (not i32) to give 64-bit time stamps room — enough for
69+
// nanosecond-resolution UNIX time well past year 2200.
70+
//
71+
// Affinely consumable: an `Interval` carries no resource obligation
72+
// at L1 (no region capability) and is dropped freely at L2 (Affine
73+
// or Linear).
74+
//
75+
// Invariant: callers should maintain `start <= end`. The smart
76+
// constructor `make` enforces it.
77+
type Interval = Interval(i64, i64)
78+
79+
// --- Smart constructor ---
80+
//
81+
// Constructs an interval, returning `Err` if `start > end`. The
82+
// degenerate point-interval `start == end` is permitted; the
83+
// half-open convention means it represents the empty interval.
84+
fn make(start: i64, end: i64) -> Result<Interval, String> {
85+
if start > end then
86+
Err("Allen.make: start > end")
87+
else
88+
Ok(Interval(start, end))
89+
}
90+
91+
// --- Accessors ---
92+
93+
fn start(i: &Interval) -> i64 {
94+
let Interval(s, _) = i in s
95+
}
96+
97+
fn end(i: &Interval) -> i64 {
98+
let Interval(_, e) = i in e
99+
}
100+
101+
fn duration(i: &Interval) -> i64 {
102+
end(i) - start(i)
103+
}
104+
105+
// --- The 13-way classifier ---
106+
//
107+
// Computes the unique Allen relation between two intervals.
108+
// Total: every (A, B) pair returns exactly one variant.
109+
//
110+
// Complexity: O(1) — at most six integer comparisons.
111+
//
112+
// The case-tree below follows Allen 1983 §2, exhaustively
113+
// partitioning the (start_a vs start_b, end_a vs end_b, end_a vs
114+
// start_b) lattice.
115+
fn relate(a: &Interval, b: &Interval) -> Relation {
116+
let sa = start(a)
117+
let ea = end(a)
118+
let sb = start(b)
119+
let eb = end(b)
120+
121+
if ea < sb then
122+
Before
123+
else if sa > eb then
124+
After
125+
else if ea == sb then
126+
Meets
127+
else if sa == eb then
128+
MetBy
129+
else if sa == sb && ea == eb then
130+
Equals
131+
else if sa == sb && ea < eb then
132+
Starts
133+
else if sa == sb && ea > eb then
134+
StartedBy
135+
else if ea == eb && sa > sb then
136+
Finishes
137+
else if ea == eb && sa < sb then
138+
FinishedBy
139+
else if sa > sb && ea < eb then
140+
During
141+
else if sa < sb && ea > eb then
142+
Contains
143+
else if sa < sb && ea < eb then
144+
Overlaps
145+
else
146+
OverlappedBy
147+
}
148+
149+
// --- Inverse ---
150+
//
151+
// `relate(a, b)` and `relate(b, a)` are related by `inverse`.
152+
// This is one of the two foundational Allen properties (the other
153+
// being composition — see Future Work).
154+
fn inverse(r: Relation) -> Relation {
155+
match r of
156+
| Before => After
157+
| After => Before
158+
| Meets => MetBy
159+
| MetBy => Meets
160+
| Overlaps => OverlappedBy
161+
| OverlappedBy => Overlaps
162+
| Starts => StartedBy
163+
| StartedBy => Starts
164+
| During => Contains
165+
| Contains => During
166+
| Finishes => FinishedBy
167+
| FinishedBy => Finishes
168+
| Equals => Equals
169+
end
170+
}
171+
172+
// --- Convex / disjoint predicates ---
173+
174+
// Two intervals are disjoint iff one is entirely before/after the
175+
// other (no contact AT ALL — meets/met-by share an endpoint).
176+
fn is_disjoint(a: &Interval, b: &Interval) -> bool {
177+
match relate(a, b) of
178+
| Before => true
179+
| After => true
180+
| _ => false
181+
end
182+
}
183+
184+
// Two intervals overlap (in the inclusive sense — share at least one
185+
// instant) iff they are NOT disjoint AND NOT merely touching at an
186+
// endpoint.
187+
fn has_proper_overlap(a: &Interval, b: &Interval) -> bool {
188+
match relate(a, b) of
189+
| Before => false
190+
| After => false
191+
| Meets => false
192+
| MetBy => false
193+
| _ => true
194+
end
195+
}
196+
197+
// --- Future work (not in this slice) ---
198+
//
199+
// 1. Composition table: `compose : Relation -> Relation -> Set<Relation>`.
200+
// Allen 1983 §3 specifies the 13×13 = 169-entry table. The table
201+
// is canonical; encoding it requires a set type — landing this
202+
// is a follow-up once `stdlib/Set.eph` exists.
203+
//
204+
// 2. Coq mechanisation: prove `inverse(inverse(r)) = r` (self-
205+
// inverse property) and `relate(a, b) = inverse(relate(b, a))`
206+
// (the foundational orientation lemma). Target file:
207+
// `formal/Allen.v`. Both proofs are case-analysis on the 13
208+
// variants; trivial.
209+
//
210+
// 3. Bitemporal extension (D12): combine valid-time interval +
211+
// transaction-time interval into a 2D Allen lattice. Snodgrass
212+
// 1995 §4.
213+
//
214+
// 4. Point intervals: half-open semantics already model the empty
215+
// interval at `start == end`. A future sibling type
216+
// `PointInstant = i64` would distinguish a single-instant event
217+
// from any interval.

0 commit comments

Comments
 (0)