Skip to content

Commit 9c97640

Browse files
authored
Merge pull request #1518 from CliMA/zs/js/ice_flux
fix radiative flux over surface models
2 parents 3164cf7 + 2e72204 commit 9c97640

File tree

16 files changed

+242
-123
lines changed

16 files changed

+242
-123
lines changed

NEWS.md

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

77
### ClimaCoupler features
88

9+
#### Provide `SW_d`, `LW_d` to surface models instead of `F_radiative` PR[#1518](https://github.com/CliMA/ClimaCoupler.jl/pull/1518)
10+
This allows us to correctly compute radiative flux over surface models by
11+
computing each contribution individually (`SW_u, SW_d, LW_u, LW_d`).
12+
913
#### Change some closures used in the Oceananigans model PR[#1524](https://github.com/CliMA/ClimaCoupler.jl/pull/1524)
1014
Per ocean team recommendation, we changed some closures to be non-default.
1115

1216
#### Change the behavior of `detect_restart_file` PR[#1515](https://github.com/CliMA/ClimaCoupler.jl/pull/1515)
1317
Users now only need to specify `restart_dir` and `restart_t` to restart a simulation from a specific file, and do
1418
not need to set `detect_restart_file` to true. `detect_restart_file` is used for detecting restart file automatically.
1519

20+
#### Fixes for sea ice PR[#1519](https://github.com/CliMA/ClimaCoupler.jl/pull/1519)
21+
Don't weight fluxes by area fraction when passing them to the surface models,
22+
only when providing them to the atmosphere.
23+
Zero out tendencies of prescribed sea ice and slab ocean where the area
24+
fraction is zero.
25+
For now, use binary area fractions for all surface models, until we correctly
26+
handle area fraction weighting.
27+
1628
#### Use `update_turbulent_fluxes!` instead of `update_field!` for atmosphere PR[#1511](https://github.com/CliMA/ClimaCoupler.jl/pull/1511)
1729
Instead of using an `update_field!` method that dispatches on `::Val{:turbulent_fluxes}`
1830
to update turbulent fluxes in the atmosphere, we switch to using a function `update_turbulent_fluxes!`.

docs/src/interfacer.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ The default coupler exchange fields are the following, defined in
9595
| `F_turb_moisture` | turbulent moisture flux | kg m⁻² s⁻¹ |
9696
| `F_turb_ρτxz` | turbulent momentum flux in the zonal direction | kg m⁻¹ s⁻² |
9797
| `F_turb_ρτyz` | turbulent momentum flux in the meridional direction | kg m⁻¹ s⁻² |
98-
| `F_radiative` | net radiative flux at the surface | W m⁻² |
98+
| `SW_d` | downward SW flux at the surface | W m⁻² |
99+
| `LW_d` | downward LW flux at the surface | W m⁻² |
99100
| `emissivity` | surface emissivity | - |
100101
| `T_sfc` | surface temperature, averaged across components | K |
101102
| `P_liq` | liquid precipitation | kg m⁻² s⁻¹ |
@@ -151,7 +152,8 @@ for the following properties:
151152
| `height_int` | height at the bottom cell center of the atmosphere space | m |
152153
| `height_sfc` | height at the bottom face of the atmosphere space | m |
153154
| `liquid_precipitation` | liquid precipitation at the surface | kg m⁻² s⁻¹ |
154-
| `radiative_energy_flux_sfc` | net radiative flux at the surface | W m⁻² |
155+
| `SW_d` | downwelling shortwave radiation at the surface | W m⁻² |
156+
| `LW_d` | downwelling longwave radiation at the surface | W m⁻² |
155157
| `radiative_energy_flux_toa` | net radiative flux at the top of the atmosphere | W m⁻² |
156158
| `snow_precipitation` | snow precipitation at the surface | kg m⁻² s⁻¹ |
157159
| `specific_humidity` | specific humidity at the bottom cell centers of the atmosphere | kg kg⁻¹ |
@@ -243,7 +245,8 @@ properties needed by a component model.
243245
|-----------------------------------------------|------------------------------------------------------------------------------|------------|
244246
| `area_fraction` | fraction of the simulation grid surface area this model covers | |
245247
| `liquid_precipitation` | liquid precipitation at the surface | kg m⁻² s⁻¹ |
246-
| `radiative_energy_flux_sfc` OR `LW_d`, `SW_d` | net radiative flux at the surface OR downward longwave, shortwave radiation | W m⁻² |
248+
| `SW_d` | downwelling shortwave radiation at the surface | W m⁻² |
249+
| `LW_d` | downwelling longwave radiation at the surface | W m⁻² |
247250
| `snow_precipitation` | snow precipitation at the surface | kg m⁻² s⁻¹ |
248251
| `turbulent_energy_flux` | aerodynamic turbulent surface fluxes of energy (sensible and latent heat) | W m⁻² |
249252
| `turbulent_moisture_flux` | aerodynamic turbulent surface fluxes of energy (evaporation) | kg m⁻² s⁻¹ |

