-
-
Couldn't load subscription status.
- Fork 235
Adaptive IDSolve #2881
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
base: master
Are you sure you want to change the base?
Adaptive IDSolve #2881
Changes from all commits
d84627c
f02492f
c96581c
e54472a
cd0fbf6
c25238c
e9a58fa
bdfad62
4b597fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,38 +4,40 @@ authors = ["vyudu <[email protected]>"] | |
| version = "1.2.0" | ||
|
|
||
| [deps] | ||
| SimpleNonlinearSolve = "727e6d20-b764-4bd8-a329-72de5adea6c7" | ||
| SymbolicIndexingInterface = "2efcf032-c050-4f8e-a9bb-153293bab1f5" | ||
| UnPack = "3a884ed6-31ef-47d7-9d2a-63182c4928ed" | ||
| ConcreteStructs = "2569d6c7-a4a2-43d3-a901-331e8e4be471" | ||
| DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" | ||
| Reexport = "189a3867-3050-52da-a836-e630ba90ab69" | ||
| NonlinearSolveBase = "be0214bd-f91f-a760-ac4e-3421ce2b2da0" | ||
| NonlinearSolveFirstOrder = "5959db7a-ea39-4486-b5fe-2dd0bf03d60d" | ||
| OrdinaryDiffEqCore = "bbf590c4-e513-4bbe-9b18-05decba2e5d8" | ||
| Reexport = "189a3867-3050-52da-a836-e630ba90ab69" | ||
| SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" | ||
| SymbolicIndexingInterface = "2efcf032-c050-4f8e-a9bb-153293bab1f5" | ||
|
|
||
| [extras] | ||
| JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" | ||
| OrdinaryDiffEqSDIRK = "2d112036-d095-4a1e-ab9a-08536f3ecdbf" | ||
| Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" | ||
| Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" | ||
| AllocCheck = "9b6a8646-10ed-4001-bbdc-1d2f46dfbb1a" | ||
| [sources] | ||
| OrdinaryDiffEqCore = {path = "../OrdinaryDiffEqCore"} | ||
|
|
||
| [compat] | ||
| Test = "1.10.0" | ||
| AllocCheck = "0.2" | ||
| Aqua = "0.8.11" | ||
| ConcreteStructs = "0.2.3" | ||
| DiffEqBase = "6.176" | ||
| JET = "0.9.18, 0.10.4" | ||
| NonlinearSolveBase = "2.0.0" | ||
| NonlinearSolveFirstOrder = "1.9.0" | ||
| OrdinaryDiffEqCore = "1.29.0" | ||
| OrdinaryDiffEqSDIRK = "1.6.0" | ||
| Reexport = "1.2" | ||
| SciMLBase = "2.99" | ||
| SimpleNonlinearSolve = "2.7" | ||
| OrdinaryDiffEqCore = "1.29.0" | ||
| Aqua = "0.8.11" | ||
| SymbolicIndexingInterface = "0.3.38" | ||
| Test = "1.10.0" | ||
| julia = "1.10" | ||
| JET = "0.9.18, 0.10.4" | ||
| UnPack = "1.0.2" | ||
| AllocCheck = "0.2" | ||
| DiffEqBase = "6.176" | ||
| Reexport = "1.2" | ||
|
|
||
| [extras] | ||
| AllocCheck = "9b6a8646-10ed-4001-bbdc-1d2f46dfbb1a" | ||
| Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" | ||
| JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" | ||
| OrdinaryDiffEqSDIRK = "2d112036-d095-4a1e-ab9a-08536f3ecdbf" | ||
| Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" | ||
|
|
||
| [targets] | ||
| test = ["OrdinaryDiffEqSDIRK", "Test", "JET", "Aqua", "AllocCheck"] | ||
|
|
||
| [sources.OrdinaryDiffEqCore] | ||
| path = "../OrdinaryDiffEqCore" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,9 +11,33 @@ end | |
| SciMLBase.isdiscrete(alg::IDSolve) = true | ||
|
|
||
| isfsal(alg::IDSolve) = false | ||
| alg_order(alg::IDSolve) = 0 | ||
| alg_order(alg::IDSolve) = 1 | ||
| beta2_default(alg::IDSolve) = 0 | ||
| beta1_default(alg::IDSolve, beta2) = 0 | ||
|
|
||
| dt_required(alg::IDSolve) = false | ||
| isdiscretealg(alg::IDSolve) = true | ||
|
|
||
| isadaptive(alg::IDSolve) = true | ||
|
|
||
| # @concrete struct ConvergenceRateTracing | ||
| # inner_tracing | ||
| # end | ||
|
|
||
| # @concrete struct ConvergenceRateTraceTrick | ||
| # incrementL2norms | ||
| # residualL2norms | ||
| # trace_wrapper | ||
| # end | ||
|
|
||
| # function NonlinearSolveBase.init_nonlinearsolve_trace( | ||
| # prob, alg::IDSolve, u, fu, J, δu; | ||
| # trace_level::ConvergenceRateTracing, kwargs... # This kind of dispatch does not work. Need to figure out a different way. | ||
| # ) | ||
| # inner_trace = NonlinearSolveBase.init_nonlinearsolve_trace( | ||
| # prob, alg, u, fu, J, δu; | ||
| # trace_level.inner_tracing, kwargs... | ||
| # ) | ||
|
|
||
| # return ConvergenceRateTraceTrick(eltype(δu)[], eltype(fu)[], inner_trace) | ||
| # end | ||
|
Comment on lines
+23
to
+43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From my understanding, it should be possible to use |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| """ | ||
| IDSolve() | ||
|
|
||
| First order solver for `ImplicitDiscreteSystems`. | ||
| """ | ||
| struct IDSolve{NLS} <: | ||
| OrdinaryDiffEqAlgorithm | ||
| nlsolve::NLS | ||
| extrapolant::Symbol | ||
| controller::Symbol | ||
| end | ||
|
|
||
| function IDSolve(; | ||
| nlsolve = NewtonRaphson(), | ||
| extrapolant = :constant, | ||
| controller = :PI | ||
| ) | ||
| IDSolve{typeof(nlsolve)}(nlsolve, extrapolant, controller) | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,36 +1,66 @@ | ||
| mutable struct ImplicitDiscreteState{uType, pType, tType} | ||
| struct ImplicitDiscreteState{uType, pType, tType} | ||
| u::uType | ||
| p::pType | ||
| t_next::tType | ||
| t::tType | ||
| end | ||
|
|
||
| mutable struct IDSolveCache{uType} <: OrdinaryDiffEqMutableCache | ||
| mutable struct IDSolveCache{uType, cType, thetaType} <: OrdinaryDiffEqMutableCache | ||
| u::uType | ||
| uprev::uType | ||
| state::ImplicitDiscreteState | ||
| prob::Union{Nothing, SciMLBase.AbstractNonlinearProblem} | ||
| z::uType | ||
| nlcache::cType | ||
| Θks::thetaType | ||
| end | ||
|
|
||
| function alg_cache(alg::IDSolve, u, rate_prototype, ::Type{uEltypeNoUnits}, | ||
| ::Type{uBottomEltypeNoUnits}, ::Type{tTypeNoUnits}, uprev, uprev2, f, t, | ||
| dt, reltol, p, calck, | ||
| ::Val{true}) where {uEltypeNoUnits, uBottomEltypeNoUnits, tTypeNoUnits} | ||
| state = ImplicitDiscreteState(isnothing(u) ? nothing : zero(u), p, t) | ||
| IDSolveCache(u, uprev, state, nothing) | ||
| end | ||
| f_nl = (resid, u_next, p) -> f(resid, u_next, p.u, p.p, p.t) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the reasoning here to include the current There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess my question is simply, why is |
||
|
|
||
| isdiscretecache(cache::IDSolveCache) = true | ||
| u_len = isnothing(u) ? 0 : length(u) | ||
| nlls = !isnothing(f.resid_prototype) && (length(f.resid_prototype) != u_len) | ||
| unl = isnothing(u) ? Float64[] : u # FIXME nonlinear solve cannot handle nothing for u | ||
| prob = if nlls | ||
| NonlinearLeastSquaresProblem{isinplace(f)}( | ||
| NonlinearFunction(f_nl; resid_prototype = f.resid_prototype), | ||
| unl, state) | ||
| else | ||
| NonlinearProblem{isinplace(f)}(f_nl, unl, state) | ||
| end | ||
|
|
||
| nlcache = init(prob, alg.nlsolve) | ||
|
|
||
| struct IDSolveConstantCache <: OrdinaryDiffEqConstantCache | ||
| prob::Union{Nothing, SciMLBase.AbstractNonlinearProblem} | ||
| IDSolveCache(u, uprev, state.u, nlcache, uBottomEltypeNoUnits[]) | ||
| end | ||
|
|
||
| isdiscretecache(cache::IDSolveCache) = true | ||
|
|
||
| function alg_cache(alg::IDSolve, u, rate_prototype, ::Type{uEltypeNoUnits}, | ||
| ::Type{uBottomEltypeNoUnits}, ::Type{tTypeNoUnits}, uprev, uprev2, f, t, | ||
| dt, reltol, p, calck, | ||
| ::Val{false}) where {uEltypeNoUnits, uBottomEltypeNoUnits, tTypeNoUnits} | ||
| @assert !isnothing(u) "Empty u not supported with out of place functions yet." | ||
|
|
||
| state = ImplicitDiscreteState(isnothing(u) ? nothing : zero(u), p, t) | ||
| IDSolveCache(u, uprev, state, nothing) | ||
| f_nl = (u_next, p) -> f(u_next, p.u, p.p, p.t) | ||
|
|
||
| u_len = isnothing(u) ? 0 : length(u) | ||
| nlls = !isnothing(f.resid_prototype) && (length(f.resid_prototype) != u_len) | ||
| unl = isnothing(u) ? Float64[] : u # FIXME nonlinear solve cannot handle nothing for u | ||
| prob = if nlls | ||
| NonlinearLeastSquaresProblem{isinplace(f)}( | ||
| NonlinearFunction(f_nl; resid_prototype = f.resid_prototype), | ||
| unl, state) | ||
| else | ||
| NonlinearProblem{isinplace(f)}(f_nl, unl, state) | ||
| end | ||
|
|
||
| nlcache = init(prob, alg.nlsolve) | ||
|
|
||
| # FIXME Use IDSolveConstantCache? | ||
| IDSolveCache(u, uprev, state.u, nlcache, uBottomEltypeNoUnits[]) | ||
| end | ||
|
|
||
| get_fsalfirstlast(cache::IDSolveCache, rate_prototype) = (nothing, nothing) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| Base.@kwdef struct KantorovichTypeController <: OrdinaryDiffEqCore.AbstractController | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO reference and documentation There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I'm not sure what this is. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, sorry. I will write the docs, no worries. I just left it here so I do not forget before merging. This is a controller derived from a posteriori estimates on how much the convergence radius in the Newton-Kantorovich theorem changes for some increment |
||
| Θmin::Float64 | ||
| p::Int64 | ||
| Θreject::Float64 = 0.95 | ||
| Θbar::Float64 = 0.5 | ||
| γ::Float64 = 0.95 | ||
| qmin::Float64 = 1 / 5 | ||
| qmax::Float64 = 5.0 | ||
| end | ||
|
|
||
| function OrdinaryDiffEqCore.default_controller( | ||
| alg::IDSolve, cache::IDSolveCache, _1, _2, _3) | ||
| return KantorovichTypeController(; Θmin = 1 // 8, p = 1) | ||
| end | ||
|
|
||
| function OrdinaryDiffEqCore.stepsize_controller!( | ||
| integrator, controller::KantorovichTypeController, alg::IDSolve | ||
| ) | ||
| @inline g(x) = √(1 + 4x) - 1 | ||
|
|
||
| # Adapt dt with a priori estimate (Eq. 5.24) | ||
| (; Θks) = integrator.cache | ||
| (; Θbar, γ, Θmin, qmin, qmax, p) = controller | ||
|
|
||
| Θ₀ = length(Θks) > 0 ? max(first(Θks), Θmin) : Θmin | ||
| q = clamp(γ * (g(Θbar) / (g(Θ₀)))^(1 / p), qmin, qmax) | ||
|
|
||
| return q | ||
termi-official marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| end | ||
|
|
||
| function OrdinaryDiffEqCore.step_accept_controller!( | ||
| integrator, controller::KantorovichTypeController, alg::IDSolve, q | ||
| ) | ||
| return q * integrator.dt | ||
| end | ||
|
|
||
| function OrdinaryDiffEqCore.step_reject_controller!( | ||
| integrator, controller::KantorovichTypeController, alg::IDSolve | ||
| ) | ||
| @inline g(x) = √(1 + 4x) - 1 | ||
|
|
||
| # Shorten dt according to (Eq. 5.24) | ||
| (; Θks) = integrator.cache.nlcache | ||
| (; Θbar, Θreject, γ, Θmin, qmin, qmax, p) = controller | ||
| for Θk in Θks | ||
| if Θk > Θreject | ||
| q = clamp(γ * (g(Θbar) / g(Θk))^(1 / p), qmin, qmax) | ||
| integrator.dt = q * integrator.dt | ||
| return | ||
| end | ||
| end | ||
| end | ||
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 1 here?
Uh oh!
There was an error while loading. Please reload this page.
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 comes from the analysis shown in Deuflhard, Newton Methods for Nonlinear Problems (Section 5.1.1, see Equation 5.6 and the surrounding definition).
The algorithms here have an associated order in the sense that for a given$dt_n = t_{n+1} - t_n$ we have for some solution $\hat{u}(t_n+1)$ derived from an initial guess given at $u({t_n})$ . Now we can define an associated ODE (Davidenko differential equation*) for each nonlinear problem with a "time parameter" by taking the time derivative of the time parameter, which has an analytical solution $\bar{u}(t_{n+1})$ , given the same initial guess ($u({t_n})$). The solver now has order $p$ if we have $$||\hat{u}(t_{n+1}) - \bar{u}(t_{n+1})|| \leq C dt^p_n . $$
Does that explain it?
*The Davidenko differential equation for$F(u,t)$ is simply $du/dt = - dF/dx (u,t)^{-1} * dF/dt (u,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.
I guess I'm confused. It's a discrete time problem, it's exact?
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.
Okay, let me try to explain it differently then. We have the parametric function$F(u,t)$ and want to find the solution of $F(u_2,t_2) = 0$ given a $u_1$ such that $F(u_1, t_1) = 0$ . With with $t_1 < t_2$ we want to find some initial guess for $u^0_2$ given $u_1$ , such that the initial guess $u^0_2$ is inside the convergence radius of a Newton method to solve $F(u_2,t_2) = 0$ . The obvious choice is that we can simply say our initial guess is simply $u_1$ , but this is typically not really great, as $t_2 - t_1$ is often quite small. However, we can use additional information contained in $F(u,t) = 0$ . To be specific the derivative with respect to the parameter $t$ contains some extra information which we can use to improve the initial guess. Here we can observe that analytically solving the associated Davidenko differential equation with initial condition $u_1$ on $[t_1, t_2]$ is equivalent to solving $F(u_2,t_2) = 0$ . Furthermore, we can exploit this information to inform how large $t_2$ can be chosen, such that the Newton method is guaranteed converge for a given initial guess. Now, the order of this extrapolation polynomial is directly related to the order of the implicit discrete solver. I hope that helps.