iTranslated by AI
Visualization with Julia: Anharmonic Oscillator
Visualize the solutions to the Schrödinger equation for a particle moving in a Morse potential.

Packages
You need to install the packages beforehand by running add Plots, add LaTeXStrings, add SpecialPolynomials, and add SpecialFunctions in Julia's package mode. Also, declare using Plots, using LaTeXStrings, using SpecialPolynomials, and using SpecialFunctions in your notebook. For instructions on installing and using Jupyter Notebook, see this article, and for how to use Plots.jl, see this article.
# using Pkg
# Pkg.add("Plots")
# Pkg.add("LaTeXStrings")
# Pkg.add("SpecialPolynomials")
# Pkg.add("SpecialFunctions")
using Plots
using LaTeXStrings
using SpecialPolynomials
using SpecialFunctions
Time-Independent Schrödinger Equation
Morse Potential
The Morse potential, with symbols slightly modified from Equation (4) in Morse (1929), is given by
and the Hamiltonian for a particle of mass
The solution to the corresponding time-independent Schrödinger equation
is given by the following, modifying Toda (1999) and Wikipedia respectively:
where
V(r, re, De, a) = De*( exp(-2*a*(r-re)) -2*exp(-a*(r-re)) )
# using SpecialFunctions
# using SpecialPolynomials
Γ(z) = try; gamma(z); catch; Inf; end
function ψ(n, r, re ,De, a, µ)
ℏ = 1.0
λ = sqrt(2*µ*De)/(a*ℏ)
ξ = 2*λ*exp(-a*(r-re))
s = 2*λ-2*n-1
N = sqrt(factorial(n)s*a/Γ(s+n+1))
return N*ξ^(s/2)*exp(-ξ/2)*basis(Laguerre{s}, n)(ξ)
end
function E(n, De, a, µ)
ℏ = 1.0
k = 2*a^2*De
ω = sqrt(k/µ)
χ = ℏ*ω/(4*De)
return - De + ℏ*ω*(n+1/2) - χ*ℏ*ω*(n+1/2)^2
end
re = 1.38436
De = 1.13133356259 - 1
k = 12.772/15.569141/2
a = sqrt(k/(2*De))
µ = 1/(1/1837.108542 + 1/1837.108542)
println("re = ", re)
println("De = ", De)
println("k = ", k)
println("a = ", a)
println("µ = ", µ)
re = 1.38436
De = 0.13133356259000006
k = 0.4101703491541377
a = 1.249623750214457
µ = 918.554271
Using the functions and parameters above, the Morse potential can be depicted as follows. It can be seen that it asymptotically approaches
# Axis labels, plot range, legend position
plot(xlabel=L"r", ylabel=L"V(r)", xlims=(0.2,6.2), ylims=(-0.14,0.01), legend=:bottomright)#, size=(450,400))
# Potential
plot!(-3:0.02:7, r->V(r,re,De,a), label="", lc="#578FC7", lw=2)
# Display
plot!() |> display

In the Morse potential, the condition
for n in 0:15
println(n, "\t", E(n+1,De,a,µ)-E(n,De,a,µ) > 0)
end
0 true
1 true
2 true
3 true
4 true
5 true
6 true
7 true
8 true
9 true
10 true
11 true
12 false
13 false
14 false
15 false
Depicting all the energy levels of the bound states:
# Axis labels, plot range, legend position
plot(xlabel=L"r", ylabel=L"V(r)", xlims=(0.2,6.2), ylims=(-0.14,0.01), legend=:bottomright)#, size=(450,400))
# Potential
plot!(0:0.02:7, r->V(r,re,De,a), label="", lc="#578FC7", lw=2)
# Energy
for n in 11:-1:0
plot!(0:0.02:7, r-> E(n,De,a,µ)<V(r,re,De,a) ? NaN : E(n,De,a,µ), label="", lc="#578FC7", lw=1)
end
# Display
plot!() |> display

