Python 如何使用numpy(和scipy)查找函数的所有零?

Python 如何使用numpy(和scipy)查找函数的所有零?,python,numpy,scipy,Python,Numpy,Scipy,假设我在a和b之间定义了一个函数f(x)。此函数可以有许多零,但也可以有许多渐近线。我需要检索此函数的所有0。最好的方法是什么 其实,我的策略是: 我在给定数量的点上评估我的函数 我检测到是否有信号变化 我在改变符号的点之间找到零 我验证找到的零是否真的是零,或者这是否是渐近线 U = numpy.linspace(a, b, 100) # evaluate function at 100 different points c = f(U) s = numpy.sign(c) for i in

假设我在
a
b
之间定义了一个函数
f(x)
。此函数可以有许多零,但也可以有许多渐近线。我需要检索此函数的所有0。最好的方法是什么

其实,我的策略是:

  • 我在给定数量的点上评估我的函数
  • 我检测到是否有信号变化
  • 我在改变符号的点之间找到零
  • 我验证找到的零是否真的是零,或者这是否是渐近线

    U = numpy.linspace(a, b, 100) # evaluate function at 100 different points
    c = f(U)
    s = numpy.sign(c)
    for i in range(100-1):
        if s[i] + s[i+1] == 0: # oposite signs
            u = scipy.optimize.brentq(f, U[i], U[i+1])
            z = f(u)
            if numpy.isnan(z) or abs(z) > 1e-3:
                continue
            print('found zero at {}'.format(u))
    
  • 该算法似乎有效,但我发现了两个潜在问题:

  • 它不会检测到不穿过x轴的零(例如,在函数中,如
    f(x)=x**2
    ),但是,我认为我正在计算的函数不会出现这种情况
  • 如果离散点太远,它们之间可能有一个以上的零,算法可能无法找到它们
  • 您是否有更好的策略(仍然有效)来查找函数的所有零


    我认为这对这个问题并不重要,但对于那些好奇的人来说,我正在处理光纤中波传播的特征方程。函数如下所示(其中先前定义了
    V
    ell
    ,并且
    ell
    为正整数):


    为什么您仅限于
    numpy
    ?Scipy有一个软件包,它可以完全满足您的需要:

    我学到了一个教训:数值编程很难,所以不要这样做:)


    无论如何,如果你已经下定决心要自己构建算法,那么
    scipy
    I linked上的doc页面(加载需要很长时间,顺便说一句)会为你提供一个算法列表。我以前使用过的一种方法是将函数离散化到问题所需的程度。(也就是说,调整\delta x,使其比问题中的特征大小小得多。)这使您可以查找函数的特征(如符号的变化)。而且,你可以很容易地计算线段的导数(可能从幼儿园开始),所以你的离散化函数有一个定义良好的一阶导数。由于您已将dx调整为小于特征大小,因此可以保证不会错过函数中对您的问题非常重要的任何功能


    如果你想知道“特征尺寸”是什么意思,可以用长度单位或1/2长度来查找函数的一些参数。也就是说,对于某些函数f(x),假设x有长度单位,而f没有长度单位。然后寻找乘以x的东西。例如,如果要离散cos(\pi x),乘以x的参数(如果x具有长度单位)必须具有1/长度的单位。所以cos(\pi x)的特征大小是1/\pi。如果你的离散化比这个小得多,你就不会有任何问题。可以肯定的是,这个技巧并不总是有效的,所以你可能需要做一些修补。

    我看到的主要问题是,如果你真的能找到所有的根,正如评论中已经提到的,这并不总是可能的。如果你确信你的功能不是完全病态的(
    sin(1/x)
    ),下一个问题是你对缺失一个或几个根的容忍度。换言之,它是关于你准备去多长时间,以确保你没有错过任何-据我所知,没有通用的方法来为你隔离所有的根,所以你必须自己做。你所展示的已经是合理的第一步了。有几点意见:

    • 布伦特的方法在这里确实是一个不错的选择
    • 首先,处理分歧。因为在你的函数中,分母中有贝塞尔,你可以先求出它们的根——最好在例如Abramovitch和Stegun()中查找它们。这将比使用您正在使用的特别网格更好
    • 一旦找到两个根或分歧,
      x\u 1
      x\u 2
      ,您可以做的是,在区间
      [x\u 1+ε,x\u 2-ε]
      中再次运行搜索。继续,直到找不到更多的根为止(Brent的方法保证收敛到一个根,只要有一个根)
    • 如果你不能列举所有的差异,你可能需要更仔细地验证一个候选者是否确实是一个差异:给定
      x
      不要只检查
      f(x)
      是否很大,检查一下,例如
      |f(x-epsilon/2)|>|f(x-epsilon)|
      epsilon的几个值(1e-8、1e-9、1e-10,诸如此类)
    • 如果要确保根不直接接触零,请查找函数的极值,对于每个极值,
      x_e
      ,请检查
      f(x_e)
      的值

    我发现使用

    • 想法:通过反复调用
      fsolve
      并更改
      x0
      ,从间隔
      (开始、停止)
      和步长
      中查找任何零。使用相对较小的步长查找所有根

    • 只能在一个维度中搜索零(其他维度必须固定)。如果您有其他需要,我建议使用来计算分析解决方案

    • 注意:它可能并不总是能找到所有的零,但我看到它给出了相对较好的结果。我把代码也放到了a,如果需要,我会更新它

    示例输出:

    放大:

    寻根器定义
    我在求解f(z)=0这样的方程时也遇到了这个问题,其中f是一个全纯函数。我想确保不遗漏任何零,最后开发了一个基于

    它有助于找到复域中零的确切数目。一旦知道了零的数目,就更容易找到
    def f(u):
        w = numpy.sqrt(V**2 - u**2)
    
        jl = scipy.special.jn(ell, u)
        jl1 = scipy.special.jnjn(ell-1, u)
        kl = scipy.special.jnkn(ell, w)
        kl1 = scipy.special.jnkn(ell-1, w)
    
        return jl / (u*jl1) + kl / (w*kl1)
    
    import numpy as np
    import scipy
    from scipy.optimize import fsolve
    from matplotlib import pyplot as plt
    
    # Defined below
    r = RootFinder(1, 20, 0.01)
    args = (90, 5)
    roots = r.find(f, *args)
    print("Roots: ", roots)
    
    # plot results
    u = np.linspace(1, 20, num=600)
    fig, ax = plt.subplots()
    ax.plot(u, f(u, *args))
    ax.scatter(roots, f(np.array(roots), *args), color="r", s=10)
    ax.grid(color="grey", ls="--", lw=0.5)
    plt.show()
    
    Roots:  [ 2.84599497  8.82720551 12.38857782 15.74736542 19.02545276]
    
    import numpy as np
    import scipy
    from scipy.optimize import fsolve
    from matplotlib import pyplot as plt
    
    
    class RootFinder:
        def __init__(self, start, stop, step=0.01, root_dtype="float64", xtol=1e-9):
    
            self.start = start
            self.stop = stop
            self.step = step
            self.xtol = xtol
            self.roots = np.array([], dtype=root_dtype)
    
        def add_to_roots(self, x):
    
            if (x < self.start) or (x > self.stop):
                return  # outside range
            if any(abs(self.roots - x) < self.xtol):
                return  # root already found.
    
            self.roots = np.append(self.roots, x)
    
        def find(self, f, *args):
            current = self.start
    
            for x0 in np.arange(self.start, self.stop + self.step, self.step):
                if x0 < current:
                    continue
                x = self.find_root(f, x0, *args)
                if x is None:  # no root found.
                    continue
                current = x
                self.add_to_roots(x)
    
            return self.roots
    
        def find_root(self, f, x0, *args):
    
            x, _, ier, _ = fsolve(f, x0=x0, args=args, full_output=True, xtol=self.xtol)
            if ier == 1:
                return x[0]
            return None
    
    
    def f(u, V=90, ell=5):
        w = np.sqrt(V ** 2 - u ** 2)
    
        jl = scipy.special.jn(ell, u)
        jl1 = scipy.special.yn(ell - 1, u)
        kl = scipy.special.kn(ell, w)
        kl1 = scipy.special.kn(ell - 1, w)
    
        return jl / (u * jl1) + kl / (w * kl1)