Skip to content

Make network.reset_state_variables() actually reach the learning rules - #794

Merged
Hananel-Hazan merged 1 commit into
masterfrom
fix/learning-rule-reset
Sep 7, 2026
Merged

Make network.reset_state_variables() actually reach the learning rules#794
Hananel-Hazan merged 1 commit into
masterfrom
fix/learning-rule-reset

Conversation

@Hananel-Hazan

Copy link
Copy Markdown
Collaborator

Supersedes #777. Co-authored with @saachigoyall, who found and diagnosed the bug.

What is wrong

AbstractFeature.reset_state_variables forwards the reset to self.learning_rule, and it is the only place that does. Every concrete feature overrode it with a bare pass and none called super(), so that forwarding line was unreachable.

The result: network.reset_state_variables() never reached any MulticompartmentConnection learning rule. Driving an MSTDPET connection for 250 steps and then resetting the network left every state variable untouched, including the two that MSTDPET.reset_state_variables already cleared.

On top of that, the rules themselves were incomplete. MSTDPET cleared 2 of its 6 variables (#777). MSTDP and PostPre cleared nothing at all.

Changes

bindsnet/network/topology_features.py

  • Drop the seven bare-pass overrides (Probability, Mask, MeanField, Weight, Bias, Intensity, Degradation) so they inherit the base implementation.
  • Drop @abstractmethod from AbstractFeature.reset_state_variables, which is why those overrides existed in the first place. This only relaxes the contract: a subclass may still override.
  • AdaptationBaseSynapsHistory and AdaptationBaseOtherSynaps keep their own reset logic and now chain to super() first.

bindsnet/learning/MCC_learning.py

Tests

Eight new cases in TestLearningRuleReset. Seven of the eight fail against master, so they genuinely pin the bug. The strongest one runs two identical episodes with a reset between them and requires identical resulting weights.

This replaces the test from #777, which passed tc_plus and average_update to Weight. Weight.__init__ has a fixed signature that never accepted them, so that test raised TypeError rather than running, on master and on its own branch alike.

Full suite: 91 passed (83 before). black --check and isort clean on the changed files.

Performance

Reset runs once per episode, not per time step, so the added clearing is off the hot path. Two edits do touch the per-step update path (hasattr to is None), so I measured: three interleaved A/B rounds against master, MSTDP fast path, MSTDPET, and MSTDP dense path, at n=64 and n=256. All differences under 1% with the sign varying between rounds, i.e. no measurable change.

🤖 Generated with Claude Code

… rules

Builds on @saachigoyall's fix. Her diagnosis was right: MSTDPET cleared
only 2 of its 6 state variables. But that change alone had no observable
effect, because the reset never reached any learning rule in the first
place.

AbstractFeature.reset_state_variables forwards to self.learning_rule, and
it is the only place that does. Every concrete feature overrode it with a
bare 'pass' and none called super(), so the forwarding line was
unreachable. After network.reset_state_variables() nothing was cleared,
not even the two variables MSTDPET already handled.

Features (topology_features.py):
  - Drop the seven bare-'pass' overrides (Probability, Mask, MeanField,
    Weight, Bias, Intensity, Degradation) so they inherit the base
    implementation, and drop @AbstractMethod from it, which is why those
    overrides existed at all.
  - The two adaptation features keep their own reset logic and now chain
    to super() first.

Rules (MCC_learning.py):
  - MSTDPET now also clears p_plus, p_minus and the moving-average buffer
    (@saachigoyall's change).
  - MSTDP cleared nothing. It now clears eligibility, p_plus, p_minus,
    the moving-average buffer, and the fast path's one-step spike lag.
    That lag is newer than this PR: without clearing it, the first step
    of an episode pairs with the last step of the previous one, which is
    the contamination this PR set out to fix.
  - PostPre cleared nothing; it now clears both averaging buffers.
  - Hebbian holds no state; its no-op is now documented as deliberate.
  - MSTDP's lazily-built state (_prev_source_s, _prev_target_s,
    eligibility) is declared None in __init__ and guarded with 'is None'
    rather than hasattr, so reset has something defined to restore.

Tests: replaces the original test, which passed tc_plus/average_update to
Weight (whose signature never accepted them) and so raised TypeError
rather than running. Eight cases now, seven of which fail without the
source change, including an end-to-end check that two identical episodes
separated by a reset produce identical weights.

Full suite 91 passed. Per-step update cost unchanged over three
interleaved A/B rounds at n=64 and n=256, all differences under 1% with
the sign varying between rounds.

Co-Authored-By: Saachi Goyal <156711741+saachigoyall@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Hananel-Hazan
Hananel-Hazan merged commit f12870f into master Sep 7, 2026
10 checks passed
@Hananel-Hazan
Hananel-Hazan deleted the fix/learning-rule-reset branch September 7, 2026 02:10
Hananel-Hazan added a commit that referenced this pull request Sep 7, 2026
Brings in the six commits master gained today: four dependency bumps, the
removal of the dead AbstractFeature.degrade hook (#793), the learning-rule
reset fix (#794), and its follow-up for MSTDP's lazily built state (#795).

No textual conflicts. The two files both sides touched, MCC_learning.py and
topology_features.py, merged cleanly and the result is correct in both
directions: this branch's perf work (the MSTDP fast path, the fold cache,
the cached decay tensors) is intact, and master's reset fix reaches the
learning rules through the feature chain as intended.

Full suite on the merge: 182 passed, which includes this branch's
test_perf_equivalence.py and test_learning_rule_specs.py alongside
master's new reset tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Hananel-Hazan added a commit that referenced this pull request Sep 7, 2026
DiehlAndCook arrived on this branch while the reset audit (#794) was
happening on master, so it kept the bare 'return' that the audit removed
everywhere else.

It holds no state between steps: each update comes from the source trace,
the target spikes and the current weight, exactly like Hebbian. So the
no-op is correct, but it now says so rather than looking like the
oversight the other rules turned out to be.

Extends the two reset tests to cover Hebbian and DiehlAndCook as well.
Full suite 186 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Hananel-Hazan added a commit that referenced this pull request Sep 7, 2026
Follow-up to #794. MSTDP creates p_plus and p_minus lazily on the first
update, because only then are the batch size and device known. #794 made
reset_state_variables zero them unconditionally, so calling
network.reset_state_variables() before the first run raised
AttributeError: 'MSTDP' object has no attribute 'p_plus'. Building a
network and resetting it before the first episode is a normal thing to
do, so this was reachable.

Declare p_plus and p_minus as None in __init__ alongside the other lazily
built state, switch the update path's hasattr guards to 'is None' to
match, and have the reset skip whatever has not been built.

MSTDPET was never affected: it builds both in __init__.

New test parametrised over MSTDP, MSTDPET and PostPre resets a freshly
built network before running it. It fails for MSTDP without this change.
Full suite 94 passed.

Co-authored-by: Claude Opus 5 <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