-
Notifications
You must be signed in to change notification settings - Fork 9
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
Implement and test vertical mass borrowing limiters #2084
Open
charleskawczynski
wants to merge
2
commits into
main
Choose a base branch
from
ck/MassBorrowerFixer
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import .DataLayouts as DL | ||
|
||
""" | ||
VerticalMassBorrowingLimiter(f::Fields.Field, q_min) | ||
|
||
A vertical-only mass borrowing limiter. | ||
|
||
The mass borrower borrows tracer mass from an adjacent, lower layer. | ||
It conserves the total tracer mass and can avoid negative tracers. | ||
|
||
At level k, it will first borrow the mass from the layer k+1 (lower level). | ||
If the mass is not sufficient in layer k+1, it will borrow mass from | ||
layer k+2. The borrower will proceed this process until the bottom layer. | ||
If the tracer mass in the bottom layer goes negative, it will repeat the | ||
process from the bottom to the top. In this way, the borrower works for | ||
any shape of mass profiles. | ||
|
||
This code was adapted from [E3SM](https://github.com/E3SM-Project/E3SM/blob/2c377c5ec9a5585170524b366ad85074ab1b1a5c/components/eam/src/physics/cam/massborrow.F90) | ||
|
||
References: | ||
- [zhang2018impact](@cite) | ||
""" | ||
struct VerticalMassBorrowingLimiter{F, T} | ||
bmass::F | ||
ic::F | ||
q_min::T | ||
end | ||
function VerticalMassBorrowingLimiter(f::Fields.Field, q_min) | ||
bmass = similar(Spaces.level(f, 1)) | ||
ic = similar(Spaces.level(f, 1)) | ||
return VerticalMassBorrowingLimiter(bmass, ic, q_min) | ||
end | ||
|
||
|
||
""" | ||
apply_limiter!(q::Fields.Field, ρ::Fields.Field, lim::VerticalMassBorrowingLimiter) | ||
|
||
Apply the vertical mass borrowing | ||
limiter `lim` to field `q`, given | ||
density `ρ`. | ||
""" | ||
apply_limiter!( | ||
q::Fields.Field, | ||
ρ::Fields.Field, | ||
lim::VerticalMassBorrowingLimiter, | ||
) = apply_limiter!(q, ρ, axes(q), lim, ClimaComms.device(axes(q))) | ||
|
||
function apply_limiter!( | ||
q::Fields.Field, | ||
ρ::Fields.Field, | ||
space::Spaces.FiniteDifferenceSpace, | ||
lim::VerticalMassBorrowingLimiter, | ||
device::ClimaComms.AbstractCPUDevice, | ||
) | ||
cache = (; bmass = lim.bmass, ic = lim.ic, q_min = lim.q_min) | ||
columnwise_massborrow_cpu(q, ρ, cache) | ||
end | ||
|
||
function apply_limiter!( | ||
q::Fields.Field, | ||
ρ::Fields.Field, | ||
space::Spaces.ExtrudedFiniteDifferenceSpace, | ||
lim::VerticalMassBorrowingLimiter, | ||
device::ClimaComms.AbstractCPUDevice, | ||
) | ||
Fields.bycolumn(axes(q)) do colidx | ||
cache = (; | ||
bmass = lim.bmass[colidx], | ||
ic = lim.ic[colidx], | ||
q_min = lim.q_min, | ||
) | ||
columnwise_massborrow_cpu(q[colidx], ρ[colidx], cache) | ||
end | ||
end | ||
|
||
# TODO: can we improve the performance? | ||
# `bycolumn` on the CPU may be better here since we could multithread it. | ||
function columnwise_massborrow_cpu(q::Fields.Field, ρ::Fields.Field, cache) # column fields | ||
(; bmass, ic, q_min) = cache | ||
|
||
Δz = Fields.Δz_field(q) | ||
Δz_vals = Fields.field_values(Δz) | ||
ρ_vals = Fields.field_values(ρ) | ||
# loop over tracers | ||
nlevels = Spaces.nlevels(axes(q)) | ||
@. ic = 0 | ||
@. bmass = 0 | ||
q_vals = Fields.field_values(q) | ||
# top to bottom | ||
for f in 1:DataLayouts.ncomponents(q_vals) | ||
for v in 1:nlevels | ||
CI = CartesianIndex(1, 1, f, v, 1) | ||
# new mass in the current layer | ||
ρΔz_v = | ||
DL.getindex_field(Δz_vals, CI) * DL.getindex_field(ρ_vals, CI) | ||
nmass = DL.getindex_field(q_vals, CI) + bmass[] / ρΔz_v | ||
if nmass > q_min[f] | ||
# if new mass in the current layer is positive, don't borrow mass any more | ||
DL.setindex_field!(q_vals, nmass, CI) | ||
bmass[] = 0 | ||
else | ||
# set mass to q_min in the current layer, and save bmass | ||
bmass[] = (nmass - q_min[f]) * ρΔz_v | ||
DL.setindex_field!(q_vals, q_min[f], CI) | ||
ic[] = ic[] + 1 | ||
end | ||
end | ||
|
||
# bottom to top | ||
for v in nlevels:-1:1 | ||
CI = CartesianIndex(1, 1, f, v, 1) | ||
# if the surface layer still needs to borrow mass | ||
if bmass[] < 0 | ||
ρΔz_v = | ||
DL.getindex_field(Δz_vals, CI) * | ||
DL.getindex_field(ρ_vals, CI) | ||
# new mass in the current layer | ||
nmass = DL.getindex_field(q_vals, CI) + bmass[] / ρΔz_v | ||
if nmass > q_min[f] | ||
# if new mass in the current layer is positive, don't borrow mass any more | ||
DL.setindex_field!(q_vals, nmass, CI) | ||
bmass[] = 0 | ||
else | ||
# if new mass in the current layer is negative, continue to borrow mass | ||
bmass[] = (nmass - q_min[f]) * ρΔz_v | ||
DL.setindex_field!(q_vals, q_min[f], CI) | ||
end | ||
end | ||
end | ||
end | ||
|
||
return nothing | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
#= | ||
julia --project | ||
using Revise; include(joinpath("test", "Limiters", "vertical_mass_borrowing_limiter.jl")) | ||
=# | ||
using ClimaComms | ||
ClimaComms.@import_required_backends | ||
using ClimaCore: Fields, Spaces, Limiters | ||
using ClimaCore.RecursiveApply | ||
using ClimaCore.Geometry | ||
using ClimaCore.Grids | ||
using ClimaCore.CommonGrids | ||
using Test | ||
using Random | ||
|
||
function perturb_field!(f::Fields.Field; perturb_radius) | ||
device = ClimaComms.device(f) | ||
ArrayType = ClimaComms.array_type(device) | ||
rand_data = ArrayType(rand(size(parent(f))...)) # [0-1] | ||
rand_data = rand_data .- sum(rand_data) / length(rand_data) # make centered about 0 ([-0.5:0.5]) | ||
rand_data = (rand_data ./ maximum(rand_data)) .* perturb_radius # rand_data now in [-perturb_radius:perturb_radius] | ||
parent(f) .= parent(f) .+ rand_data # f in [f ± perturb_radius] | ||
return nothing | ||
end | ||
|
||
@testset "Vertical mass borrowing limiter - column" begin | ||
Random.seed!(1234) | ||
z_elem = 10 | ||
z_min = 0 | ||
z_max = 1 | ||
device = ClimaComms.device() | ||
grid = ColumnGrid(; z_elem, z_min, z_max, device) | ||
cspace = Spaces.FiniteDifferenceSpace(grid, Grids.CellCenter()) | ||
tol = 0.01 | ||
perturb_q = 0.3 | ||
perturb_ρ = 0.2 | ||
|
||
# Initialize fields | ||
coords = Fields.coordinate_field(cspace) | ||
ρ = map(coord -> 1.0, coords) | ||
q = map(coord -> 0.1, coords) | ||
(; z) = coords | ||
perturb_field!(q; perturb_radius = perturb_q) | ||
perturb_field!(ρ; perturb_radius = perturb_ρ) | ||
ρq_init = ρ .⊠ q | ||
sum_ρq_init = sum(ρq_init) | ||
|
||
# Test that the minimum is below 0 | ||
@test length(parent(q)) == z_elem == 10 | ||
@test 0.3 ≤ count(x -> x < 0, parent(q)) / 10 ≤ 0.5 # ensure reasonable percentage of points are negative | ||
|
||
@test -2 * perturb_q ≤ minimum(q) ≤ -tol | ||
limiter = Limiters.VerticalMassBorrowingLimiter(q, (0.0,)) | ||
Limiters.apply_limiter!(q, ρ, limiter) | ||
@test 0 ≤ minimum(q) | ||
ρq = ρ .⊠ q | ||
@test isapprox(sum(ρq), sum_ρq_init; atol = 1e-15) | ||
@test isapprox(sum(ρq), sum_ρq_init; rtol = 1e-10) | ||
# @show sum(ρq) # 0.07388931313511024 | ||
# @show sum_ρq_init # 0.07388931313511025 | ||
end | ||
|
||
@testset "Vertical mass borrowing limiter - sphere" begin | ||
z_elem = 10 | ||
z_min = 0 | ||
z_max = 1 | ||
radius = 10 | ||
h_elem = 10 | ||
n_quad_points = 4 | ||
grid = ExtrudedCubedSphereGrid(; | ||
z_elem, | ||
z_min, | ||
z_max, | ||
radius, | ||
h_elem, | ||
n_quad_points, | ||
) | ||
cspace = Spaces.ExtrudedFiniteDifferenceSpace(grid, Grids.CellCenter()) | ||
tol = 0.01 | ||
perturb_q = 0.2 | ||
perturb_ρ = 0.1 | ||
|
||
# Initialize fields | ||
coords = Fields.coordinate_field(cspace) | ||
ρ = map(coord -> 1.0, coords) | ||
q = map(coord -> 0.1, coords) | ||
|
||
perturb_field!(q; perturb_radius = perturb_q) | ||
perturb_field!(ρ; perturb_radius = perturb_ρ) | ||
ρq_init = ρ .⊠ q | ||
sum_ρq_init = sum(ρq_init) | ||
|
||
# Test that the minimum is below 0 | ||
@test length(parent(q)) == z_elem * h_elem^2 * 6 * n_quad_points^2 == 96000 | ||
@test 0.10 ≤ count(x -> x < 0.0001, parent(q)) / 96000 ≤ 1 # ensure 10% of points are negative | ||
|
||
@test -2 * perturb_q ≤ minimum(q) ≤ -tol | ||
limiter = Limiters.VerticalMassBorrowingLimiter(q, (0.0,)) | ||
Limiters.apply_limiter!(q, ρ, limiter) | ||
@test 0 ≤ minimum(q) | ||
ρq = ρ .⊠ q | ||
@test isapprox(sum(ρq), sum_ρq_init; atol = 0.029) | ||
@test isapprox(sum(ρq), sum_ρq_init; rtol = 0.00023) | ||
# @show sum(ρq) # 125.5483524237572 | ||
# @show sum_ρq_init # 125.52052986152977 | ||
end | ||
|
||
@testset "Vertical mass borrowing limiter - deep atmosphere" begin | ||
z_elem = 10 | ||
z_min = 0 | ||
z_max = 1 | ||
radius = 10 | ||
h_elem = 10 | ||
n_quad_points = 4 | ||
grid = ExtrudedCubedSphereGrid(; | ||
z_elem, | ||
z_min, | ||
z_max, | ||
radius, | ||
global_geometry = Geometry.DeepSphericalGlobalGeometry(radius), | ||
h_elem, | ||
n_quad_points, | ||
) | ||
cspace = Spaces.ExtrudedFiniteDifferenceSpace(grid, Grids.CellCenter()) | ||
tol = 0.01 | ||
perturb_q = 0.2 | ||
perturb_ρ = 0.1 | ||
|
||
# Initialize fields | ||
coords = Fields.coordinate_field(cspace) | ||
ρ = map(coord -> 1.0, coords) | ||
q = map(coord -> 0.1, coords) | ||
|
||
perturb_field!(q; perturb_radius = perturb_q) | ||
perturb_field!(ρ; perturb_radius = perturb_ρ) | ||
ρq_init = ρ .⊠ q | ||
sum_ρq_init = sum(ρq_init) | ||
|
||
# Test that the minimum is below 0 | ||
@test length(parent(q)) == z_elem * h_elem^2 * 6 * n_quad_points^2 == 96000 | ||
@test 0.10 ≤ count(x -> x < 0.0001, parent(q)) / 96000 ≤ 1 # ensure 10% of points are negative | ||
|
||
@test -2 * perturb_q ≤ minimum(q) ≤ -tol | ||
limiter = Limiters.VerticalMassBorrowingLimiter(q, (0.0,)) | ||
Limiters.apply_limiter!(q, ρ, limiter) | ||
@test 0 ≤ minimum(q) | ||
ρq = ρ .⊠ q | ||
@test isapprox(sum(ρq), sum_ρq_init; atol = 0.269) | ||
@test isapprox(sum(ρq), sum_ρq_init; rtol = 0.00199) | ||
# @show sum(ρq) # 138.90494931641584 | ||
# @show sum_ρq_init # 139.1735731377394 | ||
end |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
where
ΔV_vals = WJ
W
has horizontal terms + has an additional/2
on boundaries, so instead useΔV_vals = J
. (thanks, @dennisYatunin!).