По-бързо преобразуване на numpy декартови в сферични координати?

Имам масив от 3 милиона точки от данни от акселерометър с 3 оси (XYZ) и искам да добавя 3 колони към масива, съдържащ еквивалентните сферични координати (r, тета, фи). Следният код работи, но изглежда твърде бавен. Как мога да се справя по-добре?

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)))

person BobC    schedule 07.11.2010    source източник


Отговори (5)


Това е подобно на отговора на 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

Обърнете внимание, че както беше предложено в коментарите, промених дефиницията на ъгъла на повдигане от оригиналната ви функция. На моята машина, тествайки с pts = np.random.rand(3000000, 3), времето премина от 76 секунди на 3,3 секунди. Нямам Cython, така че не успях да сравня времето с това решение.

person mtrw    schedule 07.11.2010
comment
Страхотна работа, моето решение Cython е само малко по-бързо (1,23 секунди срещу 1,54 секунди на моята машина). По някаква причина не видях векторизираната функция arctan2, когато потърсих да го направя направо с numpy. +1 - person Justin Peel; 07.11.2010
comment
Anon предложи ptsnew[:,4] = np.arctan2(np.sqrt(xy),xyz[:,2]) - person Sam Saffron; 28.01.2011
comment
Мисля, че тази реализация може да е малко по-бавна от тази на cpython, защото използвате zeros() в първия ред, което изисква този огромен блок (дясната половина на изхода) да бъде ненужно обходен два пъти, веднъж, за да го запълните с нули и веднъж за да го попълните с реални данни. Вместо това трябва да разпределите целия масив Nx6 с np.empty() и да запълните двете му половини чрез нарязване - person RBF06; 22.01.2019

Ето бърз код на 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

Намали времето от 62,4 секунди на 1,22 секунди, използвайки 3 000 000 точки за мен. Това не е твърде изтъркано. Сигурен съм, че има някои други подобрения, които могат да бъдат направени.

person Justin Peel    schedule 07.11.2010
comment
Оригиналният ми код (във въпроса) беше грешен? Или говориш за другия отговор(и)? - person BobC; 11.05.2017

! Все още има грешка в целия код по-горе.. и това е топ резултат от Google.. TLDR: Тествах това с VPython, използването на atan2 за theta (elev) е грешно, използвайте acos! Правилно е за фи (азим). Препоръчвам функцията sympy1.0 acos (тя дори не се оплаква от acos(z/r) с r = 0).

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

Ако преобразуваме това във физическата система (r, тета, фи) = (r, надморска височина, азимут), имаме:

r = sqrt(x*x + y*y + z*z)
phi = atan2(y,x)
theta = acos(z,r)

Неоптимизиран, но правилен код за физична система с дясна ръка:

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]

можете да го тествате сами с функция като:

test = asCartesian(asSpherical([-2.13091326,-0.0058279,0.83697319]))

някои други тестови данни за някои квадранти:

[[ 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]]

Използвах VPython допълнително за лесно визуализиране на вектори:

test   = v.arrow(pos = (0,0,0), axis = vis_ori_ALA , shaftwidth=0.05, color=v.color.red)
person Vincent    schedule 10.05.2017
comment
Не е грешка. И двете формули са правилни. Обърнете внимание на аргументите на функциите arctan и arccos и в двата случая. - person Adomas Baliuka; 19.12.2019
comment
Е, не съм сигурен дали библиотеката се е променила, но всичките ви имена на методи са неправилни, но все пак гласувах за вашия отговор, тъй като освен грешките, той е доста добър. - person Ismael Harun; 06.03.2021

За да завършите предишните отговори, ето имплементация на 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

За големи размери на масиви това позволява коефициент 2 ускоряване в сравнение с чиста реализация на Numpy и би било сравнимо със скоростите на C или Cython. Настоящото решение numpy (когато се използва с аргумента ceval=eval) също е с 25% по-бързо от функцията appendSpherical_np в отговора @mtrw за големи размери на масиви,

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

въпреки че за по-малки размери, 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
person rth    schedule 12.05.2015
comment
Не знаех за numexpr. Дългосрочната ми надежда е в крайна сметка да премина към pypy, когато numpypy може да направи всичко, от което се нуждая, така че чистото решение на Python е за предпочитане. Въпреки че това е 2,7 пъти по-бързо от appendSpherical_np(), самият appendSpherical_np() осигури 50-кратното подобрение, което търсех, без да има нужда от друг пакет. Но все пак се справихте с предизвикателството, така че +1 за вас! - person BobC; 14.05.2015

Octave има някои вградени функции за координатни трансформации, които могат да бъдат достъпни с пакета oct2py за преобразуване на numpy масиви в декартови координати в сферични или полярни координати (и обратно):

from oct2py import octave
xyz = np.random.rand(3000000,3)
%timeit thetaphir = octave.cart2sph(xyz)

724 ms ± 206 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
person N.A. Borggren    schedule 06.08.2020