Skip to content

Commit

Permalink
Merge pull request #644 from CliMA/js/interfacer
Browse files Browse the repository at this point in the history
initial Interfacer cleanup
  • Loading branch information
juliasloan25 authored Mar 6, 2024
2 parents 8c35576 + 23408c8 commit ef2b3ce
Show file tree
Hide file tree
Showing 23 changed files with 561 additions and 273 deletions.
2 changes: 1 addition & 1 deletion docs/src/checkpointer.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ This module contains general functions for logging the model states and restarti
## Checkpointer API

```@docs
ClimaCoupler.Checkpointer.get_model_state_vector
ClimaCoupler.Checkpointer.get_model_prog_state
ClimaCoupler.Checkpointer.restart_model_state!
ClimaCoupler.Checkpointer.checkpoint_model_state
```
2 changes: 1 addition & 1 deletion docs/src/fieldexchanger.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ The `FieldExchanger` needs to populate the coupler with
The component models are updated by broadcasting the coupler fields, via the `update_model_sims!` function. For an update, this function requires that `update_field!` is defined for the particular variable and component model. Currently, we support the:
- `AtmosModelSimulation`: `albedo`, `surface_temperature`
- if calculating fluxes in the atmospheric model: `roughness_momentum`, `roughness_buoyancy`, `beta`
- `SurfaceModelSimulation`: `air_density`, `turbulent_energy_flux`, `turbulent_moisture_flux`, `radiative_energy_flux`, `liquid_precipitation`, `snow_precipitation`
- `SurfaceModelSimulation`: `air_density`, `turbulent_energy_flux`, `turbulent_moisture_flux`, `radiative_energy_flux_sfc`, `liquid_precipitation`, `snow_precipitation`

If an `update_field!` function is not defined for a particular component model, it will be ignored.

Expand Down
213 changes: 172 additions & 41 deletions docs/src/interfacer.md
Original file line number Diff line number Diff line change
@@ -1,55 +1,186 @@
# Interfacer
This module contains functions that define the interface for coupling
component models, as well as stub objects that contain prescribed fields.
Here we explain each type of component model, and the functions that must
be implemented to use a component model with ClimaCoupler.jl

This module contains functions for defining the interface for coupling component models, as well as stub objects that contain prescribed fields.

## Coupled Simulation
- `CoupledSimulation` (`cs`) stores info for ESM run. We require that each `cs` contains four (`atmos_sim`, `land_sim`, `ocean_sim` and `ice_sim`) components. While this requirement will not be eventually needed, for the time being, if a simulation surface type is not needed for a given run, it should be initialized with `SurfaceStub` with a zero `area_fracion`. The `atmos_sim` should always be specified.

## Component model simulations
- all Simulations that are not the `CoupledSimulation` fall under `ComponentModelSimulation`
- the current version requires that there is:
- one `AtmosModelSimulation`
- one or more `SurfaceModelSimulation`s, which require the following adapter functions:
```
get_field(sim::SurfaceModelSimulation, ::Val{:area_fraction}) = ...
get_field(sim::SurfaceModelSimulation, ::Val{:surface_temperature}) = ...
get_field(sim::SurfaceModelSimulation, ::Val{:albedo}) = ...
get_field(sim::SurfaceModelSimulation, ::Val{:roughness_momentum}) = ...
get_field(sim::SurfaceModelSimulation, ::Val{:roughness_buoyancy}) = ...
get_field(sim::SurfaceModelSimulation, ::Val{:beta}) = ...
update_field!(sim::SurfaceModelSimulation, ::Val{:area_fraction}, field::Fields.Field) = ...
update_field!(sim::SurfaceModelSimulation, ::Val{:surface_temperature}, field::Fields.Field) = ...
```
- these adapter functions, to be defined in the component models' init files (preferably in their own repositories), allow the coupler to operate without having to assume particular data structures of the underlying component models. This allows easy swapping of model components, as well as a stable source code with coupler-specific unit tests.

