You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python中大规模独立积分的并行化与优化方案咨询

Python中大规模独立积分的并行化与优化方案咨询

我现在需要计算一大批独立的积分,目前已经用Python写了如下代码:

# We define the integrand
def integrand(tau,k,t,z,config, epsilon=1e-7):
    u = np.sqrt(np.maximum(epsilon,tau**2 - z**2))
    return np.sin(config.omega * (t - tau))*k/2, np.sin(config.omega * (t - tau)) * j1(k * u) / u


# NON-VECTORISED
partial_integral = np.zeros((len(n_values),len(t_values),len(z_values)))

for n in range(1, len(n_values)): # We skip the n=0 case as it is trivially = 0
    for j in range(1, len(z_values)): # We skip the z=0 case as it is trivially = 0
        for i in range(1, len(t_values)): # We skip the t=0 case as it is trivially = 0
            partial_integral[n,i,j],_ = quad(integrand, x_min[n,i,j], x_max[n,i,j], args=(k_n_values[n],t_values[i],z_values[j],config), limit=np.inf) # We use quad

现在len(n_values)、len(t_values)、len(z_values)这几个维度的长度都很大,所以我想尽最大可能加速这段代码,有没有什么好的优化建议?


我自己已经尝试过一些方法:

  • 试过不同的积分库,目前觉得Scipy.quad的表现最好
  • 尝试过向量化代码,但看起来没带来多少性能提升:
# VECTORISED
def compute_integral(n,i,j):
    quad(integrand, x_min[n,i,j], x_max[n,i,j], args=(k_n_values[n],t_values[i],z_values[j],config, epsilon), limit=10000) # We use quad

# Use np.meshgrid to create the index grids for n, i, j (starting from 1 to avoid 0-index)
n_grid, i_grid, j_grid = np.meshgrid(np.arange(0, len(n_values)), np.arange(0, len(t_values)), np.arange(0, len(z_values)), indexing='ij')

# Flatten the grids to vectorize the loop over n, i, j
indices = np.vstack([n_grid.ravel(), i_grid.ravel(), j_grid.ravel()]).T

# Vectorize the integral computation using np.vectorize
vectorized_integral = np.vectorize(lambda n, i, j: compute_integral(n, i, j))

# Apply the vectorized function to all combinations of (n, i, j)
partial_integral = np.empty((len(n_values),len(t_values),len(z_values)))
partial_integral[tuple(indices.T)] = vectorized_integral(*indices.T)
  • 还尝试用Numba(配合numba-scipy来支持j1函数)对被积函数做JIT编译,结果确实获得了5倍的性能提升!修改后的函数如下:
from numba import njit # Remember to also have numba-scipy installed!!!!!

# We define the integrand and JIT it with Numba (and numba-scipy) for a faster performance
@njit
def integrand(tau,k,t,z,omega, epsilon):
    u = np.sqrt(np.maximum(0,tau**2 - z**2))
    if u < epsilon:
        return np.sin(omega * (t - tau))*k/2
    else:
        return np.sin(omega * (t - tau)) * j1(k * u) / u

PS:补充一下,我现在是在个人PC上运行这个脚本,但之后应该可以在集群上运行。


完整代码

import os
import numpy as np
from scipy.special import j1
from scipy.integrate import quad
from numba import njit # Remember to also have numba-scipy installed!!!!!

from tqdm import tqdm


