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