Skip to content

Commit 920c5b2

Browse files
committed
add tests for atmos/surface fluxes
1 parent 82781b3 commit 920c5b2

File tree

3 files changed

+142
-1
lines changed

3 files changed

+142
-1
lines changed

experiments/ClimaEarth/components/ocean/prescr_seaice.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,13 @@ function ice_rhs!(dY, Y, p, t)
278278

279279
# TODO: get sigma from parameters
280280
σ = FT(5.67e-8)
281+
282+
T_flux_calc = 2 .* Y.T_sfc .- T_base
281283
rhs = @. (
282-
-p.F_turb_energy + (1 - α) * p.SW_d + ϵ * (p.LW_d - σ * Y.T_sfc^4) + F_conductive
284+
-p.F_turb_energy +
285+
(1 - α) * p.SW_d +
286+
ϵ * (p.LW_d - σ * T_flux_calc^4) +
287+
F_conductive
283288
) / (h * ρ * c)
284289

285290
# Zero out tendencies where there is no ice, so that ice temperature remains constant there
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
#
2+
# Flux consistency test (AMIP + bucket land)
3+
#
4+
# This test sets up an AMIP coupled simulation (atmosphere + bucket land + prescribed
5+
# ocean + prescribed sea ice), advances one coupling step, and verifies that the
6+
# surface radiative flux seen by the atmosphere matches what each surface model
7+
# computes/stores:
8+
# - Atmosphere: uses `sim.integrator.p.radiation.ᶠradiation_flux` (positive downward).
9+
# - Bucket land: compares against `sim.integrator.p.bucket.R_n` with opposite sign
10+
# convention (so atmos ≈ -R_n) on land-dominant cells.
11+
# - Prescribed ocean: skipped — SST is prescribed so radiative fluxes are not computed
12+
# in the same way.
13+
# - Prescribed sea ice: not stored directly; compute using cache fields and compare
14+
# to the atmospheric flux on ice-dominant cells.
15+
16+
import Test: @test, @testset
17+
import ClimaCore as CC
18+
import ClimaComms
19+
ClimaComms.@import_required_backends
20+
import ClimaCoupler
21+
22+
# Use the AMIP setup helpers to construct a coupled simulation
23+
include(joinpath("..", "setup_run.jl"))
24+
25+
@testset "surface radiative flux consistency (AMIP + integrated land)" begin
26+
# Build AMIP configuration used in CI by default
27+
config_file = joinpath(pkgdir(ClimaCoupler), "config/ci_configs/amip_default.yml")
28+
config_dict = get_coupler_config_dict(config_file)
29+
30+
# Make sure radiation is computed during the first step
31+
config_dict["land_model"] = "integrated"
32+
config_dict["dt_rad"] = config_dict["dt"]
33+
34+
# Construct coupled simulation and run one coupling step
35+
cs = CoupledSimulation(config_dict)
36+
step!(cs)
37+
boundary_space = Interfacer.boundary_space(cs)
38+
39+
# Unpack component models
40+
(; atmos_sim, land_sim, ocean_sim, ice_sim) = cs.model_sims
41+
42+
# Atmosphere: radiative flux on the surface interface
43+
# Convention: positive downward to the surface
44+
atmos_flux = CC.Spaces.level(
45+
atmos_sim.integrator.p.radiation.ᶠradiation_flux.components.data.:1,
46+
CC.Utilities.half,
47+
)
48+
49+
# Integrated land: compare to net radiation stored in the bucket cache
50+
# Convention note: bucket R_n is stored with the opposite sign (see climaland_bucket.jl),
51+
# so we compare atmos_flux ≈ -R_n on land points.
52+
p = land_sim.integrator.p
53+
land_flux = @. p.drivers.LW_d - p.LW_u + p.drivers.SW_d - p.SW_u
54+
land_fraction = Interfacer.get_field(land_sim, Val(:area_fraction))
55+
@. land_flux = ifelse(land_fraction 0, zero(land_flux), land_flux)
56+
57+
err_land = @. atmos_flux - land_flux
58+
@. err_land = ifelse(land_fraction 0, zero(err_land), err_land)
59+
@show "Integrated land flux error: $(maximum(abs.(err_land)))"
60+
@test maximum(abs.(err_land)) < 5
61+
62+
# Prescribed ice: radiative fluxes aren't stored; compute from cache and compare
63+
p = ice_sim.integrator.p
64+
Y = ice_sim.integrator.u
65+
FT = eltype(Y)
66+
67+
# Radiative flux toward surface (positive downward)
68+
# TODO: get sigma from parameters
69+
σ = FT(5.67e-8)
70+
(; k_ice, h, T_base, ρ, c, α, ϵ) = p.params
71+
ice_rad_flux =
72+
(1 .- α) .* p.SW_d .+
73+
ϵ .* (p.LW_d .- σ .* Interfacer.get_field(ice_sim, Val(:surface_temperature)) .^ 4)
74+
@. ice_rad_flux = ifelse(p.area_fraction 0, zero(ice_rad_flux), ice_rad_flux)
75+
ice_fraction = Interfacer.get_field(ice_sim, Val(:area_fraction))
76+
77+
# Prescribed ocean: SST is prescribed, but for this test we can still compute
78+
# the radiative flux seen by the ocean surface using the same formula.
79+
α = Interfacer.get_field(ocean_sim, Val(:surface_direct_albedo))
80+
ϵ = Interfacer.get_field(ocean_sim, Val(:emissivity))
81+
ocean_rad_flux =
82+
(1 .- α) .* cs.fields.SW_d .+
83+
ϵ .* (
84+
cs.fields.LW_d .-
85+
σ .* Interfacer.get_field(ocean_sim, Val(:surface_temperature)) .^ 4
86+
)
87+
ocean_fraction = Interfacer.get_field(ocean_sim, Val(:area_fraction))
88+
@. ocean_rad_flux = ifelse(ocean_fraction 0, zero(ocean_rad_flux), ocean_rad_flux)
89+
90+
# Combine component fluxes by area-weighted sum (incl. bucket sign convention):
91+
combined_fluxes =
92+
.-land_fraction .* land_flux .+ ice_fraction .* ice_rad_flux .+
93+
ocean_fraction .* ocean_rad_flux
94+
err_fluxes = atmos_flux .+ combined_fluxes
95+
@show "Combined fluxes error: $(maximum(abs.(err_fluxes)))"
96+
@test maximum(abs.(err_fluxes)) < 8
97+
end
98+
99+
@testset "surface radiative flux consistency (bucket terraplanet)" begin
100+
# Build AMIP configuration used in CI by default
101+
config_file = joinpath(pkgdir(ClimaCoupler), "config/ci_configs/amip_default.yml")
102+
config_dict = get_coupler_config_dict(config_file)
103+
104+
# Make sure radiation is computed during the first step
105+
config_dict["dt_rad"] = config_dict["dt"]
106+
# Use slabplanet_terra mode since we're only testing the land model
107+
config_dict["mode_name"] = "slabplanet_terra"
108+
109+
# Construct coupled simulation and run one coupling step
110+
cs = CoupledSimulation(config_dict)
111+
step!(cs)
112+
boundary_space = Interfacer.boundary_space(cs)
113+
114+
# Unpack component models
115+
(; atmos_sim, land_sim) = cs.model_sims
116+
117+
# Atmosphere: radiative flux on the surface interface
118+
# Convention: positive downward to the surface
119+
atmos_flux = CC.Spaces.level(
120+
atmos_sim.integrator.p.radiation.ᶠradiation_flux.components.data.:1,
121+
CC.Utilities.half,
122+
)
123+
124+
# Bucket land: compare to net radiation stored in the bucket cache
125+
# Convention note: bucket R_n is stored with the opposite sign (see climaland_bucket.jl),
126+
# so we compare atmos_flux ≈ -R_n on land points.
127+
land_flux = Interfacer.remap(land_sim.integrator.p.bucket.R_n, boundary_space)
128+
129+
err_land = @. atmos_flux - land_flux
130+
@. err_land = ifelse(land_sim.area_fraction 0, zero(err_land), err_land)
131+
@show "Bucket land flux error: $(maximum(abs.(err_land)))"
132+
@test maximum(abs.(err_land)) < 5
133+
end

experiments/ClimaEarth/test/runtests.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ end
1717
@safetestset "component model test: slab ocean" begin
1818
include("component_model_tests/slab_ocean_tests.jl")
1919
end
20+
@safetestset "surface radiative flux consistency tests" begin
21+
include("fluxes_test.jl")
22+
end
2023
@safetestset "debug diagnostics: debug plots" begin
2124
include("debug_plots_tests.jl")
2225
end

0 commit comments

Comments
 (0)