moczarr.intersect
Two-store occupancy intersection (cross-sensor cell masks) — issue #39.
The read-side composition primitive englacial/zagg#426 needs (GEDI waveform
stores composed with ATL03 stores): the exact cell occupancy TWO hive stores
share, where the store-vs-AOI helpers (root_coverage_and / box_and /
bitmap_and) answer only one store against a cover. The flow is the §5
tier discipline applied twice:
- Root prefilter — each store's root
coverage.moc(or the discovery walk when it is unusable; D9) names its committed shards, and the shared regions are their intersection. Shard orders may differ; regions sit at the FINER shard order, so each region maps to exactly one leaf per store. - Per-region exact AND — each leaf's exact occupancy (the
"bitmap"sidecar, or the whole subtree forencoding: "full", which short-circuits without a sidecar GET) is restricted to the region and intersected cell-for-cell. - Order harmonization — when the stores' cell orders differ (e.g. ATL03
o19 vs GEDI o18), the finer store's cells coarsen to the coarser order by
4:1 OR-coarsening (
clip2order+ unique: a coarse cell is occupied iff any of its four children is) before the AND. All results are atmin(cell_order_a, cell_order_b)— the manifests set the harmonized OUTPUT order, but the order of a leaf's decoded cells is what that leaf's OWN envelope declares (coverage["cell_order"], which is what the sidecar decoded against). Reading the manifest's value there is a silent false negative: a word carries its order in its low bits, so words at two orders are never bit-equal and the AND simply matches nothing.
Degradation, documented: a shared leaf WITHOUT exact occupancy at the
harmonized order — a stamp with no usable coverage envelope, a box-only
envelope, a "bitmap" envelope whose sidecar object is missing, or an
envelope whose declared cell_order sits BELOW the harmonized order (its
cells name subtrees there, not cells) — contributes its conservative cover
instead (the tier-0 box, the decoded cells read as a cover, or the whole
shard subtree when even the box is unusable). The intersection is then a
conservative SUPERSET for cells under that leaf: false positives possible,
false negatives impossible — the same posture as the box tier everywhere
else — and the first such leaf per call raises a
:class:moczarr.exceptions.ConservativeCoverageWarning (a UserWarning
subclass, so it stays filterable by class without catching everything). That is
the degrade="conservative" default; degrade="skip" drops such leaves
instead (nothing is yielded under them, so everything returned IS exact —
exact-or-under rather than superset) and degrade="raise" refuses the
call, naming the first one. Debris and absent leaves contribute nothing
under every policy (absence is definitive; a clean GET miss is trustworthy
on its own).
Two API shapes, ruled on the measured comparison of zagg#422 open question 7 — one is the surface, the other is derived from it:
- :func:
iter_occupancy_and— shape (a), THE public surface: an iterator of(leaf_id, intersected cell words)per shared leaf, streaming, and the shape the zagg#426 consumer reads through (leaf identity is what fetches the payloads for the aligned per-leaf tensors). - :func:
occupancy_and— shape (b), a documented thin convenience DERIVED from (a): one flat compacted MOC of the whole intersection, semanticallycompress_mocover (a)'s stream (a fully-shared subtree compacts to its ancestor word, so dense overlap stays small). It is the currency for MOC algebra — it feedsopen_hive(aoi=...),moc_andand :func:moczarr.coverage.aoi_maskdirectly, and is the compact thing to persist or ship.
The derivation runs ONE way: (b) is one compress_moc over (a), while
(a) is NOT recoverable from (b) — compaction discards the leaf attribution
that per-leaf reads need. Reach for (a) to read cells under a leaf, (b) to
do MOC algebra with the answer.
aoi= is SHARD-level on both, by contract: it restricts the shard cover
through the same candidate-leaves arithmetic the opener uses, and the cells
of a kept leaf are never clipped to it. Both shapes are therefore supersets
with respect to the AOI — false positives possible, false negatives
impossible, zagg's AOI-overhang convention — and
:func:moczarr.coverage.aoi_mask is the documented exact cell-level cut.
Keeping the clip out of here is what lets a fully-shared subtree stay one
compact word under a partial AOI instead of expanding to be trimmed.
The AND itself runs in MOC currency and never materializes a subtree:
exact cells against a conservative cover are filtered by containment
(coverage.aoi_mask), cover against cover is a moc_and, so the work
is bounded by the exact side wherever one exists. Cells are materialized
only where shape (a) has to yield them, and only for a region that is a
cover on BOTH sides — full-on-both (exact by construction; its whole
subtree IS the answer) or degraded on both — at
4^(out_order - region_order) words. Shape (b) never expands — which is
why it is not implemented as the literal composition over (a), only
documented as equivalent to it (see :func:occupancy_and).
Spec + fixtures only, per the moczarr contract: everything here decodes
from zagg docs/specification.md via this package's own convention /
coverage / store layers — no zagg import.
iter_occupancy_and(root_a, root_b, *, aoi=None, window_a=None, window_b=None, store_a=None, store_b=None, concurrency=32, degrade=_CONSERVATIVE, **store_kwargs)
Exact shared occupancy of two hive stores, streamed per shared leaf.
Shape (a), and the module's public surface (zagg#422 open question 7,
ruled on this PR's measured comparison): the shape per-leaf readers
want, because leaf_id is what fetches a leaf's payloads — the
zagg#426 flow builds its aligned side-by-side tensors that way
(moczarr.hhdc.read_tensors / :func:moczarr.open_leaf per yielded
leaf). :func:occupancy_and is one compress_moc over this stream;
the reverse does not exist, since compaction discards leaf attribution.
Yields (leaf_id, cells)
in ascending packed-word order — leaf_id the shared region's packed
shard word (the finer store's leaf when shard orders differ; both
stores' when equal) and cells the sorted occupied cell words BOTH
stores share under it, at the coarser of the two cell orders (4:1
OR-coarsening harmonizes a finer store — a coarse cell is occupied iff
any of its four children is). Regions whose exact intersection is empty
are skipped; an exhausted iterator with no yields means the stores share
no occupancy.
aoi (a morton cover: packed words, decimal strings, or an object
exposing __morton_moc__() — mortie's Moc — mixed orders
allowed) is a SHARD-LEVEL prefilter, and that is the contract, not an
implementation detail: it runs through the same candidate-leaves
arithmetic :func:moczarr.open_hive uses, so a leaf whose subtree
misses the cover never loads — but the cells of a KEPT leaf are NOT
clipped to the cover. Results are therefore a SUPERSET with respect to
the AOI: cells outside it can be yielded (false positives possible),
cells inside it are never dropped (false negatives impossible). That is
zagg's AOI-overhang convention, the same one-directional posture the
box tier keeps everywhere else here, and it is what lets a region full
on both sides stay compact under a partial AOI instead of expanding to
be clipped. The documented EXACT cell-level cut is
:func:moczarr.coverage.aoi_mask over the yielded cells:
cells[aoi_mask(cells, aoi_words)].
window_a / window_b name the time window per
store (morton-hive/2); store_a / store_b pass pre-built
obstore handles (required when the two stores need different
credentials — store_kwargs apply to BOTH roots otherwise).
concurrency bounds the batched stamp GETs and walk LISTs.
degrade picks what a leaf without exact occupancy at the harmonized
order does — the module docstring lists which leaves those are:
"conservative" (default) contributes its cover, so the result is a
SUPERSET under that leaf (false positives possible, false negatives
impossible) and the first such leaf per call raises one
:class:moczarr.exceptions.ConservativeCoverageWarning — its own
UserWarning subclass, so a consumer can promote it to an error or
silence it by category;
"skip" drops it, so the leaf contributes nothing and EVERYTHING
yielded is exact — exact-or-under instead of superset, the discriminator
a per-cell consumer needs — at the price of silently missing real shared
occupancy under it; "raise" refuses, naming the first such leaf.
Raises ValueError AT CALL
TIME — not at the first next() — when a root has no manifest, or when
the orders do not compose (one store's cell order above the other's shard
order): both stores are opened and validated before the iterator exists,
so a store-root typo surfaces where it was made.
Cost: yielding CELLS is what shape (a) is for, and a region whose result
is a cover on BOTH sides — full-on-both (exact, and bounded by the
region's own subtree), or degraded on both — materializes
4^(out_order - region_order) words for that one yield. Everywhere
else the intersection is bounded by the exact side and nothing is
expanded, degraded leaves included. :func:occupancy_and never expands
at all; reach for it when the harmonized order is far below the shard
order.
Source code in src/moczarr/intersect.py
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 | |
occupancy_and(root_a, root_b, *, aoi=None, window_a=None, window_b=None, store_a=None, store_b=None, concurrency=32, degrade=_CONSERVATIVE, **store_kwargs)
The :func:iter_occupancy_and intersection, compacted to one flat MOC.
Shape (b): a thin convenience DERIVED from the public surface —
semantically compress_moc over everything
:func:iter_occupancy_and yields, returned whole as a canonical
compact morton cover (uint64, ascending). A fully-shared subtree
compacts to its ancestor word, so dense overlap stays small, and the
empty intersection is an empty array. Parameters (degrade
included), degradation, and errors are identical to
:func:iter_occupancy_and.
aoi keeps the same contract it has there — the same accepted forms
(packed words, decimal strings, or an object exposing
__morton_moc__()), and a SHARD-LEVEL prefilter,
with the cells of a kept leaf left unclipped, so the returned MOC is a
superset with respect to the AOI (false positives possible, false
negatives impossible: zagg's AOI-overhang convention). Worth restating
on this shape, because a MOC is what gets composed onward: feed the
result to moc_and(result, aoi_words) (or
:func:moczarr.coverage.aoi_mask on cells) when the consumer needs the
exact cut rather than the overhang.
The derivation is one-way, and that asymmetry is what picks between the two:
- (b) from (a) is one line —
compress_moc(concatenate([cells for _leaf, cells in iter_occupancy_and(...)]))— and equals this function's result member-for-member (pinned bytest_flat_moc_is_the_compressed_stream). - (a) from (b) does not exist: compaction discards leaf
attribution, and a member may sit above, at, or below a leaf. Use
shape (a) for per-leaf reads — the zagg#426 tensor path, which needs
the leaf id to fetch payloads — and this shape as MOC-algebra
currency: it is what
open_hive(aoi=...),moc_andand :func:moczarr.coverage.aoi_maskconsume, and the compact thing to persist or ship.
Cost — the reason this is NOT implemented as the literal one-liner:
the composition above would expand a region that is full (or degraded)
on both sides to its 4^(out_order - region_order) cells, purely to
compact them back to the ancestor word they came from (~262k words at
the zagg#426 o9-shard/o18-cell scale, per region). This stays in MOC
currency end to end instead — same result, no subtree ever
materialized (pinned by test_only_the_iterator_expands_full_on_both).
Expanding every member back to the harmonized cell order yields exactly
the union of the iterator's cells (the shared golden test).
Source code in src/moczarr/intersect.py
616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 | |