简单地使用 numpy.ndarray
,不必担心你自己的类:
import numpy as np
vector1, vector2 = np.array([1, 2, 3]), np.array([3, 2, 1])
np.add(vector1, vector2)
np.cross(vector1, vector2)
np.inner(vector1, vector2)
在numpy
中没有内置的向量旋转定义,但是有多个来源可用,例如"旋转3D向量"。所以你需要自己实现它。
您可以创建一个类,独立于如何存储属性,并提供一个__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)
np.cross(vector1, vector2)
np.inner(vector1, vector2)
这将返回与第一种情况相同的结果,因此您可以提供numpy函数的接口,而不需要numpy数组。如果在类中存储了numpy数组,则__array__
方法可以简单地返回它,因此这可能是将x
、y
和z
作为numpy.ndarray
在内部存储的参数的理由(因为这基本上是“免费”的)。
您可以子类化np.ndarray
。我不会在这里深入讨论,因为这是一个高级主题,可能需要单独一篇答案来解释。如果您真的考虑过这个问题,那么您应该查看“Subclassing ndarray”的官方文档。我不建议这样做,我曾经处理过几个子类np.ndarray
的类,并且在这条路上有几个“粗糙之处”。
您可以自己实现所需的操作。这是重新发明轮子,但如果只有少数操作,这很有教育意义和有趣。我不建议在严肃的生产环境中使用这种方法,因为在这里也有几个“粗糙之处”,这些问题在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-code方法并实现我之前提到的__array__方法。这样,您就可以支持任何期望numpy.ndarray的函数,并拥有自己的自定义方法。这些方法不是互斥的,但会产生不同的结果。上述方法返回标量或向量,但如果您通过__array__进行操作,则会返回numpy.ndarray。
使用包含3D向量的库是一种方法。在某种程度上,这是最简单的方法,在其他方面可能非常复杂。好处是现有类可能可以直接使用,并且在性能方面可能已经优化。另一方面,您需要找到支持您用例的实现,您需要阅读文档(或通过其他手段弄清楚其工作原理),您可能会遇到错误或限制,这可能对项目产生严重影响。此外,您还需要附加依赖项,并检查许可证是否与您的项目兼容。另外,如果您复制了实现(请检查许可证是否允许!),则需要维护(即使只是同步)外部代码。
性能
在这种情况下,性能很棘手,所提到的用例相当简单,每个任务应该是微秒级别的 - 因此您应该能够每秒执行数千到数百万次操作。假设您不引入不必要的瓶颈!但是您可以微调操作。
让我从一些一般性提示开始:
避免numpy.ndarray <-> list / float操作。这些非常昂贵!如果大多数操作使用numpy.ndarray,则不要将值存储为列表或单独的属性。同样,如果您想访问Vector的各个值、遍历这些值或将它们作为列表执行操作,则将它们存储为列表或单独的属性。
对三个值使用numpy相对效率较低。numpy.ndarray非常适用于大数组,因为它可以更有效地存储值(空间),并且比纯Python操作更具可扩展性。然而,这些优点有一些开销,在小数组(例如,长度<<100,这是经过教育的猜测,而不是固定数字!)中很显着。针对此类小型数组的Python解决方案(我使用了我已经介绍过的解决方案)可能比numpy解决方案快得多:class VectorArray:
def __init__(self, x, y, z):
self.data = np.array([x,y,z])
%timeit VectorList(1, 2, 3) + VectorList(3, 2, 1)
%timeit VectorArray(1, 2, 3).data + VectorArray(3, 2, 1).data
v = Vector(1, 2, 3)
a = np.array([1,2,3])
%timeit v.crossproduct(v)
%timeit np.cross(a, a)
%timeit v.scalarproduct(v)
%timeit np.inner(a, a)
然而,正如我所说,这些时间是微秒级别的,因此这确实是微小优化。但是,如果你的重点是优化你的类的性能,那么使用纯Python和自实现函数可能会更快。
只要您尝试执行大量线性代数运算,您就应该利用NumPy的向量化操作。其中大多数与您描述的类不兼容,可能需要完全不同的方法:例如,存储按正确方式与NumPy函数接口的数组矢量(多维数组)的类!但我认为这超出了本回答的范围,并且无法真正回答您的问题,因为您的问题仅涉及存储3个值的类。
我使用相同的方法测试了不同的方法,但这有点欺骗性。一般来说,您不应计时一个函数调用,而是应测量程序的执行时间。在程序中,微小的函数速度差异,该函数被调用数百万次,可以比只调用几次的方法的大速度差异产生更大的差异...或者不会!因为您没有分享您的程序或用例,所以我只能提供功能的时间,因此您需要找出哪种方法对您最好(正确性和性能)。
还有其他几个因素需要考虑,哪种方法最好,但这些因素更多是“元”原因,与您的程序没有直接关系。
- 重新发明轮子(自己实现函数)是一个学习的机会。您需要确保其正确工作,可以计时它,如果速度太慢,则可以尝试不同的优化方式。您开始思考算法复杂度、常数因子、正确性等,而不是考虑“哪个函数将解决我的问题”或“如何使那个NumPy函数正确解决我的问题”。
- 对于长度为3的数组使用NumPy可能就像“用大炮打苍蝇”,但这是一个很好的机会来更加熟悉NumPy函数,并且在未来,即使NumPy对于此问题和答案并不适合,在向量化、索引、广播等方面你也会更加了解NumPy的工作原理。
- 尝试不同的方法,看看进展如何。在回答这个问题时,我学到了很多东西,尝试了不同的方法-比较结果的差异,计时方法调用并评估它们的限制!
np.array([x,y,z])
,那么就没有必要创建一个Vector
类。如果你有数百个向量,你也可以使用np.array([a,x,y,z])
并利用NumPy广播。 - Nils Wernerclass
数组中,并使用property
来访问每个对象的值。 - hpaulj