Skip to content

numpy: native det, slogdet, solve and inv, bit-exact with OpenBLAS - #218

Merged
ets-public merged 7 commits into
mainfrom
claude/project-thread-s55zzz
Nov 5, 2026
Merged

ets-public merged 7 commits into
mainfrom
claude/project-thread-s55zzz

Conversation

@ets-public

Copy link
Copy Markdown
Owner

Requested by Adam · project thread

Fourth native numpy PR. It turns on np.linalg.det, slogdet, solve and inv, which #217 had left off because their last digit sometimes differed from numpy's.

How they match exactly

numpy calls OpenBLAS's dgetrf/dgesv. The new lk_* code in rt_nplinalg.rs follows OpenBLAS 0.3.30's single-thread path step by step:

  • getrf_single.c: the recursive blocking, which depends on GEMM_UNROLL_N and GEMM_Q
  • the getf2_k.c panel: strided ddot, then dgemv_n with each microkernel's own summation order
  • dgemm kernel k-loops: the SkylakeX m4n12, m2n12 and m1n12 kernels split k across 2 or 4 accumulators, and Haswell's 4x1 kernel splits k 4 ways
  • trsm_kernel_LT/LN blocking, trsv_L/U and daxpy for the solve

I read each part from OpenBLAS's source and checked it against the real library through ctypes. The Python model is in /mnt/project-files/brevi-numpy/openblas-model/.

Checks

  • 37,819 of 37,819 values from det, slogdet, solve (vector and matrix right-hand side) and inv are identical to numpy's, over 28 sizes from 1x1 to 99x99. This holds on AVX-512 and also with OpenBLAS forced to Haswell (OPENBLAS_CORETYPE=Haswell) against a build with Brevi forced to its AVX2 model.
  • The Python model matches OpenBLAS's single-thread getrf up to 200x200.
  • Edge cases print the same as numpy: singular matrices (LinAlgError: Singular matrix, det 0.0, slogdet (0.0, -inf)), NaN and inf entries, subnormal pivots, int inputs, and the shape errors.
  • slogdet returns numpy's SlogdetResult named tuple.
  • matrix_power now takes negative powers.
  • Corpus: all 40 AI-written numpy programs match numpy, and 35 now build natively (was 33).
  • New test tests/numpy/linalg_lu.py. Its output is identical under numpy's SkylakeX, Haswell and Prescott kernels, so it passes on any CI CPU.
  • sh tools/ci_local.sh: all checks passed, 0 regressions.

Known limits (documented)

  • From 100x100 up for solve/inv, and from about 140x140 for det, numpy splits the LU across threads. The last digit then depends on the thread count. Brevi gives numpy's one-thread result, the same as OPENBLAS_NUM_THREADS=1.
  • numpy prints RuntimeWarnings from these calls when the input holds NaN or inf; Brevi does not.
  • Speed: a 400x400 solve takes 0.08 s, against numpy's 0.005 s. This is fine for typical sizes.

Generated by Claude Code

Python files that use numpy's core now build with a numpy written in Rust
(src/dynpy/rt_np.rs, rt_npfn.rs, rt_npfmt.rs): float64/int64/bool arrays with
NEP 50 promotion, strided views, masks and integer-array indexing,
broadcasting, operators, ~60 ufuncs, reductions in numpy's pairwise order,
sort/unique/stacking, and numpy 2.x printing byte for byte. Anything it lacks
(np.random, np.linalg, other dtypes) is refused at build time and the program
gets CPython's numpy through the typed build, as before. The dynamic build no
longer calls CPython's numpy itself (5 of 40 corpus programs gave wrong output
that way). BREVI_NUMPY=0 turns it off.

tests/numpy: 11 programs whose .expected files come from real numpy; the
check fails if any of them runs off Brevi's numpy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0136hkZgpjat3dn8BGZPJVF2
src/dynpy/rt_nprand.rs: default_rng(seed) as numpy does it (SeedSequence,
PCG64, the ziggurat normal and exponential with numpy's tables, Lemire bounded
integers) and the legacy np.random.seed API (MT19937 init_genrand, polar
gauss with its cached value, masked randint). random, integers, normal,
uniform, standard_normal, exponential, choice (replace=False, p=), shuffle,
permutation, rand, randn, randint, RandomState. Other distributions and ufunc
methods (np.maximum.accumulate) are refused at build time, so the program
gets CPython's numpy. Unseeded generators take OS entropy without file access.

tests/numpy: 6 random programs, expected output from real numpy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0136hkZgpjat3dn8BGZPJVF2
…apezoid, ufunc reduce/accumulate

Float vector np.dot now follows OpenBLAS's own summation order for the CPU
(AVX2 or AVX-512, with its thread split), so it matches numpy to the last digit.
det/solve/inv are written but stay off (go to CPython numpy) until they match
OpenBLAS's LU bit for bit. Fixes str * np.int64 raising TypeError.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0136hkZgpjat3dn8BGZPJVF2
Follows OpenBLAS's single-thread LU step by step (getrf_single recursive
blocking, getf2, dgemv_n, the SkylakeX/Haswell dgemm kernels' k-loop splits,
trsm_kernel_LT/LN, trsv, daxpy), so results equal numpy's to the last digit
on AVX-512 and AVX2 CPUs. slogdet returns SlogdetResult; matrix_power takes
negative powers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0136hkZgpjat3dn8BGZPJVF2
@ets-public ets-public self-assigned this Nov 5, 2026
@ets-public
ets-public merged commit cba0039 into main Nov 5, 2026
2 checks passed
ets-public pushed a commit that referenced this pull request Nov 5, 2026
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