Skip to content

Commit cea0750

Browse files
imreddyTejajuliasloan25
authored andcommitted
add ClimaLandSimulation struct
1 parent b57cbaa commit cea0750

File tree

12 files changed

+669
-170
lines changed

12 files changed

+669
-170
lines changed

NEWS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ ClimaCoupler.jl Release Notes
66

77
### ClimaCoupler features
88

9+
#### Add `ClimaLandSimulation` object PR[#1199](https://github.com/CliMA/ClimaCoupler.jl/pull/1199)
10+
Add methods to support running `ClimaLand.LandModel` in a coupled simulation.
11+
Also add tests to verify the constructor setup and taking a step.
12+
This type is not yet tested within a coupled simulation, but much
13+
of the necessary software infrastructure is added in this PR.
14+
915
#### Add default `get_field` methods for surface models PR[#1210](https://github.com/CliMA/ClimaCoupler.jl/pull/1210)
1016
Add default methods for `get_field` methods that are commonly
1117
not extended for surface models. These return reasonable default

docs/src/interfacer.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ be done in place. If this function isn't extended for a property,
109109
that property will remain constant throughout the simulation
110110
and a warning will be raised.
111111
This function is expected to be extended for the
112-
following properties:
112+
following properties, and may also be extended for any additional
113+
properties needed by a component model.
113114

114115
| Coupler name | Description | Units |
115116
|-------------------|-------------|-------|
@@ -174,7 +175,8 @@ isn't extended for a property,
174175
that property will remain constant throughout the simulation
175176
and a warning will be raised.
176177
This function is expected to be extended for the
177-
following properties:
178+
following properties, and may also be extended for any additional
179+
properties needed by a component model.
178180

179181
| Coupler name | Description | Units |
180182
|-------------------|-------------|-------|
@@ -183,8 +185,8 @@ following properties:
183185
| `liquid_precipitation` | liquid precipitation at the surface | kg m^-2 s^-1 |
184186
| `radiative_energy_flux_sfc` | net radiative flux at the surface | W m^-2 |
185187
| `snow_precipitation` | snow precipitation at the surface | kg m^-2 s^-1 |
186-
| `turbulent_energy_flux` | aerodynamic turbulent surface fluxes of energy (sensible and latent heat) | W m^-2 |
187-
| `turbulent_moisture_flux` | aerodynamic turbulent surface fluxes of energy (evaporation) | kg m^-2 s^-1 |
188+
| `turbulent_energy_flux` | aerodynamic turbulent surface fluxes of energy (sensible and latent heat); only required when using `CombinedStateFluxes` option - see our `FluxCalculator` module docs for more information | W m^-2 |
189+
| `turbulent_moisture_flux` | aerodynamic turbulent surface fluxes of energy (evaporation); only required when using `CombinedStateFluxes` option - see our `FluxCalculator` module docs for more information | kg m^-2 s^-1 |
188190
| `surface_direct_albedo` | bulk direct surface albedo; needed if calculated externally of the surface model (e.g. ocean albedo from the atmospheric state) | |
189191
| `surface_diffuse_albedo` | bulk diffuse surface albedo; needed if calculated externally of the surface model (e.g. ocean albedo from the atmospheric state) | |
190192

experiments/ClimaEarth/cli_options.jl

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,6 @@ function argparse_settings()
139139
arg_type = Vector{Dict{Any, Any}}
140140
default = []
141141
### ClimaLand specific
142-
"--land_domain_type"
143-
help = "Type of land domain. [`sphere` (default), `single_column`]"
144-
arg_type = String
145-
default = "sphere"
146142
"--land_albedo_type"
147143
help = "Access land surface albedo information from data file. [`map_static` (default), `function`, `map_temporal`]"
148144
arg_type = String

experiments/ClimaEarth/components/land/climaland_bucket.jl

Lines changed: 43 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,30 @@ import ClimaLand.Parameters as LP
1010
import ClimaDiagnostics as CD
1111
import ClimaCoupler: Checkpointer, FluxCalculator, Interfacer
1212
using NCDatasets
13+
include("climaland_helpers.jl")
1314

