Skip to content

⚡ Bolt: vectorize griddata_v4 in topoplot.py#273

Open
suraj-ranganath wants to merge 1 commit into
developfrom
bolt-vectorize-griddata-v4-1568333760095009189
Open

⚡ Bolt: vectorize griddata_v4 in topoplot.py#273
suraj-ranganath wants to merge 1 commit into
developfrom
bolt-vectorize-griddata-v4-1568333760095009189

Conversation

@suraj-ranganath

Copy link
Copy Markdown
Member

This PR implements a performance optimization for the griddata_v4 function used in EEG topographic plotting.

Changes

  • Replaced the nested loop for query point evaluation in src/eegprep/functions/sigprocfunc/topoplot.py with a vectorized implementation using NumPy broadcasting and matrix multiplication.

Performance Impact

  • Measurement: Benchmarked using a standard EEGLAB-style 67x67 grid and 64 electrodes.
  • Result: Average execution time for the evaluation phase dropped from ~62ms to ~10ms, representing a ~6x speedup.
  • Efficiency: Leveraging optimized BLAS/LAPACK routines through NumPy's @ operator reduces Python interpreter overhead significantly.

Verification

  • Ran tests/test_topoplot.py and tests/test_pop_topoplot.py to ensure numerical parity and functional correctness are maintained.
  • Successfully ran pre-commit.py.
  • Documentation updated in .jules/bolt.md.

PR created automatically by Jules for task 1568333760095009189 started by @suraj-ranganath

Vectorized the biharmonic spline interpolation evaluation in `griddata_v4` by replacing a nested Python loop with NumPy broadcasting and matrix multiplication.

💡 What: Replaced the $M \times N$ nested loop with a vectorized calculation of query point distances and Green's function values.
🎯 Why: The nested loop was a significant bottleneck in topographic plotting, especially for higher resolution grids.
📊 Impact: Provides a ~5.3x - 6x speedup for `griddata_v4` on a standard 67x67 grid.
🔬 Measurement: Verified with `tools/benchmark_griddata_vectorized.py` and numerical correctness confirmed with `tests/test_topoplot.py`.

Co-authored-by: suraj-ranganath <14310165+suraj-ranganath@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@claude

claude Bot commented Jul 4, 2026

Copy link
Copy Markdown

Claude finished @suraj-ranganath's task in 1m 40s —— View job


Review: vectorize griddata_v4 in topoplot.py

  • Read PR diff and changed files
  • Analyze correctness / numerical parity of the vectorization
  • Attempt to run tests (blocked by sandbox — see note)
  • Post review

Verdict: LGTM. The vectorization is mathematically equivalent to the original nested loop and is a clean, idiomatic NumPy rewrite. Nice speedup.

Correctness ✅

The new evaluation block is element-for-element identical to the loop it replaces:

q = (xq + 1j * yq)[..., np.newaxis]   # (m, n, 1)
d = np.abs(q - xy)                     # xy is (npts,) → broadcasts to (m, n, npts)
g = (d**2) * (np.log(d) - 1)          # same Green's function
g[d == 0] = 0                          # same zero-distance handling
vq = g @ weights                       # (m, n, npts) @ (npts,) → (m, n)
  • Broadcasting (m,n,1) - (npts,) reproduces the per-point xq[i,j] + 1j*yq[i,j] - xy.ravel() exactly.
  • g @ weights matches the per-point np.dot(g, weights).
  • The d == 0 mask (grid point coinciding with an electrode) is preserved, so the log-singularity handling is unchanged.
  • weights is real (derived from a real solve/pinv), and d is a magnitude, so vq stays real — no spurious complex output.

Minor observations (non-blocking)

  1. Output dtype changed slightly. The old code seeded the result with vq = np.zeros_like(xq), so it inherited xq's dtype; the new code returns whatever g @ weights produces (float64). For the usual float meshgrid query grids this is identical — and arguably more correct, since an integer xq would previously have truncated results. No action needed, just noting the semantics.

  2. Memory trade-off is fine. The intermediate d/g arrays are (m, n, npts) — ~2.3 MB for the 67×67×64 case cited. Negligible for topoplot grid sizes, as the PR notes.

  3. .jules/bolt.md added to the repo. This is a tool scratch/learnings note being committed to the repo root under .jules/. Per AGENTS.md ("touch only files you must"), consider whether this belongs in the repo or should be .gitignored / kept out of the PR. Also the header date is 2025-05-15 (today is 2026-07-04) — cosmetic only. Fix this →

Note on testing ⚠️

I could not execute tests/test_topoplot.py / tests/test_pop_topoplot.py or a standalone parity script in this environment (command/write permissions were denied in the sandbox). The PR reports both suites pass; given the change is provably equivalent above, I'd expect green — please confirm CI is passing before merge.

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