【问题标题】:numpy array of objectsnumpy 对象数组
【发布时间】:2011-06-20 03:24:50
【问题描述】:

我正在尝试在 Python 中实现晶格模型 (lattice boltzmann) 的模拟。格子的每个站点都有许多属性,并按照一定的规则与相邻站点交互。我认为创建一个具有所有属性的类并制作该类的实例网格可能是聪明的。 (由于我对 Python 没有经验,这可能根本不是一个好主意,所以请随时评论我的方法。)

这是我正在做的一个玩具示例

class site:
    def __init__(self,a,...):
        self.a = a
        .... other properties ...
    def set_a(self, new_a):
        self.a = new_a

现在我想处理此类站点的 2D/3D 点阵(网格),因此我尝试执行以下操作(这里以 2D 3x3 网格为例,但在模拟中我需要 >1000x1000X1000 的顺序)

lattice = np.empty( (3,3), dtype=object)
lattice[:,:] = site(3)

现在,问题是每个格点都指向同一个实例,例如

lattice[0,0].set_a(5)

还将 lattice[0,2].a 的值设置为 5。这种行为是不受欢迎的。为了避免这个问题,我可以遍历每个网格点并逐个元素地分配对象,比如

for i in range(3):
    for j in range(3):
        lattice[i,j] = site(a)

但是有没有更好的方法(不涉及循环)将对象分配给多维数组?

谢谢

【问题讨论】:

  • 如果您要处理 >1000x1000X1000 数组,不要使用对象数组!!与使用“普通”numpy 数组相比,它将使用大量内存。对象数组不是你想要的......
  • 通过模拟我猜你的意思是流体模拟?如果是这样,那么我建议您重新考虑您的方法。也许你的数组元素应该是数字元素,所以你可以利用线性代数的所有力量;-)。粒子传播和碰撞过程必须在全局范围内完成!没有本地对象格能够在任何合理的计算时间内处理它。只是toughts,不知道你的目标是什么;-)。谢谢
  • @eat:我在做流体模拟。我想编写一个通用的站点网格,其中所有本地属性都收集在一个类中(在我的书中碰撞是本地的,而不是传播),但我想你毕竟是对的。至少 bpowah 教会了我如何向量化 init 函数:)
  • 顺便问一下,你见过旗鱼吗? sailfish.us.edu.pl/index.html 这是一个 GPU 加速流体模拟包,使用 numpy 和 pyopencl/pycuda 实现的 Lattice-Boltzman 方法。从我所看到的(这只是演示视频......)来看,它非常漂亮。无论如何,认为您可能会发现它相关。
  • @Joe:谢谢!我去看看。

标签: python numpy simulation


【解决方案1】:

你可以vectorize类的__init__函数:

import numpy as np

class Site:
    def __init__(self, a):
        self.a = a
    def set_a(self, new_a):
        self.a = new_a

vSite = np.vectorize(Site)

init_arry = np.arange(9).reshape((3,3))

lattice = np.empty((3,3), dtype=object)
lattice[:,:] = vSite(init_arry)

这可能看起来更简洁,但与您的循环解决方案相比没有性能优势。列表理解答案会创建一个中间 python 列表,这会导致性能下降。

【讨论】:

  • 如果Site 接受多个参数怎么办?
  • @NeilG 您必须将a 中的所有初始化信息封装为更高维数组或recarray。 vectorize'd 函数仅假定第一个参数是一个向量(当然,除非您使用 excluded 参数来定义不同的向量)。如果您坚持使用Site 多参数,则可以为Site 编写一个包装器,将recarray 自定义dtype 扩展为站点的(*args, **vargs) 并将包装器矢量化。请记住,这一切都只是为了好玩。与 OP 的循环解决方案相比,这里没有性能优势。
  • @Paul:是的,我明白你的意思。我以前从未见过vectorize 应用于构造函数。对于这个问题,这是一个巧妙的解决方案,但它不是一个通用的解决方案,因为它使向Site 的构造函数添加参数变得很麻烦。
【解决方案2】:

对您来说缺少的部分是 Python 将所有内容都视为参考。 (有一些“不可变”对象、字符串、数字和元组,它们被视为更像值。)当你这样做时

lattice[:,:] = site(3)

您是在说“Python:创建一个新对象 site,并告诉 lattice 的每个元素都指向该对象。”要查看是否确实如此,请打印数组以查看对象的内存地址是否都相同:

array([[<__main__.Site object at 0x1029d5610>,
        <__main__.Site object at 0x1029d5610>,
        <__main__.Site object at 0x1029d5610>],
       [<__main__.Site object at 0x1029d5610>,
        <__main__.Site object at 0x1029d5610>,
        <__main__.Site object at 0x1029d5610>],
       [<__main__.Site object at 0x1029d5610>,
        <__main__.Site object at 0x1029d5610>,
        <__main__.Site object at 0x1029d5610>]], dtype=object)

循环方式是一种正确的方法。使用 numpy 数组,这可能是您的最佳选择;对于 Python 列表,您还可以使用列表推导:

lattice = [ [Site(i + j) for i in range(3)] for j in range(3) ]

您可以使用numpy.array 构造的列表推导:

lattice = np.array( [ [Site(i + j) for i in range(3)] for j in range(3) ],
                    dtype=object)

现在当你打印 lattice 时,它是

array([[<__main__.Site object at 0x1029d53d0>,
        <__main__.Site object at 0x1029d50d0>,
        <__main__.Site object at 0x1029d5390>],
       [<__main__.Site object at 0x1029d5750>,
        <__main__.Site object at 0x1029d57d0>,
        <__main__.Site object at 0x1029d5990>],
       [<__main__.Site object at 0x1029d59d0>,
        <__main__.Site object at 0x1029d5a10>,
        <__main__.Site object at 0x1029d5a50>]], dtype=object)

因此您可以看到其中的每个对象都是独一无二的。

您还应该注意,“setter”和“getter”方法(例如,set_a)不是 Pythonic。最好直接设置和获取属性,然后如果您确实需要阻止对属性的写访问,请使用 @property 装饰器。

另请注意,使用 CamelCase 编写 Python 类是标准,而不是小写。

【讨论】:

  • 非常感谢您的意见。但我不知道我是否理解正确;对于类实例 A = site(4,5),用 'A.a = 6' 更新变量是否比 'A.set_a(6)' 更好(更 Pythonic)?有没有关于 Pythonic 编码良好指南的参考资料?
  • 小心这是Site实现__len__
【解决方案3】:

我不知道更好,但作为一组显式循环的替代方案,您可以编写

lattice = np.empty( (3,3), dtype=object)
lattice.flat = [site(3) for _ in lattice.flat]

无论晶格的形状如何,它都应该起作用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-26
    • 2011-09-02
    • 1970-01-01
    • 1970-01-01
    • 2015-05-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多