linspace - numpy python tutorial
Conversión de coordenadas cartesianas a esféricas numpy más rápido? (4)
Tengo una matriz de 3 millones de puntos de datos de un acelerómetro de 3 ejes (XYZ) y deseo agregar 3 columnas a la matriz que contiene las coordenadas esféricas equivalentes (r, theta, phi). El siguiente código funciona, pero parece demasiado lento. ¿Cómo puedo hacerlo mejor?
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)))
Aquí hay un código rápido de Cython que escribí para esto:
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
Redujo el tiempo de 62.4 segundos a 1.22 segundos usando 3.000,000 puntos para mí. Eso no es muy viejo. Estoy seguro de que hay otras mejoras que se pueden hacer.
Para completar las respuestas anteriores, aquí hay una implementación de Numexpr (con una posible alternativa a 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
Para tamaños de matriz grandes, esto permite un factor de 2 aceleración en comparación con la implementación pura de Numpy, y sería comparable a las velocidades C o Cython. La solución numpy presente (cuando se utiliza con el argumento ceval=eval
) también es un 25% más rápida que la función appendSpherical_np
en la respuesta @mtrw para tamaños de matriz grandes,
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
aunque para tamaños más pequeños, appendSpherical_np
es realmente más rápido,
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
! Todavía hay un error en todo el código anterior ... y este es un resultado superior de Google ... TLDR: He probado esto con VPython, usando atan2 para theta (elev) es incorrecto, ¡usa acos! Es correcto para phi (azim). Recomiendo la función sympy1.0 acos (ni siquiera se queja de acos (z / r) con r = 0).
http://mathworld.wolfram.com/SphericalCoordinates.html
Si convertimos eso al sistema de física (r, theta, phi) = (r, elev, azimuth) tenemos:
r = sqrt(x*x + y*y + z*z)
phi = atan2(y,x)
theta = acos(z,r)
Código no optimizado pero correcto para el sistema de física diestro:
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]
puede probarlo usted mismo con una función como:
test = asCartesian(asSpherical([-2.13091326,-0.0058279,0.83697319]))
algunos otros datos de prueba para algunos cuadrantes:
[[ 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]]
Utilicé VPython además para visualizar vectores fácilmente:
test = v.arrow(pos = (0,0,0), axis = vis_ori_ALA , shaftwidth=0.05, color=v.color.red)
Esto es similar a la respuesta de Justin Peel , pero usando solo numpy
y aprovechando su vectorización incorporada:
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
Tenga en cuenta que, como se sugiere en los comentarios, he cambiado la definición de ángulo de elevación de su función original. En mi máquina, probando con pts = np.random.rand(3000000, 3)
, el tiempo pasó de 76 segundos a 3,3 segundos. No tengo Cython, así que no pude comparar el tiempo con esa solución.