mortie.Toc — the temporal coverage object
mortie.toc(...) builds a Toc: a temporal coverage as an object, so that
gappy time coverage reads as time.
from mortie import toc
when = toc("2020-01-01", "2021-06-01")
assert store_toc.overlaps(when)
sliver = store_toc & when # the canonical cover of the overlap
The two-layer rule
mortie's temporal-coverage surface is two layers and stays that way, the same split the spatial object documents:
- The kernel functions are the array/batch layer. The free
toc_*functions on the toc kernel page are words in, words out, unchanged and un-deprecated, and the segmentedtocs_reducestays function-shaped permanently. Array-first consumers keep calling these directly, at zero wrapping cost. - The object is ergonomics.
Tocis a thin view over the canonicaluint64word set —toc_normalize's sorted maximal merges — never a new representation: every public method is a single delegation to a kernel function (all three delegate totoc_and, the one set operation the issue #177 call-site audit ruled in). The array stays the interchange format —Toc.__toc_words__()hands the canonical words back, and any object exposing that dunder is accepted wherever aTocis.
The canonical form is a word set
A store observed in campaigns has gappy coverage: one merged envelope
papers over the gaps exactly where they are most informative, so the
canonical form keeps k disjoint spans (plus free instants, bit-identical).
Normalization is lossy toward coverage, one way: a timestamp subsumed by
a range's decoded span is absorbed at construction, and a cover can be
rebuilt from the sibling word arrays it came from — never the arrays from a
cover. Union needs no method (construction normalizes, so
Toc(np.append(a.words, b.words)) is the union), and the difference /
symmetric-difference directions deliberately do not ship: conservative
covers under-cover on subtraction, and no audited call site exists.
Two naming notes. Toc.overlaps / Toc.contains compare two whole covers
and answer once; the un-deprecated kernel predicates toc_overlaps /
toc_contains of the same names take a [q_start_ns, q_end_ns) query
window and answer elementwise, per word — a different question. And the
predicates are envelope algebra, not data algebra: the
conservative-direction table in the module docstring below says which way
each answer can err near a span edge (the quanta are ~2–4 s).
The time-first Toc object over the toc kernel (issue #198).
mortie's temporal-coverage surface is two layers, the same deliberate split
:mod:mortie.moc_object documents for space:
- the kernel -- the free
toc_*functions in :mod:mortie._toc, words in and words out, unchanged and un-deprecated. Array-first consumers (zagg's per-cell folds, the segmented :func:~mortie.tocs_reduce) keep calling them on plain ndarrays at zero wrapping cost. - the object -- :class:
Toc, here. It is ergonomics and nothing else: a thin view over the canonicaluint64word set (:func:~mortie.toc_normalize's sorted maximal merges), never a new representation. Every public method is a single delegation to a kernel function. There is no algebra in this module; a method body that is not one kernel call is a bug, not a feature.
All three object methods delegate to the same kernel,
:func:~mortie.toc_and -- the one set operation the issue #177 call-site
audit ruled in. The method names are the MOCpy-adjacent vocabulary (issue
198 commitment 6), so they deliberately do not line up with the batch
layer's :func:~mortie.toc_overlaps / :func:~mortie.toc_contains, which
stay un-deprecated and ask a different question: those take a
[q_start_ns, q_end_ns) query window and answer elementwise, per word,
while :meth:Toc.overlaps / :meth:Toc.contains compare two whole covers
and answer once. "The object method is the kernel of the same name with
self bound" is the wrong reading here; each method docstring names the
kernel it actually calls.
The interchange format stays the array. :meth:Toc.__toc_words__ hands back
the canonical words -- the temporal sibling of __morton_moc__() -- and any
object exposing that dunder is accepted wherever a Toc is: mortie owns the
word grammar, downstream stores own their own encodings, and the two meet at a
plain uint64 array with neither importing the other's private grammar.
Conservative directions. Every predicate below is envelope algebra, not data algebra. A range word's decoded envelope dilates the real interval it was encoded from (starts floor to the 2^31 ns grid, ends ceil to 2^32 ns), so the covered timeline is always a superset of the real one, on both sides of the comparison. Read the answers accordingly; each method's docstring points back here.
| call | the question it answers exactly | as a question about the real data |
|---|---|---|
a.overlaps(b) |
do the two covers share any decoded instant? | may say True for data that only comes within a quantum (~2-4 s) of each other; a False is decisive. The safe direction for "must I read this store?" -- never a false skip. |
a.contains(b) |
is b's decoded coverage inside a's? |
may err either way by up to a quantum at span edges (each side's envelope dilates its own data). Exact for the question that matters -- "will a store whose coverage is a answer a query for b?" |
a & b, a.intersection(b) |
the canonical cover of the shared coverage | never under-covers the true intersection (A ⊇ X and B ⊇ Y imply A ∩ B ⊇ X ∩ Y); may over-cover near piece edges by up to one quantum per side, inherited from the operands. |
| either side empty | an empty cover is contained in everything and overlaps nothing | a.contains(empty) is True (vacuously -- there is no coverage of empty outside a) while a.overlaps(empty) is False. The two predicates disagree here by definition, not by accident: ask contains about coverage and overlaps about work to do. |
a == b |
identical canonical words | not equality of the underlying timeline: an instant and the degenerate span at the same time encode different words and compare unequal. |
Toc
A temporal coverage as an object -- gappy time coverage that reads as time.
A thin view over the canonical uint64 toc word set and nothing more:
the words are the coverage, this class is the ergonomics, and every
public method is a single delegation to a kernel function (see the module
docstring for the two-layer rule and the conservative-direction table the
methods share).
The canonical form is a word set, not a single envelope (issue #198):
a store observed in campaigns has gappy coverage, and one merged
envelope papers over the gaps exactly where they are most informative --
k disjoint spans keep them. The words are normalized eagerly
(:func:~mortie.toc_normalize) and stored read-only, which makes ==
and :func:hash well defined; the instance itself is immutable.
Normalization is lossy toward coverage, one way (the #177 Q2 ruling): a timestamp subsumed by a range's decoded span adds no coverage and is absorbed at construction -- which subsumed instants existed, and how many times, is dropped. Exact instants live in the sibling word arrays a cover is built from; a cover can be rebuilt from the arrays, never the arrays from a cover. A timestamp no range subsumes survives bit-identical as an exact degenerate member, and a surviving decoded gap is never bridged (Q1).
Union needs no method: construction normalizes, so
Toc(np.append(a.words, b.words)) is the union. The #177 call-site
audit ruled exactly one set operation in (:func:~mortie.toc_and, the
:meth:intersection / & delegate); the difference and
symmetric-difference directions deliberately do not ship -- conservative
covers under-cover on subtraction, and no audited call site exists.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str, datetime64, array_like, or Toc
|
What the coverage is. A UTC instant -- an ISO 8601 string or
|
required |
end
|
str, datetime64, or array_like
|
End side of the time-pair form, same forms as |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If a time endpoint is numeric rather than datetime64 / ISO string, a
span is inverted or outside the representable epoch range (see
:func: |
See Also
mortie.toc_normalize : the eager canonicalization applied to every instance. mortie.toc_and : the intersection kernel the set methods delegate to.
Examples:
>>> import mortie
>>> when = mortie.Toc("2020-01-01", "2021-06-01")
>>> mortie.Toc("2020-06-15").overlaps(when)
True
>>> when.contains(mortie.Toc("2020-03-01", "2020-04-01"))
True
Source code in mortie/toc_object.py
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 | |
__delattr__(name)
Refuse attribute deletion -- a Toc is immutable.
Source code in mortie/toc_object.py
347 348 349 | |
__eq__(other)
Compare canonical words -- word identity, not timeline equality.
Source code in mortie/toc_object.py
459 460 461 462 463 | |
__hash__()
Hash the canonical words; sound because a Toc is immutable.
Source code in mortie/toc_object.py
465 466 467 | |
__iter__()
Iterate the cover's toc words.
Source code in mortie/toc_object.py
473 474 475 | |
__len__()
Count the words (disjoint spans and free instants) in the cover.
Source code in mortie/toc_object.py
469 470 471 | |
__reduce__()
Rebuild through the constructor -- pickle and copy cannot set slots.
A __slots__ class is restored by assigning its slot state, which
the immutability guard above refuses; reconstructing from the words
instead keeps Toc picklable (workers marshal their arguments) and
deep-copyable at the cost of one toc_normalize on already-canonical
words.
Returns:
| Type | Description |
|---|---|
tuple
|
The |
Source code in mortie/toc_object.py
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 | |
__repr__()
Show the span count, the UTC extent, and the covered duration.
Both printed bounds round outward to the second -- the start floors, the end ceils -- so the extent shown is never narrower than what the cover covers. A decoded end sits on the 2^32 ns grid (~4.295 s) and so is essentially never a whole second; flooring it would print a time strictly inside the envelope, the one direction the module docstring's conservative-direction table rules out.
Source code in mortie/toc_object.py
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 | |
__setattr__(name, value)
Refuse attribute assignment -- a Toc is immutable.
Source code in mortie/toc_object.py
341 342 343 344 345 | |
__toc_words__()
Canonical toc words -- the interchange protocol (issue #198).
Returns:
| Type | Description |
|---|---|
ndarray
|
The read-only |
Source code in mortie/toc_object.py
449 450 451 452 453 454 455 456 457 | |
contains(other)
Whether other's coverage lies entirely inside this cover.
Envelope algebra, not data algebra -- see the module docstring's
conservative-direction table. This is the "will a store whose
coverage is self answer a query for other?" test: the
intersection with other must give other back whole, and both
sides of that comparison are canonical -- the kernel's output by
construction, the operand because :func:_words canonicalizes every
operand it is handed -- so word equality is coverage equality. The
single-delegation shape costs a second :func:_words call (a foreign
__toc_words__() is therefore invoked twice per test); binding it
to a local would be a second statement, which the delegation pin
refuses.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other
|
Toc or array_like
|
The cover to test for containment. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
See Also
mortie.toc_and : the kernel this delegates to.
mortie.toc_contains : the batch layer's window predicate of the
same name, and the mirror direction -- which individual words
fall inside a [q_start_ns, q_end_ns) window, elementwise,
rather than whether one cover sits inside another.
Source code in mortie/toc_object.py
393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 | |
intersection(other)
Intersect with other: the canonical cover of the shared coverage (&).
Never under-covers the true intersection and may over-cover near piece edges by up to one quantum per side, inherited from the operands' envelopes -- see the module docstring's table.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other
|
Toc or array_like
|
The cover to intersect with. |
required |
Returns:
| Type | Description |
|---|---|
Toc
|
The intersection cover. |
Source code in mortie/toc_object.py
428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 | |
overlaps(other)
Whether this cover and other share any coverage.
Envelope algebra, not data algebra -- see the module docstring's
conservative-direction table: True can mean "within a quantum of
each other", False is decisive.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other
|
Toc or array_like
|
The cover to test against. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
See Also
mortie.toc_and : the kernel this delegates to.
mortie.toc_overlaps : the batch layer's window predicate of the
same name, a different question -- which individual words
intersect a [q_start_ns, q_end_ns) window, elementwise.
Source code in mortie/toc_object.py
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 | |