# Sixteen Step Sizes at Once

*Task 3 of 5 · [The Heat Equation & Stability](https://gpu.rocks/learn/heat-and-stability-514063bb.md) · GPU.js Learn*

You have been told where the line is. Now measure it — and measure it the way
a GPU makes cheap: not by running sixteen simulations one after another, but by
running **all sixteen in the same kernel launch**.

The grid below is 64 columns by 16 rows, and each row is its own universe: a
64-cell ring, seeded with one hot cell, stepped with *its own*
`dts[row]`. Nothing couples the rows, so the Laplacian here is the 1D one,
`left + right − 2·centre`, along `x` only. Reach for the
familiar 5-point stencil and row 3's instability leaks into row 4.

Rings are one-dimensional, so `dims = 1` and the limit moves:
`dt ≤ dx²/(2·D·1) = 1`, twice what it was on the 2D grid. That factor is
not decoration — it is the number of neighbours taking a bite out of the centre. After
90 steps the answer is unmissable: the stable rows have flattened to a few hundredths,
and the row on the other side of the line is at 10³.

## Goal

**Goal:** step all sixteen rings 90 times, then report the largest
`dt` whose row is still under 1 — and compare it with
`dx²/(2·D·1)`.

## Requirements

- The kernel forms *this row’s* diffusion number from `dts[this.thread.y]`
- The Laplacian runs along `x` only: `left + right − 2·centre` — rows must not read each other
- After 90 steps, find each row’s largest `|u|`; a row survived if that is below 1
- Log the largest surviving `dt` on a line that says `measured`, and the predicted limit beside it

## Hint 1 — which dt is mine?

`this.thread.y` is the row, so `dts[this.thread.y]` is this
ring's step size. Turn it into a diffusion number the same way as before:

```js
const a = this.constants.diff * dts[r]
  / (this.constants.dx * this.constants.dx);
```

## Hint 2 — one dimension, two neighbours

`return u[r][x] + a * (u[r][xl] + u[r][xr] - 2 * u[r][x]);` — note the
`2`, not `4`. Only the row index `r` never varies.

## Hint 3 — scanning the rows afterwards

Plain JavaScript on the finished grid:

```js
for (let r = 0; r < ROWS; r++) {
  let m = 0;
  for (let x = 0; x < CELLS; x++) {
    const a = Math.abs(u[r][x]);
    if (!(a <= m)) m = a;   // so a NaN cannot hide
  }
  if (m < 1 && dts[r] > measured) {
    measured = dts[r];
  }
}
```

## Same idea elsewhere

Sweeping a parameter by giving it an axis of the launch grid is the GPU's answer
to "try them all": CUDA codes run a batch of independent problems as extra blocks, WebGPU
dispatches a third workgroup dimension over configurations, and every autotuner on every
platform is this shape. It is also how a solver picks its own step size in production —
run the candidate, look at what came back, and back off.

## Starter code

```js
// Sixteen simulations in one grid: row r is a 64-cell ring with its own dt.
const gpu = new GPU({ mode });

const D = 8;
const dx = 4;
const CELLS = 64;
const ROWS = 16;
const STEPS = 90;

const step = gpu.createKernel(function (u, dts) {
  const x = this.thread.x;
  const r = this.thread.y;
  let xl = x - 1; if (xl < 0) xl = this.constants.cells - 1;
  let xr = x + 1; if (xr > this.constants.cells - 1) xr = 0;
  // TODO: this row's diffusion number, then ONE 1D step of ring r:
  //   a = diff * dts[r] / (dx * dx)     (both live in this.constants)
  //   u[r][x] + a * (left + right - 2 * centre)
  return u[r][x];
}, {
  output: [64, 16],
  constants: { cells: 64, diff: 8, dx: 4 },
});

let u = seed;
for (let i = 0; i < STEPS; i++) u = await step(u, dts);

// TODO: for each row print the largest |u| left, and keep the largest dt
// whose row stayed below 1.
let measured = 0;

console.log('measured limit: dt <=', measured);
console.log('predicted:      dt <=', dx * dx / (2 * D * 1));
```

---

Interactive version: https://gpu.rocks/learn/heat-and-stability-514063bb/3

[Previous task](https://gpu.rocks/learn/heat-and-stability-514063bb/2.md) · [Next task](https://gpu.rocks/learn/heat-and-stability-514063bb/4.md)
