1D Kinetic Phonon Monte Carlo

1D Kinetic Phonon Monte Carlo

https://github.com/jeanphilippeperaud/1D_KMC_matlab/blob/master/MC1D.m

J-P. Péraud and N. G. Hadjiconstantinou, "An alternative approach to efficient simulation of micro/nanoscale phonon transport", Applied Physics Letters, 101, 153114, 2012.

作者的源程序是用matlab写的,这里改成了Python实现的版本. 具体的原理可以参考蒙特卡洛中的频率抽样.

导入一些库

1
2
3
4
5
6
7
8
9
10
import numpy as np
import numba as nb
import pandas as pd
import matplotlib.pyplot as plt
from scipy import constants as c

# 如果使用Jupyter可以开启下两行魔法命令,第三行是科研绘图风格的SciencePlot库.
# %matplotlib inline
# %config InlineBackend.figure_format = 'svg'
# plt.style.use('science')

读取Si的色散

1
data = pd.read_csv('dataSi.txt', names=['frequency', 'DoS', 'velocity', 'frequency_width', 'relaxation_time', 'polarization'], sep=' ')
frequency DoS velocity frequency_width relaxation_time polarization
0 3.6875e+10 7.9627e+08 8253.5 7.375e+10 8.1497e-07 1
1 1.1063e+11 2.5122e+09 8250.7 7.375e+10 6.9282e-07 1
2 1.8438e+11 5.3781e+09 8247.8 7.375e+10 5.3303e-07 1
3 2.5813e+11 9.1975e+09 8245 7.375e+10 3.9602e-07 1
4 3.3188e+11 1.3909e+10 8242.1 7.375e+10 2.9494e-07 1
... ... ... ... ... ... ...

定义体系的基本参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Teq = 300 # 参考平衡温度
Tinit = 300 # 体系初始温度
Tl = 301 # 左侧高温热沉温度
Tr = 299 # 右侧低温热沉温度
L = 2e-7 # 体系的长度
N = 500000 # 模拟的声子能量团的数目
Nx = 20 # 空间离散的cell数目
Ntt = 60 # 观测次数
tmax = 3e-10 # 模拟时间
tt = np.linspace(0, tmax, Ntt) # 统计的时间数组,只记录特定时间节点的结果.
Dx = L / Nx # cell的长度
xx = np.arange(Dx / 2, L + Dx / 2, Dx) # 各个cell的中心坐标
de_dT = (c.hbar * data.frequency / Teq)**2 * np.exp(c.hbar * data.frequency / (c.k * Teq)) / c.k / (np.exp(c.hbar * data.frequency / (c.k * Teq)) - 1)**2 # hbar * omega * df/dT,注意这里多乘了一个hbar * omega,因为模拟的是声子能量团
T = np.zeros((Ntt, Nx)) # 温度储存矩阵
Qx = np.zeros((Ntt, Nx)) # 热流储存矩阵
# 理解了下面这三个累积分布函数就理解了phonon MC中最重要的一块抽样机制
base = data.DoS * de_dT * data.frequency_width
coll = base / data.relaxation_time
vel = base * data.velocity
cumsum_base = np.cumsum(base) / np.sum(base)
cumsum_coll = np.cumsum(coll) / np.sum(coll)
cumsum_vel = np.cumsum(vel) / np.sum(vel)
C = np.sum(base) # Teq 时的比热容
# 不同源的偏差能量
enrgInit = L*C*np.abs(Tinit - Teq)
enrgLeft = np.sum(vel) * tmax * np.abs(Tl - Teq) / 4
enrgRight = np.sum(vel) * tmax * np.abs(Tr - Teq) / 4
# 整个模拟过程中体系中总的偏差能量
enrgTot = enrgInit + enrgLeft + enrgRight
# 一个声子团对应的能量
Eeff = enrgTot / N
# 检验一下参数是否正确
print('热导率为:{} W/mK'.format(np.sum(data.frequency_width * data.DoS * data.relaxation_time * data.velocity**2 * de_dT / 3)))

热导率为:143.840918042421 W/mK

开始模拟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 这里采取了累积概率函数抽样的形式,np.random.choice可以完成相同的需求,根据cumsum_prob这个累积概率分布函数去抽样arr数组. 
@nb.jit(nopython=True)
def rand_choice_nb(arr, cumsum_prob):
"""Sample an element in arr with the probability given in prob.
"""
return arr[np.searchsorted(cumsum_prob, np.random.random(), side="right")]

