quantum mechanics
UNCERTAINTY PRINCIPLE
Interactive simulation of the Heisenberg Uncertainty Principle. Visualize the fundamental limit of simultaneous position and momentum knowledge.
Position Space ψ(x)
The wave packet in position space. |ψ(x)|² shows the probability density (red area). Re(ψ) shows the oscillating wave inside the envelope.
Momentum Space φ(k)
The Fourier transform into momentum space. As the position space narrows, momentum space broadens. Δx · Δk = 1/2
Uncertainty Relation
The uncertainty product Δx·Δp as a 2D rectangle. As one dimension shrinks, the other grows, keeping the area constant at ℏ/2.
Quantum Statistics
Position Uncertainty
1.00
Δx (σ)
Momentum Uncertainty
0.50
Δp (ℏ·Δk)
Wavevector Uncertainty
0.50
Δk (1/2σ)
Min. Uncertainty
0.50
ℏ/2
Uncertainty Product
0.50
Δx·Δp (ℏ/2 for Gaussian)
Time Domain ψ(t)
A wave packet with finite duration Δt. Shorter pulses have broader frequency spectra.
Frequency Domain φ(ω)
The frequency spectrum. ΔE = ℏ·Δω represents the energy spread. Shorter time → broader energy spectrum.
Energy-Time Uncertainty
Time Duration
1.00
Δt
Energy Spread
0.50
ΔE = ℏ·Δω
Energy-Time Product
0.50
ΔE·Δt (ℏ/2)
This relation explains why particles created in high-energy collisions are very short-lived: a short lifetime Δt implies a large energy spread ΔE. Conversely, stable particles have well-defined energies but indefinite lifetimes.
Single Slit Setup
Narrow slit = well-defined position (small Δx) but causes large diffraction (large Δp spreading).
Diffraction Pattern
The diffraction pattern shows how momentum uncertainty grows. Narrower slit → wider diffraction pattern. This IS the uncertainty principle in action!
Diffraction Analysis
Slit Width
1.00
Δx (a)
Angular Spread
0.50
θ₁st (radians)
Momentum Uncertainty
1.00
Δp (approx)
Uncertainty Product
0.50
Δx·Δp
Single-slit diffraction demonstrates the uncertainty principle directly. Confining a particle to a slit of width a (Δx ≈ a) forces a momentum uncertainty of roughly ℏ/a (Δp ≈ ℏ/a), giving Δx·Δp ≈ ℏ.
Developer Reference
Core Algorithm & Standalone Script
Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.
// Constants
const HBAR = 1; // Set to 1 for simplicity (natural units)
const PI = Math.PI;
// State
let state = {
sigma: 1.0,
k0: 2.0,
x0: 0.0,
mode: 'position-momentum',
slitWidth: 1.0
};
// ============ Gaussian Wave Packet ============
function psi(x, sigma, k0, x0) {
const norm = Math.pow(2 * PI * sigma * sigma, -0.25);
const envelope = Math.exp(-Math.pow(x - x0, 2) / (4 * sigma * sigma));
const phase = Math.cos(k0 * x);
return norm * envelope * phase;
}
function psiMagnitude(x, sigma, k0, x0) {
const norm = Math.pow(2 * PI * sigma * sigma, -0.25);
return norm * Math.exp(-Math.pow(x - x0, 2) / (4 * sigma * sigma));
}
function psiReal(x, sigma, k0, x0) {
return psiMagnitude(x, sigma, k0, x0) * Math.cos(k0 * x);
}
// Momentum space (Fourier transform of Gaussian)
function phi(k, sigma, k0) {
const width = 1 / (2 * sigma);
const norm = Math.pow(PI, -0.25) * Math.sqrt(1 / (2 * sigma));
return norm * Math.exp(-Math.pow(k - k0, 2) / (2 * width * width));
}
function phiMagnitude(k, sigma, k0) {
const width = 1 / (2 * sigma);
const norm = Math.pow(PI, -0.25) * Math.sqrt(1 / (2 * sigma));
return norm * Math.exp(-Math.pow(k - k0, 2) / (2 * width * width));
}
// ============ Canvas Drawing ============
function drawPositionSpace(canvas, sigma, k0, x0) {
const ctx = canvas.getContext('2d');
const w = canvas.width;
const h = canvas.height;
ctx.fillStyle = '#0a0a0a';
ctx.fillRect(0, 0, w, h);
const xMin = -6, xMax = 6;
const scale = w / (xMax - xMin);
const centerY = h / 2;
// Draw axes
ctx.strokeStyle = '#2a2a2a';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, centerY);
ctx.lineTo(w, centerY);
ctx.stroke();
// Draw probability density |ψ(x)|²
ctx.fillStyle = 'rgba(255, 34, 0, 0.4)';
ctx.beginPath();
ctx.moveTo(0, centerY);
for (let i = 0; i <= w; i++) {
const x = xMin + (i / w) * (xMax - xMin);
const mag = psiMagnitude(x, sigma, k0, x0);
const y = centerY - mag * h * 0.25;
if (i === 0) ctx.moveTo(i, y);
else ctx.lineTo(i, y);
}
ctx.lineTo(w, centerY);
ctx.fill();
// Draw Re(ψ) wave
ctx.strokeStyle = '#ff2200';
ctx.lineWidth = 2;
ctx.beginPath();
for (let i = 0; i <= w; i++) {
const x = xMin + (i / w) * (xMax - xMin);
const val = psiReal(x, sigma, k0, x0);
const y = centerY - val * h * 0.25;
if (i === 0) ctx.moveTo(i, y);
else ctx.lineTo(i, y);
}
ctx.stroke();
// Draw uncertainty arrow (Δx)
const deltaX = sigma;
const arrowStart = (x0 - deltaX/2 - xMin) * scale;
const arrowEnd = (x0 + deltaX/2 - xMin) * scale;
drawUncertaintyArrow(ctx, arrowStart, 20, arrowEnd, 20, 'Δx');
// Draw grid labels
ctx.fillStyle = '#555555';
ctx.font = '10px "DM Mono", monospace';
ctx.textAlign = 'center';
ctx.fillText('x', w - 10, centerY + 15);
}
function drawMomentumSpace(canvas, sigma, k0) {
const ctx = canvas.getContext('2d');
const w = canvas.width;
const h = canvas.height;
ctx.fillStyle = '#0a0a0a';
ctx.fillRect(0, 0, w, h);
const kMin = -4, kMax = 8;
const scale = w / (kMax - kMin);
const centerY = h / 2;
// Draw axes
ctx.strokeStyle = '#2a2a2a';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, centerY);
ctx.lineTo(w, centerY);
ctx.stroke();
// Draw probability density |φ(k)|²
ctx.fillStyle = 'rgba(68, 136, 255, 0.4)';
ctx.beginPath();
ctx.moveTo(0, centerY);
for (let i = 0; i <= w; i++) {
const k = kMin + (i / w) * (kMax - kMin);
const mag = phiMagnitude(k, sigma, k0);
const y = centerY - mag * h * 0.25;
if (i === 0) ctx.moveTo(i, y);
else ctx.lineTo(i, y);
}
ctx.lineTo(w, centerY);
ctx.fill();
// Draw uncertainty arrow (Δk)
const deltaK = 1 / (2 * sigma);
const arrowStart = (k0 - deltaK/2 - kMin) * scale;
const arrowEnd = (k0 + deltaK/2 - kMin) * scale;
drawUncertaintyArrow(ctx, arrowStart, 20, arrowEnd, 20, 'Δk');
// Draw grid labels
ctx.fillStyle = '#555555';
ctx.font = '10px "DM Mono", monospace';
ctx.textAlign = 'center';
ctx.fillText('k', w - 10, centerY + 15);
}
function drawUncertaintyArrow(ctx, x1, y, x2, y2, label) {
const arrowSize = 6;
ctx.strokeStyle = '#ff2200';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(x1, y);
ctx.lineTo(x2, y);
ctx.stroke();
// Arrow heads
ctx.beginPath();
ctx.moveTo(x1, y - arrowSize/2);
ctx.lineTo(x1 - arrowSize/2, y);
ctx.lineTo(x1, y + arrowSize/2);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x2, y - arrowSize/2);
ctx.lineTo(x2 + arrowSize/2, y);
ctx.lineTo(x2, y + arrowSize/2);
ctx.stroke();
// Label
ctx.fillStyle = '#ff2200';
ctx.font = 'bold 11px "DM Mono", monospace';
ctx.textAlign = 'center';
ctx.fillText(label, (x1 + x2) / 2, y - 12);
}
function drawUncertaintyRectangle(canvas, sigma, k0) {
const ctx = canvas.getContext('2d');
const w = canvas.width;
const h = canvas.height;
ctx.fillStyle = '#0a0a0a';
ctx.fillRect(0, 0, w, h);
const deltaX = sigma;
const deltaP = HBAR / (2 * sigma);
const maxX = 6;
const maxP = 3;
const centerX = w / 2;
const centerY = h / 2;
// Scale factors
const scaleX = w / (2 * maxX);
const scaleP = h / (2 * maxP);
// Draw axes
ctx.strokeStyle = '#2a2a2a';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(centerX, 0);
ctx.lineTo(centerX, h);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, centerY);
ctx.lineTo(w, centerY);
ctx.stroke();
// Draw uncertainty rectangle
const rectWidth = deltaX * scaleX;
const rectHeight = deltaP * scaleP;
ctx.fillStyle = 'rgba(255, 34, 0, 0.2)';
ctx.fillRect(
centerX - rectWidth/2,
centerY - rectHeight/2,
rectWidth,
rectHeight
);
ctx.strokeStyle = '#ff2200';
ctx.lineWidth = 2;
ctx.strokeRect(
centerX - rectWidth/2,
centerY - rectHeight/2,
rectWidth,
rectHeight
);
// Draw minimum uncertainty line
ctx.strokeStyle = '#4488ff';
ctx.lineWidth = 2;
ctx.setLineDash([4, 4]);
const minArea = HBAR / 2;
const minRectHeight = minArea / deltaX * scaleP;
ctx.strokeRect(
centerX - rectWidth/2,
centerY - minRectHeight/2,
rectWidth,
minRectHeight
);
ctx.setLineDash([]);
// Labels
ctx.fillStyle = '#e8e0d5';
ctx.font = '12px "DM Mono", monospace';
ctx.textAlign = 'center';
ctx.fillText('Δx', centerX, centerY + 40);
ctx.textAlign = 'right';
ctx.fillText('Δp', centerX - 30, centerY);
ctx.fillStyle = '#555555';
ctx.font = '10px "DM Mono", monospace';
ctx.textAlign = 'center';
ctx.fillText(`Area = Δx·Δp = ${(deltaX * deltaP).toFixed(3)}ℏ`, centerX, h - 15);
}
// ============ Energy-Time Visualizations ============
function drawTimeSpace(canvas, sigma, k0) {
const ctx = canvas.getContext('2d');
const w = canvas.width;
const h = canvas.height;
ctx.fillStyle = '#0a0a0a';
ctx.fillRect(0, 0, w, h);
const tMin = -5, tMax = 5;
const centerY = h / 2;
// Draw axes
ctx.strokeStyle = '#2a2a2a';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, centerY);
ctx.lineTo(w, centerY);
ctx.stroke();
// Draw time-domain wave packet
ctx.fillStyle = 'rgba(255, 34, 0, 0.4)';
ctx.beginPath();
ctx.moveTo(0, centerY);
for (let i = 0; i <= w; i++) {
const t = tMin + (i / w) * (tMax - tMin);
// Gaussian envelope with oscillation
const envelope = Math.exp(-t * t / (2 * sigma * sigma));
const oscillation = Math.cos(2 * k0 * t);
const y = centerY - envelope * oscillation * h * 0.25;
if (i === 0) ctx.moveTo(i, y);
else ctx.lineTo(i, y);
}
ctx.lineTo(w, centerY);
ctx.fill();
// Draw wave
ctx.strokeStyle = '#ff2200';
ctx.lineWidth = 2;
ctx.beginPath();
for (let i = 0; i <= w; i++) {
const t = tMin + (i / w) * (tMax - tMin);
const envelope = Math.exp(-t * t / (2 * sigma * sigma));
const oscillation = Math.cos(2 * k0 * t);
const y = centerY - envelope * oscillation * h * 0.25;
if (i === 0) ctx.moveTo(i, y);
else ctx.lineTo(i, y);
}
ctx.stroke();
// Draw duration arrow
const deltaT = sigma;
const scale = w / (tMax - tMin);
const arrowStart = (-deltaT/2 - tMin) * scale;
const arrowEnd = (deltaT/2 - tMin) * scale;
drawUncertaintyArrow(ctx, arrowStart, 20, arrowEnd, 20, 'Δt');
}
function drawFrequencySpace(canvas, sigma, k0) {
const ctx = canvas.getContext('2d');
const w = canvas.width;
const h = canvas.height;
ctx.fillStyle = '#0a0a0a';
ctx.fillRect(0, 0, w, h);
const omegaMin = -4, omegaMax = 8;
const centerY = h / 2;
const scale = w / (omegaMax - omegaMin);
// Draw axes
ctx.strokeStyle = '#2a2a2a';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, centerY);
ctx.lineTo(w, centerY);
ctx.stroke();
// Draw frequency spectrum (Fourier transform)
const deltaOmega = 1 / sigma;
const norm = Math.pow(PI, -0.25) * Math.sqrt(sigma);
ctx.fillStyle = 'rgba(68, 136, 255, 0.4)';
ctx.beginPath();
ctx.moveTo(0, centerY);
for (let i = 0; i <= w; i++) {
const omega = omegaMin + (i / w) * (omegaMax - omegaMin);
const mag = norm * Math.exp(-Math.pow(omega - 2 * k0, 2) / (2 * deltaOmega * deltaOmega));
const y = centerY - mag * h * 0.25;
if (i === 0) ctx.moveTo(i, y);
else ctx.lineTo(i, y);
}
ctx.lineTo(w, centerY);
ctx.fill();
// Draw uncertainty arrow (Δω)
const arrowStart = (2 * k0 - deltaOmega/2 - omegaMin) * scale;
const arrowEnd = (2 * k0 + deltaOmega/2 - omegaMin) * scale;
drawUncertaintyArrow(ctx, arrowStart, 20, arrowEnd, 20, 'Δω');
ctx.fillStyle = '#555555';
ctx.font = '10px "DM Mono", monospace';
ctx.textAlign = 'center';
ctx.fillText('ω', w - 10, centerY + 15);
}
// ============ Single Slit Diffraction ============
function drawSlitDiagram(canvas, slitWidth) {
const ctx = canvas.getContext('2d');
const w = canvas.width;
const h = canvas.height;
ctx.fillStyle = '#0a0a0a';
ctx.fillRect(0, 0, w, h);
const slitY = h / 2;
const slitPixels = (slitWidth / 3) * (h / 2);
// Draw slit
ctx.fillStyle = '#333333';
ctx.fillRect(w * 0.3, 0, w * 0.15, slitY - slitPixels / 2);
ctx.fillRect(w * 0.3, slitY + slitPixels / 2, w * 0.15, slitY - slitPixels / 2);
// Draw opening
ctx.fillStyle = 'rgba(68, 136, 255, 0.2)';
ctx.fillRect(w * 0.3, slitY - slitPixels / 2, w * 0.15, slitPixels);
// Draw incident wave
ctx.strokeStyle = '#ff2200';
ctx.lineWidth = 2;
for (let y = 0; y < h; y += 10) {
ctx.beginPath();
ctx.moveTo(20, y);
ctx.lineTo(w * 0.3, y);
ctx.stroke();
}
// Draw diffracted waves (schematic)
ctx.strokeStyle = '#4488ff';
ctx.lineWidth = 1.5;
ctx.globalAlpha = 0.5;
const diffractAngle = 0.3 / slitWidth;
for (let i = 0; i < 5; i++) {
const angle = diffractAngle * (i - 2);
ctx.beginPath();
ctx.moveTo(w * 0.45, slitY);
ctx.lineTo(w - 10, slitY + angle * (w - 10) * 0.25);
ctx.stroke();
}
ctx.globalAlpha = 1;
// Labels
ctx.fillStyle = '#e8e0d5';
ctx.font = '11px "DM Mono", monospace';
ctx.textAlign = 'left';
ctx.fillText('incident', 10, 20);
ctx.textAlign = 'right';
ctx.fillText('diffracted', w - 10, 20);
// Draw slit width indicator
ctx.strokeStyle = '#ff2200';
ctx.lineWidth = 1;
ctx.setLineDash([2, 2]);
ctx.beginPath();
ctx.moveTo(w * 0.29, slitY - slitPixels / 2 - 10);
ctx.lineTo(w * 0.29, slitY + slitPixels / 2 + 10);
ctx.stroke();
ctx.setLineDash([]);
ctx.fillStyle = '#ff2200';
ctx.font = 'bold 11px "DM Mono", monospace';
ctx.textAlign = 'right';
ctx.fillText(`Δx = ${slitWidth.toFixed(2)}`, w * 0.28, slitY);
}
function drawDiffractionPattern(canvas, slitWidth) {
const ctx = canvas.getContext('2d');
const w = canvas.width;
const h = canvas.height;
ctx.fillStyle = '#0a0a0a';
ctx.fillRect(0, 0, w, h);
const centerY = h / 2;
// Draw diffraction pattern (single slit)
ctx.fillStyle = 'rgba(68, 136, 255, 0.3)';
const wavelength = 1; // Normalized
const scale = w / (10 * wavelength);
for (let x = 0; x < w; x++) {
const angle = (x / w - 0.5) * 6 * wavelength;
const sinc = Math.abs(slitWidth * Math.sin(angle)) > 0.01
? Math.sin(slitWidth * Math.sin(angle)) / (slitWidth * Math.sin(angle))
: 1;
const intensity = sinc * sinc;
ctx.globalAlpha = intensity * 0.8;
ctx.fillStyle = intensity > 0.5 ? '#4488ff' : 'rgba(68, 136, 255, 0.2)';
ctx.fillRect(x, centerY - h * 0.35, 1, h * 0.7);
}
ctx.globalAlpha = 1;
// Draw axes
ctx.strokeStyle = '#2a2a2a';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, centerY);
ctx.lineTo(w, centerY);
ctx.stroke();
// Draw central maximum
ctx.strokeStyle = '#00c896';
ctx.lineWidth = 2;
ctx.setLineDash([4, 4]);
ctx.beginPath();
ctx.moveTo(w / 2 - 20, 0);
ctx.lineTo(w / 2 - 20, h);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(w / 2 + 20, 0);
ctx.lineTo(w / 2 + 20, h);
ctx.stroke();
ctx.setLineDash([]);
ctx.fillStyle = '#555555';
ctx.font = '10px "DM Mono", monospace';
ctx.textAlign = 'center';
ctx.fillText('angle θ', w / 2, h - 10);
}
// ============ Update Functions ============
function updateUncertaintyValues() {
const deltaX = state.sigma;
const deltaK = 1 / (2 * state.sigma);
const deltaP = HBAR * deltaK;
const product = deltaX * deltaP;
document.getElementById('deltaX').textContent = deltaX.toFixed(3);
document.getElementById('deltaK').textContent = deltaK.toFixed(3);
document.getElementById('deltaP').textContent = deltaP.toFixed(3);
document.getElementById('hbar').textContent = (HBAR / 2).toFixed(3);
document.getElementById('product').textContent = product.toFixed(3);
}
function updateEnergyTimeValues() {
const deltaT = state.sigma;
const deltaOmega = 1 / state.sigma;
const deltaE = HBAR * deltaOmega;
const product = deltaE * deltaT;
document.getElementById('deltaT').textContent = deltaT.toFixed(3);
document.getElementById('deltaE').textContent = deltaE.toFixed(3);
document.getElementById('et-product').textContent = product.toFixed(3);
}
function updateDiffractionValues() {
const a = state.slitWidth;
const wavelength = PI / 2; // Normalized
const theta1 = wavelength / a;
const deltaP = HBAR * Math.PI / a;
const product = a * deltaP / HBAR;
document.getElementById('slitWidth').textContent = a.toFixed(2);
document.getElementById('angularSpread').textContent = theta1.toFixed(3);
document.getElementById('slitDeltaP').textContent = (deltaP / HBAR).toFixed(2);
document.getElementById('slit-product').textContent = product.toFixed(2);
}
function render() {
if (state.mode === 'position-momentum') {
const posCanvas = document.getElementById('positionCanvas');
const momCanvas = document.getElementById('momentumCanvas');
const uncCanvas = document.getElementById('uncertaintyCanvas');
drawPositionSpace(posCanvas, state.sigma, state.k0, state.x0);
drawMomentumSpace(momCanvas, state.sigma, state.k0);
drawUncertaintyRectangle(uncCanvas, state.sigma, state.k0);
updateUncertaintyValues();
} else if (state.mode === 'energy-time') {
const timeCanvas = document.getElementById('timeCanvas');
const freqCanvas = document.getElementById('frequencyCanvas');
drawTimeSpace(timeCanvas, state.sigma, state.k0);
drawFrequencySpace(freqCanvas, state.sigma, state.k0);
updateEnergyTimeValues();
} else if (state.mode === 'diffraction') {
const slitCanvas = document.getElementById('slitCanvas');
const diffCanvas = document.getElementById('diffrationCanvas');
drawSlitDiagram(slitCanvas, state.slitWidth);
drawDiffractionPattern(diffCanvas, state.slitWidth);
updateDiffractionValues();
}
}
// ============ Event Listeners ============
document.getElementById('sigmaSlider').addEventListener('input', (e) => {
state.sigma = Math.pow(10, parseFloat(e.target.value));
document.getElementById('sigmaValue').textContent = state.sigma.toFixed(2);
render();
});
document.getElementById('k0Slider').addEventListener('input', (e) => {
state.k0 = parseFloat(e.target.value);
document.getElementById('k0Value').textContent = state.k0.toFixed(2);
render();
});
document.getElementById('x0Slider').addEventListener('input', (e) => {
state.x0 = parseFloat(e.target.value);
document.getElementById('x0Value').textContent = state.x0.toFixed(2);
render();
});
document.getElementById('slitSlider').addEventListener('input', (e) => {
state.slitWidth = parseFloat(e.target.value);
document.getElementById('slitValue').textContent = state.slitWidth.toFixed(2);
render();
});
document.querySelectorAll('.mode-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active'));
e.target.classList.add('active');
state.mode = e.target.dataset.mode;
// Toggle tab visibility
document.querySelectorAll('.tab-content').forEach(tab => tab.classList.remove('active'));
document.getElementById(state.mode).classList.add('active');
render();
});
});
// ============ Initial Render ============
render();