如评论中所述,当我运行您的代码(在 SciPy 中使用 LSODE)时,我得到
>>> result = odeint(f,y0,t)
lsoda-- warning..internal t (=r1) and h (=r2) are
such that in the machine, t + h = t on the next step
(h = step size). solver will continue anyway
in above, r1 = 0.2036188800000D+10 r2 = 0.1292689215500D-07
lsoda-- warning..internal t (=r1) and h (=r2) are
such that in the machine, t + h = t on the next step
(h = step size). solver will continue anyway
in above, r1 = 0.2036188800000D+10 r2 = 0.1292689215500D-07
lsoda-- at current t (=r1), mxstep (=i1) steps
taken on this call before reaching tout
in above message, i1 = 500
in above message, r1 = 0.2036188801501D+10
/usr/lib64/python2.7/site-packages/scipy/integrate/odepack.py:218: ODEintWarning: Excess work done on this call (perhaps wrong Dfun type). Run with full_output = 1 to get quantitative information.
warnings.warn(warning_msg, ODEintWarning)
>>>
>>> for i in range(0,6):
... print(result[-1][i])
...
9.07638086329e+223
9.48797814966e-81
1.04657495534e-312
2.52059148034e-306
9.94646703738e+86
8.48798317088e-313
这就是我认为您必须通过错误表示的意思。我不确定它为什么会这样做,但必须有一些错误的公差默认值,导致 lsode 的时间步长非常低。多步方法必须使用像欧拉这样的低阶方法开始,因此它们的误差估计器在前几步中非常糟糕。我认为那里会出错。
我使用DifferentialEquations.jl对此进行了一些调查。它已经准备好了一堆算法,因此可以更轻松地进行这种分析。设置问题与您的 Python 代码非常相似:
myu=398600.4418E+9
J2=1.08262668E-3
req=6378137
t0=86400*2.3567000000000000E+04
tN= 86400*2.3567250000000000E+04
y0 =[-9.0656779992979474E+05, -4.8397431127596954E+06, -5.0408120071376814E+06, -7.6805804020022015E+02, 5.4710987127502622E+03, -5.1022193482389539E+03]
function f(t,y,dy)
r2=(y[1]^2 + y[2]^2 + y[3]^2)
r3=r2^(3/2)
w=1+1.5J2*(req*req/r2)*(1-5y[3]*y[3]/r2)
dy[1] = y[4]
dy[2] = y[5]
dy[3] = y[6]
dy[4] = -myu*y[1]*w/r3
dy[5] = -myu*y[2]*w/r3
dy[6] = -myu*y[3]*w/r3
end
t = linspace(t0,tN)
using DifferentialEquations
prob = ODEProblem(f,y0,(t0,tN))
我认为这里不需要说太多评论,因为它几乎是直接复制加上将基于 0 的索引更改为基于 1 的索引。首先我用 LSODA 试过这个:
using LSODA
@time sol = solve(prob,lsoda(),abstol=1e-12)
#0.000690 seconds (17.74 k allocations: 373.578 KB)
println(sol[end])
#[1.20141e6,1.14517e6,7.13797e6,74.8469,-7231.03,1079.12]
当我用日晷运行它时:
@time sol = solve(prob,CVODE_Adams(),abstol=1e-14)
#0.000730 seconds (8.87 k allocations: 187.641 KB)
println(sol[end])
#[1.1016e6,3.68894e6,6.3157e6,513.044,-6351.17,3642.44]
似乎一切正常。当我使用 FORTRANdopri
方法时(这只是 Hairer 方法的包装):
using ODEInterfaceDiffEq
@time sol = solve(prob,dopri5(),abstol=1e-12)
#0.000885 seconds (7.83 k allocations: 180.469 KB)
println(sol[end])
[-965604.0,-1.84395e6,-5.65673e6,-292.419,7785.53,-2437.98]
您可以看到错误越来越多。我检查了使用Dormand-Prince 4/5 算法的更现代的 Julia 重新实现
@time sol = solve(prob,DP5(),abstol=1e-12)
#0.000467 seconds (6.82 k allocations: 155.828 KB)
println(sol[end])
[-9.60193e5,-1.96886e6,-5.61328e6,-319.081,7737.2,-2594.4]
看起来这种方法很容易出错。我建议使用更新的算法,比如Tsit5()
and Vern7()
。您可以从我发布的基准测试中看到它们比旧的 Fortran 代码更有效,我将在此处进行更多讨论。
@time sol = solve(prob,Tsit5(),abstol=1e-12)
#0.000485 seconds (8.97 k allocations: 218.261 KB)
println(sol[end])
[1.03967e6,-2.41448e6,6.47286e6,-527.843,-7082.73,-2564.45]
@time sol = solve(prob,Vern7(),abstol=1e-13)
#0.001131 seconds (11.19 k allocations: 266.861 KB)
println(sol[end])
[1.10658e6,-1.5227e6,6.79846e6,-369.784,-7333.39,-1590.5]
所以看起来高阶方法(CVODE_Adams
和Vern7
)是同一个方向。要查看正确的解决方案应该是什么,我们可以使用更高精度的数字重新解决它。在这里,我们将BigFloat
在算法中使用 s。我们可以通过改变我们构建问题类型的数字类型来做到这一点:
y0_big =big([-9.0656779992979474E+05, -4.8397431127596954E+06, -5.0408120071376814E+06, -7.6805804020022015E+02, 5.4710987127502622E+03, -5.1022193482389539E+03])
t0_big=big(86400*2.3567000000000000E+04)
tN_big=big(86400*2.3567250000000000E+04)
prob_big = ODEProblem(f,y0_big,(t0_big,tN_big))
然后用更高精度的数字解决问题:
@time sol = solve(prob_big,Vern7(),abstol=1e-20,reltol=1e-20)
#5.124532 seconds (20.51 M allocations: 1020.807 MB, 29.95% gc time)
println(sol[end])
BigFloat[1.1053381662331820146336046671754933670595930944811486000087489128455927171218624720058718205588449974168956065779438050345e+06,-1.5416202537237067975875829305475944291202837487809289389890156042765665624065710316274224354945044382194625246683296005503e+06,6.7926473294913369302731863142467582412609188710382594274952998303466256241086526296652443665837323225497701006408603155527e+06,-3.7312828821058534895294734492513244053231049180643905427777841548594598586886829969951338572602652254542799098196952577885e+02,-7.3297961245134371967675775894582329860535459110035746501350371674509262650960485899126786425646757794442551813929572586948e+03,-1.61096155619704279518364795928351631960055375601824673571833246176656122912319418152730921855088538873761614569864638284e+03]
@time sol = solve(prob_big,Tsit5(),abstol=1e-20,reltol=1e-20)
#46.843028 seconds (202.64 M allocations: 9.848 GB, 21.61% gc time)
println(sol[end])
BigFloat[1.1053381662331820147067348726438400371709496335752162175134992231756774664804231225139143546847966065233311732755080517428e+06,-1.5416202537237067964033929090489735675629358124880169057038770565130982115037191409720907051171224745683301005104028510218e+06,6.7926473294913369306108500165460363424229267867186882151308990443549948472674738460107391298061753478752197008344571867852e+06,-3.7312828821058534874482439660747198554520591273413619386987734456375288506664244304076979140861220572371658224120877522505e+02,-7.3297961245134371970128642262106938950880875625957763378149307834830636800982964288295248480875424454937258559641758284114e+03,-1.6109615561970427939085757874332499247300266093609788206242850106668114350752364680610553537125616823780476995508356156203e+03]
这由两种算法以高精度确认,因此必须是该值。
回顾
所以让我们回顾一下。时间确实可以说得很快,但至少在 Julia 中,获得高精度没有问题。较旧的方法喜欢lsoda
并且dopri5
具有更高的错误。甚至DP5
。自从制定这些算法以来,方法和系数已经得到了改进,这就是我不再推荐它们的原因。日晷和DifferentialEquations.jl (OrdinaryDiffEq.jl) 中较新的Runge-Kutta 方法要准确得多。
这表明有几个错误来源。一方面,每种方法都有离散化误差,这导致它们之间存在差异。但这应该以足够小的公差消失。由于浮点问题(第二个错误来源),情况不一定如此。这是一个非常长的时间跨度,因此您需要非常低的容差才能避免大量的错误累积。结果证实了这一点,该结果表明,如果我们得到更高的精度数字(大约没有得到垃圾的公差,我们需要降低!)BigFloat
,我们只能得到前几位数字。1e-13
但最后,即使在使用任意精度数字、降低容差并且基本上让两个算法在前 10 位左右的小数位上收敛到相同的结果之后,我们仍然偏离您提出的“真实结果”。这可能是因为初始条件也略微不准确,随着时间的推移会导致第一位数字出现错误。初始条件的误差会增长得非常快,因此您需要确保它非常准确!
(但是,我无法在这里重新创建 SciPy 问题......我不知道为什么)
因此,您需要小心所有 3 个错误来源,如此处所示。
添加一个好的测量图。
plot(sol)

