-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add TimeManager module #9
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# 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 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
module ClimaUtilities | ||
|
||
greet() = print("Hello World!") | ||
include("TimeManager.jl") | ||
|
||
end # module ClimaUtilities |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
""" | ||
TimeManager | ||
|
||
This module facilitates calendar functions and temporal interpolations | ||
of data. | ||
""" | ||
module TimeManager | ||
|
||
import Dates | ||
import CFTime | ||
|
||
export to_datetime, | ||
strdate_to_datetime, | ||
datetime_to_strdate, | ||
trigger_callback, | ||
Monthly, | ||
EveryTimestep | ||
|
||
""" | ||
to_datetime(date) | ||
|
||
Convert a `DateTime`-like object (e.g. `DateTimeNoLeap`) to a `DateTime`, | ||
using CFTime.jl. 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. | ||
|
||
# Arguments | ||
- `date`: `DateTime`-like object to be converted to `DateTime` | ||
""" | ||
to_datetime(date) = CFTime.reinterpret(Dates.DateTime, date) | ||
|
||
""" | ||
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")) | ||
|
||
abstract type AbstractFrequency end | ||
struct Monthly <: AbstractFrequency end | ||
struct EveryTimestep <: AbstractFrequency end | ||
|
||
""" | ||
trigger_callback(date_nextcall::Dates.DateTime, | ||
date_current::Dates.DateTime, | ||
::Monthly, | ||
func::Function,) | ||
|
||
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. | ||
|
||
The tuple of arguments `func_args` must match the types, number, and order | ||
of arguments expected by `func`. | ||
|
||
# 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, | ||
juliasloan25 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
if date_current >= date_nextcall | ||
func(func_args...) | ||
return date_nextcall + Dates.Month(1) | ||
else | ||
return date_nextcall | ||
end | ||
end | ||
|
||
end # module TimeManager |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import ClimaUtilities: TimeManager | ||
import Dates | ||
import CFTime | ||
using Test | ||
|
||
for FT in (Float32, Float64) | ||
@testset "test to_datetime for FT=$FT" begin | ||
# Test non-leap year behavior | ||
year = 2001 | ||
dt_noleap = CFTime.DateTimeNoLeap(year) | ||
dt = Dates.DateTime(year) | ||
@test TimeManager.to_datetime(dt_noleap) == dt | ||
# In non-leap year, DateTime and DateTimeNoLeap are the same | ||
@test TimeManager.to_datetime(dt_noleap + Dates.Day(365)) == | ||
dt + Dates.Day(365) | ||
|
||
# Test leap year behavior | ||
leap_year = 2000 | ||
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)) == | ||
dt_ly + Dates.Day(366) | ||
|
||
end | ||
|
||
@testset "test strdate_to_datetime for FT=$FT" 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 for FT=$FT" 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 "test trigger_callback for FT=$FT" begin | ||
# Define callback function | ||
func! = (val) -> val[1] += 1 | ||
# Case 1: date_current == date_nextcall | ||
# Define list for arg so we can mutate it in `func!` | ||
arg = [FT(0)] | ||
arg_copy = copy(arg) | ||
date_current = | ||
date_nextcall = date_nextcall_copy = Dates.DateTime(1979, 3, 21) | ||
date_nextcall = TimeManager.trigger_callback( | ||
date_nextcall, | ||
date_current, | ||
TimeManager.Monthly(), | ||
func!, | ||
(arg,), | ||
) | ||
# Test that cutoff date was updated and `func!` got called | ||
@test date_nextcall == date_nextcall_copy + Dates.Month(1) | ||
@test arg[1] == func!(arg_copy) | ||
|
||
# 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, | ||
date_current, | ||
TimeManager.Monthly(), | ||
func!, | ||
(arg,), | ||
) | ||
# Test that cutoff date was updated and `func!` got called | ||
@test date_nextcall == date_nextcall_copy + Dates.Month(1) | ||
@test arg[1] == func!(arg_copy) | ||
|
||
# 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, | ||
date_current, | ||
TimeManager.Monthly(), | ||
func!, | ||
(arg,), | ||
) | ||
# Test that cutoff date is unchanged and `func!` did not get called | ||
@test date_nextcall == date_nextcall_copy | ||
@test arg[1] == arg_copy[1] | ||
end | ||
end |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"coupled simulations" may be more accurate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some of these functions (e.g.
to_datetime
) are used in the bucket model in ClimaLSM, which can be run standalone or in a coupled simulation, so I think it makes sense to keep the comment more general