From 0d78fc4850397392aee8a5fe67493a8f479de86d Mon Sep 17 00:00:00 2001 From: AlexisRenchon Date: Tue, 16 Jul 2024 03:57:47 -0700 Subject: [PATCH] Add SoilCanopyModel diagnostics This PR finalizes the addition of SoilCanopyModel diagnostics. It also adds a macro to generate land compute functions, And an optional argument for period averaging for default diagnostics. Co-authored-by: AlexisRenchon Co-authored-by: SBozzolo Co-authored-by: kmdeck Co-authored-by: braghiere --- .buildkite/Project.toml | 1 + docs/src/diagnostics/make_diagnostic_table.jl | 9 +- docs/src/diagnostics/users_diagnostics.md | 4 + .../integrated/global/global_soil_canopy.jl | 145 ++-- experiments/long_runs/land.jl | 59 +- experiments/long_runs/soil.jl | 6 +- src/diagnostics/bucket_compute_methods.jl | 119 ---- src/diagnostics/default_diagnostics.jl | 86 ++- src/diagnostics/define_diagnostics.jl | 648 +++++++++++------- src/diagnostics/diagnostic.jl | 3 +- src/diagnostics/land_compute_methods.jl | 139 ++++ src/diagnostics/soilcanopy_compute_methods.jl | 603 ---------------- 12 files changed, 767 insertions(+), 1055 deletions(-) delete mode 100644 src/diagnostics/bucket_compute_methods.jl create mode 100644 src/diagnostics/land_compute_methods.jl delete mode 100644 src/diagnostics/soilcanopy_compute_methods.jl diff --git a/.buildkite/Project.toml b/.buildkite/Project.toml index 7550071bcd..fd887f0dc3 100644 --- a/.buildkite/Project.toml +++ b/.buildkite/Project.toml @@ -42,4 +42,5 @@ cuDNN = "02a925ec-e4fe-4b08-9a7e-0d78e3d38ccd" [compat] ClimaTimeSteppers = "0.7" +ClimaAnalysis = "0.5.7" Statistics = "1" diff --git a/docs/src/diagnostics/make_diagnostic_table.jl b/docs/src/diagnostics/make_diagnostic_table.jl index e7b7cedab6..4839efd307 100644 --- a/docs/src/diagnostics/make_diagnostic_table.jl +++ b/docs/src/diagnostics/make_diagnostic_table.jl @@ -16,7 +16,14 @@ for d in values(CL.Diagnostics.ALL_DIAGNOSTICS) push!(comments, d.comments) push!(standard_names, d.standard_name) end -data = hcat(short_names, long_names, units, comments, standard_names) +i = sortperm(short_names) # indices of short_names sorted alphabetically +data = hcat( + short_names[i], + long_names[i], + units[i], + comments[i], + standard_names[i], +) pretty_table( data; autowrap = true, diff --git a/docs/src/diagnostics/users_diagnostics.md b/docs/src/diagnostics/users_diagnostics.md index 0c9728b579..c310ef0a75 100644 --- a/docs/src/diagnostics/users_diagnostics.md +++ b/docs/src/diagnostics/users_diagnostics.md @@ -58,6 +58,10 @@ sol = SciMLBase.solve(prob, ode_algo; dt = Δt, callback = diag_cb) Your diagnostics have now been written in netcdf files in your output folder. +Note that by default, `default_diagnostics` assign two optional kwargs: `output_vars = :long` and `average_period` = :hourly. +`output_vars = :long` will write all available diagnostics, whereas `output_vars = :short` will only write essentials diagnostics. +`average_period` defines the period over which diagnostics are averaged, it can be set to `:hourly`, `:daily` and `:monthly`. + # Custom Diagnostics When defining a custom diagnostic, follow these steps: diff --git a/experiments/integrated/global/global_soil_canopy.jl b/experiments/integrated/global/global_soil_canopy.jl index 137e244b84..4588eb5cf0 100644 --- a/experiments/integrated/global/global_soil_canopy.jl +++ b/experiments/integrated/global/global_soil_canopy.jl @@ -24,6 +24,11 @@ using Statistics using Dates import NCDatasets +using ClimaDiagnostics +using ClimaAnalysis +import ClimaAnalysis.Visualize as viz +using ClimaUtilities + regridder_type = :InterpolationsRegridder extrapolation_bc = (Interpolations.Periodic(), Interpolations.Flat(), Interpolations.Flat()) @@ -361,76 +366,90 @@ prob = SciMLBase.ODEProblem( p, ) -saveat = [t0, tf] -sv = (; - t = Array{Float64}(undef, length(saveat)), - saveval = Array{NamedTuple}(undef, length(saveat)), -) -saving_cb = ClimaLand.NonInterpSavingCallback(sv, saveat) +# ClimaDiagnostics +base_output_dir = "global_soil_canopy_function/" +output_dir = + ClimaUtilities.OutputPathGenerator.generate_output_path(base_output_dir) + +nc_writer = ClimaDiagnostics.Writers.NetCDFWriter(subsurface_space, output_dir) + +diags = ClimaLand.CLD.default_diagnostics(land, t0; output_writer = nc_writer) + +diagnostic_handler = + ClimaDiagnostics.DiagnosticsHandler(diags, Y, p, t0; dt = dt) + +diag_cb = ClimaDiagnostics.DiagnosticsCallback(diagnostic_handler) + updateat = Array(t0:(3600 * 3):tf) drivers = ClimaLand.get_drivers(land) updatefunc = ClimaLand.make_update_drivers(drivers) driver_cb = ClimaLand.DriverUpdateCallback(updateat, updatefunc) -cb = SciMLBase.CallbackSet(driver_cb, saving_cb) -@time sol = SciMLBase.solve( + +sol = ClimaComms.@time ClimaComms.device() SciMLBase.solve( prob, ode_algo; dt = dt, - callback = cb, - adaptive = false, - saveat = saveat, + callback = SciMLBase.CallbackSet(driver_cb, diag_cb), ) -if device_suffix == "cpu" - longpts = range(-180.0, 180.0, 101) - latpts = range(-90.0, 90.0, 101) - hcoords = [ - ClimaCore.Geometry.LatLongPoint(lat, long) for long in longpts, - lat in reverse(latpts) - ] - remapper = ClimaCore.Remapping.Remapper(surface_space, hcoords) - S = ClimaLand.Domains.top_center_to_surface( - (sol.u[end].soil.ϑ_l .- θ_r) ./ (ν .- θ_r), - ) - S_ice = ClimaLand.Domains.top_center_to_surface(sol.u[end].soil.θ_i ./ ν) - T_soil = ClimaLand.Domains.top_center_to_surface(sv.saveval[end].soil.T) - SW = sv.saveval[end].drivers.SW_d - - GPP = sv.saveval[end].canopy.photosynthesis.GPP .* 1e6 - T_canopy = sol.u[end].canopy.energy.T - fields = [S, S_ice, T_soil, GPP, T_canopy, SW] - titles = [ - "Effective saturation", - "Effective ice saturation", - "Temperature (K) - Soil", - "GPP", - "Temperature (K) - Canopy", - "Incident SW", - ] - plotnames = ["S", "Sice", "temp", "gpp", "temp_canopy", "sw"] - mask_remap = ClimaCore.Remapping.interpolate(remapper, soil_params_mask_sfc) - for (id, x) in enumerate(fields) - title = titles[id] - plotname = plotnames[id] - x_remap = ClimaCore.Remapping.interpolate(remapper, x) - - fig = Figure(size = (600, 400)) - ax = Axis( - fig[1, 1], - xlabel = "Longitude", - ylabel = "Latitude", - title = title, - ) - clims = extrema(x_remap) - CairoMakie.heatmap!( - ax, - longpts, - latpts, - oceans_to_value.(x_remap, mask_remap, 0.0), - colorrange = clims, - ) - Colorbar(fig[:, end + 1], colorrange = clims) - outfile = joinpath(outdir, "$plotname.png") - CairoMakie.save(outfile, fig) - end +# ClimaAnalysis +simdir = ClimaAnalysis.SimDir(outdir) +short_names = [ + "sif", + "ra", + "gs", + "trans", + "crae", + "clhf", + "cshf", + "lai", + "msf", + "rai", + "sai", + "gpp", + "an", + "rd", + "vcmax25", + "nir", + "anir", + "rnir", + "tnir", + "par", + "apar", + "rpar", + "tpar", + "lwn", + "swn", + "soc", + "airp", + "rain", + "lwd", + "swd", + "snow", + "sza", + "qsfc", + "ws", + "infil", + "shc", + "stc", + "swp", + "soilrn", + "soilrae", + "soillhf", + "soilshf", + "hr", + "scd", + "scms", + "ct", + "sco2", + "swc", + "si", + "sie", +] +for short_name in short_names + var = get(simdir; short_name) + fig = CairoMakie.Figure(size = (800, 600)) + kwargs = ClimaAnalysis.has_altitude(var) ? Dict(:z => 1) : Dict() + viz.plot!(fig, var, lat = 0; kwargs...) + CairoMakie.save(joinpath(output_dir, "$short_name.png"), fig) end diff --git a/experiments/long_runs/land.jl b/experiments/long_runs/land.jl index 7aed2cd370..29867932fd 100644 --- a/experiments/long_runs/land.jl +++ b/experiments/long_runs/land.jl @@ -602,7 +602,7 @@ function setup_prob(t0, tf, Δt; outdir = outdir, nelements = (101, 15)) land, t0; output_writer = nc_writer, - output_vars = :short, + output_vars = :long, ) diagnostic_handler = @@ -646,13 +646,64 @@ setup_and_solve_problem(; greet = true); # read in diagnostics and make some plots! #### ClimaAnalysis #### simdir = ClimaAnalysis.SimDir(outdir) -short_names_2D = ["gpp", "ct", "slw", "si"] +short_names = [ + "sif", + "ra", + "gs", + "trans", + "crae", + "clhf", + "cshf", + "lai", + "msf", + "rai", + "sai", + "gpp", + "an", + "rd", + "vcmax25", + "nir", + "anir", + "rnir", + "tnir", + "par", + "apar", + "rpar", + "tpar", + "lwn", + "swn", + "soc", + "airp", + "rain", + "lwd", + "swd", + "snow", + "sza", + "qsfc", + "ws", + "infil", + "shc", + "stc", + "swp", + "soilrn", + "soilrae", + "soillhf", + "soilshf", + "hr", + "scd", + "scms", + "ct", + "sco2", + "swc", + "si", + "sie", +] times = 0.0:(60.0 * 60.0 * 12):(60.0 * 60.0 * 24 * 7) for t in times - for short_name in short_names_2D + for short_name in short_names var = get(simdir; short_name) fig = CairoMakie.Figure(size = (800, 600)) - kwargs = short_name in short_names_2D ? Dict() : Dict(:z => 1) + kwargs = ClimaAnalysis.has_altitude(var) ? Dict(:z => 1) : Dict() viz.plot!(fig, var, time = t; kwargs...) CairoMakie.save(joinpath(root_path, "$short_name $t.png"), fig) end diff --git a/experiments/long_runs/soil.jl b/experiments/long_runs/soil.jl index dfb1c0be64..e5b309b686 100644 --- a/experiments/long_runs/soil.jl +++ b/experiments/long_runs/soil.jl @@ -464,13 +464,13 @@ setup_and_solve_problem(; greet = true); # read in diagnostics and make some plots! #### ClimaAnalysis #### simdir = ClimaAnalysis.SimDir(outdir) -short_names_2D = ["slw", "si", "tsoil"] +short_names = ["swc", "si", "sie"] times = 0.0:(60.0 * 60.0 * 24 * 20):(60.0 * 60.0 * 24 * 60) for t in times - for short_name in short_names_2D + for short_name in short_names var = get(simdir; short_name) fig = CairoMakie.Figure(size = (800, 600)) - kwargs = short_name in short_names_2D ? Dict() : Dict(:z => 1) + kwargs = ClimaAnalysis.has_altitude(var) ? Dict(:z => 1) : Dict() viz.plot!(fig, var, time = t; kwargs...) CairoMakie.save(joinpath(root_path, "$short_name $t.png"), fig) end diff --git a/src/diagnostics/bucket_compute_methods.jl b/src/diagnostics/bucket_compute_methods.jl deleted file mode 100644 index 9ab65958f6..0000000000 --- a/src/diagnostics/bucket_compute_methods.jl +++ /dev/null @@ -1,119 +0,0 @@ -# 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 diff --git a/src/diagnostics/default_diagnostics.jl b/src/diagnostics/default_diagnostics.jl index feadb6e2f3..f4bc762514 100644 --- a/src/diagnostics/default_diagnostics.jl +++ b/src/diagnostics/default_diagnostics.jl @@ -62,7 +62,7 @@ function default_diagnostics(land_model::BucketModel, t_start; output_writer) "wsoil", "wsfc", "ssfc", - ] # TO DO: would it be helpful to return this list? + ] default_outputs = hourly_averages(bucket_diagnostics...; output_writer, t_start) @@ -75,51 +75,83 @@ function default_diagnostics( t_start; output_writer, output_vars = :long, + average_period = :hourly, ) define_diagnostics!(land_model) if output_vars == :long soilcanopy_diagnostics = [ - "rn_canopy", - "rn_soil", - "lhf_soil", - "lhf_canopy", - "shf_canopy", - "shf_soil", - "vflux", - "tsoil", - "slw", - "infil", - "scd", - "scms", + "sif", + "ra", "gs", - "mt", "trans", - "rain", # do we want? - "an", + "crae", + "clhf", + "cshf", + # "cwp", + # "fa", + # "far", + "lai", + "msf", + "rai", + "sai", "gpp", + "an", "rd", "vcmax25", - "par", - "apar", - "rpar", - "tpar", "nir", "anir", "rnir", "tnir", - "swn", + "par", + "apar", + "rpar", + "tpar", "lwn", - "ra", - "soilco2", + "swn", + "soc", + "airp", + "rain", + "lwd", + "swd", + "snow", + "sza", + "qsfc", + "ws", + "infil", + "shc", + "stc", + "swp", + "soilrn", + "soilrae", + "soillhf", + "soilshf", + "hr", + "scd", + "scms", + "ct", + "sco2", + "swc", + # "pwc", + "si", + "sie", ] elseif output_vars == :short - soilcanopy_diagnostics = ["gpp", "ct", "ai", "slw", "si"] + soilcanopy_diagnostics = ["gpp", "ct", "lai", "swc", "si"] + end + + if average_period == :hourly + default_outputs = + hourly_averages(soilcanopy_diagnostics...; output_writer, t_start) + elseif average_period == :daily + default_outputs = + daily_averages(soilcanopy_diagnostics...; output_writer, t_start) + elseif average_period == :monthly # !! this is currently 30 days, not exact month + default_outputs = + monthly_averages(soilcanopy_diagnostics...; output_writer, t_start) end - default_outputs = - hourly_averages(soilcanopy_diagnostics...; output_writer, t_start) return [default_outputs...] end @@ -133,7 +165,7 @@ function default_diagnostics( define_diagnostics!(land_model) - soil_diagnostics = ["slw", "si", "tsoil"] + soil_diagnostics = ["swc", "si", "sie"] default_outputs = daily_averages(soil_diagnostics...; output_writer, t_start) diff --git a/src/diagnostics/define_diagnostics.jl b/src/diagnostics/define_diagnostics.jl index b4f24380c2..c42a1b9fa3 100644 --- a/src/diagnostics/define_diagnostics.jl +++ b/src/diagnostics/define_diagnostics.jl @@ -31,7 +31,9 @@ compute function for `land_model`. """ function define_diagnostics!(land_model) - # Stored in p + ### BucketModel ### + + ## Stored in p (diagnostics variables stored in the cache) ## # Albedo add_diagnostic_variable!( @@ -65,17 +67,6 @@ function define_diagnostics!(land_model) 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 = "", - comments = "Ratio of water vapor mass to total moist air parcel mass.", - compute! = (out, Y, p, t) -> - compute_surface_specific_humidity!(out, Y, p, t, land_model), - ) - # Latent heat flux add_diagnostic_variable!( short_name = "lhf", @@ -131,7 +122,7 @@ function define_diagnostics!(land_model) compute_surface_air_density!(out, Y, p, t, land_model), ) - # Stored in Y + ## Stored in Y (prognostic or state variables) ## # Soil temperature (3D) at depth add_diagnostic_variable!( @@ -179,133 +170,284 @@ function define_diagnostics!(land_model) ###### SoilCanopyModel ###### + ## stored in p (diagnostics variables stored in the cache) ## + + ## Canopy Module ## + + ### Canopy - Solar Induced Fluorescence + # Solar Induced Fluorescence add_diagnostic_variable!( - short_name = "slw", - long_name = "Soil Liquid Water", - standard_name = "soil_liquid_water", - units = "m^3 m^-3", - comments = "Soil moisture, the liquid water volume per soil volume. This liquid water is located in the soil pores.", + short_name = "sif", + long_name = "Solar Induced Fluorescence", + standard_name = "solar_induced_fluorescence", + units = "W m^-2", + comments = "The fluorescence of leaves induced by solar radiation. This quantity is correlated with photosynthesis activity.", compute! = (out, Y, p, t) -> - compute_soil_water_liquid!(out, Y, p, t, land_model), + compute_solar_induced_fluorescence!(out, Y, p, t, land_model), ) + ### Canopy - Autotrophic respiration + # Autotrophic respiration add_diagnostic_variable!( - short_name = "infil", - long_name = "Infiltration", - standard_name = "infiltration", - units = "m s^-1", # double check - comments = "The flux of liquid water into the soil.", + short_name = "ra", + long_name = "Autotrophic Respiration", + standard_name = "autotrophic_respiration", + units = "kg C m^-2 s^-1", + comments = "The canopy autotrophic respiration, the sum of leaves, stems and roots respiration.", compute! = (out, Y, p, t) -> - compute_infiltration!(out, Y, p, t, land_model), + compute_autotrophic_respiration!(out, Y, p, t, land_model), ) + ### Canopy - Conductance + # Stomatal conductance add_diagnostic_variable!( - short_name = "scd", - long_name = "Soil CO2 Diffusivity", - standard_name = "soil_co2_diffusivity", - units = "", # need to add - comments = "The diffusivity of CO2 in the porous phase of the soil. Depends on soil texture, moisture, and temperature.", + short_name = "gs", + long_name = "Stomatal Conductance", + standard_name = "stomatal_conductance", + units = "mol H2O m^-2 s^-1", + comments = "The conductance of leaves. This depends on stomatal opening. It varies with factors such as soil moisture or atmospheric water demand.", compute! = (out, Y, p, t) -> - compute_soilco2_diffusivity!(out, Y, p, t, land_model), + compute_stomatal_conductance!(out, Y, p, t, land_model), ) + # Canopy transpiration add_diagnostic_variable!( - short_name = "scms", - long_name = "Soil CO2 Microbial Source", - standard_name = "soil_co2_microbial_source", - units = "", # check - comments = "The production of CO2 by microbes in the soil. Vary by layers of soil depth.", + short_name = "trans", + long_name = "Canopy Transpiration", + standard_name = "canopy_transpiration", + units = "m s^-1", + comments = "The water evaporated from the canopy due to leaf transpiration (flux of water volume, m^3 of water per m^2 of ground).", compute! = (out, Y, p, t) -> - compute_soilco2_source_microbe!(out, Y, p, t, land_model), + compute_canopy_transpiration!(out, Y, p, t, land_model), ) + ### Canopy - Energy + # Canopy aerodynamic resistance add_diagnostic_variable!( - short_name = "gs", - long_name = "Stomatal Conductance", - standard_name = "stomatal_conductance", - units = "m s^-1", - comments = "The conductance of leaves. This depends on stomatal opening. It varies with factors such as soil moisture or atmospheric water demand.", + short_name = "crae", + long_name = "Canopy Aerodynamic Resistance", + standard_name = "canopy_aerodynamic_resistance", + units = "s m^-1", + comments = "The canopy aerodynamic resistance. Aerodynamic resistance (r_a) is a measure of how much the air near the Earth's surface resists the movement of water vapor and heat from the surface into the air.", compute! = (out, Y, p, t) -> - compute_stomatal_conductance!(out, Y, p, t, land_model), + compute_canopy_aerodynamic_resistance!(out, Y, p, t, land_model), ) + # Canopy latent heat flux add_diagnostic_variable!( - short_name = "mt", - long_name = "Medlyn Term", - standard_name = "medlyn_term", - units = "", # check - comments = "", + short_name = "clhf", + long_name = "Canopy Latent Heat Flux", + standard_name = "canopy_latent_heat_flux", + units = "W m^-2", + comments = "The energy used for canopy transpiration.", #?? of steam, leaves, roots? compute! = (out, Y, p, t) -> - compute_medlyn_term!(out, Y, p, t, land_model), + compute_canopy_latent_heat_flux!(out, Y, p, t, land_model), ) + # Canopy sensible heat flux add_diagnostic_variable!( - short_name = "trans", - long_name = "Canopy Transpiration", - standard_name = "canopy_transpiration", - units = "", # check - comments = "The water evaporated from the canopy due to leaf transpiration.", + short_name = "cshf", + long_name = "Canopy Sensible Heat Flux", + standard_name = "canopy_sensible_heat_flux", + units = "W m^-2", + comments = "The energy used for canopy temperature change.", #?? of steam, leaves, roots? compute! = (out, Y, p, t) -> - compute_canopy_transpiration!(out, Y, p, t, land_model), + compute_canopy_sensible_heat_flux!(out, Y, p, t, land_model), ) - add_diagnostic_variable!( # not actually computed, but read from input or atmosphere model, and stored in p... - short_name = "rain", - long_name = "Rainfall", - standard_name = "rainfall", + ### Canopy - Hydraulics + # Canopy water potential + + #= + add_diagnostic_variable!( + short_name = "cwp", + long_name = "Canopy Water Potential", + standard_name = "canopy_water_potential", units = "m", - comments = "Precipitation of liquid water.", + comments = "The water potential of the canopy.", compute! = (out, Y, p, t) -> - compute_rainfall!(out, Y, p, t, land_model), + compute_canopy_water_potential!(out, Y, p, t, land_model), ) + + # Cross section add_diagnostic_variable!( - short_name = "an", - long_name = "Leaf Net Photosynthesis", - standard_name = "leaf_net_photosynthesis", - units = "", # check - comments = "Net photosynthesis of a leaf.", + short_name = "fa", + long_name = "Cross Section", + standard_name = "cross_section", + units = "m s^-1", + comments = "Flux of water volume per m^2 of plant per second, multiplied by the area index (plant area/ground area).", compute! = (out, Y, p, t) -> - compute_photosynthesis_net_leaf!(out, Y, p, t, land_model), + compute_cross_section!(out, Y, p, t, land_model), ) + =# + # cwp and fa return a Tuple + # Root cross section + add_diagnostic_variable!( + short_name = "far", + long_name = "Root Cross Section", + standard_name = "Cross Section", + units = "m s^-1", + comments = "Flux of water volume per m^2 of root per second, multiplied by the area index (root area/ground area).", + compute! = (out, Y, p, t) -> + compute_cross_section_roots!(out, Y, p, t, land_model), + ) + + # Leaf area index + add_diagnostic_variable!( + short_name = "lai", + long_name = "Leaf area Index", + standard_name = "leaf_area_index", + units = "m^2 m^-2", + comments = "The area index of leaves, expressed in surface area of leaves per surface area of ground.", + compute! = (out, Y, p, t) -> + compute_leaf_area_index!(out, Y, p, t, land_model), + ) + + # Moisture stress factor + add_diagnostic_variable!( + short_name = "msf", + long_name = "Moisture Stress Factor", + standard_name = "moisture_stress_factor", + units = "", + comments = "Sensitivity of plants conductance to soil water content. Unitless", + compute! = (out, Y, p, t) -> + compute_moisture_stress_factor!(out, Y, p, t, land_model), + ) + + # Root area index + add_diagnostic_variable!( + short_name = "rai", + long_name = "Root area Index", + standard_name = "root_area_index", + units = "m^2 m^-2", + comments = "The area index of roots, expressed in surface area of roots per surface area of ground.", + compute! = (out, Y, p, t) -> + compute_root_area_index!(out, Y, p, t, land_model), + ) + + # Stem area index + add_diagnostic_variable!( + short_name = "sai", + long_name = "Stem area Index", + standard_name = "stem_area_index", + units = "m^2 m^-2", + comments = "The area index of stems, expressed in surface area of stems per surface area of ground.", + compute! = (out, Y, p, t) -> + compute_stem_area_index!(out, Y, p, t, land_model), + ) + + ### Canopy - Photosynthesis + # GPP - Gross Primary Productivity add_diagnostic_variable!( short_name = "gpp", long_name = "Gross Primary Productivity", standard_name = "gross_primary_productivity", - units = "", - comments = "Net photosynthesis of the canopy.", + units = "mol CO2 m^-2 s^-1", + comments = "Net photosynthesis (carbon assimilation) of the canopy. This is equivalent to leaf net assimilation scaled to the canopy level.", compute! = (out, Y, p, t) -> compute_photosynthesis_net_canopy!(out, Y, p, t, land_model), ) + # Leaf net photosynthesis + add_diagnostic_variable!( + short_name = "an", + long_name = "Leaf Net Photosynthesis", + standard_name = "leaf_net_photosynthesis", + units = "mol CO2 m^-2 s^-1", # soil CO2 microbial source was in kg co2. we should make a choice and be constant throughout + comments = "Net photosynthesis (carbon assimilation) of a leaf, computed for example by the Farquhar model.", + compute! = (out, Y, p, t) -> + compute_photosynthesis_net_leaf!(out, Y, p, t, land_model), + ) + + # Leaf respiration add_diagnostic_variable!( short_name = "rd", long_name = "Leaf Respiration", standard_name = "leaf_dark_respiration", - units = "", + units = "mol CO2 m^-2 s^-1", comments = "Leaf respiration, called dark respiration because usually measured in the abscence of radiation.", compute! = (out, Y, p, t) -> compute_respiration_leaf!(out, Y, p, t, land_model), ) + # Vcmax25 add_diagnostic_variable!( short_name = "vcmax25", long_name = "Vcmax25", standard_name = "vcmax25", - units = "", + units = "mol CO2 m^-2 s^-1", comments = "The parameter vcmax at 25 degree celsius. Important for the Farquhar model of leaf photosynthesis.", compute! = (out, Y, p, t) -> compute_vcmax25!(out, Y, p, t, land_model), ) + ### Canopy - Radiative Transfer + # NIR - near infrared radiaton + add_diagnostic_variable!( + short_name = "nir", + long_name = "Near Infrared Radiation", + standard_name = "near_infrared_radiation", + units = "mol photons m^-2 s^-1", + comments = "The amount of near infrared radiation reaching the canopy.", + compute! = (out, Y, p, t) -> + compute_near_infrared_radiation_down!(out, Y, p, t, land_model), + ) + + # ANIR - absorbed near infrared radiation + add_diagnostic_variable!( + short_name = "anir", + long_name = "Absorbed Near Infrared Radiation", + standard_name = "absorbed_near_infrared_radiation", + units = "mol photons m^-2 s^-1", + comments = "The amount of near infrared radiation absorbed by the canopy.", + compute! = (out, Y, p, t) -> + compute_near_infrared_radiation_absorbed!(out, Y, p, t, land_model), + ) + + # RNIR - reflected near infrared radiation + add_diagnostic_variable!( + short_name = "rnir", + long_name = "Reflected Near Infrared Radiation", + standard_name = "reflected_near_infrared_radiation", + units = "mol photons m^-2 s^-1", + comments = "The amount of near infrared radiation reflected by the canopy.", + compute! = (out, Y, p, t) -> + compute_near_infrared_radiation_reflected!( + out, + Y, + p, + t, + land_model, + ), + ) + + # TNIR - transmitted near infrared radiation + add_diagnostic_variable!( + short_name = "tnir", + long_name = "Transmitted Near Infrared Radiation", + standard_name = "transmitted_near_infrared_radiation", + units = "mol photons m^-2 s^-1", + comments = "The amount of near infrared radiation transmitted by the canopy.", + compute! = (out, Y, p, t) -> + compute_near_infrared_radiation_transmitted!( + out, + Y, + p, + t, + land_model, + ), + ) + + # PAR - photosynthetically active radiation add_diagnostic_variable!( short_name = "par", long_name = "Photosynthetically Active Radiation", standard_name = "photosynthetically_active_radiation", - units = "", + units = "mol photons m^-2 s^-1", comments = "The subset of total radiation that activates photosynthesis reaching the canopy.", compute! = (out, Y, p, t) -> - compute_photosynthetically_active_radiation!( + compute_photosynthetically_active_radiation_down!( out, Y, p, @@ -314,11 +456,12 @@ function define_diagnostics!(land_model) ), ) + # APAR - absorbed photosynthetically active radiation add_diagnostic_variable!( short_name = "apar", long_name = "Absorbed Photosynthetically Active Radiation", standard_name = "absorbed_photosynthetically_active_radiation", - units = "", + units = "mol photons m^-2 s^-1", comments = "The amount of photosynthetically active radiation absorbed by the leaf. The rest if reflected or transmitted.", compute! = (out, Y, p, t) -> compute_photosynthetically_active_radiation_absorbed!( @@ -330,11 +473,12 @@ function define_diagnostics!(land_model) ), ) + # RPAR - reflected photosynthetically active radiation add_diagnostic_variable!( short_name = "rpar", long_name = "Reflected Photosynthetically Active Radiation", standard_name = "reflected_photosynthetically_active_radiation", - units = "", + units = "mol photons m^-2 s^-1", comments = "The amount of photosynthetically active radiation reflected by leaves.", compute! = (out, Y, p, t) -> compute_photosynthetically_active_radiation_reflected!( @@ -346,11 +490,12 @@ function define_diagnostics!(land_model) ), ) + # TPAR - transmitted photosynthetically active radiation add_diagnostic_variable!( short_name = "tpar", long_name = "Transmitted Photosynthetically Active Radiation", standard_name = "transmitted_photosynthetically_active_radiation", - units = "", + units = "mol photons m^-2 s^-1", comments = "The amount of photosynthetically active radiation transmitted by leaves.", compute! = (out, Y, p, t) -> compute_photosynthetically_active_radiation_transmitted!( @@ -362,284 +507,321 @@ function define_diagnostics!(land_model) ), ) + # Net longwave radiation add_diagnostic_variable!( - short_name = "nir", - long_name = "Near Infrared Radiation", - standard_name = "near_infrared_radiation", + short_name = "lwn", + long_name = "Net Longwave Radiation", + standard_name = "net_longwave_radiation", units = "W m^-2", - comments = "The amount of near infrared radiation reaching the canopy.", + comments = "The net (in minus out) longwave radiation at the surface.", compute! = (out, Y, p, t) -> - compute_near_infrared_radiation!(out, Y, p, t, land_model), + compute_radiation_longwave_net!(out, Y, p, t, land_model), ) + # Net shortwave radiation add_diagnostic_variable!( - short_name = "anir", - long_name = "Absorbed Near Infrared Radiation", - standard_name = "absorbed_near_infrared_radiation", + short_name = "swn", + long_name = "Net Shortwave Radiation", + standard_name = "net_shortwave_radiation", units = "W m^-2", - comments = "The amount of near infrared radiation reaching the canopy.", + comments = "The net (in minus out) shortwave radiation at the surface.", compute! = (out, Y, p, t) -> - compute_near_infrared_radiation_absorbed!(out, Y, p, t, land_model), + compute_radiation_shortwave_net!(out, Y, p, t, land_model), ) + ## Drivers Module ## + # Soil organic carbon add_diagnostic_variable!( - short_name = "rnir", - long_name = "Reflected Near Infrared Radiation", - standard_name = "reflected_near_infrared_radiation", - units = "W m^-2", - comments = "The amount of near infrared radiation reaching the canopy.", + short_name = "soc", + long_name = "Soil organic carbon", + standard_name = "soil_organic_carbon", + units = "kg C m^-3", + comments = "Mass of organic carbon per volume of soil.", compute! = (out, Y, p, t) -> - compute_near_infrared_radiation_reflected!( - out, - Y, - p, - t, - land_model, - ), + compute_soil_organic_carbon!(out, Y, p, t, land_model), ) + # Air pressure add_diagnostic_variable!( - short_name = "tnir", - long_name = "Transmitted Near Infrared Radiation", - standard_name = "transmitted_near_infrared_radiation", - units = "W m^-2", - comments = "The amount of near infrared radiation reaching the canopy.", + short_name = "airp", + long_name = "Air pressure", + standard_name = "air_pressure", + units = "Pa", + comments = "The air pressure.", compute! = (out, Y, p, t) -> - compute_near_infrared_radiation_transmitted!( - out, - Y, - p, - t, - land_model, - ), + compute_pressure!(out, Y, p, t, land_model), ) + # Rainfall add_diagnostic_variable!( - short_name = "swn", - long_name = "Net Shortwave Radiation", - standard_name = "net_shortwave_radiation", - units = "W m^-2", - comments = "The net (in minus out) radiation at the surface.", + short_name = "rain", + long_name = "Rainfall", + standard_name = "rainfall", + units = "m s^-1", + comments = "Precipitation of liquid water volume (m^3 of water per m^2 of ground per second).", compute! = (out, Y, p, t) -> - compute_radiation_shortwave_net!(out, Y, p, t, land_model), + compute_rainfall!(out, Y, p, t, land_model), ) + # Net longwave radiation add_diagnostic_variable!( - short_name = "lwn", - long_name = "Net Longwave Radiation", - standard_name = "net_longwave_radiation", + short_name = "lwd", + long_name = "Down Longwave Radiation", + standard_name = "down_longwave_radiation", units = "W m^-2", - comments = "The net (in minus out) radiation at the surface.", + comments = "The down (in) longwave radiation at the surface.", compute! = (out, Y, p, t) -> - compute_radiation_longwave_net!(out, Y, p, t, land_model), + compute_radiation_longwave_down!(out, Y, p, t, land_model), ) + # Net shortwave radiation add_diagnostic_variable!( - short_name = "ra", - long_name = "Autotrophic Respiration", - standard_name = "autotrophic_respiration", - units = "mol m^-2 s^-1", - comments = "Canopy autotrophic respiration, the sum of leaves, stems and roots respiration.", + short_name = "swd", + long_name = "Short Longwave Radiation", + standard_name = "short_longwave_radiation", + units = "W m^-2", + comments = "The short (in) longwave radiation at the surface.", compute! = (out, Y, p, t) -> - compute_autotrophic_respiration!(out, Y, p, t, land_model), - ) - - add_diagnostic_variable!( - short_name = "soilco2", - long_name = "Soil CO2 concentration", - standard_name = "soil_co2", - units = "", - comments = "Concentration of CO2 in the air of soil pores.", - compute! = (out, Y, p, t) -> compute_soilco2!(out, Y, p, t, land_model), + compute_radiation_shortwave_down!(out, Y, p, t, land_model), ) + # Snowfall add_diagnostic_variable!( - short_name = "soilrn", - long_name = "Soil Net Radiation", - standard_name = "soil_net_radiation", - units = "W m^-2", - comments = "Net radiation at the soil surface.", + short_name = "snow", + long_name = "Snowfall", + standard_name = "snowfall", + units = "m s^-1", + comments = "The precipitation of snow in liquid water volume (m^3 of water per m^2 of ground per second).", compute! = (out, Y, p, t) -> - compute_soil_net_radiation!(out, Y, p, t, land_model), + compute_snowfall!(out, Y, p, t, land_model), ) + # Solar zenith angle add_diagnostic_variable!( - short_name = "soillhf", - long_name = "Soil Latent Heat Flux", - standard_name = "soil_Latent_Heat_Flux", - units = "W m^-2", - comments = "Soil evaporation.", + short_name = "sza", + long_name = "Solar Zenith Angle", + standard_name = "solar_zenith_angle", + units = "", + comments = "Solar zenith angle.", compute! = (out, Y, p, t) -> - compute_soil_latent_heat_flux!(out, Y, p, t, land_model), + compute_solar_zenith_angle!(out, Y, p, t, land_model), ) - add_diagnostic_variable!( - short_name = "soilshf", - long_name = "Soil Sensible Heat Flux", - standard_name = "soil_sensible_Heat_Flux", - units = "W m^-2", - comments = "Soil sensible heat flux.", + # Specific humidity + add_diagnostic_variable!( # NOTE: in BucketModel too + short_name = "qsfc", + long_name = "Surface Specific Humidity", + standard_name = "surface_specific_humidity", + units = "", + comments = "Ratio of water vapor mass to total moist air parcel mass.", compute! = (out, Y, p, t) -> - compute_soil_sensible_heat_flux!(out, Y, p, t, land_model), + compute_specific_humidity!(out, Y, p, t, land_model), ) - add_diagnostic_variable!( - short_name = "soilrae", - long_name = "Soil Aerodynamic Resistance", - standard_name = "soil_aerodynamic_resistance", - units = "", - comments = "Soil aerodynamic resistance.", + # Wind speed + add_diagnostic_variable!( # NOTE: in BucketModel too + short_name = "ws", + long_name = "Wind Speed", + standard_name = "wind_speed", + units = "m s^-1", + comments = "The average wind speed.", compute! = (out, Y, p, t) -> - compute_soil_aerodynamic_resistance!(out, Y, p, t, land_model), + compute_wind_speed!(out, Y, p, t, land_model), ) + ## Soil Module ## + # Infiltration add_diagnostic_variable!( - short_name = "hr", - long_name = "Heterotrophic Respiration", - standard_name = "heterotrophic_respiration", - units = "mol m^-2 s^-1", - comments = "CO2 efflux at the soil surface due to microbial decomposition of soil organic matter.", + short_name = "infil", + long_name = "Infiltration", + standard_name = "infiltration", + units = "m s^-1", + comments = "The flux of liquid water volume into the soil (m^3 of water per m^2 of ground per second).", compute! = (out, Y, p, t) -> - compute_heterotrophic_respiration!(out, Y, p, t, land_model), + compute_infiltration!(out, Y, p, t, land_model), ) + # Soil hydraulic conductivity add_diagnostic_variable!( short_name = "shc", long_name = "Soil Hydraulic Conductivity", standard_name = "soil_hydraulic_conductivity", - units = "", + units = "m s^-1", comments = "Soil hydraulic conductivity.", compute! = (out, Y, p, t) -> compute_soil_hydraulic_conductivity!(out, Y, p, t, land_model), ) + # Soil thermal conductivity add_diagnostic_variable!( short_name = "stc", long_name = "Soil Thermal Conductivity", standard_name = "soil_thermal_conductivity", - units = "", + units = "W m^-1 K^-1", comments = "Soil thermal conductivity.", compute! = (out, Y, p, t) -> compute_soil_thermal_conductivity!(out, Y, p, t, land_model), ) + # Soil Water Potential add_diagnostic_variable!( short_name = "swp", long_name = "Soil Water Potential", standard_name = "soil_water_potential", - units = "", + units = "Pa", comments = "Soil water potential.", compute! = (out, Y, p, t) -> compute_soil_water_potential!(out, Y, p, t, land_model), ) + # Soil net radiation add_diagnostic_variable!( - short_name = "sza", - long_name = "Solar Zenith Angle", - standard_name = "solar_zenith_angle", - units = "", - comments = "Solar zenith angle.", + short_name = "soilrn", + long_name = "Soil Net Radiation", + standard_name = "soil_net_radiation", + units = "W m^-2", + comments = "Net radiation at the soil surface.", # Do we have distinct soil net radiation, canopy net radiation, surface net radiation? compute! = (out, Y, p, t) -> - compute_solar_zenith_angle!(out, Y, p, t, land_model), + compute_soil_net_radiation!(out, Y, p, t, land_model), ) + ### Soil - Turbulent Fluxes + # Soil aerodynamic resistance add_diagnostic_variable!( - short_name = "msf", - long_name = "Moisture Stress Factor", - standard_name = "moisture_stress_factor", - units = "", - comments = "Sensitivity of plants conductance to soil water content.", + short_name = "soilrae", + long_name = "Soil Aerodynamic Resistance", + standard_name = "soil_aerodynamic_resistance", + units = "s m^-1", # ??? not sure + comments = "The soil aerodynamic resistance. Aerodynamic resistance is a measure of how much the air near the Earth's surface resists the movement of water vapor and heat from the surface into the air.", # is this good? compute! = (out, Y, p, t) -> - compute_moisture_stress_factor!(out, Y, p, t, land_model), + compute_soil_aerodynamic_resistance!(out, Y, p, t, land_model), ) + # Soil latent heat flux add_diagnostic_variable!( - short_name = "cwp", - long_name = "Canopy Water Potential", - standard_name = "canopy_water_potential", - units = "", - comments = "The water potential of the canopy.", + short_name = "soillhf", + long_name = "Soil Latent Heat Flux", + standard_name = "soil_Latent_Heat_Flux", + units = "W m^-2", + comments = "Soil latent heat flux, the amount of liquid water evaporated by the soil, expressed in energy units (W m^-2).", compute! = (out, Y, p, t) -> - compute_canopy_water_potential!(out, Y, p, t, land_model), + compute_soil_latent_heat_flux!(out, Y, p, t, land_model), ) + # Soil sensible heat flux add_diagnostic_variable!( - short_name = "fa", - long_name = "Cross Section", - standard_name = "cross_section", - units = "", - comments = "The area of stem relative to ground area.", #?? + short_name = "soilshf", + long_name = "Soil Sensible Heat Flux", + standard_name = "soil_sensible_Heat_Flux", + units = "W m^-2", + comments = "Soil sensible heat flux, the amount of energy exchanged between the soil and atmosphere to change the temperature of the soil.", # is this good? compute! = (out, Y, p, t) -> - compute_cross_section!(out, Y, p, t, land_model), + compute_soil_sensible_heat_flux!(out, Y, p, t, land_model), ) + ### Soil - SoilCO2 + # Heterotrophic respiration add_diagnostic_variable!( - short_name = "far", - long_name = "Root Cross Section", - standard_name = "Cross Section", - units = "", - comments = "The area of roots relative to ground area.", #?? + short_name = "hr", + long_name = "Heterotrophic Respiration", + standard_name = "heterotrophic_respiration", + units = "mol m^-2 s^-1", # not sure if kg or mol + comments = "The CO2 efflux at the soil surface due to microbial decomposition of soil organic matter. This is not necessarily equal to CO2 production by microbes, as co2 diffusion through the soil pores takes time.", compute! = (out, Y, p, t) -> - compute_cross_section_roots!(out, Y, p, t, land_model), + compute_heterotrophic_respiration!(out, Y, p, t, land_model), ) + # Soil CO2 diffusivity add_diagnostic_variable!( - short_name = "ai", - long_name = "Area Index", - standard_name = "area_index", - units = "", - comments = "The area index of leaves.", + short_name = "scd", + long_name = "Soil CO2 Diffusivity", + standard_name = "soil_co2_diffusivity", + units = "m^2 s^-1", + comments = "The diffusivity of CO2 in the porous phase of the soil. Depends on soil texture, moisture, and temperature.", compute! = (out, Y, p, t) -> - compute_area_index!(out, Y, p, t, land_model), + compute_soilco2_diffusivity!(out, Y, p, t, land_model), ) + # Soil CO2 microbial source add_diagnostic_variable!( - short_name = "clhf", - long_name = "Canopy Latent Heat Flux", - standard_name = "canopy_latent_heat_flux", - units = "", - comments = "Canopy evaporation.", #?? of steam, leaves, roots? + short_name = "scms", + long_name = "Soil CO2 Microbial Source", + standard_name = "soil_co2_microbial_source", + units = "kg CO2 m^-2 s^-1", + comments = "The production of CO2 by microbes in the soil. Vary by layers of soil depth.", compute! = (out, Y, p, t) -> - compute_canopy_latent_heat_flux!(out, Y, p, t, land_model), + compute_soilco2_source_microbe!(out, Y, p, t, land_model), ) + ## Stored in Y (prognostic or state variables) ## + + # Canopy temperature add_diagnostic_variable!( - short_name = "cshf", - long_name = "Canopy Sensible Heat Flux", - standard_name = "canopy_sensible_heat_flux", - units = "", - comments = "Canopy sensible heat flux.", #?? of steam, leaves, roots? + short_name = "ct", + long_name = "Canopy Temperature", + standard_name = "canopy_temperature", + units = "K", + comments = "Canopy temperature.", #?? of steam, leaves, roots? compute! = (out, Y, p, t) -> - compute_canopy_sensible_heat_flux!(out, Y, p, t, land_model), + compute_canopy_temperature!(out, Y, p, t, land_model), ) + # Soil CO2 add_diagnostic_variable!( - short_name = "crae", - long_name = "Canopy Aerodynamic Resistance", - standard_name = "canopy_aerodynamic_resistance", - units = "", - comments = "Canopy aerodynamic_resistance.", #?? of steam, leaves, roots? + short_name = "sco2", + long_name = "Soil CO2", + standard_name = "soil_co2", + units = "", # need units + comments = "Concentration of CO2 in the porous air space of the soil.", #?? of steam, leaves, roots? + compute! = (out, Y, p, t) -> compute_soilco2!(out, Y, p, t, land_model), + ) + + # Soil water content + add_diagnostic_variable!( + short_name = "swc", + long_name = "Soil Water Content", + standard_name = "soil_water_content", + units = "m^3 m^-3", + comments = "The volume of soil water per volume of soil.", compute! = (out, Y, p, t) -> - compute_canopy_aerodynamic_resistance!(out, Y, p, t, land_model), + compute_soil_water_content!(out, Y, p, t, land_model), ) + # Plant water content + + #= add_diagnostic_variable!( - short_name = "ct", - long_name = "Canopy Temperature", - standard_name = "canopy_temperature", - units = "K", - comments = "Canopy temperature.", #?? of steam, leaves, roots? + short_name = "pwc", + long_name = "Plant Water Content", + standard_name = "plant_water_content", + units = "m^3 m^-3", + comments = "The volume of plant water per volume of plant.", # not sure compute! = (out, Y, p, t) -> - compute_canopy_temperature!(out, Y, p, t, land_model), + compute_plant_water_content!(out, Y, p, t, land_model), ) + =# + # return a Tuple + # Soil ice add_diagnostic_variable!( short_name = "si", long_name = "Soil Ice", standard_name = "soil_ice", units = "m^3 m^-3", - comments = "soil ice.", + comments = "The volume of soil ice per volume of soil.", compute! = (out, Y, p, t) -> - compute_soil_ice!(out, Y, p, t, land_model), + compute_soil_ice_content!(out, Y, p, t, land_model), ) + + # Soil internal energy + add_diagnostic_variable!( + short_name = "sie", + long_name = "Soil Internal Energy", + standard_name = "soil_internal_energy", + units = "W m^-2", + comments = "The energy per volume of soil.", + compute! = (out, Y, p, t) -> + compute_soil_internal_energy!(out, Y, p, t, land_model), + ) + end diff --git a/src/diagnostics/diagnostic.jl b/src/diagnostics/diagnostic.jl index 4894dd3f9c..fecc9b5fc9 100644 --- a/src/diagnostics/diagnostic.jl +++ b/src/diagnostics/diagnostic.jl @@ -89,8 +89,7 @@ function get_diagnostic_variable(short_name) end # Do you want to define more diagnostics? Add them here -include("bucket_compute_methods.jl") -include("soilcanopy_compute_methods.jl") +include("land_compute_methods.jl") # define_diagnostics.jl contains the list of all the diagnostics include("define_diagnostics.jl") diff --git a/src/diagnostics/land_compute_methods.jl b/src/diagnostics/land_compute_methods.jl new file mode 100644 index 0000000000..49c38b9643 --- /dev/null +++ b/src/diagnostics/land_compute_methods.jl @@ -0,0 +1,139 @@ +""" + @diagnostic_compute name model compute + +Macro generating a function to compute a land diagnostic, +needed in the ClimaDiagnostics framework. + +For example, +@diagnostic_compute "soil_net_radiation" SoilCanopyModel p.soil.R_n + +generates the function + +function compute_soil_net_radiation!(out, Y, p, t, land_model::SoilCanopyModel) + if isnothing(out) + return copy(p.soil.R_n) + else + out .= p.soil.R_n + end +end +""" +macro diagnostic_compute(name, model, compute) + function_name = Symbol("compute_", name, "!") + return esc(quote + function $function_name(out, Y, p, t, land_model::$model) + if isnothing(out) + return copy($compute) + else + out .= $compute + end + end + end) +end + +### BucketModel ### + +# variables stored in p (diagnostics variables stored in the cache) +@diagnostic_compute "aerodynamic_resistance" BucketModel p.bucket.turbulent_fluxes.r_ae +@diagnostic_compute "albedo" BucketModel p.bucket.α_sfc +@diagnostic_compute "latent_heat_flux" BucketModel p.bucket.turbulent_fluxes.lhf +@diagnostic_compute "net_radiation" BucketModel p.bucket.R_n +@diagnostic_compute "sensible_heat_flux" BucketModel p.bucket.turbulent_fluxes.shf +@diagnostic_compute "surface_air_density" BucketModel p.bucket.ρ_sfc +@diagnostic_compute "specific_humidity" BucketModel p.bucket.q_sfc +@diagnostic_compute "surface_temperature" BucketModel p.bucket.T_sfc +@diagnostic_compute "vapor_flux" BucketModel p.bucket.turbulent_fluxes.vapor_flux + +# variables stored in Y (prognostic or state variables) +@diagnostic_compute "snow_water_equivalent" BucketModel Y.bucket.σS +@diagnostic_compute "soil_temperature" BucketModel Y.bucket.T +@diagnostic_compute "subsurface_water_storage" BucketModel Y.bucket.W +@diagnostic_compute "surface_water_content" BucketModel Y.bucket.Ws + +### SoilCanopyModel ### + +# variables stored in p (diagnostics variables stored in the cache) + +## Canopy Module ## + +# Canopy - Solar Induced Fluorescence +@diagnostic_compute "solar_induced_fluorescence" SoilCanopyModel p.canopy.sif.SIF + +# Canopy - Autotrophic respiration +@diagnostic_compute "autotrophic_respiration" SoilCanopyModel p.canopy.autotrophic_respiration.Ra + +# Canopy - Conductance +@diagnostic_compute "stomatal_conductance" SoilCanopyModel p.canopy.conductance.gs +@diagnostic_compute "canopy_transpiration" SoilCanopyModel p.canopy.conductance.transpiration + +# Canopy - Energy +@diagnostic_compute "canopy_aerodynamic_resistance" SoilCanopyModel p.canopy.energy.r_ae +@diagnostic_compute "canopy_latent_heat_flux" SoilCanopyModel p.canopy.energy.lhf +@diagnostic_compute "canopy_sensible_heat_flux" SoilCanopyModel p.canopy.energy.shf + +# Canopy - Hydraulics +# @diagnostic_compute "canopy_water_potential" SoilCanopyModel p.canopy.hydraulics.ψ # return a Tuple +# @diagnostic_compute "cross_section" SoilCanopyModel p.canopy.hydraulics.fa # return a Tuple +@diagnostic_compute "cross_section_roots" SoilCanopyModel p.canopy.hydraulics.fa_roots +@diagnostic_compute "leaf_area_index" SoilCanopyModel p.canopy.hydraulics.area_index.leaf +@diagnostic_compute "moisture_stress_factor" SoilCanopyModel p.canopy.hydraulics.β +@diagnostic_compute "root_area_index" SoilCanopyModel p.canopy.hydraulics.area_index.root +@diagnostic_compute "stem_area_index" SoilCanopyModel p.canopy.hydraulics.area_index.stem + +# Canopy - Photosynthesis +@diagnostic_compute "photosynthesis_net_canopy" SoilCanopyModel p.canopy.photosynthesis.GPP +@diagnostic_compute "photosynthesis_net_leaf" SoilCanopyModel p.canopy.photosynthesis.An +@diagnostic_compute "respiration_leaf" SoilCanopyModel p.canopy.photosynthesis.Rd +@diagnostic_compute "vcmax25" SoilCanopyModel p.canopy.photosynthesis.Vcmax25 + +# Canopy - Radiative Transfer +@diagnostic_compute "near_infrared_radiation_down" SoilCanopyModel p.canopy.radiative_transfer.inc_nir +@diagnostic_compute "near_infrared_radiation_absorbed" SoilCanopyModel p.canopy.radiative_transfer.nir.abs +@diagnostic_compute "near_infrared_radiation_reflected" SoilCanopyModel p.canopy.radiative_transfer.nir.refl +@diagnostic_compute "near_infrared_radiation_transmitted" SoilCanopyModel p.canopy.radiative_transfer.nir.trans +@diagnostic_compute "photosynthetically_active_radiation_down" SoilCanopyModel p.canopy.radiative_transfer.inc_par +@diagnostic_compute "photosynthetically_active_radiation_absorbed" SoilCanopyModel p.canopy.radiative_transfer.par.abs +@diagnostic_compute "photosynthetically_active_radiation_reflected" SoilCanopyModel p.canopy.radiative_transfer.par.refl +@diagnostic_compute "photosynthetically_active_radiation_transmitted" SoilCanopyModel p.canopy.radiative_transfer.par.trans +@diagnostic_compute "radiation_longwave_net" SoilCanopyModel p.canopy.radiative_transfer.LW_n +@diagnostic_compute "radiation_shortwave_net" SoilCanopyModel p.canopy.radiative_transfer.SW_n + +## Drivers Module ## + +@diagnostic_compute "soil_organic_carbon" SoilCanopyModel p.drivers.soc # need to fix this in src/shared_utilities/drivers +@diagnostic_compute "pressure" SoilCanopyModel p.drivers.P +@diagnostic_compute "rainfall" SoilCanopyModel p.drivers.P_liq +@diagnostic_compute "radiation_longwave_down" SoilCanopyModel p.drivers.LW_d +@diagnostic_compute "radiation_shortwave_down" SoilCanopyModel p.drivers.SW_d +@diagnostic_compute "snowfall" SoilCanopyModel p.drivers.P_snow +@diagnostic_compute "solar_zenith_angle" SoilCanopyModel p.drivers.θs +@diagnostic_compute "specific_humidity" SoilCanopyModel p.drivers.q +@diagnostic_compute "wind_speed" SoilCanopyModel p.drivers.u + +## Soil Module ## + +@diagnostic_compute "infiltration" SoilCanopyModel p.soil.infiltration +@diagnostic_compute "soil_hydraulic_conductivity" SoilCanopyModel p.soil.K +@diagnostic_compute "soil_thermal_conductivity" SoilCanopyModel p.soil.κ +@diagnostic_compute "soil_water_potential" SoilCanopyModel p.soil.ψ +@diagnostic_compute "soil_net_radiation" SoilCanopyModel p.soil.R_n +@diagnostic_compute "soil_temperature" SoilCanopyModel p.soil.T + +# Soil - Turbulent Fluxes +@diagnostic_compute "soil_aerodynamic_resistance" SoilCanopyModel p.soil.turbulent_fluxes.r_ae +@diagnostic_compute "soil_latent_heat_flux" SoilCanopyModel p.soil.turbulent_fluxes.lhf +@diagnostic_compute "soil_sensible_heat_flux" SoilCanopyModel p.soil.turbulent_fluxes.shf +@diagnostic_compute "vapor_flux" SoilCanopyModel p.soil.turbulent_fluxes.vapor_flux + +# Soil - SoilCO2 +@diagnostic_compute "heterotrophic_respiration" SoilCanopyModel p.soilco2.top_bc +@diagnostic_compute "soilco2_diffusivity" SoilCanopyModel p.soilco2.D +@diagnostic_compute "soilco2_source_microbe" SoilCanopyModel p.soilco2.Sm + +# variables stored in Y (prognostic or state variables) + +@diagnostic_compute "canopy_temperature" SoilCanopyModel Y.canopy.energy.T +@diagnostic_compute "soilco2" SoilCanopyModel Y.soilco2.C +@diagnostic_compute "soil_water_content" SoilCanopyModel Y.soil.ϑ_l +# @diagnostic_compute "plant_water_content" SoilCanopyModel Y.canopy.hydraulics.ϑ_l # return a Tuple +@diagnostic_compute "soil_ice_content" SoilCanopyModel Y.soil.θ_i +@diagnostic_compute "soil_internal_energy" SoilCanopyModel Y.soil.ρe_int diff --git a/src/diagnostics/soilcanopy_compute_methods.jl b/src/diagnostics/soilcanopy_compute_methods.jl deleted file mode 100644 index eef836b786..0000000000 --- a/src/diagnostics/soilcanopy_compute_methods.jl +++ /dev/null @@ -1,603 +0,0 @@ -# stored in p - -function compute_soil_net_radiation!(out, Y, p, t, land_model::SoilCanopyModel) - if isnothing(out) - return copy(p.soil.R_n) - else - out .= p.soil.R_n - end -end - -function compute_soil_latent_heat_flux!( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.soil.turbulent_flux.lhf) # is this different from canopy.energy.lhf? - else - out .= p.soil.turbulent_flux.lhf - end -end - -function compute_soil_aerodynamic_resistance!( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.soil.turbulent_fluxes.r_ae) - else - out .= p.soil.turbulent_fluxes.r_ae - end -end - -function compute_soil_sensible_heat_flux!( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.soil.turbulent_fluxes.shf) - else - out .= p.soil.turbulent_fluxes.shf - end -end - -function compute_vapor_flux!(out, Y, p, t, land_model::SoilCanopyModel) - if isnothing(out) - return copy(p.soil.turbulent_fluxes.vapor_flux) - else - out .= p.soil.turbulent_fluxes.vapor_flux - end -end - -function compute_soil_temperature!( - out, - Y, - p, - t, - land_model::Union{SoilCanopyModel, EnergyHydrology}, -) - if isnothing(out) - return copy(top_center_to_surface(p.soil.T)) - else - out .= top_center_to_surface(p.soil.T) - end -end - -function compute_soil_water_liquid!( - out, - Y, - p, - t, - land_model::Union{SoilCanopyModel, EnergyHydrology}, -) - if isnothing(out) - return copy(top_center_to_surface(p.soil.θ_l)) # or is it in Y.soil? - else - out .= top_center_to_surface(p.soil.θ_l) - end -end - -function compute_infiltration( - out, - Y, - p, - t, - land_model::Union{SoilCanopyModel, EnergyHydrology}, -) - if isnothing(out) - return copy(p.soil.infiltration) - else - out .= p.soil.infiltration - end -end - -function compute_soilco2_diffusivity(out, Y, p, t, land_model::SoilCanopyModel) - if isnothing(out) - return copy(p.soilco2.D) # NOTE: we will need a method to compute surface co2 efflux - else - out .= p.soilco2.D - end -end - -function compute_soilco2_source_microbe( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.soilco2.Sm) - else - out .= p.soilco2.Sm - end -end - -function compute_stomatal_conductance(out, Y, p, t, land_model::SoilCanopyModel) - if isnothing(out) - return copy(p.canopy.conductance.gs) # doublecheck: stomata, not canopy - else - out .= p.canopy.conductance.gs - end -end - -function compute_medlyn_term(out, Y, p, t, land_model::SoilCanopyModel) - if isnothing(out) - return copy(p.canopy.conductance.medlyn_term) - else - out .= p.canopy.conductance.medlyn_term - end -end - -function compute_canopy_transpiration(out, Y, p, t, land_model::SoilCanopyModel) - if isnothing(out) - return copy(p.canopy.transpiration) # doublecheck: canopy, not leaf - else - out .= p.canopy.transpiration - end -end - -function compute_rainfall(out, Y, p, t, land_model::SoilCanopyModel) - if isnothing(out) - return copy(p.drivers.P_liq) # I guess this is read and put it p. not computed. curious if we should handle this differently. - else - out .= p.drivers.P_liq - end -end - -function compute_snowfall(out, Y, p, t, land_model::SoilCanopyModel) - if isnothing(out) - return copy(p.drivers.P_snow) # following comment above, we could have a default getting only model output, and one also getting some inputs like drivers - else - out .= p.drivers.P_snow - end -end - -function compute_pressure(out, Y, p, t, land_model::SoilCanopyModel) - if isnothing(out) - return copy(p.drivers.P) # not sure if precip or pressure - else - out .= p.drivers.P - end -end - -function compute_wind_speed(out, Y, p, t, land_model::SoilCanopyModel) - if isnothing(out) - return copy(p.drivers.u) - else - out .= p.drivers.u - end -end - -function compute_specific_humidity(out, Y, p, t, land_model::SoilCanopyModel) - if isnothing(out) - return copy(p.drivers.q) # check if this is correct. Also, check if Bucket has it and same name or not. - else - out .= p.drivers.q - end -end - -function compute_air_co2(out, Y, p, t, land_model::SoilCanopyModel) - if isnothing(out) - return copy(p.drivers.c_co2) - else - out .= p.drivers.c_co2 - end -end - -function compute_radiation_shortwave_down( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.drivers.SW_d) - else - out .= p.drivers.SW_d - end -end - -function compute_radiation_longwave_down( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.drivers.LW_d) - else - out .= p.drivers.LW_d - end -end - -function compute_photosynthesis_net_leaf( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.canopy.photosynthesis.An) - else - out .= p.canopy.photosynthesis.An - end -end - -function compute_photosynthesis_net_canopy!( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) # could be gross primary productivity, but this is consistent with leaf - if isnothing(out) - return copy(p.canopy.photosynthesis.GPP) - else - out .= p.canopy.photosynthesis.GPP - end -end - -function compute_respiration_leaf(out, Y, p, t, land_model::SoilCanopyModel) - if isnothing(out) - return copy(p.canopy.photosynthesis.Rd) - else - out .= p.canopy.photosynthesis.Rd - end -end - -function compute_vcmax25(out, Y, p, t, land_model::SoilCanopyModel) - if isnothing(out) - return copy(p.canopy.photosynthesis.vcmax25) - else - out .= p.canopy.photosynthesis.vcmax25 - end -end - -function compute_photosynthetically_active_radiation( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.canopy.radiative_transfer.par) - else - out .= p.canopy.radiative_transfer.par - end -end - -function compute_photosynthetically_active_radiation_absorbed( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.canopy.radiative_transfer.apar) - else - out .= p.canopy.radive_transfer.apar - end -end - -function compute_photosynthetically_active_radiation_reflected( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.canopy.radiative_transfer.rpar) - else - out .= p.canopy.radiative_transfer.rpar - end -end - -function compute_photosynthetically_active_radiation_transmitted( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.canopy.radiative_transfer.tpar) - else - out .= p.canopy.radiative_transfer.tpar - end -end - -function compute_near_infrared_radiation( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.canopy.radiative_transfer.nir) - else - out .= p.canopy.radiative_transfer.nir - end -end - -function compute_near_infrared_radiation_absorbed( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.canopy.radiative_transfer.anir) - else - out .= p.canopy.radiative_transfer.anir - end -end - -function compute_near_infrared_radiation_reflected( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.canopy.radiative_transfer.rnir) - else - out .= p.canopy.radiative_transfer.rnir - end -end - -function compute_near_infrared_radiation_transmitted( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.canopy.radiative_transfer.tnir) - else - out .= p.canopy.radiative_transfer.tnir - end -end - -function compute_radiation_shortwave_net( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.canopy.radiative_transfer.SW_n) - else - out .= p.canopy.radiative_transfer.SW_n - end -end - -function compute_radiation_longwave_net( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.canopy.radiative_transfer.LW_n) - else - out .= p.canopy.radiative_transfer.LW_n - end -end - -function compute_autotrophic_respiration( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.canopy.autotrophic_respiration.Ra) - else - out .= p.canopy.autotrophic_respiration.Ra - end -end - -function compute_soilco2(out, Y, p, t, land_model::SoilCanopyModel) - if isnothing(out) - return copy(Y.soilco2.C) - else - out .= Y.soilco2.C - end -end - -function compute_heterotrophic_respiration( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.soilco2.top_bc) - else - p.soilco2.top_bc - end -end - -function compute_soil_hydraulic_conductivity( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.soil.K) - else - p.soil.K - end -end - -function compute_soil_water_potential(out, Y, p, t, land_model::SoilCanopyModel) - if isnothing(out) - return copy(p.soil.ψ) - else - p.soil.ψ - end -end - -function compute_soil_thermal_conductivity( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.soil.κ) - else - p.soil.κ - end -end - -function compute_solar_zenith_angle(out, Y, p, t, land_model::SoilCanopyModel) - if isnothing(out) - return copy(p.drivers.θs) - else - p.drivers.θs - end -end - -function compute_moisture_stress_factor( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.canopy.hydraulics.β) - else - p.canopy.hydraulics.β - end -end - -function compute_canopy_water_potential( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.canopy.hydraulics.ψ) - else - p.canopy.hydraulics.ψ - end -end - -function compute_cross_section(out, Y, p, t, land_model::SoilCanopyModel) - if isnothing(out) - return copy(p.canopy.hydraulics.fa) - else - p.canopy.hydraulics.fa - end -end - -function compute_cross_section_roots(out, Y, p, t, land_model::SoilCanopyModel) - if isnothing(out) - return copy(p.canopy.hydraulics.fa_roots) - else - p.canopy.hydraulics.fa_roots - end -end - -function compute_area_index!(out, Y, p, t, land_model::SoilCanopyModel) - if isnothing(out) - return copy(p.canopy.hydraulics.area_index.leaf) - else - out .= p.canopy.hydraulics.area_index.leaf - end -end - -function compute_canopy_latent_heat_flux( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.canopy.energy.lhf) - else - p.canopy.canopy.lhf - end -end - -function compute_canopy_sensible_heat_flux( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.canopy.energy.shf) - else - p.canopy.canopy.shf - end -end - -function compute_canopy_aerodynamic_resistance( - out, - Y, - p, - t, - land_model::SoilCanopyModel, -) - if isnothing(out) - return copy(p.canopy.energy.r_ae) - else - p.canopy.canopy.r_ae - end -end - -function compute_canopy_temperature!(out, Y, p, t, land_model::SoilCanopyModel) - if isnothing(out) - return copy(Y.canopy.energy.T) - else - out .= Y.canopy.energy.T - end -end - -function compute_soil_ice!( - out, - Y, - p, - t, - land_model::Union{SoilCanopyModel, EnergyHydrology}, -) - if isnothing(out) - return copy(top_center_to_surface(Y.soil.θ_i)) - else - out .= top_center_to_surface(Y.soil.θ_i) - end -end