Skip to content

fix(brainpy): repair 18 correctness bugs found in a full-package audit#868

Merged
chaoming0625 merged 6 commits into
masterfrom
worktree-audit-fixes-20260708
Jul 9, 2026
Merged

fix(brainpy): repair 18 correctness bugs found in a full-package audit#868
chaoming0625 merged 6 commits into
masterfrom
worktree-audit-fixes-20260708

Conversation

@chaoming0625

@chaoming0625 chaoming0625 commented Jul 9, 2026

Copy link
Copy Markdown
Member

Summary

A systematic audit of the whole brainpy/ package surfaced 18 confirmed
correctness defects
, spanning 14 source modules. This PR fixes all of them,
each with a co-located regression test (sibling *_test.py, TDD-style).

Severity breakdown: 3 Critical, 6 High, 6 Medium, 3 Low.

The adaptive-pooling upsampling crash found by the same audit was fixed
independently on master in #867, so it is not duplicated here.

Highlights

  • LayerNorm normalized over the wrong axes (batch/time instead of the
    trailing normalized_shape).
  • Stratonovich Euler-Heun SDE predictor was off by a factor of sqrt(dt).
  • WeightedPhaseEncoder emitted no spikes (integer-power weight collapse).
  • MomentumNesterov was silently identical to plain Momentum; Adadelta
    ignored its learning rate.
  • Ridge / LogisticRegression offline solvers returned untrained weights /
    crashed under current JAX.

Full list

Critical

  • dnn/normalization: LayerNorm reduced over leading batch/time axes instead of the trailing normalized_shape.
  • integrators/sde/normal: Stratonovich Euler-Heun predictor used raw noise instead of the sqrt(dt)-scaled Wiener increment.
  • encoding/stateful_encoding: WeightedPhaseEncoder integer power base collapsed every phase weight to 0 (never spiked).

High

  • initialize/random_inits: TruncatedNormal(lower=None, upper=None) raised TypeError; default to 2-sigma, treat None as ±inf.
  • initialize/decay_inits: DOGDecay produced an asymmetric matrix on non-square grids and never applied its normalize flag.
  • connect/random_conn: FixedProb.build_csr built a malformed CSR indptr for pre_ratio<1; FixedTotalNum raised TypeError on a fractional num.
  • optim/optimizer: MomentumNesterov was byte-identical to plain Momentum (no look-ahead).
  • dynold STP (synplast + experimental): the facilitation ODE carried a spurious +U source term, so u drifted with no spikes.
  • algorithms/offline: the Ridge/Regression IRLS while_loop seeded within tolerance and returned untrained weights.

Medium

  • connect/regular_conn: GridConn periodic boundary produced duplicate COO edges on small grids.
  • math/object_transform/autograd: vector_grad parameterized-decorator form (func=None) crashed.
  • optim/optimizer: Adadelta silently ignored its learning rate.
  • runners: DSRunner.predict dropped shared_args in the memory-efficient path.
  • algorithms/offline: LogisticRegression Newton path tripped the JAX __jax_array__-during-abstractification error under while_loop.
  • train/online: OnlineTrainer._fit transposed histories unconditionally — crashed on scalar monitors, wrong layout in data_first_axis='T'.

Low

  • initialize/random_inits: _compute_fans raised IndexError on 0-D/1-D shapes (broke VarianceScaling on bias vectors).
  • optim/optimizer: LARS zero-norm guard used maximum (could exceed trust ratio); switched to where.
  • train/back_propagation: fit rejected a valid iterable of two (x, y) batches as the removed bare (X, Y) API.

Also included

This branch was reconciled with local master, which additionally removes three
legacy, superseded test files (brainpy/math/numpy_{einsum,indexing,ops}_test.py).
They appear in this diff because they were not yet on the remote master.

Testing

  • All 18 fixes have new regression tests reproducing the bug.
  • Full brainpy/ suite green (4248 passed, 0 failed, 12 skipped).

Summary by Sourcery

Fix multiple correctness issues across optimization, initialization, normalization, connectivity, encoding, integrators, training, runners, autograd, and offline learning components in the brainpy package, adding regression tests for each bug and cleaning up legacy numpy-based tests.

