Energies go as n squared
const E1 = n1 * n1 * 0.35, E2 = n2 * n2 * 0.35; // Eₙ ∝ n²
Not linear. That quadratic spacing is why the two states beat against each other, and why the beat gets faster the further apart you set them.
Trap a particle between two walls and it can only have certain energies. Put it in two of them at once and the probability starts sloshing back and forth, forever, with no energy going anywhere.
Move the cursor over the canvas — it is part of the simulation.
Drag your finger across the canvas — it is part of the simulation.
Set n₁ and n₂ to the same number and the motion stops dead: a single stationary state has a probability that does not depend on time. That is what stationary means, and it is why atoms do not radiate away.
The stationary states of an infinite well. They are the only shapes that fit with zero at both walls, which is why n has to be a whole number.
Energy grows as n², not n. That quadratic gap is what makes the two states beat against each other at a visible rate.
A superposition. Each piece rotates in the complex plane at its own rate, and the interference between them is the motion you see.
No integration at all: the solution is exact, so each frame evaluates the closed form at 220 points. Three curves are drawn: the real part, the imaginary part, and |ψ|² on top. The first two are not observable and are drawn faint on purpose.
Cut straight from lab.js. These are the real lines that run above, not a simplified version.
const E1 = n1 * n1 * 0.35, E2 = n2 * n2 * 0.35; // Eₙ ∝ n²
Not linear. That quadratic spacing is why the two states beat against each other, and why the beat gets faster the further apart you set them.
const f1 = Math.sin(n1 * Math.PI * u), f2 = Math.sin(n2 * Math.PI * u);
const re = a * f1 * Math.cos(-E1 * t) + b * f2 * Math.cos(-E2 * t);
const im = a * f1 * Math.sin(-E1 * t) + b * f2 * Math.sin(-E2 * t);
reP.push([x, base - re * A]); imP.push([x, base - im * A]);
pr.push([x, base - (re * re + im * im) * A * 1.5]);
No integration: the closed form is known, so each point is computed directly. Real and imaginary parts are tracked separately because the phase between them is the whole physics.
line(reP, 0.35, 1); // parte real
line(imP, 0.22, 1); // parte imaginaria
line(pr, 0.95, 1.6); // |ψ|², lo único observable
Re and Im are drawn faint on purpose. No measurement returns them: what you can actually detect is the sum of their squares, drawn bright on top.
This is the simplest system where quantisation falls out of the maths instead of being assumed. The walls are what force it: confinement plus a wave equation gives discrete energies, and that is the origin of every atomic spectrum ever measured.
Everything above, in one piece. This is the entire experiment as it lives in lab.js.
{ id: 'quantum',
name: L('Particle in a box', 'Partícula en una caja'),
note: L('Two states added together. The probability sloshes and never settles.',
'Dos estados sumados. La probabilidad chapotea y nunca se asienta.'),
params: [
{ key: 'n1', label: L('State n₁', 'Estado n₁'), min: 1, max: 6, step: 1, def: 1 },
{ key: 'n2', label: L('State n₂', 'Estado n₂'), min: 1, max: 8, step: 1, def: 2 },
{ key: 'mix', label: L('Mix', 'Mezcla'), min: 0, max: 1, step: 0.01, def: 0.5 }
],
make() {
return {
step(ctx, w, h, t, acc, P, M) {
const pad = w * 0.08, L = w - pad * 2, base = h * 0.55, A = h * 0.16;
const mix = M.in ? Math.min(1, Math.max(0, M.x / w)) : P.mix;
const a = Math.sqrt(1 - mix), b = Math.sqrt(mix);
const n1 = P.n1 | 0, n2 = P.n2 | 0;
const E1 = n1 * n1 * 0.35, E2 = n2 * n2 * 0.35; // Eₙ ∝ n²
// Paredes del pozo
ctx.strokeStyle = `rgba(${acc},0.35)`; ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(pad, h * 0.12); ctx.lineTo(pad, h * 0.9);
ctx.moveTo(pad + L, h * 0.12); ctx.lineTo(pad + L, h * 0.9);
ctx.stroke();
let reP = [], imP = [], pr = [];
for (let i = 0; i <= 220; i++) {
const u = i / 220, x = pad + u * L;
const f1 = Math.sin(n1 * Math.PI * u), f2 = Math.sin(n2 * Math.PI * u);
const re = a * f1 * Math.cos(-E1 * t) + b * f2 * Math.cos(-E2 * t);
const im = a * f1 * Math.sin(-E1 * t) + b * f2 * Math.sin(-E2 * t);
reP.push([x, base - re * A]); imP.push([x, base - im * A]);
pr.push([x, base - (re * re + im * im) * A * 1.5]);
}
const line = (pts, alpha, lw) => {
ctx.strokeStyle = `rgba(${acc},${alpha})`; ctx.lineWidth = lw;
ctx.beginPath(); pts.forEach((p, k) => k ? ctx.lineTo(p[0], p[1]) : ctx.moveTo(p[0], p[1]));
ctx.stroke();
};
line(reP, 0.35, 1); // parte real
line(imP, 0.22, 1); // parte imaginaria
line(pr, 0.95, 1.6); // |ψ|², lo único observable
ctx.fillStyle = `rgba(${acc},0.8)`; ctx.font = '11px monospace';
ctx.fillText('|ψ|² n₁=' + n1 + ' n₂=' + n2, 12, 20);
}
};
}
},