Hello Julia Community,
I’m currently working on a differential equation problem related to ice flow modeling and encountering some issues with convergence when using Dual numbers. The problem converges as expected with Float64
types, but when I switch to Dual numbers for automatic differentiation, the solver doesn’t seem to converge (or at least, not in the way I expect).
Here’s a snippet of my code:
iceflow_prob = ODEProblem{true,SciMLBase.FullSpecialize}(SIA2D!, model.iceflow.H, params.simulation.tspan, tstops=params.solver.tstops, simulation)
iceflow_sol = solve(iceflow_prob,
params.solver.solver,
callback=cb,
tstops=params.solver.tstops,
reltol=params.solver.reltol,
save_everystep=params.solver.save_everystep,
progress=params.solver.progress,
progress_steps=params.solver.progress_steps)
function SIA2D!(dH::Matrix{F}, H::Matrix{F}, simulation::SIM, t::AbstractFloat) where {F <: DualFloat, SIM <: Simulation}
N=DualFloat
# Retrieve parameters
SIA2D_model::SIA2Dmodel = simulation.model.iceflow
glacier::Sleipnir.Glacier2D = simulation.glaciers[simulation.model.iceflow.glacier_idx[]]
params::Sleipnir.Parameters = simulation.parameters
int_type = simulation.parameters.simulation.int_type
H̄::Matrix{<:N} = SIA2D_model.H̄
A::Ref{N} = SIA2D_model.A
n::Ref{N} = SIA2D_model.n
B::Matrix{<:N} = glacier.B
S::Matrix{<:N} = SIA2D_model.S
dSdx::Matrix{<:N} = SIA2D_model.dSdx
dSdy::Matrix{<:N} = SIA2D_model.dSdy
D::Matrix{<:N} = SIA2D_model.D
Dx::Matrix{<:N} = SIA2D_model.Dx
Dy::Matrix{<:N} = SIA2D_model.Dy
dSdx_edges::Matrix{<:N} = SIA2D_model.dSdx_edges
dSdy_edges::Matrix{<:N} = SIA2D_model.dSdy_edges
∇S::Matrix{<:N} = SIA2D_model.∇S
∇Sx::Matrix{<:N} = SIA2D_model.∇Sx
∇Sy::Matrix{<:N} = SIA2D_model.∇Sy
Fx::Matrix{<:N} = SIA2D_model.Fx
Fy::Matrix{<:N} = SIA2D_model.Fy
Fxx::Matrix{<:N} = SIA2D_model.Fxx
Fyy::Matrix{<:N} = SIA2D_model.Fyy
Δx::N = glacier.Δx
Δy::N = glacier.Δy
Γ::Ref{N} = SIA2D_model.Γ
ρ::F = simulation.parameters.physical.ρ
g::F = simulation.parameters.physical.g
# First, enforce values to be positive
map!(x -> ifelse(x>0.0,x,0.0), H, H)
S .= B .+ H
# All grid variables computed in a staggered grid
# Compute surface gradients on edges
diff_x!(dSdx, S, Δx)
diff_y!(dSdy, S, Δy)
avg_y!(∇Sx, dSdx)
avg_x!(∇Sy, dSdy)
∇S .= (∇Sx.^2 .+ ∇Sy.^2).^((n[] - 1)/2)
# @infiltrate
avg!(H̄, H)
Γ[] = 2.0 * A[] * (ρ * g)^n[] / (n[]+2) # 1 / m^3 s
D .= Γ[] .* H̄.^(n[] + 2) .* ∇S
# Compute flux components
@views diff_x!(dSdx_edges, S[:,2:end - 1], Δx)
@views diff_y!(dSdy_edges, S[2:end - 1,:], Δy)
# Cap surface elevaton differences with the upstream ice thickness to
# imporse boundary condition of the SIA equation
η₀ = params.physical.η₀
dSdx_edges .= @views @. min(dSdx_edges, η₀ * H[2:end, 2:end-1]/Δx)
dSdx_edges .= @views @. max(dSdx_edges, -η₀ * H[1:end-1, 2:end-1]/Δx)
dSdy_edges .= @views @. min(dSdy_edges, η₀ * H[2:end-1, 2:end]/Δy)
dSdy_edges .= @views @. max(dSdy_edges, -η₀ * H[2:end-1, 1:end-1]/Δy)
avg_y!(Dx, D)
avg_x!(Dy, D)
Fx .= .-Dx .* dSdx_edges
Fy .= .-Dy .* dSdy_edges
# Flux divergence
diff_x!(Fxx, Fx, Δx)
diff_y!(Fyy, Fy, Δy)
inn(dH) .= .-(Fxx .+ Fyy)
end
Also see the options of the ODE below:
solver: RDPK3Sp35(; stage_limiter! = trivial_limiter!, step_limiter! = trivial_limiter!, thread = static(false),)
reltol: 1.0
step: 0.08333333333333333
tstops: [2010.0833333333333, 2010.1666666666667, 2010.25, 2010.3333333333333, 2010.4166666666667, 2010.5, 2010.5833333333333, 2010.6666666666667, 2010.75, 2010.8333333333333, 2010.9166666666667, 2011.0, 2011.0833333333333, 2011.1666666666667, 2011.25, 2011.3333333333333, 2011.4166666666667, 2011.5, 2011.5833333333333, 2011.6666666666667, 2011.75, 2011.8333333333333, 2011.9166666666667, 2012.0, 2012.0833333333333, 2012.1666666666667, 2012.25, 2012.3333333333333, 2012.4166666666667, 2012.5, 2012.5833333333333, 2012.6666666666667, 2012.75, 2012.8333333333333, 2012.9166666666667, 2013.0, 2013.0833333333333, 2013.1666666666667, 2013.25, 2013.3333333333333, 2013.4166666666667, 2013.5, 2013.5833333333333, 2013.6666666666667, 2013.75, 2013.8333333333333, 2013.9166666666667, 2014.0, 2014.0833333333333, 2014.1666666666667, 2014.25, 2014.3333333333333, 2014.4166666666667, 2014.5, 2014.5833333333333, 2014.6666666666667, 2014.75, 2014.8333333333333, 2014.9166666666667, 2015.0]
save_everystep: false
progress: true
progress_steps: 10
I’ve noticed that when I adjust the relative tolerance (reltol
) to 1, the solver converges quickly, suggesting a possible sensitivity to tolerance settings or other numerical stability issues. Also I think it is good to mention that I am using a custom uniontype const DualFloat = Union{AbstractFloat, ForwardDiff.Dual}
.
I’m seeking insights or suggestions on the following:
- Are there specific considerations or best practices when using Dual numbers in differential equation solvers, particularly regarding solver settings like
reltol
? - Could this issue be indicative of a deeper problem with numerical stability in my implementation, and how might I address it?
- Any recommendations for alternative solvers or approaches within the SciML ecosystem that are known to work well with Dual numbers?
Any help or guidance on this would be greatly appreciated!
Thank you in advance!