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

时间:2013-02-14 15:21:28

标签: python numpy scipy

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

实际上,我的策略如下:

  1. 我在给定数量的点上评估我的功能
  2. 我检测到是否有符号更改
  3. 我发现正在改变符号的点之间的零点
  4. 我验证找到的零是否真的为零,或者这是否为渐近线

    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))
    
  5. 这个算法似乎有效,除了我看到两个潜在的问题:

    1. 它不会检测到没有穿过x轴的零(例如,在像f(x) = x**2这样的函数中)但是,我不认为它可以在函数I&#中出现39; m评估。
    2. 如果离散点太远,它们之间可能会有一个零,并且算法可能无法找到它们。
    3. 您是否有更好的策略(仍然有效)来查找函数的所有零点?


      我不认为这个问题很重要,但对于那些好奇的人,我会处理光纤中波传播的特征方程。该函数看起来像(之前定义了Vellell是一个正整数):

      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)
      

4 个答案:

答案 0 :(得分:3)

为什么限于numpy? Scipy有一个完全符合你想要的包:

http://docs.scipy.org/doc/scipy/reference/optimize.nonlin.html

我学到的一课:数字编程很难,所以不要这样做:)


无论如何,如果你已经开始自己构建算法了,那么我链接的scipy上的文档页面(永远需要加载,顺便说一句)会为你提供一系列算法。我之前使用过的一种方法是将函数离散化为您的问题所必需的程度。 (也就是说,调整\ delta x使其比问题中的特征尺寸小得多。)这使您可以查找函数的特征(如符号的变化)。并且,您可以很容易地计算线段的衍生物(可能是从幼儿园开始),因此您的离散化函数具有明确定义的一阶导数。因为您已将dx调整为小于特征尺寸,所以保证不会错过对您的问题很重要的功能的任何功能。

如果您想知道“特征尺寸”的含义,请查看函数的某些参数,单位为长度或1 /长度。也就是说,对于某些函数f(x),假设x具有长度单位而f没有单位。然后寻找乘以x的东西。例如,如果要离散cos(\ pi x),则乘以x的参数(如果x具有长度单位)必须具有1 /长度的单位。所以cos(\ pi x)的特征尺寸是1 / \ pi。如果您的离散化远小于此,则不会出现任何问题。可以肯定的是,这个技巧并不总是有效,所以你可能需要做一些修补。

答案 1 :(得分:1)

我看到的主要问题是,如果你真的可以找到所有根 - 正如评论中已经提到的那样,这并不总是可行的。如果您确定您的功能不是完全病态的(已经提到sin(1/x)),那么下一个是您对错过根或其中几个的容忍度。换句话说,它是你准备去的长度,以确保你没有错过任何 - 据我所知,没有一般的方法来为你隔离所有的根,所以你必须自己做。你展示的是一个合理的第一步。几条评论:

  • 布伦特的方法确实是一个不错的选择。
  • 首先,处理异议。因为在你的函数中你在分母中有贝塞尔斯,你可以先解决他们的根 - 更好地查看它们,例如Abramovitch和Stegun(Mathworld link)。这将比使用您正在使用的ad hoc网格更好。
  • 您可以执行的操作,一旦找到两个根或不同之处x_1x_2,请在间隔[x_1+epsilon, x_2-epsilon]中再次运行搜索。继续,直到找不到根(布伦特的方法保证收敛到 a 根,只要有一个)。
  • 如果你不能列举所有的不同之处,你可能想要更加小心地确认一个候选人确实是一个分歧:给定x不要只检查f(x)是否大,检查那,例如|f(x-epsilon/2)| > |f(x-epsilon)| epsilon的几个值x_e(1e-8,1e-9,1e-10,类似的东西)。
  • 如果你想确保没有只接触零的根,请查找函数的极值,并为每个极值f(x_e)检查{{1}}的值。< / LI>

答案 2 :(得分:0)

我发现使用scipy.optimize.fsolve来实现自己的root查找器相对容易。

  • 想法:在间隔(start, stop)中找到任何零,并通过更改step反复调用fsolve来逐步调整x0的大小。使用相对较小的步长查找 all 的根。

  • 只能在一维中搜索零(其他维必须固定)。如果您还有其他需求,建议您使用sympy来计算分析溶液。

  • 注意:可能不会总是 找到 all 的零,但我看到它给出了相对较好的结果。我还将代码也放在了gist上,如有需要,我将对其进行更新。

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]

enter image description here

放大: enter image description here

RootFinder定义

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

测试功能

scipy.special.jnjn不再存在,但是我为这种情况创建了类似的测试功能。

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)

答案 3 :(得分:0)

我也遇到了这个问题,无法解决f(z)= 0等式,其中f是全纯函数。我想确保不要错过任何零,并最终开发了一种基于argument principle的算法。

它有助于找到位于复杂域中的零的确切数目。一旦知道零的数目,就更容易找到它们。但是,必须考虑两个问题:

  • 注意多重性:求解(z-1)^ 2 = 0时,您会得到两个零,因为z = 1计数了两次
  • 如果函数是亚纯函数(因此包含极点),则每个极点都会减少零的数量并中断对它们进行计数的尝试。