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 5 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
10 changes: 9 additions & 1 deletion src/Interfacer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,20 @@ an atmosphere component model.
get_field(
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we should error if these get_fields aren't implemented - e.g. if we want to couple a simpler/different atmosphere with the bucket, it doesn't need to extend all these methods required only for the full land model. I was going to suggest that we could give warnings if these aren't extended, but that will print a lot of warnings, so maybe the best thing to do is just remove these fields from here and expect users to read what's required to extend from the documentation

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought this would only error if the get_field is actually called for that value. So if we used some simple atmosphere with the bucket, it would not need to extend get_field for all the methods listed here, because get_field(::SomeSimpleAtmos, ::Val{:cos_zenith}) would never be called. Is that correct?

I think it might still make sense to split the get_field(::AtmosModelSimulation) default error into what is required for coupling with all land models, and just what is required for coupling with ClimaLand.

sim::AtmosModelSimulation,
val::Union{
Val{:air_pressure},
Val{:air_temperature},
Val{:cos_zenith},
Val{:co2},
Val{:diffuse_fraction},
Val{:height_int},
Val{:height_sfc},
Val{:specific_humidity},
Val{:liquid_precipitation},
Val{:LW_d},
Val{:radiative_energy_flux_sfc},
Val{:radiative_energy_flux_toa},
Val{:snow_precipitation},
Val{:SW_d},
Val{:turblent_energy_flux},
Val{:turbulent_moisture_flux},
Val{:thermo_state_int},
Expand Down Expand Up @@ -214,7 +222,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
10 changes: 9 additions & 1 deletion test/interfacer_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,20 @@ end

# Test that get_field gives correct warnings for unextended fields
for value in (
:air_pressure,
:air_temperature,
:cos_zenith,
:co2,
:diffuse_fraction,
:height_int,
:height_sfc,
:specific_humidity,
:liquid_precipitation,
:LW_d,
:radiative_energy_flux_sfc,
:radiative_energy_flux_toa,
:snow_precipitation,
:SW_d,
:turbulent_energy_flux,
:turbulent_moisture_flux,
:thermo_state_int,
Expand Down Expand Up @@ -209,7 +217,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