## Prescribed conditions
- `SurfaceStub` is a `SurfaceModelSimulation`, but it only contains required data in `<surface_stub>.cache`, e.g., for the calculation of surface fluxes through a prescribed surface state. The above adapter functions are already predefined for `SurfaceStub`, with the cache variables specified as:
## Coupled simulation
A `CoupledSimulation` stores info for ESM run and contains each of the
component model simulations. We currently require that each `CoupledSimulation`
contains four components: `atmos_sim`, `land_sim`, `ocean_sim` and `ice_sim`.
If a simulation surface type is not needed for a given run, it should be
initialized with `SurfaceStub` with a zero `area_fracion`.
The `atmos_sim` should always be specified.


## Component simulations
Individual component model simulations fall under `ComponentModelSimulation`,
which together combine to make the `CoupledSimulation`.
We have two types of `ComponentModelSimulations`: `AtmosModelSimulation` and
`SurfaceModelSimulation`. The two have different requirements,
which are detailed below. `SurfaceModelSimulation` is further divided into
`SeaIceModelSimulation`, `LandModelSimulation`, and `OceanModelSimulation`,
representing the 3 currently-supported options for surface models.

### ComponentModelSimulation - required functions
A component model simulation should be implemented as a struct that is a concrete subtype
of a `ComponentModelSimulation`. This struct should contain all of the
information needed to run that simulation.

Each `ComponentModelSimulation` must extend the following functions to be able
to use our coupler. For some existing models, these are defined within
ClimaCoupler.jl in that model’s `init.jl` file, but it is preferable
for these to be defined in a model’s own repository. Note that the dispatch
`::ComponentModelSimulation` in the function definitions given below should
be replaced with the particular component model extending these functions.
- `init`: construct and return an instance of the `ComponentModelSimulation`,
and perform all initialization. This function should return a simulation that
is ready to be stepped in the coupled simulation. The interface for this
function varies across component models.
- `name(::ComponentModelSimulation)`: return a string containing the name of
this `ComponentModelSimulation`, which is used for printing information about
component models and writing to checkpoint files.
- `step!(::ComponentModelSimulation, t)`: A function to update the
simulation in-place with values calculate for time `t`. For the
models we currently have implemented, this is a simple wrapper around
the `step!` function implemented in SciMLBase.jl.
- `reinit!(::ComponentModelSimulation)`: A function to restart a simulation
after solving of the simulation has been paused or interrupted. Like
`step!`, this is currently a simple wrapper around the `reinit!` function
of SciMLBase.jl.
- `get_model_prog_state(::ComponentModelSimulation)`: A function that
returns the state vector of the simulation at its current state. This
is used for checkpointing the simulation.

### ComponentModelSimulation - optional functions
- `update_sim!(::ComponentModelSimulation, csf, turbulent_fluxes)`: A
function to update each of the fields of the component model simulation
that are updated by the coupler. ClimaCoupler.jl provides defaults of
this function for both `AtmosModelSimulation` and
`SurfaceModelSimulation` that update each of the fields expected by
the coupler. This function can optionally be extended to include
additional field updates as desired.

### AtmosModelSimulation - required functions
In addition to the functions required for a general
`ComponentModelSimulation`, an `AtmosModelSimulation` requires the
following functions to retrieve and update its fields.
- `get_field(::AtmosModelSimulation. ::Val{property})`: This getter
function returns the value of the field property for the simulation
in its current state. For an `AtmosModelSimulation`, it must be extended
for the following properties:

