Open topolarity opened 1 year ago
Worth mentioning also that the dynamics don't have to be very much faster for this to be a noticeable issue:
Notice that the Rosenbrock23() solution still exceeds (-1, 1)
significantly.
Of course, interpolating this algebraic solution is a whole separate can of worms.
I think, we have two problems:
1) As stated in the book of Hairer/Wanner, for stiffly accurate methods like Rodas4, Rodas5 and variants, the numerical solution of a simple algebraic condition like g(t,z)=0 results in a simplified newton iteration. For that reason the method Rodas4 and its embedded scheme both yield nearly exact results for z(t) = sin(2pi fre t) and the stepsize control fails. In that case we should set dtmax < 0.5/freq, a good choice could be 0.1/freq.
2) Why the stepsize control of Rosenbrock23 fails, is not clear to me. Here a large difference between the methods of order 2 and 3 should occur, since the method of order 3 ist not A-stable. In a simple own implementation with reltol=abstol=1.0e-4, I indeed need 2297 steps with a max. error of 6.4e-5 for a frequency = 10. When I apply Rosenbrock23 as below, only 7 steps are needed with an error = 32.9. Thus the implementation should be checked.
using OrdinaryDiffEq, Plots
function f!(du, u, fre, t)
du[1] = u[1] - sin(fre*2*pi*t)
end
u0 = [0.0]; tspan = (0.0, 1.0); M = zeros(1,1); fre = 10.0
fode = ODEFunction(f!, mass_matrix = M); prob = ODEProblem(fode, u0, tspan, fre)
sol = solve(prob, Rosenbrock23())
println(sol.destats)
err_max = maximum(abs.(sol[1,:] .- sin.(fre*2*pi*sol.t)))
println("err_max:",err_max)
Looking at rosenbrock_perform_step.jl, I see:
line 119/120
@.. broadcast=false vectmp=ifelse(cache.algebraic_vars,
dto6 * (veck₁ - 2 * veck₂ + veck₃))
and line 241/242
vectmp[i] = ifelse(cache.algebraic_vars[i], false,
dto6 * (veck₁[i] - 2 * veck₂[i] + veck₃[i]))
but line 389
utilde = dto6 * (k₁ - 2 * k₂ + k₃)
Do I understand correctly that algebraic variables are excluded from the error estimation? If so, the behaviour of Rosenbrock23 is clear.
I can only assume that this is because R(\infty)>1 applies to the stability function of the method of order p=3 and therefore it is not convergent for DAEs.
Maybe we should try to improve the method, I will start working on this topic.
Indeed, that behavior comes from:
And you're right. The underlying problem is that the 3rd order method used in the error estimation is not stable, and therefore it's not convergent in the algebraic variables and you get a wonky error estimate. This means that the only realistic way to handle them given the mathematical structure of the method is to exclude the algebraic variables from the error estimate, since otherwise you just always get dt<dtmin
failures from the error estimator not actually converging to zero as dt -> 0
. This means it's not an implementation issue with Rosenbrock23 but rather an algorithmic one. It would be nice to have a corrected 3rd order embedded form that is stiffly accurate, but I presume doing that and keeping it a RosW method at the same time is difficult.
I would be interested if there was a second order Rosenbrock that was adaptive and more stable though, but non RosW. Rosenbrock23 is quite a workhorse method in the ODE case in that space but indeed the instability of its error estimator sometimes makes it wonky (even for ODEs), but Shampine must have made a compromise there for a reason.
We should probably throw a warning in general on Rosenbrock23 for DAEs given that behavior then? It might be a sensible thing to do. https://github.com/SciML/OrdinaryDiffEq.jl/pull/2050 we could change that to just always warn on DAEs. It's still a nice method for a lot of situations though.
As stated in the book of Hairer/Wanner, for stiffly accurate methods like Rodas4, Rodas5 and variants, the numerical solution of a simple algebraic condition like g(t,z)=0 results in a simplified newton iteration. For that reason the method Rodas4 and its embedded scheme both yield nearly exact results for z(t) = sin(2pi fre t) and the stepsize control fails. In that case we should set dtmax < 0.5/freq, a good choice could be 0.1/freq.
If you're looking for the higher value topic, I think this one is it. I don't know of a single Rosenbrock that does residual adaptivity. A good example to look at is:
https://www.sciencedirect.com/science/article/abs/pii/S0168927404001187 (1-s2.0-S0168927404001187-main.pdf)
In code terms this is:
If we can just derive the maxima and minima points on the interpolation error and do something similar, this would be a good option to enable on DAEs. It does add extra f
evaluations, but if you're constructing the Jacobian already then 3 or 4 extra f
evaluations isn't the worst thing in the world. We can just alias it to another algorithm so that it's clear it has very different stepping behavior, i.e. Rodas5PR
or something for residual error estimator, but internally it's just a switch in that part of the code.
The derivation I think isn't too hard? It's just finding the critical points of the interpolation and hardcoding the error estimate to evaluate at each critical point. This would also make Rosenbrocks in DDEs better, which is a nice extra touch as well.
"We should probably throw a warning in general on Rosenbrock23 for DAEs given that behavior then? "
Regarding an alternative to Rosenbrock23: It is possible to derive a stiffly accurate A-stable Rodas23W with
Regarding residual adaptivity for rosenbrock methods: I will study this ...
It is possible to derive a stiffly accurate A-stable Rodas23W with
Wow, on paper that sounds strictly better than Rosenbrock23.
When implementing the new method, I came across a bug in Rodas3. It produces big errors for the example above. The stage 4 is missing in the implementation.
@muladd function perform_step!(integrator, cache::Rosenbrock34Cache, repeat_step = false) @unpack t, dt, uprev, u, f, p = integrator @unpack du, du1, du2, fsalfirst, fsallast, k1, k2, k3, k4, dT, J, W, uf, tf, linsolve_tmp, jac_config, atmp, weight = cache @unpack a21, a31, a32, C21, C31, C32, C41, C42, C43, b1, b2, b3, b4, btilde1, btilde2, btilde3, btilde4, gamma, c2, c3, d1, d2, d3, d4 = cache.tab
must be
@muladd function perform_step!(integrator, cache::Rosenbrock34Cache, repeat_step = false) @unpack t, dt, uprev, u, f, p = integrator @unpack du, du1, du2, fsalfirst, fsallast, k1, k2, k3, k4, dT, J, W, uf, tf, linsolve_tmp, jac_config, atmp, weight = cache @unpack a21, a31, a32, a41, a42, a43, C21, C31, C32, C41, C42, C43, b1, b2, b3, b4, btilde1, btilde2, btilde3, btilde4, gamma, c2, c3, d1, d2, d3, d4 = cache.tab
and in line 844
@.. broadcast=false u=uprev + a41 * k1 + a42 * k2 + a43 * k3
f(du, u, p, t + dt) #-- c4 = 1
integrator.stats.nf += 1
must included. This is running well. I can't say whether any changes to other functions are necessary
Interesting. Thanks for handling that. It's surprising that dropping that stage passed the 3rd order convergence tests, but I guess it just fell onto a different 3rd or method which is rare but possible.
https://github.com/SciML/OrdinaryDiffEq.jl/pull/2079 handles Rosenbrock23, so I'm going to update this title to just be about the remaining issue with interpolations.
In methods Rodas4, 42, 4P, 4P2, 5, 5P we could include an additional error test for ||M y' - f(t,y)|| / abstol < 1. Here y is the interpolation and we could check at time t = t_n + h/2. This test would cost one additional function evaluation. This gave good results in a preliminary program. However, it would be nice if there were an additional option to switch this test on or off. I could implement the test in rosenbrock_perform_step, but I don't know how to integrate an additional option.
See recent Preprint and tests in https://github.com/hbrs-cse/RosenbrockMethods
A pull request concerning Rodas5Pr and Rodas5Pe will come soon.
This is in essence the same problem as https://github.com/SciML/OrdinaryDiffEq.jl/issues/2045, except that differential variables are present but evolve far too slowly to provide indirect error control for algebraic variables.
Solve and plot with a variety of solvers:
results in:
In summary:
sol.t
but is heavily under-sampledsol.t