From 5d1154c47b0f505e128dab61386b3b6308aef8b1 Mon Sep 17 00:00:00 2001 From: Julia Sloan Date: Fri, 15 Nov 2024 16:19:42 -0800 Subject: [PATCH] add functions for config/arg parsing This commit isolates the argument and configuration file parsing to functions. Since we have one configuration file each for the coupled simulation and for the atmosphere, we end up with the following functions, as well as smaller helper functions. These are located in the new `experiments/ClimaEarth/arg_parsing.jl file`. - `get_coupler_config`, `get_coupler_args`, `get_atmos_args` These functions are called from the driver `run_amip.jl`, where the returned arguments are unpacked. After that point, the config files are not accessed further down in the driver, so we have isolated the config file access to the initial step. --- .buildkite/pipeline.yml | 8 + config/amip_configs/amip.yml | 2 +- config/benchmark_configs/amip_diagedmf.yml | 2 +- config/benchmark_configs/amip_diagedmf_io.yml | 2 +- .../climaatmos_diagedmf_io.yml | 2 +- config/ci_configs/amip_albedo_function.yml | 2 +- .../ci_configs/amip_albedo_temporal_map.yml | 2 +- .../amip_albedo_temporal_map_1M.yml | 2 +- config/ci_configs/amip_coarse_ft32.yml | 2 - .../amip_coarse_ft64_hourly_checkpoints.yml | 2 - ...coarse_ft64_hourly_checkpoints_restart.yml | 2 - config/ci_configs/amip_coarse_mpi.yml | 2 - config/ci_configs/amip_component_dts.yml | 2 +- config/ci_configs/amip_default.yml | 2 +- config/ci_configs/amip_n1_shortrun.yml | 2 +- .../amip_target_topo_diagedmf_shortrun.yml | 2 +- config/ci_configs/interactive_debug.yml | 19 -- .../ci_configs/slabplanet_albedo_function.yml | 2 +- .../slabplanet_albedo_temporal_map.yml | 2 +- config/ci_configs/slabplanet_atmos_diags.yml | 6 +- config/ci_configs/slabplanet_default.yml | 4 +- config/ci_configs/slabplanet_dry_norad.yml | 2 +- config/ci_configs/slabplanet_eisenman.yml | 2 +- config/ci_configs/slabplanet_ft32.yml | 2 +- config/ci_configs/slabplanet_nonmono.yml | 2 +- .../slabplanet_partitioned_fluxes.yml | 2 +- .../slabplanet_realinsol_rayleigh.yml | 2 +- config/longrun_configs/amip_target.yml | 2 +- config/longrun_configs/amip_target_topo.yml | 2 +- .../amip_target_topo_diagedmf_cpu.yml | 2 +- .../amip_target_topo_diagedmf_gpu.yml | 2 +- .../longrun_configs/longrun_amip_dyamond.yml | 2 +- .../slabplanet_aqua_atmos_sf_couple.yml | 2 +- .../slabplanet_aqua_atmos_sf_nocouple.yml | 2 +- .../slabplanet_aqua_coupler_sf.yml | 2 +- .../slabplanet_aqua_coupler_sf_evolve_ocn.yml | 2 +- .../slabplanet_aqua_target.yml | 2 +- .../slabplanet_aqua_target_evolve_ocn.yml | 2 +- .../slabplanet_aqua_target_nocouple.yml | 2 +- .../slabplanet_coupler_sf_evolve_ocn.yml | 2 +- config/longrun_configs/slabplanet_target.yml | 2 +- .../slabplanet_target_evolve_ocn.yml | 2 +- config/longrun_configs/slabplanet_terra.yml | 2 +- config/nightly_configs/amip_coarse.yml | 2 +- config/nightly_configs/amip_coarse_random.yml | 2 +- experiments/ClimaEarth/cli_options.jl | 175 ++++++----- .../components/atmosphere/climaatmos.jl | 31 +- experiments/ClimaEarth/run_amip.jl | 217 +++++--------- .../ClimaEarth/run_cloudless_aquaplanet.jl | 9 +- .../ClimaEarth/run_cloudy_aquaplanet.jl | 9 +- .../ClimaEarth/run_cloudy_slabplanet.jl | 9 +- experiments/ClimaEarth/run_dry_held_suarez.jl | 9 +- .../ClimaEarth/run_moist_held_suarez.jl | 9 +- experiments/ClimaEarth/test/amip_test.yml | 2 +- experiments/ClimaEarth/user_io/arg_parsing.jl | 274 ++++++++++++++++++ .../{ci_plots.jl => diagnostics_plots.jl} | 8 +- src/Utilities.jl | 9 +- 57 files changed, 553 insertions(+), 327 deletions(-) delete mode 100644 config/ci_configs/interactive_debug.yml create mode 100644 experiments/ClimaEarth/user_io/arg_parsing.jl rename experiments/ClimaEarth/user_io/{ci_plots.jl => diagnostics_plots.jl} (96%) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 59a4c7c7cd..13f667fc5b 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -188,6 +188,14 @@ steps: # AMIP EXPERIMENTS + # Test default behavior with no config file or job ID provided + - label: "AMIP: default" + key: "amip_default" + command: "julia --color=yes --project=experiments/ClimaEarth/ experiments/ClimaEarth/run_amip.jl" + artifact_paths: "experiments/ClimaEarth/output/amip_default/artifacts/*" + agents: + slurm_mem: 20GB + - label: "AMIP target: albedo from function" key: "target_amip_albedo_function" command: "julia --color=yes --project=experiments/ClimaEarth/ experiments/ClimaEarth/run_amip.jl --config_file $CONFIG_PATH/amip_albedo_function.yml --job_id target_amip_albedo_function" diff --git a/config/amip_configs/amip.yml b/config/amip_configs/amip.yml index 3bfbf92d7e..2648ea5d43 100644 --- a/config/amip_configs/amip.yml +++ b/config/amip_configs/amip.yml @@ -3,7 +3,7 @@ albedo_model: "CouplerAlbedo" atmos_config_file: "config/longrun_configs/amip_target_diagedmf.yml" coupler_toml_file: "toml/amip.toml" dt: "120secs" -dt_cpl: 120 +dt_cpl: "120secs" dt_save_state_to_disk: "30days" dt_save_to_sol: "30days" dz_bottom: 30.0 diff --git a/config/benchmark_configs/amip_diagedmf.yml b/config/benchmark_configs/amip_diagedmf.yml index e9aff1243d..13535b9b22 100644 --- a/config/benchmark_configs/amip_diagedmf.yml +++ b/config/benchmark_configs/amip_diagedmf.yml @@ -2,7 +2,7 @@ FLOAT_TYPE: "Float32" atmos_config_file: "config/benchmark_configs/climaatmos_diagedmf.yml" atmos_config_repo: "ClimaCoupler" dt: "120secs" -dt_cpl: 120 +dt_cpl: "120secs" dt_save_state_to_disk: "Inf" dt_save_to_sol: "Inf" energy_check: false diff --git a/config/benchmark_configs/amip_diagedmf_io.yml b/config/benchmark_configs/amip_diagedmf_io.yml index b776fdaa8a..61040582d0 100644 --- a/config/benchmark_configs/amip_diagedmf_io.yml +++ b/config/benchmark_configs/amip_diagedmf_io.yml @@ -2,7 +2,7 @@ FLOAT_TYPE: "Float32" atmos_config_file: "config/benchmark_configs/climaatmos_diagedmf_io.yml" atmos_config_repo: "ClimaCoupler" dt: "120secs" -dt_cpl: 120 +dt_cpl: "120secs" dt_save_state_to_disk: "12hours" dt_save_to_sol: "12hours" energy_check: false diff --git a/config/benchmark_configs/climaatmos_diagedmf_io.yml b/config/benchmark_configs/climaatmos_diagedmf_io.yml index 506cafec76..a9a5cc8a3c 100644 --- a/config/benchmark_configs/climaatmos_diagedmf_io.yml +++ b/config/benchmark_configs/climaatmos_diagedmf_io.yml @@ -32,6 +32,6 @@ output_default_diagnostics: false prescribe_ozone: true aerosol_radiation: true prescribed_aerosols: ["CB1", "CB2", "DST01", "OC1", "OC2", "SO4", "SSLT01"] -diagnostics: +extra_atmos_diagnostics: - short_name: [pfull, rsut, rlut, pr, hus, rv] period: 10hours diff --git a/config/ci_configs/amip_albedo_function.yml b/config/ci_configs/amip_albedo_function.yml index 8fb7219f7e..bf32083c70 100644 --- a/config/ci_configs/amip_albedo_function.yml +++ b/config/ci_configs/amip_albedo_function.yml @@ -1,6 +1,6 @@ apply_limiter: false dt: "150secs" -dt_cpl: 150 +dt_cpl: "150secs" dt_rad: "1hours" dt_save_to_sol: "1days" dz_bottom: 30 diff --git a/config/ci_configs/amip_albedo_temporal_map.yml b/config/ci_configs/amip_albedo_temporal_map.yml index 928acb1933..f481bc7763 100644 --- a/config/ci_configs/amip_albedo_temporal_map.yml +++ b/config/ci_configs/amip_albedo_temporal_map.yml @@ -1,6 +1,6 @@ apply_limiter: false dt: "150secs" -dt_cpl: 150 +dt_cpl: "150secs" dt_rad: "1hours" dt_save_to_sol: "1days" dz_bottom: 30 diff --git a/config/ci_configs/amip_albedo_temporal_map_1M.yml b/config/ci_configs/amip_albedo_temporal_map_1M.yml index 5353a753b9..fc424f4287 100644 --- a/config/ci_configs/amip_albedo_temporal_map_1M.yml +++ b/config/ci_configs/amip_albedo_temporal_map_1M.yml @@ -1,6 +1,6 @@ apply_limiter: false dt: "150secs" -dt_cpl: 150 +dt_cpl: "150secs" dt_rad: "1hours" dt_save_to_sol: "1days" dz_bottom: 30 diff --git a/config/ci_configs/amip_coarse_ft32.yml b/config/ci_configs/amip_coarse_ft32.yml index b7fa9d1120..283fdd80ea 100644 --- a/config/ci_configs/amip_coarse_ft32.yml +++ b/config/ci_configs/amip_coarse_ft32.yml @@ -1,7 +1,5 @@ FLOAT_TYPE: "Float32" apply_limiter: false -dt: "400secs" -dt_cpl: 400 dt_save_to_sol: "100days" energy_check: false h_elem: 6 diff --git a/config/ci_configs/amip_coarse_ft64_hourly_checkpoints.yml b/config/ci_configs/amip_coarse_ft64_hourly_checkpoints.yml index 26e1ab41ee..e1f5cd8c6a 100644 --- a/config/ci_configs/amip_coarse_ft64_hourly_checkpoints.yml +++ b/config/ci_configs/amip_coarse_ft64_hourly_checkpoints.yml @@ -1,6 +1,4 @@ apply_limiter: false -dt: "400secs" -dt_cpl: 400 dt_save_to_sol: "1days" energy_check: false h_elem: 6 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 0fc0a6b844..03367c0b21 100644 --- a/config/ci_configs/amip_coarse_ft64_hourly_checkpoints_restart.yml +++ b/config/ci_configs/amip_coarse_ft64_hourly_checkpoints_restart.yml @@ -1,6 +1,4 @@ apply_limiter: false -dt: "400secs" -dt_cpl: 400 dt_save_restart: "10days" dt_save_to_sol: "1days" energy_check: false diff --git a/config/ci_configs/amip_coarse_mpi.yml b/config/ci_configs/amip_coarse_mpi.yml index c5fea07a2e..5894fc1fe4 100644 --- a/config/ci_configs/amip_coarse_mpi.yml +++ b/config/ci_configs/amip_coarse_mpi.yml @@ -1,6 +1,4 @@ apply_limiter: false -dt: "400secs" -dt_cpl: 400 dt_save_to_sol: "1days" energy_check: false h_elem: 6 diff --git a/config/ci_configs/amip_component_dts.yml b/config/ci_configs/amip_component_dts.yml index 01725c31bd..ea0c3d5052 100644 --- a/config/ci_configs/amip_component_dts.yml +++ b/config/ci_configs/amip_component_dts.yml @@ -3,7 +3,7 @@ dt_atmos: "150secs" dt_land: "50secs" dt_ocean: "30secs" dt_seaice: "75secs" -dt_cpl: 150 +dt_cpl: "150secs" dt_rad: "1hours" dt_save_to_sol: "1days" dz_bottom: 30 diff --git a/config/ci_configs/amip_default.yml b/config/ci_configs/amip_default.yml index f88629ea37..9369e9e02a 100644 --- a/config/ci_configs/amip_default.yml +++ b/config/ci_configs/amip_default.yml @@ -1,6 +1,6 @@ apply_limiter: false dt: "150secs" -dt_cpl: 150 +dt_cpl: "150secs" dt_rad: "1hours" dt_save_to_sol: "1days" dz_bottom: 30 diff --git a/config/ci_configs/amip_n1_shortrun.yml b/config/ci_configs/amip_n1_shortrun.yml index 6595005e04..dc09486662 100644 --- a/config/ci_configs/amip_n1_shortrun.yml +++ b/config/ci_configs/amip_n1_shortrun.yml @@ -1,6 +1,6 @@ apply_limiter: false dt: "150secs" -dt_cpl: 150 +dt_cpl: "150secs" dt_rad: "1hours" dt_save_to_sol: "1days" dz_bottom: 30 diff --git a/config/ci_configs/amip_target_topo_diagedmf_shortrun.yml b/config/ci_configs/amip_target_topo_diagedmf_shortrun.yml index d01d2025c0..e73477831f 100644 --- a/config/ci_configs/amip_target_topo_diagedmf_shortrun.yml +++ b/config/ci_configs/amip_target_topo_diagedmf_shortrun.yml @@ -3,7 +3,7 @@ apply_limiter: false atmos_config_file: "config/longrun_configs/longrun_aquaplanet_allsky_diagedmf_0M.yml" dt: "100secs" dt_cloud_fraction: "1hours" -dt_cpl: 100 +dt_cpl: "100secs" dt_rad: "1hours" dt_save_state_to_disk: "1days" dt_save_to_sol: "1days" diff --git a/config/ci_configs/interactive_debug.yml b/config/ci_configs/interactive_debug.yml deleted file mode 100644 index 70abc041f9..0000000000 --- a/config/ci_configs/interactive_debug.yml +++ /dev/null @@ -1,19 +0,0 @@ -apply_limiter: false -coupler_output_dir: "experiments/ClimaEarth/output" -dt: "200secs" -dt_cpl: 200 -dt_save_to_sol: "10days" -energy_check: true -evolving_ocean: true -h_elem: 4 -land_albedo_type: "function" -mode_name: "slabplanet" -moist: "equil" -mono_surface: true -precip_model: "0M" -rad: "gray" -start_date: "20100101" -surface_setup: "PrescribedSurface" -t_end: "20days" -turb_flux_partition: "CombinedStateFluxesMOST" -vert_diff: "true" diff --git a/config/ci_configs/slabplanet_albedo_function.yml b/config/ci_configs/slabplanet_albedo_function.yml index 7b499e1002..8a4c096653 100644 --- a/config/ci_configs/slabplanet_albedo_function.yml +++ b/config/ci_configs/slabplanet_albedo_function.yml @@ -1,6 +1,6 @@ apply_limiter: false dt: "200secs" -dt_cpl: 200 +dt_cpl: "200secs" dt_save_to_sol: "3600secs" energy_check: true h_elem: 4 diff --git a/config/ci_configs/slabplanet_albedo_temporal_map.yml b/config/ci_configs/slabplanet_albedo_temporal_map.yml index 96ea2d5c43..f7fe507e86 100644 --- a/config/ci_configs/slabplanet_albedo_temporal_map.yml +++ b/config/ci_configs/slabplanet_albedo_temporal_map.yml @@ -1,6 +1,6 @@ apply_limiter: false dt: "200secs" -dt_cpl: 200 +dt_cpl: "200secs" dt_save_to_sol: "3600secs" energy_check: true h_elem: 4 diff --git a/config/ci_configs/slabplanet_atmos_diags.yml b/config/ci_configs/slabplanet_atmos_diags.yml index 2a6e83e3b9..ecb0efbfac 100644 --- a/config/ci_configs/slabplanet_atmos_diags.yml +++ b/config/ci_configs/slabplanet_atmos_diags.yml @@ -1,7 +1,7 @@ apply_limiter: false -ci_plots: true +plot_diagnostics: true dt: "200secs" -dt_cpl: 200 +dt_cpl: "200secs" dt_save_to_sol: "9days" energy_check: true h_elem: 4 @@ -12,7 +12,7 @@ precip_model: "0M" rad: "gray" t_end: "10days" vert_diff: "true" -diagnostics: +extra_atmos_diagnostics: - short_name: [mse, lr, edt, evu, ts, mass_strf, stab, vt, egr, toa_fluxes_net] reduction_time: average period: 1days diff --git a/config/ci_configs/slabplanet_default.yml b/config/ci_configs/slabplanet_default.yml index 19aea4a20f..3dd0d678c6 100644 --- a/config/ci_configs/slabplanet_default.yml +++ b/config/ci_configs/slabplanet_default.yml @@ -1,6 +1,6 @@ apply_limiter: false dt: "200secs" -dt_cpl: 200 +dt_cpl: "200secs" dt_save_to_sol: "9days" energy_check: true h_elem: 4 @@ -12,6 +12,6 @@ rad: "gray" t_end: "10days" vert_diff: "true" output_default_diagnostics: false -diagnostics: +extra_atmos_diagnostics: - short_name: [mse, lr, edt, evu, hfes, evspsbl, ts] period: 1days diff --git a/config/ci_configs/slabplanet_dry_norad.yml b/config/ci_configs/slabplanet_dry_norad.yml index 63ab9b298a..7658748e2f 100644 --- a/config/ci_configs/slabplanet_dry_norad.yml +++ b/config/ci_configs/slabplanet_dry_norad.yml @@ -1,7 +1,7 @@ apply_limiter: false conservation_softfail: true dt: "200secs" -dt_cpl: 200 +dt_cpl: "200secs" dt_save_to_sol: "9days" energy_check: true h_elem: 4 diff --git a/config/ci_configs/slabplanet_eisenman.yml b/config/ci_configs/slabplanet_eisenman.yml index 049a455d20..c277beed05 100644 --- a/config/ci_configs/slabplanet_eisenman.yml +++ b/config/ci_configs/slabplanet_eisenman.yml @@ -1,7 +1,7 @@ apply_limiter: false conservation_softfail: true dt: "200secs" -dt_cpl: 200 +dt_cpl: "200secs" dt_save_to_sol: "9days" energy_check: true h_elem: 6 diff --git a/config/ci_configs/slabplanet_ft32.yml b/config/ci_configs/slabplanet_ft32.yml index 264c95c506..810c70fde7 100644 --- a/config/ci_configs/slabplanet_ft32.yml +++ b/config/ci_configs/slabplanet_ft32.yml @@ -1,7 +1,7 @@ FLOAT_TYPE: "Float32" apply_limiter: false dt: "200secs" -dt_cpl: 200 +dt_cpl: "200secs" dt_save_to_sol: "9days" energy_check: true h_elem: 4 diff --git a/config/ci_configs/slabplanet_nonmono.yml b/config/ci_configs/slabplanet_nonmono.yml index 8cbb9b2a5b..56f522126f 100644 --- a/config/ci_configs/slabplanet_nonmono.yml +++ b/config/ci_configs/slabplanet_nonmono.yml @@ -1,6 +1,6 @@ apply_limiter: false dt: "200secs" -dt_cpl: 200 +dt_cpl: "200secs" dt_save_to_sol: "9days" energy_check: true h_elem: 4 diff --git a/config/ci_configs/slabplanet_partitioned_fluxes.yml b/config/ci_configs/slabplanet_partitioned_fluxes.yml index 4978872107..c6e8b9cd06 100644 --- a/config/ci_configs/slabplanet_partitioned_fluxes.yml +++ b/config/ci_configs/slabplanet_partitioned_fluxes.yml @@ -1,6 +1,6 @@ apply_limiter: false dt: "200secs" -dt_cpl: 200 +dt_cpl: "200secs" dt_save_to_sol: "9days" energy_check: true h_elem: 4 diff --git a/config/ci_configs/slabplanet_realinsol_rayleigh.yml b/config/ci_configs/slabplanet_realinsol_rayleigh.yml index 836dca4296..72eb630c54 100644 --- a/config/ci_configs/slabplanet_realinsol_rayleigh.yml +++ b/config/ci_configs/slabplanet_realinsol_rayleigh.yml @@ -1,7 +1,7 @@ apply_limiter: false conservation_softfail: true dt: "200secs" -dt_cpl: 3600 +dt_cpl: "3600secs" dt_rad: "6hours" dt_save_to_sol: "3600secs" energy_check: true diff --git a/config/longrun_configs/amip_target.yml b/config/longrun_configs/amip_target.yml index 898a0e44e8..2c22691535 100644 --- a/config/longrun_configs/amip_target.yml +++ b/config/longrun_configs/amip_target.yml @@ -2,7 +2,7 @@ FLOAT_TYPE: "Float32" atmos_config_file: "config/longrun_configs/longrun_aquaplanet_allsky_0M.yml" dt: "120secs" dt_cloud_fraction: "1hours" -dt_cpl: 120 +dt_cpl: "120secs" dt_rad: "1hours" dt_save_state_to_disk: "20days" energy_check: false diff --git a/config/longrun_configs/amip_target_topo.yml b/config/longrun_configs/amip_target_topo.yml index 5e01bc6632..1081e82788 100644 --- a/config/longrun_configs/amip_target_topo.yml +++ b/config/longrun_configs/amip_target_topo.yml @@ -3,7 +3,7 @@ albedo_model: "CouplerAlbedo" atmos_config_file: "config/longrun_configs/longrun_aquaplanet_allsky_0M_earth.yml" dt: "120secs" dt_cloud_fraction: "1hours" -dt_cpl: 120 +dt_cpl: "120secs" dt_rad: "1hours" dt_save_state_to_disk: "20days" energy_check: false diff --git a/config/longrun_configs/amip_target_topo_diagedmf_cpu.yml b/config/longrun_configs/amip_target_topo_diagedmf_cpu.yml index f8c827515a..bca89d6cfa 100644 --- a/config/longrun_configs/amip_target_topo_diagedmf_cpu.yml +++ b/config/longrun_configs/amip_target_topo_diagedmf_cpu.yml @@ -3,7 +3,7 @@ albedo_model: "CouplerAlbedo" atmos_config_file: "config/longrun_configs/longrun_aquaplanet_allsky_diagedmf_0M.yml" dt: "120secs" dt_cloud_fraction: "1hours" -dt_cpl: 120 +dt_cpl: "120secs" dt_rad: "1hours" dt_save_state_to_disk: "20days" energy_check: false diff --git a/config/longrun_configs/amip_target_topo_diagedmf_gpu.yml b/config/longrun_configs/amip_target_topo_diagedmf_gpu.yml index cdf0b8f8c8..439fe0ebbc 100644 --- a/config/longrun_configs/amip_target_topo_diagedmf_gpu.yml +++ b/config/longrun_configs/amip_target_topo_diagedmf_gpu.yml @@ -3,7 +3,7 @@ albedo_model: "CouplerAlbedo" atmos_config_file: "config/longrun_configs/longrun_aquaplanet_allsky_diagedmf_0M.yml" dt: "120secs" dt_cloud_fraction: "1hours" -dt_cpl: 120 +dt_cpl: "120secs" dt_rad: "1hours" dt_save_state_to_disk: "20days" energy_check: false diff --git a/config/longrun_configs/longrun_amip_dyamond.yml b/config/longrun_configs/longrun_amip_dyamond.yml index b23c0f2216..68cd8e940e 100644 --- a/config/longrun_configs/longrun_amip_dyamond.yml +++ b/config/longrun_configs/longrun_amip_dyamond.yml @@ -1,6 +1,6 @@ atmos_config_file: "config/longrun_configs/longrun_aquaplanet_dyamond.yml" dt: "30secs" -dt_cpl: 30 +dt_cpl: "30secs" dt_save_state_to_disk: "0.5days" dt_save_to_sol: "0.5days" energy_check: false diff --git a/config/longrun_configs/slabplanet_aqua_atmos_sf_couple.yml b/config/longrun_configs/slabplanet_aqua_atmos_sf_couple.yml index 31d0f5d9cb..7493bbd6db 100644 --- a/config/longrun_configs/slabplanet_aqua_atmos_sf_couple.yml +++ b/config/longrun_configs/slabplanet_aqua_atmos_sf_couple.yml @@ -1,6 +1,6 @@ conservation_softfail: true dt: "200secs" -dt_cpl: 200 +dt_cpl: "200secs" dt_save_to_sol: "10days" energy_check: true evolving_ocean: false diff --git a/config/longrun_configs/slabplanet_aqua_atmos_sf_nocouple.yml b/config/longrun_configs/slabplanet_aqua_atmos_sf_nocouple.yml index 4bec7a7b2a..764274d669 100644 --- a/config/longrun_configs/slabplanet_aqua_atmos_sf_nocouple.yml +++ b/config/longrun_configs/slabplanet_aqua_atmos_sf_nocouple.yml @@ -1,6 +1,6 @@ conservation_softfail: true dt: "200secs" -dt_cpl: 1728000 +dt_cpl: "1728000secs" dt_save_to_sol: "10days" energy_check: true evolving_ocean: false diff --git a/config/longrun_configs/slabplanet_aqua_coupler_sf.yml b/config/longrun_configs/slabplanet_aqua_coupler_sf.yml index 2ce52bb35f..ae5bcf64cf 100644 --- a/config/longrun_configs/slabplanet_aqua_coupler_sf.yml +++ b/config/longrun_configs/slabplanet_aqua_coupler_sf.yml @@ -1,6 +1,6 @@ conservation_softfail: true dt: "200secs" -dt_cpl: 200 +dt_cpl: "200secs" dt_save_to_sol: "10days" energy_check: true evolving_ocean: false diff --git a/config/longrun_configs/slabplanet_aqua_coupler_sf_evolve_ocn.yml b/config/longrun_configs/slabplanet_aqua_coupler_sf_evolve_ocn.yml index a94fd257ba..4aca82c82f 100644 --- a/config/longrun_configs/slabplanet_aqua_coupler_sf_evolve_ocn.yml +++ b/config/longrun_configs/slabplanet_aqua_coupler_sf_evolve_ocn.yml @@ -1,5 +1,5 @@ dt: "200secs" -dt_cpl: 200 +dt_cpl: "200secs" dt_save_to_sol: "10days" energy_check: true evolving_ocean: true diff --git a/config/longrun_configs/slabplanet_aqua_target.yml b/config/longrun_configs/slabplanet_aqua_target.yml index 3598204e93..59eb173088 100644 --- a/config/longrun_configs/slabplanet_aqua_target.yml +++ b/config/longrun_configs/slabplanet_aqua_target.yml @@ -1,7 +1,7 @@ atmos_config_file: "config/longrun_configs/longrun_aquaplanet_allsky_0M.yml" dt: "120secs" dt_cloud_fraction: "1hours" -dt_cpl: 120 +dt_cpl: "120secs" dt_rad: "1hours" dt_save_state_to_disk: "20days" dt_save_to_sol: "10days" diff --git a/config/longrun_configs/slabplanet_aqua_target_evolve_ocn.yml b/config/longrun_configs/slabplanet_aqua_target_evolve_ocn.yml index 2e7394dfb0..62a9bbcee4 100644 --- a/config/longrun_configs/slabplanet_aqua_target_evolve_ocn.yml +++ b/config/longrun_configs/slabplanet_aqua_target_evolve_ocn.yml @@ -1,7 +1,7 @@ atmos_config_file: "config/longrun_configs/longrun_aquaplanet_allsky_0M.yml" dt: "120secs" dt_cloud_fraction: "1hours" -dt_cpl: 120 +dt_cpl: "120secs" dt_rad: "1hours" dt_save_state_to_disk: "20days" dt_save_to_sol: "10days" diff --git a/config/longrun_configs/slabplanet_aqua_target_nocouple.yml b/config/longrun_configs/slabplanet_aqua_target_nocouple.yml index 46e658604f..e27ec6e608 100644 --- a/config/longrun_configs/slabplanet_aqua_target_nocouple.yml +++ b/config/longrun_configs/slabplanet_aqua_target_nocouple.yml @@ -1,7 +1,7 @@ atmos_config_file: "config/longrun_configs/longrun_aquaplanet_allsky_0M.yml" dt: "120secs" dt_cloud_fraction: "1hours" -dt_cpl: 10368000 +dt_cpl: "120days" dt_rad: "1hours" dt_save_state_to_disk: "20days" dt_save_to_sol: "10days" diff --git a/config/longrun_configs/slabplanet_coupler_sf_evolve_ocn.yml b/config/longrun_configs/slabplanet_coupler_sf_evolve_ocn.yml index 501b5071a9..a8aafa2cec 100644 --- a/config/longrun_configs/slabplanet_coupler_sf_evolve_ocn.yml +++ b/config/longrun_configs/slabplanet_coupler_sf_evolve_ocn.yml @@ -1,5 +1,5 @@ dt: "200secs" -dt_cpl: 200 +dt_cpl: "200secs" dt_save_to_sol: "10days" energy_check: true evolving_ocean: true diff --git a/config/longrun_configs/slabplanet_target.yml b/config/longrun_configs/slabplanet_target.yml index fb428e0fed..dee938f45d 100644 --- a/config/longrun_configs/slabplanet_target.yml +++ b/config/longrun_configs/slabplanet_target.yml @@ -1,7 +1,7 @@ atmos_config_file: "config/longrun_configs/longrun_aquaplanet_allsky_0M.yml" dt: "120secs" dt_cloud_fraction: "1hours" -dt_cpl: 120 +dt_cpl: "120secs" dt_rad: "1hours" dt_save_state_to_disk: "20days" dt_save_to_sol: "10days" diff --git a/config/longrun_configs/slabplanet_target_evolve_ocn.yml b/config/longrun_configs/slabplanet_target_evolve_ocn.yml index bcf8f4cf3e..bdb88b729e 100644 --- a/config/longrun_configs/slabplanet_target_evolve_ocn.yml +++ b/config/longrun_configs/slabplanet_target_evolve_ocn.yml @@ -1,7 +1,7 @@ atmos_config_file: "config/longrun_configs/longrun_aquaplanet_allsky_0M.yml" dt: "120secs" dt_cloud_fraction: "1hours" -dt_cpl: 120 +dt_cpl: "120secs" dt_rad: "1hours" dt_save_state_to_disk: "20days" dt_save_to_sol: "10days" diff --git a/config/longrun_configs/slabplanet_terra.yml b/config/longrun_configs/slabplanet_terra.yml index 49036dd264..9db3573a9b 100644 --- a/config/longrun_configs/slabplanet_terra.yml +++ b/config/longrun_configs/slabplanet_terra.yml @@ -1,6 +1,6 @@ conservation_softfail: true dt: "200secs" -dt_cpl: 200 +dt_cpl: "200secs" dt_save_to_sol: "10days" energy_check: true h_elem: 4 diff --git a/config/nightly_configs/amip_coarse.yml b/config/nightly_configs/amip_coarse.yml index 8dd3910a0c..e15d9e24ae 100644 --- a/config/nightly_configs/amip_coarse.yml +++ b/config/nightly_configs/amip_coarse.yml @@ -3,7 +3,7 @@ albedo_model: "CouplerAlbedo" atmos_config_file: "config/longrun_configs/amip_target_diagedmf.yml" coupler_toml_file: "toml/amip.toml" dt: "240secs" -dt_cpl: 240 +dt_cpl: "240secs" dt_save_state_to_disk: "30days" dt_save_to_sol: "30days" dz_bottom: 100.0 diff --git a/config/nightly_configs/amip_coarse_random.yml b/config/nightly_configs/amip_coarse_random.yml index 5259572ea3..2a7f772d27 100644 --- a/config/nightly_configs/amip_coarse_random.yml +++ b/config/nightly_configs/amip_coarse_random.yml @@ -3,7 +3,7 @@ albedo_model: "CouplerAlbedo" atmos_config_file: "config/longrun_configs/amip_target_diagedmf.yml" coupler_toml_file: "toml/amip.toml" dt: "240secs" -dt_cpl: 240 +dt_cpl: "240secs" dt_save_state_to_disk: "30days" dt_save_to_sol: "30days" dz_bottom: 100.0 diff --git a/experiments/ClimaEarth/cli_options.jl b/experiments/ClimaEarth/cli_options.jl index ed0e63cc75..ce3f753a6a 100644 --- a/experiments/ClimaEarth/cli_options.jl +++ b/experiments/ClimaEarth/cli_options.jl @@ -2,107 +2,138 @@ import ArgParse function argparse_settings() s = ArgParse.ArgParseSettings() ArgParse.@add_arg_table! s begin - # ClimaCoupler flags - "--dt_cpl" - help = " Coupling time step in seconds" - arg_type = Int - default = 400 - "--dt_atmos" - help = " Atmos simulation time step" - arg_type = String - "--dt_land" - help = " Land simulation time step" - arg_type = String - "--dt_ocean" - help = " Ocean simulation time step" + ### ClimaCoupler flags + # Simulation-identifying information + "--config_file" + help = "A yaml file used to set the configuration of the coupled model [\"config/ci_configs/amip_default.yml\" (default)]" arg_type = String - "--dt_seaice" - help = " Sea Ice simulation time step" + default = "config/ci_configs/amip_default.yml" + "--job_id" + help = "A unique identifier for this run, defaults to the config file name" arg_type = String - "--energy_check" - help = "Boolean flag indicating whether to check energy conservation" - arg_type = Bool - default = false - "--ci_plots" - help = "Boolean flag indicating whether to make CI plots" - arg_type = Bool - default = false - "--conservation_softfail" - help = "Boolean flag indicating whether to soft fail on conservation errors" + default = nothing + "--print_config_dict" + help = "Boolean flag indicating whether to print the final configuration dictionary [`true` (default), `false`]" arg_type = Bool - default = false + default = true "--mode_name" help = "Mode of coupled simulation. [`amip` (default), `slabplanet`, `slabplanet_aqua`, `slabplanet_terra`, `slabplanet_eisenman`]" arg_type = String default = "amip" - "--mono_surface" - help = "Boolean flag indicating whether (1st order) monotone and conservative remapping is applied." + "--coupler_toml_file" + help = "An optional toml file used to overwrite the default model parameters." + default = nothing + # Computational simulation setup information + "--unique_seed" + help = "Boolean flag indicating whether to set the random number seed to a unique value [`false` (default), `true`]" arg_type = Bool default = false - "--turb_flux_partition" - help = "Method to partition turbulent fluxes. [`PartitionedStateFluxes`, `CombinedStateFluxesMOST` (default)]" + "--FLOAT_TYPE" + help = "Floating point precision [`Float64` (default), `Float32`]" arg_type = String - default = "CombinedStateFluxesMOST" + default = "Float64" + "--device" + help = "Device type to use [\"auto\" (default), \"CPUSingleThreaded\", \"CPUMultiThreaded\", \"CUDADevice\"]" + arg_type = String + default = "auto" + # Time information + "--t_end" + help = "End time of the simulation [\"800secs\"; allowed formats: \"Nsecs\", \"Nmins\", \"Nhours\", \"Ndays\", \"Inf\"]" + arg_type = String + default = "800secs" + "--t_start" + help = "Start time of the simulation [\"0secs\" (default); allowed formats: \"Nsecs\", \"Nmins\", \"Nhours\", \"Ndays\", \"Inf\"]" + arg_type = String + default = "0secs" + "--start_date" + help = "Start date of the simulation, in format \"YYYYMMDD\" [\"20100101\" (default)]" + arg_type = String + default = "20000101" + "--dt_cpl" + help = "Coupling time step in seconds [400 (default); allowed formats: \"Nsecs\", \"Nmins\", \"Nhours\", \"Ndays\", \"Inf\"]" + arg_type = String + default = "400secs" + "--dt" + help = "Component model time step [allowed formats: \"Nsecs\", \"Nmins\", \"Nhours\", \"Ndays\", \"Inf\"]" + arg_type = String + default = "400secs" + "--dt_atmos" + help = "Atmos simulation time step (alternative to `dt`; no default) [allowed formats: \"Nsecs\", \"Nmins\", \"Nhours\", \"Ndays\", \"Inf\"]" + arg_type = String + "--dt_land" + help = "Land simulation time step (alternative to `dt`; no default) [allowed formats: \"Nsecs\", \"Nmins\", \"Nhours\", \"Ndays\", \"Inf\"]" + arg_type = String + "--dt_ocean" + help = "Ocean simulation time step (alternative to `dt`; no default) [allowed formats: \"Nsecs\", \"Nmins\", \"Nhours\", \"Ndays\", \"Inf\"]" + arg_type = String + "--dt_seaice" + help = "Sea ice simulation time step (alternative to `dt`; no default) [allowed formats: \"Nsecs\", \"Nmins\", \"Nhours\", \"Ndays\", \"Inf\"]" + arg_type = String + "--dt_save_to_sol" + 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 intervals of 1 hour or multiple hours" + 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 (20 days by default)" + help = "Time interval for hourly checkpointing in hours [480 (default)]" arg_type = Int default = 480 - "--coupler_output_dir" - help = "Directory to save output files. Note that TempestRemap fails if interactive and paths are too long." - arg_type = String - default = "experiments/ClimaEarth/output" + # Restart information "--restart_dir" help = "Directory containing restart files" arg_type = String - default = "unspecified" + default = nothing "--restart_t" - help = "Restart time" + help = "Time in seconds rounded to the nearest index to use at `t_start` for restarted simulation [0 (default)]" arg_type = Int default = 0 - "--config_file" - help = "A yaml file used to set the configuration of the coupled model" - arg_type = String - "--job_id" - help = "A unique identifier for this run" - arg_type = String - default = nothing - "--print_config_dict" - help = "Boolean flag indicating whether to print the final configuration dictionary" + # Diagnostics information + "--use_coupler_diagnostics" + help = "Boolean flag indicating whether to compute and output coupler diagnostics [`true` (default), `false`]" arg_type = Bool default = true - "--FLOAT_TYPE" - help = "Floating point precision [`Float64` (default), `Float32`]" - arg_type = String - default = "Float64" - "--coupler_toml_file" - help = "A toml file used to overwrite the model parameters. If nothing is specified, the default parameters are used." + # Physical simulation information "--evolving_ocean" - help = "Boolean flag indicating whether to use a dynamic slab ocean model or constant surface temperatures" + help = "Boolean flag indicating whether to use a dynamic slab ocean model, as opposed to constant surface temperatures [`true` (default), `false`]" arg_type = Bool default = true - "--device" - help = "Device type to use [`auto` (default) `CPUSingleThreaded`, `CPUMultiThreaded`, `CUDADevice`]" + "--mono_surface" + help = "Boolean flag indicating whether (1st order) monotone and conservative remapping is applied. [`false` (default), `true`]" + arg_type = Bool + default = false + "--turb_flux_partition" + help = "Method to partition turbulent fluxes. [`PartitionedStateFluxes`, `CombinedStateFluxesMOST` (default)]" arg_type = String - default = "auto" - "--use_coupler_diagnostics" - help = "Boolean flag indicating whether to compute and output coupler diagnostics [`true` (default), `false`]" + default = "CombinedStateFluxesMOST" + # Conservation information + "--energy_check" + help = "Boolean flag indicating whether to check energy conservation [`false` (default), `true`]" arg_type = Bool - default = true - "--unique_seed" - help = "Boolean flag indicating whether to set the random number seed to a unique value [`false` (default), `true`]" + default = false + "--conservation_softfail" + help = "Boolean flag indicating whether to soft fail on conservation errors [`false` (default), `true`]" + arg_type = Bool + default = false + # Output information + "--coupler_output_dir" + help = "Directory to save output files. Note that TempestRemap fails if interactive and paths are too long. [\"experiments/ClimaEarth/output\" (default)]" + arg_type = String + default = "experiments/ClimaEarth/output" + "--plot_diagnostics" + help = "Boolean flag indicating whether to make plot diagnostics [`false` (default), `true`]" arg_type = Bool default = false # ClimaAtmos specific "--surface_setup" - help = "Triggers ClimaAtmos into the coupled mode [`PrescribedSurface` (default)]" # retained here for standalone Atmos benchmarks + help = "Triggers ClimaAtmos into the coupled mode [`PrescribedSurface` (default), `DefaultMoninObukhov`]" # retained here for standalone Atmos benchmarks arg_type = String default = "PrescribedSurface" "--atmos_config_file" - help = "A yaml file used to set the atmospheric model configuration. If nothing is specified, the default configuration is used." + help = "An optional YAML file used to overwrite the default model parameters." "--albedo_model" help = "Type of albedo model. [`ConstantAlbedo`, `RegressionFunctionAlbedo`, `CouplerAlbedo` (default)]" arg_type = String @@ -111,15 +142,19 @@ function argparse_settings() help = "The repository containing the ClimaAtmos configuration file to use [`ClimaAtmos` (default), `ClimaCoupler`]" arg_type = String default = "ClimaAtmos" - # ClimaLand specific - "--land_albedo_type" - help = "Access land surface albedo information from data file. [`map_static` (default), `function`, `map_temporal`]" - arg_type = String - default = "map_static" # to be replaced by land config file, when available + "--extra_atmos_diagnostics" + help = "List of dictionaries containing information about additional atmosphere diagnostics to output [nothing (default)]" + arg_type = Vector{Dict{Any, Any}} + default = [] + ### ClimaLand specific "--land_domain_type" help = "Type of land domain. [`sphere` (default), `single_column`]" arg_type = String default = "sphere" + "--land_albedo_type" + help = "Access land surface albedo information from data file. [`map_static` (default), `function`, `map_temporal`]" + arg_type = String + default = "map_static" # to be replaced by land config file, when available "--land_temperature_anomaly" help = "Type of temperature anomaly for bucket model. [`amip`, `aquaplanet` (default)]" arg_type = String diff --git a/experiments/ClimaEarth/components/atmosphere/climaatmos.jl b/experiments/ClimaEarth/components/atmosphere/climaatmos.jl index 6e60e90daf..cad9f7ec4c 100644 --- a/experiments/ClimaEarth/components/atmosphere/climaatmos.jl +++ b/experiments/ClimaEarth/components/atmosphere/climaatmos.jl @@ -296,10 +296,22 @@ FluxCalculator.get_surface_params(sim::ClimaAtmosSimulation) = CAP.surface_fluxe get_atmos_config_dict(coupler_dict::Dict, job_id::String) Returns the specified atmospheric configuration (`atmos_config`) overwitten by arguments -in the coupler dictionary (`config_dict`). The returned `atmos_config` dictionary will then be passed to CA.AtmosConfig(). -The `atmos_config_repo` flag allows us to -use a configuration specified within the ClimaCoupler repo, which is useful for direct +in the coupler dictionary (`config_dict`). +The returned `atmos_config` dictionary will then be used to set up the atmosphere simulation. + +The `atmos_config_repo` flag allows us to use a configuration specified within +either the ClimaCoupler or ClimaAtmos repos, which is useful for direct coupled/atmos-only comparisons. + +In this function, parameters are overwritten in a specific order, from lowest to highest priority: + 1. Default atmos config + 2. Provided atmos config file (if any) + 3. TOML parameter file + 5. Output directory and timestep are set explicitly based on the coupler config + +The TOML parameter file to use is chosen using the following priority: +If a coupler TOML file is provided, it is used. Otherwise we use an atmos TOML +file if it's provided. If neither is provided, we use a default coupler TOML file. """ function get_atmos_config_dict(coupler_dict::Dict, job_id::String) atmos_config_file = coupler_dict["atmos_config_file"] @@ -344,23 +356,24 @@ function get_atmos_config_dict(coupler_dict::Dict, job_id::String) atmos_config = merge(atmos_config, Dict("toml" => [toml_file])) end - # specify atmos output directory to be inside the coupler output directory + # Specify atmos output directory to be inside the coupler output directory atmos_output_dir = joinpath(coupler_dict["coupler_output_dir"], job_id, "clima_atmos") + atmos_config["output_dir"] = atmos_output_dir + + # Access extra atmosphere diagnostics from coupler so we can rename for atmos code + atmos_config["diagnostics"] = coupler_dict["extra_atmos_diagnostics"] - # merge configs - # (if there are common keys, the last dictionary in the `merge` arguments takes precedence) # The Atmos `get_simulation` function expects the atmos config to contains its timestep size # in the `dt` field. If there is a `dt_atmos` field in coupler_dict, we add it to the atmos config as `dt` dt_atmos = haskey(coupler_dict, "dt_atmos") ? coupler_dict["dt_atmos"] : coupler_dict["dt"] - atmos_config = merge(atmos_config, Dict("output_dir" => atmos_output_dir, "dt" => dt_atmos)) - coupler_config = merge(atmos_config, coupler_dict) + atmos_config["dt"] = dt_atmos # set restart file to the initial file saved in this location if it is not nothing # TODO this is hardcoded and should be fixed once we have a better restart system if !isnothing(atmos_config["restart_file"]) atmos_config["restart_file"] = replace(atmos_config["restart_file"], "active" => "0000") end - return atmos_config, coupler_config + return atmos_config end diff --git a/experiments/ClimaEarth/run_amip.jl b/experiments/ClimaEarth/run_amip.jl index 0265ea0a0d..eb9b6f4a16 100644 --- a/experiments/ClimaEarth/run_amip.jl +++ b/experiments/ClimaEarth/run_amip.jl @@ -38,7 +38,6 @@ We then specify the input data file names. If these are not already downloaded, ## standard packages import Dates -import YAML import DelimitedFiles # ## ClimaESM packages @@ -86,133 +85,58 @@ dictionary and the simulation-specific configuration dictionary, which allows th We can additionally pass the configuration dictionary to the component model initializers, which will then override the default settings of the component models. =# -## coupler simulation default configuration include("cli_options.jl") -parsed_args = parse_commandline(argparse_settings()) - -## modify parsed args for fast testing from REPL #hide -if isinteractive() - parsed_args["config_file"] = - isnothing(parsed_args["config_file"]) ? joinpath(pkg_dir, "config/ci_configs/interactive_debug.yml") : - parsed_args["config_file"] - parsed_args["job_id"] = "interactive_debug" -end - -## the unique job id should be passed in via the command line -job_id = parsed_args["job_id"] -@assert !isnothing(job_id) "job_id must be passed in via the command line" - -## read in config dictionary from file, overriding the coupler defaults in `parsed_args` -config_dict = YAML.load_file(parsed_args["config_file"]) -config_dict = merge(parsed_args, config_dict) +include("user_io/arg_parsing.jl") +config_dict = get_coupler_config() + +# Select the correct timestep for each component model based on which are available +parse_component_dts!(config_dict) +# Add extra diagnostics if specified +add_extra_diagnostics!(config_dict) + +(; + job_id, + mode_name, + random_seed, + FT, + comms_ctx, + t_end, + t_start, + date0, + date, + Δt_cpl, + component_dt_dict, + saveat, + hourly_checkpoint, + hourly_checkpoint_dt, + restart_dir, + restart_t, + use_coupler_diagnostics, + use_land_diagnostics, + calendar_dt, + evolving_ocean, + mono_surface, + turb_flux_partition, + land_domain_type, + land_albedo_type, + land_temperature_anomaly, + energy_check, + conservation_softfail, + output_dir_root, + plot_diagnostics, +) = get_coupler_args(config_dict) -comms_ctx = Utilities.get_comms_context(config_dict) +## get component model dictionaries (if applicable) +## Note this step must come after parsing the coupler config dictionary, since +## some parameters are passed from the coupler config to the component model configs +atmos_config_dict = get_atmos_config_dict(config_dict, job_id) +(; dt_rad, output_default_diagnostics) = get_atmos_args(atmos_config_dict) ## set unique random seed if desired, otherwise use default -random_seed = config_dict["unique_seed"] ? time_ns() : 1234 Random.seed!(random_seed) @info "Random seed set to $(random_seed)" -## set up diagnostics before retrieving atmos config -mode_name = config_dict["mode_name"] -use_coupler_diagnostics = config_dict["use_coupler_diagnostics"] -t_end = Float64(Utilities.time_to_seconds(config_dict["t_end"])) -t_start = 0.0 - -function get_period(t_start, t_end) - sim_duration = t_end - t_start - secs_per_day = 86400 - if sim_duration >= 90 * secs_per_day - # if duration >= 90 days, take monthly means - period = "1months" - calendar_dt = Dates.Month(1) - elseif sim_duration >= 30 * secs_per_day - # if duration >= 30 days, take means over 10 days - period = "10days" - calendar_dt = Dates.Day(10) - elseif sim_duration >= secs_per_day - # if duration >= 1 day, take daily means - period = "1days" - calendar_dt = Dates.Day(1) - else - # if duration < 1 day, take hourly means - period = "1hours" - calendar_dt = Dates.Hour(1) - end - return (period, calendar_dt) -end - -if mode_name == "amip" && use_coupler_diagnostics - @info "Using default AMIP diagnostics" - (period, calendar_dt) = get_period(t_start, t_end) - - !haskey(config_dict, "diagnostics") && (config_dict["diagnostics"] = Vector{Dict{Any, Any}}()) - push!( - config_dict["diagnostics"], - Dict("short_name" => ["toa_fluxes_net"], "reduction_time" => "average", "period" => period), - ) -end - - -## read in some parsed command line arguments, required by this script -energy_check = config_dict["energy_check"] -const FT = config_dict["FLOAT_TYPE"] == "Float64" ? Float64 : Float32 -land_sim_name = "bucket" -t_end = Float64(Utilities.time_to_seconds(config_dict["t_end"])) -t_start = 0.0 tspan = (t_start, t_end) -Δt_cpl = Float64(config_dict["dt_cpl"]) -component_dt_names = ["dt_atmos", "dt_land", "dt_ocean", "dt_seaice"] -component_dt_dict = Dict{String, Float64}() -# check if all component dt's are specified -if all(key -> !isnothing(config_dict[key]), component_dt_names) - # when all component dt's are specified, ignore the dt field - if haskey(config_dict, "dt") - @warn "Removing dt in favor of individual component dt's" - delete!(config_dict, "dt") - end - for key in component_dt_names - component_dt = Float64(Utilities.time_to_seconds(config_dict[key])) - @assert Δt_cpl % component_dt == 0.0 "Coupler dt must be divisible by all component dt's\n dt_cpl = $Δt_cpl\n $key = $component_dt" - component_dt_dict[key] = component_dt - end -else - # when not all component dt's are specified, use the dt field - @assert haskey(config_dict, "dt") "dt or (dt_atmos, dt_land, dt_ocean, and dt_seaice) must be specified" - for key in component_dt_names - if !isnothing(config_dict[key]) - @warn "Removing $key from config in favor of dt because not all component dt's are specified" - end - delete!(config_dict, key) - component_dt_dict[key] = Float64(Utilities.time_to_seconds(config_dict["dt"])) - end -end -## get component model dictionaries (if applicable) -atmos_config_dict, config_dict = get_atmos_config_dict(config_dict, job_id) -atmos_config_object = CA.AtmosConfig(atmos_config_dict) - -saveat = Float64(Utilities.time_to_seconds(config_dict["dt_save_to_sol"])) -date0 = date = Dates.DateTime(config_dict["start_date"], Dates.dateformat"yyyymmdd") -mono_surface = config_dict["mono_surface"] -hourly_checkpoint = config_dict["hourly_checkpoint"] -hourly_checkpoint_dt = config_dict["hourly_checkpoint_dt"] -restart_dir = config_dict["restart_dir"] -restart_t = Int(config_dict["restart_t"]) -evolving_ocean = config_dict["evolving_ocean"] -dt_rad = config_dict["dt_rad"] -use_land_diagnostics = config_dict["use_land_diagnostics"] - -#= -## Setup Communication Context -We set up communication context for CPU single thread/CPU with MPI/GPU. If no device is passed to `ClimaComms.context()` -then `ClimaComms` automatically selects the device from which this code is called. -=# - - -## make sure we don't use animations for GPU runs -if comms_ctx.device isa ClimaComms.CUDADevice - config_dict["anim"] = false -end #= ### I/O Directory Setup @@ -221,14 +145,11 @@ the plots (from postprocessing and the conservation checks) of the simulation wi temporary files will be saved. =# -COUPLER_OUTPUT_DIR = joinpath(config_dict["coupler_output_dir"], job_id) +COUPLER_OUTPUT_DIR = joinpath(output_dir_root, job_id) dir_paths = Utilities.setup_output_dirs(output_dir = COUPLER_OUTPUT_DIR, comms_ctx = comms_ctx) @info "Coupler output directory $(dir_paths.output)" @info "Coupler artifacts directory $(dir_paths.artifacts)" -@info(dir_paths.output) -config_dict["print_config_dict"] && @info(config_dict) - #= ## Data File Paths =# @@ -264,7 +185,7 @@ This uses the `ClimaAtmos.jl` model, with parameterization options specified in Utilities.show_memory_usage() ## init atmos model component -atmos_sim = atmos_init(atmos_config_object); +atmos_sim = atmos_init(CA.AtmosConfig(atmos_config_dict)); # Get surface elevation from `atmos` coordinate field surface_elevation = CC.Fields.level(CC.Fields.coordinate_field(atmos_sim.integrator.u.f).z, CC.Utilities.half) Utilities.show_memory_usage() @@ -329,9 +250,9 @@ if mode_name == "amip" land_sim = bucket_init( FT, tspan, - config_dict["land_domain_type"], - config_dict["land_albedo_type"], - config_dict["land_temperature_anomaly"], + land_domain_type, + land_albedo_type, + land_temperature_anomaly, dir_paths; dt = component_dt_dict["dt_land"], space = boundary_space, @@ -427,9 +348,9 @@ elseif mode_name in ("slabplanet", "slabplanet_aqua", "slabplanet_terra") land_sim = bucket_init( FT, tspan, - config_dict["land_domain_type"], - config_dict["land_albedo_type"], - config_dict["land_temperature_anomaly"], + land_domain_type, + land_albedo_type, + land_temperature_anomaly, dir_paths; dt = component_dt_dict["dt_land"], space = boundary_space, @@ -477,9 +398,9 @@ elseif mode_name == "slabplanet_eisenman" land_sim = bucket_init( FT, tspan, - config_dict["land_domain_type"], - config_dict["land_albedo_type"], - config_dict["land_temperature_anomaly"], + land_domain_type, + land_albedo_type, + land_temperature_anomaly, dir_paths; dt = component_dt_dict["dt_land"], space = boundary_space, @@ -622,9 +543,9 @@ callbacks = Decide on the type of turbulent flux partition, partitioned or combined (see `FluxCalculator` documentation for more details). =# turbulent_fluxes = nothing -if config_dict["turb_flux_partition"] == "PartitionedStateFluxes" +if turb_flux_partition == "PartitionedStateFluxes" turbulent_fluxes = FluxCalculator.PartitionedStateFluxes() -elseif config_dict["turb_flux_partition"] == "CombinedStateFluxesMOST" +elseif turb_flux_partition == "CombinedStateFluxesMOST" turbulent_fluxes = FluxCalculator.CombinedStateFluxesMOST() else error("turb_flux_partition must be either PartitionedStateFluxes or CombinedStateFluxesMOST") @@ -673,7 +594,7 @@ If a restart directory is specified and contains output files from the `checkpoi is specified in the `config_dict` dictionary. The `restart_t` field specifies the time step at which the restart is performed. =# -if restart_dir !== "unspecified" +if !isnothing(restart_dir) for sim in cs.model_sims if Checkpointer.get_model_prog_state(sim) !== nothing Checkpointer.restart_model_state!(sim, comms_ctx, restart_t; input_dir = restart_dir) @@ -891,7 +812,7 @@ end #= ## Postprocessing All postprocessing is performed using the root process only, if applicable. -Our postprocessing consists of outputting a number of plots and animations to visualize the model output. +Our postprocessing consists of outputting a number of plots to visualize the model output. The postprocessing includes: - Energy and water conservation checks (if running SlabPlanet with checks enabled) @@ -910,14 +831,14 @@ if ClimaComms.iamroot(comms_ctx) plot_global_conservation( cs.conservation_checks.energy, cs, - config_dict["conservation_softfail"], + conservation_softfail, figname1 = joinpath(dir_paths.artifacts, "total_energy_bucket.png"), figname2 = joinpath(dir_paths.artifacts, "total_energy_log_bucket.png"), ) plot_global_conservation( cs.conservation_checks.water, cs, - config_dict["conservation_softfail"], + conservation_softfail, figname1 = joinpath(dir_paths.artifacts, "total_water_bucket.png"), figname2 = joinpath(dir_paths.artifacts, "total_water_log_bucket.png"), ) @@ -930,7 +851,7 @@ if ClimaComms.iamroot(comms_ctx) @info "AMIP plots" ## ClimaESM - include("user_io/ci_plots.jl") + include("user_io/diagnostics_plots.jl") # define variable names and output directories for each diagnostic amip_short_names_atmos = ["ta", "ua", "hus", "clw", "pr", "ts", "toa_fluxes_net"] @@ -939,13 +860,13 @@ if ClimaComms.iamroot(comms_ctx) output_dir_coupler = dir_paths.output # Check if all output variables are available in the specified directories - make_ci_plots( + make_diagnostics_plots( output_dir_atmos, dir_paths.artifacts, short_names = amip_short_names_atmos, output_prefix = "atmos_", ) - make_ci_plots( + make_diagnostics_plots( output_dir_coupler, dir_paths.artifacts, short_names = amip_short_names_coupler, @@ -954,7 +875,7 @@ if ClimaComms.iamroot(comms_ctx) end # Check this because we only want monthly data for making plots - if t_end > 84600 * 31 * 3 && config_dict["output_default_diagnostics"] + if t_end > 84600 * 31 * 3 && output_default_diagnostics include("leaderboard/leaderboard.jl") diagnostics_folder_path = atmos_sim.integrator.p.output_dir leaderboard_base_path = dir_paths.artifacts @@ -962,10 +883,10 @@ if ClimaComms.iamroot(comms_ctx) end end ## plot extra atmosphere diagnostics if specified - if config_dict["ci_plots"] - @info "Generating CI plots" - include("user_io/ci_plots.jl") - make_ci_plots(atmos_sim.integrator.p.output_dir, dir_paths.artifacts) + if plot_diagnostics + @info "Plotting diagnostics" + include("user_io/diagnostics_plots.jl") + make_diagnostics_plots(atmos_sim.integrator.p.output_dir, dir_paths.artifacts) end ## plot all model states and coupler fields (useful for debugging) diff --git a/experiments/ClimaEarth/run_cloudless_aquaplanet.jl b/experiments/ClimaEarth/run_cloudless_aquaplanet.jl index eaa37e206d..b70ca50305 100644 --- a/experiments/ClimaEarth/run_cloudless_aquaplanet.jl +++ b/experiments/ClimaEarth/run_cloudless_aquaplanet.jl @@ -15,7 +15,6 @@ redirect_stderr(IOContext(stderr, :stacktrace_types_limited => Ref(false))) ## standard packages import Dates -import YAML # ## ClimaESM packages import ClimaAtmos as CA @@ -45,7 +44,7 @@ include("components/ocean/slab_ocean.jl") job_id = "cloudless_aquaplanet" coupler_output_dir = "$job_id" const FT = Float64 -restart_dir = "unspecified" +restart_dir = nothing restart_t = Int(0) ## coupler simulation specific configuration @@ -90,7 +89,7 @@ config_dict = Dict( "surface_setup" => "PrescribedSurface", # diagnostic (nested with period and short_name) "output_default_diagnostics" => false, - "diagnostics" => [ + "extra_atmos_diagnostics" => [ Dict( "short_name" => ["mse", "lr", "mass_strf", "stab", "vt", "egr", "ua", "va", "wa", "ta", "rhoa", "pfull"], @@ -110,7 +109,7 @@ config_dict = Dict( ) ## merge dictionaries of command line arguments, coupler dictionary and component model dictionaries -atmos_config_dict, config_dict = get_atmos_config_dict(config_dict, job_id) +atmos_config_dict = get_atmos_config_dict(config_dict, job_id) atmos_config_object = CA.AtmosConfig(atmos_config_dict) #= @@ -262,7 +261,7 @@ cs = Interfacer.CoupledSimulation{FT}( ## Restart component model states if specified in the config_dict =# -if restart_dir !== "unspecified" +if !isnothing(restart_dir) for sim in cs.model_sims if Checkpointer.get_model_prog_state(sim) !== nothing Checkpointer.restart_model_state!(sim, comms_ctx, restart_t; input_dir = restart_dir) diff --git a/experiments/ClimaEarth/run_cloudy_aquaplanet.jl b/experiments/ClimaEarth/run_cloudy_aquaplanet.jl index f01cb7a9d8..49d363d496 100644 --- a/experiments/ClimaEarth/run_cloudy_aquaplanet.jl +++ b/experiments/ClimaEarth/run_cloudy_aquaplanet.jl @@ -14,7 +14,6 @@ redirect_stderr(IOContext(stderr, :stacktrace_types_limited => Ref(false))) ## standard packages import Dates -import YAML # ## ClimaESM packages import ClimaComms @@ -45,7 +44,7 @@ include("components/ocean/slab_ocean.jl") job_id = "cloudy_aquaplanet" coupler_output_dir = "$job_id" const FT = Float64 -restart_dir = "unspecified" +restart_dir = nothing restart_t = Int(0) ## coupler simulation specific configuration @@ -90,7 +89,7 @@ config_dict = Dict( "surface_setup" => "PrescribedSurface", # diagnostic (nested with period and short_name) "output_default_diagnostics" => false, - "diagnostics" => [ + "extra_atmos_diagnostics" => [ Dict( "short_name" => ["mse", "lr", "mass_strf", "stab", "vt", "egr", "ua", "va", "wa", "ta", "rhoa", "pfull"], @@ -123,7 +122,7 @@ config_dict = Dict( ) ## merge dictionaries of command line arguments, coupler dictionary and component model dictionaries -atmos_config_dict, config_dict = get_atmos_config_dict(config_dict, job_id) +atmos_config_dict = get_atmos_config_dict(config_dict, job_id) atmos_config_object = CA.AtmosConfig(atmos_config_dict) ## override default toml parameters @@ -277,7 +276,7 @@ cs = Interfacer.CoupledSimulation{FT}( ## Restart component model states if specified in the config_dict =# -if restart_dir !== "unspecified" +if !isnothing(restart_dir) for sim in cs.model_sims if Checkpointer.get_model_prog_state(sim) !== nothing Checkpointer.restart_model_state!(sim, comms_ctx, restart_t; input_dir = restart_dir) diff --git a/experiments/ClimaEarth/run_cloudy_slabplanet.jl b/experiments/ClimaEarth/run_cloudy_slabplanet.jl index ab3951c26b..1128407b3e 100644 --- a/experiments/ClimaEarth/run_cloudy_slabplanet.jl +++ b/experiments/ClimaEarth/run_cloudy_slabplanet.jl @@ -14,7 +14,6 @@ redirect_stderr(IOContext(stderr, :stacktrace_types_limited => Ref(false))) ## standard packages import Dates -import YAML # ## ClimaESM packages import ClimaComms @@ -50,7 +49,7 @@ include("components/land/climaland_bucket.jl") job_id = "cloudy_slabplanet" coupler_output_dir = "$job_id" const FT = Float64 -restart_dir = "unspecified" +restart_dir = nothing restart_t = Int(0) ## coupler simulation specific configuration @@ -96,7 +95,7 @@ config_dict = Dict( "surface_setup" => "PrescribedSurface", # diagnostic (nested with period and short_name) "output_default_diagnostics" => false, - "diagnostics" => [ + "extra_atmos_diagnostics" => [ Dict( "short_name" => ["mse", "lr", "mass_strf", "stab", "vt", "egr", "ua", "va", "wa", "ta", "rhoa", "pfull"], @@ -127,7 +126,7 @@ config_dict = Dict( ) ## merge dictionaries of command line arguments, coupler dictionary and component model dictionaries -atmos_config_dict, config_dict = get_atmos_config_dict(config_dict, job_id) +atmos_config_dict = get_atmos_config_dict(config_dict, job_id) atmos_config_object = CA.AtmosConfig(atmos_config_dict) # override default toml parameters @@ -328,7 +327,7 @@ cs = Interfacer.CoupledSimulation{FT}( ## Restart component model states if specified in the config_dict =# -if restart_dir !== "unspecified" +if !isnothing(restart_dir) for sim in cs.model_sims if Checkpointer.get_model_prog_state(sim) !== nothing Checkpointer.restart_model_state!(sim, comms_ctx, restart_t; input_dir = restart_dir) diff --git a/experiments/ClimaEarth/run_dry_held_suarez.jl b/experiments/ClimaEarth/run_dry_held_suarez.jl index fcb4080f95..b76b615484 100644 --- a/experiments/ClimaEarth/run_dry_held_suarez.jl +++ b/experiments/ClimaEarth/run_dry_held_suarez.jl @@ -16,7 +16,6 @@ redirect_stderr(IOContext(stderr, :stacktrace_types_limited => Ref(false))) ## standard packages using Dates -import YAML ## ClimaESM packages using ClimaComms @@ -46,7 +45,7 @@ Here we follow ClimaCore's dry Held-Suarez `held_suarez_rhoe` example. job_id = "dry_held_suarez" coupler_output_dir = "$job_id" const FT = Float64 -restart_dir = "unspecified" +restart_dir = nothing restart_t = Int(0) ## coupler simulation specific configuration @@ -89,7 +88,7 @@ config_dict = Dict( "surface_setup" => "PrescribedSurface", # diagnostic (nested with period and short_name) "output_default_diagnostics" => false, - "diagnostics" => [ + "extra_atmos_diagnostics" => [ Dict( "short_name" => ["mse", "lr", "mass_strf", "stab", "vt", "egr", "ua", "va", "wa", "ta", "rhoa", "pfull"], @@ -102,7 +101,7 @@ config_dict = Dict( ) ## merge dictionaries of command line arguments, coupler dictionary and component model dictionaries -atmos_config_dict, config_dict = get_atmos_config_dict(config_dict, job_id) +atmos_config_dict = get_atmos_config_dict(config_dict, job_id) atmos_config_object = CA.AtmosConfig(atmos_config_dict) #= @@ -207,7 +206,7 @@ cs = Interfacer.CoupledSimulation{FT}( ## Restart component model states if specified in the config_dict =# -if restart_dir !== "unspecified" +if !isnothing(restart_dir) for sim in cs.model_sims if get_model_prog_state(sim) !== nothing Checkpointer.restart_model_state!(sim, comms_ctx, restart_t; input_dir = restart_dir) diff --git a/experiments/ClimaEarth/run_moist_held_suarez.jl b/experiments/ClimaEarth/run_moist_held_suarez.jl index 98cc9094b1..7fefba21f8 100644 --- a/experiments/ClimaEarth/run_moist_held_suarez.jl +++ b/experiments/ClimaEarth/run_moist_held_suarez.jl @@ -17,7 +17,6 @@ redirect_stderr(IOContext(stderr, :stacktrace_types_limited => Ref(false))) ## standard packages import Dates -import YAML # ## ClimaESM packages import ClimaComms @@ -48,7 +47,7 @@ Here we follow Thatcher and Jablonowski (2016). job_id = "moist_held_suarez" coupler_output_dir = "$job_id" const FT = Float64 -restart_dir = "unspecified" +restart_dir = nothing restart_t = Int(0) ## coupler simulation specific configuration @@ -92,7 +91,7 @@ config_dict = Dict( "surface_setup" => "PrescribedSurface", # diagnostic (nested with period and short_name) "output_default_diagnostics" => false, - "diagnostics" => [ + "extra_atmos_diagnostics" => [ Dict( "short_name" => ["mse", "lr", "mass_strf", "stab", "vt", "egr", "ua", "va", "wa", "ta", "rhoa", "pfull"], @@ -110,7 +109,7 @@ config_dict = Dict( # TODO: may need to switch to Bulk fluxes ## merge dictionaries of command line arguments, coupler dictionary and component model dictionaries -atmos_config_dict, config_dict = get_atmos_config_dict(config_dict, job_id) +atmos_config_dict = get_atmos_config_dict(config_dict, job_id) atmos_config_object = CA.AtmosConfig(atmos_config_dict) #= @@ -258,7 +257,7 @@ cs = Interfacer.CoupledSimulation{FT}( ## Restart component model states if specified in the config_dict =# -if restart_dir !== "unspecified" +if !isnothing(restart_dir) for sim in cs.model_sims if Checkpointer.get_model_prog_state(sim) !== nothing Checkpointer.restart_model_state!(sim, comms_ctx, restart_t; input_dir = restart_dir) diff --git a/experiments/ClimaEarth/test/amip_test.yml b/experiments/ClimaEarth/test/amip_test.yml index 101a17368d..71dbbc265e 100644 --- a/experiments/ClimaEarth/test/amip_test.yml +++ b/experiments/ClimaEarth/test/amip_test.yml @@ -3,7 +3,7 @@ albedo_model: "CouplerAlbedo" atmos_config_file: "config/longrun_configs/amip_target_diagedmf.yml" coupler_toml_file: "toml/amip.toml" dt: "180secs" -dt_cpl: 180 +dt_cpl: "180secs" dt_save_state_to_disk: "30days" dt_save_to_sol: "30days" dz_bottom: 100.0 diff --git a/experiments/ClimaEarth/user_io/arg_parsing.jl b/experiments/ClimaEarth/user_io/arg_parsing.jl new file mode 100644 index 0000000000..630bb3bcec --- /dev/null +++ b/experiments/ClimaEarth/user_io/arg_parsing.jl @@ -0,0 +1,274 @@ +import YAML + +""" + get_coupler_config() + +Read in the configuration file and job ID from the command line. +A dictionary is constructed from the input configuration file and returned. + +# Returns +- `config_dict`: A dictionary mapping configuration keys to the specified settings +""" +function get_coupler_config() + # Read in command line arguments + parsed_args = parse_commandline(argparse_settings()) + + # Extract the configuration file and job ID + config_file = parsed_args["config_file"] + job_id = parsed_args["job_id"] + # Get the job ID from the config file string if not provided + job_id = isnothing(job_id) ? string(split(split(config_file, '/')[end], '.')[1]) : job_id + + # Read in config dictionary from file, overriding the defaults in `parsed_args` + config_dict = merge(parsed_args, YAML.load_file(parsed_args["config_file"])) + config_dict["job_id"] = job_id + return config_dict +end + +""" + get_coupler_args(config_dict) + +Extract the necessary arguments from the coupled configuration dictionary. +This function may modify the input dictionary to remove unnecessary keys. + +# Arguments +- `config_dict`: A dictionary mapping configuration keys to the specified settings + +# Returns +- All arguments needed for the coupled simulation +""" +function get_coupler_args(config_dict::Dict) + # Simulation-identifying information; Print `config_dict` if requested + config_dict["print_config_dict"] && @info(config_dict) + job_id = config_dict["job_id"] + mode_name = config_dict["mode_name"] + + # Computational simulation setup information + random_seed = config_dict["unique_seed"] ? time_ns() : 1234 + FT = config_dict["FLOAT_TYPE"] == "Float64" ? Float64 : Float32 + comms_ctx = Utilities.get_comms_context(config_dict) + + # Time information + t_end = Float64(Utilities.time_to_seconds(config_dict["t_end"])) + t_start = Float64(Utilities.time_to_seconds(config_dict["t_start"])) + date0 = date = Dates.DateTime(config_dict["start_date"], Dates.dateformat"yyyymmdd") + Δt_cpl = Float64(Utilities.time_to_seconds(config_dict["dt_cpl"])) + saveat = Float64(Utilities.time_to_seconds(config_dict["dt_save_to_sol"])) + component_dt_dict = config_dict["component_dt_dict"] + + # Checkpointing information + hourly_checkpoint = config_dict["hourly_checkpoint"] + hourly_checkpoint_dt = config_dict["hourly_checkpoint_dt"] + + # Restart information + restart_dir = config_dict["restart_dir"] + restart_t = Int(config_dict["restart_t"]) + + # Diagnostics information + use_coupler_diagnostics = config_dict["use_coupler_diagnostics"] + use_land_diagnostics = config_dict["use_land_diagnostics"] + calendar_dt = config_dict["calendar_dt"] + + # Physical simulation information + evolving_ocean = config_dict["evolving_ocean"] + mono_surface = config_dict["mono_surface"] + turb_flux_partition = config_dict["turb_flux_partition"] + + # Conservation information + energy_check = config_dict["energy_check"] + conservation_softfail = config_dict["conservation_softfail"] + + # Output information + output_dir_root = config_dict["coupler_output_dir"] + plot_diagnostics = config_dict["plot_diagnostics"] + + # ClimaLand-specific information + land_domain_type = config_dict["land_domain_type"] + land_albedo_type = config_dict["land_albedo_type"] + land_temperature_anomaly = config_dict["land_temperature_anomaly"] + use_land_diagnostics = config_dict["use_land_diagnostics"] + + return (; + job_id, + mode_name, + random_seed, + FT, + comms_ctx, + t_end, + t_start, + date0, + date, + Δt_cpl, + component_dt_dict, + saveat, + hourly_checkpoint, + hourly_checkpoint_dt, + restart_dir, + restart_t, + use_coupler_diagnostics, + calendar_dt, + evolving_ocean, + mono_surface, + turb_flux_partition, + energy_check, + conservation_softfail, + output_dir_root, + plot_diagnostics, + land_domain_type, + land_albedo_type, + land_temperature_anomaly, + use_land_diagnostics, + ) +end + +""" + get_atmos_args(atmos_config_dict) + +Extract the necessary arguments from the atmosphere configuration dictionary. + +# Arguments +- `atmos_config_dict`: A dictionary mapping atmosphere configuration keys to the specified settings + +# Returns +- All arguments needed for the atmosphere simulation +""" +function get_atmos_args(atmos_config_dict) + dt_rad = atmos_config_dict["dt_rad"] + output_default_diagnostics = atmos_config_dict["output_default_diagnostics"] + + return (; dt_rad, output_default_diagnostics) +end + + +### Helper functions used in argument parsing ### + +""" + get_diag_period() + +Determine the frequency at which to average and output diagnostics based on the +simulation start and end times. + +The default periods are: +- 1 month for simulations longer than 90 days +- 10 days for simulations longer than 30 days +- 1 day for simulations longer than 1 day +- 1 hour for simulations shorter than 1 day + +# Arguments +- `t_start`: The start time of the simulation +- `t_end`: The end time of the simulation + +# Returns +- `period`: A String of how often to average and output diagnostics +- `calendar_dt`: A DateTime interval representing the period +""" +function get_diag_period(t_start, t_end) + sim_duration = t_end - t_start + secs_per_day = 86400 + if sim_duration >= 90 * secs_per_day + # if duration >= 90 days, take monthly means + period = "1months" + calendar_dt = Dates.Month(1) + elseif sim_duration >= 30 * secs_per_day + # if duration >= 30 days, take means over 10 days + period = "10days" + calendar_dt = Dates.Day(10) + elseif sim_duration >= secs_per_day + # if duration >= 1 day, take daily means + period = "1days" + calendar_dt = Dates.Day(1) + else + # if duration < 1 day, take hourly means + period = "1hours" + calendar_dt = Dates.Hour(1) + end + return (period, calendar_dt) +end + +""" + parse_component_dts!(config_dict) + +Check which timesteps are specified in the config file, and use them to choose +the correct timestep for each component model. +If all component timesteps `dt_\$component` are specified in the config file, use those +and remove `dt` if it was provided. +Otherwise, use the generic component timestep `dt` specified in the config file. +If some (but not all) component timesteps and the generic timestep `dt` are specified, +use the generic timestep and remove the others from the config dict. + +The timestep for each component model is stored in the `component_dt_dict` field of the config dict. + +# Arguments +- `config_dict`: A dictionary mapping configuration keys to the specified settings +""" +function parse_component_dts!(config_dict) + # Retrieve coupling timestep + Δt_cpl = Float64(Utilities.time_to_seconds(config_dict["dt_cpl"])) + + # Specify component model names + component_dt_names = ["dt_atmos", "dt_land", "dt_ocean", "dt_seaice"] + component_dt_dict = Dict{String, Float64}() + # check if all component dt's are specified + if all(key -> !isnothing(config_dict[key]), component_dt_names) + # when all component dt's are specified, ignore the dt field + if haskey(config_dict, "dt") + @warn "Removing dt in favor of individual component dt's" + delete!(config_dict, "dt") + end + for key in component_dt_names + component_dt = Float64(Utilities.time_to_seconds(config_dict[key])) + @assert isapprox(Δt_cpl % component_dt, 0.0) "Coupler dt must be divisible by all component dt's\n dt_cpl = $Δt_cpl\n $key = $component_dt" + component_dt_dict[key] = component_dt + end + else + # when not all component dt's are specified, use the dt field + @assert haskey(config_dict, "dt") "dt or (dt_atmos, dt_land, dt_ocean, and dt_seaice) must be specified" + for key in component_dt_names + if !isnothing(config_dict[key]) + @warn "Removing $key from config in favor of dt because not all component dt's are specified" + end + delete!(config_dict, key) + component_dt_dict[key] = Float64(Utilities.time_to_seconds(config_dict["dt"])) + end + end + config_dict["component_dt_dict"] = component_dt_dict + return nothing +end + +""" + add_extra_diagnostics!(config_dict) + +Conditionally add extra diagnostics to the config dictionary based on the +simulation type and flag to use diagnostics. Currently, the only extra +diagnostic is the atmosphere TOA net flux for AMIP simulations, but more diagnostics +can be added for any component by following the structure in `climaatmos_extra_diags.jl`. + +The added atmosphere diagnostics are added to the `extra_atmos_diagnostics` field +of the config dict. The `calendar_dt` field is also added to the config dict to +coordinate the output frequency of the diagnostics. + +# Arguments +- `config_dict`: A dictionary mapping configuration keys to the specified settings +""" +function add_extra_diagnostics!(config_dict) + # Diagnostics information + mode_name = config_dict["mode_name"] + use_coupler_diagnostics = config_dict["use_coupler_diagnostics"] + t_end = Float64(Utilities.time_to_seconds(config_dict["t_end"])) + t_start = Float64(Utilities.time_to_seconds(config_dict["t_start"])) + calendar_dt = nothing + if mode_name == "amip" && use_coupler_diagnostics + @info "Using default AMIP diagnostics" + (period, calendar_dt) = get_diag_period(t_start, t_end) + + # Additional atmosphere diagnostics + !haskey(config_dict, "extra_atmos_diagnostics") && + (config_dict["extra_atmos_diagnostics"] = Vector{Dict{Any, Any}}()) + push!( + config_dict["extra_atmos_diagnostics"], + Dict("short_name" => ["toa_fluxes_net"], "reduction_time" => "average", "period" => period), + ) + end + config_dict["calendar_dt"] = calendar_dt + return nothing +end diff --git a/experiments/ClimaEarth/user_io/ci_plots.jl b/experiments/ClimaEarth/user_io/diagnostics_plots.jl similarity index 96% rename from experiments/ClimaEarth/user_io/ci_plots.jl rename to experiments/ClimaEarth/user_io/diagnostics_plots.jl index 944c49f28a..e0b2ba355b 100644 --- a/experiments/ClimaEarth/user_io/ci_plots.jl +++ b/experiments/ClimaEarth/user_io/diagnostics_plots.jl @@ -144,19 +144,19 @@ function map_comparison(func, simdirs, args) end """ - make_ci_plots( + make_diagnostics_plots( output_path::AbstractString, plot_path::AbstractString; short_names::Vector{<:AbstractString} = ["mse", "lr", "edt", "ts"], reduction::String = "average", output_prefix = "", ) -Create plots for the general CI diagnostics. The plots are saved to `plot_path`. -This is the default plotting function for the CI diagnostics and it can be extended +Create plots for diagnostics. The plots are saved to `plot_path`. +This is the default plotting function for diagnostics and it can be extended to include additional diagnostics. The `reduction` keyword argument should be consistent with the reduction used to save the diagnostics. """ -function make_ci_plots( +function make_diagnostics_plots( output_path::AbstractString, plot_path::AbstractString; short_names::Vector{<:AbstractString} = ["mse", "lr", "edt", "ts"], diff --git a/src/Utilities.jl b/src/Utilities.jl index 13c71a96bf..79c0166ee8 100644 --- a/src/Utilities.jl +++ b/src/Utilities.jl @@ -51,7 +51,14 @@ end """ get_comms_context(config_dict) -Sets up the appropriate ClimaComms context for the device the model is to be run on +Sets up the appropriate ClimaComms context for the device the model is to be run on, +choosing from the following options: + - CPU single threaded + - CPU with MPI + - GPU + +If no device is passed to `ClimaComms.context()` then `ClimaComms` automatically +selects the device from which this code is called. # Arguments `config_dict`: dictionary containing a "device" flag whcih decides which device context is needed