experiments/ClimaEarth/components/atmosphere/climaatmos.jl

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,10 @@ Interfacer.get_field(sim::ClimaAtmosSimulation, ::Val{:co2}) =
278278
sim.integrator.p.tracers.co2[1]
279279

280280
function Interfacer.get_field(sim::ClimaAtmosSimulation, ::Val{:diffuse_fraction})
281-
hasradiation(sim.integrator) || return nothing
281+
# Diffuse fraction doesn't matter when we don't have radiation, so return zero
282+
FT = eltype(sim.integrator.u)
283+
hasradiation(sim.integrator) || return zero(FT)
284+
282285
radiation_model = sim.integrator.p.radiation.rrtmgp_model
283286
# only take the first level
284287
total_flux_dn = radiation_model.face_sw_flux_dn[1, :]
@@ -302,7 +305,10 @@ end
302305
Interfacer.get_field(sim::ClimaAtmosSimulation, ::Val{:liquid_precipitation}) =
303306
surface_rain_flux(sim.integrator.p.atmos.moisture_model, sim.integrator)
304307
function Interfacer.get_field(sim::ClimaAtmosSimulation, ::Val{:LW_d})
305-
hasradiation(sim.integrator) || return nothing
308+
# If we don't have radiation, downwelling LW is zero
309+
FT = eltype(sim.integrator.u)
310+
hasradiation(sim.integrator) || return zero(FT)
311+
306312
return CC.Fields.level(
307313
CC.Fields.array2field(
308314
sim.integrator.p.radiation.rrtmgp_model.face_lw_flux_dn,
@@ -317,12 +323,13 @@ Interfacer.get_field(sim::ClimaAtmosSimulation, ::Val{:specific_humidity}) =
317323
sim.integrator.u.c.ρ,
318324
1,
319325
)
320-
Interfacer.get_field(sim::ClimaAtmosSimulation, ::Val{:radiative_energy_flux_sfc}) =
321-
surface_radiation_flux(sim.integrator.p.atmos.radiation_mode, sim.integrator)
322326
Interfacer.get_field(sim::ClimaAtmosSimulation, ::Val{:snow_precipitation}) =
323327
surface_snow_flux(sim.integrator.p.atmos.moisture_model, sim.integrator)
324328
function Interfacer.get_field(sim::ClimaAtmosSimulation, ::Val{:SW_d})
325-
hasradiation(sim.integrator) || return nothing
329+
# If we don't have radiation, downwelling SW is zero
330+
FT = eltype(sim.integrator.u)
331+
hasradiation(sim.integrator) || return zero(FT)
332+
326333
return CC.Fields.level(
327334
CC.Fields.array2field(
328335
sim.integrator.p.radiation.rrtmgp_model.face_sw_flux_dn,
@@ -416,40 +423,46 @@ end
416423

417424
# extensions required by the Interfacer
418425
function Interfacer.update_field!(sim::ClimaAtmosSimulation, ::Val{:emissivity}, field)
419-
hasradiation(sim.integrator) || return nothing
420-
# Remap field onto the atmosphere surface space in scratch field
421-
temp_field_surface = sim.integrator.p.scratch.ᶠtemp_field_level
422-
Interfacer.remap!(temp_field_surface, field)
423-
# Set each row (band) of the emissivity matrix by transposing the vector returned from `field2array`
424-
sim.integrator.p.radiation.rrtmgp_model.surface_emissivity .=
425-
CC.Fields.field2array(temp_field_surface)'
426+
if hasradiation(sim.integrator)
427+
# Remap field onto the atmosphere surface space in scratch field
428+
temp_field_surface = sim.integrator.p.scratch.ᶠtemp_field_level
429+
Interfacer.remap!(temp_field_surface, field)
430+
# Set each row (band) of the emissivity matrix by transposing the vector returned from `field2array`
431+
sim.integrator.p.radiation.rrtmgp_model.surface_emissivity .=
432+
CC.Fields.field2array(temp_field_surface)'
433+
end
434+
return nothing
426435
end
427436
function Interfacer.update_field!(
428437
sim::ClimaAtmosSimulation,
429438
::Val{:surface_direct_albedo},
430439
field,
431440
)
432-
hasradiation(sim.integrator) || return nothing
433-
# Remap field onto the atmosphere surface space in scratch field
434-
temp_field_surface = sim.integrator.p.scratch.ᶠtemp_field_level
435-
Interfacer.remap!(temp_field_surface, field)
436-
# Set each row (band) of the albedo matrix by transposing the vector returned from `field2array`
437-
sim.integrator.p.radiation.rrtmgp_model.direct_sw_surface_albedo .=
438-
CC.Fields.field2array(temp_field_surface)'
441+
if hasradiation(sim.integrator)
442+
# Remap field onto the atmosphere surface space in scratch field
443+
temp_field_surface = sim.integrator.p.scratch.ᶠtemp_field_level
444+
Interfacer.remap!(temp_field_surface, field)
445+
# Set each row (band) of the albedo matrix by transposing the vector returned from `field2array`
446+
sim.integrator.p.radiation.rrtmgp_model.direct_sw_surface_albedo .=
447+
CC.Fields.field2array(temp_field_surface)'
448+
end
449+
return nothing
439450
end
440451

441452
function Interfacer.update_field!(
442453
sim::ClimaAtmosSimulation,
443454
::Val{:surface_diffuse_albedo},
444455
field,
445456
)
446-
hasradiation(sim.integrator) || return nothing
447-
# Remap field onto the atmosphere surface space in scratch field
448-
temp_field_surface = sim.integrator.p.scratch.ᶠtemp_field_level
449-
Interfacer.remap!(temp_field_surface, field)
450-
# Set each row (band) of the albedo matrix by transposing the vector returned from `field2array`
451-
sim.integrator.p.radiation.rrtmgp_model.diffuse_sw_surface_albedo .=
452-
CC.Fields.field2array(temp_field_surface)'
457+
if hasradiation(sim.integrator)
458+
# Remap field onto the atmosphere surface space in scratch field
459+
temp_field_surface = sim.integrator.p.scratch.ᶠtemp_field_level
460+
Interfacer.remap!(temp_field_surface, field)
461+
# Set each row (band) of the albedo matrix by transposing the vector returned from `field2array`
462+
sim.integrator.p.radiation.rrtmgp_model.diffuse_sw_surface_albedo .=
463+
CC.Fields.field2array(temp_field_surface)'
464+
end
465+
return nothing
453466
end
454467

455468
function FluxCalculator.update_turbulent_fluxes!(sim::ClimaAtmosSimulation, fields)

experiments/ClimaEarth/components/land/climaland_bucket.jl

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,13 @@ struct BucketSimulation{
3434
I <: SciMLBase.AbstractODEIntegrator,
3535
A <: CC.Fields.Field,
3636
OW,
37+
RF,
3738
} <: Interfacer.LandModelSimulation
3839
model::M
3940
integrator::I
4041
area_fraction::A
4142
output_writer::OW
43+
radiative_fluxes::RF
4244
end
4345

4446
"""
@@ -239,13 +241,25 @@ function BucketSimulation(
239241
callback = SciMLBase.CallbackSet(diag_cb),
240242
)
241243

242-
return BucketSimulation(model, integrator, area_fraction, output_writer)
244+
# TODO move these to ClimaLand.jl as part of CoupledRadiativeFluxes
245+
radiative_fluxes =
246+
(; SW_d = CC.Fields.zeros(surface_space), LW_d = CC.Fields.zeros(surface_space))
247+
248+
return BucketSimulation(
249+
model,
250+
integrator,
251+
area_fraction,
252+
output_writer,
253+
radiative_fluxes,
254+
)
243255
end
244256

245257
# extensions required by Interfacer
246258
Interfacer.get_field(sim::BucketSimulation, ::Val{:area_fraction}) = sim.area_fraction
247259
Interfacer.get_field(sim::BucketSimulation, ::Val{:beta}) =
248260
CL.surface_evaporative_scaling(sim.model, sim.integrator.u, sim.integrator.p)
261+
Interfacer.get_field(sim::BucketSimulation, ::Val{:emissivity}) =
262+
CL.surface_emissivity(sim.model, sim.integrator.u, sim.integrator.p)
249263
Interfacer.get_field(sim::BucketSimulation, ::Val{:roughness_buoyancy}) =
250264
sim.model.parameters.z_0b
251265
Interfacer.get_field(sim::BucketSimulation, ::Val{:roughness_momentum}) =
@@ -294,12 +308,11 @@ function Interfacer.update_field!(
294308
ρ_liq = LP.ρ_cloud_liq(sim.model.parameters.earth_param_set)
295309
Interfacer.remap!(sim.integrator.p.drivers.P_liq, field ./ ρ_liq)
296310
end
297-
function Interfacer.update_field!(
298-
sim::BucketSimulation,
299-
::Val{:radiative_energy_flux_sfc},
300-
field,
301-
)
302-
Interfacer.remap!(sim.integrator.p.bucket.R_n, field)
311+
function Interfacer.update_field!(sim::BucketSimulation, ::Val{:SW_d}, field)
312+
Interfacer.remap!(sim.radiative_fluxes.SW_d, field)
313+
end
314+
function Interfacer.update_field!(sim::BucketSimulation, ::Val{:LW_d}, field)
315+
Interfacer.remap!(sim.radiative_fluxes.LW_d, field)
303316
end
304317
function Interfacer.update_field!(
305318
sim::BucketSimulation,
@@ -346,6 +359,39 @@ function FluxCalculator.update_turbulent_fluxes!(sim::BucketSimulation, fields::
346359
return nothing
347360
end
348361

362+
"""
363+
update_sim!(sim::BucketSimulation, csf)
364+
365+
Updates the surface component model cache with the current coupler fields besides turbulent fluxes.
366+
367+
# Arguments
368+
- `sim`: [Interfacer.SurfaceModelSimulation] containing a surface model simulation object.
369+
- `csf`: [NamedTuple] containing coupler fields.
370+
"""
371+
function update_sim!(sim::BucketSimulation, csf)
372+
# radiative fluxes
373+
# TODO add SW_d, LW_d fields to BucketSimulation and update there instead
374+
Interfacer.update_field!(sim, Val(:SW_d), csf.SW_d)
375+
Interfacer.update_field!(sim, Val(:LW_d), csf.LW_d)
376+
377+
model = sim.model
378+
Y = sim.integrator.U
379+
p = sim.integrator.p
380+
t = sim.integrator.t
381+
382+
# TODO: get sigma from parameters
383+
σ = FT(5.67e-8)
384+
@. sim.integrator.p.bucket.R_n =
385+
(1 - CL.surface_albedo(model, Y, p)) * sim.radiative_fluxes.SW_d +
386+
Interfacer.get_field(sim, Val(:emissivity)) *
387+
(sim.radiative_fluxes.LW_d - σ * CL.surface_temperature(model, Y, p, t)^4)
388+
389+
# precipitation
390+
Interfacer.update_field!(sim, Val(:liquid_precipitation), csf.P_liq)
391+
Interfacer.update_field!(sim, Val(:snow_precipitation), csf.P_snow)
392+
return nothing
393+
end
394+
349395
"""
350396
Checkpointer.get_model_prog_state(sim::BucketSimulation)
351397

experiments/ClimaEarth/components/land/climaland_integrated.jl

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -404,10 +404,10 @@ function Interfacer.update_field!(
404404
ρ_liq = (LP.ρ_cloud_liq(sim.model.soil.parameters.earth_param_set))
405405
Interfacer.remap!(sim.integrator.p.drivers.P_snow, field ./ ρ_liq)
406406
end
407-
function Interfacer.update_field!(sim::ClimaLandSimulation, ::Val{:lw_d}, field)
407+
function Interfacer.update_field!(sim::ClimaLandSimulation, ::Val{:LW_d}, field)
408408
Interfacer.remap!(sim.integrator.p.drivers.LW_d, field)
409409
end
410-
function Interfacer.update_field!(sim::ClimaLandSimulation, ::Val{:sw_d}, field)
410+
function Interfacer.update_field!(sim::ClimaLandSimulation, ::Val{:SW_d}, field)
411411
Interfacer.remap!(sim.integrator.p.drivers.SW_d, field)
412412
end
413413

@@ -423,8 +423,8 @@ Interfacer.close_output_writers(sim::ClimaLandSimulation) =
423423
function FieldExchanger.update_sim!(sim::ClimaLandSimulation, csf)
424424
# update fields for radiative transfer
425425
Interfacer.update_field!(sim, Val(:diffuse_fraction), csf.diffuse_fraction)
426-
Interfacer.update_field!(sim, Val(:sw_d), csf.SW_d)
427-
Interfacer.update_field!(sim, Val(:lw_d), csf.LW_d)
426+
Interfacer.update_field!(sim, Val(:SW_d), csf.SW_d)
427+
Interfacer.update_field!(sim, Val(:LW_d), csf.LW_d)
428428

429429
# update fields for canopy conductance and photosynthesis
430430
Interfacer.update_field!(sim, Val(:c_co2), csf.c_co2)
@@ -461,8 +461,6 @@ end
461461
Extend Interfacer.add_coupler_fields! to add the fields required for ClimaLandSimulation.
462462
463463
The fields added are:
464-
- `:SW_d` (for radiative transfer)
465-
- `:LW_d` (for radiative transfer)
466464
- `:diffuse_fraction` (for radiative transfer)
467465
- `:c_co2` (for photosynthesis, biogeochemistry)
468466
- `:P_atmos` (for canopy conductance)
@@ -472,17 +470,8 @@ The fields added are:
472470
- `P_snow` (for moisture fluxes)
473471
"""
474472
function Interfacer.add_coupler_fields!(coupler_field_names, ::ClimaLandSimulation)
475-
land_coupler_fields = [
476-
:SW_d,
477-
:LW_d,
478-
:diffuse_fraction,
479-
:c_co2,
480-
:P_atmos,
481-
:T_atmos,
482-
:q_atmos,
483-
:P_liq,
484-
:P_snow,
485-
]
473+
land_coupler_fields =
474+
[:diffuse_fraction, :c_co2, :P_atmos, :T_atmos, :q_atmos, :P_liq, :P_snow]
486475
push!(coupler_field_names, land_coupler_fields...)
487476
end
488477

experiments/ClimaEarth/components/ocean/oceananigans.jl

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -483,15 +483,32 @@ function FieldExchanger.update_sim!(sim::OceananigansSimulation, csf)
483483
CC.Remapping.interpolate!(
484484
sim.remapping.scratch_arr1,
485485
sim.remapping.remapper_cc,
486-
csf.F_radiative,
486+
csf.SW_d,
487487
)
488-
remapped_F_radiative = sim.remapping.scratch_arr1
488+
remapped_SW_d = sim.remapping.scratch_arr1
489+
490+
CC.Remapping.interpolate!(
491+
sim.remapping.scratch_arr2,
492+
sim.remapping.remapper_cc,
493+
csf.LW_d,
494+
)
495+
remapped_LW_d = sim.remapping.scratch_arr2
489496

490497
# Update only the part due to radiative fluxes. For the full update, the component due
491498
# to latent and sensible heat is missing and will be updated in update_turbulent_fluxes.
492499
oc_flux_T = surface_flux(sim.ocean.model.tracers.T)
500+
# TODO: get sigma from parameters
501+
σ = 5.67e-8
502+
α = Interfacer.get_field(sim, Val(:surface_direct_albedo)) # scalar
503+
ϵ = Interfacer.get_field(sim, Val(:emissivity)) # scalar
493504
OC.interior(oc_flux_T, :, :, 1) .=
494-
remapped_F_radiative ./ (ocean_reference_density * ocean_heat_capacity)
505+
(
506+
-(1 - α) .* remapped_SW_d .-
507+
ϵ * (
508+
remapped_LW_d .-
509+
σ .* (273.15 .+ OC.interior(sim.ocean.model.tracers.T, :, :, 1)) .^ 4
510+
)
511+
) ./ (ocean_reference_density * ocean_heat_capacity)
495512

496513
# Remap precipitation fields onto scratch arrays; rename for clarity
497514
CC.Remapping.interpolate!(

0 commit comments

Comments
 (0)