From d8c2727b6a0abacbdb2c3fdbfb92e24e908fa840 Mon Sep 17 00:00:00 2001 From: Julia Sloan Date: Tue, 19 Dec 2023 15:26:15 -0800 Subject: [PATCH] updating interfacer docs --- docs/src/interfacer.md | 60 ++++++++++++--- .../modular/components/land/bucket_utils.jl | 65 ++++++++-------- .../components/ocean/eisenman_seaice_init.jl | 77 +++++++++---------- .../components/ocean/prescr_seaice_init.jl | 22 +++--- .../components/ocean/slab_ocean_init.jl | 22 +++--- 5 files changed, 138 insertions(+), 108 deletions(-) diff --git a/docs/src/interfacer.md b/docs/src/interfacer.md index 133d7e35a..92cff0d07 100644 --- a/docs/src/interfacer.md +++ b/docs/src/interfacer.md @@ -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 `.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: diff --git a/experiments/AMIP/modular/components/land/bucket_utils.jl b/experiments/AMIP/modular/components/land/bucket_utils.jl index f63cb1efc..554b3c2e5 100644 --- a/experiments/AMIP/modular/components/land/bucket_utils.jl +++ b/experiments/AMIP/modular/components/land/bucket_utils.jl @@ -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 @@ -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 @@ -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. diff --git a/experiments/AMIP/modular/components/ocean/eisenman_seaice_init.jl b/experiments/AMIP/modular/components/ocean/eisenman_seaice_init.jl index 21fd472aa..4851d5d18 100644 --- a/experiments/AMIP/modular/components/ocean/eisenman_seaice_init.jl +++ b/experiments/AMIP/modular/components/ocean/eisenman_seaice_init.jl @@ -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 @@ -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) @@ -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 diff --git a/experiments/AMIP/modular/components/ocean/prescr_seaice_init.jl b/experiments/AMIP/modular/components/ocean/prescr_seaice_init.jl index 67c60c696..06457e06d 100644 --- a/experiments/AMIP/modular/components/ocean/prescr_seaice_init.jl +++ b/experiments/AMIP/modular/components/ocean/prescr_seaice_init.jl @@ -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 @@ -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) diff --git a/experiments/AMIP/modular/components/ocean/slab_ocean_init.jl b/experiments/AMIP/modular/components/ocean/slab_ocean_init.jl index 080485ca5..2d63013d4 100644 --- a/experiments/AMIP/modular/components/ocean/slab_ocean_init.jl +++ b/experiments/AMIP/modular/components/ocean/slab_ocean_init.jl @@ -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 @@ -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)