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))
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
中再次运行搜索。继续,直到找不到更多的根为止(Brent的方法保证收敛到一个根,只要有一个根)李>[x\u 1+ε,x\u 2-ε]
- 如果你不能列举所有的差异,你可能需要更仔细地验证一个候选者是否确实是一个差异:给定
不要只检查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)