Quantum mechanics

Bell test

Two particles are measured far apart. Each result on its own is a coin flip. Put the two lists side by side and they agree more often than any theory where the answers were decided in advance could allow.

EXP-013

Move the cursor over the canvas — it is part of the simulation.

Drag your finger across the canvas — it is part of the simulation.

What to look for

The S value climbing past 2 and settling near 2.828. Also look at each detector on its own: the plus and minus signs are an even coin flip no matter what angle you set, which is exactly why this cannot be used to send a message.

The maths

E(a,b) = −cos(a − b)

The correlation predicted for a singlet state. It depends only on the difference between the two detector angles, never on either one alone.

S = |E(a,b) − E(a,b′) + E(a′,b) + E(a′,b′)|

The CHSH combination of four measurement settings. Bell proved that any theory where the outcomes exist before measurement and nothing travels faster than light must keep this below 2.

S_max = 2√2 ≈ 2.828

What quantum mechanics gives at the optimal angles, and what experiments measure. The simulation converges here, above the classical ceiling.

How it is built

Each pair is generated honestly: one side gets a fair coin flip, and the other agrees with probability (1 + E)/2 where E is the quantum correlation for that angle difference. Nothing is passed between the detectors in the code. Four angle settings run in parallel to accumulate the CHSH sum live.

The code, step by step

Cut straight from lab.js. These are the real lines that run above, not a simplified version.

01

An honest measurement

const measure = (a, b) => {
  const E = -Math.cos((a - b) / D);
  const A = Math.random() < 0.5 ? 1 : -1;
  const B = Math.random() < (1 + E) / 2 ? A : -A;
  return [A, B];
};

One side gets a fair coin flip and the other agrees with probability (1 + E)/2. Note that no information passes between them in the code: each outcome is generated where it is measured, and only the correlation is quantum.

02

The CHSH sum

const E = tally.map(x => (x.n ? x.s / x.n : 0));
const S = Math.abs(E[0] - E[1] + E[2] + E[3]);   // parámetro CHSH

Four angle settings run in parallel and their measured correlations are combined. Any theory where the answers existed before measurement caps this at 2. The settings are the ones that maximise the quantum value, so the simulation climbs past the classical ceiling and settles near 2.828.

Why it matters

Einstein argued in 1935 that quantum mechanics had to be incomplete, that there must be hidden variables carrying the answers. Bell turned that argument into a number you can measure. Aspect, Clauser and Zeilinger measured it and shared the 2022 Nobel for closing the loopholes. The hidden variables are not there.

The whole thing

Everything above, in one piece. This is the entire experiment as it lives in lab.js.

FULL

lab.js · entanglement

