# Everyone at Once Is Wrong

*Task 3 of 6 · [The Ising Model: Colour to Break the Race](https://gpu.rocks/learn/ising-model-1f12d841.md) · GPU.js Learn*

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.

## Figures

- **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

**Goal:** write the `bondEnergy` kernel and the
`meanOf` helper, then read the energy the prewired all-at-once loop prints.

## Requirements

- The kernel returns `-0.5 * s[y][x] * (neighbour sum)`, wrapping as in task 1
- `meanOf(grid)` averages all 128 × 128 cells
- Leave the prewired `naiveSweep` kernel and its 30-sweep loop as they are

## 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`.

```js
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:

```js
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.

## Starter code

```js
// Metropolis, applied to every spin at once. Watch the energy.
const gpu = new GPU({ mode });

const naiveSweep = gpu.createKernel(function (s, temperature, seed) {
  const x = this.thread.x;
  const y = this.thread.y;
  const n = this.constants.size;
  const spin = s[y][x];
  const sum = s[y][(x + 1) % n] + s[y][(x + n - 1) % n]
            + s[(y + 1) % n][x] + s[(y + n - 1) % n][x];
  const dE = 2 * spin * sum;
  // a random number for THIS thread, this sweep — no shared state, no ordering
  let h = (x * 1103 + y * 2749 + seed * 3571) % 65536;
  let q = h % 2048;
  h = (h + q * q) % 65536;
  h = (h % 256) * 256 + Math.floor(h / 256);
  h = (h * 253 + 30011) % 65536;
  q = h % 2048;
  h = (h + q * q) % 65536;
  const u = h / 65536;
  if (dE <= 0) return -spin;
  if (u < Math.exp(-dE / temperature)) return -spin;
  return spin;
}, { output: [128, 128], constants: { size: 128 } });

const bondEnergy = gpu.createKernel(function (s) {
  const x = this.thread.x;
  const y = this.thread.y;
  const n = this.constants.size;
  // TODO 1: same four wrapped neighbours as task 1, summed.
  // TODO 2: return this cell's share of the energy: -0.5 * s[y][x] * sum.
  return 0;
}, { output: [128, 128], constants: { size: 128 } });

function meanOf(grid) {
  // TODO 3: average all 128 x 128 cells of grid.
  return 0;
}

let s = lattice;
console.log('energy per spin at the start:', meanOf(await bondEnergy(s)));

const trace = [];
for (let k = 0; k < 30; k++) {
  s = await naiveSweep(s, 1.5, k);
  trace.push(meanOf(await bondEnergy(s)));
}

console.log('energy per spin, sweep by sweep:', trace);
console.log('after 30 all-at-once sweeps: E =', trace[29]);
plot({ 'energy per spin': trace }, { title: 'all-at-once Metropolis at T = 1.5' });
```

---

Interactive version: https://gpu.rocks/learn/ising-model-1f12d841/3

[Previous task](https://gpu.rocks/learn/ising-model-1f12d841/2.md) · [Next task](https://gpu.rocks/learn/ising-model-1f12d841/4.md)