for particle in range(N):
Ri = np.random.rand() # 声子可能从三个源发射,左侧高温热沉,右侧低温热沉,以及体系内部。由于在这个模拟中体系的初始能量为0,所以实际上并不会在内部发射。在(0,1)间随机抽样一个数,确定声子从哪里发射。
if Ri < enrgInit / enrgTot: # 在这种情况下从内部发射
x0 = L * np.random.rand() # 确定发射的初始位置
phonon_index = rand_choice_nb(data.index.values, cumsum_base.values) # 在KMC里温度偏差用de_dT近似了,所以这里在cumsum_base里抽样就可以了
R = 2 * np.random.rand() - 1 # 确定发射的仰角,R = cos(theta)
vx = data.loc[phonon_index, 'velocity'] * R # x方向群速度的投影
psign = np.sign(Tinit - Teq) # 这颗能量团相对于参考温度的能量是正还是负
elif Ri > enrgInit / enrgTot and Ri < 1 - enrgRight / enrgTot: # 在这种情况下从左墙发射
x0 = 0
phonon_index = rand_choice_nb(data.index.values, cumsum_vel.values)
R = np.sqrt(np.random.rand()) # R=cos(theta),这时是面发射,所以累积分布函数和内部发射存在差别,包括仰角的积分上限以及可见面积
vx = data.loc[phonon_index, 'velocity'] * R
psign = np.sign(Tl - Teq)
else: # 此时从右墙发射
x0 = L
phonon_index = rand_choice_nb(data.index.values, cumsum_vel.values)
R = -np.sqrt(np.random.rand())
vx = data.loc[phonon_index, 'velocity'] * R
psign = np.sign(Tr - Teq)
t0 = np.random.rand() * tmax # 这个particle的初始时间.每个particle的初始时间是随机抽样的,最大为tmax,这个是为了描述时间相关的非稳态演化过程.
finished = False # False代表着这个particle的生命周期还没结束,我们还要继续追踪它的运动.
im = np.where(tt >= t0)[0][0] # 找到t0时间对应的时间数组的index,在fortran和matlab里数组的index从0开始,这和其他的语言不太一样》
while not finished:
Delta_t = - data.loc[phonon_index, 'relaxation_time'] * np.log(np.random.rand()) # Delta_t就是到下一次散射前该particle运动的时间
t1 = t0 + Delta_t
x1 = x0 + Delta_t * vx
# 统计当前particle的贡献,这个逻辑是在两次散射间粒子可能运动了很长时间,跨过了很多个时间数组节点,因此我们要统计每个节点处particle的位置.
while (im < Ntt and t0 <= tt[im] and t1 > tt[im]):
x_ = x0 + (tt[im] - t0) * vx
indx = int(np.floor(x_ / L * Nx))
if (indx < Nx) and (indx >= 0):
T[im, indx] = T[im, indx] + psign * Eeff / C / Dx # 认为在小温差下比热是不变的,此时能量偏差和温度的对应关系是很简单的
Qx[im, indx] = Qx[im, indx] + psign * Eeff * vx / Dx
im += 1
# 抽样散射后的声子团频率
phonon_index = rand_choice_nb(data.index.values, cumsum_col.values)
# 更新声子团频率、速度、位置、时间、运动方向
R = 2 * np.random.rand() - 1
vx = data.loc[phonon_index, 'velocity'] * R
x0 = x1
t0 = t1
if (t0 > tmax) or (x0 < 0) or (x0 > L):
finished = True

看一下结果

1
2
3
4
5
6
plt.scatter(xx * 1e9, T[0, :] + Teq, s=15, facecolors='none', edgecolors='#E41A1C', marker='s', label='t = 0')
plt.scatter(xx * 1e9, T[5, :] + Teq, s=15, facecolors='none', edgecolors='blue', marker='s', label='t = 25 ps')
plt.scatter(xx * 1e9, T[-1, :] + Teq, s=15, facecolors='none', edgecolors='#A65628', marker='s', label='t = 300 ps')
plt.legend()
plt.ylabel('$T$ (K)')
plt.xlabel('$x$ (nm)')
1
print('Effective k is {} W/mK'.format(np.average(Qx[-1, :]) / (2 / 200e-9)))

Effective k is 40.110370269096805 W/mK