Discuz! Board

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

python求解二维拉普拉斯方程(密度泛函系列)

[复制链接]

399

主题

1251

帖子

4020

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
4020
发表于 2023-6-27 07:22:05 来自手机 | 显示全部楼层 |阅读模式
主方程
  1. import numpy as np
  2. import time
  3. from dft2function import *
  4. time_begin = time.time()
  5. N =30 # 网格点数
  6. dx = dy =1 / (N +1) # 步长
  7. x = np.linspace(dx,1 - dx, N)
  8. y = np.linspace(dy,1 - dy, N)
  9. X, Y = np.meshgrid(x, y)
  10. num_electron = 5


  11. M_laplace=get_laplace(N);
  12. M_T = -1./2.*M_laplace;

  13.    
  14. max_iter=100
  15. energy_tolerance=1e-5
  16. log={"energy":[float("inf")], "energy_diff":[float("inf")]}
  17. nxy=np.zeros([N,N])
  18. def print_log(i,log):
  19.     print(f"step: {i:<5} energy: {log['energy'][-1]:<10.4f} energy_diff: {log['energy_diff'][-1]:.10f}")
  20. for i in range(max_iter):
  21.     ex_energy, ex_potential=get_exchange(nxy,dx,dy)
  22.     ha_potential=get_hatree(nxy,x,y)
  23.     ext = get_ext(x,y)
  24.     # Hamiltonian
  25.     H=M_T+np.diagflat((ex_potential+ha_potential+ext).flatten())
  26.    
  27.     energy, psi= np.linalg.eigh(H)
  28.    
  29.     # log
  30.     log["energy"].append(energy[0])
  31.     energy_diff=energy[0]-log["energy"][-2]
  32.     log["energy_diff"].append(energy_diff)
  33.     print_log(i,log)
  34.    
  35.     # convergence
  36.     if abs(energy_diff) < energy_tolerance:
  37.         print("converged!")
  38.         break
  39.    
  40.         # update density
  41.     nxy=get_nx(num_electron, psi, dx,dy)
  42.     nxy.resize([N,N])
  43. else:
  44.     print("not converged")



  45. time_end = time.time()        
  46. print("求解时间: {:.3f}".format(time_end-time_begin))   
  47. import matplotlib.pyplot as plt
  48. fig, axs = plt.subplots(10, 10)
  49. for i in range(10):
  50.     for j in range(10):
  51.         axs[i, j].set_title("EigV: {:.3f}".format(energy[10*i+j]))
  52.         axs[i, j].imshow(psi[:, 10*i+j].reshape(N, N))

  53. fig.tight_layout()
  54. plt.show()
