From 6fbb5839187000ff7fb7d26cd934c84485db145e Mon Sep 17 00:00:00 2001 From: Julia Sloan Date: Tue, 10 Sep 2024 15:45:57 -0700 Subject: [PATCH] use ClimaUtilities CallbackManager --- docs/make.jl | 1 - docs/src/timemanager.md | 24 --- experiments/ClimaEarth/run_amip.jl | 31 ++- .../ClimaEarth/run_cloudless_aquaplanet.jl | 18 +- .../ClimaEarth/run_cloudy_aquaplanet.jl | 14 +- .../ClimaEarth/run_cloudy_slabplanet.jl | 18 +- experiments/ClimaEarth/run_dry_held_suarez.jl | 14 +- .../ClimaEarth/run_moist_held_suarez.jl | 14 +- src/ClimaCoupler.jl | 2 +- src/Diagnostics.jl | 17 +- src/Interfacer.jl | 11 + src/Regridder.jl | 4 +- src/TimeManager.jl | 181 ----------------- test/diagnostics_tests.jl | 12 +- test/regridder_tests.jl | 4 +- test/runtests.jl | 2 +- test/time_manager_tests.jl | 188 ------------------ 17 files changed, 84 insertions(+), 471 deletions(-) delete mode 100644 docs/src/timemanager.md delete mode 100644 src/TimeManager.jl delete mode 100644 test/time_manager_tests.jl diff --git a/docs/make.jl b/docs/make.jl index c21ee0bc1e..3e06e90679 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -65,7 +65,6 @@ interface_pages = [ "interfacer.md", "postprocessor.md", "regridder.md", - "timemanager.md", "utilities.md", ] performance_pages = ["performance.md"] diff --git a/docs/src/timemanager.md b/docs/src/timemanager.md deleted file mode 100644 index ed47e791cf..0000000000 --- a/docs/src/timemanager.md +++ /dev/null @@ -1,24 +0,0 @@ -# TimeManager - -This module contains functions that handle dates and times -in simulations. The functions in this module often call -functions from Julia's [Dates](https://docs.julialang.org/en/v1/stdlib/Dates/) module. - -## TimeManager API - -```@docs -ClimaCoupler.TimeManager.current_date -ClimaCoupler.TimeManager.strdate_to_datetime -ClimaCoupler.TimeManager.datetime_to_strdate -ClimaCoupler.TimeManager.trigger_callback -ClimaCoupler.TimeManager.AbstractFrequency -ClimaCoupler.TimeManager.Monthly -ClimaCoupler.TimeManager.EveryTimestep -ClimaCoupler.TimeManager.trigger_callback! -ClimaCoupler.TimeManager.CouplerCallback -ClimaCoupler.TimeManager.HourlyCallback -ClimaCoupler.TimeManager.MonthlyCallback -ClimaCoupler.TimeManager.update_firstdayofmonth! -ClimaCoupler.TimeManager.dt_cb -ClimaCoupler.TimeManager.do_nothing -``` diff --git a/experiments/ClimaEarth/run_amip.jl b/experiments/ClimaEarth/run_amip.jl index 7f1ac1bb8d..124630e914 100644 --- a/experiments/ClimaEarth/run_amip.jl +++ b/experiments/ClimaEarth/run_amip.jl @@ -48,19 +48,12 @@ import ClimaCore as CC # ## Coupler specific imports import ClimaCoupler import ClimaCoupler: - ConservationChecker, - Checkpointer, - Diagnostics, - FieldExchanger, - FluxCalculator, - Interfacer, - Regridder, - TimeManager, - Utilities + ConservationChecker, Checkpointer, Diagnostics, FieldExchanger, FluxCalculator, Interfacer, Regridder, Utilities import ClimaUtilities.SpaceVaryingInputs: SpaceVaryingInput import ClimaUtilities.TimeVaryingInputs: TimeVaryingInput, evaluate! import ClimaUtilities.ClimaArtifacts: @clima_artifact +import ClimaUtilities: CallbackManager import Interpolations pkg_dir = pkgdir(ClimaCoupler) @@ -472,7 +465,7 @@ if use_coupler_diagnostics monthly_3d_diags = Diagnostics.init_diagnostics( (:T, :u, :q_tot, :q_liq_ice), atmos_sim.domain.center_space; - save = TimeManager.Monthly(), + save = CallbackManager.Monthly(), operations = (; accumulate = Diagnostics.TimeMean([Int(0)])), output_dir = dir_paths.output, name_tag = "monthly_mean_3d_", @@ -481,7 +474,7 @@ if use_coupler_diagnostics monthly_2d_diags = Diagnostics.init_diagnostics( (:precipitation_rate, :toa_fluxes, :T_sfc, :turbulent_energy_fluxes), boundary_space; - save = TimeManager.Monthly(), + save = CallbackManager.Monthly(), operations = (; accumulate = Diagnostics.TimeMean([Int(0)])), output_dir = dir_paths.output, name_tag = "monthly_mean_2d_", @@ -530,20 +523,20 @@ The currently implemented callbacks are: NB: Eventually, we will call all of radiation from the coupler, in addition to the albedo calculation. =# -checkpoint_cb = TimeManager.HourlyCallback( +checkpoint_cb = CallbackManager.HourlyCallback( dt = hourly_checkpoint_dt, func = checkpoint_sims, ref_date = [dates.date[1]], active = hourly_checkpoint, ) # 20 days -update_firstdayofmonth!_cb = TimeManager.MonthlyCallback( +update_firstdayofmonth!_cb = CallbackManager.MonthlyCallback( dt = FT(1), - func = TimeManager.update_firstdayofmonth!, + func = CallbackManager.update_firstdayofmonth!, ref_date = [dates.date1[1]], active = true, ) dt_water_albedo = parse(FT, filter(x -> !occursin(x, "hours"), dt_rad)) -albedo_cb = TimeManager.HourlyCallback( +albedo_cb = CallbackManager.HourlyCallback( dt = dt_water_albedo, func = FluxCalculator.water_albedo_from_atmosphere!, ref_date = [dates.date[1]], @@ -683,7 +676,7 @@ function solve_coupler!(cs) ## step in time for t in ((tspan[begin] + Δt_cpl):Δt_cpl:tspan[end]) - cs.dates.date[1] = TimeManager.current_date(cs, t) + cs.dates.date[1] = Interfacer.current_date(cs, t) ## print date on the first of month if cs.dates.date[1] >= cs.dates.date1[1] @@ -717,7 +710,7 @@ function solve_coupler!(cs) ## update water albedo from wind at dt_water_albedo ## (this will be extended to a radiation callback from the coupler) - TimeManager.trigger_callback!(cs, cs.callbacks.water_albedo) + CallbackManager.trigger_callback!(cs, cs.callbacks.water_albedo) ## update the surface fractions for surface models, @@ -752,10 +745,10 @@ function solve_coupler!(cs) FieldExchanger.import_atmos_fields!(cs.fields, cs.model_sims, cs.boundary_space, cs.turbulent_fluxes) # radiative and/or turbulent ## callback to update the fist day of month if needed - TimeManager.trigger_callback!(cs, cs.callbacks.update_firstdayofmonth!) + CallbackManager.trigger_callback!(cs, cs.callbacks.update_firstdayofmonth!) ## callback to checkpoint model state - TimeManager.trigger_callback!(cs, cs.callbacks.checkpoint) + CallbackManager.trigger_callback!(cs, cs.callbacks.checkpoint) end return nothing end diff --git a/experiments/ClimaEarth/run_cloudless_aquaplanet.jl b/experiments/ClimaEarth/run_cloudless_aquaplanet.jl index 5660ce63e0..d6aa087eb3 100644 --- a/experiments/ClimaEarth/run_cloudless_aquaplanet.jl +++ b/experiments/ClimaEarth/run_cloudless_aquaplanet.jl @@ -32,7 +32,7 @@ import ClimaCoupler: FluxCalculator, Interfacer, Regridder, - TimeManager, + CallbackManager, Utilities pkg_dir = pkgdir(ClimaCoupler) @@ -217,20 +217,20 @@ dates = (; date = [date], date0 = [date0], date1 = [Dates.firstdayofmonth(date0) ## Initialize Callbacks =# -checkpoint_cb = TimeManager.HourlyCallback( +checkpoint_cb = CallbackManager.HourlyCallback( dt = FT(480), func = checkpoint_sims, ref_date = [dates.date[1]], active = hourly_checkpoint, ) # 20 days -update_firstdayofmonth!_cb = TimeManager.MonthlyCallback( +update_firstdayofmonth!_cb = CallbackManager.MonthlyCallback( dt = FT(1), - func = TimeManager.update_firstdayofmonth!, + func = CallbackManager.update_firstdayofmonth!, ref_date = [dates.date1[1]], active = true, ) dt_water_albedo = parse(FT, filter(x -> !occursin(x, "hours"), dt_rad)) -albedo_cb = TimeManager.HourlyCallback( +albedo_cb = CallbackManager.HourlyCallback( dt = dt_water_albedo, func = FluxCalculator.water_albedo_from_atmosphere!, ref_date = [dates.date[1]], @@ -325,7 +325,7 @@ function solve_coupler!(cs) ## step in time for t in ((tspan[begin] + Δt_cpl):Δt_cpl:tspan[end]) - cs.dates.date[1] = TimeManager.current_date(cs, t) + cs.dates.date[1] = Interfacer.current_date(cs, t) ## print date on the first of month if cs.dates.date[1] >= cs.dates.date1[1] @@ -335,7 +335,7 @@ function solve_coupler!(cs) ClimaComms.barrier(comms_ctx) ## update water albedo from wind at dt_water_albedo (this will be extended to a radiation callback from the coupler) - TimeManager.trigger_callback!(cs, cs.callbacks.water_albedo) + CallbackManager.trigger_callback!(cs, cs.callbacks.water_albedo) ## run component models sequentially for one coupling timestep (Δt_cpl) FieldExchanger.update_model_sims!(cs.model_sims, cs.fields, cs.turbulent_fluxes) @@ -350,10 +350,10 @@ function solve_coupler!(cs) FieldExchanger.import_atmos_fields!(cs.fields, cs.model_sims, cs.boundary_space, cs.turbulent_fluxes) # radiative and/or turbulent ## callback to update the fist day of month if needed - TimeManager.trigger_callback!(cs, cs.callbacks.update_firstdayofmonth!) + CallbackManager.trigger_callback!(cs, cs.callbacks.update_firstdayofmonth!) ## callback to checkpoint model state - TimeManager.trigger_callback!(cs, cs.callbacks.checkpoint) + CallbackManager.trigger_callback!(cs, cs.callbacks.checkpoint) end diff --git a/experiments/ClimaEarth/run_cloudy_aquaplanet.jl b/experiments/ClimaEarth/run_cloudy_aquaplanet.jl index 961493c797..765ce8db5f 100644 --- a/experiments/ClimaEarth/run_cloudy_aquaplanet.jl +++ b/experiments/ClimaEarth/run_cloudy_aquaplanet.jl @@ -32,7 +32,7 @@ import ClimaCoupler: FluxCalculator, Interfacer, Regridder, - TimeManager, + CallbackManager, Utilities pkg_dir = pkgdir(ClimaCoupler) @@ -238,15 +238,15 @@ dates = (; date = [date], date0 = [date0], date1 = [Dates.firstdayofmonth(date0) ## Initialize Callbacks =# -checkpoint_cb = TimeManager.HourlyCallback( +checkpoint_cb = CallbackManager.HourlyCallback( dt = FT(480), func = checkpoint_sims, ref_date = [dates.date[1]], active = hourly_checkpoint, ) # 20 days -update_firstdayofmonth!_cb = TimeManager.MonthlyCallback( +update_firstdayofmonth!_cb = CallbackManager.MonthlyCallback( dt = FT(1), - func = TimeManager.update_firstdayofmonth!, + func = CallbackManager.update_firstdayofmonth!, ref_date = [dates.date1[1]], active = true, ) @@ -337,7 +337,7 @@ function solve_coupler!(cs) ## step in time for t in ((tspan[begin] + Δt_cpl):Δt_cpl:tspan[end]) - cs.dates.date[1] = TimeManager.current_date(cs, t) + cs.dates.date[1] = Interfacer.current_date(cs, t) ## print date on the first of month if cs.dates.date[1] >= cs.dates.date1[1] @@ -359,10 +359,10 @@ function solve_coupler!(cs) FieldExchanger.import_atmos_fields!(cs.fields, cs.model_sims, cs.boundary_space, cs.turbulent_fluxes) # radiative and/or turbulent ## callback to update the fist day of month if needed - TimeManager.trigger_callback!(cs, cs.callbacks.update_firstdayofmonth!) + CallbackManager.trigger_callback!(cs, cs.callbacks.update_firstdayofmonth!) ## callback to checkpoint model state - TimeManager.trigger_callback!(cs, cs.callbacks.checkpoint) + CallbackManager.trigger_callback!(cs, cs.callbacks.checkpoint) end diff --git a/experiments/ClimaEarth/run_cloudy_slabplanet.jl b/experiments/ClimaEarth/run_cloudy_slabplanet.jl index 6b87527788..68b44aee2c 100644 --- a/experiments/ClimaEarth/run_cloudy_slabplanet.jl +++ b/experiments/ClimaEarth/run_cloudy_slabplanet.jl @@ -32,7 +32,7 @@ import ClimaCoupler: FluxCalculator, Interfacer, Regridder, - TimeManager, + CallbackManager, Utilities pkg_dir = pkgdir(ClimaCoupler) @@ -284,20 +284,20 @@ model_sims = (atmos_sim = atmos_sim, ocean_sim = ocean_sim); ## Initialize Callbacks =# -checkpoint_cb = TimeManager.HourlyCallback( +checkpoint_cb = CallbackManager.HourlyCallback( dt = FT(480), func = checkpoint_sims, ref_date = [dates.date[1]], active = hourly_checkpoint, ) # 20 days -update_firstdayofmonth!_cb = TimeManager.MonthlyCallback( +update_firstdayofmonth!_cb = CallbackManager.MonthlyCallback( dt = FT(1), - func = TimeManager.update_firstdayofmonth!, + func = CallbackManager.update_firstdayofmonth!, ref_date = [dates.date1[1]], active = true, ) dt_water_albedo = parse(FT, filter(x -> !occursin(x, "hours"), dt_rad)) -albedo_cb = TimeManager.HourlyCallback( +albedo_cb = CallbackManager.HourlyCallback( dt = dt_water_albedo, func = FluxCalculator.water_albedo_from_atmosphere!, ref_date = [dates.date[1]], @@ -389,7 +389,7 @@ function solve_coupler!(cs) ## step in time for t in ((tspan[begin] + Δt_cpl):Δt_cpl:tspan[end]) - cs.dates.date[1] = TimeManager.current_date(cs, t) + cs.dates.date[1] = Interfacer.current_date(cs, t) ## print date on the first of month if cs.dates.date[1] >= cs.dates.date1[1] @@ -399,7 +399,7 @@ function solve_coupler!(cs) ClimaComms.barrier(comms_ctx) ## update water albedo from wind at dt_water_albedo (this will be extended to a radiation callback from the coupler) - TimeManager.trigger_callback!(cs, cs.callbacks.water_albedo) + CallbackManager.trigger_callback!(cs, cs.callbacks.water_albedo) ## run component models sequentially for one coupling timestep (Δt_cpl) FieldExchanger.update_model_sims!(cs.model_sims, cs.fields, cs.turbulent_fluxes) @@ -414,10 +414,10 @@ function solve_coupler!(cs) FieldExchanger.import_atmos_fields!(cs.fields, cs.model_sims, cs.boundary_space, cs.turbulent_fluxes) # radiative and/or turbulent ## callback to update the fist day of month if needed - TimeManager.trigger_callback!(cs, cs.callbacks.update_firstdayofmonth!) + CallbackManager.trigger_callback!(cs, cs.callbacks.update_firstdayofmonth!) ## callback to checkpoint model state - TimeManager.trigger_callback!(cs, cs.callbacks.checkpoint) + CallbackManager.trigger_callback!(cs, cs.callbacks.checkpoint) end diff --git a/experiments/ClimaEarth/run_dry_held_suarez.jl b/experiments/ClimaEarth/run_dry_held_suarez.jl index 373363863c..c591ffd441 100644 --- a/experiments/ClimaEarth/run_dry_held_suarez.jl +++ b/experiments/ClimaEarth/run_dry_held_suarez.jl @@ -26,7 +26,7 @@ import ClimaCore ## Coupler specific imports import ClimaCoupler -import ClimaCoupler: Checkpointer, FieldExchanger, Interfacer, TimeManager, Utilities +import ClimaCoupler: Checkpointer, FieldExchanger, Interfacer, CallbackManager, Utilities pkg_dir = pkgdir(ClimaCoupler) @@ -179,15 +179,15 @@ dates = (; date = [date], date0 = [date0], date1 = [Dates.firstdayofmonth(date0) #= ## Initialize Callbacks =# -checkpoint_cb = TimeManager.HourlyCallback( +checkpoint_cb = CallbackManager.HourlyCallback( dt = FT(480), func = checkpoint_sims, ref_date = [dates.date[1]], active = hourly_checkpoint, ) # 20 days TODO: not GPU friendly -update_firstdayofmonth!_cb = TimeManager.MonthlyCallback( +update_firstdayofmonth!_cb = CallbackManager.MonthlyCallback( dt = FT(1), - func = TimeManager.update_firstdayofmonth!, + func = CallbackManager.update_firstdayofmonth!, ref_date = [dates.date1[1]], active = true, ) @@ -237,7 +237,7 @@ function solve_coupler!(cs) ## step in time walltime = @elapsed for t in ((tspan[begin] + Δt_cpl):Δt_cpl:tspan[end]) - cs.dates.date[1] = TimeManager.current_date(cs, t) + cs.dates.date[1] = Interfacer.current_date(cs, t) ## print date on the first of month if cs.dates.date[1] >= cs.dates.date1[1] @@ -250,10 +250,10 @@ function solve_coupler!(cs) FieldExchanger.import_atmos_fields!(cs.fields, cs.model_sims, cs.boundary_space, cs.turbulent_fluxes) # radiative and/or turbulent ## callback to update the fist day of month if needed - TimeManager.trigger_callback!(cs, cs.callbacks.update_firstdayofmonth!) + CallbackManager.trigger_callback!(cs, cs.callbacks.update_firstdayofmonth!) ## callback to checkpoint model state - TimeManager.trigger_callback!(cs, cs.callbacks.checkpoint) + CallbackManager.trigger_callback!(cs, cs.callbacks.checkpoint) end ClimaComms.iamroot(comms_ctx) ? @show(walltime) : nothing diff --git a/experiments/ClimaEarth/run_moist_held_suarez.jl b/experiments/ClimaEarth/run_moist_held_suarez.jl index 4add88a711..feedd61e6e 100644 --- a/experiments/ClimaEarth/run_moist_held_suarez.jl +++ b/experiments/ClimaEarth/run_moist_held_suarez.jl @@ -35,7 +35,7 @@ import ClimaCoupler: FluxCalculator, Interfacer, Regridder, - TimeManager, + CallbackManager, Utilities pkg_dir = pkgdir(ClimaCoupler) @@ -224,15 +224,15 @@ dates = (; date = [date], date0 = [date0], date1 = [Dates.firstdayofmonth(date0) ## Initialize Callbacks =# -checkpoint_cb = TimeManager.HourlyCallback( +checkpoint_cb = CallbackManager.HourlyCallback( dt = FT(480), func = checkpoint_sims, ref_date = [dates.date[1]], active = hourly_checkpoint, ) -update_firstdayofmonth!_cb = TimeManager.MonthlyCallback( +update_firstdayofmonth!_cb = CallbackManager.MonthlyCallback( dt = FT(1), - func = TimeManager.update_firstdayofmonth!, + func = CallbackManager.update_firstdayofmonth!, ref_date = [dates.date1[1]], active = true, ) @@ -321,7 +321,7 @@ function solve_coupler!(cs) ## step in time for t in ((tspan[begin] + Δt_cpl):Δt_cpl:tspan[end]) - cs.dates.date[1] = TimeManager.current_date(cs, t) + cs.dates.date[1] = Interfacer.current_date(cs, t) ## print date on the first of month if cs.dates.date[1] >= cs.dates.date1[1] @@ -342,10 +342,10 @@ function solve_coupler!(cs) FieldExchanger.import_atmos_fields!(cs.fields, cs.model_sims, cs.boundary_space, cs.turbulent_fluxes) ## callback to update the fist day of month - TimeManager.trigger_callback!(cs, cs.callbacks.update_firstdayofmonth!) + CallbackManager.trigger_callback!(cs, cs.callbacks.update_firstdayofmonth!) ## callback to checkpoint model state - TimeManager.trigger_callback!(cs, cs.callbacks.checkpoint) + CallbackManager.trigger_callback!(cs, cs.callbacks.checkpoint) end diff --git a/src/ClimaCoupler.jl b/src/ClimaCoupler.jl index 1546a992da..4929652fd8 100644 --- a/src/ClimaCoupler.jl +++ b/src/ClimaCoupler.jl @@ -7,7 +7,7 @@ module ClimaCoupler include("Interfacer.jl") include("Utilities.jl") -include("TimeManager.jl") +include("CallbackManager.jl") include("Regridder.jl") include("ConservationChecker.jl") include("FluxCalculator.jl") diff --git a/src/Diagnostics.jl b/src/Diagnostics.jl index 29ec702d86..7f47f11e8f 100644 --- a/src/Diagnostics.jl +++ b/src/Diagnostics.jl @@ -6,7 +6,8 @@ This module contains functions for defining, gathering and outputting online mod module Diagnostics import Dates import ClimaCore as CC -import ..Interfacer, ..TimeManager +import ClimaUtilities: CallbackManager +import ..Interfacer export get_var, init_diagnostics, accumulate_diagnostics!, save_diagnostics, TimeMean @@ -55,7 +56,7 @@ end function init_diagnostics( names::Tuple, space::CC.Spaces.AbstractSpace; - save = TimeManager.EveryTimestep(), + save = CallbackManager.EveryTimestep(), operations = (;), output_dir = "", name_tag = "", @@ -66,7 +67,7 @@ Initializes diagnostics groups. function init_diagnostics( names::Tuple, space::CC.Spaces.AbstractSpace; - save = TimeManager.EveryTimestep(), + save = CallbackManager.EveryTimestep(), operations = (;), output_dir = "", name_tag = "", @@ -163,7 +164,9 @@ Saves all entries in `dg` in separate HDF5 files per variable in `output_dir`. """ function save_diagnostics(cs::Interfacer.CoupledSimulation) for dg in cs.diagnostics - if TimeManager.trigger_callback(cs, dg.save) + + # Check if the date is greater than the next date to save + if cs.dates.date[1] >= cs.dates.date1[1] pre_save(dg.operations.accumulate, cs, dg) save_diagnostics(cs, dg) post_save(dg.operations.accumulate, cs, dg) @@ -197,17 +200,17 @@ function save_diagnostics(cs::Interfacer.CoupledSimulation, dg::DiagnosticsGroup end """ - save_time_format(date::Dates.DateTime, ::TimeManager.Monthly) + save_time_format(date::Dates.DateTime, ::CallbackManager.Monthly) Converts the DateTime `date` to the conventional Unix format (seconds elapsed since 00:00:00 UTC on 1 January 1970). """ -function save_time_format(date::Dates.DateTime, ::TimeManager.Monthly) +function save_time_format(date::Dates.DateTime, ::CallbackManager.Monthly) date_m1 = date - Dates.Day(1) # obtain previous month datetime = Dates.DateTime(Dates.yearmonth(date_m1)[1], Dates.yearmonth(date_m1)[2]) Dates.datetime2unix(datetime) end -save_time_format(date::Dates.DateTime, ::TimeManager.EveryTimestep) = Dates.datetime2unix(date) +save_time_format(date::Dates.DateTime, ::CallbackManager.EveryTimestep) = Dates.datetime2unix(date) """ pre_save(::TimeMean, cs::Interfacer.CoupledSimulation, dg::DiagnosticsGroup) diff --git a/src/Interfacer.jl b/src/Interfacer.jl index c707a54b32..692c3db199 100644 --- a/src/Interfacer.jl +++ b/src/Interfacer.jl @@ -85,6 +85,17 @@ Return the floating point type backing `T`: `T` can either be an object or a typ """ float_type(::CoupledSimulation{FT}) where {FT} = FT +""" + current_date(cs::CoupledSimulation, t::Int) + +Return the model date at the current timestep. + +# Arguments +- `cs`: [CoupledSimulation] containing info about the simulation +- `t`: [Real] number of seconds since simulation began +""" +current_date(cs::CoupledSimulation, t::Real) = cs.dates.date0[1] + Dates.Second(t) + """ ComponentModelSimulation diff --git a/src/Regridder.jl b/src/Regridder.jl index cb6cd37360..d79323b2e5 100644 --- a/src/Regridder.jl +++ b/src/Regridder.jl @@ -13,7 +13,7 @@ import NCDatasets import ClimaComms import ClimaCore as CC import ClimaCoreTempestRemap as CCTR -import ..Interfacer, ..Utilities, ..TimeManager +import ..Interfacer, ..Utilities, ..CallbackManager export write_to_hdf5, read_from_hdf5, @@ -283,7 +283,7 @@ function get_time(ds) if "time" in keys(ds.dim) data_dates = Dates.DateTime.(Array(ds["time"])) elseif "date" in keys(ds.dim) - data_dates = TimeManager.strdate_to_datetime.(string.(Int.(Array(ds["date"])))) + data_dates = CallbackManager.strdate_to_datetime.(string.(Int.(Array(ds["date"])))) else @warn "No dates available in input data file" data_dates = [Dates.DateTime(0)] diff --git a/src/TimeManager.jl b/src/TimeManager.jl deleted file mode 100644 index 92b41f19f2..0000000000 --- a/src/TimeManager.jl +++ /dev/null @@ -1,181 +0,0 @@ -""" - TimeManager - -This module facilitates calendar functions and temporal interpolations -of data. -""" -module TimeManager - -import Dates -import ..Interfacer - -export current_date, - strdate_to_datetime, - datetime_to_strdate, - trigger_callback, - AbstractFrequency, - Monthly, - EveryTimestep, - trigger_callback!, - HourlyCallback, - MonthlyCallback, - update_firstdayofmonth! - - -""" - current_date(cs::Interfacer.CoupledSimulation, t::Int) - -Return the model date at the current timestep. - -# Arguments -- `cs`: [CoupledSimulation] containing info about the simulation -- `t`: [Real] number of seconds since simulation began -""" -current_date(cs::Interfacer.CoupledSimulation, t::Real) = cs.dates.date0[1] + Dates.Second(t) - -""" - strdate_to_datetime(strdate::String) - -Convert from String ("YYYYMMDD") to Date format, -required by the official AMIP input files. - -# Arguments -- `strdate`: [String] to be converted to Date type -""" -strdate_to_datetime(strdate::String) = - Dates.DateTime(parse(Int, strdate[1:4]), parse(Int, strdate[5:6]), parse(Int, strdate[7:8])) - -""" - datetime_to_strdate(datetime::DateTime) - -Convert from Date to String ("YYYYMMDD") format. - -# Arguments -- `datetime`: [Dates.DateTime] object to be converted to string -""" -datetime_to_strdate(datetime::Dates.DateTime) = - string(lpad(Dates.year(datetime), 4, "0")) * - string(string(lpad(Dates.month(datetime), 2, "0"))) * - string(lpad(Dates.day(datetime), 2, "0")) - -""" - AbstractFrequency - -This is an abstract type for the frequency of the callback function. -""" -abstract type AbstractFrequency end - -""" - Monthly -A concrete type for the monthly frequency of the callback function. -""" -struct Monthly <: AbstractFrequency end - -""" - EveryTimestep -A concrete type for the every-timestep frequency of the callback function. -""" -struct EveryTimestep <: AbstractFrequency end - -""" - trigger_callback(cs, ::Monthly) - -Returns `true` if the current date is equal to or exceeds the saved first of the month at time of 00:00:00. - -# Arguments -- `cs`: [CoupledSimulation] containing info about the simulation -""" -trigger_callback(cs::Interfacer.CoupledSimulation, ::Monthly) = cs.dates.date[1] >= cs.dates.date1[1] ? true : false - -""" - CouplerCallback - -This is an abstract type for ClimaCoupler's callback functions. -""" -abstract type CouplerCallback end - -""" - do_nothing(::Interfacer.CoupledSimulation, _) - -This is a helper callback function that does nothing. -""" -do_nothing(::Interfacer.CoupledSimulation, _) = nothing - -""" - HourlyCallback{FT} - -This is a callback type that triggers at intervals of 1h or multiple hours. - -# Fields - -- `dt` -- `func` -- `ref_date` -- `active` -- `data -""" -@kwdef struct HourlyCallback{FT} <: CouplerCallback - dt::FT = FT(1) # hours - func::Function = do_nothing - ref_date::Array = [Dates.DateTime(0)] - active::Bool = false - data::Array = [] -end - -""" - MonthlyCallback{FT} - -This is a callback type that triggers at intervals of 1 month or multiple months. - -# Fields - -- `dt` -- `func` -- `ref_date` -- `active` -- `data` -""" -@kwdef struct MonthlyCallback{FT} <: CouplerCallback - dt::FT = FT(1) # months - func::Function = do_nothing - ref_date::Array = [Dates.DateTime(0)] - active::Bool = false - data::Array = [] -end - -""" - dt_cb(cb::HourlyCallback) - dt_cb(cb::MonthlyCallback) - -This function returns the time interval for the callback function. -""" -dt_cb(cb::HourlyCallback) = Dates.Hour(cb.dt) -dt_cb(cb::MonthlyCallback) = Dates.Month(cb.dt) - -""" - trigger_callback!(cs::Interfacer.CoupledSimulation, cb::CouplerCallback) - -This function triggers a callback function if the current date is equal to or exceeds the saved callback reference date. -As well as executing the functions `func`, it automatically updates the reference date, `ref_date`, for the next callback interval. -""" -function trigger_callback!(cs::Interfacer.CoupledSimulation, cb::CouplerCallback) - if cb.active - current_date = cs.dates.date[1] - if current_date >= cb.ref_date[1] - cb.func(cs, cb) - cb.ref_date[1] = cb.ref_date[1] + dt_cb(cb) - end - end -end - -""" - update_firstdayofmonth!(cs::Interfacer.CoupledSimulation, _) - -This function updates the first of the month reference date. -""" -function update_firstdayofmonth!(cs, _) - cs.dates.date1[1] = cs.dates.date1[1] + Dates.Month(1) - @info("update_firstdayofmonth! at $(cs.dates.date)") -end - -end diff --git a/test/diagnostics_tests.jl b/test/diagnostics_tests.jl index bc794fe96d..7132cf9c2a 100644 --- a/test/diagnostics_tests.jl +++ b/test/diagnostics_tests.jl @@ -6,7 +6,7 @@ import ClimaComms @static pkgversion(ClimaComms) >= v"0.6" && ClimaComms.@import_required_backends import Dates import ClimaCore as CC -import ClimaCoupler: ConservationChecker, Diagnostics, Interfacer, TimeManager +import ClimaCoupler: ConservationChecker, Diagnostics, Interfacer, CallbackManager include("TestHelper.jl") import .TestHelper @@ -18,7 +18,7 @@ for FT in (Float32, Float64) names = (:x, :y) space = TestHelper.create_space(FT) dg = Diagnostics.init_diagnostics(names, space) - @test typeof(dg) == Diagnostics.DiagnosticsGroup{TimeManager.EveryTimestep, NamedTuple{(), Tuple{}}} + @test typeof(dg) == Diagnostics.DiagnosticsGroup{CallbackManager.EveryTimestep, NamedTuple{(), Tuple{}}} end @testset "accumulate_diagnostics!, collect_diags, iterate_operations, operation{accumulation{TimeMean, Nothing}}, get_var for FT=$FT" begin @@ -30,7 +30,7 @@ for FT in (Float32, Float64) dg_2d = Diagnostics.init_diagnostics( names, space, - save = TimeManager.EveryTimestep(), + save = CallbackManager.EveryTimestep(), operations = (; accumulate = case), ) dg_2d.field_vector .= FT(2) @@ -71,7 +71,7 @@ for FT in (Float32, Float64) dg_2d = Diagnostics.init_diagnostics( names, space, - save = TimeManager.EveryTimestep(), + save = CallbackManager.EveryTimestep(), operations = (; accumulate = Diagnostics.TimeMean([Int(0)])), output_dir = test_dir, ) # or use accumulate = nothing for snapshop save @@ -104,7 +104,7 @@ for FT in (Float32, Float64) @testset "save_time_format for FT=$FT" begin date = Dates.DateTime(1970, 2, 1, 0, 1) - unix = Diagnostics.save_time_format(date, TimeManager.Monthly()) + unix = Diagnostics.save_time_format(date, CallbackManager.Monthly()) @test unix == 0 end @@ -118,7 +118,7 @@ for FT in (Float32, Float64) dg_2d = Diagnostics.init_diagnostics( names, space, - save = TimeManager.EveryTimestep(), + save = CallbackManager.EveryTimestep(), operations = (; accumulate = case), ) dg_2d.field_vector .= FT(3) diff --git a/test/regridder_tests.jl b/test/regridder_tests.jl index 020863e0cd..2d0db50fe5 100644 --- a/test/regridder_tests.jl +++ b/test/regridder_tests.jl @@ -8,7 +8,7 @@ import ClimaComms @static pkgversion(ClimaComms) >= v"0.6" && ClimaComms.@import_required_backends import ClimaCore as CC import ClimaCoupler -import ClimaCoupler: Interfacer, Regridder, TimeManager +import ClimaCoupler: Interfacer, Regridder, CallbackManager include("TestHelper.jl") import .TestHelper @@ -289,7 +289,7 @@ for FT in (Float32, Float64) ) # read in data on CGLL grid from the last saved date - date1 = TimeManager.strdate_to_datetime.(string(Int(time[end]))) + date1 = CallbackManager.strdate_to_datetime.(string(Int(time[end]))) cgll_path = joinpath(REGRID_DIR, "$(hd_outfile_root)_$date1.hdf5") hdfreader = CC.InputOutput.HDF5Reader(cgll_path, comms_ctx) T_cgll = CC.InputOutput.read_field(hdfreader, varname) diff --git a/test/runtests.jl b/test/runtests.jl index 7aab184757..5d001278d4 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -29,7 +29,7 @@ end @safetestset "Utilities tests" begin include("utilities_tests.jl") end -@safetestset "TimeManager tests" begin +@safetestset "CallbackManager tests" begin include("time_manager_tests.jl") end @safetestset "FieldExchanger tests" begin diff --git a/test/time_manager_tests.jl b/test/time_manager_tests.jl deleted file mode 100644 index 43bebf5c2e..0000000000 --- a/test/time_manager_tests.jl +++ /dev/null @@ -1,188 +0,0 @@ -#= - Unit tests for ClimaCoupler TimeManager module -=# -import Test: @testset, @test -import Dates -import ClimaComms -@static pkgversion(ClimaComms) >= v"0.6" && ClimaComms.@import_required_backends -import ClimaCoupler: Interfacer, TimeManager - -for FT in (Float32, Float64) - @testset "test current_date" begin - date0 = date = Dates.DateTime("19790321", Dates.dateformat"yyyymmdd") - dates = (; date = [date], date0 = [date0], date1 = [Dates.firstdayofmonth(date0)]) - tspan = (Int(1), Int(90 * 86400)) # Jan-Mar - Δt_cpl = 1 * 24 * 3600 - - # Fill in only the necessary parts of the simulation - cs = Interfacer.CoupledSimulation{FT}( - ClimaComms.SingletonCommsContext(), # comms_ctx - dates, # dates - nothing, # boundary_space - nothing, # fields - nothing, # parsed_args - nothing, # conservation_checks - tspan, # tspan - Int(0), # t - Int(Δt_cpl), # Δt_cpl - (;), # surface_masks - (;), # model_sims - (;), # mode - (), # diagnostics - (;), # callbacks - (;), # dirs - nothing, # turbulent_fluxes - nothing, # thermo_params - ) - - for t in ((tspan[1] + Δt_cpl):Δt_cpl:tspan[end]) - @test TimeManager.current_date(cs, t) == date0 + Dates.Second(t) - end - end -end - -@testset "test strdate_to_datetime" begin - @test TimeManager.strdate_to_datetime("19000101") == Dates.DateTime(1900, 1, 1) - @test TimeManager.strdate_to_datetime("00000101") == Dates.DateTime(0, 1, 1) -end - -@testset "test datetime_to_strdate" begin - @test TimeManager.datetime_to_strdate(Dates.DateTime(1900, 1, 1)) == "19000101" - @test TimeManager.datetime_to_strdate(Dates.DateTime(0, 1, 1)) == "00000101" -end - -@testset "trigger_callback" begin - FT = Float64 - date0 = date = Dates.DateTime("19790321", Dates.dateformat"yyyymmdd") - dates = (; date = [date], date0 = [date0], date1 = [Dates.firstdayofmonth(date0)]) - - cs = Interfacer.CoupledSimulation{FT}( - nothing, # comms_ctx - dates, # dates - nothing, # boundary_space - nothing, # fields - nothing, # parsed_args - nothing, # conservation_checks - (Int(0), Int(1000)), # tspan - Int(200), # t - Int(200), # Δt_cpl - (;), # surface_masks - (;), # model_sims - (;), # mode - (), # diagnostics - (;), # callbacks - (;), # dirs - nothing, # turbulent_fluxes - nothing, # thermo_params - ) - @test TimeManager.trigger_callback(cs, TimeManager.Monthly()) == true -end - -@testset "trigger_callback!" begin - FT = Float64 - date0 = date = Dates.DateTime("19790321", Dates.dateformat"yyyymmdd") - dates = (; date = [date], date0 = [date0], date1 = [Dates.firstdayofmonth(date0)]) - - function counter_func(cs, cb) - cb.data .+= 1 - end - twhohourly_inactive = TimeManager.HourlyCallback{FT}(dt = 2, ref_date = [date0]) - twhohourly_nothing = TimeManager.HourlyCallback{FT}(dt = 2, ref_date = [date0], active = true) - twhohourly_counter = - TimeManager.HourlyCallback{FT}(dt = 2, ref_date = [date0], func = counter_func, data = [0], active = true) - monthly_counter = - TimeManager.MonthlyCallback{FT}(func = counter_func, ref_date = [date0], data = [0], active = true) - - cs = Interfacer.CoupledSimulation{FT}( - nothing, # comms_ctx - dates, # dates - nothing, # boundary_space - nothing, # fields - nothing, # parsed_args - nothing, # conservation_checks - (Int(0), Int(1000)), # tspan - Int(200), # t - Int(200), # Δt_cpl - (;), # surface_masks - (;), # model_sims - (;), # mode - (), # diagnostics - (; - twhohourly_inactive = twhohourly_inactive, - twhohourly_nothing = twhohourly_nothing, - twhohourly_counter = twhohourly_counter, - monthly_counter = monthly_counter, - ), # callbacks - (;), # dirs - nothing, # turbulent_fluxes - nothing, # thermo_params - ) - - TimeManager.trigger_callback!(cs, cs.callbacks.twhohourly_inactive) - TimeManager.trigger_callback!(cs, cs.callbacks.twhohourly_nothing) - TimeManager.trigger_callback!(cs, cs.callbacks.twhohourly_counter) - TimeManager.trigger_callback!(cs, cs.callbacks.monthly_counter) - @test cs.callbacks.twhohourly_inactive.ref_date[1] == date0 - @test cs.callbacks.twhohourly_nothing.ref_date[1] == cs.dates.date0[1] + Dates.Hour(2) - @test cs.callbacks.twhohourly_counter.ref_date[1] == cs.dates.date0[1] + Dates.Hour(2) - @test cs.callbacks.monthly_counter.ref_date[1] == cs.dates.date0[1] + Dates.Month(1) - @test cs.callbacks.twhohourly_counter.data[1] == 1 - @test cs.callbacks.monthly_counter.data[1] == 1 - - cs.dates.date .+= Dates.Hour(2) - TimeManager.trigger_callback!(cs, cs.callbacks.twhohourly_inactive) - TimeManager.trigger_callback!(cs, cs.callbacks.twhohourly_nothing) - TimeManager.trigger_callback!(cs, cs.callbacks.twhohourly_counter) - TimeManager.trigger_callback!(cs, cs.callbacks.monthly_counter) - @test cs.callbacks.twhohourly_inactive.ref_date[1] == date0 - @test cs.callbacks.twhohourly_nothing.ref_date[1] == cs.dates.date0[1] + Dates.Hour(4) - @test cs.callbacks.twhohourly_counter.ref_date[1] == cs.dates.date0[1] + Dates.Hour(4) - @test cs.callbacks.monthly_counter.ref_date[1] == cs.dates.date0[1] + Dates.Month(1) - @test cs.callbacks.twhohourly_counter.data[1] == 2 - @test cs.callbacks.monthly_counter.data[1] == 1 - - cs.dates.date .+= Dates.Month(1) - TimeManager.trigger_callback!(cs, cs.callbacks.twhohourly_inactive) - TimeManager.trigger_callback!(cs, cs.callbacks.twhohourly_nothing) - TimeManager.trigger_callback!(cs, cs.callbacks.twhohourly_counter) - TimeManager.trigger_callback!(cs, cs.callbacks.monthly_counter) - @test cs.callbacks.twhohourly_inactive.ref_date[1] == date0 - @test cs.callbacks.twhohourly_inactive.ref_date[1] == date0 - @test cs.callbacks.twhohourly_nothing.ref_date[1] == cs.dates.date0[1] + Dates.Hour(6) - @test cs.callbacks.twhohourly_counter.ref_date[1] == cs.dates.date0[1] + Dates.Hour(6) - @test cs.callbacks.monthly_counter.ref_date[1] == cs.dates.date0[1] + Dates.Month(2) - @test cs.callbacks.twhohourly_counter.data[1] == 3 - @test cs.callbacks.monthly_counter.data[1] == 2 - -end - -# TimeManager -@testset "update_firstdayofmonth!" begin - FT = Float64 - date0 = date = Dates.DateTime("19790321", Dates.dateformat"yyyymmdd") - dates = (; date = [date], date0 = [date0], date1 = [Dates.firstdayofmonth(date0)]) - - cs = Interfacer.CoupledSimulation{FT}( - nothing, # comms_ctx - dates, # dates - nothing, # boundary_space - nothing, # fields - nothing, # parsed_args - nothing, # conservation_checks - (Int(0), Int(1000)), # tspan - Int(200), # t - Int(200), # Δt_cpl - (;), # surface_masks - (;), # model_sims - (;), # mode - (), # diagnostics - (;), # callbacks - (;), # dirs - nothing, # turbulent_fluxes - nothing, # thermo_params - ) - - TimeManager.update_firstdayofmonth!(cs, nothing) - @test cs.dates.date1[1] == Dates.firstdayofmonth(date0) + Dates.Month(1) - -end