更快的numpy笛卡尔到球坐标转换?

Bob*_*obC 29 python numpy coordinate

我有一个来自3轴accellerometer(XYZ)的300万个数据点的数组,我想在包含等效球坐标(r,theta,phi)的数组中添加3列.以下代码有效,但似乎太慢了.我怎么能做得更好?

import numpy as np
import math as m

def cart2sph(x,y,z):
    XsqPlusYsq = x**2 + y**2
    r = m.sqrt(XsqPlusYsq + z**2)               # r
    elev = m.atan2(z,m.sqrt(XsqPlusYsq))     # theta
    az = m.atan2(y,x)                           # phi
    return r, elev, az

def cart2sphA(pts):
    return np.array([cart2sph(x,y,z) for x,y,z in pts])

def appendSpherical(xyz):
    np.hstack((xyz, cart2sphA(xyz)))
Run Code Online (Sandbox Code Playgroud)

mtr*_*trw 31

这类似于Justin Peel的回答,但只使用numpy并利用其内置的矢量化:

import numpy as np

def appendSpherical_np(xyz):
    ptsnew = np.hstack((xyz, np.zeros(xyz.shape)))
    xy = xyz[:,0]**2 + xyz[:,1]**2
    ptsnew[:,3] = np.sqrt(xy + xyz[:,2]**2)
    ptsnew[:,4] = np.arctan2(np.sqrt(xy), xyz[:,2]) # for elevation angle defined from Z-axis down
    #ptsnew[:,4] = np.arctan2(xyz[:,2], np.sqrt(xy)) # for elevation angle defined from XY-plane up
    ptsnew[:,5] = np.arctan2(xyz[:,1], xyz[:,0])
    return ptsnew
Run Code Online (Sandbox Code Playgroud)

请注意,正如评论中所建议的那样,我已经从原始函数更改了仰角的定义.在我的机器上进行测试pts = np.random.rand(3000000, 3),时间从76秒到3.3秒.我没有Cython,所以我无法将时间与该解决方案进行比较.

  • 我认为这个实现可能比 cpython 稍慢,因为您在第一行中使用了 `zeros()`,这需要不必要地遍历巨大的块(输出的右半部分)两次,一次来填充它用零和一次用真实数据填充它。相反,您应该使用“np.empty()”分配整个 Nx6 数组,并使用切片填充它的两半 (5认同)

Jus*_*eel 11

这是我为此编写的快速Cython代码:

cdef extern from "math.h":
    long double sqrt(long double xx)
    long double atan2(long double a, double b)

import numpy as np
cimport numpy as np
cimport cython

ctypedef np.float64_t DTYPE_t

@cython.boundscheck(False)
@cython.wraparound(False)
def appendSpherical(np.ndarray[DTYPE_t,ndim=2] xyz):
    cdef np.ndarray[DTYPE_t,ndim=2] pts = np.empty((xyz.shape[0],6))
    cdef long double XsqPlusYsq
    for i in xrange(xyz.shape[0]):
        pts[i,0] = xyz[i,0]
        pts[i,1] = xyz[i,1]
        pts[i,2] = xyz[i,2]
        XsqPlusYsq = xyz[i,0]**2 + xyz[i,1]**2
        pts[i,3] = sqrt(XsqPlusYsq + xyz[i,2]**2)
        pts[i,4] = atan2(xyz[i,2],sqrt(XsqPlusYsq))
        pts[i,5] = atan2(xyz[i,1],xyz[i,0])
    return pts
Run Code Online (Sandbox Code Playgroud)

对我来说,使用3,000,000点将时间从62.4秒降低到1.22秒.那不是太破旧.我相信还有其他一些改进可以做.


rth*_*rth 6

要完成以前的答案,这里是一个Numexpr实现(可能回退到 Numpy),

import numpy as np
from numpy import arctan2, sqrt
import numexpr as ne

def cart2sph(x,y,z, ceval=ne.evaluate):
    """ x, y, z :  ndarray coordinates
        ceval: backend to use: 
              - eval :  pure Numpy
              - numexpr.evaluate:  Numexpr """
    azimuth = ceval('arctan2(y,x)')
    xy2 = ceval('x**2 + y**2')
    elevation = ceval('arctan2(z, sqrt(xy2))')
    r = eval('sqrt(xy2 + z**2)')
    return azimuth, elevation, r