复制代码
函数方程
  1. import numpy as np
  2. # 构造系数矩阵
  3. def get_laplace(N):
  4.     T = np.zeros((N**2, N**2))
  5.     for i in range(N):
  6.         for j in range(N):
  7.             k = i * N + j  # 当前点索引
  8.             T[k,k] = -4
  9.             if i>0  : T[k,k-N] = 1
  10.             if i<N-1: T[k,k+N] = 1
  11.             if j>0  : T[k,k-1] = 1
  12.             if j<N-1: T[k,k+1] = 1
  13.     return T

  14. def integral(dx,dy,z,axis=0):
  15.     return np.sum(z*dx*dy, axis=axis)

  16. def get_nx(num_electron, psi, dx,dy):
  17.     # normalization
  18.     I=integral(dx,dy,psi**2)
  19.     normed_psi=psi/np.sqrt(I)[None, :]


  20.     fn=[2 for _ in range(num_electron//2)]
  21.     if num_electron % 2:
  22.         fn.append(1)

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

  28. def get_exchange(nxy,dx,dy):
  29.     energy=-3./4.*(3./np.pi)**(1./3.)*integral(dx,dy,nxy**(4./3.))
  30.     potential=-(3./np.pi)**(1./3.)*nxy**(1./3.)
  31.     return energy, potential

  32. def get_hatree(nxy,x,y,eps=1e-1):
  33.     dx  =x[1]-x[0]
  34.     dy  =y[1]-y[0]
  35.     x1 = x[:,None,None,None]
  36.     x2=  x[None,:,None,None]
  37.     y1 = y[None,None,:,None]
  38.     y2=  y[None,None,None,:]
  39.     nx2y2 = nxy[None,:None,:]
  40.     potential=np.sum(nx2y2*dx*dy/np.sqrt((x1-x2)**2+(y1-y2)**2+eps),axis=(-1,-3))
  41.     return  potential

  42. def get_ext(x,y):
  43.     x2y2 = x[:,None]**2*y[None,:]**2
  44.     return x2y2
复制代码
回复

使用道具 举报

399

主题

1251

帖子

4020

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
4020
 楼主| 发表于 2023-6-27 07:23:47 | 显示全部楼层
二维拉普拉斯本征方程可以表示为:

$$\nabla^2 u(x,y) = -\lambda u(x,y)$$
其中,$
\nabla^2$ 表示拉普拉斯算子,$u(x,y)$ 是未知的函数,$\lambda$ 是本征值。

我们可以使用有限差分法来求解这个方程。具体来说,我们可以将二维区域 $(0,1) \times (0,1)$ 划分为 $N \times N$个网格点,每个网格点的坐标为 $(i \Delta x, j \Delta y)$,其中 $\Delta x = \Delta y = \frac{1}{N+1}$。我们可以用 $u_{i,j}$ 表示在 $(i \Delta x, j \Delta y)$ 处的函数值,用 $A$ 表示系数矩阵(也就是拉普拉斯算子的离散形式),用 $\lambda_k$ 和 $v_k$ 表示第 $k$个本征值和本征向量,则方程可以表示为:

$$A \mathbf{u} = -\lambda \mathbf{u}$$其中,$\mathbf{u}$ 是 $N^2$ 维的列向量,$\lambda$ 是标量。

我们可以通过求解上面的特征值问题来得到本征值和本征向量。具体来说,我们可以使用 numpy 中的 linalg.eig 函数来求解。
求解过程中,虽然最终的波函数是二维的,但是eig函数只能求解一维的,因此需要把二维降为一维的。、
具体操作时
1.把最终的波函数矩阵按行拼接后转置变为列向量(这只是分解习惯,也可以按列拼接),假设图像N×N,那么这个列向量就有N×N个元素;
2.特征矩阵A的每行都相当于一个算子,对这个图像进行变换(第k行对第k个点进行运算,得到一个新的第k个点,k= 1,2,3...N×N),具体到拉不拉斯算子,就是对每个点求二阶导,更具体点,就是让“4个相邻点的和”减去4倍当前点"
3.根据”第k行对第k个点进行运算“的原则,假设要处理的点为i行j列的点,对应的列向量中第k = (i-1)*N+(j-1)个点(k从0开始算),前面的点就是k-1,后面的点就是k+1,上面的点就是上一行的点k-N,下面的点就是下一行的点k+N。



代码如下:
  1. import numpy as np
  2. import time
  3. time_begin = time.time()
  4. N =10 # 网格点数
  5. dx = dy =1 / (N +1) # 步长
  6. x = np.linspace(dx,1 - dx, N)
  7. y = np.linspace(dy,1 - dy, N)
  8. X, Y = np.meshgrid(x, y)

  9. # 构造系数矩阵
  10. A = np.zeros((N**2, N**2))
  11. for i in range(N):
  12.     for j in range(N):
  13.         k = i * N + j  # 当前点索引
  14.         A[k,k] = -4
  15.         if i>0  : A[k,k-N] = 1
  16.         if i<N-1: A[k,k+N] = 1
  17.         if j>0  : A[k,k-1] = 1
  18.         if j<N-1: A[k,k+1] = 1

  19.         #A[k,k] += (i-N/2)**2+(j-N/2)**2

  20. eig, psi=np.linalg.eigh(A)

  21. time_end = time.time()        
  22. print("求解时间: {:.3f}".format(time_end-time_begin))   
  23. import matplotlib.pyplot as plt
  24. fig, axs = plt.subplots(10, 10)
  25. for i in range(10):
  26.     for j in range(10):
  27.         axs[i, j].set_title("EigV: {:.3f}".format(eig[10*i+j]))
  28.         axs[i, j].imshow(psi[:, 10*i+j].reshape(N, N))

  29. fig.tight_layout()
  30. plt.show()
复制代码


这里面有几个需要着重理解的地方


运行结果如下图所示:
可以看到,我们成功地求解了二维拉普拉斯本征方程,并得到了前十个本征值和本征向量。
回复

使用道具 举报

399

主题

1251

帖子

4020

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
4020
 楼主| 发表于 2023-6-27 08:32:14 来自手机 | 显示全部楼层
https://www.qyyshop.com/info/722458.html
回复

使用道具 举报

399

主题

1251

帖子

4020

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
4020
 楼主| 发表于 2023-6-27 18:07:56 | 显示全部楼层
尝试加快运算速度
  1. import numpy as np
  2. from numpy.linalg import eigh
  3. from scipy.sparse.linalg import eigs
  4. import time
  5. time_begin = time.time()
  6. N =100 # 网格点数
  7. dx = dy =1 / (N +1) # 步长
  8. x = np.linspace(dx,1 - dx, N)
  9. y = np.linspace(dy,1 - dy, N)
  10. X, Y = np.meshgrid(x, y)

  11. # 构造系数矩阵
  12. A = np.zeros((N**2, N**2))
  13. for i in range(N):
  14.     for j in range(N):
  15.         k = i * N + j  # 当前点索引
  16.         A[k,k] = -4
  17.         if i>0  : A[k,k-N] = 1
  18.         if i<N-1: A[k,k+N] = 1
  19.         if j>0  : A[k,k-1] = 1
  20.         if j<N-1: A[k,k+1] = 1

  21.         #A[k,k] += (i-N/2)**2+(j-N/2)**2

  22. #eig, psi=eigh(A)
  23. eig, psi=eigs(A,10)

  24. time_end = time.time()        
  25. print("求解时间: {:.3f}".format(time_end-time_begin))   
  26. import matplotlib.pyplot as plt
  27. fig, axs = plt.subplots(2, 5)
  28. for i in range(2):
  29.     for j in range(5):
  30.         axs[i, j].set_title("EigV: {:.3f}".format(eig[i]))
  31.         axs[i, j].imshow(psi[:, 2*i+j].reshape(N, N).real)

  32. fig.tight_layout()
  33. plt.show()
复制代码
回复

使用道具 举报

399

主题

1251

帖子

4020

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
4020
 楼主| 发表于 2023-6-27 23:15:43 | 显示全部楼层
稀疏矩阵求解
  1. import numpy as np
  2. from scipy.sparse import diags
  3. from scipy.sparse.linalg import eigsh

  4. # 定义求解区域和边界条件
  5. L =1.0 # 矩形的长
  6. W =1.0 # 矩形的宽
  7. N =50 # 矩形的行数
  8. M =50 # 矩形的列数

  9. # 离散化步长
  10. dx = L / (N -1)
  11. dy = W / (M -1)

  12. # 定义系数矩阵和右端向量
  13. A = diags([-4.0,1.0,1.0,1.0,1.0], [0, -1,1, -N, N], shape=(N*M, N*M))
  14. A = -A / dx**2 #+ diags([1.0], [0], shape=(N*M, N*M))

  15. b = np.zeros(N*M)

  16. # 设置边界条件
  17. for i in range(N):
  18.     b[i] =0.0
  19.     b[N*(M-1)+i] =0.0
  20. for j in range(M):
  21.     b[j*N] =0.0
  22.     b[(j+1)*N-1] =0.0

  23. # 求解本征值和本征向量
  24. n_eigs =100 # 求解前5个本征值和本征向量
  25. lambdas, us = eigsh(A, k=n_eigs, which='SM')

  26. idx = np.argsort(lambdas)[:100]
  27. lambdas = lambdas[idx]
  28. us = us[:, idx]

  29. #print('可视化本征函数中。。。。')
  30. # 可视化本征函数
  31. import matplotlib.pyplot as plt
  32. fig, axs = plt.subplots(10,10, figsize=(10,4))
  33. for i in range(10):
  34.     for j in range(10):
  35.         k = i *10 + j
  36.         u = us[:, k].reshape((N, M))
  37.         print(u)
  38.         axs[i, j].imshow(u.real, cmap='coolwarm')#, extent=[0,1,0,1])
  39.         axs[i, j].set_title(f'λk+1= {lambdas[k]:.3f}')
  40. plt.tight_layout()
  41. plt.show()
复制代码
如果M和N都是1000,时间会增长为22668s(折合7小时)

回复

使用道具 举报

399

主题

1251

帖子

4020

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
4020
 楼主| 发表于 2023-6-29 08:24:41 | 显示全部楼层
接下来需要求Hatree和exchange,这两个都需要用到电子密度。
电子密度为所有归一化波函数的平方和
所以积分过程变为:
  1. def integral(dx,dy,z,axis=(0,1)):
  2.     return np.sum(z*dx*dy, axis=axis)

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

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



复制代码
回复

使用道具 举报

399

主题

1251

帖子

4020

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
4020
 楼主| 发表于 2023-7-1 21:53:08 | 显示全部楼层
LDA项 也就是交换关联项
$$\mathrm{v_X^{LDA}[n]=\frac{\partial E_X^{LDA}}{\partial n}=-(\frac3\pi)^{1/3}n^{1/3}}$$
势函数可以写为
  1. def get_exchange(nxy,dx,dy):
  2.     energy=-3./4.*(3./np.pi)**(1./3.)*integral(dx,dy,nxy**(4./3.))
  3.     potential=-(3./np.pi)**(1./3.)*nxy**(1./3.)
  4.     return energy, potential
复制代码
回复

使用道具 举报

399

主题

1251

帖子

4020

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
4020
 楼主| 发表于 2023-7-1 22:01:22 | 显示全部楼层
最后一项是Hatree项
$$\hat{V}_H =\frac{e^2}{4\pi\epsilon_0}\int\frac{\rho(\vec r')}{|\vec r-\vec r'|}dr'$$

def get_hatree(nxy,x,y,eps=1e-1):
    dx  =x[1]-x[0]
    dy  =y[1]-y[0]
    x1 = x[:,None,None,None]
    x2=  x[None,:,None,None]
    y1 = y[None,None,:,None]
    y2=  y[None,None,None,:]
    nx2y2 = nxy[None,:None,:]
    potential=np.sum(nx2y2*dx*dy/np.sqrt((x1-x2)**2+(y1-y2)**2+eps),axis=(-1,-3))
    return  potential

回复

使用道具 举报

399

主题

1251

帖子

4020

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
4020
 楼主| 发表于 2023-7-1 22:05:32 | 显示全部楼层
def get_hatree(nxy,x,y,eps=1e-1):
    x1 = x[:,None,None,None]
    x2=  x[None,:,None,None]
    y1 = y[None,None,:,None]
    y2=  y[None,None,None,:]
    nx2y2 = nxy[None,:None,:]
    potential=np.sum(nx2y2*h/np.sqrt((x1-x2)**2+(y1-y2)**2+eps),axis=(-1,-3))
    return energy, potential
回复

使用道具 举报

399

主题

1251

帖子

4020

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
4020
 楼主| 发表于 2023-7-1 22:22:23 | 显示全部楼层
最后进入主循环
for i in range(max_iter):
    ex_energy, ex_potential=get_exchange(nx,x)
    ha_potential=get_hatree(nx,x)
   
    # Hamiltonian
    H=-D2/2+np.diagflat(ex_potential+ha_potential+x*x)
   
    energy, psi= np.linalg.eigh(H)
   
    # log
    log["energy"].append(energy[0])
    energy_diff=energy[0]-log["energy"][-2]
    log["energy_diff"].append(energy_diff)
    print_log(i,log)
   
    # convergence
    if abs(energy_diff) < energy_tolerance:
        print("converged!")
        break
   
    # update density
    nx=get_nx(num_electron,psi,x)
else:
    print("not converged")

plt.plot(nx)
plt.plot(get_nx(num_electron,psi_harm,x), label="no-interaction")
plt.legend()
plt.pause(0.01)
回复

使用道具 举报

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

本版积分规则

Archiver|手机版|小黑屋|DiscuzX

GMT+8, 2025-6-8 06:39 , Processed in 0.043191 second(s), 19 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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