简体   繁体   English

Scipy 的solve_bvp 和耦合微分方程的性能问题

[英]Performance issue with Scipy's solve_bvp and coupled differential equations

I'm facing a problem while trying to implement the coupled differential equation below (also known as single-mode coupling equation) in Python 3.8.3.我在尝试在 Python 3.8.3 中实现下面的耦合微分方程(也称为单模耦合方程)时遇到问题。 As for the solver, I am using Scipy's function scipy.integrate.solve_bvp , whose documentation can be read here .至于求解器,我使用的是 Scipy 的 function scipy.integrate.solve_bvp ,可以在此处阅读其文档。 I want to solve the equations in the complex domain, for different values of the propagation axis ( z ) and different values of beta ( beta_analysis ).我想求解复域中的方程,对于不同的传播轴值( z )和不同的 beta 值( beta_analysis )。

The problem is that it is extremely slow (not manageable) compared with an equivalent implementation in Matlab using the functions bvp4c , bvpinit and bvpset .问题在于,与使用函数bvp4cbvpinitbvpset的 Matlab 中的等效实现相比,它非常慢(无法管理)。 Evaluating the first few iterations of both executions, they return the same result, except for the resulting mesh which is a lot greater in the case of Scipy.评估两次执行的前几次迭代,它们返回相同的结果,除了生成的网格在 Scipy 的情况下要大得多。 The mesh sometimes even saturates to the maximum value.网格有时甚至会饱和到最大值。

The equation to be solved is shown here below, along with the boundary conditions function.下面显示了要求解的方程以及边界条件 function。

import h5py
import numpy as np
from scipy import integrate
    
def coupling_equation(z_mesh, a):
    ka_z = k    # Global
    z_a = z     # Global
    a_p = np.empty_like(a).astype(complex)

    for idx, z_i in enumerate(z_mesh): 
        beta_zf_i = np.interp(z_i, z_a, beta_zf)    # Get beta at the desired point of the mesh
        ka_z_i = np.interp(z_i, z_a, ka_z)          # Get ka at the desired point of the mesh

        coupling_matrix = np.empty((2, 2), complex)
        coupling_matrix[0] = [-1j * beta_zf_i, ka_z_i]
        coupling_matrix[1] = [ka_z_i, 1j * beta_zf_i]

        a_p[:, idx] = np.matmul(coupling_matrix, a[:, idx])    # Solve the coupling matrix

    return a_p

def boundary_conditions(a_a, a_b):
    return np.hstack(((a_a[0]-1), a_b[1]))

Moreover, I couldn't find a way to pass k , z and beta_zf as arguments of the function coupling_equation , given that the fun argument of the solve_bpv function must be a callable with the parameters (x, y) . Moreover, I couldn't find a way to pass k , z and beta_zf as arguments of the function coupling_equation , given that the fun argument of the solve_bpv function must be a callable with the parameters (x, y) . My approach is to define some global variables, but I would appreciate any help on this too if there is a better solution.我的方法是定义一些全局变量,但如果有更好的解决方案,我也将不胜感激。

The analysis function which I am trying to code is:我试图编码的分析 function 是:

def analysis(k, z, beta_analysis, max_mesh):
    s11_analysis = np.empty_like(beta_analysis, dtype=complex)
    s21_analysis = np.empty_like(beta_analysis, dtype=complex)
    
    initial_mesh = np.linspace(z[0], z[-1], 10)    # Initial mesh of 10 samples along L
    mesh = initial_mesh
    
    # a_init must be complex in order to solve the problem in a complex domain
    a_init = np.vstack((np.ones(np.size(initial_mesh)).astype(complex), 
                        np.zeros(np.size(initial_mesh)).astype(complex)))
    
    for idx, beta in enumerate(beta_analysis):
        print(f"Iteration {idx}: beta_analysis = {beta}")
        global beta_zf 
        beta_zf = beta * np.ones(len(z))    # Global variable so as to use it in coupling_equation(x, y)
        
        a = integrate.solve_bvp(fun=coupling_equation, 
                                bc=boundary_conditions, 
                                x=mesh, 
                                y=a_init, 
                                max_nodes=max_mesh,
                                verbose=1)
#         mesh = a.x      # Mesh for the next iteration
#         a_init = a.y    # Initial guess for the next iteration, corresponding to the current solution
        s11_analysis[idx] = a.y[1][0]
        s21_analysis[idx] = a.y[0][-1]
    return s11_analysis, s21_analysis

I suspect that the problem has something to do with the initial guess that is being passed to the different iterations (see commented lines inside the loop in the analysis function).我怀疑这个问题与传递给不同迭代的初始猜测有关(请参阅analysis函数中循环内的注释行)。 I try to set the solution of an iteration as the initial guess for the following (which must reduce the time needed for the solver), but it is even slower, which I don't understand.我尝试将迭代的解决方案设置为以下的初始猜测(这必须减少求解器所需的时间),但它甚至更慢,我不明白。 Maybe I missed something, because it is my first time trying to solve differential equations.也许我错过了一些东西,因为这是我第一次尝试解决微分方程。

The parameters used for the execution are the following:用于执行的参数如下:

f2 = h5py.File(r'path/to/file', 'r')
k = np.array(f2['k']).squeeze()
z = np.array(f2['z']).squeeze()
f2.close()

