Skip to content

Commit c2ff263

Browse files
committed
add tests for atmos/surface fluxes
1 parent 39c9c8e commit c2ff263

File tree

2 files changed

+99
-0
lines changed

2 files changed

+99
-0
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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 + bucket 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["dt_rad"] = config_dict["dt"]
32+
33+
# Construct coupled simulation and run one coupling step
34+
cs = CoupledSimulation(config_dict)
35+
step!(cs)
36+
boundary_space = Interfacer.boundary_space(cs)
37+
38+
# Unpack component models
39+
(; atmos_sim, land_sim, ocean_sim, ice_sim) = cs.model_sims
40+
41+
# Atmosphere: radiative flux on the surface interface
42+
# Convention: positive downward to the surface
43+
atmos_flux = CC.Spaces.level(
44+
atmos_sim.integrator.p.radiation.ᶠradiation_flux.components.data.:1,
45+
CC.Utilities.half,
46+
)
47+
48+
# Integrated land: compare to net radiation stored in the bucket cache
49+
# Convention note: bucket R_n is stored with the opposite sign (see climaland_bucket.jl),
50+
# so we compare atmos_flux ≈ -R_n on land points.
51+
p = land_sim.integrator.p
52+
land_fraction = Interfacer.get_field(land_sim, Val(:area_fraction))
53+
land_flux = Interfacer.remap(land_sim.integrator.p.bucket.R_n, boundary_space)
54+
@. land_flux = ifelse(land_fraction 0, zero(land_flux), land_flux)
55+
56+
err_land = @. atmos_flux - land_flux
57+
@. err_land = ifelse(land_fraction 0, zero(err_land), err_land)
58+
@show "Integrated land flux error: $(maximum(abs.(err_land)))"
59+
@test maximum(abs.(err_land)) < 5
60+
61+
# Prescribed ice: radiative fluxes aren't stored; compute from cache and compare
62+
p = ice_sim.integrator.p
63+
Y = ice_sim.integrator.u
64+
FT = eltype(Y)
65+
66+
# Radiative flux toward surface (positive downward)
67+
# TODO: get sigma from parameters
68+
σ = FT(5.67e-8)
69+
(; k_ice, h, T_base, ρ, c, α, ϵ) = p.params
70+
ice_rad_flux =
71+
(1 .- α) .* p.SW_d .+
72+
ϵ .* (p.LW_d .- σ .* Interfacer.get_field(ice_sim, Val(:surface_temperature)) .^ 4)
73+
@. ice_rad_flux = ifelse(p.area_fraction 0, zero(ice_rad_flux), ice_rad_flux)
74+
ice_fraction = Interfacer.get_field(ice_sim, Val(:area_fraction))
75+
76+
# Prescribed ocean: SST is prescribed, but for this test we can still compute
77+
# the radiative flux seen by the ocean surface using the same formula.
78+
α = Interfacer.get_field(ocean_sim, Val(:surface_direct_albedo))
79+
ϵ = Interfacer.get_field(ocean_sim, Val(:emissivity))
80+
ocean_rad_flux =
81+
(1 .- α) .* cs.fields.SW_d .+
82+
ϵ .* (
83+
cs.fields.LW_d .-
84+
σ .* Interfacer.get_field(ocean_sim, Val(:surface_temperature)) .^ 4
85+
)
86+
ocean_fraction = Interfacer.get_field(ocean_sim, Val(:area_fraction))
87+
@. ocean_rad_flux = ifelse(ocean_fraction 0, zero(ocean_rad_flux), ocean_rad_flux)
88+
89+
# Combine component fluxes by area-weighted sum (incl. bucket sign convention):
90+
combined_fluxes =
91+
.-land_fraction .* land_flux .+ ice_fraction .* ice_rad_flux .+
92+
ocean_fraction .* ocean_rad_flux
93+
err_fluxes = atmos_flux .+ combined_fluxes
94+
@show "Combined fluxes error: $(maximum(abs.(err_fluxes)))"
95+
@test maximum(abs.(err_fluxes)) < 8
96+
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)