The T Model module
Run this notebook
Read the guide on setting up your computer to run Jupyter notebooks
Download
this notebookas a Jupyter notebook.
The T Model (Li et al., 2014) provides a model of both:
stem :term:
allometry, given a set of stem traits for a plant functional type (PFT), anda carbon allocation model, given stem allometry and potential GPP.
from matplotlib import pyplot as plt
import numpy as np
import pandas as pd
from pyrealm.demography.flora import PlantFunctionalType, Flora
from pyrealm.demography.tmodel import (
StemAllocation,
StemAllometry,
calculate_whole_crown_gpp,
)
To generate predictions under the T Model, we need a Flora object providing the trait values for each of the PFTs to be modelled:
# Three PFTS
short_pft = PlantFunctionalType(name="short", h_max=10)
medium_pft = PlantFunctionalType(name="medium", h_max=20)
tall_pft = PlantFunctionalType(name="tall", h_max=30)
# Combine into a Flora instance
flora = Flora([short_pft, medium_pft, tall_pft])
Stem allometry
We can visualise how the stem size, canopy size and various masses of PFTs change with
stem diameter by using the StemAllometry
class. Creating a StemAllometry instance needs an existing Flora instance and an
array of values for diameter at breast height (DBH, metres). The returned class contains
the predictions of the T Model for:
Stem height (
stem_height, m),Crown area (
crown_area, m2),Crown fraction (
crown_fraction, -),Stem mass (
stem_mass, kg),Foliage mass (
foliage_mass, kg),Sapwood mass (
sapwood_mass, kg),Crown radius scaling factor (
crown_r0, -), andHeight of maximum crown radius (
crown_z_max, m).
Note that stem_height denotes the total
tree height, as used interchangeable in Li et al. (2014), rather than just the height
of the trunk below the canopy.
The DBH input can be a scalar array or a one dimensional array providing a single value for each PFT. This then calculates a single estimate at the given size for each stem.
# Calculate a single prediction
single_allometry = StemAllometry(stem_traits=flora, at_dbh=np.array([0.1, 0.1, 0.1]))
/home/docs/checkouts/readthedocs.org/user_builds/pyrealm/checkouts/latest/pyrealm/core/experimental.py:72: ExperimentalFeatureWarning: 'Be aware that StemAllometry is an experimental feature of pyrealm and the implementation and API may change within major versions.'
warn(qualname, ExperimentalFeatureWarning)
The StemAllometry() class provides the
to_pandas() method to export the stem
data for data exploration.
single_allometry.to_pandas().transpose()
| column_stem_index | 0 | 1 | 2 |
|---|---|---|---|
| dbh | 0.100000 | 0.100000 | 0.100000 |
| stem_height | 6.865138 | 8.802033 | 9.620475 |
| crown_area | 1.814782 | 2.326795 | 2.543148 |
| crown_fraction | 0.591822 | 0.758796 | 0.829351 |
| stem_mass | 5.391867 | 6.913100 | 7.555903 |
| foliage_mass | 0.233329 | 0.299159 | 0.326976 |
| fine_root_mass | 0.555323 | 0.711999 | 0.778203 |
| reproductive_tissue_mass | 0.000000 | 0.000000 | 0.000000 |
| sapwood_mass | 4.493533 | 6.510900 | 7.335868 |
| crown_r0 | 0.261731 | 0.296362 | 0.309834 |
| crown_z_max | 5.837310 | 7.484219 | 8.180126 |
However, the DBH values can also be a column array (an N x 1 array). In this case, the
predictions are made at each DBH value for each PFT and the allometry attributes with
predictions arranged with each PFT as a column and each DBH prediction as a row. This
makes them convenient to plot using matplotlib.
# Column array of DBH values from 0.01 to 1.6 metres
dbh_col = np.arange(0.01, 1.6, 0.01)[:, None]
# Get the predictions
allometries = StemAllometry(stem_traits=flora, at_dbh=dbh_col)
/home/docs/checkouts/readthedocs.org/user_builds/pyrealm/checkouts/latest/pyrealm/core/experimental.py:72: ExperimentalFeatureWarning: 'Be aware that StemAllometry is an experimental feature of pyrealm and the implementation and API may change within major versions.'
warn(qualname, ExperimentalFeatureWarning)
The code below shows how to use the returned allometries to generate a plot of the
scaling relationships across all of the PFTs in a Flora instance.
fig, axes = plt.subplots(ncols=2, nrows=4, sharex=True, figsize=(10, 10))
plot_details = [
("stem_height", "Stem height (m)"),
("crown_area", "Crown area (m2)"),
("crown_fraction", "Crown fraction (-)"),
("stem_mass", "Stem mass (kg)"),
("foliage_mass", "Foliage mass (kg)"),
("sapwood_mass", "Sapwood mass (kg)"),
("crown_r0", "Crown scaling factor (-)"),
("crown_z_max", "Height of maximum\ncrown radius (m)"),
]
for ax, (var, ylab) in zip(axes.flatten(), plot_details):
ax.plot(dbh_col, getattr(allometries, var), label=flora.name)
ax.set_xlabel("Diameter at breast height (m)")
ax.set_ylabel(ylab)
if var == "sapwood_mass":
ax.legend(frameon=False)
The to_pandas() method of the
StemAllometry() class can still be used, but
the values are stacked into columns along with a index showing the different cohorts.
allometries.to_pandas().transpose()
| column_stem_index | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| dbh | 0.010000 | 0.020000 | 0.030000 | 0.040000 | 0.050000 | 0.060000 | 0.070000 | 0.080000 | 0.090000 | 0.100000 | ... | 1.500000 | 1.510000 | 1.520000 | 1.530000 | 1.540000 | 1.550000 | 1.560000 | 1.570000 | 1.580000 | 1.590000 |
| stem_height | 1.095248 | 2.070539 | 2.939011 | 3.712364 | 4.401016 | 5.014244 | 5.560308 | 6.046564 | 6.479563 | 6.865138 | ... | 29.909173 | 29.912618 | 29.915933 | 29.919121 | 29.922189 | 29.925140 | 29.927979 | 29.930711 | 29.933339 | 29.935867 |
| crown_area | 0.028953 | 0.109468 | 0.233076 | 0.392542 | 0.581699 | 0.795301 | 1.028897 | 1.278715 | 1.541570 | 1.814782 | ... | 118.596191 | 119.400584 | 120.204633 | 121.008349 | 121.811741 | 122.614819 | 123.417592 | 124.220067 | 125.022254 | 125.824161 |
| crown_fraction | 0.944179 | 0.892474 | 0.844543 | 0.800079 | 0.758796 | 0.720437 | 0.684767 | 0.651569 | 0.620648 | 0.591822 | ... | 0.171892 | 0.170773 | 0.169668 | 0.168577 | 0.167500 | 0.166436 | 0.165385 | 0.164346 | 0.163320 | 0.162307 |
| stem_mass | 0.008602 | 0.065048 | 0.207746 | 0.466509 | 0.864138 | 1.417744 | 2.139857 | 3.039335 | 4.122120 | 5.391867 | ... | 5285.387210 | 5356.710860 | 5428.497008 | 5500.745789 | 5573.457339 | 5646.631792 | 5720.269284 | 5794.369950 | 5868.933926 | 5943.961347 |
| foliage_mass | 0.003722 | 0.014074 | 0.029967 | 0.050470 | 0.074790 | 0.102253 | 0.132287 | 0.164406 | 0.198202 | 0.233329 | ... | 15.248082 | 15.351504 | 15.454881 | 15.558216 | 15.661510 | 15.764762 | 15.867976 | 15.971151 | 16.074290 | 16.177392 |
| fine_root_mass | 0.008859 | 0.033497 | 0.071321 | 0.120118 | 0.178000 | 0.243362 | 0.314843 | 0.391287 | 0.471720 | 0.555323 | ... | 36.290435 | 36.536579 | 36.782618 | 37.028555 | 37.274393 | 37.520135 | 37.765783 | 38.011340 | 38.256810 | 38.502193 |
| reproductive_tissue_mass | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| sapwood_mass | 0.008575 | 0.064296 | 0.202726 | 0.447864 | 0.813863 | 1.306940 | 1.927216 | 2.670347 | 3.528914 | 4.493533 | ... | 1660.863220 | 1673.344166 | 1685.816687 | 1698.280999 | 1710.737312 | 1723.185835 | 1735.626770 | 1748.060318 | 1760.486674 | 1772.906027 |
| crown_r0 | 0.033059 | 0.064282 | 0.093798 | 0.121727 | 0.148181 | 0.173264 | 0.197074 | 0.219700 | 0.241227 | 0.261731 | ... | 2.115821 | 2.122984 | 2.130121 | 2.137230 | 2.144313 | 2.151370 | 2.158401 | 2.165407 | 2.172387 | 2.179343 |
| crown_z_max | 0.931271 | 1.760544 | 2.498991 | 3.156560 | 3.742109 | 4.263526 | 4.727835 | 5.141291 | 5.509462 | 5.837310 | ... | 25.431262 | 25.434191 | 25.437009 | 25.439720 | 25.442328 | 25.444838 | 25.447252 | 25.449575 | 25.451809 | 25.453959 |
11 rows × 477 columns
Productivity allocation
The T Model also predicts how GPP will be allocated to respiration, turnover
and growth for stems with a given PFT and allometry using the
StemAllometry() class.
This requires an estimate of the GPP available to a stem. The original implementation of the T Model implemented this (Equation 12, Li et al., 2014)using an estimate of the potential GPP per square metre (\(P_0\)), scaled up to the crown area of the stem (\(A_c\)) and using the Beer-Lambert equation to estimate the proportion of potential GPP captured by the crown as a function of the canopy light extinction coefficient (\(k\)) and the canopy leaf area index (\(L\)):
This is implemented in the function calculate_whole_crown_gpp:
whole_crown_gpp = calculate_whole_crown_gpp(
potential_gpp=np.array([55]),
crown_area=single_allometry.crown_area,
par_ext=flora.par_ext,
lai=flora.lai,
)
print(whole_crown_gpp)
[[59.23205397 75.94347843 83.00495708]]
Those realised stem GPP values can then be provided to the StemAllocation class:
single_allocation = StemAllocation(
stem_traits=flora, stem_allometry=single_allometry, whole_crown_gpp=whole_crown_gpp
)
single_allocation
/home/docs/checkouts/readthedocs.org/user_builds/pyrealm/checkouts/latest/pyrealm/core/experimental.py:72: ExperimentalFeatureWarning: 'Be aware that StemAllocation is an experimental feature of pyrealm and the implementation and API may change within major versions.'
warn(qualname, ExperimentalFeatureWarning)
StemAllocation: Prediction for 3 stems at 1 observations.
The to_pandas() method of the
StemAllocation() class can be used to
export data for exploration.
single_allocation.to_pandas().transpose()
| column_stem_index | 0 | 1 | 2 |
|---|---|---|---|
| whole_crown_gpp | 59.232054 | 75.943478 | 83.004957 |
| sapwood_respiration | 0.197715 | 0.286480 | 0.322778 |
| foliar_respiration | 5.923205 | 7.594348 | 8.300496 |
| fine_root_respiration | 0.507010 | 0.650055 | 0.710500 |
| reproductive_tissue_respiration | 0.000000 | 0.000000 | 0.000000 |
| npp | 31.562474 | 40.447557 | 44.202710 |
| foliage_turnover | 0.058332 | 0.074790 | 0.081744 |
| fine_root_turnover | 0.533965 | 0.684615 | 0.748272 |
| reproductive_tissue_turnover | 0.000000 | 0.000000 | 0.000000 |
| delta_dbh | 0.208607 | 0.191874 | 0.186059 |
| delta_stem_mass | 28.453546 | 36.316410 | 39.632111 |
| delta_foliage_mass | 0.744565 | 0.997557 | 1.106681 |
| delta_fine_root_mass | 1.772066 | 2.374186 | 2.633901 |
Using a column array of potential GPP values can be used to predict multiple estimates of allocation per stem. In the first example, the code takes the allometric predictions from above and calculates the GPP allocation for stems of varying size with the same potential GPP:
# Calculate the stem GPP from potential GPP following the Li et al model
potential_gpp = np.full((dbh_col.size, 1), fill_value=5)
whole_crown_gpp = calculate_whole_crown_gpp(
potential_gpp=potential_gpp,
crown_area=single_allometry.crown_area,
par_ext=flora.par_ext,
lai=flora.lai,
)
# Calculate the T Model allocation of that GPP
allocation = StemAllocation(
stem_traits=flora, stem_allometry=allometries, whole_crown_gpp=whole_crown_gpp
)
/home/docs/checkouts/readthedocs.org/user_builds/pyrealm/checkouts/latest/pyrealm/core/experimental.py:72: ExperimentalFeatureWarning: 'Be aware that StemAllocation is an experimental feature of pyrealm and the implementation and API may change within major versions.'
warn(qualname, ExperimentalFeatureWarning)
fig, axes = plt.subplots(ncols=2, nrows=5, sharex=True, figsize=(10, 12))
plot_details = [
("whole_crown_gpp", "whole_crown_gpp"),
("sapwood_respiration", "sapwood_respiration"),
("foliar_respiration", "foliar_respiration"),
("fine_root_respiration", "fine_root_respiration"),
("npp", "npp"),
("foliage_turnover", "foliage_turnover"),
("fine_root_turnover", "fine_root_turnover"),
("delta_dbh", "delta_dbh"),
("delta_stem_mass", "delta_stem_mass"),
("delta_foliage_mass", "delta_foliage_mass"),
]
axes = axes.flatten()
for ax, (var, ylab) in zip(axes, plot_details):
ax.plot(dbh_col, getattr(allocation, var), label=flora.name)
ax.set_xlabel("Diameter at breast height (m)")
ax.set_ylabel(ylab)
if var == "whole_crown_gpp":
ax.legend(frameon=False)
# Delete unused panel in 5 x 2 grid
fig.delaxes(axes[-1])
An alternative calculation is to make allocation predictions for varying potential GPP for constant allometries:
# Column array of identical DBH values
dbh_constant = np.full((50, 1), fill_value=0.2)
# Get the allometric predictions for those stems
constant_allometries = StemAllometry(stem_traits=flora, at_dbh=dbh_constant)
# Calculate the stem GPP with _varying_ potential GPP
potential_gpp_varying = np.linspace(1, 10, num=50)[:, None]
whole_crown_gpp_varying = calculate_whole_crown_gpp(
potential_gpp=potential_gpp_varying,
crown_area=constant_allometries.crown_area,
par_ext=flora.par_ext,
lai=flora.lai,
)
# Calculate the resulting changes in the allocation with varying productivity
allocation_2 = StemAllocation(
stem_traits=flora,
stem_allometry=constant_allometries,
whole_crown_gpp=whole_crown_gpp_varying,
)
/home/docs/checkouts/readthedocs.org/user_builds/pyrealm/checkouts/latest/pyrealm/core/experimental.py:72: ExperimentalFeatureWarning: 'Be aware that StemAllometry is an experimental feature of pyrealm and the implementation and API may change within major versions.'
warn(qualname, ExperimentalFeatureWarning)
fig, axes = plt.subplots(ncols=2, nrows=5, sharex=True, figsize=(10, 12))
axes = axes.flatten()
for ax, (var, ylab) in zip(axes, plot_details):
ax.plot(potential_gpp_varying, getattr(allocation_2, var), label=flora.name)
ax.set_xlabel("Potential GPP")
ax.set_ylabel(ylab)
if var == "whole_crown_gpp":
ax.legend(frameon=False)
# Delete unused panel in 5 x 2 grid
fig.delaxes(axes[-1])
As before, the to_pandas() method of the
StemAllometry() class can be used to export
the data for each stem:
allocation.to_pandas().transpose()
| column_stem_index | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| whole_crown_gpp | 5.384732 | 5.384732 | 5.384732 | 5.384732 | 5.384732 | 5.384732 | 5.384732 | 5.384732 | 5.384732 | 5.384732 | ... | 7.545905 | 7.545905 | 7.545905 | 7.545905 | 7.545905 | 7.545905 | 7.545905 | 7.545905 | 7.545905 | 7.545905 |
| sapwood_respiration | 0.000377 | 0.002829 | 0.008920 | 0.019706 | 0.035810 | 0.057505 | 0.084797 | 0.117495 | 0.155272 | 0.197715 | ... | 73.077982 | 73.627143 | 74.175934 | 74.724364 | 75.272442 | 75.820177 | 76.367578 | 76.914654 | 77.461414 | 78.007865 |
| foliar_respiration | 0.538473 | 0.538473 | 0.538473 | 0.538473 | 0.538473 | 0.538473 | 0.538473 | 0.538473 | 0.538473 | 0.538473 | ... | 0.754591 | 0.754591 | 0.754591 | 0.754591 | 0.754591 | 0.754591 | 0.754591 | 0.754591 | 0.754591 | 0.754591 |
| fine_root_respiration | 0.008089 | 0.030583 | 0.065116 | 0.109667 | 0.162514 | 0.222190 | 0.287451 | 0.357245 | 0.430681 | 0.507010 | ... | 33.133167 | 33.357896 | 33.582530 | 33.807071 | 34.031521 | 34.255883 | 34.480160 | 34.704354 | 34.928467 | 35.152503 |
| reproductive_tissue_respiration | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| npp | 2.902676 | 2.887708 | 2.863334 | 2.830131 | 2.788761 | 2.739938 | 2.684406 | 2.622911 | 2.556184 | 2.484920 | ... | -59.651900 | -60.116235 | -60.580290 | -61.044072 | -61.507589 | -61.970847 | -62.433854 | -62.896616 | -63.359140 | -63.821432 |
| foliage_turnover | 0.000931 | 0.003519 | 0.007492 | 0.012617 | 0.018697 | 0.025563 | 0.033072 | 0.041102 | 0.049550 | 0.058332 | ... | 3.812020 | 3.837876 | 3.863720 | 3.889554 | 3.915377 | 3.941191 | 3.966994 | 3.992788 | 4.018572 | 4.044348 |
| fine_root_turnover | 0.008519 | 0.032209 | 0.068578 | 0.115498 | 0.171154 | 0.234002 | 0.302733 | 0.376237 | 0.453577 | 0.533965 | ... | 34.894649 | 35.131326 | 35.367902 | 35.604380 | 35.840762 | 36.077053 | 36.313253 | 36.549366 | 36.785394 | 37.021340 |
| reproductive_tissue_turnover | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| delta_dbh | 0.581376 | 0.205379 | 0.107872 | 0.067370 | 0.046323 | 0.033812 | 0.025671 | 0.020015 | 0.015885 | 0.012748 | ... | -0.013768 | -0.013780 | -0.013793 | -0.013805 | -0.013816 | -0.013828 | -0.013840 | -0.013851 | -0.013862 | -0.013873 |
| delta_stem_mass | 1.471862 | 1.929432 | 2.118555 | 2.188913 | 2.191902 | 2.150818 | 2.078310 | 1.982189 | 1.867750 | 1.738829 | ... | -97.877197 | -98.603832 | -99.330079 | -100.055947 | -100.781447 | -101.506589 | -102.231382 | -102.955836 | -103.679960 | -104.403763 |
| delta_foliage_mass | 0.420522 | 0.272943 | 0.197843 | 0.151806 | 0.120416 | 0.097502 | 0.079968 | 0.066090 | 0.054824 | 0.045501 | ... | -0.142418 | -0.142486 | -0.142554 | -0.142621 | -0.142687 | -0.142752 | -0.142816 | -0.142880 | -0.142943 | -0.143005 |
| delta_fine_root_mass | 1.000842 | 0.649605 | 0.470866 | 0.361297 | 0.286591 | 0.232054 | 0.190324 | 0.157294 | 0.130482 | 0.108293 | ... | -0.338955 | -0.339118 | -0.339279 | -0.339437 | -0.339594 | -0.339749 | -0.339903 | -0.340054 | -0.340204 | -0.340352 |
13 rows × 477 columns