vaelii.impl.duration is the quantitative half of interval reasoning. Allen's algebra
(time.md) says that two intervals overlap; this says how long for, in real
units, and adds up the lengths of several. Two computed predicates, neither ever stored:
(totalDuration (list I1 I2 …) D) ; D is the sum of the components' lengths
(overlapDuration I1 I2 D) ; D is how long I1 and I2 overlap
An interval's own duration is an ordinary stored fact, (length I M), whose measure M
is one of quantity.md's NAUTs — (QuantityFn 2 Hour), or
(QuantityIntervalFn 1 2 Hour) when only bounds are known. The index answers those; this
prover only reads them.
So it sits on top of three existing subsystems and adds nothing to any of them: it
normalizes each length through provers/normalize-quantity against the KB's
dimensionOf / conversionFactor table (quantity.md), reads the
qualitative relation set straight off interval/possible-allen-relations
(time.md), and takes the metric overlap window from stp/overlap-window
(stp.md). res/matches-visible is its only touch of the KB.
Every computation carries [lo hi] magnitude bounds, and the render decides the shape:
bounds within tolerance give a point (QuantityFn …), anything wider gives
(QuantityIntervalFn lo hi …).
That is what keeps the answer honest. A stored length may itself be an interval measure,
and an overlap is often only bounded rather than known — so an over-approximation renders
as an interval and says it is one, instead of as a point that would claim more than the
KB knows. Two hours overlapping half an hour "somewhere" is
(QuantityIntervalFn 0 1800 Second), not a figure.
The result is rendered in the dimension's base unit, read back out of the same
conversionFactor table the normalization used: (conversionFactor U Base F) names the
base, and a unit declaring no factor is its own base. So no separate declaration says
which unit to render in, and none can disagree with the unit the arithmetic actually
happened in. Every unit of a dimension converts to a single base — the direct-to-base
contract — so which component the base is read from cannot change the answer.
A caller who wants another unit uses the check form instead. A ground D is compared
after normalization, so all three of these are answered from lengths stated in hours and
minutes:
(totalDuration (list A B) (QuantityFn 9000 Second))
(totalDuration (list A B) (QuantityFn 2.5 Hour))
(totalDuration (list A B) (QuantityFn 150 Minute))
Bind or check is the EvaluateProver shape: a variable D takes the rendered
measure; a ground D succeeds iff it names the same dimension and the same bounds within
provers/*quantity-tolerance* — the same epsilon policy the measure comparisons use. A
magnitude is snapped to that same grid before rendering, so cross-unit normalization's
last-bit noise never reaches the answer, and an integral result comes back as an integer
so the bound answer is = to the obvious way of writing it.
interval-length snaps each stored length to that grid before comparing two of them, which
is why one interval's duration written as 66 Minute and as 1.1 Hour is one length and
not a disagreement. stp.md snaps a stated temporalDistance the same way and for
the same reason — a separation and a duration are written alike, so they are read to one
grid, and the two subsystems cannot reach different verdicts about the same pair of facts.
Read every component's length, require them all to share one dimension, sum the lo
and hi bounds separately, render. The dimension gate is the whole of the unit
consistency check: magnitudes only add up once they are in one unit, so two hours plus
five metres is refused outright rather than summed into a number that means nothing.
(list I1 I2 …) is one argument, so totalDuration is declared
binaryPredicate — the arity check holds a stored sentence to that, and the goal shape
the prover reads has to agree with it.
Three things yield no answer rather than a wrong one: a component with no stated length (an open-world gap is not zero), a component with two stated lengths that disagree once normalized, and an empty component list (zero of no unit is not a measure). Duplicates are not a disagreement — the lengths are compared as normalized values, so the same duration restated in another context or written in another unit collapses to one, and the verdict cannot depend on the order the facts were read in.
overlap-bounds is a pure function of the possible-relation set and the two lengths.
Three of its four cases are named by the qualitative vocabulary itself, and it reads their
denotations out of vaelii.impl.interval rather than restating them, so the two cannot
drift apart:
| Every possible relation is… | Overlap |
|---|---|
temporallyDisjoint — before, after, meets, met-by | exactly [0 0] |
subintervalOf — during, starts, finishes, equal | the whole of the first, [lo1 hi1] |
hasSubinterval — the mirror | [lo2 hi2] |
| anything else | [0, min(hi1, hi2)] |
The last is the sound over-approximation: they may not overlap at all, and can overlap by no more than the shorter of them. An unconstrained pair gets exactly that, which is the right answer to "how long do these two overlap?" when nothing has been said — and it renders as an interval, so it reads as ignorance rather than as a measurement.
The relation set comes from the tightened network, so a relation nobody asserted works
as well as one that was: (during A B) and (during B D) compose to put A inside D, and
the overlap of A and D is all of A. An inconsistent network yields an empty relation
set and therefore no answer at all — the same rule the qualitative prover follows, since
an unsatisfiable theory should not be mined for a number either.
The last row of that table is the honest answer to "how long do these two overlap?" when
nothing is known, and it is a poor answer when something is. If the KB says where the two
intervals' endpoints fall relative to one another — (startOf I P) / (endOf I P) and the
temporalDistance constraints of stp.md — there is a real figure, and
stp/overlap-window computes it: the shared stretch runs from the later of the two starts to
the earlier of the two ends, which is four gaps the metric closure already bounds.
sharpen-overlap intersects that with the qualitative bound. Both are sound, so the
tighter of them is:
;; A lasts two hours, B lasts half an hour, no Allen relation stated
(overlapDuration A B ?d) ;=> (QuantityIntervalFn 0 1800 Second)
;; add: A's own span is two hours, B's is thirty minutes, B begins 105 minutes into A
(overlapDuration A B ?d) ;=> (QuantityFn 900 Second)
Fifteen minutes, exactly, from a KB that never stated an interval relation at all.
Three properties hold it together:
temporalDistance — or none
reaching these two intervals, or one whose network is unsatisfiable — gets the qualitative
answer back to the digit. Naming an interval's endpoints is not itself a constraint.(QuantityIntervalFn …), exactly as an unconstrained pair does — the
honesty rule does not relax because the answer got better.Sharpening is all the metric layer does here, and that is a dependency as well as a
guarantee: the qualitative bound is what gets narrowed, and it is computed from the two
stored lengths. A KB that names all four endpoints and pins every gap between them still
gets no overlapDuration answer while either interval has no (length I M) — there is
nothing for the window to narrow, and the prover does not read the window as a bound in its
own right.
Two sound bounds with nothing in common mean the KB says two incompatible things about the
same overlap — a stated (before A D) against gaps that put D inside A — and there is then no
number to report at all. Bounds that cross by no more than the tolerance are float noise from
two routes to one figure, and collapse to a point rather than a contradiction.
length, totalDuration and overlapDuration are declared in
resources/kb/upper/TimeContext.txt, beside the Allen relations — they are about
intervals. length is a duration, not a spatial extent. The measure terms and the unit
table stay MeasureContext's.
The prover is opt-in, and needs no other prover registered — possible-allen-relations
and stp/overlap-window are both functions of the believed facts rather than queries, so
duration arithmetic works whether or not the :allen or :metric-time reasoner is
registered:
(v/add-reasoner kb :duration)
cost is :compute (a handful of belief-filtered reads and some arithmetic),
est-bindings is 1 (a computation has at most one answer), and completeness is 100 —
a duration is never a stored fact and no rule concludes one, so this is the sole complete
method and the dispatcher runs it alone.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |