Skip to content

Commit

Permalink
Improve limiters debug info, up deformation tols
Browse files Browse the repository at this point in the history
Fixes
  • Loading branch information
charleskawczynski committed Jan 23, 2025
1 parent 07a1c4f commit 259a13a
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 57 deletions.
77 changes: 35 additions & 42 deletions examples/hybrid/sphere/deformation_flow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ const Z_t = FT(1000) # vertical half-width of tracers
const D₄ = FT(1e16) # hyperviscosity coefficient

# Parameters used in run_deformation_flow
z_top = FT(1.2e4)
zelem = 36
helem = 4
npoly = 4
t_end = FT(60 * 60 * 24 * 12) # 12 days of simulation time
_dt = FT(60 * 60) # 1 hour timestep
const z_top = FT(1.2e4)
const zelem = 36
const helem = 4
const npoly = 4
const t_end = FT(60 * 60 * 24 * 12) # 12 days of simulation time
const _dt = FT(60 * 60) # 1 hour timestep
ode_algorithm = ExplicitAlgorithm(SSP33ShuOsher())

# Operators used in increment!
Expand All @@ -67,7 +67,7 @@ const Ic2f = Operators.InterpolateC2F(
bottom = Operators.Extrapolate(),
top = Operators.Extrapolate(),
)
ᶠwinterp = Operators.WeightedInterpolateC2F(
const ᶠwinterp = Operators.WeightedInterpolateC2F(
bottom = Operators.Extrapolate(),
top = Operators.Extrapolate(),
)
Expand Down Expand Up @@ -138,6 +138,11 @@ function local_velocity(coord, t)
return Geometry.UVWVector(uu, uv, uw)
end

function T_exp_T_lim!(Yₜ, Yₜ_lim, Y, cache, t)
horizontal_tendency!(Yₜ_lim, Y, cache, t)
vertical_tendency!(Yₜ, Y, cache, t)
end

function horizontal_tendency!(Yₜ, Y, cache, t)
(; u, Δₕq) = cache
coord = Fields.coordinate_field(u)
Expand Down Expand Up @@ -216,7 +221,7 @@ function lim!(Y, cache, t, Y_ref)
(; limiter) = cache
if !isnothing(limiter)
Limiters.compute_bounds!(limiter, Y_ref.c.ρq, Y_ref.c.ρ)
Limiters.apply_limiter!(Y.c.ρq, Y.c.ρ, limiter)
Limiters.apply_limiter!(Y.c.ρq, Y.c.ρ, limiter; warn = false)
end
end

Expand Down Expand Up @@ -295,17 +300,17 @@ function run_deformation_flow(use_limiter, fct_op, dt)
)

problem = ODEProblem(
ClimaODEFunction(;
T_lim! = horizontal_tendency!,
T_exp! = vertical_tendency!,
lim!,
dss!,
),
ClimaODEFunction(; T_exp_T_lim!, lim!, dss!),
Y,
(0, t_end),
cache,
)
return solve(problem, ode_algorithm; dt, saveat = t_end / 2)
sol = solve(problem, ode_algorithm; dt, saveat = t_end / 2)
if !(cache.limiter isa Nothing)
@show cache.limiter.rtol
Limiters.print_convergence_stats(cache.limiter)
end
return sol
end

function conservation_errors(sol)
Expand Down Expand Up @@ -388,36 +393,24 @@ max_q5_roundoff_err = 2 * eps(FT)
max_q5_roundoff_err
@test lim_centered_ρq_errs[5] third_upwind_ρ_err atol = max_q5_roundoff_err

compare_tracer_props(a, b; buffer = 1) = all(
x -> x[1] < x[2] * buffer || (x[1] 100eps() && x[2] 100eps()),
zip(a, b),
)

# Check that the different upwinding modes with the limiter improve the "smoothness" of the tracers.
#! format: off
@testset "Test tracer properties" begin
@test all(
tracer_roughnesses(fct_sol) .< tracer_roughnesses(third_upwind_sol),
)
@test all(
tracer_roughnesses(lim_third_upwind_sol) .<
0.99 .* tracer_roughnesses(third_upwind_sol),
)
@test all(
tracer_roughnesses(lim_fct_sol) .<
0.8 .* tracer_roughnesses(third_upwind_sol),
)
@test all(tracer_ranges(fct_sol) .< tracer_ranges(third_upwind_sol))
@test all(
tracer_ranges(lim_third_upwind_sol) .<
0.6 .* tracer_ranges(third_upwind_sol),
)
@test all(
tracer_ranges(lim_fct_sol) .< 0.55 .* tracer_ranges(third_upwind_sol),
)
@test all(
tracer_ranges(lim_first_upwind_sol) .<
0.5 .* tracer_ranges(third_upwind_sol),
)
@test all(
tracer_ranges(lim_centered_sol) .<
0.9 .* tracer_ranges(third_upwind_sol),
)
@test compare_tracer_props(tracer_roughnesses(fct_sol) , tracer_roughnesses(third_upwind_sol); buffer = 1.0)
@test compare_tracer_props(tracer_roughnesses(lim_third_upwind_sol), tracer_roughnesses(third_upwind_sol); buffer = 1.0)
@test compare_tracer_props(tracer_roughnesses(lim_fct_sol) , tracer_roughnesses(third_upwind_sol); buffer = 0.93)
@test compare_tracer_props(tracer_ranges(fct_sol) , tracer_ranges(third_upwind_sol); buffer = 1.0)
@test compare_tracer_props(tracer_ranges(lim_third_upwind_sol) , tracer_ranges(third_upwind_sol); buffer = 1.0)
@test compare_tracer_props(tracer_ranges(lim_fct_sol) , tracer_ranges(third_upwind_sol); buffer = 1.0)
@test compare_tracer_props(tracer_ranges(lim_first_upwind_sol) , tracer_ranges(third_upwind_sol); buffer = 0.6)
@test compare_tracer_props(tracer_ranges(lim_centered_sol) , tracer_ranges(third_upwind_sol); buffer = 1.3)
end
#! format: on

ENV["GKSwstype"] = "nul"
using ClimaCorePlots, Plots
Expand Down
11 changes: 11 additions & 0 deletions ext/cuda/adapt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,14 @@ Adapt.adapt_structure(
to::CUDA.KernelAdaptor,
topology::Topologies.IntervalTopology,
) = Topologies.DeviceIntervalTopology(topology.boundaries)

Adapt.adapt_structure(
to::CUDA.KernelAdaptor,
lim::Limiters.QuasiMonotoneLimiter,
) = Limiters.QuasiMonotoneLimiter(
Adapt.adapt(to, lim.q_bounds),
Adapt.adapt(to, lim.q_bounds_nbr),
Adapt.adapt(to, lim.ghost_buffer),
lim.rtol,
Limiters.NoConvergenceStats(),
)
3 changes: 2 additions & 1 deletion ext/cuda/limiters.jl
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ function apply_limiter!(
ρq::Fields.Field,
ρ::Fields.Field,
limiter::QuasiMonotoneLimiter,
dev::ClimaComms.CUDADevice,
dev::ClimaComms.CUDADevice;
warn::Bool = true,
)
ρq_data = Fields.field_values(ρq)
us = DataLayouts.UniversalSize(ρq_data)
Expand Down
115 changes: 101 additions & 14 deletions src/Limiters/quasimonotone.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,52 @@ import ..RecursiveApply: ⊠, ⊞, ⊟, rmap, rzero, rdiv
import ..DataLayouts: slab_index
import Adapt


abstract type AbstractLimiterConvergenceStats end

struct NoConvergenceStats <: AbstractLimiterConvergenceStats end

Base.@kwdef mutable struct LimiterConvergenceStats{FT} <:
AbstractLimiterConvergenceStats
n_times_unconverged::Int = 0
max_rel_err::FT = 0
min_tracer_mass::FT = Inf
end

print_convergence_stats(io::IO, lcs::AbstractLimiterConvergenceStats) =
print_convergence_stats(io, lcs)

print_convergence_stats(io::IO, ::NoConvergenceStats) = nothing
print_convergence_stats(io::IO, lcs::LimiterConvergenceStats) =
print(io, convergence_stats_str(lcs))

function convergence_stats_str(lcs::LimiterConvergenceStats)
return string(
"Limiter convergence stats:\n",
" `n_times_unconverged = $(lcs.n_times_unconverged)`\n",
" `max_rel_err = $(lcs.max_rel_err)`\n",
" `min_tracer_mass = $(lcs.min_tracer_mass)`\n",
)
end

function update_convergence_stats!(
lcs::LimiterConvergenceStats,
max_rel_err,
min_tracer_mass,
)
lcs.max_rel_err = max(lcs.max_rel_err, max_rel_err)
lcs.min_tracer_mass = min(lcs.min_tracer_mass, min_tracer_mass)
lcs.n_times_unconverged += 1
return nothing
end

function reset_convergence_stats!(lcs::LimiterConvergenceStats)
lcs.max_rel_err = 0
lcs.min_tracer_mass = Inf
lcs.n_times_unconverged = 0
return nothing
end

"""
QuasiMonotoneLimiter
Expand All @@ -28,9 +74,18 @@ a few iterations (until `abs(Δtracer_mass) <= rtol * tracer_mass`).
# Constructor
limiter = QuasiMonotoneLimiter(ρq::Field; rtol = eps(eltype(parent(ρq))))
limiter = QuasiMonotoneLimiter(
ρq::Field;
rtol = eps(eltype(parent(ρq))),
convergence_stats = LimiterConvergenceStats()
)
Creates a limiter instance for the field `ρq` with relative tolerance `rtol`,
and `convergence_stats`, which collects statistics in `apply_limiter!`
(e.g., number of times that convergence is met or not). Users can call
Creates a limiter instance for the field `ρq` with relative tolerance `rtol`.
`Limiters.print_convergence_stats(::QuasiMonotoneLimiter)` to print the
convergence stats.
# Usage
Expand All @@ -42,7 +97,7 @@ Then call [`apply_limiter!`](@ref) on the output fields:
apply_limiter!(ρq, ρ, limiter)
"""
struct QuasiMonotoneLimiter{D, G, FT}
struct QuasiMonotoneLimiter{D, G, FT, CS}
"contains the min and max of each element"
q_bounds::D
"contains the min and max of each element and its neighbors"
Expand All @@ -51,20 +106,36 @@ struct QuasiMonotoneLimiter{D, G, FT}
ghost_buffer::G
"relative tolerance for tracer mass change"
rtol::FT
"Convergence statistics"
convergence_stats::CS
end

print_convergence_stats(lim::QuasiMonotoneLimiter) =
print_convergence_stats(stdout, lim.convergence_stats)

Adapt.adapt_structure(to, lim::QuasiMonotoneLimiter) = QuasiMonotoneLimiter(
Adapt.adapt(to, lim.q_bounds),
Adapt.adapt(to, lim.q_bounds_nbr),
Adapt.adapt(to, lim.ghost_buffer),
lim.rtol,
Adapt.adapt(to, lim.convergence_stats),
)

function QuasiMonotoneLimiter(ρq::Fields.Field; rtol = eps(eltype(parent(ρq))))
function QuasiMonotoneLimiter(
ρq::Fields.Field;
rtol = eps(eltype(parent(ρq))),
convergence_stats = LimiterConvergenceStats{eltype(parent(ρq))}(),
)
q_bounds = make_q_bounds(Fields.field_values(ρq))
ghost_buffer =
Topologies.create_ghost_buffer(q_bounds, Spaces.topology(axes(ρq)))
return QuasiMonotoneLimiter(q_bounds, similar(q_bounds), ghost_buffer, rtol)
return QuasiMonotoneLimiter(
q_bounds,
similar(q_bounds),
ghost_buffer,
rtol,
convergence_stats,
)
end

