Skip to content

Commit 930818e

Browse files
committed
Fix column integrals for deep atmosphere
1 parent 0f4b23e commit 930818e

File tree

4 files changed

+105
-48
lines changed

4 files changed

+105
-48
lines changed

src/Operators/integrals.jl

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,37 +5,50 @@ import ClimaComms
55
"""
66
column_integral_definite!(ϕ_top, ᶜ∂ϕ∂z, [ϕ_bot])
77
8-
Sets `ϕ_top```{}= \\int_{z_{bot}}^{z_{top}}\\,```ᶜ∂ϕ∂z```(z)\\,dz +{}```ϕ_bot`,
9-
where ``z_{bot}`` and ``z_{top}`` are the values of `z` at the bottom and top of
10-
the domain, respectively. The input `ᶜ∂ϕ∂z` should be a cell-center `Field` or
11-
`AbstractBroadcasted`, and the output `ϕ_top` should be a horizontal `Field`.
12-
The default value of `ϕ_bot` is 0.
8+
Sets `ϕ_top```{}= \\frac{1}{ΔA(z_{bot})}\\int_{z_{bot}}^{z_{top}}\\,
9+
```ᶜ∂ϕ∂z```(z)\\,ΔA(z)\\,dz +{}```ϕ_bot`, where ``z_{bot}`` and ``z_{top}`` are
10+
the values of `z` at the bottom and top of the domain, and where `ΔA` is the
11+
area differential `J/Δz`, with `J` denoting the metric Jacobian. The input
12+
`ᶜ∂ϕ∂z` should be a cell-center `Field` or `AbstractBroadcasted`, and the output
13+
`ϕ_top` should be a horizontal `Field`. The default value of `ϕ_bot` is 0.
1314
"""
1415
function column_integral_definite!(ϕ_top, ᶜ∂ϕ∂z, ϕ_bot = rzero(eltype(ϕ_top)))
15-
ᶜΔϕ = Base.Broadcast.broadcasted(, ᶜ∂ϕ∂z, Fields.Δz_field(axes(ᶜ∂ϕ∂z)))
16+
ᶜJ = Fields.local_geometry_field(axes(ᶜ∂ϕ∂z)).J
17+
f_space = Spaces.face_space(axes(ᶜ∂ϕ∂z))
18+
J_bot = Fields.level(Fields.local_geometry_field(f_space).J, half)
19+
Δz_bot = Fields.level(Fields.Δz_field(f_space), half)
20+
ΔA_bot = Base.broadcasted(/, J_bot, Δz_bot)
21+
ᶜΔϕ = Base.broadcasted(, ᶜ∂ϕ∂z, Base.broadcasted(/, ᶜJ, ΔA_bot))
1622
column_reduce!(, ϕ_top, ᶜΔϕ; init = ϕ_bot)
1723
end
1824

1925
"""
2026
column_integral_indefinite!(ᶠϕ, ᶜ∂ϕ∂z, [ϕ_bot])
2127
22-
Sets `ᶠϕ```(z) = \\int_{z_{bot}}^z\\,```ᶜ∂ϕ∂z```(z')\\,dz' +{}```ϕ_bot`, where
23-
``z_{bot}`` is the value of `z` at the bottom of the domain. The input `ᶜ∂ϕ∂z`
24-
should be a cell-center `Field` or `AbstractBroadcasted`, and the output `ᶠϕ`
25-
should be a cell-face `Field`. The default value of `ϕ_bot` is 0.
28+
Sets `ᶠϕ```(z) = \\frac{1}{ΔA(z_{bot})}\\int_{z_{bot}}^z\\,```ᶜ∂ϕ∂z```(z')\\,
29+
ΔA(z')\\,dz' +{}```ϕ_bot`, where ``z_{bot}`` is the value of `z` at the bottom
30+
of the domain, and where `ΔA` is the area differential `J/Δz`, with `J` denoting
31+
the metric Jacobian. The input `ᶜ∂ϕ∂z` should be a cell-center `Field` or
32+
`AbstractBroadcasted`, and the output `ᶠϕ` should be a cell-face `Field`. The
33+
default value of `ϕ_bot` is 0.
2634
2735
column_integral_indefinite!(∂ϕ∂z, ᶠϕ, [ϕ_bot], [rtol])
2836
29-
Sets
30-
`ᶠϕ```(z) = \\int_{z_{bot}}^z\\,```∂ϕ∂z```(```ᶠϕ```(z'), z')\\,dz' +{}```ϕ_bot`,
31-
where `∂ϕ∂z` can be any scalar-valued two-argument function. The output `ᶠϕ`
32-
satisfies `ᶜgradᵥ.(ᶠϕ) ≈ ∂ϕ∂z.(ᶜint.(ᶠϕ), ᶜz)`, where `ᶜgradᵥ = GradientF2C()`,
33-
`ᶜint = InterpolateF2C()`, and `ᶜz = Fields.coordinate_field(ᶜint.(ᶠϕ)).z`, and
34-
where the approximation is accurate to a relative tolerance of `rtol`. The
37+
Sets `ᶠϕ```(z) = \\frac{1}{ΔA(z_{bot})}\\int_{z_{bot}}^z\\,
38+
```∂ϕ∂z```(```ᶠϕ```(z'), z')\\,ΔA(z')\\,dz' +{}```ϕ_bot`, where `∂ϕ∂z` can be
39+
any scalar-valued two-argument function. When a shallow atmosphere approximation
40+
is used, `ΔA = ΔA_{bot}` at all values of `z`, and the output `ᶠϕ` satisfies
41+
`ᶜgradᵥ.(ᶠϕ) ≈ ∂ϕ∂z.(ᶜint.(ᶠϕ), ᶜz)` with a relative tolerance of `rtol`, where
42+
`ᶜgradᵥ = GradientF2C()` and `ᶜint = InterpolateF2C()`. When a deep atmosphere
43+
is used, the vertical gradient is replaced with an area-weighted gradient. The
3544
default value of `ϕ_bot` is 0, and the default value of `rtol` is 0.001.
3645
"""
3746
function column_integral_indefinite!(ᶠϕ, ᶜ∂ϕ∂z, ϕ_bot = rzero(eltype(ᶠϕ)))
38-
ᶜΔϕ = Base.Broadcast.broadcasted(, ᶜ∂ϕ∂z, Fields.Δz_field(axes(ᶜ∂ϕ∂z)))
47+
ᶜJ = Fields.local_geometry_field(axes(ᶜ∂ϕ∂z)).J
48+
J_bot = Fields.level(Fields.local_geometry_field(ᶠϕ).J, half)
49+
Δz_bot = Fields.level(Fields.Δz_field(ᶠϕ), half)
50+
ΔA_bot = Base.broadcasted(/, J_bot, Δz_bot)
51+
ᶜΔϕ = Base.broadcasted(, ᶜ∂ϕ∂z, Base.broadcasted(/, ᶜJ, ΔA_bot))
3952
column_accumulate!(, ᶠϕ, ᶜΔϕ; init = ϕ_bot)
4053
end
4154
function column_integral_indefinite!(
@@ -45,26 +58,23 @@ function column_integral_indefinite!(
4558
rtol = eltype(ᶠϕ)(0.001),
4659
) where {F <: Function}
4760
device = ClimaComms.device(ᶠϕ)
48-
face_space = axes(ᶠϕ)
49-
center_space = if face_space isa Spaces.FaceFiniteDifferenceSpace
50-
Spaces.CenterFiniteDifferenceSpace(face_space)
51-
elseif face_space isa Spaces.FaceExtrudedFiniteDifferenceSpace
52-
Spaces.CenterExtrudedFiniteDifferenceSpace(face_space)
53-
else
54-
error("output of column_integral_indefinite! must be on cell faces")
55-
end
56-
ᶜz = Fields.coordinate_field(center_space).z
57-
ᶜΔz = Fields.Δz_field(center_space)
58-
ᶜz_and_Δz = Base.Broadcast.broadcasted(tuple, ᶜz, ᶜΔz)
59-
column_accumulate!(ᶠϕ, ᶜz_and_Δz; init = ϕ_bot) do ϕ_prev, (z, Δz)
60-
residual(ϕ_new) = (ϕ_new - ϕ_prev) / Δz - ∂ϕ∂z((ϕ_prev + ϕ_new) / 2, z)
61+
c_space = Spaces.center_space(axes(ᶠϕ))
62+
ᶜz = Fields.coordinate_field(c_space).z
63+
ᶜJ = Fields.local_geometry_field(c_space).J
64+
J_bot = Fields.level(Fields.local_geometry_field(ᶠϕ).J, half)
65+
Δz_bot = Fields.level(Fields.Δz_field(ᶠϕ), half)
66+
ΔA_bot = Base.broadcasted(/, J_bot, Δz_bot)
67+
ᶜz_and_Δz = Base.broadcasted(tuple, ᶜz, Base.broadcasted(/, ᶜJ, ΔA_bot))
68+
column_accumulate!(ᶠϕ, ᶜz_and_Δz; init = ϕ_bot) do ϕ_prev, (z, weighted_Δz)
69+
residual(ϕ_new) =
70+
(ϕ_new - ϕ_prev) / weighted_Δz - ∂ϕ∂z((ϕ_prev + ϕ_new) / 2, z)
6171
(; converged, root) = RootSolvers.find_zero(
6272
residual,
6373
RootSolvers.NewtonsMethodAD(ϕ_prev),
6474
RootSolvers.CompactSolution(),
6575
RootSolvers.RelativeSolutionTolerance(rtol),
6676
)
67-
ClimaComms.@assert device converged "∂ϕ∂z could not be integrated over \
77+
ClimaComms.@assert device converged "unable to integrate through \
6878
z = $z with rtol set to $rtol"
6979
return root
7080
end

src/Spaces/extruded.jl

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
ExtrudedFiniteDifferenceSpace(
66
horizontal_space::AbstractSpace,
77
vertical_space::FiniteDifferenceSpace,
8-
hypsography::Grids.HypsographyAdaption = Grids.Flat(),
8+
hypsography::Grids.HypsographyAdaption = Grids.Flat();
9+
deep::Bool = false,
910
)
1011
1112
An extruded finite-difference space,
@@ -68,12 +69,14 @@ ExtrudedFiniteDifferenceSpace{S}(
6869
function ExtrudedFiniteDifferenceSpace(
6970
horizontal_space::AbstractSpace,
7071
vertical_space::FiniteDifferenceSpace,
71-
hypsography::Grids.HypsographyAdaption = Grids.Flat(),
72+
hypsography::Grids.HypsographyAdaption = Grids.Flat();
73+
deep = false,
7274
)
7375
grid_space = Grids.ExtrudedFiniteDifferenceGrid(
7476
grid(horizontal_space),
7577
grid(vertical_space),
76-
hypsography,
78+
hypsography;
79+
deep,
7780
)
7881
return ExtrudedFiniteDifferenceSpace(grid_space, vertical_space.staggering)
7982
end

test/Operators/integrals.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Test
22
using JET
3+
import Random
34
import CUDA # explicitly required due to JET
45
import ClimaComms
56
ClimaComms.@import_required_backends
@@ -152,6 +153,22 @@ function test_column_reduce_and_accumulate!(center_space)
152153
end
153154
end
154155

156+
function test_fubinis_theorem(space)
157+
FT = Spaces.undertype(space)
158+
center_field = zeros(space)
159+
face_field = zeros(center_to_face_space(space))
160+
surface_field = Fields.level(face_field, Fields.half)
161+
surface_Δz = Fields.Δz_field(surface_field) ./ 2
162+
163+
Random.seed!(1) # ensures reproducibility
164+
parent(center_field) .= rand.(FT)
165+
volume_sum = sum(center_field)
166+
167+
column_integral_definite!(surface_field, center_field)
168+
horizontal_sum_of_vertical_sum = sum(surface_field ./ surface_Δz)
169+
@test horizontal_sum_of_vertical_sum volume_sum rtol = 10 * eps(FT)
170+
end
171+
155172
@testset "Integral operations unit tests" begin
156173
device = ClimaComms.device()
157174
context = ClimaComms.SingletonCommsContext(device)
@@ -167,3 +184,15 @@ end
167184
end
168185
end
169186
end
187+
188+
@testset "Volume integral consistency" begin
189+
device = ClimaComms.device()
190+
context = ClimaComms.SingletonCommsContext(device)
191+
for FT in (Float32, Float64)
192+
for topography in (false, true), deep in (false, true)
193+
space_kwargs = (; context, topography, deep)
194+
space = TU.CenterExtrudedFiniteDifferenceSpace(FT; space_kwargs...)
195+
test_fubinis_theorem(space)
196+
end
197+
end
198+
end

test/TestUtilities/TestUtilities.jl

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import ClimaCore.Meshes
2020
import ClimaCore.Spaces
2121
import ClimaCore.Topologies
2222
import ClimaCore.Domains
23+
import ClimaCore.Hypsography
2324

2425
function PointSpace(
2526
::Type{FT};
@@ -110,17 +111,20 @@ function CenterExtrudedFiniteDifferenceSpace(
110111
context = ClimaComms.SingletonCommsContext(),
111112
helem = 4,
112113
Nq = 4,
114+
deep = false,
115+
topography = false,
113116
horizontal_layout_type = DataLayouts.IJFH,
114117
) where {FT}
115118
radius = FT(128)
116-
zlim = (0, 1)
117-
vertdomain = Domains.IntervalDomain(
118-
Geometry.ZPoint{FT}(zlim[1]),
119-
Geometry.ZPoint{FT}(zlim[2]);
119+
zlim = (FT(0), FT(1))
120+
121+
vdomain = Domains.IntervalDomain(
122+
Geometry.ZPoint(zlim[1]),
123+
Geometry.ZPoint(zlim[2]);
120124
boundary_names = (:bottom, :top),
121125
)
122-
vertmesh = Meshes.IntervalMesh(vertdomain, nelems = zelem)
123-
vtopology = Topologies.IntervalTopology(context, vertmesh)
126+
vmesh = Meshes.IntervalMesh(vdomain, nelems = zelem)
127+
vtopology = Topologies.IntervalTopology(context, vmesh)
124128
vspace = Spaces.CenterFiniteDifferenceSpace(vtopology)
125129

126130
hdomain = Domains.SphereDomain(radius)
@@ -129,16 +133,27 @@ function CenterExtrudedFiniteDifferenceSpace(
129133
quad = Quadratures.GLL{Nq}()
130134
hspace =
131135
Spaces.SpectralElementSpace2D(htopology, quad; horizontal_layout_type)
132-
return Spaces.ExtrudedFiniteDifferenceSpace(hspace, vspace)
136+
137+
hypsography = if topography
138+
# some non-trivial function of latitude and longitude
139+
H = (zlim[2] - zlim[1]) / zelem
140+
(; lat, long) = Fields.coordinate_field(hspace)
141+
surface_elevation =
142+
@. Geometry.ZPoint(H * (cosd(lat) + cosd(long) + 1))
143+
Hypsography.LinearAdaption(surface_elevation)
144+
else
145+
Hypsography.Flat()
146+
end
147+
return Spaces.ExtrudedFiniteDifferenceSpace(
148+
hspace,
149+
vspace,
150+
hypsography;
151+
deep,
152+
)
133153
end
134154

135-
function FaceExtrudedFiniteDifferenceSpace(
136-
::Type{FT};
137-
zelem = 10,
138-
helem = 4,
139-
context = ClimaComms.SingletonCommsContext(),
140-
) where {FT}
141-
cspace = CenterExtrudedFiniteDifferenceSpace(FT; zelem, context, helem)
155+
function FaceExtrudedFiniteDifferenceSpace(::Type{FT}; kwargs...) where {FT}
156+
cspace = CenterExtrudedFiniteDifferenceSpace(FT; kwargs...)
142157
return Spaces.FaceExtrudedFiniteDifferenceSpace(cspace)
143158
end
144159

0 commit comments

Comments
 (0)