Python numpy中的快速标量三重积

Python numpy中的快速标量三重积,python,numpy,Python,Numpy,我有很多向量三元组,我想计算它们的值。我能行 import numpy n = 871 a = numpy.random.rand(n, 3) b = numpy.random.rand(n, 3) c = numpy.random.rand(n, 3) # <a, b x c> omega = numpy.einsum('ij, ij->i', a, numpy.cross(b, c)) 导入numpy n=871 a=numpy.random.rand(n,3) b=

我有很多向量三元组,我想计算它们的值。我能行

import numpy

n = 871
a = numpy.random.rand(n, 3)
b = numpy.random.rand(n, 3)
c = numpy.random.rand(n, 3)

# <a, b x c>
omega = numpy.einsum('ij, ij->i', a, numpy.cross(b, c))
导入numpy
n=871
a=numpy.random.rand(n,3)
b=numpy.random.rand(n,3)
c=numpy.random.rand(n,3)
# 
ω=numpy.einsum('ij,ij->i',a,numpy.cross(b,c))
但是
numpy.cross
相当慢。这个问题的对称性(它的Levi-Civita表达式是eps_{ijk}a_i b_j c_k)表明可能有更好(更快)的方法来计算它,但我似乎无法理解


有什么提示吗?

这只是决定因素

omega=det(dstack([a,b,c]))
但是速度慢了

另一个等价解是ω=点(a,交叉点(b,c))。和(1)

但我认为你必须为每个det计算大约9(交叉)+3(点)+2(求和)=14次运算,所以它似乎接近最优。在numpy,你最多只能赢得两个因素

编辑:

如果速度很关键,你必须以低水平前进
numba
是一种简单的方法,这里的系数为15倍:

from numba import njit

@njit
def multidet(a,b,c):
    n=a.shape[0]
    d=np.empty(n)
    for i in range(n):
        u,v,w=a[i],b[i],c[i]
        d[i]=\
        u[0]*(v[1]*w[2]-v[2]*w[1])+\
        u[1]*(v[2]*w[0]-v[0]*w[2])+\
        u[2]*(v[0]*w[1]-v[1]*w[0])  # 14 operations / det
    return d
一些测试:

In [155]: %timeit multidet(a,b,c)
100000 loops, best of 3: 7.79 µs per loop

In [156]: %timeit numpy.einsum('ij, ij->i', a, numpy.cross(b, c))
10000 loops, best of 3: 114 µs per loop


In [159]: allclose(multidet(a,b,c),omega)
Out[159]: True

下面是一种使用
切片
和求和的方法-

def slicing_summing(a,b,c):
    c0 = b[:,1]*c[:,2] - b[:,2]*c[:,1]
    c1 = b[:,2]*c[:,0] - b[:,0]*c[:,2]
    c2 = b[:,0]*c[:,1] - b[:,1]*c[:,0]
    return a[:,0]*c0 + a[:,1]*c1 + a[:,2]*c2
我们可以将前三个计算
c0、c1、c2的步骤及其叠加版本替换为一行,如下所示-

b[:,[1,2,0]]*c[:,[2,0,1]] - b[:,[2,0,1]]*c[:,[1,2,0]]
这将创建另一个
(n,3)
数组,该数组必须与
a
一起使用,以减少总和,从而形成
(n,)
形状的数组。利用所提出的
切片(summing)
方法,我们直接得到
(n,)
形状的数组,将这三个切片相加,从而避免了中间的
(n,3)
数组

样本运行-

In [86]: # Setup inputs    
    ...: n = 871
    ...: a = np.random.rand(n, 3)
    ...: b = np.random.rand(n, 3)
    ...: c = np.random.rand(n, 3)
    ...: 

In [87]: # Original approach
    ...: omega = np.einsum('ij, ij->i', a, np.cross(b, c))

In [88]: np.allclose(omega, slicing_summing(a,b,c))
Out[88]: True
运行时测试-

In [90]: %timeit np.einsum('ij, ij->i', a, np.cross(b, c))
10000 loops, best of 3: 84.6 µs per loop

In [91]: %timeit slicing_summing(a,b,c)
1000 loops, best of 3: 63 µs per loop

我对答案中提到的方法做了比较。 结果是:

@迪瓦卡一点一点地击败了埃因苏姆十字军

为了完整性起见,请允许我注意,还有另一种方法完全依赖于点积和sqrt,请参阅。该方法比einsum交叉和切片求和略慢


这个情节是用

导入numpy
导入性能图
def einsum_交叉点(数据):
a、 b,c=数据
返回numpy.einsum(“ij,ij->i”,a,numpy.cross(b,c))
def det(数据):
a、 b,c=数据
返回numpy.linalg.det(numpy.dstack([a,b,c]))
def切片和(数据):
a、 b,c=数据
c0=b[:,1]*c[:,2]-b[:,2]*c[:,1]
c1=b[:,2]*c[:,0]-b[:,0]*c[:,2]
c2=b[:,0]*c[:,1]-b[:,1]*c[:,0]
返回a[:,0]*c0+a[:,1]*c1+a[:,2]*c2
perfplot.show(
设置=λn:(
numpy.random.rand(n,3),
numpy.random.rand(n,3),
numpy.random.rand(n,3),
),
n_范围=[2**k表示范围(1,20)中的k],
内核=[einsum\u交叉、det、切片\u和],
logx=True,
逻辑=正确,
)

来自
numpy.linalg.det
linalgeror:数组的最后两个维度必须是正方形
。请注意,我需要一次计算许多标量三重积。我更正了det版本,但它没有优化。我添加了一个其他的解决方案,希望能有所帮助!当其他一切都失败时!:)过去的问题表明,对于它所做的事情,当前的
交叉
是有效的,仅交叉积就可以了,这是一个点积,它赋予了它一种特殊的对称性。我觉得奇怪的是,不应该对它进行进一步优化。如果A、B和C是特定的3向量,那么你要寻找的爱因斯坦符号是
\epsilon{ijk}A^i B^j C^k
,而epsilon是Levi Civita符号。它不认为这是在
numpy.einsum
中实现的东西。正如@B.M.所说,它完全等同于
np.dstack(A,B,C)
。Levi Civita之前在
einsum
中的用法。新的
optimize
可能会提高这种性能。