Deepesh Thakur / Jun 26 2019
Filament Work-Precision Diagrams
The Model
using OrdinaryDiffEq, ODEInterfaceDiffEq, Sundials, DiffEqDevTools, LSODA using LinearAlgebra using Plots gr()
GRBackend()
const T = Float64 abstract type AbstractFilamentCache end abstract type AbstractMagneticForce end abstract type AbstractInextensibilityCache end abstract type AbstractSolver end abstract type AbstractSolverCache end
struct FerromagneticContinuous <: AbstractMagneticForce ω :: T F :: Vector{T} end mutable struct FilamentCache{ MagneticForce <: AbstractMagneticForce, InextensibilityCache <: AbstractInextensibilityCache, SolverCache <: AbstractSolverCache } <: AbstractFilamentCache N :: Int μ :: T Cm :: T x :: SubArray{T,1,Vector{T},Tuple{StepRange{Int,Int}},true} y :: SubArray{T,1,Vector{T},Tuple{StepRange{Int,Int}},true} z :: SubArray{T,1,Vector{T},Tuple{StepRange{Int,Int}},true} A :: Matrix{T} P :: InextensibilityCache F :: MagneticForce Sc :: SolverCache end
struct NoHydroProjectionCache <: AbstractInextensibilityCache J :: Matrix{T} P :: Matrix{T} J_JT :: Matrix{T} J_JT_LDLT :: LinearAlgebra.LDLt{T, SymTridiagonal{T}} P0 :: Matrix{T} NoHydroProjectionCache(N::Int) = new( zeros(N, 3*(N+1)), # J zeros(3*(N+1), 3*(N+1)), # P zeros(N,N), # J_JT LinearAlgebra.LDLt{T,SymTridiagonal{T}}(SymTridiagonal(zeros(N), zeros(N-1))), zeros(N, 3*(N+1)) ) end
struct DiffEqSolverCache <: AbstractSolverCache S1 :: Vector{T} S2 :: Vector{T} DiffEqSolverCache(N::Integer) = new(zeros(T,3*(N+1)), zeros(T,3*(N+1))) end
function FilamentCache(N=20; Cm=32, ω=200, Solver=SolverDiffEq) InextensibilityCache = NoHydroProjectionCache SolverCache = DiffEqSolverCache tmp = zeros(3*(N+1)) FilamentCache{FerromagneticContinuous, InextensibilityCache, SolverCache}( N, N+1, Cm, view(tmp,1:3:3*(N+1)), view(tmp,2:3:3*(N+1)), view(tmp,3:3:3*(N+1)), zeros(3*(N+1), 3*(N+1)), # A InextensibilityCache(N), # P FerromagneticContinuous(ω, zeros(3*(N+1))), SolverCache(N) ) end
FilamentCache
0.8s
Julia
function stiffness_matrix!(f::AbstractFilamentCache) N, μ, A = f.N, f.μ, f.A for j in axes(A, 2), i in axes(A, 1) A[i, j] = j == i ? 1 : 0 end for i in 1 : 3 A[i,i] = 1 A[i,3+i] = -2 A[i,6+i] = 1 A[3+i,i] = -2 A[3+i,3+i] = 5 A[3+i,6+i] = -4 A[3+i,9+i] = 1 A[3*(N-1)+i,3*(N-3)+i] = 1 A[3*(N-1)+i,3*(N-2)+i] = -4 A[3*(N-1)+i,3*(N-1)+i] = 5 A[3*(N-1)+i,3*N+i] = -2 A[3*N+i,3*(N-2)+i] = 1 A[3*N+i,3*(N-1)+i] = -2 A[3*N+i,3*N+i] = 1 for j in 2 : N-2 A[3*j+i,3*j+i] = 6 A[3*j+i,3*(j-1)+i] = -4 A[3*j+i,3*(j+1)+i] = -4 A[3*j+i,3*(j-2)+i] = 1 A[3*j+i,3*(j+2)+i] = 1 end end rmul!(A, -μ^4) nothing end
stiffness_matrix! (generic function with 1 method)
function update_separate_coordinates!(f::AbstractFilamentCache, r) N, x, y, z = f.N, f.x, f.y, f.z for i in 1 : length(x) x[i] = r[3*i-2] y[i] = r[3*i-1] z[i] = r[3*i] end nothing end function update_united_coordinates!(f::AbstractFilamentCache, r) N, x, y, z = f.N, f.x, f.y, f.z for i in 1 : length(x) r[3*i-2] = x[i] r[3*i-1] = y[i] r[3*i] = z[i] end nothing end function update_united_coordinates(f::AbstractFilamentCache) r = zeros(T, 3*length(f.x)) update_united_coordinates!(f, r) r end
update_united_coordinates (generic function with 1 method)
function initialize!(initial_conf_type::Symbol, f::AbstractFilamentCache) N, x, y, z = f.N, f.x, f.y, f.z if initial_conf_type == :StraightX x .= range(0, stop=1, length=N+1) y .= 0 z .= 0 else error("Unknown initial configuration requested.") end update_united_coordinates(f) end
initialize! (generic function with 1 method)
function magnetic_force!(::FerromagneticContinuous, f::AbstractFilamentCache, t) # TODO: generalize this for different magnetic fields as well N, μ, Cm, ω, F = f.N, f.μ, f.Cm, f.F.ω, f.F.F F[1] = -μ * Cm * cos(ω*t) F[2] = -μ * Cm * sin(ω*t) F[3*(N+1)-2] = μ * Cm * cos(ω*t) F[3*(N+1)-1] = μ * Cm * sin(ω*t) nothing end
magnetic_force! (generic function with 1 method)
struct SolverDiffEq <: AbstractSolver end function (f::FilamentCache)(dr, r, p, t) f.x, f.y, f.z = r[1:3:end], r[2:3:end], r[3:3:end] jacobian!(f) projection!(f) magnetic_force!(f.F, f, t) A, P, F, S1, S2 = f.A, f.P.P, f.F.F, f.Sc.S1, f.Sc.S2 # implement dr = P * (A*r + F) in an optimized way to avoid temporaries mul!(S1, A, r) S1 .+= F mul!(S2, P, S1) copy!(dr, S2) return dr end
function jacobian!(f::FilamentCache) N, x, y, z, J = f.N, f.x, f.y, f.z, f.P.J for i in 1 : N J[i, 3*i-2] = -2 * (x[i+1]-x[i]) J[i, 3*i-1] = -2 * (y[i+1]-y[i]) J[i, 3*i] = -2 * (z[i+1]-z[i]) J[i, 3*(i+1)-2] = 2 * (x[i+1]-x[i]) J[i, 3*(i+1)-1] = 2 * (y[i+1]-y[i]) J[i, 3*(i+1)] = 2 * (z[i+1]-z[i]) end nothing end
jacobian! (generic function with 1 method)
function projection!(f::FilamentCache) # implement P[:] = I - J'/(J*J')*J in an optimized way to avoid temporaries J, P, J_JT, J_JT_LDLT, P0 = f.P.J, f.P.P, f.P.J_JT, f.P.J_JT_LDLT, f.P.P0 mul!(J_JT, J, J') LDLt_inplace!(J_JT_LDLT, J_JT) ldiv!(P0, J_JT_LDLT, J) mul!(P, P0', J) subtract_from_identity!(P) nothing end
projection! (generic function with 1 method)
function subtract_from_identity!(A) lmul!(-1, A) for i in 1 : size(A,1) A[i,i] += 1 end nothing end
subtract_from_identity! (generic function with 1 method)
function LDLt_inplace!(L::LinearAlgebra.LDLt{T,SymTridiagonal{T}}, A::Matrix{T}) where {T<:Real} n = size(A,1) dv, ev = L.data.dv, L.data.ev for (i,d) in enumerate(diagind(A)) dv[i] = A[d] end for (i,d) in enumerate(diagind(A,-1)) ev[i] = A[d] end for i in 1 : n-1 ev[i] /= dv[i] dv[i+1] -= abs2(ev[i]) * dv[i] end L end
LDLt_inplace! (generic function with 1 method)
Investigating the model
Let's take a look at what results of the model look like:
function run(::SolverDiffEq; N=20, Cm=32, ω=200, time_end=1., solver=TRBDF2(autodiff=false), reltol=1e-6, abstol=1e-6) f = FilamentCache(N, Solver=SolverDiffEq, Cm=Cm, ω=ω) r0 = initialize!(:StraightX, f) stiffness_matrix!(f) prob = ODEProblem(ODEFunction(f, jac=(J, u, p, t)->(mul!(J, f.P.P, f.A); nothing)), r0, (0., time_end)) sol = solve(prob, solver, dense=false, reltol=reltol, abstol=abstol) end
run (generic function with 1 method)
This method runs the model with the TRBDF2
method and the default parameters.
sol = run(SolverDiffEq()) plot(sol,vars = (0,25),dpi=200,linewidth=1,legendfontsize=6,legend=:topright)
The model quickly falls into a highly oscillatory mode which then dominates throughout the rest of the solution.
Work-Precision Diagrams
Now let's build the problem and solve it once at high accuracy to get a reference solution:
N=20 f = FilamentCache(N, Solver=SolverDiffEq) r0 = initialize!(:StraightX, f) stiffness_matrix!(f) prob = ODEProblem(f, r0, (0., 0.01)) sol = solve(prob, Vern9(), reltol=1e-14, abstol=1e-14) test_sol = TestSolution(sol);
High Tolerance (Low Accuracy)
abstols=1 ./10 .^(4:9) reltols=1 ./10 .^(4:9) setups = [ Dict(:alg => Rosenbrock23(autodiff=false)), Dict(:alg => Rodas4(autodiff=false)), #Dict(:alg=>RKC()), Dict(:alg=>ROCK2()), Dict(:alg=>ROCK4()), Dict(:alg=>SERK2v2(controller=:PI)), #Dict(:alg=>SERK2v2(controller=:Predictive)), Dict(:alg=>ESERK5()) ]; #Names = ["Rosen23" "Rodas4" "BS3" "Tsit5" "Vern6" "RKC" "ROCK2" "ROCK4" "SERK2 PI" "SERK2 Predictive" "ESERK5"] Names = ["Rosen23" "Rodas4" "ROCK2" "ROCK4" "SERK2 PI" "ESERK5"]
1×6 Array{String,2}:
"Rosen23" "Rodas4" "ROCK2" "ROCK4" "SERK2 PI" "ESERK5"
Endpoint Error
wp = WorkPrecisionSet(prob, abstols, reltols, setups; names=Names, appxsol=test_sol, maxiters=Int(1e6), verbose = false) plot(wp,dpi=200,linewidth=1,legend=:topright,legendfontsize=6)
Timeseries Error
wp = WorkPrecisionSet(prob, abstols, reltols, setups; names=Names, appxsol=test_sol, maxiters=Int(1e6), verbose = false, error_estimate=:l2) plot(wp,dpi=200,linewidth=1,legend=:topright,legendfontsize=6)
Dense Error
wp = WorkPrecisionSet(prob, abstols, reltols, setups; names=Names, appxsol=test_sol, maxiters=Int(1e6), verbose = false, dense_errors = true, error_estimate=:L2) plot(wp,dpi=200,linewidth=1,legend=:topright,legendfontsize=6)
Conclusion
As we can see Stabilized methods are clearly out performing the implicit methods.