Skip to content

Fix AbstractFeature.degrade calling itself instead of degrade_function - #792

Closed
Anai-Guo wants to merge 1 commit into
BindsNET:masterfrom
Anai-Guo:fix-abstractfeature-degrade-recursion
Closed

Fix AbstractFeature.degrade calling itself instead of degrade_function#792
Anai-Guo wants to merge 1 commit into
BindsNET:masterfrom
Anai-Guo:fix-abstractfeature-degrade-recursion

Conversation

@Anai-Guo

@Anai-Guo Anai-Guo commented Sep 4, 2026

Copy link
Copy Markdown

Problem

AbstractFeature.degrade calls itself:

def degrade(self) -> None:
    """
    Degrade the value of the propagated spikes according to the features value. A lambda function should be passed
    into the constructor which takes a single argument (which represent the value), and returns a value which will
    be *subtracted* from the propagated spikes.
    """

    return self.degrade(self.value)

degrade takes no arguments after self, so this is not even infinite recursion — it raises on the very first call, for every feature, including Degradation:

>>> from bindsnet.network.topology_features import Degradation
>>> f = Degradation(name="d", value=torch.tensor([[0.4, 0.8]]),
...                 degrade_function=lambda v: v * 0.5)
>>> f.compute(None)
tensor([[0.2000, 0.4000]])
>>> f.degrade()
TypeError: AbstractFeature.degrade() takes 1 positional argument but 2 were given

No other class overrides degrade, so there is no code path on which it works.

Fix

The docstring describes exactly the callable that Degradation.__init__ stores as degrade_function ("a lambda ... which takes a single argument (which represent the value), and returns a value which will be subtracted from the propagated spikes"), and Degradation.compute already contains the intended form a few hundred lines below:

def compute(self, s) -> Union[torch.Tensor, float, int]:
    # Subtractive offset (via degrade_function) applied to every synapse.
    if self.degrade_function is not None:
        return self.degrade_function(self.value)
    return self.value

So this is a one-word slip: self.degrade(...)self.degrade_function(...). One line changed, plus a regression test next to the existing test_degradation_feature_output.

test/network/test_connections.py  25 passed   (24 before, all still passing)

The new test fails on master with the TypeError above and passes with the fix.

One thing worth your call

degrade_function only exists on Degradation, so calling degrade() on some other feature (Weight, Mask, …) now raises AttributeError instead of the current TypeError. That seemed like the smaller change to make, but if you would rather the method live on Degradation — or guard with if self.degrade_function is not None the way compute does — say the word and I will move it.

🤖 Generated with Claude Code

…of degrade_function

degrade() ended in `return self.degrade(self.value)`, so the first call raises
TypeError: AbstractFeature.degrade() takes 1 positional argument but 2 were
given. Degradation.compute already has the correct form,
`return self.degrade_function(self.value)`.

Signed-off-by: Tai An <antai12232931@outlook.com>
@Hananel-Hazan

Copy link
Copy Markdown
Collaborator

Thanks @Anai-Guo — your diagnosis is exactly right, and I confirmed every part of it. degrade calls itself with an argument it does not accept, so it raises TypeError on the first call for every feature.

I went one step further than the fix, for the reason you raised yourself at the end of your description.

Two things decided it. First, git log -L 286,295:bindsnet/network/topology_features.py returns a single commit, 4b577e9 "Add topology_features.py" — the self-call has been there since the file was created, so the method has never run successfully once. Second, grepping the whole repo across all file types, nothing calls it: the only occurrences of degrade as a method are the definition and the broken self-call inside it.

It looks like an intended third per-feature lifecycle hook alongside update and normalize, which MulticompartmentConnection does call over its pipeline in topology.py. The matching for f in self.pipeline: f.degrade() loop was never written, and degradation ended up in Degradation.compute instead — which is where the pipeline applies it, and which already guards against degrade_function being None. So fixing the typo would leave two spellings of the same operation, the fixed one being the unguarded one, plus a base-class method only Degradation can satisfy — the AttributeError you flagged.

So #793 removes the method instead. Closing this in favour of that, but the bug is yours — the PR credits you and links back here. Please do send more; this was a good catch and a well-written report.

Hananel-Hazan added a commit that referenced this pull request Sep 7, 2026
AbstractFeature.degrade called itself with an argument it does not
accept, so it raised TypeError on the first call for every feature. It
has been that way since 4b577e9 added topology_features.py, and nothing
in the repo has ever called it.

It looks like an intended third per-feature lifecycle hook alongside
update and normalize, which MulticompartmentConnection does call over
its pipeline. The matching 'for f in self.pipeline: f.degrade()' loop
was never written; degradation lives in Degradation.compute instead,
which is where the pipeline applies it and which already guards against
degrade_function being None.

Fixing the method would leave two spellings of the same operation, one
of them unguarded, so remove it. Reported by @Anai-Guo in #792.

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.

2 participants