moczarr.composition
zagg-composition/1 decoding, read side — pure functions.
A composition field is one dense uint64 word per cell carrying eight
8-bit lanes of quantized fractions of the cell's signal stratum
(N_signal = the of digest's total weight — magnitude lives in the
digest, composition here). Lanes pack LSB byte first: lane i is bits
8*i .. 8*i + 7. An empty stratum packs to 0.
Precondition on the array, not on the words. §3 requires a composition
array to declare fill_value: 0 (a MUST, enforced writer-side at zagg's
config validation) so an unwritten cell decodes to the same word an empty
stratum packs to. Nothing in this module can check it: the functions here take
words (and attrs), never the zarr.Array, so a store declaring a nonzero
fill would hand every unoccupied cell to :func:lane_presence as spurious
occurrences — a fill of 1 reads as a phantom land. The claims below hold
provided the array declares fill_value: 0.
That precondition is enforced on the open path, not here: open_hive
raises on any array whose attrs carry a composition.spec block and whose
declared fill is not 0 (moczarr.open._check_composition_fill), where the
array metadata is already in hand. So a store reaching these functions through
open_hive has been checked, this module stays pure numpy over words and
attrs, and a caller who opens an array by other means should check
array.fill_value == 0 itself alongside the §3.3 attrs gate.
Normative home: zagg docs/specification.md §3 — merged to zagg main
(englacial/zagg#346, rebase merge); the section references here are read
against main at d52e3063 (a pure pin advance: §3 is byte-identical
between the prior 9e11e65 pin and d52e3063, measured by extracting
the section at both shas and diffing), and the branch-era shas
(166e908, 555874b) are historical. Narrative in zagg
docs/signal_strata.md; the writer is zagg.stats.composition.
Quantization (§3.2) uses a presence floor: k = round(255 * c / N)
(round-half-even), except any nonzero count quantizes to at least 1. So
lane > 0 means "this flag occurred" exactly, at every N, through
arbitrary merge chains (:func:lane_presence); count recovery
round(k * N / 255) is exact whenever N <= 254 and within
±(N/510 + ½) above (:func:counts_from_composition) — the writer's
quantization costs N/510 and the reader's own round adds up to
another half.
Lane meaning is attrs-bound, never positional: the array's composition
attrs block (§3.3) declares lanes (names in bit order), of (the
sibling digest field whose total weight is N_signal), and threshold
(the committed signal cut). :func:unpack_composition therefore returns
positional lanes only; name them via :func:named_lanes, which binds to a
:func:parse_composition_attrs-validated block. The spec gate is strict on
both halves — a future zagg-composition/2 is adopted deliberately, never
half-parsed, and for /1 the declared lanes must be exactly the §3.1
order (:data:COMPOSITION_LANES), which §3.3 makes writer-stamped rather than
a product knob. A permuted declaration is a non-conforming store, not a
relabeling instruction: binding it would decode every lane under the wrong
name, and the store says nothing about whether the declaration or the packer
is the side that is wrong.
Deliberately absent: a read-side merge. The §3.4 merge law is symmetric and,
up to the bounded re-quantization error, associative — every fold
re-quantizes, so fold order never affects presence but shifts counts within
O(n/510) and folds are not byte-stable across orders. It operates on
(word, n_signal) pairs, and the weights come from the of digests'
total weights, which this module never opens: a merge(word_a, word_b)
that could not demand them would be a trap. So the law stays zagg-owned and
moczarr consumes committed words.
counts_from_composition(words, n_signal)
Recovered per-lane counts — round(k * N / 255), (N, 8) int64.
n_signal is the per-cell total weight of the attrs-declared of
digest (scalar or (N,), broadcast against words). Recovery is
exact whenever n_signal <= 254: the writer's quantization error is
<= N/510 < 1/2 (spec §3.2), so this function's round lands back on
the integer count. Above that it is a bounded estimate within
±(N/510 + 1/2) — the two roundings compose, N/510 from the writer
plus up to 1/2 here, i.e. <= floor(N/510 + 1/2) in integer terms —
plus O(N/510) per re-quantizing merge. Returned, not raised, since
presence stays exact and large-N cells are the common case. The bound
covers lanes the writer rounded; a lane lifted by the presence floor
(k = 1 for a count that would have quantized to 0) recovers
~N/255 by design, trading count accuracy for exact presence.
n_signal <= 0 (empty stratum) recovers all-zero counts; both tails are
guarded, so an n_signal above :data:_FLOAT64_EXACT_MAX_INT raises
(naming n_signal) instead of saturating int64 under a bare numpy
warning.
Source code in src/moczarr/composition.py
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | |
lane_presence(words)
Per-lane occurrence flags of a composition word — lane > 0, (N, 8) bool.
Exact at every n_signal, through arbitrary merge chains, by the
presence floor (spec §3.2: any nonzero count quantizes to at least 1) —
exact for written cells, and for unwritten ones only if the array
declares fill_value: 0 as §3 requires (see the module docstring: this
function sees words, never the array, so it cannot check that itself).
Positional columns, as in :func:unpack_composition.
Source code in src/moczarr/composition.py
187 188 189 190 191 192 193 194 195 196 197 | |
named_lanes(words, attrs)
Unpacked lanes keyed by their attrs-declared names — {name: (N,) uint8}.
The binding step over :func:unpack_composition: lane order comes from
the validated composition.lanes attr (via
:func:parse_composition_attrs), so no caller ever indexes by a
hardcoded position and a future revision that re-means the bytes needs no
change here. For /1 that declaration is gated to the §3.1 order, so
the mechanism is order-driven while the accepted values are not
open-ended.
Source code in src/moczarr/composition.py
259 260 261 262 263 264 265 266 267 268 269 270 271 272 | |
parse_composition_attrs(attrs)
The validated composition block from an array's attrs — or raise.
Strict by design, unlike the tolerant coverage caches: the word is data,
not an index, so a reader that cannot bind it correctly must say so
rather than degrade. Raises ValueError on a missing/malformed block,
on any spec other than zagg-composition/1 — a future revision is
adopted deliberately, never half-parsed (§3.3 conformance rule) — and on
a /1 block whose lanes are anything but :data:COMPOSITION_LANES,
which §3.3 fixes as the exact /1 value.
Returns {"lanes": tuple[str, ...], "of": str, "threshold": int}:
lanes names the bit-order lanes (LSB byte first), of names the
sibling digest field whose total weight is N_signal (the word is
uninterpretable without it), threshold is the committed signal cut.
Source code in src/moczarr/composition.py
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 | |
unpack_composition(words)
Composition words as positional u8 lanes — (N,) uint64 -> (N, 8) uint8.
Lane i is bits 8*i .. 8*i + 7 of the word (LSB byte first, spec
§3.1). Columns are POSITIONAL: what a lane means comes from the
array's attrs (composition.lanes), never from a hardcoded order —
use :func:named_lanes to bind names. Scalars pass through
np.atleast_1d; the result is always 2-D. Raises ValueError on a
non-integer or negative words rather than coerce it (:func:_as_words).
Source code in src/moczarr/composition.py
131 132 133 134 135 136 137 138 139 140 141 142 143 | |