Shift the wavefunctions to the height of their energy levels, scale them vertically, and add them to the plot. Here,
# Axis labels, plot range, legend position
plot(xlabel=L"r", ylabel=L"V(r)", xlims=(0.2,6.2), ylims=(-0.14,0.01), legend=:bottomright)#, size=(450,400))
# Potential
plot!(-3:0.02:7, r->V(r,re,De,a), label="", lc="#000000", lw=2)
for n in 11:-1:0
# Energy (dashed line)
hline!([E(n,De,a,µ)], label="", lc="#000000", ls=:dash)
# Energy (solid line)
plot!(0:0.02:7, r-> E(n,De,a,µ)<V(r,re,De,a) ? NaN : E(n,De,a,µ), label="", lc="#000000", lw=2)
# Wavefunction
plot!(0:0.02:7, r->0.004*ψ(n,r,re,De,a,µ)+E(n,De,a,µ), label=latexstring("n=$n"), lc=n+1, lw=2)
end
# Display
plot!() |> display

Verification by Numerical Integration
Since it is often impossible to judge whether the eigenfunctions are correctly declared just by looking at them, we perform a simple calculation to check if the normalization condition
# Numerical integration routine
integral(func, x_min, x_max, dx) = sum(func.(x_min:dx:x_max)*dx)
# Verification of normalization condition
println("n \t∫|ψ(r)|²dr")
for n in 0:11
println(n, "\t", integral(r-\u003eψ(n,r,re,De,a,µ)^2, -10, 50, 0.001))
end
n ∫|ψ(r)|²dr
0 1.0000000000000002
1 1.0
2 0.9999999999999998
3 1.0000000000000004
4 1.0
5 0.9999999999999997
6 0.9999999999999999
7 0.9999999999999997
8 0.9999999999999989
9 1.0
10 0.9999999999999999
11 0.9999999999999988
Next, we evaluate the energy through numerical integration. Unlike the harmonic oscillator, the virial theorem cannot be applied here, so we must deal with the kinetic term directly. The second derivative can be approximated using the finite difference method as:
Using the fact that the wavefunction is a real function,
The starting index was changed to
println("n \tE\t\t\t∫ψHψdr")
dr = 0.005
ψVψ(n,r,re,De,a,µ) = V(r,re,De,a) * ψ(n,r,re,De,a,µ)^2
ψTψ(n,r,re,De,a,µ,dr) = -1/(2*µ) * ψ(n,r,re,De,a,µ) * (ψ(n,r+dr,re,De,a,µ)-2*ψ(n,r,re,De,a,µ)+ψ(n,r-dr,re,De,a,µ))/dr^2
ψHψ(n,r,re,De,a,µ,dr) = ψTψ(n,r,re,De,a,µ,dr) + ψVψ(n,r,re,De,a,µ)
for n in 0:11
println(n, "\t", E(n,De,a,µ), "\t", integral(r-\u003eψHψ(n,r,re,De,a,µ,dr), dr, 100, dr))
end
n E ∫ψHψdr
0 -0.12098032966578513 -0.12098063308527979
1 -0.10154887790721412 -0.10155018470683336
2 -0.08381744493512158 -0.08382034362426742
3 -0.06778603074950751 -0.06779072698018129
4 -0.05345463535037193 -0.053461018312416515
5 -0.040823258737714835 -0.040830967539695506
6 -0.0298919009115362 -0.02990039094983932
7 -0.020660561871836063 -0.020669171190581332
8 -0.013129241618614396 -0.0131372572629767
9 -0.007297940151871213 -0.0073046645173990635
10 -0.003166657471606507 -0.0031714746521237978
11 -0.0007353935778202786 -0.0007378357145039691
Harmonic Approximation
The harmonic oscillator potential corresponding to the harmonic approximation of this potential, along with the eigenvalues and eigenfunctions of its Hamiltonian, are given by
where
Comparing the energy with the Morse potential, it can be seen that the difference lies only in the third term (the anharmonic term). Additionally, although it is called the harmonic approximation, it is often more practical to determine the spring constant
module HO
using SpecialPolynomials
# Potential
V(r, re, De, k) = - De + 1/2*k*(r-re)^2
# Eigenfunctions
# using SpecialPolynomials
function ψ(r,re,n,k,m)
ℏ = 1.0
ω = sqrt(k/m) # mu = 1 / (1/m1 + 1/m2)
A = sqrt(1/(factorial(n)*2^n)*sqrt(m*ω/(π*ℏ)))
ξ = sqrt(m*ω/ℏ) * (r-re)
return A*basis(Hermite, n)(ξ)*exp(-ξ^2/2)
end
# Eigenvalues
function E(n,De,k,m)
ℏ = 1
ω = sqrt(k/m)
return - De + ℏ*ω*(n+1/2)
end
end
Depicting it and comparing it with the Morse potential, we can see that they have a very similar shape near the minimum point.
# Axis labels, plot range, legend position
plot(xlabel=L"r", ylabel=L"V(r)", xlims=(0.2,6.2), ylims=(-0.14,0.01), legend=:bottomright)#, size=(450,400))
# Potential
plot!(0:0.02:7, r-\u003eV(r,re,De,a), label="Morse Potential", lc="#578FC7", lw=2)
plot!(0:0.02:7, r-\u003eHO.V(r,re,De,k), label="Harmonic Oscillator", lc="#BC1C5F", lw=2)
# Display
plot!() |\u003e display

