Skip to content

Extract BinSchedule: shared bin-boundary primitive#158

Merged
cboulay merged 2 commits into
generalize-binnerfrom
shared-bin-schedule
Jun 26, 2026
Merged

Extract BinSchedule: shared bin-boundary primitive#158
cboulay merged 2 commits into
generalize-binnerfrom
shared-bin-schedule

Conversation

@cboulay

@cboulay cboulay commented Jun 26, 2026

Copy link
Copy Markdown
Member

Hoist the bin-boundary arithmetic out of BinnedAggregateTransformer into a reusable, backend-agnostic BinSchedule (ezmsg.sigproc.util.binning) so that every consumer that needs fixed-duration binning lands on the same grid by sharing one schedule rather than re-deriving the formula.

  • BinSchedule owns fs, samples-per-bin, output gain/offset, the global bin index, and the carried sample count; it touches no array data. Consumers layer their own data carry (raw samples here; a scalar partial-sum for an event counter) on top of the shared boundaries.
  • BinnedAggregateTransformer now delegates all boundary/gain/offset math to BinSchedule.advance() and only slices+aggregates at the returned cut points.
  • Integer mode now truncates (int) instead of round() so the sample-locked grid matches Window's int(window_dur*fs) for any rate, not just when the fractional part is < 0.5.
  • test_bin_schedule.py pins fractional=True against a faithful port of EventRate's accumulator algorithm and fractional=False against Window's grid, across chunkings and at fs in {30000, 30012, 30030}. The 30030 case (spb=600.6) is where truncation vs rounding diverges.

This converts the previous "agreement with EventRate by construction, untested" into shared code with a regression test, and sets up EventRate (ezmsg-event) to delegate to the same primitive.

cboulay added 2 commits June 26, 2026 00:15
Hoist the bin-boundary arithmetic out of BinnedAggregateTransformer into a
reusable, backend-agnostic BinSchedule (ezmsg.sigproc.util.binning) so that
every consumer that needs fixed-duration binning lands on the same grid by
sharing one schedule rather than re-deriving the formula.

- BinSchedule owns fs, samples-per-bin, output gain/offset, the global bin
  index, and the carried sample count; it touches no array data. Consumers
  layer their own data carry (raw samples here; a scalar partial-sum for an
  event counter) on top of the shared boundaries.
- BinnedAggregateTransformer now delegates all boundary/gain/offset math to
  BinSchedule.advance() and only slices+aggregates at the returned cut points.
- Integer mode now truncates (int) instead of round() so the sample-locked
  grid matches Window's int(window_dur*fs) for any rate, not just when the
  fractional part is < 0.5.
- test_bin_schedule.py pins fractional=True against a faithful port of
  EventRate's accumulator algorithm and fractional=False against Window's
  grid, across chunkings and at fs in {30000, 30012, 30030}. The 30030 case
  (spb=600.6) is where truncation vs rounding diverges.

This converts the previous "agreement with EventRate by construction, untested"
into shared code with a regression test, and sets up EventRate (ezmsg-event) to
delegate to the same primitive.
test_concat_along_ch drove its coroutine via asyncio.get_event_loop()
.run_until_complete(), which raises "no current event loop" on Python 3.10+
once any earlier test in the suite has called asyncio.run() (which clears the
loop policy). It passed in isolation but failed after, e.g.,
test_binned_aggregate's asyncio.run()-based test. Use asyncio.run(), which is
self-contained and order-independent.
@cboulay cboulay merged commit 2f12c6c into generalize-binner Jun 26, 2026
12 checks passed
@cboulay cboulay deleted the shared-bin-schedule branch June 26, 2026 04:52
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