Bug Fixes:

  • Correct MomentumNesterov and Adadelta optimizers and fix LARS trust-ratio guarding for degenerate norms.
  • Fix truncated normal initialization defaults and low-rank fan computation, and repair DOGDecay symmetry and normalization behavior.
  • Ensure LayerNorm normalizes over trailing feature axes and Stratonovich Euler-Heun SDE integrator uses the properly scaled Wiener increment.
  • Fix WeightedPhaseEncoder default weights so phase coding produces spikes and correctly reconstructs inputs.
  • Repair FixedProb CSR construction under pre_ratio<1 and FixedTotalNum handling of fractional num, and remove duplicate edges in periodic Grid connectors.
  • Correct short-term plasticity facilitation dynamics in both legacy and experimental STP implementations so facilitation decays without spikes.
  • Make vector_grad support decorator usage, and prevent LogisticRegression Newton path from triggering forbidden JAX jax_array usage, while ensuring LinearRegression gradient-descent path actually trains.
  • Ensure DSRunner memory-efficient predict path forwards shared_args and OnlineTrainer preserves time-major monitor layouts and supports scalar monitors without crashing.
  • Relax BPTT fit data validation to accept iterable datasets of two (x, y) batches instead of misclassifying them as the removed raw (X, Y) API.

Tests:

  • Add focused regression tests for each repaired bug across optimizers, initializers, connectors, normalization, encoding, SDE integrators, plasticity modules, autograd, runners, and trainers.
  • Remove obsolete numpy_* test modules now superseded by JAX-based or other test coverage.

A systematic audit of the brainpy/ package surfaced 18 confirmed defects
across dnn, integrators, encoding, initialize, connect, optim, dynold,
algorithms, math, runners and train. Each fix ships with a co-located
regression test (sibling *_test.py).

