Skip to content

Commit

Permalink
Merge branch 'develop' into 236-develop-pyrealmcoresolar-function-rad…
Browse files Browse the repository at this point in the history
…iation-calc-library
  • Loading branch information
j-emberton authored Oct 29, 2024
2 parents c13f81f + 3d814c2 commit 486e91a
Show file tree
Hide file tree
Showing 20 changed files with 1,194 additions and 300 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ repos:
- id: debug-statements
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.6.9
rev: v0.7.0
hooks:
# Run the linter.
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
# Run the formatter.
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: "v1.11.2"
rev: "v1.12.1"
hooks:
- id: mypy
additional_dependencies: [numpy, types-tabulate, pandas-stubs]
Expand Down
8 changes: 8 additions & 0 deletions docs/source/api/demography_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ language_info:
:members:
```

## The {mod}`~pyrealm.demography.core` module

```{eval-rst}
.. automodule:: pyrealm.demography.core
:autosummary:
:members:
```

## The {mod}`~pyrealm.demography.flora` module

```{eval-rst}
Expand Down
62 changes: 33 additions & 29 deletions docs/source/users/demography/canopy.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import numpy as np
import pandas as pd
from pyrealm.demography.flora import PlantFunctionalType, Flora
from pyrealm.demography.community import Community
from pyrealm.demography.community import Cohorts, Community
from pyrealm.demography.crown import CrownProfile, get_crown_xy
from pyrealm.demography.canopy import Canopy
from pyrealm.demography.t_model_functions import StemAllometry
Expand Down Expand Up @@ -117,9 +117,11 @@ simple_community = Community(
flora=simple_flora,
cell_area=total_area,
cell_id=1,
cohort_dbh_values=stem_dbh,
cohort_n_individuals=np.array([1]),
cohort_pft_names=np.array(["defaults"]),
cohorts=Cohorts(
dbh_values=stem_dbh,
n_individuals=np.array([1]),
pft_names=np.array(["defaults"]),
),
)
# Get the canopy model for the simple case from the canopy top
Expand All @@ -137,7 +139,7 @@ should equal the whole canopy $f_{abs}$ calculated using the simple Beer-Lambert
equation and the PFT trait values.

```{code-cell} ipython3
print(simple_canopy.extinction_profile[-1])
print(simple_canopy.community_data.extinction_profile[-1])
```

```{code-cell} ipython3
Expand Down Expand Up @@ -180,15 +182,15 @@ ax1.set_xlabel("Profile radius (m)")
ax1.set_ylabel("Vertical height (m)")
# Plot the leaf area between heights for stems
ax2.plot(simple_canopy.stem_leaf_area, hghts, color="red")
ax2.plot(simple_canopy.cohort_data.stem_leaf_area, hghts, color="red")
ax2.set_xlabel("Leaf area (m2)")
# Plot the fraction of light absorbed at different heights
ax3.plot(simple_canopy.f_abs, hghts, color="red")
ax3.plot(simple_canopy.cohort_data.f_abs, hghts, color="red")
ax3.set_xlabel("Light absorption fraction (-)")
# Plot the light extinction profile through the canopy.
ax4.plot(simple_canopy.extinction_profile, hghts, color="red")
ax4.plot(simple_canopy.community_data.extinction_profile, hghts, color="red")
ax4.set_xlabel("Cumulative light\nabsorption fraction (-)")
```

