Skip to content

Commit b5ef5e8

Browse files
authored
Merge pull request #1497 from CliMA/js/t-radfluxes
compute T_sfc from longwave fluxes for radiation
2 parents d2bf7d6 + dbf2170 commit b5ef5e8

File tree

8 files changed

+113
-44
lines changed

8 files changed

+113
-44
lines changed

experiments/ClimaEarth/components/atmosphere/climaatmos.jl

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -399,12 +399,9 @@ function Interfacer.update_field!(sim::ClimaAtmosSimulation, ::Val{:emissivity},
399399
# Remap field onto the atmosphere surface space in scratch field
400400
temp_field_surface = sim.integrator.p.scratch.ᶠtemp_field_level
401401
Interfacer.remap!(temp_field_surface, field)
402-
# sets all 16 bands (rows) to the same values
403-
sim.integrator.p.radiation.rrtmgp_model.surface_emissivity .= reshape(
404-
CC.Fields.field2array(temp_field_surface),
405-
1,
406-
length(parent(temp_field_surface)),
407-
)
402+
# Set each row (band) of the emissivity matrix by transposing the vector returned from `field2array`
403+
sim.integrator.p.radiation.rrtmgp_model.surface_emissivity .=
404+
CC.Fields.field2array(temp_field_surface)'
408405
end
409406
function Interfacer.update_field!(
410407
sim::ClimaAtmosSimulation,
@@ -415,11 +412,9 @@ function Interfacer.update_field!(
415412
# Remap field onto the atmosphere surface space in scratch field
416413
temp_field_surface = sim.integrator.p.scratch.ᶠtemp_field_level
417414
Interfacer.remap!(temp_field_surface, field)
418-
sim.integrator.p.radiation.rrtmgp_model.direct_sw_surface_albedo .= reshape(
419-
CC.Fields.field2array(temp_field_surface),
420-
1,
421-
length(parent(temp_field_surface)),
422-
)
415+
# Set each row (band) of the albedo matrix by transposing the vector returned from `field2array`
416+
sim.integrator.p.radiation.rrtmgp_model.direct_sw_surface_albedo .=
417+
CC.Fields.field2array(temp_field_surface)'
423418
end
424419

425420
function Interfacer.update_field!(
@@ -431,11 +426,9 @@ function Interfacer.update_field!(
431426
# Remap field onto the atmosphere surface space in scratch field
432427
temp_field_surface = sim.integrator.p.scratch.ᶠtemp_field_level
433428
Interfacer.remap!(temp_field_surface, field)
434-
sim.integrator.p.radiation.rrtmgp_model.diffuse_sw_surface_albedo .= reshape(
435-
CC.Fields.field2array(temp_field_surface),
436-
1,
437-
length(parent(temp_field_surface)),
438-
)
429+
# Set each row (band) of the albedo matrix by transposing the vector returned from `field2array`
430+
sim.integrator.p.radiation.rrtmgp_model.diffuse_sw_surface_albedo .=
431+
CC.Fields.field2array(temp_field_surface)'
439432
end
440433

441434
function Interfacer.update_field!(

experiments/ClimaEarth/components/land/climaland_integrated.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@ function ClimaLandSimulation(
295295
# Initialize the surface temperature so the atmosphere can compute radiation.
296296
@. p.T_sfc = orog_adjusted_T_surface
297297
end
298+
# Initialize the surface emissivity so the atmosphere can compute radiation.
299+
# Otherwise, it's initialized to 0 which causes NaNs in the radiation calculation.
300+
@. p.ϵ_sfc = FT(1)
298301

299302
# Update cos(zenith angle) within land model every hour
300303
update_dt = dt isa ITime ? ITime(3600) : 3600

experiments/ClimaEarth/components/ocean/climaocean.jl

Lines changed: 0 additions & 1 deletion
This file was deleted.

experiments/ClimaEarth/components/ocean/oceananigans.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ Interfacer.get_field(sim::OceananigansSimulation, ::Val{:roughness_buoyancy}) =
252252
Interfacer.get_field(sim::OceananigansSimulation, ::Val{:roughness_momentum}) =
253253
Float32(5.8e-5)
254254
Interfacer.get_field(sim::OceananigansSimulation, ::Val{:beta}) = Float32(1)
255+
Interfacer.get_field(sim::OceananigansSimulation, ::Val{:emissivity}) = Float32(0.97)
255256
Interfacer.get_field(sim::OceananigansSimulation, ::Val{:surface_direct_albedo}) =
256257
Float32(0.06)
257258
Interfacer.get_field(sim::OceananigansSimulation, ::Val{:surface_diffuse_albedo}) =

experiments/ClimaEarth/components/ocean/prescr_ocean.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ Interfacer.get_field(sim::PrescribedOceanSimulation, ::Val{:surface_direct_albed
129129
sim.cache.α_direct
130130
Interfacer.get_field(sim::PrescribedOceanSimulation, ::Val{:surface_diffuse_albedo}) =
131131
sim.cache.α_diffuse
132+
Interfacer.get_field(sim::PrescribedOceanSimulation, ::Val{:emissivity}) =
133+
eltype(sim.cache.T_sfc)(1)
132134

133135
function Interfacer.update_field!(
134136
sim::PrescribedOceanSimulation,

src/FieldExchanger.jl

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -121,19 +121,34 @@ Updates the coupler with the surface properties. The `Interfacer.get_field`
121121
functions for (`:surface_temperature`, `:surface_direct_albedo`,
122122
`:surface_diffuse_albedo`) need to be specified for each surface model.
123123
124-
Note: The calculation of surface humidity uses atmospheric properties stored in
124+
Note: The calculation of surface humidity done here uses atmospheric properties stored in
125125
the coupled fields. For these values to be correct, this function should be called
126126
after `import_atmos_fields!` in a timestep.
127127
128+
Note 2: Not all surface fields are imported here. Some quantities are retrieved
129+
from each surface model when surface fluxes are computed, in `compute_surface_fluxes!`.
130+
128131
# Arguments
129132
- `csf`: [NamedTuple] containing coupler fields.
130133
- `model_sims`: [NamedTuple] containing `ComponentModelSimulation`s.
131134
- `thermo_params`: [TD.Parameters.ThermodynamicsParameters] the thermodynamic parameters.
132135
"""
133136
function import_combined_surface_fields!(csf, model_sims, thermo_params)
134-
combine_surfaces!(csf.T_sfc, model_sims, Val(:surface_temperature))
135-
combine_surfaces!(csf.surface_direct_albedo, model_sims, Val(:surface_direct_albedo))
136-
combine_surfaces!(csf.surface_diffuse_albedo, model_sims, Val(:surface_diffuse_albedo))
137+
combine_surfaces!(csf.emissivity, model_sims, Val(:emissivity), csf.temp1)
138+
combine_surfaces!(
139+
csf.surface_direct_albedo,
140+
model_sims,
141+
Val(:surface_direct_albedo),
142+
csf.temp1,
143+
)
144+
combine_surfaces!(
145+
csf.surface_diffuse_albedo,
146+
model_sims,
147+
Val(:surface_diffuse_albedo),
148+
csf.temp1,
149+
)
150+
# Temperature requires emissivity, so we provide all the coupler fields
151+
combine_surfaces!(csf, model_sims, Val(:surface_temperature))
137152

138153
# q_sfc is computed from the atmosphere state and surface temperature, so it's handled differently
139154
# This is computed on the exchange grid, so there's no need to remap
@@ -181,7 +196,8 @@ end
181196
"""
182197
update_sim!(atmos_sim::Interfacer.AtmosModelSimulation, csf)
183198
184-
Updates the surface fields for temperature, roughness length, albedo, and specific humidity.
199+
Updates the atmosphere's fields for surface direct and diffuse albedos, emissivity, and temperature,
200+
as well as the turbulent fluxes.
185201
186202
# Arguments
187203
- `atmos_sim`: [Interfacer.AtmosModelSimulation] containing an atmospheric model simulation object.
@@ -198,6 +214,7 @@ function update_sim!(atmos_sim::Interfacer.AtmosModelSimulation, csf)
198214
Val(:surface_diffuse_albedo),
199215
csf.surface_diffuse_albedo,
200216
)
217+
Interfacer.update_field!(atmos_sim, Val(:emissivity), csf.emissivity)
201218
Interfacer.update_field!(atmos_sim, Val(:surface_temperature), csf)
202219
Interfacer.update_field!(atmos_sim, Val(:turbulent_fluxes), csf)
203220
return nothing
@@ -270,36 +287,79 @@ function step_model_sims!(cs::Interfacer.CoupledSimulation)
270287
end
271288

272289
"""
273-
combine_surfaces!(combined_field::CC.Fields.Field, sims, field_name::Val)
290+
combine_surfaces!(combined_field::CC.Fields.Field, sims, field_name::Val, temp1)
274291
275292
Sums the fields, specified by `field_name`, weighted by the respective area fractions of all
276293
surface simulations. THe result is saved in `combined_field`.
277294
295+
For surface temperature, upward longwave radiation is computed from the temperatures
296+
of each surface, weighted by their area fractions, and then the combined temperature
297+
is computed from the combined upward longwave radiation.
298+
278299
# Arguments
279300
- `combined_field`: [CC.Fields.Field] output object containing weighted values.
301+
Note: For the surface temperature, all coupler fields are passed in a NamedTuple.
280302
- `sims`: [NamedTuple] containing simulations .
281303
- `field_name`: [Val] containing the name Symbol of the field t be extracted by the `Interfacer.get_field` functions.
304+
- `temp1`: [CC.Fields.Field] temporary field for intermediate calculations.
305+
Omitted for surface temperature method.
282306
283307
# Example
284308
- `combine_surfaces!(temp_field, cs.model_sims, Val(:surface_temperature))`
285309
"""
286-
function combine_surfaces!(combined_field, sims, field_name)
310+
function combine_surfaces!(combined_field, sims, field_name, temp1)
287311
boundary_space = axes(combined_field)
288312
combined_field .= 0
289313
for sim in sims
290314
if sim isa Interfacer.SurfaceModelSimulation
291-
# Zero out the contribution from this surface if the area fraction is zero
315+
# Store the area fraction of this simulation in `temp1`
316+
Interfacer.get_field!(temp1, sim, Val(:area_fraction))
317+
# Zero out the contribution from this surface if the area fraction is zero.
292318
# Note that multiplying by `area_fraction` is not sufficient in the case of NaNs
293-
area_fraction = Interfacer.get_field(sim, Val(:area_fraction))
294319
combined_field .+=
295-
area_fraction .*
320+
temp1 .*
296321
ifelse.(
297-
area_fraction .≈ 0,
322+
temp1 .≈ 0,
298323
zero(combined_field),
299324
Interfacer.get_field(sim, field_name, boundary_space),
300325
)
301326
end
302327
end
328+
return nothing
329+
end
330+
function combine_surfaces!(csf, sims, field_name::Val{:surface_temperature})
331+
# extract the coupler fields we need to get the surface temperature
332+
T_sfc = csf.T_sfc
333+
emissivity_sfc = csf.emissivity
334+
335+
boundary_space = axes(T_sfc)
336+
FT = CC.Spaces.undertype(boundary_space)
337+
338+
T_sfc .= FT(0)
339+
for sim in sims
340+
if sim isa Interfacer.SurfaceModelSimulation
341+
# Store the area fraction and emissivity of this simulation in temp fields
342+
Interfacer.get_field!(csf.temp1, sim, Val(:area_fraction))
343+
area_fraction = csf.temp1
344+
Interfacer.get_field!(csf.temp2, sim, Val(:emissivity))
345+
emissivity_sim = csf.temp2
346+
347+
# Zero out the contribution from this surface if the area fraction is zero.
348+
# Note that multiplying by `area_fraction` is not sufficient in the case of NaNs
349+
# Compute upward longwave radiation from surface temperature for this simulation
350+
T_sfc .+=
351+
area_fraction .*
352+
ifelse.(
353+
area_fraction .≈ 0,
354+
zero(T_sfc),
355+
emissivity_sim .*
356+
Interfacer.get_field(sim, field_name, boundary_space) .^ FT(4),
357+
)
358+
end
359+
end
360+
# Convert the combined upward longwave radiation into a surface temperature
361+
@. T_sfc = (T_sfc / emissivity_sfc)^FT(1 / 4)
362+
return nothing
303363
end
304364

305365
"""

test/field_exchanger_tests.jl

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,15 @@ Interfacer.get_field(
5252
sim::Union{TestSurfaceSimulation1, TestSurfaceSimulation2},
5353
::Val{:beta},
5454
) = sim.cache_field
55+
Interfacer.get_field(
56+
sim::Union{TestSurfaceSimulation1, TestSurfaceSimulation2},
57+
::Val{:emissivity},
58+
) = eltype(sim.cache_field)(1)
5559

5660
Interfacer.get_field(sim::TestSurfaceSimulation1, ::Val{:area_fraction}) =
57-
sim.cache_field .* 0
61+
sim.cache_field .* CC.Spaces.undertype(axes(sim.cache_field))(0.25)
5862
Interfacer.get_field(sim::TestSurfaceSimulation2, ::Val{:area_fraction}) =
59-
sim.cache_field .* CC.Spaces.undertype(axes(sim.cache_field))(0.5)
63+
sim.cache_field .* CC.Spaces.undertype(axes(sim.cache_field))(0.75)
6064

6165
Interfacer.step!(::TestSurfaceSimulation1, _) = nothing
6266

@@ -145,6 +149,8 @@ Interfacer.get_field(sim::TestSurfaceSimulationLand, ::Val{:surface_diffuse_albe
145149
sim.cache.albedo_diffuse
146150
Interfacer.get_field(sim::TestSurfaceSimulationLand, ::Val{:surface_temperature}) =
147151
sim.cache.surface_temperature
152+
Interfacer.get_field(sim::TestSurfaceSimulationLand, ::Val{:emissivity}) =
153+
eltype(sim.cache.surface_temperature)(1)
148154
function Interfacer.update_field!(
149155
sim::TestSurfaceSimulationLand,
150156
::Val{:turbulent_energy_flux},
@@ -266,8 +272,9 @@ for FT in (Float32, Float64)
266272
Interfacer.get_field(sims.c, var_name),
267273
Interfacer.get_field(sims.d, var_name),
268274
)
275+
temp_field = CC.Fields.zeros(test_space)
269276

270-
FieldExchanger.combine_surfaces!(combined_field, sims, var_name)
277+
FieldExchanger.combine_surfaces!(combined_field, sims, var_name, temp_field)
271278
@test combined_field == fill(FT(sum(fractions .* fields)), test_space)
272279
end
273280

@@ -322,30 +329,31 @@ for FT in (Float32, Float64)
322329
coupler_names_additional = [:surface_direct_albedo, :surface_diffuse_albedo]
323330
coupler_names =
324331
push!(Interfacer.default_coupler_fields(), coupler_names_additional...)
325-
326-
# coupler cache setup
327-
exchanged_fields = (
328-
:surface_temperature,
329-
:surface_direct_albedo,
330-
:surface_diffuse_albedo,
331-
:roughness_momentum,
332-
:roughness_buoyancy,
333-
:beta,
334-
)
332+
coupler_fields = Interfacer.init_coupler_fields(FT, coupler_names, boundary_space)
335333

336334
sims = (;
337335
a = TestSurfaceSimulation1(ones(boundary_space)),
338336
b = TestSurfaceSimulation2(ones(boundary_space)),
339337
)
340338

341-
coupler_fields = Interfacer.init_coupler_fields(FT, coupler_names, boundary_space)
342-
343339
thermo_params = TDP.ThermodynamicsParameters(FT)
344340
FieldExchanger.import_combined_surface_fields!(coupler_fields, sims, thermo_params)
341+
342+
# Analytically compute expected values and compare
343+
expected_field_temp =
344+
(
345+
Interfacer.get_field(sims.a, Val(:area_fraction)) .*
346+
Interfacer.get_field(sims.a, Val(:emissivity)) .*
347+
sims.a.cache_field .^ FT(4) .+
348+
Interfacer.get_field(sims.b, Val(:area_fraction)) .*
349+
Interfacer.get_field(sims.b, Val(:emissivity)) .*
350+
sims.b.cache_field .^ FT(4)
351+
) .^ FT(1 / 4)
352+
@test coupler_fields.T_sfc == expected_field_temp
353+
345354
expected_field =
346355
Interfacer.get_field(sims.a, Val(:area_fraction)) .* sims.a.cache_field .+
347356
Interfacer.get_field(sims.b, Val(:area_fraction)) .* sims.b.cache_field
348-
@test coupler_fields.T_sfc == expected_field
349357
@test coupler_fields.surface_direct_albedo == expected_field
350358
@test coupler_fields.surface_diffuse_albedo == expected_field
351359
end

test/flux_calculator_tests.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ Interfacer.get_field(
7777
sim::TestOcean,
7878
::Union{Val{:surface_direct_albedo}, Val{:surface_diffuse_albedo}},
7979
) = sim.integrator.p.α
80+
Interfacer.get_field(sim::TestOcean, ::Val{:emissivity}) = eltype(sim.integrator.T)(1)
8081

8182
FieldExchanger.import_atmos_fields!(csf, sim::TestOcean, atmos_sim) = nothing
8283

@@ -96,6 +97,8 @@ Interfacer.get_field(sim::DummySurfaceSimulation3, ::Val{:surface_temperature})
9697
Interfacer.get_field(sim::DummySurfaceSimulation3, ::Val{:area_fraction}) =
9798
sim.integrator.p.area_fraction
9899
Interfacer.get_field(sim::DummySurfaceSimulation3, ::Val{:beta}) = sim.integrator.p.beta
100+
Interfacer.get_field(sim::DummySurfaceSimulation3, ::Val{:emissivity}) =
101+
eltype(sim.integrator.T)(1)
99102

100103
for FT in (Float32, Float64)
101104
@testset "calculate correct fluxes: dry for FT=$FT" begin

0 commit comments

Comments
 (0)