def perform_integrals(config):
    '''
    We perform the integrals using quad
    '''

    # We store the range of n, kn and gn in arrays of length N_max
    n_values = np.linspace(0, config.N_max-1, config.N_max, dtype=int)
    k_n_values = 2 * np.pi * n_values

    # We store the range of t, z in the arrays of dimension (N_t) and (N_z)
    t_values = np.linspace(0, config.N_t*config.delta_t, config.N_t)
    z_values = np.linspace(0, config.N_z*config.delta_z, config.N_z)

    # Preallocate the result arrays (shape: len(n_values) x len(z_values))
    x_min = np.zeros((len(t_values), len(z_values)))
    x_max = np.empty((len(t_values), len(z_values)))

    # Compute the values
    t1_values = np.roll(t_values, 1)
    t1_values[0] = 0.
    x_min = np.maximum(z_values[None, :], t1_values[:, None])  # Max between z_values[j] and t_values[i-1]
    x_max = np.maximum(z_values[None, :], t_values[:, None])  # Max between z_values[j] and t_values[i]


    # We define the integrand and JIT it with Numba (and numba-scipy) for a faster performance
    @njit
    def integrand(tau,k,t,z,omega, epsilon=1e-7):
        u = np.sqrt(np.maximum(0,tau**2 - z**2))
        if u < epsilon:
            return np.sin(omega * (t - tau))*k/2
        else:
            return np.sin(omega * (t - tau)) * j1(k * u) / u
        


    # NON-VECTORISED
    partial_integral = np.zeros((len(n_values),len(t_values),len(z_values)))

    for n in tqdm(range(1, len(n_values))): # We skip the n=0 case as it is trivially = 0
        for i in range(1, len(t_values)): # We skip the t=0 case as it is trivially = 0
            for j in range(1, len(z_values)): # We skip the z=0 case as it is trivially = 0
                partial_integral[n,i,j],_ = quad(integrand, x_min[i,j], x_max[i,j], args=(k_n_values[n],t_values[i],z_values[j],config.omega, 1e-7), limit=10000, epsabs=1e-7, epsrel=1e-4) # We use quad


    return partial_integral

class TalbotConfig:
    def __init__(self):
        self.A = 1. # Amplitude of signal
        self.c = 1. # Speed of light
        self.d = 1. # Distance between gratings we fix it = 1
        self._lambda = self.d / 10. # Wavelength
        self.w = 2 * self._lambda # Width of the gratings

        # Other relevant magnitudes
        self.omega = 2 * np.pi * self.c / self._lambda # Frequency of the signal
        self.z_T = self._lambda/(1. - np.sqrt(1.-(self._lambda/self.d) ** 2)) # Talbot distance = 2 d^2/λ

        # Simulation parameters
        self.N_x = 27*2 -1 # Number of samples in x direction
        self.N_z = 192*2 -1 # Number of samples in z direction
        self.N_t = 100 -1 # Number of samples in time
        self.N_max = int(self.d / self._lambda * 4) # Number of terms in the series

        # Other relevant magnitudes
        self.last_t_zT = 1. # Final time / Z_t
        self.delta_t = self.z_T/self.c/self.N_t * self.last_t_zT # Time between photos
        self.delta_x = self.d/2/self.N_x # X-Distance between points
        self.delta_z = self.z_T/self.N_z # Z-Distance between points


    def __str__(self):
        params = {
            "Amplitude of signal (A)": self.A,
            "Speed of light (c)": self.c,
            "Distance between gratings (d)": self.d,
            "Wavelength (lambda)": self._lambda,
            "Width of the gratings (w)": self.w,
            "Frequency of the signal (omega)": self.omega,
            "Talbot distance (z_T)": self.z_T,
            "Number of samples in x direction (N_x)": self.N_x,
            "Number of samples in z direction (N_z)": self.N_z,
            "Number of samples in time (N_t)": self.N_t,
            "Number of terms in the series (N_max)": self.N_max,
            "Time between photos (delta_t)": self.delta_t,
            "X-Distance between points (delta_x)": self.delta_x,
            "Z-Distance between points (delta_z)": self.delta_z,
            "Final time / z_T": self.last_t_zT
        }
        
        print("{:<45} {:<40}".format('\nParameter', 'Value'))
        print("-" * 65)
        for key, value in params.items():
            print("{:<45} {:<40}".format(key, value))
        return ""
    
    def debugging(self):
        if self.Debugging:
            # Simulation parameters
            self.N_x = 5 # Number of samples in x direction
            self.N_z = 5 # Number of samples in z direction
            self.N_t = 5 # Number of samples in time
            self.N_max = int(self.d / self._lambda)*2 # Number of terms in the series

        return

if __name__ == "__main__":
    config = TalbotConfig()

    # Are we debugging?
    config.Debugging = False
    config.debugging()

    print(config)

 

    integral = perform_integrals(config)

备注:内容来源于stack exchange,提问作者Gabriel Ybarra Marcaida

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 03:24:13