Python中的高效Vector / Point类

时间:2013-10-18 20:13:58

标签: python performance python-3.x vector

实现高效的Vector / Point类的最佳方法是什么(甚至更好:是否已有),可以在Python 2.7+和3.x中使用?

我找到了the blender-mathutils,但它们似乎只支持Python 3.x.然后是使用this Vector classnumpy,但它只是一个3D矢量。使用具有静态属性(x和y)的向量kivy's vector classsourcecode)的列表似乎也很奇怪。 (所有这些列表方法都有。)

目前我正在使用扩展了namedtuple的类(如下所示),但这样做的缺点是无法更改坐标。我认为当数千个对象移动并且每次都创建一个新的(矢量)元组时,这可能会成为一个性能问题。 (右?)

class Vector2D(namedtuple('Vector2D', ('x', 'y'))):
    __slots__ = ()

    def __abs__(self):
        return type(self)(abs(self.x), abs(self.y))

    def __int__(self):
        return type(self)(int(self.x), int(self.y))

    def __add__(self, other):
        return type(self)(self.x + other.x, self.y + other.y)

    def __sub__(self, other):
        return type(self)(self.x - other.x, self.y - other.y)

    def __mul__(self, other):
        return type(self)(self.x * other, self.y * other)

    def __div__(self, other):
        return type(self)(self.x / other, self.y / other)

    def dot_product(self, other):
        return self.x * other.x + self.y * other.y

    def distance_to(self, other):
        """ uses the Euclidean norm to calculate the distance """
        return hypot((self.x - other.x), (self.y - other.y))

编辑:我做了一些测试,似乎使用numpy.arraynumpy.ndarray作为向量太慢了。 (例如,获取一个项目需要几乎两倍的时间,更不用说创建一个数组了。我认为它对于对大量项目进行计算更加优化。)

所以,我正在寻找一个可以用于游戏的具有固定数量字段(在我的情况下只有xy)的轻量级矢量类。 (如果已经有一个经过充分测试的车轮,我不想重新发明轮子。)

3 个答案:

答案 0 :(得分:15)

是的,有一个矢量类:它位于事实上的标准NumPy模块中。你可以像这样创建矢量:

>>> v = numpy.array([1, 10, 123])
>>> 2*v
array([  2,  20, 246])
>>> u = numpy.array([1, 1, 1])
>>> v-u
array([  0,   9, 122])

NumPy非常丰富,可让您访问快速数组操作:点积(numpy.dot()),范数(numpy.linalg.norm())等。

答案 1 :(得分:3)

线性代数中numpy中的 vector 类可能是numpy.matrix,它是numpy.ndarray的子类。它不是更清晰本身,但它使代码更清晰,因为假设代数运算而不是元素。

In [77]: a = np.array([1,2])

In [78]: b = np.array([3,3])

In [79]: a*b
Out[79]: array([3, 6])

In [80]: np.dot(a,b)
Out[80]: 9

In [81]: np.outer(a,b)
Out[81]: 
array([[3, 3],
       [6, 6]])

In [82]: a = np.matrix(a).T

In [83]: b = np.matrix(b)

In [84]: b*a
Out[84]: matrix([[9]])

In [85]: a*b
Out[85]: 
matrix([[3, 3],
        [6, 6]])

如果您想创建自己的,请将其基于其中一个,例如:

class v2d(np.ndarray):
    def __abs__(self):
        return np.linalg.norm(self)
    def dist(self,other):
        return np.linalg.norm(self-other)
    def dot(self, other):
        return np.dot(self, other)
    # and so on

在最简单的情况下,您可以通过查看ndarray作为新班级来制作:

In [63]: a = np.array([1,2]).view(v2d)

In [64]: b = np.array([3,3]).view(v2d)

In [65]: a
Out[65]: v2d([1, 2])

In [66]: abs(b)
Out[66]: 4.2426406871192848

In [67]: a - b
Out[67]: v2d([-2, -1])

In [68]: a*b
Out[68]: v2d([3, 6])

In [69]: a*3
Out[69]: v2d([3, 6]) 

In [70]: a.dist(b)
Out[70]: 2.2360679774997898

In [71]: b.dist(a)
Out[71]: 2.2360679774997898

In [72]: a.dot(b)
Out[72]: 9

以下是有关subclassing the ndarray的更多信息。

答案 2 :(得分:0)

我也需要一个快速的解决方案,所以我只是将numpy的数组包装到我自己的数组中。您会注意到一些可以根据自己的需求进行更改的设计决策(如默认设置)。 如果您想使用它:https://gist.github.com/eigencoder/c029d7557e1f0828aec5

import numpy as np


class Point(np.ndarray):
    """
    n-dimensional point used for locations.
    inherits +, -, * (as dot-product)
    > p1 = Point([1, 2])
    > p2 = Point([4, 5])
    > p1 + p2
    Point([5, 7])
    See ``test()`` for more usage.
    """
    def __new__(cls, input_array=(0, 0)):
        """
        :param cls:
        :param input_array: Defaults to 2d origin
        """
        obj = np.asarray(input_array).view(cls)
        return obj

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

    @property
    def y(self):
        return self[1]

    @property
    def z(self):
        """
        :return: 3rd dimension element. 0 if not defined
        """
        try:
            return self[2]
        except IndexError:
            return 0

    def __eq__(self, other):
        return np.array_equal(self, other)

    def __ne__(self, other):
        return not np.array_equal(self, other)

    def __iter__(self):
        for x in np.nditer(self):
            yield x.item()


    def dist(self, other):
        """
        Both points must have the same dimensions
        :return: Euclidean distance
        """
        return np.linalg.norm(self - other)


def test():
    v1 = Point([1, 2, 3])
    v2 = Point([4, 5, 7])
    v3 = Point([4, ])
    sum12 = Point([5, 7, 10])
    dot12 = Point([4, 10, 21])

    # Access
    assert v2.x == 4
    assert v2.y == 5
    assert v2.z == 7
    assert v3.z == 0
    assert Point().x == 0
    assert v2[0] == 4
    assert v1[-1] == 3  # Not needed but inherited
    assert [x for x in v2] == [4, 5, 7], "Iteration should return all elements"

    # Operations
    assert v1 + v2 == sum12
    assert v1 * v2 == dot12
    assert v1.dist(v2) ** 2 == 34
    assert v1 != v2
    assert v2.size == 3, "v2 should be a 3d point"

    print "pass"


if __name__ == "__main__":
    test()