| Coupler name | Description | Units |
|-------------------|-------------|-------|
| `air_density` | air density at the surface of the atmosphere | kg m^-3 |
| `air_temperature` | air temperature at the surface of the atmosphere | K |
| `energy` | globally integrated energy | J |
| `height_int` | height at the first internal model level | m |
| `height_sfc` | height at the surface (only required when using `PartitionedStateFluxes`) | m |
| `liquid_precipitation` | liquid precipitation at the surface | kg m^-2 s^-1 |
| `radiative_energy_flux_sfc` | net radiative flux at the surface | W m^-2 |
| `radiative_energy_flux_toa` | net radiative flux at the top of the atmosphere | W m^-2 |
| `snow_precipitation` | snow precipitation at the surface | kg m^-2 s^-1 |
| `turbulent_energy_flux` | aerodynamic turbulent surface fluxes of energy (sensible and latent heat) | W m^-2 |
| `turbulent_moisture_flux` | aerodynamic turbulent surface fluxes of energy (evaporation) | kg m^-2 s^-1 |
| `thermo_state_int` | thermodynamic state at the first internal model level | |
| `uv_int` | horizontal wind velocity vector at the first internal model level | m s^-1 |
| `water` | globally integrated water | kg |



- `update_field!(::AtmosModelSimulation. ::Val{property}, field)`:
A function to update the value of property in the component model
simulation, using the values in `field`. This update should
be done in place. If this function isn't extended for a property,
that property will remain constant throughout the simulation
and a warning will be raised.
This function is expected to be extended for the
following properties:

| Coupler name | Description | Units |
|-------------------|-------------|-------|
| `co2` | global mean co2 | ppm |
| `surface_albedo` | bulk surface albedo over the whole surface space | |
| `surface_temperature` | temperature over the combined surface space | K |
| `turbulent_fluxes` | turbulent fluxes (note: only required when using `PartitionedStateFluxes` option - see our `FluxCalculator` module docs for more information) | W m^-2 |


### SurfaceModelSimulation - required functions
Analogously to the `AtmosModelSimulation`, a `SurfaceModelSimulation`
requires additional functions to those required for a general `ComponentModelSimulation`.
- `get_field(::SurfaceModelSimulation. ::Val{property})`: This getter
function returns the value of the field property for the simulation at
the current time. For a `SurfaceModelSimulation`, it must be extended
for the following properties:

| Coupler name | Description | Units |
|-------------------|-------------|-------|
| `air_density` | surface air density | kg m^-3 |
| `area_fraction` | fraction of the simulation grid surface area this model covers | |
| `beta` | factor that scales evaporation based on its estimated level of saturation | |
| `roughness_buoyancy` | aerodynamic roughness length for buoyancy | m |
| `roughness_momentum` | aerodynamic roughness length for momentum | m |
| `surface_albedo` | bulk surface albedo | |
| `surface_humidity` | surface humidity | kg kg^-1 |
| `surface_temperature` | surface temperature | K |

- `update_field!(::SurfaceModelSimulation. ::Val{property}, field)`:
A function to update the value of property in the component model
simulation, using the values in `field` passed from the coupler
This update should be done in place. If this function
isn't extended for a property,
that property will remain constant throughout the simulation
and a warning will be raised.
This function is expected to be extended for the
following properties:

| Coupler name | Description | Units |
|-------------------|-------------|-------|
| `air_density` | surface air density | kg m^-3 |
| `area_fraction` | fraction of the simulation grid surface area this model covers | |
| `liquid_precipitation` | liquid precipitation at the surface | kg m^-2 s^-1 |
| `radiative_energy_flux_sfc` | net radiative flux at the surface | W m^-2 |
| `snow_precipitation` | snow precipitation at the surface | kg m^-2 s^-1 |
| `turbulent_energy_flux` | aerodynamic turbulent surface fluxes of energy (sensible and latent heat) | W m^-2 |
| `turbulent_moisture_flux` | aerodynamic turbulent surface fluxes of energy (evaporation) | kg m^-2 s^-1 |

### SurfaceModelSimulation - optional functions
- `update_turbulent_fluxes_point!(::ComponentModelSimulation, fields::NamedTuple, colidx)`:
This function updates the turbulent fluxes of the component model simulation
at this point in horizontal space. The values are updated using the energy
and moisture turbulent fluxes stored in fields which are calculated by the
coupler. Note that this function is only required when using the
`PartitionedStateFluxes` option of ClimaCoupler.jl. See our `FluxCalculator`
module docs for more information.

