Python 如何使用numpy快速找到Bragg反射?

Python 如何使用numpy快速找到Bragg反射?,python,performance,numpy,scientific-computing,Python,Performance,Numpy,Scientific Computing,对于x射线衍射,需要找到所谓劳厄方程的解 G_hkl-k_in+| k|u in |*(sin(theta)cos(phi),sin(theta)sin(phi),cos(theta))=0 其中,G_hkl是给定的三维向量,k_in可以选择为(0,0,1),θ和φ是满足方程的自由参数。在一个典型的实验中,G_hkl然后绕x轴旋转,旋转的每一步都需要找到方程的解。在给定的旋转方向上,该方程不能有多个解 我编写这个python脚本是为了找到这些解决方案,但对于我的应用程序来说,它不够快 impor

对于x射线衍射,需要找到所谓劳厄方程的解

G_hkl-k_in+| k|u in |*(sin(theta)cos(phi),sin(theta)sin(phi),cos(theta))=0

其中,G_hkl是给定的三维向量,k_in可以选择为(0,0,1),θ和φ是满足方程的自由参数。在一个典型的实验中,G_hkl然后绕x轴旋转,旋转的每一步都需要找到方程的解。在给定的旋转方向上,该方程不能有多个解

我编写这个python脚本是为了找到这些解决方案,但对于我的应用程序来说,它不够快

import numpy as np
import time
# Just initialization of variables. This is fast enough
res_list = []
N_rot = 100
Ghkl = np.array([0,0.7,0.7])
Ghkl_norm = np.linalg.norm(Ghkl)
kin = np.array([0,0,1])
kin_norm = np.linalg.norm(kin)
alpha_step = 2*np.pi/(N_rot-1)
rot_mat = np.array([[1,0,0],[0,np.cos(alpha_step),-np.sin(alpha_step)],[0,np.sin(alpha_step),np.cos(alpha_step)]])

# You can deduce theta from the norm of the vector equation
theta = np.arccos(1-(Ghkl_norm**2/(2*kin_norm**2))) 
sint = np.sin(theta)
cost = np.cos(theta)

# This leaves only phi as paramter to find
# I find phi by introducing a finite test vector
# and testing whether the norm of the vector equation is close
# to zero for any of those test phis
phi_test = np.linspace(0,2*np.pi,200)

kout = kin_norm * np.array([sint * np.cos(phi_test), sint * np.sin(phi_test), cost + 0*phi_test]).T
##############
start_time = time.time()

for j in range(100): # just to make it longer to measure the time
    res_list = []
    for i in range(N_rot): # This loop is too slow
        # Here the norm of the vector equation is calculated for all phi_test
        norm_vec = np.linalg.norm(Ghkl[np.newaxis, :] - kin[np.newaxis, :] + kout, axis=1)
        if (norm_vec < 0.01 * kin_norm).any(): # check whether any fulfills the criterion
            minarg = np.argmin(norm_vec)
            res_list.append([theta, phi_test[minarg]])
        Ghkl = np.dot(rot_mat,Ghkl)

