【Plotly】水素原子の図鑑:電子波動関数の可視化
0.概要
この記事ではシュレディンガー方程式に従う水素原子の電子波動関数の可視化方法について解説し、水素原子の図鑑を作成した。グラフィックライブラリとして、インタラクティブなグラフ描画が可能なPlotly(参考[1])を使用する。この記事ではPythonからPlotlyを使用するが、PlotlyはJulia, Matlab, R, Javascriptなどからも呼び出し可能である。
Plotlyには4次元データの可視化に優れたplotly.graph_objects.Volume() (3D Volume Plots)というモジュールがあり(参考[2,3])、今回はこれを利用する。ここでいう4次元データとは、3次元デカルト座標系で定義されるスカラー場のことを指す。温度場
図1. Plotlyの3D Volume Plots描画例:電子波動関数
一般に、シュレディンガー方程式に従う波動関数
実数化された電子波動関数
- 点描で電子雲を表現する(
の大きさを点の密度として捉え、散布図を描く)\Psi(x,y,z) -
を満たすような等値面を描画する(\Psi(x,y,z)=A_0 はA_0 に依存しない定数)x,y,z -
を例えば\Psi(x,y,z) 平面上で等高線プロットする(コンター図を描く)。z=0
1、2に共通する問題点として、
Plotlyのplotly.graph_objects.Volume()を用いた描画も完全な描画方法ではないものの、1~3の描画方法の短所を上手く補うことができる。plotly.graph_objects.Volume()は、2の描画方法を発展させたもので、等値面を多数描画し、それぞれの等値面を半透明化することで、
ただ、この描画方法はインタラクティブな描画になっていることと、多数の等値面を描画する必要があるため、描画自体に時間がかかるという短所がある。具体的な計算条件は後述するが(3.1節)、私の計算環境だと1つのグラフを出力するまでに約25秒かかった。電子波動関数の計算自体は約1秒で終了しており、グラフの描画自体にかかった時間は約24秒である。全体の計算時間のうち95%以上がグラフの描画にかかった時間に占められる。
図2. 内部情報の可視化例:電子波動関数
1.可視化する関数:水素原子の電子波動関数
定常状態において、主量子数
式(1)のようなシュレディンガー方程式は解析的に解くことができ、
式(3)~(11)であらわされる電子波動関数
実数化された球面調和関数
また、本論とはあまり関係はないが、
2.プログラム
本記事で使用したプログラムの全文は下記である。PlotlyはPythonから呼び出している。プログラムの説明は、「2-1.電子波動関数の計算」と「2-2.グラフの描画」に分けて行う。下記のプログラムは主量子数
下記のプログラムを使用する上での注意点は、
# 電子波動関数の計算に必要なモジュール
import numpy as np
from scipy.special import sph_harm
from scipy.special import factorial
from scipy.special import assoc_laguerre
# グラフの描画に必要なモジュール
import plotly.graph_objects as go
import plotly.io as pio
#pio.renderers.default='svg'
pio.renderers.default='browser'
#動径波動関数
def R_nl(r, n, l):
a0 = 1.0
zeta = 2.0/n/a0 * r
#修正係数
n_d = n-l-1
L_coff = (-1)**(2*l+1) * factorial(n+l)
f_coff = - ((2.0/n/a0)**3 * factorial(n-l-1)/2.0/n/factorial(n+l)**3)**(1.0/2.0)
f = f_coff * np.exp(-zeta/2.0) * zeta**l * L_coff * assoc_laguerre(zeta, n_d, 2*l+1)
return f
# 球面調和関数のノーテーションや文字を物理の教科書用にカスタマイズ
def my_sph_harm(l, m, theta, phi):
return sph_harm(m, l, phi, theta)
# 実数化した球面調和関数を算出する関数:関数の中でmy_sph_harm()使用
def real_sph_harm(l, m, theta, phi):
if m < 0:
# m < 0であることに注意: np.fabs(m) = -m
Y = np.real(1.j * np.sqrt(1/2) * (my_sph_harm(l,m, theta, phi) + (-1)**(-m+1) * my_sph_harm(l, -m, theta, phi)))
if m == 0:
Y = np.real(my_sph_harm(l, m, theta, phi))
if m > 0:
Y = np.real(np.sqrt(1/2) * (my_sph_harm(l,-m, theta, phi) + (-1)**m * my_sph_harm(l, m, theta, phi)))
return Y
# 量子数の指定
n = 5 # 主量子数: 1 <= n
l = 4 # 方位量子数 0 <= l <= n-1
m = 0 # 磁気量子数 -l <= m <= l
# 計算領域設定
N = 60 # 分割数
xmin, ymin, zmin = -50, -50, -50
xmax, ymax, zmax = 50, 50, 50
# メッシュ作成
x_a = np.linspace(xmin,xmax,N)
y_a = np.linspace(ymin,ymax,N)
z_a = np.linspace(zmin,zmax,N)
x, y, z = np.meshgrid(x_a, y_a, z_a)
# 極座標化
r = np.sqrt(x**2+y**2+z**2)
theta = np.arccos(z/r)
phi = np.sign(y) * np.arccos(x/np.sqrt(x**2 + y**2))
# 電子波動関数の計算
R_wave = R_nl(r, n, l) # 動径波動関数
Y_wave = real_sph_harm(l, m, theta, phi) # 実数化された球面調和関数
psi = R_wave * Y_wave # 実数化された電子波動関数
# 配列の1次元化
X = x.flatten()
Y = y.flatten()
Z = z.flatten()
Psi = psi.flatten()
# 描画する等値面の範囲設定
Max = np.maximum( np.fabs(np.amin(psi)), np.fabs(np.amax(psi)))
Max = 1/5 * Max
# グラフの描画
fig = go.Figure(data=go.Volume(
x=X,
y=Y,
z=Z,
value=Psi,
isomin=-Max, # 描画する等値面の最小値
isomax=Max, # 描画する等値面の最大値
opacity=0.1, # 不透明度:0~1
surface_count=20, # 描画する等値面の数
opacityscale="extremes", # 不透明度のカスタマイズ
))
# グラフレイアウトの調整
fig.update_layout(
scene = dict(
xaxis = dict(backgroundcolor="rgb(0, 0, 0)"), # 黒背景
yaxis = dict(backgroundcolor="rgb(0, 0, 0)"), # 黒背景
zaxis = dict(backgroundcolor="rgb(0, 0, 0)"), # 黒背景
),
scene_camera= dict(eye=dict(x=1.5, y=1.5, z=0.5)) # カメラアングル
)
# 図の表示
fig.show()
# グラフの保存
name = "psi" + str(n)
name = name + str(l)
name = name + str(m)
name = name + ".png"
fig.write_image(name)
2-1.電子波動関数の計算
実数化された電子波動関数
# 電子波動関数の計算に必要なモジュール
import numpy as np
from scipy.special import sph_harm
from scipy.special import factorial
from scipy.special import assoc_laguerre
"""
~~~ 中略 ~~~
"""
#動径波動関数
def R_nl(r, n, l):
a0 = 1.0
zeta = 2.0/n/a0 * r
#修正係数
n_d = n-l-1
L_coff = (-1)**(2*l+1) * factorial(n+l)
f_coff = - ((2.0/n/a0)**3 * factorial(n-l-1)/2.0/n/factorial(n+l)**3)**(1.0/2.0)
f = f_coff * np.exp(-zeta/2.0) * zeta**l * L_coff * assoc_laguerre(zeta, n_d, 2*l+1)
return f
# 球面調和関数のノーテーションや文字を物理の教科書用にカスタマイズ
def my_sph_harm(l, m, theta, phi):
return sph_harm(m, l, phi, theta)
# 実数化した球面調和関数を算出する関数:関数の中でmy_sph_harm()使用
def real_sph_harm(l, m, theta, phi):
if m < 0:
# m < 0であることに注意: np.fabs(m) = -m
Y = np.real(1.j * np.sqrt(1/2) * (my_sph_harm(l,m, theta, phi) + (-1)**(-m+1) * my_sph_harm(l, -m, theta, phi)))
if m == 0:
Y = np.real(my_sph_harm(l, m, theta, phi))
if m > 0:
Y = np.real(np.sqrt(1/2) * (my_sph_harm(l,-m, theta, phi) + (-1)**m * my_sph_harm(l, m, theta, phi)))
return Y
# 量子数の指定
n = 5 # 主量子数: 1 <= n
l = 4 # 方位量子数 0 <= l <= n-1
m = 0 # 磁気量子数 -l <= m <= l
# 計算領域設定
N = 60 # 分割数
xmin, ymin, zmin = -50, -50, -50
xmax, ymax, zmax = 50, 50, 50
# メッシュ作成
x_a = np.linspace(xmin,xmax,N)
y_a = np.linspace(ymin,ymax,N)
z_a = np.linspace(zmin,zmax,N)
x, y, z = np.meshgrid(x_a, y_a, z_a)
# 極座標化
r = np.sqrt(x**2+y**2+z**2)
theta = np.arccos(z/r)
phi = np.sign(y) * np.arccos(x/np.sqrt(x**2 + y**2))
# 電子波動関数の計算
R_wave = R_nl(r, n, l) # 動径波動関数
Y_wave = real_sph_harm(l, m, theta, phi) # 実数化された球面調和関数
psi = R_wave * Y_wave # 実数化された電子波動関数
R_{nl}(r)
2-1-1.動径波動関数の計算:本記事のプログラムでは、動径波動関数R_nl(r, n, l)
を、下記のように定義している。動径波動関数scipy.special.assoc_laguerre()
はこの関数の中から呼び出している。
scipy.special.assoc_laguerre()
を使用する際の注意点として、ノーテーションと係数の修正を式(14)のようにしていることが挙げられる。これは、式(6)のような一般的な量子力学の教科書でなされるラゲール陪多項式の定義とscipyでの定義が異なるためである。詳細については以前書いた記事(参考[4])でまとめたため、気になる方はそちらを参照されたい。
#動径波動関数
def R_nl(r, n, l):
a0 = 1.0
zeta = 2.0/n/a0 * r
#修正係数
n_d = n-l-1
L_coff = (-1)**(2*l+1) * factorial(n+l)
f_coff = - ((2.0/n/a0)**3 * factorial(n-l-1)/2.0/n/factorial(n+l)**3)**(1.0/2.0)
f = f_coff * np.exp(-zeta/2.0) * zeta**l * L_coff * assoc_laguerre(zeta, n_d, 2*l+1)
return f
Y_l^m (\theta, \phi) とY_{lm} (\theta, \phi)
2-1-2.球面調和関数の計算:球面調和関数my_sph_harm(l, m, theta, phi)
で行っている。my_sph_harm(l, m, theta, phi)
の戻り値はscipyの球面調和関数計算モジュールscipy.special.sph_harm(m, l, phi, theta)
である。この記事のプログラムではmy_sph_harm(l, m, theta, phi)
を使用し、ノーテーション
このような処理をしている理由は、一般的な資料では球面調和関数は式(7)~(10)のように定義されているが、scipy.special.sph_harm()
での定義は一般的なものと異なるからである。球面調和関数の定義の違いの詳細については、以前書いた記事(参考[5])でまとめたため、気になる方はそちらを参照されたい。
# 球面調和関数のノーテーションや文字を物理の教科書用にカスタマイズ
def my_sph_harm(l, m, theta, phi):
return sph_harm(m, l, phi, theta)
実数化された球面調和関数real_sph_harm(l, m, theta, phi)
で行っている。実数化の手続きは式(11)に従う。この関数内では関数my_sph_harm(l, m, theta, phi)
を呼び出しており、関数my_sph_harm(l, m, theta, phi)
では
# 実数化した球面調和関数を算出する関数:関数の中でmy_sph_harm()使用
def real_sph_harm(l, m, theta, phi):
if m < 0:
# m < 0であることに注意: np.fabs(m) = -m
Y = np.real(1.j * np.sqrt(1/2) * (my_sph_harm(l,m, theta, phi) + (-1)**(-m+1) * my_sph_harm(l, -m, theta, phi)))
if m == 0:
Y = np.real(my_sph_harm(l, m, theta, phi))
if m > 0:
Y = np.real(np.sqrt(1/2) * (my_sph_harm(l,-m, theta, phi) + (-1)**m * my_sph_harm(l, m, theta, phi)))
return Y
2-1-3.計算領域の設定とメッシュ分割
メインプログラムの下記箇所で計算領域の設定と、N=51
と設定するとゼロ割りが発生する。if文等を使ってゼロ割りを回避することもできるが、プログラムが複雑化するため、今回はそこまでしなかった。
# 計算領域設定
N = 60 # 分割数
xmin, ymin, zmin = -50, -50, -50
xmax, ymax, zmax = 50, 50, 50
# メッシュ作成
x_a = np.linspace(xmin,xmax,N)
y_a = np.linspace(ymin,ymax,N)
z_a = np.linspace(zmin,zmax,N)
x, y, z = np.meshgrid(x_a, y_a, z_a)
# 極座標化
r = np.sqrt(x**2+y**2+z**2)
theta = np.arccos(z/r)
phi = np.sign(y) * np.arccos(x/np.sqrt(x**2 + y**2))
プログラムの上記箇所について、plotly.graph_objects.Volume()
(プログラム中ではgo.Volume()
と記述)へ渡す引数との関係から、今回はこのような不格好なプログラムを組まざるを得なかった。plotly.graph_objects.Volume()
の仕様については、後述の「2-2.グラフの描画」で解説する。
# 計算領域設定
N = 60 # 分割数
r_min, theta_min, phi_min = 0, 0, 0
r_max, theta_max, phi_max = 50, np.pi, 2*np.pi
# メッシュ作成
r_a = np.linspace(r_min,r_max,N)
theta_a = np.linspace(theta_min,theta_max,N)
phi_a = np.linspace(phi_min,phi_max,N)
r, theta, phi = np.meshgrid(r_a, theta_a, phi_a)
2-2.グラフの描画
全体のプログラムの内、グラフの描画に必要な箇所のみ下記に抜粋した。説明が必要と思われる箇所には適宜説明を入れる。また、今回はPlotlyのplotly.graph_objects.Volume()
モジュール(参考[2,3])で等値面を多数描画したが、このモジュールは体積レンダリング用のモジュールである。一方で、plotly.graph_objects.Isosurface()
という等値面自体を描くことを目的とした、ほぼ同等機能を持つモジュールもPlotlyには存在する(参考[6])。
両者の使用方法や得られるグラフはほぼ同じであるが、plotly.graph_objects.Volume()
はオプショナルパラメータとしてopacityscale
を設定できる。opacityscale
は等値面の不透明度に関係するパラメータで、詳しくは後述するが、plotly.graph_objects.Volume()
は等値面ごとに不透明度を細かく設定でき、体積レンダリングの際(今回のように4次元データの可視化をする際)は、深さ方向の視認性に優れる。
また、等値面を1枚描くだけで十分な場合はplotly.graph_objects.Surface()
を使えば良い(参考[7])。ただこの場合、Plotlyを使うまでも無く、matplotlibのplot_surface()
でも十分目的は達成できる。
# グラフの描画に必要なモジュール
import plotly.graph_objects as go
import plotly.io as pio
#pio.renderers.default='svg'
pio.renderers.default='browser'
"""
~~~ 中略 ~~~
"""
# 配列の1次元化
X = x.flatten()
Y = y.flatten()
Z = z.flatten()
Psi = psi.flatten()
# 描画する等値面の範囲設定
Max = np.maximum( np.fabs(np.amin(psi)), np.fabs(np.amax(psi)))
Max = 1/5 * Max
# グラフの描画
fig = go.Figure(data=go.Volume(
x=X,
y=Y,
z=Z,
value=Psi,
isomin=-Max, # 描画する等値面の最小値
isomax=Max, # 描画する等値面の最大値
opacity=0.1, # 不透明度:0~1
surface_count=20, # 描画する等値面の数
opacityscale="extremes", # 不透明度のカスタマイズ
))
# グラフレイアウトの調整
fig.update_layout(
scene = dict(
xaxis = dict(backgroundcolor="rgb(0, 0, 0)"), # 黒背景
yaxis = dict(backgroundcolor="rgb(0, 0, 0)"), # 黒背景
zaxis = dict(backgroundcolor="rgb(0, 0, 0)"), # 黒背景
),
scene_camera= dict(eye=dict(x=1.5, y=1.5, z=0.5)) # カメラアングル
)
# 図の表示
fig.show()
# グラフの保存
name = "psi" + str(n)
name = name + str(l)
name = name + str(m)
name = name + ".png"
fig.write_image(name)
2-2-1.Spyder上から実行する場合の設定
私はAnacondaが提供するPythonの統合開発環境であるSpyder上でプログラムを実行したが、SpyderからPlotlyを使用する際は下記の記述が必要である。pio.renderers.default='browser'
のように記述すると、グラフがブラウザ上に描画される。pio.renderers.default='svg'
と記述すると、Spyderのプロット画面上にグラフが描画される。pio.renderers.default='svg'
とした場合は、グラフのインタラクティブ機能は使えない。これはstack overflowの記事(参考[8])を参考にした。
import plotly.io as pio
#pio.renderers.default='svg'
pio.renderers.default='browser'
2-2-2.引数の仕様:plotly.graph_objects.Volume()
現時点では問題の切り分けが明確にできていないが、plotly.graph_objects.Volume()
へ渡す引数の仕様(条件)に関し、下記3点の注意事項がある。1.に関しては公式ヘルプに明確に記載があり、仕様として明らかである(参考[2,3])。2,3.の仕様については、私の推測に基づくもので、「2-1.電子波動関数の計算」で言及した、ゼロ割りが発生しうるようなプログラムを書かざるを得なくなった理由にも関係する。2,3.の仕様については未検証な部分が現時点では多く残る。
- 引数は1次元配列である必要がある
- 引数の1次元配列は規則的な数列になっていなければならない(例えばxの値の昇順など)
- 引数の1次元配列は立方体メッシュから生成されたものでなければならない
まずは1.の仕様について解説する。本記事のプログラムでは下記のように.flatten()
で3次元配列を1次元化してplotly.graph_objects.Volume()
(プログラム中ではgo.Volume()
と記述)に渡している。pythonなどのスクリプト言語を使用する際は、np.meshgrid()
などで作成した3次元配列x, y, z, phi
をplotly.graph_objects.Volume()
に直接引数として渡したくなるが、仕様としてそれは許されていない。
# 計算領域設定
N = 60 # 分割数
xmin, ymin, zmin = -50, -50, -50
xmax, ymax, zmax = 50, 50, 50
# メッシュ作成
x_a = np.linspace(xmin,xmax,N)
y_a = np.linspace(ymin,ymax,N)
z_a = np.linspace(zmin,zmax,N)
x, y, z = np.meshgrid(x_a, y_a, z_a)
"""
~~~ 中略 ~~~
"""
# 配列の1次元化
X = x.flatten()
Y = y.flatten()
Z = z.flatten()
Psi = psi.flatten()
"""
~~~ 中略 ~~~
"""
# グラフの描画
fig = go.Figure(data=go.Volume(
x=X,
y=Y,
z=Z,
value=Psi,
isomin=-Max, # 描画する等値面の最小値
isomax=Max, # 描画する等値面の最大値
opacity=0.1, # 不透明度:0~1
surface_count=20, # 描画する等値面の数
opacityscale="extremes", # 不透明度のカスタマイズ
))
次は2,3.の仕様について解説する。今回の目的は、実数化された電子波動関数np.meshgrid()
などで
ただ、この方法でplotly.graph_objects.Volume()
に値を渡すと空のグラフが生成され、意図した描画ができない。前述した通り、公式ドキュメントにはplotly.graph_objects.Volume()
の引数が満たすべき仕様について細かい記載がない(参考[2,3])。そのため、本記事のプログラムではゼロ割りが発生しうるような不格好な方法をとっている。
また、この問題に最も近い記事がstack overflowにあったが、スマートな解決方法は提示されていなかった(参考[9])。stack overflowに記載される投稿者の疑問は、本記事の疑問とは少し異なるが、この記事の投稿者はPlotlyを使用することを諦めて、MayaViという別のグラフ描画ライブラリ(参考[10,11])を使用することで問題が解決したと報告していた。
今回はMayaViを実際に使用して確認するまでには至らなかったが、公式ドキュメントのサンプルコードを読む限り、MayaViのmayavi.mlab.contour3d()
モジュールではPlotlyのplotly.graph_objects.Volume()
モジュールとほぼ同等のことができることは確認できた(参考[12,13])。また、MayaViのmayavi.mlab.contour3d()
モジュールではnp.meshgrid()
で生成した3次元配列を直接引数に取ることができることも、サンプルコードから確認できた。一方、下記コードのような極座標メッシュを取り扱うことが可能かまでは確認できなかった。
# 計算領域設定
N = 60 # 分割数
r_min, theta_min, phi_min = 0, 0, 0
r_max, theta_max, phi_max = 50, np.pi, 2*np.pi
# メッシュ作成
r_a = np.linspace(r_min,r_max,N)
theta_a = np.linspace(theta_min,theta_max,N)
phi_a = np.linspace(phi_min,phi_max,N)
r, theta, phi = np.meshgrid(r_a, theta_a, phi_a)
"""
~~~ 中略:psiの計算 ~~~
"""
# デカルト座標化
x = r * np.sin(theta) * np.cos(phi)
y = r * np.sin(theta) * np.sin(phi)
z = r * np.cos(theta)
# 配列の1次元化
X = x.flatten()
Y = y.flatten()
Z = z.flatten()
Psi = psi.flatten()
"""
~~~ 中略 ~~~
"""
# グラフの描画
fig = go.Figure(data=go.Volume(
x=X,
y=Y,
z=Z,
value=Psi,
isomin=-Max, # 描画する等値面の最小値
isomax=Max, # 描画する等値面の最大値
opacity=0.1, # 不透明度:0~1
surface_count=20, # 描画する等値面の数
opacityscale="extremes", # 不透明度のカスタマイズ
))
2-2-3.等値面の描画設定:plotly.graph_objects.Volume()
描画する等値面の設定に関する説明をここで行う。plotly.graph_objects.Volume()
の引数として、isomin
とisomax
を指定することで描画する等値面の最小値と最大値を陽に指定することができる。isomin
とisomax
はオプショナルパラメータで、指定しなければvalue
の最小値と最大値の範囲で等値面が描画される。
下記プログラムではMax = 1/5 * Max
として等値面の描画範囲を狭めているが、これは電子波動関数Max = 1/5 * Max
のようにしなくても別に良い。
opacity
は等値面の不透明度を指定するパラメータで、0から1の範囲で値を代入する。1に近いほど等値面は不透明になる。具体的にどういう値を代入するかは、考えている問題や描画する等値面の数によって異なるため解析者が自分で決めなければならない。今回はopacity=0.1
とした。
opacityscale
も等値面の不透明度を指定するパラメータで、等値面の値に応じて不透明度を変更できる。opacityscale="extremes"
と指定すると、等値面の最小値と最大値で最も不透明度が上がり、真ん中の等値面で最も不透明度が下がる。つまり、等値面の最小値と最大値が強調された描画となる。opacityscale="uniform"
と指定すると、全ての等値面で均一な不透明度となる。図3にextremes
とuniform
の違いを示す。opacityscale
に二次元配列のテーブルデータを代入し、より柔軟性を持たせた不透明度の設定もできるが詳細は公式ヘルプを参照されたい(参考[2,3])。また、opacityscale
を指定できるか否かがplotly.graph_objects.Volume()
とplotly.graph_objects.Isosurface()
の大きな違いである。
surface_count
は描画する等値面の数を指定するパラメータで、大きな値を指定するほど体積レンダリングの性能は上がるが、同時にopacity
を適切に設定しないと内部情報が見えにくくなる。また、surface_count
に大きな値を指定するとグラフの描画完了までに時間がかかる。今回はsurface_count=20
とし、20枚の等値面を描画している。
また、今回は特に設定していないが、colorscale="jet"
のように指定することで等値面の色を変更できる。デフォルトで用意されているカラーパレットは参考[14]の記事でまとめられている。
# 描画する等値面の範囲設定
Max = np.maximum( np.fabs(np.amin(psi)), np.fabs(np.amax(psi)))
Max = 1/5 * Max
# グラフの描画
fig = go.Figure(data=go.Volume(
x=X,
y=Y,
z=Z,
value=Psi,
isomin=-Max, # 描画する等値面の最小値
isomax=Max, # 描画する等値面の最大値
opacity=0.1, # 不透明度:0~1
surface_count=20, # 描画する等値面の数
opacityscale="extremes", # 不透明度のカスタマイズ
))
図3. "extremes"と"uniform"の比較(電子波動関数
2-2-4.グラフレイアウトの調整
グラフ背景色はscene
のbackgroundcolor
にRGB値を指定することで変更できる(参考[15])。デフォルトでは淡い青色に設定されている。今回は黒色の背景に変更している。
カメラアングル(視点位置と視点方向)はscene_camera
のeye
にベクトル値を指定することで変更できる(参考[16])。デフォルト値はx=1.25, y=1.25, z=1.25
である。今回は静止画保存も行っているためこの設定も行っているが、インタラクティブなグラフ描画のみをする場合は、指定する意味があまりない。
# グラフレイアウトの調整
fig.update_layout(
scene = dict(
xaxis = dict(backgroundcolor="rgb(0, 0, 0)"), # 黒背景
yaxis = dict(backgroundcolor="rgb(0, 0, 0)"), # 黒背景
zaxis = dict(backgroundcolor="rgb(0, 0, 0)"), # 黒背景
),
scene_camera= dict(eye=dict(x=1.5, y=1.5, z=0.5)) # カメラアングル
)
3.計算結果
3-1.計算時間
今回は
グラフの描画の際、pythonからplotly.graph_objects.Volume()を呼び出しhtmlファイルを出力する。Plotlyの動作仕様を正しく把握できていないが、plotly.graph_objects.Volume()の動作の際は裏でJavascriptが動作していると推定される。そのため、pythonよりも早く動作するプログラミング言語からPlotlyを呼び出しても、おそらく計算速度は劇的に速くならないし、並列計算などのソフト的処理で高速化が見込めるかについては私の知識不足で不明である。単純にハードのマシンスペックを上げるか、別のグラフ描画ライブラリを使用するしか有効な高速化方法は無いように思える。
3-2.電子波動関数(静止画)
実数化された電子波動関数
- 動径波動関数が0になる位置で電子波動関数も0になっているか?
- 球面調和関数の形と電子波動関数の形が大まかに同じ形をしているか?
以前書いた記事に載せている球面調和関数のグラフと今回の記事で載せている電子波動関数のグラフとでカメラアングルが異なり、モニターサイズやグラフの解像度の問題で、良く見比べてみないとx軸とy軸が入れ替わっているように見えるので注意されたい。
図4. 実数化された電子波動関数
図5. 実数化された電子波動関数
図5-1. 実数化された電子波動関数
図5-2. 実数化された電子波動関数
図5-3. 実数化された電子波動関数
図5-4. 実数化された電子波動関数
図6. 実数化された電子波動関数
図6-1. 実数化された電子波動関数
図6-2. 実数化された電子波動関数
図6-3. 実数化された電子波動関数
図6-4. 実数化された電子波動関数
図6-5. 実数化された電子波動関数
図6-6. 実数化された電子波動関数
図6-7. 実数化された電子波動関数
図6-8. 実数化された電子波動関数
図6-9. 実数化された電子波動関数
図7. 実数化された電子波動関数
図7-1. 実数化された電子波動関数
図7-2. 実数化された電子波動関数
図7-3. 実数化された電子波動関数
図7-4. 実数化された電子波動関数
図7-5. 実数化された電子波動関数
図7-6. 実数化された電子波動関数
図7-7. 実数化された電子波動関数
図7-8. 実数化された電子波動関数
図7-9. 実数化された電子波動関数
図7-10. 実数化された電子波動関数
図7-11. 実数化された電子波動関数
図7-12. 実数化された電子波動関数
図7-13. 実数化された電子波動関数
図7-14. 実数化された電子波動関数
図7-15. 実数化された電子波動関数
図7-16. 実数化された電子波動関数
3-3.電子波動関数(インタラクティブ描画)
ここでは、電子波動関数
図8. 実数化された電子波動関数
図9. 実数化された電子波動関数
図10. 実数化された電子波動関数
図11. 実数化された電子波動関数
図12. 実数化された電子波動関数
図13. 実数化された電子波動関数
図14. 実数化された電子波動関数
図15. 実数化された電子波動関数
図16. 実数化された電子波動関数
図17. 実数化された電子波動関数
図18. 実数化された電子波動関数
謝辞
今回、水素原子の可視化関連の記事(参考[4,5])を作成するにあたり、沢山の資料を参考にさせて頂きました。量子力学の基本的な考え方は参考[17]の教科書から得たものが大きく、特に水素原子の解析解の求め方は参考[18]の教科書を参考にさせて頂きました。これらの教科書の著者である桜井先生、猪木先生は残念ながら亡くなられていますが(川合先生は2022年現在ご健在です)、感謝申し上げたいと思います。大変勉強になりました。有難うございます。Web上に公開されている記事の中では参考[19]と参考[20]の記事を一番よく参照し、勉強させて頂きました。記事の製作者の方、有難うございます。また、無料の計算ライブラリを開発してくださっているコントリビュータの方にも頭が上がりません。有難うございます。この他にも参照させて頂いた教科書やWeb上の記事が沢山ありますが、最小限のものしか参考資料の欄に記載できていません。
また、解説が徒に冗長で纏まりも無く、間違った解説をしてしまっている可能性もあるため、Web上に記事として公開するのに躊躇する気持ちもありましたが、自分の勉強の過程として残しておくことにしました。久しぶりに量子力学の勉強がしたくなり、教科書に答えは載っているし簡単だろうという軽い気持ちで水素原子の解の描画に取り掛かりましたが、案外難しかったです。量子力学そのものの難しさというより、特殊関数の定義の"揺れ"やグラフ描画の技術的な面で手こずりました。
一方で、"水素原子の解を描いてみる"という初等的で具体的な問題を記事化していく事を通し、量子力学の中で自分が何を理解できていないかが浮き彫りになり、まだまだ勉強が足りないなということを再確認できました。記事中の誤りの指摘などは歓迎しますので、誤りを見つけた方はコメント欄やTwitterのメッセージ等で教えて頂けますと幸いです。
最後に、こんな長ったらしい記事を読んでくれた方にも感謝申し上げたいと思います。有難うございます。
環境
- ソフト
・plotly 5.9.0
・scipy 1.7.3
・numpy 1.21.5
・python 3.9.13
・IPython 8.4.0
・Anaconda 4.14.0
・Spyder 5.9.2
・Windows 10 Home 64bit
- ハード:2020年頃に5~6万円で購入したLenovoのノートPC
・製品名:IdeaPad S540-14API
・CPU:AMD Ryzen 5 3500U with Radeon Vega Mobile Gfx 2.10 GHz
・RAM:8.00 GB
・内臓GPU:AMD Radeon(TM) Vega 8 Graphics
参考
[1]. Plotly公式ホームページ(参照2022-09-30)
[2]. 3D Volume Plots(Plotly公式ヘルプ)(参照2022-09-30)
[3]. plotly.graph_objects.Volume(Plotly公式ヘルプ)(参照2022-09-30)
[4].【Python】水素原子の可視化:ラゲール陪多項式計算モジュール(参照2022-09-30)
[5].【Python】水素原子の可視化:球面調和関数(参照2022-09-30)
[6]. 3D Isosurface Plots(Plotly公式ヘルプ)(参照2022-09-30)
[7]. 3D Surface Plots(Plotly公式ヘルプ)(参照2022-09-30)
[8]. Plotly: How to display charts in Spyder?(stack overflow)(参照2022-09-30)
[9]. How to plot a Plotly Isosurface with non-analytic data?(stack overflow)(参照2022-09-30)
[10]. MayaVi(公式ホームページ)(英語)(参照2022-09-30)
[11]. MayaVi(公式ホームページ)(日本語)(参照2022-09-30)
[12]. ボリュームスカラーデータの視覚化(MayaVi公式ヘルプ)(参照2022-09-30)
[13]. mayavi.mlab.contour3d(MayaVi公式ヘルプ)(参照2022-09-30)
[14]. 【Python】Plotly のカラースケールの種類と使い方(参照2022-09-30)
[15]. 3D Axes(Plotly公式ヘルプ)(参照2022-09-30)
[16]. 3D Camera Controls(Plotly公式ヘルプ)(参照2022-09-30)
[17]. J.J.Sakurai. 現代の量子力学(上). 吉岡書店, 2006
[18]. 猪木, 川合. 量子力学Ⅰ. 講談社サイエンティフィク, 2010
[19]. 日曜化学:量子力学の基本と球面調和関数の可視化(Python/matplotlib)(参照2022-09-30)
[20]. Juliaで可視化:水素原子(参照2022-09-30)
Discussion