diff --git a/experiments/standalone/Biogeochemistry/disordered_kinetics.jl b/experiments/standalone/Biogeochemistry/disordered_kinetics.jl new file mode 100644 index 0000000000..df6d7685be --- /dev/null +++ b/experiments/standalone/Biogeochemistry/disordered_kinetics.jl @@ -0,0 +1,90 @@ +import SciMLBase +import ClimaComms +ClimaComms.@import_required_backends +import ClimaTimeSteppers as CTS +using ClimaCore +using ClimaLand +using ClimaLand.Domains: Column +using ClimaLand.Soil +using ClimaLand.Soil.DisorderedKinetics +using Dates + +import ClimaLand.Parameters as LP + +# Define simulation times +t0 = Float64(0); +tf = Float64(100); +dt = Float64(0.01); + +FT = Float64; + +model_params = DisorderedKineticsModelParameters{FT}( + mu = FT(-1.0), + sigma = FT(2.0)); +zmax = FT(0); +zmin = FT(-1); +nelems = 1; +# we use a vertical column with one layer to in the future interface with other variables like temperature and moisture that are depth dependent +lsm_domain = Column(; zlim = (zmin, zmax), nelements = nelems); + +# Make biogeochemistry model args +NPP = PrescribedSOCInputs{FT}(TimeVaryingInput((t) -> 500)); +model_sources = (LitterInput{FT}(),); + +model = DisorderedKineticsModel{FT}(; + parameters=model_params, + npools=100, + domain=lsm_domain, + sources=model_sources, + drivers=NPP +); + +Y, p, coords = initialize(model); +set_initial_cache! = make_set_initial_cache(model); + + + + +function init_model!(Y, p, model) + N = NTuple{model.npools, FT} + function set_k(k::N) where {N} + k = collect(exp.(range(model.parameters.mu-8*model.parameters.sigma,model.parameters.mu+8*model.parameters.sigma,length=model.npools))); + return ntuple(x->k[x],model.npools) + end + p.soilC.ks .= set_k.(p.soilC.ks); +end + + +init_model!(Y, p, model); + +set_initial_cache!(p, Y, t0); +disordered_kinetics_exp_tendency! = make_exp_tendency(model); + +timestepper = CTS.RK4(); +ode_algo = CTS.ExplicitAlgorithm(timestepper); + +saveat = collect(t0:FT(10 * dt):tf); +sv = (; + t = Array{Float64}(undef, length(saveat)), + saveval = Array{NamedTuple}(undef, length(saveat)), +); +saving_cb = ClimaLand.NonInterpSavingCallback(sv, saveat); +# updateat = deepcopy(saveat) +# drivers = ClimaLand.get_drivers(model) +# updatefunc = ClimaLand.make_update_drivers(drivers) +# driver_cb = ClimaLand.DriverUpdateCallback(updateat, updatefunc) +# cb = SciMLBase.CallbackSet(driver_cb, saving_cb) + +prob = SciMLBase.ODEProblem( + CTS.ClimaODEFunction(T_exp! = disordered_kinetics_exp_tendency!), + Y, + (t0, tf), + p, +) +# sol = SciMLBase.solve(prob, ode_algo; dt = dt, callback = cb) +sol = SciMLBase.solve(prob, ode_algo; dt = dt, callback = saving_cb); + +# Check that simulation still has correct float type +@assert eltype(sol.u[end].soilC) == FT; + + diff --git a/experiments/yinon_script/code.jl b/experiments/yinon_script/code.jl new file mode 100644 index 0000000000..10a374ff48 --- /dev/null +++ b/experiments/yinon_script/code.jl @@ -0,0 +1,67 @@ +# parameters: mu and sigma (mean and std distributions) +# auxiliary: k is an array of decay rate (constant) +# state variable: u (array of C with different decay rate) +# input: input of carbon per area per unit time +# we prescribe input for now, but it should be given by another +# module prognostically, e.g., litter fall etc. + +using QuadGK +using DifferentialEquations +using StaticArrays +using Distributions +using DataFrames +using CSV +using BenchmarkTools +# integral, error = quadgk(x -> cos(200x), 0, 1) + +# function dc_kt_dt(u, p, t)#, fixed=true) +# k,mu,sigma,input = p +# if fixed==true +# return SA[input*pdf(LogNormal(mu,sigma),k) - k*u] +# else +# ind = min(int(t+1),length(input)) +# return SA[input[ind]*pdf(LogNormal(mu,sigma),k) - k*u] +# end + + +# end + +function dc_kt_dt(u, p, t)#, fixed=true) + k,mu,sigma,input,fixed = p + if fixed== true + SA[input.*pdf(LogNormal(mu,sigma),k) .- k.*u[1]] + else + ind = min(floor(Int,t+1),length(input)) + return SA[input[ind]*pdf(LogNormal(mu,sigma),k) - k*u[1]] + end + + # SA[p[4].*pdf(LogNormal(p[2],p[3]),p[1]) .- p[1].*u[1]] +end +# prob = ODEProblem(dc_kt_dt, SA[0.], (0.,100.),[0.1,1.,2.,0.25,true]) +# solve(prob) + + +function solve_ode(k,tau,age,input,fixed=true) + sigma = sqrt(log(age/tau)) + mu = - log(sqrt(tau^3/age)); + ps = (k,mu,sigma,input,fixed) + tspan = (0.,100.) + u0 = SA[0.] + prob = ODEProblem(dc_kt_dt, u0,tspan , ps) + solve(prob,saveat=1) +end +function run_diskin(tau,age,input,fixed=true,discrete=false) + if discrete==true + krange = exp.(range(-10,stop=10,length=100000)); + dk = diff(vcat(0,krange)); + res = sum(reduce(hcat,[solve_ode(k,tau,age,input,fixed).u.*d for (k,d) in zip(krange,dk)]),dims=2); + else + res, error = quadgk(x -> solve_ode(x,tau,age,input,fixed), 0, Inf) + end + res +end +NPP = Array(CSV.read("experiments/yinon_script/test_NPP.csv",DataFrame)); + +cont = run_diskin(17,1000,0.25,true,false); +@btime noncont = run_diskin(17,1000,0.25,true,true); +cont'./noncont diff --git a/src/shared_utilities/drivers.jl b/src/shared_utilities/drivers.jl index feed5fce62..c59de1c8b6 100644 --- a/src/shared_utilities/drivers.jl +++ b/src/shared_utilities/drivers.jl @@ -18,6 +18,7 @@ export AbstractAtmosphericDrivers, PrescribedAtmosphere, PrescribedPrecipitation, PrescribedSoilOrganicCarbon, + PrescribedSOCInputs, CoupledAtmosphere, PrescribedRadiativeFluxes, CoupledRadiativeFluxes, @@ -95,6 +96,23 @@ end PrescribedSoilOrganicCarbon{FT}(soc) where {FT} = PrescribedSoilOrganicCarbon{FT, typeof(soc)}(soc) + +""" + PrescribedSOCInputs{FT} + +A type for prescribing soil organic carbon inputs into our disordered kinetics model. +$(DocStringExtensions.FIELDS) +""" +struct PrescribedSOCInputs{FT, SOC_input <: AbstractTimeVaryingInput} <: + AbstractClimaLandDrivers{FT} + "Soil organic carbon input, function of time and space: kg C/m^2/yr" + soc_input::SOC_input +end + +PrescribedSOCInputs{FT}(soc_input) where {FT} = + PrescribedSoilOrganicCarbon{FT, typeof(soc_input)}(soc_input) + + """ PrescribedAtmosphere{FT, CA, DT} <: AbstractAtmosphericDrivers{FT} diff --git a/src/standalone/Soil/Biogeochemistry/DisorderedKinetics.jl b/src/standalone/Soil/Biogeochemistry/DisorderedKinetics.jl new file mode 100644 index 0000000000..fb0ebbbb22 --- /dev/null +++ b/src/standalone/Soil/Biogeochemistry/DisorderedKinetics.jl @@ -0,0 +1,217 @@ +module DisorderedKinetics +using Distributions +using ClimaLand +using DocStringExtensions +using ClimaCore +import ...Parameters as LP +import ClimaCore: Fields, Operators, Geometry, Spaces + +import ClimaLand.Domains: AbstractDomain +import ClimaLand: + AbstractExpModel, + make_update_aux, + make_compute_exp_tendency, + make_update_boundary_fluxes, + prognostic_vars, + auxiliary_vars, + name, + prognostic_types, + auxiliary_types, + prognostic_domain_names, + auxiliary_domain_names, + TopBoundary, + BottomBoundary, + AbstractBC, + boundary_flux!, + AbstractSource, + source! + +export DisorderedKineticsModelParameters, + DisorderedKineticsModel, + LitterInput, + AbstractSoilDriver, + DisorderedKineticsDrivers + + +""" +DisorderedKineticsModelParameters{FT <: AbstractFloat, PSE} + +A struct for storing parameters of the `DisorderedKineticsModel`. + +All of these parameters are currently treated as global constants. +$(DocStringExtensions.FIELDS) +""" +Base.@kwdef struct DisorderedKineticsModelParameters{FT <: AbstractFloat} + "the mean of the log-normal distribution of the soil C decay rate []" + mu::FT + "the standard deviation of the log-normal distribution of the soil C decay rate []" + sigma::FT +end + + +""" +AbstractSoilCarbonModel{FT} <: ClimaLand.AbstractExpModel{FT} + +An abstract model type for soil biogeochemistry models. +""" +abstract type AbstractSoilCarbonModel{FT} <: + ClimaLand.AbstractExpModel{FT} end + +""" +DisorderedKineticsModel + +A model for simulating soil organic carbon dynamics using a disordered kinetics model. + +$(DocStringExtensions.FIELDS) +""" +struct DisorderedKineticsModel{FT, PS, N, D, S, DT} <: + AbstractSoilCarbonModel{FT} + "the parameter set" + parameters::PS + "the number of soil carbon pools for discretization" + npools::N + "the soil domain, using ClimaCore.Domains" + domain::D + "A tuple of sources, each of type AbstractSource" + sources::S + "Drivers" + drivers::DT +end + + +""" +DisorderedKineticsModel{FT}(; + parameters::DisorderedKineticsModelParameters{FT}, + npools::Int64, + domain::ClimaLand.AbstractDomain, + sources::Tuple, + drivers::DT + ) where {FT, BC} + +A constructor for `DisorderedKineticsModel`. +""" +function DisorderedKineticsModel{FT}(; + parameters::DisorderedKineticsModelParameters{FT}, + npools::Int64, + domain::ClimaLand.AbstractDomain, + sources::Tuple, + drivers::DT, +) where {FT, DT} + args = (parameters, npools, domain, sources, drivers) + DisorderedKineticsModel{FT, typeof.(args)...}(args...) +end + +ClimaLand.name(model::DisorderedKineticsModel) = :soilC +ClimaLand.prognostic_vars(::DisorderedKineticsModel) = (:C,) +ClimaLand.prognostic_types(model::DisorderedKineticsModel{FT}) where {FT} = (NTuple{model.npools, FT},) +# we use a vertical column with one layer to in the future interface with other variables like temperature and moisture that are depth dependent +ClimaLand.prognostic_domain_names(::DisorderedKineticsModel) = (:subsurface,) + +ClimaLand.auxiliary_vars(model::DisorderedKineticsModel) = (:ks,:inputs) +ClimaLand.auxiliary_types(model::DisorderedKineticsModel{FT}) where {FT} = (NTuple{model.npools, FT}, FT) +# we use a vertical column with one layer to in the future interface with other variables like temperature and moisture that are depth dependent +ClimaLand.auxiliary_domain_names(model::DisorderedKineticsModel) = (:subsurface, :subsurface) + +""" + make_compute_exp_tendency(model::DisorderedKineticsModel) + +An extension of the function `make_compute_exp_tendency`, for the disordered kinetics model. +This function creates and returns a function which computes the entire +right hand side of the ODE for `C`, and updates `dY.soilC.C` in place +with that value. These quantities will be stepped explicitly. + +This has been written so as to work with Differential Equations.jl. +""" +function ClimaLand.make_compute_exp_tendency(model::DisorderedKineticsModel) + function compute_exp_tendency!(dY, Y, p, t) + + @. dY.soilC.C = - p.soilC.ks * Y.soilC.C + + # Source terms are added in here + for src in model.sources + print(src) + source!(dY, src, Y, p, model.parameters) + end + end + return compute_exp_tendency! +end + + +""" + LitterInput{FT} <: AbstractCarbonSource{FT} + +Struct for the litter input of C into the SOC pool, appearing as a source +term in the differential equation. +""" +struct LitterInput{FT} <: ClimaLand.AbstractSource{FT} end + +""" + ClimaLand.source!(dY::ClimaCore.Fields.FieldVector, + src::AbstractSoilCarbonSource, + Y::ClimaCore.Fields.FieldVector, + p::NamedTuple, + params) + +A method which extends the ClimaLand source! function for the +case of the disordered kinetics model. +""" +function ClimaLand.source!( + dY::ClimaCore.Fields.FieldVector, + src::LitterInput, + Y::ClimaCore.Fields.FieldVector, + p::NamedTuple, + params::DisorderedKineticsModelParameters, +) + @. dY.soilC.C += p.soilC.inputs * p_k(p.soilC.ks,params.mu,params.sigma); +end + +""" + p_k(k::N) + + a function to calculate the pdf of the lognormal distribution of each decay rate k +""" +function p_k(k,mu,sigma) @. (1/(k* sigma * sqrt(2*π))) * exp(-((log(k) - mu)^2)/(2*sigma^2)); end + + + + + +""" + SoilDrivers + +A container which passes in the soil drivers to the biogeochemistry +model. These drivers are either of type Prescribed (for standalone mode) +or Prognostic (for running with a prognostic model for soil temp and moisture). + +$(DocStringExtensions.FIELDS) +""" +struct DisorderedKineticsDrivers{ + FT, + SOC_input <: PrescribedSOCInputs{FT}, +} + "Soil SOM driver - Prescribed only" + soc_inputs::SOC_input +end + + + +""" + make_update_aux(model::DisorderedKineticsModel) + +An extension of the function `make_update_aux`, for the disordered kinetics model. +This function creates and returns a function which updates the auxiliary +variables `p.soilC.variable` in place. +This has been written so as to work with Differential Equations.jl. + +This function is empty because the auxiliary variable `ks` is not updated in time. +""" +function ClimaLand.make_update_aux(model::DisorderedKineticsModel) + function update_aux!(p, Y, t) + p.soilC.inputs .= model.drivers.soc.func.(t) + end + return update_aux! +end + +Base.broadcastable(ps::DisorderedKineticsModelParameters) = tuple(ps) + +end \ No newline at end of file diff --git a/src/standalone/Soil/Soil.jl b/src/standalone/Soil/Soil.jl index 19f7a9a5f7..9daede9706 100644 --- a/src/standalone/Soil/Soil.jl +++ b/src/standalone/Soil/Soil.jl @@ -184,4 +184,6 @@ include("./boundary_conditions.jl") include("./soil_hydrology_parameterizations.jl") include("Biogeochemistry/Biogeochemistry.jl") using .Biogeochemistry +include("Biogeochemistry/DisorderedKinetics.jl") +using .DisorderedKinetics end