├── .gitignore ├── docs ├── Project.toml ├── make.jl └── src │ └── index.md ├── test ├── runtests.jl ├── maespa_utils.jl ├── shared.jl ├── maespa_environment.jl ├── construct.jl ├── maespa_core.jl └── maespa_enbal.jl ├── src ├── interfaces │ ├── energy_balance.jl │ ├── photosynthesis.jl │ └── stomatal_conductance.jl ├── formulations │ ├── medlyn.jl │ ├── leuning.jl │ ├── fvcb.jl │ ├── emax.jl │ ├── tuzet.jl │ ├── ballberry.jl │ ├── maespa.jl │ └── jarvis.jl ├── vars.jl ├── biophysical │ ├── decoupling.jl │ ├── biophysical.jl │ ├── shape.jl │ ├── radiation_conductance.jl │ ├── boundary_conductance.jl │ └── evapotranspiration.jl ├── core │ ├── rubisco_regen.jl │ ├── non_stomatal_soilmoisture.jl │ ├── respiration.jl │ ├── compensation.jl │ ├── stomatal_soilmoisture.jl │ └── flux.jl ├── constants.jl └── Photosynthesis.jl ├── .travis.yml ├── Project.toml ├── README.md └── LICENSE.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.jl.cov 2 | *.jl.*.cov 3 | *.jl.mem 4 | test/maespa 5 | -------------------------------------------------------------------------------- /docs/Project.toml: -------------------------------------------------------------------------------- 1 | [deps] 2 | Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" 3 | Photosynthesis = "5e10c064-2706-53a3-a67d-d473e313a663" 4 | Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" 5 | -------------------------------------------------------------------------------- /docs/make.jl: -------------------------------------------------------------------------------- 1 | using Documenter, Photosynthesis, Unitful 2 | 3 | makedocs( 4 | modules = [Photosynthesis], 5 | sitename = "Photosynthesis.jl", 6 | pages = Any[ 7 | "Home" => "index.md", 8 | ], 9 | clean = false, 10 | ) 11 | 12 | deploydocs( 13 | repo = "github.com/rafaqz/Photosynthesis.jl.git", 14 | ) 15 | -------------------------------------------------------------------------------- /test/runtests.jl: -------------------------------------------------------------------------------- 1 | using SafeTestsets 2 | 3 | @time @safetestset "test all constructors work" begin include("construct.jl") end 4 | @time @safetestset "test core functions against maespa core" begin include("maespa_core.jl") end 5 | @time @safetestset "test environmental functions against maespa core" begin include("maespa_environment.jl") end 6 | @time @safetestset "test utils against maespa fortran" begin include("maespa_utils.jl") end 7 | @time @safetestset "test energy balance against maespa fortran" begin include("maespa_enbal.jl") end 8 | -------------------------------------------------------------------------------- /src/interfaces/energy_balance.jl: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | Energy balance models calculate leaf temperature, usually also running 4 | photosynthesis and all other model components along with environmental models 5 | like radiation and boundary layer conductance. 6 | 7 | They are run in [`enbal!`](@ref) methods. 8 | """ 9 | abstract type AbstractEnergyBalance end 10 | 11 | """ 12 | enbal!(v, m::AbstractEnergyBalance) 13 | 14 | Calculates leaf photosynthesis and transpiration for an 15 | [`AbstractEnergyBalance`](@ref) model `m` and variables `v`. 16 | 17 | Results are written to `v`. 18 | """ 19 | function enbal! end 20 | -------------------------------------------------------------------------------- /src/interfaces/photosynthesis.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Abstract supertype for all photosynthesis models. 3 | 4 | These expect to run inside [`enbal!`](@ref), and otherwise 5 | nead to have the `tleaf` and `vpdleaf` variables manually set. 6 | """ 7 | abstract type AbstractPhotosynthesis end 8 | 9 | """ 10 | photosynthesis!(vars, params::AbstractPhotosynthesis) 11 | 12 | Run a photosynthesis model, writing results to `vars`. 13 | """ 14 | function photosynthesis! end 15 | 16 | """ 17 | check_extremes!(v, p::AbstractFvCBPhotosynthesis) 18 | 19 | Check extreme values are in tolerable ranges. 20 | """ 21 | function check_extremes! end 22 | -------------------------------------------------------------------------------- /src/formulations/medlyn.jl: -------------------------------------------------------------------------------- 1 | """ 2 | MedlynStomatalConductanceSubModel(vpdmin, gk, gamma, g1) 3 | 4 | Medlyn stomatal conductance formulation parameters 5 | Has the extra `vpdmin` paramater in Pa. 6 | (modelgs = 4 in maestra) 7 | 8 | $(FIELDDOCTABLE) 9 | """ 10 | @MixinBallBerryStomatalConductanceSubModel struct MedlynStomatalConductanceSubModel{Pa} <: AbstractBallBerryStomatalConductanceSubModel 11 | vpdmin::Pa | 1500.0 | kPa | _ | _ 12 | gk::F | 0.3 | _ | _ | _ 13 | end 14 | 15 | gs_div_a(f::MedlynStomatalConductanceSubModel, v) = 16 | (oneunit(f.g1) + f.g1 / (max(f.vpdmin, v.vpdleaf)/MPa)^(1 - f.gk)) / v.cs 17 | -------------------------------------------------------------------------------- /src/formulations/leuning.jl: -------------------------------------------------------------------------------- 1 | """ 2 | LeuningStomatalConductanceSubModel(D0, gamma, g1) 3 | 4 | Leuning stomatal conductance formulation with extra `D0` paramater. 5 | 6 | From R. Leuning, A critical appraisal of a combined stomatal-photosynthesis 7 | model for C3 plants. Plant, Celt and Environment (1995) 18, 339-355 8 | 9 | $(FIELDDOCTABLE) 10 | """ 11 | @MixinBallBerryStomatalConductanceSubModel struct LeuningStomatalConductanceSubModel{D0} <: AbstractBallBerryStomatalConductanceSubModel 12 | D0::D0 | 1.5e6 | Pa | (0.0, 2e6) | _ 13 | end 14 | 15 | gs_div_a(f::LeuningStomatalConductanceSubModel, v) = 16 | f.g1 / (v.cs - f.gamma) / (1 + v.vpdleaf / f.D0) 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: julia 2 | 3 | os: 4 | - linux 5 | - osx 6 | 7 | julia: 8 | - 1 9 | - nightly 10 | 11 | notifications: 12 | email: false 13 | 14 | addons: 15 | apt: 16 | packages: 17 | - gfortran 18 | 19 | jobs: 20 | allow_failures: 21 | - julia: nightly 22 | fast_finish: true 23 | include: 24 | - stage: "Documentation" 25 | julia: 1 26 | os: linux 27 | script: 28 | - julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate();' 29 | - julia --project=docs/ docs/make.jl 30 | after_success: skip 31 | 32 | after_success: 33 | - julia -e 'using Pkg; Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())' 34 | -------------------------------------------------------------------------------- /src/vars.jl: -------------------------------------------------------------------------------- 1 | ########################################################################################### 2 | # Environmental Variables 3 | 4 | @chain vars @udefault_kw @units @description 5 | 6 | """ 7 | @MixinEnviroVars mixin macro. 8 | """ 9 | @mix @vars struct MixinEnviroVars{TA,WS,PA,RN,SM,PR,SWP,VPD,CA,RH} 10 | tair::TA | (273.15 + 25.0) | K | _ 11 | windspeed::WS | 1.0 | m*s^-1 | _ 12 | par::PA | 4.575*250.0 | μmol*m^-2*s^-1 | _ 13 | rnet::RN | 250.0 | W*m^-2 | _ 14 | soilmoist::SM | 0.2 | _ | _ 15 | pressure::PR | 101250.0 | Pa | _ 16 | swp::SWP | -0.1 | MPa | _ 17 | vpd::VPD | 500.0 | Pa | _ 18 | ca::CA | 400.0 | μmol*mol^-1 | _ 19 | rh::RH | 0.5 | _ | _ 20 | end 21 | -------------------------------------------------------------------------------- /src/biophysical/decoupling.jl: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | Canopy-atmospheric decoupling models, 4 | calculated in [`decoupling`](@ref) method. 5 | """ 6 | abstract type AbstractDecoupling end 7 | 8 | """ 9 | decoupling(f::AbstractDecoupling, v) 10 | 11 | Calculate decoupling, returning a float between 0.0 and 1.0. 12 | """ 13 | function decoupling end 14 | 15 | """ 16 | McNaughtonJarvisDecoupling() 17 | 18 | Calculate decoupling coefficient following McNaughton and Jarvis 1986 19 | """ 20 | struct McNaughtonJarvisDecoupling <: AbstractDecoupling end 21 | 22 | decoupling(f::McNaughtonJarvisDecoupling, v) = begin 23 | γc = CPAIR * AIRMA * v.pressure / v.lhv 24 | epsilon = ustrip(v.slope / γc) # TODO why is ustrip needed here? 25 | (1.0 + epsilon) / (1.0 + epsilon + v.gbv / v.gsv) 26 | end 27 | 28 | """ 29 | NoDecoupling() 30 | 31 | Don't calculate decoupling. 32 | """ 33 | struct NoDecoupling <: AbstractDecoupling end 34 | 35 | decoupling(f::NoDecoupling, v) = 0.0 36 | -------------------------------------------------------------------------------- /src/core/rubisco_regen.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Rubisco regeneration models. 3 | These are run in the [`rubisco_regeneration`](@ref) method. 4 | """ 5 | abstract type AbstractRubiscoRegen end 6 | 7 | """ 8 | rubisco_regeneration(f::RubiscoRegen, v) 9 | 10 | Returns RuBP regeneration rate, in `u"umol*m-2*s-1"` 11 | for formulation `f` given variables `v`. 12 | """ 13 | function rubisco_regeneration end 14 | 15 | """ 16 | RubiscoRegen(theta, ajq) 17 | 18 | Rubisco regeneration model. 19 | 20 | TODO: specify origin of formulation 21 | 22 | $(FIELDDOCTABLE) 23 | """ 24 | @columns struct RubiscoRegen{} <: AbstractRubiscoRegen 25 | theta::Float64 | 0.4 | _ | (0.0, 1.0) | "Shape parameter of the non-rectangular hyperbola" 26 | ajq::Float64 | 0.324 | _ | (0.0, 1.0) | "Quantum yield of electron transport" 27 | end 28 | 29 | rubisco_regeneration(f::RubiscoRegen, v) = begin 30 | a = f.theta 31 | b = -(f.ajq * v.par + v.jmax) 32 | c = f.ajq * v.par * v.jmax 33 | j = quad(Lower(), a, b, c) # Actual e- transport rate, umol m-2 s-1 34 | return j / 4 35 | end 36 | -------------------------------------------------------------------------------- /Project.toml: -------------------------------------------------------------------------------- 1 | name = "Photosynthesis" 2 | uuid = "5e10c064-2706-53a3-a67d-d473e313a663" 3 | authors = ["Rafael Schouten "] 4 | version = "0.1.0" 5 | 6 | [deps] 7 | DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" 8 | FieldDefaults = "49426c49-986f-5969-8844-d5cc96441cfc" 9 | FieldDocTables = "a5d692f0-33f0-11e9-293e-eb83c8d6177d" 10 | FieldMetadata = "bf96fef3-21d2-5d20-8afa-0e7d4c32a885" 11 | Mixers = "2a8e4939-dab8-5edc-8f64-72a8776f13de" 12 | SimpleRoots = "6accb78c-d340-5052-9dc3-a835939e19d0" 13 | Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" 14 | 15 | [compat] 16 | DocStringExtensions = "0.8" 17 | FieldDefaults = "0.3.1" 18 | FieldDocTables = "0.1" 19 | FieldMetadata = "0.3" 20 | Flatten = "0.3" 21 | SimpleRoots = "0.1" 22 | Unitful = "1.0" 23 | julia = "1" 24 | 25 | [extras] 26 | Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" 27 | Flatten = "4c728ea3-d9ee-5c9a-9642-b6f7d7dc04fa" 28 | Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" 29 | SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" 30 | Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" 31 | 32 | [targets] 33 | test = ["Combinatorics", "Flatten", "Libdl", "SafeTestsets", "Test"] 34 | -------------------------------------------------------------------------------- /test/maespa_utils.jl: -------------------------------------------------------------------------------- 1 | using Photosynthesis 2 | using Photosynthesis: quad, Lower, Upper 3 | 4 | include(joinpath(dirname(pathof(Photosynthesis)), "../test/shared.jl")) 5 | 6 | # Quadratic solvers 7 | # quadm: quad 8 | quadm_fortran = Libdl.dlsym(maespa_photosynlib, :quadm_) 9 | a = 0.5 10 | b = -0.5 11 | c = 0.05 12 | quad_ref = ccall(quadm_fortran, Float32, (Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Int32}), a, b, c, 1)/4.0 13 | quad_test = quad(Lower(), a,b,c)/4 14 | @test quad_ref ≈ quad_test atol=1e-5 15 | 16 | # quadm: quap 17 | quadp_fortran = Libdl.dlsym(maespa_photosynlib, :quadp_) 18 | a = 0.5 19 | b = -0.5 20 | c = 0.05 21 | quad_ref = ccall(quadp_fortran, Float32, (Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Int32}), a, b, c, 1)/4.0 22 | quad_test = quad(Upper(), a,b,c)/4 23 | @test quad_ref ≈ quad_test atol=1e-5 24 | 25 | 26 | # Arrenius equations 27 | # arrhfn: arrhenius 28 | arrhfn_fortran = Libdl.dlsym(maespa_photosynlib, :arrh_) 29 | arrh_ref = ccall(arrhfn_fortran, Float32, (Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}), 42.75, 37830.0, 30.0, 25.0) 30 | arrh = arrhenius(42.75u"μmol*mol^-1", 37830.0u"J*mol^-1", 30.0u"°C", 25.0u"°C") 31 | @test arrh.val ≈ arrh_ref 32 | -------------------------------------------------------------------------------- /src/biophysical/biophysical.jl: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | leaftemp(p, v) 4 | 5 | Calculate the leaf temperature given variables `v`. 6 | 7 | Returns a value in u"K". 8 | """ 9 | @inline leaftemp(p, v) = v.tair + (v.rnet - v.et * v.lhv) / (4CPAIR * AIRMA * v.gh) 10 | 11 | """ 12 | latent_heat_water_vapour(tair) 13 | 14 | Caculates the late hear of water vapour from the air temperature. 15 | 16 | Returns a value in u"K". 17 | """ 18 | @inline latent_heat_water_vapour(tair) = begin 19 | T = ustrip(°C, tair) 20 | (H2OLV0 - 2.365e3J*kg^-1 * T) * H2OMW 21 | end 22 | 23 | 24 | """ 25 | vapour_pressure_deficit(tair, rh) 26 | 27 | Calculate vapour pressure deficit given air temperature 28 | `tair` and relative humidity `rh` 29 | 30 | Returns a value in u"kPa" 31 | """ 32 | vapour_pressure_deficit(tair, rh) = begin 33 | es = saturated_vapour_pressure(tair) 34 | ea = rh * es 35 | ea - es 36 | end 37 | 38 | 39 | """ 40 | arrhenius(kref, Ea, T, Tref) 41 | 42 | The Arrhenius function. 43 | 44 | -`kref`: is the value at Tref deg 45 | -`Ea`: the activation energy (j mol - 1) and 46 | -`T`: the temp in °C or K 47 | -`Tref`: the reference temp in °C or K 48 | Standard form and temperature difference form. 49 | """ 50 | arrhenius(kref, Ea, T, Tref) = arrhenius(kref, Ea, T |> K, Tref |> K) 51 | arrhenius(kref, Ea, T::typeof(1.0K), Tref::typeof(1.0K)) = kref * exp(Ea * (T - Tref) / (R * T * Tref)) 52 | arrhenius(A, Ea, T) = arrhenius(A, Ea, T |> K) 53 | arrhenius(A, Ea, T::typeof(1.0K)) = A * exp(Ea / (R * T)) 54 | -------------------------------------------------------------------------------- /src/biophysical/shape.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Determines the shape of stomatal conductance as it approaches zero, 3 | run in [`shape_gs`](@ref) method. 4 | """ 5 | abstract type StomatalConductanceShape end 6 | 7 | """ 8 | shape_gs(f::StomatalConductanceShape, v, p) 9 | 10 | Function to determine the hyperbolic minimum shape, 11 | for a formulation `f` in [`StomatalConductanceShape`](@ref), 12 | fiven variables `v` and stomatal conductance parameters `p`. 13 | 14 | Returns the stomatal conductance `gs` in `u"mol*m^-2*s^-1"`. 15 | """ 16 | function shape_gs end 17 | 18 | """ 19 | HardMinimum() 20 | 21 | Return stomatal conductance value that abrutly 22 | switches to the minimum value `g0`. 23 | """ 24 | struct HardMinimum <: StomatalConductanceShape end 25 | 26 | shape_gs(f::HardMinimum, v, p) = begin 27 | gs = p.g0 + v.gs_div_a * v.aleaf 28 | max(p.g0, gs) 29 | end 30 | 31 | """ 32 | HyperbolicMinimum() 33 | 34 | Return stomatal conductance value that switches to the minimum value `g0` 35 | using a hyperbolic curve, used for optimization to avoid discontinuity. 36 | `hmshape` determines how smooth the transition is. 37 | 38 | $(FIELDDOCTABLE) 39 | """ 40 | @columns struct HyperbolicMinimum <: StomatalConductanceShape 41 | hmshape::Float64 | 0.999 | _ | (0.9, 1.0) | _ 42 | end 43 | 44 | shape_gs(f::HyperbolicMinimum, v, p) = begin 45 | aleafhypmin = (v.ac + v.aj - sqrt((v.ac + v.aj)^2 - 4f.hmshape * v.ac * v.aj)) / 46 | (2 * f.hmshape) - v.rd 47 | p.g0 + v.gs_div_a * aleafhypmin 48 | end 49 | -------------------------------------------------------------------------------- /src/biophysical/radiation_conductance.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Radiation conductance models, run in [`radiation_conductance`](@ref). 3 | """ 4 | abstract type AbstractRadiationConductance end 5 | 6 | 7 | """ 8 | radiation_conductance(f::AbstractRadiationConductance, v) 9 | 10 | Calculates radiation conductance for formular `f`, given 11 | variable `v'. 12 | 13 | Returns quantity with units `u"mol*m^-2*s^-1"` 14 | """ 15 | function radiation_conductance end 16 | 17 | """ 18 | WangRadiationConductance(rdfipt, tuipt, tdipt) 19 | 20 | Returns the "radiation conductance" at given temperature. 21 | Formula from Ying-Ping"s version of Maestro. 22 | 23 | See also Jones (1992) p. 108.0 24 | 25 | $(FIELDDOCTABLE) 26 | """ 27 | @columns struct WangRadiationConductance{T} <: AbstractRadiationConductance 28 | rdfipt::T | 1.0 | _ | (0.0, 2.0) | "Not documented in MAESPA" 29 | tuipt::T | 1.0 | _ | (0.0, 2.0) | "Not documented in MAESPA" 30 | tdipt::T | 1.0 | _ | (0.0, 2.0) | "Not documented in MAESPA" 31 | end 32 | 33 | radiation_conductance(f::WangRadiationConductance, v) = 34 | wang_radiation_conductance(v.tair, f.rdfipt, f.tuipt, f.tdipt) 35 | 36 | """ 37 | wang_radiation_conductance(tair, rdfipt, tuipt, tdipt) 38 | 39 | Returns the "radiation conductance" at given temperature. 40 | Formula from Ying-Ping Wang's version of Maestro. 41 | 42 | See also Jones (1992) p. 108.0 43 | 44 | Returns quantity with units `u"mol*m^-2*s^-1` 45 | """ 46 | wang_radiation_conductance(tair, rdfipt, tuipt, tdipt) = 47 | 4 * σ * ((tair |> K)^3) * rdfipt / tdipt * EMLEAF * 48 | (tdipt + tuipt) / (CPAIR * AIRMA) 49 | -------------------------------------------------------------------------------- /src/interfaces/stomatal_conductance.jl: -------------------------------------------------------------------------------- 1 | # Interface for stomatal conductance 2 | 3 | """ 4 | Stomatal conductance models 5 | """ 6 | abstract type AbstractStomatalConductance end 7 | 8 | """ 9 | Stomatal conductance submodels 10 | """ 11 | abstract type AbstractStomatalConductanceSubModel end 12 | 13 | """ 14 | stomatal_conductance!(v, p) 15 | Stomatal conductance and intercellular CO2 partial pressure calculations. 16 | 17 | v.aleaf is NET leaf photosynthesis. 18 | """ 19 | function stomatal_conductance! end 20 | 21 | """ 22 | photo_init!(vars, params::AbstractStomatalConductance) 23 | 24 | Initialise variables based on the AbstractStomatalConductance model. 25 | """ 26 | function gs_init! end 27 | 28 | """ 29 | photo_update!(vars, params::AbstractStomatalConductance) 30 | 31 | Update variables based on the model. 32 | """ 33 | function gs_update! end 34 | 35 | """ 36 | gs_div_a(m::AbstractStomatalConductanceSubModel, v) 37 | 38 | Returns the value of stomatal conductance `gs` divided by 39 | assimilation `a` for sub-model `m` given variables `v`. 40 | """ 41 | function gs_div_a end 42 | 43 | """ 44 | update_extremes!(v, m::AbstractStomatalConductance) 45 | 46 | Update variables in extreme conditions. 47 | """ 48 | function update_extremes! end 49 | 50 | """ 51 | transport_limited_rate(m::AbstractStomatalConductance, v, gs_div_a) 52 | 53 | Transport limited rate of assimilation. 54 | """ 55 | function transport_limited_rate end 56 | 57 | """ 58 | rubisco_limited_rate(m::AbstractStomatalConductance, v, gs_div_a) 59 | 60 | Rubisco limited rate of assimilation. 61 | """ 62 | function rubisco_limited_rate end 63 | -------------------------------------------------------------------------------- /src/biophysical/boundary_conductance.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Boundary conductance models. 3 | 4 | Provide parameters for [`boundary_conductance_free`](@ref), 5 | and [`boundary_conductance_forced`](@ref). 6 | """ 7 | abstract type AbstractBoundaryConductance end 8 | 9 | """ 10 | BoundaryConductance(leafwidth) 11 | BoundaryConductance(; leafwidth=0.05u"m") 12 | 13 | Standard boundary conductance formulation parameters. 14 | 15 | $(FIELDDOCTABLE) 16 | """ 17 | @columns struct BoundaryConductance{LW} <: AbstractBoundaryConductance 18 | leafwidth::LW | 0.05 | m | (0.0, 1.0) | "Mean width of leaves" 19 | end 20 | 21 | """ 22 | boundary_conductance_free(f::AbstractBoundaryConductance, v) 23 | 24 | Boundary layer conductance for heat - single sided, free convection, 25 | given variables `v`. 26 | 27 | Retuns conductance in `u"mol*m-2*s-1"`. """ 28 | boundary_conductance_free(f::BoundaryConductance, v) = 29 | cmolar(v) * 0.5DHEAT * (grashof_number(v.tleaf, v.tair, f.leafwidth)^(1/4)) / f.leafwidth 30 | 31 | """ 32 | boundary_conductance_forced(f::AbstractBoundaryConductance, v) 33 | 34 | Boundary layer conductance for heat - single sided, forced convection, 35 | given variables `v`. 36 | 37 | Retuns conductance in `u"mol*m-2*s-1"`. 38 | """ 39 | boundary_conductance_forced(f::BoundaryConductance, v) = 40 | # TODO name this 0.003 41 | 0.003m*s^-1 * sqrt(ustrip(u"s^-1", v.windspeed / f.leafwidth)) * cmolar(v) 42 | 43 | """ 44 | cmolar(pressure, airtemp) 45 | 46 | Convert from m.s-1 to mol.m-2.s-1 47 | """ 48 | cmolar(pressure, airtemp) = pressure / (R * K(airtemp)) 49 | cmolar(v) = cmolar(v.pressure, v.tair) 50 | 51 | """ 52 | grashof_number(tleaf, tair, leafwidth) 53 | 54 | Calculates the Grashof number given leaf temperature, 55 | air tempereature and leaf width. 56 | 57 | return: dimensionless 58 | 59 | See Leuning et al (1995) PCE 18:1183-1200 Eqn E4 60 | """ 61 | grashof_number(tleaf, tair, leafwidth) = 62 | 1.6e8m^-3*K^-1 * leafwidth^3 * abs(tleaf - tair) 63 | -------------------------------------------------------------------------------- /src/core/non_stomatal_soilmoisture.jl: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | Models for non-stomatal dependence of of vcmax and jmax on soil water 4 | potential. Calculated in [`non_stomatal_potential_dependence`](@ref). 5 | """ 6 | abstract type AbstractPotentialDependence end 7 | 8 | """ 9 | non_stomatal_potential_dependence(f::AbstractPotentialDependence, swp) 10 | 11 | Calculate dependence on soil water potential outside of stomatal effects. 12 | 13 | Returns a value between `0.0` and `1.0` 14 | """ 15 | function non_stomatal_potential_dependence end 16 | 17 | """ 18 | NoPotentialDependence() 19 | 20 | Parameterless model where soil moisture has no non-stomatal effects. 21 | 22 | Simply returns `1` 23 | """ 24 | struct NoPotentialDependence{} <: AbstractPotentialDependence end 25 | 26 | non_stomatal_potential_dependence(f::NoPotentialDependence, swp) = 1.0 27 | 28 | """ 29 | LinearPotentialDependence(vpara, vparb) 30 | 31 | Simple linear dependance. `vpara` is the swp where vcmax and 32 | jmax are set to zero, while at `vparb` fluxes are at their maximum rate. 33 | 34 | $(FIELDDOCTABLE) 35 | """ 36 | @columns struct LinearPotentialDependence{MPa} <: AbstractPotentialDependence 37 | vpara::MPa | -0.3 | MPa | (0.0, -2.0) | _ 38 | vparb::MPa | -0.1 | MPa | (0.0, -2.0) | _ 39 | end 40 | 41 | non_stomatal_potential_dependence(f::LinearPotentialDependence, swp) = 42 | if soilwaterpotential < f.vpara 43 | zero(oneunit(f.vpara) / oneunit(f.vpara)) 44 | elseif soilwaterpotential > f.vparb 45 | oneunit(f.vpara) / oneunit(f.vpara) 46 | else 47 | (soilwaterpotential - f.vpara) / (f.vparb - f.vpara) 48 | end 49 | 50 | """ 51 | ZhouPotentialDependence(s, ψ) 52 | 53 | Parameters following Zhou, et al. Agricultural and Forest Meteorology, 2013. 54 | 55 | $(FIELDDOCTABLE) 56 | """ 57 | @columns struct ZhouPotentialDependence{S,Ψ} <: AbstractPotentialDependence 58 | s::S | 2.0 | MPa^-1 | (0.4, 12.0) | "Sensitivity parameter indicating the steepness of the decline" 59 | ψ::Ψ | -1.0 | MPa | (-0.1, -4.0) | "The water potential at which f(Ψpd) decreases to half of its maximum value" 60 | end 61 | 62 | non_stomatal_potential_dependence(f::ZhouPotentialDependence, swp) = 63 | (1 + exp(f.s * f.ψ)) / (1 + exp(f.s * (f.ψ - swp))) 64 | -------------------------------------------------------------------------------- /test/shared.jl: -------------------------------------------------------------------------------- 1 | using Photosynthesis, Unitful, Test, Libdl, Flatten, Combinatorics 2 | 3 | using Unitful: °C, K 4 | 5 | const BALLBERRY_GS = 2 6 | const LEUNING_GS = 3 7 | const MEDLYN_GS = 4 8 | 9 | const BERNACCI = 0 10 | const BADGERCOLLATZ = 1 11 | 12 | const SOILMETHOD_NONE = 0 13 | const SOILMETHOD_EMAX = 1 14 | const SOILMETHOD_VOLUMETRIC = 2 15 | const SOILMETHOD_POTENTIAL = 2 16 | const SOILMETHOD_DEFICIT = 4 17 | 18 | const SOILDATA_POTENTIAL = 1 # SOIL MOISTURE DATA IS POTENTIAL (MPa) 19 | const SOILDATA_DEFICIT = 2 # SOIL MOISTURE DATA IS DEFICIT (DIMNLESS) 20 | const SOILDATA_CONTENT = 3 # SOIL MOISTURE IS CONTENT (m3 m-3), as when simulated. 21 | const SOILDATA_SIMULATED = 4 # 22 | const SOILDATA_NONE = 0 # NO SOIL MOISTURE DATA 23 | 24 | # Download and compile maespa and maepa if they're not present 25 | maespa_dir = joinpath(dirname(pathof(Photosynthesis)), "../test/maespa") 26 | maestra_dir = joinpath(dirname(pathof(Photosynthesis)), "../test/maestra") 27 | if !isdir(maespa_dir) 28 | run(`git clone --depth 1 https://github.com/rafaqz/maespa.git $maespa_dir`) 29 | run(`git clone --depth 1 https://github.com/rafaqz/Maestra.git $maestra_dir`) 30 | end 31 | 32 | if Sys.islinux() 33 | if !isfile(joinpath(maespa_dir, "physiol.so")) 34 | cd(maespa_dir) 35 | println("Compiling maespa...") 36 | run(`gfortran -ffree-line-length-200 -shared -O2 maestcom.f90 -o maestcom.so -fPIC`) 37 | run(`gfortran -ffree-line-length-200 -shared -O2 metcom.f90 -o metcom.so -fPIC`) 38 | run(`gfortran -shared -O2 physiol.f90 -o physiol.so -fPIC`) 39 | # cd(maestra_dir) 40 | # println("Compiling maestra...") 41 | # run(`gfortran -ffree-line-length-200 -shared -O2 maestcom.f90 -o maestcom.so -fPIC`) 42 | # run(`gfortran -ffree-line-length-200 -shared -O2 metcom.f90 -o metcom.so -fPIC`) 43 | # run(`gfortran -shared -O2 physiol.f90 -o physiol.so -fPIC`) 44 | end 45 | elseif Sys.isapple() 46 | if !isfile(joinpath(maestra_dir, "physiol.dylib")) 47 | cd(maespa_dir) 48 | println("Compiling maespa...") 49 | run(`gfortran -ffree-line-length-200 -shared -O2 maestcom.f90 -o maestcom.dylib -fpic`) 50 | run(`gfortran -ffree-line-length-200 -shared -O2 metcom.f90 -o metcom.dylib -fPIC`) 51 | run(`gfortran -shared -O2 physiol.f90 -o physiol.dylib -fPIC`) 52 | end 53 | else 54 | error("Only linux and apple can be used to test") 55 | end 56 | 57 | maespa_photosynlib = dlopen(joinpath(maespa_dir, "physiol")) 58 | # maestra_photosynlib = dlopen(joinpath(maestra_dir, "physiol")) 59 | -------------------------------------------------------------------------------- /src/biophysical/evapotranspiration.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Evapotranspiration models, define an [`evapotranspiration`](@ref) method. 3 | """ 4 | abstract type AbstractEvapotranspiration end 5 | 6 | """ 7 | evapotranspiration(m::AbstractEvapotranspiration, v) 8 | 9 | Calculate the rate of leaf evapotranspiration in `u"mol*m^-2*s^-1"` 10 | for some evapotranspiration model `m`, where `v` is a variables object. 11 | """ 12 | function evapotranspiration end 13 | 14 | """ 15 | PenmanMonteithEvapotranspiration() 16 | 17 | Calculates leaf evapotranspiration using the Penman-Monteith equation. 18 | """ 19 | struct PenmanMonteithEvapotranspiration <: AbstractEvapotranspiration end 20 | 21 | evapotranspiration(f::PenmanMonteithEvapotranspiration, v) = 22 | penman_monteith_evapotranspiration(v.pressure, v.slope, v.lhv, v.rnet, v.vpd, v.gh, v.gv) 23 | 24 | """ 25 | penman_monteith_evapotranspiration(pressure, slope, lhv, rnet, vpd, gh, gv) 26 | 27 | Calculates leaf evapotranspiration using the Penman-Monteith equation. 28 | 29 | Inputs: 30 | 31 | ρ atmospheric pressure, Pa 32 | Δ slope of VPD / T curve, Pa K-1 33 | lhv latent heat of water at air T, J mol-1 34 | Rn net radiation, J m-2 s-1 35 | Da vapour pressure deficit of air, Pa 36 | gh boundary layer conductance to heat (freeforcedradiative components), mol m-2 s-1 37 | gv conductance to water vapour (stomatalbdry layer components), mol*m^-2*s^-1 38 | 39 | Result in mol*m^-2*s^-1 40 | """ 41 | penman_monteith_evapotranspiration(ρa, Δ, lhv, Rn, Da, gh, gv) = 42 | if gv > zero(gv) 43 | γ = CPAIR * ρa * AIRMA / lhv 44 | (Δ * Rn + CPAIR * gh * Da * AIRMA) / (Δ + γ * gh / gv) / lhv 45 | else 46 | zero(Rn / lhv) 47 | end 48 | 49 | 50 | """ 51 | slope(tair) 52 | 53 | Calculate vapour pressure change with temperature, 54 | `slope` or `Δ` for the Penman-Monteith equation. 55 | 56 | Returns value in `u"Pa*K^-1"`. 57 | 58 | Expensive to calculate so should be precomputed separately to 59 | evapotranspiration where possible. 60 | """ 61 | vapour_pressure_slope(m::PenmanMonteithEvapotranspiration, v) = 62 | (saturated_vapour_pressure(v.tair + 0.1oneunit(v.tair)) - 63 | saturated_vapour_pressure(v.tair)) / 0.1oneunit(v.tair) 64 | 65 | """ 66 | saturated_vapour_pressure(tair) 67 | 68 | Calculate saturated water vapour pressure in `u"Pa"` 69 | at air temperature `tair` in `u"K"`. 70 | 71 | From Jones 1992 p 110 (note error in a - wrong units) 72 | 73 | TODO: name the magic numbers 74 | explain the °C multiplication: this is an empirical hack 75 | """ 76 | @inline saturated_vapour_pressure(tair) = begin 77 | T = ustrip(°C, tair) 78 | 613.75Pa * exp(17.502 * T / (240.97 + T)) 79 | end 80 | -------------------------------------------------------------------------------- /src/core/respiration.jl: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | Respiration models, run in a [`respiration`](@ref) method. 4 | """ 5 | abstract type AbstractRespiration end 6 | 7 | """ 8 | respiration(f::Union{Nothing,Respiration}, tleaf) 9 | 10 | Respiration for formulation `f` at temperature `tleaf` in `u"K"`. 11 | Returns respiration in `u"μmol*m^-2*s^-1"`. 12 | """ 13 | function respiration end 14 | 15 | @mix @columns struct MixinResp{pK,K,F,μMoM2S} 16 | # Field | Default | Unit | Bonds | Description 17 | q10f::pK | 0.67 | K^-1 | (0.0, 1.0) | "Logarithm of the Q10" 18 | dayresp::F | 1.0 | _ | (0.0, 1.0) | "Respiration in the light as fraction of that in the dark" 19 | rd0::μMoM2S | 0.001 | μmol*m^-2*s^-1 | (0.0, 0.1) | "Dark respiration at the reference temperature" 20 | tbelow::K | 173.15 | K | (250.0, 300.0) | "Temperature below which no respiration occurs" 21 | tref::K | 298.15 | K | (250.0, 350.0) | "Reference temperature at which rd0 was measured" 22 | end 23 | 24 | respiration(f::Nothing, tleaf) = 0.0μmol*m^-2*s^-1 25 | 26 | """ 27 | Respiration(q10f, dayresp, rd0, tbelow, tref) 28 | 29 | Standard respiration model. 30 | Calculates respiration from temperature using a Q10 (exponential) formulation 31 | 32 | TODO: specify origin of formulation 33 | 34 | $(FIELDDOCTABLE) 35 | """ 36 | @MixinResp struct Respiration{} <: AbstractRespiration end 37 | 38 | respiration(f::Respiration, tleaf) = 39 | if tleaf >= f.tbelow 40 | f.rd0 * exp(f.q10f * (tleaf - f.tref)) * f.dayresp 41 | else 42 | zero(f.rd0) 43 | end 44 | 45 | """ 46 | AcclimatizedRespiration(k10f, tmove, q10f, dayresp, rd0, tbelow, tref) 47 | 48 | Respiration with acclimatization parameters `k10f` and `tmove`. 49 | 50 | TODO test this. The formulation appears to need a variable `tmove`, 51 | not a parameter. 52 | 53 | $(FIELDDOCTABLE) 54 | """ 55 | @MixinResp struct AcclimatizedRespiration{pK,K} <: AbstractRespiration 56 | # Field | Default | Unit | Bonds | Description 57 | k10f::pK | 10.0 | K^-1 | (0.0, 10.0) | _ 58 | tmove::K | 0.0 | K | (0.0, 10.0) | _ 59 | end 60 | 61 | respiration(f::AcclimatizedRespiration, tleaf) = begin 62 | tleaf < f.tbelow && return zero(f.rd0) 63 | rd0acc = f.rd0 * exp(f.k10f * (f.tmove - f.tref)) 64 | rd0acc * exp(f.q10f * (tleaf - f.tref)) * f.dayresp 65 | end 66 | 67 | # dayresp(f) = f.dayresp 68 | 69 | # Make sure light suppression of dark respiration only occurs when it is light. 70 | # See Atkin et al. 1998 (or 2001?). From yplantmc 71 | # TODO: The cutoff should be a parameter 72 | # lightfrac = v.par < 100oneunit(v.par) ? oneunit(dayresp(f.formulation)) : dayresp(f.formulation) 73 | # respiration(f.formulation, v) * lightfrac 74 | # 75 | -------------------------------------------------------------------------------- /src/constants.jl: -------------------------------------------------------------------------------- 1 | const TOL = 0.005 # Tolerance for leaf temp iteration 2 | const GBHGBC = 1.32 # Ratio of Gbh:Gbc 3 | const GSVGSC = 1.57 # Ratio of Gsw:Gsc 4 | const GBVGBH = 1.075 # Ratio of Gbw:Gbh 5 | const OI = 205000μmol*mol^-1 # Oxygen partial pressure (umol mol-1) 6 | const CPAIR = 1010.0J*kg^-1*K^-1 # heat capacity of air (J kg-1 K-1) 7 | const AIRMA = 29.0g*mol^-1 # mol mass air (kg / mol) 8 | const CPAIR = 1010.0J*kg^-1*K^-1 # heat capacity of air (J kg-1 K-1) 9 | const EMLEAF = 0.95 # Emissivity of thermal radiation by leaf TODO: should not be constant, and is often higher 10 | const H2OLV0 = 2.501e6J*kg^-1 # latent heat H2O (J/kg) 11 | const H2OMW = 18.0e-3kg*mol^-1# mol mass H2O (kg/mol) 12 | const DHEAT = 21.5e-6m^2*s^-1 # molecular diffusivity for heat 13 | 14 | 15 | # Values of physical constants 16 | # const DEFWIND = 2.5m*s^-1 # Default wind speed (m s-1) 17 | # const UMOLPERJ = 4.57μmol*J^-1 # Conversion from J to umol quanta 18 | # const FPAR = 0.5 # Fraction of global radiation that is PAR 19 | # const ABSZERO = -273.15 # Absolute zero in degrees Celsius 20 | # const FREEZE = 273.15 # Zero degrees Celsius in Kelvin 21 | # const TAU = 0.76 # Transmissivity of atmosphere 22 | # const PID2 = pi / 2.0 # Pi divided by two 23 | # const PID180 = pi / 180.0 # Pi divided by 180 degrees 24 | # const AIRMA = 29.0g*mol^-1 # mol mass air (kg / mol) 25 | # const PATM = 1.0125e5Pa # atmospheric pressure - standard condns (Pa) 26 | # const CPH2O = 4.186e06J*kg^-1*K^-1 # heat capacity of water (J kg-1 K-1) 27 | # const CPQUARTZ = 1.942e06J*kg^-1*K^-1 # heat capacity of quartz (J kg-1 K-1) 28 | # const TCQUARTZ = 7.7W*m^-1*K^-1 # thermal conductivity of quartz (W m-1 K-1) 29 | # const TCH2O = 0.594W*m^-1*K^-1 # thermal conductivity of water (W m-1 K-1) 30 | # const TCORG = 0.25W*m^-1*K^-1 # thermal conductivity of organic matter (W m-1 K-1) 31 | # const EMLEAF = 0.95 # Emissivity of thermal radiation by leaf 32 | # const EMSOIL = 0.95 # Emissivity of thermal radiation by soil 33 | # const H2OLV0 = 2.501e6J*kg^-1 # latent heat H2O (J/kg) 34 | # const H2OMW = 18.0e-3kg*mol^-1# mol mass H2O (kg/mol) 35 | # const H2OVW = 18.05e-6m^3*mol^-1 # partial molal volume of water at 20C (m3 mol-1) 36 | # const RCONST = 8.314J*mol^-1*K^-1 # universal gas constant (J/mol/K) 37 | # const SIGMA = 5.67e-8W*m^-2*K^-4 # Steffan Boltzman constant (W/m2/K4) 38 | # const ALPHAQ = 0.425mol*mol^-1 # Quantum yield of RuBP regen (mol mol-1) 39 | # const SOLARC = 1370J*m^-2*s^-1 # Solar constant (J m-2 s-1) 40 | # const GCPERMOL = 12.0 # grams # per mol # 41 | # const CPERDW = 0.5 # fraction per DW 42 | # const VONKARMAN = 0.41 # von Karman"s constant 43 | # const EMLEAF = 0.95 # Emissivity of thermal radiation by leaf 44 | # const H2OLV0 = 2.501e6J*kg^-1 # latent heat H2O (J/kg) 45 | # const H2OMW = 18.0e-3kg*mol^-1# mol mass H2O (kg/mol) 46 | -------------------------------------------------------------------------------- /src/core/compensation.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Physiological constants for CO2 and rubisco compensation point 3 | formulations, calculated in the methods [`co2_compensation_point`](@ref) 4 | and [`rubisco_compensation_point`](@ref) respectively. 5 | """ 6 | abstract type Compensation end 7 | 8 | """ 9 | co2_compensation_point(formulation::Compensation, vars) 10 | 11 | Calculates Γ*, or the CO2 compensation point in the absence of 12 | non-photorespiratory respiration. 13 | """ 14 | function co2_compensation_point end 15 | 16 | """ 17 | rubisco_compensation_point(f::Compensation, tleaf) 18 | 19 | Calculates Km, or the effective Michaelis-Menten coefficient of Rubisco activity. 20 | """ 21 | function rubisco_compensation_point end 22 | 23 | rubisco_compensation_point(f::Compensation, tleaf) = begin 24 | Kc = arrhenius(f.Kc25, f.ΔHa_Kc, tleaf, f.tref) 25 | Ko = arrhenius(f.Ko25, f.ΔHa_Ko, tleaf, f.tref) 26 | Kc * (1 + OI / Ko) 27 | end 28 | 29 | 30 | """ 31 | BadgerCollatzCompensation(Kc25, Ko25, ΔHa_Kc, ΔHa_Ko, tref) 32 | 33 | Parameters to calculate co2 and rubisco compensation 34 | points using the Badger-Collatz formulation. 35 | 36 | $(FIELDDOCTABLE) 37 | """ 38 | @flattenable @columns struct BadgerCollatzCompensation{μMoMo,kJMo,K} <: Compensation 39 | # Field | Flat | Default | Units | Bounds | Description 40 | Kc25::μMoMo | false | 404.0 | μmol*mol^-1 | (0.0, 1000.0) | "MM coefft of Rubisco for CO2" 41 | Ko25::μMoMo | false | 248000.0 | μmol*mol^-1 | (0.0, 10000000.0) | "MM coefft of Rubisco for O2" 42 | ΔHa_Kc::kJMo | false | 59.4 | kJ*mol^-1 | (0.0, 200.0) | "Temp. response of Kc" 43 | ΔHa_Ko::kJMo | false | 36.0 | kJ*mol^-1 | (0.0, 200.0) | "Temp. response of Ko" 44 | tref::K | false | 298.15 | K | (250.0, 350.0) | "Temperature reference, usually 25.0° C" 45 | end 46 | 47 | co2_compensation_point(f::BadgerCollatzCompensation, tleaf) = begin 48 | # if tleaf < -1.0 calculate gamma for t = -1 (quadratic not applicable) 49 | tleaf = max(K(-1.0°C), tleaf) 50 | # TODO these numbers should be parameters or constants 51 | 36.9μmol*mol^-1 + 1.88μmol*mol^-1*K^-1 * 52 | (tleaf - f.tref) + 0.036μmol*mol^-1*K^-2 * (tleaf - f.tref)^2 53 | end 54 | 55 | 56 | """ 57 | BernacchiCompensation(Kc25, Ko25, Γ☆25, ΔHa_Kc, ΔHa_Ko, ΔHa_Γ☆, tref) 58 | 59 | Parameters to calculate CO2 and Rubisco compensation points using the 60 | formulation from Bernacchi et al 2001, PCE 24: 253-260. 61 | 62 | Note: Extra deactivation terms may be required above 40°C. 63 | 64 | $(FIELDDOCTABLE) 65 | """ 66 | @flattenable @columns struct BernacchiCompensation{μMoMo,kJMo,K} <: Compensation 67 | # Field | Flat | Default | Units | Bounds | Description 68 | Kc25::μMoMo | false | 404.9 | μmol*mol^-1 | (1.0, 1000.0) | "MM coefft of Rubisco for CO2" 69 | Ko25::μMoMo | false | 278400.0 | μmol*mol^-1 | (1.0, 10000000.0) | "MM coefft of Rubisco for O2" 70 | Γ☆25::μMoMo | false | 42.75 | μmol*mol^-1 | (1.0, 100.0) | _ 71 | ΔHa_Kc::kJMo | false | 79.43 | kJ*mol^-1 | (1.0, 100.0) | "Temp. response of Kc" 72 | ΔHa_Ko::kJMo | false | 36.38 | kJ*mol^-1 | (1.0, 100.0) | "Temp. response of Ko" 73 | ΔHa_Γ☆::kJMo | false | 37.83 | kJ*mol^-1 | (1.0, 100.0) | _ 74 | tref::K | false | 298.15 | K | (250.0, 350.0) | _ 75 | end 76 | 77 | co2_compensation_point(f::BernacchiCompensation, tleaf) = 78 | arrhenius(f.Γ☆25, f.ΔHa_Γ☆, tleaf, f.tref) 79 | -------------------------------------------------------------------------------- /src/formulations/fvcb.jl: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | Abstract supertype for all Farquhar/von Caemmerer/Berry derived photosynthesis models 4 | """ 5 | abstract type AbstractFvCBPhotosynthesis <: AbstractPhotosynthesis end 6 | 7 | flux_model(p::AbstractFvCBPhotosynthesis) = p.flux_model 8 | compensation_model(p::AbstractFvCBPhotosynthesis) = p.compensation_model 9 | rubisco_regen_model(p::AbstractFvCBPhotosynthesis) = p.rubisco_regen_model 10 | respiration_model(p::AbstractFvCBPhotosynthesis) = p.respiration_model 11 | stomatal_conductance_model(p::AbstractFvCBPhotosynthesis) = p.stomatal_conductance_model 12 | 13 | """ 14 | FvCBPhotosynthesis(flux, compensation, rubisco_regen, respiration, stomatal_conductance) 15 | 16 | General Farquhar von Caemmerer Berry model of photosynthesis. Organised as a modular 17 | components of electron flux, CO2 and rubusco compensation, rubisco regeneration, 18 | respiration and stomatal conductance. 19 | 20 | Calculates photosynthesis according to the ECOCRAFT 21 | agreed formulation of the Farquharvon Caemmerer (1982) equations. 22 | 23 | Farquhar, G.D., S. Caemmerer and J.A. Berry. 1980. 24 | A biochemical model of photosynthetic CO2 assimilation in leaves of C3 species. 25 | Planta. 149:78-90. 26 | 27 | $(FIELDDOCTABLE) 28 | """ 29 | @default_kw struct FvCBPhotosynthesis{F,KM,Ru,Re,GS} <: AbstractFvCBPhotosynthesis 30 | flux_model::F | Flux() 31 | compensation_model::KM | BernacchiCompensation() 32 | rubisco_regen_model::Ru | RubiscoRegen() 33 | respiration_model::Re | Respiration() 34 | stomatal_conductance_model::GS | BallBerryStomatalConductance() 35 | end 36 | 37 | check_extremes!(v, p::AbstractFvCBPhotosynthesis) = 38 | if v.jmax <= zero(v.jmax) || v.vcmax <= zero(v.vcmax) 39 | update_extremes!(v, stomatal_conductance_model(p)) 40 | true 41 | else 42 | false 43 | end 44 | 45 | update_extremes!(v, m::AbstractStomatalConductance) = begin 46 | v.aleaf = -v.rd 47 | v.gs = g0(m) 48 | end 49 | 50 | function photosynthesis!(v, m::AbstractFvCBPhotosynthesis) 51 | v.gammastar = co2_compensation_point(compensation_model(m), v.tleaf) # CO2 compensation point, umol mol-1 52 | v.km = rubisco_compensation_point(compensation_model(m), v.tleaf) # Michaelis-Menten for Rubisco, umol mol-1 53 | v.jmax, v.vcmax = flux(flux_model(m), v) # Potential electron transport rate and maximum Rubisco activity 54 | v.rd = respiration(respiration_model(m), v.tleaf) # Day leaf respiration, umol m-2 s-1 55 | v.vj = rubisco_regeneration(rubisco_regen_model(m), v) 56 | 57 | # Zero values and exit in extreme cases. 58 | check_extremes!(v, m) && return 59 | 60 | v.aleaf, v.gs = stomatal_conductance!(v, stomatal_conductance_model(m)) 61 | 62 | v.ci = if v.gs > zero(v.gs) && v.aleaf > zero(v.aleaf) 63 | v.cs - v.aleaf / v.gs 64 | else 65 | v.cs 66 | end 67 | return 68 | end 69 | 70 | """ 71 | @MixinEnviroVars 72 | 73 | Mixin variables for [`AbstractMaespaEnergyBalance`](@ref) variables objects. 74 | """ 75 | @mix @vars struct MixinFvCBVars{GSDA,KM,CI,GAM,GS,JMX,VCMX,RD,AC,AL,VJ,AJ,FS} 76 | # photosynthesis 77 | gs_div_a::GSDA | 0.0 | mol*μmol^-1 | _ 78 | km::KM | 0.0 | μmol*mol^-1 | _ 79 | ci::CI | 0.0 | μmol*mol^-1 | _ 80 | gammastar::GAM | 0.0 | μmol*mol^-1 | _ 81 | gs::GS | 0.0 | mol*m^-2*s^-1 | _ 82 | jmax::JMX | 0.0 | μmol*m^-2*s^-1 | _ 83 | vcmax::VCMX | 0.0 | μmol*m^-2*s^-1 | _ 84 | rd::RD | 0.0 | μmol*m^-2*s^-1 | _ 85 | ac::AC | 0.0 | μmol*m^-2*s^-1 | _ 86 | aleaf::AL | 0.0 | μmol*m^-2*s^-1 | _ 87 | vj::VJ | 0.0 | μmol*m^-2*s^-1 | _ 88 | aj::AJ | 0.0 | μmol*m^-2*s^-1 | _ 89 | # soil 90 | fsoil::FS | 0.0 | _ | _ 91 | end 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/formulations/emax.jl: -------------------------------------------------------------------------------- 1 | """ 2 | EmaxSoilMethod(soilmethod, non_stomatal) 3 | 4 | Emax implementation of soil method. 5 | 6 | $(FIELDDOCTABLE) 7 | """ 8 | @default_kw struct EmaxSoilMethod{T,NS} <: AbstractSoilMethod 9 | soilmethod::T | ConstantSoilMethod() 10 | non_stomatal::NS | ZhouPotentialDependence() 11 | end 12 | 13 | soilmoisture_conductance!(v, m::EmaxSoilMethod) = begin 14 | pd = non_stomatal_potential_dependence(m.non_stomatal, v.swp) 15 | v.vcmax *= pd 16 | v.jmax *= pd 17 | oneunit(v.fsoil) 18 | end 19 | 20 | """ 21 | EmaxStomatalConductance(BallBerryStomatalConductance(gsshape, g0, gs_submodel, soil_model) 22 | 23 | The same options are available for specialised stomatal conducance, 24 | but using emax soil water methods. 25 | 26 | WARNING: Currently not passing tests 27 | 28 | $(FIELDDOCTABLE) 29 | """ 30 | @MixinBallBerryStomatalConductance struct EmaxStomatalConductance{SH} <: AbstractBallBerryStomatalConductance 31 | gsshape::SH | HardMinimum() | _ | _ | _ 32 | end 33 | 34 | gsshape(m::EmaxStomatalConductance) = m.gsshape 35 | 36 | struct JarvisMode <: AbstractJarvisStomatalConductance end 37 | 38 | function stomatal_conductance!(v, m::EmaxStomatalConductance) 39 | v.gs_div_a = gs_div_a(gs_submodel(m), v) 40 | 41 | # Maximum transpiration rate 42 | emaxleaf = v.ktot * (v.swp - v.minleafwp) 43 | 44 | # Leaf transpiration: ignoring boundary layer effects 45 | etest = (v.vpdleaf / v.pressure) * v.gs * GSVGSC 46 | 47 | # Leaf water potential 48 | v.psil = v.swp - etest / v.ktot 49 | 50 | if etest > emaxleaf 51 | # Just for output 52 | v.fsoil = emaxleaf / etest 53 | 54 | gsv = emaxleaf / (v.vpdleaf / v.pressure) 55 | v.gs = gsv / GSVGSC 56 | 57 | # Minimum leaf water potential reached, recalculate psil 58 | v.psil = v.swp - emaxleaf / v.ktot 59 | 60 | v.ac = rubisco_limited_rate(JarvisMode(), v) 61 | v.aj = transport_limited_rate(JarvisMode(), v) 62 | v.aleaf = min(v.ac, v.aj) - v.rd 63 | 64 | v.gs = shape_gs(gsshape(m), v, m) 65 | end 66 | 67 | v.aleaf, v.gs 68 | end 69 | 70 | 71 | """ 72 | EmaxVars() 73 | 74 | Varbles for Emax models 75 | 76 | $(FIELDDOCTABLE) 77 | """ 78 | @MixinEnviroVars @MixinMaespaVars @MixinFvCBVars mutable struct EmaxVars{MLWP,EML,KT,PSIL} 79 | minleafwp::MLWP | 1e-4 | MPa | _ 80 | emaxleaf::EML | 400.0 | mmol*m^-2*s^-1 | _ 81 | ktot::KT | 2.0 | mmol*m^-2*s^-1*MPa^-1 | _ 82 | psil::PSIL | -0.111 | MPa | _ 83 | end 84 | 85 | 86 | """ 87 | EmaxEnergyBalance(energy_balance_model, totsoilres, plantk) 88 | 89 | Wrapper to MaespaEnergyBalance model, adding `totsoilres` and `plantk` parameters. 90 | 91 | $(FIELDDOCTABLE) 92 | """ 93 | @columns struct EmaxEnergyBalance{EB,SR,PK} <: AbstractMaespaEnergyBalance 94 | energy_balance_model::EB | MaespaEnergyBalance(photosynthesis=FvCBPhotosynthesis( 95 | stomatal_conductance=EmaxStomatalConductance( 96 | soilmethod=EmaxSoilMethod()))) | _ | _ | _ 97 | totsoilres::SR | 0.5 | m^2*s^1*MPa^1*mmol^-1 | (0.0, 10.0) | _ 98 | plantk::PK | 3.0 | mmol*m^-2*s^-1*MPa^-1 | (0.0, 10.0) | _ 99 | end 100 | 101 | energy_balance_model(m::EmaxEnergyBalance) = m.energy_balance_model 102 | totsoilres(m::EmaxEnergyBalance) = m.totsoilres 103 | plantk(m::EmaxEnergyBalance) = m.plantk 104 | 105 | enbal!(v, m::EmaxEnergyBalance) = begin 106 | v.ktot = 1 / (totsoilres(m) + 1.0 / plantk(m)) 107 | enbal!(v, energy_balance_model(m)) 108 | 109 | #= MAESPA comments: 110 | Return re-calculated leaf water potential (using ET without boundary layer conductance). 111 | We use etest otherwise psil < psilmin quite frequently when soil is dry. 112 | This is difficult to interpret, especially because PHOTOSYN does not account 113 | for boundary layer conductance. 114 | =# 115 | etest = (v.vpd / v.pressure) * v.gsv 116 | v.psil = v.swp - (etest / v.ktot) 117 | 118 | @show etest 119 | @show v.swp 120 | @show v.pressure 121 | @show v.vpd 122 | @show v.gsv 123 | @show v.psil 124 | return 125 | end 126 | -------------------------------------------------------------------------------- /src/formulations/tuzet.jl: -------------------------------------------------------------------------------- 1 | 2 | # Tuzet implementation of stomatal conductance --------------------------------------- 3 | 4 | """ 5 | TuzetStomatalConductanceSubModel(gamma, g1) 6 | 7 | Tuzet stomatal conductance formulation parameters. (modelgs = 5 in maestra) 8 | 9 | $(FIELDDOCTABLE) 10 | """ 11 | @MixinBallBerryStomatalConductanceSubModel struct TuzetStomatalConductanceSubModel{} <: AbstractStomatalConductanceSubModel end 12 | 13 | gs_div_a(f::TuzetStomatalConductanceSubModel, v) = 14 | (f.g1 / (v.cs - f.gamma)) * fpsil(v.psilin, v.sf, v.psiv) 15 | 16 | """ 17 | fpsil(psil, sf, psiv) 18 | 19 | Tuzet et al. 2003 leaf water potential function 20 | """ 21 | fpsil(psil, sf, psiv) = (1 + exp(sf * psiv)) / (1 + exp(sf * (psiv - psil))) 22 | 23 | # Tuzet mplementation of soil method 24 | 25 | @default_kw struct TuzetSoilMethod{T,NS} <: AbstractSoilMethod 26 | soilmethod::T | ConstantSoilMethod() 27 | potentialdependence_model::NS | ZhouPotentialDependence() 28 | end 29 | 30 | # TODO: Fix this 31 | soilmoisture_conductance!(v, f::TuzetSoilMethod) = begin 32 | non_stomatal_potential_dependence(f.potentialdependence_model, v.weightedswp) 33 | end 34 | 35 | """ 36 | TuzetStomatalConductance(gs_submodel, soilmethod) 37 | 38 | Tuzet stomatal conductance model. 39 | 40 | This model is limited to using `TuzetStomatalConductance` and `TuzetSoilMethods`. 41 | 42 | The internal methods are tested, but the zero-finding loop is not. 43 | Assume this is broken. 44 | 45 | $(FIELDDOCTABLE) 46 | """ 47 | @MixinBallBerryStomatalConductance struct TuzetStomatalConductance{} <: AbstractBallBerryStomatalConductance end 48 | 49 | # Change defaults for gs_submodel and soilmethod 50 | @default TuzetStomatalConductance begin 51 | gs_submodel | TuzetStomatalConductanceSubModel() 52 | soilmethod | TuzetSoilMethod() 53 | end 54 | 55 | """ 56 | TuzetVars() 57 | 58 | Variables object for Tuzet models. 59 | 60 | $(FIELDDOCTABLE) 61 | """ 62 | @MixinEnviroVars @MixinMaespaVars @MixinFvCBVars mutable struct TuzetVars{KT,PS,PSi,PV,SF} 63 | # Field | Default | Units | Description 64 | ktot::KT | 2.0 | mmol*m^-2*s^-1*MPa^-1 | _ 65 | psil::PS | -0.0 | MPa | _ 66 | psilin::PSi | 0.0 | MPa | _ 67 | psiv::PV | -0.0019 | MPa | _ 68 | sf::SF | 0.0032 | MPa^-1 | _ 69 | end 70 | 71 | 72 | # Tuzet implementation of energy balance --------------------------------------------- 73 | 74 | """ 75 | TuzetEnergyBalance(energy_balance) 76 | TuzetEnergyBalance(; energy_balance=MaespaEnergyBalance( 77 | photosynthesis=FvCBPhotosynthesis(stomatal_conductance=TuzetStomatalConductance)) 78 | 79 | Tuzet implementation of energy balance. 80 | 81 | Essentially a wrapper that runs a standard [`MaespaEnergyBalance`](@ref) in a root-finder, 82 | with the Tuzet stomatal conductance formulation and soil moisture routines. 83 | 84 | $(FIELDDOCTABLE) 85 | """ 86 | @default_kw struct TuzetEnergyBalance{EB} <: AbstractMaespaEnergyBalance 87 | energy_balance_model::EB | MaespaEnergyBalance(photosynthesis=FvCBPhotosynthesis(stomatal_conductance=TuzetStomatalConductance)) 88 | end 89 | 90 | energy_balance_model(m::TuzetEnergyBalance) = m.energy_balance_model 91 | 92 | """ 93 | enbal!(v, pm::TuzetEnergyBalance) 94 | 95 | Runs energy balance inside a root finding algorithm to calculate leaf water potential. 96 | """ 97 | function enbal!(v, m::TuzetEnergyBalance) 98 | v.psilin = -0.1oneunit(v.psilin) 99 | v.psil = -0.1oneunit(v.psil) 100 | bracket = -100.0oneunit(v.psil), zero(v.psil) 101 | tolz = 1e-03oneunit(v.psil) 102 | 103 | findzero(psilin -> leaf_water_potential_finder!(m, v, psilin), bracket; atol=tolz) 104 | nothing 105 | end 106 | 107 | """ 108 | leaf_water_potential_finder(p, v, psilin) 109 | 110 | PSIL finder. A wrapper function for the energy balance that returns the squared 111 | difference in PSILIN and PSIL, for use in a root finder. 112 | """ 113 | function leaf_water_potential_finder!(m, v, psilin) 114 | v.ktot = 1 / (totsoilres(m) + 1.0 / plantk(m)) 115 | v.psilin = psilin 116 | v.ci = zero(v.ci) 117 | enbal!(v, energy_balance_model(m)) 118 | 119 | #= MAESPA comments: 120 | Return re-calculated leaf water potential (using ET without boundary layer conductance). 121 | We use etest otherwise psil < psilmin quite frequently when soil is dry. 122 | This is difficult to interpret, especially because PHOTOSYN does not account 123 | for boundary layer conductance. 124 | =# 125 | etest = (v.vpd / v.pressure) * v.gsv 126 | v.psil = v.swp - etest / v.ktot 127 | 128 | v.psilin - v.psil 129 | end 130 | -------------------------------------------------------------------------------- /test/maespa_environment.jl: -------------------------------------------------------------------------------- 1 | using Photosynthesis 2 | 3 | include(joinpath(dirname(pathof(Photosynthesis)), "../test/shared.jl")) 4 | 5 | @testset "boundary_conductance_forced/GBHFORCED" begin 6 | gbhforced_fortran = Libdl.dlsym(maespa_photosynlib, :gbhforced_) 7 | v = BallBerryVars() 8 | for tair in (-50.0, 0.0, 15.0, 28.0, 40.0, 60.0, 100.0), 9 | press in (80000.0, 101250.0, 150000.0), 10 | wind in (0.1, 1.0, 10.0), 11 | wleaf in (0.01, 0.05, 0.1, 1.0) 12 | v.windspeed = wind * u"m*s^-1" 13 | v.tair = tair * u"°C" 14 | v.pressure = press * u"Pa" 15 | bc_model = BoundaryConductance(wleaf * u"m") 16 | bcforced_ref = ccall(gbhforced_fortran, Float32, 17 | (Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}), 18 | tair, press, wind, wleaf) 19 | bcforced = boundary_conductance_forced(bc_model, v) 20 | upreferred(bcforced) 21 | @test ustrip(u"mol*m^-2*s^-1", bcforced) ≈ bcforced_ref 22 | end 23 | end 24 | 25 | @testset "boundary_conductance_free/GBHFREE" begin 26 | v = BallBerryVars() 27 | gbhfree_fortran = Libdl.dlsym(maespa_photosynlib, :gbhfree_) 28 | for tleaf in (-50.0, 0.0, 15.0, 28.0, 40.0, 60.0, 100.0), 29 | tair in (-50.0, 0.0, 15.0, 28.0, 40.0, 60.0, 100.0), 30 | press in (80000.0, 101250.0, 150000.0), 31 | wleaf in (0.01, 0.05, 0.1, 1.0) 32 | v.tleaf = tleaf * °C |> K 33 | v.tair = tair * °C |> K 34 | v.pressure = press * u"Pa" 35 | bc_model = BoundaryConductance(wleaf * u"m") 36 | wleaf = ustrip(u"m", bc_model.leafwidth) 37 | bcfree_ref = ccall(gbhfree_fortran, Float32, 38 | (Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}), 39 | tair, tleaf, press, wleaf) 40 | bcfree = boundary_conductance_free(bc_model, v) 41 | @test ustrip(u"mol*m^-2*s^-1", bcfree) ≈ bcfree_ref 42 | end 43 | end 44 | 45 | @testset "radiation_conductance/GRADIATION" begin 46 | v = BallBerryVars() 47 | for tair in (-50.0, 0.0, 15.0, 28.0, 40.0, 60.0, 100.0) 48 | rc_model = WangRadiationConductance() 49 | gradiation_fortran = Libdl.dlsym(maespa_photosynlib, :gradiation_) 50 | tair = ustrip(°C, v.tair) 51 | rdfipt = rc_model.rdfipt 52 | tuipt = rc_model.tuipt 53 | tdipt = rc_model.tdipt 54 | rc_ref = ccall(gradiation_fortran, Float32, 55 | (Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}), 56 | tair, rdfipt, tuipt, tdipt) 57 | rc = radiation_conductance(rc_model, v) 58 | @test ustrip(u"mol*m^-2*s^-1", rc) ≈ rc_ref 59 | end 60 | end 61 | 62 | @testset "saturated_vapour_pressure/SATUR" begin 63 | for tair in (-50.0, 0.0, 15.0, 28.0, 40.0, 100.0) 64 | satur_fortran = Libdl.dlsym(maespa_photosynlib, :satur_) 65 | svp_ref = ccall(satur_fortran, Float32, (Ref{Float32},), tair) 66 | svp = saturated_vapour_pressure(tair * °C) 67 | @test ustrip(u"Pa", svp) ≈ svp_ref 68 | end 69 | end 70 | 71 | @testset "latent_heat_water_vapour" begin 72 | @test ustrip(u"kJ/mol", latent_heat_water_vapour(40°C)) ≈ 43.35 rtol=1e-3 73 | @test ustrip(u"kJ/mol", latent_heat_water_vapour(25°C)) ≈ 43.99 rtol=1e-3 74 | @test ustrip(u"kJ/mol", latent_heat_water_vapour(0°C)) ≈ 45.06 rtol=1e-3 75 | end 76 | 77 | @testset "PenmanMonteithEvapotranspiration/PENMON" begin 78 | for press in (80000.0, 101250.0, 150000.0), 79 | vpd in (10.0, 100.0, 1000.0, 10000.0), 80 | lhv in (1000.0, 10000.0, 50000.0), 81 | gv in (0.0, 1.0, 10), 82 | gh in (0.0, 1.0, 10), 83 | rnet in (0.0, 10.0, 1000.0), 84 | slope in (10.0, 100.0, 1.000) 85 | penmon_fortran = Libdl.dlsym(maespa_photosynlib, :penmon_) 86 | p = MaespaEnergyBalance( 87 | photosynthesis_model=FvCBPhotosynthesis( 88 | stomatal_conductance_model=BallBerryStomatalConductance( 89 | gs_submodel=MedlynStomatalConductanceSubModel(), 90 | soil_model=NoSoilMethod()), 91 | flux_model=DukeFlux(), 92 | compensation_model=BernacchiCompensation(), 93 | respiration_model=Respiration(), 94 | ), 95 | evapotranspiration_model = PenmanMonteithEvapotranspiration(), 96 | max_iterations=0 97 | ) 98 | # Prime the variables to reasonable values 99 | v = BallBerryVars() 100 | et_model = p.evapotranspiration_model 101 | enbal!(v, p) 102 | v.pressure = press * u"Pa" 103 | v.rnet = rnet * u"J*m^-2*s^-1" 104 | v.vpd = vpd * u"Pa" 105 | v.gv = gv * u"mol*m^-2*s^-1" 106 | v.gh = gh * u"mol*m^-2*s^-1" 107 | v.lhv = lhv * u"J*mol^-1" 108 | v.slope = slope * u"Pa*K^-1" 109 | et_ref = ccall( 110 | penmon_fortran, Float32, 111 | (Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}), 112 | press, slope, lhv, rnet, vpd, gh, gv 113 | ) 114 | et = evapotranspiration(et_model, v) 115 | @test ustrip(u"kg*mol*J^-1*s^-3", et) ≈ et_ref 116 | end 117 | end 118 | 119 | -------------------------------------------------------------------------------- /src/core/stomatal_soilmoisture.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Abstract supertype for component to specify the source data for soil moisture. 3 | """ 4 | abstract type AbstractSoilData end 5 | 6 | """ 7 | Soil water data is measure as moisture-deficit. 8 | """ 9 | abstract type AbstractDeficitSoilData <: AbstractSoilData end 10 | 11 | " @Deficit mixin macro adds water deficit fields to a struct" 12 | @mix @default_kw struct MixinDeficit{T} 13 | smd1::T | 1.0 14 | smd2::T | 1.0 15 | end 16 | 17 | 18 | """ 19 | Soil water data is measure by volumetric content. 20 | """ 21 | abstract type AbstractContentSoilData <: AbstractDeficitSoilData end 22 | 23 | "@Content mixin macro adds water content fields to a struct" 24 | @MixinDeficit @mix struct MixinContent{W} 25 | swmin::W | 0.0 26 | swmax::W | 1.0 27 | end 28 | 29 | """ 30 | DeficitSoilData(smd1, smd2) 31 | 32 | Soil water is measured as deficit 33 | 34 | $(FIELDDOCTABLE) 35 | """ 36 | @MixinDeficit struct DeficitSoilData{} <: AbstractDeficitSoilData end 37 | 38 | """ 39 | ContentSoilData(swmin, swmax, smd1, smd2) 40 | 41 | Soil water is measured as volumetric content 42 | 43 | $(FIELDDOCTABLE) 44 | """ 45 | @MixinContent struct ContentSoilData{} <: AbstractContentSoilData end 46 | 47 | """ 48 | SimulatedSoilData(swmin, swmax, smd1, smd2) 49 | 50 | Simulated soil volumetric content. 51 | 52 | $(FIELDDOCTABLE) 53 | """ 54 | @MixinContent struct SimulatedSoilData{} <: AbstractContentSoilData end 55 | 56 | 57 | 58 | """ 59 | Methods for calculating the effect of soil moisture on stomatal conductance. 60 | """ 61 | abstract type AbstractSoilMethod end 62 | 63 | """ 64 | soilmoisture_conductance(soilmethod::AbstractSoilMethod, vars) 65 | 66 | Calculate the effect of soil water content on stomatal conductance 67 | 68 | These methods dispatch on AbstractSoilMethod or AbstractSoilData types. 69 | """ 70 | function soilmoisture_conductance end 71 | 72 | 73 | """ 74 | VolumetricSoilMethod(soildata, wc1, wc2, soilroot, soildepth) 75 | 76 | Soil method where soil water is handled volumetrically. 77 | 78 | $(FIELDDOCTABLE) 79 | """ 80 | @default_kw struct VolumetricSoilMethod{WC,R,D,T<:AbstractContentSoilData} <: AbstractSoilMethod 81 | soildata::T | ContentSoilData() 82 | wc1::WC | 0.5 83 | wc2::WC | 0.5 84 | soilroot::R | 0.5 85 | soildepth::D | 0.5 86 | end 87 | 88 | soilmoisture_conductance(f::VolumetricSoilMethod{<:SimulatedSoilData}, v) = 89 | v.soilmoist = f.soilroot / f.soildepth # TODO fix this, where is fsoil set?? 90 | 91 | soilmoisture_conductance(f::VolumetricSoilMethod{<:AbstractContentSoilData}, v) = begin 92 | # TODO: check in constructor f.wc1 > f.wc2 && error("error: wc1 needs to be smaller than wc2.") 93 | fsoil = -f.wc1 / (f.wc2 - f.wc1) + v.soilmoist / (f.wc2 - f.wc1) 94 | max(min(fsoil, 1.0), 0.0) 95 | end 96 | 97 | 98 | """ 99 | NoSoilMethod() 100 | 101 | No soil method is used, soil moisture has no effect. 102 | """ 103 | struct NoSoilMethod <: AbstractSoilMethod end 104 | 105 | soilmoisture_conductance(f::NoSoilMethod, v) = 1.0 106 | 107 | """ 108 | Soil method where soil water is held constant. 109 | """ 110 | struct ConstantSoilMethod <: AbstractSoilMethod end 111 | 112 | """ 113 | PotentialSoilMethod(soildata, swpexp) 114 | 115 | Soil mmodel where soil water is measured as water potential. 116 | 117 | $(FIELDDOCTABLE) 118 | """ 119 | @udefault_kw @units @bounds @description struct PotentialSoilMethod{E} <: AbstractSoilMethod 120 | swpexp::E | 0.5 | MPa^-1 | (0.0, 10.0) | "Exponent for soil water-potential response of stomata" 121 | end 122 | 123 | soilmoisture_conductance(f::PotentialSoilMethod, v) = exp(f.swpexp * v.swp) 124 | 125 | 126 | """ 127 | DeficitSoilMethod(soildata) 128 | 129 | Soil method where soil water is handled as a moisture deficit. 130 | 131 | $(FIELDDOCTABLE) 132 | """ 133 | @default_kw struct DeficitSoilMethod{T<:AbstractDeficitSoilData} <: AbstractSoilMethod 134 | soildata::T | ContentSoilData() 135 | end 136 | 137 | "Convert volumetric data to deficit" 138 | soilmoisture_conductance(f::DeficitSoilMethod{<:AbstractContentSoilData}, v) = begin 139 | s = f.soildata 140 | soilmoist = (s.swmax - v.soilmoist) / (s.swmax - s.swmin) 141 | soilmoisture_conductance(s, soilmoist, v) 142 | end 143 | 144 | soilmoisture_conductance(f::DeficitSoilMethod{<:DeficitSoilData}, v) = 145 | soilmoisture_conductance(f.soildata, v.soilmoist, v) 146 | 147 | """ 148 | soilmoisture_conductance(soil::AbstractDeficitSoilData, soilmoist, v) 149 | 150 | GranierLoustau 1994 Fs = 1 - a exp(b SMD) where SMD is soil 151 | moisture deficit, dimless 152 | 153 | TODO: move smd1/smd2 fields to SoilMethod 154 | """ 155 | soilmoisture_conductance(soil::AbstractDeficitSoilData, soilmoist, v) = begin 156 | effect = 1.0 157 | 158 | # Exponential relationship with deficit: params SMD1, SMD2 159 | if soil.smd1 > zero(soil.smd1) 160 | effect = 1.0 - soil.smd1 / oneunit(soil.smd1) * exp(soil.smd2 * soilmoist / oneunit(soilmoist)^2) 161 | # Linear decline with increasing deficit: pUT SMD1 < 0, param SMD2 162 | elseif soil.smd2 > zero(soil.smd2) 163 | if oneunit(soilmoist) - soilmoist < soil.smd2 164 | effect = (oneunit(soilmoist) - soilmoist) / soil.smd2 165 | end 166 | end 167 | return max(effect, zero(effect)) 168 | end 169 | -------------------------------------------------------------------------------- /docs/src/index.md: -------------------------------------------------------------------------------- 1 | 2 | ```@docs 3 | Photosynthesis 4 | ``` 5 | 6 | 7 | ## Energy balance model 8 | 9 | ```@docs 10 | Photosynthesis.AbstractEnergyBalance 11 | Photosynthesis.AbstractMaespaEnergyBalance 12 | MaespaEnergyBalance 13 | enbal! 14 | Photosynthesis.enbal_init! 15 | ``` 16 | 17 | ## Biophysical components 18 | 19 | ```@docs 20 | Photosynthesis.saturated_vapour_pressure 21 | Photosynthesis.vapour_pressure_deficit 22 | Photosynthesis.latent_heat_water_vapour 23 | Photosynthesis.arrhenius 24 | Photosynthesis.leaftemp 25 | ``` 26 | 27 | ### Boundary conductance 28 | 29 | ```@docs 30 | AbstractBoundaryConductance 31 | BoundaryConductance 32 | Photosynthesis.boundary_conductance_free 33 | Photosynthesis.boundary_conductance_forced 34 | Photosynthesis.grashof_number 35 | Photosynthesis.cmolar 36 | ``` 37 | 38 | ### Canopy-atmosphere decoupling 39 | 40 | ```@docs 41 | AbstractDecoupling 42 | McNaughtonJarvisDecoupling 43 | NoDecoupling 44 | Photosynthesis.decoupling 45 | ``` 46 | 47 | ### Evapotranspiration 48 | 49 | ```@docs 50 | AbstractEvapotranspiration 51 | PenmanMonteithEvapotranspiration 52 | Photosynthesis.evapotranspiration 53 | Photosynthesis.vapour_pressure_slope 54 | Photosynthesis.penman_monteith_evapotranspiration 55 | ``` 56 | 57 | ### Radiation conductance 58 | 59 | ```@docs 60 | AbstractRadiationConductance 61 | WangRadiationConductance 62 | radiation_conductance 63 | wang_radiation_conductance 64 | ``` 65 | 66 | ## Photosynthesis model 67 | 68 | ```@docs 69 | AbstractPhotosynthesis 70 | AbstractFvCBPhotosynthesis 71 | FvCBPhotosynthesis 72 | photosynthesis! 73 | ``` 74 | 75 | ### CO2 and Rubisco Compensation 76 | 77 | ```@docs 78 | Compensation 79 | BadgerCollatzCompensation 80 | BernacchiCompensation 81 | Photosynthesis.co2_compensation_point 82 | Photosynthesis.rubisco_compensation_point 83 | ``` 84 | 85 | ### Flux 86 | 87 | ```@docs 88 | AbstractFlux 89 | Flux 90 | DukeFlux 91 | PotentialModifiedFlux 92 | Photosynthesis.flux 93 | ``` 94 | 95 | #### Electron transport rate 96 | 97 | ```@docs 98 | AbstractJmax 99 | Jmax 100 | Photosynthesis.max_electron_transport_rate 101 | ``` 102 | 103 | ### Rubisco activity 104 | 105 | ```@docs 106 | AbstractVcmax 107 | OptimumVcmax 108 | NoOptimumVcmax 109 | Photosynthesis.max_rubisco_activity 110 | ``` 111 | 112 | ### Rubisco regeneration 113 | 114 | ```@docs 115 | AbstractRubiscoRegen 116 | RubiscoRegen 117 | Photosynthesis.rubisco_regeneration 118 | ``` 119 | 120 | ### Respiration 121 | 122 | ```@docs 123 | AbstractRespiration 124 | Respiration 125 | AcclimatizedRespiration 126 | Photosynthesis.respiration 127 | ``` 128 | 129 | ### Non-stomatal soil water-potential dependence 130 | 131 | ```@docs 132 | AbstractPotentialDependence 133 | LinearPotentialDependence 134 | ZhouPotentialDependence 135 | NoPotentialDependence 136 | Photosynthesis.non_stomatal_potential_dependence 137 | ``` 138 | 139 | ## Stomatal conductance models 140 | 141 | ```@docs 142 | AbstractStomatalConductance 143 | AbstractBallBerryStomatalConductance 144 | BallBerryStomatalConductance 145 | BallBerryVars 146 | Photosynthesis.stomatal_conductance! 147 | Photosynthesis.rubisco_limited_rate 148 | Photosynthesis.transport_limited_rate 149 | Photosynthesis.gs_init! 150 | Photosynthesis.gs_update! 151 | Photosynthesis.check_extremes! 152 | Photosynthesis.update_extremes! 153 | ``` 154 | 155 | ### Stomatal conductance sub-models 156 | 157 | ```@docs 158 | AbstractStomatalConductanceSubModel 159 | AbstractBallBerryStomatalConductanceSubModel 160 | BallBerryStomatalConductanceSubModel 161 | LeuningStomatalConductanceSubModel 162 | MedlynStomatalConductanceSubModel 163 | Photosynthesis.gs_div_a 164 | ``` 165 | 166 | ### Stomatal conductance shape 167 | 168 | ```@docs 169 | StomatalConductanceShape 170 | HardMinimum 171 | HyperbolicMinimum 172 | Photosynthesis.shape_gs 173 | ``` 174 | 175 | 176 | ### Stomatal soil water dependence 177 | 178 | ```@docs 179 | AbstractSoilMethod 180 | VolumetricSoilMethod 181 | ConstantSoilMethod 182 | DeficitSoilMethod 183 | PotentialSoilMethod 184 | NoSoilMethod 185 | Photosynthesis.soilmoisture_conductance 186 | ``` 187 | 188 | #### Soil Data 189 | 190 | Kinds of soil water data used in [`DeficitSoilMethod`](@ref) and 191 | [`VolumetricSoilMethod`](@ref). 192 | 193 | ```@docs 194 | AbstractSoilData 195 | AbstractDeficitSoilData 196 | AbstractContentSoilData 197 | DeficitSoilData 198 | ContentSoilData 199 | SimulatedSoilData 200 | ``` 201 | 202 | ## EMAX Model 203 | 204 | ```@docs 205 | EmaxEnergyBalance 206 | EmaxStomatalConductance 207 | EmaxSoilMethod 208 | EmaxVars 209 | ``` 210 | 211 | ## Tuzet Model 212 | 213 | ```@docs 214 | TuzetEnergyBalance 215 | TuzetStomatalConductance 216 | TuzetStomatalConductanceSubModel 217 | TuzetVars 218 | Photosynthesis.leaf_water_potential_finder! 219 | ``` 220 | 221 | ## Jarvis Model 222 | 223 | ```@docs 224 | JarvisStomatalConductance 225 | Photosynthesis.factor_conductance 226 | AbstractJarvisLight 227 | JarvisLight 228 | Photosynthesis.light_factor 229 | 230 | AbstractJarvisTemp 231 | JarvisNoTemp 232 | JarvisTemp1 233 | JarvisTemp2 234 | Photosynthesis.temp_factor 235 | 236 | AbstractJarvisCO2 237 | JarvisNoCO2 238 | JarvisLinearCO2 239 | JarvisNonlinearCO2 240 | Photosynthesis.co2_factor 241 | 242 | AbstractJarvisVPD 243 | JarvisHyperbolicVPD 244 | JarvisLohammerVPD 245 | JarvisFractionDeficitVPD 246 | JarvisLinearDeclineVPD 247 | Photosynthesis.vpd_factor 248 | ``` 249 | -------------------------------------------------------------------------------- /src/Photosynthesis.jl: -------------------------------------------------------------------------------- 1 | module Photosynthesis 2 | @doc let 3 | path = joinpath(dirname(@__DIR__), "README.md") 4 | include_dependency(path) 5 | read(path, String) 6 | end Photosynthesis 7 | 8 | using DocStringExtensions, 9 | FieldDefaults, 10 | FieldDocTables, 11 | FieldMetadata, 12 | Mixers, 13 | SimpleRoots, 14 | Unitful 15 | 16 | using Unitful: R, °C, K, Pa, kPa, MPa, J, W, kJ, kg, g, m, s, mol, mmol, μmol, σ 17 | 18 | import FieldMetadata: @flattenable, @bounds, @default, @description, @units, 19 | flattenable, bounds, default, description, units 20 | 21 | @metadata units NoUnits 22 | 23 | 24 | export enbal!, 25 | photosynthesis!, 26 | stomatal_conductance!, 27 | soil_water_conductance!, 28 | co2_compensation_point, 29 | rubisco_compensation_point, 30 | rubisco_regeneration, 31 | rubisco_limited_rate, 32 | transport_limited_rate, 33 | slope, 34 | max_electron_transport_rate, 35 | max_rubisco_activity, 36 | decoupling, 37 | leaftemp, 38 | cmolar, 39 | shape_gs, 40 | respiration, 41 | factor_conductance, 42 | radiation_conductance, 43 | latent_heat_water_vapour, 44 | saturated_vapour_pressure, 45 | vapour_pressure_deficit, 46 | wang_radiation_conductance, 47 | boundary_conductance_free, 48 | boundary_conductance_forced, 49 | forced_boundary_conductance, 50 | arrhenius, 51 | grashof_number, 52 | gs_div_a, 53 | evapotranspiration, 54 | penman_monteith_evapotranspiration 55 | 56 | export Compensation, BadgerCollatzCompensation, BernacchiCompensation 57 | 58 | export AbstractJmax, Jmax 59 | 60 | export AbstractVcmax, NoOptimumVcmax, OptimumVcmax 61 | 62 | export AbstractFlux, Flux, DukeFlux, PotentialModifiedFlux 63 | 64 | export AbstractRubiscoRegen, RubiscoRegen 65 | 66 | export AbstractRespiration, Respiration, AcclimatizedRespiration 67 | 68 | export StomatalConductanceShape, HardMinimum, HyperbolicMinimum 69 | 70 | export AbstractRadiationConductance, WangRadiationConductance 71 | 72 | export AbstractBoundaryConductance, BoundaryConductance 73 | 74 | export AbstractDecoupling, McNaughtonJarvisDecoupling, NoDecoupling 75 | 76 | export AbstractEvapotranspiration, PenmanMonteithEvapotranspiration 77 | 78 | export AbstractSoilData, AbstractDeficitSoilData, AbstractContentSoilData, 79 | DeficitSoilData, ContentSoilData, SimulatedSoilData, PotentialSoilData, NoSoilData 80 | 81 | export AbstractPotentialDependence, LinearPotentialDependence, 82 | ZhouPotentialDependence, NoPotentialDependence 83 | 84 | export AbstractSoilMethod, NoSoilMethod, VolumetricSoilMethod, ConstantSoilMethod, 85 | DeficitSoilMethod, PotentialSoilMethod, EmaxSoilMethod, TuzetSoilMethod 86 | 87 | 88 | export AbstractJarvisCO2, JarvisNoCO2, JarvisLinearCO2, JarvisNonlinearCO2 89 | 90 | export AbstractJarvisVPD, JarvisHyperbolicVPD, JarvisLohammerVPD, 91 | JarvisFractionDeficitVPD, JarvisLinearDeclineVPD 92 | 93 | export AbstractJarvisLight, JarvisLight 94 | 95 | export AbstractJarvisTemp, JarvisNoTemp, JarvisTemp1, JarvisTemp2 96 | 97 | 98 | export AbstractStomatalConductanceSubModel, AbstractBallBerryStomatalConductanceSubModel, 99 | BallBerryStomatalConductanceSubModel, LeuningStomatalConductanceSubModel, 100 | MedlynStomatalConductanceSubModel, TuzetStomatalConductanceSubModel 101 | 102 | export AbstractStomatalConductance, AbstractBallBerryStomatalConductance, 103 | BallBerryStomatalConductance, TuzetStomatalConductance, 104 | AbstractEmaxStomatalConductance, EmaxStomatalConductance, JarvisStomatalConductance 105 | 106 | export AbstractPhotosynthesis, AbstractFvCBPhotosynthesis, FvCBPhotosynthesis 107 | 108 | export AbstractEnergyBalance, AbstractMaespaEnergyBalance, MaespaEnergyBalance, 109 | EmaxEnergyBalance, TuzetEnergyBalance 110 | 111 | export BallBerryVars, EmaxVars, TuzetVars, JarvisVars 112 | 113 | 114 | const FIELDDOCTABLE = FieldDocTable((:Default, :Units, :Description), 115 | (default, units, description); 116 | truncation=(40, 40, 100)) 117 | 118 | @chain columns @udefault_kw @units @bounds @description 119 | 120 | include("constants.jl") 121 | include("vars.jl") 122 | 123 | include("biophysical/biophysical.jl") 124 | include("biophysical/boundary_conductance.jl") 125 | include("biophysical/decoupling.jl") 126 | include("biophysical/evapotranspiration.jl") 127 | include("biophysical/radiation_conductance.jl") 128 | include("biophysical/shape.jl") 129 | 130 | include("core/compensation.jl") 131 | include("core/flux.jl") 132 | include("core/non_stomatal_soilmoisture.jl") 133 | include("core/stomatal_soilmoisture.jl") 134 | include("core/respiration.jl") 135 | include("core/rubisco_regen.jl") 136 | 137 | include("interfaces/energy_balance.jl") 138 | include("interfaces/photosynthesis.jl") 139 | include("interfaces/stomatal_conductance.jl") 140 | 141 | include("formulations/fvcb.jl") 142 | include("formulations/maespa.jl") 143 | include("formulations/jarvis.jl") 144 | include("formulations/ballberry.jl") 145 | include("formulations/medlyn.jl") 146 | include("formulations/leuning.jl") 147 | include("formulations/emax.jl") 148 | include("formulations/tuzet.jl") 149 | 150 | end # module 151 | -------------------------------------------------------------------------------- /src/formulations/ballberry.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Stomatal conductance submodels. 3 | 4 | Many stomatal conductance formulations are identical to Ball-Berry, 5 | with changes only to this submodel. 6 | """ 7 | abstract type AbstractBallBerryStomatalConductanceSubModel <: AbstractStomatalConductanceSubModel end 8 | 9 | 10 | # mixin macro that adds stomatal conductance fields to a struct 11 | @mix @columns struct MixinBallBerryStomatalConductanceSubModel{μMoMo,F} 12 | gamma::μMoMo | 0.0 | μmol*mol^-1 | (0.0, 10.0) | "Gamma for all Ball-Berry type models" 13 | g1::F | 7.0 | _ | (0.0, 10.0) | "Slope parameter" 14 | end 15 | 16 | gamma(m::AbstractStomatalConductanceSubModel) = m.gamma 17 | g1(m::AbstractStomatalConductanceSubModel) = m.g1 18 | 19 | 20 | """ 21 | BallBerryStomatalConductanceSubModel(gamma, g1) 22 | 23 | Basic Ball-Berry stomatal conductance formulation. 24 | Parameters are `gamma` in μmol*mol^-1 and `g1` scalar, 25 | which are also used for all Ball-Berry type models. 26 | 27 | (modelgs = 2 in maestra) 28 | 29 | $(FIELDDOCTABLE) 30 | """ 31 | @MixinBallBerryStomatalConductanceSubModel struct BallBerryStomatalConductanceSubModel{} <: AbstractStomatalConductanceSubModel end 32 | 33 | gs_div_a(m::BallBerryStomatalConductanceSubModel, v) = 34 | g1(m) * v.rhleaf / (v.cs - gamma(m)) 35 | 36 | 37 | """ 38 | Ball-Berry implementation of stomatal conductance. 39 | Most other models inherit fields and behaviours from this. 40 | """ 41 | abstract type AbstractBallBerryStomatalConductance <: AbstractStomatalConductance end 42 | 43 | # Mixin fields for objects inheriting AbstractBallBerryStomatalConductance 44 | @mix @columns struct MixinBallBerryStomatalConductance{MoM2S,GS,SM} 45 | g0::MoM2S | 0.03 | mol*m^-2*s^-1 | (0.0, 0.2) | "Stomatal leakiness (gs when photosynthesis is zero)" 46 | gs_submodel::GS | BallBerryStomatalConductanceSubModel() | _ | _ | _ 47 | soil_model::SM | PotentialSoilMethod() | _ | _ | _ 48 | end 49 | 50 | g0(m::AbstractBallBerryStomatalConductance) = m.g0 51 | gs_submodel(m::AbstractBallBerryStomatalConductance) = m.gs_submodel 52 | soil_model(m::AbstractBallBerryStomatalConductance) = m.soil_model 53 | 54 | 55 | """ 56 | BallBerryStomatalConductance(g0, gs_submodel, soil_model) 57 | 58 | Simple Ball-Berry stomatal conductance model. 59 | 60 | $(FIELDDOCTABLE) 61 | """ 62 | @MixinBallBerryStomatalConductance struct BallBerryStomatalConductance{} <: AbstractBallBerryStomatalConductance end 63 | 64 | """ 65 | BallBerryVars(; kwargs...) 66 | 67 | Variables for Ball-Berry stomatal conductance models. 68 | 69 | $(FIELDDOCTABLE) 70 | """ 71 | @MixinEnviroVars @MixinMaespaVars @MixinFvCBVars mutable struct BallBerryVars{} end 72 | 73 | 74 | """ 75 | stomatal_conductance!(v, m::AbstractBallBerryStomatalConductance) 76 | 77 | Stomatal conductance calculations. 78 | 79 | Returns a tuple of leaf assimilation and stomatal conductance 80 | `(aleaf, gs)` in `u"μmol*m^-2*s^-1" and `u"mol*m^-2*s^-1"` 81 | """ 82 | function stomatal_conductance!(v, m::AbstractBallBerryStomatalConductance) 83 | v.fsoil = soilmoisture_conductance(soil_model(m), v) 84 | v.gs_div_a = gs_div_a(gs_submodel(m), v) * v.fsoil 85 | 86 | ac = rubisco_limited_rate(m, v, v.gs_div_a) 87 | aj = transport_limited_rate(m, v, v.gs_div_a) 88 | 89 | aleaf = min(ac, aj) - v.rd 90 | gs = max(g0(m) + v.gs_div_a * aleaf, g0(m)) 91 | 92 | return aleaf, gs 93 | end 94 | 95 | """ 96 | rubisco_limited_rate(m::AbstractBallBerryStomatalConductance, v, gs_div_a) 97 | 98 | Solution for assimilation when Rubisco activity is limiting, for all Ball-Berry models 99 | 100 | Returns assimilation rate in `u"μmol*m^-2*s^-1"` 101 | """ 102 | function rubisco_limited_rate(m::AbstractBallBerryStomatalConductance, v, gs_div_a) 103 | a = g0(m) + gs_div_a * (v.vcmax - v.rd) 104 | b = (1.0 - v.cs * gs_div_a) * (v.vcmax - v.rd) + g0(m) * (v.km - v.cs) - 105 | gs_div_a * (v.vcmax * v.gammastar + v.km * v.rd) 106 | c = -(1.0 - v.cs * gs_div_a) * (v.vcmax * v.gammastar + v.km * v.rd) - 107 | g0(m) * v.km * v.cs 108 | cic = quad(Upper(), a, b, c) 109 | 110 | ac = if (cic <= zero(cic)) || (cic > v.cs) 111 | zero(v.vcmax) 112 | else 113 | v.vcmax * (cic - v.gammastar) / (cic + v.km) 114 | end 115 | return ac 116 | end 117 | 118 | 119 | """ 120 | transport_limited_rate(m::AbstractBallBerryStomatalConductance, v, gs_div_a) 121 | 122 | Solution for assimilation rate when electron transport rate is limiting, 123 | for all Ball-Berry type models. 124 | 125 | Returns assimilation rate in `u"μmol*m^-2*s^-1"` 126 | """ 127 | function transport_limited_rate(m::AbstractBallBerryStomatalConductance, v, gs_div_a) 128 | a = g0(m) + gs_div_a * (v.vj - v.rd) 129 | b = (1 - v.cs * gs_div_a) * (v.vj - v.rd) + g0(m) * (2v.gammastar - v.cs) - 130 | gs_div_a * (v.vj * v.gammastar + 2v.gammastar * v.rd) 131 | c = -(1 - v.cs * gs_div_a) * v.gammastar * (v.vj + 2v.rd) - g0(m) * 2v.gammastar * v.cs 132 | cij = quad(Upper(), a, b, c) 133 | aj = v.vj * (cij - v.gammastar) / (cij + 2v.gammastar) 134 | 135 | if (aj - v.rd < 1e-6oneunit(v.rd)) # Below light compensation point. TODO: why the magic number? 136 | cij = v.cs 137 | aj = v.vj * (cij - v.gammastar) / (cij + 2v.gammastar) 138 | end 139 | return aj 140 | end 141 | -------------------------------------------------------------------------------- /src/core/flux.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Electron flux formulation, calculated in [`max_electron_transport_rate`](@ref). 3 | """ 4 | abstract type AbstractJmax end 5 | 6 | """ 7 | Jmax(jmax25, delsj, eavj, edvj) 8 | 9 | Standard formulation for maximum electron transport rate. 10 | 11 | $(FIELDDOCTABLE) 12 | """ 13 | @columns struct Jmax{μMoM2S,JMoK,JMo} <: AbstractJmax 14 | jmax25::μMoM2S | 184.0 | μmol*m^-2*s^-1 | (0.0, 1000.0) | "Maximum rate of electron transport at 25° C" 15 | delsj::JMoK | 640.02 | J*mol^-1*K^-1 | (0.0, 1000.0) | "DELTAS in Medlyn et al. (2002)" 16 | eavj::JMo | 37259.0 | J*mol^-1 | (0.0, 100000.0) | "Ha in Medlyn et al. (2002)" 17 | edvj::JMo | 200000.0 | J*mol^-1 | (0.0, 1000000.0) | "Hd in Medlyn et al. (2002)" 18 | end 19 | 20 | """ 21 | max_electron_transport_rate(f::Jmax, tleaf) 22 | 23 | Calculates the potential max_electron transport rate (Jmax) at the leaf temperature 24 | """ 25 | max_electron_transport_rate(f::Jmax, tleaf) = begin 26 | tleafK = tleaf |> K 27 | K25 = 25.0°C |> K 28 | f.jmax25 * exp((tleafK - K25) * f.eavj / (R * tleafK * K25)) * 29 | (1 + exp((f.delsj * K25 - f.edvj) / (R * K25))) / 30 | (1 + exp((f.delsj * tleafK - f.edvj) / (R * tleafK))) 31 | end 32 | 33 | 34 | """ 35 | Maximum rubisco activity formulations, 36 | calculated in [`max_rubisco_activity`](@ref). 37 | """ 38 | abstract type AbstractVcmax end 39 | 40 | " Mixin fields for maximum rubisco transport rate " 41 | @mix @columns struct Vcmax{μMoM2S,JMo} 42 | vcmax25::μMoM2S | 110.0 | μmol*m^-2*s^-1 | (0.0, 200.0) | "Maximumrate rate of rubisco activity at 25° C" 43 | eavc::JMo | 47590.0 | J*mol^-1 | (0.0, 100000.0) | "Ha Medlyn et al. (2002)" 44 | end 45 | 46 | """ 47 | max_rubisco_activity(formulation::AbstractVcmax, tleaf) 48 | 49 | Calculates the maximum Rubisco activity (Vcmax) at the leaf temperature `tleaf`. 50 | 51 | There is still disagreement as to whether this function has an optimum or not. 52 | Both versions here are well-behaved for `tleaf < 0.0` 53 | """ 54 | function max_rubisco_activity end 55 | 56 | """ 57 | NoOptimumVcmax(vcmax25, eavc) 58 | 59 | Formulation for maximum Rubisco activity with no optimum. 60 | 61 | $(FIELDDOCTABLE) 62 | """ 63 | @Vcmax struct NoOptimumVcmax{} <: AbstractVcmax end 64 | 65 | max_rubisco_activity(f::NoOptimumVcmax, tleaf) = begin 66 | tleafK = K(tleaf) 67 | K25 = K(25°C) 68 | f.vcmax25 * exp((f.eavc * (tleaf - K25)) / (K25 * R * tleafK)) 69 | end 70 | 71 | """ 72 | OptimumVcmax(edvc, delsc, vcmax25, eavc) 73 | 74 | Formulation for maximum Rubisco activity with an optimum. 75 | 76 | $(FIELDDOCTABLE) 77 | """ 78 | @Vcmax struct OptimumVcmax{JMo,JMoK} <: AbstractVcmax 79 | edvc::JMo | 1.0 | J*mol^-1 | (0.0, 10.0) | "Hd in Medlyn et al. (2002)" 80 | delsc::JMoK | 629.26 | J*mol^-1*K^-1 | (0.0, 2000.0) | "DELTAS in Medlyn et al. (2002)" 81 | end 82 | 83 | max_rubisco_activity(f::OptimumVcmax, tleaf) = begin 84 | tleafK = K(tleaf) 85 | K25 = K(25°C) 86 | f.vcmax25 * exp((tleaf - K25) * f.eavc / (R * tleafK * K25)) * 87 | (1.0 + exp((f.delsc * K25 - f.edvc) / (R * K25))) / 88 | (1.0 + exp((f.delsc * tleafK - f.edvc) / (R * tleafK))) 89 | end 90 | 91 | 92 | """ 93 | Abstract supertype for flux models. 94 | 95 | Electron flux Jmax and Rubisco activity Vcmax are often modified 96 | by the same function, so we group them. 97 | """ 98 | abstract type AbstractFlux end 99 | 100 | """ 101 | flux(f::AbstractFlux, v) 102 | 103 | Run jamax and vcmax formulations and any modifications, returning a 2-tuple 104 | """ 105 | function flux end 106 | 107 | """ 108 | Flux(jmaxformulation, vcmaxformulation) 109 | 110 | Formulation grouping jmax and vcmax formultions 111 | 112 | $(FIELDDOCTABLE) 113 | """ 114 | @udefault_kw struct Flux{J,V} <: AbstractFlux 115 | jmaxformulation::J | Jmax() 116 | vcmaxformulation::V | NoOptimumVcmax() 117 | end 118 | 119 | flux_model(x::Flux) = x 120 | flux_model(x) = flux_model(x) 121 | 122 | flux(f::Flux, v) = 123 | max_electron_transport_rate(f.jmaxformulation, v.tleaf), 124 | max_rubisco_activity(f.vcmaxformulation, v.tleaf) 125 | 126 | """ 127 | DukeFlux(flux_model, tvjup, tvjdn) 128 | 129 | Flux model modified that allow Jmax and Vcmax to be forced linearly to zero at low T. 130 | 131 | $(FIELDDOCTABLE) 132 | """ 133 | @columns struct DukeFlux{F,K} <: AbstractFlux 134 | flux_model::F | Flux() | _ | _ | _ 135 | tvjup::K | 283.15 | K | (250.0, 350.0) | _ 136 | tvjdn::K | 273.15 | K | (250.0, 350.0) | _ 137 | end 138 | 139 | flux_model(m::DukeFlux) = m.flux_model 140 | 141 | flux(f::DukeFlux, v) = begin 142 | jmax, vcmax = flux(flux_model(f), v) 143 | v.tleaf < f.tvjdn && return zero.((jmax, vcmax)) 144 | 145 | if v.tleaf < f.tvjup 146 | (jmax, vcmax) .* ((v.tleaf - f.tvjdn) / (f.tvjup - f.tvjdn)) 147 | else 148 | jmax, vcmax 149 | end 150 | end 151 | 152 | 153 | """ 154 | PotentialModifiedFlux(flux, potential_model) 155 | 156 | Flux model modified by non-stomatal potential dependence. 157 | 158 | Mdifying both electron flux and rubisco activity by the result of 159 | [`non_stomatal_potential_dependence`](@ref) for `potential_model`. 160 | 161 | $(FIELDDOCTABLE) 162 | """ 163 | @default_kw struct PotentialModifiedFlux{F,P} <: AbstractFlux 164 | flux_model::F | Flux() 165 | potential_model::P | ZhouPotentialDependence() 166 | end 167 | 168 | flux_model(m::PotentialModifiedFlux) = m.flux_model 169 | 170 | flux(f::PotentialModifiedFlux, v) = begin 171 | jmax, vcmax = flux(flux_model(f), v) 172 | pd = non_stomatal_potential_dependence(f.potential_model, v.swp) 173 | jmax * pd, vcmax * pd 174 | end 175 | 176 | -------------------------------------------------------------------------------- /test/construct.jl: -------------------------------------------------------------------------------- 1 | using Photosynthesis, Unitful, Test 2 | 3 | @testset "it all actually builds" begin 4 | BadgerCollatzCompensation() 5 | BernacchiCompensation() 6 | 7 | JarvisLinearCO2() 8 | JarvisNonlinearCO2() 9 | JarvisHyperbolicVPD() 10 | JarvisLohammerVPD() 11 | JarvisFractionDeficitVPD() 12 | JarvisLinearDeclineVPD() 13 | 14 | BallBerryStomatalConductanceSubModel() 15 | LeuningStomatalConductanceSubModel() 16 | MedlynStomatalConductanceSubModel() 17 | TuzetStomatalConductanceSubModel() 18 | 19 | Jmax() 20 | NoOptimumVcmax() 21 | OptimumVcmax() 22 | Flux() 23 | DukeFlux() 24 | RubiscoRegen() 25 | Respiration() 26 | 27 | WangRadiationConductance() 28 | BoundaryConductance() 29 | McNaughtonJarvisDecoupling() 30 | NoDecoupling() 31 | DeficitSoilData() 32 | ContentSoilData() 33 | SimulatedSoilData() 34 | LinearPotentialDependence() 35 | ZhouPotentialDependence() 36 | NoPotentialDependence() 37 | VolumetricSoilMethod() 38 | ConstantSoilMethod() 39 | DeficitSoilMethod() 40 | PotentialSoilMethod() 41 | EmaxSoilMethod() 42 | # TuzetSoilMethod() 43 | BallBerryStomatalConductance() 44 | EmaxStomatalConductance() 45 | JarvisStomatalConductance() 46 | BallBerryVars() 47 | EmaxVars() 48 | TuzetVars() 49 | JarvisVars() 50 | FvCBPhotosynthesis() 51 | MaespaEnergyBalance() 52 | end 53 | 54 | @testset "it all actuall runs" begin 55 | p = MaespaEnergyBalance() 56 | v = EmaxVars() 57 | 58 | 59 | # Biophysical ################################################################# 60 | 61 | latent_heat_water_vapour(v.tair) 62 | arrhenius(42.75u"J/mol", 37830.0u"J/mol", v.tleaf, 300.0u"K") 63 | 64 | # Radiation conductance 65 | radiation_conductance(WangRadiationConductance(), v) 66 | 67 | # Shape 68 | shape_gs(HardMinimum(), v, BallBerryStomatalConductance()) 69 | shape_gs(HyperbolicMinimum(), v, BallBerryStomatalConductance()) 70 | 71 | # Boundary conductance 72 | grashof_number(v.tleaf, v.tair, BoundaryConductance().leafwidth) 73 | cmolar(v.pressure, v.tair) 74 | boundary_conductance_free(BoundaryConductance(), v) 75 | boundary_conductance_forced(BoundaryConductance(), v) 76 | 77 | # Decoupling 78 | decoupling(McNaughtonJarvisDecoupling(), v) 79 | decoupling(NoDecoupling(), v) 80 | 81 | # Evapotranspiration 82 | evapotranspiration(PenmanMonteithEvapotranspiration(), v) 83 | 84 | 85 | # Core ######################################################################## 86 | 87 | # Compensation 88 | co2_compensation_point(BadgerCollatzCompensation(), v.tleaf) 89 | co2_compensation_point(BernacchiCompensation(), v.tleaf) 90 | rubisco_compensation_point(BadgerCollatzCompensation(), v.tleaf) 91 | rubisco_compensation_point(BernacchiCompensation(), v.tleaf) 92 | 93 | # Flux 94 | Photosynthesis.flux(Flux(), v) 95 | Photosynthesis.flux(DukeFlux(), v) 96 | Photosynthesis.flux(Flux(), v) 97 | Photosynthesis.flux(DukeFlux(), v) 98 | 99 | # Respiration 100 | respiration(Respiration(), v.tleaf) 101 | 102 | # Rubisco regeneration 103 | rubisco_regeneration(RubiscoRegen(), v) 104 | 105 | # Stomatal conductance submodels 106 | gs_div_a(BallBerryStomatalConductanceSubModel(), BallBerryVars()) 107 | gs_div_a(LeuningStomatalConductanceSubModel(), BallBerryVars()) 108 | gs_div_a(MedlynStomatalConductanceSubModel(), BallBerryVars()) 109 | gs_div_a(TuzetStomatalConductanceSubModel(), TuzetVars()) 110 | 111 | # Stomatal conductance models 112 | stomatal_conductance!(v, BallBerryStomatalConductance(gs_submodel=BallBerryStomatalConductanceSubModel())) 113 | stomatal_conductance!(v, BallBerryStomatalConductance(gs_submodel=LeuningStomatalConductanceSubModel())) 114 | stomatal_conductance!(v, BallBerryStomatalConductance(gs_submodel=MedlynStomatalConductanceSubModel())) 115 | stomatal_conductance!(v, EmaxStomatalConductance(gs_submodel=BallBerryStomatalConductanceSubModel())) 116 | stomatal_conductance!(v, EmaxStomatalConductance(gs_submodel=LeuningStomatalConductanceSubModel())) 117 | stomatal_conductance!(v, EmaxStomatalConductance(gs_submodel=MedlynStomatalConductanceSubModel())) 118 | # stomatal_conductance!(v, JarvisStomatalConductance(), JarvisVars()) 119 | # stomatal_conductance!(TuzetVars(), TuzetStomatalConductance()) 120 | 121 | 122 | # Formulations ################################################################ 123 | 124 | v = BallBerryVars() 125 | ph = FvCBPhotosynthesis(stomatal_conductance=BallBerryStomatalConductance()) 126 | p = MaespaEnergyBalance(photosynthesis=ph) 127 | enbal!(v, p) 128 | photosynthesis!(v, p.photosynthesis_model) 129 | v.aleaf 130 | v.tleaf 131 | v.gs 132 | v.cs 133 | enbal!(v, p) 134 | 135 | v = JarvisVars() 136 | ph = FvCBPhotosynthesis(stomatal_conductance_model=JarvisStomatalConductance()) 137 | p = MaespaEnergyBalance(photosynthesis_model=ph) 138 | factor_conductance(ph.stomatal_conductance_model, v) 139 | enbal!(v, p) 140 | photosynthesis!(v, p.photosynthesis_model) 141 | v.aleaf 142 | v.tleaf 143 | v.gs 144 | v.cs 145 | enbal!(v, p) 146 | 147 | ph = FvCBPhotosynthesis(stomatal_conductance=TuzetStomatalConductance()) # p = TuzetEnergyBalance(photosynthesis=ph) 148 | v = TuzetVars() 149 | enbal!(v, p) 150 | photosynthesis!(v, p.photosynthesis_model) 151 | v.aleaf 152 | v.tleaf 153 | v.gs 154 | v.cs 155 | enbal!(v, p) 156 | 157 | v = BallBerryVars() 158 | ph = FvCBPhotosynthesis(stomatal_conductance_model=EmaxStomatalConductance()) 159 | v = EmaxVars() 160 | enbal!(v, p) 161 | photosynthesis!(v, p.photosynthesis_model) 162 | v.aleaf 163 | v.tleaf 164 | v.gs 165 | v.cs 166 | enbal!(v, p) 167 | 168 | end 169 | -------------------------------------------------------------------------------- /src/formulations/maespa.jl: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | Energy balance models derived from MAESPA/MAESTRA models. 4 | """ 5 | abstract type AbstractMaespaEnergyBalance <: AbstractEnergyBalance end 6 | 7 | 8 | """ 9 | MaespaEnergyBalance(radiation_conductance, 10 | boundary_conductance, 11 | decoupling, 12 | evapotranspiration, 13 | photosynthesis, 14 | max_iterations) 15 | 16 | Energy-balance model composed of submodels from radiation conductance, 17 | boundary conductance, decoupling, evapotranspiration 18 | photosynthesis. 19 | 20 | `max_iterations` determines the maximum number of iterations to perform to determine 21 | flux and temperature. 22 | 23 | $(FIELDDOCTABLE) 24 | """ 25 | @flattenable @default_kw @description struct MaespaEnergyBalance{ 26 | Ra<:AbstractRadiationConductance, 27 | Bo<:AbstractBoundaryConductance, 28 | De<:AbstractDecoupling,Ev,Ph,I,A} <: AbstractMaespaEnergyBalance 29 | radiation_conductance_model::Ra | true | WangRadiationConductance() | "Radiation condutance model" 30 | boundary_conductance_model::Bo | true | BoundaryConductance() | "Boundary conductance model" 31 | decoupling_model::De | true | McNaughtonJarvisDecoupling() | "" 32 | evapotranspiration_model::Ev | true | PenmanMonteithEvapotranspiration() | "Evapotranspiration model" 33 | photosynthesis_model::Ph | true | FvCBPhotosynthesis() | "Photosynthesis model" 34 | max_iterations::I | false | 100 | "Number of iterations used to find leaf temperature" 35 | atol::A | false | 0.02K | "Tolerance for difference with previous leaf temperature" 36 | end 37 | 38 | radiation_conductance_model(p::AbstractMaespaEnergyBalance) = p.radiation_conductance_model 39 | boundary_conductance_model(p::AbstractMaespaEnergyBalance) = p.boundary_conductance_model 40 | decoupling_model(p::AbstractMaespaEnergyBalance) = p.decoupling_model 41 | evapotranspiration_model(p::AbstractMaespaEnergyBalance) = p.evapotranspiration_model 42 | photosynthesis_model(p::AbstractMaespaEnergyBalance) = p.photosynthesis_model 43 | max_iterations(p::AbstractMaespaEnergyBalance) = p.max_iterations 44 | atol(p::AbstractMaespaEnergyBalance) = p.atol 45 | 46 | """ 47 | enbal!(v, m::AbstractMaespaEnergyBalance) 48 | 49 | Calculates leaf photosynthesis and transpiration for an `AbstractEnergyBalance` 50 | model `m` and variables `v`. 51 | 52 | Results are written to v. 53 | 54 | These may be calculated by: 55 | 56 | 1. Assuming leaf temperature is the same as air temperature, 57 | and stomatal carbon has the same conentration as in the air. 58 | 2. Using iterative scheme of Leuning et al (1995) (PCE 18:1183-1200) 59 | to calculate leaf temperature and stomatal carbon concentration. 60 | 61 | Setting `max_iterations=0` gives 1, max_iterations > 0 (default 100) gives 2. 62 | """ 63 | function enbal!(v, m::AbstractMaespaEnergyBalance) 64 | 65 | # Initialise to ambient conditions 66 | v.tleaf = v.tair 67 | v.vpdleaf = v.vpd 68 | v.rhleaf = v.rh 69 | v.cs = v.ca 70 | 71 | # Calculations that don't depend on tleaf 72 | v.lhv = latent_heat_water_vapour(v.tair) 73 | v.gradn = radiation_conductance(radiation_conductance_model(m), v) 74 | v.gbhu = boundary_conductance_forced(boundary_conductance_model(m), v) 75 | # Slope is precalculated as it's expensive 76 | v.slope = vapour_pressure_slope(evapotranspiration_model(m), v) 77 | 78 | iter = 1 79 | # Converge on leaf temperature 80 | while true 81 | photosynthesis!(v, photosynthesis_model(m)) 82 | conductance!(v, m) 83 | # This isn't actually used - it's only for output 84 | v.decoup = decoupling(decoupling_model(m), v) 85 | 86 | # End of subroutine if no iterations wanted. 87 | (max_iterations(m) == 0 || v.aleaf <= zero(v.aleaf)) && return true 88 | 89 | gbc = v.gbh / GBHGBC 90 | v.cs = v.ca - v.aleaf / gbc 91 | tleaf = leaftemp(m, v) 92 | 93 | # Recalculate 94 | conductance!(v, m) 95 | 96 | v.vpdleaf = v.et * v.pressure / v.gv 97 | v.rhleaf = 1 - v.vpdleaf / saturated_vapour_pressure(tleaf) 98 | 99 | # Check to see whether convergence has occurred 100 | if abs(v.tleaf - tleaf) < atol(m) 101 | v.tleaf = tleaf 102 | return true 103 | end 104 | 105 | v.tleaf = tleaf # Update temperature for another iteration 106 | 107 | iter >= max_iterations(m) && break 108 | iter += 1 109 | end 110 | 111 | @warn "leaf temperature convergence failed" 112 | return false 113 | end 114 | 115 | function conductance!(v, m) 116 | # Total boundary layer conductance for heat 117 | # See Leuning et al (1995) PCE 18:1183-1200 Eqn E5 118 | v.gbhf = boundary_conductance_free(boundary_conductance_model(m), v) 119 | v.gbh = v.gbhu + v.gbhf 120 | # Total conductance for heat: two-sided 121 | v.gh = 2.0(v.gbh + v.gradn) 122 | # Total conductance for water vapour 123 | v.gbv = GBVGBH * v.gbh 124 | v.gsv = GSVGSC * v.gs 125 | v.gv = (v.gbv * v.gsv) / (v.gbv + v.gsv) 126 | v.et = evapotranspiration(evapotranspiration_model(m), v) 127 | end 128 | 129 | 130 | """ 131 | @MixinMaespaVars 132 | 133 | Mixin variables for [`AbstractMaespaEnergyBalance`](@ref) variables objects. 134 | """ 135 | @mix @vars struct MixinMaespaVars{TL,VPDL,RHL,CD,FHi,GBHU,GBHF,GH,GSV,GBV,GBH,GV,GR,LHV,ET,LS,DC} 136 | # shared 137 | tleaf::TL | 298.15 | K | _ 138 | vpdleaf::VPDL | 0.0 | Pa | _ 139 | rhleaf::RHL | 0.0 | _ | _ 140 | cs::CD | 0.0 | μmol*mol^-1 | _ 141 | # energy balance 142 | fheat::FHi | 0.0 | W*m^-2 | _ 143 | gbhu::GBHU | 0.0 | mol*m^-2*s^-1 | _ 144 | gbhf::GBHF | 0.0 | mol*m^-2*s^-1 | _ 145 | gh::GH | 0.0 | mol*m^-2*s^-1 | _ 146 | gsv::GSV | 0.0 | mol*m^-2*s^-1 | _ 147 | gbv::GBV | 0.0 | mol*m^-2*s^-1 | _ 148 | gbh::GBH | 0.0 | mol*m^-2*s^-1 | _ 149 | gv::GV | 0.0 | mol*m^-2*s^-1 | _ 150 | gradn::GR | 0.0 | mol*m^-2*s^-1 | _ 151 | lhv::LHV | 0.0 | J*mol^-1 | _ 152 | et::ET | 0.0 | mol*m^-2*s^-1 | _ 153 | slope::LS | 0.0 | Pa*K^-1 | _ 154 | decoup::DC | 0.0 | _ | _ 155 | end 156 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Photosynthesis 2 | 3 | 4 | ## *This package is not registered and is not currently maintained* 5 | 6 | I don't work on photosynthesis currently and have no personal need to use this. I also currently work on a lot of other packages and have very little time to spare to update this. 7 | 8 | There is a lot of useful code here, if you need it you will need to do some work for it a little. Likely a fraction of what I put into writing it. It's fast, well structured and mostly verified to be correct. But its not a polished experience - this was a proof of concept from a masters project. 9 | 10 | If you want examples of everything that currently works, *Read the tests in the test folder*. You can work through the functions in there and see how it all works. 11 | 12 | Just ignore the code required to run the original fortran binaries I tested againsts. 13 | 14 | 15 | [![](https://img.shields.io/badge/docs-dev-blue.svg)](https://rafaqz.github.io/Photosynthesis.jl/dev) 16 | [![Build Status](https://travis-ci.com/rafaqz/Photosynthesis.jl.svg?branch=master)](https://travis-ci.com/rafaqz/Photosynthesis.jl) 17 | [![codecov.io](http://codecov.io/github/rafaqz/Photosynthesis.jl/coverage.svg?branch=master)](http://codecov.io/github/rafaqz/Photosynthesis.jl?branch=master) 18 | 19 | 20 | 21 | 22 | A Farquhar-von-Cammerer-Berry (FvCB) photosynthesis modelling framework. 23 | 24 | This module at its core is a rewrite of the Maespa/Maestra photosynthesis models in Julia. 25 | It has been written to have a modular structure that can be easily modified or 26 | added to at any level. It is also designed to itself be a component of larger model. 27 | 28 | It aims to provide a comprehensive yet minimalist set of photosynthesis and leaf 29 | energy balance models, eventually including as many as possible published 30 | photosynthesis formulations. But nothing else. Growth models, 3D canopy models, 31 | nutrient uptake etc. can be supplied from another package. 32 | 33 | Included formulations of the basic FvCB model include Ball-Berry (and multiple 34 | sub-variants), EMAX, Tuzet, and Jarvis formulations. 35 | 36 | These are written out in interchangeable components: you can write you own 37 | components in a script and swap them into the model - for _any_ part of this 38 | package. You Just need to define the `struct` for parameters, and define the 39 | corresponding method for the formulation you with to write. 40 | 41 | This means the old problem of multiple forks with minor formulation 42 | changes is essentially solved. Never edit the source code of this package, 43 | just define new alternate components with your desired changes. 44 | 45 | 46 | ## Example 47 | 48 | Here we will define a basic Ball-Berry model, and run it: 49 | 50 | ```julia 51 | v = BallBerryVars() 52 | p1 = MaespaEnergyBalance( 53 | photosynthesis=FvCBPhotosynthesis( 54 | stomatal_conductance=EmaxStomatalConductance( 55 | gs_submodel=BallBerryStomatalConductanceSubModel(), 56 | soilmethod=EmaxSoilMethod(), 57 | ), 58 | flux=DukeFlux(), 59 | compensation=BadgerCollatzCompensation(), 60 | respiration_model=Respiration(), 61 | ), 62 | atol=0.005K, 63 | ) 64 | enbal!(v, p) 65 | ``` 66 | 67 | 68 | Formulations using the MAESPA energy balance and FvCB photosynthesis, 69 | all tested against MAESPA: 70 | 71 | - BallBerry stomatal conducance types: 72 | - `BallBerry` 73 | - `Medlyn` 74 | - `Leuning` 75 | - `Emax` 76 | 77 | 78 | Other formulations, working but not tested 79 | - `Tuzet` : PSILFIND is not yet tested against MAESPA. Use with caution. 80 | - `Jarvis` : Not tested against Maestra, also use with caution. 81 | 82 | 83 | 84 | Notice we run `enbal!` not `photosynthesis!`, as `enbal!` runs 85 | `photosynthesis!` in a loop to calculate both the temperature 86 | and the assimilated C. 87 | 88 | 89 | # Notes about strategies used in this package 90 | 91 | This package was intentionally written as an exploration of how process model 92 | components could generally be structured in better ways to traditional models - 93 | to reduce the overheads of making small changes to any part of the model and 94 | improve the potential for collaboration. This method also facilitates composing 95 | formulations from multiple packages into new models without having to change the 96 | codebases. 97 | 98 | ## Units 99 | 100 | This implementation takes advantage of 101 | [Unitful.jl](https://github.com/PainterQubits/Unitful.jl) to provide unit 102 | checking and conversion. This removes the possibility of a large class of 103 | errors, especially when adding your own custom formulations, and has little or 104 | no runtime cost in most cases. 105 | 106 | ## Nested parameters 107 | 108 | Parameters are composed of nested `struct`s, so that components can be modified 109 | and new components can be added without altering the original. This structure 110 | should be liberating for those wanting to make modifications to existing 111 | formulations, and to share them. 112 | 113 | Formulations all follow the pattern of defining an abstract type and a 114 | function at the top of the file. All formulation variants inherit from the 115 | abstract type, and define a method of the function. 116 | 117 | This allows easy addition of any model components without altering the package 118 | code. 119 | 120 | To convert model parameters to a vector for optimisation or interactive 121 | interfaces, [Flatten.jl](https://github.com/rafaqz/Flatten.jl) can be used. 122 | 123 | [FieldMetadata.jl](https://github.com/rafaqz/FieldMetadata.jl) allows adding 124 | other metadata to parameters (all the `| XX |` in struct definitions), such as 125 | descriptions, defaults, units and bounds. These can also be flattened with 126 | Flatten.jl for used in an optimiser or interface without writing any custom 127 | code. 128 | 129 | Other tools like the (unpublished) 130 | [Codify.jl](https://github.com/rafaqz/Codify.jl) turns a nested model struct 131 | into the code for keyword argument constructors that would build it. 132 | 133 | 134 | # Tests 135 | 136 | Currently this package has extensive tests against the original formaultions in the 137 | MAESPA package. The diffrences between 32bit and 64bit usually breaks parity in anwsers 138 | when some value get to the extreme end of ranges, such as above 50 degrees C. 139 | 140 | The Jarvis model is not tested, as it is not included in Maespa. The outer loops of the 141 | Tuzet model are also not yet tested. Please put in an issue if you would like to use 142 | the Tuzet model. 143 | 144 | There are, however _no real unit tests_, as there were none for the original Fortran. 145 | It would, of course be good to write them, but beyond the current scope of writing 146 | this package. 147 | 148 | 149 | # Credits 150 | 151 | Thanks got to the creators of the Maestra Fortran libraries including Belinda 152 | Medlyn and Remko Duursma, and all earlier contributors to Maestra and Maestro. 153 | -------------------------------------------------------------------------------- /src/formulations/jarvis.jl: -------------------------------------------------------------------------------- 1 | 2 | abstract type AbstractJarvisStomatalConductance <: AbstractStomatalConductance end 3 | 4 | """ 5 | JarvisStomatalConductance(co2method, vpdmethod, lightmethod, tempmethod, g0, gsref, vmfd) 6 | 7 | Jarvis stomatal conductance model. 8 | 9 | Combines factors from soilmethod, co2 method, vpdmethod and tempmethod 10 | to gain an overall stomatal conductance. 11 | 12 | $(FIELDDOCTABLE) 13 | """ 14 | @columns struct JarvisStomatalConductance{JC,JV,JL,JT,G0,GS,V} <: AbstractJarvisStomatalConductance 15 | co2method::JC | JarvisNonlinearCO2() | _ | _ | _ 16 | vpdmethod::JV | JarvisLohammerVPD() | _ | _ | _ 17 | lightmethod::JL | JarvisLight() | _ | _ | _ 18 | tempmethod::JT | JarvisTemp2() | _ | _ | _ 19 | g0::G0 | 1.0 | mol*m^-2*s^-1 | _ | _ 20 | gsref::GS | 1.0 | mol*m^-2*s^-1 | _ | _ 21 | vmfd::V | 1.0 | mmol*mol^-1 | _ | _ 22 | end 23 | 24 | g0(f::JarvisStomatalConductance) = f.g0 25 | 26 | @MixinEnviroVars @MixinMaespaVars @MixinFvCBVars mutable struct JarvisVars{mMoMo} 27 | vmleaf::mMoMo | 1.0 | mmol*mol^-1 | _ 28 | end 29 | 30 | gs_init!(v, m::AbstractJarvisStomatalConductance) = v.vmleaf = m.vmfd 31 | gs_update!(v, m::AbstractJarvisStomatalConductance, tleaf1) = v.vmleaf = v.vpdleaf / v.pressure 32 | 33 | stomatal_conductance!(v, m::AbstractJarvisStomatalConductance) = begin 34 | v.gs = factor_conductance(m, v) 35 | v.ac = rubisco_limited_rate(m, v) 36 | v.aj = transport_limited_rate(m, v) 37 | v.aleaf = min(v.ac, v.aj) - v.rd 38 | v.aleaf, v.gs 39 | end 40 | 41 | @inline function rubisco_limited_rate(m::AbstractJarvisStomatalConductance, v) 42 | a = 1.0 / v.gs 43 | b = (v.rd - v.vcmax) / v.gs - v.cs - v.km 44 | c = v.vcmax * (v.cs - v.gammastar) - v.rd * (v.cs + v.km) 45 | quad(Lower(), a, b, c) 46 | end 47 | 48 | @inline function transport_limited_rate(m::AbstractJarvisStomatalConductance, v) 49 | a = 1.0 / v.gs 50 | b = (v.rd - v.vj) / v.gs - v.cs - 2v.gammastar 51 | c = v.vj * (v.cs - v.gammastar) - v.rd * (v.cs + 2v.gammastar) 52 | quad(Lower(), a, b, c) 53 | end 54 | 55 | """ 56 | factor_conductance(m, v) 57 | 58 | Calculate stomatal conductance `gs` according to the 59 | Jarvis model `m`, given variables `v`. 60 | 61 | This model calculates `gs` by multiplying together factors for several 62 | environmental variables: light, VPD, CO2 and temperature. 63 | """ 64 | function factor_conductance(m, v) 65 | flight = min(max(light_factor(m.lightmethod, v), 1.0), 0.0) 66 | fvpd = min(max(vpd_factor(m.vpdmethod, v), 1.0), 0.0) 67 | fco2 = min(max(co2_factor(m.co2method, v), 1.0), 0.0) 68 | ftemp = min(max(temp_factor(m.tempmethod, v), 1.0), 0.0) 69 | 70 | return (m.gsref - m.g0) * flight * fvpd * fco2 * ftemp * v.fsoil + m.g0 71 | end 72 | 73 | 74 | 75 | """ 76 | Jarvis light response factor formulations 77 | Run in [`light_factor`](@ref) methods. 78 | """ 79 | abstract type AbstractJarvisLight end 80 | 81 | """ 82 | light_factor(m::AbstractJarvisLight, v) 83 | 84 | Calculates the light related conductance factor. 85 | 86 | Returns a value between 0.0 and 1.0 87 | """ 88 | function light_factor end 89 | 90 | """ 91 | JarvisLight(i0)i0 92 | 93 | Factor response to incident radiation 94 | 95 | $(FIELDDOCTABLE) 96 | """ 97 | @columns mutable struct JarvisLight{T} <: AbstractJarvisLight 98 | i0::T | 1.0 | mol*m^-2*s^-1 | _ | _ 99 | end 100 | 101 | light_factor(m::JarvisLight, v) = m.i0 > zero(m.i0) ? v.par / (v.par + m.i0) : oneunit(m.i0) 102 | 103 | 104 | """ 105 | Jarvis CO2 response factor formulations. 106 | Run in [`co2_factor`](@ref) methods. 107 | """ 108 | abstract type AbstractJarvisCO2 end 109 | 110 | """ 111 | co2_factor(m::AbstractJarvisCO2, v) 112 | 113 | Calculates the CO2 related conductance factor. 114 | 115 | Returns a value between 0.0 and 1.0 116 | """ 117 | function co2_factor end 118 | 119 | """ 120 | JarvisNoCO2() 121 | 122 | No influence from CO2 for Jarvis stomatal conductance 123 | """ 124 | struct JarvisNoCO2 <: AbstractJarvisCO2 end 125 | 126 | co2_factor(m::JarvisNoCO2, v) = 1.0 127 | 128 | """ 129 | JarvisLinearDeclineVPD(gsja) 130 | 131 | Linear response to CO2 for Jarvis stomatal conductance 132 | 133 | $(FIELDDOCTABLE) 134 | """ 135 | @columns mutable struct JarvisLinearCO2{T} <: AbstractJarvisCO2 136 | gsja::T | 1.0 | μmol^-1*mol | _ | _ 137 | end 138 | 139 | co2_factor(m::JarvisLinearCO2, v) = 140 | m.gsja != 0.0 ? 1 - m.gsja * (v.cs - 350.0) : 1.0 141 | 142 | """ 143 | JarvisNonlinearCO2(gsjb) 144 | 145 | Non-linear response to CO2 for Jarvis stomatal conductance 146 | 147 | $(FIELDDOCTABLE) 148 | """ 149 | @columns mutable struct JarvisNonlinearCO2{T} <: AbstractJarvisCO2 150 | gsjb::T | 1.0 | μmol*mol^-1 | _ | _ 151 | end 152 | 153 | co2_factor(m::JarvisNonlinearCO2, v) = 154 | m.gsjb != 0.0 ? (m.gsjb + 350.0) / (m.gsjb + v.cs) : 1.0 155 | 156 | 157 | 158 | """ 159 | Temperature response factor formulations 160 | Run in [`temp_factor`](@ref) methods. 161 | """ 162 | abstract type AbstractJarvisTemp end 163 | 164 | @mix @columns struct JarTemp{T} 165 | tmax::T | (273.15 + 40.0) | K | _ | _ 166 | tref::T | (273.15 + 25.0) | K | _ | _ 167 | t0::T | 273.15 | K | _ | _ 168 | end 169 | 170 | """ 171 | temp_factor(m::AbstractJarvisTemp, v) 172 | 173 | Calculates the temperature related conductance factor. 174 | 175 | Returns a value between 0.0 and 1.0 176 | """ 177 | function temp_factor end 178 | 179 | """ 180 | JarvisNoTemp() 181 | 182 | No response to temperature in Jarvis stomatal conductance 183 | """ 184 | struct JarvisNoTemp <: AbstractJarvisTemp end 185 | 186 | temp_factor(m::JarvisNoTemp, v) = 1.0 187 | 188 | """ 189 | JarvisTemp1(tmax, tref, t0) 190 | 191 | $(FIELDDOCTABLE) 192 | """ 193 | @JarTemp mutable struct JarvisTemp1{} <: AbstractJarvisTemp end 194 | 195 | temp_factor(m::JarvisTemp1, v) = begin 196 | p = (m.tmax - m.tref) / (m.tref - m.t0) 197 | (v.tleaf - m.t0) * ((m.tmax - v.tleaf)^p) / ((m.tref - m.t0) * ((m.tmax - tref)^p)) 198 | end 199 | 200 | """ 201 | JarvisTemp2(tmax, tref, t0) 202 | 203 | $(FIELDDOCTABLE) 204 | """ 205 | @JarTemp mutable struct JarvisTemp2{} <: AbstractJarvisTemp end 206 | 207 | temp_factor(m::JarvisTemp2, v) = 208 | (v.tleaf - m.t0) * (2 * m.tmax - m.t0 - v.tleaf) / 209 | ((m.tref - m.t0) * (2 * m.tmax - m.t0 - m.tref)) 210 | 211 | 212 | 213 | """ 214 | Vapour pressure deficit response-factor formulations 215 | Run in [`vpd_factor`](@ref) methods. 216 | """ 217 | abstract type AbstractJarvisVPD end 218 | 219 | """ 220 | vpd_factor(m::AbstractJarvisVPD, v) 221 | 222 | Calculates the vapour-pressure-deficit related conductance factor. 223 | 224 | Returns a value between 0.0 and 1.0 225 | """ 226 | function vpd_factor end 227 | 228 | """ 229 | JarvisHyperbolicVPD(vk1, vk2) 230 | 231 | Hyperbolic decline with VPD. BRAY (ALEX BOSC) 232 | Parameters vk1 and vk2 are the dimensionless scalar and exponent. 233 | 234 | $(FIELDDOCTABLE) 235 | """ 236 | @columns mutable struct JarvisHyperbolicVPD{T} <: AbstractJarvisVPD 237 | vk1::T | 1.0 | _ | _ | _ 238 | vk2::T | 1.0 | _ | _ | _ 239 | end 240 | 241 | vpd_factor(m::JarvisHyperbolicVPD, v) = 242 | v.vpdleaf > zero(v.vpdleaf) ? 1.0 / (m.vk1 * v.vpdleaf^m.vk2) : 0.0 243 | 244 | """ 245 | JarvisLohammerVPD(vpd1, vpd2) 246 | 247 | Non-linear Lohammer response to vapour pressure deficit. 248 | Parameters vpd1 and vpd2 are in Pascals. 249 | 250 | $(FIELDDOCTABLE) 251 | """ 252 | @columns mutable struct JarvisLohammerVPD{T} <: AbstractJarvisVPD 253 | vpd1::T | 1.0 | Pa | _ | _ 254 | vpd2::T | 1.0 | Pa | _ | _ 255 | end 256 | 257 | vpd_factor(m::JarvisLohammerVPD, v) = 258 | v.vpdleaf >= m.vpd1 ? 1.0 - (v.vpdleaf - m.vpd1) / (m.vpd2 - m.vpd1) : 1.0 259 | 260 | """ 261 | JarvisFractionDeficitVPD(vmdf0) 262 | 263 | Mole fraction deficit MARK RAYMENT 264 | 265 | $(FIELDDOCTABLE) 266 | """ 267 | @columns mutable struct JarvisFractionDeficitVPD{T} <: AbstractJarvisVPD 268 | vmfd0::T | 1.0 | mmol*mol^-1 | _ | _ 269 | end 270 | 271 | vpd_factor(m::JarvisFractionDeficitVPD, v) = 272 | m.vmfd0 > 0.0 ? 1.0 - v.vmleaf / m.vmfd0 : 1.0 273 | 274 | """ 275 | JarvisLinearDeclineVPD(d0) 276 | 277 | Linear decline with vpd (Tim Randle) 278 | 279 | $(FIELDDOCTABLE) 280 | """ 281 | @columns mutable struct JarvisLinearDeclineVPD{T} <: AbstractJarvisVPD 282 | d0::T | 1.0 | Pa | _ | _ 283 | end 284 | 285 | vpd_factor(m::JarvisLinearDeclineVPD, v) = 286 | m.d0 > 0.0 ? 1.0 / (1.0 + v.vpdleaf / m.d0) : 1.0 287 | 288 | -------------------------------------------------------------------------------- /test/maespa_core.jl: -------------------------------------------------------------------------------- 1 | using Photosynthesis 2 | 3 | include(joinpath(dirname(pathof(Photosynthesis)), "../test/shared.jl")) 4 | 5 | # Setup 6 | emax = MaespaEnergyBalance( 7 | photosynthesis_model=FvCBPhotosynthesis( 8 | stomatal_conductance_model=EmaxStomatalConductance() 9 | ) 10 | ) 11 | ph = emax.photosynthesis_model 12 | 13 | @testset "rubisco_compensation_point/kmfn" begin 14 | for tleaf in (-50, -20.0, 0.0, 15.0, 25.0, 45.0, 60.0, 90.0) 15 | v = EmaxVars() 16 | v.tleaf = tleaf * °C 17 | kmfn_fortran = Libdl.dlsym(maespa_photosynlib, :kmfn_) 18 | ieco = BERNACCI 19 | km_ref = ccall(kmfn_fortran, Float32, (Ref{Float32}, Ref{Int32}), tleaf, ieco) 20 | km = rubisco_compensation_point(BernacchiCompensation(), v.tleaf) # Michaelis-Menten for Rubisco, umol mol-1 21 | @test ustrip(u"μmol/mol", km) ≈ km_ref 22 | println("Bernacchi: ", (v.tleaf, km)) 23 | ieco = BADGERCOLLATZ 24 | km_ref = ccall(kmfn_fortran, Float32, (Ref{Float32}, Ref{Int32}), tleaf, ieco) 25 | km = rubisco_compensation_point(BadgerCollatzCompensation(), v.tleaf) # Michaelis-Menten for Rubisco, umol mol-1 26 | @test ustrip(u"μmol/mol", km) ≈ km_ref 27 | println("Badger-Collatz: ", (v.tleaf, km)) 28 | end 29 | end 30 | 31 | 32 | @testset "co2_compensation_point/GAMMAFN" begin 33 | for tleaf in (-60, -20.0, 0.0, 15.0, 25.0, 45.0, 60.0, 100.0) 34 | v = EmaxVars() 35 | v.tleaf = tleaf * °C 36 | gammafn_fortran = Libdl.dlsym(maespa_photosynlib, :gammafn_) 37 | gammastar_ref = ccall(gammafn_fortran, Float32, (Ref{Float32}, Ref{Int32}), tleaf, BERNACCI) 38 | gammastar = co2_compensation_point(BernacchiCompensation(), v.tleaf) # Michaelis-Menten for Rubisco, umol mol-1 39 | @test ustrip(u"μmol/mol", gammastar) ≈ gammastar_ref 40 | println("Bernacchi: ", (v.tleaf, gammastar)) 41 | gammastar_ref = ccall(gammafn_fortran, Float32, (Ref{Float32}, Ref{Int32}), tleaf, BADGERCOLLATZ) 42 | gammastar = co2_compensation_point(BadgerCollatzCompensation(), v.tleaf) # Michaelis-Menten for Rubisco, umol mol-1 43 | @test ustrip(u"μmol/mol", gammastar) ≈ gammastar_ref 44 | println("Badger-Collatz: ", (v.tleaf, gammastar)) 45 | end 46 | end 47 | 48 | 49 | @testset "max_rubisco_activity/VCMAXTFN" begin 50 | vcmaxtfn_fortran = Libdl.dlsym(maespa_photosynlib, :vcmaxtfn_) 51 | 52 | for parmult in (0.001, 0.01, 0.5, 1.0, 1.03) 53 | # Breaks below 0.0, but only by small amounts 54 | for tleaf in (0.0, 10.0, 15.0, 25.0, 50.0) 55 | v = EmaxVars() 56 | v.tleaf = tleaf * °C 57 | vc = Flatten.modify(x -> x * rand(min(parmult, 1.0):0.000001:max(parmult)), NoOptimumVcmax()) 58 | vcmax25 = ustrip(u"μmol*m^-2*s^-1", vc.vcmax25) 59 | eavc = ustrip(u"J*mol^-1", vc.eavc) 60 | edvc = 0.0 61 | delsc = 0.0 62 | tvjup = -100.0 63 | tvjdn = -100.0 64 | vcmax_ref = ccall(vcmaxtfn_fortran, Float32, (Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32} 65 | ), vcmax25, tleaf, eavc, edvc, delsc, tvjup, tvjdn) 66 | @test ustrip(max_rubisco_activity(vc, v.tleaf)) ≈ vcmax_ref rtol=1e-4 67 | 68 | vcmax_ref = ccall(vcmaxtfn_fortran, Float32, (Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32} 69 | ), vcmax25, tleaf, eavc, edvc, delsc, tvjup, tvjdn) 70 | @test ustrip(max_rubisco_activity(vc, v.tleaf)) ≈ vcmax_ref 71 | end 72 | 73 | for tleaf in (-40.0, 0.0, 10.0, 15.0, 25.0, 55.0) 74 | v = EmaxVars() 75 | v.tleaf = tleaf * °C 76 | 77 | # The FORTRAN breaks with parmult > 1.1 78 | vc = Flatten.modify(x -> x * parmult, OptimumVcmax()) 79 | vcmax25 = ustrip(u"μmol*m^-2*s^-1", vc.vcmax25) 80 | eavc = ustrip(u"J*mol^-1", vc.eavc) 81 | edvc = ustrip(u"J*mol^-1", vc.edvc) 82 | delsc = ustrip(u"J*K^-1*mol^-1", vc.delsc) 83 | tvjup = -100.0 84 | tvjdn = -100.0 85 | vcmax_ref = ccall(vcmaxtfn_fortran, Float32, 86 | (Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32} 87 | ), vcmax25, tleaf, eavc, edvc, delsc, tvjup, tvjdn) 88 | @test ustrip(max_rubisco_activity(vc, v.tleaf)) ≈ vcmax_ref 89 | 90 | # vc = OptimumVcmax() 91 | # f = DukeFlux() 92 | # vcmax25 = ustrip(u"μmol*m^-2*s^-1", vc.vcmax25) 93 | # eavc = ustrip(u"J*mol^-1", vc.eavc) 94 | # edvc = ustrip(u"J*mol^-1", vc.edvc) 95 | # delsc = ustrip(u"J*K^-1*mol^-1", vc.delsc) 96 | # tvjup = ustrip(°C, f.tvjup) 97 | # tvjdn = ustrip(°C, f.tvjdn) 98 | # vcmax_ref = ccall(vcmaxtfn_fortran, Float32, 99 | # (Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}), 100 | # vcmax25, tleaf, eavc, edvc, delsc, tvjup, tvjdn) 101 | # @test ustrip(max_rubisco_activity(vc, v.tleaf)) ≈ vcmax_ref 102 | 103 | end 104 | end 105 | end 106 | 107 | 108 | @testset "max_electron_transport_rate/JMAXTFN" begin 109 | params = Flatten.flatten(Jmax(), Number) 110 | fns = Flatten.fieldnameflatten(Jmax(), Number) 111 | for parmult in permutations((0.01, 0.1, 1.0, 1.04)) 112 | for tleaf in (-50.0, -10, 0.0, 15.0, 25.0, 50.0) 113 | v = EmaxVars() 114 | v.tleaf = tleaf * °C 115 | ps = NamedTuple{fns}(params .* parmult) 116 | @show ps 117 | f = Flatten.reconstruct(Jmax(), ps, Number) 118 | jmaxtfn_fortran = Libdl.dlsym(maespa_photosynlib, :jmaxtfn_) 119 | jmax25 = ustrip(u"μmol*m^-2*s^-1", f.jmax25) 120 | eavj = ustrip(u"J*mol^-1", f.eavj) 121 | edvj = ustrip(u"J*mol^-1", f.edvj) 122 | delsj = ustrip(u"J*K^-1*mol^-1", f.delsj) 123 | tvjup = -100.0 124 | tvjdn = -100.0 125 | jmax_ref = ccall(jmaxtfn_fortran, Float32, 126 | (Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}), 127 | jmax25, tleaf, eavj, edvj, delsj, tvjup, tvjdn) 128 | @test ustrip(max_electron_transport_rate(f, v.tleaf)) ≈ jmax_ref 129 | end 130 | end 131 | end 132 | 133 | 134 | @testset "respiration/RESP" begin 135 | resp_fortran = Libdl.dlsym(maespa_photosynlib, :resp_) 136 | 137 | params = Flatten.flatten(Respiration(), Number) 138 | fns = Flatten.fieldnameflatten(Respiration(), Number) 139 | for parmult in permutations((0.7, 0.9, 1.0, 1.05, 1.15)) 140 | for tleaf in (-20.0, 0.0, 15.0, 25.0, 50.0) 141 | v = EmaxVars() 142 | v.tleaf = tleaf * °C 143 | ps = NamedTuple{fns}(params .* parmult) 144 | @show ps 145 | f = Flatten.reconstruct(Respiration(), ps, Number) 146 | rd0 = ustrip(u"μmol*m^-2*s^-1", f.rd0) 147 | rdacc = 1.0 148 | q10f = ustrip(u"K^-1", f.q10f) 149 | tref = ustrip(°C, f.tref) 150 | dayresp = f.dayresp 151 | tbelow = ustrip(°C, f.tbelow) 152 | k10f = 0.0 # No acclimation 153 | tmove = 0.0 # No acclimation 154 | resp_ref = ccall(resp_fortran, Float32, 155 | (Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}), 156 | rd0, rdacc, tleaf, tmove, q10f, k10f, tref, dayresp, tbelow) 157 | v.rd = respiration(f, v.tleaf) 158 | println(v.rd) 159 | @test ustrip(u"μmol*m^-2*s^-1", v.rd) ≈ resp_ref 160 | end 161 | end 162 | 163 | # params = Flatten.flatten(AcclimatizedRespiration(), Number) 164 | # fns = Flatten.fieldnameflatten(AcclimatizedRespiration(), Number) 165 | # for parmult in permutations((0.20, 0.5, 0.8, 1.0, 1.1, 1.5, 2.0)) 166 | # ps = NamedTuple{fns}(params .* parmult) 167 | # @show ps 168 | # f = Flatten.reconstruct(AcclimatizedRespiration(), ps, Number) 169 | # rd0 = ustrip(u"μmol*m^-2*s^-1", f.rd0) 170 | # rdacc = 1.0 # this isn't actually a parameter 171 | # q10f = ustrip(u"K^-1", f.q10f) 172 | # tref = ustrip(°C, f.tref) 173 | # dayresp = f.dayresp 174 | # tbelow = ustrip(°C, f.tbelow) 175 | # k10f = ustrip(u"K^-1", f.k10f) 176 | # tmove = ustrip(u"K", f.tmove) 177 | # resp_ref = ccall(resp_fortran, Float32, 178 | # (Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}, Ref{Float32}), 179 | # rd0, rdacc, tleaf, tmove, q10f, k10f, tref, dayresp, tbelow) 180 | # v.rd = respiration(f, v.tleaf) 181 | # # This is actually commented out in the maespa FORTRAN 182 | # # @test v.rd.val ≈ resp_ref 183 | # end 184 | 185 | end 186 | -------------------------------------------------------------------------------- /test/maespa_enbal.jl: -------------------------------------------------------------------------------- 1 | using Photosynthesis, Flatten, FieldMetadata 2 | using Photosynthesis: flux_model 3 | 4 | using FieldMetadata: units 5 | 6 | include(joinpath(dirname(pathof(Photosynthesis)), "../test/shared.jl")) 7 | 8 | function run_fortran_enbal(p, v, vfun=1) 9 | 10 | emaxenbal = if p isa EmaxEnergyBalance 11 | p 12 | else 13 | EmaxEnergyBalance() 14 | end 15 | 16 | tuzetvars = TuzetVars() 17 | emaxvars = EmaxVars() 18 | psil = Float32[ustrip(units(EmaxVars, :psil), emaxvars.psil)] 19 | # Use current vars or dummy if they arent needed 20 | if v isa TuzetVars 21 | tuzetvars = v 22 | psil = Float32[ustrip(units(TuzetVars, :psil), tuzetvars.psil)] 23 | elseif v isa EmaxVars 24 | emaxvars = v 25 | end 26 | 27 | ph = p.photosynthesis_model 28 | sc = ph.stomatal_conductance_model 29 | gs = sc.gs_submodel 30 | vcj = flux_model(ph.flux_model) 31 | vc = vcj.vcmaxformulation 32 | j = vcj.jmaxformulation 33 | sm = sc.soil_model 34 | # This is not even used in Maespa, its a Maestra relic but the parameters 35 | # are still passed in... 36 | jsc = JarvisStomatalConductance() 37 | 38 | ieco = ph.compensation_model isa BadgerCollatzCompensation ? BADGERCOLLATZ : BERNACCI 39 | if gs isa BallBerryStomatalConductanceSubModel 40 | vpdmin = 0.0 41 | gk = 0.0 42 | modelgs = BALLBERRY_GS 43 | elseif gs isa LeuningStomatalConductanceSubModel 44 | vpdmin = 0.0 45 | gk = 0.0 46 | modelgs = LEUNING_GS 47 | elseif gs isa MedlynStomatalConductanceSubModel 48 | vpdmin = ustrip(units(MedlynStomatalConductanceSubModel, :vpdmin), MedlynStomatalConductanceSubModel().vpdmin) 49 | gk = MedlynStomatalConductanceSubModel().gk 50 | modelgs = MEDLYN_GS 51 | end 52 | if sm isa NoSoilMethod 53 | wc1 = 0.0 54 | wc2 = 0.0 55 | swpexp = 0.0 56 | soilmoisture = v.soilmoist 57 | wsoilmethod = SOILMETHOD_NONE 58 | soildata = SOILDATA_NONE 59 | elseif sm isa PotentialSoilMethod 60 | wc1 = 0.0 61 | wc2 = 0.0 62 | soilmoisture = ustrip(units(typeof(v), :swp), v.swp) 63 | swpexp = ustrip(units(typeof(sm), :swpexp), sm.swpexp) 64 | wsoilmethod = SOILMETHOD_POTENTIAL 65 | soildata = SOILDATA_POTENTIAL 66 | elseif sm isa VolumetricSoilMethod 67 | wc1 = sm.wc1 68 | wc2 = sm.wc2 69 | soilmoisture = v.soilmoist 70 | swpexp = 0.0 71 | wsoilmethod = SOILMETHOD_VOLUMETRIC 72 | soildata = SOILDATA_CONTENT 73 | elseif sm isa EmaxSoilMethod 74 | soilmoisture = v.soilmoist 75 | swpexp = 0.0 76 | wc1 = 0.0 77 | wc2 = 0.0 78 | wsoilmethod = EMAXSOILMETHOD 79 | soildata = SOILDATA_NONE 80 | end 81 | ismaespabool = sc isa EmaxStomatalConductance ? true : false 82 | ismaespa = unsigned(0) + ismaespabool 83 | 84 | gk = typeof(sc.gs_submodel) <: MedlynStomatalConductanceSubModel ? sc.gs_submodel.gk : 0.0 85 | D0 = if typeof(sc.gs_submodel) <: LeuningStomatalConductanceSubModel 86 | ustrip(units(LeuningStomatalConductanceSubModel, :D0), sc.gs_submodel.D0) 87 | else 88 | 0.0 89 | end 90 | par = ustrip(units(typeof(v), :par), v.par) 91 | tleaf = Float32[ustrip(°C, v.tleaf)] 92 | cs = ustrip(units(typeof(v), :cs), v.cs) 93 | ca = ustrip(units(typeof(v), :ca), v.ca) 94 | rh = v.rh 95 | rnet = ustrip(units(typeof(v), :rnet), v.rnet) 96 | tair = ustrip(°C, v.tair) 97 | wind = ustrip(units(typeof(v), :windspeed), v.windspeed) 98 | vpd = ustrip(units(typeof(v), :vpd), v.vpd) 99 | press = ustrip(units(typeof(v), :pressure), v.pressure) 100 | vmfd = JarvisStomatalConductance().vmfd.val 101 | jmax25 = ustrip(units(typeof(vcj.jmaxformulation), :jmax25), vcj.jmaxformulation.jmax25) 102 | eavj = ustrip(units(typeof(j), :eavj), j.eavj) 103 | edvj = ustrip(units(typeof(j), :edvj), j.edvj) 104 | delsj = ustrip(units(typeof(j), :delsj), j.delsj) 105 | vcmax25 = ustrip(units(typeof(vc), :vcmax25), vc.vcmax25) 106 | eavc = ustrip(units(typeof(vc), :eavc), vc.eavc) 107 | if typeof(vc) <: OptimumVcmax 108 | edvc = ustrip(units(typeof(vc), :edvc), vc.edvc) 109 | delsc = ustrip(units(typeof(vc), :delsc), vc.delsc) 110 | else 111 | edvc = 0.0 112 | delsc = 0.0 113 | end 114 | if ph.flux_model isa DukeFlux 115 | tvjup = ustrip(°C, ph.flux_model.tvjup) 116 | tvjdn = ustrip(°C, ph.flux_model.tvjdn) 117 | else 118 | tvjup = -100.0 119 | tvjdn = -100.0 120 | end 121 | theta = ph.rubisco_regen_model.theta 122 | ajq = ph.rubisco_regen_model.ajq 123 | rd0 = ustrip(u"μmol*m^-2*s^-1", ph.respiration_model.rd0) 124 | q10f = ustrip(u"K^-1", ph.respiration_model.q10f) 125 | if ph.respiration_model isa AcclimatizedRespiration 126 | k10f = ustrip(u"K^-1", AcclimatizedRespiration().k10f) 127 | tmove = ustrip(K, AcclimatizedRespiration().tmove) 128 | else 129 | k10f = 0.0 130 | tmove = 0.0 131 | end 132 | tref = ustrip(°C, ph.respiration_model.tref) 133 | rtemp = ustrip(°C, ph.respiration_model.tref) 134 | dayresp = ph.respiration_model.dayresp 135 | tbelow = ustrip(°C, ph.respiration_model.tbelow) 136 | gsref = jsc.gsref.val 137 | gsmin = jsc.g0.val 138 | i0 = jsc.lightmethod.i0.val 139 | d0 = JarvisLinearDeclineVPD().d0.val 140 | vk1 = JarvisHyperbolicVPD().vk1 141 | vk2 = JarvisHyperbolicVPD().vk2 142 | vpd1 = JarvisLohammerVPD().vpd1.val 143 | vpd2 = JarvisLohammerVPD().vpd2.val 144 | vmfd0 = JarvisFractionDeficitVPD().vmfd0.val 145 | gsja = JarvisLinearCO2().gsja.val 146 | gsjb = JarvisNonlinearCO2().gsjb.val 147 | t0 = ustrip(°C, JarvisTemp1().t0) 148 | tmax = ustrip(°C, JarvisTemp1().tmax) 149 | emaxleaf = emaxvars.emaxleaf.val 150 | plantk = emaxenbal.plantk.val 151 | totsoilres = emaxenbal.totsoilres.val 152 | 153 | smd1 = DeficitSoilData().smd1 154 | smd2 = DeficitSoilData().smd2 155 | fsoil = Float32[v.fsoil] 156 | g0 = ustrip(units(typeof(sc), :g0), sc.g0) 157 | gamma = sc.gs_submodel.gamma 158 | g1 = sc.gs_submodel.g1 159 | gs = Float32[ustrip(units(typeof(v), :gs), v.gs)] 160 | aleaf = Float32[ustrip(units(typeof(v), :aleaf), v.aleaf)] 161 | rd = Float32[ustrip(units(typeof(v), :rd), v.rd)] 162 | minleafwp = ustrip(units(EmaxVars, :minleafwp), emaxvars.minleafwp) 163 | ktot = ustrip(units(EmaxVars, :ktot), emaxvars.ktot) 164 | weightedswp = ustrip(units(typeof(emaxvars), :swp), emaxvars.swp) 165 | vpara = ustrip(units(LinearPotentialDependence, :vparb), LinearPotentialDependence().vpara) 166 | vparb = ustrip(units(LinearPotentialDependence, :vparb), LinearPotentialDependence().vparb) 167 | vparc = 0.0 # unused 168 | fheat = 0.0 # unused 169 | etest = 0.0 # unused 170 | gbh = ustrip(units(typeof(v), :gbh), v.gbh) 171 | sf = ustrip(units(TuzetVars, :sf), tuzetvars.sf) 172 | psiv = ustrip(units(TuzetVars, :psiv), tuzetvars.psiv) 173 | hmshape = HyperbolicMinimum().hmshape 174 | psilin = ustrip(units(TuzetVars, :psilin), tuzetvars.psilin) 175 | ci = Float32[ustrip(units(typeof(v), :ci), v.ci)] 176 | 177 | iday = 1 178 | ihour = 1 179 | nsides = 1 180 | itermax = p.max_iterations 181 | yp = WangRadiationConductance() 182 | rdfipt = yp.rdfipt 183 | tuipt = yp.tuipt 184 | tdipt = yp.tdipt 185 | wleaf = ustrip(u"m", p.boundary_conductance_model.leafwidth) 186 | gsc = Float32[0.0] 187 | et = Float32[0.0] 188 | 189 | pstranspif = Libdl.dlsym(maespa_photosynlib, :pstranspif_) 190 | ccall(pstranspif, Nothing, ( 191 | Ref{Int32}, 192 | Ref{Int32}, 193 | Ref{Float32}, 194 | Ref{Float32}, 195 | Ref{Float32}, 196 | Ref{Float32}, 197 | Ref{Float32}, 198 | Ref{Float32}, 199 | Ref{Float32}, 200 | Ref{Float32}, 201 | Ref{Float32}, 202 | Ref{Float32}, 203 | Ref{Float32}, 204 | Ref{Float32}, 205 | Ref{Float32}, 206 | Ref{Float32}, 207 | Ref{Int32}, 208 | Ref{Float32}, 209 | Ref{Float32}, 210 | Ref{Float32}, 211 | Ref{Float32}, 212 | Ref{Float32}, 213 | Ref{Float32}, 214 | Ref{Float32}, 215 | Ref{Float32}, 216 | Ref{Float32}, 217 | Ref{Float32}, 218 | Ref{Float32}, 219 | Ref{Float32}, 220 | Ref{Float32}, 221 | Ref{Float32}, 222 | Ref{Float32}, 223 | Ref{Float32}, 224 | Ref{Float32}, 225 | Ref{Int32}, 226 | Ref{Int32}, 227 | Ref{Float32}, 228 | Ref{Float32}, 229 | Ref{Float32}, 230 | Ref{Float32}, 231 | Ref{Float32}, 232 | Ref{Float32}, 233 | Ref{Int32}, 234 | Ref{Float32}, 235 | Ref{Float32}, 236 | Ref{Float32}, 237 | Ref{Float32}, 238 | Ref{Float32}, 239 | Ref{Float32}, 240 | Ref{Float32}, 241 | Ref{Float32}, 242 | Ref{Float32}, 243 | Ref{Float32}, 244 | Ref{Float32}, 245 | Ref{Float32}, 246 | Ref{Float32}, 247 | Ref{Int32}, 248 | Ref{Float32}, 249 | Ref{Float32}, 250 | Ref{Int32}, 251 | Ref{Float32}, 252 | Ref{Float32}, 253 | Ref{Float32}, 254 | Ref{Float32}, 255 | Ref{Float32}, 256 | Ref{Float32}, 257 | Ref{Float32}, 258 | Ref{Float32}, 259 | Ref{Float32}, 260 | Ref{Float32}, 261 | Ref{Float32}, 262 | Ref{Float32}, 263 | Ref{Float32}, 264 | Ref{Float32}, 265 | Ref{Float32}, 266 | Ref{Float32}, 267 | Ref{Bool}), 268 | iday, 269 | ihour, 270 | rdfipt, 271 | tuipt, 272 | tdipt, 273 | rnet, 274 | wind, 275 | par, 276 | tair, 277 | tmove, 278 | ca, 279 | rh, 280 | vpd, 281 | vmfd, 282 | press, 283 | jmax25, 284 | ieco, 285 | eavj, 286 | edvj, 287 | delsj, 288 | vcmax25, 289 | eavc, 290 | edvc, 291 | delsc, 292 | tvjup, 293 | tvjdn, 294 | theta, 295 | ajq, 296 | rd0, 297 | q10f, 298 | k10f, 299 | rtemp, 300 | dayresp, 301 | tbelow, 302 | modelgs, 303 | wsoilmethod, 304 | emaxleaf, 305 | soilmoisture, 306 | smd1, 307 | smd2, 308 | wc1, 309 | wc2, 310 | soildata, 311 | swpexp, 312 | fsoil, 313 | g0, 314 | D0, 315 | gamma, 316 | vpdmin, 317 | g1, 318 | gk, 319 | wleaf, 320 | nsides, 321 | vpara, 322 | vparb, 323 | vparc, 324 | vfun, 325 | sf, 326 | psiv, 327 | itermax, 328 | gsc, 329 | aleaf, 330 | rd, 331 | et, 332 | fheat, 333 | tleaf, 334 | gbh, 335 | plantk, 336 | totsoilres, 337 | minleafwp, 338 | weightedswp, 339 | ktot, 340 | hmshape, 341 | psil, 342 | etest, 343 | ci, 344 | ismaespa) 345 | (tleaf[1], rd[1], emaxleaf[1], psil[1], fsoil[1], aleaf[1], gs[1], ci[1], et[1], gsc[1]) 346 | end 347 | 348 | basetypeof(x) = typeof(x).name.wrapper 349 | 350 | @testset "Tuzet inner" begin 351 | p = MaespaEnergyBalance( 352 | photosynthesis=FvCBPhotosynthesis( 353 | stomatal_conductance=EmaxStomatalConductance( 354 | gs_submodel=BallBerryStomatalConductanceSubModel(), 355 | soilmethod=TuzetSoilMethod(), 356 | ), 357 | flux=DukeFlux(), 358 | compensation=BadgerCollatzCompensation(), 359 | respiration_model=Respiration(), 360 | ), 361 | atol=0.005K, 362 | ) 363 | for varmult in (0.95, 1.0, 1.04) 364 | # p = TuzetEnergyBalance(energy_balance_model=p1) 365 | v = TuzetVars() 366 | v = Flatten.modify(x -> varmult * x, v) 367 | println("var_multiplier: ", varmult) 368 | enbal!(v, p) 369 | tleaf, rd, emaxleaf, psil, fsoil, aleaf, gs, ci, et = run_fortran_enbal(p, v) 370 | @test tleaf ≈ ustrip(v.tleaf |> °C) 371 | @test rd ≈ v.rd.val 372 | @test_broken psil ≈ ustrip(u"MPa", v.psil) 373 | @test fsoil ≈ v.fsoil 374 | @test aleaf ≈ ustrip(u"μmol*m^-2*s^-1", v.aleaf) 375 | @test gs ≈ ustrip(u"mol*m^-2*s^-1", v.gs) 376 | @test ci ≈ ustrip(u"μmol*mol^-1", v.ci) 377 | @test et ≈ ustrip(u"μmol*m^-2*s^-1", v.et) 378 | end 379 | end 380 | 381 | @testset "Emax" begin 382 | p1 = MaespaEnergyBalance( 383 | photosynthesis=FvCBPhotosynthesis( 384 | stomatal_conductance=EmaxStomatalConductance( 385 | gs_submodel=BallBerryStomatalConductanceSubModel(), 386 | soilmethod=EmaxSoilMethod(), 387 | ), 388 | flux=DukeFlux(), 389 | compensation=BadgerCollatzCompensation(), 390 | respiration_model=Respiration(), 391 | ), 392 | atol=0.005K, 393 | ) 394 | p = EmaxEnergyBalance(energy_balance_model=p1) 395 | for varmult in (0.95, 1.0, 1.001), 396 | parmult in (0.9999999, 1.0, 1.0000001) 397 | v = EmaxVars() 398 | v = Flatten.modify(x -> varmult * x, v) 399 | p = Flatten.modify(x -> x * parmult, p) 400 | println("var_multiplier: ", varmult) 401 | enbal!(v, p) 402 | tleaf, rd, emaxleaf, psil, fsoil, aleaf, gs, ci, et = run_fortran_enbal(p1, v); 403 | @test tleaf ≈ ustrip(v.tleaf |> °C) 404 | @test rd ≈ v.rd.val 405 | @test emaxleaf ≈ ustrip(u"mmol*m^-2*s^-1", v.emaxleaf) 406 | @test psil ≈ ustrip(u"MPa", v.psil) 407 | @test fsoil ≈ v.fsoil 408 | @test aleaf ≈ ustrip(u"μmol*m^-2*s^-1", v.aleaf) 409 | @test gs ≈ ustrip(u"mol*m^-2*s^-1", v.gs) 410 | @test ci ≈ ustrip(u"μmol*mol^-1", v.ci) 411 | @test et ≈ ustrip(u"μmol*m^-2*s^-1", v.et) 412 | end 413 | end 414 | 415 | function test_components(submodel, compensation, soilmethod, resp, flux, v) 416 | println("Testing: ", basetypeof.((submodel, compensation, soilmethod, resp, flux))) 417 | p = MaespaEnergyBalance( 418 | photosynthesis_model=FvCBPhotosynthesis( 419 | stomatal_conductance_model=BallBerryStomatalConductance( 420 | gs_submodel=submodel, 421 | soil_model=soilmethod, 422 | ), 423 | compensation_model=compensation, 424 | respiration_model=resp, 425 | flux_model=flux, 426 | ), 427 | atol=0.005K, 428 | ) 429 | enbal!(v, p) 430 | tleaf, rd, emaxleaf, psil, fsoil, aleaf, gs, ci, et = run_fortran_enbal(p, v) 431 | @test tleaf ≈ ustrip(°C, v.tleaf) 432 | @test rd ≈ ustrip(u"μmol*m^-2*s^-1", v.rd) 433 | @test fsoil ≈ v.fsoil 434 | @test aleaf ≈ ustrip(u"μmol*m^-2*s^-1", v.aleaf) 435 | @test gs ≈ ustrip(u"mol*m^-2*s^-1", v.gs) 436 | @test ci ≈ ustrip(u"μmol*mol^-1", v.ci) 437 | @test et ≈ ustrip(u"μmol*m^-2*s^-1", v.et) 438 | end 439 | 440 | @testset "Test combinatorics of MaespaEnergyBalance/BallBerryStomatalConductance models" begin 441 | gs_submodels = (BallBerryStomatalConductanceSubModel(), 442 | LeuningStomatalConductanceSubModel(), 443 | MedlynStomatalConductanceSubModel(),) 444 | compensation = BernacchiCompensation(), BadgerCollatzCompensation() 445 | soilmethods = (NoSoilMethod(), PotentialSoilMethod()) 446 | respiration = (Respiration(),)# AcclimatizedRespiration()) 447 | flux = (DukeFlux(), Flux()) 448 | # Multiply the variable defaults to make sure it handles more values 449 | # Muliplying v.tair by 1.05 (setting it to 313k) breaks things. 450 | # So this range is the current limit. 451 | var_multipliers = (0.96, 1.0, 1.03) 452 | par_multipliers = (0.9, 1.0, 1.04) 453 | EB = MaespaEnergyBalance 454 | for gs in gs_submodels, comp in compensation, sm in soilmethods, 455 | resp in respiration, fl in flux, varmult in var_multipliers, 456 | parmult in par_multipliers 457 | v = Flatten.modify(x -> x * varmult, BallBerryVars()) 458 | # resp fails lower 459 | # gs fails both 460 | comp, sm, fl = map((comp, sm, fl)) do p 461 | Flatten.modify(x -> x * parmult, p) 462 | end 463 | println("parmult: ", parmult, "varmult: ", varmult) 464 | println(map(x -> nameof(typeof(x)), (gs, comp, sm, resp, fl))) 465 | test_components(gs, comp, sm, resp, fl, v) 466 | end 467 | end 468 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The Photosynthesis.jl package is licensed under the GNU Public License, Version 2.0+: 2 | 3 | > Copyright (c) 2018: Rafael Schouten 4 | > Copyright (c) 2013: Remko Duursma, Belinda Medlyn 5 | > This program is free software; you can redistribute it and/or modify 6 | > it under the terms of the GNU General Public License as published by 7 | > the Free Software Foundation; either version 2 of the License, or 8 | > (at your option) any later version. 9 | > 10 | > This program is distributed in the hope that it will be useful, 11 | > but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | > MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | > GNU General Public License for more details. 14 | > 15 | > You should have received a copy of the GNU General Public License along 16 | > with this program; if not, write to the Free Software Foundation, Inc., 17 | > 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | > 19 | > 20 | > GNU GENERAL PUBLIC LICENSE 21 | > Version 2, June 1991 22 | > 23 | > Copyright (C) 1989, 1991 Free Software Foundation, Inc., 24 | > 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 25 | > Everyone is permitted to copy and distribute verbatim copies 26 | > of this license document, but changing it is not allowed. 27 | > 28 | > Preamble 29 | > 30 | > The licenses for most software are designed to take away your 31 | > freedom to share and change it. By contrast, the GNU General Public 32 | > License is intended to guarantee your freedom to share and change free 33 | > software--to make sure the software is free for all its users. This 34 | > General Public License applies to most of the Free Software 35 | > Foundation's software and to any other program whose authors commit to 36 | > using it. (Some other Free Software Foundation software is covered by 37 | > the GNU Lesser General Public License instead.) You can apply it to 38 | > your programs, too. 39 | > 40 | > When we speak of free software, we are referring to freedom, not 41 | > price. Our General Public Licenses are designed to make sure that you 42 | > have the freedom to distribute copies of free software (and charge for 43 | > this service if you wish), that you receive source code or can get it 44 | > if you want it, that you can change the software or use pieces of it 45 | > in new free programs; and that you know you can do these things. 46 | > 47 | > To protect your rights, we need to make restrictions that forbid 48 | > anyone to deny you these rights or to ask you to surrender the rights. 49 | > These restrictions translate to certain responsibilities for you if you 50 | > distribute copies of the software, or if you modify it. 51 | > 52 | > For example, if you distribute copies of such a program, whether 53 | > gratis or for a fee, you must give the recipients all the rights that 54 | > you have. You must make sure that they, too, receive or can get the 55 | > source code. And you must show them these terms so they know their 56 | > rights. 57 | > 58 | > We protect your rights with two steps: (1) copyright the software, and 59 | > (2) offer you this license which gives you legal permission to copy, 60 | > distribute and/or modify the software. 61 | > 62 | > Also, for each author's protection and ours, we want to make certain 63 | > that everyone understands that there is no warranty for this free 64 | > software. If the software is modified by someone else and passed on, we 65 | > want its recipients to know that what they have is not the original, so 66 | > that any problems introduced by others will not reflect on the original 67 | > authors' reputations. 68 | > 69 | > Finally, any free program is threatened constantly by software 70 | > patents. We wish to avoid the danger that redistributors of a free 71 | > program will individually obtain patent licenses, in effect making the 72 | > program proprietary. To prevent this, we have made it clear that any 73 | > patent must be licensed for everyone's free use or not licensed at all. 74 | > 75 | > The precise terms and conditions for copying, distribution and 76 | > modification follow. 77 | > 78 | > GNU GENERAL PUBLIC LICENSE 79 | > TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 80 | > 81 | > 0. This License applies to any program or other work which contains 82 | > a notice placed by the copyright holder saying it may be distributed 83 | > under the terms of this General Public License. The "Program", below, 84 | > refers to any such program or work, and a "work based on the Program" 85 | > means either the Program or any derivative work under copyright law: 86 | > that is to say, a work containing the Program or a portion of it, 87 | > either verbatim or with modifications and/or translated into another 88 | > language. (Hereinafter, translation is included without limitation in 89 | > the term "modification".) Each licensee is addressed as "you". 90 | > 91 | > Activities other than copying, distribution and modification are not 92 | > covered by this License; they are outside its scope. The act of 93 | > running the Program is not restricted, and the output from the Program 94 | > is covered only if its contents constitute a work based on the 95 | > Program (independent of having been made by running the Program). 96 | > Whether that is true depends on what the Program does. 97 | > 98 | > 1. You may copy and distribute verbatim copies of the Program's 99 | > source code as you receive it, in any medium, provided that you 100 | > conspicuously and appropriately publish on each copy an appropriate 101 | > copyright notice and disclaimer of warranty; keep intact all the 102 | > notices that refer to this License and to the absence of any warranty; 103 | > and give any other recipients of the Program a copy of this License 104 | > along with the Program. 105 | > 106 | > You may charge a fee for the physical act of transferring a copy, and 107 | > you may at your option offer warranty protection in exchange for a fee. 108 | > 109 | > 2. You may modify your copy or copies of the Program or any portion 110 | > of it, thus forming a work based on the Program, and copy and 111 | > distribute such modifications or work under the terms of Section 1 112 | > above, provided that you also meet all of these conditions: 113 | > 114 | > a) You must cause the modified files to carry prominent notices 115 | > stating that you changed the files and the date of any change. 116 | > 117 | > b) You must cause any work that you distribute or publish, that in 118 | > whole or in part contains or is derived from the Program or any 119 | > part thereof, to be licensed as a whole at no charge to all third 120 | > parties under the terms of this License. 121 | > 122 | > c) If the modified program normally reads commands interactively 123 | > when run, you must cause it, when started running for such 124 | > interactive use in the most ordinary way, to print or display an 125 | > announcement including an appropriate copyright notice and a 126 | > notice that there is no warranty (or else, saying that you provide 127 | > a warranty) and that users may redistribute the program under 128 | > these conditions, and telling the user how to view a copy of this 129 | > License. (Exception: if the Program itself is interactive but 130 | > does not normally print such an announcement, your work based on 131 | > the Program is not required to print an announcement.) 132 | > 133 | > These requirements apply to the modified work as a whole. If 134 | > identifiable sections of that work are not derived from the Program, 135 | > and can be reasonably considered independent and separate works in 136 | > themselves, then this License, and its terms, do not apply to those 137 | > sections when you distribute them as separate works. But when you 138 | > distribute the same sections as part of a whole which is a work based 139 | > on the Program, the distribution of the whole must be on the terms of 140 | > this License, whose permissions for other licensees extend to the 141 | > entire whole, and thus to each and every part regardless of who wrote it. 142 | > 143 | > Thus, it is not the intent of this section to claim rights or contest 144 | > your rights to work written entirely by you; rather, the intent is to 145 | > exercise the right to control the distribution of derivative or 146 | > collective works based on the Program. 147 | > 148 | > In addition, mere aggregation of another work not based on the Program 149 | > with the Program (or with a work based on the Program) on a volume of 150 | > a storage or distribution medium does not bring the other work under 151 | > the scope of this License. 152 | > 153 | > 3. You may copy and distribute the Program (or a work based on it, 154 | > under Section 2) in object code or executable form under the terms of 155 | > Sections 1 and 2 above provided that you also do one of the following: 156 | > 157 | > a) Accompany it with the complete corresponding machine-readable 158 | > source code, which must be distributed under the terms of Sections 159 | > 1 and 2 above on a medium customarily used for software interchange; or, 160 | > 161 | > b) Accompany it with a written offer, valid for at least three 162 | > years, to give any third party, for a charge no more than your 163 | > cost of physically performing source distribution, a complete 164 | > machine-readable copy of the corresponding source code, to be 165 | > distributed under the terms of Sections 1 and 2 above on a medium 166 | > customarily used for software interchange; or, 167 | > 168 | > c) Accompany it with the information you received as to the offer 169 | > to distribute corresponding source code. (This alternative is 170 | > allowed only for noncommercial distribution and only if you 171 | > received the program in object code or executable form with such 172 | > an offer, in accord with Subsection b above.) 173 | > 174 | > The source code for a work means the preferred form of the work for 175 | > making modifications to it. For an executable work, complete source 176 | > code means all the source code for all modules it contains, plus any 177 | > associated interface definition files, plus the scripts used to 178 | > control compilation and installation of the executable. However, as a 179 | > special exception, the source code distributed need not include 180 | > anything that is normally distributed (in either source or binary 181 | > form) with the major components (compiler, kernel, and so on) of the 182 | > operating system on which the executable runs, unless that component 183 | > itself accompanies the executable. 184 | > 185 | > If distribution of executable or object code is made by offering 186 | > access to copy from a designated place, then offering equivalent 187 | > access to copy the source code from the same place counts as 188 | > distribution of the source code, even though third parties are not 189 | > compelled to copy the source along with the object code. 190 | > 191 | > 4. You may not copy, modify, sublicense, or distribute the Program 192 | > except as expressly provided under this License. Any attempt 193 | > otherwise to copy, modify, sublicense or distribute the Program is 194 | > void, and will automatically terminate your rights under this License. 195 | > However, parties who have received copies, or rights, from you under 196 | > this License will not have their licenses terminated so long as such 197 | > parties remain in full compliance. 198 | > 199 | > 5. You are not required to accept this License, since you have not 200 | > signed it. However, nothing else grants you permission to modify or 201 | > distribute the Program or its derivative works. These actions are 202 | > prohibited by law if you do not accept this License. Therefore, by 203 | > modifying or distributing the Program (or any work based on the 204 | > Program), you indicate your acceptance of this License to do so, and 205 | > all its terms and conditions for copying, distributing or modifying 206 | > the Program or works based on it. 207 | > 208 | > 6. Each time you redistribute the Program (or any work based on the 209 | > Program), the recipient automatically receives a license from the 210 | > original licensor to copy, distribute or modify the Program subject to 211 | > these terms and conditions. You may not impose any further 212 | > restrictions on the recipients' exercise of the rights granted herein. 213 | > You are not responsible for enforcing compliance by third parties to 214 | > this License. 215 | > 216 | > 7. If, as a consequence of a court judgment or allegation of patent 217 | > infringement or for any other reason (not limited to patent issues), 218 | > conditions are imposed on you (whether by court order, agreement or 219 | > otherwise) that contradict the conditions of this License, they do not 220 | > excuse you from the conditions of this License. If you cannot 221 | > distribute so as to satisfy simultaneously your obligations under this 222 | > License and any other pertinent obligations, then as a consequence you 223 | > may not distribute the Program at all. For example, if a patent 224 | > license would not permit royalty-free redistribution of the Program by 225 | > all those who receive copies directly or indirectly through you, then 226 | > the only way you could satisfy both it and this License would be to 227 | > refrain entirely from distribution of the Program. 228 | > 229 | > If any portion of this section is held invalid or unenforceable under 230 | > any particular circumstance, the balance of the section is intended to 231 | > apply and the section as a whole is intended to apply in other 232 | > circumstances. 233 | > 234 | > It is not the purpose of this section to induce you to infringe any 235 | > patents or other property right claims or to contest validity of any 236 | > such claims; this section has the sole purpose of protecting the 237 | > integrity of the free software distribution system, which is 238 | > implemented by public license practices. Many people have made 239 | > generous contributions to the wide range of software distributed 240 | > through that system in reliance on consistent application of that 241 | > system; it is up to the author/donor to decide if he or she is willing 242 | > to distribute software through any other system and a licensee cannot 243 | > impose that choice. 244 | > 245 | > This section is intended to make thoroughly clear what is believed to 246 | > be a consequence of the rest of this License. 247 | > 248 | > 8. If the distribution and/or use of the Program is restricted in 249 | > certain countries either by patents or by copyrighted interfaces, the 250 | > original copyright holder who places the Program under this License 251 | > may add an explicit geographical distribution limitation excluding 252 | > those countries, so that distribution is permitted only in or among 253 | > countries not thus excluded. In such case, this License incorporates 254 | > the limitation as if written in the body of this License. 255 | > 256 | > 9. The Free Software Foundation may publish revised and/or new versions 257 | > of the General Public License from time to time. Such new versions will 258 | > be similar in spirit to the present version, but may differ in detail to 259 | > address new problems or concerns. 260 | > 261 | > Each version is given a distinguishing version number. If the Program 262 | > specifies a version number of this License which applies to it and "any 263 | > later version", you have the option of following the terms and conditions 264 | > either of that version or of any later version published by the Free 265 | > Software Foundation. If the Program does not specify a version number of 266 | > this License, you may choose any version ever published by the Free Software 267 | > Foundation. 268 | > 269 | > 10. If you wish to incorporate parts of the Program into other free 270 | > programs whose distribution conditions are different, write to the author 271 | > to ask for permission. For software which is copyrighted by the Free 272 | > Software Foundation, write to the Free Software Foundation; we sometimes 273 | > make exceptions for this. Our decision will be guided by the two goals 274 | > of preserving the free status of all derivatives of our free software and 275 | > of promoting the sharing and reuse of software generally. 276 | > 277 | > NO WARRANTY 278 | > 279 | > 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 280 | > FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 281 | > OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 282 | > PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 283 | > OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 284 | > MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 285 | > TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 286 | > PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 287 | > REPAIR OR CORRECTION. 288 | > 289 | > 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 290 | > WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 291 | > REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 292 | > INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 293 | > OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 294 | > TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 295 | > YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 296 | > PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 297 | > POSSIBILITY OF SUCH DAMAGES. 298 | > 299 | > END OF TERMS AND CONDITIONS 300 | > 301 | --------------------------------------------------------------------------------