Skip to content

Commit 79287d2

Browse files
committed
Add topography module and types
1 parent 5f05cc5 commit 79287d2

File tree

7 files changed

+206
-57
lines changed

7 files changed

+206
-57
lines changed

docs/src/api.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,23 @@ ClimaAtmos.AutoDenseJacobian
5959
ClimaAtmos.AutoSparseJacobian
6060
```
6161

62+
## Topography
63+
64+
```@docs
65+
ClimaAtmos.Topography.CosineTopography
66+
ClimaAtmos.Topography.AgnesiTopography
67+
ClimaAtmos.Topography.ScharTopography
68+
ClimaAtmos.Topography.EarthTopography
69+
ClimaAtmos.Topography.DCMIP200Topography
70+
ClimaAtmos.Topography.Hughes2023Topography
71+
ClimaAtmos.Topography.topography_schar
72+
ClimaAtmos.Topography.topography_cosine_3d
73+
ClimaAtmos.Topography.topography_agnesi
74+
ClimaAtmos.Topography.topography_hughes2023
75+
ClimaAtmos.Topography.topography_dcmip200
76+
ClimaAtmos.Topography.topography_cosine_2d
77+
```
78+
6279
### Internals
6380

6481
```@docs

src/ClimaAtmos.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ include(joinpath("utils", "AtmosArtifacts.jl"))
3131
import .AtmosArtifacts as AA
3232

3333
include(joinpath("topography", "topography.jl"))
34+
include(joinpath("topography", "steady_state_velocity.jl"))
3435
include(joinpath("topography", "steady_state_solutions.jl"))
3536

3637
include(joinpath("parameterized_tendencies", "radiation", "RRTMGPInterface.jl"))

src/solver/type_getters.jl

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -424,39 +424,30 @@ function get_initial_condition(parsed_args, atmos)
424424
end
425425
end
426426

