天天看点

python三维向量运算,在Python中实现3D向量:numpy vs x,y,z字段

这个问题有不同的方面,我可以给你一些提示,告诉你如何解决这些问题。请注意,这些都是建议,你肯定需要看看你最喜欢哪一个。

支持线性代数

您提到要支持线性代数,例如向量加法(元素加法)、叉积和内积。这些可用于numpy.ndarray,因此您可以选择不同的方法来支持它们:只需使用一个numpy.ndarray,而不必为自己的类操心:import numpy as np

vector1, vector2 = np.array([1, 2, 3]), np.array([3, 2, 1])

np.add(vector1, vector2) # vector addition

np.cross(vector1, vector2) # cross product

np.inner(vector1, vector2) # inner product

在numpy中没有定义内建向量旋转,但是有几个可用的源,例如"Rotation of 3D vector"。所以你需要自己实现它。

您可以创建一个类,独立于存储属性的方式,并提供一个__array__方法。这样,您就可以支持(所有)numpy函数,就像您的实例本身是numpy.ndarray一样:class VectorArrayInterface(object):

def __init__(self, x, y, z):

self.x, self.y, self.z = x, y, z

def __array__(self, dtype=None):

if dtype:

return np.array([self.x, self.y, self.z], dtype=dtype)

else:

return np.array([self.x, self.y, self.z])

vector1, vector2 = VectorArrayInterface(1, 2, 3), VectorArrayInterface(3, 2, 1)

np.add(vector1, vector2) # vector addition

np.cross(vector1, vector2) # cross product

np.inner(vector1, vector2) # inner product

这将返回与第一种情况相同的结果,因此您可以为numpy函数提供接口,而无需使用numpy数组。如果您的类中存储了一个numpy数组,那么__array__方法可以简单地返回它,因此这可能是一个参数,用于在内部将x、y和z存储为numpy.ndarray(因为这基本上是“免费的”)。

您可以子类化np.ndarray。我不想在这里详细讨论,因为这是一个高级的话题,可以很容易地证明一个完整的答案是正确的。如果您真的考虑到这一点,那么您应该看看"Subclassing ndarray"的官方文档。我不推荐这样做,我在几个类上做了子类np.ndarray并且在这条路上有几个“粗糙的egde”。

你可以自己实现你需要的操作。这是重新发明轮子,但它的教育和乐趣-如果只有少数。我不建议在严肃的生产中使用这个,因为这里还有几个已经在numpy函数中处理过的“粗糙边”。例如溢出或下溢问题,函数的正确性。。。

可能的实现(不包括旋转)可能如下所示(这次是使用内部存储的列表):class VectorList(object):

def __init__(self, x, y, z):

self.vec = [x, y, z]

def __repr__(self):

return '{self.__class__.__name__}(x={self.vec[0]}, y={self.vec[1]}, z={self.vec[2]})'.format(self=self)

def __add__(self, other):

x1, y1, z1 = self.vec

x2, y2, z2 = other.vec

return VectorList(x1+x2, y1+y2, z1+z2)

def crossproduct(self, other):

x1, y1, z1 = self.vec

x2, y2, z2 = other.vec

return VectorList(y1*z2 - z1*y2,

z1*x2 - x1*z2,

x1*y2 - y1*x1)

def scalarproduct(self, other):

x1, y1, z1 = self.vec

x2, y2, z2 = other.vec

return x1*x2 + y1*y2 + z1*z2

注意:您可以实现这些can编码方法,并实现前面提到的__array__方法。这样,您就可以支持任何期望值为numpy.ndarray的函数,并且还可以拥有自己的方法。这些方法不是独占的,但是您将得到不同的结果,上面的方法返回标量或Vector,但是如果您通过__array__,您将得到numpy.ndarray。

使用包含三维矢量的库。从某种意义上说,这是其他方面最简单的方法,可能非常复杂。另一方面,现有的类可能会开箱即用,而且可能在性能方面得到了优化。另一方面,您需要找到一个支持您的用例的实现,您需要阅读文档(或通过其他方式了解它是如何工作的),并且您可能会遇到错误或限制,这些错误或限制最终会对您的项目造成影响。啊,还有一个附加的依赖项,您需要检查许可证是否与您的项目兼容。另外,如果您复制了实现(请检查许可证是否允许这样做!)你需要维护(即使只是同步)外来代码。

