FiniteDifferenceMatrices.jlの紹介
Julia Advent Calendar 2024 の5日目です🎄
はじめに
FiniteDifferenceMatrices.jl というパッケージを開発・公開しました🎉 このパッケージの宣伝も兼ねて, 調和振動子における変分法, 固有値問題, 虚時間発展の計算例について解説します.
FiniteDifferenceMatrices.jl
以下では, FiniteDifferenceMatrices.jlの定式化と使い方を解説していきます. まずインストールしましょう. 下記を実行してください.
import Pkg; Pkg.add("FiniteDifferenceMatrices.jl")
使用時は下記を実行します.
using FiniteDifferenceMatrices
2階微分の2次精度の中心差分近似は
です. これはTaylor展開を使ってゴリゴリ計算していけば求められます. 具体的には, 中心の異なる2つのTaylor級数
の和を取って整理すると,
が得られます.
と書けます. 係数fdcoefficient
を実装しました. 2階微分の4次精度(:c
の例を示します.
julia> fdcoefficient(n=2, m=2, d=:c)
Dict{Int64, Rational{Int64}} with 3 entries:
0 => -2
-1 => 1
1 => 1
続いて, 2階微分の4次精度の中心差分の例は次のようになります.
julia> fdcoefficient(n=2, m=4, d=:c)
Dict{Int64, Rational{Int64}} with 5 entries:
0 => -5//2
-1 => 4//3
2 => -1//12
-2 => -1//12
1 => 4//3
一応, Wikipediaと一致する結果が得られました. 実装が本当に合っているか不安だったので, 論文やWikipediaに載っている表を愚直に手作業でハードコードし, 出力が一致することを確認するテストを実装してあります.
次に, 差分近似を行列で表してきましょう. 上記の近似を行列で表すと次のように書けます.
行列の記法に慣れない人のために抜き出すと,
このとき両端では fdmatrix
を実装しました. n
階, m
次精度, 中心差分(:c
), h=1//1
) のときの差分行列は次のように計算できます.
julia> fdmatrix(5, n=2, m=2, d=:c, h=1//1)
5×5 SparseArrays.SparseMatrixCSC{Rational{Int64}, Int64} with 13 stored entries:
-2 1 ⋅ ⋅ ⋅
1 -2 1 ⋅ ⋅
⋅ 1 -2 1 ⋅
⋅ ⋅ 1 -2 1
⋅ ⋅ ⋅ 1 -2
これと同様に, ハミルトニアンを疎行列として近似できます. 2次精度の中心差分近似における, ハミルトニアン
実際に調和振動子のエネルギーが正しく計算できるか試していきましょう. 調和振動子の無次元化されたハミルトニアン, 基底状態の波動関数, 基底状態のエネルギーはそれぞれ下記のように書けます.
コードはほとんど数式そのままです.
using FiniteDifferenceMatrices
using SparseArrays
Δx = 0.1
X = -10:Δx:10
H = -1/2 * fdmatrix(length(X), n=2, m=2, d=:c, h=Δx) + spdiagm([1/2*x^2 for x in X])
ψ = [π^(-1//4)*exp(-1/2*x^2) for x in X]
E = (ψ' * H * ψ) / (ψ' * ψ)
0.49968776025398826
実際に厳密解0.5
に近い値が計算できました.
変分法
エネルギーが計算できるようになりましたので, 変分法も簡単に実装できます. パラメータ
パラメータ
using FiniteDifferenceMatrices
using SparseArrays
using Printf
Δx = 0.1
X = -10:Δx:10
H = -1/2 * fdmatrix(length(X), n=2, m=8, d=:c, h=Δx) + spdiagm([1/2*x^2 for x in X])
println(" α E")
println("------------------")
for α in 0.1:0.1:1.0
ψ = [((2*α)/π)^(1//4)*exp(-α*x^2) for x in X]
E = (ψ' * H * ψ) / (ψ' * ψ)
@printf("%.1f %.9f\n", α, E)
end
α E
------------------
0.1 1.299999995
0.2 0.725000000
0.3 0.566666667
0.4 0.512500000
0.5 0.500000000
0.6 0.508333333
0.7 0.528571428
0.8 0.556250000
0.9 0.588888888
1.0 0.624999999
実際に α = 0.5
で最小値を取っていることが確かめられました. CairoMakie.jlで波動関数もプロットしてみましょう.
using CairoMakie
CairoMakie.activate!(type = "svg")
f = Figure(size=(420,300), fontsize=11.5)
ax = Axis(f[1,1], xlabel=L"x", ylabel=L"\psi(x)", limits=(-5,5,0.0,0.8), titlesize=16.5, ylabelsize=16.5, xlabelsize=16.5)
for α in 0.3:0.1:0.6
ψ = [((2*α)/π)^(1//4)*exp(-α*x^2) for x in X]
lines!(ax, X, ψ, label="α = $α")
end
lines!(ax, X, x -> π^(-1//4)*exp(-1/2*x^2), label="exact", color=:black, linestyle=:dash, linewidth=2)
axislegend(ax, position=:rt, rowgap=0, padding=(0,0,0,0), framevisible=false)
f
試行波動関数が α = 0.5
で厳密解に一致することも確かめられました.
固有値問題
上記で計算したハミルトニアン行列 H
を渡すだけです. ベンチマークには Antique.jl を用います.
using FiniteDifferenceMatrices
using SparseArrays
using LinearAlgebra
# using ArnoldiMethod
using Printf
import Antique
HO = Antique.HarmonicOscillator(k=1.0, m=1.0, ℏ=1.0)
Δx = 0.1
X = -5:Δx:5
I = keys(X)
T = -HO.ℏ^2/2/HO.m * fdmatrix(length(X), n=2, m=8, d=:c, h=Δx)
V = Diagonal([Antique.V(HO,x) for x in X])
H = T + V
# decomp, history = partialschur(H, nev=10, tol=1e-9, which=SR())
# E = real.(decomp.eigenvalues)
E, ψ = eigen(Matrix{Float64}(H))
println(" n numerical analytical")
println("-- -------------- --------------")
for n in 0:9
@printf("%2d %.12f %.12f\n", n, E[n+1], Antique.E(HO, n=n))
end
n numerical analytical
-- -------------- --------------
0 0.499999999985 0.500000000000
1 1.500000001062 1.500000000000
2 2.500000034551 2.500000000000
3 3.500000555298 3.500000000000
4 4.500006084209 4.500000000000
5 5.500049973732 5.500000000000
6 6.500321040771 6.500000000000
7 7.501649561884 7.500000000000
8 8.506863078481 8.500000000000
9 9.523313467073 9.500000000000
このように固有値としてエネルギーの近似値が得られました. 基底状態についてはかなり良い近似になっています. 固有ベクトルψ[:,1]
が基底状態(ψ[:,n+1]
が第
using CairoMakie
CairoMakie.activate!(type = "svg")
f = Figure(size=(840,600), fontsize=11.5)
for i in 1:4
n = i - 1
ax = Axis(
f[Int(ceil(i/2)),rem(i-1,2)+1],
xlabel = L"x",
ylabel = L"|\psi(x)|^2",
titlesize = 16.5,
ylabelsize = 16.5,
xlabelsize = 16.5,
)
lines!(ax, -5..5, x -> Antique.ψ(HO,x,n=n)^2, label="analytical", color=:black, linestyle=:solid, linewidth=1)
scatter!(ax, X, (ψ[:,i] / sqrt(Δx)) .^ 2, label="numerical", markersize=6)
axislegend(ax, "n = $n", position=:rt, rowgap=0, framevisible=false, titlesize=15.0)
end
f
save("eigen.svg", f) # hide
nothing # hide
いずれも精度よく決定できていますが, 高い準位では振動の節が増えていくため, 格子間隔も小さくしないと記述が難しいであろうことが想像できます. 実際, エネルギーの誤差は
虚時間発展
虚時間発展が基底状態への射影に相当することは偏微分方程式の変数分離による解法を知っていれば容易に理解できます. 興味のある方は Kosztin et al. (1996) 等を参照してください. ここでは詳細に踏み込まず, 虚時間のSchrödinger方程式
の形式解
を直接的に計算する方法を紹介します. Juliaには行列指数関数が最初から組み込まれていますので行列表示したハミルトニアンH
を初期値のベクトルΨ₀
にかけてΨ = exp(-Matrix{Float64}(H)*τ) * Ψ₀
を計算すれば虚時間τ
に対する近似解Ψ
が計算できます. 行列指数関数の部分を疎行列として効率的に計算したければFastExpm.jlを使用してください.
using FiniteDifferenceMatrices
using SparseArrays
using CairoMakie
CairoMakie.activate!(type = "svg")
# domain X, Hamiltonian H, initial state ψ₀
Δx = 0.1
X = -10:Δx:10
H = -1/2 * fdmatrix(length(X), n=2, m=4, d=:c, h=Δx) + spdiagm([1/2*x^2 for x in X])
Ψ₀ = [-2.5 ≤ x ≤ 2.5 ? 0.2 : 0.0 for x in X]
# plot
f = Figure(size=(420,300), fontsize=11.5)
ax = Axis(f[1,1], xlabel="x", ylabel="Ψ(x,τ)", limits=(-5,5,0.0,0.8), titlesize=16.5, ylabelsize=16.5, xlabelsize=16.5)
for τ in [0.0, 0.1, 1.0, 10.0]
Ψ = exp(-Matrix{Float64}(H)*τ) * Ψ₀
lines!(ax, X, Vector{Float64}(real(Ψ / sqrt(Δx * Ψ' * Ψ))), label="τ = $τ")
end
lines!(ax, X, x -> π^(-1//4)*exp(-1/2*x^2), label="g.s.", color=:black, linestyle=:dash, linewidth=2)
axislegend(ax, position=:rt, rowgap=0, padding=(0,0,0,0), framevisible=false)
f
初期値は水色の台形ですが, 右辺がうまく表示されずに切れてしまっています. 計算上は問題ありませんので気にしないでください. このように, 虚時間発展を計算すると波動関数の励起状態の成分を少なくして基底状態の成分だけを取り出す(基底状態に射影する)ことができます.
まとめ
FiniteDifferenceMatrices.jl というパッケージを紹介しました. 差分近似の係数については先行研究と一致することがテストされています. 変分法, 固有値問題, 虚時間発展などについて教育的な計算例を提供しました.
宣伝
下記の要綱で Julia in Physics 2024 を開催します! 奮ってご参加ください🎉
【日時】2024年12月14日(土) 13:00 - 19:00
【方式】対面 / Zoom ハイブリット
【会場】東京大学 本郷地区キャンパス
【最寄】根津駅(千代田線)
【詳細】https://ohno.github.io/julia_in_physics_2024/
Discussion