import numpy as np
from landlab import Component
_VALID_METHODS = {"Grid"}
GRASS = 0
SHRUB = 1
TREE = 2
BARE = 3
SHRUBSEEDLING = 4
TREESEEDLING = 5
[docs]
def assert_method_is_valid(method):
if method not in _VALID_METHODS:
raise ValueError("%s: Invalid method name" % method)
[docs]
class VegCA(Component):
"""Landlab component that simulates inter-species plant competition using a
2D cellular automata model.
This code is based on Cellular Automata Tree Grass Shrub Simulator (CATGraSS).
It simulates spatial competition of multiple plant functional types through
establishment and mortality. In the current code, tree, grass and
shrubs are used.
Ref: Zhou, X., Istanbulluoglu, E., & Vivoni, E. R. (2013). Modeling the
ecohydrological role of aspect controlled radiation on tree grass shrub
coexistence in a semiarid climate. Water Resources Research,
49(5), 2872-2895.
.. codeauthor:: Sai Nudurupati and Erkan Istanbulluoglu
Examples
--------
>>> from landlab import RasterModelGrid
>>> from landlab.components import VegCA
>>> grid = RasterModelGrid((5, 4), xy_spacing=(0.2, 0.2))
>>> VegCA.name
'Cellular Automata Plant Competition'
>>> sorted(VegCA.output_var_names)
['plant__age', 'plant__live_index']
>>> sorted(VegCA.units)
[('plant__age', 'Years'),
('plant__live_index', 'None'),
('vegetation__cumulative_water_stress', 'None'),
('vegetation__plant_functional_type', 'None')]
>>> grid["cell"]["vegetation__plant_functional_type"] = np.arange(
... 0, grid.number_of_cells, dtype=int
... )
>>> grid["cell"]["vegetation__cumulative_water_stress"] = np.ones(
... grid.number_of_cells
... )
>>> ca_veg = VegCA(grid)
>>> ca_veg.grid.number_of_cell_rows
3
>>> ca_veg.grid.number_of_cell_columns
2
>>> ca_veg.grid is grid
True
>>> import numpy as np
>>> A = np.copy(grid["cell"]["plant__age"])
>>> ca_veg.update()
>>> np.all(grid["cell"]["plant__age"] == A)
False
References
----------
**Required Software Citation(s) Specific to this Component**
None Listed
**Additional References**
Zhou, X., Istanbulluoglu, E., and Vivoni, E. R.: Modeling the
ecohydrological role of aspect-controlled radiation on tree-grass-shrub
coexistence in a semiarid climate, Water Resour. Res., 49, 2872– 2895,
doi:10.1002/wrcr.20259, 2013.
"""
_name = "Cellular Automata Plant Competition"
_unit_agnostic = False
_info = {
"plant__age": {
"dtype": float,
"intent": "out",
"optional": False,
"units": "Years",
"mapping": "cell",
"doc": "Age of plant",
},
"plant__live_index": {
"dtype": float,
"intent": "out",
"optional": False,
"units": "None",
"mapping": "cell",
"doc": "1 - vegetation__cumulative_water_stress",
},
"vegetation__cumulative_water_stress": {
"dtype": float,
"intent": "in",
"optional": False,
"units": "None",
"mapping": "cell",
"doc": "cumulative vegetation__water_stress over the growing season",
},
"vegetation__plant_functional_type": {
"dtype": int,
"intent": "in",
"optional": False,
"units": "None",
"mapping": "cell",
"doc": (
"classification of plants (int), grass=0, shrub=1, tree=2, "
"bare=3, shrub_seedling=4, tree_seedling=5"
),
},
}
[docs]
def __init__(
self,
grid,
Pemaxg=0.35,
ING=2.0,
ThetaGrass=0.62,
PmbGrass=0.05,
Pemaxsh=0.2,
ThetaShrub=0.8,
PmbShrub=0.01,
tpmaxShrub=600,
Pemaxtr=0.25,
ThetaTree=0.72,
PmbTree=0.01,
tpmaxTree=350,
ThetaShrubSeedling=0.64,
PmbShrubSeedling=0.03,
tpmaxShrubSeedling=18,
ThetaTreeSeedling=0.64,
PmbTreeSeedling=0.03,
tpmaxTreeSeedling=18,
method="Grid",
Edit_VegCov=True,
):
"""
Parameters
----------
grid: RasterModelGrid
A grid.
Pemaxg: float, optional
Maximal establishment probability of grass.
ING: float, optional
Parameter to define allelopathic effect of creosote on grass.
ThetaGrass: float, optional
Drought resistance threshold of grass.
PmbGrass: float, optional
Background mortality probability of grass.
Pemaxsh: float, optional
Maximal establishment probability of shrub.
ThetaShrub: float, optional
Drought resistance threshold of shrub.
PmbShrub: float, optional
Background mortality probability of shrub.
tpmaxShrub: float, optional
Maximum age of shrub (years).
Pemaxtr: float, optional
Maximal establishment probability of tree.
Thetatree: float, optional
Drought resistance threshold of tree.
PmbTree: float, optional
Background mortality probability of tree.
tpmaxTree: float, optional
Maximum age of tree (years).
ThetaShrubSeedling: float, optional
Drought resistance threshold of shrub seedling.
PmbShrubSeedling: float, optional
Background mortality probability of shrub seedling.
tpmaxShrubSeedling: float, optional
Maximum age of shrub seedling (years).
ThetaTreeSeedling: float, optional
Drought resistance threshold of tree seedling.
PmbTreeSeedling: float, optional
Background mortality probability of tree seedling.
tpmaxTreeSeedling: float, optional
Maximum age of tree seedling (years).
method: str, optional
Method used.
Edit_VegCov: bool, optional
If Edit_VegCov=True, an optional field
'vegetation__boolean_vegetated' will be output, (i.e.) if a cell is
vegetated the corresponding cell of the field will be 1, otherwise
it will be 0.
"""
super().__init__(grid)
self.Edit_VegCov = Edit_VegCov
self._Pemaxg = Pemaxg # Pe-max-grass - max probability
self._Pemaxsh = Pemaxsh # Pe-max-shrub
self._Pemaxtr = Pemaxtr # Pe-max-tree
self._INg = ING # Allelopathic effect on grass from creosotebush
self._th_g = ThetaGrass # grass
self._th_sh = ThetaShrub # shrub - Creosote
self._th_tr = ThetaTree # Juniper pine
self._th_sh_s = ThetaShrubSeedling # shrub seedling
self._th_tr_s = ThetaTreeSeedling # Juniper pine seedling
self._Pmb_g = PmbGrass # Background mortality probability - grass
self._Pmb_sh = PmbShrub # shrub
self._Pmb_tr = PmbTree # tree
self._Pmb_sh_s = PmbShrubSeedling # shrub seedling
self._Pmb_tr_s = PmbTreeSeedling # tree seedling
self._tpmax_sh = tpmaxShrub # Maximum age - shrub
self._tpmax_tr = tpmaxTree # Maximum age - tree
self._tpmax_sh_s = tpmaxShrubSeedling # Maximum age - shrub seedling
self._tpmax_tr_s = tpmaxTreeSeedling # Maximum age - tree seedling
self._method = method
assert_method_is_valid(self._method)
# if "vegetation__plant_functional_type" not in self._grid.at_cell:
# grid["cell"]["vegetation__plant_functional_type"] = np.random.randint(
# 0, 6, grid.number_of_cells
# )
self.initialize_output_fields()
self._cell_values = self._grid["cell"]
VegType = grid["cell"]["vegetation__plant_functional_type"]
tp = np.zeros(grid.number_of_cells, dtype=int)
tp[VegType == TREE] = np.random.randint(
0, self._tpmax_tr, np.where(VegType == TREE)[0].shape
)
tp[VegType == SHRUB] = np.random.randint(
0, self._tpmax_sh, np.where(VegType == SHRUB)[0].shape
)
locs_trees = np.where(VegType == TREE)[0]
locs_shrubs = np.where(VegType == SHRUB)[0]
VegType[locs_trees[tp[locs_trees] < self._tpmax_tr_s]] = TREESEEDLING
VegType[locs_shrubs[tp[locs_shrubs] < self._tpmax_sh_s]] = SHRUBSEEDLING
grid["cell"]["plant__age"] = tp.astype(float)
@property
def Edit_VegCov(self):
"""Flag to indicate whether an optional field is created.
If Edit_VegCov=True, an optional field
'vegetation__boolean_vegetated' will be output, (i.e.) if a cell
is vegetated the corresponding cell of the field will be 1,
otherwise it will be 0.
"""
return self._Edit_VegCov
@Edit_VegCov.setter
def Edit_VegCov(self, Edit_VegCov):
assert isinstance(Edit_VegCov, bool)
self._Edit_VegCov = Edit_VegCov
[docs]
def update(self, dt=1):
"""Update fields with current loading conditions.
Parameters
----------
dt: int, optional
Time elapsed - time step (years).
"""
self._VegType = self._cell_values["vegetation__plant_functional_type"]
self._CumWS = self._cell_values["vegetation__cumulative_water_stress"]
self._live_index = self._cell_values["plant__live_index"]
self._tp = self._cell_values["plant__age"] + dt
# Check if shrub and tree seedlings have matured
shrub_seedlings = np.where(self._VegType == SHRUBSEEDLING)[0]
tree_seedlings = np.where(self._VegType == TREESEEDLING)[0]
matured_shrubs = np.where(self._tp[shrub_seedlings] > self._tpmax_sh_s)[0]
matured_trees = np.where(self._tp[tree_seedlings] > self._tpmax_tr_s)[0]
self._VegType[shrub_seedlings[matured_shrubs]] = SHRUB
self._VegType[tree_seedlings[matured_trees]] = TREE
self._tp[shrub_seedlings[matured_shrubs]] = 0
self._tp[tree_seedlings[matured_trees]] = 0
# Establishment
self._live_index = 1 - self._CumWS # Plant live index = 1 - WS
bare_cells = np.where(self._VegType == BARE)[0]
n_bare = len(bare_cells)
first_ring = self._grid.looped_neighbors_at_cell[bare_cells]
second_ring = self._grid.second_ring_looped_neighbors_at_cell[bare_cells]
veg_type_fr = self._VegType[first_ring]
veg_type_sr = self._VegType[second_ring]
Sh_WS_fr = WS_PFT(veg_type_fr, SHRUB, self._live_index[first_ring])
Tr_WS_fr = WS_PFT(veg_type_fr, TREE, self._live_index[first_ring])
Tr_WS_sr = WS_PFT(veg_type_sr, TREE, self._live_index[second_ring])
n = count(veg_type_fr, SHRUB)
Phi_sh = Sh_WS_fr / 8.0
Phi_tr = (Tr_WS_fr + Tr_WS_sr / 2.0) / 8.0
Phi_g = np.mean(self._live_index[np.where(self._VegType == GRASS)])
Pemaxg = self._Pemaxg * np.ones(n_bare)
Pemaxsh = self._Pemaxsh * np.ones(n_bare)
Pemaxtr = self._Pemaxtr * np.ones(n_bare)
Peg = np.amin(np.vstack((Phi_g / (n * self._INg), Pemaxg)), axis=0)
Pesh = np.amin(np.vstack((Phi_sh, Pemaxsh)), axis=0)
Petr = np.amin(np.vstack((Phi_tr, Pemaxtr)), axis=0)
Select_PFT_E = np.random.choice([GRASS, SHRUBSEEDLING, TREESEEDLING], n_bare)
# Grass - 0; Shrub Seedling - 4; Tree Seedling - 5
Pest = np.choose(Select_PFT_E, [Peg, 0, 0, 0, Pesh, Petr])
# Probability of establishment
R_Est = np.random.rand(n_bare)
# Random number for comparison to establish
Establish = np.int32(np.where(np.greater_equal(Pest, R_Est))[0])
self._VegType[bare_cells[Establish]] = Select_PFT_E[Establish]
self._tp[bare_cells[Establish]] = 0
# Mortality
plant_cells = np.where(self._VegType != BARE)[0]
n_plant = len(plant_cells)
Theta = np.choose(
self._VegType[plant_cells],
[self._th_g, self._th_sh, self._th_tr, 0, self._th_sh_s, self._th_tr_s],
)
PMd = self._CumWS[plant_cells] - Theta
PMd[PMd < 0.0] = 0.0
tpmax = np.choose(
self._VegType[plant_cells],
[
200000,
self._tpmax_sh,
self._tpmax_tr,
0,
self._tpmax_sh_s,
self._tpmax_tr_s,
],
)
PMa = np.zeros(n_plant)
tp_plant = self._tp[plant_cells]
tp_greater = np.where(tp_plant > 0.5 * tpmax)[0]
PMa[tp_greater] = (
(tp_plant[tp_greater] - 0.5 * tpmax[tp_greater]) / (0.5 * tpmax[tp_greater])
) - 1
PMb = np.choose(
self._VegType[plant_cells],
[
self._Pmb_g,
self._Pmb_sh,
self._Pmb_tr,
0,
self._Pmb_sh_s,
self._Pmb_tr_s,
],
)
PM = PMd + PMa + PMb
PM[PM > 1.0] = 1.0
R_Mor = np.random.rand(n_plant) # Random number for comparison to kill
Mortality = np.int32(np.where(np.greater_equal(PM, R_Mor))[0])
self._VegType[plant_cells[Mortality]] = BARE
self._tp[plant_cells[Mortality]] = 0
self._cell_values["plant__age"] = self._tp
if self._Edit_VegCov:
self._grid["cell"]["vegetation__boolean_vegetated"] = np.zeros(
self._grid.number_of_cells, dtype=int
)
self._grid["cell"]["vegetation__boolean_vegetated"][
self._VegType != BARE
] = 1
# For debugging purposes
self._bare_cells = bare_cells
self._Established = bare_cells[Establish]
self._plant_cells = plant_cells
self._Mortified = plant_cells[Mortality]
[docs]
def count(Arr, value):
Res = np.zeros(Arr.shape[0], dtype=int)
x, y = Arr.shape
for i in range(0, x):
for j in range(0, y):
if Arr[i][j] == value:
Res[i] += 1
return Res
[docs]
def WS_PFT(VegType, PlantType, WS):
Phi = np.zeros(WS.shape[0])
x, y = WS.shape
for i in range(0, x):
for j in range(0, y):
if VegType[i][j] == PlantType:
Phi[i] += WS[i][j]
return Phi