Module sarra_py.bilan_hydro
View Source
import numpy as np
import xarray as xr
from sarra_py.bilan_carbo import estimate_kcp
def InitPlotMc(data, grid_width, grid_height, paramITK, paramTypeSol, duration):
"""
Initializes variables related to crop residues boimass (mulch) in the data
xarray dataset. This code has been adapted from the original InitPlotMc
procedure, Bileau.pas code. Comments with tab indentation are from the
original code. As the rain is the first variable to be initialized in the
data xarray dataset, its dimensions are used to initialize the other
variables.
"""
# Soil maximum water storage capacity (mm)
# Capacité maximale de la RU (mm)
# StRurMax := Ru * ProfRacIni / 1000;
#! renaming stRurMax with root_tank_capacity
#// data["stRurMax"] = data["ru"] * paramITK["profRacIni"] / 1000
data["root_tank_capacity"] = (data["rain"].dims, np.repeat(np.array(data["ru"] * paramITK["profRacIni"] / 1000)[np.newaxis,:,:], duration, axis=0))
#// data["stRurMax"].attrs = {"units": "mm", "long_name": "Soil maximum water storage capacity"}
data["root_tank_capacity"].attrs = {"units": "mm", "long_name": "Soil maximum water storage capacity"}
# Maximum water capacity of surface tank (mm)
# Reserve utile de l'horizon de surface (mm)
# RuSurf := EpaisseurSurf / 1000 * Ru;
#! renaming ruSurf with surface_tank_capacity
#// data["ruSurf"] = data["epaisseurSurf"] / 1000 * data["ru"]
data["surface_tank_capacity"] = data["epaisseurSurf"] / 1000 * data["ru"]
#// data["ruSurf"].attrs = {"units": "mm", "long_name": "Maximum water capacity of surface tank"}
data["surface_tank_capacity"].attrs = {"units": "mm", "long_name": "Maximum water capacity of surface tank"}
# ?
# // PfTranspi := EpaisseurSurf * HumPf;
# // StTot := StockIniSurf - PfTranspi/2 + StockIniProf;
# StTot := StockIniSurf + StockIniProf;
# data["stTot"] = np.full((grid_width, grid_height, duration), (paramTypeSol["stockIniSurf"] + paramTypeSol["stockIniProf"]))
#! modifié pour faire correspondre les résultats de simulation, à remettre en place pour un calcul correct dès que possible
# data["stTot"] = np.full((grid_width, grid_height, duration), (paramTypeSol["stockIniProf"]))
#! renaming stTot to total_tank_stock
#// data["stTot"] = data["stockIniProf"]
#//data["total_tank_stock"] = data["stockIniProf"]
#! coorecting total_tank_stock initialization as it did not have the time dimensions that are required as stock evolves through time
data["total_tank_stock"] = (data["rain"].dims, np.repeat(np.array(data["stockIniProf"])[np.newaxis,:,:], duration, axis=0))
#// data["stTot"].attrs = {"units": "mm", "long_name": "?"}
data["total_tank_stock"].attrs = {"units": "mm", "long_name": "?"}
# Soil maximal depth (mm)
# Profondeur maximale de sol (mm)
# ProfRU := EpaisseurSurf + EpaisseurProf;
data["profRu"] = data["epaisseurProf"] + data["epaisseurSurf"]
data["profRu"].attrs = {"units": "mm", "long_name": "Soil maximal depth"}
# Maximum water capacity to humectation front (mm)
# Quantité d'eau maximum jusqu'au front d'humectation (mm)
# // modif 10/06/2015 resilience stock d'eau
# // Front d'humectation egal a RuSurf trop de stress initial
# // Hum := max(StTot, StRurMax);
# Hum := max(RuSurf, StRurMax);
# // Hum mis a profRuSurf
# Hum := max(StTot, Hum);
data["humectation_front"] = (data["rain"].dims, np.full((duration, grid_width, grid_height),
np.maximum(
np.maximum(
#! renaming ruSurf with surface_tank_capacity
#// data["ruSurf"],
data["surface_tank_capacity"].expand_dims({"time":duration}),
#! renaming stRurMax with root_tank_capacity
#// data["stRurMax"],
data["root_tank_capacity"],
),
#! renaming stTot with total_tank_stock
#// data["stTot"],
data["total_tank_stock"],
)
))
data["humectation_front"].attrs = {"units": "mm", "long_name": "Maximum water capacity to humectation front"}
# Previous value for Maximum water capacity to humectation front (mm)
# HumPrec := Hum;
data["previous_humectation_front"] = data["humectation_front"]
# ?
# StRurPrec := 0;
# Previous value for stTot
# StRurMaxPrec := 0;
# //modif 10/06/2015 resilience stock d'eau
#! renaming stTot with total_tank_stock
#! renaminog stRuPrec with previous_total_tank_stock
#// data["stRuPrec"] = data["stTot"]
data["previous_total_tank_stock"] = data["total_tank_stock"]
return data
def update_irrigation_tank_stock(j, data):
"""
This function updates the water stock of the irrigation tank.
If the simulation is run with automatic irrigation mode
(`data["irrigAuto"]==True`), if the simulation is between phases 0 and 6,
and if `root_tank_capacity` is lower than `surface_tank_capacity` (which
indicates that the roots have not yet reached the limit between the surface
compartment and deep compartment), `irrigation_tank_stock` will be set to
the value of `surface_tank_stock`, which means, it will take the minimum
value equal to `surface_tank_stock`. For phase 7, the existing
`irrigation_tank_stock` value will be kept unchanged.
Note : the logic of this function has not yet been validated in SARRA-Py, as
simulations are mainly based on rainfed conditions.
Args:
j (int): Index of time step in data
data (xarray Dataset): Dataset that contains various data fields
Returns:
xarray Dataset: updated data set with the irrigation_tank_stock field updated based on the conditions.
"""
condition = (data["irrigAuto"][j, :, :] == True) & \
(data["numPhase"][j, :, :] > 0) & \
(data["numPhase"][j, :, :] < 6)
data["irrigation_tank_stock"][j, :, :] = np.where(
condition,
xr.where(
(data["root_tank_capacity"] < data["surface_tank_capacity"])[j, :, :],
data["surface_tank_stock"][j, :, :],
data["root_tank_stock"][j, :, :],
),
data["irrigation_tank_stock"][j, :, :],
)
return data
def update_irrigation_tank_capacity(j, data):
"""
This function updates the capacity if the irrigation tank.
If the simulation is run with automatic irrigation mode
(`data["irrigAuto"]==True`), if the current phase is between 0 and 6, and if
the root tank capacity is less than the surface tank capacity (meaning that
the roots have not reached the limit between the surface compartment and
deep compartment), then `irrigation_tank_capacity` is set to the value of
`surface_tank_capacity`, which is given a minimum value equal to the
`surface_tank_capacity`. Otherwise, the irrigation tank capacity remains
unchanged.
Note : the logic of this function has not yet been validated in SARRA-Py, as
simulations are mainly based on rainfed conditions.
Args:
j (int): Index of the time step being processed.
data (xarray dataset): The input dataset containing all the information necessary to run the model.
Returns:
xarray dataset: The input dataset with updated values of the irrigation tank capacity.
"""
condition = \
(data["irrigAuto"][j, :, :] == True) & \
(data["numPhase"][j, :, :] > 0) & \
(data["numPhase"][j, :, :] < 6)
data["irrigation_tank_capacity"][j, :, :] = np.where(
condition,
np.where(
data["root_tank_capacity"][j,:,:] < data["surface_tank_capacity"],
data["surface_tank_capacity"],
data["root_tank_capacity"][j,:,:],
),
data["irrigation_tank_capacity"][j, :, :],
)
return data
def compute_daily_irrigation(j, data, paramITK):
"""
This function computes the total daily irrigation
If the simulation is run with automatic irrigation mode
(`data["irrigAuto"]==True`), if the current phase is between 0 and 6, and if
the filling rate the irrigation tank is below the target filling value
(`irrigAutoTarget`, decimal percentage), we first compute 90% of the
difference between the current volume of water in the irrigation tank
(`irrigation_tank_stock`) and the total capacity of the irrigation tank
(`irrigation_tank_capacity`), bounded by a minimum of 0 and a maximum of
`maxIrrig`. This computed value represents the amount of water to be added
to the irrigation tank. If the above conditions are not met, the computed
value is 0.
Then, we calculate the total irrigation of the day by summing the estimated
irrigation need (`irrigation`) with the previous irrigation history of the
day (`irrigTotDay`).
Note : the logic of this function has not yet been validated in SARRA-Py, as
simulations are mainly based on rainfed conditions.
Args:
j: An integer representing the current day.
data: A xarray dataset.
paramITK: A dictionary of parameters.
Returns:
data: A xarray dataset with the updated irrigationTotDay field.
"""
condition = (data["irrigAuto"][j, :, :] == True) & \
(data["numPhase"][j, :, :] > 0) & \
(data["numPhase"][j, :, :] < 6) & \
(data["irrigation_tank_stock"][j, :, :] / data["irrigation_tank_capacity"][j,:,:] \
< paramITK["irrigAutoTarget"])
data["irrigTotDay"][j, :, :] = xr.where(
condition,
np.minimum(
np.maximum(
0,
((data["irrigation_tank_capacity"][j, :, :] - data["irrigation_tank_stock"][j, :, :]) * 0.9) \
- data["irrigation"][j, :, :]
),
paramITK["maxIrrig"]
),
0,
)
data["irrigTotDay"][j, :, :] = (
data["irrigation"][j, :, :] + data["irrigTotDay"][j, :, :])
return data
def compute_irrigation_state(j, data, paramITK):
"""
This wrapper function computes the irrigation state for a given day,
including the size and filling of the irrigation tank and the irrigation
demand. It is computed only if `paramITK["irrigAuto"] == True` ; this means
that irrigAuto shall be the same all over the grid, which is a reasonable
assumption
It has been Translated from the procedure EvalIrrigPhase, of the original
Pascal codes bileau.pas and exmodules2.pas. Calculation precision is not
taken into account anymore.
irrigation_tank_stock and irrigation_tank_capacity are only computed in
order to avoid issues with very shallow rooting, where calculation of
filling of root_tank_capacity by root_tank_stock can be inappropriate and
lead to inadapted results for automatic irrigation
Notes from CB, 2014 : "Modification due à la prise en compte effet Mulch
Soit on a une irrigation observée, soit on calcul la dose d'irrigation. Elle
est calculée en fonction d'un seuil d'humidité (IrrigAutoTarget) et de
possibilité technique ou choix (MaxIrrig, Precision). Dans cette gestion
d'irrigation la pluie du jour n'est pas prise en compte."
Args:
j (int): Index of the day for which the irrigation state is being computed.
data (xarray.Dataset): The input data, including the arrays for irrigation and correctedIrrigation.
paramITK (dict): The parameters for the ITK model.
Returns:
xarray.Dataset: The updated data, including the computed values for the irrigation state.
"""
if paramITK["irrigAuto"] == True :
data = update_irrigation_tank_stock(j, data)
data = update_irrigation_tank_capacity(j, data)
data = compute_daily_irrigation(j, data, paramITK)
return data
def compute_total_available_water(j, data):
"""
This function computes the total available water for a day (mm) by adding
rainfall and irrigation.
This calculation is performed to allow for subsequent calculations of the
mulch filling and water runoff.
The available_water variable is later updated during the same day process
list, so its value is not the same at the beginning and the end of the daily
computation loop.
This function has benn translated from the procedure PluieIrrig, of the
original Pascal codes bileau.pas and exmodules2.pas.
Args:
j (int): The index of the current day.
data (xarray.Dataset): The data set containing information about the rainfall, irrigation, and water availability.
Returns:
xarray.Dataset: The data set with updated information about the total water availability for the current day.
"""
data["available_water"][j,:,:] = data["rain"][j,:,:] + data["irrigTotDay"][j,:,:]
return data
def compute_water_captured_by_mulch(j, data, paramITK):
"""
This function computes the height of water captured by the mulch.
For this, we multiply the 'available_water' (rain + irrigation, in mm) by an
exponential function taking both into consideration the mulch covering
capacity (surfMc, ha/t) and mulch biomass (biomMc, kg/ha), representing the
fraction of soil covered by mulch. If the fraction is 0 (no mulch), the
value of water_captured_by_mulch is 0.
The value of water_captured_by_mulch is bounded by the maximum capacity of
the mulch to gather water (humSatMc, kg H2O/kg biomass), minus stock of
water already present in it (mulch_water_stock, mm).
Note : the logic of this function has not yet been validated in SARRA-Py, as
simulations are mainly based on situations without mulch.
Notes from CB, 2014 :
Hypotheses : A chaque pluie, on estime la quantité d'eau pour saturer le
couvert. On la retire à l'eauDispo (pluie + irrig). On calcule la capacité
maximum de stockage fonction de la biomasse et du taux de saturation
rapportée en mm (humSatMc en kg H2O/kg de biomasse). La pluie est en mm : 1
mm = 1 litre d'eau / m2 1 mm = 10 tonnes d'eau / hectare = 10 000 kg/ha La
biomasse est en kg/ha pour se rapporter à la quantité de pluie captée en mm
Kg H2O/kg Kg/ha et kg/m2 on divise par 10 000 (pour 3000 kg/ha à humSat 2.8
kg H2O/kg on a un stockage max de 0.84 mm de pluie !?) Cette capacité à
capter est fonction du taux de couverture du sol calculé comme le LTR SurfMc
est spécifié en ha/t (0.39), on rapporte en ha/kg en divisant par 1000 On
retire alors les mm d'eau captées à la pluie incidente. Le ruisselement est
ensuite calculé avec l'effet de contrainte du mulch
Args:
j (_type_): _description_
data (_type_): _description_
paramITK (_type_): _description_
Returns:
_type_: _description_
"""
data["water_captured_by_mulch"][j,:,:] = np.minimum(
data["available_water"][j,:,:] * (1 - np.exp(-paramITK["surfMc"] / 1000 * data["biomMc"][j,:,:])),
(paramITK["humSatMc"] * data["biomMc"][j,:,:] / 10000) - data["mulch_water_stock"][j,:,:],
)
return data
def update_available_water_after_mulch_filling(j, data):
"""
This function updates available water after mulch filling.
As some water is captured by the mulch (rain or irrigation water falling on
it), the available_water is updated by subtracting the captured water
(water_captured_by_mulch, mm) from the total available water
(available_water, mm), to represent the remaining available water after
capture by the mulch. This value is bounded by 0, as the available water
cannot be negative.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["available_water"][j:,:,:] = np.maximum(data["available_water"][j,:,:] - data["water_captured_by_mulch"][j,:,:], 0)
return data
def update_mulch_water_stock(j, data):
"""
This function updates the water stock in mulch.
The water stock in mulch is updated by adding the captured water (water_captured_by_mulch, mm)
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["mulch_water_stock"][j:,:,:] = (data["mulch_water_stock"][j,:,:] + data["water_captured_by_mulch"][j,:,:])
return data
def fill_mulch(j, data, paramITK):
"""
This wrapper function computes the filling of the mulch for a given day.
It has been translated from the procedure PluieIrrig, of the original Pascal codes
bileau.pas and exmodules2.pas
For more details, it is advised to refer to the works of Eric Scopel (UR
AIDA), and the PhD dissertation of Fernando Maceina.
"""
data = compute_water_captured_by_mulch(j, data, paramITK)
data = update_available_water_after_mulch_filling(j, data)
data = update_mulch_water_stock(j, data)
return data
def estimate_runoff(j, data):
"""
This function evaluates the water runoff (mm).
If the quantity of rain (mm) is above the runoff_threshold (mm), runoff is
computed as the difference between the available water (mm) and the
runoff_threshold multiplied by the runoff_rate (%). Else, runoff value is
set to 0.
runoff_threshold and runoff_rate are defined in load_iSDA_soil_data
Question : should runoff be computed taking in consideration water captured by
mulch to account for mulch effect on runoff mitigation ?
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["runoff"][j,:,:] = xr.where(
data["rain"][j,:,:] > data["runoff_threshold"],
(data["available_water"][j,:,:] - data["runoff_threshold"]) * data["runoff_rate"],
0,
)
return data
def update_available_water_after_runoff(j, data):
"""
Updating available water (eauDispo, mm) :
The available water is updated by subtracting the runoff (lr, mm) from the
total available water (eauDispo, mm). This value is broadcasted onto the
days axis.
Args:
j (_type_): _description_
data (_type_): _description_
"""
data["available_water"][j:,:,:] = (data["available_water"][j,:,:] - data["runoff"][j,:,:])
return data
def compute_runoff(j, data):
"""
Translated from the procedure PluieIrrig, of the original Pascal codes
bileau.pas, exmodules1.pas and exmodules2.pas
Notes from CB, 2014 :
On a regroupé avant la pluie et l'irrigation (a cause de l'effet Mulch)
si mulch on a enlevé l'eau captée
oN CALCUL SIMPLEMENT LE RUISSELLEMENT EN FN DE SEUILS
Args:
j (_type_): _description_
data (_type_): _description_
paramTypeSol (_type_): _description_
Returns:
_type_: _description_
"""
data = estimate_runoff(j, data)
data = update_available_water_after_runoff(j, data)
return data
def initialize_root_tank_capacity(j, data, paramITK):
"""
This function initializes the root tank.
If during the considered day j there are pixels in phase 1 (initialisation),
we test for pixels at phase change between phases 0 and 1 ('changePhase = 1'
and 'numPhase = 1').
On these pixels, the maximum root tank water storage ('root_tank_capacity',
mm) is initialised by multiplying the initial root depth ('profRacIni', mm)
with the soil water storage capacity ('ru', mm/m). This value is broadcasted
on the time series. For every other day in the cycle where there are pixels
at , the value remains unchanged.
https://docs.google.com/presentation/d/1QHhbNjF9ysCG_yZzWXb7ns0vOFlGfNhYw8AYrotm0fY/edit#slide=id.g27a6b3e8a72_0_30
Args:
j (int): day identifier
data (xarray dataset): an xarray dataset of dimensions (day, width,
height) containing the variables 'numPhase', 'root_tank_capacity',
'changePhase', 'ru'
paramITK (dict): a dictionary containing the ITK parameter 'profRacIni'
Returns:
_type_: _description_
"""
if np.any(data["numPhase"][j,:,:] == 1) :
data["root_tank_capacity"][j:,:,:] = xr.where(
(data["changePhase"][j,:,:] == 1) & (data["numPhase"][j,:,:] == 1),
paramITK["profRacIni"] / 1000 * data["ru"],
data["root_tank_capacity"][j,:,:],
)
return data
def initialize_delta_root_tank_capacity(j, data):
"""
This function initializes the daily variation in root tank capacity.
This variable represents daily variation in water height accessible to
roots, in mm.
For each pixel at a developmental stage different from zero, and that is not
at initialization phase ('changePhase = 1' and 'numPhase = 1'), the daily
variation in root tank capacity (delta_root_tank_capacity, mm) is updated.
The updated value depends on the daily root growth speed (itself depending
on the current development phase of the plant), the drought stress
coefficient ('cstr'), and the soil water storage capacity ('ru', mm/m).
https://docs.google.com/presentation/d/1QHhbNjF9ysCG_yZzWXb7ns0vOFlGfNhYw8AYrotm0fY/edit#slide=id.g27a6b3e8a72_0_118
However, when 'root_tank_capacity' is above 'surface_tank_capacity'
(meaning that the roots are prospecting water deeper than the surface tank),
the daily root capacity variation is calculated as the product of soil water
storage capacity ('ru'), the daily root growth speed ('vRac'), and a
coefficient made from 'cstr' shifted by 0.3, capped at 1.0.
https://docs.google.com/presentation/d/1QHhbNjF9ysCG_yZzWXb7ns0vOFlGfNhYw8AYrotm0fY/edit#slide=id.g27a6b3e8a72_0_71
That is to say, when roots are going deep, the root growth speed is
modulated by drought stress.
The drought stress coefficient 'cstr' measures the level of drought stress
with 0 being full stress. The root growth speed is assumed to remain
non-null during a drought stress as a matter of survival, with a certain
level of tolerance given by the [0.3, 1] bound of the coefficient. Using the
[0.3, 1] bound is a way to tell that in the [0.7, 1] 'cstr' interval, there
is no effect of drought stress on the root growth speed, allowing for a
certain level of tolerance of the plant.
When 'root_tank_capacity' is lower than 'surface_tank_capacity', the root growth
speed is not modulated by drought stress.
Args:
j (int): The current iteration step of the process.
data (xarray.Dataset): The input data containing relevant information.
Returns:
xarray.Dataset: The updated input data with the daily root capacity variation calculated and stored.
"""
condition = (data["numPhase"][j,:,:] > 0) & \
np.invert((data["numPhase"][j,:,:] == 1) & (data["changePhase"][j,:,:] == 1))
data["delta_root_tank_capacity"][j,:,:] = xr.where(
condition,
xr.where(
(data["root_tank_capacity"][j,:,:] > data["surface_tank_capacity"]),
(data["vRac"][j,:,:] * np.minimum(data["cstr"][j,:,:] + 0.3, 1.0)) / 1000 * data["ru"],
data["vRac"][j,:,:] / 1000 * data["ru"],
),
data["delta_root_tank_capacity"][j,:,:],
)
return data
def update_delta_root_tank_capacity(j, data):
"""
This function updates the daily variation in root tank capacity
(delta_root_tank_capacity, mm) depending on the water height to humectation
front (hum, mm) and the root tank capacity (root_tank_capacity, mm).
For each pixel at a developmental stage different from zero, and that is not
at initialization phase ('changePhase = 1' and 'numPhase = 1'), when the
difference between the water height to humectation front (hum, mm) and the
root_tank_capacity is less than the delta_root_tank_capacity (meaning that
the daily variation in root tank capacity is higher that the height of water
necessary to reach the height of water of the humectation front),
delta_root_tank_capacity is updated to be equal to the difference between
the water height to humectation front and the root_tank_capacity.
In other words, the change in root tank capacity delta_root_tank_capacity is
limited by the water height to humectation front. Which can be interpreted as :
the roots cannot grow deeper than the humectation front.
? ...which means the humectation from has to be updated somewhere ?
For any other day or if root_tank_capacity is above
delta_root_tank_capacity, delta_root_tank_capacity value is unchanged.
https://docs.google.com/presentation/d/1QHhbNjF9ysCG_yZzWXb7ns0vOFlGfNhYw8AYrotm0fY/edit#slide=id.g27a6b3e8a72_0_161
Args:
j (_type_): _description_
data (_type_): _description_
"""
condition = (data["numPhase"][j,:,:] > 0) & \
np.invert((data["numPhase"][j,:,:] == 1) & (data["changePhase"][j,:,:] == 1))
data["delta_root_tank_capacity"][j:,:,:] = np.where(
condition,
np.where(
(data["humectation_front"][j,:,:] - data["root_tank_capacity"][j,:,:]) < data["delta_root_tank_capacity"][j,:,:],
data["humectation_front"][j,:,:] - data["root_tank_capacity"][j,:,:],
data["delta_root_tank_capacity"][j,:,:],
),
data["delta_root_tank_capacity"][j,:,:],
)
return data
def update_root_tank_capacity(j, data):
"""
This function updates the root tank capacity (root_tank_capacity, mm) by
adding the daily variation in root tank capacity.
For each pixel at a developmental stage different from zero, and that is not
at initialization phase ('changePhase = 1' and 'numPhase = 1'),
root_tank_capacity is updated to be summed with the change in root water
storage capacity delta_root_tank_capacity.
In other words, root_tank_capacity is incremented by the change in root
water storage capacity related to root growth. Easy, right ?
https://docs.google.com/presentation/d/1QHhbNjF9ysCG_yZzWXb7ns0vOFlGfNhYw8AYrotm0fY/edit#slide=id.g27a6b3e8a72_0_238
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
condition = (data["numPhase"][j,:,:] > 0) & \
np.invert((data["numPhase"][j,:,:] == 1) & (data["changePhase"][j,:,:] == 1))
data["root_tank_capacity"][j:,:,:] = np.where(
condition,
data["root_tank_capacity"][j,:,:] + data["delta_root_tank_capacity"][j,:,:],
data["root_tank_capacity"][j,:,:],
)
return data
def update_root_tank_stock(j, data):
"""
This functions update the quantity of water of the root tank ('root_tank_stock', mm).
For each pixel at a developmental stage different from zero, and that is not
at initialization phase ('changePhase = 1' and 'numPhase = 1'), and for
which the 'root_tank_capacity' is greater than 'surface_tank_capacity' (meaning
that roots go beyond the surface water storage capacity), 'root_tank_stock'
is incremented by delta_root_tank_capacity.
However, if 'root_tank_capacity' is lesser than 'surface_tank_capacity' (meaning
that roots do not plunge into the deep reservoir), 'root_tank_stock' is
updated to be equal to surface_tank_stock minus 1/10th of the
surface_tank_capacity, multiplied by the ratio between root_tank_capacity
and surface_tank_capacity. That is to say "we take at the prorata of depth
and surface stock".
For any other day, root_tank_stock is unchanged.
? Why is the tank stock incremented instead of root tank capacity ? If the
? root tank capacity is incremented, that makes sense as we add to the root
? tank capacity the capacity newly gained through delta_root_tank_capacity.
? There is no sense in incrementing the root tank stock with the
? delta_root_tank_capacity, as the delta root tank capacity, representing
? growing of roots is independant of the quantity of water in the soil.
? However, the delta root tank capacity is blocked by hum the humidity front.
? Still, humidity front only grows and limits the maximum growth of roots, and
? is not involved in root water stock.
? Also, if the roots do not go in the deep reservoir, there is an increase in
? root tank stock. Considering this is a mistake and that root_tank_capacity
? should be increased, this would mean root tank capacity is increased by a
? value that depends on the filling of the surface tank first
? (surface_tank_stock minus 1/10th of the surface_tank_capacity, that would be
? about the bound water), times the ratio between root_tank_capacity and
? surface_tank_capacity. This would mean if when there is few roots the
? increase in root tank capacity is small, and if roots are close to passing
? into the deep reservoir, the increase in root tank capacity nears the
? surface_tank_stock. Again, there is no sense in increasing the root tank
? capacity with such value however this would be ok for root_tank_stock...
? Overall there seems to be a mixup between the objectives of the two parts of
? this function ?
? at the moment this function is applied, root_tank_capacity is already
? updated to take into consideration the root growth from the day, limited by
? both the water stress and the depth of the humectation front. i still do not
? understand why we would increase root tank stock, as we do not have
? supplementary water. it would be like creating water from nowhere.
? so until further notice i will let this function as it is, but i will keep
? in mind that it is probably wrong.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
condition = (data["numPhase"][j,:,:] > 0) & \
np.invert((data["changePhase"][j,:,:] == 1) & (data["numPhase"][j,:,:] == 1)),
data["root_tank_stock"][j:,:,:] = xr.where(
condition,
xr.where(
(data["root_tank_capacity"][j,:,:] > data["surface_tank_capacity"]),
data["root_tank_stock"][j,:,:] + data["delta_root_tank_capacity"][j,:,:],
np.maximum(
((data["surface_tank_stock"][j,:,:] - data["surface_tank_capacity"] * 1/10) * \
(data["root_tank_capacity"][j,:,:] / data["surface_tank_capacity"])),
0),
).expand_dims("time", axis=0),
data["root_tank_stock"][j,:,:],
)
return data
def EvolRurCstr2(j, data, paramITK):
"""
This function is a legacy wrapper for the functions related to the
calculation of the root tank capacity and stock.
It has been translated from the procedure EvolRurCstr2, of the original
Pascal codes bileau.pas.
Notes from CB, 10/06/2015 :
Stress trop fort enracinement
Trop d'effet de stress en tout début de croissance :
1) la plantule a des réserves et favorise l'enracinement
2) dynamique spécifique sur le réservoir de surface
Cet effet stress sur l'enracinement ne s'applique que quand l'enracinement
est supérieur é la profondeur du réservoir de surface. Effet stres a un
effet sur la vitesse de prof d'enracinement au dessus d'un certain seuil de
cstr (on augmente le cstr de 0.3 pour que sa contrainte soit affaiblie sur
la vitesse) La vitesse d'enracinement potentielle de la plante peut etre
bloque par manque d'eau en profondeur (Hum). La profondeur d'humectation est
convertie en quantite d'eau maximum equivalente
"""
data = initialize_root_tank_capacity(j, data, paramITK)
data = initialize_delta_root_tank_capacity(j, data)
data = update_delta_root_tank_capacity(j, data)
data = update_root_tank_capacity(j, data)
data = update_root_tank_stock(j, data) #! we keep this function for now even though it is probably wrong, it will need further screening
return data
####################### list of functions for rempliRes #######################
def update_previous_humectation_front_at_end_of_season(j, data):
"""
This function saves information about the water height to humectation front
to another variable (previous_humectation_front, mm) at the end of season so
it can be used in the next cycle.
previous_humectation_front is initialized in the function InitPlotMc, and
set to be equal to hum. hum itself is initialized to take the maximum value
between surface_tank_capacity, root_tank_capacity and total_tank_stock.
At the harvest date (numPhase = 7), the previous_humectation_front variable
is set to equal the highest value between hum (mm, water height to
humectation front) and surface_tank_capacity (mm). This value is broadcasted
over the time dimension.
At any other point in time, its value is unchanged.
Args:
j (int): number of the day
data (xarray dataset): _description_
Returns:
xarray dataset: _description_
"""
condition = (data["numPhase"][j,:,:] == 7) & (data["changePhase"][j,:,:] == 1)
data["previous_humectation_front"][j:,:,:] = np.where(
condition,
np.maximum(data["humectation_front"][j,:,:], data["surface_tank_capacity"]),
data["previous_humectation_front"][j,:,:],
)
return data
def update_humectation_front_at_end_of_season(j, data):
"""
This function updates the value of water height to humectation front
(humectation_front, mm) at the end of season.
At the harvest date (numPhase = 7), the humectation_front variable is set to
equal the surface_tank_capacity (mm). This value is broadcasted over the
time dimension.
At any other point in time, its value is unchanged.
? The way of resetting the humectation_front at harvest date hasn't a real
? agronomical meaning. This function allows for resetting the variable at an
? initial state.
? However it is a way to say that when the plant dies, the new ones will have
? to make up their new humectation fronts starting again from surface_tank_capacity
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
condition = (data["numPhase"][j,:,:] == 7) & (data["changePhase"][j,:,:] == 1)
data["humectation_front"][j:,:,:] = np.where(
condition,
data["surface_tank_capacity"],
data["humectation_front"][j,:,:],
)
return data
def update_root_tank_capacity_at_end_of_season(j, data):
"""
This function saves information about the root_tank_capacity to another
variable (previous_root_tank_capacity, mm) at the end of season so it
can be used in the next cycle.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
condition = (data["numPhase"][j,:,:] == 7) & (data["changePhase"][j,:,:] == 1)
data["previous_root_tank_capacity"][j:,:,:] = np.where(
condition,
data["root_tank_capacity"][j,:,:],
data["previous_root_tank_capacity"][j,:,:],
)
return data
def update_previous_root_tank_stock_at_end_of_season(j, data):
"""
This function updates the value of previous stock of water in the root tank
(previous_root_tank_stock, mm) at the end of season.
When the phase changes from 7 to 1, previous_root_tank_stock is set to equal
the ratio between root_tank_stock and root_tank_capacity, that is to say the
filling rate of the root reservoir. Otherwise, it stays at its initial value
of 0. Its value is broadcasted along j. previous_root_tank_stock is
initialized with a value of 0.
? The way of resetting the previous_root_tank_stock at harvest date hasn't a real
? agronomical meaning.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
condition = (data["numPhase"][j,:,:] == 7) & (data["changePhase"][j,:,:] == 1)
data["previous_root_tank_stock"][j:,:,:] = np.where(
condition,
data["root_tank_stock"][j,:,:] / data["root_tank_capacity"][j,:,:],
data["previous_root_tank_stock"][j,:,:],
)
return data
def update_previous_total_tank_stock_at_end_of_season(j, data):
"""
This function updates the value of previous total stock of water
(previous_total_tank_stock, mm) at the end of season.
When the phase changes from 7 to 1, previous_total_tank_stock is set to equal
the difference between total_tank_stock and surface_tank_stock.
Otherwise, it stays at its initial value of 0. Its value is broadcasted
along j. previous_total_tank_stock is initialized with a value of 0.
? The way of resetting the previous_total_tank_stock at harvest does not seem to have a real
? agronomical meaning.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
condition = (data["numPhase"][j,:,:] == 7) & (data["changePhase"][j,:,:] == 1)
data["previous_total_tank_stock"][j:,:,:] = np.where(
condition,
data["total_tank_stock"][j,:,:] - data["surface_tank_stock"][j,:,:], # essai stTot #? what is that ?
data["previous_total_tank_stock"][j,:,:],
)
return data
def reset_total_tank_capacity(j, data):
"""
This function resets the value total_tank_capacity at each loop.
? Why redfining stRuMax at each loop ? Neither ru, profRu
? nor total_tank_capacity are modified during the simulation.
? At the same time, its value is initialized at 0, and this function
? is the only place where it is initialized taking ru and profRu into account.
? We modify it so it runs only at day one.
? But it should be moved to the initialization part of the code.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
if j == 0:
data["total_tank_capacity"][j:,:,:] = (data["ru"] * data["profRu"] / 1000)
return data
def update_surface_tank_stock(j, data):
"""
This function updates the value of water stock in the surface tank
(surface_tank_stock, mm) with the water available for the day
(available_water, mm), within the limits of 110% surface_tank_capacity.
We update surface_tank_stock by adding the available_water, which as this
point in the process list corresponds to the water available from 1) rain,
2) irrigation for the day, corrected from 3) intake by mulch (fill_mulch
function), and 4) runoff (compute_runoff). However, we do not allow
surface_tank_stock to exceed 110% of the surface_tank_capacity.
? This means it is possible that the surface tank fill rate is above 100%,
? which is a rather strange assumption.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["surface_tank_stock"][j:,:,:] = np.minimum(
data["surface_tank_stock"][j,:,:] + data["available_water"][j,:,:],
# 1.1 * data["surface_tank_capacity"]
data["surface_tank_capacity"]
)
return data
def estimate_transpirable_water(j, data):
"""
This function estimates the daily height of water available from the surface
reservoir for transpiration by the plant ('eauTranspi', mm).
If the filling rate of the surface tank ('surface_tank_stock' /
'surface_tank_capacity') for the previous day is under 10%, we set the
quantity of transpirable water as the water available for the day
('eauDispo') minus the water height necessary to keep the filling rate of
the surface tank at 10%.
Said otherwise, a part of the water available for the day ('eauDispo') is used
to maintain the surface reservoir at a minimum level of 10% of its capacity,
as this water is considered as bound to the surface reservoir and cannot be
transpired.
Of course, if the filling rate of the previous day is above 10%, the transpirable
water is equal to the water available for the day.
Furthermore, transpirable water cannot be negative.
? Remark : if the use of j-1 indices is troublesome, it should be feasible to
? run this function just before update_surface_tank_stock.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
# data["eauTranspi"][j,:,:] = np.where(
# data["surface_tank_stock"][j-1,:,:] < 0.1 * data["surface_tank_capacity"],
# np.maximum(
# 0,
# data["available_water"][j,:,:] - (0.1 * data["surface_tank_capacity"] - data["surface_tank_stock"][j-1,:,:])
# ),
# data["available_water"][j,:,:],
# )
#! simplification : we are already working with water height between permanent wilting point and field capacity,
#! so there is no need to consider bound water as this is already taken into consideration in the calculation of RU
#! if we want to take this further correctly we have to rewrite everything, so better keep it simple for now
data["eauTranspi"][j,:,:] = data["available_water"][j,:,:]
return data
def update_total_tank_stock(j, data):
"""
This functions updates the total height of transpirable water
('total_tank_stock', mm) with the amount of transpirable water for the day
('eauTranspi', mm).
? Said differently, 'total_tank_stock' represents the total amount of water
? available for the plant in the soil column ?
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["total_tank_stock"][j:,:,:] = (data["total_tank_stock"][j,:,:] + data["eauTranspi"][j,:,:])
return data
def update_delta_total_tank_stock(j, data):
"""
This function estimates the positive variation of the total height of
transpirable water ('delta_total_tank_stock', mm).
It is computed as the difference between the total_tank_stock and
previous_total_tank_stock, bound in 0.
'previous_total_tank_stock' is initialized to be equal to 'total_tank_stock'
at the beginning of the simulation. As 'total_tank_stock' is initialized
with the 'stockIrr' parameter, simulations should start with a 0 value.
'previous_total_tank_stock' is updated each day with the 'update_struprec'
function.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["delta_total_tank_stock"][j:,:,:] = np.maximum(0, data["total_tank_stock"][j,:,:] - data["previous_total_tank_stock"][j,:,:])
return data
def update_total_tank_stock_for_second_crop_cycle(j, data):
"""
This function updates the total height of transpirable water
('total_tank_stock', mm), specifically if a second crop cycle starts.
This update is applied only if a second crop cycle starts, as
previous_root_tank_capacity and previous_total_tank_stock are initialized as
null. That means conditions 2 and 3 of this function will fail during a
first crop cycle, leading to no change in total_tank_stock.
However, at numPhase = 7, which corresponds to the harvesting date and that
opens the possibility for a second crop cycle, previous_root_tank_capacity
and previous_total_tank_stock will be updated.
From now on, if delta_total_tank_stock is greater than humectation front
(condition 1 passed), and previous_root_tank_capacity is greater or equal to
the humectation_front (condition 2 passed), and if the humectation_front is
below previous_humectation_front (condition 3 passed), then the total tank
stock is updated to be increased with the difference between
delta_total_tank_stock and humectation_front, times the previous root tank
stock.
Thus, if the root tank is empty, total_tank_stock will remain unchanged, and
if the root tank is full, total_tank_stock will be increased up to the
amount of water making the difference between quantity of water for
humectation front and the variation in daily transpirable water.
Also, if delta_total_tank_stock is greater than humectation front (condition
1 passed), but previous_root_tank_capacity is lower than the
humectation_front (condition 2 failed), while the humectation_front is below
previous_humectation_front (condition 3 passed), we update the total tank
stock to be equal to delta_total_tank_stock.
In other words, if during the second crop cycle the humectation front is too
low, we increase the total tank stock.
? To my opinion, this function is way too complicated for a borderline use case
? (multiple cropping cycles during one simulation).
? We'd want to keep the code for legacy reasons but if really this simulation
? feature is needed we'll have to simplify it.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
condition_1 = (data["delta_total_tank_stock"][j,:,:] > data["humectation_front"][j,:,:])
condition_2 = (data["humectation_front"][j,:,:] <= data["previous_root_tank_capacity"][j,:,:])
condition_3 = (data["humectation_front"][j,:,:] < data["previous_humectation_front"][j,:,:])
data["total_tank_stock"][j:,:,:] = np.where(
condition_1,
np.where(
condition_2,
data["total_tank_stock"][j,:,:] + (data["delta_total_tank_stock"][j,:,:] - data["humectation_front"][j,:,:]) * \
data["previous_root_tank_stock"][j,:,:],
np.where(
condition_3,
data["delta_total_tank_stock"][j,:,:],
data["total_tank_stock"][j,:,:],
),
),
data["total_tank_stock"][j,:,:],
)
return data
def update_previous_total_tank_stock_for_second_crop_cycle(j, data):
"""
This function performs the update of the previous total height of
transpirable water (previous_total_tank_stock, mm).
It will decrease the previous_total_tank_stock depending on the variation of
transpirable water and height of humectation front.
This update is applied only if a second crop cycle starts, as
previous_root_tank_capacity and previous_total_tank_stock are initialized as
null. That means conditions 2 and 3 of this function will fail during a
first crop cycle, leading to no change in previous_total_tank_stock.
In this function, if the variation of transpirable water
(delta_total_tank_stock) increases above humectation_front (condition 1
passed), and if humectation_front is above the previous_root_tank_capacity
(condition failed), and if the depth of humectation front has decreased
since the previous day (condition 3 passed), then previous_total_tank_stock
equals 0.
Starting from second simulation season (previous_root_tank_capacity != 0),
if the variation of transpirable water (delta_total_tank_stock) increases
above the depth of humectation front (hum), and if the depth of humectation
front stays below or equel to the total soil capacity (conditions 1 and 2
passed), then we decrease the value of previous_total_tank_stock by a the
difference of water height between the variation of total tank stock
(delta_total_tank_stock) and the depth of humectation front (hum),
proportionally to the filling of the root tank capacity of previous season
(previous_root_tank_stock). Thus, if the root tank is empty,
previous_total_tank_stock will remain unchanged, and if the root tank is
full, previous_total_tank_stock will be decreased up to the amount of water
making the difference between quantity of water for humectation front and
the variation in daily transpirable water.
? To my opinion, this function is way too complicated for a borderline use case
? (multiple cropping cycles during one simulation).
? We'd want to keep the code for legacy reasons but if really this simulation
? feature is needed we'll have to simplify it.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
condition_1 = (data["delta_total_tank_stock"][j,:,:] > data["humectation_front"][j,:,:])
condition_2 = (data["humectation_front"][j,:,:] <= data["previous_root_tank_capacity"][j,:,:])
condition_3 = (data["humectation_front"][j,:,:] < data["previous_humectation_front"][j,:,:])
data["previous_total_tank_stock"][j:,:,:] = np.where(
condition_1,
np.where(
condition_2,
np.maximum(0, data["previous_total_tank_stock"][j,:,:] - (data["delta_total_tank_stock"][j,:,:] - data["humectation_front"][j,:,:]) * \
data["previous_root_tank_stock"][j,:,:]),
np.where(
condition_3,
0,
data["previous_total_tank_stock"][j,:,:],
),
),
data["previous_total_tank_stock"][j,:,:],
)
return data
def update_delta_total_tank_stock_step_2(j, data):
"""
? Ok the logic is the same as for the two previous functions
? and i don't want to document it as it is way too complicated
? and we won't be using it for now.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
condition_1 = (data["delta_total_tank_stock"][j,:,:] > data["humectation_front"][j,:,:])
condition_2 = (data["humectation_front"][j,:,:] <= data["previous_root_tank_capacity"][j,:,:])
condition_3 = (data["humectation_front"][j,:,:] < data["previous_humectation_front"][j,:,:])
data["delta_total_tank_stock"][j:,:,:] = np.where(
condition_1,
np.where(
condition_2,
data["delta_total_tank_stock"][j,:,:] + (data["delta_total_tank_stock"][j,:,:] - data["humectation_front"][j,:,:]) * data["previous_root_tank_stock"][j,:,:],
np.where(
condition_3,
data["delta_total_tank_stock"][j,:,:] + data["previous_total_tank_stock"][j,:,:],
data["delta_total_tank_stock"][j,:,:],
),
),
data["delta_total_tank_stock"][j,:,:],
)
return data
def apply_humectation_front_boundaries(j, data):
"""
This function updates the water height to humectation front
(humectation_front, mm) by bounding it between delta_total_tank_stock and
total_tank_capacity.
That is to say depth of humectation front can only increase, and that
humectation front can not go down indefinitely.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["humectation_front"][j:,:,:] = np.maximum(data["delta_total_tank_stock"][j,:,:], data["humectation_front"][j,:,:])
data["humectation_front"][j:,:,:] = np.minimum(data["total_tank_capacity"][j,:,:], data["humectation_front"][j,:,:])
return data
def update_drainage(j, data):
"""
This function estimates the daily drainage (dr).
When total tank overflows (total_tank_stock > total_tank_capacity), the
drainage is computed from the difference between total_tank_stock and
total_tank_capacity. This means the drainage value will be positive
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
condition = (data["total_tank_stock"][j,:,:] > data["total_tank_capacity"][j,:,:])
data["drainage"][j,:,:] = np.where(
condition,
data["total_tank_stock"][j,:,:] - data["total_tank_capacity"][j,:,:],
0,
)
return data
def update_total_tank_stock_after_drainage(j, data):
"""
This function updates the total tank stock (total_tank_stock, mm) when these is overflowing.
When capacity of total_tank_stock is exceeded, total_tank_stock value is replaced with
total_tank_capacity
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
condition = (data["total_tank_stock"][j,:,:] > data["total_tank_capacity"][j,:,:])
data["total_tank_stock"][j:,:,:] = np.where(
condition,
data["total_tank_capacity"][j,:,:],
data["total_tank_stock"][j,:,:],
)
return data
def update_humectation_front_after_drainage(j, data):
"""
We update the depth to humectation front (hum) again, to reflect eventual changes in
total_tank_stock values.
? we could have placed the previous hum update function here
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["humectation_front"][j:,:,:] = np.maximum(data["humectation_front"][j,:,:], data["total_tank_stock"][j,:,:])
return data
def compute_drainage(j, data):
"""
This wrapper function aims to regroup computations related to drainage :
- update_drainage
- update total_tank_stock
- update_humectation_front_after_drainage
Args:
j (_type_): _description_
data (_type_): _description_
"""
data = update_drainage(j, data)
data = update_total_tank_stock_after_drainage(j, data)
data = update_humectation_front_after_drainage(j, data)
return data
def update_root_tank_stock_step_2(j, data):
"""
This function updates the height of water in the tank of water accessible to
roots ("root_tank_stock", mm).
It increments root_tank_stock with transpirable water (eauTranspi), within
the bounds of root_tank_capacity and total_tank_stock.
This means the sum of transpirable water and root tank stock for the day
firstly cannot be higher than the root tank capacity, which is fine to represent
the height of water accessible to roots. But also, that this sum limited by
the root tank capacity cannot be higher than the total tank stock, which seems unlikely.
? This raises the question about where does the potential water in excess go.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["root_tank_stock"][j:,:,:] = np.minimum(data["root_tank_stock"][j,:,:] + data["eauTranspi"][j,:,:], data["root_tank_capacity"][j,:,:])
data["root_tank_stock"][j:,:,:] = np.minimum(data["root_tank_stock"][j,:,:], data["total_tank_stock"][j,:,:])
return data
def fill_tanks(j, data):
"""
Translated from the procedure rempliRes, of the original Pascal codes
bileau.pas
Main hypotheses :
- the water dynamics is represented by a filling from the top and an evolution
of the reservoirs sizes when the filling is above the maximum quantity of the
current size (humectation front).
- when the maximum size is reached by filling, it is considered as drainage.
- inside a reservoir, water is distributed homogeneously (may be considered
valid up to 2m depth, according to CB, from other sources).
3 reservoirs are represented:
1) a global reservoir, evolving in depth according to the humectation front
2) a surface reservoir (fixed size) where evaporation and a part of the
transpiration occurs when roots are present
3) a root reservoir, evolving according to the root front (when roots are
present)
REMARK : these reservoirs overlap, and instead of managing depths, we manage water stocks
Notes from CB, 10/06/2015 :
prise en compte de stock d'eau résilient pour les simulation continues
Hypothèse de la MAJ des stock en fn de l'eau r�siliente de l'ann�e pr�c�dente
dans le cas des simulations pluri annuelle en continue (NbAn = 1):
A la r�colte on recup�re les stock d'eau (StRuPrec), la prof d'Humectation (Humprec)
et la prof d'enracinement (stRurMaxprec). Pour le reservoir de surface on ne change rien.
On MAJ le stRu avec le stock de surface stRuSurf, Hum avec le max de remplissage de surface (RuSurf)
Si le StRu avec l'apport d'eau devinet sup au Hum
alors on tient compte dans cette augmentation du stock r�silient avec deux cas possible :
Si StRu est < � stRurMaxprec
alors on ajoute l'eau r�siliente contenue dans l'ancienne zone racinaire en fn
de la diff�rence de stock
Sinon on a de l'eau r�siliente au maximum de la CC jusqu'� l'ancienne HumPrec,
on rempli alors StRu de la diff�rence etre ces deux valeurs puis on fait la MAJ
des Dr, StRur, Hum etc...
! simplification : we want to simplify the code, and we don't want to keep the possibility of multiple crop cycles
! we keep the old code for maintainance and future developments though
"""
#! simplification
#// section 1 : updating the end_of_season memory variables
#// in order to save resources, we test if there is at least one pixel at phase 7
#// and one pixel at changePhase 1 in the current time step before applying the "end_of_season" functions
#// if (np.any(data["numPhase"][j,:,:] == 7)) & (np.any(data["changePhase"][j,:,:] == 1)):
#// data = update_previous_humectation_front_at_end_of_season(j, data)
#// data = update_humectation_front_at_end_of_season(j, data)
#// data = update_root_tank_capacity_at_end_of_season(j, data)
#// data = update_previous_root_tank_stock_at_end_of_season(j, data)
#// data = update_previous_total_tank_stock_at_end_of_season(j, data)
#! simplification
#// we let this function here, conditioned to work for j0 only, but it should be moved into initialization
data = reset_total_tank_capacity(j, data)
# Updates the value of water stock in the surface tank (surface_tank_stock,
# mm) with the water available for the day (available_water, mm), within the
# limits of 110% surface_tank_capacity.
data = update_surface_tank_stock(j, data)
# Estimates the daily height of water available from the surface reservoir
# for transpiration by the plant ('eauTranspi', mm). A part of the water
# available for the day ('eauDispo') is used to maintain the surface
# reservoir at a minimum level of 10% of its capacity, as this water is
# considered as bound to the surface reservoir and cannot be transpired.
data = estimate_transpirable_water(j, data)
# Updates the total height of transpirable water ('total_tank_stock', mm)
# with the amount of transpirable water for the day ('eauTranspi', mm)
data = update_total_tank_stock(j, data)
#! simplification
#// delta_total_tank_stock is used with second cycle computations
#// estimating positive delta between total_root_tank and stRuPrec
#// data = update_delta_total_tank_stock(j, data)
#! simplification
#// # first we update total_tank_stock that can 1) take delta_total_tank_stock or 2) be unchanged
#// data = update_total_tank_stock_for_second_crop_cycle(j, data)# verif ok
#// # # then previous_total_tank_stock can 1) take 0 or 2) be unchanged
#// data = update_previous_total_tank_stock_for_second_crop_cycle(j, data)
#// # # delta_total_tank_stock can 1) be incremented of previous_total_tank_stock or 2) be unchanged
#//data = update_delta_total_tank_stock_step_2(j, data)
#// # # here, in case 1, In this function, if the variation of transpirable water
#// # (delta_total_tank_stock) increases above the depth of humectation front
#// # (hum), if the depth of humectation front (hum) is above the
#// # previous_root_tank_capacity (condition 1 passed, and 2 failed,
#// # which should be the case for most of the simulations that will be
#// # single-season), and if the depth of humectation front (hum) has decreased
#// # since the previous day (condition 3 passed), then total_tank_stock takes the value of
#// # delta_total_tank_stock, previous_total_tank_stock equals 0, and
#// # delta_total_tank_stock is incremented by previous_total_tank_stock.
#// #
#// # in case 2, nothing happens.
# Updates the water height to humectation front (humectation_front, mm) by
# bounding it between delta_total_tank_stock and total_tank_capacity, so
# that humectation front cannot go down indefinitely.
data = apply_humectation_front_boundaries(j, data)
# computes drainage
data = compute_drainage(j, data)
# Updates the height of water in the tank of water accessible to roots
# ("root_tank_stock", mm), so that the transpirable water added to the root
# tank stock for the day cannot be higher than both the root tank capacity,
# and the total tank stock.
data = update_root_tank_stock_step_2(j, data)
return data
########################################################################################
def estimate_fesw(j, data):
"""
This function estimates the fraction of evaporable soil water (fesw, mm).
fesw is defined as the ratio of water stock in the surface tank over 110% of
the surface tank capacity.
It is adapted from the EvalFESW procedure, from bileau.pas and
bhytypeFAO.pas files from the original FORTRAN code.
Depuis thèse Alhassane : "FESW = fraction d'eau évaporable dans le sol (en
%), calculée à partir du taux d'humidité du sol (en %) à la capacité de
rétention et 1/2 du taux d'humidité du sol au pF 4.2 (Allen et al., 1998)"
in Alhassane thesis, FESW = (Stock CR - 0,5 Stock pF 4,2) x H) so is calculated taking only
half of the surface reservoir
? Why is it calculated over 110% of surface_tank_capacity ?
? it seems the 110% thingy comes from the update_surface_tank_stock
? function where it is allowed to fill the surface tank up to 110% of its
? capacity. but this does not make se,se to me.
? in this case, fesw can take values between 0 and 1
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
# data["fesw"][j,:,:] = data["surface_tank_stock"][j,:,:] / (1.1 * data["surface_tank_capacity"])
data["fesw"][j,:,:] = data["surface_tank_stock"][j,:,:] / data["surface_tank_capacity"]
return data
def estimate_kce(j, data, paramITK):
"""
This function estimates the coefficient of evaporation from the soil (kce).
This approach takes into consideration three factors acting on limitation of
kce :
1) ltr : plant cover, 1 = no plant cover, 0 = full plant cover
2) Mulch - permanent covering effect : we consider a value of 1.0 for no
covering, and 0.0 is full covering with plastic sheet ; this mulch parameter
has been used in previous versions of the model where evolution of mulch
biomass was not explicitely taken into consideration, can be used in the
case of crops with self-mulching phenomena, where a standard mulch parameter
value of 0.7 can be applied.
3) Mulch - evolutive covering effect BiomMc : biomass of mulch
This function has been adapted from EvalKceMC procedure, bileau.pas and
exmodules 2.pas from the original FORTRAN code. In its spirit, it looks like
it has been adapted from the dual crop coefficient from the FAO56 paper. But
this is still to confirm on a point of view of the history of the model.
Depuis thèse Alhassane : "LTR = fraction de radiation non interceptée par le
couvert = [exp(-k*LAI)] où k = coefficient d'extinction de la lumière qui
est fonction des propriétés géométriques du couvert et LAI = indice de
surface foliaire"
Args:
j (_type_): _description_
data (_type_): _description_
paramITK (_type_): _description_
Returns:
_type_: _description_
"""
data["kce"][j,:,:] = data["ltr"][j,:,:] * paramITK["mulch"] * \
np.exp(-paramITK["coefMc"] * paramITK["surfMc"] * data["biomMc"][j,:,:]/1000)
return data
def estimate_soil_potential_evaporation(j, data):
"""
This function computes estimation of potential soil evaporation (mm,
evapPot).
It performs its computations solely from the evaporation forcing driven by
climatic demand, limited by the coefficient of evaporation from the soil
(kce).
Note : difference in humectation of the top and bottom tanks is not taken
into consideration in this approach. The
This function has been adapted from DemandeSol procedure, from bileau.pas
and exmodules 1 & 2.pas file from the original FORTRAN code.
in Alhassane thesis, EvapPot = Kmulch x ETo x LTR ; here kce = kmulch x LTR so this formalism is respected
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["evapPot"][j,:,:] = data["ET0"][j,:,:] * data["kce"][j,:,:]
return data
def estimate_soil_evaporation(j, data):
"""
This function computes estimation of soil evaporable water (`evap`, mm). It uses
the potential soil evaporation (`evapPot`, mm) and the fraction of evaporable soil
water (`fesw`), bounded by the `surface_tank_stock` (mm).
We remind `fesw` is defined as the ratio of water stock in the surface tank
over 110% of the surface tank capacity, meaning it will be equal to 1 when
the surface tank is full, and 0 when the surface tank is empty.
This approach is somewhat comparable to the soil evaporation reduction
coefficient kr approach presented in FAO56 paper, to the exception the soil
evaporation reduction coefficient kr is built using two linear functions
where the squared fesw approach uses a square function. Furthermore, the kr
approach function is build using REW and TEW values that are specific to the
type of soil, whereas the squared fesw approach uses a generic function that
is not soil specific.
in Alhassane thesis, evap is called EVj for "evaporation journanière", and
is calculated as Evj = EvapPot x FESW. More details are available in the PhD
dissertation of Alhassane https://hdl.handle.net/20.500.12177/1576
The `estimate_effective_evaporation_from_evaporable_water` function computed
later in the daily cycle uses the `evap` value to determine the effective
evaporation (`consoRur`, mm) on the quantity of water in the surface tank
(`surface_tank_stock`, mm).
It has been adapted from the EvapRuSurf procedure, from bileau.pas and
exmodules 1 & 2.pas file from the original FORTRAN code.
? evaporation is bounded by the surface tank stock, which means it is meant
? to happen only in the depth described by the surface_tank
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["evap"][j:,:,:] = np.minimum(
data["evapPot"][j,:,:] * data["fesw"][j,:,:]**2,
data["surface_tank_stock"][j,:,:]
)
return data
def compute_soil_evaporation(j, data, paramITK):
data = estimate_fesw(j, data)
data = estimate_kce(j, data, paramITK)
data = estimate_soil_potential_evaporation(j, data)
data = estimate_soil_evaporation(j, data)
return data
def estimate_FEMcW_and_update_mulch_water_stock(j, data, paramITK):
"""
This function calculates the fraction of evaporable water from the mulch
(FEMcW).
If the mulch water stock is greater than 0, then we compute FEMcW, which we
consider to be equal to the filling ratio of the mulch water capacity. We
then update the mulch water stock by removing the water height equivalent to
the climate forcing demand, modulated by FEMcW and the plant cover (ltr).
This function is adapted from the procedure EvapMC, from bileau.pas and
exmodules 2.pas file from the original FORTRAN code.
Args:
j (_type_): _description_
data (_type_): _description_
paramITK (_type_): _description_
Returns:
_type_: _description_
"""
data["FEMcW"][j,:,:] = np.where(
data["mulch_water_stock"][j,:,:] > 0,
#! inverting the fraction to get stock over capacity, and not the other way round
#// (paramITK["humSatMc"] * data["biomMc"][j,:,:] * 0.001) / data["stockMc"][j,:,:],
data["mulch_water_stock"][j,:,:] / (paramITK["humSatMc"] * data["biomMc"][j,:,:] / 1000),
data["FEMcW"][j,:,:],
)
data["mulch_water_stock"][j:,:,:] = np.maximum(
0,
#! removing the power of 2 in the equation
#// data["stockMc"][j,:,:] - data["ltr"][j,:,:] * data["ET0"][j,:,:] * data["FEMcW"][j,:,:]**2,
data["mulch_water_stock"][j,:,:] - (data["ltr"][j,:,:] * data["ET0"][j,:,:] * data["FEMcW"][j,:,:]**2),
)
return data
def estimate_ftsw(j, data):
"""
This function estimates the fraction of transpirable soil water (ftsw) from
the root reservoir.
It is based on the EvalFTSW procedure, from the bileau.pas, exmodules 1 &
2.pas, risocas.pas, riz.pas files from the original FORTRAN code.
d'après alhassane thesis, "La fraction d'eau transpirable par la plante ou
"fraction of transpirable soil water (FTSW)" a été calculée une fois par
semaine durant le cycle de la variété : FTSW = ((Stock - pF4.2)/(RU x
profondeur racinaire)) x profondeur racinaire Avec : Stock = stock d'eau du
sol dans la zone racinaire (mm), pF4.2 = point de flétrissement et RU =
réserve utile (mm/msol). Ces variables sont fonction de la profondeur
racinaire, donc de la croissance de la culture"
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["ftsw"][j:,:,:] = np.where(
data["root_tank_capacity"][j,:,:] > 0,
data["root_tank_stock"][j,:,:] / data["root_tank_capacity"][j,:,:],
0,
)
return data
def estimate_potential_plant_transpiration(j, data):
"""
This function computes the potential transpiration from the plant.
Computation is based on the climate forcing (ET0), as well as the kcp coefficient.
This code is based on the DemandePlante procedure, from the bileau.pas, bhytypeFAO.pas, and
exmodules 1 & 2.pas files from the original FORTRAN code.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
# ggroup 51
data["trPot"][j,:,:] = (data["kcp"][j,:,:] * data["ET0"][j,:,:])
return data
# def estimate_kcTot(j, data):
# """
# This function computes the total kc coefficient.
# Computation is based on the kcp (transpiration coefficient) and kce
# (evaporation from the soil) coefficients. Where the crop coefficient is 0
# (meaning that there was no emergence yet), kcTot takes the value of kce.
# This function is based on the EvalKcTot procedure, from the bileau.pas and
# exmodules 1 & 2.pas files, from the original FORTRAN code.
# #! Note : code has been modified to match the original SARRA-H behavior.
# #! The kcTot function is computed but not used anywhere
# Args:
# j (_type_): _description_
# data (_type_): _description_
# Returns:
# _type_: _description_
# """
# # added a condition on 19/08/22 to match SARRA-H original behavior
# data["kcTot"][j,:,:] = np.where(
# data["kcp"][j,:,:] == 0.0,
# data["kce"][j,:,:],
# data["kce"][j,:,:] + data["kcp"][j,:,:],
# )
# return data
def estimate_pFact(j, data, paramVariete):
"""_summary_
This function computes the pFactor, which is a bound coefficient used in the
computation of cstr from ftsw. This coefficient delimits the portion of the
FTSW below which water stress starts to influence the transpiration.
FAO reference for critical FTSW value for transpiration response (0 =
stomata respond immediately if FTSW<1; 0.5 for most of the crops)
pFact is bounded in [0.1, 0.8].
For details see https://agritrop.cirad.fr/556855/1/document_556855.pdf
This function is based on the CstrPFactor procedure, from bileau.pas,
exmodules 1 & 2.pas, risocas.pas files, from the original FORTRAN code.
d'après alhassane :
"Selon Allen et al., (1998), on peut également appliquer la méthode de
calculs développée par la FAO, dite du pFactor, basée sur les notions de
réserve d'eau facilement utilisable (RFU) et difficilement utilisable (RDU)
définies par un point d'inflexion, si on considère que la dynamique de
consommation hydrique de la plante diffère selon la demande climatique (ETo)
et la fraction d'eau du sol transpirable (FTSW). En effet, pfactor est un
coefficient utilisé pour le calcul du taux de transpiration et qui s'obtient
en divisant la réserve d'eau du sol utilisable par les racines par la
réserve totale disponible dans la zone racinaire de la plante (Allen et al.,
1998). On l'obtient également par la formule suivante :
pfactor = parP + 0,04 x (5 - ETM)
Avec : parP = paramètre spécifique à
l'espèce, qui exprime le seuil critique d'humidité du sol à partir duquel le
stress hydrique réduit linéairement la transpiration (Doorenbos et Kassam,
1979)."
#! this function seems quite arbitrary and would need verifications regarding the underlying assumptions
ok so this function comes from FAO56 paper, https://www.fao.org/3/x0490e/x0490e0e.htm#chapter%208%20%20%20etc%20under%20soil%20water%20stress%20conditions
table 22 annex 2
FAO56 uses the RAW/TAW formalism where RAW = p TAW
with TAW being the total available water in the root zone TAW = 1000(q FC - q WP) Zr
corresponding to the root tank capacity
and RAW being calculated as RAW = p TAW
p average fraction of Total Available Soil Water (TAW) that can be depleted from the root zone before moisture stress (reduction in ET) occurs
"A value of 0.50 for p is commonly used for many crops"
"A numerical approximation for adjusting p for ETc rate is p = pTable 22 + 0.04 (5 - ETc) where the adjusted p is limited to 0.1 £ p £ 0.8 and ETc is in mm/day"
in the legacy code, ETc is computed with np.maximum(data["kcp"][j,:,:], 1) * data["ET0"][j,:,:]
however why is kcp bound to 1 ?
there seem to be no reason of keeping this bound.
Args:
j (_type_): _description_
data (_type_): _description_
paramVariete (_type_): _description_
Returns:
_type_: _description_
"""
# we keep these lines for legacy reference
# data["pFact"][j:,:,:] = paramVariete["PFactor"] + \
# 0.04 * (5 - np.maximum(data["kcp"][j,:,:], 1) * data["ET0"][j,:,:])
# adjustinf pfactor
data["pFact"][j:,:,:] = paramVariete["PFactor"] + 0.04 * (5 - data["kcp"][j,:,:] * data["ET0"][j,:,:])
# group 54
data["pFact"][j:,:,:] = np.minimum(
np.maximum(
0.1,
data["pFact"][j,:,:],
),
0.8,
)
return data
def estimate_cstr(j, data):
"""
This function computes the water stress coefficient cstr.
It uses ftsw and pFact. cstr is bounded in [0, 1].
This function is based on the CstrPFactor procedure, from bileau.pas,
exmodules 1 & 2.pas, risocas.pas files, from the original FORTRAN code.
in FAO56 paper RAW being calculated as RAW = p TAW and TAW is
p average fraction of Total Available Soil Water (TAW) that can be depleted from the root zone before moisture stress (reduction in ET) occurs
donc pour avoir la proportion de remplissage correspondant à la limite de stress hydrique, il faut faire
1 - pFact
et donc quand ftsw est inférieur à 1 - pFact, on est en stress hydrique et cstr est inférieur à 1
et quand ftsw est au dessus de 1 - pFact, on est pas en stress hydrique et cstr est égal à 1
en fait cstr correspond au coefficient Ks de FAO56 (figure 42 FAO56)
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
#group 55
data["cstr"][j:,:,:] = np.minimum((data["ftsw"][j,:,:] / (1 - data["pFact"][j,:,:])), 1)
# group 56
data["cstr"][j:,:,:] = np.maximum(0, data["cstr"][j,:,:])
return data
def estimate_plant_transpiration(j, data):
"""
This function computes the adjusted plant transpiration (tr, mm) from the
plant, by adjusting the potential transpiration (trPot, mm) with cstr.
This function adjusts the potential transpiration (trPot, mm) that was
calculated through trPot = kcp * ET0, by adding the stress coefficient cstr
(that corresponds to Ks in the FAO56 paper) Thus we obtain an adjusted plant
transpiration tr, which corresponds to ETc_adj in the FAO56 (see equation
80).
This function is based on the EvalTranspi procedure, from bileau.pas,
bhytypeFAO.pas, exmodules 1 & 2.pas, from the original FORTRAN code.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["tr"][j:,:,:] = data["trPot"][j,:,:] * data["cstr"][j,:,:]
return data
def compute_transpiration(j, data, paramVariete):
# we can take out the estimate_kcTot function
data = estimate_ftsw(j, data)
data = estimate_kcp(j, data, paramVariete)
data = estimate_potential_plant_transpiration(j, data)
#// data = estimate_kcTot(j, data)
data = estimate_pFact(j, data, paramVariete)
data = estimate_cstr(j, data)
data = estimate_plant_transpiration(j, data)
return data
def set_evapotranspirable_surface_water(j, data):
"""
This function stores the initial value of `surface_tank_stock` in a new
variable representing the quantity of evapotranspirable surface water (`trSurf`,
mm), as the value `surface_tank_stock` will later be updated.
The original function (based on the ConsoResSep procedure, from bileau.pas,
exmodules 1 & 2.pas files, from the original FORTRAN code) removed 1/10th of
surface tank capacity as water was condidered as bound to the soil.
However, as we are working with the water volumes in between permanent
wilting point and field capacity - thus already considering water bound to
the soil, we will remove all of these arbitrary 10% corrections.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["trSurf"][j:,:,:] = np.maximum(
0,
#// data["surface_tank_stock"][j,:,:] - data["surface_tank_capacity"] * 0.1,
data["surface_tank_stock"][j,:,:],
)
return data
def subtract_evap_from_surface_tank_stock(j, data):
"""
This function updates the water stock of the surface tank
(`surface_tank_stock`, mm) by subtracting the daily amount of evaporation
(`evap`, mm) from it.
`evap` is calculated in the `estimate_soil_evaporation` function earlier in
the daily loop from the potential soil evaporation (`evapPot`, mm) and the
fraction of evaporable soil water (`fesw`), and cannot exceed the value of
`surface_tank_stock`. This approach is somewhat comparable to the soil
evaporation reduction coefficient kr approach presented in FAO56 paper. See
documentation of the `estimate_soil_evaporation` function for further
details.
#? As `evap` cannot exceed the value of `surface_tank_stock` according to the
#? `estimate_soil_evaporation` function and is not modified until this function
#? is applied, it is funny to see that the
#? `subtract_evap_from_surface_tank_stock` function enforces
#? `surface_tank_stock` not to take a negative value. Hence, we could probably
#? simplify this function.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["surface_tank_stock"][j:,:,:] = np.maximum(0, data["surface_tank_stock"][j,:,:] - data["evap"][j,:,:])
return data
def estimate_effective_evaporation_from_evaporable_water(j, data):
"""
This function estimates the effective evaporation (`consoRur`, mm) from the
evaporable water (`evap`, mm) limited by the evapotranspirable surface water
(`trSurf`, mm).
If `evap` is higher than the quantity of water in the surface tank at the
beginning of the daily cycle/before evaporation (`trSurf`), meaning that
evaporation will deplete the surface water tank, then water consumption
shall take the value of `trSurf`.
Else, `consoRur` equals `evap`.
It is a way to bound the water consumption by the water available in the
surface tank.
#? the name of this function is not that great. thinking about something
#? about evaporation demand vs evaporated water
Args:
j (_type_): _description_
data (_type_): _description_
"""
data["consoRur"][j:,:,:] = np.where(
data["evap"][j,:,:] > data["trSurf"][j,:,:],
data["trSurf"][j,:,:],
data["evap"][j,:,:],
)
return data
def subtract_effective_evaporation_from_total_tank_stock(j, data):
"""
This function updates the total quantity of water (`total_tank_stock`, mm)
by subtracting the effective evaporation (`consoRur`, mm).
`total_tank_stock` value cannot be negative, so it is bound by 0.
#? though it seems impossible to have a negative value of `total_tank_stock`
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["total_tank_stock"][j:,:,:] = np.maximum(0, data["total_tank_stock"][j,:,:] - data["consoRur"][j,:,:])
return data
def update_effective_evaporation_for_shallow_roots(j, data):
"""
This function updates the value of effective evaporation (`consoRur`, mm) in
order to be used specifically to update the quantity of water in the root
tank (`root_tank_stock`, mm).
If the root tank capacity is lower than the surface tank capacity, meaning
than the roots do not dive into the deep tank yet, then the effective
evaporation is updated to equal the evaporable water (`evap`, mm) modulated
by the ratio between `root_tank_stock` and `surface_tank_capacity`, that is
to say at the prorata of the exploration of surface tank by the roots.
Else, `consoRur` keeps it value, which was previously computed by the
`estimate_effective_evaporation_from_evaporable_water` function.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["consoRur"][j:,:,:] = np.where(
data["root_tank_capacity"][j:,:,:] < data["surface_tank_capacity"],
data["evap"][j,:,:] * data["root_tank_stock"][j,:,:] / data["surface_tank_capacity"],
data["consoRur"][j,:,:],
)
return data
def subtract_effective_evaporation_from_root_tank_stock(j, data):
"""
This function updates the quantity of water in the root tank
(`root_tank_stock`, mm) according to the effective evaporation, taking into
consideration potential shallow rooting ( as `consoRur` was calculated
through both `estimate_effective_evaporation_from_evaporable_water` and
`update_effective_evaporation_for_shallow_roots` functions).
`root_tank_stock` value cannot be negative, so it is bound by 0.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["root_tank_stock"][j:,:,:] = np.maximum(0, data["root_tank_stock"][j,:,:] - data["consoRur"][j,:,:])
return data
def update_plant_transpiration(j, data):
"""
This function updates the value of plant transpiration (`tr`, mm) according
to the quantity of water in the root tank (`root_tank_stock`, mm).
If the transpiration (which as this point on the daily cycle equals `trPot *
cstr`) is higher than the root tank stock (which at this point has been
updated to reflect the effective evaporation), then plant transpiration is
updated to be equal to the difference between the root tank stock and the
plant transpiration.
Else, its value is unmodified.
#? However, if this test is true, this always leads to transpiration value = 0.
#? We may want to rethink it to check if it does what it was supposed to.
#? Instead it would make more sense to bound tr by the value of root tank stock,
#? meaning the maximum quantity of water that can be transpired by the plant
#? is limited by the quantity of water accessible to roots...
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["tr"][j:,:,:] = np.where(
data["tr"][j,:,:] > data["root_tank_stock"][j,:,:],
np.maximum(data["root_tank_stock"][j,:,:] - data["tr"][j,:,:], 0),
#! il peut être intéressant d'introduire dans la prochaine version
#! le correctif suivant :
# data["root_tank_stock"][j,:,:],
data["tr"][j,:,:],
)
return data
def subtract_transpiration_from_surface_tank_stock_according_to_root_tank_stock(j, data):
"""
This function updates the surface tank stock to reflect plant transpiration,
in accordance with the repartition of water between surface and root tanks.
If `root_tank_stock` is above 0, then `surface_tank_stock` is updated by
subtracting the plant transpiration modulated by the ratio between the
transpirable water `trSurf` (representing the amount of water in surface
tank at the beginning of the day) and the root tank stock.
This ratio is bounded by 1, meaning that if there is a higher quantity of
transpirable water that water accessible to roots, then the ratio is set to
1, and `surface_tank_stock` will be updated by being subtracted by `tr`.
On the contrary, if transpirable water `trSurf` is much lower than the root
tank stock, meaning that there is a lot of water accessible to roots but
this water is in the deep tank, then the ratio will be close to 0, and the
`surface_tank_stock` will be updated by being subtracted by a very low value
of `tr`.
#? The rules and underlying assumptions for these transfers seem somewhat
#? arbitrary, so we want to be cautious about them.
#? Also, it is not clear why we use trSurf instead of surface_tank_stock
#? in the calculations.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["surface_tank_stock"][j:,:,:] = np.where(
data["root_tank_stock"][j,:,:] > 0,
np.maximum(
data["surface_tank_stock"][j,:,:] - (data["tr"][j,:,:] * np.minimum(
data["trSurf"][j,:,:] / data["root_tank_stock"][j,:,:],
1,
)),
0,
),
data["surface_tank_stock"][j,:,:],
)
return data
def subtract_transpiration_from_root_tank_stock(j, data):
"""
This function updates the quantity of water in the root tank
(`root_tank_stock`, mm) by subtracting the plant transpiration (`tr`, mm)
from it.
`root_tank_stock` value cannot be negative, so it is bound by 0.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["root_tank_stock"][j:,:,:] = np.maximum(0, data["root_tank_stock"][j,:,:] - data["tr"][j,:,:])
return data
def subtract_transpiration_from_total_tank_stock(j, data):
"""
This function updates the total quantity of water (`total_tank_stock`, mm)
by subtracting the plant transpiration (`tr`, mm) from it.
`total_tank_stock` value cannot be negative, so it is bound by 0.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["total_tank_stock"][j:,:,:] = np.maximum(0, data["total_tank_stock"][j,:,:] - data["tr"][j,:,:])
return data
# def update_etr_etm(j, data):
# """
# This function computes etm and etr values.
# #? However they are not used anywhere else in the code.
# #? We may want to deprecate it.
# Args:
# j (_type_): _description_
# data (_type_): _description_
# Returns:
# _type_: _description_
# """
# data["etr"][j:,:,:] = (data["tr"][j,:,:] + data["evap"][j,:,:]).copy()
# data["etm"][j:,:,:] = (data["trPot"][j,:,:] + data["evapPot"][j,:,:]).copy()
# return data
def ConsoResSep(j, data):
"""
This function is a wrapper function, that calls all the functions that take
part into the calculation of the water consumption from the soil, hence the
different tanks represented by the model.
It is based on the ConsoResSep procedure, from bileau.pas original source
code. The general spirit of separation of evaporation from transpiration is
probably based on the FAO56 paper, but the formalism is different.
A few notes on the original assumptions behind these processes :
- The order in which the different computations are done is based on the
hypothesis that evaporation is the fastest process, and that it will deplete
the surface tank stock first.
- As surface tank and root tank overlap, we need to take into consideration
the effect of the water already depleted by evaporation on the root tank
stock.
- When root tank depth is lower than surface tank depth, we only evaporate
the fraction of water corresponding to the root tank depth over the surface
tank depth.
Previous notes also stated that as the computations are done separately for
evaporation and transpiration, we could end up with a water consumption
slightly higher than the available water. In this case, we would decrease
transpiration accordingly. However, this statement is dubious as this
behavior does not seem to be implemented in the code.
"""
data = set_evapotranspirable_surface_water(j, data)
data = subtract_evap_from_surface_tank_stock(j, data)
data = estimate_effective_evaporation_from_evaporable_water(j, data)
data = subtract_effective_evaporation_from_total_tank_stock(j, data)
data = update_effective_evaporation_for_shallow_roots(j, data)
data = subtract_effective_evaporation_from_root_tank_stock(j, data)
data = update_plant_transpiration(j, data)
data = subtract_transpiration_from_surface_tank_stock_according_to_root_tank_stock(j, data)
data = subtract_transpiration_from_root_tank_stock(j, data)
data = subtract_transpiration_from_total_tank_stock(j, data)
#// data = update_etr_etm(j, data)
return data
Functions
ConsoResSep
def ConsoResSep(
j,
data
)
This function is a wrapper function, that calls all the functions that take
part into the calculation of the water consumption from the soil, hence the different tanks represented by the model.
It is based on the ConsoResSep procedure, from bileau.pas original source code. The general spirit of separation of evaporation from transpiration is probably based on the FAO56 paper, but the formalism is different.
A few notes on the original assumptions behind these processes :
- The order in which the different computations are done is based on the hypothesis that evaporation is the fastest process, and that it will deplete the surface tank stock first.
- As surface tank and root tank overlap, we need to take into consideration the effect of the water already depleted by evaporation on the root tank stock.
- When root tank depth is lower than surface tank depth, we only evaporate the fraction of water corresponding to the root tank depth over the surface tank depth.
Previous notes also stated that as the computations are done separately for evaporation and transpiration, we could end up with a water consumption slightly higher than the available water. In this case, we would decrease transpiration accordingly. However, this statement is dubious as this behavior does not seem to be implemented in the code.
View Source
def ConsoResSep(j, data):
"""
This function is a wrapper function, that calls all the functions that take
part into the calculation of the water consumption from the soil, hence the
different tanks represented by the model.
It is based on the ConsoResSep procedure, from bileau.pas original source
code. The general spirit of separation of evaporation from transpiration is
probably based on the FAO56 paper, but the formalism is different.
A few notes on the original assumptions behind these processes :
- The order in which the different computations are done is based on the
hypothesis that evaporation is the fastest process, and that it will deplete
the surface tank stock first.
- As surface tank and root tank overlap, we need to take into consideration
the effect of the water already depleted by evaporation on the root tank
stock.
- When root tank depth is lower than surface tank depth, we only evaporate
the fraction of water corresponding to the root tank depth over the surface
tank depth.
Previous notes also stated that as the computations are done separately for
evaporation and transpiration, we could end up with a water consumption
slightly higher than the available water. In this case, we would decrease
transpiration accordingly. However, this statement is dubious as this
behavior does not seem to be implemented in the code.
"""
data = set_evapotranspirable_surface_water(j, data)
data = subtract_evap_from_surface_tank_stock(j, data)
data = estimate_effective_evaporation_from_evaporable_water(j, data)
data = subtract_effective_evaporation_from_total_tank_stock(j, data)
data = update_effective_evaporation_for_shallow_roots(j, data)
data = subtract_effective_evaporation_from_root_tank_stock(j, data)
data = update_plant_transpiration(j, data)
data = subtract_transpiration_from_surface_tank_stock_according_to_root_tank_stock(j, data)
data = subtract_transpiration_from_root_tank_stock(j, data)
data = subtract_transpiration_from_total_tank_stock(j, data)
#// data = update_etr_etm(j, data)
return data
EvolRurCstr2
def EvolRurCstr2(
j,
data,
paramITK
)
This function is a legacy wrapper for the functions related to the
calculation of the root tank capacity and stock.
It has been translated from the procedure EvolRurCstr2, of the original Pascal codes bileau.pas.
Notes from CB, 10/06/2015 : Stress trop fort enracinement Trop d'effet de stress en tout début de croissance : 1) la plantule a des réserves et favorise l'enracinement 2) dynamique spécifique sur le réservoir de surface Cet effet stress sur l'enracinement ne s'applique que quand l'enracinement est supérieur é la profondeur du réservoir de surface. Effet stres a un effet sur la vitesse de prof d'enracinement au dessus d'un certain seuil de cstr (on augmente le cstr de 0.3 pour que sa contrainte soit affaiblie sur la vitesse) La vitesse d'enracinement potentielle de la plante peut etre bloque par manque d'eau en profondeur (Hum). La profondeur d'humectation est convertie en quantite d'eau maximum equivalente
View Source
def EvolRurCstr2(j, data, paramITK):
"""
This function is a legacy wrapper for the functions related to the
calculation of the root tank capacity and stock.
It has been translated from the procedure EvolRurCstr2, of the original
Pascal codes bileau.pas.
Notes from CB, 10/06/2015 :
Stress trop fort enracinement
Trop d'effet de stress en tout début de croissance :
1) la plantule a des réserves et favorise l'enracinement
2) dynamique spécifique sur le réservoir de surface
Cet effet stress sur l'enracinement ne s'applique que quand l'enracinement
est supérieur é la profondeur du réservoir de surface. Effet stres a un
effet sur la vitesse de prof d'enracinement au dessus d'un certain seuil de
cstr (on augmente le cstr de 0.3 pour que sa contrainte soit affaiblie sur
la vitesse) La vitesse d'enracinement potentielle de la plante peut etre
bloque par manque d'eau en profondeur (Hum). La profondeur d'humectation est
convertie en quantite d'eau maximum equivalente
"""
data = initialize_root_tank_capacity(j, data, paramITK)
data = initialize_delta_root_tank_capacity(j, data)
data = update_delta_root_tank_capacity(j, data)
data = update_root_tank_capacity(j, data)
data = update_root_tank_stock(j, data) #! we keep this function for now even though it is probably wrong, it will need further screening
return data
InitPlotMc
def InitPlotMc(
data,
grid_width,
grid_height,
paramITK,
paramTypeSol,
duration
)
Initializes variables related to crop residues boimass (mulch) in the data
xarray dataset. This code has been adapted from the original InitPlotMc procedure, Bileau.pas code. Comments with tab indentation are from the original code. As the rain is the first variable to be initialized in the data xarray dataset, its dimensions are used to initialize the other variables.
View Source
def InitPlotMc(data, grid_width, grid_height, paramITK, paramTypeSol, duration):
"""
Initializes variables related to crop residues boimass (mulch) in the data
xarray dataset. This code has been adapted from the original InitPlotMc
procedure, Bileau.pas code. Comments with tab indentation are from the
original code. As the rain is the first variable to be initialized in the
data xarray dataset, its dimensions are used to initialize the other
variables.
"""
# Soil maximum water storage capacity (mm)
# Capacité maximale de la RU (mm)
# StRurMax := Ru * ProfRacIni / 1000;
#! renaming stRurMax with root_tank_capacity
#// data["stRurMax"] = data["ru"] * paramITK["profRacIni"] / 1000
data["root_tank_capacity"] = (data["rain"].dims, np.repeat(np.array(data["ru"] * paramITK["profRacIni"] / 1000)[np.newaxis,:,:], duration, axis=0))
#// data["stRurMax"].attrs = {"units": "mm", "long_name": "Soil maximum water storage capacity"}
data["root_tank_capacity"].attrs = {"units": "mm", "long_name": "Soil maximum water storage capacity"}
# Maximum water capacity of surface tank (mm)
# Reserve utile de l'horizon de surface (mm)
# RuSurf := EpaisseurSurf / 1000 * Ru;
#! renaming ruSurf with surface_tank_capacity
#// data["ruSurf"] = data["epaisseurSurf"] / 1000 * data["ru"]
data["surface_tank_capacity"] = data["epaisseurSurf"] / 1000 * data["ru"]
#// data["ruSurf"].attrs = {"units": "mm", "long_name": "Maximum water capacity of surface tank"}
data["surface_tank_capacity"].attrs = {"units": "mm", "long_name": "Maximum water capacity of surface tank"}
# ?
# // PfTranspi := EpaisseurSurf * HumPf;
# // StTot := StockIniSurf - PfTranspi/2 + StockIniProf;
# StTot := StockIniSurf + StockIniProf;
# data["stTot"] = np.full((grid_width, grid_height, duration), (paramTypeSol["stockIniSurf"] + paramTypeSol["stockIniProf"]))
#! modifié pour faire correspondre les résultats de simulation, à remettre en place pour un calcul correct dès que possible
# data["stTot"] = np.full((grid_width, grid_height, duration), (paramTypeSol["stockIniProf"]))
#! renaming stTot to total_tank_stock
#// data["stTot"] = data["stockIniProf"]
#//data["total_tank_stock"] = data["stockIniProf"]
#! coorecting total_tank_stock initialization as it did not have the time dimensions that are required as stock evolves through time
data["total_tank_stock"] = (data["rain"].dims, np.repeat(np.array(data["stockIniProf"])[np.newaxis,:,:], duration, axis=0))
#// data["stTot"].attrs = {"units": "mm", "long_name": "?"}
data["total_tank_stock"].attrs = {"units": "mm", "long_name": "?"}
# Soil maximal depth (mm)
# Profondeur maximale de sol (mm)
# ProfRU := EpaisseurSurf + EpaisseurProf;
data["profRu"] = data["epaisseurProf"] + data["epaisseurSurf"]
data["profRu"].attrs = {"units": "mm", "long_name": "Soil maximal depth"}
# Maximum water capacity to humectation front (mm)
# Quantité d'eau maximum jusqu'au front d'humectation (mm)
# // modif 10/06/2015 resilience stock d'eau
# // Front d'humectation egal a RuSurf trop de stress initial
# // Hum := max(StTot, StRurMax);
# Hum := max(RuSurf, StRurMax);
# // Hum mis a profRuSurf
# Hum := max(StTot, Hum);
data["humectation_front"] = (data["rain"].dims, np.full((duration, grid_width, grid_height),
np.maximum(
np.maximum(
#! renaming ruSurf with surface_tank_capacity
#// data["ruSurf"],
data["surface_tank_capacity"].expand_dims({"time":duration}),
#! renaming stRurMax with root_tank_capacity
#// data["stRurMax"],
data["root_tank_capacity"],
),
#! renaming stTot with total_tank_stock
#// data["stTot"],
data["total_tank_stock"],
)
))
data["humectation_front"].attrs = {"units": "mm", "long_name": "Maximum water capacity to humectation front"}
# Previous value for Maximum water capacity to humectation front (mm)
# HumPrec := Hum;
data["previous_humectation_front"] = data["humectation_front"]
# ?
# StRurPrec := 0;
# Previous value for stTot
# StRurMaxPrec := 0;
# //modif 10/06/2015 resilience stock d'eau
#! renaming stTot with total_tank_stock
#! renaminog stRuPrec with previous_total_tank_stock
#// data["stRuPrec"] = data["stTot"]
data["previous_total_tank_stock"] = data["total_tank_stock"]
return data
apply_humectation_front_boundaries
def apply_humectation_front_boundaries(
j,
data
)
This function updates the water height to humectation front
(humectation_front, mm) by bounding it between delta_total_tank_stock and total_tank_capacity.
That is to say depth of humectation front can only increase, and that humectation front can not go down indefinitely.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def apply_humectation_front_boundaries(j, data):
"""
This function updates the water height to humectation front
(humectation_front, mm) by bounding it between delta_total_tank_stock and
total_tank_capacity.
That is to say depth of humectation front can only increase, and that
humectation front can not go down indefinitely.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["humectation_front"][j:,:,:] = np.maximum(data["delta_total_tank_stock"][j,:,:], data["humectation_front"][j,:,:])
data["humectation_front"][j:,:,:] = np.minimum(data["total_tank_capacity"][j,:,:], data["humectation_front"][j,:,:])
return data
compute_daily_irrigation
def compute_daily_irrigation(
j,
data,
paramITK
)
This function computes the total daily irrigation
If the simulation is run with automatic irrigation mode
(data["irrigAuto"]==True
), if the current phase is between 0 and 6, and if
the filling rate the irrigation tank is below the target filling value
(irrigAutoTarget
, decimal percentage), we first compute 90% of the
difference between the current volume of water in the irrigation tank
(irrigation_tank_stock
) and the total capacity of the irrigation tank
(irrigation_tank_capacity
), bounded by a minimum of 0 and a maximum of
maxIrrig
. This computed value represents the amount of water to be added
to the irrigation tank. If the above conditions are not met, the computed
value is 0.
Then, we calculate the total irrigation of the day by summing the estimated
irrigation need (irrigation
) with the previous irrigation history of the
day (irrigTotDay
).
Note : the logic of this function has not yet been validated in SARRA-Py, as simulations are mainly based on rainfed conditions.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | None | An integer representing the current day. | None |
data | None | A xarray dataset. | None |
paramITK | None | A dictionary of parameters. | None |
Returns:
Type | Description |
---|---|
data | A xarray dataset with the updated irrigationTotDay field. |
View Source
def compute_daily_irrigation(j, data, paramITK):
"""
This function computes the total daily irrigation
If the simulation is run with automatic irrigation mode
(`data["irrigAuto"]==True`), if the current phase is between 0 and 6, and if
the filling rate the irrigation tank is below the target filling value
(`irrigAutoTarget`, decimal percentage), we first compute 90% of the
difference between the current volume of water in the irrigation tank
(`irrigation_tank_stock`) and the total capacity of the irrigation tank
(`irrigation_tank_capacity`), bounded by a minimum of 0 and a maximum of
`maxIrrig`. This computed value represents the amount of water to be added
to the irrigation tank. If the above conditions are not met, the computed
value is 0.
Then, we calculate the total irrigation of the day by summing the estimated
irrigation need (`irrigation`) with the previous irrigation history of the
day (`irrigTotDay`).
Note : the logic of this function has not yet been validated in SARRA-Py, as
simulations are mainly based on rainfed conditions.
Args:
j: An integer representing the current day.
data: A xarray dataset.
paramITK: A dictionary of parameters.
Returns:
data: A xarray dataset with the updated irrigationTotDay field.
"""
condition = (data["irrigAuto"][j, :, :] == True) & \
(data["numPhase"][j, :, :] > 0) & \
(data["numPhase"][j, :, :] < 6) & \
(data["irrigation_tank_stock"][j, :, :] / data["irrigation_tank_capacity"][j,:,:] \
< paramITK["irrigAutoTarget"])
data["irrigTotDay"][j, :, :] = xr.where(
condition,
np.minimum(
np.maximum(
0,
((data["irrigation_tank_capacity"][j, :, :] - data["irrigation_tank_stock"][j, :, :]) * 0.9) \
- data["irrigation"][j, :, :]
),
paramITK["maxIrrig"]
),
0,
)
data["irrigTotDay"][j, :, :] = (
data["irrigation"][j, :, :] + data["irrigTotDay"][j, :, :])
return data
compute_drainage
def compute_drainage(
j,
data
)
This wrapper function aims to regroup computations related to drainage :
- update_drainage
- update total_tank_stock
- update_humectation_front_after_drainage
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
View Source
def compute_drainage(j, data):
"""
This wrapper function aims to regroup computations related to drainage :
- update_drainage
- update total_tank_stock
- update_humectation_front_after_drainage
Args:
j (_type_): _description_
data (_type_): _description_
"""
data = update_drainage(j, data)
data = update_total_tank_stock_after_drainage(j, data)
data = update_humectation_front_after_drainage(j, data)
return data
compute_irrigation_state
def compute_irrigation_state(
j,
data,
paramITK
)
This wrapper function computes the irrigation state for a given day,
including the size and filling of the irrigation tank and the irrigation
demand. It is computed only if paramITK["irrigAuto"] == True
; this means
that irrigAuto shall be the same all over the grid, which is a reasonable
assumption
It has been Translated from the procedure EvalIrrigPhase, of the original Pascal codes bileau.pas and exmodules2.pas. Calculation precision is not taken into account anymore.
irrigation_tank_stock and irrigation_tank_capacity are only computed in order to avoid issues with very shallow rooting, where calculation of filling of root_tank_capacity by root_tank_stock can be inappropriate and lead to inadapted results for automatic irrigation
Notes from CB, 2014 : "Modification due à la prise en compte effet Mulch Soit on a une irrigation observée, soit on calcul la dose d'irrigation. Elle est calculée en fonction d'un seuil d'humidité (IrrigAutoTarget) et de possibilité technique ou choix (MaxIrrig, Precision). Dans cette gestion d'irrigation la pluie du jour n'est pas prise en compte."
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | int | Index of the day for which the irrigation state is being computed. | None |
data | xarray.Dataset | The input data, including the arrays for irrigation and correctedIrrigation. | None |
paramITK | dict | The parameters for the ITK model. | None |
Returns:
Type | Description |
---|---|
xarray.Dataset | The updated data, including the computed values for the irrigation state. |
View Source
def compute_irrigation_state(j, data, paramITK):
"""
This wrapper function computes the irrigation state for a given day,
including the size and filling of the irrigation tank and the irrigation
demand. It is computed only if `paramITK["irrigAuto"] == True` ; this means
that irrigAuto shall be the same all over the grid, which is a reasonable
assumption
It has been Translated from the procedure EvalIrrigPhase, of the original
Pascal codes bileau.pas and exmodules2.pas. Calculation precision is not
taken into account anymore.
irrigation_tank_stock and irrigation_tank_capacity are only computed in
order to avoid issues with very shallow rooting, where calculation of
filling of root_tank_capacity by root_tank_stock can be inappropriate and
lead to inadapted results for automatic irrigation
Notes from CB, 2014 : "Modification due à la prise en compte effet Mulch
Soit on a une irrigation observée, soit on calcul la dose d'irrigation. Elle
est calculée en fonction d'un seuil d'humidité (IrrigAutoTarget) et de
possibilité technique ou choix (MaxIrrig, Precision). Dans cette gestion
d'irrigation la pluie du jour n'est pas prise en compte."
Args:
j (int): Index of the day for which the irrigation state is being computed.
data (xarray.Dataset): The input data, including the arrays for irrigation and correctedIrrigation.
paramITK (dict): The parameters for the ITK model.
Returns:
xarray.Dataset: The updated data, including the computed values for the irrigation state.
"""
if paramITK["irrigAuto"] == True :
data = update_irrigation_tank_stock(j, data)
data = update_irrigation_tank_capacity(j, data)
data = compute_daily_irrigation(j, data, paramITK)
return data
compute_runoff
def compute_runoff(
j,
data
)
Translated from the procedure PluieIrrig, of the original Pascal codes
bileau.pas, exmodules1.pas and exmodules2.pas
Notes from CB, 2014 : On a regroupé avant la pluie et l'irrigation (a cause de l'effet Mulch) si mulch on a enlevé l'eau captée oN CALCUL SIMPLEMENT LE RUISSELLEMENT EN FN DE SEUILS
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
paramTypeSol | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def compute_runoff(j, data):
"""
Translated from the procedure PluieIrrig, of the original Pascal codes
bileau.pas, exmodules1.pas and exmodules2.pas
Notes from CB, 2014 :
On a regroupé avant la pluie et l'irrigation (a cause de l'effet Mulch)
si mulch on a enlevé l'eau captée
oN CALCUL SIMPLEMENT LE RUISSELLEMENT EN FN DE SEUILS
Args:
j (_type_): _description_
data (_type_): _description_
paramTypeSol (_type_): _description_
Returns:
_type_: _description_
"""
data = estimate_runoff(j, data)
data = update_available_water_after_runoff(j, data)
return data
compute_soil_evaporation
def compute_soil_evaporation(
j,
data,
paramITK
)
View Source
def compute_soil_evaporation(j, data, paramITK):
data = estimate_fesw(j, data)
data = estimate_kce(j, data, paramITK)
data = estimate_soil_potential_evaporation(j, data)
data = estimate_soil_evaporation(j, data)
return data
compute_total_available_water
def compute_total_available_water(
j,
data
)
This function computes the total available water for a day (mm) by adding
rainfall and irrigation.
This calculation is performed to allow for subsequent calculations of the mulch filling and water runoff.
The available_water variable is later updated during the same day process list, so its value is not the same at the beginning and the end of the daily computation loop.
This function has benn translated from the procedure PluieIrrig, of the original Pascal codes bileau.pas and exmodules2.pas.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | int | The index of the current day. | None |
data | xarray.Dataset | The data set containing information about the rainfall, irrigation, and water availability. | None |
Returns:
Type | Description |
---|---|
xarray.Dataset | The data set with updated information about the total water availability for the current day. |
View Source
def compute_total_available_water(j, data):
"""
This function computes the total available water for a day (mm) by adding
rainfall and irrigation.
This calculation is performed to allow for subsequent calculations of the
mulch filling and water runoff.
The available_water variable is later updated during the same day process
list, so its value is not the same at the beginning and the end of the daily
computation loop.
This function has benn translated from the procedure PluieIrrig, of the
original Pascal codes bileau.pas and exmodules2.pas.
Args:
j (int): The index of the current day.
data (xarray.Dataset): The data set containing information about the rainfall, irrigation, and water availability.
Returns:
xarray.Dataset: The data set with updated information about the total water availability for the current day.
"""
data["available_water"][j,:,:] = data["rain"][j,:,:] + data["irrigTotDay"][j,:,:]
return data
compute_transpiration
def compute_transpiration(
j,
data,
paramVariete
)
View Source
def compute_transpiration(j, data, paramVariete):
# we can take out the estimate_kcTot function
data = estimate_ftsw(j, data)
data = estimate_kcp(j, data, paramVariete)
data = estimate_potential_plant_transpiration(j, data)
#// data = estimate_kcTot(j, data)
data = estimate_pFact(j, data, paramVariete)
data = estimate_cstr(j, data)
data = estimate_plant_transpiration(j, data)
return data
compute_water_captured_by_mulch
def compute_water_captured_by_mulch(
j,
data,
paramITK
)
This function computes the height of water captured by the mulch.
For this, we multiply the 'available_water' (rain + irrigation, in mm) by an exponential function taking both into consideration the mulch covering capacity (surfMc, ha/t) and mulch biomass (biomMc, kg/ha), representing the fraction of soil covered by mulch. If the fraction is 0 (no mulch), the value of water_captured_by_mulch is 0.
The value of water_captured_by_mulch is bounded by the maximum capacity of the mulch to gather water (humSatMc, kg H2O/kg biomass), minus stock of water already present in it (mulch_water_stock, mm).
Note : the logic of this function has not yet been validated in SARRA-Py, as simulations are mainly based on situations without mulch.
Notes from CB, 2014 : Hypotheses : A chaque pluie, on estime la quantité d'eau pour saturer le couvert. On la retire à l'eauDispo (pluie + irrig). On calcule la capacité maximum de stockage fonction de la biomasse et du taux de saturation rapportée en mm (humSatMc en kg H2O/kg de biomasse). La pluie est en mm : 1 mm = 1 litre d'eau / m2 1 mm = 10 tonnes d'eau / hectare = 10 000 kg/ha La biomasse est en kg/ha pour se rapporter à la quantité de pluie captée en mm Kg H2O/kg Kg/ha et kg/m2 on divise par 10 000 (pour 3000 kg/ha à humSat 2.8 kg H2O/kg on a un stockage max de 0.84 mm de pluie !?) Cette capacité à capter est fonction du taux de couverture du sol calculé comme le LTR SurfMc est spécifié en ha/t (0.39), on rapporte en ha/kg en divisant par 1000 On retire alors les mm d'eau captées à la pluie incidente. Le ruisselement est ensuite calculé avec l'effet de contrainte du mulch
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 compute_water_captured_by_mulch(j, data, paramITK):
"""
This function computes the height of water captured by the mulch.
For this, we multiply the 'available_water' (rain + irrigation, in mm) by an
exponential function taking both into consideration the mulch covering
capacity (surfMc, ha/t) and mulch biomass (biomMc, kg/ha), representing the
fraction of soil covered by mulch. If the fraction is 0 (no mulch), the
value of water_captured_by_mulch is 0.
The value of water_captured_by_mulch is bounded by the maximum capacity of
the mulch to gather water (humSatMc, kg H2O/kg biomass), minus stock of
water already present in it (mulch_water_stock, mm).
Note : the logic of this function has not yet been validated in SARRA-Py, as
simulations are mainly based on situations without mulch.
Notes from CB, 2014 :
Hypotheses : A chaque pluie, on estime la quantité d'eau pour saturer le
couvert. On la retire à l'eauDispo (pluie + irrig). On calcule la capacité
maximum de stockage fonction de la biomasse et du taux de saturation
rapportée en mm (humSatMc en kg H2O/kg de biomasse). La pluie est en mm : 1
mm = 1 litre d'eau / m2 1 mm = 10 tonnes d'eau / hectare = 10 000 kg/ha La
biomasse est en kg/ha pour se rapporter à la quantité de pluie captée en mm
Kg H2O/kg Kg/ha et kg/m2 on divise par 10 000 (pour 3000 kg/ha à humSat 2.8
kg H2O/kg on a un stockage max de 0.84 mm de pluie !?) Cette capacité à
capter est fonction du taux de couverture du sol calculé comme le LTR SurfMc
est spécifié en ha/t (0.39), on rapporte en ha/kg en divisant par 1000 On
retire alors les mm d'eau captées à la pluie incidente. Le ruisselement est
ensuite calculé avec l'effet de contrainte du mulch
Args:
j (_type_): _description_
data (_type_): _description_
paramITK (_type_): _description_
Returns:
_type_: _description_
"""
data["water_captured_by_mulch"][j,:,:] = np.minimum(
data["available_water"][j,:,:] * (1 - np.exp(-paramITK["surfMc"] / 1000 * data["biomMc"][j,:,:])),
(paramITK["humSatMc"] * data["biomMc"][j,:,:] / 10000) - data["mulch_water_stock"][j,:,:],
)
return data
estimate_FEMcW_and_update_mulch_water_stock
def estimate_FEMcW_and_update_mulch_water_stock(
j,
data,
paramITK
)
This function calculates the fraction of evaporable water from the mulch
(FEMcW).
If the mulch water stock is greater than 0, then we compute FEMcW, which we consider to be equal to the filling ratio of the mulch water capacity. We then update the mulch water stock by removing the water height equivalent to the climate forcing demand, modulated by FEMcW and the plant cover (ltr).
This function is adapted from the procedure EvapMC, from bileau.pas and exmodules 2.pas file from the original FORTRAN code.
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 estimate_FEMcW_and_update_mulch_water_stock(j, data, paramITK):
"""
This function calculates the fraction of evaporable water from the mulch
(FEMcW).
If the mulch water stock is greater than 0, then we compute FEMcW, which we
consider to be equal to the filling ratio of the mulch water capacity. We
then update the mulch water stock by removing the water height equivalent to
the climate forcing demand, modulated by FEMcW and the plant cover (ltr).
This function is adapted from the procedure EvapMC, from bileau.pas and
exmodules 2.pas file from the original FORTRAN code.
Args:
j (_type_): _description_
data (_type_): _description_
paramITK (_type_): _description_
Returns:
_type_: _description_
"""
data["FEMcW"][j,:,:] = np.where(
data["mulch_water_stock"][j,:,:] > 0,
#! inverting the fraction to get stock over capacity, and not the other way round
#// (paramITK["humSatMc"] * data["biomMc"][j,:,:] * 0.001) / data["stockMc"][j,:,:],
data["mulch_water_stock"][j,:,:] / (paramITK["humSatMc"] * data["biomMc"][j,:,:] / 1000),
data["FEMcW"][j,:,:],
)
data["mulch_water_stock"][j:,:,:] = np.maximum(
0,
#! removing the power of 2 in the equation
#// data["stockMc"][j,:,:] - data["ltr"][j,:,:] * data["ET0"][j,:,:] * data["FEMcW"][j,:,:]**2,
data["mulch_water_stock"][j,:,:] - (data["ltr"][j,:,:] * data["ET0"][j,:,:] * data["FEMcW"][j,:,:]**2),
)
return data
estimate_cstr
def estimate_cstr(
j,
data
)
This function computes the water stress coefficient cstr.
It uses ftsw and pFact. cstr is bounded in [0, 1].
This function is based on the CstrPFactor procedure, from bileau.pas, exmodules 1 & 2.pas, risocas.pas files, from the original FORTRAN code.
in FAO56 paper RAW being calculated as RAW = p TAW and TAW is p average fraction of Total Available Soil Water (TAW) that can be depleted from the root zone before moisture stress (reduction in ET) occurs
donc pour avoir la proportion de remplissage correspondant à la limite de stress hydrique, il faut faire 1 - pFact
et donc quand ftsw est inférieur à 1 - pFact, on est en stress hydrique et cstr est inférieur à 1 et quand ftsw est au dessus de 1 - pFact, on est pas en stress hydrique et cstr est égal à 1
en fait cstr correspond au coefficient Ks de FAO56 (figure 42 FAO56)
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def estimate_cstr(j, data):
"""
This function computes the water stress coefficient cstr.
It uses ftsw and pFact. cstr is bounded in [0, 1].
This function is based on the CstrPFactor procedure, from bileau.pas,
exmodules 1 & 2.pas, risocas.pas files, from the original FORTRAN code.
in FAO56 paper RAW being calculated as RAW = p TAW and TAW is
p average fraction of Total Available Soil Water (TAW) that can be depleted from the root zone before moisture stress (reduction in ET) occurs
donc pour avoir la proportion de remplissage correspondant à la limite de stress hydrique, il faut faire
1 - pFact
et donc quand ftsw est inférieur à 1 - pFact, on est en stress hydrique et cstr est inférieur à 1
et quand ftsw est au dessus de 1 - pFact, on est pas en stress hydrique et cstr est égal à 1
en fait cstr correspond au coefficient Ks de FAO56 (figure 42 FAO56)
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
#group 55
data["cstr"][j:,:,:] = np.minimum((data["ftsw"][j,:,:] / (1 - data["pFact"][j,:,:])), 1)
# group 56
data["cstr"][j:,:,:] = np.maximum(0, data["cstr"][j,:,:])
return data
estimate_effective_evaporation_from_evaporable_water
def estimate_effective_evaporation_from_evaporable_water(
j,
data
)
This function estimates the effective evaporation (consoRur
, mm) from the
evaporable water (evap
, mm) limited by the evapotranspirable surface water
(trSurf
, mm).
If evap
is higher than the quantity of water in the surface tank at the
beginning of the daily cycle/before evaporation (trSurf
), meaning that
evaporation will deplete the surface water tank, then water consumption
shall take the value of trSurf
.
Else, consoRur
equals evap
.
It is a way to bound the water consumption by the water available in the surface tank.
? the name of this function is not that great. thinking about something
? about evaporation demand vs evaporated water
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
View Source
def estimate_effective_evaporation_from_evaporable_water(j, data):
"""
This function estimates the effective evaporation (`consoRur`, mm) from the
evaporable water (`evap`, mm) limited by the evapotranspirable surface water
(`trSurf`, mm).
If `evap` is higher than the quantity of water in the surface tank at the
beginning of the daily cycle/before evaporation (`trSurf`), meaning that
evaporation will deplete the surface water tank, then water consumption
shall take the value of `trSurf`.
Else, `consoRur` equals `evap`.
It is a way to bound the water consumption by the water available in the
surface tank.
#? the name of this function is not that great. thinking about something
#? about evaporation demand vs evaporated water
Args:
j (_type_): _description_
data (_type_): _description_
"""
data["consoRur"][j:,:,:] = np.where(
data["evap"][j,:,:] > data["trSurf"][j,:,:],
data["trSurf"][j,:,:],
data["evap"][j,:,:],
)
return data
estimate_fesw
def estimate_fesw(
j,
data
)
This function estimates the fraction of evaporable soil water (fesw, mm).
fesw is defined as the ratio of water stock in the surface tank over 110% of the surface tank capacity.
It is adapted from the EvalFESW procedure, from bileau.pas and bhytypeFAO.pas files from the original FORTRAN code.
Depuis thèse Alhassane : "FESW = fraction d'eau évaporable dans le sol (en %), calculée à partir du taux d'humidité du sol (en %) à la capacité de rétention et 1/2 du taux d'humidité du sol au pF 4.2 (Allen et al., 1998)"
in Alhassane thesis, FESW = (Stock CR - 0,5 Stock pF 4,2) x H) so is calculated taking only half of the surface reservoir
? Why is it calculated over 110% of surface_tank_capacity ?
? it seems the 110% thingy comes from the update_surface_tank_stock ? function where it is allowed to fill the surface tank up to 110% of its ? capacity. but this does not make se,se to me.
? in this case, fesw can take values between 0 and 1
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def estimate_fesw(j, data):
"""
This function estimates the fraction of evaporable soil water (fesw, mm).
fesw is defined as the ratio of water stock in the surface tank over 110% of
the surface tank capacity.
It is adapted from the EvalFESW procedure, from bileau.pas and
bhytypeFAO.pas files from the original FORTRAN code.
Depuis thèse Alhassane : "FESW = fraction d'eau évaporable dans le sol (en
%), calculée à partir du taux d'humidité du sol (en %) à la capacité de
rétention et 1/2 du taux d'humidité du sol au pF 4.2 (Allen et al., 1998)"
in Alhassane thesis, FESW = (Stock CR - 0,5 Stock pF 4,2) x H) so is calculated taking only
half of the surface reservoir
? Why is it calculated over 110% of surface_tank_capacity ?
? it seems the 110% thingy comes from the update_surface_tank_stock
? function where it is allowed to fill the surface tank up to 110% of its
? capacity. but this does not make se,se to me.
? in this case, fesw can take values between 0 and 1
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
# data["fesw"][j,:,:] = data["surface_tank_stock"][j,:,:] / (1.1 * data["surface_tank_capacity"])
data["fesw"][j,:,:] = data["surface_tank_stock"][j,:,:] / data["surface_tank_capacity"]
return data
estimate_ftsw
def estimate_ftsw(
j,
data
)
This function estimates the fraction of transpirable soil water (ftsw) from
the root reservoir.
It is based on the EvalFTSW procedure, from the bileau.pas, exmodules 1 & 2.pas, risocas.pas, riz.pas files from the original FORTRAN code.
d'après alhassane thesis, "La fraction d'eau transpirable par la plante ou "fraction of transpirable soil water (FTSW)" a été calculée une fois par semaine durant le cycle de la variété : FTSW = ((Stock - pF4.2)/(RU x profondeur racinaire)) x profondeur racinaire Avec : Stock = stock d'eau du sol dans la zone racinaire (mm), pF4.2 = point de flétrissement et RU = réserve utile (mm/msol). Ces variables sont fonction de la profondeur racinaire, donc de la croissance de la culture"
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def estimate_ftsw(j, data):
"""
This function estimates the fraction of transpirable soil water (ftsw) from
the root reservoir.
It is based on the EvalFTSW procedure, from the bileau.pas, exmodules 1 &
2.pas, risocas.pas, riz.pas files from the original FORTRAN code.
d'après alhassane thesis, "La fraction d'eau transpirable par la plante ou
"fraction of transpirable soil water (FTSW)" a été calculée une fois par
semaine durant le cycle de la variété : FTSW = ((Stock - pF4.2)/(RU x
profondeur racinaire)) x profondeur racinaire Avec : Stock = stock d'eau du
sol dans la zone racinaire (mm), pF4.2 = point de flétrissement et RU =
réserve utile (mm/msol). Ces variables sont fonction de la profondeur
racinaire, donc de la croissance de la culture"
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["ftsw"][j:,:,:] = np.where(
data["root_tank_capacity"][j,:,:] > 0,
data["root_tank_stock"][j,:,:] / data["root_tank_capacity"][j,:,:],
0,
)
return data
estimate_kce
def estimate_kce(
j,
data,
paramITK
)
This function estimates the coefficient of evaporation from the soil (kce).
This approach takes into consideration three factors acting on limitation of kce :
1) ltr : plant cover, 1 = no plant cover, 0 = full plant cover 2) Mulch - permanent covering effect : we consider a value of 1.0 for no covering, and 0.0 is full covering with plastic sheet ; this mulch parameter has been used in previous versions of the model where evolution of mulch biomass was not explicitely taken into consideration, can be used in the case of crops with self-mulching phenomena, where a standard mulch parameter value of 0.7 can be applied. 3) Mulch - evolutive covering effect BiomMc : biomass of mulch
This function has been adapted from EvalKceMC procedure, bileau.pas and exmodules 2.pas from the original FORTRAN code. In its spirit, it looks like it has been adapted from the dual crop coefficient from the FAO56 paper. But this is still to confirm on a point of view of the history of the model.
Depuis thèse Alhassane : "LTR = fraction de radiation non interceptée par le couvert = [exp(-k*LAI)] où k = coefficient d'extinction de la lumière qui est fonction des propriétés géométriques du couvert et LAI = indice de surface foliaire"
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 estimate_kce(j, data, paramITK):
"""
This function estimates the coefficient of evaporation from the soil (kce).
This approach takes into consideration three factors acting on limitation of
kce :
1) ltr : plant cover, 1 = no plant cover, 0 = full plant cover
2) Mulch - permanent covering effect : we consider a value of 1.0 for no
covering, and 0.0 is full covering with plastic sheet ; this mulch parameter
has been used in previous versions of the model where evolution of mulch
biomass was not explicitely taken into consideration, can be used in the
case of crops with self-mulching phenomena, where a standard mulch parameter
value of 0.7 can be applied.
3) Mulch - evolutive covering effect BiomMc : biomass of mulch
This function has been adapted from EvalKceMC procedure, bileau.pas and
exmodules 2.pas from the original FORTRAN code. In its spirit, it looks like
it has been adapted from the dual crop coefficient from the FAO56 paper. But
this is still to confirm on a point of view of the history of the model.
Depuis thèse Alhassane : "LTR = fraction de radiation non interceptée par le
couvert = [exp(-k*LAI)] où k = coefficient d'extinction de la lumière qui
est fonction des propriétés géométriques du couvert et LAI = indice de
surface foliaire"
Args:
j (_type_): _description_
data (_type_): _description_
paramITK (_type_): _description_
Returns:
_type_: _description_
"""
data["kce"][j,:,:] = data["ltr"][j,:,:] * paramITK["mulch"] * \
np.exp(-paramITK["coefMc"] * paramITK["surfMc"] * data["biomMc"][j,:,:]/1000)
return data
estimate_pFact
def estimate_pFact(
j,
data,
paramVariete
)
summary
This function computes the pFactor, which is a bound coefficient used in the computation of cstr from ftsw. This coefficient delimits the portion of the FTSW below which water stress starts to influence the transpiration.
FAO reference for critical FTSW value for transpiration response (0 = stomata respond immediately if FTSW<1; 0.5 for most of the crops)
pFact is bounded in [0.1, 0.8].
For details see https://agritrop.cirad.fr/556855/1/document_556855.pdf
This function is based on the CstrPFactor procedure, from bileau.pas, exmodules 1 & 2.pas, risocas.pas files, from the original FORTRAN code.
d'après alhassane :
"Selon Allen et al., (1998), on peut également appliquer la méthode de calculs développée par la FAO, dite du pFactor, basée sur les notions de réserve d'eau facilement utilisable (RFU) et difficilement utilisable (RDU) définies par un point d'inflexion, si on considère que la dynamique de consommation hydrique de la plante diffère selon la demande climatique (ETo) et la fraction d'eau du sol transpirable (FTSW). En effet, pfactor est un coefficient utilisé pour le calcul du taux de transpiration et qui s'obtient en divisant la réserve d'eau du sol utilisable par les racines par la réserve totale disponible dans la zone racinaire de la plante (Allen et al., 1998). On l'obtient également par la formule suivante :
pfactor = parP + 0,04 x (5 - ETM)
Avec : parP = paramètre spécifique à l'espèce, qui exprime le seuil critique d'humidité du sol à partir duquel le stress hydrique réduit linéairement la transpiration (Doorenbos et Kassam, 1979)."
! this function seems quite arbitrary and would need verifications regarding the underlying assumptions
ok so this function comes from FAO56 paper, https://www.fao.org/3/x0490e/x0490e0e.htm#chapter%208%20%20%20etc%20under%20soil%20water%20stress%20conditions table 22 annex 2
FAO56 uses the RAW/TAW formalism where RAW = p TAW with TAW being the total available water in the root zone TAW = 1000(q FC - q WP) Zr corresponding to the root tank capacity and RAW being calculated as RAW = p TAW p average fraction of Total Available Soil Water (TAW) that can be depleted from the root zone before moisture stress (reduction in ET) occurs
"A value of 0.50 for p is commonly used for many crops" "A numerical approximation for adjusting p for ETc rate is p = pTable 22 + 0.04 (5 - ETc) where the adjusted p is limited to 0.1 £ p £ 0.8 and ETc is in mm/day"
in the legacy code, ETc is computed with np.maximum(data["kcp"][j,:,:], 1) * data["ET0"][j,:,:] however why is kcp bound to 1 ? there seem to be no reason of keeping this bound.
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 estimate_pFact(j, data, paramVariete):
"""_summary_
This function computes the pFactor, which is a bound coefficient used in the
computation of cstr from ftsw. This coefficient delimits the portion of the
FTSW below which water stress starts to influence the transpiration.
FAO reference for critical FTSW value for transpiration response (0 =
stomata respond immediately if FTSW<1; 0.5 for most of the crops)
pFact is bounded in [0.1, 0.8].
For details see https://agritrop.cirad.fr/556855/1/document_556855.pdf
This function is based on the CstrPFactor procedure, from bileau.pas,
exmodules 1 & 2.pas, risocas.pas files, from the original FORTRAN code.
d'après alhassane :
"Selon Allen et al., (1998), on peut également appliquer la méthode de
calculs développée par la FAO, dite du pFactor, basée sur les notions de
réserve d'eau facilement utilisable (RFU) et difficilement utilisable (RDU)
définies par un point d'inflexion, si on considère que la dynamique de
consommation hydrique de la plante diffère selon la demande climatique (ETo)
et la fraction d'eau du sol transpirable (FTSW). En effet, pfactor est un
coefficient utilisé pour le calcul du taux de transpiration et qui s'obtient
en divisant la réserve d'eau du sol utilisable par les racines par la
réserve totale disponible dans la zone racinaire de la plante (Allen et al.,
1998). On l'obtient également par la formule suivante :
pfactor = parP + 0,04 x (5 - ETM)
Avec : parP = paramètre spécifique à
l'espèce, qui exprime le seuil critique d'humidité du sol à partir duquel le
stress hydrique réduit linéairement la transpiration (Doorenbos et Kassam,
1979)."
#! this function seems quite arbitrary and would need verifications regarding the underlying assumptions
ok so this function comes from FAO56 paper, https://www.fao.org/3/x0490e/x0490e0e.htm#chapter%208%20%20%20etc%20under%20soil%20water%20stress%20conditions
table 22 annex 2
FAO56 uses the RAW/TAW formalism where RAW = p TAW
with TAW being the total available water in the root zone TAW = 1000(q FC - q WP) Zr
corresponding to the root tank capacity
and RAW being calculated as RAW = p TAW
p average fraction of Total Available Soil Water (TAW) that can be depleted from the root zone before moisture stress (reduction in ET) occurs
"A value of 0.50 for p is commonly used for many crops"
"A numerical approximation for adjusting p for ETc rate is p = pTable 22 + 0.04 (5 - ETc) where the adjusted p is limited to 0.1 £ p £ 0.8 and ETc is in mm/day"
in the legacy code, ETc is computed with np.maximum(data["kcp"][j,:,:], 1) * data["ET0"][j,:,:]
however why is kcp bound to 1 ?
there seem to be no reason of keeping this bound.
Args:
j (_type_): _description_
data (_type_): _description_
paramVariete (_type_): _description_
Returns:
_type_: _description_
"""
# we keep these lines for legacy reference
# data["pFact"][j:,:,:] = paramVariete["PFactor"] + \
# 0.04 * (5 - np.maximum(data["kcp"][j,:,:], 1) * data["ET0"][j,:,:])
# adjustinf pfactor
data["pFact"][j:,:,:] = paramVariete["PFactor"] + 0.04 * (5 - data["kcp"][j,:,:] * data["ET0"][j,:,:])
# group 54
data["pFact"][j:,:,:] = np.minimum(
np.maximum(
0.1,
data["pFact"][j,:,:],
),
0.8,
)
return data
estimate_plant_transpiration
def estimate_plant_transpiration(
j,
data
)
This function computes the adjusted plant transpiration (tr, mm) from the
plant, by adjusting the potential transpiration (trPot, mm) with cstr.
This function adjusts the potential transpiration (trPot, mm) that was calculated through trPot = kcp * ET0, by adding the stress coefficient cstr (that corresponds to Ks in the FAO56 paper) Thus we obtain an adjusted plant transpiration tr, which corresponds to ETc_adj in the FAO56 (see equation 80).
This function is based on the EvalTranspi procedure, from bileau.pas, bhytypeFAO.pas, exmodules 1 & 2.pas, from the original FORTRAN code.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def estimate_plant_transpiration(j, data):
"""
This function computes the adjusted plant transpiration (tr, mm) from the
plant, by adjusting the potential transpiration (trPot, mm) with cstr.
This function adjusts the potential transpiration (trPot, mm) that was
calculated through trPot = kcp * ET0, by adding the stress coefficient cstr
(that corresponds to Ks in the FAO56 paper) Thus we obtain an adjusted plant
transpiration tr, which corresponds to ETc_adj in the FAO56 (see equation
80).
This function is based on the EvalTranspi procedure, from bileau.pas,
bhytypeFAO.pas, exmodules 1 & 2.pas, from the original FORTRAN code.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["tr"][j:,:,:] = data["trPot"][j,:,:] * data["cstr"][j,:,:]
return data
estimate_potential_plant_transpiration
def estimate_potential_plant_transpiration(
j,
data
)
This function computes the potential transpiration from the plant.
Computation is based on the climate forcing (ET0), as well as the kcp coefficient.
This code is based on the DemandePlante procedure, from the bileau.pas, bhytypeFAO.pas, and exmodules 1 & 2.pas files from the original FORTRAN code.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def estimate_potential_plant_transpiration(j, data):
"""
This function computes the potential transpiration from the plant.
Computation is based on the climate forcing (ET0), as well as the kcp coefficient.
This code is based on the DemandePlante procedure, from the bileau.pas, bhytypeFAO.pas, and
exmodules 1 & 2.pas files from the original FORTRAN code.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
# ggroup 51
data["trPot"][j,:,:] = (data["kcp"][j,:,:] * data["ET0"][j,:,:])
return data
estimate_runoff
def estimate_runoff(
j,
data
)
This function evaluates the water runoff (mm).
If the quantity of rain (mm) is above the runoff_threshold (mm), runoff is computed as the difference between the available water (mm) and the runoff_threshold multiplied by the runoff_rate (%). Else, runoff value is set to 0.
runoff_threshold and runoff_rate are defined in load_iSDA_soil_data
Question : should runoff be computed taking in consideration water captured by mulch to account for mulch effect on runoff mitigation ?
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def estimate_runoff(j, data):
"""
This function evaluates the water runoff (mm).
If the quantity of rain (mm) is above the runoff_threshold (mm), runoff is
computed as the difference between the available water (mm) and the
runoff_threshold multiplied by the runoff_rate (%). Else, runoff value is
set to 0.
runoff_threshold and runoff_rate are defined in load_iSDA_soil_data
Question : should runoff be computed taking in consideration water captured by
mulch to account for mulch effect on runoff mitigation ?
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["runoff"][j,:,:] = xr.where(
data["rain"][j,:,:] > data["runoff_threshold"],
(data["available_water"][j,:,:] - data["runoff_threshold"]) * data["runoff_rate"],
0,
)
return data
estimate_soil_evaporation
def estimate_soil_evaporation(
j,
data
)
This function computes estimation of soil evaporable water (evap
, mm). It uses
the potential soil evaporation (evapPot
, mm) and the fraction of evaporable soil
water (fesw
), bounded by the surface_tank_stock
(mm).
We remind fesw
is defined as the ratio of water stock in the surface tank
over 110% of the surface tank capacity, meaning it will be equal to 1 when
the surface tank is full, and 0 when the surface tank is empty.
This approach is somewhat comparable to the soil evaporation reduction coefficient kr approach presented in FAO56 paper, to the exception the soil evaporation reduction coefficient kr is built using two linear functions where the squared fesw approach uses a square function. Furthermore, the kr approach function is build using REW and TEW values that are specific to the type of soil, whereas the squared fesw approach uses a generic function that is not soil specific.
in Alhassane thesis, evap is called EVj for "evaporation journanière", and is calculated as Evj = EvapPot x FESW. More details are available in the PhD dissertation of Alhassane https://hdl.handle.net/20.500.12177/1576
The estimate_effective_evaporation_from_evaporable_water
function computed
later in the daily cycle uses the evap
value to determine the effective
evaporation (consoRur
, mm) on the quantity of water in the surface tank
(surface_tank_stock
, mm).
It has been adapted from the EvapRuSurf procedure, from bileau.pas and exmodules 1 & 2.pas file from the original FORTRAN code.
? evaporation is bounded by the surface tank stock, which means it is meant ? to happen only in the depth described by the surface_tank
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def estimate_soil_evaporation(j, data):
"""
This function computes estimation of soil evaporable water (`evap`, mm). It uses
the potential soil evaporation (`evapPot`, mm) and the fraction of evaporable soil
water (`fesw`), bounded by the `surface_tank_stock` (mm).
We remind `fesw` is defined as the ratio of water stock in the surface tank
over 110% of the surface tank capacity, meaning it will be equal to 1 when
the surface tank is full, and 0 when the surface tank is empty.
This approach is somewhat comparable to the soil evaporation reduction
coefficient kr approach presented in FAO56 paper, to the exception the soil
evaporation reduction coefficient kr is built using two linear functions
where the squared fesw approach uses a square function. Furthermore, the kr
approach function is build using REW and TEW values that are specific to the
type of soil, whereas the squared fesw approach uses a generic function that
is not soil specific.
in Alhassane thesis, evap is called EVj for "evaporation journanière", and
is calculated as Evj = EvapPot x FESW. More details are available in the PhD
dissertation of Alhassane https://hdl.handle.net/20.500.12177/1576
The `estimate_effective_evaporation_from_evaporable_water` function computed
later in the daily cycle uses the `evap` value to determine the effective
evaporation (`consoRur`, mm) on the quantity of water in the surface tank
(`surface_tank_stock`, mm).
It has been adapted from the EvapRuSurf procedure, from bileau.pas and
exmodules 1 & 2.pas file from the original FORTRAN code.
? evaporation is bounded by the surface tank stock, which means it is meant
? to happen only in the depth described by the surface_tank
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["evap"][j:,:,:] = np.minimum(
data["evapPot"][j,:,:] * data["fesw"][j,:,:]**2,
data["surface_tank_stock"][j,:,:]
)
return data
estimate_soil_potential_evaporation
def estimate_soil_potential_evaporation(
j,
data
)
This function computes estimation of potential soil evaporation (mm,
evapPot).
It performs its computations solely from the evaporation forcing driven by climatic demand, limited by the coefficient of evaporation from the soil (kce).
Note : difference in humectation of the top and bottom tanks is not taken into consideration in this approach. The
This function has been adapted from DemandeSol procedure, from bileau.pas and exmodules 1 & 2.pas file from the original FORTRAN code.
in Alhassane thesis, EvapPot = Kmulch x ETo x LTR ; here kce = kmulch x LTR so this formalism is respected
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def estimate_soil_potential_evaporation(j, data):
"""
This function computes estimation of potential soil evaporation (mm,
evapPot).
It performs its computations solely from the evaporation forcing driven by
climatic demand, limited by the coefficient of evaporation from the soil
(kce).
Note : difference in humectation of the top and bottom tanks is not taken
into consideration in this approach. The
This function has been adapted from DemandeSol procedure, from bileau.pas
and exmodules 1 & 2.pas file from the original FORTRAN code.
in Alhassane thesis, EvapPot = Kmulch x ETo x LTR ; here kce = kmulch x LTR so this formalism is respected
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["evapPot"][j,:,:] = data["ET0"][j,:,:] * data["kce"][j,:,:]
return data
estimate_transpirable_water
def estimate_transpirable_water(
j,
data
)
This function estimates the daily height of water available from the surface
reservoir for transpiration by the plant ('eauTranspi', mm).
If the filling rate of the surface tank ('surface_tank_stock' / 'surface_tank_capacity') for the previous day is under 10%, we set the quantity of transpirable water as the water available for the day ('eauDispo') minus the water height necessary to keep the filling rate of the surface tank at 10%.
Said otherwise, a part of the water available for the day ('eauDispo') is used to maintain the surface reservoir at a minimum level of 10% of its capacity, as this water is considered as bound to the surface reservoir and cannot be transpired.
Of course, if the filling rate of the previous day is above 10%, the transpirable water is equal to the water available for the day.
Furthermore, transpirable water cannot be negative.
? Remark : if the use of j-1 indices is troublesome, it should be feasible to ? run this function just before update_surface_tank_stock.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def estimate_transpirable_water(j, data):
"""
This function estimates the daily height of water available from the surface
reservoir for transpiration by the plant ('eauTranspi', mm).
If the filling rate of the surface tank ('surface_tank_stock' /
'surface_tank_capacity') for the previous day is under 10%, we set the
quantity of transpirable water as the water available for the day
('eauDispo') minus the water height necessary to keep the filling rate of
the surface tank at 10%.
Said otherwise, a part of the water available for the day ('eauDispo') is used
to maintain the surface reservoir at a minimum level of 10% of its capacity,
as this water is considered as bound to the surface reservoir and cannot be
transpired.
Of course, if the filling rate of the previous day is above 10%, the transpirable
water is equal to the water available for the day.
Furthermore, transpirable water cannot be negative.
? Remark : if the use of j-1 indices is troublesome, it should be feasible to
? run this function just before update_surface_tank_stock.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
# data["eauTranspi"][j,:,:] = np.where(
# data["surface_tank_stock"][j-1,:,:] < 0.1 * data["surface_tank_capacity"],
# np.maximum(
# 0,
# data["available_water"][j,:,:] - (0.1 * data["surface_tank_capacity"] - data["surface_tank_stock"][j-1,:,:])
# ),
# data["available_water"][j,:,:],
# )
#! simplification : we are already working with water height between permanent wilting point and field capacity,
#! so there is no need to consider bound water as this is already taken into consideration in the calculation of RU
#! if we want to take this further correctly we have to rewrite everything, so better keep it simple for now
data["eauTranspi"][j,:,:] = data["available_water"][j,:,:]
return data
fill_mulch
def fill_mulch(
j,
data,
paramITK
)
This wrapper function computes the filling of the mulch for a given day.
It has been translated from the procedure PluieIrrig, of the original Pascal codes bileau.pas and exmodules2.pas
For more details, it is advised to refer to the works of Eric Scopel (UR AIDA), and the PhD dissertation of Fernando Maceina.
View Source
def fill_mulch(j, data, paramITK):
"""
This wrapper function computes the filling of the mulch for a given day.
It has been translated from the procedure PluieIrrig, of the original Pascal codes
bileau.pas and exmodules2.pas
For more details, it is advised to refer to the works of Eric Scopel (UR
AIDA), and the PhD dissertation of Fernando Maceina.
"""
data = compute_water_captured_by_mulch(j, data, paramITK)
data = update_available_water_after_mulch_filling(j, data)
data = update_mulch_water_stock(j, data)
return data
fill_tanks
def fill_tanks(
j,
data
)
Translated from the procedure rempliRes, of the original Pascal codes
bileau.pas
Main hypotheses : - the water dynamics is represented by a filling from the top and an evolution of the reservoirs sizes when the filling is above the maximum quantity of the current size (humectation front). - when the maximum size is reached by filling, it is considered as drainage. - inside a reservoir, water is distributed homogeneously (may be considered valid up to 2m depth, according to CB, from other sources).
3 reservoirs are represented: 1) a global reservoir, evolving in depth according to the humectation front 2) a surface reservoir (fixed size) where evaporation and a part of the transpiration occurs when roots are present 3) a root reservoir, evolving according to the root front (when roots are present)
REMARK : these reservoirs overlap, and instead of managing depths, we manage water stocks
Notes from CB, 10/06/2015 : prise en compte de stock d'eau résilient pour les simulation continues Hypothèse de la MAJ des stock en fn de l'eau r�siliente de l'ann�e pr�c�dente dans le cas des simulations pluri annuelle en continue (NbAn = 1): A la r�colte on recup�re les stock d'eau (StRuPrec), la prof d'Humectation (Humprec) et la prof d'enracinement (stRurMaxprec). Pour le reservoir de surface on ne change rien. On MAJ le stRu avec le stock de surface stRuSurf, Hum avec le max de remplissage de surface (RuSurf) Si le StRu avec l'apport d'eau devinet sup au Hum alors on tient compte dans cette augmentation du stock r�silient avec deux cas possible : Si StRu est < � stRurMaxprec alors on ajoute l'eau r�siliente contenue dans l'ancienne zone racinaire en fn de la diff�rence de stock Sinon on a de l'eau r�siliente au maximum de la CC jusqu'� l'ancienne HumPrec, on rempli alors StRu de la diff�rence etre ces deux valeurs puis on fait la MAJ des Dr, StRur, Hum etc...
! simplification : we want to simplify the code, and we don't want to keep the possibility of multiple crop cycles ! we keep the old code for maintainance and future developments though
View Source
def fill_tanks(j, data):
"""
Translated from the procedure rempliRes, of the original Pascal codes
bileau.pas
Main hypotheses :
- the water dynamics is represented by a filling from the top and an evolution
of the reservoirs sizes when the filling is above the maximum quantity of the
current size (humectation front).
- when the maximum size is reached by filling, it is considered as drainage.
- inside a reservoir, water is distributed homogeneously (may be considered
valid up to 2m depth, according to CB, from other sources).
3 reservoirs are represented:
1) a global reservoir, evolving in depth according to the humectation front
2) a surface reservoir (fixed size) where evaporation and a part of the
transpiration occurs when roots are present
3) a root reservoir, evolving according to the root front (when roots are
present)
REMARK : these reservoirs overlap, and instead of managing depths, we manage water stocks
Notes from CB, 10/06/2015 :
prise en compte de stock d'eau résilient pour les simulation continues
Hypothèse de la MAJ des stock en fn de l'eau r�siliente de l'ann�e pr�c�dente
dans le cas des simulations pluri annuelle en continue (NbAn = 1):
A la r�colte on recup�re les stock d'eau (StRuPrec), la prof d'Humectation (Humprec)
et la prof d'enracinement (stRurMaxprec). Pour le reservoir de surface on ne change rien.
On MAJ le stRu avec le stock de surface stRuSurf, Hum avec le max de remplissage de surface (RuSurf)
Si le StRu avec l'apport d'eau devinet sup au Hum
alors on tient compte dans cette augmentation du stock r�silient avec deux cas possible :
Si StRu est < � stRurMaxprec
alors on ajoute l'eau r�siliente contenue dans l'ancienne zone racinaire en fn
de la diff�rence de stock
Sinon on a de l'eau r�siliente au maximum de la CC jusqu'� l'ancienne HumPrec,
on rempli alors StRu de la diff�rence etre ces deux valeurs puis on fait la MAJ
des Dr, StRur, Hum etc...
! simplification : we want to simplify the code, and we don't want to keep the possibility of multiple crop cycles
! we keep the old code for maintainance and future developments though
"""
#! simplification
#// section 1 : updating the end_of_season memory variables
#// in order to save resources, we test if there is at least one pixel at phase 7
#// and one pixel at changePhase 1 in the current time step before applying the "end_of_season" functions
#// if (np.any(data["numPhase"][j,:,:] == 7)) & (np.any(data["changePhase"][j,:,:] == 1)):
#// data = update_previous_humectation_front_at_end_of_season(j, data)
#// data = update_humectation_front_at_end_of_season(j, data)
#// data = update_root_tank_capacity_at_end_of_season(j, data)
#// data = update_previous_root_tank_stock_at_end_of_season(j, data)
#// data = update_previous_total_tank_stock_at_end_of_season(j, data)
#! simplification
#// we let this function here, conditioned to work for j0 only, but it should be moved into initialization
data = reset_total_tank_capacity(j, data)
# Updates the value of water stock in the surface tank (surface_tank_stock,
# mm) with the water available for the day (available_water, mm), within the
# limits of 110% surface_tank_capacity.
data = update_surface_tank_stock(j, data)
# Estimates the daily height of water available from the surface reservoir
# for transpiration by the plant ('eauTranspi', mm). A part of the water
# available for the day ('eauDispo') is used to maintain the surface
# reservoir at a minimum level of 10% of its capacity, as this water is
# considered as bound to the surface reservoir and cannot be transpired.
data = estimate_transpirable_water(j, data)
# Updates the total height of transpirable water ('total_tank_stock', mm)
# with the amount of transpirable water for the day ('eauTranspi', mm)
data = update_total_tank_stock(j, data)
#! simplification
#// delta_total_tank_stock is used with second cycle computations
#// estimating positive delta between total_root_tank and stRuPrec
#// data = update_delta_total_tank_stock(j, data)
#! simplification
#// # first we update total_tank_stock that can 1) take delta_total_tank_stock or 2) be unchanged
#// data = update_total_tank_stock_for_second_crop_cycle(j, data)# verif ok
#// # # then previous_total_tank_stock can 1) take 0 or 2) be unchanged
#// data = update_previous_total_tank_stock_for_second_crop_cycle(j, data)
#// # # delta_total_tank_stock can 1) be incremented of previous_total_tank_stock or 2) be unchanged
#//data = update_delta_total_tank_stock_step_2(j, data)
#// # # here, in case 1, In this function, if the variation of transpirable water
#// # (delta_total_tank_stock) increases above the depth of humectation front
#// # (hum), if the depth of humectation front (hum) is above the
#// # previous_root_tank_capacity (condition 1 passed, and 2 failed,
#// # which should be the case for most of the simulations that will be
#// # single-season), and if the depth of humectation front (hum) has decreased
#// # since the previous day (condition 3 passed), then total_tank_stock takes the value of
#// # delta_total_tank_stock, previous_total_tank_stock equals 0, and
#// # delta_total_tank_stock is incremented by previous_total_tank_stock.
#// #
#// # in case 2, nothing happens.
# Updates the water height to humectation front (humectation_front, mm) by
# bounding it between delta_total_tank_stock and total_tank_capacity, so
# that humectation front cannot go down indefinitely.
data = apply_humectation_front_boundaries(j, data)
# computes drainage
data = compute_drainage(j, data)
# Updates the height of water in the tank of water accessible to roots
# ("root_tank_stock", mm), so that the transpirable water added to the root
# tank stock for the day cannot be higher than both the root tank capacity,
# and the total tank stock.
data = update_root_tank_stock_step_2(j, data)
return data
initialize_delta_root_tank_capacity
def initialize_delta_root_tank_capacity(
j,
data
)
This function initializes the daily variation in root tank capacity.
This variable represents daily variation in water height accessible to roots, in mm.
For each pixel at a developmental stage different from zero, and that is not at initialization phase ('changePhase = 1' and 'numPhase = 1'), the daily variation in root tank capacity (delta_root_tank_capacity, mm) is updated.
The updated value depends on the daily root growth speed (itself depending on the current development phase of the plant), the drought stress coefficient ('cstr'), and the soil water storage capacity ('ru', mm/m).
https://docs.google.com/presentation/d/1QHhbNjF9ysCG_yZzWXb7ns0vOFlGfNhYw8AYrotm0fY/edit#slide=id.g27a6b3e8a72_0_118
However, when 'root_tank_capacity' is above 'surface_tank_capacity' (meaning that the roots are prospecting water deeper than the surface tank), the daily root capacity variation is calculated as the product of soil water storage capacity ('ru'), the daily root growth speed ('vRac'), and a coefficient made from 'cstr' shifted by 0.3, capped at 1.0.
https://docs.google.com/presentation/d/1QHhbNjF9ysCG_yZzWXb7ns0vOFlGfNhYw8AYrotm0fY/edit#slide=id.g27a6b3e8a72_0_71
That is to say, when roots are going deep, the root growth speed is modulated by drought stress.
The drought stress coefficient 'cstr' measures the level of drought stress with 0 being full stress. The root growth speed is assumed to remain non-null during a drought stress as a matter of survival, with a certain level of tolerance given by the [0.3, 1] bound of the coefficient. Using the [0.3, 1] bound is a way to tell that in the [0.7, 1] 'cstr' interval, there is no effect of drought stress on the root growth speed, allowing for a certain level of tolerance of the plant.
When 'root_tank_capacity' is lower than 'surface_tank_capacity', the root growth speed is not modulated by drought stress.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | int | The current iteration step of the process. | None |
data | xarray.Dataset | The input data containing relevant information. | None |
Returns:
Type | Description |
---|---|
xarray.Dataset | The updated input data with the daily root capacity variation calculated and stored. |
View Source
def initialize_delta_root_tank_capacity(j, data):
"""
This function initializes the daily variation in root tank capacity.
This variable represents daily variation in water height accessible to
roots, in mm.
For each pixel at a developmental stage different from zero, and that is not
at initialization phase ('changePhase = 1' and 'numPhase = 1'), the daily
variation in root tank capacity (delta_root_tank_capacity, mm) is updated.
The updated value depends on the daily root growth speed (itself depending
on the current development phase of the plant), the drought stress
coefficient ('cstr'), and the soil water storage capacity ('ru', mm/m).
https://docs.google.com/presentation/d/1QHhbNjF9ysCG_yZzWXb7ns0vOFlGfNhYw8AYrotm0fY/edit#slide=id.g27a6b3e8a72_0_118
However, when 'root_tank_capacity' is above 'surface_tank_capacity'
(meaning that the roots are prospecting water deeper than the surface tank),
the daily root capacity variation is calculated as the product of soil water
storage capacity ('ru'), the daily root growth speed ('vRac'), and a
coefficient made from 'cstr' shifted by 0.3, capped at 1.0.
https://docs.google.com/presentation/d/1QHhbNjF9ysCG_yZzWXb7ns0vOFlGfNhYw8AYrotm0fY/edit#slide=id.g27a6b3e8a72_0_71
That is to say, when roots are going deep, the root growth speed is
modulated by drought stress.
The drought stress coefficient 'cstr' measures the level of drought stress
with 0 being full stress. The root growth speed is assumed to remain
non-null during a drought stress as a matter of survival, with a certain
level of tolerance given by the [0.3, 1] bound of the coefficient. Using the
[0.3, 1] bound is a way to tell that in the [0.7, 1] 'cstr' interval, there
is no effect of drought stress on the root growth speed, allowing for a
certain level of tolerance of the plant.
When 'root_tank_capacity' is lower than 'surface_tank_capacity', the root growth
speed is not modulated by drought stress.
Args:
j (int): The current iteration step of the process.
data (xarray.Dataset): The input data containing relevant information.
Returns:
xarray.Dataset: The updated input data with the daily root capacity variation calculated and stored.
"""
condition = (data["numPhase"][j,:,:] > 0) & \
np.invert((data["numPhase"][j,:,:] == 1) & (data["changePhase"][j,:,:] == 1))
data["delta_root_tank_capacity"][j,:,:] = xr.where(
condition,
xr.where(
(data["root_tank_capacity"][j,:,:] > data["surface_tank_capacity"]),
(data["vRac"][j,:,:] * np.minimum(data["cstr"][j,:,:] + 0.3, 1.0)) / 1000 * data["ru"],
data["vRac"][j,:,:] / 1000 * data["ru"],
),
data["delta_root_tank_capacity"][j,:,:],
)
return data
initialize_root_tank_capacity
def initialize_root_tank_capacity(
j,
data,
paramITK
)
This function initializes the root tank.
If during the considered day j there are pixels in phase 1 (initialisation), we test for pixels at phase change between phases 0 and 1 ('changePhase = 1' and 'numPhase = 1').
On these pixels, the maximum root tank water storage ('root_tank_capacity', mm) is initialised by multiplying the initial root depth ('profRacIni', mm) with the soil water storage capacity ('ru', mm/m). This value is broadcasted on the time series. For every other day in the cycle where there are pixels at , the value remains unchanged.
https://docs.google.com/presentation/d/1QHhbNjF9ysCG_yZzWXb7ns0vOFlGfNhYw8AYrotm0fY/edit#slide=id.g27a6b3e8a72_0_30
Args: j (int): day identifier data (xarray dataset): an xarray dataset of dimensions (day, width, height) containing the variables 'numPhase', 'root_tank_capacity', 'changePhase', 'ru' paramITK (dict): a dictionary containing the ITK parameter 'profRacIni'
Returns: type: description
View Source
def initialize_root_tank_capacity(j, data, paramITK):
"""
This function initializes the root tank.
If during the considered day j there are pixels in phase 1 (initialisation),
we test for pixels at phase change between phases 0 and 1 ('changePhase = 1'
and 'numPhase = 1').
On these pixels, the maximum root tank water storage ('root_tank_capacity',
mm) is initialised by multiplying the initial root depth ('profRacIni', mm)
with the soil water storage capacity ('ru', mm/m). This value is broadcasted
on the time series. For every other day in the cycle where there are pixels
at , the value remains unchanged.
https://docs.google.com/presentation/d/1QHhbNjF9ysCG_yZzWXb7ns0vOFlGfNhYw8AYrotm0fY/edit#slide=id.g27a6b3e8a72_0_30
Args:
j (int): day identifier
data (xarray dataset): an xarray dataset of dimensions (day, width,
height) containing the variables 'numPhase', 'root_tank_capacity',
'changePhase', 'ru'
paramITK (dict): a dictionary containing the ITK parameter 'profRacIni'
Returns:
_type_: _description_
"""
if np.any(data["numPhase"][j,:,:] == 1) :
data["root_tank_capacity"][j:,:,:] = xr.where(
(data["changePhase"][j,:,:] == 1) & (data["numPhase"][j,:,:] == 1),
paramITK["profRacIni"] / 1000 * data["ru"],
data["root_tank_capacity"][j,:,:],
)
return data
reset_total_tank_capacity
def reset_total_tank_capacity(
j,
data
)
This function resets the value total_tank_capacity at each loop.
? Why redfining stRuMax at each loop ? Neither ru, profRu ? nor total_tank_capacity are modified during the simulation. ? At the same time, its value is initialized at 0, and this function ? is the only place where it is initialized taking ru and profRu into account.
? We modify it so it runs only at day one. ? But it should be moved to the initialization part of the code.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def reset_total_tank_capacity(j, data):
"""
This function resets the value total_tank_capacity at each loop.
? Why redfining stRuMax at each loop ? Neither ru, profRu
? nor total_tank_capacity are modified during the simulation.
? At the same time, its value is initialized at 0, and this function
? is the only place where it is initialized taking ru and profRu into account.
? We modify it so it runs only at day one.
? But it should be moved to the initialization part of the code.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
if j == 0:
data["total_tank_capacity"][j:,:,:] = (data["ru"] * data["profRu"] / 1000)
return data
set_evapotranspirable_surface_water
def set_evapotranspirable_surface_water(
j,
data
)
This function stores the initial value of surface_tank_stock
in a new
variable representing the quantity of evapotranspirable surface water (trSurf
,
mm), as the value surface_tank_stock
will later be updated.
The original function (based on the ConsoResSep procedure, from bileau.pas, exmodules 1 & 2.pas files, from the original FORTRAN code) removed 1/10th of surface tank capacity as water was condidered as bound to the soil.
However, as we are working with the water volumes in between permanent wilting point and field capacity - thus already considering water bound to the soil, we will remove all of these arbitrary 10% corrections.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def set_evapotranspirable_surface_water(j, data):
"""
This function stores the initial value of `surface_tank_stock` in a new
variable representing the quantity of evapotranspirable surface water (`trSurf`,
mm), as the value `surface_tank_stock` will later be updated.
The original function (based on the ConsoResSep procedure, from bileau.pas,
exmodules 1 & 2.pas files, from the original FORTRAN code) removed 1/10th of
surface tank capacity as water was condidered as bound to the soil.
However, as we are working with the water volumes in between permanent
wilting point and field capacity - thus already considering water bound to
the soil, we will remove all of these arbitrary 10% corrections.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["trSurf"][j:,:,:] = np.maximum(
0,
#// data["surface_tank_stock"][j,:,:] - data["surface_tank_capacity"] * 0.1,
data["surface_tank_stock"][j,:,:],
)
return data
subtract_effective_evaporation_from_root_tank_stock
def subtract_effective_evaporation_from_root_tank_stock(
j,
data
)
This function updates the quantity of water in the root tank
(root_tank_stock
, mm) according to the effective evaporation, taking into
consideration potential shallow rooting ( as consoRur
was calculated
through both estimate_effective_evaporation_from_evaporable_water
and
update_effective_evaporation_for_shallow_roots
functions).
root_tank_stock
value cannot be negative, so it is bound by 0.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def subtract_effective_evaporation_from_root_tank_stock(j, data):
"""
This function updates the quantity of water in the root tank
(`root_tank_stock`, mm) according to the effective evaporation, taking into
consideration potential shallow rooting ( as `consoRur` was calculated
through both `estimate_effective_evaporation_from_evaporable_water` and
`update_effective_evaporation_for_shallow_roots` functions).
`root_tank_stock` value cannot be negative, so it is bound by 0.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["root_tank_stock"][j:,:,:] = np.maximum(0, data["root_tank_stock"][j,:,:] - data["consoRur"][j,:,:])
return data
subtract_effective_evaporation_from_total_tank_stock
def subtract_effective_evaporation_from_total_tank_stock(
j,
data
)
This function updates the total quantity of water (total_tank_stock
, mm)
by subtracting the effective evaporation (consoRur
, mm).
total_tank_stock
value cannot be negative, so it is bound by 0.
? though it seems impossible to have a negative value of total_tank_stock
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def subtract_effective_evaporation_from_total_tank_stock(j, data):
"""
This function updates the total quantity of water (`total_tank_stock`, mm)
by subtracting the effective evaporation (`consoRur`, mm).
`total_tank_stock` value cannot be negative, so it is bound by 0.
#? though it seems impossible to have a negative value of `total_tank_stock`
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["total_tank_stock"][j:,:,:] = np.maximum(0, data["total_tank_stock"][j,:,:] - data["consoRur"][j,:,:])
return data
subtract_evap_from_surface_tank_stock
def subtract_evap_from_surface_tank_stock(
j,
data
)
This function updates the water stock of the surface tank
(surface_tank_stock
, mm) by subtracting the daily amount of evaporation
(evap
, mm) from it.
evap
is calculated in the estimate_soil_evaporation
function earlier in
the daily loop from the potential soil evaporation (evapPot
, mm) and the
fraction of evaporable soil water (fesw
), and cannot exceed the value of
surface_tank_stock
. This approach is somewhat comparable to the soil
evaporation reduction coefficient kr approach presented in FAO56 paper. See
documentation of the estimate_soil_evaporation
function for further
details.
? As evap
cannot exceed the value of surface_tank_stock
according to the
? estimate_soil_evaporation
function and is not modified until this function
? is applied, it is funny to see that the
? subtract_evap_from_surface_tank_stock
function enforces
? surface_tank_stock
not to take a negative value. Hence, we could probably
? simplify this function.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def subtract_evap_from_surface_tank_stock(j, data):
"""
This function updates the water stock of the surface tank
(`surface_tank_stock`, mm) by subtracting the daily amount of evaporation
(`evap`, mm) from it.
`evap` is calculated in the `estimate_soil_evaporation` function earlier in
the daily loop from the potential soil evaporation (`evapPot`, mm) and the
fraction of evaporable soil water (`fesw`), and cannot exceed the value of
`surface_tank_stock`. This approach is somewhat comparable to the soil
evaporation reduction coefficient kr approach presented in FAO56 paper. See
documentation of the `estimate_soil_evaporation` function for further
details.
#? As `evap` cannot exceed the value of `surface_tank_stock` according to the
#? `estimate_soil_evaporation` function and is not modified until this function
#? is applied, it is funny to see that the
#? `subtract_evap_from_surface_tank_stock` function enforces
#? `surface_tank_stock` not to take a negative value. Hence, we could probably
#? simplify this function.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["surface_tank_stock"][j:,:,:] = np.maximum(0, data["surface_tank_stock"][j,:,:] - data["evap"][j,:,:])
return data
subtract_transpiration_from_root_tank_stock
def subtract_transpiration_from_root_tank_stock(
j,
data
)
This function updates the quantity of water in the root tank
(root_tank_stock
, mm) by subtracting the plant transpiration (tr
, mm)
from it.
root_tank_stock
value cannot be negative, so it is bound by 0.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def subtract_transpiration_from_root_tank_stock(j, data):
"""
This function updates the quantity of water in the root tank
(`root_tank_stock`, mm) by subtracting the plant transpiration (`tr`, mm)
from it.
`root_tank_stock` value cannot be negative, so it is bound by 0.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["root_tank_stock"][j:,:,:] = np.maximum(0, data["root_tank_stock"][j,:,:] - data["tr"][j,:,:])
return data
subtract_transpiration_from_surface_tank_stock_according_to_root_tank_stock
def subtract_transpiration_from_surface_tank_stock_according_to_root_tank_stock(
j,
data
)
This function updates the surface tank stock to reflect plant transpiration,
in accordance with the repartition of water between surface and root tanks.
If root_tank_stock
is above 0, then surface_tank_stock
is updated by
subtracting the plant transpiration modulated by the ratio between the
transpirable water trSurf
(representing the amount of water in surface
tank at the beginning of the day) and the root tank stock.
This ratio is bounded by 1, meaning that if there is a higher quantity of
transpirable water that water accessible to roots, then the ratio is set to
1, and surface_tank_stock
will be updated by being subtracted by tr
.
On the contrary, if transpirable water trSurf
is much lower than the root
tank stock, meaning that there is a lot of water accessible to roots but
this water is in the deep tank, then the ratio will be close to 0, and the
surface_tank_stock
will be updated by being subtracted by a very low value
of tr
.
? The rules and underlying assumptions for these transfers seem somewhat
? arbitrary, so we want to be cautious about them.
? Also, it is not clear why we use trSurf instead of surface_tank_stock
? in the calculations.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def subtract_transpiration_from_surface_tank_stock_according_to_root_tank_stock(j, data):
"""
This function updates the surface tank stock to reflect plant transpiration,
in accordance with the repartition of water between surface and root tanks.
If `root_tank_stock` is above 0, then `surface_tank_stock` is updated by
subtracting the plant transpiration modulated by the ratio between the
transpirable water `trSurf` (representing the amount of water in surface
tank at the beginning of the day) and the root tank stock.
This ratio is bounded by 1, meaning that if there is a higher quantity of
transpirable water that water accessible to roots, then the ratio is set to
1, and `surface_tank_stock` will be updated by being subtracted by `tr`.
On the contrary, if transpirable water `trSurf` is much lower than the root
tank stock, meaning that there is a lot of water accessible to roots but
this water is in the deep tank, then the ratio will be close to 0, and the
`surface_tank_stock` will be updated by being subtracted by a very low value
of `tr`.
#? The rules and underlying assumptions for these transfers seem somewhat
#? arbitrary, so we want to be cautious about them.
#? Also, it is not clear why we use trSurf instead of surface_tank_stock
#? in the calculations.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["surface_tank_stock"][j:,:,:] = np.where(
data["root_tank_stock"][j,:,:] > 0,
np.maximum(
data["surface_tank_stock"][j,:,:] - (data["tr"][j,:,:] * np.minimum(
data["trSurf"][j,:,:] / data["root_tank_stock"][j,:,:],
1,
)),
0,
),
data["surface_tank_stock"][j,:,:],
)
return data
subtract_transpiration_from_total_tank_stock
def subtract_transpiration_from_total_tank_stock(
j,
data
)
This function updates the total quantity of water (total_tank_stock
, mm)
by subtracting the plant transpiration (tr
, mm) from it.
total_tank_stock
value cannot be negative, so it is bound by 0.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def subtract_transpiration_from_total_tank_stock(j, data):
"""
This function updates the total quantity of water (`total_tank_stock`, mm)
by subtracting the plant transpiration (`tr`, mm) from it.
`total_tank_stock` value cannot be negative, so it is bound by 0.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["total_tank_stock"][j:,:,:] = np.maximum(0, data["total_tank_stock"][j,:,:] - data["tr"][j,:,:])
return data
update_available_water_after_mulch_filling
def update_available_water_after_mulch_filling(
j,
data
)
This function updates available water after mulch filling.
As some water is captured by the mulch (rain or irrigation water falling on it), the available_water is updated by subtracting the captured water (water_captured_by_mulch, mm) from the total available water (available_water, mm), to represent the remaining available water after capture by the mulch. This value is bounded by 0, as the available water cannot be negative.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_available_water_after_mulch_filling(j, data):
"""
This function updates available water after mulch filling.
As some water is captured by the mulch (rain or irrigation water falling on
it), the available_water is updated by subtracting the captured water
(water_captured_by_mulch, mm) from the total available water
(available_water, mm), to represent the remaining available water after
capture by the mulch. This value is bounded by 0, as the available water
cannot be negative.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["available_water"][j:,:,:] = np.maximum(data["available_water"][j,:,:] - data["water_captured_by_mulch"][j,:,:], 0)
return data
update_available_water_after_runoff
def update_available_water_after_runoff(
j,
data
)
Updating available water (eauDispo, mm) :
The available water is updated by subtracting the runoff (lr, mm) from the total available water (eauDispo, mm). This value is broadcasted onto the days axis.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
View Source
def update_available_water_after_runoff(j, data):
"""
Updating available water (eauDispo, mm) :
The available water is updated by subtracting the runoff (lr, mm) from the
total available water (eauDispo, mm). This value is broadcasted onto the
days axis.
Args:
j (_type_): _description_
data (_type_): _description_
"""
data["available_water"][j:,:,:] = (data["available_water"][j,:,:] - data["runoff"][j,:,:])
return data
update_delta_root_tank_capacity
def update_delta_root_tank_capacity(
j,
data
)
This function updates the daily variation in root tank capacity
(delta_root_tank_capacity, mm) depending on the water height to humectation front (hum, mm) and the root tank capacity (root_tank_capacity, mm).
For each pixel at a developmental stage different from zero, and that is not at initialization phase ('changePhase = 1' and 'numPhase = 1'), when the difference between the water height to humectation front (hum, mm) and the root_tank_capacity is less than the delta_root_tank_capacity (meaning that the daily variation in root tank capacity is higher that the height of water necessary to reach the height of water of the humectation front), delta_root_tank_capacity is updated to be equal to the difference between the water height to humectation front and the root_tank_capacity.
In other words, the change in root tank capacity delta_root_tank_capacity is limited by the water height to humectation front. Which can be interpreted as : the roots cannot grow deeper than the humectation front.
? ...which means the humectation from has to be updated somewhere ?
For any other day or if root_tank_capacity is above delta_root_tank_capacity, delta_root_tank_capacity value is unchanged.
https://docs.google.com/presentation/d/1QHhbNjF9ysCG_yZzWXb7ns0vOFlGfNhYw8AYrotm0fY/edit#slide=id.g27a6b3e8a72_0_161
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
View Source
def update_delta_root_tank_capacity(j, data):
"""
This function updates the daily variation in root tank capacity
(delta_root_tank_capacity, mm) depending on the water height to humectation
front (hum, mm) and the root tank capacity (root_tank_capacity, mm).
For each pixel at a developmental stage different from zero, and that is not
at initialization phase ('changePhase = 1' and 'numPhase = 1'), when the
difference between the water height to humectation front (hum, mm) and the
root_tank_capacity is less than the delta_root_tank_capacity (meaning that
the daily variation in root tank capacity is higher that the height of water
necessary to reach the height of water of the humectation front),
delta_root_tank_capacity is updated to be equal to the difference between
the water height to humectation front and the root_tank_capacity.
In other words, the change in root tank capacity delta_root_tank_capacity is
limited by the water height to humectation front. Which can be interpreted as :
the roots cannot grow deeper than the humectation front.
? ...which means the humectation from has to be updated somewhere ?
For any other day or if root_tank_capacity is above
delta_root_tank_capacity, delta_root_tank_capacity value is unchanged.
https://docs.google.com/presentation/d/1QHhbNjF9ysCG_yZzWXb7ns0vOFlGfNhYw8AYrotm0fY/edit#slide=id.g27a6b3e8a72_0_161
Args:
j (_type_): _description_
data (_type_): _description_
"""
condition = (data["numPhase"][j,:,:] > 0) & \
np.invert((data["numPhase"][j,:,:] == 1) & (data["changePhase"][j,:,:] == 1))
data["delta_root_tank_capacity"][j:,:,:] = np.where(
condition,
np.where(
(data["humectation_front"][j,:,:] - data["root_tank_capacity"][j,:,:]) < data["delta_root_tank_capacity"][j,:,:],
data["humectation_front"][j,:,:] - data["root_tank_capacity"][j,:,:],
data["delta_root_tank_capacity"][j,:,:],
),
data["delta_root_tank_capacity"][j,:,:],
)
return data
update_delta_total_tank_stock
def update_delta_total_tank_stock(
j,
data
)
This function estimates the positive variation of the total height of
transpirable water ('delta_total_tank_stock', mm).
It is computed as the difference between the total_tank_stock and previous_total_tank_stock, bound in 0.
'previous_total_tank_stock' is initialized to be equal to 'total_tank_stock' at the beginning of the simulation. As 'total_tank_stock' is initialized with the 'stockIrr' parameter, simulations should start with a 0 value.
'previous_total_tank_stock' is updated each day with the 'update_struprec' function.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_delta_total_tank_stock(j, data):
"""
This function estimates the positive variation of the total height of
transpirable water ('delta_total_tank_stock', mm).
It is computed as the difference between the total_tank_stock and
previous_total_tank_stock, bound in 0.
'previous_total_tank_stock' is initialized to be equal to 'total_tank_stock'
at the beginning of the simulation. As 'total_tank_stock' is initialized
with the 'stockIrr' parameter, simulations should start with a 0 value.
'previous_total_tank_stock' is updated each day with the 'update_struprec'
function.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["delta_total_tank_stock"][j:,:,:] = np.maximum(0, data["total_tank_stock"][j,:,:] - data["previous_total_tank_stock"][j,:,:])
return data
update_delta_total_tank_stock_step_2
def update_delta_total_tank_stock_step_2(
j,
data
)
? Ok the logic is the same as for the two previous functions
? and i don't want to document it as it is way too complicated ? and we won't be using it for now.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_delta_total_tank_stock_step_2(j, data):
"""
? Ok the logic is the same as for the two previous functions
? and i don't want to document it as it is way too complicated
? and we won't be using it for now.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
condition_1 = (data["delta_total_tank_stock"][j,:,:] > data["humectation_front"][j,:,:])
condition_2 = (data["humectation_front"][j,:,:] <= data["previous_root_tank_capacity"][j,:,:])
condition_3 = (data["humectation_front"][j,:,:] < data["previous_humectation_front"][j,:,:])
data["delta_total_tank_stock"][j:,:,:] = np.where(
condition_1,
np.where(
condition_2,
data["delta_total_tank_stock"][j,:,:] + (data["delta_total_tank_stock"][j,:,:] - data["humectation_front"][j,:,:]) * data["previous_root_tank_stock"][j,:,:],
np.where(
condition_3,
data["delta_total_tank_stock"][j,:,:] + data["previous_total_tank_stock"][j,:,:],
data["delta_total_tank_stock"][j,:,:],
),
),
data["delta_total_tank_stock"][j,:,:],
)
return data
update_drainage
def update_drainage(
j,
data
)
This function estimates the daily drainage (dr).
When total tank overflows (total_tank_stock > total_tank_capacity), the drainage is computed from the difference between total_tank_stock and total_tank_capacity. This means the drainage value will be positive
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_drainage(j, data):
"""
This function estimates the daily drainage (dr).
When total tank overflows (total_tank_stock > total_tank_capacity), the
drainage is computed from the difference between total_tank_stock and
total_tank_capacity. This means the drainage value will be positive
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
condition = (data["total_tank_stock"][j,:,:] > data["total_tank_capacity"][j,:,:])
data["drainage"][j,:,:] = np.where(
condition,
data["total_tank_stock"][j,:,:] - data["total_tank_capacity"][j,:,:],
0,
)
return data
update_effective_evaporation_for_shallow_roots
def update_effective_evaporation_for_shallow_roots(
j,
data
)
This function updates the value of effective evaporation (consoRur
, mm) in
order to be used specifically to update the quantity of water in the root
tank (root_tank_stock
, mm).
If the root tank capacity is lower than the surface tank capacity, meaning
than the roots do not dive into the deep tank yet, then the effective
evaporation is updated to equal the evaporable water (evap
, mm) modulated
by the ratio between root_tank_stock
and surface_tank_capacity
, that is
to say at the prorata of the exploration of surface tank by the roots.
Else, consoRur
keeps it value, which was previously computed by the
estimate_effective_evaporation_from_evaporable_water
function.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_effective_evaporation_for_shallow_roots(j, data):
"""
This function updates the value of effective evaporation (`consoRur`, mm) in
order to be used specifically to update the quantity of water in the root
tank (`root_tank_stock`, mm).
If the root tank capacity is lower than the surface tank capacity, meaning
than the roots do not dive into the deep tank yet, then the effective
evaporation is updated to equal the evaporable water (`evap`, mm) modulated
by the ratio between `root_tank_stock` and `surface_tank_capacity`, that is
to say at the prorata of the exploration of surface tank by the roots.
Else, `consoRur` keeps it value, which was previously computed by the
`estimate_effective_evaporation_from_evaporable_water` function.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["consoRur"][j:,:,:] = np.where(
data["root_tank_capacity"][j:,:,:] < data["surface_tank_capacity"],
data["evap"][j,:,:] * data["root_tank_stock"][j,:,:] / data["surface_tank_capacity"],
data["consoRur"][j,:,:],
)
return data
update_humectation_front_after_drainage
def update_humectation_front_after_drainage(
j,
data
)
We update the depth to humectation front (hum) again, to reflect eventual changes in
total_tank_stock values.
? we could have placed the previous hum update function here
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_humectation_front_after_drainage(j, data):
"""
We update the depth to humectation front (hum) again, to reflect eventual changes in
total_tank_stock values.
? we could have placed the previous hum update function here
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["humectation_front"][j:,:,:] = np.maximum(data["humectation_front"][j,:,:], data["total_tank_stock"][j,:,:])
return data
update_humectation_front_at_end_of_season
def update_humectation_front_at_end_of_season(
j,
data
)
This function updates the value of water height to humectation front
(humectation_front, mm) at the end of season.
At the harvest date (numPhase = 7), the humectation_front variable is set to equal the surface_tank_capacity (mm). This value is broadcasted over the time dimension.
At any other point in time, its value is unchanged.
? The way of resetting the humectation_front at harvest date hasn't a real ? agronomical meaning. This function allows for resetting the variable at an ? initial state.
? However it is a way to say that when the plant dies, the new ones will have ? to make up their new humectation fronts starting again from surface_tank_capacity
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_humectation_front_at_end_of_season(j, data):
"""
This function updates the value of water height to humectation front
(humectation_front, mm) at the end of season.
At the harvest date (numPhase = 7), the humectation_front variable is set to
equal the surface_tank_capacity (mm). This value is broadcasted over the
time dimension.
At any other point in time, its value is unchanged.
? The way of resetting the humectation_front at harvest date hasn't a real
? agronomical meaning. This function allows for resetting the variable at an
? initial state.
? However it is a way to say that when the plant dies, the new ones will have
? to make up their new humectation fronts starting again from surface_tank_capacity
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
condition = (data["numPhase"][j,:,:] == 7) & (data["changePhase"][j,:,:] == 1)
data["humectation_front"][j:,:,:] = np.where(
condition,
data["surface_tank_capacity"],
data["humectation_front"][j,:,:],
)
return data
update_irrigation_tank_capacity
def update_irrigation_tank_capacity(
j,
data
)
This function updates the capacity if the irrigation tank.
If the simulation is run with automatic irrigation mode
(data["irrigAuto"]==True
), if the current phase is between 0 and 6, and if
the root tank capacity is less than the surface tank capacity (meaning that
the roots have not reached the limit between the surface compartment and
deep compartment), then irrigation_tank_capacity
is set to the value of
surface_tank_capacity
, which is given a minimum value equal to the
surface_tank_capacity
. Otherwise, the irrigation tank capacity remains
unchanged.
Note : the logic of this function has not yet been validated in SARRA-Py, as simulations are mainly based on rainfed conditions.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | int | Index of the time step being processed. | None |
data | xarray dataset | The input dataset containing all the information necessary to run the model. | None |
Returns:
Type | Description |
---|---|
None | xarray dataset: The input dataset with updated values of the irrigation tank capacity. |
View Source
def update_irrigation_tank_capacity(j, data):
"""
This function updates the capacity if the irrigation tank.
If the simulation is run with automatic irrigation mode
(`data["irrigAuto"]==True`), if the current phase is between 0 and 6, and if
the root tank capacity is less than the surface tank capacity (meaning that
the roots have not reached the limit between the surface compartment and
deep compartment), then `irrigation_tank_capacity` is set to the value of
`surface_tank_capacity`, which is given a minimum value equal to the
`surface_tank_capacity`. Otherwise, the irrigation tank capacity remains
unchanged.
Note : the logic of this function has not yet been validated in SARRA-Py, as
simulations are mainly based on rainfed conditions.
Args:
j (int): Index of the time step being processed.
data (xarray dataset): The input dataset containing all the information necessary to run the model.
Returns:
xarray dataset: The input dataset with updated values of the irrigation tank capacity.
"""
condition = \
(data["irrigAuto"][j, :, :] == True) & \
(data["numPhase"][j, :, :] > 0) & \
(data["numPhase"][j, :, :] < 6)
data["irrigation_tank_capacity"][j, :, :] = np.where(
condition,
np.where(
data["root_tank_capacity"][j,:,:] < data["surface_tank_capacity"],
data["surface_tank_capacity"],
data["root_tank_capacity"][j,:,:],
),
data["irrigation_tank_capacity"][j, :, :],
)
return data
update_irrigation_tank_stock
def update_irrigation_tank_stock(
j,
data
)
This function updates the water stock of the irrigation tank.
If the simulation is run with automatic irrigation mode
(data["irrigAuto"]==True
), if the simulation is between phases 0 and 6,
and if root_tank_capacity
is lower than surface_tank_capacity
(which
indicates that the roots have not yet reached the limit between the surface
compartment and deep compartment), irrigation_tank_stock
will be set to
the value of surface_tank_stock
, which means, it will take the minimum
value equal to surface_tank_stock
. For phase 7, the existing
irrigation_tank_stock
value will be kept unchanged.
Note : the logic of this function has not yet been validated in SARRA-Py, as simulations are mainly based on rainfed conditions.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | int | Index of time step in data | None |
data | xarray Dataset | Dataset that contains various data fields | None |
Returns:
Type | Description |
---|---|
None | xarray Dataset: updated data set with the irrigation_tank_stock field updated based on the conditions. |
View Source
def update_irrigation_tank_stock(j, data):
"""
This function updates the water stock of the irrigation tank.
If the simulation is run with automatic irrigation mode
(`data["irrigAuto"]==True`), if the simulation is between phases 0 and 6,
and if `root_tank_capacity` is lower than `surface_tank_capacity` (which
indicates that the roots have not yet reached the limit between the surface
compartment and deep compartment), `irrigation_tank_stock` will be set to
the value of `surface_tank_stock`, which means, it will take the minimum
value equal to `surface_tank_stock`. For phase 7, the existing
`irrigation_tank_stock` value will be kept unchanged.
Note : the logic of this function has not yet been validated in SARRA-Py, as
simulations are mainly based on rainfed conditions.
Args:
j (int): Index of time step in data
data (xarray Dataset): Dataset that contains various data fields
Returns:
xarray Dataset: updated data set with the irrigation_tank_stock field updated based on the conditions.
"""
condition = (data["irrigAuto"][j, :, :] == True) & \
(data["numPhase"][j, :, :] > 0) & \
(data["numPhase"][j, :, :] < 6)
data["irrigation_tank_stock"][j, :, :] = np.where(
condition,
xr.where(
(data["root_tank_capacity"] < data["surface_tank_capacity"])[j, :, :],
data["surface_tank_stock"][j, :, :],
data["root_tank_stock"][j, :, :],
),
data["irrigation_tank_stock"][j, :, :],
)
return data
update_mulch_water_stock
def update_mulch_water_stock(
j,
data
)
This function updates the water stock in mulch.
The water stock in mulch is updated by adding the captured water (water_captured_by_mulch, mm)
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_mulch_water_stock(j, data):
"""
This function updates the water stock in mulch.
The water stock in mulch is updated by adding the captured water (water_captured_by_mulch, mm)
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["mulch_water_stock"][j:,:,:] = (data["mulch_water_stock"][j,:,:] + data["water_captured_by_mulch"][j,:,:])
return data
update_plant_transpiration
def update_plant_transpiration(
j,
data
)
This function updates the value of plant transpiration (tr
, mm) according
to the quantity of water in the root tank (root_tank_stock
, mm).
If the transpiration (which as this point on the daily cycle equals trPot *
cstr
) is higher than the root tank stock (which at this point has been
updated to reflect the effective evaporation), then plant transpiration is
updated to be equal to the difference between the root tank stock and the
plant transpiration.
Else, its value is unmodified.
? However, if this test is true, this always leads to transpiration value = 0.
? We may want to rethink it to check if it does what it was supposed to.
? Instead it would make more sense to bound tr by the value of root tank stock,
? meaning the maximum quantity of water that can be transpired by the plant
? is limited by the quantity of water accessible to roots...
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_plant_transpiration(j, data):
"""
This function updates the value of plant transpiration (`tr`, mm) according
to the quantity of water in the root tank (`root_tank_stock`, mm).
If the transpiration (which as this point on the daily cycle equals `trPot *
cstr`) is higher than the root tank stock (which at this point has been
updated to reflect the effective evaporation), then plant transpiration is
updated to be equal to the difference between the root tank stock and the
plant transpiration.
Else, its value is unmodified.
#? However, if this test is true, this always leads to transpiration value = 0.
#? We may want to rethink it to check if it does what it was supposed to.
#? Instead it would make more sense to bound tr by the value of root tank stock,
#? meaning the maximum quantity of water that can be transpired by the plant
#? is limited by the quantity of water accessible to roots...
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["tr"][j:,:,:] = np.where(
data["tr"][j,:,:] > data["root_tank_stock"][j,:,:],
np.maximum(data["root_tank_stock"][j,:,:] - data["tr"][j,:,:], 0),
#! il peut être intéressant d'introduire dans la prochaine version
#! le correctif suivant :
# data["root_tank_stock"][j,:,:],
data["tr"][j,:,:],
)
return data
update_previous_humectation_front_at_end_of_season
def update_previous_humectation_front_at_end_of_season(
j,
data
)
This function saves information about the water height to humectation front
to another variable (previous_humectation_front, mm) at the end of season so it can be used in the next cycle.
previous_humectation_front is initialized in the function InitPlotMc, and set to be equal to hum. hum itself is initialized to take the maximum value between surface_tank_capacity, root_tank_capacity and total_tank_stock.
At the harvest date (numPhase = 7), the previous_humectation_front variable is set to equal the highest value between hum (mm, water height to humectation front) and surface_tank_capacity (mm). This value is broadcasted over the time dimension.
At any other point in time, its value is unchanged.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | int | number of the day | None |
data | xarray dataset | description | None |
Returns:
Type | Description |
---|---|
None | xarray dataset: description |
View Source
def update_previous_humectation_front_at_end_of_season(j, data):
"""
This function saves information about the water height to humectation front
to another variable (previous_humectation_front, mm) at the end of season so
it can be used in the next cycle.
previous_humectation_front is initialized in the function InitPlotMc, and
set to be equal to hum. hum itself is initialized to take the maximum value
between surface_tank_capacity, root_tank_capacity and total_tank_stock.
At the harvest date (numPhase = 7), the previous_humectation_front variable
is set to equal the highest value between hum (mm, water height to
humectation front) and surface_tank_capacity (mm). This value is broadcasted
over the time dimension.
At any other point in time, its value is unchanged.
Args:
j (int): number of the day
data (xarray dataset): _description_
Returns:
xarray dataset: _description_
"""
condition = (data["numPhase"][j,:,:] == 7) & (data["changePhase"][j,:,:] == 1)
data["previous_humectation_front"][j:,:,:] = np.where(
condition,
np.maximum(data["humectation_front"][j,:,:], data["surface_tank_capacity"]),
data["previous_humectation_front"][j,:,:],
)
return data
update_previous_root_tank_stock_at_end_of_season
def update_previous_root_tank_stock_at_end_of_season(
j,
data
)
This function updates the value of previous stock of water in the root tank
(previous_root_tank_stock, mm) at the end of season.
When the phase changes from 7 to 1, previous_root_tank_stock is set to equal the ratio between root_tank_stock and root_tank_capacity, that is to say the filling rate of the root reservoir. Otherwise, it stays at its initial value of 0. Its value is broadcasted along j. previous_root_tank_stock is initialized with a value of 0.
? The way of resetting the previous_root_tank_stock at harvest date hasn't a real ? agronomical meaning.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_previous_root_tank_stock_at_end_of_season(j, data):
"""
This function updates the value of previous stock of water in the root tank
(previous_root_tank_stock, mm) at the end of season.
When the phase changes from 7 to 1, previous_root_tank_stock is set to equal
the ratio between root_tank_stock and root_tank_capacity, that is to say the
filling rate of the root reservoir. Otherwise, it stays at its initial value
of 0. Its value is broadcasted along j. previous_root_tank_stock is
initialized with a value of 0.
? The way of resetting the previous_root_tank_stock at harvest date hasn't a real
? agronomical meaning.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
condition = (data["numPhase"][j,:,:] == 7) & (data["changePhase"][j,:,:] == 1)
data["previous_root_tank_stock"][j:,:,:] = np.where(
condition,
data["root_tank_stock"][j,:,:] / data["root_tank_capacity"][j,:,:],
data["previous_root_tank_stock"][j,:,:],
)
return data
update_previous_total_tank_stock_at_end_of_season
def update_previous_total_tank_stock_at_end_of_season(
j,
data
)
This function updates the value of previous total stock of water
(previous_total_tank_stock, mm) at the end of season.
When the phase changes from 7 to 1, previous_total_tank_stock is set to equal the difference between total_tank_stock and surface_tank_stock.
Otherwise, it stays at its initial value of 0. Its value is broadcasted along j. previous_total_tank_stock is initialized with a value of 0.
? The way of resetting the previous_total_tank_stock at harvest does not seem to have a real ? agronomical meaning.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_previous_total_tank_stock_at_end_of_season(j, data):
"""
This function updates the value of previous total stock of water
(previous_total_tank_stock, mm) at the end of season.
When the phase changes from 7 to 1, previous_total_tank_stock is set to equal
the difference between total_tank_stock and surface_tank_stock.
Otherwise, it stays at its initial value of 0. Its value is broadcasted
along j. previous_total_tank_stock is initialized with a value of 0.
? The way of resetting the previous_total_tank_stock at harvest does not seem to have a real
? agronomical meaning.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
condition = (data["numPhase"][j,:,:] == 7) & (data["changePhase"][j,:,:] == 1)
data["previous_total_tank_stock"][j:,:,:] = np.where(
condition,
data["total_tank_stock"][j,:,:] - data["surface_tank_stock"][j,:,:], # essai stTot #? what is that ?
data["previous_total_tank_stock"][j,:,:],
)
return data
update_previous_total_tank_stock_for_second_crop_cycle
def update_previous_total_tank_stock_for_second_crop_cycle(
j,
data
)
This function performs the update of the previous total height of
transpirable water (previous_total_tank_stock, mm).
It will decrease the previous_total_tank_stock depending on the variation of transpirable water and height of humectation front.
This update is applied only if a second crop cycle starts, as previous_root_tank_capacity and previous_total_tank_stock are initialized as null. That means conditions 2 and 3 of this function will fail during a first crop cycle, leading to no change in previous_total_tank_stock.
In this function, if the variation of transpirable water (delta_total_tank_stock) increases above humectation_front (condition 1 passed), and if humectation_front is above the previous_root_tank_capacity (condition failed), and if the depth of humectation front has decreased since the previous day (condition 3 passed), then previous_total_tank_stock equals 0.
Starting from second simulation season (previous_root_tank_capacity != 0), if the variation of transpirable water (delta_total_tank_stock) increases above the depth of humectation front (hum), and if the depth of humectation front stays below or equel to the total soil capacity (conditions 1 and 2 passed), then we decrease the value of previous_total_tank_stock by a the difference of water height between the variation of total tank stock (delta_total_tank_stock) and the depth of humectation front (hum), proportionally to the filling of the root tank capacity of previous season (previous_root_tank_stock). Thus, if the root tank is empty, previous_total_tank_stock will remain unchanged, and if the root tank is full, previous_total_tank_stock will be decreased up to the amount of water making the difference between quantity of water for humectation front and the variation in daily transpirable water.
? To my opinion, this function is way too complicated for a borderline use case ? (multiple cropping cycles during one simulation). ? We'd want to keep the code for legacy reasons but if really this simulation ? feature is needed we'll have to simplify it.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_previous_total_tank_stock_for_second_crop_cycle(j, data):
"""
This function performs the update of the previous total height of
transpirable water (previous_total_tank_stock, mm).
It will decrease the previous_total_tank_stock depending on the variation of
transpirable water and height of humectation front.
This update is applied only if a second crop cycle starts, as
previous_root_tank_capacity and previous_total_tank_stock are initialized as
null. That means conditions 2 and 3 of this function will fail during a
first crop cycle, leading to no change in previous_total_tank_stock.
In this function, if the variation of transpirable water
(delta_total_tank_stock) increases above humectation_front (condition 1
passed), and if humectation_front is above the previous_root_tank_capacity
(condition failed), and if the depth of humectation front has decreased
since the previous day (condition 3 passed), then previous_total_tank_stock
equals 0.
Starting from second simulation season (previous_root_tank_capacity != 0),
if the variation of transpirable water (delta_total_tank_stock) increases
above the depth of humectation front (hum), and if the depth of humectation
front stays below or equel to the total soil capacity (conditions 1 and 2
passed), then we decrease the value of previous_total_tank_stock by a the
difference of water height between the variation of total tank stock
(delta_total_tank_stock) and the depth of humectation front (hum),
proportionally to the filling of the root tank capacity of previous season
(previous_root_tank_stock). Thus, if the root tank is empty,
previous_total_tank_stock will remain unchanged, and if the root tank is
full, previous_total_tank_stock will be decreased up to the amount of water
making the difference between quantity of water for humectation front and
the variation in daily transpirable water.
? To my opinion, this function is way too complicated for a borderline use case
? (multiple cropping cycles during one simulation).
? We'd want to keep the code for legacy reasons but if really this simulation
? feature is needed we'll have to simplify it.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
condition_1 = (data["delta_total_tank_stock"][j,:,:] > data["humectation_front"][j,:,:])
condition_2 = (data["humectation_front"][j,:,:] <= data["previous_root_tank_capacity"][j,:,:])
condition_3 = (data["humectation_front"][j,:,:] < data["previous_humectation_front"][j,:,:])
data["previous_total_tank_stock"][j:,:,:] = np.where(
condition_1,
np.where(
condition_2,
np.maximum(0, data["previous_total_tank_stock"][j,:,:] - (data["delta_total_tank_stock"][j,:,:] - data["humectation_front"][j,:,:]) * \
data["previous_root_tank_stock"][j,:,:]),
np.where(
condition_3,
0,
data["previous_total_tank_stock"][j,:,:],
),
),
data["previous_total_tank_stock"][j,:,:],
)
return data
update_root_tank_capacity
def update_root_tank_capacity(
j,
data
)
This function updates the root tank capacity (root_tank_capacity, mm) by
adding the daily variation in root tank capacity.
For each pixel at a developmental stage different from zero, and that is not at initialization phase ('changePhase = 1' and 'numPhase = 1'), root_tank_capacity is updated to be summed with the change in root water storage capacity delta_root_tank_capacity.
In other words, root_tank_capacity is incremented by the change in root water storage capacity related to root growth. Easy, right ?
https://docs.google.com/presentation/d/1QHhbNjF9ysCG_yZzWXb7ns0vOFlGfNhYw8AYrotm0fY/edit#slide=id.g27a6b3e8a72_0_238
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_root_tank_capacity(j, data):
"""
This function updates the root tank capacity (root_tank_capacity, mm) by
adding the daily variation in root tank capacity.
For each pixel at a developmental stage different from zero, and that is not
at initialization phase ('changePhase = 1' and 'numPhase = 1'),
root_tank_capacity is updated to be summed with the change in root water
storage capacity delta_root_tank_capacity.
In other words, root_tank_capacity is incremented by the change in root
water storage capacity related to root growth. Easy, right ?
https://docs.google.com/presentation/d/1QHhbNjF9ysCG_yZzWXb7ns0vOFlGfNhYw8AYrotm0fY/edit#slide=id.g27a6b3e8a72_0_238
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
condition = (data["numPhase"][j,:,:] > 0) & \
np.invert((data["numPhase"][j,:,:] == 1) & (data["changePhase"][j,:,:] == 1))
data["root_tank_capacity"][j:,:,:] = np.where(
condition,
data["root_tank_capacity"][j,:,:] + data["delta_root_tank_capacity"][j,:,:],
data["root_tank_capacity"][j,:,:],
)
return data
update_root_tank_capacity_at_end_of_season
def update_root_tank_capacity_at_end_of_season(
j,
data
)
This function saves information about the root_tank_capacity to another
variable (previous_root_tank_capacity, mm) at the end of season so it can be used in the next cycle.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_root_tank_capacity_at_end_of_season(j, data):
"""
This function saves information about the root_tank_capacity to another
variable (previous_root_tank_capacity, mm) at the end of season so it
can be used in the next cycle.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
condition = (data["numPhase"][j,:,:] == 7) & (data["changePhase"][j,:,:] == 1)
data["previous_root_tank_capacity"][j:,:,:] = np.where(
condition,
data["root_tank_capacity"][j,:,:],
data["previous_root_tank_capacity"][j,:,:],
)
return data
update_root_tank_stock
def update_root_tank_stock(
j,
data
)
This functions update the quantity of water of the root tank ('root_tank_stock', mm).
For each pixel at a developmental stage different from zero, and that is not at initialization phase ('changePhase = 1' and 'numPhase = 1'), and for which the 'root_tank_capacity' is greater than 'surface_tank_capacity' (meaning that roots go beyond the surface water storage capacity), 'root_tank_stock' is incremented by delta_root_tank_capacity.
However, if 'root_tank_capacity' is lesser than 'surface_tank_capacity' (meaning that roots do not plunge into the deep reservoir), 'root_tank_stock' is updated to be equal to surface_tank_stock minus 1/10th of the surface_tank_capacity, multiplied by the ratio between root_tank_capacity and surface_tank_capacity. That is to say "we take at the prorata of depth and surface stock".
For any other day, root_tank_stock is unchanged.
? Why is the tank stock incremented instead of root tank capacity ? If the ? root tank capacity is incremented, that makes sense as we add to the root ? tank capacity the capacity newly gained through delta_root_tank_capacity. ? There is no sense in incrementing the root tank stock with the ? delta_root_tank_capacity, as the delta root tank capacity, representing ? growing of roots is independant of the quantity of water in the soil. ? However, the delta root tank capacity is blocked by hum the humidity front. ? Still, humidity front only grows and limits the maximum growth of roots, and ? is not involved in root water stock.
? Also, if the roots do not go in the deep reservoir, there is an increase in ? root tank stock. Considering this is a mistake and that root_tank_capacity ? should be increased, this would mean root tank capacity is increased by a ? value that depends on the filling of the surface tank first ? (surface_tank_stock minus 1/10th of the surface_tank_capacity, that would be ? about the bound water), times the ratio between root_tank_capacity and ? surface_tank_capacity. This would mean if when there is few roots the ? increase in root tank capacity is small, and if roots are close to passing ? into the deep reservoir, the increase in root tank capacity nears the ? surface_tank_stock. Again, there is no sense in increasing the root tank ? capacity with such value however this would be ok for root_tank_stock...
? Overall there seems to be a mixup between the objectives of the two parts of ? this function ?
? at the moment this function is applied, root_tank_capacity is already ? updated to take into consideration the root growth from the day, limited by ? both the water stress and the depth of the humectation front. i still do not ? understand why we would increase root tank stock, as we do not have ? supplementary water. it would be like creating water from nowhere.
? so until further notice i will let this function as it is, but i will keep ? in mind that it is probably wrong.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_root_tank_stock(j, data):
"""
This functions update the quantity of water of the root tank ('root_tank_stock', mm).
For each pixel at a developmental stage different from zero, and that is not
at initialization phase ('changePhase = 1' and 'numPhase = 1'), and for
which the 'root_tank_capacity' is greater than 'surface_tank_capacity' (meaning
that roots go beyond the surface water storage capacity), 'root_tank_stock'
is incremented by delta_root_tank_capacity.
However, if 'root_tank_capacity' is lesser than 'surface_tank_capacity' (meaning
that roots do not plunge into the deep reservoir), 'root_tank_stock' is
updated to be equal to surface_tank_stock minus 1/10th of the
surface_tank_capacity, multiplied by the ratio between root_tank_capacity
and surface_tank_capacity. That is to say "we take at the prorata of depth
and surface stock".
For any other day, root_tank_stock is unchanged.
? Why is the tank stock incremented instead of root tank capacity ? If the
? root tank capacity is incremented, that makes sense as we add to the root
? tank capacity the capacity newly gained through delta_root_tank_capacity.
? There is no sense in incrementing the root tank stock with the
? delta_root_tank_capacity, as the delta root tank capacity, representing
? growing of roots is independant of the quantity of water in the soil.
? However, the delta root tank capacity is blocked by hum the humidity front.
? Still, humidity front only grows and limits the maximum growth of roots, and
? is not involved in root water stock.
? Also, if the roots do not go in the deep reservoir, there is an increase in
? root tank stock. Considering this is a mistake and that root_tank_capacity
? should be increased, this would mean root tank capacity is increased by a
? value that depends on the filling of the surface tank first
? (surface_tank_stock minus 1/10th of the surface_tank_capacity, that would be
? about the bound water), times the ratio between root_tank_capacity and
? surface_tank_capacity. This would mean if when there is few roots the
? increase in root tank capacity is small, and if roots are close to passing
? into the deep reservoir, the increase in root tank capacity nears the
? surface_tank_stock. Again, there is no sense in increasing the root tank
? capacity with such value however this would be ok for root_tank_stock...
? Overall there seems to be a mixup between the objectives of the two parts of
? this function ?
? at the moment this function is applied, root_tank_capacity is already
? updated to take into consideration the root growth from the day, limited by
? both the water stress and the depth of the humectation front. i still do not
? understand why we would increase root tank stock, as we do not have
? supplementary water. it would be like creating water from nowhere.
? so until further notice i will let this function as it is, but i will keep
? in mind that it is probably wrong.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
condition = (data["numPhase"][j,:,:] > 0) & \
np.invert((data["changePhase"][j,:,:] == 1) & (data["numPhase"][j,:,:] == 1)),
data["root_tank_stock"][j:,:,:] = xr.where(
condition,
xr.where(
(data["root_tank_capacity"][j,:,:] > data["surface_tank_capacity"]),
data["root_tank_stock"][j,:,:] + data["delta_root_tank_capacity"][j,:,:],
np.maximum(
((data["surface_tank_stock"][j,:,:] - data["surface_tank_capacity"] * 1/10) * \
(data["root_tank_capacity"][j,:,:] / data["surface_tank_capacity"])),
0),
).expand_dims("time", axis=0),
data["root_tank_stock"][j,:,:],
)
return data
update_root_tank_stock_step_2
def update_root_tank_stock_step_2(
j,
data
)
This function updates the height of water in the tank of water accessible to
roots ("root_tank_stock", mm).
It increments root_tank_stock with transpirable water (eauTranspi), within the bounds of root_tank_capacity and total_tank_stock.
This means the sum of transpirable water and root tank stock for the day firstly cannot be higher than the root tank capacity, which is fine to represent the height of water accessible to roots. But also, that this sum limited by the root tank capacity cannot be higher than the total tank stock, which seems unlikely.
? This raises the question about where does the potential water in excess go.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_root_tank_stock_step_2(j, data):
"""
This function updates the height of water in the tank of water accessible to
roots ("root_tank_stock", mm).
It increments root_tank_stock with transpirable water (eauTranspi), within
the bounds of root_tank_capacity and total_tank_stock.
This means the sum of transpirable water and root tank stock for the day
firstly cannot be higher than the root tank capacity, which is fine to represent
the height of water accessible to roots. But also, that this sum limited by
the root tank capacity cannot be higher than the total tank stock, which seems unlikely.
? This raises the question about where does the potential water in excess go.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["root_tank_stock"][j:,:,:] = np.minimum(data["root_tank_stock"][j,:,:] + data["eauTranspi"][j,:,:], data["root_tank_capacity"][j,:,:])
data["root_tank_stock"][j:,:,:] = np.minimum(data["root_tank_stock"][j,:,:], data["total_tank_stock"][j,:,:])
return data
update_surface_tank_stock
def update_surface_tank_stock(
j,
data
)
This function updates the value of water stock in the surface tank
(surface_tank_stock, mm) with the water available for the day (available_water, mm), within the limits of 110% surface_tank_capacity.
We update surface_tank_stock by adding the available_water, which as this point in the process list corresponds to the water available from 1) rain, 2) irrigation for the day, corrected from 3) intake by mulch (fill_mulch function), and 4) runoff (compute_runoff). However, we do not allow surface_tank_stock to exceed 110% of the surface_tank_capacity.
? This means it is possible that the surface tank fill rate is above 100%, ? which is a rather strange assumption.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_surface_tank_stock(j, data):
"""
This function updates the value of water stock in the surface tank
(surface_tank_stock, mm) with the water available for the day
(available_water, mm), within the limits of 110% surface_tank_capacity.
We update surface_tank_stock by adding the available_water, which as this
point in the process list corresponds to the water available from 1) rain,
2) irrigation for the day, corrected from 3) intake by mulch (fill_mulch
function), and 4) runoff (compute_runoff). However, we do not allow
surface_tank_stock to exceed 110% of the surface_tank_capacity.
? This means it is possible that the surface tank fill rate is above 100%,
? which is a rather strange assumption.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["surface_tank_stock"][j:,:,:] = np.minimum(
data["surface_tank_stock"][j,:,:] + data["available_water"][j,:,:],
# 1.1 * data["surface_tank_capacity"]
data["surface_tank_capacity"]
)
return data
update_total_tank_stock
def update_total_tank_stock(
j,
data
)
This functions updates the total height of transpirable water
('total_tank_stock', mm) with the amount of transpirable water for the day ('eauTranspi', mm).
? Said differently, 'total_tank_stock' represents the total amount of water ? available for the plant in the soil column ?
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_total_tank_stock(j, data):
"""
This functions updates the total height of transpirable water
('total_tank_stock', mm) with the amount of transpirable water for the day
('eauTranspi', mm).
? Said differently, 'total_tank_stock' represents the total amount of water
? available for the plant in the soil column ?
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
data["total_tank_stock"][j:,:,:] = (data["total_tank_stock"][j,:,:] + data["eauTranspi"][j,:,:])
return data
update_total_tank_stock_after_drainage
def update_total_tank_stock_after_drainage(
j,
data
)
This function updates the total tank stock (total_tank_stock, mm) when these is overflowing.
When capacity of total_tank_stock is exceeded, total_tank_stock value is replaced with total_tank_capacity
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_total_tank_stock_after_drainage(j, data):
"""
This function updates the total tank stock (total_tank_stock, mm) when these is overflowing.
When capacity of total_tank_stock is exceeded, total_tank_stock value is replaced with
total_tank_capacity
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
condition = (data["total_tank_stock"][j,:,:] > data["total_tank_capacity"][j,:,:])
data["total_tank_stock"][j:,:,:] = np.where(
condition,
data["total_tank_capacity"][j,:,:],
data["total_tank_stock"][j,:,:],
)
return data
update_total_tank_stock_for_second_crop_cycle
def update_total_tank_stock_for_second_crop_cycle(
j,
data
)
This function updates the total height of transpirable water
('total_tank_stock', mm), specifically if a second crop cycle starts.
This update is applied only if a second crop cycle starts, as previous_root_tank_capacity and previous_total_tank_stock are initialized as null. That means conditions 2 and 3 of this function will fail during a first crop cycle, leading to no change in total_tank_stock.
However, at numPhase = 7, which corresponds to the harvesting date and that opens the possibility for a second crop cycle, previous_root_tank_capacity and previous_total_tank_stock will be updated.
From now on, if delta_total_tank_stock is greater than humectation front (condition 1 passed), and previous_root_tank_capacity is greater or equal to the humectation_front (condition 2 passed), and if the humectation_front is below previous_humectation_front (condition 3 passed), then the total tank stock is updated to be increased with the difference between delta_total_tank_stock and humectation_front, times the previous root tank stock.
Thus, if the root tank is empty, total_tank_stock will remain unchanged, and if the root tank is full, total_tank_stock will be increased up to the amount of water making the difference between quantity of water for humectation front and the variation in daily transpirable water.
Also, if delta_total_tank_stock is greater than humectation front (condition 1 passed), but previous_root_tank_capacity is lower than the humectation_front (condition 2 failed), while the humectation_front is below previous_humectation_front (condition 3 passed), we update the total tank stock to be equal to delta_total_tank_stock.
In other words, if during the second crop cycle the humectation front is too low, we increase the total tank stock.
? To my opinion, this function is way too complicated for a borderline use case ? (multiple cropping cycles during one simulation). ? We'd want to keep the code for legacy reasons but if really this simulation ? feature is needed we'll have to simplify it.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j | type | description | None |
data | type | description | None |
Returns:
Type | Description |
---|---|
type | description |
View Source
def update_total_tank_stock_for_second_crop_cycle(j, data):
"""
This function updates the total height of transpirable water
('total_tank_stock', mm), specifically if a second crop cycle starts.
This update is applied only if a second crop cycle starts, as
previous_root_tank_capacity and previous_total_tank_stock are initialized as
null. That means conditions 2 and 3 of this function will fail during a
first crop cycle, leading to no change in total_tank_stock.
However, at numPhase = 7, which corresponds to the harvesting date and that
opens the possibility for a second crop cycle, previous_root_tank_capacity
and previous_total_tank_stock will be updated.
From now on, if delta_total_tank_stock is greater than humectation front
(condition 1 passed), and previous_root_tank_capacity is greater or equal to
the humectation_front (condition 2 passed), and if the humectation_front is
below previous_humectation_front (condition 3 passed), then the total tank
stock is updated to be increased with the difference between
delta_total_tank_stock and humectation_front, times the previous root tank
stock.
Thus, if the root tank is empty, total_tank_stock will remain unchanged, and
if the root tank is full, total_tank_stock will be increased up to the
amount of water making the difference between quantity of water for
humectation front and the variation in daily transpirable water.
Also, if delta_total_tank_stock is greater than humectation front (condition
1 passed), but previous_root_tank_capacity is lower than the
humectation_front (condition 2 failed), while the humectation_front is below
previous_humectation_front (condition 3 passed), we update the total tank
stock to be equal to delta_total_tank_stock.
In other words, if during the second crop cycle the humectation front is too
low, we increase the total tank stock.
? To my opinion, this function is way too complicated for a borderline use case
? (multiple cropping cycles during one simulation).
? We'd want to keep the code for legacy reasons but if really this simulation
? feature is needed we'll have to simplify it.
Args:
j (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
condition_1 = (data["delta_total_tank_stock"][j,:,:] > data["humectation_front"][j,:,:])
condition_2 = (data["humectation_front"][j,:,:] <= data["previous_root_tank_capacity"][j,:,:])
condition_3 = (data["humectation_front"][j,:,:] < data["previous_humectation_front"][j,:,:])
data["total_tank_stock"][j:,:,:] = np.where(
condition_1,
np.where(
condition_2,
data["total_tank_stock"][j,:,:] + (data["delta_total_tank_stock"][j,:,:] - data["humectation_front"][j,:,:]) * \
data["previous_root_tank_stock"][j,:,:],
np.where(
condition_3,
data["delta_total_tank_stock"][j,:,:],
data["total_tank_stock"][j,:,:],
),
),
data["total_tank_stock"][j,:,:],
)
return data