Next, we plot the energy levels. At lower levels, they match well compared to higher levels. Since the level spacing is constant, the number of bound states is smaller.
# Axis labels, plot range, legend position
plot(xlabel=L"r", ylabel=L"V(r)", xlims=(0.2,6.2), ylims=(-0.14,0.01), legend=:bottomright)#, size=(450,400))
# Potential
plot!(0:0.02:7, r-\u003eV(r,re,De,a), label="Morse Potential", lc="#578FC7", lw=2)
plot!(0:0.02:7, r-\u003eHO.V(r,re,De,k), label="Harmonic Oscillator", lc="#BC1C5F", lw=2)
# Energy
for n in 11:-1:0
plot!(0:0.02:7, r-\u003e E(n,De,a,µ)\u003cV(r,re,De,a) ? NaN : E(n,De,a,µ), label="", lc="#578FC7")
end
for n in 5:-1:0
plot!(0:0.02:7, r-\u003e HO.E(n,De,k,µ)\u003cHO.V(r,re,De,k) ? NaN : HO.E(n,De,k,µ), label="", lc="#BC1C5F")
end
# Display
plot!() |\u003e display

Next, we plot the wavefunctions of the harmonic oscillator.
# Axis labels, plot range, legend position
plot(xlabel=L"r", ylabel=L"V(r)", xlims=(0.2,6.2), ylims=(-0.14,0.01), legend=:bottomright)#, size=(450,400))
# Potential
plot!(-3:0.02:17, r-\u003eV(r,re,De,a), label="", lc="#737B85", lw=1, ls=:dash)
plot!(-3:0.02:17, r-\u003eHO.V(r,re,De,k), label="", lc="#000000", lw=2)
for n in 11:-1:0
# Energy (solid line)
plot!(0:0.02:7, r-\u003e E(n,De,a,µ)\u003cV(r,re,De,a) ? NaN : E(n,De,a,µ), label="", lc="#737B85", lw=1, ls=:dash)
end
for n in 5:-1:0
# Energy (dashed line)
# hline!([HO.E(n,De,k,µ)], label="", lc="#000000", ls=:dash)
# Energy (solid line)
plot!(0:0.02:7, r-\u003e HO.E(n,De,k,µ)\u003cHO.V(r,re,De,k) ? NaN : HO.E(n,De,k,µ), label="", lc="#000000", lw=2)
# Wavefunction
plot!(0:0.02:7, r-\u003e0.004*HO.ψ(r,re,n,k,µ)+HO.E(n,De,k,µ), label=latexstring("n=$n"), lc=n+1, lw=2)
end
# Display
plot!() |\u003e display

Comparing the wavefunctions of the harmonic oscillator (dashed lines) and the Morse oscillator (solid lines) yields the following. At lower levels, the anharmonicity is small, and the description by the harmonic approximation is not bad compared to higher levels. At higher vibrational levels, in addition to the energy difference, the tendency for the maximum value of the wavefunction to shift toward the long-range side is also clearly apparent.
# Axis labels, plot range, legend position
plot(xlabel=L"r", ylabel=L"V(r)", xlims=(0.2,6.2), ylims=(-0.14,0.01), legend=:bottomright)#, size=(450,400))
# Potential
plot!(-3:0.02:17, r-\u003eV(r,re,De,a), label="", lc="#000000", lw=2)
plot!(-3:0.02:17, r-\u003eHO.V(r,re,De,k), label="", lc="#000000", lw=1, ls=:dot)
# Wavefunction
for n in 5:-1:0
plot!(0:0.02:7, r-\u003e0.004*HO.ψ(r,re,n,k,µ)+HO.E(n,De,k,µ), label="", lc=n+1, lw=1, ls=:dot)
plot!(0:0.02:7, r-\u003e0.004*ψ(n,r,re,De,a,µ)+E(n,De,a,µ), label=latexstring("n=$n"), lc=n+1, lw=2)
end
# Display
plot!() |\u003e display

Time-Dependent Schrödinger Equation
The solution to the time-dependent Schrödinger equation
is generally given by
That this is a solution can be shown simply by substituting it (verify the conditions under which the summation and the differential operator are interchangeable). Here,
function Ψ(N,t,r,re,De,a,µ) # ← Note that this is an uppercase Ψ
sum = 0.0 + im*0.0
for n in 0:N
sum += c(n) * ψ(n,r,re,De,a,µ) * exp(im*E(n,De,a,µ)*t)
end
return sum
end
# Depicting time evolution
function draw(num)
anim = Animation()
N = 10
for i in 0:100
# Time
t = 10.0*i
# Plot range, title, etc.
plt = plot(title="t=$t", xlabel="r", ylabel="V(r)", xlims=(0.2,6.2), ylims=(-0.14,0.01), legend=:bottomright)#, size=(450,400))
# Potential
plot!(0:0.01:17, r->V(r,re,De,a), label="", lc="#000000", lw=2)
# Real part Re Ψ(x,t)
plot!(plt, 0:0.01:7, r->real(Ψ(N,t,r,re,De,a,µ))*0.05 + sum(c.(0:N) .* E.(0:N,De,a,µ)), label="Re", lc=1)
# Imaginary part Im Ψ(x,t)
plot!(plt, 0:0.01:7, r->imag(Ψ(N,t,r,re,De,a,µ))*0.05 + sum(c.(0:N) .* E.(0:N,De,a,µ)), label="Im", lc=2)
# Probability density |Ψ(x,t)|^2
plot!(plt, 0:0.01:7, r->abs(Ψ(N,t,r,re,De,a,µ))^2*0.05 + sum(c.(0:N) .* E.(0:N,De,a,µ)), label="P(x,t)", lc=3, lw=2)
# Add frame
frame(anim, plt)
end
gif(anim, "TDSE_$(num).gif", fps = 20)
end
First, if the initial condition is purely the ground state (
c(n) = n==0 ? 1 : 0
draw(1)

Next, considering an initial condition where the ground state and the first excited state are mixed in equal proportions (
c(n) = n<2 ? 0.5 : 0
draw(2)

Finally, if we set
c(n) = 2.0^(-n-1)
draw(3)

Operating Environment
versioninfo()
Julia Version 1.6.2
Commit 1b93d53fc4 (2021-07-14 15:36 UTC)
Platform Info:
OS: Windows (x86_64-w64-mingw32)
CPU: Intel(R) Core(TM) i7-4650U CPU @ 1.70GHz
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-11.0.1 (ORCJIT, haswell)
References
- Wikipedia - Morse potential
- Morikazu Toda, "30 Lectures on Quantum Mechanics" (Asakura Shoten, 1999)
The Jupyter Notebook data that this article is based on can be found at the following link.
Julia Visualization Series
Discussion