【问题标题】:How to optimise the numerical evaluation of a SymPy integral?如何优化 SymPy 积分的数值评估?
【发布时间】:2021-04-06 12:55:52
【问题描述】:

我是 SymPy 的新手,希望有人能指出优化我的代码的方法。

我需要对一个包含非常高小数位 (150–300) 的表达式进行数值计算,并且每个参数集需要 30 秒或更长时间——考虑到要计算的参数空间,这非常长。

我在积分处理中使用了lambdifympmath 后端和meijerg=True,它显着降低了运行时间。还有其他可以使用的方法吗?理想情况下,将评估时间推到 1 秒以下会很棒。我的代码是:

import mpmath
from mpmath import mpf, mp
mp.dps = 150 # ideally would like to have this set to 300 
import numpy as np
from sympy import besselj, symbols, hankel2, legendre, sin, cos, tan, summation, I
from sympy import  lambdify, expand, Integral
import time
x, alpha, k, m,n, r1, R, theta = symbols('x alpha k m n r1 R theta')

r1 = (R*cos(alpha))/cos(theta) #

Imn_part1 = (n*hankel2(n-1,k*r1)-(n+1)*hankel2(n+1,k*r1))*legendre(n, cos(theta))*cos(theta)
Imn_part2 = n*(n+1)*hankel2(n, k*r1)*(legendre(n-1, cos(theta)-legendre(n+1, cos(theta))))/k*r1
Imn_parts = expand(Imn_part1+Imn_part2)
Imn_expr = expand(Imn_parts*legendre(m,cos(theta))*(r1**2/R**2)*tan(theta))
Imn = Integral(Imn_expr, (theta, 0, alpha)).doit(meijerg=True)

# the lambdified expression
Imn_lambdify = lambdify([m,n,k,R,alpha], Imn,'mpmath')

向函数提供数字输入时 – 需要很长时间(30 秒 - 40 秒)。

substitute_dict = {'alpha':mpf(np.radians(10)), 'k':5,'R':mpf(0.1), 'm':20,'n':10}

print('starting calculation...')
start = time.time()
output = Imn_lambdify(substitute_dict['m'],
             substitute_dict['n'],
             substitute_dict['k'],
             substitute_dict['R'],
            substitute_dict['alpha'])
print(time.time()-start)

使用的操作系统/软件包版本:

  • Linux Mint 19.2
  • Python 3.8.5
  • SymPy 1.7.1
  • MPMath 1.2.1

【问题讨论】:

  • 为什么需要这么多数字?这种准确性几乎不需要。我唯一能想象的是,如果您在表达式中遇到极端的cancellation
  • 嗨@Wrzlprmft 感谢您的ping!我正在尝试移植一些 Mathematica 代码,原作者推荐了如此高的精度。极端取消是否会导致更长的计算时间 - 关于如何解决这个问题的任何想法?
  • 我并不是说会发生取消,只是这将是我能想象得到如此高精度的唯一原因。如果处理不当,取消可能会导致大量错误。这些有时可能会导致更长的计算时间,但这将是您遇到的最少问题。
  • 两个见解: 1) Imn 仍然包含抽象积分,这意味着doit 将对它们进行数值评估。这几乎肯定比你的 150 位数要高。 2)当您删除 MPMath 时(除了作为 lambdify 的参数,因为它似乎能够根据需要处理复杂的值),一切都在一秒钟内运行并产生相同的结果(对于许多数字)。 —据此,我猜没有理由使用高精度。为了进一步加快速度,最好的方法可能是使用矢量被积函数。
  • @Wrzlprmft doit() 以符号方式而非数字方式评估积分。这样做不会损失准确性(尽管 SymPy 实际上不能以封闭形式计算这个特定的积分,因此调用 doit() 实际上并不是很有用)。

标签: python optimization sympy symbolic-math mpmath


【解决方案1】:

设置meijerg=True 刚刚导致 SymPy 在评估积分时不那么努力。它仍然无法评估它,但它已将其拆分为 5 个子积分,如果您打印 Imn 可以看到。你不妨把它作为一个整体(离开doit()):

Imn = Integral(Imn_expr, (theta, 0, alpha))

对我来说,分裂积分的计算速度要快一些,但这也差不多

Imn = Integral(simplify(Imn_expr), (theta, 0, alpha))

归根结底,让事情变慢的是您使用的位数。如果您实际上不需要这么多数字,则不应使用它们。请注意,mpmath 会在内部自动增加精度以避免取消,因此您不必自己这样做。我得到相同的值(数字更少),默认 dps 为 15 和 150。

您可以尝试将您的值直接替换到您的表达式中,如果它们没有改变,并查看 SymPy 是否可以使用它们进一步简化 Imn_expr。

顺便说一句,您正在使用np.radians(10),这是一个机器浮动,因为这是 NumPy 使用的。这完全违背了计算 150 位数字的最终答案的目的,因为此输入参数仅精确到 15。请考虑使用 mpmath.pi/18 来获取与您指定的位数正确的值。

【讨论】:

  • 非常感谢@asmeurer 的回复,学到了很多!拆分/简化积分确实确实评估得更快,并将时间缩短了 25%——这非常好。我也很高兴听到dps 是导致延误的主要因素。保持“默认”dps 不会从我尝试移植的原始代码重新创建结果。 Imn 类型项输入矩阵,并且有一个 mpmath.lu_solve 操作,它的错误往往比集合 dps 多 6 dps。也许这就是“更多”错误出现的地方?
  • 有点独立但相关的问题,想知道你们中是否有人知道哪种计算机可以更快地完成这项工作(高 RAM/处理器类型) - 鉴于这是代码可能达到的最快速度,使用肌肉更多的东西会有所帮助吗?或者这已经是极限了吗?
最近更新 更多