Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tr/add get fields atmos #1205

Merged
merged 6 commits into from
Mar 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion docs/src/interfacer.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ following properties:

| Coupler name | Description | Units |
|-------------------|-------------|-------|
| `co2` | global mean co2 | ppm |
| `emissivity` | surface emissivity | |
| `surface_direct_albedo` | bulk direct surface albedo over the whole surface space | |
| `surface_diffuse_albedo` | bulk diffuse surface albedo over the whole surface space | |
| `surface_temperature` | temperature over the combined surface space | K |
Expand All @@ -123,6 +123,31 @@ following properties:
A function to return the air density of the atmosphere simulation
extrapolated to the surface, with units of [kg m^-3].

<!-- replace "full ClimaLand model" with name of coupler sim struct-->
### AtmosModelSimulation - required functions to run with the full ClimaLand model

Coupling with full `ClimaLand` model requires the following functions, in addition
to the functions required for coupling with a general `SurfaceModelSimulation`.

- `get_field(::AtmosModelSimulation. ::Val{property})`:
This getter function must be extended
for the following properties:

| Coupler name | Description | Units |
|-------------------|-------------|-------|
| `air_pressure` | air pressure at the bottom cell centers of the atmosphere | Pa |
| `air_temperature` | air temperature at the bottom cell centers of the atmosphere | K |
| `cos_zenith` | cosine of the zenith angle | |
| `co2` | global mean co2 | ppm |
| `diffuse_fraction` | fraction of downwards shortwave flux that is direct | |
| `specific_humidity` | specific humidity at the bottom cell centers of the atmosphere| kg kg^-1 |
| `LW_d` | downwards longwave flux | W m^-2 |
| `SW_d` | downwards shortwave flux | W m^-2 |

Note that `air_temperature`, `air_pressure`, `cos_zenith`, `co2`, `diffuse_fraction`, `LW_d` and
`SW_d` will not be present in a `ClimaAtmosSimulation` if the model is setup with no radiation.
Because of this, a `ClimaAtmosSimulation` must have radiation if running with the full `ClimaLand` model.

### SurfaceModelSimulation - required functions
Analogously to the `AtmosModelSimulation`, a `SurfaceModelSimulation`
requires additional functions to those required for a general `ComponentModelSimulation`.
Expand Down
55 changes: 47 additions & 8 deletions experiments/ClimaEarth/components/atmosphere/climaatmos.jl
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,54 @@ moisture_flux(::Union{CA.EquilMoistModel, CA.NonEquilMoistModel}, integrator) =
ρq_tot(::Union{CA.EquilMoistModel, CA.NonEquilMoistModel}, integrator) = integrator.u.c.ρq_tot

# extensions required by the Interfacer
Interfacer.get_field(sim::ClimaAtmosSimulation, ::Val{:air_pressure}) =
CC.Fields.level(sim.integrator.p.precomputed.ᶜp, 1)
Interfacer.get_field(sim::ClimaAtmosSimulation, ::Val{:air_temperature}) =
TD.air_temperature.(
sim.integrator.p.params.thermodynamics_params,
CC.Fields.level(sim.integrator.p.precomputed.ᶜts, 1),
)
Interfacer.get_field(sim::ClimaAtmosSimulation, ::Val{:cos_zenith}) = CC.Fields.array2field(
sim.integrator.p.radiation.rrtmgp_model.cos_zenith,
CC.Fields.level(axes(sim.integrator.u.c), 1),
)
Interfacer.get_field(sim::ClimaAtmosSimulation, ::Val{:co2}) =
sim.integrator.p.radiation.rrtmgp_model.volume_mixing_ratio_co2[]
function Interfacer.get_field(sim::ClimaAtmosSimulation, ::Val{:diffuse_fraction})
radiation_model = sim.integrator.p.radiation.rrtmgp_model
# only take the first level
total_flux_dn = radiation_model.face_sw_flux_dn[1, :]
lowest_face_space = CC.Spaces.level(axes(sim.integrator.u.f), CC.Utilities.half)
if radiation_model.radiation_mode isa CA.RRTMGPInterface.GrayRadiation
diffuse_fraction = zero(total_flux_dn)
else
direct_flux_dn = radiation_model.face_sw_direct_flux_dn[1, :]
FT = eltype(total_flux_dn)
diffuse_fraction =
clamp.(
((x, y) -> y > zero(y) ? x / y : zero(y)).(total_flux_dn .- direct_flux_dn, total_flux_dn),
zero(FT),
one(FT),
)
end
return CC.Fields.array2field(diffuse_fraction, lowest_face_space)
end
Interfacer.get_field(sim::ClimaAtmosSimulation, ::Val{:liquid_precipitation}) =
surface_rain_flux(sim.integrator.p.atmos.moisture_model, sim.integrator)
Interfacer.get_field(sim::ClimaAtmosSimulation, ::Val{:LW_d}) = CC.Fields.level(
CC.Fields.array2field(sim.integrator.p.radiation.rrtmgp_model.face_lw_flux_dn, axes(sim.integrator.u.f)),
CC.Utilities.half,
)
Interfacer.get_field(sim::ClimaAtmosSimulation, ::Val{:specific_humidity}) =
CC.Fields.level(sim.integrator.u.c.ρq_tot ./ sim.integrator.u.c.ρ, 1)
Interfacer.get_field(sim::ClimaAtmosSimulation, ::Val{:radiative_energy_flux_sfc}) =
surface_radiation_flux(sim.integrator.p.atmos.radiation_mode, sim.integrator)
Interfacer.get_field(sim::ClimaAtmosSimulation, ::Val{:snow_precipitation}) =
surface_snow_flux(sim.integrator.p.atmos.moisture_model, sim.integrator)
Interfacer.get_field(sim::ClimaAtmosSimulation, ::Val{:SW_d}) = CC.Fields.level(
CC.Fields.array2field(sim.integrator.p.radiation.rrtmgp_model.face_sw_flux_dn, axes(sim.integrator.u.f)),
CC.Utilities.half,
)
Interfacer.get_field(sim::ClimaAtmosSimulation, ::Val{:turbulent_energy_flux}) =
CC.Geometry.WVector.(sim.integrator.p.precomputed.sfc_conditions.ρ_flux_h_tot)
Interfacer.get_field(sim::ClimaAtmosSimulation, ::Val{:turbulent_moisture_flux}) =
Expand All @@ -201,15 +243,12 @@ function Interfacer.get_field(sim::ClimaAtmosSimulation, ::Val{:uv_int})
return @. StaticArrays.SVector(uₕ_int.components.data.:1, uₕ_int.components.data.:2)
end

