Intercept out= ufunc writes: close the silent callback bypass (#379 item 4a)#385
Conversation
Adversarial review — round 1A 36-operation differential battery (pre/post hook, including UnitAwareArray arithmetic, reductions, divmod, accumulate, outer, matmul,
Fix for item 1 follows on this branch. Underworld development team with AI support from Claude Code |
97d4491 to
bcbdcad
Compare
Round 1 — response (fixed; branch rebased onto the updated #383)Item 1 fixed: any Items 2 and 3 stand as recorded notes: the reduction scalar type change ( Units suite 37 passed; quick gate 424 passed (the growing count reflects the accumulated stack tests). Underworld development team with AI support from Claude Code |
…eview) Round-1 review: np.add(x, 1, out=x) bypassed the guard that x += 1 enforces, re-arming the per-write hazard the flag exists to prevent (its only in-tree user was the legacy swarm.points wrapper, whose callback ran collective migrate() — removed by #382, but the flag's contract must hold regardless). The hook now raises the same RuntimeError for any out= target carrying the flag, before any computation. Regression test added. Underworld development team with AI support from Claude Code
5b1b40a to
213c036
Compare
…eview) Round-1 review: np.add(x, 1, out=x) bypassed the guard that x += 1 enforces, re-arming the per-write hazard the flag exists to prevent (its only in-tree user was the legacy swarm.points wrapper, whose callback ran collective migrate() — removed by #382, but the flag's contract must hold regardless). The hook now raises the same RuntimeError for any out= target carrying the flag, before any computation. Regression test added. Underworld development team with AI support from Claude Code
bcbdcad to
ce0beb3
Compare
ce0beb3 to
58091e5
Compare
…eview) Round-1 review: np.add(x, 1, out=x) bypassed the guard that x += 1 enforces, re-arming the per-write hazard the flag exists to prevent (its only in-tree user was the legacy swarm.points wrapper, whose callback ran collective migrate() — removed by #382, but the flag's contract must hold regardless). The hook now raises the same RuntimeError for any out= target carrying the flag, before any computation. Regression test added. Underworld development team with AI support from Claude Code
213c036 to
4a09bdf
Compare
Round 2 — verified closedThe verifier confirmed the The stack is review-clean end to end: round 1 (4 reviewers, 15 findings, all fixed) and round 2 (all fixes verified, 2 new defects found and fixed, 3 notes recorded). Ready to merge in order #381 → #382 → #383 → #385. Underworld development team with AI support from Claude Code |
58091e5 to
de644a9
Compare
…pass (#379 item 4a) np.add(x, 1, out=x) — and every in-place operator, which numpy routes through the same machinery — wrote straight into the buffer with no __setitem__ and no callback: values landed (the canonical array wraps the PETSc vec memory) but ghost synchronisation and the state increment silently did not happen, leaving stale ghosts and caches. NDArray_With_Callback now implements __array_ufunc__ using the standard override recipe: operands unwrap to base-class views for the compute (ndarray's default refuses mixed overriding operands), each requested out= comes back AS THE ORIGINAL OBJECT (so x += 1 keeps its subclass and callbacks), and each out= target that is one of ours is notified after the write. The notification composes with the canonical guard — an out= view resolves to the canonical array, an out= detached copy is skipped — and with synchronised_array_update (dirty-mark, one deferred flush). The 12 in-place operators drop their explicit triggers, which would now fire twice per operation. Remaining documented bypasses: np.copyto and ufunc.at (neither passes out=). Gates: units suite 240 passed; quick gate 419 passed; matplotlib quiver/masked-array smoke clean; ufunc dispatch overhead ~2-3 us per call (fixed cost, noise against any PETSc pack). Underworld development team with AI support from Claude Code
…eview) Round-1 review: np.add(x, 1, out=x) bypassed the guard that x += 1 enforces, re-arming the per-write hazard the flag exists to prevent (its only in-tree user was the legacy swarm.points wrapper, whose callback ran collective migrate() — removed by #382, but the flag's contract must hold regardless). The hook now raises the same RuntimeError for any out= target carrying the flag, before any computation. Regression test added. Underworld development team with AI support from Claude Code
4a09bdf to
e09d89b
Compare
Last of the #379 series, stacked on #383 (base is
bugfix/synchronised-update-collective-flush; retarget as the stack merges). Deliberately a single, independently revertable commit.What this does
np.add(x, 1, out=x)— and every in-place operator, which numpy routes through the same ufunc machinery — wrote straight into the buffer with no__setitem__and no callback. Values landed, because the canonical array wraps the PETSc vec memory, but the ghost synchronisation and the state increment silently did not happen: stale ghosts and stale caches rather than hangs.NDArray_With_Callbacknow implements__array_ufunc__using the standard numpy override recipe:out=is returned as the original object, sox += 1keeps its subclass and callbacks — this is the recipe's load-bearing detail;out=target of ours is notified after the write. The notification composes with the Callback-array parallel safety: central view/copy guard, honest exceptions, no dead snapshots (#379 items 3+4b) #381 canonical guard (anout=view resolves to the canonical array; anout=detached copy is skipped) and with the synchronised_array_update: collective dirty-variable flush replaces rank-local event replay (#379 item 2) #383 flush (insideuw.synchronised_array_updateit dirty-marks for the single deferred flush).The twelve in-place operators drop their explicit triggers — with the hook in place they would notify twice per operation.
Remaining documented bypasses:
np.copytoandufunc.atdo not passout=and are not intercepted; they are recorded in the class docstring and the test module.Verification
tests/test_0061_ufunc_out_callback.py(level_1 / tier_a, written first and shown to fail):out=on canonical / view / detached copy, plain arithmetic stays silent and returns plain ndarrays,+=fires exactly once, masked+=still nets one notification via the parent write-back, deferral insidesynchronised_array_update, and an end-to-end mesh-variableout=write reaching the PETSc vec.level_1 and tier_a: 419 passed, 1 pre-existing xfail.Underworld development team with AI support from Claude Code