"""
有限温度における電子のエネルギーとエントロピーへの寄与をVASPのDOSCARファイルから計算します。
このスクリプトは、VASPのDOSCARファイルから読み込んだ電子状態密度(DOS)データを使用して、
有限温度における電子の内部エネルギー、エントロピー、およびヘルムホルツ自由エネルギーを計算します。
フェルミ・ディラック分布を考慮し、温度に応じたフェルミ準位の変動もニュートン法を用いて決定します。
最終的に、温度に対するこれらの物理量の変化をプロットして可視化します。
:doc:`Electron_Helmholtz_energy_usage`
"""
import os
import sys
import numpy as np
from numpy import arange
from scipy import integrate
from scipy import optimize
from scipy.interpolate import interp1d
from matplotlib import pyplot as plt
# constants
e = 1.60218e-19 # C
kB = 1.380658e-23 # JK<sup>-1</sup>
Debug = 0
# Relative accuracy of the quad functin
eps = 1.0e-5
nmaxdiv = 150
# Temperature range
Tmin = 0 # K
Tmax = 1500
Tstep = 50
nT = int((Tmax - Tmin) / Tstep + 1)
#set EF in OUTCAR of VASP
EF0 = 7.5883 # eV
#read from DOSCAR
file = "DOSCAR"
[ドキュメント]
def f_dos(E):
"""
与えられたエネルギーにおける電子の状態密度(DOS)を計算します。
DOSCARファイルから読み込んだ離散的なDOSデータに対して、3次スプライン補間を用いて
任意のエネルギーにおけるDOS値を求めます。
:param E: エネルギー (float, eV)
:returns: 指定されたエネルギーにおける状態密度 (float, states/eV/u.c.)
"""
global E_raw, dos_raw
global Emin, Emax, Estep, nDOS
if E < Emin or Emax < E:
print("Error in f_dos: Given E={} exceeds the DOS E range [{}, {}]".format(E, Emin, Emax))
exit()
fdos = interp1d(E_raw, dos_raw, kind = 'cubic')
return fdos(E)
[ドキュメント]
def fe(E, T, EF):
"""
フェルミ・ディラック分布関数を計算します。
与えられたエネルギー、温度、フェルミ準位に基づいて、電子がそのエネルギー準位を占有する確率を計算します。
絶対零度の場合には、E < EF で 1.0、それ以外で 0.0 を返します。
:param E: エネルギー (float, eV)
:param T: 温度 (float, K)
:param EF: フェルミ準位 (float, eV)
:returns: フェルミ・ディラック分布関数の値 (float)
"""
global e, kB
if T == 0.0:
if E < EF:
return 1.0
else:
return 0.0
return 1.0 / (np.exp((E - EF) * e / kB / T) + 1.0)
[ドキュメント]
def fh(E, T, EF):
"""
電子が存在しない確率 (1 - fe) を計算します。
フェルミ・ディラック分布関数 `fe` を用いて、電子がそのエネルギー準位に存在しない確率を計算します。
:param E: エネルギー (float, eV)
:param T: 温度 (float, K)
:param EF: フェルミ準位 (float, eV)
:returns: 1 - fe(E, T, EF) の値 (float)
"""
return 1.0 - fe(E, T, EF)
[ドキュメント]
def dosfe(E, T, EF):
"""
状態密度とフェルミ・ディラック分布関数の積を計算します。
この関数は、積分計算で使用される被積分関数として定義されます。
:param E: エネルギー (float, eV)
:param T: 温度 (float, K)
:param EF: フェルミ準位 (float, eV)
:returns: f_dos(E) * fe(E, T, EF) の値 (float)
"""
return f_dos(E) * fe(E, T, EF)
[ドキュメント]
def Ne(T, EF, E0, E1):
"""
指定されたエネルギー範囲における電子の総数を計算します。
絶対零度ではフェルミ準位までのDOSを積分し、有限温度ではDOSとフェルミ・ディラック分布関数の積を積分します。
:param T: 温度 (float, K)
:param EF: フェルミ準位 (float, eV)
:param E0: 積分下限エネルギー (float, eV)
:param E1: 積分上限エネルギー (float, eV)
:returns: 指定された範囲における電子の総数 (float)
"""
if T == 0.0:
Ne_temp2 = integrate.quad(lambda E: f_dos(E), E0, EF, limit = nmaxdiv, epsrel = eps)
else:
Ne_temp2 = integrate.quad(lambda E: dosfe(E, T, EF), E0, E1, limit = nmaxdiv, epsrel = eps)
if Debug:
print(" Ne integ error: {}".format(Ne_temp2[1]))
return Ne_temp2[0]
[ドキュメント]
def Ne0(EF0, E0, E1):
"""
絶対零度における電子の総数を計算します。
`Ne` 関数を呼び出し、温度を0 Kに設定して絶対零度での電子数を計算します。
:param EF0: 0 Kにおけるフェルミ準位 (float, eV)
:param E0: 積分下限エネルギー (float, eV)
:param E1: 積分上限エネルギー (float, eV)
:returns: 絶対零度における電子の総数 (float)
"""
return Ne(0.0, EF0, E0, E1)
[ドキュメント]
def CalEF(T, EF0, E0, E1, totNe):
"""
温度Tにおけるフェルミ準位EFを、電子数保持の条件から計算します。
指定された温度Tにおいて、電子の総数が `totNe` となるようにフェルミ準位を調整します。
ニュートン法 (`scipy.optimize.newton`) を用いて、`Ne(T, EF, E0, E1) - totNe = 0` を満たすEFを求めます。
:param T: 温度 (float, K)
:param EF0: 0 Kにおける初期フェルミ準位 (float, eV)
:param E0: 積分下限エネルギー (float, eV)
:param E1: 積分上限エネルギー (float, eV)
:param totNe: 保持されるべき電子の総数 (float)
:returns: 温度Tにおけるフェルミ準位 (float, eV)
"""
EF = optimize.newton(lambda EF: Ne(T, EF, E0, E1) - totNe, EF0)
return EF
[ドキュメント]
def E_int(T, EF, E0, E1):
"""
指定されたエネルギー範囲における電子の内部エネルギーを計算します。
`f_dos(E) * fe(E, T, EF) * E` を指定されたエネルギー範囲で積分することにより、
電子の内部エネルギーを求めます。
:param T: 温度 (float, K)
:param EF: フェルミ準位 (float, eV)
:param E0: 積分下限エネルギー (float, eV)
:param E1: 積分上限エネルギー (float, eV)
:returns: 電子の内部エネルギー (float, eV/u.c.)
"""
Eint1 = integrate.quad(lambda E: dosfe(E, T, EF) * E, E0, E1, limit = nmaxdiv, epsrel = eps)
if Debug:
print(" E_int integ errors: {}".format(Eint1[1]))
return Eint1[0]
[ドキュメント]
def S(T, EF, E0, E1):
"""
電子のエントロピーにおける混合項を、kBの係数を除いて計算します。
フェルミ・ディラック分布関数 `fe` および `fh` を用いて、エントロピーの積分項を計算します。
絶対零度ではエントロピーは0と見なされます。
:param T: 温度 (float, K)
:param EF: フェルミ準位 (float, eV)
:param E0: 積分下限エネルギー (float, eV)
:param E1: 積分上限エネルギー (float, eV)
:returns: エントロピーの混合項 (float)
"""
if T == 0.0:
return 0.0
S1 = integrate.quad(lambda E: f_dos(E) * fe(E, T, EF) * np.log(fe(E, T, EF)), E0, E1, limit = nmaxdiv, epsrel = eps)
S2 = integrate.quad(lambda E: f_dos(E) * fh(E, T, EF) * np.log(fh(E, T, EF)), E0, E1, limit = nmaxdiv, epsrel = eps)
if Debug:
print(" S integ errors: {}, {}".format(S1[1], S2[1]))
return S1[0] + S2[0]
[ドキュメント]
def entropy(T, EF, E0, E1):
"""
電子のエントロピーをエネルギー単位 (eV) で計算します。
`S` 関数で計算された混合項に `-T * kB / e` の係数を乗じて、
エントロピーを温度と電気素量でスケーリングしたエネルギー単位で返します。
:param T: 温度 (float, K)
:param EF: フェルミ準位 (float, eV)
:param E0: 積分下限エネルギー (float, eV)
:param E1: 積分上限エネルギー (float, eV)
:returns: 電子のエントロピー (float, eV/u.c.)
"""
return -T * kB / e * S(T, EF, E0, E1)
[ドキュメント]
def main():
"""
スクリプトのメイン実行関数です。
VASPのDOSCARファイルから電子の状態密度を読み込み、指定された温度範囲で
電子の内部エネルギー、エントロピー、およびヘルムホルツ自由エネルギーを計算します。
結果はコンソールに出力され、matplotlibを用いてグラフ化されます。
"""
global EF0
E_raw, dos_raw = np.genfromtxt(file, skip_header=6, usecols=(0,1), unpack=True)
nDOS = len(E_raw)
Emin = E_raw[0]
Emax = E_raw[nDOS-1]
Estep = E_raw[1] - E_raw[0]
print("quad() parameters")
print(" releps=", eps)
print(" nmaxdiv=", nmaxdiv)
print("")
print("Temperature range: {} - {}, {} K step".format(Tmin, Tmax, Tstep))
print("")
print("DOS configuration")
print(" Read from [{}]".format(file))
print(" E range: {} - {}, {} eV step".format(Emin, Emax, Estep))
print("")
print("EF at 0K: {} eV".format(EF0))
Ne0K = Ne0(EF0, Emin, EF0)
Eint0 = E_int(0.0, EF0, 0.0, EF0)
print(" total Ne at 0 K: ", Ne0K)
print(" total E at 0 K: ", Eint0, " eV")
print("")
xT = []
yEF = []
yEint = []
yEentropy = []
yEFa = []
EFprev = EF0
print ("%5s\t%8s\t%14s\t%14s\t%14s\t%14s"
% ("T(K)", "EF(eV)", "dE_ele(eV/u.c.)", "dS_ele*T(eV/u.c.) (approx)", "dF_ele(eV/u.c.)",
"Ne(0 K) (check)"))
for i in range(nT):
T = Tmin + i * Tstep
kBTe = kB * T / e
dE = 6 * kBTe
# Integration lowerlimit: Choose one of E0 and compare the result for debugging
# The first one is the most robust for bug, but very slow
# The third one is faster, but use after careful debugging
# E0 = 0.0
# E0 = Emin
E0 = EFprev - dE
E1 = EFprev + dE
if E0 < Emin or Emax < E1:
print("*** Integration range error: [{}, {}] exceeds DOS E range [{}, {}]".format(E0, E1, Emin, Emax))
exit()
Ne0K = Ne0(EF0, E0, EF0)
Eint0 = E_int(0.0, EF0, E0, EF0)
EF = CalEF(T, EFprev, E0, E1, Ne0K)
NeCheck = Ne(T, EF, E0, E1)
Eint = E_int(T, EF, E0, E1) - Eint0
E_entropy = entropy(T, EF, E0, E1)
F_electron = Eint + E_entropy
# The contribution of electrons to dS is roughly in the range [EF - kBT, EF + kBT]
# The maximum contribution to S is at EF with fe = fh = 0.5
E_entropy_approx = -kBTe * f_dos(EF) * (2.0 * kBTe) * 2.0 * 0.5 * np.log(0.5)
xT.append(T)
yEF.append(EF)
yEint.append(Eint)
yEentropy.append(E_entropy)
yEFa.append(F_electron)
EFprev = EF
print ("%5.0f\t%12.6g\t%14.6g\t%14.6g(%14.6g)\t%14.6g\t%10.4g(%10.4g)"
% (T, EF, Eint, E_entropy, E_entropy_approx, F_electron, Ne0K, NeCheck))
#=============================
# Plot graphs
#=============================
fig = plt.figure(figsize = (10, 10))
ax1 = fig.add_subplot(2, 2, 1)
ax2 = fig.add_subplot(2, 2, 2)
ax3 = fig.add_subplot(2, 2, 4)
ax1.plot(xT, yEint, label = 'E_int', color = 'navy')
ax1.plot(xT, yEentropy, label = 'E_entropy', color = 'green')
ax1.plot(xT, yEFa, label = 'Thermal_electron', color = 'red')
ax1.set_xlabel("T (K)")
ax1.set_ylabel("E (eV)")
ax1.legend()
ax2.plot(xT, yEF, label = 'EF', color = 'black')
ax2.set_xlabel("T (K)")
ax2.set_ylabel("EF (eV)")
ax2.legend()
ax3.plot(E_raw, dos_raw, label = 'DOS', color = 'black')
ax3.plot([EF0, EF0], [min(dos_raw), max(dos_raw)], color = 'red', linestyle = 'dashed')
ax3.set_xlabel("E (eV)")
ax3.set_ylabel("DOS (states/u.c.)")
ax3.legend()
# Rearange the graph axes so that they are not overlapped
plt.tight_layout()
plt.pause(0.1)
print("Press ENTER to exit>>", end = '')
input()
if __name__ == '__main__':
main()