Source code for pyPhenology.models.thermaltime

from . import utils
from .base import BaseModel
import numpy as np


[docs]class ThermalTime(BaseModel): """Thermal Time Model The classic growing degree day model using a fixed temperature threshold above which forcing accumulates. Event happens on :math:`DOY` when the following is met: .. math:: \sum_{t=t_{1}}^{DOY}R_{f}(T_{i})\geq F^{*} where: .. math:: R_{f}(T_{i}) = max(T_{i}-threshold, 0) Parameters: t1 : int | :math:`t_{1}` - The DOY which forcing accumulating beings | default : (-67,298) T : int | :math:`T` - The threshold above which forcing accumulates | default : (-25,25) F : int, > 0 | :math:`F^{*}` - The total forcing units required | default : (0,1000) """
[docs] def __init__(self, parameters={}): BaseModel.__init__(self) self.all_required_parameters = {'t1': (-67, 298), 'T': (-25, 25), 'F': (0, 1000)} self._organize_parameters(parameters) self._required_data = {'predictor_columns': ['site_id', 'year', 'doy', 'temperature'], 'predictors': ['temperature', 'doy_series']}
def _apply_model(self, temperature, doy_series, t1, T, F): # Temperature threshold temperature[temperature < T] = 0 # Only accumulate forcing after t1 temperature[doy_series < t1] = 0 accumulated_gdd = utils.transforms.forcing_accumulator(temperature) return utils.transforms.doy_estimator(forcing=accumulated_gdd, doy_series=doy_series, threshold=F)
[docs]class M1(BaseModel): """The Thermal Time Model with a daylength correction. Event happens on :math:`DOY` when the following is met: .. math:: \sum_{t=t_{1}}^{DOY}R_{f}(T_{i}) \geq (L_{i}/24)^kF^* where: .. math:: R_{f}(T_{i}) = max(T_{i}-threshold, 0) This model requires a daylength column in the predictors in addition to daily mean temperature. Parameters: t1 : int | :math:`t_{1}` - The DOY which forcing accumulating beings | default : (-67,298) T : int | :math:`T` - The threshold above which forcing accumulates | default : (-25,25) F : int, > 0 | :math:`F^{*}` - The total forcing units required | default : (0,1000) k : int, > 0 | :math:`k^{*}` - Daylength coefficient | default : (0,50) Notes: Blümel, K., & Chmielewski, F. M. (2012). Shortcomings of classical phenological forcing models and a way to overcome them. Agricultural and Forest Meteorology, 164, 10–19. http://doi.org/10.1016/j.agrformet.2012.05.001 """
[docs] def __init__(self, parameters={}): BaseModel.__init__(self) self.all_required_parameters = {'t1': (-67, 298), 'T': (-25, 25), 'F': (0, 1000), 'k': (0, 50)} self._organize_parameters(parameters) self._required_data = {'predictor_columns': ['site_id', 'year', 'doy', 'temperature', 'daylength'], 'predictors': ['temperature', 'doy_series', 'daylength']}
def _organize_predictors(self, predictors, observations, for_prediction): """Convert data to internal structure used by M1 model """ # daylength for each observation. 1d array with length n, which should match # axis 0 of temperature_array obs_with_daylength = observations.merge(predictors, on=['site_id', 'year'], how='left') daylength_array = obs_with_daylength.daylength.values if for_prediction: temperature_array, doy_series = utils.misc.temperature_only_data_prep(observations=observations, predictors=predictors, for_prediction=for_prediction) return {'temperature': temperature_array, 'daylength': daylength_array, 'doy_series': doy_series} else: cleaned_observations, temperature_array, doy_series = utils.misc.temperature_only_data_prep(observations, predictors, for_prediction=for_prediction) self.fitting_predictors = {'temperature': temperature_array, 'daylength': daylength_array, 'doy_series': doy_series} self.obs_fitting = cleaned_observations def _validate_formatted_predictors(self, predictors): pass def _apply_model(self, temperature, doy_series, daylength, t1, T, F, k): # Temperature threshold temperature[temperature < T] = 0 # Only accumulate forcing after t1 temperature[doy_series < t1] = 0 accumulated_gdd = utils.transforms.forcing_accumulator(temperature) daylength_adjustment = (daylength / 24) ** k # Make it the same shape as gdd for easy adjustment num_days = len(doy_series) daylength_adjustment = np.tile(np.expand_dims(daylength_adjustment, 1), num_days) return utils.transforms.doy_estimator(forcing=accumulated_gdd, doy_series=doy_series, threshold=F)
[docs]class FallCooling(BaseModel): """Fall senesence model A model for fall senesence. Essential a Thermal Time model, but instead of accumulating warming above a base temperature, it accumulates cooling below a max temperature. Event happens on :math:`DOY` when the following is met: .. math:: \sum_{t=t_{1}}^{DOY}R_{f}(T_{i})\geq F^{*} where: .. math:: R_{f}(T_{i}) = max(threshold-T_{i}, 0) This is a simplified version of the model in Delpierre et al. 2009. The full version also has a photoperiod compoenent. Parameters: t1 : int | :math:`t_{1}` - The DOY which forcing accumulating beings | default : (182,365) T : int | :math:`T` - The threshold below which cooling accumulates | default : (-25,25) F : int, > 0 | :math:`F^{*}` - The total cooling units required | default : (0,1000) Notes: Delpierre, N., Dufrêne, E., Soudani, K., Ulrich, E., Cecchini, S., Boé, J., & François, C. (2009). Modelling interannual and spatial variability of leaf senescence for three deciduous tree species in France. Agricultural and Forest Meteorology, 149(6–7), 938–948. http://doi.org/10.1016/j.agrformet.2008.11.014 """
[docs] def __init__(self, parameters={}): BaseModel.__init__(self) self.all_required_parameters = {'t1': (182, 365), 'T': (-25, 25), 'F': (0, 1000)} self._organize_parameters(parameters) self._required_data = {'predictor_columns': ['site_id', 'year', 'doy', 'temperature'], 'predictors': ['temperature', 'doy_series']}
def _apply_model(self, temperature, doy_series, t1, T, F): # Temperature threshold temperature[temperature > T] = 0 # Only accumulate forcing after t1 temperature[doy_series < t1] = 0 accumulated_gdd = utils.transforms.forcing_accumulator(temperature) return utils.transforms.doy_estimator(forcing=accumulated_gdd, doy_series=doy_series, threshold=F)