from py_ibm import *
from trees_ibm.tree_agent import Tree
#from plot_trees import plot_tree_pos
import pdb
from tables import *
import json
import numpy as np
[docs]class Tree_Grid(Rectangular_Grid):
""" Represent space as a grid.
Args:
x_max : int
Number of horizontal cells
y_max : int
Number of vertical cells
patch_area : float
Area of each patch (cell) in squared meters
I0 : float
Incoming irradiance on top of canopy [umol photon / m^2s]
k : float
Light extinction coefficient.
delta_h :float
Height of each vertical layer.
phi_act : float
Photosynthetic active period.
lday : int
Mean day length during the vegetation period phi_act.
ndim : int
Number of dimensions. One per environmental variable.
dim_names : list
A list of 'ndim' strings representing the names of dimensions.
Attributes:
LAIs : dict
Dictionary with patches as keys and a list of averaged Leaf Area Indices (one for each vertical layer) as values.
CCAs : dict
Dictionary with patches as keys a a list of Cummulative Crown Areas (one for each vertical layer) as values.
trees_per_patch : list
Dictionary with patches ('(x,y)') as keys and a list of tree ids as values
total_area :float
Area of all simulated patches in ha
"""
def __init__(self, x_max, y_max, patch_area, I0, k, delta_h, phi_act, lday, ndim=5, dim_names=["GroundLight", "LianaCoverage", "Temperature", "WindSpeed", "Humidity"]):
super().__init__(x_max, y_max, ndim=ndim, dim_names=dim_names)
self.patch_area = patch_area
self.delta_h = delta_h
self.I0 = I0
self.k = k
self.lday = lday
self.phi_act = phi_act
self.LAIs = {}
self.CCAs = {}
self.total_area = self.x_max * self.y_max * self.patch_area / 10000
# s={}
self.trees_per_patch = {(x, y): [] for x in range(
0, self.x_max) for y in range(0, self.y_max)}
# self.FTs=["FT1","FT2","FT3","FT4","FT5","FT6"]
self.FTs = [k for k in Tree.DERIVED_TREES.keys()]
self.seedbank = {k: [] for k in self.FTs}
[docs] def exeeding_CCA(self):
""" Finds which patches have the cummulative crown area bigger than the patch area in at least one layer.
Returns: list
Alist of patchs ([(x,y),(x,y),...,(x,y)])
"""
patches = [patch for patch, layers in self.CCAs.items() if any(
layer > self.patch_area for layer in layers)]
return patches
[docs] def update(self):
""" Execute the methods that calculate values for each dimension of the grid.
Returns:
None.
"""
self.LAIs = self.patch_based_LAI(Tree.TreesPerPatch())
self.CCAs = self.patch_based_CCA(Tree.TreesPerPatch())
self.ground_level_light()
self.surface[self.dim_names["LianaCoverage"]
] = self.calculate_liana_coverage()
[docs] def seed_establishment(self, seedbank):
"""Check if the seeds in the seedbank have the light and space conditions to germinate.
Note: All seeds in the given seedbank are assumed to have the same conditions. Multiple calls to this method passing different seedbanks allow the representation of different requirements.
All established individuals are initiated with a predefined DBH (the same for all individuals), but initial height is calculated based on functional relationships and only if there is enough space in that layer a new individual will be stablished.
Args:
seedbank : dict
Dictionary with Functional Type (FT) and list of seed positions as values.
lmin : float
Minimum light intensity at ground level for seed to germinate.
h0 : float
Allometric paramenter used to calculate initial height.
h1 : float
Allometric paramenter used to calculate initial height.
Returns: dict
est_seeds: a dictionary with patches as keys ('(x,y)') and number of established seeds as values.
"""
est_seeds = {}
for ft in seedbank.keys():
h0 = Tree.DERIVED_TREES[ft].h0
h1 = Tree.DERIVED_TREES[ft].h1
min_light = Tree.DERIVED_TREES[ft].Iseed
seeds_to_keep = []
for i in range(len(seedbank[ft])):
#print(i, len(seedbank[ft]), ft)
seed = seedbank[ft][i]
patch = (int(np.floor(seed[0])), int(np.floor(seed[1])))
if np.random.rand() > 0.9995:
seeds_to_keep.append(i)
if not self.check_light(min_light, patch):
seeds_to_keep.remove(i)
# break
elif not self.check_CCA(h0, h1, patch):
seeds_to_keep.remove(i)
est_seeds[ft] = [seedbank[ft][s] for s in seeds_to_keep]
# est_seeds[ft]=seeds_to_keep
return est_seeds
[docs] def check_light(self, min_light, patch):
""" Check if the light intensity at ground level meets the needs for seed germination.
Note: Ligth intensity is homogeneized at the patch level.
Args:
min_light : float
Minimum light intensity at ground for seeds to germinate.
patch : tuple
(x,y), patch that will have the light conditions assessed.
Returns: bool.
True if light level is above minimum.
"""
return self.surface[self.dim_names["GroundLight"]][patch] > min_light
[docs] def check_CCA(self, h0, h1, patch):
""" Check if the there is enough space forthe new plants to be established
Note: Cummulative Crown Area is homogeneized at the patch level
Args:
h0 : float
Allometric paramenter used to calculate initial height
h1 : float
Allometric paramenter used to calculate initial height
patch : tuple
(x,y), patch that will have the light conditions assessed.
Returns: bool.
True if there is enough space.
"""
Hmin = h0 * 2**h1
l = int(np.floor(Hmin / self.delta_h))
if self.CCAs.get(patch) == None:
return True
else:
return self.CCAs[patch][l] < 1.0
[docs] def ground_level_light(self):
"""Calculate the amount of light that reaches the ground.
The value per patch is stored in the "GroundLight" dimension.
Values are homogeneized at the patch level.
Returns:
None.
"""
self.surface[self.dim_names["GroundLight"]] = self.I0
for patch in self.LAIs.keys():
self.surface[self.dim_names["GroundLight"]][patch] = self.I0 * \
np.exp(-self.k * sum(self.LAIs[patch]))
[docs] def patch_based_LAI(self, trees_per_patch):
"""
Calculate the Leaf Area Index for each patch in the grid.
Args:
trees_per_patch :dict
Dictionary in which keys are patch coordinates (x,y) and values are a list of the tree ids in that patch.
Returns: dict
L_per_layer: dictionary in which the keys are patch coordinates('(x,y)')-the same as the ones in trees_per_patch- and values are a list of the LAI per vertical layer
"""
L_per_layer = {}
nlayers = int(np.ceil(np.ceil(Tree.MaxHeight()) / self.delta_h))
for k in trees_per_patch.keys():
L_per_layer[k] = []
lower_limit = int(
min([Tree.Instances.get(t).lmin for t in trees_per_patch[k]]))
upper_limit = int(
max([Tree.Instances.get(t).lmax for t in trees_per_patch[k]]))
for i in range(nlayers + 1):
L_per_layer[k].append((1 / self.patch_area) * sum([Tree.Instances.get(
tree_id).L_mean for tree_id in trees_per_patch[k] if lower_limit <= i and upper_limit >= i]))
# upper and lower limits
#Tree.Instances.get(tree_id).lmin<=i*self.delta_h and Tree.Instances.get(tree_id).lmax>=i*self.delta_h
return L_per_layer
[docs] def patch_based_CCA(self, trees_per_patch):
"""Calculate the Leaf Area Index for each patch in the grid.
Args:
trees_per_patch : dict
Dictionary in which keys are patches ('(x,y)')' and values are lists of the trees ids in that patch.
Returns: dict
L_per_layer: dictionary in which the keys are the same as the ones in trees_per_patch and values are a list of the CCA per vertical layer
"""
L_per_layer = {}
nlayers = int(np.ceil(np.ceil(Tree.MaxHeight()) / self.delta_h))
for k in trees_per_patch.keys():
L_per_layer[k] = []
lower_limit = int(
min([Tree.Instances.get(t).lmin for t in trees_per_patch[k]]))
upper_limit = int(
max([Tree.Instances.get(t).lmax for t in trees_per_patch[k]]))
for i in range(nlayers + 1):
L_per_layer[k].append((1 / self.patch_area) * sum([Tree.Instances.get(
tree_id).CA for tree_id in trees_per_patch[k] if lower_limit <= i and upper_limit >= i]))
return L_per_layer
[docs] def trees_in_patches(self, patches):
""" Make a list of tree ids and their respective positions.
Args:
patch : list
A list of tuples representing the patch coordinates '(x,y)'.
Returns: list
id_pos: a list tuples with tree ids and positions '(id,(x_pos,y_pos))'
"""
# one list of ids for each patch
ids = [self.trees_per_patch[p] for p in patches]
# one single list of tuples with (id, pos)
id_pos = [(tree, Tree.Instances.get(tree).position)
for patch in ids for tree in patch]
return id_pos
[docs] def trees_per_age_groups(self, list_of_trees):
"""
Return a dictionary in which keys are a age group and values are lists containing the id of the respective trees.
Args:
list_of_trees: list
A list with the id of trees.
Returns: dict
A dictionary age as keys and a list of tree ids as values.
"""
age_groups = {}
for t in list_of_trees:
if age_groups.get(Tree.Instances[t].age) == None:
age_groups[Tree.Instances[t].age] = [Tree.Instances[t].id]
else:
age_groups[Tree.Instances[t].age].append(Tree.Instances[t].id)
return age_groups
[docs] def trees_per_type(self, list_of_trees):
"""Organize trees by functional type.
Args:
list_of_trees :list
A list of tree ids.
Returns: dict
type_groups: a dictionary in which keys are a Functional
Types and values are lists containing the id of the respective trees.
"""
type_groups = {}
for t in list_of_trees:
if type_groups.get(Tree.Instances[t].Ftype) == None:
type_groups[Tree.Instances[t].Ftype] = [Tree.Instances[t].id]
else:
type_groups[Tree.Instances[t].Ftype].append(
Tree.Instances[t].id)
return type_groups
[docs] def cohorts(self, patches):
""" Assemble cohorts based on tree type and age.
Uses the trees_per_type() and trees_by_age_groups() methods to
organize a dictionary of dictionaries containing the indices of
trees in each age group within each type
Args:
Patches :list
A list of tuples representing patch coordinatas
[(x1,y1),(x2,y2)...]
Returns: dict
cohorsts: a dictionary with trees organized by type and age:
{FunctionalType1:{AgeGroup1:[tree1,tree2,tree3...]}}
"""
trees_in_patch = self.trees_in_patches(patches)
list_of_trees = [i[0] for i in trees_in_patch]
FT = self.trees_per_type(list_of_trees)
cohorts = {}
for group in FT.keys():
cohorts[group] = self.trees_per_age_groups(FT[group])
return cohorts
[docs] def calculate_liana_coverage(self):
""" Calculate the coverage (%) os lianas in on patch based on the previous coverage, temperature, humidity and wind speed
Returns:
None
"""
return np.zeros((self.x_max, self.y_max))
[docs] def calculate_liana_biomass(self):
"""
Calculate the biomass of lianas in one patch based on the coverage.
"""
pass