Discuz! Board

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 1705|回复: 5

用python实现密度泛函理论(1维)

[复制链接]

399

主题

1251

帖子

4020

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
4020
发表于 2023-6-25 14:44:10 | 显示全部楼层 |阅读模式
用python实现密度泛函理论https://blog.csdn.net/vor234/article/details/125088386

求解的总方程如下
$$\hat{\mathrm{H}}=-\frac{1}{2}\frac{\mathrm{d}^2}{\mathrm{dx}^2}+\mathrm{v}_{\mathrm{Ha}}\left(\mathrm{x}\right)+\mathrm{v}_{\mathrm{LDA}}\left(\mathrm{x}\right)+\mathrm{x}^2 $$
(1)不考虑势能时
$$\hat{\mathrm{H}}=\hat{\mathrm{T}}=-\frac{1}{2}\frac{\mathrm{d}^{2}}{\mathrm{dx}^{2}}$$
求解的关键在于如何处理二次微分,如果定义$(\frac{dy}{dx})_i=\frac{y_{i+1}-y_{i}}{h} $和$\mathrm{D}_{\mathrm{ij}}={\frac{\delta_{i+1,j}-\delta_{i,j}}{\mathrm{h}}} $(这里的i对应着矩阵的行,表示第几个点的微分;j表示列,表示每个点微分的计算过程),那么可以获得如下结论:
$$(\frac{\mathrm{dy}}{\mathrm{dx}})_i=\mathrm{D}_{ij}\mathrm{y}_j $$
这时,这个矩阵和y列向量相乘,就相当于后面的点减去了前面的点。
如果求两次导,则形式变为
$$\mathrm D_{ij}^2=\frac{\delta_{i+1,j}-2\delta_{i,j}+\delta_{i-1,j}}{\mathrm h^2}$$
最终的结果是两边之和减去2倍中间。
这时的代码如下
  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3. n_grid = 200
  4. x = np.linspace(-5,5,n_grid)
  5. h = x[1]-x[0]
  6. D = -np.eye(n_grid)+np.diagflat(np.ones(n_grid-1),1)
  7. D = D / h
  8. D2=D.dot(-D.T)
  9. D2[-1,-1]=D2[0,0]
  10. eig_non, psi_non=np.linalg.eigh(-D2/2)
  11. for i in range(3):
  12.     print(i)   
  13.     print(eig_non[i])
  14.     plt.plot(x,psi_non[:,i], label=f"{eig_non[i]:.4f}")
  15.     plt.legend(loc=1)
  16.     plt.pause(0.01)
复制代码



回复

使用道具 举报

399

主题

1251

帖子

4020

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
4020
 楼主| 发表于 2023-6-25 15:22:37 | 显示全部楼层
(2)考虑外部势能项$x^2$
需要注意的是,把该项要放在矩阵的对角线上,这是因为H矩阵的对角线是和当前点相关的,而其他地方的值指的是点之间的交互。
  1. X=np.diagflat(x*x)
  2. eig_harm, psi_harm = np.linalg.eigh(-D2/2+X)
  3. for i in range(5):
  4.     plt.plot(x,psi_harm[:,i], label=f"{eig_harm[i]:.4f}")
  5.     plt.legend(loc=1)
  6.     plt.pause(0.01)
复制代码

回复

使用道具 举报

399

主题

1251

帖子

4020

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
4020
 楼主| 发表于 2023-6-25 15:53:06 | 显示全部楼层
