Task 5 of 6

Two Halves Make a Sweep

The red half is not a sweep — half the lattice has not been offered a move. The black half is the same kernel with its parity test flipped, and the ordering that matters is in the chaining: black(red(s)). The black cells read the lattice the red half produced, so their ΔE accounts for the reds that just moved. That is not an optimisation, it is the correctness argument: a black cell's four neighbours are all red, and the reds have finished.

Give the two halves different seeds too — 2k and 2k + 1 — or every black cell would draw the same number its red neighbours just used.

Then run it. Same starting lattice as task 3, same temperature, same thirty sweeps, and the energy that climbed from −0.01 to +1.30 now falls to about −1.84, sweep after sweep — 26 of the 29 steps go down, and the three that do not tick back up by less than 0.004. That wobble is the temperature doing its job: T = 1.5 is cold, not zero, so a handful of uphill moves are accepted every sweep and the energy is allowed to breathe. Nothing about the physics changed and nothing got slower: the same 16,384 spins are offered the same moves. All that changed is which of them are allowed to move at the same time.

Goal: write the black half-sweep and chain the two halves into one full sweep, black(red(s)).

Requirements

Hint 1 — the black kernel

Copy the red kernel and change one digit:

const parity = (x + y) % 2;
if (parity !== 1) return spin;

Still a number, never a boolean — the WebGL backend rejects const isBlack = … exactly as it rejects isRed.

Hint 2 — the chain is the lesson
const afterRed = await red(s, temperature, k * 2);
s = await black(afterRed, temperature, k * 2 + 1);   // NOT black(s, …)

Or in one line, await black(await red(s, T, 2 * k), T, 2 * k + 1). The inner await is not decoration: an un-awaited kernel call hands the black half a Promise instead of a lattice.

Same idea elsewhere

Two dispatches with a dependency between them is the ordinary shape of GPU work — WebGPU puts a barrier between compute passes, CUDA orders them on a stream, Vulkan wants an explicit pipeline barrier. What no platform will do is order threads inside one dispatch, which is exactly why the update had to be split in two. Production lattice Monte Carlo goes one step further and launches only the cells of one colour, so the half that would copy itself is never scheduled at all; the trade is a strided memory access pattern against half the threads, and which wins is a benchmark, not an argument.

All tasks in The Ising Model: Colour to Break the Race

  1. What a Flip Would Cost
  2. Randomness Without a Random Number Generator
  3. Everyone at Once Is Wrong
  4. Colour the Lattice
  5. Two Halves Make a Sweep
  6. Drag the Temperature Across Tc

This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.