Skip to content

RangedAggregateTransformer: bands is missing from _hash_message #196

Description

@cboulay

Summary

RangedAggregateTransformer._hash_message hashes only message metadata:

def _hash_message(self, message: AxisArray) -> int:
    axis = self.settings.axis or message.dims[0]
    target_axis = message.get_axis(axis)

    hash_components = (message.key,)
    if hasattr(target_axis, "data"):
        hash_components += (len(target_axis.data),)
    elif isinstance(target_axis, AxisArray.LinearAxis):
        hash_components += (target_axis.gain, target_axis.offset)
    return hash(hash_components)

But _reset_state derives self._state.slices and self._state.out_axis from self.settings.bands. Since bands is not in the hash, changing it at runtime without also changing the message metadata leaves the cached slices in place, and _process keeps aggregating the old bands while reporting the new ones in settings.

update_settings covers the common path — NONRESET_SETTINGS_FIELDS is empty on this class, so a bands change through it calls _request_reset(). The gap is direct assignment:

proc.settings = dataclasses.replace(proc.settings, bands=[(10.0, 20.0)])
# no reset queued; the next message is aggregated with the previous bands

Second case: the passthrough shortcut

def __call__(self, message: AxisArray) -> AxisArray:
    # Override for shortcut passthrough mode.
    if self.settings.bands is None:
        return message
    return super().__call__(message)

Setting bands = None returns before hashing, so _hash is frozen at its pre-passthrough value. Setting bands back to a different list than before resumes with the slices from the original list.

Unlike the EWMA/scaler case (see #195), this is not about accumulated signal state — slices, out_axis, and ax_vec are all derived, so there is no discontinuity to reason about, just a stale cache producing wrong output.

Suggested fix

Add bands to the hash:

hash_components = (message.key, self.settings.bands)

bands is a list[tuple[float, float]], so it needs tuple(...) (or None) to be hashable. Requesting a reset in the bands is None branch of __call__ would also close the passthrough case.

Test to add

Assert that assigning new bands directly (not via update_settings) changes the output, and that toggling bands to None and back to a different list does not reuse the first list's slices.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions