# Two Halves Make a Sweep

*Task 4 of 5 · [Iterative Linear Solvers](https://gpu.rocks/learn/iterative-solvers-e73b8e1f.md) · GPU.js Learn*

The red half alone is not a sweep — half the grid has not been touched. The black
half is the same kernel with its parity test flipped, and the ordering that matters is in
the *chaining*: `black(red(u))`. The black cells read the grid the red
half produced, so they see this sweep's reds, not last sweep's. That single fact is all
that separates Gauss-Seidel from Jacobi.

Write `black(u)` instead and both halves read the same old grid. The code
still runs, the answer still looks plausible, and you have written Jacobi with an extra
kernel launch — which is the most expensive way to be wrong in this module, because
nothing about the output shouts.

## Goal

**Goal:** write the black half-sweep and chain the two halves into one
full red-black Gauss-Seidel sweep.

## Requirements

- The black kernel is the red kernel with its parity test flipped: cells with `(x + y) % 2 === 1` update, everything else passes through
- Create the red kernel first and the black kernel second
- One full sweep is `await black(await red(u))` — the black half reads what the red half wrote
- The boundary is untouched by both halves

## Hint 1 — the black kernel

Copy the red kernel and change one character:

```js
const parity = (x + y) % 2;
if (parity !== 1) return u[y][x];
```

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

## Hint 2 — the chain is the lesson

```js
const afterRed = await red(guess);
const afterBoth = await black(afterRed);   // NOT black(guess)
```

Or, in one line: `await black(await red(guess))` — the inner
`await` is not optional, because an un-awaited kernel call hands the next
kernel a Promise instead of a grid.

## Same idea elsewhere

Two dispatches with a dependency between them is the ordinary shape of GPU work:
WebGPU inserts a barrier between compute passes, CUDA orders them on a stream, Vulkan wants
an explicit pipeline barrier. What you cannot do — on any of them — is order threads
*inside* one dispatch, which is exactly why the sequential Gauss-Seidel had to be
split into two of them in the first place.

## Starter code

```js
// One full red-black sweep = the red half, then the black half
// reading what the red half just wrote.
const gpu = new GPU({ mode });

const red = gpu.createKernel(function (u) {
  const x = this.thread.x;
  const y = this.thread.y;
  if (x === 0 || y === 0 || x === this.constants.size - 1 || y === this.constants.size - 1) {
    return u[y][x];
  }
  // parity as a NUMBER — gpu.js cannot keep a boolean in a kernel variable
  const parity = (x + y) % 2;
  if (parity !== 0) return u[y][x];
  return (u[y][x - 1] + u[y][x + 1] + u[y - 1][x] + u[y + 1][x]) / 4;
}, { output: [32, 32], constants: { size: 32 } });

const black = gpu.createKernel(function (u) {
  const x = this.thread.x;
  const y = this.thread.y;
  if (x === 0 || y === 0 || x === this.constants.size - 1 || y === this.constants.size - 1) {
    return u[y][x];
  }
  // TODO 1: same shape as the red kernel, with the parity test flipped —
  //         cells where (x + y) % 2 is 1 take the neighbours' average.
  return u[y][x];
}, { output: [32, 32], constants: { size: 32 } });

const afterRed = await red(guess);
// TODO 2: finish the sweep. The black half must read afterRed, not guess.
const afterSweep = afterRed;

console.log('a black cell [16][17]:', guess[16][17], '→', afterSweep[16][17]);
```

---

Interactive version: https://gpu.rocks/learn/iterative-solvers-e73b8e1f/4

[Previous task](https://gpu.rocks/learn/iterative-solvers-e73b8e1f/3.md) · [Next task](https://gpu.rocks/learn/iterative-solvers-e73b8e1f/5.md)
