You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
π€ from Claude (filed at espg's direction, 2026-08-08 β design transfer from englacial/zagg#410)
What this is
mortie grows from spatial indexing to a coverage library: spatial (HEALPix/morton, existing) plus temporal. The temporal cell type was designed on zagg#410 β see the superseding design comment and the T-MOC research that ruled out the hierarchical-cell alternative β and lands here because:
Dependency topology: zagg writes the field, moczarr reads it, and mortie is the dependency both already share.
Kernel synergy: a mortie spatial MOC is already, internally, a sorted list of disjoint u64 ranges (the depth-29 linearization in src_rust/src/moc.rs); a temporal coverage is the same object over a 1-D axis, and the range-walk machinery (cf. the Batch MOC set operations: mocs_and / mocs_intersect and the 1xN broadcast familyΒ #173mocs_intersect predicate) generalizes directly.
It fits mortie's constraints exactly: pure bit manipulation, zero new dependencies, numpy-only surface.
zagg#410 keeps the consumer side (the per-centroid companion array, its fold law, the config knob, normative spec text + fixtures). This issue is where the type itself gets planned and built.
The type, as decided on zagg#410
One u64, a tagged union of an exact timestamp and a conservative time range:
variant
layout (bit 0 = LSB)
timestamp
bits 63..1 = nanoseconds since epoch (2^63 ns β 292 yr); bit 0 = 0
range
bits 63..32 = start, 32 bits at 2^31 ns (β2.15 s); bits 31..1 = end, 31 bits at 2^32 ns (β4.29 s); bit 0 = 1
Bit split 32/31 β decided (espg, 2026-08-08, on #410): spatial overview folds merge same-pass centroids constantly, so short-span ranges are the bulk of the range population, and their stored width is pure quantization slop β worst case q_start + q_end β 6.4 s under 32/31 vs β 9.7 s under the 33/30 alternative (typical: ~3.2 s vs ~4.8 s). The tighter end bound wins over the finer sort granularity.
Outward rounding: start floored, end ceiled to their grids β a range is always a conservative superset of its members (the same honesty contract common_ancestor gives the spatial location companion).
Sort: discriminator in the LSB, shared field high (the zuniq trick). A timestamp's top 32 bits are ns >> 31 β the same 2^31 ns units as a range's start field β so plain unsigned sort orders timestamps and ranges chronologically by start, interleaved, no comparator.
Merge: min on start codes, max on end codes, timestamps cast outward on first merge. A semilattice join on a fixed epoch-anchored lattice: exactly associative, commutative, idempotent β bit-identical under any fold tree, with error bounded at one quantum per end regardless of fold depth.
Epoch/timescale β decided: nanoseconds since 1850-01-01T00:00:00 on a continuous, leap-free, GPS-aligned scale (equals GPS time + one fixed constant after 1980; proleptic before). Verified endpoints: CMIP historical starts 1850 (the binding constraint), NCEP/NCAR R1 starts 1948; ceiling β 2142 covers standard ScenarioMIP to 2100. Documented exclusions: CMIP extended-to-2300 scenarios, pre-1850 reanalyses. Leap seconds appear only at the UTC boundary (datetime64/pandas import-export), via the static leap table.
Rejected alternatives (recorded on #410 with the kill arguments): hierarchical T-MOC-style cell (unbounded merge coarsening, measured), floating-point absolute time (precision inverted β worst on newest data), unit-scale fields and calendar tuples (sort/path-independence/radix-waste failures). Held in reserve: uniform start + float duration (finer everywhere, but ULP-level merge path-dependence).
Open questions
Base quantum: 1 ns vs 2 ns. 2 ns doubles the span to 585 yr (1850β2434), covering CMIP extended scenarios, at zero physical cost for ICESat-2 (float64 delta_time carries only ~50 ns at mission magnitudes). Lean: plain ns, exclusions documented; flip only if 2300-scenario output is declared in scope. Must be settled before the epoch constant freezes.
Module and naming. A new mortie/temporal.py (mirrored by src_rust/src/temporal.rs)? Names for the type's operations (time_encode/time_merge/β¦? a TimeWord framing?) β should read as one family with the morton word ops.
Phase-1 scope. Scalar encode/decode/merge + vectorized numpy batch forms (encode from datetime64[ns], merge two arrays, reduce an array), UTCβinternal conversion helpers with the leap table. Interval-set coverage algebra (union/intersect over sorted arrays of these) is explicitly deferred β espg parked it pending the scalar type; when it lands it should share the range-walk kernels with the spatial set ops.
Conformance surface. Which invariants are normative and fixture-pinned: sort-order property, semilattice laws (associativity/commutativity/idempotence), outward-conservatism, round-trip bounds, the epoch/scale constants.
Sequencing
Blocked on nothing in mortie; gated only on the tail of the zagg#410 discussion (the zagg-side fold law and spec text develop in parallel there). The batch entry points, when they come, land in mortie/batch.py per #170.
Refs: englacial/zagg#410 (design record), englacial/zagg#414 (the compression-factor change the leaf-precision story depends on), #173 (the range-walk kernels this shares), #170 (batch.py).
π€ from Claude (filed at espg's direction, 2026-08-08 β design transfer from englacial/zagg#410)
What this is
mortie grows from spatial indexing to a coverage library: spatial (HEALPix/morton, existing) plus temporal. The temporal cell type was designed on zagg#410 β see the superseding design comment and the T-MOC research that ruled out the hierarchical-cell alternative β and lands here because:
src_rust/src/moc.rs); a temporal coverage is the same object over a 1-D axis, and the range-walk machinery (cf. the Batch MOC set operations: mocs_and / mocs_intersect and the 1xN broadcast familyΒ #173mocs_intersectpredicate) generalizes directly.zagg#410 keeps the consumer side (the per-centroid companion array, its fold law, the config knob, normative spec text + fixtures). This issue is where the type itself gets planned and built.
The type, as decided on zagg#410
One u64, a tagged union of an exact timestamp and a conservative time range:
01q_start + q_endβ 6.4 s under 32/31 vs β 9.7 s under the 33/30 alternative (typical: ~3.2 s vs ~4.8 s). The tighter end bound wins over the finer sort granularity.common_ancestorgives the spatiallocationcompanion).ns >> 31β the same 2^31 ns units as a range's start field β so plain unsigned sort orders timestamps and ranges chronologically by start, interleaved, no comparator.minon start codes,maxon end codes, timestamps cast outward on first merge. A semilattice join on a fixed epoch-anchored lattice: exactly associative, commutative, idempotent β bit-identical under any fold tree, with error bounded at one quantum per end regardless of fold depth.Open questions
585 yr (1850β2434), covering CMIP extended scenarios, at zero physical cost for ICESat-2 (float64delta_timecarries only ~50 ns at mission magnitudes). Lean: plain ns, exclusions documented; flip only if 2300-scenario output is declared in scope. Must be settled before the epoch constant freezes.mortie/temporal.py(mirrored bysrc_rust/src/temporal.rs)? Names for the type's operations (time_encode/time_merge/β¦? aTimeWordframing?) β should read as one family with the morton word ops.datetime64[ns], merge two arrays, reduce an array), UTCβinternal conversion helpers with the leap table. Interval-set coverage algebra (union/intersect over sorted arrays of these) is explicitly deferred β espg parked it pending the scalar type; when it lands it should share the range-walk kernels with the spatial set ops.Sequencing
Blocked on nothing in mortie; gated only on the tail of the zagg#410 discussion (the zagg-side fold law and spec text develop in parallel there). The batch entry points, when they come, land in
mortie/batch.pyper #170.Refs: englacial/zagg#410 (design record), englacial/zagg#414 (the compression-factor change the leaf-precision story depends on), #173 (the range-walk kernels this shares), #170 (
batch.py).