quantum mechanics

UNCERTAINTY PRINCIPLE

Interactive simulation of the Heisenberg Uncertainty Principle. Visualize the fundamental limit of simultaneous position and momentum knowledge.

1.00
2.00
0.00

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.
1.00

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();