From 97be0c517980f4d32a033b651311afd47b8e79e9 Mon Sep 17 00:00:00 2001 From: Julia Sloan Date: Tue, 10 Sep 2024 11:49:14 -0700 Subject: [PATCH 1/3] rename TimeManager to CallbackManager --- README.md | 5 ++-- docs/make.jl | 2 +- docs/src/callbackmanager.md | 16 ++++++++++++ docs/src/timemanager.md | 16 ------------ src/{TimeManager.jl => CallbackManager.jl} | 6 ++--- src/ClimaUtilities.jl | 2 +- test/{timemanager.jl => callbackmanager.jl} | 28 ++++++++++----------- test/runtests.jl | 4 +-- 8 files changed, 39 insertions(+), 40 deletions(-) create mode 100644 docs/src/callbackmanager.md delete mode 100644 docs/src/timemanager.md rename src/{TimeManager.jl => CallbackManager.jl} (97%) rename test/{timemanager.jl => callbackmanager.jl} (75%) diff --git a/README.md b/README.md index b8786a2d..dc224d2d 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ ClimaUtilities.jl to process input data and remap it onto the simulation grid. - [`OutputPathGenerator`](https://clima.github.io/ClimaUtilities.jl/dev/outputpathgenerator/) to prepare the output directory structure of a simulation. -- [`TimeManager`](https://clima.github.io/ClimaUtilities.jl/dev/timemanager/) to +- [`CallbackManager`](https://clima.github.io/ClimaUtilities.jl/dev/callbackmanager/) to handle dates. ## ClimaUtilities.jl Developer Guidelines @@ -178,6 +178,5 @@ two commits when the second just fixes the first). The `Space` and `TimeVaryingInputs` modules were initially developed in the context of [`ClimaLand`](https://github.com/CliMA/ClimaLand.jl), the -`TempestRegridder` and `TimeManager` ones were initially developed in +`TempestRegridder` and `CallbackManager` ones were initially developed in [`ClimaCoupler`](https://github.com/CliMA/ClimaCoupler.jl). - diff --git a/docs/make.jl b/docs/make.jl index d0770fde..2de001e4 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -23,7 +23,7 @@ pages = [ "DataHandling" => "datahandling.md", "Regridders" => "regridders.md", "OutputPathGenerator" => "outputpathgenerator.md", - "TimeManager" => "timemanager.md", + "CallbackManager" => "callbackmanager.md", "Frequently Asked Questions" => "faqs.md", ] diff --git a/docs/src/callbackmanager.md b/docs/src/callbackmanager.md new file mode 100644 index 00000000..3ad4307e --- /dev/null +++ b/docs/src/callbackmanager.md @@ -0,0 +1,16 @@ +# CallbackManager + +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. + +## CallbackManager API + +```@docs +ClimaUtilities.CallbackManager.to_datetime +ClimaUtilities.CallbackManager.strdate_to_datetime +ClimaUtilities.CallbackManager.datetime_to_strdate +ClimaUtilities.CallbackManager.trigger_callback +ClimaUtilities.CallbackManager.Monthly +ClimaUtilities.CallbackManager.EveryTimestep +``` diff --git a/docs/src/timemanager.md b/docs/src/timemanager.md deleted file mode 100644 index f69eac74..00000000 --- a/docs/src/timemanager.md +++ /dev/null @@ -1,16 +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 -ClimaUtilities.TimeManager.to_datetime -ClimaUtilities.TimeManager.strdate_to_datetime -ClimaUtilities.TimeManager.datetime_to_strdate -ClimaUtilities.TimeManager.trigger_callback -ClimaUtilities.TimeManager.Monthly -ClimaUtilities.TimeManager.EveryTimestep -``` diff --git a/src/TimeManager.jl b/src/CallbackManager.jl similarity index 97% rename from src/TimeManager.jl rename to src/CallbackManager.jl index 7f129c01..bd38f24f 100644 --- a/src/TimeManager.jl +++ b/src/CallbackManager.jl @@ -1,10 +1,10 @@ """ - TimeManager + CallbackManager This module facilitates calendar functions and temporal interpolations of data. """ -module TimeManager +module CallbackManager import Dates @@ -105,4 +105,4 @@ function trigger_callback( end end -end # module TimeManager +end # module CallbackManager diff --git a/src/ClimaUtilities.jl b/src/ClimaUtilities.jl index 5063558a..6278db57 100644 --- a/src/ClimaUtilities.jl +++ b/src/ClimaUtilities.jl @@ -2,7 +2,7 @@ module ClimaUtilities include("Utils.jl") include("MPIUtils.jl") -include("TimeManager.jl") +include("CallbackManager.jl") include("DataStructures.jl") include("FileReaders.jl") include("Regridders.jl") diff --git a/test/timemanager.jl b/test/callbackmanager.jl similarity index 75% rename from test/timemanager.jl rename to test/callbackmanager.jl index da5fba1a..3eb9ca85 100644 --- a/test/timemanager.jl +++ b/test/callbackmanager.jl @@ -1,4 +1,4 @@ -import ClimaUtilities: TimeManager +import ClimaUtilities: CallbackManager import Dates import CFTime using Test @@ -9,9 +9,9 @@ for FT in (Float32, Float64) year = 2001 dt_noleap = CFTime.DateTimeNoLeap(year) dt = Dates.DateTime(year) - @test TimeManager.to_datetime(dt_noleap) == dt + @test CallbackManager.to_datetime(dt_noleap) == dt # In non-leap year, DateTime and DateTimeNoLeap are the same - @test TimeManager.to_datetime(dt_noleap + Dates.Day(365)) == + @test CallbackManager.to_datetime(dt_noleap + Dates.Day(365)) == dt + Dates.Day(365) # Test leap year behavior @@ -19,22 +19,22 @@ for FT in (Float32, Float64) dt_noleap_ly = CFTime.DateTimeNoLeap(leap_year) dt_ly = Dates.DateTime(leap_year) # DateTime includes leap days, DateTimeNoLeap does not, so DateTime has one extra day in leap year - @test TimeManager.to_datetime(dt_noleap_ly + Dates.Day(365)) == + @test CallbackManager.to_datetime(dt_noleap_ly + Dates.Day(365)) == dt_ly + Dates.Day(366) end @testset "test strdate_to_datetime for FT=$FT" begin - @test TimeManager.strdate_to_datetime("19000101") == + @test CallbackManager.strdate_to_datetime("19000101") == Dates.DateTime(1900, 1, 1) - @test TimeManager.strdate_to_datetime("00000101") == + @test CallbackManager.strdate_to_datetime("00000101") == Dates.DateTime(0, 1, 1) end @testset "test datetime_to_strdate for FT=$FT" begin - @test TimeManager.datetime_to_strdate(Dates.DateTime(1900, 1, 1)) == + @test CallbackManager.datetime_to_strdate(Dates.DateTime(1900, 1, 1)) == "19000101" - @test TimeManager.datetime_to_strdate(Dates.DateTime(0, 1, 1)) == + @test CallbackManager.datetime_to_strdate(Dates.DateTime(0, 1, 1)) == "00000101" end @@ -47,10 +47,10 @@ for FT in (Float32, Float64) arg_copy = copy(arg) date_current = date_nextcall = date_nextcall_copy = Dates.DateTime(1979, 3, 21) - date_nextcall = TimeManager.trigger_callback( + date_nextcall = CallbackManager.trigger_callback( date_nextcall, date_current, - TimeManager.Monthly(), + CallbackManager.Monthly(), func!, (arg,), ) @@ -61,10 +61,10 @@ for FT in (Float32, Float64) # Case 2: date_current > date_nextcall date_nextcall = date_nextcall_copy = Dates.DateTime(1979, 3, 21) date_current = date_nextcall + Dates.Day(1) - date_nextcall = TimeManager.trigger_callback( + date_nextcall = CallbackManager.trigger_callback( date_nextcall, date_current, - TimeManager.Monthly(), + CallbackManager.Monthly(), func!, (arg,), ) @@ -75,10 +75,10 @@ for FT in (Float32, Float64) # Case 3: date_current < date_nextcall date_nextcall = date_nextcall_copy = Dates.DateTime(1979, 3, 21) date_current = date_nextcall - Dates.Day(1) - date_nextcall = TimeManager.trigger_callback( + date_nextcall = CallbackManager.trigger_callback( date_nextcall, date_current, - TimeManager.Monthly(), + CallbackManager.Monthly(), func!, (arg,), ) diff --git a/test/runtests.jl b/test/runtests.jl index beeb92bc..1b983489 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -28,8 +28,8 @@ end include("output_path_generator.jl") end -@safetestset "TimeManager tests" begin - include("timemanager.jl") +@safetestset "CallbackManager tests" begin + include("callbackmanager.jl") end @safetestset "DataStructures tests" begin From 8b8ffcc030908cd434d832b559cef83d41d1f570 Mon Sep 17 00:00:00 2001 From: Julia Sloan Date: Tue, 10 Sep 2024 16:12:57 -0700 Subject: [PATCH 2/3] add functions for ClimaCoupler --- docs/src/callbackmanager.md | 6 +- src/CallbackManager.jl | 141 +++++++++++++++++++++++++----------- 2 files changed, 104 insertions(+), 43 deletions(-) diff --git a/docs/src/callbackmanager.md b/docs/src/callbackmanager.md index 3ad4307e..ff748caa 100644 --- a/docs/src/callbackmanager.md +++ b/docs/src/callbackmanager.md @@ -7,10 +7,12 @@ functions from Julia's [Dates](https://docs.julialang.org/en/v1/stdlib/Dates/) m ## CallbackManager API ```@docs +ClimaUtilities.CallbackManager.HourlyCallback +ClimaUtilities.CallbackManager.MonthlyCallback +ClimaUtilities.CallbackManager.Monthly +ClimaUtilities.CallbackManager.EveryTimestep ClimaUtilities.CallbackManager.to_datetime ClimaUtilities.CallbackManager.strdate_to_datetime ClimaUtilities.CallbackManager.datetime_to_strdate ClimaUtilities.CallbackManager.trigger_callback -ClimaUtilities.CallbackManager.Monthly -ClimaUtilities.CallbackManager.EveryTimestep ``` diff --git a/src/CallbackManager.jl b/src/CallbackManager.jl index bd38f24f..955336b6 100644 --- a/src/CallbackManager.jl +++ b/src/CallbackManager.jl @@ -8,64 +8,71 @@ module CallbackManager import Dates -export to_datetime, - strdate_to_datetime, - datetime_to_strdate, - trigger_callback, +export HourlyCallback, + MonthlyCallback, Monthly, - EveryTimestep + EveryTimestep, + trigger_callback, + to_datetime, + strdate_to_datetime, + datetime_to_strdate """ - to_datetime(date) + AbstractCallback +""" +abstract type AbstractCallback end -Convert a `DateTime`-like object (e.g. `DateTimeNoLeap`) to a `DateTime`. -We need this since some data files we use contain -`DateTimeNoLeap` objects for dates, which can't be used for math with `DateTime`s. -The `DateTimeNoLeap` type uses the Gregorian calendar without leap years, while -the `DateTime` type uses Gregorian calendar with leap years. +""" + HourlyCallback{FT} -For consistency, all input data files should have dates converted to `DateTime` -before being used in a simulation. +This is a callback type that triggers at intervals of 1h or multiple hours. +""" +@kwdef struct HourlyCallback{FT} <: AbstractCallback + """ Time interval at which the callback is triggered. """ + dt::FT = FT(1) # hours + """ Function to be called at each trigger. """ + func::Function = do_nothing + """ Reference date for the callback. """ + ref_date::Array = [Dates.DateTime(0)] + """ Whether the callback is active. """ + active::Bool = false + """ Data to be passed to the callback function. """ + data::Array = [] +end -This function is similar to `reinterpret` in CFTime.jl. +""" + MonthlyCallback{FT} -# Arguments -- `date`: `DateTime`-like object to be converted to `DateTime` +This is a callback type that triggers at intervals of 1 month or multiple months. """ -function to_datetime(date) - return Dates.DateTime( - Dates.year(date), - Dates.month(date), - Dates.day(date), - Dates.hour(date), - Dates.minute(date), - Dates.second(date), - Dates.millisecond(date), - ) +@kwdef struct MonthlyCallback{FT} <: AbstractCallback + """ Time interval at which the callback is triggered. """ + dt::FT = FT(1) # months + """ Function to be called at each trigger. """ + func::Function = do_nothing + """ Reference date for the callback. """ + ref_date::Array = [Dates.DateTime(0)] + """ Whether the callback is active. """ + active::Bool = false + """ Data to be passed to the callback function. """ + data::Array = [] end """ - strdate_to_datetime(strdate::String) + dt_cb(cb::HourlyCallback) + dt_cb(cb::MonthlyCallback) -Convert from String ("YYYYMMDD") to Date format, -required by the official AMIP input files. +This function returns the time interval for the callback. """ -strdate_to_datetime(strdate::String) = Dates.DateTime( - parse(Int, strdate[1:4]), - parse(Int, strdate[5:6]), - parse(Int, strdate[7:8]), -) +dt_cb(cb::HourlyCallback) = Dates.Hour(cb.dt) +dt_cb(cb::MonthlyCallback) = Dates.Month(cb.dt) -""" - datetime_to_strdate(datetime::Dates.DateTime) -Convert from DateTime to String ("YYYYMMDD") format. """ -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 a callback function. +""" abstract type AbstractFrequency end struct Monthly <: AbstractFrequency end struct EveryTimestep <: AbstractFrequency end @@ -105,4 +112,56 @@ function trigger_callback( end end +""" + to_datetime(date) + +Convert a `DateTime`-like object (e.g. `DateTimeNoLeap`) to a `DateTime`. +We need this since some data files we use contain +`DateTimeNoLeap` objects for dates, which can't be used for math with `DateTime`s. +The `DateTimeNoLeap` type uses the Gregorian calendar without leap years, while +the `DateTime` type uses Gregorian calendar with leap years. + +For consistency, all input data files should have dates converted to `DateTime` +before being used in a simulation. + +This function is similar to `reinterpret` in CFTime.jl. + +# Arguments +- `date`: `DateTime`-like object to be converted to `DateTime` +""" +function to_datetime(date) + return Dates.DateTime( + Dates.year(date), + Dates.month(date), + Dates.day(date), + Dates.hour(date), + Dates.minute(date), + Dates.second(date), + Dates.millisecond(date), + ) +end + +""" + strdate_to_datetime(strdate::String) + +Convert from String ("YYYYMMDD") to Date format, +required by the official AMIP input files. +""" +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::Dates.DateTime) + +Convert from DateTime to String ("YYYYMMDD") format. +""" +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")) + + end # module CallbackManager From 450641dddafa8ff3c9c5bd30682a6e2476256a34 Mon Sep 17 00:00:00 2001 From: Julia Sloan Date: Tue, 10 Sep 2024 16:35:03 -0700 Subject: [PATCH 3/3] generalize trigger_callback --- docs/src/callbackmanager.md | 2 +- src/CallbackManager.jl | 50 +++++++++++++++---------------------- test/callbackmanager.jl | 8 +++--- 3 files changed, 25 insertions(+), 35 deletions(-) diff --git a/docs/src/callbackmanager.md b/docs/src/callbackmanager.md index ff748caa..3859c36d 100644 --- a/docs/src/callbackmanager.md +++ b/docs/src/callbackmanager.md @@ -14,5 +14,5 @@ ClimaUtilities.CallbackManager.EveryTimestep ClimaUtilities.CallbackManager.to_datetime ClimaUtilities.CallbackManager.strdate_to_datetime ClimaUtilities.CallbackManager.datetime_to_strdate -ClimaUtilities.CallbackManager.trigger_callback +ClimaUtilities.CallbackManager.trigger_callback! ``` diff --git a/src/CallbackManager.jl b/src/CallbackManager.jl index 955336b6..3afde8e1 100644 --- a/src/CallbackManager.jl +++ b/src/CallbackManager.jl @@ -12,7 +12,7 @@ export HourlyCallback, MonthlyCallback, Monthly, EveryTimestep, - trigger_callback, + trigger_callback!, to_datetime, strdate_to_datetime, datetime_to_strdate @@ -32,7 +32,7 @@ This is a callback type that triggers at intervals of 1h or multiple hours. dt::FT = FT(1) # hours """ Function to be called at each trigger. """ func::Function = do_nothing - """ Reference date for the callback. """ + """ Reference date when the callback should be called. """ ref_date::Array = [Dates.DateTime(0)] """ Whether the callback is active. """ active::Bool = false @@ -78,37 +78,27 @@ struct Monthly <: AbstractFrequency end struct EveryTimestep <: AbstractFrequency end """ - trigger_callback(date_nextcall::Dates.DateTime, - date_current::Dates.DateTime, - ::Monthly, - func::Function,) + trigger_callback!(callback, date_current) -If the current date is equal to or later than the "next call" date at time -00:00:00, call the callback function and increment the next call date by one -month. Otherwise, do nothing and leave the next call date unchanged. +If the callback is active and the current date is equal to or later than the +"next call" reference date/time, call the callback function and increment the +next call date based on the callback frequency. Otherwise, do nothing and leave +the next call date unchanged. -The tuple of arguments `func_args` must match the types, number, and order -of arguments expected by `func`. +Note that the collection of data in `callback.data` must match the types, number, +and orderof arguments expected by `callback.func`. +""" +function trigger_callback!(callback::HourlyCallback, date_current) + if callback.active && date_current >= callback.ref_date[1] + callback.func(callback.data...) + callback.ref_date[1] += Dates.Hour(1) + end +end -# Arguments -- `date_nextcall::DateTime` the next date to call the callback function at or after -- `date_current::DateTime` the current date of the simulation -- `save_freq::AbstractFrequency` frequency with which to trigger callback -- `func::Function` function to be triggered if date is at or past the next call date -- `func_args::Tuple` a tuple of arguments to be passed into the callback function -""" -function trigger_callback( - date_nextcall::Dates.DateTime, - date_current::Dates.DateTime, - ::Monthly, - func::Function, - func_args::Tuple, -) - if date_current >= date_nextcall - func(func_args...) - return date_nextcall + Dates.Month(1) - else - return date_nextcall +function trigger_callback!(callback::MonthlyCallback, date_current) + if callback.active && date_current >= callback.ref_date[1] + callback.func(callback.data...) + callback.ref_date[1] += Dates.Month(1) end end diff --git a/test/callbackmanager.jl b/test/callbackmanager.jl index 3eb9ca85..34d3e403 100644 --- a/test/callbackmanager.jl +++ b/test/callbackmanager.jl @@ -38,7 +38,7 @@ for FT in (Float32, Float64) "00000101" end - @testset "test trigger_callback for FT=$FT" begin + @testset "test trigger_callback! for FT=$FT" begin # Define callback function func! = (val) -> val[1] += 1 # Case 1: date_current == date_nextcall @@ -47,7 +47,7 @@ for FT in (Float32, Float64) arg_copy = copy(arg) date_current = date_nextcall = date_nextcall_copy = Dates.DateTime(1979, 3, 21) - date_nextcall = CallbackManager.trigger_callback( + date_nextcall = CallbackManager.trigger_callback!( date_nextcall, date_current, CallbackManager.Monthly(), @@ -61,7 +61,7 @@ for FT in (Float32, Float64) # Case 2: date_current > date_nextcall date_nextcall = date_nextcall_copy = Dates.DateTime(1979, 3, 21) date_current = date_nextcall + Dates.Day(1) - date_nextcall = CallbackManager.trigger_callback( + date_nextcall = CallbackManager.trigger_callback!( date_nextcall, date_current, CallbackManager.Monthly(), @@ -75,7 +75,7 @@ for FT in (Float32, Float64) # Case 3: date_current < date_nextcall date_nextcall = date_nextcall_copy = Dates.DateTime(1979, 3, 21) date_current = date_nextcall - Dates.Day(1) - date_nextcall = CallbackManager.trigger_callback( + date_nextcall = CallbackManager.trigger_callback!( date_nextcall, date_current, CallbackManager.Monthly(),