Python如何处理大型3D背景网格数据以快速计算依赖网格的数值积分
我是一名物理方向的研究生研究助理,目前正在开发Python代码,核心功能是在大型数组描述的背景场中计算线积分。
我生成的数据集为t×n×n格式的数组,n和t的量级均为100,代表二维空间内随时间变化的温度和速度场。我需要收集多组这类网格数据,随机选择数据集后,沿平面内的随机路径计算依赖网格数据的数值积分(涉及x方向速度、y方向速度、温度三类独立网格,矢量信息为必要参数),最终目标是对给定数据集的积分值做大量统计分析。
上述需求要求我能对(t, x, y)格式的时空点进行背景场采样。原始数据为大尺寸点表,每行结构为['time','xpos','ypos','temp','xvel','yvel'],每个时间步下的每个(xpos, ypos)点都对应一条记录,我可按需对数据做预处理。
目前的核心问题是我需要在多组不同背景场中对数千条随机路径做采样,运行速度是核心约束。排除网格生成的耗时,最大的性能瓶颈是数据点的实时访问效率。
我之前在Mathematica中开发过项目原型,该工具更适配我的分析思路:原型中导入了10组背景场,使用Mathematica的ListInterpolation[]函数生成代表离散网格数据的连续函数,将这10个插值函数存入数组,计算数值积分时随机调用即可。
该方法在10组数据集的场景下经调整后可正常运行,但项目后续可能需要扩展到10000组数据集的规模。最终项目会迁移到大型集群上运行,支持并行处理,且支持动态生成数据集并按需存储,但该方案落地尚需时间。
现阶段我希望先生成一定数量的数据集,以最快的方式实现按需采样。数据必须经过插值得到连续场,除此之外实现方案可灵活调整。我的初步计划是参考上述原型的逻辑,提前为每个数据集生成插值函数并存储到文件中,代码运行时随机选择背景场、加载对应插值函数后计算线积分。
我查找了同类问题的解决方案,找到了相关讨论:
该问题的题主只需要得到更密的网格而非可调用的插值函数,该方案在没有其他选择时对我也有参考价值,但给出的解决方案似乎受限于我使用的数据集规模。
我检索了可满足需求的插值工具包,目前找到的可选方案包括:
- Scipy
griddata() - Scipy
interpn() - Numpy
interp()
我目前有一份约200MB的示例数据集,正尝试为温度网格生成并存储插值函数。由于我对Python并不熟练,仅这一步就遇到了问题。我发现通过pandas加载数据、筛选所需字段后转换为numpy数组的方案速度稍快:
import numpy as np import pandas as pd import matplotlib.pyplot as plt from scipy.interpolate import griddata # Load grid data from file gridData = pd.read_fwf('Backgrounds\\viscous_14_moments_evo.dat', header=None, names=['time','xpos','ypos','temp','xvel','yvel']) # Set grid parameters # nGridSpaces is total number of grid spaces / bins. # Will be data-dependent in the future. nGridSpaces = 27225 # Number of timesteps is gridData's time column divided by number of grid spaces. NT = int(len(gridData['time'])/nGridSpaces)
之后我尝试使用Scipy的interpnd()和griddata()函数,但都没有成功。我认为问题出在我没有正确提供符合要求的输入数据,核心问题是如何将与温度值对应的(t, x, y)点整理为可用的格式。
我主要尝试将这些点传入Numpy的meshgrid()函数,但似乎触发了Numpy处理这类数据的尺寸上限:
# Lists of points individually tList=np.ndarray.flatten(pd.DataFrame(gridData[['time']]).to_numpy()) xList=np.ndarray.flatten(pd.DataFrame(gridData[['xpos']]).to_numpy()) yList=np.ndarray.flatten(pd.DataFrame(gridData[['ypos']]).to_numpy()) # 3D grid of points points = np.meshgrid(tList, xList, yList) # List of temperature values tempValues=np.ndarray.flatten(pd.DataFrame(gridData[['temp']]).to_numpy()) # Interpolate and spit out a value for a point somewhere central-ish as a check point = np.array([1,80,80]) griddata(points, tempValues, point)
调用meshgrid()的代码行返回如下值错误:
ValueError: array is too big; `arr.size * arr.dtype.itemsize` is larger than the maximum possible size.
- 我使用的这类数据集在Numpy中的尺寸限制是多少?我没有在Numpy官方文档中找到关于最大尺寸的说明。
- 我的实现思路是否合理?有没有更优的技术框架可以实现我的需求?
- 处理这类大型数组时,对速度影响最大的因素有哪些,我可以通过哪些方式优化性能?
解答
问题1:Numpy数组尺寸限制
你触发的错误不是Numpy的全局尺寸上限,完全是meshgrid调用逻辑错误导致的:你当前每个坐标列表的长度是NT*n²,按你给出的参数计算,每个列表长度约为272万,meshgrid(tList, xList, yList)会生成维度为272万×272万×272万的超量数组,占用空间超过10^19字节,远远超过物理内存上限,自然会报错。
Numpy单个数组的实际尺寸上限只取决于硬件和系统:32位Python单个数组最大不超过2GB,64位Python没有人为设置的上限,只要内存足够就能正常加载,你的原始数据集总大小仅200MB,远没有达到Numpy的理论上限。
问题2:实现思路的合理性与优化方案
你的整体逻辑是可行的,但可以大幅优化:
你不需要提前存储插值函数,也不需要用griddata这类针对非结构化网格的插值工具——你的原始数据本身就是规则正交网格,用RegularGridInterpolator是性能最优的选择,而且只需要存储原始网格数据即可,不需要序列化插值函数。
具体调整逻辑:
- 预处理阶段把扁平的原始数据重排成规则网格:你已经知道每个时间步的空间网格点数是
n²,将temp、xvel、yvel三个字段分别reshape成(NT, n, n)的三维数组,同时提取去重后的t、x、y一维坐标数组即可。 - 每次需要调用某个背景场的插值能力时,直接用加载的网格坐标和三维数组初始化
RegularGridInterpolator,初始化速度极快,不需要提前存储到文件。 - 如果后续要扩展到10000组数据集,预处理阶段把每个数据集的坐标和三个场数组用
numpy.save存成npy文件,加载速度比读文本快至少10倍。
如果追求更高的采样速度,也可以用numba手写线性插值核函数,比Scipy内置插值工具快2~5倍,非常适合大量随机点采样的场景。
问题3:性能影响因素与优化手段
对速度影响最大的因素优先级从高到低为:
- 插值工具选型:不要用非结构化网格插值工具(如
griddata),规则网格用RegularGridInterpolator性能高一个数量级。 - 数据存储格式:不要用文本格式存储预处理后的网格,用npy或者zarr格式存储,加载速度提升10~100倍。
- 批量采样:不要单次采样一个点,把路径上所有需要采样的点一次性打包传入插值函数,向量化操作比循环采样快至少10倍。
- 内存布局:预处理后的数组用默认的C顺序存储,访问速度比Fortran顺序快很多。
- 并行方案:单组背景场的多条路径积分可以用多线程或者numba并行,多组背景场的统计直接用进程池并行即可。
修正后的核心代码示例:
from scipy.interpolate import RegularGridInterpolator import numpy as np # 预处理阶段:提取去重的坐标 t_coords = np.sort(gridData['time'].unique()) x_coords = np.sort(gridData['xpos'].unique()) y_coords = np.sort(gridData['ypos'].unique()) n = len(x_coords) NT = len(t_coords) # 把场数据重排成规则网格,排序和坐标顺序对应 gridData_sorted = gridData.sort_values(['time', 'xpos', 'ypos']) temp_grid = gridData_sorted['temp'].values.reshape(NT, n, n) xvel_grid = gridData_sorted['xvel'].values.reshape(NT, n, n) yvel_grid = gridData_sorted['yvel'].values.reshape(NT, n, n) # 初始化插值函数 temp_interp = RegularGridInterpolator((t_coords, x_coords, y_coords), temp_grid) xvel_interp = RegularGridInterpolator((t_coords, x_coords, y_coords), xvel_grid) yvel_interp = RegularGridInterpolator((t_coords, x_coords, y_coords), yvel_grid) # 测试采样,可一次性传入N个点的(N,3)数组 test_point = np.array([1, 80, 80]) print(temp_interp(test_point))
内容的提问来源于stack exchange,提问作者Jopacabra

