Merged Pull Requests
Numba CPU permuted Pearson helpers for Mantel
Direct Cython-to-Numba conversion of mantel_perm_pearsonr_cy and its condensed-form variant. The Numba implementation preserves the same loop structure and parallelism axis as the original Cython. A NUMBA_AVAILABLE dispatch keeps Numba an optional dependency: if it is not installed, the existing Cython path stays in service unchanged.
Numba-aware Mantel tests and CI workflow
Test infrastructure for the Numba conversion work: paired _cy and _numba tests against the same fixtures, dispatch fallback tests via patch.object(NUMBA_AVAILABLE, False), and a numba_code pytest marker. The CI workflow runs the marker across four operating systems (Ubuntu x86_64, Ubuntu ARM64, macOS, Windows) on Python 3.14.
Pull Request in Active Review
Numba CPU s_W helpers for PERMANOVA
Direct conversion of permanova_f_stat_sW_cy and the condensed-form variant. CI is green across the full Python matrix on four operating systems. Review feedback has been addressed through follow-up commits including a partial-sum simplification, a test class restructure that mirrors the Mantel test architecture, and replacement of an inline Python reference computation with a hardcoded value from the trusted Cython implementation.
Numba 0.65.1 parfor cycle bug
The natural Numba code for PERMANOVA's s_W crashes at compile time with AssertionError: unexpected cycle in lookup() from inside the parfor reduction-analysis pass. The crash needs two features together: a cross-iteration scalar reduction in the outer prange, and an if conditional inside the inner loop that reads from a parameter array. Either feature alone compiles fine.
@njit(parallel=True)
def natural_sW(mat, group_sizes, grouping):
n = mat.shape[0]
s_W = 0.0
for row in prange(n - 1):
group = grouping[row]
local = 0.0
for col in range(row + 1, n):
if grouping[col] == group: # conditional read of a parameter
val = mat[row, col]
local += val * val
s_W += local / group_sizes[group] # cross-iteration scalar reduction
return s_W
The workaround replaces the scalar reduction with per-iteration writes to a partials array, summed once after the prange exits. The conditional stays in place. Beyond sidestepping the bug, this pattern has a useful property for the future GPU work: parallel=True reductions are a CPU-only Numba feature, but the per-iteration-slot pattern maps directly to @cuda.jit.
Engineering lesson: tests should validate against trusted references
One review point that came up on PR #2470 was about test design. The initial test class included a Python reimplementation of the s_W computation as the "expected value" for the paired _cy and _numba tests. That carries a subtle bug-masking risk: if the test reimplementation and the function under test share the same misunderstanding of the formula, both pass together while being wrong together.
The fix looks obvious in hindsight. Compute the expected value once using the trusted reference implementation (here, the pre-existing Cython), hardcode the resulting number, and assert against it. The test no longer carries an algorithmic implementation. It carries a single value from a trusted source.
Peer collaboration on PCoA
Another GSoC contributor on the same scikit-bio project is working on Numba CPU and GPU implementations of center_distance_matrix for PCoA. The first week of collaboration covered how to structure the work as separate CPU and GPU PRs, how to slot the new tests into the existing Numba CI workflow, and how the Mantel and PERMANOVA architecture can serve as a template for that branch.
What's queued next
Three pieces of work are queued behind PR #2470:
- Interface cleanup PR. Make Numba opt-in rather than auto-selected on installation, and rename the
_numbafunction suffix to_nbto match the existing_cyconvention. This will target Mantel and PERMANOVA at the same time. - Block-permutation optimization for PERMANOVA. Process multiple permutations per matrix scan, with cache tiling and a parallelization axis flipped from matrix rows to permutation blocks. The C++ reference for this is in the
scikit-bio-binariesrepository. - GPU
@cuda.jitimplementation. Builds on the optimized CPU code. The partials pattern from the parfor workaround already aligns with the structure needed for this transition.
All of this landed by the end of June — covered in the next milestone.