(3)电子密度计算
接下来我们要计算Hatree和项和LDA交换项,然而这两个需要电子密度,因此需要先建立电子密度
电子密度为波函数的平方和
  1. # integral
  2. def integral(x,y,axis=0):
  3.     dx=x[1]-x[0]
  4.     return np.sum(y*dx, axis=axis)

  5. num_electron=17

  6. def get_nx(num_electron, psi, x):
  7.     # normalization
  8.     I=integral(x,psi**2,axis=0)
  9.     normed_psi=psi/np.sqrt(I)[None, :]
  10.    
  11.     # occupation num
  12.     fn=[2 for _ in range(num_electron//2)]
  13.     if num_electron % 2:
  14.         fn.append(1)

  15.     # density
  16.     res=np.zeros_like(normed_psi[:,0])
  17.     for ne, psi  in zip(fn,normed_psi.T):
  18.         res += ne*(psi**2)
  19.     return res

  20. plt.plot(get_nx(num_electron,psi_harm, x), label="harm")
  21. plt.legend(loc=1)
  22. plt.pause(0.01)
复制代码
回复

使用道具 举报

399

主题

1251

帖子

4020

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
4020
 楼主| 发表于 2023-6-25 15:57:21 | 显示全部楼层
(3)求ex项LDA
根据密度可以求得LDA(忽略校正项)
$${E}_{X}^{LDA}[\mathrm{n}]=-\frac{3}{4}(\frac{3}{\pi})^{1/3}\int{n}^{4/3}\mathrm{dx}$$
$$\mathrm{v}_X^{\mathrm{LDA}}[\mathrm{n}]=\frac{\partial\mathrm{E}_X^{\mathrm{LDA}}}{\partial\mathrm{n}}=-(\frac{3}{\pi})^{1/3}\mathrm{n}^{1/3}$$
  1. def get_exchange(nx,x):
  2.     energy=-3./4.*(3./np.pi)**(1./3.)*integral(x,nx**(4./3.))
  3.     potential=-(3./np.pi)**(1./3.)*nx**(1./3.)
  4.     return energy, potential
复制代码
回复

使用道具 举报

399

主题

1251

帖子

4020

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
4020
 楼主| 发表于 2023-6-25 16:02:09 | 显示全部楼层
(4)计算coulomb势
3D的Hatree在1D不收敛,因此用了一个近似。
  1. def get_hatree(nx,x, eps=1e-1):
  2.     h=x[1]-x[0]
  3.     energy=np.sum(nx[None,:]*nx[:,None]*h**2/np.sqrt((x[None,:]-x[:,None])**2+eps)/2)
  4.     potential=np.sum(nx[None,:]*h/np.sqrt((x[None,:]-x[:,None])**2+eps),axis=-1)
  5.     return energy, potential
复制代码
这个实际上用了Numpy很有意思的一个算法
x[None,:]和x[:,None]实际是给x加了两个维度,比如原来x是10个元素的一维向量,x[None,:]就变为了1*10,而x[:,None]就变成了10*1,分别代表了xj和xi
换句话说,加入空维度None后,xi和xj在位置上就可以有区分,可以理解为冒号的位置,xj在第二个位置,xi在第一个位置
从公式也可以看到,最终的积分是对最后一个位置进行积分(axis = -1),也就是对j进行积分。



回复

使用道具 举报

399

主题

1251

帖子

4020

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
4020
 楼主| 发表于 2023-6-25 16:12:15 | 显示全部楼层
最终代码
  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3. n_grid = 200
  4. x = np.linspace(-5,5,n_grid)
  5. h = x[1]-x[0]
  6. D = -np.eye(n_grid)+np.diagflat(np.ones(n_grid-1),1)
  7. D = D / h
  8. D2=D.dot(-D.T)
  9. D2[-1,-1]=D2[0,0]

  10. X=np.diagflat(x*x)
  11. eig_harm, psi_harm = np.linalg.eigh(-D2/2+X)


  12. # integral
  13. def integral(x,y,axis=0):
  14.     dx=x[1]-x[0]
  15.     return np.sum(y*dx, axis=axis)

  16. num_electron=17

  17. def get_nx(num_electron, psi, x):
  18.     # normalization
  19.     I=integral(x,psi**2,axis=0)
  20.     normed_psi=psi/np.sqrt(I)[None, :]
  21.    
  22.     # occupation num
  23.     fn=[2 for _ in range(num_electron//2)]
  24.     if num_electron % 2:
  25.         fn.append(1)

  26.     # density
  27.     res=np.zeros_like(normed_psi[:,0])
  28.     for ne, psi  in zip(fn,normed_psi.T):
  29.         res += ne*(psi**2)
  30.     return res

  31. def get_exchange(nx,x):
  32.     energy=-3./4.*(3./np.pi)**(1./3.)*integral(x,nx**(4./3.))
  33.     potential=-(3./np.pi)**(1./3.)*nx**(1./3.)
  34.     return energy, potential

  35. def get_hatree(nx,x, eps=1e-1):
  36.     h=x[1]-x[0]
  37.     energy=np.sum(nx[None,:]*nx[:,None]*h**2/np.sqrt((x[None,:]-x[:,None])**2+eps)/2)
  38.     potential=np.sum(nx[None,:]*h/np.sqrt((x[None,:]-x[:,None])**2+eps),axis=-1)
  39.     return energy, potential

  40. def print_log(i,log):
  41.     print(f"step: {i:<5} energy: {log['energy'][-1]:<10.4f} energy_diff: {log['energy_diff'][-1]:.10f}")

  42. max_iter=1000
  43. energy_tolerance=1e-5
  44. log={"energy":[float("inf")], "energy_diff":[float("inf")]}

  45. nx=np.zeros(n_grid)
  46. for i in range(max_iter):
  47.     ex_energy, ex_potential=get_exchange(nx,x)
  48.     ha_energy, ha_potential=get_hatree(nx,x)
  49.    
  50.     # Hamiltonian
  51.     H=-D2/2+np.diagflat(ex_potential+ha_potential+x*x)
  52.    
  53.     energy, psi= np.linalg.eigh(H)
  54.    
  55.     # log
  56.     log["energy"].append(energy[0])
  57.     energy_diff=energy[0]-log["energy"][-2]
  58.     log["energy_diff"].append(energy_diff)
  59.     print_log(i,log)
  60.    
  61.     # convergence
  62.     if abs(energy_diff) < energy_tolerance:
  63.         print("converged!")
  64.         break
  65.    
  66.     # update density
  67.     nx=get_nx(num_electron,psi,x)
  68. else:
  69.     print("not converged")

  70. plt.plot(nx)
  71. plt.plot(get_nx(num_electron,psi_harm,x), label="no-interaction")
  72. plt.legend()
  73. plt.pause(0.01)
复制代码
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|DiscuzX

GMT+8, 2025-6-8 10:59 , Processed in 0.037586 second(s), 18 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表