-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Calculation of annual fAPAR and LAI values from a fitted PModel and precipitation data #348
Comments
For the parameter z, insteady of directly giving a global fitted value (12.227 mol m-2 year-1), Shirely suggested it is better to allow the users to fit this parameter values based on their own satellite/climate data. z accounts for the costs of building and maintaining leaves and the total below-ground allocation required to support the nutrient demand of those leaves and should have a globally fitted value for simplicity. Considering the fitted z value could vary with inputted satellite data, thus, it is better to fit z value based on the satellite data users have. Methods to fit z values are: In equation 1, fAPARmax is calculated as the minimum values of energy-limited fAPAR (fAPARc, Eqn5) and water-limited fAPAR (fAPARw, Eqn 4). (Eqn. 4) fAPARw= [𝑐𝑎 (1–𝜒)/1.6 𝐷] [𝑓0 𝑃/𝐴0] The minimum of energy-limited fAPAR (fAPARc, Eqn 5) and water-limited fAPAR (fAPARw, Eqn4) was smoothly approximated using the log-sum-exp function: (Eqn.6) 𝑚𝑖𝑛{𝑓𝐴𝑃𝐴𝑅𝑐,𝑓𝐴𝑃𝐴𝑅𝑤}≈−1𝐾𝑙𝑜𝑔[exp(−𝐾.𝑓𝐴𝑃𝐴𝑅𝑐)+exp(−𝐾.𝑓𝐴𝑃𝐴𝑅𝑤)] where K (≥ 1) is a constant. The larger the value for K, the closer the approximation is to minimum function (here we adopted K=10). With fAPARw and fAPARc defined by equations (4) and (5) respectively, parameter z can be fitted using non-linear regression (nls function in R), with user's own satellite data as inputs. |
For the inputted data (P) of Eqn 1: |
@davidorme , working on the implementation I am wondering: Why does fAPAR_max need to be a class, rather than just a function (same for LAI_max)? Is there a lot of stuff we need to handle later on which is useful to keep in a class? |
@MarionBWeinzierl My initial thoughts for the workflow was that this calculation would be more of a building block for other calculations. I'm less sure now, but I think this should also tell users which of the two terms (water or energy) limits |
I've been thinking about the API for this some more. Big picture, this algorithm is making a prediction whether a site is water or energy limited in a given year and the outputs should give annual predictions of :
With three outputs, I think it might make sense to package the results in a simple dataclass. Users might want average behaviour over many years of inputs, but we should make annual predictions and then users can average over those if they want to (or provide an I think there are two ways people will want to use this.
@Shirleycwj - can you comment on the thoughts above. Does that seem like an API you want for this and does the application across different data scales make sense? I basically think people might want this from monthly CRU models all the way down to subdaily WFDE models, and I don't see a fundamental reason to restrict it? |
I agree with David's idea of providing the option of annual prediction of water- and energy-limited region, as you could observed the shift of energy-limited area to water-limited area through recent decade and that's something that people would be interested in. Also, I used multi-year average of inputs to estimate spatially and temporally constant value for z and f0 and consenquently for annual maximum fAPAR, but once we are able to relate z and f0 with environmental variables, it certainly makes more sense to predict fAPARmax and LAImax year by year in order to investigate temporal trends and prepare for future projections - I think this is what we aim for. For combining the fAPARmax with various version of Pmodel, I think it's reasonable to add output 'potential GPP (A0)' as the product of LUE and ppfd. On an annual basis, the total potential GPP would be sum of A0 in a year defined by date.time, whether it's running on monthly or subdaily timesteps. If the user provide their own GPP and benchmarking fAPAR they used to evaluate predicted fAPAR, then they could simply calculate A0 as GPP/fAPAR (both should be in the same timestep). With SPLASH included in Pyrealm, I actually thought we already have precipitation input included? Of course, SPLASH require daily inputs of precipitation while in estimating fAPARmax we only need annual precipitation. So there are two ways to do this: (1) user provide precipitation data with time axis that could be summed up to annual totals; Climate datasets rarely provide annual precipitation directly; (2) if user adopted SPLASH to calculate soil moisture stress, then we could directly use daily precipitation to calculate annual totals and apply to the equation. But I guess that would depend on how SPLASH is incorporated into pyrealm. It's a little bit more complicated when it comes to the growing season. I think we should open up the option of different methods to define growing season but use the temperature threshold as the simplest, default method. Using the temperature threshold only (above 0 degree), timestep up to monthly is actually acceptable due to temperature oscillation at the beginning and end of the growing season. Currently there are no absolute consensus on how growing season is defined (thermal or radiative or productive growing season, see Körner et al 2023 Ecology letters and Fu et al 2022 Global change biology ) or method to define growing season...so we could ask the user to provide DOY of start of the growning season (SOS) and end of growing season (EOS) to calculate growing season value for input variables. If the user works on phenology then they would have such information, otherwise they could use our simplified way to define productive growning season. To conclude, I think it's reasonable to provide a universal method (or function) to apply across different version of P model with different timesteps. Thanks David for putting things together! |
Thanks @Shirleycwj! There's a lot in there! Some thoughts:
Interesting - my assumption so far has been that users explicitly provide a model that is set up to calculate potential GPP, but we could have a @Shirleycwj - how does "potential GPP" work with I'm inclined to keep it that users provide a model setup to reflect what they think represents potential GPP, rather than trying to assert that it is one particular product? We should document this of course!
This option is intended purely to allow people to apply the Stocker or Mengoli corrections to GPP (or any similar posthoc corrections down the line). So we wouldn't need
Yes - absolutely. If someone has run SPLASH already (to get AET and PET and hence AI) then that does contain precipitation data and we could use that SPLASH model as an input. However we do also need to support models where the user hasn't run SPLASH and is just using e.g. monthly precipitation from CRU and an AI from the literature.
It really, really, really is. I think the only sensible way forward here is to get users to provide a boolean array showing which observations along the time access are "growing season" or not. We can provide tools to help with that - the tricky one is going from subdaily models. If we're going with temperature threshold is the reference mean daily temp? That's most consistent with what you'd do at coarser temporal scales, but you could also do mean daily temp during daylight hours or even during the acclimation window. Probably start with the easy one though 😄 I've added that bit of the comment to the relevant issue here: #352 (comment) |
I'm wondering if what we are after here is a class (or dataclass) that broadly looks like: class FaparLimitation():
def __init__(
self,
annual_total_potential_gpp,
annual_mean_ca,
annual_mean_chi,
annual_mean_vpd,
annual_total_precip,
aridity_index,
z=12.227,
k=0.5
):
...
@classmethod
def from_pmodel(
cls,
pmodel,
growing_season,
datetimes,
precip,
aridity_index,
z=12.227,
k=0.5
):
...
# do stuff to calculate inputs
return cls('calculated inputs here')
@classmethod
def from_pmodel_and_splash(
cls,
pmodel,
growing_season,
splash_model,
aridity_index,
z=12.227,
k=0.5
):
...
# do stuff to calculate inputs
return cls('calculated inputs here') We could definitely merge those two class methods but it seems easier to keep them as separate interfaces and not have to do a bunch of logic on which combinations of arguments are not |
Thanks @davidorme for the clarification. For the definition of potential GPP, I think we generally associate with only one constraint, so that this constraint can be explicitly separated and quantified. For example, some literature would describe the potential GPP without N limitation so that they could focus on nutrient constraint. Here becuase seasonal leaf area dynamics is what we looking for, we explicitly defined potential GPP as carbon uptake not constrained by leaf area (fAPAR or LAI), and vegetation are still realistically affected by temperature or water availability. This is the foundation of how we apply energy- or water-limited scheme and how the energy- and water-limited region can be defined, if it's under well-watered condition, then we wouldn't have water-limited region. I think the applying the boolean array to define growing season is a nice and clear one. For subdialy model, my personal experience would be daytime temperature is more accurate and realistic, although the improvement compared to daily temperautre is overall marginal (might be important at site level so we could think about the function of calculating daytime temperature if possible). On the wider audience I agree that the two method class should be separated as people could experiment on different ways to calculate water limitation. But then they should add their own panel factor of water limitation between the step from_pmodel to FaparLimitation right? |
I started a "dirty" implementation (skipping the pre-commit hooks to share it with you) on a branch here. This is very much work in progress and lots is tbd at the moment, but let me know if I am getting something completely wrong (e.g., guessed the data type of a parameter wrong). I am now looking through @davidorme 's Would it make sense to separate out the "data wrangling" code into it's own file/class? And/or will each data set look completely different and need separate handling outside of PyRealm? |
@MarionBWeinzierl I think that sketch looks great. I think the data wrangling code has to be something users come up with. We'll provide a demo using the example data to show some example boilerplate. I think most people will set it up using the PModel interface, and we already have a fair bit of code showing how to do that. The main complexity in this is juggling the time resolutions of the data and setting the growing season days. That's kinda hard to pin down, but I think we can get this code up and running and then refine the API for how people select what the "growing season conditions" actually mean. |
This is the first step in implementing #347 and the description below is taken from the draft implementation document in the meta issue.
Theory
The predicted fAPARmax / LAImax mainly has two roles:
The equations to calculate fAPARmax (Eqn 1) and LAImax (Eqn 2) are:
(Eqn. 1)$f_{APAR_{max}}= min\left[ (1 – z/(kA_0)), [c_a (1–\chi)/1.6 D] [f_0 P/A_0]\right]$ $LAI_{max}= –(1/k) ln (1 –f_{APAR_{opt}})$
(Eqn. 2)
Of those terms, the following are new constants across the calculations. There aren't too many, so I think these are parameters to the calculation but we might want to bundle them into a new constants class (
MaxFAPARConst
?)(Eqn 3)$f_0=0.65 e^{-b ln^2 \left(\frac{AI}{1.9}\right) }$
where 0.65 is the maximum value, 1.9 is the AI at which this maximum occurs, and b = 0.604169. For AI , see below.
The following need to be be extracted from a P Model
And lastly, precipitation data and aridity data is needed:
*AI is a climatological estimate of local aridity index, typically calculated as total PET over total precipitation for an appropriate period, typically at least 20 years.
An Example to predict fAPARmax:
Implementation
This is a draft workflow for implementing the calculation
SubdailyPModel
andSPLASH
of insisting this is the first axis of the arrays.So, I think the signature will look like this:
The requirement for precipitation and aridity index ties this quite closely to the SPLASH module. The demonstration implementation should probably use SPLASH to set up the scenario.
The text was updated successfully, but these errors were encountered: