Skip to content

Derive frozen-rows transition mask lazily so it stays out of vars()/get_params()#43

Open
edeno wants to merge 1 commit into
mainfrom
spyglass-serialization-compat
Open

Derive frozen-rows transition mask lazily so it stays out of vars()/get_params()#43
edeno wants to merge 1 commit into
mainfrom
spyglass-serialization-compat

Conversation

@edeno

@edeno edeno commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Problem

_DetectorBase.__init__ stores _frozen_discrete_transition_rows_mask_ -- a
boolean row mask derived from the frozen_discrete_transition_rows constructor
argument -- as an instance attribute. Because it is a normal instance attribute,
it appears in both vars(model) and get_params(), even though it is not a
constructor parameter.

This breaks serialization round-trips that reconstruct a model from its
attributes. For example, Spyglass's DecodingParameters stores vars(model)
and later reconstructs the model with Detector(**params); the derived mask is
passed back into __init__ and raises:

TypeError: __init__() got an unexpected keyword argument '_frozen_discrete_transition_rows_mask_'

Any consumer that treats a model's public attributes as its constructor
parameters hits the same problem, and it will recur whenever a new derived
attribute is added in __init__.

Solution

Compute the mask lazily via a read-only _frozen_discrete_transition_rows_mask_
property (recomputed from frozen_discrete_transition_rows and the state count
on access) instead of storing it as an instance attribute in __init__. The
up-front validation of frozen_discrete_transition_rows against n_states is
kept, so invalid input still raises at construction time. The derived value is
unchanged; it simply no longer appears in vars() / get_params().

Validation

  • Existing constructor-plumbing and slow end-to-end EM frozen-row tests pass
    unchanged (behavior preserved, including byte-exact frozen rows after EM).
  • New tests assert the mask is absent from vars() and get_params() for the
    default models, that the property still returns the correct mask (and None
    when no rows are frozen), and that a ContFrag model's vars() now
    reconstructs through the base detector.
  • ruff check / ruff format clean.

Risks / possible issues

  • The mask is recomputed on each access rather than cached. It is read only a
    handful of times per fit/predict (discrete transition-matrix construction) and
    _normalize_frozen_discrete_transition_rows is inexpensive, so there is no
    meaningful performance impact; it is not on a per-timestep hot path.
  • Validation timing is unchanged: invalid frozen_discrete_transition_rows is
    still rejected at construction.
  • This change alone fixes vars()/get_params()-based consumers for the base
    and ContFrag models. The NonLocal* models additionally expose subclass-only
    parameters (non_local_position_penalty, non_local_penalty_std); a paired
    Spyglass change reconstructs via the concrete model class so those round-trip
    as well. The two changes are independently beneficial.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

_frozen_discrete_transition_rows_mask_ was stored as an instance attribute in
__init__, so it leaked into vars() and get_params(). Serialization round-trips
that rebuild a model from its public parameters (e.g. Spyglass's
DecodingParameters, which stores vars(model) and reconstructs with
Detector(**params)) then passed it back into __init__ as an unexpected keyword
argument and raised TypeError.

Compute the mask lazily via a read-only property instead (recomputed from
frozen_discrete_transition_rows on access); keep the up-front validation in
__init__ so bad input still raises at construction. Behavior is unchanged
(existing constructor + slow EM frozen-row tests pass); the derived attribute
no longer appears in vars()/get_params(), so ContFrag* defaults now reconstruct
cleanly through the base detector.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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