diff --git a/NEWS.md b/NEWS.md index ade6657534..8669091297 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,14 +6,14 @@ ClimaCoupler.jl Release Notes ### ClimaEarth features -### Sea-surface temperature and sea ice concentration data can now be automatically downloaded +#### Sea-surface temperature and sea ice concentration data can now be automatically downloaded Sea-surface temperature and sea ice concentration require external files. Now, a low-resolution version of such files is automatically downloaded when a higher-resolution version is not available. Please, refer to [ClimaArtifacts](https://github.com/CliMA/ClimaArtifacts) for more information. -### A higher resolution land-sea mask is now used and automatically downloaded - PR [#1006](https://github.com/CliMA/ClimaCoupler.jl/pull/1006) +#### A higher resolution land-sea mask is now used and automatically downloaded - PR [#1006](https://github.com/CliMA/ClimaCoupler.jl/pull/1006) A 60 arcsecond land-sea mask constructed from topographic data is now used. Topographic data is automatically downloaded and a land-sea mask is constructed @@ -21,12 +21,37 @@ by identifying where elevation is greater than 0. Note, this can lead to misidentification of ocean in some areas of the globe that are inland but below sea level (Dead Sea, Death Valley, ...). -### Leaderboard for variables over longitude, latitude, time, and pressure - PR [#1094](https://github.com/CliMA/ClimaCoupler.jl/pull/1094) +#### Leaderboard for variables over longitude, latitude, time, and pressure - PR [#1094](https://github.com/CliMA/ClimaCoupler.jl/pull/1094) As a part of the post processing pipeline, bias plots for variables at the pressure levels of 850.0, 500.0, 250.0 hPa and bias plots over latitude and pressure levels are being created. +### Breaking changes + +#### `hourly_checkpoint_dt` is now just `checkpoint_dt` + +Previously, the checkpointing frequency had to be specified in hours and it was +not possible to checkpoint with smaller frequencies. Now, the argument +`hourly_checkpoint_dt` was renamed to `checkpoint_dt` and any frequency can be +specified (e.g., "2months", "200secs"). + +Callbacks were also reworked, and the previous system was removed. Here is an example of the +new way to create a callback that runs every month: +```julia +import ClimaCouper.TimeManager: Callback, maybe_trigger_callback +import ClimaDiagnostics.Schedules: EveryCalendarDtSchedule # This will be moved to ClimaUtilities +import Dates + +start_date = Dates.DateTime(1912, 4, 15) +func(cs) = println(maxima(cs.fields.T_s)) +schedule = EveryCalendarDtSchedule(Dates.Month(1); start_date) +callback = Callback(schedule, func) + +# Then, call it with +maybe_trigger_callback(callback, cs, t) +``` + ### Code cleanup #### Output path updates - PRs [#1106](https://github.com/CliMA/ClimaCoupler.jl/pull/1058), diff --git a/config/amip_configs/amip.yml b/config/amip_configs/amip.yml index 2648ea5d43..1622d926a6 100644 --- a/config/amip_configs/amip.yml +++ b/config/amip_configs/amip.yml @@ -9,8 +9,7 @@ dt_save_to_sol: "30days" dz_bottom: 30.0 energy_check: false h_elem: 16 -hourly_checkpoint: true -hourly_checkpoint_dt: 720 +checkpoint_dt: "720hours" land_albedo_type: "map_temporal" mode_name: "amip" mono_surface: false diff --git a/config/ci_configs/amip_coarse_ft64_hourly_checkpoints.yml b/config/ci_configs/amip_coarse_ft64_hourly_checkpoints.yml index e1f5cd8c6a..15ba3c21fd 100644 --- a/config/ci_configs/amip_coarse_ft64_hourly_checkpoints.yml +++ b/config/ci_configs/amip_coarse_ft64_hourly_checkpoints.yml @@ -2,7 +2,6 @@ apply_limiter: false dt_save_to_sol: "1days" energy_check: false h_elem: 6 -hourly_checkpoint: true mode_name: "amip" moist: "equil" mono_surface: false diff --git a/config/ci_configs/amip_coarse_ft64_hourly_checkpoints_restart.yml b/config/ci_configs/amip_coarse_ft64_hourly_checkpoints_restart.yml index 03367c0b21..c2d03cc556 100644 --- a/config/ci_configs/amip_coarse_ft64_hourly_checkpoints_restart.yml +++ b/config/ci_configs/amip_coarse_ft64_hourly_checkpoints_restart.yml @@ -3,8 +3,7 @@ dt_save_restart: "10days" dt_save_to_sol: "1days" energy_check: false h_elem: 6 -hourly_checkpoint: true -hourly_checkpoint_dt: 1 +checkpoint_dt: 1 mode_name: "amip" moist: "equil" mono_surface: false diff --git a/config/ci_configs/amip_target_topo_diagedmf_shortrun.yml b/config/ci_configs/amip_target_topo_diagedmf_shortrun.yml index e73477831f..e72bf8142b 100644 --- a/config/ci_configs/amip_target_topo_diagedmf_shortrun.yml +++ b/config/ci_configs/amip_target_topo_diagedmf_shortrun.yml @@ -8,7 +8,6 @@ dt_rad: "1hours" dt_save_state_to_disk: "1days" dt_save_to_sol: "1days" energy_check: false -hourly_checkpoint: true insolation: "timevarying" land_albedo_type: "map_temporal" mode_name: "amip" diff --git a/config/longrun_configs/amip_target.yml b/config/longrun_configs/amip_target.yml index 2c22691535..1b6467b3ae 100644 --- a/config/longrun_configs/amip_target.yml +++ b/config/longrun_configs/amip_target.yml @@ -6,7 +6,6 @@ dt_cpl: "120secs" dt_rad: "1hours" dt_save_state_to_disk: "20days" energy_check: false -hourly_checkpoint: true insolation: "timevarying" land_albedo_type: "map_temporal" mode_name: "amip" diff --git a/config/longrun_configs/amip_target_topo.yml b/config/longrun_configs/amip_target_topo.yml index 1081e82788..c6596bd743 100644 --- a/config/longrun_configs/amip_target_topo.yml +++ b/config/longrun_configs/amip_target_topo.yml @@ -7,7 +7,6 @@ dt_cpl: "120secs" dt_rad: "1hours" dt_save_state_to_disk: "20days" energy_check: false -hourly_checkpoint: true insolation: "timevarying" land_albedo_type: "map_temporal" mode_name: "amip" diff --git a/config/longrun_configs/amip_target_topo_diagedmf_cpu.yml b/config/longrun_configs/amip_target_topo_diagedmf_cpu.yml index bca89d6cfa..e0b32e0151 100644 --- a/config/longrun_configs/amip_target_topo_diagedmf_cpu.yml +++ b/config/longrun_configs/amip_target_topo_diagedmf_cpu.yml @@ -8,7 +8,7 @@ dt_rad: "1hours" dt_save_state_to_disk: "20days" energy_check: false insolation: "timevarying" -hourly_checkpoint: false +checkpoint_dt: "30days" land_albedo_type: "map_temporal" mode_name: "amip" mono_surface: false diff --git a/config/longrun_configs/amip_target_topo_diagedmf_gpu.yml b/config/longrun_configs/amip_target_topo_diagedmf_gpu.yml index 439fe0ebbc..0e94c9384c 100644 --- a/config/longrun_configs/amip_target_topo_diagedmf_gpu.yml +++ b/config/longrun_configs/amip_target_topo_diagedmf_gpu.yml @@ -8,7 +8,7 @@ dt_rad: "1hours" dt_save_state_to_disk: "20days" energy_check: false insolation: "timevarying" -hourly_checkpoint: false +checkpoint_dt: "300days" land_albedo_type: "map_temporal" mode_name: "amip" mono_surface: false diff --git a/config/longrun_configs/slabplanet_aqua_target.yml b/config/longrun_configs/slabplanet_aqua_target.yml index 59eb173088..85d1cad8af 100644 --- a/config/longrun_configs/slabplanet_aqua_target.yml +++ b/config/longrun_configs/slabplanet_aqua_target.yml @@ -7,7 +7,6 @@ dt_save_state_to_disk: "20days" dt_save_to_sol: "10days" energy_check: false evolving_ocean: false -hourly_checkpoint: true land_albedo_type: "function" mode_name: "slabplanet_aqua" mono_surface: false diff --git a/config/longrun_configs/slabplanet_aqua_target_evolve_ocn.yml b/config/longrun_configs/slabplanet_aqua_target_evolve_ocn.yml index 62a9bbcee4..637797f6d1 100644 --- a/config/longrun_configs/slabplanet_aqua_target_evolve_ocn.yml +++ b/config/longrun_configs/slabplanet_aqua_target_evolve_ocn.yml @@ -7,7 +7,6 @@ dt_save_state_to_disk: "20days" dt_save_to_sol: "10days" energy_check: false evolving_ocean: true -hourly_checkpoint: true land_albedo_type: "function" mode_name: "slabplanet_aqua" mono_surface: false diff --git a/config/longrun_configs/slabplanet_aqua_target_nocouple.yml b/config/longrun_configs/slabplanet_aqua_target_nocouple.yml index e27ec6e608..e64a475abc 100644 --- a/config/longrun_configs/slabplanet_aqua_target_nocouple.yml +++ b/config/longrun_configs/slabplanet_aqua_target_nocouple.yml @@ -7,7 +7,6 @@ dt_save_state_to_disk: "20days" dt_save_to_sol: "10days" energy_check: false evolving_ocean: false -hourly_checkpoint: true land_albedo_type: "function" mode_name: "slabplanet_aqua" mono_surface: false diff --git a/config/longrun_configs/slabplanet_target.yml b/config/longrun_configs/slabplanet_target.yml index dee938f45d..5d48ce1313 100644 --- a/config/longrun_configs/slabplanet_target.yml +++ b/config/longrun_configs/slabplanet_target.yml @@ -7,7 +7,6 @@ dt_save_state_to_disk: "20days" dt_save_to_sol: "10days" energy_check: false evolving_ocean: false -hourly_checkpoint: true land_albedo_type: "function" mode_name: "slabplanet" mono_surface: false diff --git a/config/longrun_configs/slabplanet_target_evolve_ocn.yml b/config/longrun_configs/slabplanet_target_evolve_ocn.yml index bdb88b729e..25abd47199 100644 --- a/config/longrun_configs/slabplanet_target_evolve_ocn.yml +++ b/config/longrun_configs/slabplanet_target_evolve_ocn.yml @@ -7,7 +7,6 @@ dt_save_state_to_disk: "20days" dt_save_to_sol: "10days" energy_check: false evolving_ocean: true -hourly_checkpoint: true land_albedo_type: "function" mode_name: "slabplanet" mono_surface: false diff --git a/config/nightly_configs/amip_coarse.yml b/config/nightly_configs/amip_coarse.yml index e15d9e24ae..87c7d8c4f2 100644 --- a/config/nightly_configs/amip_coarse.yml +++ b/config/nightly_configs/amip_coarse.yml @@ -9,8 +9,7 @@ dt_save_to_sol: "30days" dz_bottom: 100.0 energy_check: false h_elem: 8 -hourly_checkpoint: true -hourly_checkpoint_dt: 720 +checkpoint_dt: "720hours" land_albedo_type: "map_temporal" mode_name: "amip" mono_surface: false diff --git a/config/nightly_configs/amip_coarse_random.yml b/config/nightly_configs/amip_coarse_random.yml index 2a7f772d27..46bebb1429 100644 --- a/config/nightly_configs/amip_coarse_random.yml +++ b/config/nightly_configs/amip_coarse_random.yml @@ -9,8 +9,7 @@ dt_save_to_sol: "30days" dz_bottom: 100.0 energy_check: false h_elem: 8 -hourly_checkpoint: true -hourly_checkpoint_dt: 720 +checkpoint_dt: "720hours" land_albedo_type: "map_temporal" mode_name: "amip" mono_surface: false diff --git a/docs/src/timemanager.md b/docs/src/timemanager.md index ed47e791cf..214196d2a8 100644 --- a/docs/src/timemanager.md +++ b/docs/src/timemanager.md @@ -10,15 +10,5 @@ functions from Julia's [Dates](https://docs.julialang.org/en/v1/stdlib/Dates/) m 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 +ClimaCoupler.TimeManager.maybe_trigger_callback ``` diff --git a/experiments/ClimaEarth/cli_options.jl b/experiments/ClimaEarth/cli_options.jl index ce3f753a6a..39ba60877f 100644 --- a/experiments/ClimaEarth/cli_options.jl +++ b/experiments/ClimaEarth/cli_options.jl @@ -73,15 +73,10 @@ function argparse_settings() help = "Time interval for saving output [\"10days\" (default); allowed formats: \"Nsecs\", \"Nmins\", \"Nhours\", \"Ndays\", \"Inf\"]" arg_type = String default = "10days" - # Checkpointing information - "--hourly_checkpoint" - help = "Boolean flag indicating whether to checkpoint at multiple-hourly intervals [false (default), true]" - arg_type = Bool - default = false - "--hourly_checkpoint_dt" - help = "Time interval for hourly checkpointing in hours [480 (default)]" - arg_type = Int - default = 480 + "--checkpoint_dt" + help = "Time interval for hourly checkpointing [480hours (default)]" + arg_type = String + default = "480hours" # Restart information "--restart_dir" help = "Directory containing restart files" diff --git a/experiments/ClimaEarth/run_amip.jl b/experiments/ClimaEarth/run_amip.jl index 504cf17cf9..e01c3e60af 100644 --- a/experiments/ClimaEarth/run_amip.jl +++ b/experiments/ClimaEarth/run_amip.jl @@ -58,6 +58,9 @@ import Interpolations # triggers InterpolationsExt in ClimaUtilities # Random is used by RRMTGP for some cloud properties import Random +# TODO: Move to ClimaUtilities once we move the Schedules to ClimaUtilities +import ClimaDiagnostics.Schedules: EveryCalendarDtSchedule + pkg_dir = pkgdir(ClimaCoupler) #= @@ -106,8 +109,7 @@ add_extra_diagnostics!(config_dict) Δt_cpl, component_dt_dict, saveat, - hourly_checkpoint, - hourly_checkpoint_dt, + checkpoint_dt, restart_dir, restart_t, use_coupler_diagnostics, @@ -472,7 +474,7 @@ Utilities.show_memory_usage() model_sims = (atmos_sim = atmos_sim, ice_sim = ice_sim, land_sim = land_sim, ocean_sim = ocean_sim); ## dates -dates = (; date = [date], date0 = [date0], date1 = [Dates.firstdayofmonth(date0)], new_month = [false]) +dates = (; date = [date], date0 = [date0]) #= ## Initialize Conservation Checks @@ -500,38 +502,23 @@ end Callbacks are used to update at a specified interval. The callbacks are initialized here and saved in a global `Callbacks` struct, `callbacks`. The `trigger_callback!` function is used to call the callback during the simulation below. -The frequency of the callbacks is specified in the `HourlyCallback` and `MonthlyCallback` structs. The `func` field specifies the function to be called, -the `ref_date` field specifies the first date for the callback, and the `active` field specifies whether the callback is active or not. - The currently implemented callbacks are: - `checkpoint_cb`: generates a checkpoint of all model states at a specified interval. This is mainly used for restarting simulations. -- `update_firstdayofmonth!_cb`: generates a callback to update the first day of the month for monthly message print (and other monthly operations). - `albedo_cb`: for the amip mode, the water albedo is time varying (since the reflectivity of water depends on insolation and wave characteristics, with the latter being approximated from wind speed). It is updated at the same frequency as the atmospheric radiation. NB: Eventually, we will call all of radiation from the coupler, in addition to the albedo calculation. =# +schedule_checkpoint = EveryCalendarDtSchedule(TimeManager.time_to_period(dt_checkpoint); start_date = date0) +checkpoint_cb = Callback(schedule_checkpoint, Checkpointer.checkpoint_sims) -checkpoint_cb = TimeManager.HourlyCallback( - dt = hourly_checkpoint_dt, - func = Checkpointer.checkpoint_sims, - ref_date = [dates.date[1]], - active = hourly_checkpoint, -) # 20 days -update_firstdayofmonth!_cb = TimeManager.MonthlyCallback( - dt = FT(1), - func = TimeManager.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( - dt = dt_water_albedo, - func = FluxCalculator.water_albedo_from_atmosphere!, - ref_date = [dates.date[1]], - active = mode_name == "amip", -) -callbacks = - (; checkpoint = checkpoint_cb, update_firstdayofmonth! = update_firstdayofmonth!_cb, water_albedo = albedo_cb) +if mode_name == "amip" + schedule_albedo = EveryCalendarDtSchedule(TimeManager.time_to_period(dt_water_albedo); start_date = date0) +else + schedule_albedo = TimeManager.NeverSchedule() +end +albedo_cb = Callback(schedule_albedo, func = FluxCalculator.water_albedo_from_atmosphere!) + +callbacks = (; checkpoint = checkpoint_cb, water_albedo = albedo_cb) #= ## Initialize turbulent fluxes @@ -670,14 +657,10 @@ function solve_coupler!(cs) @info("Starting coupling loop") ## step in time for t in ((tspan[begin] + Δt_cpl):Δt_cpl:tspan[end]) - - cs.dates.date[1] = TimeManager.current_date(cs, t) - - ## print date on the first of month - cs.dates.date[1] >= cs.dates.date1[1] && @info(cs.dates.date[1]) + # Update date + cs.dates.date[] = TimeManager.current_date(cs, t) if cs.mode.name == "amip" - evaluate!(Interfacer.get_field(ocean_sim, Val(:surface_temperature)), cs.mode.SST_timevaryinginput, t) evaluate!(Interfacer.get_field(ice_sim, Val(:area_fraction)), cs.mode.SIC_timevaryinginput, t) @@ -694,8 +677,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) - + TimeManager.maybe_trigger_callback(cs.callbacks.water_albedo, cs, t) ## update the surface fractions for surface models, ## and update all component model simulations with the current fluxes stored in the coupler @@ -728,11 +710,8 @@ function solve_coupler!(cs) ## update the coupler with the new atmospheric properties 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!) - ## callback to checkpoint model state - TimeManager.trigger_callback!(cs, cs.callbacks.checkpoint) + TimeManager.maybe_trigger_callback(cs.callbacks.checkpoint, cs, t) ## compute/output AMIP diagnostics if scheduled for this timestep ## wrap the current CoupledSimulation fields and time in a NamedTuple to match the ClimaDiagnostics interface diff --git a/experiments/ClimaEarth/run_cloudless_aquaplanet.jl b/experiments/ClimaEarth/run_cloudless_aquaplanet.jl index 7ca6c71e52..af29368bc5 100644 --- a/experiments/ClimaEarth/run_cloudless_aquaplanet.jl +++ b/experiments/ClimaEarth/run_cloudless_aquaplanet.jl @@ -26,6 +26,9 @@ import ClimaCoupler import ClimaCoupler: ConservationChecker, Checkpointer, FieldExchanger, FluxCalculator, Interfacer, Regridder, TimeManager, Utilities +# TODO: Move to ClimaUtilities once we move the Schedules to ClimaUtilities +import ClimaDiagnostics.Schedules: EveryCalendarDtSchedule + pkg_dir = pkgdir(ClimaCoupler) #= @@ -52,8 +55,8 @@ restart_t = Int(0) t_end = "1000days" tspan = (Float64(0.0), Float64(Utilities.time_to_seconds(t_end))) start_date = "19790301" -hourly_checkpoint = true dt_rad = "6hours" +checkpoint_dt = "480hours" #= ### I/O Directory Setup @@ -196,33 +199,18 @@ model_sims = (atmos_sim = atmos_sim, ocean_sim = ocean_sim); ## dates date0 = date = Dates.DateTime(start_date, Dates.dateformat"yyyymmdd") -dates = (; date = [date], date0 = [date0], date1 = [Dates.firstdayofmonth(date0)], new_month = [false]) +dates = (; date = [date], date0 = [date0]) #= ## Initialize Callbacks =# +schedule_checkpoint = EveryCalendarDtSchedule(TimeManager.time_to_period(checkpoint_dt); start_date = date0) +checkpoint_cb = Callback(schedule_checkpoint, Checkpointer.checkpoint_sims) -checkpoint_cb = TimeManager.HourlyCallback( - dt = FT(480), - func = Checkpointer.checkpoint_sims, - ref_date = [dates.date[1]], - active = hourly_checkpoint, -) # 20 days -update_firstdayofmonth!_cb = TimeManager.MonthlyCallback( - dt = FT(1), - func = TimeManager.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( - dt = dt_water_albedo, - func = FluxCalculator.water_albedo_from_atmosphere!, - ref_date = [dates.date[1]], - active = true, -) -callbacks = - (; checkpoint = checkpoint_cb, update_firstdayofmonth! = update_firstdayofmonth!_cb, water_albedo = albedo_cb) +schedule_albedo = EveryCalendarDtSchedule(TimeManager.time_to_period(dt_water_albedo); start_date = date0) +albedo_cb = Callback(schedule_albedo, func = FluxCalculator.water_albedo_from_atmosphere!) + +callbacks = (; checkpoint = checkpoint_cb, water_albedo = albedo_cb) #= ## Initialize turbulent fluxes @@ -306,16 +294,13 @@ function solve_coupler!(cs) @info("Starting coupling loop") ## step in time for t in ((tspan[begin] + Δt_cpl):Δt_cpl:tspan[end]) - - cs.dates.date[1] = TimeManager.current_date(cs, t) - - ## print date on the first of month - cs.dates.date[1] >= cs.dates.date1[1] && @info(cs.dates.date[1]) + # Update date + cs.dates.date[] = TimeManager.current_date(cs, t) 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) + TimeManager.maybe_trigger_callback(cs.callbacks.water_albedo, cs, t) ## run component models sequentially for one coupling timestep (Δt_cpl) FieldExchanger.update_model_sims!(cs.model_sims, cs.fields, cs.turbulent_fluxes) @@ -329,12 +314,8 @@ 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!) - ## callback to checkpoint model state - TimeManager.trigger_callback!(cs, cs.callbacks.checkpoint) - + TimeManager.maybe_trigger_callback(cs.callbacks.checkpoint, cs, t) end return nothing diff --git a/experiments/ClimaEarth/run_cloudy_aquaplanet.jl b/experiments/ClimaEarth/run_cloudy_aquaplanet.jl index 5601948507..5923dd86de 100644 --- a/experiments/ClimaEarth/run_cloudy_aquaplanet.jl +++ b/experiments/ClimaEarth/run_cloudy_aquaplanet.jl @@ -26,6 +26,9 @@ import ClimaCoupler import ClimaCoupler: ConservationChecker, Checkpointer, FieldExchanger, FluxCalculator, Interfacer, Regridder, TimeManager, Utilities +# TODO: Move to ClimaUtilities once we move the Schedules to ClimaUtilities +import ClimaDiagnostics.Schedules: EveryCalendarDtSchedule + pkg_dir = pkgdir(ClimaCoupler) #= @@ -52,8 +55,8 @@ restart_t = Int(0) t_end = "1000days" tspan = (Float64(0.0), Float64(Utilities.time_to_seconds(t_end))) start_date = "19790301" -hourly_checkpoint = true dt_rad = "6hours" +checkpoint_dt = "480hours" #= ### I/O Directory Setup @@ -220,25 +223,18 @@ model_sims = (atmos_sim = atmos_sim, ocean_sim = ocean_sim); ## dates date0 = date = Dates.DateTime(start_date, Dates.dateformat"yyyymmdd") -dates = (; date = [date], date0 = [date0], date1 = [Dates.firstdayofmonth(date0)], new_month = [false]) +dates = (; date = [date], date0 = [date0]) #= ## Initialize Callbacks =# +schedule_checkpoint = EveryCalendarDtSchedule(TimeManager.time_to_period(checkpoint_dt); start_date = date0) +checkpoint_cb = Callback(schedule_checkpoint, Checkpointer.checkpoint_sims) -checkpoint_cb = TimeManager.HourlyCallback( - dt = FT(480), - func = Checkpointer.checkpoint_sims, - ref_date = [dates.date[1]], - active = hourly_checkpoint, -) # 20 days -update_firstdayofmonth!_cb = TimeManager.MonthlyCallback( - dt = FT(1), - func = TimeManager.update_firstdayofmonth!, - ref_date = [dates.date1[1]], - active = true, -) -callbacks = (; checkpoint = checkpoint_cb, update_firstdayofmonth! = update_firstdayofmonth!_cb) +schedule_albedo = EveryCalendarDtSchedule(TimeManager.time_to_period(dt_water_albedo); start_date = date0) +albedo_cb = Callback(schedule_albedo, func = FluxCalculator.water_albedo_from_atmosphere!) + +callbacks = (; checkpoint = checkpoint_cb, water_albedo = albedo_cb) #= ## Initialize turbulent fluxes @@ -321,11 +317,8 @@ function solve_coupler!(cs) @info("Starting coupling loop") ## step in time for t in ((tspan[begin] + Δt_cpl):Δt_cpl:tspan[end]) - - cs.dates.date[1] = TimeManager.current_date(cs, t) - - ## print date on the first of month - cs.dates.date[1] >= cs.dates.date1[1] && @info(cs.dates.date[1]) + # Update date + cs.dates.date[] = TimeManager.current_date(cs, t) ClimaComms.barrier(comms_ctx) @@ -341,12 +334,8 @@ 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!) - ## callback to checkpoint model state - TimeManager.trigger_callback!(cs, cs.callbacks.checkpoint) - + TimeManager.maybe_trigger_callback(cs.callbacks.checkpoint, cs, t) end return nothing diff --git a/experiments/ClimaEarth/run_cloudy_slabplanet.jl b/experiments/ClimaEarth/run_cloudy_slabplanet.jl index 898c0a711b..c76d1bb837 100644 --- a/experiments/ClimaEarth/run_cloudy_slabplanet.jl +++ b/experiments/ClimaEarth/run_cloudy_slabplanet.jl @@ -30,6 +30,9 @@ import ClimaUtilities.ClimaArtifacts: @clima_artifact import ClimaUtilities.SpaceVaryingInputs: SpaceVaryingInput import Interpolations # triggers InterpolationsExt in ClimaUtilities +# TODO: Move to ClimaUtilities once we move the Schedules to ClimaUtilities +import ClimaDiagnostics.Schedules: EveryCalendarDtSchedule + pkg_dir = pkgdir(ClimaCoupler) #= @@ -57,7 +60,7 @@ restart_t = Int(0) t_end = "1000days" tspan = (Float64(0.0), Float64(Utilities.time_to_seconds(t_end))) start_date = "19790321" -hourly_checkpoint = true +checkpoint_dt = "20days" dt_rad = "6hours" #= @@ -165,7 +168,7 @@ land_mask_data = joinpath(@clima_artifact("landsea_mask_60arcseconds", comms_ctx ## dates date0 = date = Dates.DateTime(start_date, Dates.dateformat"yyyymmdd") -dates = (; date = [date], date0 = [date0], date1 = [Dates.firstdayofmonth(date0)], new_month = [false]) +dates = (; date = [date], date0 = [date0]) #= @@ -264,28 +267,13 @@ model_sims = (atmos_sim = atmos_sim, ocean_sim = ocean_sim); #= ## Initialize Callbacks =# +schedule_checkpoint = EveryCalendarDtSchedule(TimeManager.time_to_period(checkpoint_dt); start_date = date0) +checkpoint_cb = Callback(schedule_checkpoint, Checkpointer.checkpoint_sims) -checkpoint_cb = TimeManager.HourlyCallback( - dt = FT(480), - func = Checkpointer.checkpoint_sims, - ref_date = [dates.date[1]], - active = hourly_checkpoint, -) # 20 days -update_firstdayofmonth!_cb = TimeManager.MonthlyCallback( - dt = FT(1), - func = TimeManager.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( - dt = dt_water_albedo, - func = FluxCalculator.water_albedo_from_atmosphere!, - ref_date = [dates.date[1]], - active = true, -) -callbacks = - (; checkpoint = checkpoint_cb, update_firstdayofmonth! = update_firstdayofmonth!_cb, water_albedo = albedo_cb) +schedule_albedo = EveryCalendarDtSchedule(TimeManager.time_to_period(dt_water_albedo); start_date = date0) +albedo_cb = Callback(schedule_albedo, func = FluxCalculator.water_albedo_from_atmosphere!) + +callbacks = (; checkpoint = checkpoint_cb, water_albedo = albedo_cb) #= ## Initialize turbulent fluxes @@ -366,16 +354,13 @@ function solve_coupler!(cs) @info("Starting coupling loop") ## step in time for t in ((tspan[begin] + Δt_cpl):Δt_cpl:tspan[end]) - - cs.dates.date[1] = TimeManager.current_date(cs, t) - - ## print date on the first of month - cs.dates.date[1] >= cs.dates.date1[1] && @info(cs.dates.date[1]) + # Update date + cs.dates.date[] = TimeManager.current_date(cs, t) 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) + TimeManager.maybe_trigger_callback(cs.callbacks.water_albedo, cs, t) ## run component models sequentially for one coupling timestep (Δt_cpl) FieldExchanger.update_model_sims!(cs.model_sims, cs.fields, cs.turbulent_fluxes) @@ -389,12 +374,8 @@ 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!) - ## callback to checkpoint model state - TimeManager.trigger_callback!(cs, cs.callbacks.checkpoint) - + TimeManager.maybe_trigger_callback(cs.callbacks.checkpoint, cs, t) end return nothing diff --git a/experiments/ClimaEarth/run_dry_held_suarez.jl b/experiments/ClimaEarth/run_dry_held_suarez.jl index 9e09fc2b27..59ad5c1654 100644 --- a/experiments/ClimaEarth/run_dry_held_suarez.jl +++ b/experiments/ClimaEarth/run_dry_held_suarez.jl @@ -27,6 +27,9 @@ import ClimaCore import ClimaCoupler import ClimaCoupler: Checkpointer, FieldExchanger, Interfacer, TimeManager, Utilities +# TODO: Move to ClimaUtilities once we move the Schedules to ClimaUtilities +import ClimaDiagnostics.Schedules: EveryCalendarDtSchedule + pkg_dir = pkgdir(ClimaCoupler) #= @@ -53,7 +56,7 @@ restart_t = Int(0) t_end = "1000days" tspan = (Float64(0.0), Float64(Utilities.time_to_seconds(t_end))) start_date = "19790301" -hourly_checkpoint = true +checkpoint_dt = "480hours" #= ### I/O Directory Setup @@ -172,19 +175,10 @@ dates = (; date = [date], date0 = [date0], date1 = [Dates.firstdayofmonth(date0) #= ## Initialize Callbacks =# -checkpoint_cb = TimeManager.HourlyCallback( - dt = FT(480), - func = Checkpointer.checkpoint_sims, - ref_date = [dates.date[1]], - active = hourly_checkpoint, -) # 20 days TODO: not GPU friendly -update_firstdayofmonth!_cb = TimeManager.MonthlyCallback( - dt = FT(1), - func = TimeManager.update_firstdayofmonth!, - ref_date = [dates.date1[1]], - active = true, -) -callbacks = (; checkpoint = checkpoint_cb, update_firstdayofmonth! = update_firstdayofmonth!_cb) +schedule_checkpoint = EveryCalendarDtSchedule(TimeManager.time_to_period(checkpoint_dt); start_date = date0) +checkpoint_cb = Callback(schedule_checkpoint, Checkpointer.checkpoint_sims) + +callbacks = (; checkpoint = checkpoint_cb) cs = Interfacer.CoupledSimulation{FT}( comms_ctx, @@ -227,23 +221,16 @@ 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) - - ## print date on the first of month - cs.dates.date[1] >= cs.dates.date1[1] && @info(cs.dates.date[1]) + # Update date + cs.dates.date[] = TimeManager.current_date(cs, t) ## step sims FieldExchanger.step_model_sims!(cs.model_sims, t) 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!) - ## callback to checkpoint model state - TimeManager.trigger_callback!(cs, cs.callbacks.checkpoint) - + TimeManager.maybe_trigger_callback(cs.callbacks.checkpoint, cs, t) end @info(walltime) diff --git a/experiments/ClimaEarth/run_moist_held_suarez.jl b/experiments/ClimaEarth/run_moist_held_suarez.jl index 0c89aa356c..9b63b48989 100644 --- a/experiments/ClimaEarth/run_moist_held_suarez.jl +++ b/experiments/ClimaEarth/run_moist_held_suarez.jl @@ -29,6 +29,9 @@ import ClimaCoupler import ClimaCoupler: ConservationChecker, Checkpointer, FieldExchanger, FluxCalculator, Interfacer, Regridder, TimeManager, Utilities +# TODO: Move to ClimaUtilities once we move the Schedules to ClimaUtilities +import ClimaDiagnostics.Schedules: EveryCalendarDtSchedule + pkg_dir = pkgdir(ClimaCoupler) #= @@ -55,7 +58,7 @@ restart_t = Int(0) t_end = "1000days" tspan = (Float64(0.0), Float64(Utilities.time_to_seconds(t_end))) start_date = "19790301" -hourly_checkpoint = true +checkpoint_dt = "480hours" #= ### I/O Directory Setup @@ -209,20 +212,9 @@ dates = (; date = [date], date0 = [date0], date1 = [Dates.firstdayofmonth(date0) #= ## Initialize Callbacks =# - -checkpoint_cb = TimeManager.HourlyCallback( - dt = FT(480), - func = Checkpointer.checkpoint_sims, - ref_date = [dates.date[1]], - active = hourly_checkpoint, -) -update_firstdayofmonth!_cb = TimeManager.MonthlyCallback( - dt = FT(1), - func = TimeManager.update_firstdayofmonth!, - ref_date = [dates.date1[1]], - active = true, -) -callbacks = (; checkpoint = checkpoint_cb, update_firstdayofmonth! = update_firstdayofmonth!_cb) +schedule_checkpoint = EveryCalendarDtSchedule(TimeManager.time_to_period(checkpoint_dt); start_date = date0) +checkpoint_cb = Callback(schedule_checkpoint, Checkpointer.checkpoint_sims) +callbacks = (; checkpoint = checkpoint_cb) #= ## Initialize turbulent fluxes @@ -303,11 +295,9 @@ function solve_coupler!(cs) @info("Starting coupling loop") ## step in time for t in ((tspan[begin] + Δt_cpl):Δt_cpl:tspan[end]) + # Update date + cs.dates.date[] = TimeManager.current_date(cs, t) - cs.dates.date[1] = TimeManager.current_date(cs, t) - - ## print date on the first of month - cs.dates.date[1] >= cs.dates.date1[1] && @info(cs.dates.date[1]) ClimaComms.barrier(comms_ctx) ## run component models sequentially for one coupling timestep (Δt_cpl) @@ -322,12 +312,8 @@ 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!) - ## callback to checkpoint model state - TimeManager.trigger_callback!(cs, cs.callbacks.checkpoint) - + TimeManager.maybe_trigger_callback(cs.callbacks.checkpoint, cs, t) end return nothing diff --git a/experiments/ClimaEarth/test/amip_test.yml b/experiments/ClimaEarth/test/amip_test.yml index 71dbbc265e..590c69072e 100644 --- a/experiments/ClimaEarth/test/amip_test.yml +++ b/experiments/ClimaEarth/test/amip_test.yml @@ -9,8 +9,7 @@ dt_save_to_sol: "30days" dz_bottom: 100.0 energy_check: false h_elem: 8 -hourly_checkpoint: true -hourly_checkpoint_dt: 720 +checkpoint_dt: "720hours" land_albedo_type: "map_temporal" mode_name: "amip" mono_surface: false diff --git a/experiments/ClimaEarth/user_io/arg_parsing.jl b/experiments/ClimaEarth/user_io/arg_parsing.jl index 630bb3bcec..378883cd6a 100644 --- a/experiments/ClimaEarth/user_io/arg_parsing.jl +++ b/experiments/ClimaEarth/user_io/arg_parsing.jl @@ -57,8 +57,7 @@ function get_coupler_args(config_dict::Dict) component_dt_dict = config_dict["component_dt_dict"] # Checkpointing information - hourly_checkpoint = config_dict["hourly_checkpoint"] - hourly_checkpoint_dt = config_dict["hourly_checkpoint_dt"] + checkpoint_dt = config_dict["checkpoint_dt"] # Restart information restart_dir = config_dict["restart_dir"] @@ -101,8 +100,7 @@ function get_coupler_args(config_dict::Dict) Δt_cpl, component_dt_dict, saveat, - hourly_checkpoint, - hourly_checkpoint_dt, + checkpoint_dt, restart_dir, restart_t, use_coupler_diagnostics, diff --git a/src/TimeManager.jl b/src/TimeManager.jl index 92b41f19f2..151ff408fc 100644 --- a/src/TimeManager.jl +++ b/src/TimeManager.jl @@ -8,18 +8,9 @@ module TimeManager import Dates import ..Interfacer +import ..Utilities: time_to_seconds -export current_date, - strdate_to_datetime, - datetime_to_strdate, - trigger_callback, - AbstractFrequency, - Monthly, - EveryTimestep, - trigger_callback!, - HourlyCallback, - MonthlyCallback, - update_firstdayofmonth! +export current_date, strdate_to_datetime, datetime_to_strdate """ @@ -59,123 +50,82 @@ datetime_to_strdate(datetime::Dates.DateTime) = string(lpad(Dates.day(datetime), 2, "0")) """ - AbstractFrequency + time_to_period(s::String) -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 +Convert a string to a `Dates.Period` object. -""" - EveryTimestep -A concrete type for the every-timestep frequency of the callback function. -""" -struct EveryTimestep <: AbstractFrequency end - -""" - trigger_callback(cs, ::Monthly) +The string can be in one of two formats: -Returns `true` if the current date is equal to or exceeds the saved first of the month at time of 00:00:00. +1. **Months:** `months`, where `` is an integer representing the number of months. For example, `"2months"` represents 2 months. +2. **Other units:** ``, where `` is a number (integer or floating point) and `` is one of `secs`, `mins`, `hours`, or `days`. + This format is handled by the `time_to_seconds` function and converted to a `Dates.Millisecond` object. # 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 = [] +- `s::String`: The string to convert to a `Dates.Period`. + +# Returns +- A `Dates.Period` object representing the time period specified in the string. + +# Examples +```julia +julia> time_to_period("2months") +2 months + +julia> time_to_period("10secs") +10000 milliseconds + +julia> time_to_period("2.5hours") +9000000 milliseconds +``` +""" +function time_to_period(s::String) + if occursin("months", s) + months = match(r"^(\d+)months$", s) + isnothing(months) && error("$(s) has to be of the form months, e.g. 2months for 2 months") + return Dates.Month(parse(Int, first(months))) + else + # Milliseconds to support fractional seconds + return Dates.Millisecond(1000 * time_to_seconds(s)) + end end """ - MonthlyCallback{FT} + Callback -This is a callback type that triggers at intervals of 1 month or multiple months. +A small struct containing a schedule, and the function to be executed on the +schedule is true. -# Fields +A `schedule` is a callable object (ie, a function) that takes an integrator-type +of object and returns true or false. +TODO: If `cs` contained the correct time, we could just pass `cs` to the schedule -- `dt` -- `func` -- `ref_date` -- `active` -- `data` +The function `func` calls the coupled state `cs`. """ -@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 = [] +struct Callback{SCHEDULE, FUNC} + schedule::SCHEDULE + func::FUNC 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) + maybe_trigger_callback(callback, cs, t) +Check if it time to call `callback`, if yes, call its function on `cs`. """ - 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 +function maybe_trigger_callback(callback, cs, t) + # TODO: If `cs` contained the correct time, we could just pass `cs` to the schedule + callback.schedule((; t)) && callback.func(cs) + return nothing end + """ - update_firstdayofmonth!(cs::Interfacer.CoupledSimulation, _) +A schedule that is never true. Useful to disable something. -This function updates the first of the month reference date. +TODO: Move this to ClimaUtilities once we move Schedules there """ -function update_firstdayofmonth!(cs, _) - cs.dates.date1[1] = cs.dates.date1[1] + Dates.Month(1) - @info("update_firstdayofmonth! at $(cs.dates.date)") +struct NeverSchedule end +function (NeverSchedule)(args...; kwargs; ...) + return false end end diff --git a/test/time_manager_tests.jl b/test/time_manager_tests.jl index 2afbe2a8da..0813483cc8 100644 --- a/test/time_manager_tests.jl +++ b/test/time_manager_tests.jl @@ -47,130 +47,3 @@ end @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, # conservation_checks - (Int(0), Int(1000)), # tspan - Int(200), # Δt_cpl - (;), # model_sims - (;), # mode - (;), # callbacks - (;), # dirs - nothing, # turbulent_fluxes - nothing, # thermo_params - nothing, # amip_diags_handler - ) - @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, # conservation_checks - (Int(0), Int(1000)), # tspan - Int(200), # Δt_cpl - (;), # model_sims - (;), # mode - (; - twhohourly_inactive = twhohourly_inactive, - twhohourly_nothing = twhohourly_nothing, - twhohourly_counter = twhohourly_counter, - monthly_counter = monthly_counter, - ), # callbacks - (;), # dirs - nothing, # turbulent_fluxes - nothing, # thermo_params - nothing, # amip_diags_handler - ) - - 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, # conservation_checks - (Int(0), Int(1000)), # tspan - Int(200), # Δt_cpl - (;), # model_sims - (;), # mode - (;), # callbacks - (;), # dirs - nothing, # turbulent_fluxes - nothing, # thermo_params - nothing, # amip_diags_handler - ) - - TimeManager.update_firstdayofmonth!(cs, nothing) - @test cs.dates.date1[1] == Dates.firstdayofmonth(date0) + Dates.Month(1) - -end