Expand Down Expand Up @@ -255,9 +257,11 @@ community = Community(
flora=flora,
cell_area=32,
cell_id=1,
cohort_dbh_values=np.array([0.1, 0.20, 0.5]),
cohort_n_individuals=np.array([7, 3, 2]),
cohort_pft_names=np.array(["short", "short", "tall"]),
cohorts=Cohorts(
dbh_values=np.array([0.1, 0.20, 0.5]),
n_individuals=np.array([7, 3, 2]),
pft_names=np.array(["short", "short", "tall"]),
),
)
# Calculate the canopy profile across vertical heights
Expand All @@ -281,7 +285,7 @@ profiles = get_crown_xy(
for idx, crown in enumerate(profiles):
# Get spaced but slightly randomized stem locations
n_stems = community.cohort_data["n_individuals"][idx]
n_stems = community.cohorts.n_individuals[idx]
stem_locations = np.linspace(0, 10, num=n_stems) + np.random.normal(size=n_stems)
# Plot the crown model for each stem
Expand All @@ -298,7 +302,7 @@ vertical profile is equal to the expected value across the whole community.
```{code-cell} ipython3
# Calculate L_h for each cohort
cohort_lai = (
community.cohort_data["n_individuals"]
community.cohorts.n_individuals
* community.stem_traits.lai
* community.stem_allometry.crown_area
) / community.cell_area
Expand All @@ -308,7 +312,7 @@ print(1 - np.exp(np.sum(-community.stem_traits.par_ext * cohort_lai)))
```

```{code-cell} ipython3
print(canopy.extinction_profile[-1])
print(canopy.community_data.extinction_profile[-1])
```

```{code-cell} ipython3
Expand Down Expand Up @@ -345,16 +349,16 @@ ax1.set_xlabel("Profile radius (m)")
ax1.set_ylabel("Vertical height (m)")
# Plot the leaf area between heights for stems
ax2.plot(canopy.stem_leaf_area, hghts)
ax2.plot(canopy.cohort_data.stem_leaf_area, hghts)
ax2.set_xlabel("Leaf area per stem (m2)")
# Plot the fraction of light absorbed at different heights
ax3.plot(canopy.f_abs, hghts, color="grey")
ax3.plot(1 - canopy.cohort_f_trans, hghts)
ax3.plot(canopy.cohort_data.f_abs, hghts, color="grey")
ax3.plot(1 - canopy.cohort_data.f_trans, hghts)
ax3.set_xlabel("Light absorption fraction (-)")
# Plot the light extinction profile through the canopy.
ax4.plot(canopy.extinction_profile, hghts, color="grey")
ax4.plot(canopy.community_data.extinction_profile, hghts, color="grey")
_ = ax4.set_xlabel("Cumulative light\nabsorption fraction (-)")
```

Expand Down Expand Up @@ -412,7 +416,7 @@ l_m = \left\lceil \frac{\sum_1^{N_s}{A_c}}{ A(1 - f_G)}\right\rceil
$$

```{code-cell} ipython3
canopy_ppa = Canopy(community=community, canopy_gap_fraction=2 / 32, fit_ppa=True)
canopy_ppa = Canopy(community=community, canopy_gap_fraction=0 / 32, fit_ppa=True)
```

The `canopy_ppa.heights` attribute now contains the heights at which the PPA
Expand All @@ -426,7 +430,7 @@ And the final value in the canopy extinction profile still matches the expectati
above:

```{code-cell} ipython3
print(canopy_ppa.extinction_profile[-1])
print(canopy_ppa.community_data.extinction_profile[-1])
```

### Visualizing layer closure heights and areas
Expand All @@ -439,13 +443,13 @@ individuals in each cohort.
```{code-cell} ipython3
# Calculate the total projected crown area across the community at each height
community_crown_area = np.nansum(
canopy.crown_profile.projected_crown_area * community.cohort_data["n_individuals"],
canopy.crown_profile.projected_crown_area * community.cohorts.n_individuals,
axis=1,
)
# Do the same for the projected leaf area
community_leaf_area = np.nansum(
canopy.crown_profile.projected_leaf_area * community.cohort_data["n_individuals"],
canopy.crown_profile.projected_leaf_area * community.cohorts.n_individuals,
axis=1,
)
```
Expand Down Expand Up @@ -499,18 +503,18 @@ ax1.legend(frameon=False)
for val in canopy_ppa.heights:
ax2.axhline(val, color="red", linewidth=0.5, zorder=0)
for val in canopy_ppa.extinction_profile:
for val in canopy_ppa.community_data.extinction_profile:
ax2.axvline(val, color="red", linewidth=0.5, zorder=0)
ax2.plot(canopy.extinction_profile, hghts)
ax2.plot(canopy.community_data.extinction_profile, hghts)
ax2_top = ax2.twiny()
ax2_top.set_xlim(ax2.get_xlim())
extinction_labels = [
f"$f_{{abs{l + 1}}}$ = {z:.3f}"
for l, z in enumerate(np.nditer(canopy_ppa.extinction_profile))
for l, z in enumerate(np.nditer(canopy_ppa.community_data.extinction_profile))
]
ax2_top.set_xticks(canopy_ppa.extinction_profile)
ax2_top.set_xticks(canopy_ppa.community_data.extinction_profile)
ax2_top.set_xticklabels(extinction_labels, rotation=90)
ax2.set_xlabel("Light extinction (-)")
Expand Down Expand Up @@ -544,11 +548,11 @@ The steps below show this partitioning process for the PPA layers calculated abo

```{code-cell} ipython3
print("k = \n", community.stem_traits.par_ext, "\n")
print("L_H = \n", canopy_ppa.cohort_lai)
print("L_H = \n", canopy_ppa.cohort_data.lai)
```

```{code-cell} ipython3
layer_cohort_f_tr = np.exp(-community.stem_traits.par_ext * canopy_ppa.cohort_lai)
layer_cohort_f_tr = np.exp(-community.stem_traits.par_ext * canopy_ppa.cohort_data.lai)
print(layer_cohort_f_tr)
```

Expand Down Expand Up @@ -609,6 +613,6 @@ print(cohort_fapar)
cohort to given the $f_{APAR}$ for each stem at each height.

```{code-cell} ipython3
stem_fapar = cohort_fapar / community.cohort_data["n_individuals"]
stem_fapar = cohort_fapar / community.cohorts.n_individuals
print(stem_fapar)
```
14 changes: 6 additions & 8 deletions docs/source/users/demography/community.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import numpy as np
import pandas as pd
from pyrealm.demography.flora import PlantFunctionalType, Flora
from pyrealm.demography.community import Community
from pyrealm.demography.community import Cohorts, Community
```

```{code-cell} ipython3
Expand All @@ -57,16 +57,14 @@ community = Community(
flora=flora,
cell_area=32,
cell_id=1,
cohort_dbh_values=np.array([0.02, 0.20, 0.5]),
cohort_n_individuals=np.array([15, 5, 2]),
cohort_pft_names=np.array(["short", "short", "tall"]),
cohorts=Cohorts(
dbh_values=np.array([0.02, 0.20, 0.5]),
n_individuals=np.array([15, 5, 2]),
pft_names=np.array(["short", "short", "tall"]),
),
)
```

```{code-cell} ipython3
community
```

```{code-cell} ipython3
```
24 changes: 12 additions & 12 deletions docs/source/users/demography/crown.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,12 +218,10 @@ flora = Flora([narrow_pft, medium_pft, wide_pft])
flora
```

The Flora object can also be used to show a table of canopy variables:
The key canopy variables from the flora are:

```{code-cell} ipython3
# TODO - add a Flora.to_pandas() method
flora_data = pd.DataFrame({k: getattr(flora, k) for k in flora.trait_attrs})
flora_data[["name", "ca_ratio", "m", "n", "f_g", "q_m", "z_max_prop"]]
flora.to_pandas()[["name", "h_max", "ca_ratio", "m", "n", "f_g", "q_m", "z_max_prop"]]
```

The next section of code generates the `StemAllometry` to use for the profiles.
Expand All @@ -249,14 +247,12 @@ allometry = StemAllometry(stem_traits=flora, at_dbh=stem_dbh)
We can again use {mod}`pandas` to get a table of those allometric predictions:

```{code-cell} ipython3
pd.DataFrame({k: getattr(allometry, k) for k in allometry.allometry_attrs})
allometry.to_pandas()
```

Finally, we can define a set of vertical heights. In order to calculate the
variables for each PFT at each height, this needs to be provided as a column array,
that is with a shape `(N, 1)`.

We can then calculate the crown profiles.
that is with a shape `(N, 1)`. We can then calculate the crown profiles:

```{code-cell} ipython3
# Create a set of vertical heights as a column array.
Expand All @@ -278,6 +274,14 @@ above calculated at each height $z$:
crown_profiles
```

The {meth}`~pyrealm.demography.core.PandasExporter.to_pandas()` method of the
{meth}`~pyrealm.demography.crown.CrownProfile` class can be used to extract the data
into a table, with the separate stems identified by the column index field.

```{code-cell} ipython3
crown_profiles.to_pandas()
```

### Visualising crown profiles

The code below generates a plot of the vertical shape profiles of the crowns for each
Expand Down Expand Up @@ -532,7 +536,3 @@ plt.legend(
bbox_to_anchor=(0.5, 1.15),
)
```

```{code-cell} ipython3
```
15 changes: 12 additions & 3 deletions docs/source/users/demography/flora.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,13 @@ flora = Flora([short_pft, medium_pft, tall_pft])
flora
```

The {meth}`~pyrealm.demography.core.PandasExporter.to_pandas()` method of the
{meth}`~pyrealm.demography.flora.StemTraits` class exports the trait data as a
{class}`pandas.DataFrame`, making it easier to use for plotting or calculations outside
of `pyrealm`.

```{code-cell} ipython3
pd.DataFrame({k: getattr(flora, k) for k in flora.trait_attrs})
flora.to_pandas()
```

You can also create `Flora` instances using PFT data stored TOML, JSON and CSV file
Expand All @@ -168,7 +173,11 @@ more easily created from a `Flora` object by providing a list of PFT names:
# Get stem traits for a range of stems
stem_pfts = ["short", "short", "short", "medium", "medium", "tall"]
stem_traits = flora.get_stem_traits(pft_names=stem_pfts)
```

# Show the repeated values
stem_traits.h_max
Again, the {meth}`~pyrealm.demography.core.PandasExporter.to_pandas()` method of the
{meth}`~pyrealm.demography.flora.StemTraits` class can be use to extract the data:

```{code-cell} ipython3
stem_traits.to_pandas()
```
Loading

0 comments on commit 486e91a

Please sign in to comment.