使用叠加模拟球体周围的扬声器 - 需要提高速度

Simulate speakers around a sphere using superposition - speed improvments needed

注意:自发布以来速度显着提高,请参阅底部的编辑。

我有一些使用循环的工作代码,我很确定应该有更快的方法来完成它。输出数组的大小最终变得非常大,因此当我尝试使其他数组与输出大小相同时,我 运行 内存很快就用完了。

我正在模拟围绕一个球体放置的许多扬声器都指向中心。我有一个单个扬声器的模拟,我想通过叠加原理来利用这个单一模拟。基本上我想总结单个 t运行sducer 模拟的旋转副本以获得我的最终结果。

我在圆柱坐标系 ("polar_coord_r", "polar_coord_z") 中对声压数据进行了轴对称模拟。来自模拟的压力场在每个 R 和 Z 值处都是唯一的,并且完全由二维数组 ("P_real_RZ") 描述。

我想将此压力场的多个旋转副本汇总到 3D 笛卡尔输出数组中。每个副本都旋转到球体上的不同位置。目前,我使用 x、y、z 点指定旋转,因为它允许我进行矢量数学运算(球坐标不允许我优雅地进行此操作)。输出数组相当大 (770 × 770 × 804)。

我有一些工作代码可以从单个扬声器副本 ("transducer") 获取输出。每个切片大约需要 12 秒,因此添加每个新发言人需要 两个多小时 !我想要一打左右的演讲者副本,所以这会花很长时间。

代码采用常量 X 的切片并计算该切片中每个位置的 R 和 Z 位置。 "r_distance" 是一个二维数组,包含从 一条在原点和点 ("point") 之间通过的线的​​径向距离。相似性,"z_distance" 是一个二维数组,包含 沿 同一直线的距离。

为了获得切片的压力,我找到了与计算出的 R 距离和 Z 距离最匹配的 "polar_coord_r" 和 "polar_coord_z" 的索引。我使用这些索引来查找输出中每个值的压力值(来自 P_real_RZ)。

一些定义:

非常感谢任何加速此代码的建议。

工作循环:

for i, x in enumerate(xx):
    # Creates a unit vector from origin to a point
    vector = normalize(point)

    # Create a slice of the cartesian space with constant X
    xyz_slice = np.array([x*np.ones_like(XXX[i,:,:]), YYY[i,:,:], ZZZ[i,:,:]])

    # Projects the position vector of each point of the slice onto the unit vector.
    projection = np.array(list(map(np.dot, xyz_slice, vector )))

    # Normalizes the projection which results in the Z distance along the line passing through the point
    #z_distance = np.apply_along_axis(np.linalg.norm, 0, projection) # this is the slow bit
    z_distance = np.linalg.norm(projection, axis=0) # I'm an idiot

    # Uses vector math to determine the distance from the line
    # Each point in the XYZ slice is the sum of vector along the line and the vector away from the line (radial vector).
    # By extension the position of the xyz point minus the projection of the point against the unit vector, results in the radial vector
    # Norm the radial vector to get the R value for everywhere in the slice
    #r_distance = np.apply_along_axis(np.linalg.norm, 0, xyz_slice - projection) # this is the slow bit
    r_distance = np.linalg.norm(xyz_slice - projection, axis=0) # I'm an idiot

    # Map the pressure data to each point in the slice using the R and Z distance with the RZ pressure slice.
    # look for a more efficient way to do this perhaps. currently takes about 12 seconds per slice
    r_indices = r_map_v(r_distance) # 1.3 seconds by itself
    z_indices = z_map_v(z_distance)
    r_indices[r_indices>384] = 384 # find and remove indicies above the max for r_distance
    z_indices[r_indices>803] = 803
    current_transducer[i,:,:] = P_real_RZ[z_indices, r_indices]

    # Sum the mapped pressure data into the output.
    output += current_transducer

我也尝试过使用 3D 笛卡尔数组形式的模拟数据。这是模拟的所有 x、y 和 z 值的压力数据,大小与 output.I 相同,可以在一个方向上旋转此 3D 阵列(扬声器 ar运行 不需要两次旋转一个球体)。这占用了太多的内存,而且速度仍然很慢。我最终使用这种方法遇到了内存错误。

编辑: 我找到了一种稍微简单一点的方法,但它仍然很慢。我已经更新了上面的代码,因此不再有嵌套循环。

我 运行 一个线路分析器,到目前为止最慢的线路是包含 np.apply_along_axis() 的两条线路。恐怕我可能不得不重新考虑如何完全做到这一点。

最终编辑: 我最初有一个嵌套循环,我认为这是问题所在。我不知道是什么让我认为我需要将 apply_along_axis 与 linalg.norm 一起使用。无论如何,这就是问题所在。

我还没有寻找可以优化此代码的所有方法,但这个问题突然出现在我面前:"I ran a line profiler and the slowest lines by far were the two containing np.apply_along_axis()." np.linalg.norm accepts一个 axis 参数。您可以替换行

z_distance = np.apply_along_axis(np.linalg.norm, 0, projection)

z_distance = np.linalg.norm(projection, axis=0)

(以及 np.apply_along_axisnp.linalg.norm 的其他用途)。 这应该会稍微提高性能。