Skip to content

Refactor STDP weight update logic and improve error handling, requiring brainevent>=0.0.4#771

Merged
chaoming0625 merged 4 commits into
masterfrom
plasticity
Aug 7, 2025
Merged

Refactor STDP weight update logic and improve error handling, requiring brainevent>=0.0.4#771
chaoming0625 merged 4 commits into
masterfrom
plasticity

Conversation

@chaoming0625

@chaoming0625 chaoming0625 commented Aug 3, 2025

Copy link
Copy Markdown
Member

Summary by Sourcery

Refactor STDP weight update logic to use new JAX-based primitives, improve error handling for weight updates, update plasticity pipeline and tests for TrainingMode, and bump minimum versions of brainevent and braintools.

Enhancements:

  • Replace Taichi-based STDP kernels with csr_on_pre and csr2csc_on_post primitives and explicit shape arguments
  • Require variable weights in stdp_update and remove legacy constant node check
  • Convert spike and trace inputs to JAX arrays and access .value in plasticity update
  • Raise NotImplementedError for removed tracing_variable feature

Build:

  • Exclude docker*, examples, scripts, images, and dist from package discovery

Tests:

  • Instantiate connection layers with TrainingMode in STDP tests

Chores:

  • Bump brainevent to >=0.0.4 and braintools to >=0.0.7 in setup.py and requirements.txt
  • tracing_variable() is no longer supported

@sourcery-ai

sourcery-ai Bot commented Aug 3, 2025

Copy link
Copy Markdown

Reviewer's Guide

Refactors and centralizes STDP weight update logic by removing taichi-based kernels and enforcing variable type checks; updates plasticity projections and STDP tests to use JAX-compatible data access and TrainingMode; bumps minimum versions for brainevent and braintools; and deprecates tracing_variable support.

Class diagram for updated STDP weight update logic

classDiagram
    class STDPPlasticity {
        +update()
    }
    class Comm {
        +stdp_update(on_pre, on_post, w_min, w_max)
    }
    STDPPlasticity --> Comm : uses

    class Variable {
    }
    Comm --> Variable : checks W is Variable
Loading

Class diagram for deprecated tracing_variable and error handling

classDiagram
    class ObjectTransformBase {
        +tracing_variable(name, var, shape)
    }
    ObjectTransformBase : tracing_variable() now raises NotImplementedError
Loading

Class diagram for AllToAll and CSRLinear STDP update changes

classDiagram
    class AllToAll {
        +stdp_update(on_pre, on_post, w_min, w_max)
    }
    class CSRLinear {
        +stdp_update(on_pre, on_post, w_min, w_max)
    }
    AllToAll --> CSRLinear : similar STDP update logic
    AllToAll : uses csr_on_pre, csr2csc_on_post
    CSRLinear : uses csr_on_pre, csr2csc_on_post
Loading

File-Level Changes

Change Details Files
Refactor and simplify stdp_update in dnn/linear.py
  • Removed all taichi-backed kernel implementations and custom-op wrappers
  • Replaced on_pre and on_post update calls with csr_on_pre and csr2csc_on_post including explicit shape arguments
  • Consolidated weight validation: dropped constant-node check, now raising ValueError if W is not a bm.Variable
brainpy/_src/dnn/linear.py
Adapt plasticity projection stdp_update usage
  • Extract spike and trace values via .value
  • Wrap spike, trace, W_min and W_max with bm.as_jax
  • Restructure on_pre/on_post stdp_update calls with dict parameters
brainpy/_src/dyn/projections/plasticity.py
Update STDP tests to use TrainingMode and remove skip
  • Removed pytest.skip call from Test_STDP
  • Added mode=bm.TrainingMode() to all communication object constructors
brainpy/_src/dyn/projections/tests/test_STDP.py
Bump dependencies and refine package discovery
  • Extended find_packages exclude list to omit docs, tests, examples, scripts, and dist
  • Updated install_requires to require brainevent>=0.0.4 and braintools>=0.0.7
  • Set brainstate>=0.1.6 in requirements.txt
setup.py
requirements.txt
Deprecate tracing_variable in object_transform/base.py
  • Replaced tracing_variable code path with NotImplementedError and deprecation message
brainpy/_src/math/object_transform/base.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@chaoming0625

Copy link
Copy Markdown
Member Author

requiring brainevent>=0.0.4

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @chaoming0625 - I've reviewed your changes - here's some feedback:

  • Consider defaulting the mode parameter of your communication layers to TrainingMode so existing code (and tests) won’t have to be updated everywhere to explicitly pass mode=bm.TrainingMode().
  • Before turning tracing_variable into a hard NotImplementedError, it would be helpful to emit a deprecation warning in an earlier release to give users time to migrate.
  • Since you’ve removed the Taichi‐based STDP kernels entirely, you may want to provide a pure‐Python fallback or guard so users without Taichi/Braintaichi support still get CPU updates rather than a runtime error.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider defaulting the `mode` parameter of your communication layers to `TrainingMode` so existing code (and tests) won’t have to be updated everywhere to explicitly pass `mode=bm.TrainingMode()`.
- Before turning `tracing_variable` into a hard `NotImplementedError`, it would be helpful to emit a deprecation warning in an earlier release to give users time to migrate.
- Since you’ve removed the Taichi‐based STDP kernels entirely, you may want to provide a pure‐Python fallback or guard so users without Taichi/Braintaichi support still get CPU updates rather than a runtime error.

