Closed bradcarman closed 4 months ago
using OrdinaryDiffEq
dt = 1e-6
abstol = 1e-7
d=1
k=1000
F = 100
autodiff=false
function f2(du,u,p,t)
F, k, d = p
x, dx, ddx = u
du[1] = dx
du[2] = ddx
du[3] = (d*dx + k*x^2) - (F)
end
fmm2 = ODEFunction(f2; mass_matrix=[1 0 0;0 1 0;0 0 0])
prob2 = ODEProblem(fmm2, [0.0, F/d, 0.0], (0.0, 0.01), [F, k, d])
ref1 = solve(prob2, Rodas5P(;autodiff); abstol, dt, initializealg=NoInit())
plot(ref1)
ref2 = solve(prob2, ImplicitEuler(;autodiff, nlsolve=NLNewton(check_div = false, always_new=true)); abstol, dt, adaptive=false, initializealg=NoInit())
plot(ref2)
The solution is fine with that. If adaptivity is turned off then the nonlinear solver needs to be tweaked in order to not easily give up. It easily gives up on purpose because the quickest thing to do is just change dt
. It's a common strategy that all stiff ODE solvers do, and it's also why most software doesn't allow you to set adaptive=false
.
We should when adaptive=false
set those two options. But instead of doing that, I want to just complete https://github.com/SciML/OrdinaryDiffEq.jl/issues/1570 and https://github.com/SciML/NonlinearSolve.jl/issues/241, in which case I would propose a very different handling. Since that's scheduled for the next two weeks, no reason to do anything here and just finish the real answer.
Thanks Chris, that sounds good. A couple points:
ImplicitEuler
with adaptivity on still doesn't work, gives DtLessThanMin
. I need to set reltol
very high to get a solution which is then not stable.ImplicitEuler
only needs check_div=false
, as seen below, it does work with larger time steps and always_new=false
I would assume that if ImplicitEuler
worked with non-adaptive (with any size time step), then it should work with adaptive mode. This is what leads me to post this as a "bug".
using OrdinaryDiffEq
using SimpleEuler: BackwardEuler
using Plots
dt = 1e-4
abstol = 1e-3
d=100
k=1000
F = 100
autodiff=true
always_new=false
function f3(du,u,p,t)
F, k, d = p
x, dx, ddx, dddx = u
du[1] = dx
du[2] = ddx
du[3] = dddx
du[4] = (d*dx + k*x^2) - (F)
end
fmm3 = ODEFunction(f3; mass_matrix=[1 0 0 0;0 1 0 0;0 0 1 0;0 0 0 0])
prob3 = ODEProblem(fmm3, [0.0, F/d, 0.0, 0.0], (0.0, 2), [F, k, d])
ref3 = solve(prob3, ImplicitEuler(;autodiff, nlsolve=NLNewton(;always_new, check_div=false)); abstol) #always fails: DtLessThanMin
ref3 = solve(prob3, ImplicitEuler(;autodiff, nlsolve=NLNewton(;always_new, check_div=false)); abstol, dt, adaptive=false) #works
sol3 = solve(prob3, BackwardEuler(;autodiff, always_new); abstol, dt, adaptive=false) #works
plot(ref3; idxs=3)
plot!(sol3; idxs=3)
ImplicitEuler with adaptivity on still doesn't work, gives DtLessThanMin. I need to set reltol very high to get a solution which is then not stable.
Yes ImplicitEuler is first order so solving to a low tolerance is pretty infeasible and requires a really small dt.
sol3 = solve(prob3, BackwardEuler(;autodiff, always_new); abstol, dt, adaptive=false) #works
That's kind of a misnomer since it "works" because it's ignoring the error and not actually solving to tolerance. Only the nonlinear solver iterations are bounded, not the truncation error.
Just as a point of reference, I'd just like to present a different example as a different perspective on this issue. The problem that Brad is proposing is effectively:
$$\dot{x}+1000 x^2 = 100$$
From this equation (and this equation alone), we can determine what $x(t)$ is. My point is not that we can arrive at a closed form solution (at least I'm not aware of a close form solution for this form but it's possible one exists) but rather that all the information necessary is provided. Now, generally speaking, to arrive at a solution, we need to use a differential equation solver.
But what I'd just like to introduce mostly as a thought experiment is the idea that imagine there was a closed form solution here (that we could derive from the equation above) such that:
$$x(t) = f(x)$$
If that were the case and we could skip the numerical solution altogether, then what are the implications of adding the following relations:
$$ \begin{array} \ x(t) & = & f(x) \ xd(t) & = & \dot{x}(t) \ x{dd}(t) & = & \dot{x_d}(t) \end{array} $$
Because we know $x(t) = f(t)$, then we get:
$$ \begin{array} \ x(t) & = & f(x) \ xd(t) & = & f'(t) \ x{dd}(t) & = & f''(t) \end{array} $$
The key thing I want to point out here is that we aren't actually integrating anything, we are only differentiating. Now, imagine you plugged the equations above into ImplicitEuler
. What would you expect it to do? There is nothing to integrate here. It seems to me that this is really no different than the problem @bradcarman is trying to solve. The key point here is that this is an index 2 DAE and in order to solve it you need to differentiate one of the equations (twice) in order to solve for all variables. As far as I know, ImplicitEuler
is designed for semi-explicit DAEs at best (which are index 1).
The fact that @bradcarman can solve this by doing a backward Euler substitution seems to me just evidence that such a substitution is actually being used to differentiate in his case, not to integrate. In other words, it leads to this:
$$ \begin{array} \ x(t{i+1}) & = & f(x{i+1}) \ xd(t{i+1}) & = & \frac{x(t{i+1})-x(t)}{\delta t} \ x{dd}(t_{i+1}) & = & \frac{xd(t{i+1})-x_d(t)}{\delta t} \end{array} $$
...which is equivalent to:
$$ \begin{array} \ x(t{i+1}) & = & f(x{i+1}) \ xd(t{i+1}) & = & \frac{f(t_{i+1})-f(ti)}{\delta t} \ x{dd}(t{i+1}) & = & \frac{\frac{f(t{i+1})-f(t_i)}{\delta t}-\frac{f(ti)-f(t{i-1})}{\delta t}}{\delta t} \end{array} $$
My point here is that substituting backward Euler into an equation system isn't necessarily solving any differential equations. I'm not saying it doesn't "work", I'm just pointing out that it isn't necessarily accomplishing the same thing that ImplicitEuler
is trying to accomplish.
As of DifferentialEquations v7.12.0 this is now working... Works with both ImplicitEuler()
and default Rodas5P()
and all others I tried.
Code with analytical comparison...
using DifferentialEquations
# F = d*dx + k*x^2
function f(du,u,p,t)
F, k, d = p
x, dx, ddx = u
du[1] = dx #D(x) ~ dx
du[2] = ddx #D(dx) ~ ddx
du[3] = (F) - (d*dx + k*x^2)
end
abstol = 1e-3
d=1
k=1000
F = 100
odef = ODEFunction(f; mass_matrix=[1 0 0;0 1 0;0 0 0])
prob = ODEProblem(odef, [0.0, F/d, 0], (0.0, 0.01), [F, k, d])
sol = solve(prob; abstol) # Success
sol′ = solve(prob, ImplicitEuler(); abstol) # Success
c₁ = 0.0
t = 0:0.0001:0.01
x(t) = (sqrt(F)/sqrt(k))*tanh((c₁*d*sqrt(F)*sqrt(k)+ sqrt(F)*sqrt(k)*t)/d)
dx(t) = ForwardDiff.derivative(x, t)
ddx(t) = ForwardDiff.derivative(dx, t)
fig = Figure(resolution=(1600,400))
ax = Axis(fig[1,1]; ylabel="x(t)", xlabel="t")
lines!(ax, t, x.(t))
scatter!(ax, sol.t, sol[1,:])
ax = Axis(fig[1,2]; ylabel="dx(t)", xlabel="t")
lines!(ax, t, dx.(t))
scatter!(ax, sol.t, sol[2,:])
ax = Axis(fig[1,3]; ylabel="ddx(t)", xlabel="t")
lines!(ax, t, ddx.(t))
scatter!(ax, sol.t, sol[3,:])
fig
Bug and MWE
The following problem does not solve (i.e. gives
Warning: Newton steps could not converge and algorithm is not adaptive. Use a lower dt.
)Expected behavior
This problem solves without issue using a clean implementation of the Implicit/Backwards Euler method. See https://github.com/bradcarman/SimpleEuler.jl/blob/main/test/runtests.jl for more information.
Note: this problem also solves correctly using
f0
andf1
versions of the problem, as also seen in the SimpleEuler.jl runtests.jl file. Adding additional derivatives to be calculated should not be causing theImplicitEuler
method to fail.Additionally the Modelica problem solves without issue and gives the same result: link
Here is a plot of
ddx
from ref0, ref1, sol0, sol1, sol2, and sol3. * -ddx
calculated manuallyHere is the modelica result:
Environment