Run Code Online (Sandbox Code Playgroud)

对于大型阵列,与纯Numpy实现相比,这可以使速度提高2倍,并且可以与C或Cython速度相媲美.目前的numpy解决方案(与ceval=eval参数一起使用时)也比大数组大小appendSpherical_np@mtrw答案中的函数快25%,

In [1]: xyz = np.random.rand(3000000,3)
   ...: x,y,z = xyz.T
In [2]: %timeit -n 1 appendSpherical_np(xyz)
1 loops, best of 3: 397 ms per loop
In [3]: %timeit -n 1 cart2sph(x,y,z, ceval=eval)
1 loops, best of 3: 280 ms per loop
In [4]: %timeit -n 1 cart2sph(x,y,z, ceval=ne.evaluate)
1 loops, best of 3: 145 ms per loop
Run Code Online (Sandbox Code Playgroud)

虽然尺寸较小,但appendSpherical_np实际上更快,

In [5]: xyz = np.random.rand(3000,3)
...: x,y,z = xyz.T
In [6]: %timeit -n 1 appendSpherical_np(xyz)
1 loops, best of 3: 206 µs per loop
In [7]: %timeit -n 1 cart2sph(x,y,z, ceval=eval)
1 loops, best of 3: 261 µs per loop
In [8]: %timeit -n 1 cart2sph(x,y,z, ceval=ne.evaluate)
1 loops, best of 3: 271 µs per loop
Run Code Online (Sandbox Code Playgroud)

  • 我没有意识到numexpr.我的长期希望是当numpypy可以完成所有我需要的工作时最终转向pypy,因此首选"纯Python"解决方案.虽然这比appendSpherical_np()快2.7倍,但appendSpherical_np()本身提供了50倍的改进,我正在寻找而不需要另一个包.但是,你仍然遇到了挑战,所以给你+1! (2认同)

Vin*_*ent 6

!上面的所有代码都有一个错误..这是谷歌的最高结果.. TLDR:我用VPython测试了这个,使用atan2 for theta(elev)是错误的,使用acos!它对于phi(azim)是正确的.我推荐使用sympy1.0 acos功能(它甚至不会抱怨acos(z/r),r = 0).

http://mathworld.wolfram.com/SphericalCoordinates.html

如果我们将其转换为物理系统(r,theta,phi)=(r,elev,azimuth),我们有:

r = sqrt(x*x + y*y + z*z)
phi = atan2(y,x)
theta = acos(z,r)
Run Code Online (Sandbox Code Playgroud)

右手物理系统的非优化但正确的代码:

from sympy import *
def asCartesian(rthetaphi):
    #takes list rthetaphi (single coord)
    r       = rthetaphi[0]
    theta   = rthetaphi[1]* pi/180 # to radian
    phi     = rthetaphi[2]* pi/180
    x = r * sin( theta ) * cos( phi )
    y = r * sin( theta ) * sin( phi )
    z = r * cos( theta )
    return [x,y,z]

def asSpherical(xyz):
    #takes list xyz (single coord)
    x       = xyz[0]
    y       = xyz[1]
    z       = xyz[2]
    r       =  sqrt(x*x + y*y + z*z)
    theta   =  acos(z/r)*180/ pi #to degrees
    phi     =  atan2(y,x)*180/ pi
    return [r,theta,phi]
Run Code Online (Sandbox Code Playgroud)

您可以使用以下功能自行测试:

test = asCartesian(asSpherical([-2.13091326,-0.0058279,0.83697319]))
Run Code Online (Sandbox Code Playgroud)

某些象限的一些其他测试数据:

[[ 0.          0.          0.        ]
 [-2.13091326 -0.0058279   0.83697319]
 [ 1.82172775  1.15959835  1.09232283]
 [ 1.47554111 -0.14483833 -1.80804324]
 [-1.13940573 -1.45129967 -1.30132008]
 [ 0.33530045 -1.47780466  1.6384716 ]
 [-0.51094007  1.80408573 -2.12652707]]
Run Code Online (Sandbox Code Playgroud)

我还使用VPython轻松地可视化矢量:

test   = v.arrow(pos = (0,0,0), axis = vis_ori_ALA , shaftwidth=0.05, color=v.color.red)
Run Code Online (Sandbox Code Playgroud)