Skip to content

Commit

Permalink
updating interfacer docs
Browse files Browse the repository at this point in the history
  • Loading branch information
juliasloan25 committed Dec 19, 2023
1 parent 8571a76 commit d8c2727
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 108 deletions.
60 changes: 49 additions & 11 deletions docs/src/interfacer.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,55 @@ This module contains functions for defining the interface for coupling component
- 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.


## `AtmosModelSimulation` requirements

## `SurfaceModelSimulation` requirements
### "Getter" functions (`get_field`)
- Each `SurfaceModelSimulation` is required to implement the following "getter"
functions, which retrieve a value from the model and provide it to the coupler.
This requires accessing the model's internal structure, so they should be
implemented in an `init` function in the model's own repository to abstract
model specifics away from the coupler.
```
get_field(sim::SurfaceModelSimulation, ::Val{:surface_temperature}) = ...
get_field(sim::SurfaceModelSimulation, ::Val{:surface_humidity}) = ...
get_field(sim::SurfaceModelSimulation, ::Val{:roughness_momentum}) = ...
get_field(sim::SurfaceModelSimulation, ::Val{:roughness_buoyancy}) = ...
get_field(sim::SurfaceModelSimulation, ::Val{:beta}) = ...
get_field(sim::SurfaceModelSimulation, ::Val{:albedo}) = ...
get_field(sim::SurfaceModelSimulation, ::Val{:area_fraction}, field::Fields.Field) = ...
get_field(sim::SurfaceModelSimulation, ::Val{:air_density}, field::Fields.Field) = ...
get_field(sim::SurfaceModelSimulation, ::Val{:energy}, field::Fields.Field) = ...
get_field(sim::SurfaceModelSimulation, ::Val{:water}, 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.
- note that these functions are currently defined in component models' init
files in `experiments/AMIP`, but these will be moved to the components'
own repositories eventually.

### Update functions (`update_field!`)
- Each `SurfaceModelSimulation` is also required to implement the following
update methods. These should overwrite the model's value for a particular
quantity with the value stored in `field`, again accessing the internals of
the particular model:
```
update_field!(sim::SurfaceModelSimulation, ::Val{:turbulent_energy_flux}, field) = ...
update_field!(sim::SurfaceModelSimulation, ::Val{:turbulent_moisture_flux}, field) = ...
update_field!(sim::SurfaceModelSimulation, ::Val{:radiative_energy_flux}, field) = ...
update_field!(sim::SurfaceModelSimulation, ::Val{:liquid_precipitation}, field) = ...
update_field!(sim::SurfaceModelSimulation, ::Val{:snow_precipitation}, field)
update_field!(sim::SurfaceModelSimulation, ::Val{:air_density}, field)
```

### Timestepping functions
- Each component model is required to provide methods for the following
functions so that the coupler is able to timestep each component model:
```
step!(sim::SurfaceModelSimulation, t) = ...
reinit!(sim::SurfaceModelSimulation) = ...
```

## 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:
Expand Down
65 changes: 32 additions & 33 deletions experiments/AMIP/modular/components/land/bucket_utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,38 @@ get_field(sim::BucketSimulation, ::Val{:albedo}) =
get_field(sim::BucketSimulation, ::Val{:area_fraction}) = sim.area_fraction
get_field(sim::BucketSimulation, ::Val{:air_density}) = sim.integrator.p.bucket.ρ_sfc

"""
get_field(bucket_sim::BucketSimulation, ::Val{:energy})
Extension of Interfacer.get_field that provides the total energy contained in the bucket, including the latent heat due to snow melt.
"""
function get_field(bucket_sim::BucketSimulation, ::Val{:energy})
# required by ConservationChecker
e_per_area = zeros(axes(bucket_sim.integrator.u.bucket.W))
ClimaCore.Fields.bycolumn(axes(bucket_sim.integrator.u.bucket.T)) do colidx
e_per_area[colidx] .=
bucket_sim.model.parameters.ρc_soil .* mean(bucket_sim.integrator.u.bucket.T[colidx]) .*
bucket_sim.domain.soil_depth
end

e_per_area .+=
-LSMP.LH_f0(bucket_sim.model.parameters.earth_param_set) .*
LSMP.ρ_cloud_liq(bucket_sim.model.parameters.earth_param_set) .* bucket_sim.integrator.u.bucket.σS
return e_per_area
end

"""
get_field(bucket_sim::BucketSimulation, ::Val{:water})
Extension of Interfacer.get_field that provides the total water contained in the bucket, including the liquid water in snow.
"""
function get_field(bucket_sim::BucketSimulation, ::Val{:water})
ρ_cloud_liq = ClimaLSM.LSMP.ρ_cloud_liq(bucket_sim.model.parameters.earth_param_set)
return
@. (bucket_sim.integrator.u.bucket.σS + bucket_sim.integrator.u.bucket.W + bucket_sim.integrator.u.bucket.Ws) *
ρ_cloud_liq # kg water / m2
end

function update_field!(sim::BucketSimulation, ::Val{:turbulent_energy_flux}, field)
parent(sim.integrator.p.bucket.turbulent_energy_flux) .= parent(field)
end
Expand All @@ -67,7 +99,6 @@ function update_field!(sim::BucketSimulation, ::Val{:snow_precipitation}, field)
ρ_liq = (LSMP.ρ_cloud_liq(sim.model.parameters.earth_param_set))
parent(sim.integrator.p.bucket.P_snow) .= parent(field ./ ρ_liq)
end

function update_field!(sim::BucketSimulation, ::Val{:air_density}, field)
parent(sim.integrator.p.bucket.ρ_sfc) .= parent(field)
end
Expand Down Expand Up @@ -111,38 +142,6 @@ function get_model_state_vector(sim::BucketSimulation)
return sim.integrator.u.bucket
end

"""
get_field(bucket_sim::BucketSimulation, ::Val{:energy})
Extension of Interfacer.get_field that provides the total energy contained in the bucket, including the latent heat due to snow melt.
"""
function get_field(bucket_sim::BucketSimulation, ::Val{:energy})
# required by ConservationChecker
e_per_area = zeros(axes(bucket_sim.integrator.u.bucket.W))
ClimaCore.Fields.bycolumn(axes(bucket_sim.integrator.u.bucket.T)) do colidx
e_per_area[colidx] .=
bucket_sim.model.parameters.ρc_soil .* mean(bucket_sim.integrator.u.bucket.T[colidx]) .*
bucket_sim.domain.soil_depth
end

e_per_area .+=
-LSMP.LH_f0(bucket_sim.model.parameters.earth_param_set) .*
LSMP.ρ_cloud_liq(bucket_sim.model.parameters.earth_param_set) .* bucket_sim.integrator.u.bucket.σS
return e_per_area
end

"""
get_field(bucket_sim::BucketSimulation, ::Val{:water})
Extension of Interfacer.get_field that provides the total water contained in the bucket, including the liquid water in snow.
"""
function get_field(bucket_sim::BucketSimulation, ::Val{:water})
ρ_cloud_liq = ClimaLSM.LSMP.ρ_cloud_liq(bucket_sim.model.parameters.earth_param_set)
return
@. (bucket_sim.integrator.u.bucket.σS + bucket_sim.integrator.u.bucket.W + bucket_sim.integrator.u.bucket.Ws) *
ρ_cloud_liq # kg water / m2
end

"""
get_land_temp_from_state(land_sim, u)
Returns the surface temperature of the earth, computed from the state u.
Expand Down
77 changes: 37 additions & 40 deletions experiments/AMIP/modular/components/ocean/eisenman_seaice_init.jl
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,40 @@ get_field(sim::EisenmanIceSimulation, ::Val{:albedo}) =
sim.integrator.p.params.p_o.α .* (1 - sim.integrator.p.ice_area_fraction)
get_field(sim::EisenmanIceSimulation, ::Val{:area_fraction}) = sim.integrator.p.area_fraction
get_field(sim::EisenmanIceSimulation, ::Val{:air_density}) = sim.integrator.p.Ya.ρ_sfc
get_field(sim::EisenmanIceSimulation, ::Val{:water}) = nothing

"""
get_field(sim::EisenmanIceSimulation, ::Val{:energy})
Extension of Interfacer.get_field to get the energy of the ocean.
It is the sum of the heat content of the mixed layer, the heat content of the ice, the heat flux from the ocean below ice.
"""
function get_field(sim::EisenmanIceSimulation, ::Val{:energy})
p_i = sim.integrator.p.params.p_i
p_o = sim.integrator.p.params.p_o
C0_base = p_i.C0_base
T_base = p_i.T_base
L_ice = p_i.L_ice
T_freeze = p_i.T_freeze
k_ice = p_i.k_ice
ocean_qflux = sim.integrator.p.Ya.ocean_qflux

cache = sim.integrator.p
Δt = cache.Δt
e_base = cache.Ya.e_base
ocean_qflux = cache.Ya.ocean_qflux

FT = eltype(sim.integrator.u)

hρc_ml = p_o.h * p_o.ρ * p_o.c

e_ml = @. p_o.h * p_o.ρ * p_o.c * sim.integrator.u.T_ml # heat
e_ice = @. p_i.L_ice * sim.integrator.u.h_ice # phase
e_qflux = @. ocean_qflux * FT(sim.integrator.t)

return @. e_ml + e_ice + e_qflux + e_base

end

function update_field!(sim::EisenmanIceSimulation, ::Val{:area_fraction}, field::Fields.Field)
sim.integrator.p.area_fraction .= field
Expand All @@ -265,6 +299,9 @@ end
function update_field!(sim::EisenmanIceSimulation, ::Val{:air_density}, field)
parent(sim.integrator.p.Ya.ρ_sfc) .= parent(field)
end
function update_field!(sim::EisenmanIceSimulation, ::Val{:∂F_turb_energy∂T_sfc}, field, colidx)
sim.integrator.p.Ya.∂F_turb_energy∂T_sfc[colidx] .= field
end

# extensions required by FieldExchanger
step!(sim::EisenmanIceSimulation, t) = step!(sim.integrator, t - sim.integrator.t, true)
Expand Down Expand Up @@ -292,43 +329,3 @@ Extension of differentiate_turbulent_fluxes! from FluxCalculator to get the turb
"""
differentiate_turbulent_fluxes!(sim::EisenmanIceSimulation, args) =
differentiate_turbulent_fluxes!(sim::EisenmanIceSimulation, args..., ΔT_sfc = 0.1)

function update_field!(sim::EisenmanIceSimulation, ::Val{:∂F_turb_energy∂T_sfc}, field, colidx)
sim.integrator.p.Ya.∂F_turb_energy∂T_sfc[colidx] .= field
end


"""
get_field(sim::EisenmanIceSimulation, ::Val{:energy})
Extension of Interfacer.get_field to get the energy of the ocean.
It is the sum of the heat content of the mixed layer, the heat content of the ice, the heat flux from the ocean below ice.
"""
function get_field(sim::EisenmanIceSimulation, ::Val{:energy})
p_i = sim.integrator.p.params.p_i
p_o = sim.integrator.p.params.p_o
C0_base = p_i.C0_base
T_base = p_i.T_base
L_ice = p_i.L_ice
T_freeze = p_i.T_freeze
k_ice = p_i.k_ice
ocean_qflux = sim.integrator.p.Ya.ocean_qflux

cache = sim.integrator.p
Δt = cache.Δt
e_base = cache.Ya.e_base
ocean_qflux = cache.Ya.ocean_qflux

FT = eltype(sim.integrator.u)

hρc_ml = p_o.h * p_o.ρ * p_o.c

e_ml = @. p_o.h * p_o.ρ * p_o.c * sim.integrator.u.T_ml # heat
e_ice = @. p_i.L_ice * sim.integrator.u.h_ice # phase
e_qflux = @. ocean_qflux * FT(sim.integrator.t)

return @. e_ml + e_ice + e_qflux + e_base

end

get_field(sim::EisenmanIceSimulation, ::Val{:water}) = nothing
22 changes: 10 additions & 12 deletions experiments/AMIP/modular/components/ocean/prescr_seaice_init.jl
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,20 @@ get_field(sim::PrescribedIceSimulation, ::Val{:beta}) = convert(eltype(sim.integ
get_field(sim::PrescribedIceSimulation, ::Val{:albedo}) = sim.integrator.p.params.α
get_field(sim::PrescribedIceSimulation, ::Val{:area_fraction}) = sim.integrator.p.area_fraction
get_field(sim::PrescribedIceSimulation, ::Val{:air_density}) = sim.integrator.p.ρ_sfc
get_field(sim::PrescribedIceSimulation, ::Val{:water}) = nothing

"""
get_field(sim::PrescribedIceSimulation, ::Val{:energy})
Extension of Interfacer.get_field to get the energy of the ocean.
It multiplies the the slab temperature by the heat capacity, density, and depth.
"""
get_field(sim::PrescribedIceSimulation, ::Val{:energy}) =
sim.integrator.p.params.ρ .* sim.integrator.p.params.c .* sim.integrator.u.T_sfc .* sim.integrator.p.params.h

function update_field!(sim::PrescribedIceSimulation, ::Val{:area_fraction}, field::Fields.Field)
sim.integrator.p.area_fraction .= field
end

function update_field!(sim::PrescribedIceSimulation, ::Val{:turbulent_energy_flux}, field)
parent(sim.integrator.p.F_turb_energy) .= parent(field)
end
Expand Down Expand Up @@ -174,17 +183,6 @@ function get_model_state_vector(sim::PrescribedIceSimulation)
return sim.integrator.u
end

"""
get_field(sim::PrescribedIceSimulation, ::Val{:energy})
Extension of Interfacer.get_field to get the energy of the ocean.
It multiplies the the slab temperature by the heat capacity, density, and depth.
"""
get_field(sim::PrescribedIceSimulation, ::Val{:energy}) =
sim.integrator.p.params.ρ .* sim.integrator.p.params.c .* sim.integrator.u.T_sfc .* sim.integrator.p.params.h

get_field(sim::PrescribedIceSimulation, ::Val{:water}) = nothing

"""
dss_state!(sim::PrescribedIceSimulation)
Expand Down
22 changes: 10 additions & 12 deletions experiments/AMIP/modular/components/ocean/slab_ocean_init.jl
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,20 @@ get_field(sim::SlabOceanSimulation, ::Val{:beta}) = convert(eltype(sim.integrato
get_field(sim::SlabOceanSimulation, ::Val{:albedo}) = sim.integrator.p.params.α
get_field(sim::SlabOceanSimulation, ::Val{:area_fraction}) = sim.integrator.p.area_fraction
get_field(sim::SlabOceanSimulation, ::Val{:air_density}) = sim.integrator.p.ρ_sfc
get_field(sim::SlabOceanSimulation, ::Val{:water}) = nothing

"""
get_field(sim::SlabOceanSimulation, ::Val{:energy})
Extension of Interfacer.get_field to get the energy of the ocean.
It multiplies the the slab temperature by the heat capacity, density, and depth.
"""
get_field(sim::SlabOceanSimulation, ::Val{:energy}) =
sim.integrator.p.params.ρ .* sim.integrator.p.params.c .* sim.integrator.u.T_sfc .* sim.integrator.p.params.h

function update_field!(sim::SlabOceanSimulation, ::Val{:area_fraction}, field::Fields.Field)
sim.integrator.p.area_fraction .= field
end

function update_field!(sim::SlabOceanSimulation, ::Val{:turbulent_energy_flux}, field)
parent(sim.integrator.p.F_turb_energy) .= parent(field)
end
Expand Down Expand Up @@ -175,17 +184,6 @@ function get_model_state_vector(sim::SlabOceanSimulation)
return sim.integrator.u
end

"""
get_field(sim::SlabOceanSimulation, ::Val{:energy})
Extension of Interfacer.get_field to get the energy of the ocean.
It multiplies the the slab temperature by the heat capacity, density, and depth.
"""
get_field(sim::SlabOceanSimulation, ::Val{:energy}) =
sim.integrator.p.params.ρ .* sim.integrator.p.params.c .* sim.integrator.u.T_sfc .* sim.integrator.p.params.h

get_field(sim::SlabOceanSimulation, ::Val{:water}) = nothing

"""
dss_state!(sim::SlabOceanSimulation)
Expand Down

0 comments on commit d8c2727

Please sign in to comment.