Quantum Tunneling
Inspired by the quantum tunneling series at physicspython.wordpress.com.
Roll a ball at a hill with less energy than it takes to reach the top, and the ball always comes back. Send a quantum wavepacket at a barrier taller than its energy, and some of it shows up on the other side. Let’s watch that happen, and then build the simulation that produced it.

The blue curve is the probability density of the particle, and the red rectangle is the barrier (drawn at a tenth of its height so it fits on the plot). Notice the ripples that appear when the packet reaches the wall. The incoming and reflected waves overlap and interfere, which is something a bouncing ball would never do. Most of the packet comes back, but watch the region inside and beyond the barrier closely. A small piece leaks through and keeps going. That piece is the tunneling.
Setting up the problem
We work in units where . The particle starts as a Gaussian wavepacket centered at with width , given a push to the right by the plane-wave factor ,
The mean momentum is , so the mean kinetic energy is . In the animation and the barrier height is , which puts the packet’s energy right at the top of the barrier. Even so, most of it reflects. The barrier is wide (50 length units against a de Broglie wavelength of ), and a wide barrier suppresses transmission severely.
The packet then evolves under the time-dependent Schrödinger equation
with inside the barrier and zero outside.
Putting it on a grid
A computer cannot hold a continuous function, so we sample at evenly spaced points and turn into a matrix. The second derivative becomes the familiar three-point finite difference (think back to the limit definition of a derivative, applied twice),
which makes a tridiagonal matrix. The diagonal holds and the two off-diagonals hold . The animation uses 500 points on the interval .
Time evolution
How do we step forward in time? The obvious move is to replace the time derivative by a forward difference and write . Try it, and the wavefunction grows without bound. The operator is not unitary. Every step therefore inflates the norm a little, and the error compounds over many steps. The fix is the Crank-Nicolson scheme, which treats the step half explicitly and half implicitly,
The combined one-step operator is unitary (it is the Cayley form of ), so the norm is preserved to machine precision no matter how long we run. Each frame of the animation is a handful of these steps, with the elapsed time converted to femtoseconds through the atomic unit of time, fs.
A caveat about the red vertical lines at the edges. The grid ends there, and the wavefunction is pinned to zero at the boundary, which acts like an infinitely hard wall. Run the simulation long enough and the reflected packet bounces off the left wall and comes back for another round. The walls are an artifact of the finite grid, which is why the animation stops before the packet reaches them.
Feel free to contact me if there is an error or confusion!