{ id: 'entanglement',
      name: L('Bell test', 'Test de Bell'),
      note: L('Two detectors, random outcomes each, correlations no local theory can produce.',
              'Dos detectores, resultados azarosos en cada uno, correlaciones que ninguna teoría local produce.'),
      params: [
        { key: 'angA',  label: L('Detector A angle', 'Ángulo del detector A'), min: 0, max: 180, step: 1, def: 0, unit: '°' },
        { key: 'angB',  label: L('Detector B angle', 'Ángulo del detector B'), min: 0, max: 180, step: 1, def: 45, unit: '°' },
        { key: 'speed', label: L('Pairs per frame', 'Pares por frame'), min: 1, max: 200, step: 1, def: 40 }
      ],
      make() {
        // Ajustes que maximizan CHSH con esta convención de signo (E = −cos):
        //   a = 0°, a' = 90°, b = 45°, b' = 135°  →  S = 2√2
        // Con 0/45/22.5/67.5 el mismo estado da solo 2.39: viola el límite
        // clásico igual, pero no alcanza el máximo cuántico.
        const SET = [[0, 45], [0, 135], [90, 45], [90, 135]];
        let tally = SET.map(() => ({ n: 0, s: 0 })), hist = [], shots = [];
        const D = 180 / Math.PI;
        return {
          reset() { tally = SET.map(() => ({ n: 0, s: 0 })); hist = []; shots = []; },
          step(ctx, w, h, t, acc, P, M) {
            const aA = M.in ? (M.x / w) * 180 : P.angA;
            const aB = P.angB;

            // Una medición: cada lado da ±1 al azar, pero la correlación del
            // estado singlete es E = −cos(a − b). No hay señal entre ellos.
            const measure = (a, b) => {
              const E = -Math.cos((a - b) / D);
              const A = Math.random() < 0.5 ? 1 : -1;
              const B = Math.random() < (1 + E) / 2 ? A : -A;
              return [A, B];
            };

            for (let k = 0; k < (P.speed | 0); k++) {
              SET.forEach((cfg, i) => {
                const [A, B] = measure(cfg[0], cfg[1]);
                tally[i].n++; tally[i].s += A * B;
              });
              const [A, B] = measure(aA, aB);
              shots.push({ A, B, life: 0 });
            }
            if (shots.length > 40) shots.splice(0, shots.length - 40);

            const E = tally.map(x => (x.n ? x.s / x.n : 0));
            const S = Math.abs(E[0] - E[1] + E[2] + E[3]);   // parámetro CHSH

            // Correlación medida del par visible contra la predicción
            const Emeas = -Math.cos((aA - aB) / D);
            hist.push(Emeas);
            if (hist.length > 200) hist.shift();

            const cx = w / 2, cy = h * 0.34, arm = Math.min(w * 0.3, h * 0.3);
            // Fuente al centro, detectores a los lados
            ctx.strokeStyle = `rgba(${acc},0.25)`; ctx.lineWidth = 1;
            ctx.beginPath(); ctx.moveTo(cx - arm, cy); ctx.lineTo(cx + arm, cy); ctx.stroke();
            ctx.fillStyle = `rgba(${acc},0.9)`;
            ctx.beginPath(); ctx.arc(cx, cy, 4, 0, TAU); ctx.fill();

            const dial = (x, ang, label, val) => {
              ctx.strokeStyle = `rgba(${acc},0.5)`; ctx.lineWidth = 1.2;
              ctx.beginPath(); ctx.arc(x, cy, 16, 0, TAU); ctx.stroke();
              const r = ang / D;
              ctx.strokeStyle = `rgba(${acc},0.95)`; ctx.lineWidth = 2;
              ctx.beginPath(); ctx.moveTo(x - 16 * Math.cos(r), cy - 16 * Math.sin(r));
              ctx.lineTo(x + 16 * Math.cos(r), cy + 16 * Math.sin(r)); ctx.stroke();
              ctx.font = '10px monospace'; ctx.fillStyle = `rgba(${acc},0.7)`;
              ctx.fillText(label + ' ' + ang.toFixed(0) + '°', x - 22, cy + 34);
              ctx.fillStyle = val > 0 ? `rgba(${acc},1)` : 'rgba(235,238,245,0.9)';
              ctx.fillText(val > 0 ? '+1' : '−1', x - 7, cy - 26);
            };
            const last = shots[shots.length - 1] || { A: 1, B: -1 };
            dial(cx - arm, aA, 'A', last.A);
            dial(cx + arm, aB, 'B', last.B);

            // Curva de correlación: medida contra −cos(Δ)
            const gy = h * 0.72, gh = h * 0.2, gw = w * 0.76, gx = w * 0.12;
            ctx.strokeStyle = `rgba(${acc},0.18)`; ctx.lineWidth = 1;
            ctx.beginPath(); ctx.moveTo(gx, gy); ctx.lineTo(gx + gw, gy); ctx.stroke();
            ctx.strokeStyle = `rgba(${acc},0.5)`;
            ctx.beginPath();
            for (let i = 0; i <= 90; i++) {
              const d = (i / 90) * 180, y = gy - (-Math.cos(d / D)) * gh;
              i ? ctx.lineTo(gx + (i / 90) * gw, y) : ctx.moveTo(gx, y);
            }
            ctx.stroke();
            const px = gx + (Math.abs(aA - aB) / 180) * gw;
            ctx.fillStyle = `rgba(${acc},1)`;
            ctx.beginPath(); ctx.arc(px, gy - Emeas * gh, 3.4, 0, TAU); ctx.fill();

            ctx.font = '11px monospace';
            ctx.fillStyle = `rgba(${acc},0.55)`;
            ctx.fillText('E(a,b) = −cos(a−b)      classical limit S ≤ 2', 12, h - 28);
            ctx.fillStyle = S > 2 ? `rgba(${acc},1)` : 'rgba(235,238,245,0.8)';
            ctx.fillText('CHSH  S = ' + S.toFixed(3) + '   (2√2 = 2.828)' +
              (S > 2 ? '   violated' : ''), 12, h - 12);
          }
        };
      }
    },