diff --git a/docs/src/fieldexchanger.md b/docs/src/fieldexchanger.md index 663770cc2..733905434 100644 --- a/docs/src/fieldexchanger.md +++ b/docs/src/fieldexchanger.md @@ -13,8 +13,6 @@ The component models are updated by broadcasting the coupler fields, via the `up If an `update_field!` function is not defined for a particular component model, it will be ignored. -Each component model is also required to define its own `step!` and `reinit!` functions, otherwise this will be ignored. - ## FieldExchanger API ```@docs @@ -25,11 +23,3 @@ Each component model is also required to define its own `step!` and `reinit!` fu ClimaCoupler.FieldExchanger.reinit_model_sims! ClimaCoupler.FieldExchanger.step_model_sims! ``` - - -## FieldExchanger Internal Functions - -```@docs - ClimaCoupler.FieldExchanger.step! - ClimaCoupler.FieldExchanger.reinit! -``` diff --git a/docs/src/interfacer.md b/docs/src/interfacer.md index 1e237d5cd..be170d284 100644 --- a/docs/src/interfacer.md +++ b/docs/src/interfacer.md @@ -163,7 +163,8 @@ module docs for more information. - `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 +adapter functions are already predefined for `SurfaceStub` +in the `surface_stub.jl` file, with the cache variables specified as: ``` get_field(sim::SurfaceStub, ::Val{:air_density}) = sim.cache.ρ_sfc diff --git a/experiments/AMIP/components/atmosphere/climaatmos.jl b/experiments/AMIP/components/atmosphere/climaatmos.jl index 27f64a5f4..ab3c28e58 100644 --- a/experiments/AMIP/components/atmosphere/climaatmos.jl +++ b/experiments/AMIP/components/atmosphere/climaatmos.jl @@ -17,9 +17,9 @@ import ClimaCoupler.FluxCalculator: extrapolate_ρ_to_sfc, get_surface_params, water_albedo_from_atmosphere! -import ClimaCoupler.Interfacer: get_field, update_field!, name +import ClimaCoupler.Interfacer: get_field, update_field!, name, step!, reinit! import ClimaCoupler.Checkpointer: get_model_prog_state -import ClimaCoupler.FieldExchanger: update_sim!, step!, reinit! +import ClimaCoupler.FieldExchanger: update_sim! import ClimaCoupler.Utilities: swap_space! include("climaatmos_extra_diags.jl") diff --git a/experiments/AMIP/components/land/climaland_bucket.jl b/experiments/AMIP/components/land/climaland_bucket.jl index 916e20b57..58669d37c 100644 --- a/experiments/AMIP/components/land/climaland_bucket.jl +++ b/experiments/AMIP/components/land/climaland_bucket.jl @@ -18,8 +18,7 @@ using ClimaLand: CoupledAtmosphere import ClimaLand.Parameters as LP -import ClimaCoupler.Interfacer: LandModelSimulation, get_field, update_field!, name -import ClimaCoupler.FieldExchanger: step!, reinit! +import ClimaCoupler.Interfacer: LandModelSimulation, get_field, update_field!, name, step!, reinit! import ClimaCoupler.FluxCalculator: update_turbulent_fluxes_point!, surface_thermo_state ### diff --git a/experiments/AMIP/components/ocean/eisenman_seaice.jl b/experiments/AMIP/components/ocean/eisenman_seaice.jl index 75ac89f91..9732df570 100644 --- a/experiments/AMIP/components/ocean/eisenman_seaice.jl +++ b/experiments/AMIP/components/ocean/eisenman_seaice.jl @@ -7,8 +7,7 @@ import ClimaTimeSteppers as CTS import ClimaCoupler: FluxCalculator import ClimaCoupler.FluxCalculator: update_turbulent_fluxes_point!, differentiate_turbulent_fluxes!, surface_thermo_state -import ClimaCoupler.Interfacer: get_field, update_field! -import ClimaCoupler.FieldExchanger: step!, reinit! +import ClimaCoupler.Interfacer: get_field, update_field!, step!, reinit! ### ### Functions required by ClimaCoupler.jl for a SurfaceModelSimulation diff --git a/experiments/AMIP/components/ocean/prescr_seaice.jl b/experiments/AMIP/components/ocean/prescr_seaice.jl index 47807fa5c..a255c0b90 100644 --- a/experiments/AMIP/components/ocean/prescr_seaice.jl +++ b/experiments/AMIP/components/ocean/prescr_seaice.jl @@ -4,8 +4,7 @@ using ClimaCore import ClimaTimeSteppers as CTS import Thermodynamics as TD -import ClimaCoupler.Interfacer: SeaIceModelSimulation, get_field, update_field!, name -import ClimaCoupler.FieldExchanger: step!, reinit! +import ClimaCoupler.Interfacer: SeaIceModelSimulation, get_field, update_field!, name, step!, reinit! import ClimaCoupler.FluxCalculator: update_turbulent_fluxes_point! using ClimaCoupler: Regridder import ClimaCoupler.Utilities: swap_space! diff --git a/experiments/AMIP/components/ocean/slab_ocean.jl b/experiments/AMIP/components/ocean/slab_ocean.jl index fa03a7471..d3e1d394a 100644 --- a/experiments/AMIP/components/ocean/slab_ocean.jl +++ b/experiments/AMIP/components/ocean/slab_ocean.jl @@ -2,8 +2,7 @@ import SciMLBase: ODEProblem, init using ClimaCore import ClimaTimeSteppers as CTS -import ClimaCoupler.Interfacer: OceanModelSimulation, get_field, update_field!, name -import ClimaCoupler.FieldExchanger: step!, reinit! +import ClimaCoupler.Interfacer: OceanModelSimulation, get_field, update_field!, name, step!, reinit! import ClimaCoupler.FluxCalculator: update_turbulent_fluxes_point! import ClimaCoupler.Utilities: swap_space! import ClimaCoupler.BCReader: float_type_bcf diff --git a/experiments/AMIP/coupler_driver.jl b/experiments/AMIP/coupler_driver.jl index f6ee47e17..6e554f91e 100644 --- a/experiments/AMIP/coupler_driver.jl +++ b/experiments/AMIP/coupler_driver.jl @@ -60,7 +60,7 @@ using ClimaCoupler.FluxCalculator: MoninObukhovScheme, partitioned_turbulent_fluxes!, water_albedo_from_atmosphere! -using ClimaCoupler.Interfacer: CoupledSimulation, SurfaceStub, get_field, update_field! +using ClimaCoupler.Interfacer: CoupledSimulation, SurfaceStub, get_field, update_field!, step! using ClimaCoupler.Regridder using ClimaCoupler.Regridder: update_surface_fractions!, combine_surfaces!, binary_mask using ClimaCoupler.TimeManager: diff --git a/src/FieldExchanger.jl b/src/FieldExchanger.jl index c38558415..ef4dad3fb 100644 --- a/src/FieldExchanger.jl +++ b/src/FieldExchanger.jl @@ -6,11 +6,11 @@ atmospheric and surface component models. """ module FieldExchanger -import SciMLBase: step!, reinit! export import_atmos_fields!, import_combined_surface_fields!, update_sim!, update_model_sims!, reinit_model_sims!, step_model_sims! using ClimaCoupler: Interfacer, FluxCalculator, Regridder, Utilities +import ClimaCoupler.Interfacer: step!, reinit! """ import_atmos_fields!(csf, model_sims, boundary_space, turbulent_fluxes) @@ -148,14 +148,6 @@ function update_sim!(sim::Interfacer.SurfaceModelSimulation, csf, turbulent_flux Interfacer.update_field!(sim, Val(:snow_precipitation), csf.P_snow) end -""" - update_sim!(::SurfaceStub, csf, area_fraction) - -The stub surface simulation only updates the air density (needed for the turbulent flux calculation). -""" -function update_sim!(sim::Interfacer.SurfaceStub, csf, area_fraction) - Interfacer.update_field!(sim, Val(:air_density), csf.ρ_sfc) -end """ update_model_sims!(model_sims, csf, turbulent_fluxes) @@ -192,13 +184,6 @@ function reinit_model_sims!(model_sims) end end -""" - reinit!(cs::SurfaceStub) - -The stub surface simulation is not updated by this function. Extends `SciMLBase.reinit!`. -""" -reinit!(::Interfacer.SurfaceStub) = nothing - """ step_model_sims!(model_sims, t) @@ -214,11 +199,4 @@ function step_model_sims!(model_sims, t) end end -""" - step!(::SurfaceStub, t) - -The stub surface simulation is not updated by this function. Extends `SciMLBase.step!`. -""" -step!(::Interfacer.SurfaceStub, _) = nothing - end # module diff --git a/src/FluxCalculator.jl b/src/FluxCalculator.jl index b8a924cc0..1858960fd 100644 --- a/src/FluxCalculator.jl +++ b/src/FluxCalculator.jl @@ -377,8 +377,6 @@ function update_turbulent_fluxes_point!( ) end -update_turbulent_fluxes_point!(sim::Interfacer.SurfaceStub, fields::NamedTuple, colidx::Fields.ColumnIndex) = nothing - """ differentiate_turbulent_fluxes!(sim::Interfacer.SurfaceModelSimulation, args) diff --git a/src/Interfacer.jl b/src/Interfacer.jl index 530b123d0..9f87eb0fe 100644 --- a/src/Interfacer.jl +++ b/src/Interfacer.jl @@ -5,6 +5,7 @@ This modules contains abstract types, interface templates and model stubs for co """ module Interfacer import Thermodynamics as TD +import SciMLBase: step!, reinit! using ClimaCore: Fields export CoupledSimulation, @@ -12,13 +13,15 @@ export CoupledSimulation, ComponentModelSimulation, AtmosModelSimulation, SurfaceModelSimulation, - SurfaceStub, SeaIceModelSimulation, LandModelSimulation, OceanModelSimulation, name, get_field, - update_field! + update_field!, + SurfaceStub, + step!, + reinit! """ @@ -163,41 +166,6 @@ get_field(sim::ComponentModelSimulation, val::Val) = get_field_error(sim, val) get_field_error(sim, val::Val{X}) where {X} = error("undefined field `$X` for " * name(sim)) - -""" - SurfaceStub - -On object containing simulation-like info, used as a stub or for prescribed data. -""" -struct SurfaceStub{I} <: SurfaceModelSimulation - cache::I -end - -""" - stub_init(cache) - -Initialization function for SurfaceStub simulation type. -""" -stub_init(cache) = SurfaceStub(cache) - -""" - get_field(::SurfaceStub, ::Val) - -A getter function, that should not allocate. If undefined, it returns a descriptive error. -""" -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{: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_direct_albedo}) = sim.cache.α_direct -get_field(sim::SurfaceStub, ::Val{:surface_diffuse_albedo}) = sim.cache.α_diffuse -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 - """ get_field(::ComponentModelSimulation, ::Val, colidx::Fields.ColumnIndex) @@ -257,32 +225,38 @@ update_field_warning(sim, val::Val{X}) where {X} = @warn("`update_field!` is not extended for the `$X` field of " * name(sim) * ": skipping update.", maxlog = 1) """ - update_field!(sim::SurfaceStub, ::Val{:area_fraction}, field::Fields.Field) + name(::ComponentModelSimulation) -Updates the specified value in the cache of `SurfaceStub`. +Returns simulation name, if defined, or `Unnamed` if not. """ -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 -function update_field!(sim::SurfaceStub, ::Val{:air_density}, field) - parent(sim.cache.ρ_sfc) .= parent(field) -end -function update_field!(sim::SurfaceStub, ::Val{:surface_direct_albedo}, field::Fields.Field) - sim.cache.α_direct .= field -end -function update_field!(sim::SurfaceStub, ::Val{:surface_diffuse_albedo}, field::Fields.Field) - sim.cache.α_diffuse .= field -end +name(::ComponentModelSimulation) = "Unnamed" """ - name(::ComponentModelSimulation) + step!(sim::ComponentModelSimulation, t) -Returns simulation name, if defined, or `Unnamed` if not. +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. + +This must be extended for all component models - otherwise this default +function will be called and an error will be raised. """ -name(::ComponentModelSimulation) = "Unnamed" -name(::SurfaceStub) = "SurfaceStub" +step!(sim::ComponentModelSimulation, t) = error("undefined step! for " * name(sim)) + +""" + reinit!(sim::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. + +This must be extended for all component models - otherwise this default +function will be called and an error will be raised. +""" +reinit!(sim::ComponentModelSimulation) = error("undefined reinit! for " * name(sim)) + + +# Include file containing the surface stub simulation type. +include("surface_stub.jl") end # module diff --git a/src/surface_stub.jl b/src/surface_stub.jl new file mode 100644 index 000000000..dc66f6f11 --- /dev/null +++ b/src/surface_stub.jl @@ -0,0 +1,94 @@ +""" + SurfaceStub + +On object containing simulation-like info, used as a stub or for prescribed data. +""" +struct SurfaceStub{I} <: SurfaceModelSimulation + cache::I +end + +""" + stub_init(cache) + +Initialization function for SurfaceStub simulation type. +""" +stub_init(cache) = SurfaceStub(cache) + +## Extensions of Interfacer.jl functions + +""" + get_field(::SurfaceStub, ::Val) + +A getter function, that should not allocate. If undefined, it returns a descriptive error. +""" +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{: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_direct_albedo}) = sim.cache.α_direct +get_field(sim::SurfaceStub, ::Val{:surface_diffuse_albedo}) = sim.cache.α_diffuse +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 + +""" + update_field!(sim::SurfaceStub, ::Val{:area_fraction}, field::Fields.Field) + +Updates the specified value in the cache of `SurfaceStub`. +""" +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 +function update_field!(sim::SurfaceStub, ::Val{:air_density}, field) + parent(sim.cache.ρ_sfc) .= parent(field) +end +function update_field!(sim::SurfaceStub, ::Val{:surface_direct_albedo}, field::Fields.Field) + sim.cache.α_direct .= field +end +function update_field!(sim::SurfaceStub, ::Val{:surface_diffuse_albedo}, field::Fields.Field) + sim.cache.α_diffuse .= field +end + +""" + name(::ComponentModelSimulation) + +Returns simulation name, if defined, or `Unnamed` if not. +""" +name(::SurfaceStub) = "SurfaceStub" + + +## Extensions of FieldExchanger.jl functions + +""" + update_sim!(::SurfaceStub, csf, area_fraction) + +The stub surface simulation only updates the air density (needed for the turbulent flux calculation). +""" +function update_sim!(sim::SurfaceStub, csf, area_fraction) + update_field!(sim, Val(:air_density), csf.ρ_sfc) +end + +""" + reinit!(cs::SurfaceStub) + +The stub surface simulation is not updated by this function. Extends `SciMLBase.reinit!`. +""" +reinit!(::SurfaceStub) = nothing + +""" + step!(::SurfaceStub, t) + +The stub surface simulation is not updated by this function. Extends `SciMLBase.step!`. +""" +step!(::SurfaceStub, _) = nothing + + +## Extensions of FluxCalculator.jl functions + +update_turbulent_fluxes_point!(sim::SurfaceStub, fields::NamedTuple, colidx::Fields.ColumnIndex) = nothing diff --git a/test/component_model_tests/eisenman_seaice_tests.jl b/test/component_model_tests/eisenman_seaice_tests.jl index 53957ca60..75edbc04f 100644 --- a/test/component_model_tests/eisenman_seaice_tests.jl +++ b/test/component_model_tests/eisenman_seaice_tests.jl @@ -5,7 +5,7 @@ using ClimaCore: Fields, Spaces import ClimaCoupler import ClimaCoupler.TestHelper -import ClimaCoupler.Interfacer: SeaIceModelSimulation +import ClimaCoupler.Interfacer: SeaIceModelSimulation, step! import ClimaCoupler: Regridder import ClimaParams as CP diff --git a/test/field_exchanger_tests.jl b/test/field_exchanger_tests.jl index d3fca071a..970331e23 100644 --- a/test/field_exchanger_tests.jl +++ b/test/field_exchanger_tests.jl @@ -2,16 +2,11 @@ using Test using ClimaCore: Meshes, Domains, Topologies, Spaces, Fields, InputOutput using ClimaCoupler: Utilities, Regridder, TestHelper import ClimaCoupler.Interfacer: - update_field!, AtmosModelSimulation, SurfaceModelSimulation, SurfaceStub, get_field, update_field! + update_field!, AtmosModelSimulation, SurfaceModelSimulation, SurfaceStub, get_field, update_field!, step!, reinit! import ClimaCoupler.FluxCalculator: CombinedStateFluxes, PartitionedStateFluxes, calculate_surface_air_density import ClimaCoupler.FieldExchanger: - import_atmos_fields!, - import_combined_surface_fields!, - update_model_sims!, - reinit_model_sims!, - reinit!, - step_model_sims!, - step! + import_atmos_fields!, import_combined_surface_fields!, update_model_sims!, reinit_model_sims!, step_model_sims! + # test for a simple generic atmos model @@ -60,6 +55,8 @@ get_field(sim::Union{TestSurfaceSimulation1, TestSurfaceSimulation2}, ::Val{:sur get_field(sim::Union{TestSurfaceSimulation2, TestSurfaceSimulation2}, ::Val{:surface_humidity}) = sim.cache_field .* eltype(sim.cache_field)(0) +reinit!(::TestSurfaceSimulation1) = nothing +step!(::TestSurfaceSimulation1, _) = nothing # atmos sim struct TestAtmosSimulation{C} <: AtmosModelSimulation @@ -239,10 +236,10 @@ for FT in (Float32, Float64) end end @testset "reinit_model_sims! for FT=$FT" begin - @test reinit_model_sims!((; stub = SurfaceStub(FT(0)))) == nothing + @test reinit_model_sims!((; stub = TestSurfaceSimulation1(FT(0)))) == nothing end @testset "step_model_sims! for FT=$FT" begin - @test step_model_sims!((; stub = SurfaceStub(FT(0))), 1) == nothing + @test step_model_sims!((; stub = TestSurfaceSimulation1(FT(0))), 1) == nothing end end diff --git a/test/flux_calculator_tests.jl b/test/flux_calculator_tests.jl index 078f9c095..06ba59fea 100644 --- a/test/flux_calculator_tests.jl +++ b/test/flux_calculator_tests.jl @@ -301,15 +301,13 @@ for FT in (Float32, Float64) end @testset "update_turbulent_fluxes_point! for FT=$FT" begin - sim = Interfacer.SurfaceStub([]) - sim2 = DummySurfaceSimulation3([], [], [], []) + sim = DummySurfaceSimulation3([], [], [], []) colidx = Fields.ColumnIndex{2}((1, 1), 73) # arbitrary index - @test update_turbulent_fluxes_point!(sim, (;), colidx) == nothing @test_throws ErrorException( "update_turbulent_fluxes_point! is required to be dispatched on" * - Interfacer.name(sim2) * + Interfacer.name(sim) * ", but no method defined", - ) update_turbulent_fluxes_point!(sim2, (;), colidx) == ErrorException + ) update_turbulent_fluxes_point!(sim, (;), colidx) == ErrorException end @testset "surface_thermo_state for FT=$FT" begin diff --git a/test/interfacer_tests.jl b/test/interfacer_tests.jl index ef09cfdc4..5d54c27e6 100644 --- a/test/interfacer_tests.jl +++ b/test/interfacer_tests.jl @@ -15,7 +15,10 @@ import ClimaCoupler.Interfacer: SeaIceModelSimulation, AtmosModelSimulation, SurfaceStub, - update_field! + update_field!, + reinit!, + step!, + update_turbulent_fluxes_point! # test for a simple generic surface model struct DummySimulation{S} <: SeaIceModelSimulation @@ -231,6 +234,34 @@ end end end +@testset "undefined step! error" begin + FT = Float32 + sim = DummySimulation3(nothing) + @test_throws ErrorException("undefined step! for " * name(sim)) step!(sim, 1) +end + +@testset "undefined reinit! error" begin + FT = Float32 + sim = DummySimulation3(nothing) + @test_throws ErrorException("undefined reinit! for " * name(sim)) reinit!(sim) +end + +@testset "SurfaceStub step!" begin + FT = Float32 + @test step!(SurfaceStub(FT(0)), 1) == nothing +end + +@testset "SurfaceStub reinit!" begin + FT = Float32 + @test reinit!(SurfaceStub(FT(0))) == nothing +end + +@testset "SurfaceStub update_turbulent_fluxes_point!" begin + FT = Float32 + colidx = Fields.ColumnIndex{2}((1, 1), 73) # arbitrary index + @test update_turbulent_fluxes_point!(SurfaceStub(FT(0)), (;), colidx) == nothing +end + # # Test that update_field! gives correct warnings for unextended fields # for value in ( # :air_density,