在Python中实现3D向量:numpy vs x,y,z字段

时间:2017-04-21 11:47:17

标签: python arrays class numpy vector

我正在Python中实现3D Vector类。 我的向量有坐标x,y和z(所有浮点数),我需要决定如何存储这些信息。我在这里至少可以看到三个选项:

1)制作三个独立的浮点字段:self.x,self.y,self.z

class Vector:

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

2)制作一个列表,比如说self.data,有三个元素。如果对象可以是常量,我也可以使用元组。

class Vector:

  def __init__(self, x, y, z):
    self.data = [x,y,z]

3)制作一个numpy数组,比如说self.data,有三个元素。

import numpy as np    

class Vector:

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

对于选项(2)和(3),我可以实现属性和设置器来访问单个坐标

@property
def x(self):
  return self.data[0]

4)为什么没有冗余?我可以同时拥有一个列表(或元组或numpy数组)和单独的字段x,y和z。

该课程旨在用于执行常规操作,例如向量添加,内积,叉积,旋转等。需要考虑这些操作的性能。

是否有我更喜欢的解决方案,为什么?

3 个答案:

答案 0 :(得分:8)

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

支持线性代数

您提到要支持线性代数,例如向量加法(逐元素加法),交叉积和内积。这些可用于numpy.ndarray,因此您可以选择不同的方法来支持它们:

  1. 只需使用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"。所以你需要自己实现它。

  2. 您可以创建一个类,独立于_how您存储属性并提供__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-array的情况下为numpy函数提供接口。如果您的班级中存储了numpy-array,__array__方法可以简单地返回它,因此这可能是将xyz存储为{的参数内部{1}}(因为那基本上是"免费")。

  3. 您可以继承numpy.ndarray。我不会在这里详细介绍,因为这是一个高级主题,可以很容易地证明整个答案本身。如果您真的考虑到这一点,那么您应该查看"Subclassing ndarray"的官方文档。我不推荐它,我参与了几个子类np.ndarray的类,还有几个"粗糙的egdes"走那条路。

  4. 您可以自己实施所需的操作。这是重新发明的轮子,但它的教育和乐趣 - 如果只有少数几个。我不推荐这个用于严肃的生产,因为这里还有几个粗糙的边缘"已经在numpy函数中得到了解决。例如溢出或下溢问题,功能的正确性,......

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

    np.ndarray

    注意:您可以实现这些可编码的方法并实现我之前提到的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 方法。这样你就可以支持任何期望__array__的函数,也可以使用你自己开发的方法。这些方法并不是唯一的,但您会得到不同的结果,上述方法会返回标量或numpy.ndarray,但如果您通过Vector,则会得到__array__。< / p>

  5. 使用包含3D矢量的库。从某种意义上说,这是其他方面最简单的方法,它可能非常复杂。从好的方面来说,现有的类可能是开箱即用的,它可能在性能方面进行了优化。另一方面,您需要找到一个支持您的用例的实现,您需要阅读文档(或通过其他方式弄清楚它是如何工作的),并且您可能会遇到对您的项目来说非常糟糕的错误或限制。啊,你得到一个额外的依赖,你需要检查许可证是否与您的项目兼容。此外,如果您复制实施(检查许可证是否允许!),您需要维护(即使它只是同步和更换)外国代码。

  6. 性能

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

    让我先谈谈一些一般性的问题:

    • 避免numpy.ndarray&lt; - &gt; numpy.ndarray / list次操作。这些都很贵!如果大部分操作都使用float,则您不希望将值存储在列表中或作为单独的属性存储。同样,如果您想访问numpy.ndarray的各个值或迭代这些值或对它们执行Vector的操作,则将它们存储为列表或单独的属性。

    • 使用list对三个值进行操作相对低效。 numpy非常适合大数组,因为它可以更有效地存储值(空间)并且比纯Python操作更好地扩展。然而,这些优点有一些对小阵列很重要的开销(比如numpy.ndarray,这是一个有根据的猜测,而不是一个固定的数字!)。一个python解决方案(我使用上面已经介绍过的解决方案)可以比这种小型数组的numpy解决方案快得多:

      length << 100

      然而就像我说的那样,这些时间是微秒级,所以这就是微观优化。但是,如果您的重点是您的班级的最佳表现,那么使用纯Python和自我实现的功能可以更快。

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

    • 我使用相同的方法用不同的方法做了一些基准测试,但这有点作弊。一般来说,你不应该进行一次函数调用,你应该测量一个程序的执行时间。在程序中,一个被称为数百万次的函数的微小速度差异可以比一个只被称为少数的方法中的大速度差异产生更大的整体差异时代......或不!我只能为功能提供时间安排,因为您还没有共享您的程序或用例,因此您需要找出哪种方法最适合您(正确性和性能)。

    结论

    还有其他一些因素需要考虑哪种方法最好,但这些因素更多的是&#34; meta&#34; -reasons,与您的计划没有直接关系。

    • 重新发明轮子(自己实现这些功能)是一个学习的机会。你需要确保它正常工作,你可以计时,如果它太慢,你可以尝试不同的方法来优化它。你开始考虑算法复杂性,常数因素,正确性......而不是考虑&#34;哪个函数将解决我的问题&#34;或者&#34;如何让numpy-function正确解决我的问题&#34;。

    • 将NumPy用于长度为3的阵列可能就像在苍蝇中用大炮射击一样。但这是一个很好的机会,可以更熟悉numpy功能,并且在未来你会更多地了解NumPy如何工作(矢量化,索引,广播......)即使NumPy不是一个好的适合这个问题和答案。

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

答案 1 :(得分:1)

考虑到使用课程Vector,我更喜欢选项-3。由于它产生numpy数组,因此使用numpy,矢量操作相对简单,直观且快速。

In [81]: v1 = Vector(1.0, 2.0, 3.0)

In [82]: v2 = Vector(0.0, 1.0, 2.0)

In [83]: v1.data + v2.data
Out[83]: array([1.0, 3.0, 5.0])

In [85]: np.inner(v1.data, v2.data)
Out[85]: 8.0

这些操作已经在numpy中针对性能进行了很好的优化。

答案 2 :(得分:1)

如果您的目标是简单的矢量类型行为,那么绝对坚持使用纯粹的numpy解决方案。这有很多原因:

  • numpy已经开箱即用了所有基本的解决方案 您描述的行为(跨产品等)
  • 对于具有可观大小的阵列(即,它的位置),它将更快 事项)
  • 矢量化/数组语法往往更紧凑 一旦你习惯了它/有经验,就表现出色,
  • 最重要的是;整个numpy / scipy生态系统都是建立在周围的 ndarray提供的接口;所有图书馆都说得很普遍 ndarray的语言;与您的自定义接口 矢量类型正在进入一个痛苦的世界。