(The adaptive-pooling upsampling crash surfaced by the same audit was fixed
independently on master in #867, so it is not duplicated here.)

Critical
- dnn/normalization: LayerNorm reduced over the leading batch/time axes
  instead of the trailing normalized_shape dimensions.
- integrators/sde/normal: Stratonovich Euler-Heun predictor used the raw
  standard-normal noise instead of the sqrt(dt)-scaled Wiener increment,
  making it inconsistent with the corrector.
- encoding/stateful_encoding: WeightedPhaseEncoder used an integer power
  base 2 ** (-k), which truncates to 0 under int tracing and collapsed
  every phase weight to zero (the encoder never spiked).

High
- initialize/random_inits: TruncatedNormal(lower=None, upper=None) raised
  TypeError; default to 2-sigma truncation, treat None as +/- inf.
- initialize/decay_inits: DOGDecay produced an asymmetric connectivity
  matrix on non-square grids (bad meshgrid moveaxis) and never applied its
  normalize flag.
- connect/random_conn: FixedProb.build_csr built a structurally-invalid
  CSR indptr when pre_ratio < 1; FixedTotalNum raised TypeError on a
  fractional num.
- optim/optimizer: MomentumNesterov was byte-identical to plain Momentum
  (missing the Nesterov look-ahead step).
- dynold (synplast + experimental) STP: the continuous facilitation ODE
  carried a spurious +U source term, so u drifted toward U*tau_f even with
  no presynaptic spikes.
- algorithms/offline: the Ridge/Regression IRLS while_loop seeded par_old
  within allclose tolerance of par_new, so it exited immediately and
  returned the untrained initial weights.

Medium
- connect/regular_conn: GridConn periodic boundary produced duplicate
  (pre, post) COO edges on small grids (e.g. 2x2 GridFour double-counted).
- math/object_transform/autograd: vector_grad used as a parameterized
  decorator (func=None) crashed instead of returning a decorator.
- optim/optimizer: Adadelta silently ignored its learning rate.
- runners: DSRunner.predict dropped shared_args in the memory-efficient path.
- algorithms/offline: LogisticRegression Newton path built its diagonal via
  in-place bm.Array assignment, tripping the JAX __jax_array__-during-
  abstractification error under while_loop.
- train/online: OnlineTrainer._fit transposed histories unconditionally,
  crashing on scalar monitor histories and returning batch-major output in
  data_first_axis='T' mode.

Low
- initialize/random_inits: _compute_fans raised IndexError on 0-D/1-D
  shapes, breaking every VarianceScaling init on bias vectors.
- optim/optimizer: LARS zero-norm guard used maximum (could exceed the
  trust ratio); switched to where to clamp to exactly 1.0.
- train/back_propagation: fit rejected a valid iterable dataset holding
  exactly two (x, y) batches as if it were the removed bare (X, Y) API.
…atio<1

Two coverage tests pinned the pre-fix malformed CSR (a truncated indptr of
length int(pre_num*pre_ratio)+1). Update them to assert the correct CSR shape
(pre_num+1 entries, non-selected pre rows have zero out-degree) that the H4 fix
now produces, plus internal indptr/indices consistency.
@sourcery-ai

sourcery-ai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Reviewer's Guide

This PR fixes 18 audited correctness bugs across optimizers, initialization, connectivity, normalization, runners, training, encoders, SDE integrators, STP dynamics, autograd, and offline algorithms, with co-located regression tests and some legacy test cleanup.

Sequence diagram for DSRunner.predict shared_args propagation

sequenceDiagram
    actor User
    participant DSRunner
    participant _fun_predict
    participant run_fun_step

    User->>DSRunner: predict(inputs, shared_args)
    DSRunner->>_fun_predict: _fun_predict(indices, *inputs, shared_args)
    _fun_predict->>_fun_predict: choose _jit_step_func_predict or _step_func_predict
    _fun_predict->>run_fun_step: functools.partial(run_fun_step, shared_args=shared_args)
    loop over time indices
        _fun_predict->>run_fun_step: run_fun_step(i, *inputs, shared_args)
        run_fun_step-->>_fun_predict: step_outputs
    end
    _fun_predict-->>DSRunner: stacked step_outputs
    DSRunner-->>User: predictions
Loading

Sequence diagram for Stratonovich Euler-Heun SDE step noise scaling

sequenceDiagram
    participant SDEIntegrator as NormalSDEIntegrator
    participant Random as bm_random
    participant Diffusion as g

    SDEIntegrator->>SDEIntegrator: step(*args, **kwargs)
    SDEIntegrator->>bm_random: randn(*shape)
    bm_random-->>SDEIntegrator: noise
    SDEIntegrator->>SDEIntegrator: all_noises[key] = noise * sqrt(dt)
    alt VECTOR_WIENER
        SDEIntegrator->>Diffusion: diffusions[key]
        Diffusion-->>SDEIntegrator: diffusions[key]
        SDEIntegrator->>SDEIntegrator: y_bar = all_args[key] + sum(diffusions[key] * all_noises[key])
    else
        SDEIntegrator->>Diffusion: diffusions[key]
        Diffusion-->>SDEIntegrator: diffusions[key]
        SDEIntegrator->>SDEIntegrator: y_bar = all_args[key] + diffusions[key] * all_noises[key]
    end
    SDEIntegrator->>Diffusion: diffusion_bars = g(**all_args_bar)
    Diffusion-->>SDEIntegrator: diffusion_bars
    SDEIntegrator-->>SDEIntegrator: corrector step using diffusion_bars
Loading

File-Level Changes

Change Details Files
Correct LayerNorm axis selection to normalize over trailing normalized_shape dimensions.
  • Change LayerNorm reduction axes from leading batch/time axes to trailing normalized_shape axes.
  • Add regression test that compares LayerNorm output to explicit per-block normalization and checks zero-mean/unit-variance per block.
brainpy/dnn/normalization.py
brainpy/dnn/normalization_test.py
Fix Stratonovich Euler-Heun SDE integrator to use the scaled Wiener increment consistently in the predictor.
  • Scale noise by sqrt(dt) and use the scaled increment in both predictor and corrector paths for vector and scalar Wiener types.
  • Add geometric Brownian motion regression test verifying weak convergence to analytical mean and divergence from buggy behavior.
brainpy/integrators/sde/normal.py
brainpy/integrators/sde/normal_test.py
Repair WeightedPhaseEncoder default weights so phase weights are nonzero and encoding is invertible.
  • Change default weight_fun base to float (2.0**-n) to avoid integer-power collapse to zero.
  • Add tests that default weights are positive and that decoding the spike train reconstructs the normalized input within tolerance.
brainpy/encoding/stateful_encoding.py
brainpy/encoding/stateful_encoding_test.py
Fix optimizer bugs in MomentumNesterov, Adadelta, and LARS including proper Nesterov lookahead, honoring learning rate, and correct degenerate-norm guard.
  • Implement Nesterov lookahead update using momentumv - lrg instead of plain momentum velocity.
  • Change Adadelta default lr to 1.0 and scale parameter updates by lr while keeping accumulators unscaled.
  • Replace jnp.maximum-based guard in LARS with jnp.where to set trust ratio to 1 when p_norm or g_norm is zero.
  • Add focused regression tests for MomentumNesterov behavior, Adadelta learning rate scaling, and LARS zero-gradient guard.
brainpy/optim/optimizer.py
brainpy/optim/optimizer_test.py
Make initialization utilities robust: support low-rank shapes in _compute_fans, and define safe defaults/None semantics for TruncatedNormal bounds.
  • Add explicit handling for 0-D/1-D shapes in _compute_fans, following JAX convention with fan_in==fan_out==size and returning numpy scalars.
  • Change TruncatedNormal default lower/upper to ±2 and treat explicit None as ±inf, avoiding TypeError in bound arithmetic.
  • Add regression tests for TruncatedNormal default and None bounds and for VarianceScaling initializers on 1-D shapes via _compute_fans.
brainpy/initialize/random_inits.py
brainpy/initialize/random_inits_test.py
Correct DOGDecay initialization to produce symmetric kernels on rectangular grids and honor the normalize flag.
  • Align voxel_ids meshgrid/flatten order with post_values to maintain symmetric distance-based weights on non-square grids.
  • Apply normalize flag by scaling weights to unit peak magnitude before removing self-connections and thresholding.
  • Add tests that DOGDecay is symmetric on various rectangular grids and that normalize rescales the kernel to unit peak while preserving shape.
brainpy/initialize/decay_inits.py
brainpy/initialize/decay_inits_test.py
Fix random connectivity builders FixedProb and FixedTotalNum to produce valid CSR/COO outputs for partial pre_ratio and fractional num.
  • Route FixedProb.build_csr with pre_ratio<1 through coo2csr over full pre_num, ensuring indptr length pre_num+1 and internal consistency.
  • Interpret float num in FixedTotalNum as fraction of all-to-all connection count, coercing to int and using consistently across RNG calls.
  • Update tests to assert valid CSR structure and correct edge counts for both float and int num cases and for boost_connect coverage paths.
brainpy/connect/random_conn.py
brainpy/connect/random_conn_test.py
brainpy/connect/boost_connect_test.py
brainpy/connect/random_conn_coverage_test.py
Ensure regular grid connectors avoid duplicate edges under periodic boundary conditions.
  • Post-process periodic Grid* connections by de-duplicating (pre, post) COO pairs via np.unique while preserving order.
  • Add regression tests that periodic GridFour/GridEight/GridN produce no duplicate edges on small grids.
brainpy/connect/regular_conn.py
brainpy/connect/regular_conn_test.py
Fix DSRunner and OnlineTrainer to correctly propagate shared_args and handle monitor history layouts/scalar monitors.
  • In DSRunner memory_efficient predict path, wrap step function with functools.partial to forward shared_args to each call.
  • In OnlineTrainer._fit, only moveaxis time->batch when data_first_axis=='B' and skip moveaxis for <2-D leaves to avoid axis errors.
  • Add new DSRunner regression tests for shared_args under memory_efficient and standard paths, and OnlineTrainer tests for time-major histories and scalar monitors.
brainpy/runners.py
brainpy/runners_test.py
brainpy/train/online.py
brainpy/train/online_test.py
Allow vector_grad to be used as a parameterized decorator without crashing.
  • Detect func is None in vector_grad and return a decorator that re-invokes vector_grad with the provided parameters on the target function.
  • Add tests covering @bm.vector_grad(argnums=...) decorator usage and equivalence between decorator and direct-call forms.
brainpy/math/object_transform/autograd.py
brainpy/math/object_transform/autograd_test.py
Correct short-term plasticity facilitation dynamics to be purely decaying between spikes in both legacy and experimental STP implementations.
  • Change du/dt from U - u/tau_f to -u/tau_f in dynold/synplast and experimental syn_plasticity STP classes, keeping discrete jumps on spikes in update.
  • Adjust tests to use exponential decay for u between spikes and add regressions showing u decays without spikes and stays non-negative.
  • Update analytical checks in tests to match corrected dynamics for du and dx.
brainpy/dynold/synplast/short_term_plasticity.py
brainpy/dynold/synplast/short_term_plasticity_test.py
brainpy/dynold/experimental/syn_plasticity.py
brainpy/dynold/experimental/syn_plasticity_test.py
Fix offline Ridge/LinearRegression and LogisticRegression while_loop behavior to actually train and be compatible with current JAX.
  • Seed LinearRegression gradient-descent while_loop with par_old far from par_new (w+1.), ensuring at least one iteration and avoiding immediate convergence with untrained weights.
  • Replace bm.zeros+in-place diagonal construction in LogisticRegression Newton path with jnp.diag over gradient to avoid jax_array during abstractification.
  • Add tests confirming LinearRegression gradient-descent path recovers known slope and LogisticRegression Newton path runs, produces finite weights, and recovers coefficient signs.
brainpy/algorithms/offline.py
brainpy/algorithms/offline_test.py
Relax BPTT.fit input validation to accept two-element iterable datasets of (x,y) batches while still rejecting the removed bare (X,Y) API.
  • Refine length-2 train_data guard to only reject when both elements are raw tensors/arrays/dicts, allowing lists of (x,y) batches to pass.
  • Add regression test that a list of two (X,Y) batches trains successfully and populates train_losses.
brainpy/train/back_propagation.py
brainpy/train/back_propagation_test.py
Remove legacy numpy-based math tests that have been deleted on master.
  • Delete superseded test files numpy_einsum_test.py, numpy_indexing_test.py, and numpy_ops_test.py from brainpy/math.
  • Align branch with local master’s test suite cleanup.
brainpy/math/numpy_einsum_test.py
brainpy/math/numpy_indexing_test.py
brainpy/math/numpy_ops_test.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

@github-actions github-actions Bot added the tests label Jul 9, 2026

@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 - I've left some high level feedback:

  • The periodic-boundary de-duplication in GridConn.build_coo() uses np.unique on the full (pre, post) pair array, which could be quite expensive on large grids; consider a more incremental or JAX-native de-duplication strategy to avoid the extra host round-trip and quadratic-ish behavior on big connectivity sets.
  • The new FixedTotalNum handling for float num rounds num * mat_element_num to an integer; if callers expect a strict fraction or a maximum count, you may want to clamp (e.g., floor and enforce a minimum of 1) or explicitly document the rounding semantics to avoid surprising over- or under-selection on edge cases.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The periodic-boundary de-duplication in `GridConn.build_coo()` uses `np.unique` on the full `(pre, post)` pair array, which could be quite expensive on large grids; consider a more incremental or JAX-native de-duplication strategy to avoid the extra host round-trip and quadratic-ish behavior on big connectivity sets.
- The new `FixedTotalNum` handling for float `num` rounds `num * mat_element_num` to an integer; if callers expect a strict fraction or a maximum count, you may want to clamp (e.g., floor and enforce a minimum of 1) or explicitly document the rounding semantics to avoid surprising over- or under-selection on edge cases.

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.

@chaoming0625 chaoming0625 merged commit 6220755 into master Jul 9, 2026
14 checks passed
@codecov

codecov Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@chaoming0625 chaoming0625 deleted the worktree-audit-fixes-20260708 branch July 9, 2026 05:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant