-
-
Notifications
You must be signed in to change notification settings - Fork 235
Prepare for changes of non-linear solvers #912
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
34abbed to
23a6170
Compare
23a6170 to
148cbad
Compare
|
OK, so two things:
|
|
@YingboMa might want to dig in a bit here. |
| update_W!(integrator, cache, dt, false) | ||
| @.. gprev = dt*0.5*(du₂ - f2ⱼ₋₁) + dt*(0.5 - μs₁)*(du₁ - f1ⱼ₋₁) | ||
| nlsolver.linsolve(vec(tmp),get_W(nlsolver),vec(gprev),false) | ||
| get_linsolve(nlsolver)(vec(tmp),get_W(nlsolver),vec(gprev),false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels pretty bad to me (appears in multiple other places as well). I think we should rather define a generic method linsolve(out, nlsolver, in, flag; kwargs...) (or maybe some other less generic name) (in DiffEqBase?) that's defined as f_linsolve = get_linsolve(nlsolver); W = get_W(nlsolver); f_linsolve(vec(out), W, vec(in), flag; kwargs...).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah that makes sense.
|
Reading through IV.8 ("Stopping criterion") in Hairer's book gives me the impression that the convergence/divergence checks of the non-linear solvers are not completely according to his suggestions. Denoting the current iterate by
So, if we want to include the tolerances implicitly in the norm it should be done in one specific way throughout all iterations, if we want to follow Hairer's reasoning. In particular, we should NOT make the current change and make it dependent on the iteration step. However, before it was also iteration-dependent, but only for some algorithms. A bit unfortunate is also that usually the error estimators are based on Moreover, it seems the criterion for (fast) convergence is different in our implements. We could simplify the convergence check to A third point is, that I'm not sure anymore if removing the updates of the residuals for Anderson acceleration is a good idea. If it's not updated, the triangle inequality cannot be applied to the sum of |
|
Way back we were matching Hairer, but that ended up being very inefficient, so we moved more towards matching Sundials and Kennedy and Carpenter. @YingboMa will keep continuing on the Jacobian reuse so I wouldn't mix changes to this during the refactor. Any changes here needs lots of benchmarks |
|
I completely agree, we shouldn't mix any algorithm changes with the refactoring. In fact, we have done that already (changing the computation of the residuals and not touching
Is that the reason for setting |
|
Quick update: found a stupid bug, the type signature of |
|
OK, I think I tracked down the issue. Apparently the crucial point is (at least for the Newton algorithm) to return not the current but the next iterate. I'm pretty certain that's the main culprit but I changed a bunch of things during the debugging, so I want to recheck once again. |
|
Of course, it's more complicated... I could fix most issues but there are two main annoyances left:
using OrdinaryDiffEq, Test, ParameterizedFunctions
d_alembert = @ode_def DAlembert begin
dx = a - b*x + c*t
end a b c
function d_alembert_analytic(u0,p,t::Number)
a,b,c = p
ebt = exp(b*t)
@. exp(-b*t)*(-a*b + c + ebt*(a*b + c*(b*t - 1)) + b^2 * u0)/(b^2)
end
p = (1., 2., 3.)
u0 = [1.0]
tspan = (0.0,10.0)
prob = ODEProblem(ODEFunction(d_alembert.f,
jac = d_alembert.jac,
analytic=d_alembert_analytic),
u0,tspan,p)
println("Rosenbrock23")
sol = solve(prob,Rosenbrock23(),abstol=1e-8,reltol=1e-8)
@show sol.destats
@show sol.errors
@test sol.errors[:l2] < 1e-7
println("Rodas4")
sol = solve(prob,Rodas4(),abstol=1e-10,reltol=1e-10)
@show sol.destats
@show sol.errors
@test sol.errors[:l2] < 1e-7
println("Veldd4")
sol = solve(prob,Veldd4(),abstol=1e-10,reltol=1e-10)
@show sol.destats
@show sol.errors
@test sol.errors[:l2] < 1e-7
println("Rodas5")
sol = solve(prob,Rodas5(),abstol=1e-10,reltol=1e-10)
@show sol.destats
@show sol.errors
@test sol.errors[:l2] < 1e-7
println("Trapezoid")
sol = solve(prob,Trapezoid(),abstol=1e-10,reltol=1e-10)
@show sol.destats
@show sol.errors
@test sol.errors[:l2] < 2e-6
println("KenCarp3")
sol = solve(prob,KenCarp3(),abstol=1e-10,reltol=1e-10)
@show sol.destats
@show sol.errors
@test sol.errors[:l2] < 8e-4
println("KenCarp4")
sol = solve(prob,KenCarp4(),abstol=1e-10,reltol=1e-10)
@show sol.destats
@show sol.errors
@test sol.errors[:l2] < 1e-7
println("KenCarp4 - Functional")
sol = solve(prob,KenCarp4(nlsolve=NLFunctional()),abstol=1e-10,reltol=1e-10)
@show sol.errors
println("TRBDF2 - Functional")
sol = solve(prob,TRBDF2(nlsolve=NLFunctional()),abstol=1e-10,reltol=1e-10)
@show sol.errors
println("TRBDF2")
sol = solve(prob,TRBDF2(),abstol=1e-10,reltol=1e-10)
@show sol.destats
@show sol.errors
@test sol.errors[:l2] < 2e-6
I'm trying to get OrdinaryDiffEq to the new design in https://github.com/JuliaDiffEq/OrdinaryDiffEq.jl/tree/nlsolve_full, starting with a basic restructuring that does not affect the algorithm. Then step by step different bugs/inconsistencies/inefficiencies can be addressed. Since I'm not happy with building against the DiffEqBase branch and it slows down the development process to depend on changes in two packages, I moved all the non-linear solver code apart from the status and the abstract types to OrdinaryDiffEq for now, so all changes are restricted to OrdinaryDiffEq. If it's working, the nonlinear solvers can be moved back to DiffEqBase. I would be happy for any pointers why TRBDF2 and KenCarp4 in the example above cannot reproduce the results on master. |
|
It's fine if they don't reproduce the same results but if they do well on the benchmarks. We also need to get @isaacsas 's example into the stiff ODE benchmarks. |
| nlsolve_f(integrator.f, unwrap_alg(integrator, true)) | ||
|
|
||
| function iip_generate_W(alg,u,uprev,p,t,dt,f,uEltypeNoUnits) | ||
| function DiffEqBase.build_J_W(alg,u,uprev,p,t,dt,f,uEltypeNoUnits,::Val{true}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should not be in integrator_utils. It's a derivative utility, so it should go with the derivative utilities.
| nlsolve!(integrator, cache) = DiffEqBase.nlsolve!(cache.nlsolver, cache.nlsolver.cache, integrator) | ||
| nlsolve!(nlsolver::NLSolver, integrator) = DiffEqBase.nlsolve!(nlsolver, nlsolver.cache, integrator) | ||
|
|
||
| DiffEqBase.nlsolve_f(f, alg::OrdinaryDiffEqAlgorithm) = f isa SplitFunction && issplit(alg) ? f.f1 : f |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this doesn't belong here either.
| nlsolver.uf.p = p | ||
| J = jacobian(nlsolver.uf,uprev,integrator) | ||
| @unpack uf = DiffEqBase.get_cache(nlsolver) | ||
| uf.t = t |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uf.f = ...
|
|
||
| function calc_J(nlsolver, integrator, cache::OrdinaryDiffEqConstantCache, is_compos) | ||
| @unpack t,dt,uprev,u,f,p = integrator | ||
| function calc_J(nlsolver, integrator, cache::OrdinaryDiffEqConstantCache) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we still need 2 dispatches here? They do the same thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely should remove one. It's just such a massive change that I wanted to do the derivative_utils.jl cleanup in a separate PR/commit.
| `jac_config` of the cache. | ||
| """ | ||
| function calc_J!(integrator, cache::OrdinaryDiffEqCache, is_compos) | ||
| function calc_J!(integrator, cache::OrdinaryDiffEqCache) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just pass in J and get rid of all of this extra code?
Let's make use of the new CI test feature 😃