Task 5 of 5
Everything is wired: the Jacobi sweep, both halves of the red-black sweep, and
the residual. Same plate, same starting guess of zero, same finish line — an RMS residual
below 0.0005. Count sweeps.
"The same work per sweep" is the claim worth being careful about. One Jacobi sweep updates all 900 interior cells once. One red-black sweep updates all 900 once too, in two halves — the same averages, split across two kernel launches instead of one, with half the threads in each launch copying themselves. Sweeps are what we are comparing; the extra launch is what it costs. (A production kernel launches only the cells of one colour and pays nothing for the copies.)
The residual is sampled every 5 sweeps rather than every sweep: the read-back is the expensive part of this loop, and five sweeps barely move the number.
sweepsToTolerance helper the Jacobi baseline usesblack(red(u)) — both halves, in that ordersweepsToTolerance takes one argument: a function that turns a grid
into the next grid. Because a kernel call is awaited, that function is
async — Jacobi's is async u => await sweep(u), and the
helper awaits whatever it returns. Red-black's is one sweep — both halves — expressed
the same way.
const redBlackSweeps = await sweepsToTolerance(
async u => await black(await red(u))
);
Both halves inside one call, so the helper counts a full sweep each time it runs it. Both
awaits matter: the outer one hands the helper a grid, and the inner one is
what makes the black half read the red half's output instead of a Promise.
This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.