function Interfacer.update_field!(atmos_sim::ClimaAtmosSimulation, ::Val{:co2}, field)
if atmos_sim.integrator.p.atmos.radiation_mode isa CA.RRTMGPI.GrayRadiation
@warn("Gray radiation model initialized, skipping CO2 update", maxlog = 1)
return
else
atmos_sim.integrator.p.radiation.rrtmgp_model.volume_mixing_ratio_co2 .= Statistics.mean(parent(field))
end
end
# extensions required by the Interfacer
function Interfacer.update_field!(sim::ClimaAtmosSimulation, ::Val{:emissivity}, field)
# sets all 16 bands (rows) to the same values
sim.integrator.p.radiation.rrtmgp_model.surface_emissivity .=
reshape(CC.Fields.field2array(field), 1, length(parent(field)))
end
Comment on lines +247 to +251
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the long term, maybe we can make the RRTMGPInterface assume every band is the same if only one value is passed in (maybe this is already the behavior not? I'm not sure). This way we don't need to hard code the length(parent) here.

function Interfacer.update_field!(sim::ClimaAtmosSimulation, ::Val{:surface_direct_albedo}, field)
sim.integrator.p.radiation.rrtmgp_model.direct_sw_surface_albedo .=
reshape(CC.Fields.field2array(field), 1, length(parent(field)))
Expand Down
2 changes: 1 addition & 1 deletion src/Interfacer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ If it isn't extended, the field won't be updated and a warning will be raised.
update_field!(
sim::AtmosModelSimulation,
val::Union{
Val{:co2},
Val{:emissivity},
Val{:surface_direct_albedo},
Val{:surface_diffuse_albedo},
Val{:surface_temperature},
Expand Down
1 change: 0 additions & 1 deletion test/field_exchanger_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ end
function Interfacer.update_field!(sim::TestAtmosSimulation, ::Val{:roughness_momentum}, field)
parent(sim.cache.roughness_momentum) .= parent(field)
end
Interfacer.update_field!(sim::TestAtmosSimulation, ::Val{:co2}, field) = nothing
Interfacer.update_field!(sim::TestAtmosSimulation, ::Val{:surface_temperature}, field) = nothing
Interfacer.update_field!(sim::TestAtmosSimulation, ::Val{:roughness_buoyancy}, field) = nothing
Interfacer.update_field!(sim::TestAtmosSimulation, ::Val{:beta}, field) = nothing
Expand Down
2 changes: 1 addition & 1 deletion test/interfacer_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ end
sim = DummySimulation4(space)

# Test that update_field! gives correct warnings for unextended fields
for value in (:co2, :surface_direct_albedo, :surface_diffuse_albedo, :surface_temperature, :turbulent_fluxes)
for value in (:emissivity, :surface_direct_albedo, :surface_diffuse_albedo, :surface_temperature, :turbulent_fluxes)
val = Val(value)
@test_logs (
:warn,
Expand Down
Loading