我不知道你的问题,但你应该经常仔细检查情节!
我们可以绕过浮点错误吗?
下一个要解决的问题是,我们能绕过浮点错误吗?是的,我们可以重新调整问题。没有理由对每个数字都有这么大的数字!相反,我们可以通过更改单位将每个数字向下移动 10^3。我建议在因变量和时间上都改变这样的单位,看看它是否能用Float64
's 得到更好的结果。
但最重要的是,我希望这显示了一种调查结果的好方法。
编辑:更多验证
因为评论中提到了,所以我用
@time sol = solve(prob_big,Vern9(),abstol=1e-25,reltol=1e-25)
println(sol[end])
BigFloat[1.1053381662331822596169776849641239592819519884844738459016726262029644062128757651516073415939693720405423642205909058064e+06,-1.5416202537237025194193402796238592848398830150314947716991093949659223769788623589030795132080240499488785780018224381598e+06,6.7926473294913380453538931349439465820220085181755818261687088166926884907951721678701419759674780893612240340931187099615e+06,-3.7312828821058461338614861968657625567908036695262270829525438975640521184553269025483381748908274236026477510068778050456e+02,-7.3297961245134380914206076512518039416894076161354630688132265052545303764963243830516528946565213622293265857749536691384e+03,-1.6109615561970383265735660519068721854582438082504069296606070807069042413835561362598331583440076713255077056006112545402e+03]
@time sol = solve(prob_big,Feagin14(),abstol=1e-25,reltol=1e-25)
println(sol[end])
BigFloat[1.1053381662331822596169777141870567234610511965557086835077483653533437799111462386523440877691562284278052381367094500505e+06,-1.5416202537237025194193397999916272100650863657026496400673463469591815591302649671026419236684086191564320676752769500241e+06,6.7926473294913380453538932693163486282736145849617871379561032756359556363271724242234264204395645390979862595520421289454e+06,-3.7312828821058461338614853616747368391649624257444346156270400632544621072526444156009615705008474094021665763109262437141e+02,-7.3297961245134380914206077511660934608408482056577325775582350370158926687300526721134971356964860345847978865296553096107e+03,-1.6109615561970383265735655400955954012952369987169718357788394884494500522058249689736956400471634474979232165731626140887e+03]
如此多的不同算法以非常高的精度和准确度给出了前 14 位小数的相同结果。因此,对于这些输入,这是您可以信任的值。如果该值关闭,则关闭的是输入数字(初始条件、常数或时间)。
另一个编辑
我们在 JuliaDiffEq Gitter 聊天频道中解决了这个问题。z 加速度组件中存在错误。新脚本是:
myu=398600.4418E+9
J2=1.08262668E-3
req=6378137
t0=86400*2.3567000000000000E+04
tN= 86400*2.3567250000000000E+04
y0 =[-9.0656779992979474E+05, -4.8397431127596954E+06, -5.0408120071376814E+06, -7.6805804020022015E+02, 5.4710987127502622E+03, -5.1022193482389539E+03]
function f(t,y,dy)
r2=(y[1]^2 + y[2]^2 + y[3]^2)
r3=r2^(3/2)
w=1+1.5J2*(req*req/r2)*(1-5y[3]*y[3]/r2)
w2=1+1.5J2*(req*req/r2)*(3-5y[3]*y[3]/r2)
dy[1] = y[4]
dy[2] = y[5]
dy[3] = y[6]
dy[4] = -myu*y[1]*w/r3
dy[5] = -myu*y[2]*w/r3
dy[6] = -myu*y[3]*w2/r3
end
t = linspace(t0,tN)
using DifferentialEquations
prob = ODEProblem(f,y0,(t0,tN))
sol = solve(prob,DP5(),abstol=1e-13,reltol=1e-13)
println(sol[end])
[1.09206e6,-1.97193e6,6.68529e6,-408.338,-7208.31,-2057.99]
它击中了真正的解决方案。感谢@crbinz