性能

在这种情况下,性能是很棘手的,上面提到的用例非常简单,每个任务的顺序应该是微秒——因此您应该已经能够每秒执行几千到一百万个操作。假设你没有引入不必要的瓶颈!但是,您可以对操作进行微观优化。

让我从一些一般的技巧开始:避免numpy.ndarray<->list/float操作。这些东西很贵!如果大多数操作使用numpy.ndarrays,则不希望存储va列表中的值或作为单独的属性。同样,如果您想访问Vector的各个值,或者对这些值进行迭代,或者对它们执行list操作,那么将它们存储为列表或单独的属性。

使用numpy对三个值进行操作相对来说效率较低。numpy.ndarray对于大型数组来说非常好,因为它可以更有效地存储值(空间)并比纯python操作具有更好的伸缩性。然而,这些优点有一些开销,这对于小数组来说是非常重要的(比如length << 100,这是一个有根据的猜测,而不是一个固定的数字!)。对于这样小的数组,python解决方案(我使用上面已经介绍过的解决方案)可能比numpy解决方案快得多:class VectorArray:

def __init__(self, x, y, z):

self.data = np.array([x,y,z])

# addition: python solution 3 times faster

%timeit VectorList(1, 2, 3) + VectorList(3, 2, 1)

# 100000 loops, best of 3: 9.48 µs per loop

%timeit VectorArray(1, 2, 3).data + VectorArray(3, 2, 1).data

# 10000 loops, best of 3: 35.6 µs per loop

# cross product: python solution 16 times faster

v = Vector(1, 2, 3)

a = np.array([1,2,3]) # using a plain array to avoid the class-overhead

%timeit v.crossproduct(v)

# 100000 loops, best of 3: 5.27 µs per loop

%timeit np.cross(a, a)

# 10000 loops, best of 3: 84.9 µs per loop

# inner product: python solution 4 times faster

%timeit v.scalarproduct(v)

# 1000000 loops, best of 3: 1.3 µs per loop

%timeit np.inner(a, a)

# 100000 loops, best of 3: 5.11 µs per loop

不过,正如我所说,这些计时是微秒级的,所以这实际上是微观优化。但是,如果您的重点是类的最佳性能,那么使用纯python和自实现函数可以更快。

一旦你尝试做很多线性代数运算,你就应该利用numpys向量化运算。其中大多数与您描述的类不兼容,完全不同的方法可能是合适的:例如,一个类以与numpy函数正确接口的方式存储数组向量数组(多维数组)!但我认为这超出了这个答案的范围,也不会真正回答您的问题,因为这个问题仅限于一个只存储3个值的类。

我用同样的方法用不同的方法做了一些基准测试,但是这有点作弊。一般来说,您不应该为一个函数调用计时,您应该测量程序的执行时间。在程序中,一个被称为数百万次的函数中的一个微小的速度差可以比一个只被称为几次的方法中的一个巨大的速度差产生更大的整体差异。。。。或者不!我只能提供函数的计时,因为您没有共享程序或用例,所以您需要找到最适合您的方法(正确性和性能)。

结论

还有其他几个因素可以考虑哪种方法是最好的,但这些都是“元”原因,与您的程序没有直接关系。重新发明轮子(自己实现功能)是一个学习的机会。你需要确保它工作正常,你可以计时,如果它太慢,你可以尝试不同的方法来优化它。你开始考虑算法的复杂性,常数因子,正确性。。。而不是考虑“哪个函数将解决我的问题”或“我如何使那个numpy函数正确地解决我的问题”。

对length-3数组使用NumPy可能就像“用大炮向苍蝇射击”一样,但这是一个很好的机会,可以让您更加熟悉NumPy的功能,将来您将更加了解NumPy的工作原理(矢量化、索引、广播等),即使NumPy不适合这个问题和答案。

尝试不同的方法,看看你能走多远。我在回答这个问题时学到了很多,尝试这些方法很有趣——比较结果是否有差异,计时方法调用并评估它们的局限性!