ECE 606 Lecture 6.1
Electron tunneling — the transfer matrix method
In the lecture
- where Section 6 starts: the closed-system recipe of Section 5 (ansatz, edge conditions, interface conditions, determinant, normalization) and what changes for an open system
- a single barrier as a scattering problem: an incident wave A, a reflected wave B, a transmitted wave E, and no wave F coming in from the right
- the wave in each region: plane waves where the electron is above the band edge, growing and decaying exponentials inside the barrier
- the interface conditions written as 2×2 matrices M₁ and M₂, and their product, the transfer matrix
- the general transfer-matrix method for any stack of layers, conceptually simple but with numerical limits
In this notebook
builds the interface matrices of the slides in a few lines of numpy, checks them against a general transfer-matrix code, and compares both with open-nemo's transmission, which comes from a discretized Schrödinger equation with open boundary conditions. It then rebuilds the wave function inside a barrier from the coefficients and compares it with open-nemo's injected density, and ends with a first look at why the method is said to have numerical issues.
Run it on your own machine
The notebook needs open-nemo (Python ≥ 3.10 and a C++20 compiler; BLAS/LAPACK).
git clone https://github.com/denphi/open-nemo.git && cd open-nemo
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[notebooks]"
jupyter lab From_Atoms_to_Devices/03_tunneling_and_bandstructure/06_1_transfer_matrix_method.ipynb
Code
import numpy as np
import matplotlib.pyplot as plt
import opennemo as nemo
from opennemo.visualization import style
style.use(notebook=True)
H2M0 = nemo.core.units.HBAR2_2M0 # ħ²/2m₀ = 0.0381 eV nm²
EM = nemo.structures.materials.EffectiveMassMaterial
1. From a closed box to an open system#
Section 5 solved closed problems: an electron in an infinite or finite well. The recipe had five steps:
- write a solution ansatz in each of the \(N\) regions of constant potential (\(2N\) unknown coefficients),
- impose \(\psi(\pm\infty) = 0\) at the two outer edges (2 unknowns fewer),
- impose continuity of \(\psi\) and \(d\psi/dx\) at every interface (\(2N-2\) equations),
- set the determinant of the coefficient matrix to zero, which selects the allowed energies,
- normalize \(\int|\psi|^2dx = 1\) to fix the last coefficient.
A tunneling problem is different: the electron comes in from one contact and may leave through the other. Every energy is allowed, so step 4 disappears, and the wave extends to infinity, so step 5 disappears too. Step 2 is replaced by an open boundary condition: we fix what comes in. The interface conditions of step 3 are unchanged, and they are all the physics there is.
Klimeck draws the single barrier as a two-port network, familiar from circuit analysis: each interface is a box that couples a wave coming in and a wave going out on each side. A wave of amplitude \(A\) is incident from the left, \(B\) is reflected, \(E\) is transmitted; \(C\) and \(D\) live inside the barrier. Usually nothing is injected from the right, \(F = 0\), and the incident wave is normalized, \(A = 1\). That removes two unknowns, and no particles are lost: what is not transmitted is reflected.
2. The three regions of a single barrier#
A barrier of height \(V_0\) and width \(L\) sits between \(x = 0\) and \(x = L\). The energy is measured from the conduction-band edge of the two outer regions. An electron of energy \(E < V_0\) is classically allowed in regions 1 and 3 and forbidden in region 2. The effective-mass Schrödinger equation \(d^2\psi/dx^2 + k^2\psi = 0\) has the solutions
As the lecture stresses, \(k\) and \(\gamma\) are the same object: each is measured from the band edge of its own region, upwards for \(k\), downwards from the barrier top for \(\gamma\). Inside the barrier the wave number is imaginary, \(k_2 = i\gamma\).
The barrier material has its own band structure, \(E = V_0 + \hbar^2k^2/2m\). At an energy below its
edge, its Bloch "waves" have a complex wave number: these are the evanescent states that the ansatz
\(e^{\pm\gamma x}\) describes. open-nemo's complex_bands computes them for the discretized barrier
material (a chain of sites with spacing \(a\)); its imaginary wave number is the decay constant
\(\gamma\). Throughout this notebook the mass is GaAs's, \(m = 0.067\,m_0\), and the barrier is
\(V_0 = 0.4\) eV high.
Code
M, V0, DX = 0.067, 0.4, 0.01 # mass (m₀), barrier height (eV), grid spacing (nm)
t0 = H2M0 / (M * DX**2) # hopping of the discretized barrier material (eV)
barrier_chain = nemo.Lead(np.array([[2 * t0 + V0]]), np.array([[-t0]]))
energies = np.array([0.05, 0.1, 0.2, 0.3, 0.35])
cb = nemo.complex_bands(barrier_chain, energies, period=DX)
gamma_nemo = np.abs(cb["k_imag"]).max(axis=1) # 1/nm
gamma_exact = np.sqrt(M * (V0 - energies) / H2M0)
print(" E (eV) γ open-nemo (1/nm) γ formula (1/nm) decay length 1/γ (nm)")
for e, gn, ge in zip(energies, gamma_nemo, gamma_exact):
print(f"{e:6.2f} {gn:15.5f} {ge:16.5f} {1 / ge:14.3f}")
E (eV) γ open-nemo (1/nm) γ formula (1/nm) decay length 1/γ (nm) 0.05 0.78453 0.78453 1.275 0.10 0.72633 0.72633 1.377 0.20 0.59305 0.59305 1.686 0.30 0.41935 0.41935 2.385 0.35 0.29652 0.29652 3.372
The decay constants agree to the fourth digit; the small difference is the grid (\(a = 0.01\) nm): a discretized chain has \(\cosh(\gamma a) = 1 + (V_0 - E)/2t_0\) rather than \(\gamma^2 = (V_0-E)/t_0a^2\). The decay length \(1/\gamma\) is about a nanometre: a barrier a few nanometres wide is crossed by an amplitude that has dropped by \(e^{-\gamma L}\), small but not zero. That is tunneling.
3. The interface conditions as matrices#
At \(x = 0\) continuity of \(\psi\) and \(\psi'\) gives \(A + B = C + D\) and \(ik(A - B) = -\gamma(C - D)\); at \(x = L\) the same two conditions connect \(C, D\) with \(E, F\). Each pair can be solved for the amplitudes on the left, which gives two 2×2 matrices:
Chaining the two, \([A, B]^T = M_1 M_2 [E, F]^T = M [E, F]^T\). With \(F = 0\) the first row reads \(A = M_{11}E\), so the transmitted amplitude is \(t = E/A = 1/M_{11}\), the reflected one \(r = B/A = M_{21}/M_{11}\), and the transmission is
The cell below codes \(M_1\) and \(M_2\) exactly as printed on the slide. Above the barrier the same formulas hold with \(\gamma = \sqrt{2m(V_0 - E)}/\hbar\) imaginary, so one function covers all energies.
Code
def slide_matrices(E, V0, L, m=M):
# M1 and M2 of slide 11, for arrays of energies E (eV); L in nm
k = np.sqrt(m * E / H2M0 + 0j)
g = np.sqrt(m * (V0 - E) / H2M0 + 0j) # imaginary above the barrier
M1 = 0.5 * np.array([[1 + 1j * g / k, 1 - 1j * g / k], [1 - 1j * g / k, 1 + 1j * g / k]])
M2 = 0.5 * np.array(
[
[(1 - 1j * k / g) * np.exp((1j * k + g) * L), (1 + 1j * k / g) * np.exp(-(1j * k - g) * L)],
[(1 + 1j * k / g) * np.exp((1j * k - g) * L), (1 - 1j * k / g) * np.exp(-(1j * k + g) * L)],
]
)
return np.moveaxis(M1, -1, 0), np.moveaxis(M2, -1, 0) # shape (nE, 2, 2)
def device(layers, dx=DX):
# layers: (width nm, band edge eV, mass m₀); the first and the last layer are the contacts
mats = [
nemo.Layer(EM(f"layer {j}", m, 12.9, conduction_band_edge=v), w) for j, (w, v, m) in enumerate(layers)
]
return nemo.Heterostructure1D(mats, spacing=dx)
L = 3.0
E = np.linspace(0.002, 1.0, 1500)
M1, M2 = slide_matrices(E, V0, L)
Mtot = M1 @ M2
T_slide = 1 / np.abs(Mtot[:, 0, 0]) ** 2
R_slide = np.abs(Mtot[:, 1, 0] / Mtot[:, 0, 0]) ** 2
single = device([(5.0, 0.0, M), (L, V0, M), (5.0, 0.0, M)])
T_nemo = nemo.Transport(single.hamiltonian).transmission(E)
print(f"largest |R + T − 1| from the slide's matrices: {np.abs(R_slide + T_slide - 1).max():.1e}")
print(f"largest |ln(T open-nemo / T slide)|: {np.abs(np.log(T_nemo / T_slide)).max():.1e}")
print(f"T at {E[0]*1e3:.0f} meV = {T_nemo[0]:.1e}, at the barrier top = {np.interp(V0, E, T_nemo):.2f}")
fig, (a1, a2) = plt.subplots(1, 2, figsize=(11, 3.8))
a1.semilogy(E, T_slide, **style.series("reference"), label="slide 11: 1/|M₁₁|²")
a1.semilogy(E, T_nemo, **style.series(lw=1.1), label="open-nemo (finite differences, open leads)")
a1.axvline(V0, color=style.CONTEXT, ls=":", lw=0.8)
a1.set(xlabel="E (eV)", ylabel="T(E)", title=f"one barrier, {V0} eV, {L} nm, m = {M} m₀")
a1.legend(frameon=False, fontsize=8)
a2.plot(E, np.log(T_nemo / T_slide), **style.series(lw=1))
a2.axhline(0, color=style.CONTEXT, lw=0.6)
a2.set(xlabel="E (eV)", ylabel="ln(T open-nemo / T slide)", title="difference")
fig.tight_layout();
largest |R + T − 1| from the slide's matrices: 1.3e-15 largest |ln(T open-nemo / T slide)|: 3.0e-05 T at 2 meV = 5.3e-04, at the barrier top = 0.39

The two transmissions lie on top of each other, from \(5\cdot10^{-4}\) at 2 meV to 0.39 at the barrier top. \(T\) is far from zero below the top and, as the next lecture shows, still below one above it: there is no sharp classical threshold (the subject of the next lecture). The matrices conserve the current: reflection and transmission add up to one to round-off. The remaining difference, at most \(3\cdot10^{-5}\) in \(\ln T\), is the discretization of open-nemo's grid, which grows with energy because the error of the three-point second derivative scales as \((ka)^2\) (Lecture 6.5).
4. The general transfer-matrix method#
Nothing in this construction is special to one barrier. For any stack of layers \(j = 1 \dots N\) of constant potential \(V_j\) and mass \(m_j\), write in each layer
(real or imaginary), with \(x_j\) the left edge of the layer. Continuity of \(\psi\) and of the flux \(\psi'/m\) (the BenDaniel–Duke condition, needed when the mass changes from layer to layer) at each interface gives one 2×2 matrix per interface, and
With \(A_1^+ = 1\) and \(A_N^- = 0\) the transmission is \(T = \frac{k_N/m_N}{k_1/m_1}\,|1/M_{11}|^2\): the prefactor is the ratio of the group velocities in the two contacts, which is 1 when they are the same material. The function below implements this for many energies at once, and the first check is that it reproduces the slide's \(M_1M_2\).
Code
def interface_steps(layers, E):
# one 2×2 matrix per interface (nE, 2, 2): [A_j⁺, A_j⁻] = step_j [A_j+1⁺, A_j+1⁻]
E = np.atleast_1d(E).astype(complex)
k = [np.sqrt(m * (E - v) / H2M0 + 0j) for _, v, m in layers]
steps = []
for j in range(len(layers) - 1):
d = layers[j][0] if j > 0 else 0.0 # the first contact: origin at the interface
(mj, kj), (mn, kn) = (layers[j][2], k[j]), (layers[j + 1][2], k[j + 1])
ep, em = np.exp(1j * kj * d), np.exp(-1j * kj * d)
W = np.array([[ep, em], [1j * kj / mj * ep, -1j * kj / mj * em]]) # ψ, ψ'/m of layer j
Wn = np.array([[np.ones_like(kn), np.ones_like(kn)], [1j * kn / mn, -1j * kn / mn]])
steps.append(np.linalg.solve(np.moveaxis(W, -1, 0), np.moveaxis(Wn, -1, 0)))
return steps, k
def transfer_matrix(layers, E):
# the product of the interface matrices, and the wave numbers k_j(E) (1/nm)
steps, k = interface_steps(layers, E)
total = steps[0]
for s in steps[1:]:
total = total @ s
return total, k
def tmm_transmission(layers, E):
total, k = transfer_matrix(layers, E)
flux = (k[-1] / layers[-1][2]).real / (k[0] / layers[0][2]).real
return flux / np.abs(total[:, 0, 0]) ** 2
one = [(0.0, 0.0, M), (L, V0, M), (0.0, 0.0, M)]
total, k = transfer_matrix(one, E)
# the slide counts the phase of E and F from x = 0, this code from x = L: E_slide = E e^(−ikL)
phase = np.stack([np.exp(1j * k[0] * L), np.exp(-1j * k[0] * L)], axis=-1)[:, None, :]
print(
f"general code against the slide's M1·M2: largest |ΔM| / |M| = "
f"{np.abs(total * phase - Mtot).max() / np.abs(Mtot).max():.1e}"
)
general code against the slide's M1·M2: largest |ΔM| / |M| = 5.0e-16
Same matrices, once the phase convention is matched: the slide counts the phase of \(E\) and \(F\) from \(x = 0\), the general code from the right interface \(x = L\), which multiplies \(E\) by \(e^{ikL}\) and leaves \(|M_{11}|\), and \(T\), unchanged. The homework the lecture suggests is the double barrier: four interfaces instead of two. Here it is computed both ways, together with a heterostructure in which the barrier has a heavier mass (\(0.092\,m_0\), as in AlGaAs) and the right contact sits 0.1 eV lower than the left one, so that all three ingredients of the general formula (complex \(k_j\), the mass in the flux, the velocity ratio) are exercised.
Code
cases = {
"double barrier 2 | 6 | 2 nm, 0.4 eV": [(5, 0, M), (2, V0, M), (6, 0, M), (2, V0, M), (5, 0, M)],
"barrier mass 0.092 m₀, right contact −0.1 eV": [(5, 0, M), (3, V0, 0.092), (5, -0.1, M)],
}
E2 = np.linspace(0.003, 0.8, 4000)
fig, axes = plt.subplots(1, 2, figsize=(11, 3.8))
for ax, (name, layers) in zip(axes, cases.items()):
t_tmm = tmm_transmission(layers, E2)
t_nemo = nemo.Transport(device(layers).hamiltonian).transmission(E2)
ax.semilogy(E2, t_tmm, **style.series("reference"), label="transfer matrices")
ax.semilogy(E2, t_nemo, **style.series(lw=1.0), label="open-nemo")
ax.set(xlabel="E (eV)", ylabel="T(E)", title=name)
ax.title.set_fontsize(10)
print(
f"{name}: median |ln T ratio| {np.median(np.abs(np.log(t_nemo / t_tmm))):.1e}, "
f"largest {np.abs(np.log(t_nemo / t_tmm)).max():.1e}"
)
axes[0].legend(frameon=False, fontsize=8)
fig.tight_layout();
double barrier 2 | 6 | 2 nm, 0.4 eV: median |ln T ratio| 2.4e-05, largest 1.6e-04 barrier mass 0.092 m₀, right contact −0.1 eV: median |ln T ratio| 1.0e-05, largest 4.1e-05

The double barrier transmits completely, \(T = 1\), at an energy well below the barrier top: resonant tunneling, the topic of Lecture 6.3. The largest differences sit on the flanks of that sharp peak, where a tiny shift of the peak energy changes \(T\) a lot; the median difference is \(2\cdot10^{-5}\). In the heterostructure, waves enter only above the left contact's edge (\(E = 0\)), and the step between the two contacts reflects part of the wave even far above the barrier, so \(T\) stays below one there.
The difference between the two methods is open-nemo's grid. Halving the spacing should divide it by four:
Code
layers = cases["double barrier 2 | 6 | 2 nm, 0.4 eV"]
Ec = np.linspace(0.01, 0.39, 400)
t_ref = tmm_transmission(layers, Ec)
print(" spacing (nm) median |ln T_nemo − ln T_TMM|")
for dx in (0.1, 0.05, 0.025, 0.0125):
t = nemo.Transport(device(layers, dx).hamiltonian).transmission(Ec)
print(f"{dx:10.4f} {np.median(np.abs(np.log(t / t_ref))):.2e}")
spacing (nm) median |ln T_nemo − ln T_TMM|
0.1000 3.69e-03
0.0500 9.21e-04
0.0250 2.30e-04
0.0125 5.75e-05It does: the finite-difference solution converges to the transfer-matrix one as \(a^2\). For piecewise constant potentials the transfer matrices are exact, and they are an excellent check on any numerical code.
5. The wave function from the coefficients#
The transfer matrix gives more than \(T\): once \(E = t\) is known, \([A_j^+, A_j^-]\) follows in every
layer, and with it \(\psi(x)\). Below, the single barrier at \(E = 0.2\) eV (half the barrier height).
open-nemo returns the density injected from the left contact (spectral_left, the diagonal of
\(G\Gamma_LG^\dagger\)), which is \(|\psi|^2\) of the same scattering state up to a constant; it is
scaled here so that it equals \(|t|^2\) in the right contact.
Code
def wave_function(layers, E, x):
# ψ(x) of the state injected from the left with A = 1; x = 0 at the first interface
steps, k = interface_steps(layers, [E])
total, _ = transfer_matrix(layers, [E])
coeffs = [None] * len(layers)
coeffs[-1] = np.array([1 / total[0, 0, 0], 0]) # last layer: E = t, F = 0
for j in range(len(layers) - 2, -1, -1): # walk back to the left
coeffs[j] = steps[j][0] @ coeffs[j + 1]
edges = np.concatenate([[0.0], np.cumsum([w for w, _, _ in layers[1:-1]])])
origin = np.concatenate([[0.0], edges]) # where each layer's phase is counted from
bounds = np.concatenate([[-np.inf], edges, [np.inf]])
psi = np.zeros_like(x, dtype=complex)
for j, c in enumerate(coeffs):
sel = (x >= bounds[j]) & (x < bounds[j + 1])
psi[sel] = c[0] * np.exp(1j * k[j][0] * (x[sel] - origin[j])) + c[1] * np.exp(
-1j * k[j][0] * (x[sel] - origin[j])
)
return psi, coeffs
Code
LEAD = 5.0
layers = [(LEAD, 0.0, M), (L, V0, M), (LEAD, 0.0, M)]
Ew = 0.2
dev = device(layers)
x = dev.x - LEAD # the barrier starts at x = 0
psi, coeffs = wave_function(layers, Ew, x)
res = nemo.Transport(dev.hamiltonian).solve(np.array([Ew]))
rho = res["spectral_left"][0]
t2 = np.abs(coeffs[-1][0]) ** 2
rho = rho * t2 / rho[x > L + 1].mean()
print(
f"E = {Ew} eV: |t|² = {t2:.4e}, |r|² = {np.abs(coeffs[0][1])**2:.6f}, C = {coeffs[1][0]:.4f}, D = "
f"{coeffs[1][1]:.2e}"
)
print(f"largest | |ψ|²(TMM) − scaled open-nemo density | = {np.abs(np.abs(psi)**2 - rho).max():.1e}")
fig, ax = plt.subplots(figsize=(9, 3.8))
ax.axvspan(0, L, color=style.CONTEXT, alpha=0.18, lw=0, label="barrier")
ax.plot(
x, np.abs(psi) ** 2, **style.series("reference"), label="|ψ|² from the transfer matrices"
)
ax.plot(x, rho, **style.series(lw=1.1), label="open-nemo injected density (scaled)")
ax.set(
xlabel="x (nm)",
ylabel="|ψ(x)|² (incident amplitude 1)",
yscale="log",
ylim=(1e-3, 5),
title=f"single barrier {V0} eV, {L} nm, at E = {Ew} eV",
)
ax.legend(frameon=False, fontsize=8, loc="lower left");
E = 0.2 eV: |t|² = 1.0772e-01, |r|² = 0.892276, C = 0.9723-0.9723j, D = 2.77e-02+2.77e-02j largest | |ψ|²(TMM) − scaled open-nemo density | = 3.5e-05

On the left, the incident and reflected waves interfere: \(|\psi|^2 = |e^{ikx} + re^{-ikx}|^2\) oscillates between \((1-|r|)^2\) and \((1+|r|)^2\), almost a standing wave because \(|r|^2\) is close to one. Inside the barrier the density falls exponentially, mostly through \(C e^{-\gamma x}\); the growing term \(De^{\gamma x}\) has a tiny coefficient and only bends the curve near \(x = L\), where it is needed to match the outgoing wave. On the right only the transmitted wave remains, with the constant density \(|t|^2\). The two methods give the same state.
6. Simple and pleasing, with a catch#
The lecture calls the method "conceptually very simple and analytically pleasing", and recommends it for the double-barrier homework, but warns that it has numerical issues. The cause is visible in the matrices: an evanescent layer contributes \(e^{+\gamma d}\) and \(e^{-\gamma d}\) to the same product. The size of \(M_{11}\) grows exponentially with the barrier width, and every quantity that results from a cancellation between such terms keeps only the digits left over by floating-point arithmetic (about 16).
Code
widths = np.array([1, 2, 5, 10, 20, 30, 40])
print(" barrier width (nm) γL |M11| T = 1/|M11|² det M as computed (exactly 1)")
for w in widths:
tot, _ = transfer_matrix([(0, 0, M), (w, V0, M), (0, 0, M)], [0.2])
m11 = np.abs(tot[0, 0, 0])
det = tot[0, 0, 0] * tot[0, 1, 1] - tot[0, 0, 1] * tot[0, 1, 0]
print(
f"{w:12d} {np.sqrt(M * (V0 - 0.2) / H2M0) * w:6.1f} {m11:10.3e} {1 / m11**2:12.3e} "
f"{det.real:+.6e}"
)
barrier width (nm) γL |M11| T = 1/|M11|² det M as computed (exactly 1)
1 0.6 1.181e+00 7.169e-01 +1.000000e+00
2 1.2 1.790e+00 3.122e-01 +1.000000e+00
5 3.0 9.726e+00 1.057e-02 +1.000000e+00
10 5.9 1.882e+02 2.824e-05 +1.000000e+00
20 11.9 7.082e+04 1.994e-10 +9.999990e-01
30 17.8 2.665e+07 1.408e-15 +1.125000e+00
40 23.7 1.003e+10 9.941e-21 +0.000000e+00The transfer matrix between two identical contacts has determinant exactly 1 (current conservation), yet its elements grow like \(e^{\gamma L}\). The determinant is the difference of two products of size \(|M_{11}|^2\): as computed, it keeps fewer and fewer correct digits as the barrier widens, and when \(|M_{11}|^2\) approaches \(10^{16}\) none are left (the last rows of the table). For a single barrier \(T = 1/|M_{11}|^2\) is still accurate, because no cancellation is needed. For many layers, for the wave function deep inside thick barriers, or for a resonance that needs a large \(M_{11}\) to cancel down to one, the precision is lost. Lecture 6.5 returns to this, and to the discretized Schrödinger equation that open-nemo solves instead.
Code
# exploration: T at E = V0/2 against barrier width, transfer matrices and open-nemo
w = np.linspace(0.5, 12, 24)
t_tmm = [tmm_transmission([(0, 0, M), (wi, V0, M), (0, 0, M)], [V0 / 2])[0] for wi in w]
t_nemo = [
nemo.Transport(device([(3, 0, M), (wi, V0, M), (3, 0, M)], dx=0.05).hamiltonian).transmission([V0 / 2])[0]
for wi in w
]
gam = np.sqrt(M * V0 / 2 / H2M0)
fig, ax = plt.subplots(figsize=(7, 3.6))
ax.semilogy(w, t_tmm, "o", color=style.REFERENCE, mfc="none", ms=5, label="transfer matrices")
ax.semilogy(w, t_nemo, "-", color=style.OPEN_NEMO, lw=1.2, label="open-nemo (spacing 0.05 nm)")
ax.semilogy(w, np.exp(-2 * gam * w), ":", color=style.CONTEXT, label=r"$e^{-2\gamma L}$")
ax.set(
xlabel="barrier width L (nm)", ylabel="T at E = V₀/2", title="tunneling falls exponentially with width"
)
ax.legend(frameon=False, fontsize=8);

Every nanometre of barrier costs the same factor, \(e^{-2\gamma\cdot 1\,\text{nm}}\), once the barrier is wider than about one decay length: the exponential law that Lecture 6.2 derives from the exact formula. The dotted line has that slope; the curves lie above it by the prefactor \((4k\gamma/(k^2+\gamma^2))^2\), which is 4 at \(E = V_0/2\), where \(k = \gamma\).
Summary#
- An open system differs from a closed one only in its boundary conditions: the incident amplitude is fixed (\(A = 1\), \(F = 0\)), every energy is allowed, and there is no normalization.
- In each region of constant potential the solution is \(e^{\pm ikx}\) (allowed) or \(e^{\pm\gamma x}\) (forbidden, \(k = i\gamma\)); \(\gamma\) is the imaginary wave number of the barrier's complex band structure.
- Continuity of \(\psi\) and \(\psi'/m\) at each interface is a 2×2 matrix; their product is the transfer matrix \(M\), and \(T = (v_N/v_1)/|M_{11}|^2\).
- The slide's \(M_1M_2\), a general transfer-matrix code and open-nemo's finite-difference transport agree; the latter converges as \(a^2\) to the exact piecewise-constant result. The wave function rebuilt from the coefficients is open-nemo's injected density.
- The matrix elements grow as \(e^{\gamma L}\) while \(\det M = 1\): the method loses precision when large terms must cancel.
Exercises#
- Use
tmm_transmissionto compute \(T(E)\) of a double barrier with 3 nm barriers and a 5 nm well, and find the energy of the transmission peak. Compare withnemo.resonances(device(...)). - Make the right contact 0.2 eV lower than the left one (a step) and remove the barrier. Check that \(R + T = 1\) when \(T\) includes the velocity ratio, and that it fails without it.
- Plot \(|\psi(x)|^2\) for the single barrier at \(E = 0.6\) eV, above the barrier. What replaces the exponential decay inside it?
- Repeat the table of section 6 at \(E = 0.39\) eV. Why does \(M_{11}\) grow so much more slowly?
Further reading#
- Quantum transport from scratch: Green's functions on a 1-D chain: the open boundary conditions that open-nemo uses instead of transfer matrices.
- Benchmark: piecewise-constant barriers against nanoHUB's PCPBT: open-nemo against the transfer-matrix tool used throughout Section 6.
- Resonant tunneling diode, step by step.
- The tool of the lecture: Piece-Wise Constant Potential Barriers Tool (PCPBT) on nanoHUB.
- The numerical limits of transfer matrices and better methods: G. Klimeck, Nanoelectronic Modeling: From Quantum Mechanics and Atoms to Realistic Devices, nanoHUB resource 8086 (2010).


