Module sarra_py.bilan_pheno
View Source
import numpy as np
import copy
import xarray as xr
def reset(j, data):
data = data.copy(deep=True)
# when reaching stage 7, we reset the main phenological variables to zero
data["changePhase"][j:,:,:] = np.where(data["numPhase"][j,:,:] == 7, 0, data["changePhase"][j,:,:])#[np.newaxis,...]
data["sdj"][j:,:,:] = np.where(data["numPhase"][j,:,:] == 7, 0, data["sdj"][j,:,:])#[np.newaxis,...]
data["ruRac"][j:,:,:] = np.where(data["numPhase"][j,:,:] == 7, 0, data["numPhase"][j,:,:])#[np.newaxis,...]
data["nbJourCompte"][j:,:,:] = np.where(data["numPhase"][j,:,:] == 7, 0, data["numPhase"][j,:,:])#[np.newaxis,...]
data["startLock"][j:,:,:] = np.where(data["numPhase"][j,:,:] == 7, 1, data["startLock"][j,:,:])#[np.newaxis,...]
# and we leave numPhas last
data["numPhase"][j:,:,:] = np.where(data["numPhase"][j,:,:] == 7, 0, data["numPhase"][j,:,:])#[np.newaxis,...]
return data
def testing_for_initialization(j, data, paramITK, paramVariete):
"""
This function tests if the conditions are met to initiate the crop.
If numPhase is 0, if the current day is equal or above the sowing date, and
if surface_tank_stock is above the threshold for sowing, we initiate the
crop :
1) we set numPhase to 1 ; we broadcast the value over remaining days.
2) we set changePhase of this particular day to 1.
3) set the sum of thermal time to the next phase (seuilTempPhaseSuivante) to
be SDJLevee ; we broadcast the value over remaining days.
4) we set initPhase to 1 ; we broadcast the value over remaining days.
initPhase is only used in update_pheno_phase_1_to_2 function, so that we do
not go directly from phase 0 to phase 2. It is used as a specific flag for
phase 0 to 1 transition.
Args:
j (_type_): _description_
data (_type_): _description_
paramITK (_type_): _description_
Returns:
_type_: _description_
"""
#! replacing stRuSurf by surface_tank_stock
condition = \
(data["numPhase"][j, :, :] == 0) & \
(j >= data["sowing_date"][j,:,:]) & \
(data["surface_tank_stock"][j, :, :] >= paramITK["seuilEauSemis"])
# & (data["startLock"][j,:,:] == 0)
data["numPhase"][j:, :, :] = xr.where(
condition, 1, data["numPhase"][j, :, :])
data["changePhase"][j, :, :] = xr.where(
condition, 1, data["changePhase"][j, :, :])
data["seuilTempPhaseSuivante"][j:, :, :] = xr.where(
condition,
paramVariete["SDJLevee"],
data["seuilTempPhaseSuivante"][j, :, :],
)
#flagging phase change has been done
data["initPhase"][j, :, :] = xr.where(
condition,
1,
data["initPhase"][j, :, :]
)
return data
def flag_change_phase(j, data, num_phase):
"""
This function flags the day for phase change.
If the phase number is above the num_phase value, and if the sum of thermal
time is above the threshold, this function returns changePhase flags.
Args:
j (_type_): _description_
data (_type_): _description_
num_phase (_type_): _description_
Returns:
_type_: _description_
"""
# flagging the day for phase change
condition = \
(data["numPhase"][j,:,:] == num_phase) & \
(data["sdj"][j,:,:] >= data["seuilTempPhaseSuivante"][j,:,:])
data["changePhase"][j,:,:] = xr.where(
condition,
1,
data["changePhase"][j,:,:],
)
return data
def update_thermal_time_next_phase(j, data, num_phase, thermal_time_threshold):
"""
This function updates the sum of thermal time needed to reach the next
phase.
When numPhase equals the requested phase number, and if changePhase is 1
(meaning that we are at a phase transition day), the seuilTempPhaseSuivante
is incremented by the thermal_time_threshold value. This value is
stage-specific :
- 1 to 2 : SDJLevee
- 2 to 3 : SDJBVP
- 4 to 5 : SDJRPR
- 5 to 6 : SDJMatu1
- 6 to 7 : SDJMatu2
These parameters are passed explicitly when calling this function.
Args:
j (_type_): _description_
data (_type_): _description_
num_phase (_type_): _description_
Returns:
_type_: _description_
"""
condition = \
(data["numPhase"][j,:,:] == num_phase) & \
(data["changePhase"][j,:,:] == 1)
data["seuilTempPhaseSuivante"][j:,:,:] = np.where(
condition,
data["seuilTempPhaseSuivante"][j,:,:] + thermal_time_threshold,
data["seuilTempPhaseSuivante"][j,:,:]
)
return data
def increment_phase_number(j, data):
"""
This function increments the phase number.
When the phase number is not 0, and if changePhase is 1 (meaning that we are
at a phase transition day), and initPhase is 0 (meaning that the phase
number has not been incremented yet this day), the phase number is
incremented by 1. Also, the phase change flag initPhase is set to 1.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
condition = \
(data["numPhase"][j,:,:] != 0) & \
(data["changePhase"][j,:,:] == 1) & \
(data["initPhase"][j,:,:] != 1)
# incrementing phase number
data["numPhase"][j:,:,:] = np.where(
condition,
data["numPhase"][j,:,:] + 1 ,
data["numPhase"][j,:,:],
)
# flagging this day as having been incremented
data["initPhase"][j, :, :] = xr.where(
condition,
1,
data["initPhase"][j, :, :]
)
return data
def update_thermal_time_previous_phase(j, data, num_phase):
"""
This function stores the present thermal time threshold in the
seuilTempPhasePrec variable.
Args:
j (_type_): _description_
data (_type_): _description_
num_phase (_type_): _description_
Returns:
_type_: _description_
"""
condition = \
(data["numPhase"][j,:,:] == num_phase) & \
(data["changePhase"][j,:,:] == 1)
data["seuilTempPhasePrec"][j:,:,:] = xr.where(
condition,
data["seuilTempPhaseSuivante"][j,:,:],
data["seuilTempPhasePrec"][j,:,:]
)
return data
def update_pheno_phase_1_to_2(j, data, paramVariete):
"""
This function manages phase change from phases number 1 to 2.
First, it flags the day for phase change : If numPhase is 1 and sum of
thermal time is above the threshold (which value comes here from the
previous function testing_for_initialization), we set changePhase of this
particular day to 1.
Second, we update the thermal time to next phase : if numPhase is 1 and
changePhase is 1 (meaning that we are at the transition day between phases 1
and 2), we set the sum of thermal time to the next phase as SDJLevee ; we
broadcast the value over remaining days.
We do it before updating phase number because we need to test what is the
phase number before updating it
Third, we update the phase number : if numPhase is different from 0 and
changePhase is 1 (meaning that we are at the transition day between two
phases, to the exception of transition day between phases 0 and 1), we
increment numPhase by 1 ; we broadcast the value over remaining days.
Args:
j (_type_): _description_
data (_type_): _description_
paramITK (_type_): _description_
paramVariete (_type_): _description_
Returns:
_type_: _description_
"""
num_phase = 1
thermal_time_threshold = paramVariete["SDJLevee"]
# flagging the day for phase change
data = flag_change_phase(j, data, num_phase)
# updating thermal time to next phase
data = update_thermal_time_next_phase(j, data, num_phase, thermal_time_threshold)
# updating phase number and flagging incrementation
data = increment_phase_number(j, data)
return data
def update_pheno_phase_2_to_3(j, data, paramVariete):
"""
This function manages phase change from phases number 2 to 3.
It has the same structure as update_pheno_phase_1_to_2, with the exception
of update_thermal_time_previous_phase function, which is called to store the
present thermal time threshold in the seuilTempPhasePrec variable.
Args:
j (_type_): _description_
data (_type_): _description_
paramITK (_type_): _description_
paramVariete (_type_): _description_
Returns:
_type_: _description_
"""
num_phase = 2
thermal_time_threshold = paramVariete["SDJBVP"]
# flagging the day for phase change
data = flag_change_phase(j, data, num_phase)
# saving "previous thermal time to next phase" to be used
data = update_thermal_time_previous_phase(j, data, num_phase)
# updating thermal time to next phase
data = update_thermal_time_next_phase(j, data, num_phase, thermal_time_threshold)
# updating phase number and flagging incrementation
data = increment_phase_number(j, data)
return data
def update_pheno_phase_3_to_4(j, data):
"""
This function manages phase change from phases number 3 to 4.
It is specific as phase 3 is photoperiodic ; its length is not computed the
same way as the other phases. Notably, the phasePhotoper flag is updated
with the PhotoperSarrahV3() function.
First, this function flags the day for phase change : If numPhase is 3 and
the phasePhotoper flag is 0, we set changePhase of this particular day to 1.
This means the photoperiodic phase is over.
Second, we update the phasePhotoper flag : if numPhase is 3 and changePhase
is 1 (meaning that we are at the transition day between phases 3 and 4), we
set the phasePhotoper flag to 1.
Third, we update the phase number and flag incrementation using
increment_phase_number().
Args:
j (_type_): _description_
data (_type_): _description_
paramITK (_type_): _description_
paramVariete (_type_): _description_
Returns:
_type_: _description_
"""
# flagging the day for phase change (specific to phase 3)
condition = \
(data["numPhase"][j,:,:] == 3) & \
(data["phasePhotoper"][j,:,:] == 0)
data["changePhase"][j,:,:] = xr.where(
condition,
1,
data["changePhase"][j,:,:],
)
# updating phasePhotoper (specific to phase 3)
condition = \
(data["numPhase"][j,:,:] == 3) & \
(data["changePhase"][j,:,:] == 1)
data["phasePhotoper"][j,:,:] = np.where(
condition,
1,
data["phasePhotoper"][j,:,:],
)
# updating phase number and flagging incrementation
data = increment_phase_number(j, data)
return data
def update_pheno_phase_4_to_5(j, data, paramVariete):
"""
This function manages phase change from phases number 4 to 5.
It has the same structure as update_pheno_phase_2_to_3.
Args:
j (_type_): _description_
data (_type_): _description_
paramITK (_type_): _description_
paramVariete (_type_): _description_
Returns:
_type_: _description_
"""
num_phase = 4
thermal_time_threshold = paramVariete["SDJRPR"]
# flagging the day for phase change
data = flag_change_phase(j, data, num_phase)
# saving "previous thermal time to next phase" to be used
data = update_thermal_time_previous_phase(j, data, num_phase)
# updating thermal time to next phase
data = update_thermal_time_next_phase(j, data, num_phase, thermal_time_threshold)
# updating phase number and flagging incrementation
data = increment_phase_number(j, data)
return data
def update_pheno_phase_5_to_6(j, data, paramVariete):
"""
This function manages phase change from phases number 5 to 6.
It has the same structure as update_pheno_phase_2_to_3.
Args:
j (_type_): _description_
data (_type_): _description_
paramITK (_type_): _description_
paramVariete (_type_): _description_
Returns:
_type_: _description_
"""
num_phase = 5
thermal_time_threshold = paramVariete["SDJMatu1"]
# flagging the day for phase change
data = flag_change_phase(j, data, num_phase)
# saving "previous thermal time to next phase" to be used
data = update_thermal_time_previous_phase(j, data, num_phase)
# updating thermal time to next phase
data = update_thermal_time_next_phase(j, data, num_phase, thermal_time_threshold)
# updating phase number and flagging incrementation
data = increment_phase_number(j, data)
return data
def update_pheno_phase_6_to_7(j, data, paramVariete):
"""
This function manages phase change from phases number 6 to 7.
It has the same structure as update_pheno_phase_2_to_3.
Args:
j (_type_): _description_
data (_type_): _description_
paramITK (_type_): _description_
paramVariete (_type_): _description_
Returns:
_type_: _description_
"""
num_phase = 6
thermal_time_threshold = paramVariete["SDJMatu2"]
# flagging the day for phase change
data = flag_change_phase(j, data, num_phase)
# saving "previous thermal time to next phase" to be used
data = update_thermal_time_previous_phase(j, data, num_phase)
# updating thermal time to next phase
data = update_thermal_time_next_phase(j, data, num_phase, thermal_time_threshold)
# updating phase number and flagging incrementation
data = increment_phase_number(j, data)
return data
def EvalPhenoSarrahV3(j, data, paramITK, paramVariete):
"""
This function manages the evolution of the phenological phases. It is a
wrapper function that calls the specific functions for each phase.
This function is called at the beginning of the day and makes the
phenological phases evolve. For this, it increments the phase number and
changes the value of the thermal time threshold of the next phase.
ChangePhase is a boolean informing the model to know if a day is a day of
phase change, which is used to initialize specific variables in certain
functions. It includes a generic method for the test of the end of the
photoperiodic phase. PhasePhotoper = 0 at the end of the photoperiodic phase
and = 1 at the beginning of the phase.
Phenological phases used in this model (as for cereal crops) :
0. from the sowing day to the beginning of the conditions favorable for
germination, and from the harvest to the end of the simulation (no crop)
1. from the beginning of the conditions favorable for germination to the day
of germination (du début des conditions favorables pour la germination au
jour de la levée)
2. from the day of germination to the beginning of the photoperiodic phase
(du jour de la levée au début de la phase photopériodique)
3. from the beginning of the photoperiodic phase to the beginning of the
reproductive phase
4. from the beginning of the reproductive phase to the beginning of the
maturation (only for maize and rice)
5. from the beginning of the maturation to the grain milk stage (du début de
la maturation au stade grain laiteux)
6. from the grain milk stage to the end of the maturation (du début du stade
grain laiteux au jour de récolte)
7. the day of the harvest
We first test if the considered day has any pixel with the considered
phases, and only if it is the case we either test for initialization or
update the phenological phases.
Notes :
In the case of multiannual continuous simulations, we do not reinitialize
the reservoirs, at harvest we put the moisture front at the depth of the
surface reservoir This allows to keep the rooting constraint phenomenon for
the following season if there is little rain while having the water stock in
depth remaining from the previous season.
This function has been originally translated from the EvalPhenoSarrahV3
procedure of the phenologie.pas and exmodules.pas files of the Sarra-H
model, Pascal version.
"""
# in order to save computational resources, we test if there is
# the time step contains any pixel with the considered phases
# before testing for initialization or updating the phenological phases
data = testing_for_initialization(j, data, paramITK, paramVariete)
data = update_pheno_phase_1_to_2(j, data, paramVariete)
data = update_pheno_phase_2_to_3(j, data, paramVariete)
data = update_pheno_phase_3_to_4(j, data)
data = update_pheno_phase_4_to_5(j, data, paramVariete)
data = update_pheno_phase_5_to_6(j, data, paramVariete)
data = update_pheno_phase_6_to_7(j, data, paramVariete)
return data
def calculate_daily_thermal_time(j, data, paramVariete):
"""calculating daily thermal time
Translated from the EvalDegresJourSarrahV3 procedure of the phenologie.pas and exmodules.pas files of theSarra-H model, Pascal version.
Pb de méthode !?
v1:= ((Max(TMin,TBase)+Min(TOpt1,TMax))/2 -TBase )/( TOpt1 - TBase);
v2:= (TL - max(TMax,TOpt2)) / (TL - TOpt2);
v:= (v1 * (min(TMax,TOpt1) - TMin)+(min(TOpt2,max(TOpt1,TMax)) - TOpt1) + v2 * (max(TOpt2,TMax)-TOpt2))/( TMax-TMin);
DegresDuJour:= v * (TOpt1-TBase);
# If Tmoy <= Topt2 then
# DegresDuJour:= max(min(TOpt1,TMoy),TBase)-Tbase
# else
# DegresDuJour := (TOpt1-TBase) * (1 - ( (min(TL, TMoy) - TOpt2 )/(TL -TOpt2)));
# If (Numphase >=1) then
# SomDegresJour := SomDegresJour + DegresDuJour
# else SomDegresJour := 0;
Returns:
_type_: _description_
"""
data["ddj"][j,:,:] = xr.where(
data["tpMoy"][j,:,:] <= paramVariete["TOpt2"],
np.maximum(np.minimum(paramVariete["TOpt1"], data["tpMoy"][j,:,:]), paramVariete["TBase"]) - paramVariete["TBase"],
(paramVariete["TOpt1"] - paramVariete["TBase"]) * (1 - ((np.minimum(paramVariete["TLim"], data["tpMoy"][j,:,:]) - paramVariete["TOpt2"]) / (paramVariete["TLim"] - paramVariete["TOpt2"]))),
)
return data
def calculate_once_daily_thermal_time(data, paramVariete):
"""calculating daily thermal time
Translated from the EvalDegresJourSarrahV3 procedure of the phenologie.pas and exmodules.pas files of theSarra-H model, Pascal version.
Pb de méthode !?
v1:= ((Max(TMin,TBase)+Min(TOpt1,TMax))/2 -TBase )/( TOpt1 - TBase);
v2:= (TL - max(TMax,TOpt2)) / (TL - TOpt2);
v:= (v1 * (min(TMax,TOpt1) - TMin)+(min(TOpt2,max(TOpt1,TMax)) - TOpt1) + v2 * (max(TOpt2,TMax)-TOpt2))/( TMax-TMin);
DegresDuJour:= v * (TOpt1-TBase);
# If Tmoy <= Topt2 then
# DegresDuJour:= max(min(TOpt1,TMoy),TBase)-Tbase
# else
# DegresDuJour := (TOpt1-TBase) * (1 - ( (min(TL, TMoy) - TOpt2 )/(TL -TOpt2)));
# If (Numphase >=1) then
# SomDegresJour := SomDegresJour + DegresDuJour
# else SomDegresJour := 0;
Returns:
_type_: _description_
"""
data["ddj"].data = xr.where(
data["tpMoy"] <= paramVariete["TOpt2"],
np.maximum(np.minimum(paramVariete["TOpt1"], data["tpMoy"]), paramVariete["TBase"]) - paramVariete["TBase"],
(paramVariete["TOpt1"] - paramVariete["TBase"]) * (1 - ((np.minimum(paramVariete["TLim"], data["tpMoy"]) - paramVariete["TOpt2"]) / (paramVariete["TLim"] - paramVariete["TOpt2"]))),
)
return data
def calculate_sum_of_thermal_time(j, data):
"""
This function calculates the sum of thermal time ("somme de degrés jour",
sdj) for the current day. Accumulation of sum of thermal time is performed
starting from the sowing date, and only on pixels where numPhase is above 0.
If these conditions are not met, sdj is set to 0.
sdj value has to be broadcasted along time dimension (or computed first in
the process list) so that phenology-related functions can work properly.
Here, it is broadcasted.
This function has been translated from the EvalDegresJourSarrahV3 procedure
of the phenologie.pas and exmodules.pas files of the Sarra-H model, Pascal
version.
Note : in SARRA-H, when numPhase > 7, sdj is set to 0 and sdj stops
accumulating. This behavior has not been translated here.
Returns:
_type_: _description_
"""
data["sdj"][j:,:,:] = xr.where(
(j >= data["sowing_date"][j,:,:]) & (data["numPhase"][j,:,:] >= 1),
data["sdj"][j-1,:,:] + data["ddj"][j,:,:],
0,
)
return data
def update_root_growth_speed(j, data, paramVariete):
"""
This function updates the root growth speed (`vRac`, mm/day) according to
the current phase (`numPhase`).
This function has been adapted from the EvalVitesseRacSarraV3 procedure of
the phenologie.pas and exmodules 1 & 2.pas files of the Sarra-H model,
Pascal version.
Args:
j (_type_): _description_
data (_type_): _description_
paramVariete (_type_): _description_
Returns:
_type_: _description_
"""
phase_correspondances = {
1: paramVariete['VRacLevee'],
2: paramVariete['VRacBVP'],
3: paramVariete['VRacPSP'],
4: paramVariete['VRacRPR'],
5: paramVariete['VRacMatu1'],
6: paramVariete['VRacMatu2'],
}
# phases 1 to 6
for phase in range(1,6):
data["vRac"][j:,:,:] = np.where(
data["numPhase"][j,:,:] == phase,
phase_correspondances[phase],
data["vRac"][j,:,:],
)
# phases 0 or 7
data["vRac"][j:,:,:] = np.where(
(data["numPhase"][j,:,:] == 0) | (data["numPhase"][j,:,:] == 7),
0,
data["vRac"][j,:,:],
)
return data
def update_photoperiodism(j, data, paramVariete):
"""
This function aims at managing the photoperiodic sensitivity of the crop.
It first updates the sumPP variable : on the transition day between phase 2
and 3 (numPhase = 3 and changePhase = 1), the sumPP variable is set to 100.
Then, we compute the thermal_time_since_previous_phase (thermal time since
the transition between phases 2 and 3), and the
time_above_critical_day_length, which is the difference between day length
and critical day length PPcrit, in decimal hours.
On all days with numPhase = 3 (so including the transition day), the sumPP
is calculated as a function of thermal_time_since_previous_phase and PPExp
(attenuator for progressive PSP response to PP ; rarely used in calibration
procedure, a robust value is 0.17), multiplied by a ratio between the daily
time above critical day length and the difference between SeuilPP (Upper day
length limit of PP response) and PPCrit (Lower day length limit to PP
response).
Finally, phasePhotoper is updated : when numPhase = 3 and sumPP is lower than
PPsens, phasePhotoper is set to 0. PP sensitivity, important variable. Range
0.3-0.6 is PP sensitive, sensitivity disappears towards values of 0.7 to
1. Described in Dingkuhn et al. 2008; Euro.J.Agron. (Impatience model)
This function has been adapted from the PhotoperSarrahV3 procedure of the
phenologie.pas and exmodules 1 et 2.pas of the Sarra-H model, Pascal version.
Notes CB :
Procedure speciale Vaksman Dingkuhn valable pour tous types de sensibilite
photoperiodique et pour les varietes non photoperiodique. PPsens varie de
0,4 a 1,2. Pour PPsens > 2.5 = variété non photoperiodique.
SeuilPP = 13.5
PPcrit = 12
SumPP est dans ce cas une variable quotidienne (et non un cumul)
Args:
j (_type_): _description_
data (_type_): _description_
paramVariete (_type_): _description_
Returns:
_type_: _description_
"""
thermal_time_since_previous_phase = np.maximum(0.01, data["sdj"][j,:,:] - data["seuilTempPhasePrec"][j,:,:])
time_above_critical_day_length = np.maximum(0, data["dureeDuJour"][j,:,:] - paramVariete["PPCrit"])
data["sumPP"][j,:,:] = np.where(
data["numPhase"][j,:,:] == 3,
np.where(
data["changePhase"][j,:,:] == 1,
# if numPhase = 3 and changePhase == 1, sumPP = 100
100,
# if numPhase = 3 and changePhase != 1, sumPP calculated through formula
((1000 / thermal_time_since_previous_phase) ** (paramVariete["PPExp"])) \
* time_above_critical_day_length / (paramVariete["SeuilPP"] - paramVariete["PPCrit"]),
),
# if numPhase != 3, sumPP is not updated
data["sumPP"][j,:,:],
)
data["phasePhotoper"][j:,:,:] = np.where(
(data["numPhase"][j,:,:] == 3) & (data["sumPP"][j,:,:] < paramVariete["PPsens"]),
0,
data["phasePhotoper"][j,:,:],
)
return data
def MortaliteSarraV3(j, data, paramITK, paramVariete):
"""
This functions tests for death of young plants.
First, for numphase = 2 and changePhase = 1, hence at the transition day
between phase 1 and 2 at this point of the loop, the nbJourCompte and
nbjStress variables are set to 0.
Second, for numPhase equal or above 2, on each day nbJourCompte is
incremented by 1. Thus this part just count days since emergence.
Third, for numPhase equal or above 2, for days where nbJourCompte is lower
than nbjTestSemis and where deltaBiomasseAerienne is negative, the nbjStress
variable is incremented by 1. Thus, we count the number of days with
negative deltaBiomasseAerienne since emergence as stress days.
Finally, for days where nbjStress is equal or higher than
seuilCstrMortality, the crop is reset by setting numPhase,
root_tank_capacity and nbjStress to 0.
This all seems a bit simplistic though, and can be improved.
This function has been adapted from the MortaliteSarraV3 procedure of the
bilancarbonsarra.pas and exmodules 1 & 2.pas codes of the Sarra-H model,
Pascal version.
Args:
j (_type_): _description_
data (_type_): _description_
paramITK (_type_): _description_
paramVariete (_type_): _description_
Returns:
_type_: _description_
"""
condition = (data["numPhase"][j,:,:] >= 2) & \
(data["numPhase"][j,:,:] == 2) & \
(data["changePhase"][j,:,:] == 1)
data['nbJourCompte'][j:,:,:] = np.where(
condition,
0,
data['nbJourCompte'][j,:,:],
)
data['nbjStress'][j:,:,:] = np.where(
condition,
0,
data['nbjStress'][j,:,:],
)
condition = (data["numPhase"][j,:,:] >= 2)
data['nbJourCompte'][j:,:,:] = np.where(
condition,
data['nbJourCompte'][j,:,:] + 1,
data['nbJourCompte'][j,:,:],
)
condition = (data["numPhase"][j,:,:] >= 2) & \
(data["nbJourCompte"][j,:,:] < paramITK["nbjTestSemis"]) & \
(data["deltaBiomasseAerienne"][j,:,:] < 0)
data["nbjStress"][j:,:,:] = np.where(
condition,
data["nbjStress"][j,:,:] + 1,
data["nbjStress"][j,:,:],
)
condition = (data["numPhase"][j,:,:] >= 2) & \
(data["nbjStress"][j,:,:] == paramVariete["seuilCstrMortality"])
data["numPhase"][j,:,:] = np.where(
condition,
0,
data["numPhase"][j,:,:],
)
#! renaming stRurMax with root_tank_capacity
#// data["stRurMax"][j,:,:] = np.where(
data["root_tank_capacity"][j,:,:] = np.where(
condition,
0,
#// data["stRurMax"][j,:,:],
data["root_tank_capacity"][j,:,:],
)
data["nbjStress"][j:,:,:] = np.where(
condition,
0,
data["nbjStress"][j,:,:],
)
return data
Functions
EvalPhenoSarrahV3
def EvalPhenoSarrahV3(
j,
data,
paramITK,
paramVariete
)
This function manages the evolution of the phenological phases. It is a
wrapper function that calls the specific functions for each phase.
This function is called at the beginning of the day and makes the phenological phases evolve. For this, it increments the phase number and changes the value of the thermal time threshold of the next phase. ChangePhase is a boolean informing the model to know if a day is a day of phase change, which is used to initialize specific variables in certain functions. It includes a generic method for the test of the end of the photoperiodic phase. PhasePhotoper = 0 at the end of the photoperiodic phase and = 1 at the beginning of the phase.
Phenological phases used in this model (as for cereal crops) :
-
from the sowing day to the beginning of the conditions favorable for germination, and from the harvest to the end of the simulation (no crop)
-
from the beginning of the conditions favorable for germination to the day of germination (du début des conditions favorables pour la germination au jour de la levée)
-
from the day of germination to the beginning of the photoperiodic phase (du jour de la levée au début de la phase photopériodique)
-
from the beginning of the photoperiodic phase to the beginning of the reproductive phase
-
from the beginning of the reproductive phase to the beginning of the maturation (only for maize and rice)
-
from the beginning of the maturation to the grain milk stage (du début de la maturation au stade grain laiteux)
-
from the grain milk stage to the end of the maturation (du début du stade grain laiteux au jour de récolte)
-
the day of the harvest
We first test if the considered day has any pixel with the considered phases, and only if it is the case we either test for initialization or update the phenological phases.
Notes :
In the case of multiannual continuous simulations, we do not reinitialize the reservoirs, at harvest we put the moisture front at the depth of the surface reservoir This allows to keep the rooting constraint phenomenon for the following season if there is little rain while having the water stock in depth remaining from the previous season.
This function has been originally translated from the EvalPhenoSarrahV3 procedure of the phenologie.pas and exmodules.pas files of the Sarra-H model, Pascal version.
View Source
def EvalPhenoSarrahV3(j, data, paramITK, paramVariete):
"""
This function manages the evolution of the phenological phases. It is a
wrapper function that calls the specific functions for each phase.
This function is called at the beginning of the day and makes the
phenological phases evolve. For this, it increments the phase number and
changes the value of the thermal time threshold of the next phase.
ChangePhase is a boolean informing the model to know if a day is a day of
phase change, which is used to initialize specific variables in certain
functions. It includes a generic method for the test of the end of the
photoperiodic phase. PhasePhotoper = 0 at the end of the photoperiodic phase
and = 1 at the beginning of the phase.
Phenological phases used in this model (as for cereal crops) :
0. from the sowing day to the beginning of the conditions favorable for
germination, and from the harvest to the end of the simulation (no crop)
1. from the beginning of the conditions favorable for germination to the day
of germination (du début des conditions favorables pour la germination au
jour de la levée)
2. from the day of germination to the beginning of the photoperiodic phase
(du jour de la levée au début de la phase photopériodique)
3. from the beginning of the photoperiodic phase to the beginning of the
reproductive phase
4. from the beginning of the reproductive phase to the beginning of the
maturation (only for maize and rice)
5. from the beginning of the maturation to the grain milk stage (du début de
la maturation au stade grain laiteux)
6. from the grain milk stage to the end of the maturation (du début du stade
grain laiteux au jour de récolte)
7. the day of the harvest
We first test if the considered day has any pixel with the considered
phases, and only if it is the case we either test for initialization or
update the phenological phases.
Notes :
In the case of multiannual continuous simulations, we do not reinitialize
the reservoirs, at harvest we put the moisture front at the depth of the
surface reservoir This allows to keep the rooting constraint phenomenon for
the following season if there is little rain while having the water stock in
depth remaining from the previous season.
This function has been originally translated from the EvalPhenoSarrahV3
procedure of the phenologie.pas and exmodules.pas files of the Sarra-H
model, Pascal version.
"""
# in order to save computational resources, we test if there is
# the time step contains any pixel with the considered phases
# before testing for initialization or updating the phenological phases
data = testing_for_initialization(j, data, paramITK, paramVariete)
data = update_pheno_phase_1_to_2(j, data, paramVariete)
data = update_pheno_phase_2_to_3(j, data, paramVariete)
data = update_pheno_phase_3_to_4(j, data)
data = update_pheno_phase_4_to_5(j, data, paramVariete)
data = update_pheno_phase_5_to_6(j, data, paramVariete)
data = update_pheno_phase_6_to_7(j, data, paramVariete)
return data
MortaliteSarraV3
def MortaliteSarraV3(
j,
data,
paramITK,
paramVariete
)
This functions tests for death of young plants.
First, for numphase = 2 and changePhase = 1, hence at the transition day between phase 1 and 2 at this point of the loop, the nbJourCompte and nbjStress variables are set to 0.
Second, for numPhase equal or above 2, on each day nbJourCompte is incremented by 1. Thus this part just count days since emergence.
Third, for numPhase equal or above 2, for days where nbJourCompte is lower than nbjTestSemis and where deltaBiomasseAerienne is negative, the nbjStress variable is incremented by 1. Thus, we count the number of days with negative deltaBiomasseAerienne since emergence as stress days.
Finally, for days where nbjStress is equal or higher than seuilCstrMortality, the crop is reset by setting numPhase, root_tank_capacity and nbjStress to 0.
This all seems a bit simplistic though, and can be improved.
This function has been adapted from the MortaliteSarraV3 procedure of the bilancarbonsarra.pas and exmodules 1 & 2.pas codes of the Sarra-H model, Pascal version.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
paramITK | type | description | None |
paramVariete | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def MortaliteSarraV3(j, data, paramITK, paramVariete):
"""
This functions tests for death of young plants.
First, for numphase = 2 and changePhase = 1, hence at the transition day
between phase 1 and 2 at this point of the loop, the nbJourCompte and
nbjStress variables are set to 0.
Second, for numPhase equal or above 2, on each day nbJourCompte is
incremented by 1. Thus this part just count days since emergence.
Third, for numPhase equal or above 2, for days where nbJourCompte is lower
than nbjTestSemis and where deltaBiomasseAerienne is negative, the nbjStress
variable is incremented by 1. Thus, we count the number of days with
negative deltaBiomasseAerienne since emergence as stress days.
Finally, for days where nbjStress is equal or higher than
seuilCstrMortality, the crop is reset by setting numPhase,
root_tank_capacity and nbjStress to 0.
This all seems a bit simplistic though, and can be improved.
This function has been adapted from the MortaliteSarraV3 procedure of the
bilancarbonsarra.pas and exmodules 1 & 2.pas codes of the Sarra-H model,
Pascal version.
Args:
j (_type_): _description_
data (_type_): _description_
paramITK (_type_): _description_
paramVariete (_type_): _description_
Returns:
_type_: _description_
"""
condition = (data["numPhase"][j,:,:] >= 2) & \
(data["numPhase"][j,:,:] == 2) & \
(data["changePhase"][j,:,:] == 1)
data['nbJourCompte'][j:,:,:] = np.where(
condition,
0,
data['nbJourCompte'][j,:,:],
)
data['nbjStress'][j:,:,:] = np.where(
condition,
0,
data['nbjStress'][j,:,:],
)
condition = (data["numPhase"][j,:,:] >= 2)
data['nbJourCompte'][j:,:,:] = np.where(
condition,
data['nbJourCompte'][j,:,:] + 1,
data['nbJourCompte'][j,:,:],
)
condition = (data["numPhase"][j,:,:] >= 2) & \
(data["nbJourCompte"][j,:,:] < paramITK["nbjTestSemis"]) & \
(data["deltaBiomasseAerienne"][j,:,:] < 0)
data["nbjStress"][j:,:,:] = np.where(
condition,
data["nbjStress"][j,:,:] + 1,
data["nbjStress"][j,:,:],
)
condition = (data["numPhase"][j,:,:] >= 2) & \
(data["nbjStress"][j,:,:] == paramVariete["seuilCstrMortality"])
data["numPhase"][j,:,:] = np.where(
condition,
0,
data["numPhase"][j,:,:],
)
#! renaming stRurMax with root_tank_capacity
#// data["stRurMax"][j,:,:] = np.where(
data["root_tank_capacity"][j,:,:] = np.where(
condition,
0,
#// data["stRurMax"][j,:,:],
data["root_tank_capacity"][j,:,:],
)
data["nbjStress"][j:,:,:] = np.where(
condition,
0,
data["nbjStress"][j,:,:],
)
return data
calculate_daily_thermal_time
def calculate_daily_thermal_time(
j,
data,
paramVariete
)
calculating daily thermal time
Translated from the EvalDegresJourSarrahV3 procedure of the phenologie.pas and exmodules.pas files of theSarra-H model, Pascal version. Pb de méthode !? v1:= ((Max(TMin,TBase)+Min(TOpt1,TMax))/2 -TBase )/( TOpt1 - TBase); v2:= (TL - max(TMax,TOpt2)) / (TL - TOpt2); v:= (v1 * (min(TMax,TOpt1) - TMin)+(min(TOpt2,max(TOpt1,TMax)) - TOpt1) + v2 * (max(TOpt2,TMax)-TOpt2))/( TMax-TMin); DegresDuJour:= v * (TOpt1-TBase);
If Tmoy <= Topt2 then
DegresDuJour:= max(min(TOpt1,TMoy),TBase)-Tbase
else
DegresDuJour := (TOpt1-TBase) * (1 - ( (min(TL, TMoy) - TOpt2 )/(TL -TOpt2)));
If (Numphase >=1) then
SomDegresJour := SomDegresJour + DegresDuJour
else SomDegresJour := 0;
Returns:
Type | Description |
---|---|
type | description |
View Source
def calculate_daily_thermal_time(j, data, paramVariete):
"""calculating daily thermal time
Translated from the EvalDegresJourSarrahV3 procedure of the phenologie.pas and exmodules.pas files of theSarra-H model, Pascal version.
Pb de méthode !?
v1:= ((Max(TMin,TBase)+Min(TOpt1,TMax))/2 -TBase )/( TOpt1 - TBase);
v2:= (TL - max(TMax,TOpt2)) / (TL - TOpt2);
v:= (v1 * (min(TMax,TOpt1) - TMin)+(min(TOpt2,max(TOpt1,TMax)) - TOpt1) + v2 * (max(TOpt2,TMax)-TOpt2))/( TMax-TMin);
DegresDuJour:= v * (TOpt1-TBase);
# If Tmoy <= Topt2 then
# DegresDuJour:= max(min(TOpt1,TMoy),TBase)-Tbase
# else
# DegresDuJour := (TOpt1-TBase) * (1 - ( (min(TL, TMoy) - TOpt2 )/(TL -TOpt2)));
# If (Numphase >=1) then
# SomDegresJour := SomDegresJour + DegresDuJour
# else SomDegresJour := 0;
Returns:
_type_: _description_
"""
data["ddj"][j,:,:] = xr.where(
data["tpMoy"][j,:,:] <= paramVariete["TOpt2"],
np.maximum(np.minimum(paramVariete["TOpt1"], data["tpMoy"][j,:,:]), paramVariete["TBase"]) - paramVariete["TBase"],
(paramVariete["TOpt1"] - paramVariete["TBase"]) * (1 - ((np.minimum(paramVariete["TLim"], data["tpMoy"][j,:,:]) - paramVariete["TOpt2"]) / (paramVariete["TLim"] - paramVariete["TOpt2"]))),
)
return data
calculate_once_daily_thermal_time
def calculate_once_daily_thermal_time(
data,
paramVariete
)
calculating daily thermal time
Translated from the EvalDegresJourSarrahV3 procedure of the phenologie.pas and exmodules.pas files of theSarra-H model, Pascal version. Pb de méthode !? v1:= ((Max(TMin,TBase)+Min(TOpt1,TMax))/2 -TBase )/( TOpt1 - TBase); v2:= (TL - max(TMax,TOpt2)) / (TL - TOpt2); v:= (v1 * (min(TMax,TOpt1) - TMin)+(min(TOpt2,max(TOpt1,TMax)) - TOpt1) + v2 * (max(TOpt2,TMax)-TOpt2))/( TMax-TMin); DegresDuJour:= v * (TOpt1-TBase);
If Tmoy <= Topt2 then
DegresDuJour:= max(min(TOpt1,TMoy),TBase)-Tbase
else
DegresDuJour := (TOpt1-TBase) * (1 - ( (min(TL, TMoy) - TOpt2 )/(TL -TOpt2)));
If (Numphase >=1) then
SomDegresJour := SomDegresJour + DegresDuJour
else SomDegresJour := 0;
Returns:
Type | Description |
---|---|
type | description |
View Source
def calculate_once_daily_thermal_time(data, paramVariete):
"""calculating daily thermal time
Translated from the EvalDegresJourSarrahV3 procedure of the phenologie.pas and exmodules.pas files of theSarra-H model, Pascal version.
Pb de méthode !?
v1:= ((Max(TMin,TBase)+Min(TOpt1,TMax))/2 -TBase )/( TOpt1 - TBase);
v2:= (TL - max(TMax,TOpt2)) / (TL - TOpt2);
v:= (v1 * (min(TMax,TOpt1) - TMin)+(min(TOpt2,max(TOpt1,TMax)) - TOpt1) + v2 * (max(TOpt2,TMax)-TOpt2))/( TMax-TMin);
DegresDuJour:= v * (TOpt1-TBase);
# If Tmoy <= Topt2 then
# DegresDuJour:= max(min(TOpt1,TMoy),TBase)-Tbase
# else
# DegresDuJour := (TOpt1-TBase) * (1 - ( (min(TL, TMoy) - TOpt2 )/(TL -TOpt2)));
# If (Numphase >=1) then
# SomDegresJour := SomDegresJour + DegresDuJour
# else SomDegresJour := 0;
Returns:
_type_: _description_
"""
data["ddj"].data = xr.where(
data["tpMoy"] <= paramVariete["TOpt2"],
np.maximum(np.minimum(paramVariete["TOpt1"], data["tpMoy"]), paramVariete["TBase"]) - paramVariete["TBase"],
(paramVariete["TOpt1"] - paramVariete["TBase"]) * (1 - ((np.minimum(paramVariete["TLim"], data["tpMoy"]) - paramVariete["TOpt2"]) / (paramVariete["TLim"] - paramVariete["TOpt2"]))),
)
return data
calculate_sum_of_thermal_time
def calculate_sum_of_thermal_time(
j,
data
)
This function calculates the sum of thermal time ("somme de degrés jour",
sdj) for the current day. Accumulation of sum of thermal time is performed starting from the sowing date, and only on pixels where numPhase is above 0. If these conditions are not met, sdj is set to 0.
sdj value has to be broadcasted along time dimension (or computed first in the process list) so that phenology-related functions can work properly. Here, it is broadcasted.
This function has been translated from the EvalDegresJourSarrahV3 procedure of the phenologie.pas and exmodules.pas files of the Sarra-H model, Pascal version.
Note : in SARRA-H, when numPhase > 7, sdj is set to 0 and sdj stops accumulating. This behavior has not been translated here.
Returns:
Type | Description |
---|---|
type | description |
View Source
def calculate_sum_of_thermal_time(j, data):
"""
This function calculates the sum of thermal time ("somme de degrés jour",
sdj) for the current day. Accumulation of sum of thermal time is performed
starting from the sowing date, and only on pixels where numPhase is above 0.
If these conditions are not met, sdj is set to 0.
sdj value has to be broadcasted along time dimension (or computed first in
the process list) so that phenology-related functions can work properly.
Here, it is broadcasted.
This function has been translated from the EvalDegresJourSarrahV3 procedure
of the phenologie.pas and exmodules.pas files of the Sarra-H model, Pascal
version.
Note : in SARRA-H, when numPhase > 7, sdj is set to 0 and sdj stops
accumulating. This behavior has not been translated here.
Returns:
_type_: _description_
"""
data["sdj"][j:,:,:] = xr.where(
(j >= data["sowing_date"][j,:,:]) & (data["numPhase"][j,:,:] >= 1),
data["sdj"][j-1,:,:] + data["ddj"][j,:,:],
0,
)
return data
flag_change_phase
def flag_change_phase(
j,
data,
num_phase
)
This function flags the day for phase change.
If the phase number is above the num_phase value, and if the sum of thermal time is above the threshold, this function returns changePhase flags.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
num_phase | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def flag_change_phase(j, data, num_phase):
"""
This function flags the day for phase change.
If the phase number is above the num_phase value, and if the sum of thermal
time is above the threshold, this function returns changePhase flags.
Args:
j (_type_): _description_
data (_type_): _description_
num_phase (_type_): _description_
Returns:
_type_: _description_
"""
# flagging the day for phase change
condition = \
(data["numPhase"][j,:,:] == num_phase) & \
(data["sdj"][j,:,:] >= data["seuilTempPhaseSuivante"][j,:,:])
data["changePhase"][j,:,:] = xr.where(
condition,
1,
data["changePhase"][j,:,:],
)
return data
increment_phase_number
def increment_phase_number(
j,
data
)
This function increments the phase number.
When the phase number is not 0, and if changePhase is 1 (meaning that we are at a phase transition day), and initPhase is 0 (meaning that the phase number has not been incremented yet this day), the phase number is incremented by 1. Also, the phase change flag initPhase is set to 1.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def increment_phase_number(j, data):
"""
This function increments the phase number.
When the phase number is not 0, and if changePhase is 1 (meaning that we are
at a phase transition day), and initPhase is 0 (meaning that the phase
number has not been incremented yet this day), the phase number is
incremented by 1. Also, the phase change flag initPhase is set to 1.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
condition = \
(data["numPhase"][j,:,:] != 0) & \
(data["changePhase"][j,:,:] == 1) & \
(data["initPhase"][j,:,:] != 1)
# incrementing phase number
data["numPhase"][j:,:,:] = np.where(
condition,
data["numPhase"][j,:,:] + 1 ,
data["numPhase"][j,:,:],
)
# flagging this day as having been incremented
data["initPhase"][j, :, :] = xr.where(
condition,
1,
data["initPhase"][j, :, :]
)
return data
reset
def reset(
j,
data
)
View Source
def reset(j, data):
data = data.copy(deep=True)
# when reaching stage 7, we reset the main phenological variables to zero
data["changePhase"][j:,:,:] = np.where(data["numPhase"][j,:,:] == 7, 0, data["changePhase"][j,:,:])#[np.newaxis,...]
data["sdj"][j:,:,:] = np.where(data["numPhase"][j,:,:] == 7, 0, data["sdj"][j,:,:])#[np.newaxis,...]
data["ruRac"][j:,:,:] = np.where(data["numPhase"][j,:,:] == 7, 0, data["numPhase"][j,:,:])#[np.newaxis,...]
data["nbJourCompte"][j:,:,:] = np.where(data["numPhase"][j,:,:] == 7, 0, data["numPhase"][j,:,:])#[np.newaxis,...]
data["startLock"][j:,:,:] = np.where(data["numPhase"][j,:,:] == 7, 1, data["startLock"][j,:,:])#[np.newaxis,...]
# and we leave numPhas last
data["numPhase"][j:,:,:] = np.where(data["numPhase"][j,:,:] == 7, 0, data["numPhase"][j,:,:])#[np.newaxis,...]
return data
testing_for_initialization
def testing_for_initialization(
j,
data,
paramITK,
paramVariete
)
This function tests if the conditions are met to initiate the crop.
If numPhase is 0, if the current day is equal or above the sowing date, and if surface_tank_stock is above the threshold for sowing, we initiate the crop :
1) we set numPhase to 1 ; we broadcast the value over remaining days. 2) we set changePhase of this particular day to 1. 3) set the sum of thermal time to the next phase (seuilTempPhaseSuivante) to be SDJLevee ; we broadcast the value over remaining days. 4) we set initPhase to 1 ; we broadcast the value over remaining days.
initPhase is only used in update_pheno_phase_1_to_2 function, so that we do not go directly from phase 0 to phase 2. It is used as a specific flag for phase 0 to 1 transition.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
paramITK | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def testing_for_initialization(j, data, paramITK, paramVariete):
"""
This function tests if the conditions are met to initiate the crop.
If numPhase is 0, if the current day is equal or above the sowing date, and
if surface_tank_stock is above the threshold for sowing, we initiate the
crop :
1) we set numPhase to 1 ; we broadcast the value over remaining days.
2) we set changePhase of this particular day to 1.
3) set the sum of thermal time to the next phase (seuilTempPhaseSuivante) to
be SDJLevee ; we broadcast the value over remaining days.
4) we set initPhase to 1 ; we broadcast the value over remaining days.
initPhase is only used in update_pheno_phase_1_to_2 function, so that we do
not go directly from phase 0 to phase 2. It is used as a specific flag for
phase 0 to 1 transition.
Args:
j (_type_): _description_
data (_type_): _description_
paramITK (_type_): _description_
Returns:
_type_: _description_
"""
#! replacing stRuSurf by surface_tank_stock
condition = \
(data["numPhase"][j, :, :] == 0) & \
(j >= data["sowing_date"][j,:,:]) & \
(data["surface_tank_stock"][j, :, :] >= paramITK["seuilEauSemis"])
# & (data["startLock"][j,:,:] == 0)
data["numPhase"][j:, :, :] = xr.where(
condition, 1, data["numPhase"][j, :, :])
data["changePhase"][j, :, :] = xr.where(
condition, 1, data["changePhase"][j, :, :])
data["seuilTempPhaseSuivante"][j:, :, :] = xr.where(
condition,
paramVariete["SDJLevee"],
data["seuilTempPhaseSuivante"][j, :, :],
)
#flagging phase change has been done
data["initPhase"][j, :, :] = xr.where(
condition,
1,
data["initPhase"][j, :, :]
)
return data
update_pheno_phase_1_to_2
def update_pheno_phase_1_to_2(
j,
data,
paramVariete
)
This function manages phase change from phases number 1 to 2.
First, it flags the day for phase change : If numPhase is 1 and sum of thermal time is above the threshold (which value comes here from the previous function testing_for_initialization), we set changePhase of this particular day to 1.
Second, we update the thermal time to next phase : if numPhase is 1 and changePhase is 1 (meaning that we are at the transition day between phases 1 and 2), we set the sum of thermal time to the next phase as SDJLevee ; we broadcast the value over remaining days.
We do it before updating phase number because we need to test what is the phase number before updating it
Third, we update the phase number : if numPhase is different from 0 and changePhase is 1 (meaning that we are at the transition day between two phases, to the exception of transition day between phases 0 and 1), we increment numPhase by 1 ; we broadcast the value over remaining days.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
paramITK | type | description | None |
paramVariete | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_pheno_phase_1_to_2(j, data, paramVariete):
"""
This function manages phase change from phases number 1 to 2.
First, it flags the day for phase change : If numPhase is 1 and sum of
thermal time is above the threshold (which value comes here from the
previous function testing_for_initialization), we set changePhase of this
particular day to 1.
Second, we update the thermal time to next phase : if numPhase is 1 and
changePhase is 1 (meaning that we are at the transition day between phases 1
and 2), we set the sum of thermal time to the next phase as SDJLevee ; we
broadcast the value over remaining days.
We do it before updating phase number because we need to test what is the
phase number before updating it
Third, we update the phase number : if numPhase is different from 0 and
changePhase is 1 (meaning that we are at the transition day between two
phases, to the exception of transition day between phases 0 and 1), we
increment numPhase by 1 ; we broadcast the value over remaining days.
Args:
j (_type_): _description_
data (_type_): _description_
paramITK (_type_): _description_
paramVariete (_type_): _description_
Returns:
_type_: _description_
"""
num_phase = 1
thermal_time_threshold = paramVariete["SDJLevee"]
# flagging the day for phase change
data = flag_change_phase(j, data, num_phase)
# updating thermal time to next phase
data = update_thermal_time_next_phase(j, data, num_phase, thermal_time_threshold)
# updating phase number and flagging incrementation
data = increment_phase_number(j, data)
return data
update_pheno_phase_2_to_3
def update_pheno_phase_2_to_3(
j,
data,
paramVariete
)
This function manages phase change from phases number 2 to 3.
It has the same structure as update_pheno_phase_1_to_2, with the exception of update_thermal_time_previous_phase function, which is called to store the present thermal time threshold in the seuilTempPhasePrec variable.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
paramITK | type | description | None |
paramVariete | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_pheno_phase_2_to_3(j, data, paramVariete):
"""
This function manages phase change from phases number 2 to 3.
It has the same structure as update_pheno_phase_1_to_2, with the exception
of update_thermal_time_previous_phase function, which is called to store the
present thermal time threshold in the seuilTempPhasePrec variable.
Args:
j (_type_): _description_
data (_type_): _description_
paramITK (_type_): _description_
paramVariete (_type_): _description_
Returns:
_type_: _description_
"""
num_phase = 2
thermal_time_threshold = paramVariete["SDJBVP"]
# flagging the day for phase change
data = flag_change_phase(j, data, num_phase)
# saving "previous thermal time to next phase" to be used
data = update_thermal_time_previous_phase(j, data, num_phase)
# updating thermal time to next phase
data = update_thermal_time_next_phase(j, data, num_phase, thermal_time_threshold)
# updating phase number and flagging incrementation
data = increment_phase_number(j, data)
return data
update_pheno_phase_3_to_4
def update_pheno_phase_3_to_4(
j,
data
)
This function manages phase change from phases number 3 to 4.
It is specific as phase 3 is photoperiodic ; its length is not computed the same way as the other phases. Notably, the phasePhotoper flag is updated with the PhotoperSarrahV3() function.
First, this function flags the day for phase change : If numPhase is 3 and the phasePhotoper flag is 0, we set changePhase of this particular day to 1. This means the photoperiodic phase is over.
Second, we update the phasePhotoper flag : if numPhase is 3 and changePhase is 1 (meaning that we are at the transition day between phases 3 and 4), we set the phasePhotoper flag to 1.
Third, we update the phase number and flag incrementation using increment_phase_number().
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
paramITK | type | description | None |
paramVariete | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_pheno_phase_3_to_4(j, data):
"""
This function manages phase change from phases number 3 to 4.
It is specific as phase 3 is photoperiodic ; its length is not computed the
same way as the other phases. Notably, the phasePhotoper flag is updated
with the PhotoperSarrahV3() function.
First, this function flags the day for phase change : If numPhase is 3 and
the phasePhotoper flag is 0, we set changePhase of this particular day to 1.
This means the photoperiodic phase is over.
Second, we update the phasePhotoper flag : if numPhase is 3 and changePhase
is 1 (meaning that we are at the transition day between phases 3 and 4), we
set the phasePhotoper flag to 1.
Third, we update the phase number and flag incrementation using
increment_phase_number().
Args:
j (_type_): _description_
data (_type_): _description_
paramITK (_type_): _description_
paramVariete (_type_): _description_
Returns:
_type_: _description_
"""
# flagging the day for phase change (specific to phase 3)
condition = \
(data["numPhase"][j,:,:] == 3) & \
(data["phasePhotoper"][j,:,:] == 0)
data["changePhase"][j,:,:] = xr.where(
condition,
1,
data["changePhase"][j,:,:],
)
# updating phasePhotoper (specific to phase 3)
condition = \
(data["numPhase"][j,:,:] == 3) & \
(data["changePhase"][j,:,:] == 1)
data["phasePhotoper"][j,:,:] = np.where(
condition,
1,
data["phasePhotoper"][j,:,:],
)
# updating phase number and flagging incrementation
data = increment_phase_number(j, data)
return data
update_pheno_phase_4_to_5
def update_pheno_phase_4_to_5(
j,
data,
paramVariete
)
This function manages phase change from phases number 4 to 5.
It has the same structure as update_pheno_phase_2_to_3.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
paramITK | type | description | None |
paramVariete | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_pheno_phase_4_to_5(j, data, paramVariete):
"""
This function manages phase change from phases number 4 to 5.
It has the same structure as update_pheno_phase_2_to_3.
Args:
j (_type_): _description_
data (_type_): _description_
paramITK (_type_): _description_
paramVariete (_type_): _description_
Returns:
_type_: _description_
"""
num_phase = 4
thermal_time_threshold = paramVariete["SDJRPR"]
# flagging the day for phase change
data = flag_change_phase(j, data, num_phase)
# saving "previous thermal time to next phase" to be used
data = update_thermal_time_previous_phase(j, data, num_phase)
# updating thermal time to next phase
data = update_thermal_time_next_phase(j, data, num_phase, thermal_time_threshold)
# updating phase number and flagging incrementation
data = increment_phase_number(j, data)
return data
update_pheno_phase_5_to_6
def update_pheno_phase_5_to_6(
j,
data,
paramVariete
)
This function manages phase change from phases number 5 to 6.
It has the same structure as update_pheno_phase_2_to_3.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
paramITK | type | description | None |
paramVariete | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_pheno_phase_5_to_6(j, data, paramVariete):
"""
This function manages phase change from phases number 5 to 6.
It has the same structure as update_pheno_phase_2_to_3.
Args:
j (_type_): _description_
data (_type_): _description_
paramITK (_type_): _description_
paramVariete (_type_): _description_
Returns:
_type_: _description_
"""
num_phase = 5
thermal_time_threshold = paramVariete["SDJMatu1"]
# flagging the day for phase change
data = flag_change_phase(j, data, num_phase)
# saving "previous thermal time to next phase" to be used
data = update_thermal_time_previous_phase(j, data, num_phase)
# updating thermal time to next phase
data = update_thermal_time_next_phase(j, data, num_phase, thermal_time_threshold)
# updating phase number and flagging incrementation
data = increment_phase_number(j, data)
return data
update_pheno_phase_6_to_7
def update_pheno_phase_6_to_7(
j,
data,
paramVariete
)
This function manages phase change from phases number 6 to 7.
It has the same structure as update_pheno_phase_2_to_3.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
paramITK | type | description | None |
paramVariete | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_pheno_phase_6_to_7(j, data, paramVariete):
"""
This function manages phase change from phases number 6 to 7.
It has the same structure as update_pheno_phase_2_to_3.
Args:
j (_type_): _description_
data (_type_): _description_
paramITK (_type_): _description_
paramVariete (_type_): _description_
Returns:
_type_: _description_
"""
num_phase = 6
thermal_time_threshold = paramVariete["SDJMatu2"]
# flagging the day for phase change
data = flag_change_phase(j, data, num_phase)
# saving "previous thermal time to next phase" to be used
data = update_thermal_time_previous_phase(j, data, num_phase)
# updating thermal time to next phase
data = update_thermal_time_next_phase(j, data, num_phase, thermal_time_threshold)
# updating phase number and flagging incrementation
data = increment_phase_number(j, data)
return data
update_photoperiodism
def update_photoperiodism(
j,
data,
paramVariete
)
This function aims at managing the photoperiodic sensitivity of the crop.
It first updates the sumPP variable : on the transition day between phase 2 and 3 (numPhase = 3 and changePhase = 1), the sumPP variable is set to 100.
Then, we compute the thermal_time_since_previous_phase (thermal time since the transition between phases 2 and 3), and the time_above_critical_day_length, which is the difference between day length and critical day length PPcrit, in decimal hours.
On all days with numPhase = 3 (so including the transition day), the sumPP is calculated as a function of thermal_time_since_previous_phase and PPExp (attenuator for progressive PSP response to PP ; rarely used in calibration procedure, a robust value is 0.17), multiplied by a ratio between the daily time above critical day length and the difference between SeuilPP (Upper day length limit of PP response) and PPCrit (Lower day length limit to PP response).
Finally, phasePhotoper is updated : when numPhase = 3 and sumPP is lower than PPsens, phasePhotoper is set to 0. PP sensitivity, important variable. Range 0.3-0.6 is PP sensitive, sensitivity disappears towards values of 0.7 to 1. Described in Dingkuhn et al. 2008; Euro.J.Agron. (Impatience model)
This function has been adapted from the PhotoperSarrahV3 procedure of the phenologie.pas and exmodules 1 et 2.pas of the Sarra-H model, Pascal version.
Notes CB : Procedure speciale Vaksman Dingkuhn valable pour tous types de sensibilite photoperiodique et pour les varietes non photoperiodique. PPsens varie de 0,4 a 1,2. Pour PPsens > 2.5 = variété non photoperiodique. SeuilPP = 13.5 PPcrit = 12 SumPP est dans ce cas une variable quotidienne (et non un cumul)
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
paramVariete | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_photoperiodism(j, data, paramVariete):
"""
This function aims at managing the photoperiodic sensitivity of the crop.
It first updates the sumPP variable : on the transition day between phase 2
and 3 (numPhase = 3 and changePhase = 1), the sumPP variable is set to 100.
Then, we compute the thermal_time_since_previous_phase (thermal time since
the transition between phases 2 and 3), and the
time_above_critical_day_length, which is the difference between day length
and critical day length PPcrit, in decimal hours.
On all days with numPhase = 3 (so including the transition day), the sumPP
is calculated as a function of thermal_time_since_previous_phase and PPExp
(attenuator for progressive PSP response to PP ; rarely used in calibration
procedure, a robust value is 0.17), multiplied by a ratio between the daily
time above critical day length and the difference between SeuilPP (Upper day
length limit of PP response) and PPCrit (Lower day length limit to PP
response).
Finally, phasePhotoper is updated : when numPhase = 3 and sumPP is lower than
PPsens, phasePhotoper is set to 0. PP sensitivity, important variable. Range
0.3-0.6 is PP sensitive, sensitivity disappears towards values of 0.7 to
1. Described in Dingkuhn et al. 2008; Euro.J.Agron. (Impatience model)
This function has been adapted from the PhotoperSarrahV3 procedure of the
phenologie.pas and exmodules 1 et 2.pas of the Sarra-H model, Pascal version.
Notes CB :
Procedure speciale Vaksman Dingkuhn valable pour tous types de sensibilite
photoperiodique et pour les varietes non photoperiodique. PPsens varie de
0,4 a 1,2. Pour PPsens > 2.5 = variété non photoperiodique.
SeuilPP = 13.5
PPcrit = 12
SumPP est dans ce cas une variable quotidienne (et non un cumul)
Args:
j (_type_): _description_
data (_type_): _description_
paramVariete (_type_): _description_
Returns:
_type_: _description_
"""
thermal_time_since_previous_phase = np.maximum(0.01, data["sdj"][j,:,:] - data["seuilTempPhasePrec"][j,:,:])
time_above_critical_day_length = np.maximum(0, data["dureeDuJour"][j,:,:] - paramVariete["PPCrit"])
data["sumPP"][j,:,:] = np.where(
data["numPhase"][j,:,:] == 3,
np.where(
data["changePhase"][j,:,:] == 1,
# if numPhase = 3 and changePhase == 1, sumPP = 100
100,
# if numPhase = 3 and changePhase != 1, sumPP calculated through formula
((1000 / thermal_time_since_previous_phase) ** (paramVariete["PPExp"])) \
* time_above_critical_day_length / (paramVariete["SeuilPP"] - paramVariete["PPCrit"]),
),
# if numPhase != 3, sumPP is not updated
data["sumPP"][j,:,:],
)
data["phasePhotoper"][j:,:,:] = np.where(
(data["numPhase"][j,:,:] == 3) & (data["sumPP"][j,:,:] < paramVariete["PPsens"]),
0,
data["phasePhotoper"][j,:,:],
)
return data
update_root_growth_speed
def update_root_growth_speed(
j,
data,
paramVariete
)
This function updates the root growth speed (vRac
, mm/day) according to
the current phase (numPhase
).
This function has been adapted from the EvalVitesseRacSarraV3 procedure of the phenologie.pas and exmodules 1 & 2.pas files of the Sarra-H model, Pascal version.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
paramVariete | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_root_growth_speed(j, data, paramVariete):
"""
This function updates the root growth speed (`vRac`, mm/day) according to
the current phase (`numPhase`).
This function has been adapted from the EvalVitesseRacSarraV3 procedure of
the phenologie.pas and exmodules 1 & 2.pas files of the Sarra-H model,
Pascal version.
Args:
j (_type_): _description_
data (_type_): _description_
paramVariete (_type_): _description_
Returns:
_type_: _description_
"""
phase_correspondances = {
1: paramVariete['VRacLevee'],
2: paramVariete['VRacBVP'],
3: paramVariete['VRacPSP'],
4: paramVariete['VRacRPR'],
5: paramVariete['VRacMatu1'],
6: paramVariete['VRacMatu2'],
}
# phases 1 to 6
for phase in range(1,6):
data["vRac"][j:,:,:] = np.where(
data["numPhase"][j,:,:] == phase,
phase_correspondances[phase],
data["vRac"][j,:,:],
)
# phases 0 or 7
data["vRac"][j:,:,:] = np.where(
(data["numPhase"][j,:,:] == 0) | (data["numPhase"][j,:,:] == 7),
0,
data["vRac"][j,:,:],
)
return data
update_thermal_time_next_phase
def update_thermal_time_next_phase(
j,
data,
num_phase,
thermal_time_threshold
)
This function updates the sum of thermal time needed to reach the next
phase.
When numPhase equals the requested phase number, and if changePhase is 1 (meaning that we are at a phase transition day), the seuilTempPhaseSuivante is incremented by the thermal_time_threshold value. This value is stage-specific :
- 1 to 2 : SDJLevee
- 2 to 3 : SDJBVP
- 4 to 5 : SDJRPR
- 5 to 6 : SDJMatu1
- 6 to 7 : SDJMatu2
These parameters are passed explicitly when calling this function.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
num_phase | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_thermal_time_next_phase(j, data, num_phase, thermal_time_threshold):
"""
This function updates the sum of thermal time needed to reach the next
phase.
When numPhase equals the requested phase number, and if changePhase is 1
(meaning that we are at a phase transition day), the seuilTempPhaseSuivante
is incremented by the thermal_time_threshold value. This value is
stage-specific :
- 1 to 2 : SDJLevee
- 2 to 3 : SDJBVP
- 4 to 5 : SDJRPR
- 5 to 6 : SDJMatu1
- 6 to 7 : SDJMatu2
These parameters are passed explicitly when calling this function.
Args:
j (_type_): _description_
data (_type_): _description_
num_phase (_type_): _description_
Returns:
_type_: _description_
"""
condition = \
(data["numPhase"][j,:,:] == num_phase) & \
(data["changePhase"][j,:,:] == 1)
data["seuilTempPhaseSuivante"][j:,:,:] = np.where(
condition,
data["seuilTempPhaseSuivante"][j,:,:] + thermal_time_threshold,
data["seuilTempPhaseSuivante"][j,:,:]
)
return data
update_thermal_time_previous_phase
def update_thermal_time_previous_phase(
j,
data,
num_phase
)
This function stores the present thermal time threshold in the
seuilTempPhasePrec variable.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
num_phase | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_thermal_time_previous_phase(j, data, num_phase):
"""
This function stores the present thermal time threshold in the
seuilTempPhasePrec variable.
Args:
j (_type_): _description_
data (_type_): _description_
num_phase (_type_): _description_
Returns:
_type_: _description_
"""
condition = \
(data["numPhase"][j,:,:] == num_phase) & \
(data["changePhase"][j,:,:] == 1)
data["seuilTempPhasePrec"][j:,:,:] = xr.where(
condition,
data["seuilTempPhaseSuivante"][j,:,:],
data["seuilTempPhasePrec"][j,:,:]
)
return data