如何有效地结合 class 设计和矩阵数学?
How to combine class design and matrix math efficiently?
一段时间以来,我一直为物理系统建模的两种设计理念的冲突所困扰,我想知道社区为此提出了什么样的解决方案。
对于复杂的(呃)模拟,我喜欢为对象创建 类 的抽象,以及如何将 类 的对象实例与我想要研究的真实对象进行识别,以及如何对象的某些属性代表现实生活中对象的物理特征。
我们以弹道粒子系统为例:
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]
现在,让我们假设我需要对我的所有粒子做一些特定的事情,比如添加一个横向风矢量,只是导致这个特定操作的 x,y 偏移,因为我只有一堆(列表)在我所有的粒子中,我需要遍历我的所有粒子才能做到这一点,这需要花费这么长的时间:
%timeit _ = [p.apply_lateral_wind(0.5, 1.2) for p in particles]
1 loop, best of 3: 551 ms per loop
现在,显然更有效的相反明显范例是保持 numpy
水平,直接对数组进行数学运算,速度提高了 10 倍以上:
%timeit start_values[...,:2] += np.array([0.5,1.2])
10 loops, best of 3: 20.3 ms per loop
我现在的问题是,是否有任何设计模式可以有效地结合这两种方法,从而不会损失那么多效率?我个人发现根据对象方法和属性来思考真的更容易,在我的脑海中更清晰,对我来说也是面向对象编程成功的根本原因(或者它在(物理)建模中的使用) .但缺点也很明显。如果这些方法之间有某种优雅的来回可能,我会很高兴吗?
您可以定义一个 class 来处理多个粒子:
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
Cython extension types 如果你真的想使用一个对象而不是裸 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 数组慢一个数量级,但我想这就是可读性的代价。
一段时间以来,我一直为物理系统建模的两种设计理念的冲突所困扰,我想知道社区为此提出了什么样的解决方案。
对于复杂的(呃)模拟,我喜欢为对象创建 类 的抽象,以及如何将 类 的对象实例与我想要研究的真实对象进行识别,以及如何对象的某些属性代表现实生活中对象的物理特征。 我们以弹道粒子系统为例:
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]
现在,让我们假设我需要对我的所有粒子做一些特定的事情,比如添加一个横向风矢量,只是导致这个特定操作的 x,y 偏移,因为我只有一堆(列表)在我所有的粒子中,我需要遍历我的所有粒子才能做到这一点,这需要花费这么长的时间:
%timeit _ = [p.apply_lateral_wind(0.5, 1.2) for p in particles]
1 loop, best of 3: 551 ms per loop
现在,显然更有效的相反明显范例是保持 numpy
水平,直接对数组进行数学运算,速度提高了 10 倍以上:
%timeit start_values[...,:2] += np.array([0.5,1.2])
10 loops, best of 3: 20.3 ms per loop
我现在的问题是,是否有任何设计模式可以有效地结合这两种方法,从而不会损失那么多效率?我个人发现根据对象方法和属性来思考真的更容易,在我的脑海中更清晰,对我来说也是面向对象编程成功的根本原因(或者它在(物理)建模中的使用) .但缺点也很明显。如果这些方法之间有某种优雅的来回可能,我会很高兴吗?
您可以定义一个 class 来处理多个粒子:
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
Cython extension types 如果你真的想使用一个对象而不是裸 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 数组慢一个数量级,但我想这就是可读性的代价。