### Prescribed surface conditions - SurfaceStub
- `SurfaceStub` is a `SurfaceModelSimulation`, but it only contains
required data in `<surface_stub>.cache`, e.g., for the calculation
of surface fluxes through a prescribed surface state. The above
adapter functions are already predefined for `SurfaceStub`, with
the cache variables specified as:
```
get_field(sim::SurfaceStub, ::Val{:air_density}) = sim.cache.ρ_sfc
get_field(sim::SurfaceStub, ::Val{:area_fraction}) = sim.cache.area_fraction
get_field(sim::SurfaceStub, ::Val{:surface_temperature}) = sim.cache.T_sfc
get_field(sim::SurfaceStub, ::Val{:albedo}) = sim.cache.α
get_field(sim::SurfaceStub, ::Val{:roughness_momentum}) = sim.cache.z0m
get_field(sim::SurfaceStub, ::Val{:roughness_buoyancy}) = sim.cache.z0b
get_field(sim::SurfaceStub, ::Val{:beta}) = sim.cache.beta
get_field(sim::SurfaceStub, ::Val{:energy}) = nothing
get_field(sim::SurfaceStub, ::Val{:roughness_buoyancy}) = sim.cache.z0b
get_field(sim::SurfaceStub, ::Val{:roughness_momentum}) = sim.cache.z0m
get_field(sim::SurfaceStub, ::Val{:surface_albedo}) = sim.cache.α
get_field(sim::SurfaceStub, ::Val{:surface_humidity}) = TD.q_vap_saturation_generic.(sim.cache.thermo_params, sim.cache.T_sfc, sim.cache.ρ_sfc, sim.cache.phase)
get_field(sim::SurfaceStub, ::Val{:surface_temperature}) = sim.cache.T_sfc
get_field(sim::SurfaceStub, ::Val{:water}) = nothing
```
with the corresponding `update_field!` functions
and with the corresponding `update_field!` functions
```
function update_field!(sim::SurfaceStub, ::Val{:air_density}, field)
sim.cache.ρ_sfc .= field
end
function update_field!(sim::SurfaceStub, ::Val{:area_fraction}, field::Fields.Field)
sim.cache.area_fraction .= field
end
function update_field!(sim::SurfaceStub, ::Val{:surface_temperature}, field::Fields.Field)
sim.cache.T_sfc .= field
end
```

## Interfacer API

```@docs
ClimaCoupler.Interfacer.ComponentModelSimulation
ClimaCoupler.Interfacer.AtmosModelSimulation
ClimaCoupler.Interfacer.SurfaceModelSimulation
ClimaCoupler.Interfacer.SurfaceStub
ClimaCoupler.Interfacer.name
ClimaCoupler.Interfacer.get_field
ClimaCoupler.Interfacer.update_field!
```
23 changes: 10 additions & 13 deletions experiments/AMIP/components/atmosphere/climaatmos_init.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import ClimaCoupler.FluxCalculator:
get_surface_params
using ClimaCore: Fields.level, Geometry
import ClimaCoupler.Interfacer: get_field, update_field!, name
import ClimaCoupler.Checkpointer: get_model_state_vector
import ClimaCoupler.Checkpointer: get_model_prog_state
using StaticArrays

include("climaatmos_extra_diags.jl")
Expand Down Expand Up @@ -105,7 +105,7 @@ function atmos_init(::Type{FT}, atmos_config_dict::Dict) where {FT}
end

# extensions required by the Interfacer
get_field(sim::ClimaAtmosSimulation, ::Val{:radiative_energy_flux}) =
get_field(sim::ClimaAtmosSimulation, ::Val{:radiative_energy_flux_sfc}) =
Fields.level(sim.integrator.p.radiation.ᶠradiation_flux, half)
get_field(sim::ClimaAtmosSimulation, ::Val{:liquid_precipitation}) = sim.integrator.p.precipitation.col_integrated_rain # kg/m^2/s
get_field(sim::ClimaAtmosSimulation, ::Val{:snow_precipitation}) = sim.integrator.p.precipitation.col_integrated_snow # kg/m^2/s
Expand All @@ -128,13 +128,10 @@ get_field(sim::ClimaAtmosSimulation, ::Val{:air_density}) =
TD.air_density.(thermo_params, sim.integrator.p.precomputed.ᶜts)
get_field(sim::ClimaAtmosSimulation, ::Val{:air_temperature}) =
TD.air_temperature.(thermo_params, sim.integrator.p.precomputed.ᶜts)
get_field(sim::ClimaAtmosSimulation, ::Val{:cv_m}) = TD.cv_m.(thermo_params, sim.integrator.p.precomputed.ᶜts)
get_field(sim::ClimaAtmosSimulation, ::Val{:gas_constant_air}) =
TD.gas_constant_air.(thermo_params, sim.integrator.p.precomputed.ᶜts)

get_surface_params(sim::ClimaAtmosSimulation) = CAP.surface_fluxes_params(sim.integrator.p.params)

function update_field!(atmos_sim::ClimaAtmosSimulation, ::Val{:co2_gm}, field)
function 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
Expand All @@ -147,7 +144,7 @@ function update_field!(sim::ClimaAtmosSimulation, ::Val{:surface_temperature}, c
sim.integrator.p.radiation.radiation_model.surface_temperature .= CA.RRTMGPI.field2array(csf.T_S)
end

function update_field!(sim::ClimaAtmosSimulation, ::Val{:albedo}, field)
function update_field!(sim::ClimaAtmosSimulation, ::Val{:surface_albedo}, field)
sim.integrator.p.radiation.radiation_model.diffuse_sw_surface_albedo .=
reshape(CA.RRTMGPI.field2array(field), 1, length(parent(field)))
sim.integrator.p.radiation.radiation_model.direct_sw_surface_albedo .=
Expand Down Expand Up @@ -188,7 +185,7 @@ step!(sim::ClimaAtmosSimulation, t) = step!(sim.integrator, t - sim.integrator.t
reinit!(sim::ClimaAtmosSimulation) = reinit!(sim.integrator)

function update_sim!(atmos_sim::ClimaAtmosSimulation, csf, turbulent_fluxes)
update_field!(atmos_sim, Val(:albedo), csf.albedo)
update_field!(atmos_sim, Val(:surface_albedo), csf.surface_albedo)
update_field!(atmos_sim, Val(:surface_temperature), csf)

if turbulent_fluxes isa PartitionedStateFluxes
Expand Down Expand Up @@ -295,21 +292,21 @@ function calculate_surface_air_density(atmos_sim::ClimaAtmosSimulation, T_S::Fie
end

"""
get_model_state_vector(sim::ClimaAtmosSimulation)
get_model_prog_state(sim::ClimaAtmosSimulation)
Extension of Checkpointer.get_model_state_vector to get the model state.
Extension of Checkpointer.get_model_prog_state to get the model state.
"""
function get_model_state_vector(sim::ClimaAtmosSimulation)
function get_model_prog_state(sim::ClimaAtmosSimulation)
return sim.integrator.u
end

"""
get_field(atmos_sim::ClimaAtmosSimulation, ::Val{:F_radiative_TOA})
get_field(atmos_sim::ClimaAtmosSimulation, ::Val{:radiative_energy_flux_toa})
Extension of Interfacer.get_field to get the net TOA radiation, which is a sum of the
upward and downward longwave and shortwave radiation.
"""
function get_field(atmos_sim::ClimaAtmosSimulation, ::Val{:F_radiative_TOA})
function get_field(atmos_sim::ClimaAtmosSimulation, ::Val{:radiative_energy_flux_toa})
FT = eltype(atmos_sim.integrator.u)
# save radiation source
if atmos_sim.integrator.p.radiation.radiation_model != nothing
Expand Down
Loading

0 comments on commit ef2b3ce

Please sign in to comment.