## Individual Comments

### Comment 1
<location> `brainpy/_src/dyn/projections/plasticity.py:212` </location>
<code_context>
         # post spikes
         if not hasattr(self.refs['post'], 'spike'):
             raise AttributeError(f'{self} needs a "spike" variable for the post-synaptic neuron group.')
-        post_spike = self.refs['post'].spike
+        post_spike = self.refs['post'].spike.value

         # weight updates
</code_context>

<issue_to_address>
Accessing .value on spike and trace is now required; ensure all code paths provide compatible objects.

If spike or trace are not always Variable-like, this change could raise AttributeError. Consider adding type checks or conversions to handle such cases.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

# post spikes
if not hasattr(self.refs['post'], 'spike'):
raise AttributeError(f'{self} needs a "spike" variable for the post-synaptic neuron group.')
post_spike = self.refs['post'].spike

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Accessing .value on spike and trace is now required; ensure all code paths provide compatible objects.

If spike or trace are not always Variable-like, this change could raise AttributeError. Consider adding type checks or conversions to handle such cases.

Comment on lines 29 to 61
if comm_method == 'all2all':
comm = bp.dnn.AllToAll(self.pre.num, self.post.num, weight=bp.init.Uniform(.1, 0.1))
comm = bp.dnn.AllToAll(
self.pre.num, self.post.num, weight=bp.init.Uniform(.1, 0.1),
mode=bm.TrainingMode()
)
elif comm_method == 'csr':
if syn_model == 'exp':
comm = bp.dnn.EventCSRLinear(bp.conn.FixedProb(1, pre=self.pre.num, post=self.post.num),
weight=bp.init.Uniform(0., 0.1))
comm = bp.dnn.EventCSRLinear(
bp.conn.FixedProb(1, pre=self.pre.num, post=self.post.num),
weight=bp.init.Uniform(0., 0.1),
mode=bm.TrainingMode()
)
else:
comm = bp.dnn.CSRLinear(bp.conn.FixedProb(1, pre=self.pre.num, post=self.post.num),
weight=bp.init.Uniform(0., 0.1))
comm = bp.dnn.CSRLinear(
bp.conn.FixedProb(1, pre=self.pre.num, post=self.post.num),
weight=bp.init.Uniform(0., 0.1),
mode=bm.TrainingMode()
)
elif comm_method == 'masked_linear':
comm = bp.dnn.MaskedLinear(bp.conn.FixedProb(1, pre=self.pre.num, post=self.post.num),
weight=bp.init.Uniform(0., 0.1))
comm = bp.dnn.MaskedLinear(
bp.conn.FixedProb(1, pre=self.pre.num, post=self.post.num),
weight=bp.init.Uniform(0., 0.1),
mode=bm.TrainingMode()
)
elif comm_method == 'dense':
comm = bp.dnn.Dense(self.pre.num, self.post.num, W_initializer=bp.init.Uniform(.1, 0.1))
comm = bp.dnn.Dense(
self.pre.num, self.post.num, W_initializer=bp.init.Uniform(.1, 0.1),
mode=bm.TrainingMode()
)
elif comm_method == 'one2one':
comm = bp.dnn.OneToOne(self.pre.num, weight=bp.init.Uniform(.1, 0.1))
comm = bp.dnn.OneToOne(self.pre.num, weight=bp.init.Uniform(.1, 0.1), mode=bm.TrainingMode())
else:
raise ValueError

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid conditionals in tests. (no-conditionals-in-tests)

ExplanationAvoid complex code, like conditionals, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

Comment on lines 35 to +46
if syn_model == 'exp':
comm = bp.dnn.EventCSRLinear(bp.conn.FixedProb(1, pre=self.pre.num, post=self.post.num),
weight=bp.init.Uniform(0., 0.1))
comm = bp.dnn.EventCSRLinear(
bp.conn.FixedProb(1, pre=self.pre.num, post=self.post.num),
weight=bp.init.Uniform(0., 0.1),
mode=bm.TrainingMode()
)
else:
comm = bp.dnn.CSRLinear(bp.conn.FixedProb(1, pre=self.pre.num, post=self.post.num),
weight=bp.init.Uniform(0., 0.1))
comm = bp.dnn.CSRLinear(
bp.conn.FixedProb(1, pre=self.pre.num, post=self.post.num),
weight=bp.init.Uniform(0., 0.1),
mode=bm.TrainingMode()
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid conditionals in tests. (no-conditionals-in-tests)

ExplanationAvoid complex code, like conditionals, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

raise ValueError(f'Cannot update the weight of a constant node.')
if not isinstance(self.W, bm.Variable):
self.tracing_variable('W', self.W, self.W.shape)
raise ValueError(f'When using STDP to update synaptic weights, the weight must be a variable.')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Replace f-string with no interpolated values with string (remove-redundant-fstring)

Suggested change
raise ValueError(f'When using STDP to update synaptic weights, the weight must be a variable.')
raise ValueError(
'When using STDP to update synaptic weights, the weight must be a variable.'
)

@chaoming0625

Copy link
Copy Markdown
Member Author

See chaobrain/brainevent#44

@chaoming0625 chaoming0625 merged commit 0dbb9b0 into master Aug 7, 2025
51 checks passed
@chaoming0625 chaoming0625 deleted the plasticity branch August 7, 2025 03:23
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