1415
include("../shared/restore.jl")
1516

1617
###
1718
### Functions required by ClimaCoupler.jl for a SurfaceModelSimulation
1819
###
1920
"""
20-
BucketSimulation{M, D, I}
21+
BucketSimulation{M, D, I, A}
2122
2223
The bucket model simulation object.
24+
25+
It contains the following objects:
26+
- `model::M`: The `ClimaLand.Bucket.BucketModel`.
27+
- `domain::D`: The land domain object, which must be a spherical shell.
28+
- `integrator::I`: The integrator used in timestepping this model.
29+
- `area_fraction::A`: A ClimaCore Field representing the surface area fraction of this component model.
2330
"""
24-
struct BucketSimulation{M, D, I, A} <: Interfacer.LandModelSimulation
31+
struct BucketSimulation{
32+
M <: ClimaLand.Bucket.BucketModel,
33+
D <: ClimaLand.Domains.SphericalShell,
34+
I <: SciMLBase.AbstractODEIntegrator,
35+
A <: CC.Fields.Field,
36+
} <: Interfacer.LandModelSimulation
2537
model::M
2638
domain::D
2739
integrator::I
@@ -31,7 +43,6 @@ Interfacer.name(::BucketSimulation) = "BucketSimulation"
3143

3244
"""
3345
get_new_cache(p, Y, energy_check)
34-
3546
Returns a new `p` with an updated field to store e_per_area if energy conservation
3647
checks are turned on.
3748
"""
@@ -50,43 +61,37 @@ end
5061
Initializes the bucket model variables.
5162
"""
5263
function BucketSimulation(
53-
::Type{FT},
54-
tspan::Tuple{TT, TT},
55-
config::String,
56-
albedo_type::String,
57-
land_initial_condition::String,
58-
land_temperature_anomaly::String,
59-
output_dir::String;
60-
space,
64+
::Type{FT};
6165
dt::TT,
62-
saveat::Vector{TT},
66+
tspan::Tuple{TT, TT},
67+
start_date::Dates.DateTime,
68+
output_dir::String,
69+
boundary_space,
6370
area_fraction,
71+
saveat::Vector{TT} = [tspan[1], tspan[2]],
72+
domain_type::String = "sphere",
73+
surface_elevation = CC.Fields.zeros(boundary_space),
74+
land_temperature_anomaly::String = "amip",
75+
use_land_diagnostics::Bool = true,
6476
stepper = CTS.RK4(),
65-
date_ref::Dates.DateTime,
66-
t_start::TT,
67-
energy_check::Bool,
68-
surface_elevation,
69-
use_land_diagnostics::Bool,
77+
albedo_type::String = "map_static",
78+
land_initial_condition::String = "",
79+
energy_check::Bool = false,
7080
) where {FT, TT <: Union{Float64, ITime}}
71-
if config != "sphere"
72-
println(
73-
"Currently only spherical shell domains are supported; single column set-up will be addressed in future PR.",
74-
)
75-
@assert config == "sphere"
76-
end
81+
@assert domain_type == "sphere" "Currently only spherical shell domains are supported; single column may be supported in the future."
7782

7883
α_snow = FT(0.8) # snow albedo
7984
if albedo_type == "map_static" # Read in albedo from static data file (default type)
8085
# By default, this uses a file containing bareground albedo without a time component. Snow albedo is specified separately.
81-
albedo = CL.Bucket.PrescribedBaregroundAlbedo{FT}(α_snow, space)
86+
albedo = CL.Bucket.PrescribedBaregroundAlbedo{FT}(α_snow, boundary_space)
8287
elseif albedo_type == "map_temporal" # Read in albedo from data file containing data over time
8388
# By default, this uses a file containing linearly-interpolated monthly data of clear-sky albedo, generated from CERES.
8489
if pkgversion(CL) < v"0.15"
85-
albedo = CL.Bucket.PrescribedSurfaceAlbedo{FT}(date_ref, t_start, space)
90+
albedo = CL.Bucket.PrescribedSurfaceAlbedo{FT}(start_date, tspan[1], boundary_space)
8691
else
8792
albedo = CL.Bucket.PrescribedSurfaceAlbedo{FT}(
88-
date_ref,
89-
space;
93+
start_date,
94+
boundary_space;
9095
albedo_file_path = CL.Artifacts.ceres_albedo_dataset_path(),
9196
varname = "sw_alb_clr",
9297
)
@@ -96,7 +101,7 @@ function BucketSimulation(
96101
(; lat, long) = coordinate_point
97102
return typeof(lat)(0.38)
98103
end
99-
albedo = CL.Bucket.PrescribedBaregroundAlbedo{FT}(α_snow, α_bareground, space)
104+
albedo = CL.Bucket.PrescribedBaregroundAlbedo{FT}(α_snow, α_bareground, boundary_space)
100105
else
101106
error("invalid albedo type $albedo_type")
102107
end
@@ -117,12 +122,14 @@ function BucketSimulation(
117122
n_vertical_elements = 7
118123
# Note that this does not take into account topography of the surface, which is OK for this land model.
119124
# But it must be taken into account when computing surface fluxes, for Δz.
120-
domain = make_land_domain(space, (-d_soil, FT(0.0)), n_vertical_elements)
125+
domain = make_land_domain(boundary_space, (-d_soil, FT(0.0)), n_vertical_elements)
121126
args = (params, CL.CoupledAtmosphere{FT}(), CL.CoupledRadiativeFluxes{FT}(), domain)
122127
model = CL.Bucket.BucketModel{FT, typeof.(args)...}(args...)
123128

124129
# Initial conditions with no moisture
125130
Y, p, coords = CL.initialize(model)
131+
132+
# Add space in the cache for the energy if energy checks are enabled
126133
p = get_new_cache(p, Y, energy_check)
127134

128135
# Get temperature anomaly function
@@ -137,10 +144,11 @@ function BucketSimulation(
137144
T_sfc_0 = FT(271)
138145
@. Y.bucket.T = T_sfc_0 + temp_anomaly(coords.subsurface)
139146
# `surface_elevation` is a ClimaCore.Fields.Field(`half` level)
140-
orog_adjusted_T = CC.Fields.field_values(Y.bucket.T) .- lapse_rate .* CC.Fields.field_values(surface_elevation)
147+
orog_adjusted_T_data = CC.Fields.field_values(Y.bucket.T) .- lapse_rate .* CC.Fields.field_values(surface_elevation)
148+
orog_adjusted_T = CC.Fields.Field(orog_adjusted_T_data, domain.space.subsurface)
141149
# Adjust T based on surface elevation (p.bucket.T_sfc is then set using the
142150
# set_initial_cache! function)
143-
parent(Y.bucket.T) .= parent(orog_adjusted_T)
151+
Y.bucket.T .= orog_adjusted_T
144152

145153
Y.bucket.W .= 0.15
146154
Y.bucket.Ws .= 0.0
@@ -199,16 +207,16 @@ function BucketSimulation(
199207

200208
exp_tendency! = CL.make_exp_tendency(model)
201209
ode_algo = CTS.ExplicitAlgorithm(stepper)
202-
bucket_ode_function = CTS.ClimaODEFunction(T_exp! = exp_tendency!, dss! = CL.dss!)
210+
bucket_ode_function = CTS.ClimaODEFunction(T_exp! = exp_tendency!)
203211
prob = SciMLBase.ODEProblem(bucket_ode_function, Y, tspan, p)
204212

205213
# Add diagnostics
206214
if use_land_diagnostics
207215
netcdf_writer = CD.Writers.NetCDFWriter(domain.space.subsurface, output_dir)
208216
scheduled_diagnostics =
209-
CL.default_diagnostics(model, date_ref, output_writer = netcdf_writer, average_period = :monthly)
217+
CL.default_diagnostics(model, start_date, output_writer = netcdf_writer, average_period = :monthly)
210218

211-
diagnostic_handler = CD.DiagnosticsHandler(scheduled_diagnostics, Y, p, t_start; dt = dt)
219+
diagnostic_handler = CD.DiagnosticsHandler(scheduled_diagnostics, Y, p, tspan[1]; dt = dt)
212220
diag_cb = CD.DiagnosticsCallback(diagnostic_handler)
213221
else
214222
diag_cb = nothing
@@ -223,11 +231,7 @@ function BucketSimulation(
223231
callback = SciMLBase.CallbackSet(diag_cb),
224232
)
225233

226-
sim = BucketSimulation(model, (; domain = domain, soil_depth = d_soil), integrator, area_fraction)
227-
228-
# DSS state to ensure we have continuous fields
229-
dss_state!(sim)
230-
return sim
234+
return BucketSimulation(model, domain, integrator, area_fraction)
231235
end
232236

233237
# extensions required by Interfacer
@@ -352,64 +356,6 @@ function Checkpointer.get_model_prog_state(sim::BucketSimulation)
352356
return sim.integrator.u.bucket
353357
end
354358

355-
"""
356-
temp_anomaly_aquaplanet(coord)
357-
358-
Introduce a temperature IC anomaly for the aquaplanet case.
359-
The values for this case follow the moist Held-Suarez setup of Thatcher &
360-
Jablonowski (2016, eq. 6), consistent with ClimaAtmos aquaplanet.
361-
"""
362-
temp_anomaly_aquaplanet(coord) = 29 * exp(-coord.lat^2 / (2 * 26^2))
363-
364-
"""
365-
temp_anomaly_amip(coord)
366-
367-
Introduce a temperature IC anomaly for the AMIP case.
368-
The values used in this case have been tuned to align with observed temperature
369-
and result in stable simulations.
370-
"""
371-
temp_anomaly_amip(coord) = 40 * cosd(coord.lat)^4
372-
373-
"""
374-
make_land_domain(
375-
atmos_boundary_space::CC.Spaces.SpectralElementSpace2D,
376-
zlim::Tuple{FT, FT},
377-
nelements_vert::Int,) where {FT}
378-
379-
Creates the land model domain from the horizontal space of the atmosphere, and information
380-
about the number of elements and extent of the vertical domain.
381-
"""
382-
function make_land_domain(
383-
atmos_boundary_space::CC.Spaces.SpectralElementSpace2D,
384-
zlim::Tuple{FT, FT},
385-
nelements_vert::Int,
386-
) where {FT}
387-
@assert zlim[1] < zlim[2]
388-
depth = zlim[2] - zlim[1]
389-
390-
mesh = CC.Spaces.topology(atmos_boundary_space).mesh
391-
392-
radius = mesh.domain.radius
393-
nelements_horz = mesh.ne
394-
npolynomial = CC.Spaces.Quadratures.polynomial_degree(CC.Spaces.quadrature_style(atmos_boundary_space))
395-
nelements = (nelements_horz, nelements_vert)
396-
vertdomain = CC.Domains.IntervalDomain(
397-
CC.Geometry.ZPoint(FT(zlim[1])),
398-
CC.Geometry.ZPoint(FT(zlim[2]));
399-
boundary_names = (:bottom, :top),
400-
)
401-
402-
vertmesh = CC.Meshes.IntervalMesh(vertdomain, CC.Meshes.Uniform(), nelems = nelements[2])
403-
verttopology = CC.Topologies.IntervalTopology(vertmesh)
404-
vert_center_space = CC.Spaces.CenterFiniteDifferenceSpace(verttopology)
405-
subsurface_space = CC.Spaces.ExtrudedFiniteDifferenceSpace(atmos_boundary_space, vert_center_space)
406-
space = (; surface = atmos_boundary_space, subsurface = subsurface_space)
407-
408-
fields = CL.Domains.get_additional_coordinate_field_data(subsurface_space)
409-
410-
return CL.Domains.SphericalShell{FT}(radius, depth, nothing, nelements, npolynomial, space, fields)
411-
end
412-
413359
function Checkpointer.get_model_cache(sim::BucketSimulation)
414360
return sim.integrator.p
415361
end
@@ -424,16 +370,3 @@ function Checkpointer.restore_cache!(sim::BucketSimulation, new_cache)
424370
ignore = Set([:rc, :params, :dss_buffer_2d, :dss_buffer_3d, :graph_context]),
425371
)
426372
end
427-
428-
"""
429-
dss_state!(sim::BucketSimulation)
430-
431-
Perform DSS on the state of a component simulation, intended to be used
432-
before the initial step of a run. This method acts on bucket land simulations.
433-
The `dss!` function of ClimaLand must be called because it uses either the 2D
434-
or 3D dss buffer stored in the cache depending on space of each variable in
435-
`sim.integrator.u`.
436-
"""
437-
function dss_state!(sim::BucketSimulation)
438-
CL.dss!(sim.integrator.u, sim.integrator.p, sim.integrator.t)
439-
end
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import ClimaCore as CC
2+
import ClimaLand
3+
"""
4+
temp_anomaly_aquaplanet(coord)
5+
6+
Introduce a temperature IC anomaly for the aquaplanet case.
7+
The values for this case follow the moist Held-Suarez setup of Thatcher &
8+
Jablonowski (2016, eq. 6), consistent with ClimaAtmos aquaplanet.
9+
"""
10+
temp_anomaly_aquaplanet(coord) = 29 * exp(-coord.lat^2 / (2 * 26^2))
11+
12+
"""
13+
temp_anomaly_amip(coord)
14+
15+
Introduce a temperature IC anomaly for the AMIP case.
16+
The values used in this case have been tuned to align with observed temperature
17+
and result in stable simulations.
18+
"""
19+
temp_anomaly_amip(coord) = 40 * cosd(coord.lat)^4
20+
21+
"""
22+
make_land_domain(
23+
atmos_boundary_space::CC.Spaces.SpectralElementSpace2D,
24+
zlim::Tuple{FT, FT},
25+
nelements_vert::Int,) where {FT}
26+
27+
Creates the land model domain from the horizontal space of the atmosphere, and information
28+
about the number of elements and extent of the vertical domain.
29+
"""
30+
function make_land_domain(
31+
atmos_boundary_space::CC.Spaces.SpectralElementSpace2D,
32+
zlim::Tuple{FT, FT},
33+
nelements_vert::Int,
34+
) where {FT}
35+
@assert zlim[1] < zlim[2]
36+
depth = zlim[2] - zlim[1]
37+
38+
mesh = CC.Spaces.topology(atmos_boundary_space).mesh
39+
40+
radius = mesh.domain.radius
41+
nelements_horz = mesh.ne
42+
npolynomial = CC.Spaces.Quadratures.polynomial_degree(CC.Spaces.quadrature_style(atmos_boundary_space))
43+
nelements = (nelements_horz, nelements_vert)
44+
vertdomain = CC.Domains.IntervalDomain(
45+
CC.Geometry.ZPoint(FT(zlim[1])),
46+
CC.Geometry.ZPoint(FT(zlim[2]));
47+
boundary_names = (:bottom, :top),
48+
)
49+
50+
vertmesh = CC.Meshes.IntervalMesh(vertdomain, CC.Meshes.Uniform(), nelems = nelements[2])
51+
verttopology = CC.Topologies.IntervalTopology(vertmesh)
52+
vert_center_space = CC.Spaces.CenterFiniteDifferenceSpace(verttopology)
53+
subsurface_space = CC.Spaces.ExtrudedFiniteDifferenceSpace(atmos_boundary_space, vert_center_space)
54+
space = (; surface = atmos_boundary_space, subsurface = subsurface_space)
55+
56+
fields = ClimaLand.Domains.get_additional_coordinate_field_data(subsurface_space)
57+
58+
return ClimaLand.Domains.SphericalShell{FT}(radius, depth, nothing, nelements, npolynomial, space, fields)
59+
end

0 commit comments

Comments
 (0)