Skip to content

NumPy parity: Sum/Prod over an empty axis, Slice with fewer indices; differential fuzzer - #25

Merged
tannevaled merged 2 commits into
mainfrom
parity/empty-reductions
Oct 4, 2026
Merged

tannevaled merged 2 commits into
mainfrom
parity/empty-reductions

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

Parity fixes (each checked against NumPy 2.5.3 on cfarm421)

  • SumAxis/ProdAxis along a zero-length axis now return the identity: np.zeros((3,0)).sum(axis=1) is [0,0,0] and .prod(axis=1) is [1,1,1]. Before, both returned an error, which was inconsistent with Sum()/Prod() of an empty array (0 and 1). Max/Min/ArgMax/ArgMin still error, matching NumPy's ValueError. MeanAxis still errors, matching Mean(): NumPy returns NaN with a RuntimeWarning, and Go has no warnings. The old docstring said the whole-array reductions "error on empty input", which was false for Sum/Prod; it is corrected. A MeanAxis error branch that can no longer be reached was removed, since the coverage gate flagged it.
  • Slice with fewer indices than axes takes the remaining axes whole, as m[1] does in NumPy. This used to be an error. The padding never writes into the caller's index slice; a test covers this and fails when the guard is removed.
  • MaskSelect: NumPy rejects a mask that only broadcasts to the array (verified). Here that is kept as a documented extension, and the docstring now states exactly where the two differ (leading-axes masks, 0-d masks).

Differential fuzzer (FuzzOps)

Arbitrary bytes drive slices, reshapes, transposes, squeezes, broadcasting arithmetic, axis reductions, Take, MaskSelect, Concatenate, MatMul and CumSum. Each result is compared element by element with an oracle written from NumPy's definitions (Python's slice.indices, the broadcasting rule, and so on). The library must reject exactly the calls the oracle rejects.

  • 47M executions, no divergence after the two fixes above, which it found.
  • Positive control: a first version only compared each array with its own Copy, and it missed a deliberate Squeeze stride bug in 31M executions. The oracle version catches it in 11 seconds (At = Copy = -3, oracle 3). That input is kept as a seed.

Docs

  • The README and SECURITY.md now state the memory-amplification caveat. The library checks that a shape is representable, not that it fits in memory, and in Go running out of memory kills the process. Broadcasting, Outer, MatMul and summing the empty axis of (0, n) produce more than their operands hold.

Version: v0.4.0. Calls that used to error now return results.

🤖 Generated with Claude Code

tannevaled and others added 2 commits October 4, 2026 22:02
…nindexed axes whole; differential fuzzer

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit 7a2a31f into main Oct 4, 2026
12 checks passed
@tannevaled
tannevaled deleted the parity/empty-reductions branch October 4, 2026 20:58
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