Skip to content

Declare chunk_dim, prime channel fingerprints, adopt the axis-aware state hash - #8

Merged
cboulay merged 1 commit into
devfrom
cboulay/chunk-dim-and-fingerprint
Sep 4, 2026
Merged

Declare chunk_dim, prime channel fingerprints, adopt the axis-aware state hash#8
cboulay merged 1 commit into
devfrom
cboulay/chunk-dim-and-fingerprint

Conversation

@cboulay

@cboulay cboulay commented Sep 4, 2026

Copy link
Copy Markdown
Member

Completes the chunk_dim / fingerprint sweep across the ezmsg source packages. simbiophys was the one I had marked clean on the wrong evidence — its axes are built once per stream and reused, but that says nothing about whether the stream is described.

Before this, the package had zero chunk_dim declarations, zero primed fingerprints, and was pinned to ezmsg-baseproc>=1.6.1, resolving to 1.10.2 whose default _hash_message was a constant 0 (reset once, then never).

Producers describe their stream

All five templates — noise.py, oscillator.py (spiral and sin), dnss/lfp.py, dnss/spike.py — plus cosine_encoder's two model-init paths now:

  • declare chunk_dim="time". Without it a consumer falls back to guessing "time", which is right for these raw streams and wrong the moment anything windows them into (win, time, ch).
  • touch ch_axis.fingerprint at construction. It caches on the instance and pickles with it, so one checksum covers the whole stream — including across a process boundary, where unpickling hands out a fresh axis per message and a cold one is re-checksummed by the first consumer in every receiving process, on every message, forever.

Dependencies

  • ezmsg>=3.9.0>=3.10.0b2, for AxisArray.chunk_dim and CoordinateAxis.fingerprint.
  • ezmsg-baseproc>=1.6.1>=1.12.0, for the axis-aware default state hash and the hash witness.

The baseproc bump changes reset semantics, so each stage now says what it wants

stage before now
BaselineDrift hashed (shape[1], time_gain) override deleted
DynamicColoredNoise hashed (shape[1], time_gain) override deleted
LineNoise hashed the "time" axis gain narrowed override kept, chunk-dim aware
CosineEncoder no override → constant 0 explicit backend-only hash

BaselineDrift / DynamicColoredNoise. Their hand-written hashes covered exactly what the default covers, minus the one thing that mattered: the channel fingerprint. Their delay lines, drift anchors and per-channel coefficients are one-per-channel, so a relabel at a fixed channel count was handing each new channel its predecessor's history.

LineNoise. Genuinely common-mode — every state array is (1, 1) and broadcasts over however many channels arrive — so the default would restart the phase accumulator for a sinusoid that did not change. The narrowing stays, but it now resolves the declared chunk dim instead of the literal name "time"; on a stream growing along anything else the old lookup silently yielded a sample period of zero.

CosineEncoder. Its tuning parameters come from settings alone (a file, or a seeded draw) and never from the message. Under the default hash, the first upstream relabel would redraw every channel's preferred direction and silently change the simulated population mid-run. The only thing _reset_state reads from the message is the array backend, so that is all the hash folds in.

That last one surfaced as two real failures the moment the dependency was bumped: test_directional_tuning and test_speed_modulation set transformer._hash = 0 to mean "treat this hand-set state as initialized", which only worked because the old default was 0. They now adopt transformer._hash_message(msg) and say so.

Tests

New tests/unit/test_state_reset_semantics.py, 19 tests: every producer declares its chunk dim and hands over a primed axis; the per-channel stages reset on a relabel at fixed count but not on chunk-size jitter; LineNoise does the opposite and reads a non-time chunk dim; CosineEncoder keeps its population and reuses its output axis object.

Mutation-tested — 10 mutations, all killed. The first version of the CosineEncoder redraw test was vacuous (seed=42 makes a redraw reproduce the same preferred directions); it uses seed=None so a reset is actually visible.

161 passed.

🤖 Generated with Claude Code

…l identity

Every producer here built its channel axis once per stream and reused the
object, but never said which dimension the stream grows along and never
computed the axis's fingerprint. Both now happen at construction:

* `chunk_dim="time"` on all five templates. Without it a consumer has to fall
  back to guessing `"time"`, which is right for these raw streams and wrong the
  moment anything windows them into `(win, time, ch)`.
* `ch_axis.fingerprint` touched once. It caches on the instance and pickles with
  it, so one checksum covers the whole stream -- including across a process
  boundary, where unpickling hands out a fresh axis per message and a cold one
  is re-checksummed by the first consumer in every receiving process, forever.

Requires ezmsg 3.10.0b2 for those two fields and baseproc 1.12.0 for the
axis-aware default state hash, which replaces the old reset-once-then-never
constant. That changes what the stateful stages here treat as a new stream, so
each one now says which it wants:

* BaselineDrift and DynamicColoredNoise drop their hand-written hashes. Both
  hashed `(shape[1], time_gain)`, which the default covers -- plus the channel
  *fingerprint*, which they were missing: their delay lines and drift anchors
  are one-per-channel, so a relabel at a fixed count was handing each new
  channel its predecessor's history.
* LineNoise keeps an explicit narrowing. Its state is all (1, 1) and broadcasts,
  so folding in channel identity would only restart the phase accumulator for a
  sinusoid that did not change. It now reads the declared chunk dim rather than
  the literal name "time", which on a stream that grows along anything else was
  silently yielding a sample period of zero.
* CosineEncoder gains a constant-except-for-backend hash. Its tuning parameters
  come from settings alone, so under the default an upstream relabel would
  redraw every preferred direction and silently change the simulated population
  mid-run.

tests/unit/test_state_reset_semantics.py pins all of the above; two assertions
in test_cosine_encoder.py stop asserting the old constant-0 hash.
@cboulay
cboulay merged commit d978d75 into dev Sep 4, 2026
14 checks passed
@cboulay
cboulay deleted the cboulay/chunk-dim-and-fingerprint branch September 4, 2026 19:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant