当 r=0 时,在 python 中数值计算 1/r*d/dr(r*f)。 f 是 r 的函数

Calculating 1/r*d/dr(r*f) numerically in python when r=0. f is a function of r

通常当您手动执行此操作时没有问题,因为 1/r 通常会被另一个 r 取消。但是用 scipy.misc.derivative 以数字方式执行此操作就像 r 不同于零的魅力一样。但是,当然,只要我要求 r = 0,我就会得到除以零的结果,这是我所期望的。那么你怎么能用数字计算这个呢。我坚持认为所有事情都必须用数字来完成,因为我的函数现在太复杂了,我无法手动找到导数。谢谢!

我的代码:

rAtheta = lambda _r: _r*Atheta(_r,theta,z,t)
if r != 0:
    return derivative(rAtheta,r,dx=1e-10,order=3)/r
else:
    #What should go here so that it doesn't blow up when calculating the gradient?

tl;dr:使用符号微分,如果失败则使用复步微分

如果你坚持使用数值方法,你真的必须以一种或另一种方式逼近导数的极限r->0

我建议尝试 complex step differentiation。这个想法是在你试图微分的函数内部使用复杂的参数,但它通常会消除标准有限差分方案强加的数值不稳定性。结果是一个需要复杂算术的过程(hooray numpy,通常 python!)但反过来在小 dx 值时可以更加稳定。

这里还有一点:复步微分使用

F′(x0) = Im(F(x0+ih))/h + O(h^2)

让我们将其应用到您的 r=0 案例中:

F′(0) = Im(F(ih))/h + O(h^2)

即使 r=0 也没有奇点!选择一个小 h,可能与您传递给函数的 dx 相同,然后使用它:

def rAtheta(_r):
    # note that named lambdas are usually frowned upon
    return _r*Atheta(_r,theta,z,t)

tol = 1e-10
dr = 1e-12

if np.abs(r) > tol: # or math.abs or your favourite other abs
    return derivative(rAtheta,r,dx=dr,order=3)/r
else:
    return rAtheta(r + 1j*dr).imag/dr/r

这是 f = r*ln(r) 的上述操作:

即使 r=1e-10 以下的点是使用复杂的步长微分计算的,结果还是非常平滑的。

非常重要的注意事项:请注意代码中 toldr 之间的分隔。前者用于确定何时在方法之间切换,后者用作复杂步骤分化中的一个步骤。看看 tol=dr=1e-10:

时会发生什么

结果是r=1e-10下面的顺利错函数!这就是为什么您始终必须小心数值微分的原因。而且我不建议在 dr 中低于太多,因为机器精度迟早会咬你。

但为什么要到此为止呢?我相当确定您的函数可以用矢量化的方式编写,即它们可以接受一组径向点。使用复杂的步长微分,您不必遍历径向点(您必须求助于使用 scipy.misc.derivative)。示例:

import numpy as np
import matplotlib.pyplot as plt

def Atheta(r,*args):
    return r*np.log(r) # <-- vectorized expression

def rAtheta(r):
    return r*Atheta(r) #,theta,z,t) # <-- vectorized as much as Atheta is

def vectorized_difffun(rlist):
    r = np.asarray(rlist)
    dr = 1e-12

    return (rAtheta(r + 1j*dr)).imag/dr/r

rarr = np.logspace(-12,-2,20)
darr = vectorized_difffun(rarr)
plt.figure()
plt.loglog(rarr,np.abs(darr),'.-')
plt.xlabel(r'$r$')
plt.ylabel(r'$|\frac{1}{r} \frac{d}{dr}(r^2 \ln r)|$')
plt.tight_layout()

plt.show()

结果应该很熟悉:


清除了复杂步骤微分的有趣怪癖后,我应该注意到您应该认真考虑使用符号数学。在这种 1/r 因素完全消失的情况下,如果您准确地得出这个结论也不会受到伤害。毕竟双精度仍然只是双精度。

为此,您需要 sympy module, define your function symbolically once, differentiate it symbolically once, turn your simplified result into a numpy function using sympy.lambdify,并根据需要使用 this 数值函数(假设整个过程在有限时间内运行,并且生成的函数使用起来不会太慢)。示例:

import sympy as sym

# only needed for the example:
import numpy as np
import matplotlib.pyplot as plt

r = sym.symbols('r')
f = r*sym.ln(r)
df = sym.diff(r*f,r)
res_sym = sym.simplify(df/r)
res_num = sym.lambdify(r,res_sym,'numpy')

rarr = np.logspace(-12,-2,20)
darr = res_num(rarr)

plt.figure()
plt.loglog(rarr,np.abs(darr),'.-')
plt.xlabel(r'$r$')
plt.ylabel(r'$|\frac{1}{r} \frac{d}{dr}(r^2 \ln r)|$')    
plt.tight_layout()
plt.show()

导致

如您所见,由于 lambdify 在从符号函数到数字函数的转换过程中使用 numpy,结果函数被矢量化。显然,最好的解决方案是符号解决方案,只要生成的函数不会复杂到无法实际使用的程度。我敦促您首先尝试符号版本,如果由于某种原因它不适用,请谨慎切换到复步微分。