如何有效地结合班级设计和矩阵数学?

时间:2016-02-14 23:35:34

标签: python oop numpy data-modeling modeling

有一段时间我现在受到两种物理系统建模设计理念冲突的困扰,我想知道社区为此提出了什么样的解决方案。

对于复杂(呃)模拟,我喜欢为对象创建类的抽象,以及如何使用我想要研究的真实对象来识别类的对象实例,以及对象的某些属性如何表示对象的物理特征。现实生活中的物体 让我们以弹道粒子系统为例:

class Particle(object):
    def __init__(self, x=0, y=0, z=0):
        self.x = x
        self.y = y
        self.z = z
    def __repr__(self):
        return "x={}\ny={}\nz={}".format(self.x, self.y, self.z)
    def apply_lateral_wind(self, dx, dy):
        self.x += dx
        self.y += dy

如果我用一百万个值初始化它,我可能会这样做:

start_values = np.random.random((int(1e6),3))

particles = [Particle(*i) for i in start_values]

现在,让我们假设我需要对我的所有粒子做一个特定的事情,比如添加一个横向风向量,只为这个特定的操作导致ax,y shift,因为我只有一堆(列表)所有我的粒子,我需要遍历我的所有粒子才能做到这一点,这需要花费这么多时间:

%timeit _ = [p.apply_lateral_wind(0.5, 1.2) for p in particles]
1 loop, best of 3: 551 ms per loop

现在,对此明显更有效的反对明显范式是保持numpy水平,直接对数组进行数学运算,速度超过10倍:< / p>

%timeit start_values[...,:2] += np.array([0.5,1.2])
10 loops, best of 3: 20.3 ms per loop

我现在的问题是,如果有任何设计模式可以有效地结合这两种方法,那么人们不会失去那么高的效率?我觉得从对象方法和属性的角度来说,个人思考起来真的更容易,在我的脑海中更清晰,对我而言,实际上也是面向对象编程成功的根本原因(或者它用于(物理)建模) 。但缺点是显而易见的。如果这些方法之间有某种优雅的反复可能会爱吗?

2 个答案:

答案 0 :(得分:6)

您可以定义一个处理多个粒子的类:

class Particles(object):
    def __init__(self, coords):
        self.coords = coords

    def __repr__(self):
        return "Particles(coords={})".format(self.coords)

    def apply_lateral_wind(self, dx, dy):
        self.coords[:, 0] += dx
        self.coords[:, 1] += dy

start_values = np.random.random((int(1e6), 3))        
particles = Particles(start_values)

我的系统上的时间显示这实际上比你的numpy版本更快,可能是因为它没有构建一个额外的数组而且不必担心广播:

%timeit particles.apply_lateral_wind(0.5, 1.2)
100 loops, best of 3: 3.17 ms per loop

而使用你的numpy例子给出了

%timeit start_values[..., :2] += np.array([0.5, 1.2])
10 loops, best of 3: 21.1 ms per loop

答案 1 :(得分:2)

如果你真的想要使用一个对象而不是一个裸NumPy数组,那么

Cython extension types可能是一个有趣的选项。 (尽管此链接文档页面中也描述了一些限制。)

我编辑了你的示例代码以进行Cythonize,然后提出了这个:

cdef class Particle(object):
    cdef double x, y, z

    def __init__(self, double x=0, double y=0, double z=0):
        self.x = x
        self.y = y
        self.z = z
    def __repr__(self):
        return "x={}\ny={}\nz={}".format(self.x, self.y, self.z)
    cpdef apply_lateral_wind(self, double dx, double dy):
        self.x += dx
        self.y += dy

使用这个基本的Cython版本,我在循环超过100万个粒子时获得了以下时间:

>>> %timeit _ = [p.apply_lateral_wind(0.5, 1.2) for p in particles]
10 loops, best of 3: 102 ms per loop

这比普通Python版本快5倍,在同一台机器上耗时532毫秒。话虽如此,它显然仍然比使用裸NumPy阵列慢一个数量级,但我想这是可读性的代价。