print('Time was {0:1.2f} s'.format( (time.time()-start_time)))
# On my machine it takes 0.3s and res_list should be 
# [[1.0356115365192968, 1.578689775673263]]
将numpy导入为np
导入时间
#只是初始化变量。这足够快了
res_list=[]
N_rot=100
Ghkl=np.数组([0,0.7,0.7])
Ghkl_范数=np.linalg.norm(Ghkl)
kin=np.数组([0,0,1])
kin_norm=np.linalg.norm(kin)
alpha_阶跃=2*np.pi/(N_-1)
rot_mat=np.数组([[1,0,0],[0,np.cos(alpha_步骤),-np.sin(alpha_步骤)],[0,np.sin(alpha_步骤),np.cos(alpha_步骤)])
#你可以从向量方程的范数推导θ
θ=np.arccos(1-(Ghkl_范数**2/(2*kin_范数**2)))
sint=np.sin(θ)
成本=净成本(θ)
#这只剩下phi作为要查找的参数
#我通过引入有限测试向量来求φ
#检验向量方程的范数是否接近
#对于任何这些测试潜在危险装置,设置为零
φu检验=np.linspace(0,2*np.pi,200)
kout=kin_norm*np.数组([sint*np.cos(phi_测试),sint*np.sin(phi_测试),cost+0*phi_测试])T
##############
开始时间=time.time()
对于范围(100)内的j:#只是为了延长测量时间
res_list=[]
对于范围内的i(N#rot):#此循环太慢
#这里计算所有φu检验的向量方程范数
norm_vec=np.linalg.norm(Ghkl[np.newaxis,:]-kin[np.newaxis,:]+kout,axis=1)
如果(norm_vec<0.01*kin_norm).any():#检查是否有任何符合标准
minarg=np.argmin(标准向量)
res_list.append([theta,phi_test[minarg]]))
Ghkl=np.dot(旋转垫,Ghkl)
打印('Time was{0:1.2f}s'。格式((Time.Time()-start_Time)))
#在我的机器上,它需要0.3秒,res_列表应该是
# [[1.0356115365192968, 1.578689775673263]]

你知道一种更快的计算方法吗,或者从概念上解决完全不同的方程,或者用我现有的方法使它更快?

有一种依赖关系,因为
Ghkl
在每次迭代中都会更新,并在下一次迭代中重新使用。相应的封闭形式可能很难找到。因此,我将专注于改进最内部循环中其余代码的性能

现在,我们正在计算
norm\u vec
,我认为可以使用下面列出的两种方法加快计算速度

应用方法1使用-

应用方法2使用-

因此,方法1并没有显示出对这些输入大小的任何改进,但方法2在计算
norm\u vec
时加快了
3x


简短说明为什么
np.einsum
在这里是有益的:
好吧,
einsum
对于元素级乘法和求和运算非常有用。我们正在利用这个问题的本质
np.linalg.norm
给出了输入平方版本上沿第二轴的总和。因此,
einsum
对应项将只向其提供两倍于两个输入的相同输入,从而负责平方运算,然后失去第二个轴,其总和减少值用字符串表示。它一次完成这两个操作,这可能就是为什么它在这里速度如此之快。

您在哪里使用迭代器
i
?这控制旋转。Ghkl总是在循环的最后一行更新(旋转一步)。我的意思是,我没有看到迭代器
I
用于该循环内的计算:
用于范围内的I(N\u rot):
因此,我们可以将这些计算推到循环之外。循环内部不使用我,你可以用uu替换它,但我认为你不能从循环中提取任何东西。Ghkl在循环的每个迭代中都是更新的,norm_vec和其他一切都依赖于GhklAh,我现在看到了!当我们更新Ghkl时,迭代之间存在依赖关系,这在循环中被重复使用。非常感谢!“你能简单地向我解释一下为什么第二种方法更快吗?”詹尼克在最后补充了一条。再次感谢你的回答。将来我将更多地使用einsum。我也考虑过,np.linalg.norm还需要在某个点计算平方根,这在您的解决方案中是不必要的。所以这可能会增加速度increase@Jannick啊,是的!还忘了提到
sqrt
部分,这是我们从
einsum
中得到的另一个重大改进。
from scipy.spatial.distance import cdist

norm_vec = cdist(kout,(kin-Ghkl)[None]) # Rest of code stays the same
sums = (Ghkl-kin)+kout
norm_vec_sq = np.einsum('ij,ij->i',sums,sums)
if (norm_vec_sq < (0.01 * kin_norm)**2 ).any():
    minarg = np.argmin(norm_vec_sq) # Rest of code stays the same
In [91]: %timeit np.linalg.norm(Ghkl- kin + kout, axis=1)
10000 loops, best of 3: 31.1 µs per loop

In [92]: sums = (Ghkl-kin)+kout
    ...: norm_vec_sq = np.einsum('ij,ij->i',sums,sums)
    ...: 

In [93]: %timeit (Ghkl-kin)+kout                 # Approach2 - step1
100000 loops, best of 3: 7.09 µs per loop

In [94]: %timeit np.einsum('ij,ij->i',sums,sums) # Approach2 - step2
100000 loops, best of 3: 3.82 µs per loop

In [95]: %timeit cdist(kout,(kin-Ghkl)[None])    # Approach1
10000 loops, best of 3: 44.1 µs per loop