427+
function get_topography(parsed_args)
428+
topo_str = parsed_args["topography"]
429+
topo_types = Dict("NoWarp" => Topography.NoTopography(),
430+
"Cosine2D" => Topography.CosineTopography(; dim = 2),
431+
"Cosine3D" => Topography.CosineTopography(; dim = 3),
432+
"Agnesi" => Topography.AgnesiTopography(),
433+
"Schar" => Topography.ScharTopography(),
434+
"Earth" => Topography.EarthTopography(),
435+
"Hughes2023" => Topography.Hughes2023Topography(),
436+
"DCMIP200" => Topography.DCMIP200Topography(),
437+
)
438+
439+
@assert topo_str in keys(topo_types)
440+
return topo_types[topo_str]
441+
end
442+
427443
function get_steady_state_velocity(params, Y, parsed_args)
428444
parsed_args["check_steady_state"] || return nothing
429445
parsed_args["initial_condition"] == "ConstantBuoyancyFrequencyProfile" &&
430446
parsed_args["mesh_warp_type"] == "Linear" ||
431447
error("The steady-state velocity can currently be computed only for a \
432448
ConstantBuoyancyFrequencyProfile with Linear mesh warping")
433-
topography = parsed_args["topography"]
434-
steady_state_velocity = if topography == "NoWarp"
435-
steady_state_velocity_no_warp
436-
elseif topography == "Cosine2D"
437-
steady_state_velocity_cosine_2d
438-
elseif topography == "Cosine3D"
439-
steady_state_velocity_cosine_3d
440-
elseif topography == "Agnesi"
441-
steady_state_velocity_agnesi
442-
elseif topography == "Schar"
443-
steady_state_velocity_schar
444-
else
445-
error("The steady-state velocity for $topography topography cannot \
446-
be computed analytically")
447-
end
448-
top_level = Spaces.nlevels(axes(Y.c)) + Fields.half
449-
z_top = Fields.level(Fields.coordinate_field(Y.f).z, top_level)
450-
451-
# TODO: This can be very expensive! It should be moved to a separate CI job.
452-
@info "Approximating steady-state velocity"
453-
s = @timed_str begin
454-
ᶜu = steady_state_velocity.(params, Fields.coordinate_field(Y.c), z_top)
455-
ᶠu =
456-
steady_state_velocity.(params, Fields.coordinate_field(Y.f), z_top)
457-
end
458-
@info "Steady-state velocity approximation completed: $s"
459-
return (; ᶜu, ᶠu)
449+
topography = get_topography(parsed_args)
450+
return compute_steady_state_velocity(topography, params, Y)
460451
end
461452

462453
function get_surface_setup(parsed_args)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import .Topography:
2+
NoTopography, CosineTopography, AgnesiTopography, ScharTopography
3+
4+
"""
5+
compute_steady_state_velocity(topography, params, z_top)
6+
7+
Compute the steady-state velocity for the given topography type.
8+
"""
9+
function compute_steady_state_velocity(::NoTopography, params, Y)
10+
top_level = Spaces.nlevels(axes(Y.c)) + Fields.half
11+
z_top = Fields.level(Fields.coordinate_field(Y.f).z, top_level)
12+
13+
ᶜu = steady_state_velocity_no_warp.(params, Fields.coordinate_field(Y.c), z_top)
14+
ᶠu = steady_state_velocity_no_warp.(params, Fields.coordinate_field(Y.f), z_top)
15+
16+
return (; ᶜu, ᶠu)
17+
end
18+
19+
function compute_steady_state_velocity(topography::CosineTopography, params, Y)
20+
top_level = Spaces.nlevels(axes(Y.c)) + Fields.half
21+
z_top = Fields.level(Fields.coordinate_field(Y.f).z, top_level)
22+
23+
if topography.dimension == 2
24+
ᶜu = steady_state_velocity_cosine_2d.(params, Fields.coordinate_field(Y.c), z_top)
25+
ᶠu = steady_state_velocity_cosine_2d.(params, Fields.coordinate_field(Y.f), z_top)
26+
elseif topography.dimension == 3
27+
ᶜu = steady_state_velocity_cosine_3d.(params, Fields.coordinate_field(Y.c), z_top)
28+
ᶠu = steady_state_velocity_cosine_3d.(params, Fields.coordinate_field(Y.f), z_top)
29+
else
30+
throw(
31+
ArgumentError(
32+
"CosineTopography dimension must be 2 or 3, got $(topography.dimension)",
33+
),
34+
)
35+
end
36+
37+
return (; ᶜu, ᶠu)
38+
end
39+
40+
function compute_steady_state_velocity(::AgnesiTopography, params, Y)
41+
top_level = Spaces.nlevels(axes(Y.c)) + Fields.half
42+
z_top = Fields.level(Fields.coordinate_field(Y.f).z, top_level)
43+
44+
ᶜu = steady_state_velocity_agnesi.(params, Fields.coordinate_field(Y.c), z_top)
45+
ᶠu = steady_state_velocity_agnesi.(params, Fields.coordinate_field(Y.f), z_top)
46+
47+
return (; ᶜu, ᶠu)
48+
end
49+
50+
function compute_steady_state_velocity(topography::ScharTopography, params, Y)
51+
top_level = Spaces.nlevels(axes(Y.c)) + Fields.half
52+
z_top = Fields.level(Fields.coordinate_field(Y.f).z, top_level)
53+
54+
ᶜu = steady_state_velocity_schar.(params, Fields.coordinate_field(Y.c), z_top)
55+
ᶠu = steady_state_velocity_schar.(params, Fields.coordinate_field(Y.f), z_top)
56+
57+
return (; ᶜu, ᶠu)
58+
end

src/topography/topography.jl

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
module Topography
2+
3+
using ClimaCore: Geometry
4+
5+
export topography_dcmip200, topography_hughes2023
6+
export topography_agnesi, agnesi_params
7+
export topography_schar, schar_params
8+
export topography_cosine_2d, topography_cosine_3d
9+
export topography_cosine, cosine_params
10+
111
"""
212
topography_dcmip200(coord)
313
@@ -113,3 +123,97 @@ end
113123
topography_cosine(x, y, λ_x, λ_y, h_max) =
114124
h_max * cospi(2 * x / λ_x) * cospi(2 * y / λ_y)
115125
cosine_params(::Type{FT}) where {FT} = (; h_max = FT(25), λ = FT(25e3))
126+
127+
abstract type AbstractTopography end
128+
129+
topo_func(t::AbstractTopography) = error("No topography function for topography $t")
130+
131+
struct NoTopography <: AbstractTopography end
132+
133+
# Analytical topography types for idealized test cases
134+
135+
"""
136+
CosineTopography(; h_max = 25, λ = 25e3, dim = 2)
137+
138+
Cosine hill topography in 2D or 3D.
139+
140+
# Arguments
141+
- `h_max::FT`: Maximum elevation (m)
142+
- `λ::FT`: Wavelength of the cosine hills (m)
143+
- `dim::Int`: Spatial dimension (2 or 3)
144+
"""
145+
Base.@kwdef struct CosineTopography <: AbstractTopography
146+
h_max = 25
147+
λ = 25e3
148+
dim::Int = 2
149+
end
150+
151+
topo_func(t::CosineTopography) = t.dim == 2 ? topography_cosine_2d : topography_cosine_3d
152+
153+
"""
154+
AgnesiTopography(; h_max = 25, x_center = 50e3, a = 5e3)
155+
156+
Witch of Agnesi mountain topography for 2D simulations.
157+
158+
# Arguments
159+
- `h_max`: Maximum elevation (m)
160+
- `x_center`: Center position (m)
161+
- `a`: Mountain width parameter (m)
162+
"""
163+
Base.@kwdef struct AgnesiTopography <: AbstractTopography
164+
h_max = 25
165+
x_center = 50e3
166+
a = 5e3
167+
end
168+
169+
topo_func(::AgnesiTopography) = topography_agnesi
170+
171+
172+
"""
173+
ScharTopography(; h_max = 25, x_center = 50e3, λ = 4e3, a = 5e3)
174+
175+
Schar mountain topography for 2D simulations.
176+
177+
# Arguments
178+
- `h_max`: Maximum elevation (m)
179+
- `x_center`: Center position (m)
180+
- `λ`: Wavelength parameter (m)
181+
- `a`: Mountain width parameter (m)
182+
"""
183+
Base.@kwdef struct ScharTopography <: AbstractTopography
184+
h_max = 25
185+
x_center = 50e3
186+
λ = 4e3
187+
a = 5e3
188+
end
189+
topo_func(::ScharTopography) = topography_schar
190+
191+
192+
# Data-based topography types
193+
194+
"""
195+
EarthTopography()
196+
197+
Earth topography from ETOPO2022 data files.
198+
"""
199+
struct EarthTopography <: AbstractTopography end
200+
201+
"""
202+
DCMIP200Topography()
203+
204+
Surface elevation for the DCMIP-2-0-0 test problem.
205+
"""
206+
struct DCMIP200Topography <: AbstractTopography end
207+
208+
topo_func(::DCMIP200Topography) = topography_dcmip200
209+
210+
"""
211+
Hughes2023Topography()
212+
213+
Surface elevation for baroclinic wave test from Hughes and Jablonowski (2023).
214+
"""
215+
struct Hughes2023Topography <: AbstractTopography end
216+
217+
topo_func(::Hughes2023Topography) = topography_hughes2023
218+
219+
end

src/utils/common_spaces.jl

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -96,21 +96,11 @@ function make_hybrid_spaces(
9696
)
9797
z_grid = Grids.FiniteDifferenceGrid(z_topology)
9898

99-
topography = parsed_args["topography"]
100-
@assert topography in (
101-
"NoWarp",
102-
"Earth",
103-
"DCMIP200",
104-
"Hughes2023",
105-
"Agnesi",
106-
"Schar",
107-
"Cosine2D",
108-
"Cosine3D",
109-
)
110-
if topography == "NoWarp"
99+
topography = get_topography(parsed_args)
100+
if topography isa Topography.NoTopography
111101
z_surface = zeros(h_space)
112102
@info "No surface orography warp applied"
113-
elseif topography == "Earth"
103+
elseif topography isa Topography.EarthTopography
114104
z_surface = SpaceVaryingInput(
115105
AA.earth_orography_file_path(;
116106
context = ClimaComms.context(h_space),
@@ -120,26 +110,14 @@ function make_hybrid_spaces(
120110
)
121111
@info "Remapping Earth orography from ETOPO2022 data onto horizontal space"
122112
else
123-
topography_function = if topography == "DCMIP200"
124-
topography_dcmip200
125-
elseif topography == "Hughes2023"
126-
topography_hughes2023
127-
elseif topography == "Agnesi"
128-
topography_agnesi
129-
elseif topography == "Schar"
130-
topography_schar
131-
elseif topography == "Cosine2D"
132-
topography_cosine_2d
133-
elseif topography == "Cosine3D"
134-
topography_cosine_3d
135-
end
113+
topography_function = Topography.topo_func(topography)
136114
z_surface = SpaceVaryingInput(topography_function, h_space)
137115
@info "Using $topography orography"
138116
end
139117

140-
if topography == "NoWarp"
118+
if topography isa Topography.NoTopography
141119
hypsography = Hypsography.Flat()
142-
elseif topography == "Earth"
120+
elseif topography isa Topography.EarthTopography
143121
mask(x::FT) where {FT} = x * FT(x > 0)
144122
z_surface = @. mask(z_surface)
145123
# diff_cfl = νΔt/Δx²

test/topography.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ include("test_helpers.jl")
3030

3131
@testset "test topography functions" begin
3232
(; FT, coords) = get_spherical_spaces()
33-
@test extrema(CA.topography_dcmip200.(coords)) == (FT(0), FT(2000))
34-
loc = findmax(parent(CA.topography_dcmip200.(coords)))
33+
@test extrema(CA.Topography.topography_dcmip200.(coords)) == (FT(0), FT(2000))
34+
loc = findmax(parent(CA.Topography.topography_dcmip200.(coords)))
3535
@test parent(coords.lat)[loc[2]] == FT(0)
3636
@test parent(coords.long)[loc[2]] == FT(-90)
3737
end

0 commit comments

Comments
 (0)