workshop private

← all creations

Zero-One

viz · created 2026-09-17

A sorting network has no branches, so you cannot argue with it — you can only test it, and testing it properly means all n! orderings. The 0-1 principle collapses that to 2^n, drawn here as every binary input at once, banded by weight, failures in red. Pull one comparator out of bitonic and exactly one cell in 256 turns — which is also 576 of the 40,320 permutations, because a binary counterexample is never one input, it is a class.

algorithmscanvas

A sorting network is a fixed list of compare-exchange operations. Wire a against wire b: afterwards the smaller value is on a and the larger on b. That is the entire instruction set, and the list never changes — no branches, no loop counters, no data-dependent anything. The same comparators fire in the same order whether you hand it a sorted array or its reverse, which is exactly why you can etch one into silicon, unroll it across SIMD lanes, or run every comparator in a layer at once.

The bill for that rigidity comes due when you ask whether it works. There is no invariant to carry through a loop, because there is no loop. All you can do is run inputs through it. And “all inputs” means every ordering of n distinct values: 40,320 at eight wires, and twenty-one trillion at sixteen.

The 0-1 principle says you do not have to. A comparator network that sorts every 0-1 vector sorts every input, of every type. Eight wires: 256 cases. That is not a sample and it is not a heuristic, it is the whole proof, and it runs before your finger leaves the mouse button.

So the piece draws those 256 cases. Every binary input is a cell, banded by how many ones it has — one all-zeros case at the top, 70 balanced ones through the middle, one all-ones case at the bottom, which is why the sweep is shaped like the binomial coefficients it is made of. Grey passes. Red does not.

Five networks and one that is one short

One short is the point of the whole thing. Of the 24 ways to remove a single comparator from bitonic, exactly one leaves a network that fails just one of the 256 binary inputs: 11110000. Every other removal breaks between 8 and 225 of them. So this is the gentlest possible damage to a sorting network, and the sweep still finds it instantly, as one red cell in a field of grey.

What a red cell is worth

Press lift it. The failing vector 11110000 becomes a permutation of 1…8 — the four zeros get four distinct low values in any order, the four ones get the four high values — and the network makes the identical comparisons and lands in the identical wrong place. That is the mechanism behind the principle: comparators commute with every monotone function, so thresholding a real input reproduces its binary run exactly. Running it backward, a binary counterexample is not an anecdote, it is a class: 11110000 stands for 4!·4! = 576 of the 40,320 permutations, and the readout counts them.

Which produces the fact this piece exists to show, and it is the opposite of the one you expect. Random testing is not blind here — it cannot be. A broken n-wire network fails at least ⌊n/2⌋!·⌈n/2⌉! of the n! orderings, because failure always arrives in whole lift classes, so even the subtlest possible bug in an eight-wire network fires on one input in seventy. Throw two thousand random arrays at one short and roughly thirty of them come back wrong.

So random testing will tell you that something is broken. What it will never do is tell you nothing is — 40,320 is not a suite you run, and 16! is not a number you run anything over. The 256-case sweep answers the question random testing structurally cannot answer, in less time than it takes to draw the answer.

Building one

Drag between two wires to add a comparator; the wire you start on keeps the minimum, so dragging upward builds a descending comparator and you can draw bitonic by hand. Click a comparator to remove it. Where you drop it along the wire decides where it lands in the sequence, not just where it renders. The sweep re-runs on every edit, so the honest way to use this is to delete the six-layer network, build something you think sorts, and watch the grid tell you.

Switch to 16 wires and the two numbers separate for good: 65,536 binary cases against 20,922,789,888,000 orderings. The sweep still runs in about thirty milliseconds. There is exactly one removal from the 80-comparator bitonic network that leaves a single failing case, and it is in there — one red pixel out of 65,536, which is a picture of what exhaustive means.

Reuse

src/zero-one.js is a framework-free ES module, no rendering and no timers:

scripts/analyse.mjs regenerates every number quoted here; re-run it after touching the module.

Gotchas