quantum mechanics

Quantum Tunneling

Explore how quantum particles tunnel through potential barriers. Adjust energy, barrier width, and particle mass to see how tunneling probability changes. Compare quantum predictions with classical physics.

Wave Representation
Classical prediction: 0% transmission (impossible)
Quantum result: Depends on parameters
Energy Level Diagram
Transmission vs Energy
Developer Reference

Core Algorithm & Standalone Script

Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.

// Physics constants and state
    const state = {
      energy: 0.5,        // E/V0
      barrierWidth: 1.0,  // d (in units of ℏ/√(2mV0))
      mass: 1.0,          // m (relative)
      speedFactor: 1.0,   // animation speed
      barrierType: 'rectangular',
      animating: false,
      time: 0,
      maxTime: 20,
      displayMode: 'magnitude', // magnitude, real, imag
      v0: 1.0 // barrier height (constant)
    };

    const canvas = document.getElementById('waveCanvas');
    const ctx = canvas.getContext('2d');

    // Canvas dimensions
    const canvasWidth = canvas.width;
    const canvasHeight = canvas.height;
    const padding = 40;
    const plotWidth = canvasWidth - 2 * padding;
    const plotHeight = canvasHeight - 2 * padding;

    // Constants for simulation (using atomic units)
    const hBar = 1.0;
    const xMin = -5;
    const xMax = 8;
    const dx = (xMax - xMin) / 300;
    const k0 = Math.sqrt(2 * state.mass * state.energy * state.v0) / hBar;

    // Barrier position
    const barrierXmin = 2;
    const barrierXmax = barrierXmin + state.barrierWidth;

    // UI Elements
    const energySlider = document.getElementById('energySlider');
    const widthSlider = document.getElementById('widthSlider');
    const massSlider = document.getElementById('massSlider');
    const speedSlider = document.getElementById('speedSlider');
    const barrierTypeSelect = document.getElementById('barrierType');
    const playBtn = document.getElementById('playBtn');
    const resetBtn = document.getElementById('resetBtn');
    const modeButtons = document.querySelectorAll('.tab-btn');
    const energyVal = document.getElementById('energyVal');
    const widthVal = document.getElementById('widthVal');
    const massVal = document.getElementById('massVal');
    const speedVal = document.getElementById('speedVal');

    // Event listeners
    energySlider.addEventListener('input', (e) => {
      state.energy = parseFloat(e.target.value);
      energyVal.textContent = state.energy.toFixed(2);
      updateStats();
      animate();
    });

    widthSlider.addEventListener('input', (e) => {
      state.barrierWidth = parseFloat(e.target.value);
      widthVal.textContent = state.barrierWidth.toFixed(1);
      updateStats();
      animate();
    });

    massSlider.addEventListener('input', (e) => {
      state.mass = parseFloat(e.target.value);
      massVal.textContent = state.mass.toFixed(1);
      updateStats();
      animate();
    });

    speedSlider.addEventListener('input', (e) => {
      state.speedFactor = parseFloat(e.target.value);
      speedVal.textContent = state.speedFactor.toFixed(1) + '×';
    });

    barrierTypeSelect.addEventListener('change', (e) => {
      state.barrierType = e.target.value;
      state.time = 0;
      animate();
    });

    playBtn.addEventListener('click', () => {
      state.animating = !state.animating;
      playBtn.textContent = state.animating ? '⏸ Pause' : '▶ Play';
      if (state.animating) {
        animationLoop();
      }
    });

    resetBtn.addEventListener('click', () => {
      state.time = 0;
      state.animating = false;
      playBtn.textContent = '▶ Play';
      animate();
    });

    modeButtons.forEach(btn => {
      btn.addEventListener('click', () => {
        modeButtons.forEach(b => b.classList.remove('active'));
        btn.classList.add('active');
        state.displayMode = btn.dataset.mode;
        animate();
      });
    });

    // Physics calculations
    function calculateTransmission() {
      const E = state.energy * state.v0;
      const V0 = state.v0;
      const m = state.mass;
      const d = state.barrierWidth;

      if (E < V0) {
        // Tunneling regime
        const kappa = Math.sqrt(2 * m * (V0 - E)) / hBar;
        const sinhKd = Math.sinh(kappa * d);
        const numerator = V0 * V0 * sinhKd * sinhKd;
        const denominator = 4 * E * (V0 - E) + numerator;
        return 1 / (1 + numerator / denominator);
      } else {
        // Above barrier regime
        const kPrime = Math.sqrt(2 * m * (E - V0)) / hBar;
        const sinKd = Math.sin(kPrime * d);
        const numerator = V0 * V0 * sinKd * sinKd;
        const denominator = 4 * E * (E - V0) + numerator;
        return 1 / (1 + numerator / denominator);
      }
    }

    function getDecayLength() {
      if (state.energy >= state.v0) return 0;
      const kappa = Math.sqrt(2 * state.mass * (state.v0 - state.energy)) / hBar;
      return 1 / kappa;
    }

    function updateStats() {
      const T = calculateTransmission();
      const R = 1 - T;
      const decayLen = getDecayLength();
      const ratio = state.energy / state.v0;
      const mode = state.energy < state.v0 ? 'Tunneling' : 'Above Barrier';

      document.getElementById('transmissionStat').textContent = (T * 100).toFixed(2) + '%';
      document.getElementById('reflectionStat').textContent = (R * 100).toFixed(2) + '%';
      document.getElementById('ratioStat').textContent = ratio.toFixed(2);
      document.getElementById('decayLenStat').textContent = decayLen.toFixed(3);
      document.getElementById('modeStat').textContent = mode;
      document.getElementById('classicalText').textContent =
        state.energy < state.v0
          ? `${(T * 100).toFixed(1)}% (quantum tunneling!)`
          : `${(T * 100).toFixed(1)}% (above barrier)`;

      drawEnergyDiagram();
      drawTransmissionPlot();
    }

    function getBarrierHeight(x) {
      if (x < barrierXmin || x > barrierXmax) return 0;

      switch (state.barrierType) {
        case 'rectangular':
          return state.v0;
        case 'double':
          const d1 = barrierXmin;
          const d2 = (barrierXmin + barrierXmax) / 2;
          const d3 = barrierXmax;
          if (x < d2) {
            return (x - d1) / (d2 - d1) * state.v0;
          } else {
            return ((d3 - x) / (d3 - d2)) * state.v0;
          }
        case 'ramp':
          const progress = (x - barrierXmin) / (barrierXmax - barrierXmin);
          return state.v0 * progress;
        default:
          return state.v0;
      }
    }

    function complex(real, imag = 0) {
      return { real, imag };
    }

    function complexExp(phase) {
      return { real: Math.cos(phase), imag: Math.sin(phase) };
    }

    function complexMult(a, b) {
      return {
        real: a.real * b.real - a.imag * b.imag,
        imag: a.real * b.imag + a.imag * b.real
      };
    }

    function complexAdd(a, b) {
      return { real: a.real + b.real, imag: a.imag + b.imag };
    }

    function complexMag(c) {
      return Math.sqrt(c.real * c.real + c.imag * c.imag);
    }

    function computeWave(t) {
      const wave = [];
      const E = state.energy * state.v0;
      const k = Math.sqrt(2 * state.mass * E) / hBar;
      const omega = E / hBar;
      const T = calculateTransmission() ** 0.5;
      const R = (1 - calculateTransmission()) ** 0.5;

      for (let x = xMin; x <= xMax; x += dx) {
        let psi;

        if (x < barrierXmin) {
          // Region I: incident + reflected
          const incident = complexExp(k * x - omega * t);
          const reflected = complexMult(complex(R), complexExp(-k * x - omega * t));
          psi = complexAdd(incident, reflected);
        } else if (x > barrierXmax) {
          // Region III: transmitted
          const transmitted = complexMult(complex(T * 0.5), complexExp(k * (x - barrierXmax) - omega * t));
          psi = transmitted;
        } else {
          // Region II: inside barrier
          const kappa = Math.sqrt(2 * state.mass * Math.abs(state.v0 - E)) / hBar;
          const decay = Math.exp(-kappa * (x - barrierXmin));
          const phase = -state.v0 * t / hBar;
          psi = complexMult(complex(decay * 0.5), complexExp(phase));
        }

        wave.push({ x, psi });
      }

      return wave;
    }

    function drawCanvas() {
      ctx.fillStyle = '#0a0a0a';
      ctx.fillRect(0, 0, canvasWidth, canvasHeight);

      // Draw axes
      ctx.strokeStyle = '#1e1e1e';
      ctx.lineWidth = 1;
      ctx.beginPath();
      ctx.moveTo(padding, canvasHeight - padding);
      ctx.lineTo(canvasWidth - padding, canvasHeight - padding);
      ctx.stroke();

      ctx.beginPath();
      ctx.moveTo(padding, padding);
      ctx.lineTo(padding, canvasHeight - padding);
      ctx.stroke();

      // Draw grid
      ctx.strokeStyle = '#111111';
      for (let i = 0; i <= 10; i++) {
        const x = padding + (plotWidth / 10) * i;
        ctx.beginPath();
        ctx.moveTo(x, canvasHeight - padding);
        ctx.lineTo(x, canvasHeight - padding + 5);
        ctx.stroke();

        const y = canvasHeight - padding - (plotHeight / 10) * i;
        ctx.beginPath();
        ctx.moveTo(padding - 5, y);
        ctx.lineTo(padding, y);
        ctx.stroke();
      }

      // Draw potential barrier
      ctx.fillStyle = 'rgba(136, 136, 136, 0.2)';
      const barrierX1 = padding + ((barrierXmin - xMin) / (xMax - xMin)) * plotWidth;
      const barrierX2 = padding + ((barrierXmax - xMin) / (xMax - xMin)) * plotWidth;
      const barrierY = canvasHeight - padding - (state.v0 / (state.v0 * 1.5)) * plotHeight;
      ctx.fillRect(barrierX1, barrierY, barrierX2 - barrierX1, canvasHeight - padding - barrierY);

      // Draw barrier outline
      ctx.strokeStyle = '#888888';
      ctx.lineWidth = 2;
      ctx.strokeRect(barrierX1, barrierY, barrierX2 - barrierX1, canvasHeight - padding - barrierY);

      // Compute and draw wave
      const wave = computeWave(state.time * 0.5);

      // Draw probability density |Ψ|²
      if (state.displayMode === 'magnitude') {
        ctx.fillStyle = 'rgba(68, 136, 255, 0.3)';
        ctx.beginPath();
        ctx.moveTo(padding, canvasHeight - padding);

        for (let i = 0; i < wave.length; i++) {
          const { x, psi } = wave[i];
          const mag = complexMag(psi);
          const xPos = padding + ((x - xMin) / (xMax - xMin)) * plotWidth;
          const yPos = canvasHeight - padding - (mag * mag / (state.v0 * 0.3)) * plotHeight;
          ctx.lineTo(xPos, yPos);
        }

        ctx.lineTo(canvasWidth - padding, canvasHeight - padding);
        ctx.closePath();
        ctx.fill();

        // Incident wave outline
        ctx.strokeStyle = '#4488ff';
        ctx.lineWidth = 2;
        ctx.beginPath();
        for (let i = 0; i < wave.length; i++) {
          const { x, psi } = wave[i];
          const mag = complexMag(psi);
          const xPos = padding + ((x - xMin) / (xMax - xMin)) * plotWidth;
          const yPos = canvasHeight - padding - (mag * mag / (state.v0 * 0.3)) * plotHeight;
          if (i === 0) ctx.moveTo(xPos, yPos);
          else ctx.lineTo(xPos, yPos);
        }
        ctx.stroke();
      }

      // Draw real part
      if (state.displayMode === 'real') {
        ctx.strokeStyle = '#4488ff';
        ctx.lineWidth = 2;
        ctx.beginPath();
        for (let i = 0; i < wave.length; i++) {
          const { x, psi } = wave[i];
          const xPos = padding + ((x - xMin) / (xMax - xMin)) * plotWidth;
          const yPos = canvasHeight - padding - (psi.real / (state.v0 * 0.5)) * plotHeight;
          if (i === 0) ctx.moveTo(xPos, yPos);
          else ctx.lineTo(xPos, yPos);
        }
        ctx.stroke();
      }

      // Draw imaginary part
      if (state.displayMode === 'imag') {
        ctx.strokeStyle = '#ff8800';
        ctx.lineWidth = 2;
        ctx.beginPath();
        for (let i = 0; i < wave.length; i++) {
          const { x, psi } = wave[i];
          const xPos = padding + ((x - xMin) / (xMax - xMin)) * plotWidth;
          const yPos = canvasHeight - padding - (psi.imag / (state.v0 * 0.5)) * plotHeight;
          if (i === 0) ctx.moveTo(xPos, yPos);
          else ctx.lineTo(xPos, yPos);
        }
        ctx.stroke();
      }

      // Draw axis labels
      ctx.fillStyle = '#555555';
      ctx.font = '12px "DM Mono"';
      ctx.textAlign = 'center';
      ctx.fillText('x (position)', canvasWidth / 2, canvasHeight - 10);

      ctx.save();
      ctx.translate(10, canvasHeight / 2);
      ctx.rotate(-Math.PI / 2);
      ctx.textAlign = 'center';
      ctx.fillText(state.displayMode === 'magnitude' ? '|Ψ|²' : (state.displayMode === 'real' ? 'Re(Ψ)' : 'Im(Ψ)'), 0, 0);
      ctx.restore();

      // Draw time display
      ctx.fillStyle = '#e8e0d5';
      ctx.textAlign = 'left';
      ctx.font = 'bold 14px "DM Mono"';
      ctx.fillText(`t = ${state.time.toFixed(1)}`, padding + 10, padding + 20);
    }

    function drawEnergyDiagram() {
      const canvas = document.getElementById('energyDiagram');
      if (!canvas) return;

      const ctx = canvas.getContext('2d');
      const width = canvas.offsetWidth;
      const height = canvas.offsetHeight;
      canvas.width = width;
      canvas.height = height;

      ctx.fillStyle = '#0a0a0a';
      ctx.fillRect(0, 0, width, height);

      const margin = 40;
      const diagramWidth = width - 2 * margin;
      const diagramHeight = height - 2 * margin;
      const centerY = margin + diagramHeight / 2;

      // Draw barrier
      ctx.fillStyle = 'rgba(136, 136, 136, 0.3)';
      const barrierX = margin + diagramWidth * 0.3;
      const barrierW = diagramWidth * 0.4;
      const barrierH = diagramHeight * 0.3;
      ctx.fillRect(barrierX, centerY - barrierH, barrierW, barrierH);

      ctx.strokeStyle = '#888888';
      ctx.lineWidth = 2;
      ctx.strokeRect(barrierX, centerY - barrierH, barrierW, barrierH);

      // Draw V0 line
      ctx.strokeStyle = '#ff2200';
      ctx.lineWidth = 2;
      ctx.setLineDash([5, 5]);
      ctx.beginPath();
      ctx.moveTo(margin, centerY - barrierH);
      ctx.lineTo(width - margin, centerY - barrierH);
      ctx.stroke();
      ctx.setLineDash([]);

      // Draw E line
      const eY = centerY - barrierH + (state.v0 - state.energy * state.v0) / state.v0 * barrierH;
      ctx.strokeStyle = '#00c896';
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.moveTo(margin, eY);
      ctx.lineTo(width - margin, eY);
      ctx.stroke();

      // Labels
      ctx.fillStyle = '#555555';
      ctx.font = '12px "DM Mono"';
      ctx.textAlign = 'right';
      ctx.fillText('V₀', margin - 10, centerY - barrierH - 5);
      ctx.fillText('E', margin - 10, eY + 5);

      ctx.fillStyle = '#e8e0d5';
      ctx.font = 'bold 12px "DM Mono"';
      ctx.textAlign = 'center';
      ctx.fillText('Potential Barrier', width / 2, height - 10);
    }

    function drawTransmissionPlot() {
      const canvas = document.getElementById('transmissionPlot');
      if (!canvas) return;

      const ctx = canvas.getContext('2d');
      const width = canvas.offsetWidth;
      const height = canvas.offsetHeight;
      canvas.width = width;
      canvas.height = height;

      ctx.fillStyle = '#0a0a0a';
      ctx.fillRect(0, 0, width, height);

      const margin = 40;
      const plotWidth = width - 2 * margin;
      const plotHeight = height - 2 * margin;

      // Draw axes
      ctx.strokeStyle = '#1e1e1e';
      ctx.lineWidth = 1;
      ctx.beginPath();
      ctx.moveTo(margin, height - margin);
      ctx.lineTo(width - margin, height - margin);
      ctx.stroke();

      ctx.beginPath();
      ctx.moveTo(margin, margin);
      ctx.lineTo(margin, height - margin);
      ctx.stroke();

      // Draw grid
      ctx.strokeStyle = '#111111';
      for (let i = 0; i <= 5; i++) {
        const x = margin + (plotWidth / 5) * i;
        ctx.beginPath();
        ctx.moveTo(x, height - margin);
        ctx.lineTo(x, height - margin + 5);
        ctx.stroke();

        const y = height - margin - (plotHeight / 5) * i;
        ctx.beginPath();
        ctx.moveTo(margin - 5, y);
        ctx.lineTo(margin, y);
        ctx.stroke();
      }

      // Plot transmission curve
      const savedEnergy = state.energy;
      ctx.strokeStyle = '#ff2200';
      ctx.lineWidth = 2;
      ctx.beginPath();

      for (let eRatio = 0.1; eRatio <= 3.0; eRatio += 0.05) {
        state.energy = eRatio;
        const T = calculateTransmission();
        const x = margin + (eRatio - 0.1) / 2.9 * plotWidth;
        const y = height - margin - T * plotHeight;

        if (eRatio === 0.1) {
          ctx.moveTo(x, y);
        } else {
          ctx.lineTo(x, y);
        }
      }
      ctx.stroke();

      // Highlight current point
      state.energy = savedEnergy;
      const currentX = margin + (savedEnergy - 0.1) / 2.9 * plotWidth;
      const currentT = calculateTransmission();
      const currentY = height - margin - currentT * plotHeight;

      ctx.fillStyle = '#00c896';
      ctx.beginPath();
      ctx.arc(currentX, currentY, 6, 0, Math.PI * 2);
      ctx.fill();

      // Labels
      ctx.fillStyle = '#555555';
      ctx.font = '11px "DM Mono"';
      ctx.textAlign = 'center';
      ctx.fillText('E / V₀', width / 2, height - 5);

      ctx.save();
      ctx.translate(10, height / 2);
      ctx.rotate(-Math.PI / 2);
      ctx.textAlign = 'center';
      ctx.fillText('Transmission (T)', 0, 0);
      ctx.restore();

      // Axis values
      ctx.textAlign = 'center';
      ctx.fillStyle = '#555555';
      for (let i = 0; i <= 5; i++) {
        const val = 0.1 + (3.0 - 0.1) * (i / 5);
        const x = margin + (plotWidth / 5) * i;
        ctx.fillText(val.toFixed(1), x, height - margin + 20);

        const yVal = (i / 5);
        const y = height - margin - (plotHeight / 5) * i;
        ctx.textAlign = 'right';
        ctx.fillText((yVal * 100).toFixed(0) + '%', margin - 10, y + 5);
        ctx.textAlign = 'center';
      }
    }

    function animate() {
      drawCanvas();
    }

    function animationLoop() {
      state.time += 0.1 * state.speedFactor;
      if (state.time > state.maxTime) {
        state.time = 0;
      }
      animate();

      if (state.animating) {
        requestAnimationFrame(animationLoop);
      }
    }

    // Initial render
    updateStats();
    animate();