1D Ensemble Phonon Monte Carlo
写一写感觉对MC的认识又加深了一些. 实际上Ensemble MC相比于Kinetic MC,如果不考虑Phonon性质随温度的变化,那它在模拟过程中没有引入任何更多的信息,除了比Tracing慢以外给出的结果应该是一样的. 在纯phonon模拟中哪些性质需要我们知道局域格点温度呢?实际上就是散射时的频率抽样分布函数 \[
\frac{D(\omega, p) \hbar \omega}{\tau(\omega, p, T_{\text{loc}})}\left[f(\omega,T_\text{loc} ) - f(\omega, T_\text{eq})\right]
\] 这一项里有两个地方出现了\(T_\text{loc}\) ,一个是弛豫时间,一个是后面的玻色爱因斯坦分布,但是一旦我们采用差分近似 \[
\frac{D(\omega, p) \hbar \omega}{\tau(\omega, p, T_{\text{loc}})}\left[f(\omega,T_\text{loc} ) - f(\omega, T_\text{eq})\right] \approx \frac{D(\omega, p) \hbar \omega}{\tau(\omega, p, T_{\text{eq}})}\frac{\mathrm{d}f(\omega)}{\mathrm{d}T}\Big|_{T_\text{eq}} (T_\text{eq} - T_\text{loc})
\] 就不再需要统计局域温度了,因为此时同一格点内的\(T_\text{loc}\) 都是一样的,在抽样分布函数归一化后最后的温度项就没了. 所以一旦采用了这种近似,Ensemble MC和Kinetic MC主要的区别就是对散射过程的处理.
对于局域温度统计过程,在MC中严格的统计是使用玻色爱因斯坦分布计算局域能量密度或者局域Pesudo能量密度, \[
\begin{aligned}
(N^+ - N^-)\mathcal{E}_{\mathrm{eff}}^{d}/V &= \sum_p \int_{0}^{\omega_m} \hbar\omega D(\omega, p) \times \left[ f(\omega, T_\text{loc}) - f(\omega, T_{eq})\right] \mathrm{d}\omega \\
&= \sum_p \int_{0}^{\omega_m} \hbar\omega D(\omega, p) \times \left[\frac{1}{\exp(\frac{\hbar\omega}{k_bT_\text{loc}})- 1} - \frac{1}{\exp(\frac{\hbar\omega}{k_bT_{eq}}) - 1} \right] \mathrm{d}\omega
\end{aligned}
\] 当然我们也可以做同样的近似, \[
\begin{aligned}
(N^+ - N^-) \mathcal{E}_{\mathrm{eff}}^{d}/V &\approx (T_\text{eq} - T_\text{loc})\sum_p \int_{0}^{\omega_m} \hbar\omega D(\omega, p)\frac{\mathrm{d}f(\omega)}{\mathrm{d}T}\Big|_{T_\text{eq}} \mathrm{d}\omega
\end{aligned}
\] 把温度从积分里提出来,从而简化温度的统计方式,但是这种近似与模拟过程无关,只与对模拟结果的统计有关.
Python写的1D薄膜导热的Ensemble phonon MC程序,还是使用Si的色散,和之前写过的Kinetic MC是一样的. 这里不考虑散射率随温度的变化并且对散射中的频率抽样分布函数进行差分近似,所以定义的抽样数组是基本一样的,但是在这里采用了严格的方式进行能量-温度转换,构建look-up table来对能量和温度进行映射.
导入一些库
1 2 3 4 5 6 7 8 9 10 11 12 13 from dataclasses import dataclassimport numpy as npimport numba as nbimport pandas as pdimport matplotlib.pyplot as pltfrom scipy import constants as cfrom scipy.interpolate import interp1d
读取Si的色散
1 data = pd.read_csv('dataSi.txt' , names=['frequency' , 'DoS' , 'velocity' , 'frequency_width' , 'relaxation_time' , 'polarization' ], sep=' ' )
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 Teq = 300 Tl = 301 Tr = 299 L = 10e-7 Nx = 30 dt = 5e-12 Ntt = 200 Dx = L / Nx xx = np.arange(Dx / 2 , L + Dx / 2 , Dx) x_lines = np.arange(Dx, L+Dx, Dx) Temperature = np.zeros((Ntt, Nx)) PesudoTemperature = np.zeros((Ntt, Nx)) drift_projection = np.zeros(len (data)) @dataclass class Phonon : x: float next_x: float mu: float index: int sign: int
构建能量和温度间的映射
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def f (data, T ): return 1 / (np.exp(c.hbar * data.frequency / (c.k * T)) - 1 ) def GetEnergyDeviation (data, T ): return np.sum (c.hbar * data.frequency * data.DoS * (f(data, T) - f(data, Teq)) * data.frequency_width) def GetPesudoEnergyDeviation (data, T ): return np.sum (c.hbar * data.frequency * data.DoS * (f(data, T) - f(data, Teq)) / data.relaxation_time * data.frequency_width) T_range = np.linspace(298.8 , 301.2 , 30 ) EnergyToT = interp1d([GetEnergyDeviation(data, T) for T in T_range], T_range) PesudoEnergyToT = interp1d([GetPesudoEnergyDeviation(data, T) for T in T_range], T_range)
构建抽样数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 base = data.DoS * de_dT * data.frequency_width coll = base / data.relaxation_time vel_hot = data.DoS * data.velocity * c.hbar * data.frequency * data.frequency_width * (f(data, Tl) - f(data, Teq)) vel_cold = data.DoS * data.velocity * c.hbar * data.frequency * data.frequency_width * (f(data, Teq) - f(data, Tr)) cumsum_base = np.cumsum(base) / np.sum (base) cumsum_coll = np.cumsum(coll) / np.sum (coll) cumsum_hot = np.cumsum(vel_hot) / np.sum (vel_hot) cumsum_cold = np.cumsum(vel_cold) / np.sum (vel_cold)
确定一颗声子能量团代表的能量
1 2 3 4 5 6 7 N_phonon = 20000 Eeff = GetEnergyDeviation(data, Tl) * L / N_phonon q_hot = np.sum (data.DoS * data.velocity * c.hbar * data.frequency * (f(data, Tl) - f(data, Teq)) * data.frequency_width) / 4 * dt q_cold = - np.sum (data.DoS * data.velocity * c.hbar * data.frequency * (f(data, Tr) - f(data, Teq)) * data.frequency_width) / 4 * dt q_hot_emitted_per_dt = int (q_hot / Eeff) q_cold_emitted_per_dt = int (q_cold / Eeff)
开始模拟
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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 @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" )] phonons = [] for t in range (Ntt): remove_indexes = [] drift_projection = np.zeros(len (data)) for i, phonon in enumerate (phonons): phonon.next_x = phonon.x + data.loc[phonon.index, "velocity" ] * phonon.mu * dt if phonon.next_x < 0 : phonon.next_x = 0 remove_indexes.append(i) elif phonon.next_x > L: phonon.next_x = L remove_indexes.append(i) drift_projection[phonon.index] += phonon.sign * (phonon.next_x - phonon.x) for index in remove_indexes[::-1 ]: phonons.pop(index) for i in range (q_hot_emitted_per_dt): mu = np.sqrt(np.random.rand()) phonon_index = rand_choice_nb(data.index.values, cumsum_hot.values) phonon = Phonon(0 , 0 , mu, phonon_index, 1 ) time = dt * np.random.rand() phonon.next_x = phonon.x + data.loc[phonon.index, "velocity" ] * phonon.mu * time if phonon.next_x >= L: phonon.next_x = L drift_projection[phonon.index] += phonon.sign * (phonon.next_x - phonon.x) else : drift_projection[phonon.index] += phonon.sign * (phonon.next_x - phonon.x) phonons.append(phonon) for i in range (q_cold_emitted_per_dt): mu = -np.sqrt(np.random.rand()) phonon_index = rand_choice_nb(data.index.values, cumsum_cold.values) phonon = Phonon(L, 0 , mu, phonon_index, -1 ) time = dt * np.random.rand() phonon.next_x = phonon.x + data.loc[phonon.index, "velocity" ] * phonon.mu * time if phonon.next_x <= 0 : phonon.next_x = 0 drift_projection[phonon.index] += phonon.sign * (phonon.next_x - phonon.x) else : drift_projection[phonon.index] += phonon.sign * (phonon.next_x - phonon.x) phonons.append(phonon) for phonon in phonons: phonon.x = phonon.next_x heat_flux = np.sum (drift_projection) / dt * Eeff / L scatter_index_arrays = [[] for _ in range (len (xx))] Energy = np.zeros(Nx) PesudoEnergy = np.zeros(Nx) for index, phonon in enumerate (phonons): position_index = np.searchsorted(x_lines, phonon.x) Energy[position_index] += phonon.sign * Eeff / Dx PesudoEnergy[position_index] += ( phonon.sign * Eeff / data.loc[phonon.index, "relaxation_time" ] / Dx ) Temperature[t] = EnergyToT(Energy) PesudoTemperature[t] = PesudoEnergyToT(PesudoEnergy) if np.random.rand() < 1 - np.exp( -dt / data.loc[phonon.index, "relaxation_time" ] ): scatter_index_arrays[position_index].append(index) remove_indexes = [] for position_index, scatter_index_array in enumerate (scatter_index_arrays): net_scatter_phonon_number = abs ( int (np.sum ([phonons[i].sign for i in scatter_index_arrays[position_index]])) ) phonon_sign = np.sign(Temperature[t, position_index] - Teq) scattered_phonons = np.random.choice( scatter_index_array, net_scatter_phonon_number, replace=False ) for phonon_index in scattered_phonons: phonons[phonon_index] = Phonon( phonons[phonon_index].x, phonons[phonon_index].next_x, 2 * np.random.rand() - 1 , rand_choice_nb(data.index.values, cumsum_coll.values), phonon_sign, ) remove_indexes.append(set (scatter_index_array) - set (scattered_phonons)) remove_set = set () for subset in remove_indexes: remove_set = remove_set | subset for index in list (sorted (remove_set))[::-1 ]: phonons.pop(index) print ('Effective k is {} W/mK' .format (heat_flux / (2 /L)))
看一下结果
1 print ('Effective k is {} W/mK' .format (heat_flux / (2 / L)))
Effective k is 68.82739201872596 W/mK