analysis_points = 501
max_mesh = 1e6 

beta_0 = 3e2; 
beta_low = 0;       # Lower value of the frequency for the analysis
beta_up = beta_0;   # Upper value of the frequency for the analysis
beta_analysis = np.linspace(beta_low, beta_up, analysis_points);

s11_analysis, s21_analysis = analysis(k, z, beta_analysis, max_mesh)

Any ideas on how to improve the performance of these functions?关于如何提高这些功能的性能的任何想法? Thank you all in advance, and sorry if the question is not well-formulated, I accept any suggestions about this.提前谢谢大家,如果问题没有很好地表达,我很抱歉,我接受任何关于此的建议。

Edit: Added some information about performance and sizing of the problem.编辑:添加了一些关于性能和问题大小的信息。

  • In practice, I can't find a relation that determines de number of times coupling_equation is called.在实践中,我找不到决定coupling_equation被调用次数的关系。 It must be a matter of the internal operation of the solver.这一定是求解器内部操作的问题。 I checked the number of callings in one iteration by printing a line, and it happened in 133 ocasions (this was one of the fastests).我通过打印一行检查了一次迭代中的调用次数,它发生在 133 次(这是最快的一次)。 This must be multiplied by the number of iterations of beta.这必须乘以 beta 的迭代次数。 For the analyzed one, the solver returned this:对于分析过的,求解器返回了这个:

Solved in 11 iterations, number of nodes 529. Maximum relative residual: 9.99e-04 Maximum boundary residual: 0.00e+00 11 次迭代求解,节点数 529。最大相对残差:9.99e-04 最大边界残差:0.00e+00

  • The shapes of a and z_mesh are correlated, since z_mesh is a vector whose length corresponds with the size of the mesh, recalculated by the solver each time it calls coupling_equation . az_mesh的形状是相关的,因为 z_mesh 是一个向量,其长度与网格的大小相对应,由求解器在每次调用coupling_equation方程时重新计算。 Given that a contains the amplitudes of the progressive and regressive waves at each point of z_mesh , the shape of a is (2, len(z_mesh)) .假设a包含z_mesh的每个点的行进波和回归波的幅度, a的形状是(2, len(z_mesh))
  • In terms of computation times, I only managed to achieve 19 iterations in about 2 hours with Python.在计算时间方面,我只用 Python 在大约 2 小时内完成了 19 次迭代。 In this case, the initial iterations were faster, but they start to take more time as their mesh grows, until the point that the mesh saturates to the maximum allowed value.在这种情况下,初始迭代速度更快,但随着网格的增长,它们开始花费更多时间,直到网格饱和到最大允许值为止。 I think this is because of the value of the input coupling coefficients in that point, because it also happens when no loop in beta_analysis is executed (just the solve_bvp function for the intermediate value of beta).我认为这是因为输入耦合系数在该点的值,因为它也会在beta_analysis中没有执行循环时发生(只是 beta 的中间值的solve_bvp function)。 Instead, Matlab managed to return a solution for the entire problem in just 6 minutes, aproximately.相反,Matlab 设法在大约 6 分钟内返回了整个问题的解决方案。 If I pass the result of the last iteration as initial_guess (commented lines in the analysis function, the mesh overflows even faster and it is impossible to get more than a couple iterations.如果我将最后一次迭代的结果作为initial_guess传递( analysis function 中的注释行,则网格溢出的速度会更快,并且不可能进行多次迭代。

Based on semi-random inputs, we can see that max_mesh is sometimes reached.基于半随机输入,我们可以看到有时会达到max_mesh This means that coupling_equation can be called with a quite big z_mesh and a arrays.这意味着可以使用相当大a z_mesh和 arrays 调用coupling_equation方程。 The problem is that coupling_equation contains a slow pure-Python loop iterating on each column of the arrays.问题在于, coupling_equation包含一个缓慢的纯 Python 循环,该循环在 arrays 的每一列上进行迭代。 You can speed the computation up a lot using Numpy vectorization .您可以使用Numpy vectorization 大大加快计算速度。 Here is an implementation:这是一个实现:

def coupling_equation_fast(z_mesh, a):
    ka_z = k    # Global
    z_a = z     # Global
    a_p = np.empty(a.shape, dtype=np.complex128)
    beta_zf_i = np.interp(z_mesh, z_a, beta_zf)    # Get beta at the desired point of the mesh
    ka_z_i = np.interp(z_mesh, z_a, ka_z)          # Get ka at the desired point of the mesh
    # Fast manual matrix multiplication
    a_p[0] = (-1j * beta_zf_i) * a[0] + ka_z_i * a[1]
    a_p[1] = ka_z_i * a[0] + (1j * beta_zf_i) * a[1]
    return a_p

This code provides a similar output with semi-random inputs compared to the original implementation but is roughly 20 times faster on my machine.与原始实现相比,此代码提供了类似的 output 与半随机输入,但在我的机器上大约快 20 倍

Furthermore, I do not know if max_mesh happens to be big with your inputs too and even if this is normal/intended.此外,我不知道max_mesh是否会因您的输入而变大,即使这是正常/有意的。 It may make sense to decrease the value of max_mesh in order to reduce the execution time even more.为了进一步减少执行时间,减小max_mesh的值可能是有意义的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM