有什么方法可以限制 Julia 中 NLsolve 搜索的区域吗?

Rob*_*ati 5 nonlinear-optimization equation-solving julia

我正在尝试找到一个非线性(大致四次)方程的根。\n该方程始终有四个根,其中一对接近于零,一个大的正根和一个大的负根。我想确定任何一个接近零的根,但是nlsolve,即使最初的猜测非常接近这些根,似乎总是收敛于大的正根或负根。

\n

该函数的图本质上看起来像一个恒定的负值,具有接近零的(非常窄的)偶数阶极点,并逐渐上升到在大的正根和负根处交叉零。

\n

有什么方法可以限制 搜索的区域nlsolve,或者做一些事情让它对我的函数中这个极点的存在更加敏感?

\n

编辑:\n这里有一些重现问题的示例代码:

\n
using NLsolve\n\nfunction f!(F,x)\n    x = x[1]\n    F[1] = -15000 + x^4 / (x+1e-5)^2\nend\n# nlsolve will find the root at -122\nnlsolve(f!,[0.0])\n
Run Code Online (Sandbox Code Playgroud)\n

作为输出,我得到:

\n
Results of Nonlinear Solver Algorithm\n * Algorithm: Trust-region with dogleg and autoscaling\n * Starting Point: [0.0]\n * Zero: [-122.47447713915808]\n * Inf-norm of residuals: 0.000000\n * Iterations: 15\n * Convergence: true\n   * |x - x'| < 0.0e+00: false\n   * |f(x)| < 1.0e-08: true\n * Function Calls (f): 16\n * Jacobian Calls (df/dx): 6\n
Run Code Online (Sandbox Code Playgroud)\n

我们可以通过将目标函数转换为多项式来找到这种情况下的精确根:

\n
using PolynomialRoots\nroots([-1.5e-6,-0.3,-15000,0,1])\n
Run Code Online (Sandbox Code Playgroud)\n

产生

\n
4-element Array{Complex{Float64},1}:\n     122.47449713915809 - 0.0im\n    -122.47447713915808 + 0.0im\n -1.0000000813048448e-5 + 0.0im\n  -9.999999186951818e-6 + 0.0im\n
Run Code Online (Sandbox Code Playgroud)\n

我希望有一种方法可以在不知道目标函数的确切形式的情况下识别 x = -1e-5 处极点周围的一对根。

\n

编辑2:\n尝试Roots.jl

\n
using Roots\nf(x) = -15000 + x^4 / (x+1e-5)^2\n\nfind_zero(f,0.0) # finds +122... root\nfind_zero(f,(-1e-4,0.0)) # error, not a bracketing interval\nfind_zeros(f,-1e-4,0.0) # finds 0-element Array{Float64,1}\nfind_zeros(f,-1e-4,0.0,no_pts=6) # finds root slightly less than -1e-5\nfind_zeros(f,-1e-4,0.0,no_pts=10) # finds 0-element Array{Float64,1}, sensitive to value of no_pts\n
Run Code Online (Sandbox Code Playgroud)\n

我可以开始find_zeros工作,但它对no_pts参数和我选择的端点的确切值非常敏感。进行循环no_pts并获取第一个非空结果可能会起作用,但更确定的收敛结果会更好。

\n

EDIT3 :\n这里应用 Bogumi\xc5\x82 建议的 tanh 变换

\n
using NLsolve\nfunction f_tanh!(F,x)\n    x = x[1]\n    x = -1e-4 * (tanh(x)+1) / 2\n    F[1] = -15000 + x^4 / (x+1e-5)^2\nend\n\nnlsolve(f_tanh!,[100.0]) # doesn't converge\nnlsolve(f_tanh!,[1e5]) # doesn't converge\n\nusing Roots\nfunction f_tanh(x)\n    x = -1e-4 * (tanh(x)+1) / 2\n    return -15000 + x^4 / (x+1e-5)^2\nend\n\nfind_zeros(f_tanh,-1e10,1e10) # 0-element Array\nfind_zeros(f_tanh,-1e3,1e3,no_pts=100) # 0-element Array\nfind_zero(f_tanh,0.0) # convergence failed\nfind_zero(f_tanh,0.0,max_evals=1_000_000,maxfnevals=1_000_000) # convergence failed\n
Run Code Online (Sandbox Code Playgroud)\n

EDIT4:这种技术组合在大约 95% 的时间内至少识别出一个根,这对我来说已经足够好了。

\n
using Peaks\nusing Primes\nusing Roots\n\n# randomize pole location\na = 1e-4*rand()\nf(x) = -15000 + x^4 / (x+a)^2\n\n# do an initial sample to find the pole location\nl = 1000\nminval = -1e-4\nmaxval = 0\nm = []\nsample_r = []\nwhile l < 1e6\n    sample_r = range(minval,maxval,length=l)\n    rough_sample = f.(sample_r)\n    m = maxima(rough_sample)\n    if length(m) > 0\n        break\n    else\n        l *= 10\n    end\nend\nguess = sample_r[m[1]]\n\n# functions to compress the range around the estimated pole\ncube(x) = (x-guess)^3 + guess \nuncube(x) = cbrt(x-guess) + guess\nf_cube(x) = f(cube(x))\n\nshift = l \xc3\xb7 1000\nlow = sample_r[m[1]-shift]\nhigh = sample_r[m[1]+shift]\n# search only over prime no_pts, so no samplings divide into each other\n# possibly not necessary?\nfor i in primes(500)\n    z = find_zeros(f_cube,uncube(low),uncube(high),no_pts=i)\n    if length(z)>0\n        println(i)\n        println(cube.(z))\n        break\n    end\nend\n
Run Code Online (Sandbox Code Playgroud)\n

Bog*_*ski 2

如果您提供有关您的问题的更多信息,可以给出更多评论。

但总的来说:

  1. 看来您的问题是单变量,在这种情况下您可以使用 Roots.jl wherefind_zerofind_zeros给出您要求的接口(即允许指定搜索区域)
  2. 如果问题是多变量的,您可以在问题规范中选择如何执行此操作nlsolve(因为默认情况下不允许指定边界框 AFAICT)。最简单的是使用变量变换。例如,您可以对每个变量应用ai * tanh(xi) + bi转换选择ai和 ,bi以便将其限制在所需的区间内

您在定义中遇到的第一个问题是,您定义它的方式永远不会在您正在寻找的两个根附近f交叉,因为在编写 时没有足够的精度。您需要使用更高精度的计算:0Float641e-5

julia> using Roots

julia> f(x) = -15000 + x^4 / (x+1/big(10.0^5))^2
f (generic function with 1 method)

julia> find_zeros(f,big(-2*10^-5), big(-8*10^-6), no_pts=100)
2-element Array{BigFloat,1}:
 -1.000000081649671426108658262468117284940444265467160592853348997523986352593615e-05
 -9.999999183503552405580084054429938261707450678661727461293670518591720605751116e-06
Run Code Online (Sandbox Code Playgroud)

并设置no_pts得足够大以找到包围根的间隔。