Task 3 of 6

Everyone at Once Is Wrong

Now the dynamics. The Metropolis rule is two lines: work out what flipping this spin would cost; if the cost is zero or negative, flip it; if it is positive, flip it anyway with probability exp(−ΔE / T). That exponential is the whole of temperature — near T = 0 an expensive flip essentially never happens and the lattice freezes into agreement; at large T almost everything is accepted and the lattice is noise. You have both halves already: task 1's cost and task 2's u.

The obvious GPU move is to do all 16,384 at once, and it looks watertight. Every thread reads the old lattice and writes only its own cell, so there is no memory race — nothing is overwritten while something else is reading it. The race is in the physics. Each spin computed its cost on the assumption that its neighbours would hold still, and they did not. Two neighbours that agree share one bond; each one separately prices the cost of breaking it; both flip; the bond is not broken at all, and both of them paid for a change that never happened.

Don't take it on trust — measure it. The energy per spin is −½ · mean(s · neighbour sum), the same five reads as task 1 assembled differently, with the ½ because each bond is counted once from each end. It runs from −2 (perfectly aligned) through 0 (random) to +2 (a perfect checkerboard, every neighbour disagreeing). Metropolis at T = 1.5 should walk downhill from a random start. Run it all-at-once and watch which way it actually goes.

nobody overwrote anybody. the arithmetic was still describing a lattice that had already moved — Two neighbouring up spins sharing a satisfied bond. Each thread prices flipping itself assuming the other holds still, both accept, and after both flip the bond is satisfied again — so the cost they each paid was never incurred.
Goal: write the bondEnergy kernel and the meanOf helper, then read the energy the prewired all-at-once loop prints.

Requirements

Hint 1 — why the ½

The bond between two neighbours belongs to both of them. If every cell claimed the full −s · (neighbour sum), summing over the lattice would count each bond twice, and an aligned lattice would report −4 per spin instead of −2. Halving each cell's claim fixes it exactly.

Hint 2 — the same reads as task 1

Identical neighbour sum, different assembly: the flip cost multiplies it by 2 · s, the energy by −½ · s.

return -0.5 * s[y][x] * sum;
Hint 3 — the mean

bondEnergy(s) hands back an ordinary 2D grid of numbers, so this is plain JavaScript:

let total = 0;
for (let y = 0; y < 128; y++) {
  for (let x = 0; x < 128; x++) total += grid[y][x];
}
return total / (128 * 128);

Totalling a grid on the GPU is the halving ladder Reductions builds; at 16,384 cells the read-back is cheaper than the ladder, so this one stays in JavaScript.

Same idea elsewhere

The failure you are about to watch is not a gpu.js quirk, it is what "synchronous Metropolis" does on any platform: the update rule was derived for one spin moving against a fixed background, and running it in parallel silently changes the algorithm into a different, wrong one. The general lesson is worth more than the physics — a parallel version of a sequential algorithm is a different algorithm until you have proved otherwise, and "no thread writes another thread's memory" proves nothing about whether the maths still holds.

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.