function make_q_bounds(
Expand Down Expand Up @@ -277,14 +348,16 @@ converge for any element, a warning is issued.
apply_limiter!(
ρq::Fields.Field,
ρ::Fields.Field,
limiter::QuasiMonotoneLimiter,
) = apply_limiter!(ρq, ρ, limiter, ClimaComms.device(ρ))
limiter::QuasiMonotoneLimiter;
warn::Bool = true,
) = apply_limiter!(ρq, ρ, limiter, ClimaComms.device(ρ); warn)

function apply_limiter!(
ρq::Fields.Field,
ρ::Fields.Field,
limiter::QuasiMonotoneLimiter,
dev::ClimaComms.AbstractCPUDevice,
dev::ClimaComms.AbstractCPUDevice;
warn::Bool = true,
)
(; q_bounds_nbr, rtol) = limiter

Expand All @@ -294,21 +367,31 @@ function apply_limiter!(

converged = true
max_rel_err = zero(rtol)
min_tracer_mass = Inf
(_, _, _, Nv, Nh) = size(ρq_data)
for h in 1:Nh
for v in 1:Nv
slab_ρ = slab(ρ_data, v, h)
slab_ρq = slab(ρq_data, v, h)
slab_WJ = slab(WJ_data, v, h)
slab_q_bounds = slab(q_bounds_nbr, v, h)
(_converged, rel_err) =
(_converged, slab_max_rel_err, slab_min_tracer_mass) =
apply_limit_slab!(slab_ρq, slab_ρ, slab_WJ, slab_q_bounds, rtol)
converged &= _converged
max_rel_err = max(rel_err, max_rel_err)
max_rel_err = max(slab_max_rel_err, max_rel_err)
min_tracer_mass = max(min_tracer_mass, slab_min_tracer_mass)
end
end
converged ||
@warn "Limiter failed to converge with rtol = $rtol, `max_rel_err`=$max_rel_err"
if !converged
lcs = limiter.convergence_stats
update_convergence_stats!(lcs, max_rel_err, min_tracer_mass)
end
if warn
lcs = limiter.convergence_stats
msg = convergence_stats_str(lcs)
msg *= "Use `warn = false` in `Limiters.apply_limiter!` to suppress this message.\n"
@warn msg
end

call_post_op_callback() && post_op_callback(ρq, ρq, ρ, limiter, dev)
return ρq
Expand All @@ -331,9 +414,10 @@ function apply_limit_slab!(slab_ρq, slab_ρ, slab_WJ, slab_q_bounds, rtol)
array_ρ = parent(slab_ρ)
array_w = parent(slab_WJ)
array_q_bounds = parent(slab_q_bounds)
FT = eltype(array_ρq)

# 1) compute ∫ρ
total_mass = zero(eltype(array_ρ))
total_mass = zero(FT)
for j in 1:Nj, i in 1:Ni
total_mass += array_ρ[i, j, 1] * array_w[i, j, 1]
end
Expand All @@ -342,6 +426,8 @@ function apply_limit_slab!(slab_ρq, slab_ρ, slab_WJ, slab_q_bounds, rtol)

converged = true
max_rel_err = zero(rtol)
min_tracer_mass = FT(Inf)
rel_err = zero(FT)
for f in 1:Nf
q_min = array_q_bounds[1, f]
q_max = array_q_bounds[2, f]
Expand Down Expand Up @@ -380,6 +466,7 @@ function apply_limit_slab!(slab_ρq, slab_ρ, slab_WJ, slab_q_bounds, rtol)

rel_err = abs(Δtracer_mass) / abs(tracer_mass)
max_rel_err = max(max_rel_err, rel_err)
min_tracer_mass = min(min_tracer_mass, abs(tracer_mass))
if rel_err <= rtol
break
end
Expand Down Expand Up @@ -427,5 +514,5 @@ function apply_limit_slab!(slab_ρq, slab_ρ, slab_WJ, slab_q_bounds, rtol)
end
end
end
return (converged, max_rel_err)
return (converged, max_rel_err, min_tracer_mass)
end

0 comments on commit 259a13a

Please sign in to comment.