From ca0e5701b20f5a4371e5f98f8157b62022a78b07 Mon Sep 17 00:00:00 2001 From: Alexis Renchon Date: Fri, 24 May 2024 11:16:49 -0700 Subject: [PATCH] Bucket diagnostics --- .buildkite/Manifest.toml | 117 +++----- .buildkite/Project.toml | 2 + .buildkite/pipeline.yml | 5 + .github/workflows/ClimaLandSimulations.yml | 6 +- Project.toml | 2 + README.md | 1 + docs/Manifest.toml | 10 +- docs/Project.toml | 1 + docs/list_diagnostics.jl | 5 + docs/make.jl | 6 +- docs/src/diagnostics/available_diagnostics.md | 6 + .../diagnostics/developpers_diagnostics.md | 94 +++++++ docs/src/diagnostics/make_diagnostic_table.jl | 27 ++ docs/src/diagnostics/users_diagnostics.md | 58 ++++ experiments/Project.toml | 2 + .../Bucket/global_bucket_diagnostics.jl | 212 ++++++++++++++ lib/ClimaLandSimulations/Project.toml | 8 +- lib/ClimaLandSimulations/README.md | 6 +- .../src/ClimaLandSimulations.jl | 1 + src/ClimaLand.jl | 4 + src/Diagnostics/Diagnostics.jl | 18 ++ src/Diagnostics/bucket_compute_methods.jl | 172 ++++++++++++ src/Diagnostics/default_diagnostics.jl | 63 +++++ src/Diagnostics/define_diagnostics.jl | 143 ++++++++++ src/Diagnostics/diagnostic.jl | 100 +++++++ .../standard_diagnostic_frequencies.jl | 264 ++++++++++++++++++ 26 files changed, 1249 insertions(+), 84 deletions(-) create mode 100644 docs/list_diagnostics.jl create mode 100644 docs/src/diagnostics/available_diagnostics.md create mode 100644 docs/src/diagnostics/developpers_diagnostics.md create mode 100644 docs/src/diagnostics/make_diagnostic_table.jl create mode 100644 docs/src/diagnostics/users_diagnostics.md create mode 100644 experiments/standalone/Bucket/global_bucket_diagnostics.jl create mode 100644 src/Diagnostics/Diagnostics.jl create mode 100644 src/Diagnostics/bucket_compute_methods.jl create mode 100644 src/Diagnostics/default_diagnostics.jl create mode 100644 src/Diagnostics/define_diagnostics.jl create mode 100644 src/Diagnostics/diagnostic.jl create mode 100644 src/Diagnostics/standard_diagnostic_frequencies.jl diff --git a/.buildkite/Manifest.toml b/.buildkite/Manifest.toml index f2d6685974..99ec93c822 100644 --- a/.buildkite/Manifest.toml +++ b/.buildkite/Manifest.toml @@ -2,7 +2,7 @@ julia_version = "1.10.4" manifest_format = "2.0" -project_hash = "dae5b8a0ef6cad3a789e38991e9bafc7e71f7ab5" +project_hash = "8f674e311ce137cbeb26c8e00409aca502afec54" [[deps.ADTypes]] git-tree-sha1 = "fc02d55798c1af91123d07915a990fbb9a10d146" @@ -225,12 +225,6 @@ git-tree-sha1 = "9a9610fbe5779636f75229e423e367124034af41" uuid = "8e7c35d0-a365-5155-bbbb-fb81a777f24e" version = "0.16.43" -[[deps.Blosc_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Lz4_jll", "Zlib_jll", "Zstd_jll"] -git-tree-sha1 = "19b98ee7e3db3b4eff74c5c9c72bf32144e24f10" -uuid = "0b7ba130-8d10-5ba8-a3d6-c5182647fed9" -version = "1.21.5+0" - [[deps.Bzip2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "9e2a6b69137e6969bab0152632dcb3bc108c8bdd" @@ -319,9 +313,9 @@ version = "1.0.5" [[deps.CairoMakie]] deps = ["CRC32c", "Cairo", "Colors", "FileIO", "FreeType", "GeometryBasics", "LinearAlgebra", "Makie", "PrecompileTools"] -git-tree-sha1 = "9e8eaaff3e5951d8c61b7c9261d935eb27e0304b" +git-tree-sha1 = "d69c7593fe9d7d617973adcbe4762028c6899b2c" uuid = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" -version = "0.12.2" +version = "0.11.11" [[deps.Cairo_jll]] deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"] @@ -351,6 +345,20 @@ weakdeps = ["SparseArrays"] [deps.ChainRulesCore.extensions] ChainRulesCoreSparseArraysExt = "SparseArrays" +[[deps.ClimaAnalysis]] +deps = ["NCDatasets", "OrderedCollections", "Statistics"] +git-tree-sha1 = "af5e012e13bc9609f712dea0434cb4ce2b3e709e" +uuid = "29b5916a-a76c-4e73-9657-3c8fd22e65e6" +version = "0.5.2" + + [deps.ClimaAnalysis.extensions] + CairoMakieExt = "CairoMakie" + GeoMakieExt = "GeoMakie" + + [deps.ClimaAnalysis.weakdeps] + CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" + GeoMakie = "db073c08-6b98-4ee5-b6a4-5efafb3259c6" + [[deps.ClimaComms]] git-tree-sha1 = "d76590e99fa942e07f1e992a4d4a5e25121a26d6" uuid = "3a4d1b5c-c61d-41fd-a00a-5873ba7a1b0d" @@ -372,8 +380,14 @@ weakdeps = ["CUDA", "Krylov"] ClimaCoreCUDAExt = "CUDA" KrylovExt = "Krylov" +[[deps.ClimaDiagnostics]] +deps = ["Accessors", "ClimaComms", "ClimaCore", "Dates", "NCDatasets", "SciMLBase"] +git-tree-sha1 = "aff194804df0fcfcf69a80c58978b84777272619" +uuid = "1ecacbb8-0713-4841-9a07-eb5aa8a2d53f" +version = "0.2.2" + [[deps.ClimaLand]] -deps = ["Adapt", "ArtifactWrappers", "ClimaComms", "ClimaCore", "ClimaUtilities", "DataFrames", "Dates", "DocStringExtensions", "Insolation", "Interpolations", "IntervalSets", "LazyArtifacts", "LinearAlgebra", "NCDatasets", "SciMLBase", "StaticArrays", "SurfaceFluxes", "Thermodynamics", "UnrolledUtilities"] +deps = ["Adapt", "ArtifactWrappers", "ClimaComms", "ClimaCore", "ClimaDiagnostics", "ClimaUtilities", "DataFrames", "Dates", "DocStringExtensions", "Insolation", "Interpolations", "IntervalSets", "LazyArtifacts", "LinearAlgebra", "NCDatasets", "SciMLBase", "StaticArrays", "SurfaceFluxes", "Thermodynamics", "UnrolledUtilities"] path = ".." uuid = "08f4d4ce-cf43-44bb-ad95-9d2d5f413532" version = "0.12.3" @@ -790,10 +804,10 @@ uuid = "c87230d0-a227-11e9-1b43-d7ebe4e7570a" version = "0.4.1" [[deps.FFMPEG_jll]] -deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "Libdl", "Ogg_jll", "OpenSSL_jll", "Opus_jll", "PCRE2_jll", "Zlib_jll", "libaom_jll", "libass_jll", "libfdk_aac_jll", "libvorbis_jll", "x264_jll", "x265_jll"] -git-tree-sha1 = "466d45dc38e15794ec7d5d63ec03d776a9aff36e" +deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "Libdl", "Ogg_jll", "OpenSSL_jll", "Opus_jll", "PCRE2_jll", "Pkg", "Zlib_jll", "libaom_jll", "libass_jll", "libfdk_aac_jll", "libvorbis_jll", "x264_jll", "x265_jll"] +git-tree-sha1 = "74faea50c1d007c85837327f6775bea60b5492dd" uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5" -version = "4.4.4+1" +version = "4.4.2+2" [[deps.FFTW]] deps = ["AbstractFFTs", "FFTW_jll", "LinearAlgebra", "MKL_jll", "Preferences", "Reexport"] @@ -987,11 +1001,6 @@ git-tree-sha1 = "ff38ba61beff76b8f4acad8ab0c97ef73bb670cb" uuid = "0656b61e-2033-5cc2-a64a-77c0f6c09b89" version = "3.3.9+0" -[[deps.GMP_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "781609d7-10c4-51f6-84f2-b8444358ff6d" -version = "6.2.1+6" - [[deps.GPUArrays]] deps = ["Adapt", "GPUArraysCore", "LLVM", "LinearAlgebra", "Printf", "Random", "Reexport", "Serialization", "Statistics"] git-tree-sha1 = "38cb19b8a3e600e509dc36a6396ac74266d108c1" @@ -1057,12 +1066,6 @@ git-tree-sha1 = "7c82e6a6cd34e9d935e9aa4051b66c6ff3af59ba" uuid = "7746bdde-850d-59dc-9ae8-88ece973131d" version = "2.80.2+0" -[[deps.GnuTLS_jll]] -deps = ["Artifacts", "GMP_jll", "JLLWrappers", "Libdl", "Nettle_jll", "P11Kit_jll", "Zlib_jll"] -git-tree-sha1 = "383db7d3f900f4c1f47a8a04115b053c095e48d3" -uuid = "0951126a-58fd-58f1-b5b3-b08c7c4a876d" -version = "3.8.4+0" - [[deps.Graphics]] deps = ["Colors", "LinearAlgebra", "NaNMath"] git-tree-sha1 = "d61890399bc535850c4bf08e4e0d3a7ad0f21cbd" @@ -1077,9 +1080,9 @@ version = "1.3.14+0" [[deps.GridLayoutBase]] deps = ["GeometryBasics", "InteractiveUtils", "Observables"] -git-tree-sha1 = "fc713f007cff99ff9e50accba6373624ddd33588" +git-tree-sha1 = "6f93a83ca11346771a93bbde2bdad2f65b61498f" uuid = "3955a311-db13-416c-9275-1d80ed98e5e9" -version = "0.11.0" +version = "0.10.2" [[deps.Grisu]] git-tree-sha1 = "53bb909d1151e57e2484c3d1b53e19552b887fb2" @@ -1097,10 +1100,10 @@ weakdeps = ["MPI"] MPIExt = "MPI" [[deps.HDF5_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "OpenSSL_jll", "TOML", "Zlib_jll", "libaec_jll"] -git-tree-sha1 = "82a471768b513dc39e471540fdadc84ff80ff997" +deps = ["Artifacts", "JLLWrappers", "LibCURL_jll", "Libdl", "OpenSSL_jll", "Pkg", "Zlib_jll"] +git-tree-sha1 = "4cc2bb72df6ff40b055295fdef6d92955f9dede8" uuid = "0234f1f7-429e-5d53-9886-15a909be8d59" -version = "1.14.3+3" +version = "1.12.2+2" [[deps.HTTP]] deps = ["Base64", "CodecZlib", "ConcurrentUtilities", "Dates", "ExceptionUnwrapping", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "OpenSSL", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"] @@ -1561,12 +1564,6 @@ git-tree-sha1 = "c1dd6d7978c12545b4179fb6153b9250c96b0075" uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36" version = "1.0.3" -[[deps.Lz4_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "6c26c5e8a4203d43b5497be3ec5d4e0c3cde240a" -uuid = "5ced341a-0733-55b8-9ab6-a4889d929147" -version = "1.9.4+0" - [[deps.MKL_jll]] deps = ["Artifacts", "IntelOpenMP_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "oneTBB_jll"] git-tree-sha1 = "80b2833b56d466b3858d565adcd16a4a05f2089b" @@ -1623,16 +1620,16 @@ uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" version = "0.5.13" [[deps.Makie]] -deps = ["Animations", "Base64", "CRC32c", "ColorBrewer", "ColorSchemes", "ColorTypes", "Colors", "Contour", "Dates", "DelaunayTriangulation", "Distributions", "DocStringExtensions", "Downloads", "FFMPEG_jll", "FileIO", "FilePaths", "FixedPointNumbers", "Format", "FreeType", "FreeTypeAbstraction", "GeometryBasics", "GridLayoutBase", "ImageIO", "InteractiveUtils", "IntervalSets", "Isoband", "KernelDensity", "LaTeXStrings", "LinearAlgebra", "MacroTools", "MakieCore", "Markdown", "MathTeXEngine", "Observables", "OffsetArrays", "Packing", "PlotUtils", "PolygonOps", "PrecompileTools", "Printf", "REPL", "Random", "RelocatableFolders", "Scratch", "ShaderAbstractions", "Showoff", "SignedDistanceFields", "SparseArrays", "Statistics", "StatsBase", "StatsFuns", "StructArrays", "TriplotBase", "UnicodeFun", "Unitful"] -git-tree-sha1 = "ec3a60c9de787bc6ef119d13e07d4bfacceebb83" +deps = ["Animations", "Base64", "CRC32c", "ColorBrewer", "ColorSchemes", "ColorTypes", "Colors", "Contour", "DelaunayTriangulation", "Distributions", "DocStringExtensions", "Downloads", "FFMPEG_jll", "FileIO", "FilePaths", "FixedPointNumbers", "Format", "FreeType", "FreeTypeAbstraction", "GeometryBasics", "GridLayoutBase", "ImageIO", "InteractiveUtils", "IntervalSets", "Isoband", "KernelDensity", "LaTeXStrings", "LinearAlgebra", "MacroTools", "MakieCore", "Markdown", "MathTeXEngine", "Observables", "OffsetArrays", "Packing", "PlotUtils", "PolygonOps", "PrecompileTools", "Printf", "REPL", "Random", "RelocatableFolders", "Scratch", "ShaderAbstractions", "Showoff", "SignedDistanceFields", "SparseArrays", "Statistics", "StatsBase", "StatsFuns", "StructArrays", "TriplotBase", "UnicodeFun"] +git-tree-sha1 = "4d49c9ee830eec99d3e8de2425ff433ece7cc1bc" uuid = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" -version = "0.21.2" +version = "0.20.10" [[deps.MakieCore]] -deps = ["ColorTypes", "GeometryBasics", "IntervalSets", "Observables"] -git-tree-sha1 = "c1c9da1a69f6c635a60581c98da252958c844d70" +deps = ["Observables", "REPL"] +git-tree-sha1 = "248b7a4be0f92b497f7a331aed02c1e9a878f46b" uuid = "20f20a25-4f0e-4fdf-b5d1-57303727442b" -version = "0.8.2" +version = "0.7.3" [[deps.ManualMemory]] git-tree-sha1 = "bcaef4fc7a0cfe2cba636d84cda54b5e4e4ca3cd" @@ -1650,9 +1647,9 @@ uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" [[deps.MathTeXEngine]] deps = ["AbstractTrees", "Automa", "DataStructures", "FreeTypeAbstraction", "GeometryBasics", "LaTeXStrings", "REPL", "RelocatableFolders", "UnicodeFun"] -git-tree-sha1 = "1865d0b8a2d91477c8b16b49152a32764c7b1f5f" +git-tree-sha1 = "96ca8a313eb6437db5ffe946c457a401bbb8ce1d" uuid = "0a4f8689-d25c-4efe-a92b-7142dfc1aa53" -version = "0.6.0" +version = "0.5.7" [[deps.MbedTLS]] deps = ["Dates", "MbedTLS_jll", "MozillaCACerts_jll", "NetworkOptions", "Random", "Sockets"] @@ -1776,10 +1773,10 @@ uuid = "71a1bf82-56d0-4bbc-8a3c-48b961074391" version = "0.1.5" [[deps.NetCDF_jll]] -deps = ["Artifacts", "Blosc_jll", "Bzip2_jll", "HDF5_jll", "JLLWrappers", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "TOML", "XML2_jll", "Zlib_jll", "Zstd_jll", "libzip_jll"] -git-tree-sha1 = "4686378c4ae1d1948cfbe46c002a11a4265dcb07" +deps = ["Artifacts", "HDF5_jll", "JLLWrappers", "LibCURL_jll", "Libdl", "Pkg", "XML2_jll", "Zlib_jll"] +git-tree-sha1 = "072f8371f74c3b9e1b26679de7fbf059d45ea221" uuid = "7243133f-43d8-5620-bbf4-c2c921802cf3" -version = "400.902.211+1" +version = "400.902.5+1" [[deps.Netpbm]] deps = ["FileIO", "ImageCore", "ImageMetadata"] @@ -1787,12 +1784,6 @@ git-tree-sha1 = "d92b107dbb887293622df7697a2223f9f8176fcd" uuid = "f09324ee-3d7c-5217-9330-fc30815ba969" version = "1.1.1" -[[deps.Nettle_jll]] -deps = ["Artifacts", "GMP_jll", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "eca63e3847dad608cfa6a3329b95ef674c7160b4" -uuid = "4c82536e-c426-54e4-b420-14f461c4ed8b" -version = "3.7.2+0" - [[deps.NetworkOptions]] uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" version = "1.2.0" @@ -1886,12 +1877,6 @@ git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5" uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" version = "1.6.3" -[[deps.P11Kit_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "2cd396108e178f3ae8dedbd8e938a18726ab2fbf" -uuid = "c2071276-7c44-58a7-b746-946036e04d0a" -version = "0.24.1+0" - [[deps.PCRE2_jll]] deps = ["Artifacts", "Libdl"] uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" @@ -2096,9 +2081,9 @@ version = "1.0.0" [[deps.Qt6Base_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Fontconfig_jll", "Glib_jll", "JLLWrappers", "Libdl", "Libglvnd_jll", "OpenSSL_jll", "Vulkan_Loader_jll", "Xorg_libSM_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Xorg_libxcb_jll", "Xorg_xcb_util_cursor_jll", "Xorg_xcb_util_image_jll", "Xorg_xcb_util_keysyms_jll", "Xorg_xcb_util_renderutil_jll", "Xorg_xcb_util_wm_jll", "Zlib_jll", "libinput_jll", "xkbcommon_jll"] -git-tree-sha1 = "37b7bb7aabf9a085e0044307e1717436117f2b3b" +git-tree-sha1 = "7c29f0e8c575428bd84dc3c72ece5178caa67336" uuid = "c0090381-4147-56d7-9ebc-da0b1113ec56" -version = "6.5.3+1" +version = "6.5.2+2" [[deps.QuadGK]] deps = ["DataStructures", "LinearAlgebra"] @@ -2957,12 +2942,6 @@ git-tree-sha1 = "51b5eeb3f98367157a7a12a1fb0aa5328946c03c" uuid = "9a68df92-36a6-505f-a73e-abb412b6bfb4" version = "0.2.3+0" -[[deps.libaec_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "46bf7be2917b59b761247be3f317ddf75e50e997" -uuid = "477f73a3-ac25-53e9-8cc3-50b2fa2566f0" -version = "1.1.2+0" - [[deps.libaom_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] git-tree-sha1 = "1827acba325fdcdf1d2647fc8d5301dd9ba43a9d" @@ -3016,12 +2995,6 @@ git-tree-sha1 = "b910cb81ef3fe6e78bf6acee440bda86fd6ae00c" uuid = "f27f6e37-5d2b-51aa-960f-b287f2bc3b7a" version = "1.3.7+1" -[[deps.libzip_jll]] -deps = ["Artifacts", "Bzip2_jll", "GnuTLS_jll", "JLLWrappers", "Libdl", "XZ_jll", "Zlib_jll", "Zstd_jll"] -git-tree-sha1 = "3282b7d16ae7ac3e57ec2f3fa8fafb564d8f9f7f" -uuid = "337d8026-41b4-5cde-a456-74a10e5b31d1" -version = "1.10.1+0" - [[deps.mtdev_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "814e154bdb7be91d78b6802843f76b6ece642f11" diff --git a/.buildkite/Project.toml b/.buildkite/Project.toml index 04bc1a43be..3cee10ee64 100644 --- a/.buildkite/Project.toml +++ b/.buildkite/Project.toml @@ -5,8 +5,10 @@ BSON = "fbb218c0-5317-5bc6-957e-2ee96dd4b1f0" CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +ClimaAnalysis = "29b5916a-a76c-4e73-9657-3c8fd22e65e6" ClimaComms = "3a4d1b5c-c61d-41fd-a00a-5873ba7a1b0d" ClimaCore = "d414da3d-4745-48bb-8d80-42e94e092884" +ClimaDiagnostics = "1ecacbb8-0713-4841-9a07-eb5aa8a2d53f" ClimaLand = "08f4d4ce-cf43-44bb-ad95-9d2d5f413532" ClimaParams = "5c42b081-d73a-476f-9059-fd94b934656c" ClimaTimeSteppers = "595c0a79-7f3d-439a-bc5a-b232dc3bde79" diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 05b5b4495a..77eefe5d73 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -142,6 +142,11 @@ steps: key: "global_bucket_temporalmap_cpu" command: "julia --color=yes --project=.buildkite experiments/standalone/Bucket/global_bucket_temporalmap.jl" artifact_paths: "experiments/standalone/Bucket/artifacts_temporalmap/*cpu*" + - label: "Global Bucket on CPU (temporal map albedo), ClimaDiagnostics" + key: "global_bucket_temporalmap_cpu_diagnostics" + command: "julia --color=yes --project=.buildkite experiments/standalone/Bucket/global_bucket_diagnostics.jl" + artifact_paths: "experiments/*png" + - group: "GPU: unit tests and global bucket" steps: diff --git a/.github/workflows/ClimaLandSimulations.yml b/.github/workflows/ClimaLandSimulations.yml index 76cdb84487..8f052c4820 100644 --- a/.github/workflows/ClimaLandSimulations.yml +++ b/.github/workflows/ClimaLandSimulations.yml @@ -19,13 +19,13 @@ jobs: - uses: julia-actions/setup-julia@latest with: version: '1.10' - - uses: julia-actions/cache@v2 + # - uses: julia-actions/cache@v2 - name: Install Julia dependencies run: > - julia --project= -e 'using Pkg; Pkg.develop(path="$(pwd())"); Pkg.develop(path="$(pwd())/lib/ClimaLandSimulations")' + julia --project=lib/ClimaLandSimulations -e 'using Pkg; Pkg.develop(path=".")' - name: Run the tests continue-on-error: true env: CI_OUTPUT_DIR: output run: > - julia --project= -e 'using Pkg; Pkg.test("ClimaLandSimulations")' + julia --project=lib/ClimaLandSimulations -e 'using Pkg; Pkg.test("ClimaLandSimulations")' diff --git a/Project.toml b/Project.toml index 63374bcaa9..1e254858ba 100644 --- a/Project.toml +++ b/Project.toml @@ -8,6 +8,7 @@ Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" ArtifactWrappers = "a14bc488-3040-4b00-9dc1-f6467924858a" ClimaComms = "3a4d1b5c-c61d-41fd-a00a-5873ba7a1b0d" ClimaCore = "d414da3d-4745-48bb-8d80-42e94e092884" +ClimaDiagnostics = "1ecacbb8-0713-4841-9a07-eb5aa8a2d53f" ClimaUtilities = "b3f4f4ca-9299-4f7f-bd9b-81e1242a7513" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" @@ -44,6 +45,7 @@ CSV = "0.10" CUDA = "5.3" ClimaComms = "0.5.6, 0.6" ClimaCore = "0.13.2, 0.14" +ClimaDiagnostics = "0.2" ClimaParams = "0.10.2" ClimaUtilities = "0.1.2" DataFrames = "1" diff --git a/README.md b/README.md index b4325e0912..3125c38bf3 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ component) or standalone (single component) modes.

+[![Downloads](https://img.shields.io/badge/dynamic/json?url=http%3A%2F%2Fjuliapkgstats.com%2Fapi%2Fv1%2Ftotal_downloads%2FClimaLand&query=total_requests&suffix=%2Ftotal&label=Downloads)](http://juliapkgstats.com/pkg/ClimaLand) Recommended Julia Version: Stable release v1.10.0. CI no longer tests earlier versions of Julia. diff --git a/docs/Manifest.toml b/docs/Manifest.toml index 4dfbf82356..3c486ade58 100644 --- a/docs/Manifest.toml +++ b/docs/Manifest.toml @@ -2,7 +2,7 @@ julia_version = "1.10.4" manifest_format = "2.0" -project_hash = "bf43a7452b446a9ba967dab8eeb0bc21ba10184f" +project_hash = "6c3f79ccae513840cc13b9ac947575c2305b507f" [[deps.ADTypes]] git-tree-sha1 = "fc02d55798c1af91123d07915a990fbb9a10d146" @@ -369,8 +369,14 @@ weakdeps = ["CUDA", "Krylov"] ClimaCoreCUDAExt = "CUDA" KrylovExt = "Krylov" +[[deps.ClimaDiagnostics]] +deps = ["Accessors", "ClimaComms", "ClimaCore", "Dates", "NCDatasets", "SciMLBase"] +git-tree-sha1 = "aff194804df0fcfcf69a80c58978b84777272619" +uuid = "1ecacbb8-0713-4841-9a07-eb5aa8a2d53f" +version = "0.2.2" + [[deps.ClimaLand]] -deps = ["Adapt", "ArtifactWrappers", "ClimaComms", "ClimaCore", "ClimaUtilities", "DataFrames", "Dates", "DocStringExtensions", "Insolation", "Interpolations", "IntervalSets", "LazyArtifacts", "LinearAlgebra", "NCDatasets", "SciMLBase", "StaticArrays", "SurfaceFluxes", "Thermodynamics", "UnrolledUtilities"] +deps = ["Adapt", "ArtifactWrappers", "ClimaComms", "ClimaCore", "ClimaDiagnostics", "ClimaUtilities", "DataFrames", "Dates", "DocStringExtensions", "Insolation", "Interpolations", "IntervalSets", "LazyArtifacts", "LinearAlgebra", "NCDatasets", "SciMLBase", "StaticArrays", "SurfaceFluxes", "Thermodynamics", "UnrolledUtilities"] path = ".." uuid = "08f4d4ce-cf43-44bb-ad95-9d2d5f413532" version = "0.12.3" diff --git a/docs/Project.toml b/docs/Project.toml index fcacd6e6a2..e7f6f974ed 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -20,6 +20,7 @@ Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" diff --git a/docs/list_diagnostics.jl b/docs/list_diagnostics.jl new file mode 100644 index 0000000000..238b0db6ab --- /dev/null +++ b/docs/list_diagnostics.jl @@ -0,0 +1,5 @@ +diagnostics = [ + "Available diagnostics" => "diagnostics/available_diagnostics.md", + "For developpers" => "diagnostics/developpers_diagnostics.md", + "For users" => "diagnostics/users_diagnostics.md", +] diff --git a/docs/make.jl b/docs/make.jl index f1355b2e21..03bb4ff9dd 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -7,7 +7,7 @@ using Distributed using ClimaLand include("pages_helper.jl") tutorials = [ - "For model developers" => [ +#= "For model developers" => [ "Intro to standalone models" => "standalone/Usage/model_tutorial.jl", "Intro to multi-component models" => "standalone/Usage/LSM_single_column_tutorial.jl", "Intro to ClimaLand Domains" => "standalone/Usage/domain_tutorial.jl", @@ -42,7 +42,7 @@ tutorials = [ "standalone/Snow/base_tutorial.jl", "standalone/Snow/data_tutorial.jl", ], - ], + ],=# ] @everywhere const clima_dir = dirname(dirname(pathof(ClimaLand))); @everywhere source_dir = joinpath(@__DIR__, "src") @@ -84,6 +84,7 @@ ext_jl2md(x) = joinpath(basename(GENERATED_DIR), replace(x, ".jl" => ".md")) tutorials = transform_second(x -> ext_jl2md(x), tutorials) include("list_of_apis.jl") include("list_standalone_models.jl") +include("list_diagnostics.jl") pages = Any[ "Home" => "index.md", "APIs" => apis, @@ -91,6 +92,7 @@ pages = Any[ "Tutorials" => tutorials, "Repository structure" => "folderstructure.md", "Standalone models" => standalone_models, + "Diagnostics" => diagnostics, ] diff --git a/docs/src/diagnostics/available_diagnostics.md b/docs/src/diagnostics/available_diagnostics.md new file mode 100644 index 0000000000..d9bc061cd3 --- /dev/null +++ b/docs/src/diagnostics/available_diagnostics.md @@ -0,0 +1,6 @@ +# Available diagnostic variables + +Autogenerate table of available diagnostics: +```@example +include("diagnostics/make_diagnostic_table.jl") +``` diff --git a/docs/src/diagnostics/developpers_diagnostics.md b/docs/src/diagnostics/developpers_diagnostics.md new file mode 100644 index 0000000000..685340f677 --- /dev/null +++ b/docs/src/diagnostics/developpers_diagnostics.md @@ -0,0 +1,94 @@ +# Compute methods + +Each model defines all its compute methods in a script (bucket_compute_methods.jl for the bucket model, for example). +The structure of a diagnostic variable compute method is, for example: + +``` +function compute_albedo!(out, Y, p, t, land_model::BucketModel) + if isnothing(out) + return copy(p.bucket.α_sfc) + else + out .= p.bucket.α_sfc + end +end +``` + +It defines how to access your diagnostic (here, p.bucket.α_sfc), in your model type (here, ::BucketModel). + +We also define helper functions returning error messages if a user tries to compute a diagnostic variable that doesn't exist in their model type. + +``` +error_diagnostic_variable(variable, land_model::T) where {T} = + error("Cannot compute $variable with model = $T") + +compute_albedo!(_, _, _, _, land_model) = + error_diagnostic_variable("albedo", land_model) +``` + +# Define diagnostics + +Once the compute functions have been defined, they are added to `define_diagnostics!(land_model)`, which adds diagnostics variables to ALL_DIAGNOSTICS dict, +defined in diagnostic.jl. In these functions, you also define a `short_name`, `long_name`, `standard_name`, `units` and `comment`. For example: + +``` +add_diagnostic_variable!( + short_name = "alpha", + long_name = "Albedo", + standard_name = "albedo", + units = "", + compute! = (out, Y, p, t) -> compute_albedo!(out, Y, p, t, land_model), + ) +``` + +# Default diagnostics + +For each model, we define a function `default_diagnostics` which will define what diagnostic variables to compute by default for a specific model, and +on what schedule (for example, hourly average). For example, + +``` +function default_diagnostics(land_model::BucketModel, t_start; output_writer) + + define_diagnostics!(land_model) + + bucket_diagnostics = [ + "alpha", + "rn", + "tsfc", + "qsfc", + "lhf", + "rae", + "shf", + "vflux", + "rhosfc", + "t", + "w", + "ws", + "sigmas", + ] + + default_outputs = + hourly_averages(bucket_diagnostics...; output_writer, t_start) + return [default_outputs...] +end +``` + +is the default for the BucketModel, it will return hourly averages for the variables listed in `bucket_diagnostics` (which are all variables in the BucketModel). + +# Standard diagnostic frequencies + +We defined some functions of diagnostic schedule that may often be used in standard_diagnostic_frequencies.jl, for example + +``` +hourly_averages(short_names...; output_writer, t_start) = common_diagnostics( + 60 * 60 * one(t_start), + (+), + output_writer, + t_start, + short_names...; + pre_output_hook! = average_pre_output_hook!, +) +``` + +will return a list of `ScheduledDiagnostics` that compute the hourly average for the given variables listed in `short_names`. +We also, so far, provide functions for mins, maxs and averages aggregated monthly, over ten days, daily, and hourly. +As a developper, you may want to add more standard diagnostics here. \ No newline at end of file diff --git a/docs/src/diagnostics/make_diagnostic_table.jl b/docs/src/diagnostics/make_diagnostic_table.jl new file mode 100644 index 0000000000..73e1bfd47e --- /dev/null +++ b/docs/src/diagnostics/make_diagnostic_table.jl @@ -0,0 +1,27 @@ +import ClimaLand as CL +using PrettyTables + +# Print all available diagnostics to an ASCII table + +short_names = [] +long_names = [] +units = [] +comments = [] +standard_names = [] +for d in values(CL.Diagnostics.ALL_DIAGNOSTICS) + push!(short_names, d.short_name) + push!(long_names, d.long_name) + push!(units, d.units) + push!(comments, d.comments) + push!(standard_names, d.standard_name) +end +data = hcat(short_names, long_names, units, comments, standard_names) +pretty_table( + data; + autowrap = true, + linebreaks = true, + columns_width = [10, 15, 8, 32, 15], # Width = 80 + body_hlines = collect(1:size(data)[1]), + header = ["Short name", "Long name", "Units", "Comments", "Standard name"], + alignment = :l, +) diff --git a/docs/src/diagnostics/users_diagnostics.md b/docs/src/diagnostics/users_diagnostics.md new file mode 100644 index 0000000000..596874a217 --- /dev/null +++ b/docs/src/diagnostics/users_diagnostics.md @@ -0,0 +1,58 @@ +# default diagnostics + +Once you have defined your model and are ready to run a simulation, and after adding ClimaDiagnostics (using ClimaDiagnostics), + you can add default diagnostics to it by doing the following steps: + +1. define an output folder + +``` +mkdir("output") +output_dir = "output/" +``` + +you may want to check if this directory exist and remove it if so, so that when you re-run the simulation, you start fresh: + +``` +if isdir("output") + rm("output", force = true, recursive = true) +end + +mkdir("output") +output_dir = "output/" +``` + +2. define a space + +Your diagnostics will be written in time and space. These may be defined in your model, but usually land model space is a sphere with no vertical dimension. +You may have variables varying with soil depth, and so you will need: + +``` +space = bucket_domain.space.subsurface +``` + +3. define your writter + +Your diagnostics will be written in a Julia Dict or a netcdf file, for example. This is up to you. For a netcdf file, you define your writter like this: + +``` +nc_writer = ClimaDiagnostics.Writers.NetCDFWriter(space, output_dir) +``` + +providing the space and output_dir defined in steps 1. and 2. + +4. make your diagnostics on your model, using your writter, and define a callback + +Now that you defined your model and your writter, you can create a callback function to be called when solving your model. For example: + +``` +diags = ClimaLand.CLD.default_diagnostics(model, 1.0; output_writer = nc_writer) + +diagnostic_handler = + ClimaDiagnostics.DiagnosticsHandler(diags, Y, p, t0; dt = Δt) + +diag_cb = ClimaDiagnostics.DiagnosticsCallback(diagnostic_handler) + +sol = SciMLBase.solve(prob, ode_algo; dt = Δt, callback = diag_cb) +``` + +Your diagnostics have now been written in netcdf files in your output folder. \ No newline at end of file diff --git a/experiments/Project.toml b/experiments/Project.toml index cbf600f9d1..6ccb4f3696 100644 --- a/experiments/Project.toml +++ b/experiments/Project.toml @@ -1,7 +1,9 @@ [deps] CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +ClimaAnalysis = "29b5916a-a76c-4e73-9657-3c8fd22e65e6" ClimaComms = "3a4d1b5c-c61d-41fd-a00a-5873ba7a1b0d" ClimaCore = "d414da3d-4745-48bb-8d80-42e94e092884" +ClimaDiagnostics = "1ecacbb8-0713-4841-9a07-eb5aa8a2d53f" ClimaLand = "08f4d4ce-cf43-44bb-ad95-9d2d5f413532" ClimaParams = "5c42b081-d73a-476f-9059-fd94b934656c" ClimaTimeSteppers = "595c0a79-7f3d-439a-bc5a-b232dc3bde79" diff --git a/experiments/standalone/Bucket/global_bucket_diagnostics.jl b/experiments/standalone/Bucket/global_bucket_diagnostics.jl new file mode 100644 index 0000000000..9f0c06d9ae --- /dev/null +++ b/experiments/standalone/Bucket/global_bucket_diagnostics.jl @@ -0,0 +1,212 @@ +import SciMLBase +import ClimaComms +@static pkgversion(ClimaComms) >= v"0.6" && ClimaComms.@import_required_backends +using CairoMakie +using Dates +using DelimitedFiles +using Statistics + +import ClimaUtilities.TimeVaryingInputs: TimeVaryingInput + +import ClimaTimeSteppers as CTS +import NCDatasets +using ClimaCore +using ClimaCore: Remapping, Geometry +import ClimaParams as CP +import ClimaComms +import ClimaLand +import ClimaLand.Parameters as LP +using ClimaLand.Bucket: + BucketModel, BucketModelParameters, PrescribedSurfaceAlbedo +using ClimaLand.Domains: coordinates, Column +using ClimaLand: + initialize, + make_update_aux, + make_exp_tendency, + make_set_initial_cache, + PrescribedAtmosphere, + PrescribedRadiativeFluxes + +PROFILING = false +try + import Profile, ProfileCanvas + global PROFILING = true + @info "ProfileCanvas found, running with profiler" +catch +end + +""" + compute_extrema(v) + +Computes and returns the minimum value in `v` and +the maximum value in `v`, as a tuple, assuming that +`v` is a vector of arrays. +""" +function compute_extrema(v) + maxes = [maximum(u) for u in v] + mins = [minimum(u) for u in v] + return (minimum(mins), maximum(maxes)) +end + +anim_plots = false +FT = Float64; +context = ClimaComms.context() +earth_param_set = LP.LandParameters(FT); +outdir = joinpath( + pkgdir(ClimaLand), + "experiments/standalone/Bucket/artifacts_temporalmap", +) +device_suffix = + typeof(ClimaComms.context().device) <: ClimaComms.CPUSingleThreaded ? + "cpu" : "gpu" +!ispath(outdir) && mkpath(outdir) +# Use separate output directory for CPU and GPU runs to avoid race condition +device_suffix = + typeof(ClimaComms.context().device) <: ClimaComms.CPUSingleThreaded ? + "cpu" : "gpu" +t0 = 0.0; +tf = 2 * 86400; +Δt = 3600.0; + + +# function setup_prob(t0, tf, Δt) +# We set up the problem in a function so that we can make multiple copies (for profiling) + +# Set up simulation domain +soil_depth = FT(3.5) +bucket_domain = ClimaLand.Domains.SphericalShell(; + radius = FT(6.3781e6), + depth = soil_depth, + nelements = (10, 10), # this failed with (50,10) + npolynomial = 1, + dz_tuple = FT.((1.0, 0.05)), +) +ref_time = DateTime(2005) + +# Initialize parameters +σS_c = FT(0.2) +W_f = FT(0.15) +z_0m = FT(1e-2) +z_0b = FT(1e-3) +κ_soil = FT(0.7) +ρc_soil = FT(2e6) +τc = FT(3600) + +surface_space = bucket_domain.space.surface +# Construct albedo parameter object using temporal map +albedo = PrescribedSurfaceAlbedo{FT}(ref_time, t0, surface_space) + +bucket_parameters = BucketModelParameters(FT; albedo, z_0m, z_0b, τc) + +# Precipitation: +precip = (t) -> 0 +snow_precip = (t) -> -5e-7 * (t < 1 * 86400) +# Diurnal temperature variations: +T_atmos = (t) -> 275.0 + 5.0 * sin(2.0 * π * t / 86400 - π / 2) +# Constant otherwise: +u_atmos = (t) -> 3.0 +q_atmos = (t) -> 0.001 +h_atmos = FT(2) +P_atmos = (t) -> 101325 + +bucket_atmos = PrescribedAtmosphere( + TimeVaryingInput(precip), + TimeVaryingInput(snow_precip), + TimeVaryingInput(T_atmos), + TimeVaryingInput(u_atmos), + TimeVaryingInput(q_atmos), + TimeVaryingInput(P_atmos), + ref_time, + h_atmos, + earth_param_set, +) + +# Prescribed radiation -- a prescribed downwelling SW diurnal cycle, with a +# peak at local noon, and a prescribed downwelling LW radiative +# flux, assuming the air temperature is on average 275 degrees +# K with a diurnal amplitude of 5 degrees K: +SW_d = (t) -> max(1361 * sin(2π * t / 86400 - π / 2), 0.0) +LW_d = (t) -> 5.67e-8 * (275.0 + 5.0 * sin(2.0 * π * t / 86400 - π / 2))^4 +bucket_rad = PrescribedRadiativeFluxes( + FT, + TimeVaryingInput(SW_d), + TimeVaryingInput(LW_d), + ref_time, +) + + +model = BucketModel( + parameters = bucket_parameters, + domain = bucket_domain, + atmosphere = bucket_atmos, + radiation = bucket_rad, +) + +Y, p, _coords = initialize(model) + +Y.bucket.T .= FT(270) +Y.bucket.W .= FT(0.05) +Y.bucket.Ws .= FT(0.0) +Y.bucket.σS .= FT(0.08) + +set_initial_cache! = make_set_initial_cache(model) +set_initial_cache!(p, Y, t0) +exp_tendency! = make_exp_tendency(model) +prob = SciMLBase.ODEProblem( + CTS.ClimaODEFunction((T_exp!) = exp_tendency!, (dss!) = ClimaLand.dss!), + Y, + (t0, tf), + p, +) +saveat = collect(t0:(Δt * 3):tf) +saved_values = (; + t = Array{Float64}(undef, length(saveat)), + saveval = Array{NamedTuple}(undef, length(saveat)), +) +saving_cb = ClimaLand.NonInterpSavingCallback(saved_values, saveat) +updateat = copy(saveat) +updatefunc = ClimaLand.make_update_drivers(bucket_atmos, bucket_rad) +driver_cb = ClimaLand.DriverUpdateCallback(updateat, updatefunc) +cb = SciMLBase.CallbackSet(driver_cb, saving_cb) + +# return prob, cb, saveat, saved_values +# end + +# prob, cb, saveat, saved_values = setup_prob(t0, tf, Δt); +timestepper = CTS.RK4() +ode_algo = CTS.ExplicitAlgorithm(timestepper) + +#### ClimaDiagnostics #### +using ClimaDiagnostics + +if isdir("output") + rm("output", force = true, recursive = true) +end + +mkdir("output") +output_dir = "output/" + +space = bucket_domain.space.subsurface + +nc_writer = ClimaDiagnostics.Writers.NetCDFWriter(space, output_dir) + +diags = ClimaLand.CLD.default_diagnostics(model, 1.0; output_writer = nc_writer) + +diagnostic_handler = + ClimaDiagnostics.DiagnosticsHandler(diags, Y, p, t0; dt = Δt) + +diag_cb = ClimaDiagnostics.DiagnosticsCallback(diagnostic_handler) + +sol_test = SciMLBase.solve(prob, ode_algo; dt = Δt, callback = diag_cb) + +#### ClimaAnalysis #### + +using ClimaAnalysis +simdir = ClimaAnalysis.SimDir("output") +println(summary(simdir)) +alpha = get(simdir; short_name = "alpha") +alpha.dims +import ClimaAnalysis.Visualize as viz +fig = CairoMakie.Figure(size = (400, 600)) +viz.plot!(fig, alpha, time = 2 * 86400) +CairoMakie.save("alpha.png", fig) diff --git a/lib/ClimaLandSimulations/Project.toml b/lib/ClimaLandSimulations/Project.toml index 812b3fe89b..225e6abd77 100644 --- a/lib/ClimaLandSimulations/Project.toml +++ b/lib/ClimaLandSimulations/Project.toml @@ -9,6 +9,7 @@ Bonito = "824d6782-a2ef-11e9-3a09-e5662e0c26f8" CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" ClimaComms = "3a4d1b5c-c61d-41fd-a00a-5873ba7a1b0d" ClimaCore = "d414da3d-4745-48bb-8d80-42e94e092884" +ClimaDiagnostics = "1ecacbb8-0713-4841-9a07-eb5aa8a2d53f" ClimaLand = "08f4d4ce-cf43-44bb-ad95-9d2d5f413532" ClimaParams = "5c42b081-d73a-476f-9059-fd94b934656c" ClimaTimeSteppers = "595c0a79-7f3d-439a-bc5a-b232dc3bde79" @@ -42,9 +43,12 @@ cuDNN = "02a925ec-e4fe-4b08-9a7e-0d78e3d38ccd" ArtifactWrappers = "0.2" Bonito = "3" CairoMakie = "0.11" -ClimaComms = "0.5.6" -ClimaParams = "0.10" +ClimaComms = "0.5.6, 0.6" +ClimaCore = "0.13.2, 0.14" +ClimaDiagnostics = "0.2" +ClimaParams = "0.10.2" ClimaTimeSteppers = "0.7" +ClimaUtilities = "0.1.2" DataFrames = "1" Dates = "1" DelimitedFiles = "1" diff --git a/lib/ClimaLandSimulations/README.md b/lib/ClimaLandSimulations/README.md index d24b377315..9bc8eaf0fa 100644 --- a/lib/ClimaLandSimulations/README.md +++ b/lib/ClimaLandSimulations/README.md @@ -17,11 +17,11 @@ For examples, see the [experiments](https://github.com/CliMA/ClimaLand.jl/lib/Cl ### Development of the `ClimaLandSimulations` subpackage - cd ClimaCore/lib/ClimaLandSimulations + cd ClimaLand/lib/ClimaLandSimulations - # Add ClimaCore to subpackage environment + # Add ClimaLand to subpackage environment julia --project -e 'using Pkg; Pkg.develop(path="../../")' - # Instantiate ClimaCoreMakie project environment + # Instantiate ClimaLandSimulations project environment julia --project -e 'using Pkg; Pkg.instantiate()' julia --project -e 'using Pkg; Pkg.test()' diff --git a/lib/ClimaLandSimulations/src/ClimaLandSimulations.jl b/lib/ClimaLandSimulations/src/ClimaLandSimulations.jl index 0bea798b49..bdc60e6e9d 100644 --- a/lib/ClimaLandSimulations/src/ClimaLandSimulations.jl +++ b/lib/ClimaLandSimulations/src/ClimaLandSimulations.jl @@ -1,6 +1,7 @@ module ClimaLandSimulations using CairoMakie +using ClimaDiagnostics using WGLMakie using DataFrames using LaTeXStrings diff --git a/src/ClimaLand.jl b/src/ClimaLand.jl index 1c68029a1d..39f72d766d 100644 --- a/src/ClimaLand.jl +++ b/src/ClimaLand.jl @@ -293,6 +293,10 @@ initialize. """ lsm_aux_domain_names(m::AbstractLandModel) = () +# Diagnostics +include(joinpath("Diagnostics", "Diagnostics.jl")) +import .Diagnostics as CLD # ClimaLand Diagnostics + # Methods extended by the LSM models we support include("standalone/SurfaceWater/Pond.jl") using .Pond diff --git a/src/Diagnostics/Diagnostics.jl b/src/Diagnostics/Diagnostics.jl new file mode 100644 index 0000000000..e360a48bc2 --- /dev/null +++ b/src/Diagnostics/Diagnostics.jl @@ -0,0 +1,18 @@ +module Diagnostics + +import ClimaComms + +using ..Bucket: BucketModel + +# using ClimaLand: SoilCanopyModel # This bugs, but I do need SoilCanopyModel... + +import ClimaDiagnostics: + DiagnosticVariable, ScheduledDiagnostic, average_pre_output_hook! + +import ClimaDiagnostics.Schedules: EveryStepSchedule, EveryDtSchedule + +import ClimaDiagnostics.Writers: HDF5Writer, NetCDFWriter + +include("diagnostic.jl") + +end diff --git a/src/Diagnostics/bucket_compute_methods.jl b/src/Diagnostics/bucket_compute_methods.jl new file mode 100644 index 0000000000..06a54e3750 --- /dev/null +++ b/src/Diagnostics/bucket_compute_methods.jl @@ -0,0 +1,172 @@ +# General helper functions for undefined diagnostics for a particular model +error_diagnostic_variable(variable, land_model::T) where {T} = + error("Cannot compute $variable with model = $T") + +#compute_soil_temperature!(_, _, _, _, model::T) where {T} = +# error_diagnostic_variable("soil_temperature", model) + +compute_albedo!(_, _, _, _, land_model) = + error_diagnostic_variable("albedo", land_model) +compute_net_radiation!(_, _, _, _, land_model) = + error_diagnostic_variable("net_radiation", land_model) +compute_surface_temperature!(_, _, _, _, land_model) = + error_diagnostic_variable("surface_temperature", land_model) +compute_surface_specific_humidity!(_, _, _, _, land_model) = + error_diagnostic_variable("surface_specific_humidity", land_model) +compute_latent_heat_flux!(_, _, _, _, land_model) = + error_diagnostic_variable("latent_heat_flux", land_model) +compute_aerodynamic_resistance!(_, _, _, _, land_model) = + error_diagnostic_variable("aerodynamic_resistance", land_model) +compute_sensible_heat_flux!(_, _, _, _, land_model) = + error_diagnostic_variable("sensible_heat_flux", land_model) +compute_vapor_flux!(_, _, _, _, land_model) = + error_diagnostic_variable("vapor_flux", land_model) +compute_surface_air_density!(_, _, _, _, land_model) = + error_diagnostic_variable("surface_air_density", land_model) +compute_soil_temperature!(_, _, _, _, land_model) = + error_diagnostic_variable("soil_temperature", land_model) +compute_subsurface_water_storage!(_, _, _, _, land_model) = + error_diagnostic_variable("subsurface_water_storage", land_model) +compute_surface_water_content!(_, _, _, _, land_model) = + error_diagnostic_variable("surface_water_content", land_model) +compute_snow_water_equivalent!(_, _, _, _, land_model) = + error_diagnostic_variable("snow_water_equivalent", land_model) + +# stored in p + +function compute_albedo!(out, Y, p, t, land_model::BucketModel) + if isnothing(out) + return copy(p.bucket.α_sfc) + else + out .= p.bucket.α_sfc + end +end + +function compute_net_radiation!(out, Y, p, t, land_model::BucketModel) + if isnothing(out) + return copy(p.bucket.R_n) + else + out .= p.bucket.R_n + end +end + +function compute_surface_temperature!(out, Y, p, t, land_model::BucketModel) + if isnothing(out) + return copy(p.bucket.T_sfc) + else + out .= p.bucket.T_sfc + end +end + +function compute_surface_specific_humidity!( + out, + Y, + p, + t, + land_model::BucketModel, +) + if isnothing(out) + return copy(p.bucket.q_sfc) + else + out .= p.bucket.q_sfc + end +end + +function compute_latent_heat_flux!(out, Y, p, t, land_model::BucketModel) + if isnothing(out) + return copy(p.bucket.turbulent_fluxes.lhf) + else + out .= p.bucket.turbulent_fluxes.lhf + end +end + +function compute_aerodynamic_resistance!(out, Y, p, t, land_model::BucketModel) + if isnothing(out) + return copy(p.bucket.turbulent_fluxes.r_ae) + else + out .= p.bucket.turbulent_fluxes.r_ae + end +end + +function compute_sensible_heat_flux!(out, Y, p, t, land_model::BucketModel) + if isnothing(out) + return copy(p.bucket.turbulent_fluxes.shf) + else + out .= p.bucket.turbulent_fluxes.shf + end +end + +function compute_vapor_flux!(out, Y, p, t, land_model::BucketModel) + if isnothing(out) + return copy(p.bucket.turbulent_fluxes.vapor_flux) + else + out .= p.bucket.turbulent_fluxes.vapor_flux + end +end + +function compute_surface_air_density!(out, Y, p, t, land_model::BucketModel) + if isnothing(out) + return copy(p.bucket.ρ_sfc) + else + out .= p.bucket.ρ_sfc + end +end + +# stored in Y + +function compute_soil_temperature!(out, Y, p, t, land_model::BucketModel) + if isnothing(out) + return copy(Y.bucket.T) + else + out .= Y.bucket.T + end +end + +function compute_subsurface_water_storage!( + out, + Y, + p, + t, + land_model::BucketModel, +) + if isnothing(out) + return copy(Y.bucket.W) + else + out .= Y.bucket.W + end +end + +function compute_surface_water_content!(out, Y, p, t, land_model::BucketModel) + if isnothing(out) + return copy(Y.bucket.Ws) + else + out .= Y.bucket.Ws + end +end + +function compute_snow_water_equivalent!(out, Y, p, t, land_model::BucketModel) + if isnothing(out) + return copy(Y.bucket.σS) + else + out .= Y.bucket.σS + end +end + + + +## below would be in SoilCanopyModel_compute_methods.jl +#= +function compute_soil_temperature!( + out, + Y, + p, + t, + land_model::SoilCanopyModel, + ) + if isnothing(out) + return copy(p.soil.T) + else + out .= p.soil.T + end +end +=# diff --git a/src/Diagnostics/default_diagnostics.jl b/src/Diagnostics/default_diagnostics.jl new file mode 100644 index 0000000000..a83846e76c --- /dev/null +++ b/src/Diagnostics/default_diagnostics.jl @@ -0,0 +1,63 @@ +export default_diagnostics + +# This file is included by Diagnostics.jl and defines all the defaults for various models (e.g., Bucket, SoilCanopyModel). +# A model here is either a standalone (e.g., Bucket) or integrated (e.g., SoilCanopy) model. +# +# If you are developing new models, add your defaults here. If you want to add more high +# level interfaces, add them here. Feel free to include extra files. + + +# Bucket model + +""" + produce_common_diagnostic_function(period, reduction) + +Helper function to define functions like `daily_max`. +""" +function common_diagnostics( + period, + reduction, + output_writer, + t_start, + short_names...; + pre_output_hook! = nothing, +) + return [ + ScheduledDiagnostic( + variable = get_diagnostic_variable(short_name), + compute_schedule_func = EveryStepSchedule(), + output_schedule_func = EveryDtSchedule(period; t_start), + reduction_time_func = reduction, + output_writer = output_writer, + pre_output_hook! = pre_output_hook!, + ) for short_name in short_names + ] +end + +include("standard_diagnostic_frequencies.jl") + +# Bucket +function default_diagnostics(land_model::BucketModel, t_start; output_writer) + + define_diagnostics!(land_model) + + bucket_diagnostics = [ + "alpha", + "rn", + "tsfc", + "qsfc", + "lhf", + "rae", + "shf", + "vflux", + "rhosfc", + "t", + "w", + "ws", + "sigmas", + ] + + default_outputs = + hourly_averages(bucket_diagnostics...; output_writer, t_start) + return [default_outputs...] +end diff --git a/src/Diagnostics/define_diagnostics.jl b/src/Diagnostics/define_diagnostics.jl new file mode 100644 index 0000000000..19efea6fac --- /dev/null +++ b/src/Diagnostics/define_diagnostics.jl @@ -0,0 +1,143 @@ +""" + define_diagnostics!(land_model) + +Calls add_diagnostic_viariable! for all variables +""" +function define_diagnostics!(land_model) + + # Stored in p + + # Albedo + add_diagnostic_variable!( + short_name = "alpha", + long_name = "Albedo", + standard_name = "albedo", + units = "", + compute! = (out, Y, p, t) -> compute_albedo!(out, Y, p, t, land_model), + ) + + # Net radiation + add_diagnostic_variable!( + short_name = "rn", + long_name = "Net Radiation", + standard_name = "net_radiation", + units = "W m^-2", + compute! = (out, Y, p, t) -> + compute_net_radiation!(out, Y, p, t, land_model), + ) + + # Surface temperature + add_diagnostic_variable!( + short_name = "tsfc", + long_name = "Surface Temperature", + standard_name = "surface_temperature", + units = "K", + compute! = (out, Y, p, t) -> + compute_surface_temperature!(out, Y, p, t, land_model), + ) + + # Surface specific humidity + add_diagnostic_variable!( + short_name = "qsfc", + long_name = "Surface Specific Humidity", + standard_name = "surface_specific_humidity", + units = "", + compute! = (out, Y, p, t) -> + compute_surface_specific_humidity!(out, Y, p, t, land_model), + ) + + # Latent heat flux + add_diagnostic_variable!( + short_name = "lhf", + long_name = "Latent Heat Flux", + standard_name = "latent_heat_flux", + units = "W m^-2", + compute! = (out, Y, p, t) -> + compute_latent_heat_flux!(out, Y, p, t, land_model), + ) + + # Aerodynamic resistance + add_diagnostic_variable!( + short_name = "rae", + long_name = "Aerodynamic Resistance", + standard_name = "aerodynamic_resistance", + units = "m s^-1", + compute! = (out, Y, p, t) -> + compute_aerodynamic_resistance!(out, Y, p, t, land_model), + ) + + # Sensible heat flux + add_diagnostic_variable!( + short_name = "shf", + long_name = "Sensible Heat Flux", + standard_name = "sensible_heat_flux", + units = "W m^-2", + compute! = (out, Y, p, t) -> + compute_sensible_heat_flux!(out, Y, p, t, land_model), + ) + + # Vapor flux + add_diagnostic_variable!( + short_name = "vflux", + long_name = "Liquid water evaporation", + standard_name = "vapor_flux", + units = "m s^-1", + compute! = (out, Y, p, t) -> + compute_vapor_flux!(out, Y, p, t, land_model), + ) + + # Surface air density + add_diagnostic_variable!( + short_name = "rhosfc", + long_name = "Surface Air Density", + standard_name = "surface_air_density", + units = "kg m^−3", + compute! = (out, Y, p, t) -> + compute_surface_air_density!(out, Y, p, t, land_model), + ) + + # Stored in Y + + # Soil temperature (3D) at depth + add_diagnostic_variable!( + short_name = "t", + long_name = "Soil temperature", + standard_name = "soil_temperature", + units = "K", + compute! = (out, Y, p, t) -> + compute_soil_temperature!(out, Y, p, t, land_model), + ) + + # Surbsurface water storage + add_diagnostic_variable!( + short_name = "w", + long_name = "subsurface Water Storage", + standard_name = "subsurface_water_storage", + units = "m", + compute! = (out, Y, p, t) -> + compute_subsurface_water_storage!(out, Y, p, t, land_model), + ) + + # Surface water content + add_diagnostic_variable!( + short_name = "ws", + long_name = "Surface Water Content", + standard_name = "surface_water_content", + units = "m", + compute! = (out, Y, p, t) -> + compute_surface_water_content!(out, Y, p, t, land_model), + ) + + # Surface water content + add_diagnostic_variable!( + short_name = "sigmas", + long_name = "Snow Water Equivalent", + standard_name = "snow_water_equivalent", + units = "m", + compute! = (out, Y, p, t) -> + compute_snow_water_equivalent!(out, Y, p, t, land_model), + ) + +end + +define_diagnostics!(1.0) # populates ALL_DIAGNOSTICS from diagnostic.jl diff --git a/src/Diagnostics/diagnostic.jl b/src/Diagnostics/diagnostic.jl new file mode 100644 index 0000000000..6582287784 --- /dev/null +++ b/src/Diagnostics/diagnostic.jl @@ -0,0 +1,100 @@ +# ClimaLand diagnostics + +# - A dictionary `ALL_DIAGNOSTICS` with all the diagnostics we know how to compute, keyed +# over their short name. If you want to add more diagnostics, look at the included files. +# You can add your own file if you want to define several new diagnostics that are +# conceptually related. The dictionary `ALL_DIAGNOSTICS` should be considered an +# implementation detail. + +const ALL_DIAGNOSTICS = Dict{String, DiagnosticVariable}() + +""" + + add_diagnostic_variable!(; short_name, + long_name, + standard_name, + units, + description, + compute!) + + +Add a new variable to the `ALL_DIAGNOSTICS` dictionary (this function mutates the state of +`ClimaLand.ALL_DIAGNOSTICS`). + +If possible, please follow the naming scheme outline in +https://airtable.com/appYNLuWqAgzLbhSq/shrKcLEdssxb8Yvcp/tblL7dJkC3vl5zQLb + +Keyword arguments +================= + + +- `short_name`: Name used to identify the variable in the output files and in the file + names. Short but descriptive. `ClimaLand` diagnostics are identified by the + short name. We follow the Coupled Model Intercomparison Project conventions. + +- `long_name`: Name used to identify the variable in the output files. + +- `standard_name`: Standard name, as in + http://cfconventions.org/Data/cf-standard-names/71/build/cf-standard-name-table.html + +- `units`: Physical units of the variable. + +- `comments`: More verbose explanation of what the variable is, or comments related to how + it is defined or computed. + +- `compute!`: Function that compute the diagnostic variable from the state. + It has to take two arguments: the `integrator`, and a + pre-allocated area of memory where to write the result of the + computation. If no pre-allocated area is available, a new + one will be allocated. To avoid extra allocations, this + function should perform the calculation in-place (i.e., using + `.=`). + +""" +function add_diagnostic_variable!(; + short_name, + long_name, + standard_name = "", + units, + comments = "", + compute!, +) + haskey(ALL_DIAGNOSTICS, short_name) && @warn( + "overwriting diagnostic `$short_name` entry containing fields\n" * + "$(map( + field -> "$(getfield(ALL_DIAGNOSTICS[short_name], field))", + filter(field -> field != :compute!, fieldnames(DiagnosticVariable)), + ))" + ) + + + ALL_DIAGNOSTICS[short_name] = DiagnosticVariable(; + short_name, + long_name, + standard_name, + units, + comments, + compute!, + ) +end + +""" + + get_diagnostic_variable!(short_name) + + +Return a `DiagnosticVariable` from its `short_name`, if it exists. +""" +function get_diagnostic_variable(short_name) + haskey(ALL_DIAGNOSTICS, short_name) || + error("diagnostic $short_name does not exist") + + return ALL_DIAGNOSTICS[short_name] +end + +# Do you want to define more diagnostics? Add them here +include("bucket_compute_methods.jl") +include("define_diagnostics.jl") + +# Default diagnostics and higher level interfaces +include("default_diagnostics.jl") diff --git a/src/Diagnostics/standard_diagnostic_frequencies.jl b/src/Diagnostics/standard_diagnostic_frequencies.jl new file mode 100644 index 0000000000..1410d41d8e --- /dev/null +++ b/src/Diagnostics/standard_diagnostic_frequencies.jl @@ -0,0 +1,264 @@ +""" + monthly_maxs(short_names...; output_writer, t_start) + +Return a list of `ScheduledDiagnostics` that compute the monthly max for the given variables. + +A month is defined as 30 days. +""" +monthly_maxs(short_names...; output_writer, t_start) = common_diagnostics( + 30 * 24 * 60 * 60 * one(t_start), + max, + output_writer, + t_start, + short_names..., +) +""" + monthly_max(short_names; output_writer, t_start) + +Return a `ScheduledDiagnostics` that computes the monthly max for the given variable. + +A month is defined as 30 days. +""" +monthly_max(short_names; output_writer, t_start) = + monthly_maxs(short_names; output_writer, t_start)[1] + +""" + monthly_mins(short_names...; output_writer, t_start) + +Return a list of `ScheduledDiagnostics` that compute the monthly min for the given variables. +""" +monthly_mins(short_names...; output_writer, t_start) = common_diagnostics( + 30 * 24 * 60 * 60 * one(t_start), + min, + output_writer, + t_start, + short_names..., +) +""" + monthly_min(short_names; output_writer, t_start) + +Return a `ScheduledDiagnostics` that computes the monthly min for the given variable. + +A month is defined as 30 days. +""" +monthly_min(short_names; output_writer, t_start) = + monthly_mins(short_names; output_writer, t_start)[1] + +""" + monthly_averages(short_names...; output_writer, t_start) + +Return a list of `ScheduledDiagnostics` that compute the monthly average for the given variables. + +A month is defined as 30 days. +""" +# An average is just a sum with a normalization before output +monthly_averages(short_names...; output_writer, t_start) = common_diagnostics( + 30 * 24 * 60 * 60 * one(t_start), + (+), + output_writer, + t_start, + short_names...; + pre_output_hook! = average_pre_output_hook!, +) +""" + monthly_average(short_names; output_writer, t_start) + +Return a `ScheduledDiagnostics` that compute the monthly average for the given variable. + +A month is defined as 30 days. +""" +# An average is just a sum with a normalization before output +monthly_average(short_names; output_writer, t_start) = + monthly_averages(short_names; output_writer, t_start)[1] + +""" + tendaily_maxs(short_names...; output_writer, t_start) + +Return a list of `ScheduledDiagnostics` that compute the max over ten days for the given variables. +""" +tendaily_maxs(short_names...; output_writer, t_start) = common_diagnostics( + 10 * 24 * 60 * 60 * one(t_start), + max, + output_writer, + t_start, + short_names..., +) +""" + tendaily_max(short_names; output_writer, t_start) + +Return a `ScheduledDiagnostics` that computes the max over ten days for the given variable. +""" +tendaily_max(short_names; output_writer, t_start) = + tendaily_maxs(short_names; output_writer, t_start)[1] + +""" + tendaily_mins(short_names...; output_writer, t_start) + +Return a list of `ScheduledDiagnostics` that compute the min over ten days for the given variables. +""" +tendaily_mins(short_names...; output_writer, t_start) = common_diagnostics( + 10 * 24 * 60 * 60 * one(t_start), + min, + output_writer, + t_start, + short_names..., +) +""" + tendaily_min(short_names; output_writer, t_start) + +Return a `ScheduledDiagnostics` that computes the min over ten days for the given variable. +""" +tendaily_min(short_names; output_writer, t_start) = + tendaily_mins(short_names; output_writer, t_start)[1] + +""" + tendaily_averages(short_names...; output_writer, t_start) + +Return a list of `ScheduledDiagnostics` that compute the average over ten days for the given variables. +""" +# An average is just a sum with a normalization before output +tendaily_averages(short_names...; output_writer, t_start) = common_diagnostics( + 10 * 24 * 60 * 60 * one(t_start), + (+), + output_writer, + t_start, + short_names...; + pre_output_hook! = average_pre_output_hook!, +) +""" + tendaily_average(short_names; output_writer, t_start) + +Return a `ScheduledDiagnostics` that compute the average over ten days for the given variable. +""" +# An average is just a sum with a normalization before output +tendaily_average(short_names; output_writer, t_start) = + tendaily_averages(short_names; output_writer, t_start)[1] + +""" + daily_maxs(short_names...; output_writer, t_start) + +Return a list of `ScheduledDiagnostics` that compute the daily max for the given variables. +""" +daily_maxs(short_names...; output_writer, t_start) = common_diagnostics( + 24 * 60 * 60 * one(t_start), + max, + output_writer, + t_start, + short_names..., +) +""" + daily_max(short_names; output_writer, t_start) + + +Return a `ScheduledDiagnostics` that computes the daily max for the given variable. +""" +daily_max(short_names; output_writer, t_start) = + daily_maxs(short_names; output_writer, t_start)[1] + +""" + daily_mins(short_names...; output_writer, t_start) + +Return a list of `ScheduledDiagnostics` that compute the daily min for the given variables. +""" +daily_mins(short_names...; output_writer, t_start) = common_diagnostics( + 24 * 60 * 60 * one(t_start), + min, + output_writer, + t_start, + short_names..., +) +""" + daily_min(short_names; output_writer, t_start) + +Return a `ScheduledDiagnostics` that computes the daily min for the given variable. +""" +daily_min(short_names; output_writer, t_start) = + daily_mins(short_names; output_writer, t_start)[1] + +""" + daily_averages(short_names...; output_writer, t_start) + +Return a list of `ScheduledDiagnostics` that compute the daily average for the given variables. +""" +# An average is just a sum with a normalization before output +daily_averages(short_names...; output_writer, t_start) = common_diagnostics( + 24 * 60 * 60 * one(t_start), + (+), + output_writer, + t_start, + short_names...; + pre_output_hook! = average_pre_output_hook!, +) +""" + daily_average(short_names; output_writer, t_start) + +Return a `ScheduledDiagnostics` that compute the daily average for the given variable. +""" +# An average is just a sum with a normalization before output +daily_average(short_names; output_writer, t_start) = + daily_averages(short_names; output_writer, t_start)[1] + +""" + hourly_maxs(short_names...; output_writer, t_start) + +Return a list of `ScheduledDiagnostics` that compute the hourly max for the given variables. +""" +hourly_maxs(short_names...; output_writer, t_start) = common_diagnostics( + 60 * 60 * one(t_start), + max, + output_writer, + t_start, + short_names..., +) + +""" + hourly_max(short_names; output_writer, t_start) + +Return a `ScheduledDiagnostics` that computes the hourly max for the given variable. +""" +hourly_max(short_names; output_writer, t_start) = + hourly_maxs(short_names; output_writer, t_start)[1] + +""" + hourly_mins(short_names...; output_writer, t_start) + +Return a list of `ScheduledDiagnostics` that compute the hourly min for the given variables. +""" +hourly_mins(short_names...; output_writer, t_start) = common_diagnostics( + 60 * 60 * one(t_start), + min, + output_writer, + t_start, + short_names..., +) +""" + hourly_mins(short_names...; output_writer, t_start) + + +Return a `ScheduledDiagnostics` that computes the hourly min for the given variable. +""" +hourly_min(short_names; output_writer, t_start) = + hourly_mins(short_names; output_writer, t_start)[1] + +# An average is just a sum with a normalization before output +""" + hourly_averages(short_names...; output_writer, t_start) + +Return a list of `ScheduledDiagnostics` that compute the hourly average for the given variables. +""" +hourly_averages(short_names...; output_writer, t_start) = common_diagnostics( + 60 * 60 * one(t_start), + (+), + output_writer, + t_start, + short_names...; + pre_output_hook! = average_pre_output_hook!, +) + +""" + hourly_average(short_names...; output_writer, t_start) + +Return a `ScheduledDiagnostics` that computes the hourly average for the given variable. +""" +hourly_average(short_names; output_writer, t_start) = + hourly_averages(short_names; output_writer, t_start)[1]