You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

2007 lines
92 KiB

#!/usr/bin/python3.4
# -*-coding:Utf-8 -*
"""Module containing the functions for :
VegAu, STEP2: Building a typical Crop Rotation for each PRA according to Climate and Soil Data
ATTENTION :
This program only works with the imported version of the original file "inputFR.ods" or a file with the same layout (sheets, columns in sheets...).
The import of this sheet is ensured by the module 'importingODS.py'"""
#########################################
# #
# IMPORTING EXTERNAL MODULES #
# #
#########################################
#########################################
# #
# IMPORTING INTERNAL MODULES #
# #
#########################################
from importedVariables import * # lambda functions to access easier to the data from the abode imported dicts
from Functions_step1 import CORR_TOLERdf
from Functions_step1 import WaterResources
#########################################
# #
# SECUNDARY FUNCTIONS #
# (short, just for adapting data) #
# #
#########################################
def MonthID(month):
"""INPUT:
'month' is the amount of months after the beginning of the computed rotation.
OUTPUT:
Returns the month ID composed by the 3 first letters of the month and the amount of years after the beginning
of the rotation.
"""
MonthNum = month % 12
MonthName = ['dec', 'jan', 'feb', 'mar', 'apr', 'mai', 'jun', 'jul', 'agu', 'sep', 'oct', 'nov']
year = int(month//12 + 1)
if month % 12 == 0:
return "dec" + str(year - 1)
else:
return MonthName[int(MonthNum)] + str(year)
#================================================================================================================
def ETc_GSmax(crop, month, x, PRA):
"""INPUT :
* crop is the ID of a current crop. It allows to call the related data in the 'plants' dictionary.
* month is the number of the current month (1 for January, 3 for March, 10 for October, etc)
* x is the class that contains all self variables used in all VegAu's functions
* PRA is the ID of a current PRA. It allows to call the related data in the 'environment' dictionary.
OUTPUT :
ETc value for each month of the growing season calculated in STEP2 (not STEP1 !!) for the rotation simulation.
It uses the variable x.EndPreviousCrop and GSmax(crop) to calculate its position in the GS duration.
"""
month = month % 12 - x.EndPreviousCrop_earlier % 12 + 1
# -> index of the current month of the rotation - index of the month of the begining of the rotation
# e.g.: x.EndPreviousCrop_earlier = 3 (March) and the current month is 5 (Mai) : we are 5-3+1 = 3nd month of the GSmax(crop) (March + April + Mai).
maximum_growing_season_duration = GSmax(crop)
GS1_4 = round(maximum_growing_season_duration * 0.25 )
GS2_4 = round(maximum_growing_season_duration * 0.50 )
GS3_4 = round(maximum_growing_season_duration * 0.75 )
if month <= GSmax(crop):
return Kc4_4(crop)*ETPmoy(month, PRA) # ETc = Kc (index) ETPmoy (in mm)
elif month <= GS3_4:
return Kc3_4(crop)*ETPmoy(month, PRA)
elif month <= GS2_4 :
return Kc2_4(crop) * ETPmoy(month, PRA)
elif month <= GS1_4:
return Kc1_4(crop) * ETPmoy(month, PRA)
#================================================================================================================
def ETc_GSmin(crop, month, x, PRA):
"""INPUT :
* crop is the ID of a current crop. It allows to call the related data in the 'plants' dictionary.
* month is the number of the current month (1 for January, 3 for March, 10 for October, etc)
* x is the class that contains all self variables used in all VegAu's functions
* PRA is the ID of a current PRA. It allows to call the related data in the 'environment' dictionary.
OUTPUT :
ETc value for each month of the growing season calculated in STEP2 (not STEP1 !!) for the rotation simulation.
It uses the variable x.EndPreviousCrop_earlier and GSmin(crop) to calculate its position in the GS duration.
"""
month = abs( month - (x.EndPreviousCrop_earlier + 1))
# -> index of the current month of the rotation - index of the month of the beginning of the rotation
# e.g.: x.EndPreviousCrop = 3 (March) and the current month is 5 (Mai) : we are 5-3+1 = 3nd month of the GSmin(crop) (March + April + Mai).
minimum_growing_season_duration = GSmin(crop)
GS1_4 = round(x.EndPreviousCrop_earlier%12 + minimum_growing_season_duration * 0.25 )
GS2_4 = round(x.EndPreviousCrop_earlier%12 + minimum_growing_season_duration * 0.50 )
GS3_4 = round(x.EndPreviousCrop_earlier%12 + minimum_growing_season_duration * 0.75 )
# BUT DU BREAKPOINT : comprendre pourquoi un "noneType" sort ---> TypeError: unsupported operand type(s) for *: 'NoneType' and 'float'
if month <= GS1_4:
return Kc1_4(crop) * ETPmoy(month, PRA) # ETc = Kc (index) ETPmoy (in mm)
elif month <= GS2_4:
return Kc2_4(crop) * ETPmoy(month, PRA)
elif month <= GS3_4:
return Kc3_4(crop) * ETPmoy(month, PRA)
else:
return Kc4_4(crop) * ETPmoy(month, PRA)
#================================================================================================================
def WRmargin_GSmax(crop, month, x, PRA): # inutilisée
"""INPUT :
* crop is the ID of a current crop. It allows to call the related data in the 'plants' dictionary.
* month is the number of the current month (1 for January, 3 for March, 10 for October, etc)
* x is the class that contains all self variables used in all VegAu's functions
* PRA is the ID of a current PRA. It allows to call the related data in the 'environment' dictionary.
OUTPUT :
Proportional margin between the PRA's WaterResources and the most extremes plant's needs/tolerance :
---> Water Resources / Crops Requirement * Tolerance to Drought
The more the monthly WR are near from this extreme value, the highest
the margin.
The Kc values and its appliance to the months of the growing season
depends on GSmax(crop).
This function is used in ASSESS_OptimalWaterResources(crop, PRA, x) where the
WRmargin_GSmax(crop, month, x, PRA) values are standardized to get an index from 0 to 1.
"""
#~ FUNCTION AS BEFORE 2017.03.22:
#~ if WaterResources(month, x.GSstart, PRA, crop, x) < 0.90 * ETc_GSmax(crop, month, x, PRA):
#~ i = 0.9 - Waterressources(month) / ETc_GSmax(crop, month, x, PRA) # i for Waterressources(month) = ETc_effective
#~ ETc_effective = (0.9 - i) * ETc_GSmax(crop, month, x, PRA) # i is the % of ETc_GSmax(crop, month, x, PRA)
#~ return (ETc_effective - ETc_GSmax(crop, month, x, PRA) * TOLERdrought(crop))/(ETc_GSmax(crop, month, x, PRA) * TOLERdrought(crop))
# Function "corrected" (?)
water_stress_threshold = ETc_GSmax(crop, month, x, PRA) * x.TOLERdrought
try:
return_value = 1- (water_stress_threshold / WaterResources(month, x.GSstart[crop], PRA, crop, x))
except :
return_value = 1 - (water_stress_threshold / WaterResources(month, x.GSstart, PRA, crop, x))
return return_value
#================================================================================================================
def WRmargin_GSmin(crop, month, x, PRA):
"""INPUT :
* crop is the ID of a current crop. It allows to call the related data in the 'plants' dictionary.
* month is the number of the current month (1 for January, 3 for March, 10 for October, etc)
* x is the class that contains all self variables used in all VegAu's functions
* PRA is the ID of a current PRA. It allows to call the related data in the 'environment' dictionary.
OUTPUT:
Proportional margin between the PRA's WaterResources and the most extremes plant's needs :
---> Water Resources / Crops Requirement * Tolerance to Drought
The more the monthly WR are near from this extreme value, the highest
the margin.
The Kc values and its appliance to the months of the growing season
depends on GSmin(crop).
This function is used in ASSESS_OptimalWaterResources(crop, PRA, x) where the
WRmargin_GSmax(crop, month, x, PRA) values are standardized to get an index from 0 to 1.
"""
#~ FUNCTION AS BEFORE 2017.03.22:
#~ if WaterResources(month, GSstart, PRA, crop, x) < 0.90 * ETc_GSmin(crop, month, x):
#~ i = 0.9 - Waterressources(month) / ETc_GSmin(crop, month, x) # i for Waterressources(month) = ETc_effective
#~ ETc_effective = (0.9 - i) * ETc_GSmin(crop, month, x) # i is the % of ETc_GSmin(crop, month, x)
#~ return (ETc_effective - ETc_GSmin(crop, month, x) * TOLERdrought(crop))/(ETc_GSmin(crop, month, x) * TOLERdrought(crop))
# Function "corrected" (?)
water_stress_threshold = ETc_GSmin(crop, month, x, PRA) * x.TOLERdrought
if WaterResources(month, x.GSstart[crop], PRA, crop, x) > ETc_GSmin(crop, month, x, PRA):
# if water resources are optimal :
return_value = 1
else:
try:
return_value = 1- (water_stress_threshold / WaterResources(month, x.GSstart[crop], PRA, crop, x))
except:
return_value = 1 - (water_stress_threshold / WaterResources(month, x.GSstart, PRA, crop, x))
return return_value
#=============================================================================================================
## NOTICE: 'YIELD' is a variable calculated just before the function 'SelectedCrop_Impact()': NOT A LAMBDA !!
HarvWeight = lambda prodID: expYIELD(prodID) * 1000 # conversion from tons to kilograms
def StrawWeight(prodID):
"""Using the crop's % of straw to determine the straw weight.
If the crop is a cover/companion crop, everything is killed and returned to the ground:
if ZeroDivisionError occurs, the function returns the expected yield. """
try:
return (expSTRAW_rate(prodID) * HarvWeight(prodID)) / (1 - expSTRAW_rate(prodID))
except ZeroDivisionError:
return expYIELD(prodID)
#-------------------------------------------------------------------------------------------------------------
fixedN = lambda prodID: round ( (HarvWeight(prodID) + StrawWeight(prodID)) * fixN(prodID), 4)
# removedN ---> Amount of removed N according to the harvested wett mass.
# Fixed N is not removed from the soil because it comes from the atmosphere.
# The Ndfa values given in the database are supposed to be the percentage of the plant N.
removedN = lambda prodID: round ( prodN(prodID) * HarvWeight(prodID) + strawN(prodID) * StrawWeight(prodID) - fixedN(prodID) * (strawN(prodID) * StrawWeight(prodID) + strawN(prodID) * StrawWeight(prodID) * fixN(prodID)), 4)
returnedN = lambda prodID: round ( strawN(prodID) * StrawWeight(prodID) + strawN(prodID) * StrawWeight(prodID) * fixN(prodID) * (strawN(prodID) * StrawWeight(prodID) + strawN(prodID) * StrawWeight(prodID)), 4)
# N that have being fixed in straw (assuming that N fixation is homogeneous: it is false, but no data about it for now)
# returns to the soil with the crop residues
removedP = lambda prodID: round (prodP(prodID) * HarvWeight(prodID) + prodP(prodID) * StrawWeight(prodID), 4)
returnedP = lambda prodID: round (strawP(prodID) * StrawWeight(prodID), 4)
removedK = lambda prodID: round (prodK(prodID) * HarvWeight(prodID) + prodK(prodID) * StrawWeight(prodID), 4)
returnedK = lambda prodID: round (strawK(prodID) * StrawWeight(prodID), 4)# does not already exist (no data)
removedNa = lambda prodID: round (prodNa(prodID) * HarvWeight(prodID), 4)
returnedNa = lambda prodID: round( 0 * StrawWeight(prodID), 4) # does not already exist (no data)
removedMg = lambda prodID: round( prodMg(prodID) * HarvWeight(prodID) + prodMg(prodID) * StrawWeight(prodID), 4)
returnedMg = lambda prodID: round( 0 * StrawWeight(prodID), 4) # does not already exist (no data)
removedCa = lambda prodID: round( prodCa(prodID) * HarvWeight(prodID) + prodCa(prodID) * StrawWeight(prodID), 4)
returnedCa = lambda prodID: round( 0 * StrawWeight(prodID), 4) # does not already exist (no data)
removedMn = lambda prodID: round( prodMn(prodID) * HarvWeight(prodID) + prodMn(prodID) * StrawWeight(prodID), 4)
returnedMn = lambda prodID: round( 0 * StrawWeight(prodID), 4) # does not already exist (no data)
removedFe = lambda prodID: round( prodFe(prodID) * HarvWeight(prodID) + prodFe(prodID) * StrawWeight(prodID), 4)
returnedFe = lambda prodID: round( 0 * StrawWeight(prodID), 4) # does not already exist (no data)
removedCu = lambda prodID: round( prodCu(prodID) * HarvWeight(prodID) + prodCu(prodID) * StrawWeight(prodID), 4)
returnedCu = lambda prodID: round( 0 * StrawWeight(prodID), 4) # does not already exist (no data)
returnedOM = lambda prodID: round( returnedN(prodID) * CN(prodID), 4)
#================================================================================================================
def mineralizedCPK(crop, month):
"""INPUT :
* crop is the ID of a current crop. It allows to call the related data in the 'plants' dictionary.
* month is the number of the current month (1 for January, 3 for March, 10 for October, etc)
FUNCTION:
Function based on the article of Justes et al. (2009) for the model STICS : https://www.researchgate.net/publication/225690616_Quantifying_and_modelling_C_and_N_mineralization_kinetics_of_catch_crop_residues_in_soil_Parameterization_of_the_residue_decomposition_module_of_STICS_model_for_mature_and_non_mature_residues)
It returns the mineralized C (OM) in function of time. For the moment, we assume than, because C is not the bacteria's fodder,
this function can also be applied to P and K. If there is other known functions/parameters for P and K, the code should be updated
with these new functions/parameters.
OUTPUT:
the OM amount returned to the soil after decomposition
"""
from math import exp
t = month * 30.5 # this function requires days
R = CN(crop)
a = 15.4
b = 76
# ----------------------------------------------------------------
Rb = a - b / R # C:N of zymogeneous bacterial biomass (g/g)
Rbmin = 7.8
if Rb < Rbmin:
Rb = Rbmin
# ----------------------------------------------------------------
c = 0.07
d = 1.94
e = 0.73
f = 10.2
h = 1 - (e * R) / (f + R) # humification rate of microbial biomass (ndays^-1)
k = c + (d / R) # decomposition rate of plant residues (ndays^-1), value from the article
# k = c + d/R * 0.98 * 0.74 # decomposition rate of plant residues (ndays^-1), value from the table
λ = 0.0076
Y0 = 0.62 # Assimilation yield residue-C by microbial biomass (g/g)
Y = Y0
return (1 - Y * h - (1 + (Y * (k - λ * h) / (λ - k))) * exp(-k * t) + ((k * Y * (1 - h) / (λ - k)) * exp(-λ * t)))
#================================================================================================================
def mineralizedN(crop, month):
"""INPUT :
* crop is the ID of a current crop. It allows to call the related data in the 'plants' dictionary.
* month is the number of the current month (1 for January, 3 for March, 10 for October, etc)
FUNCTION:
Function based on the article of Justes et al. (2009) for the model STICS : https://www.researchgate.net/publication/225690616_Quantifying_and_modelling_C_and_N_mineralization_kinetics_of_catch_crop_residues_in_soil_Parameterization_of_the_residue_decomposition_module_of_STICS_model_for_mature_and_non_mature_residues)
It returns the mineralized N in function of time.
OUTPUT:
the amount of N that is returned to the soil after decomposition
"""
# from math import exp
# t = month * 30.5 # this function requires days
# R = CN(crop)
# a = 15.4
# b = 76
# Rb = a + b/R # C:N of zymogeneous bacterial biomass (g/g)
# Rbmin = 7.8
# if Rb < Rbmin :
# Rb = Rmin
#
# c = 0.098
# d = 1.94
# e = 0.73
# f = 10.2
# h = 1 - (e*R) / ( f + R) # humification rate of microbial biomass (n days^-1)
# k = c + d/R * 0.98 * 0.74 # decomposition rate of plant residues (n days^-1)
# λ = 0.0076
#
# Y0 = 0.62 # Assimilation yield residue-C by microbial biomass (g/g)
# Y = Y0
# CN_humus = 9.5
# Wr = 1/R # N:C ratio of the plant residues
# Wb = 1/Rb # N:C ratio of the newly formed microbial biomass
# Wh = 1/CN_humus # N:C ratio of the newly formed humified organic matter
# alpha = 1 - Wh/Wr * Y * h
# beta = 1- (k * Wb - λ * h * Wh) * Y/Wr / (k-λ)
# gamma = k * Y * (Wb - h * Wh) / Wr / (k - λ)
# return (1 - exp(-k * t) - ((Wb * k * Y)/(Wr *( λ - k))) * (exp(-k * t)) - ((Wh * Y * h)/(Wr * (λ - k)))* (exp(-λ*t) - λ * exp(-k*t)) - (Wh/Wr)*Y*h )
# return (alpha - beta * exp(-k*t)- gamma * exp(-λ*t) )
# TEMPORARY workaround :
return mineralizedCPK(crop, month) / CN(crop)
#================================================================================================================
#================================================================================================================
#########################################
# #
# PRIMARY FUNCTIONS #
# #
#########################################
def VERIF_TreesInRegion(PRA, x, data):
""""INPUT :
* PRA is the ID of a current PRA. It allows to call the related data in the 'environment' dictionary.
* x is the class that contains all self variables used in all VegAu's functions
FUNCTION:
Verifying if a permanent crop is edible for this PRA. If there is no other tree crop in the region, this crop is
chosen by default. Else, if there is a FRT crop fro the PRA, it is chosen anyway (only 14 matches in whole France).
If there are FRT and NUT crops for the same PRA, the chosen crop will be the one with the lower representativity.
OUTPUT:
x.SelectedCrop = crop
"""
print(" Verifying if there are permanent crops with optimal Water Requirements for this PRA...")
# representativity limit for permanent crops
MaxPermaCrops = round((0.15 * len([pra for pra in data.environment if pra != 'headers_full' and pra != 'headers_ID']) / len([c for c in data.plants if prodCAT(c) == 1 or prodCAT(c) == 2]) ))
PRA_in_region = [pra for pra in data.environment if pra in x.results and (CODE_REG(pra) == CODE_REG(PRA) and pra != PRA)]
PRA_in_dept = [pra for pra in data.environment if pra in x.results and (CODE_DEPT(pra) == CODE_DEPT(PRA) and pra != PRA)]
crops_in_region = []
crops_in_dept = []
for pra in PRA_in_region :
if pra in x.results:
if x.results[pra][6] != '':
crops_in_region.append(x.results[pra][6])
if pra in PRA_in_dept:
crops_in_dept.append(x.results[pra][6])
if len([c for c in crops_in_dept if prodCAT(c) == 1 or prodCAT(c) == 2]) <= 2:
# if there are already 2 permanent crops in the departement, we give priority to changing crops. Else:
permanent_crops_in_PRA = [c for c in x.edibleCrops if 1 <= prodCAT(c) <= 2 and round(x.indexWR[c], 2) >= 0.75]
fruits_in_PRA = [c for c in permanent_crops_in_PRA if ('FRT' in c or 'EXOT' in c)]
nuts_in_PRA = [c for c in permanent_crops_in_PRA if 'NUT' in c]
berries_in_PRA = [c for c in permanent_crops_in_PRA if 'BERRY' in c]
other_trees_in_PRA = [c for c in permanent_crops_in_PRA if (c not in fruits_in_PRA and c not in nuts_in_PRA and c not in berries_in_PRA)]
#-------------------------------------------------------------------------------------------------------------------
# Olive trees have twice more chance to appear in a region because olives can be green or mature:
# if there is already an "olive-PRA" in the region
# or if olives trees reached the maximal amount of permanent trees, olives are rejected for this one.
if 'OLVmat' in x.representativity and 'OLVgreen' in x.representativity :
too_much_olive_trees = x.representativity['OLVgreen'] + x.representativity['OLVmat'] >= MaxPermaCrops
else:
too_much_olive_trees = False
if ([c for c in crops_in_region if 'OLV' in c] != [] and [c for c in other_trees_in_PRA if 'OLV' in c] != []) or too_much_olive_trees:
other_trees_in_PRA = [c for c in other_trees_in_PRA if 'OLV' not in c]
# ------------------------------------------------------------------------------------------------------------------
permanent_crops_in_PRA = [fruits_in_PRA, nuts_in_PRA, berries_in_PRA, other_trees_in_PRA]
if permanent_crops_in_PRA != []:
# if there is a permanent crop in the edible crops list for this PRA :
for CropType in permanent_crops_in_PRA :
# first fruits and exotic trees, then nuts and finally berries:
if CropType != [] :
if len(CropType) == 1 :
if CropType[0] in x.representativity:
# if the crop is already represented in the country, verifying if it has not been chosen too often:
if x.representativity[CropType[0]] < MaxPermaCrops:
if CropType[0] not in crops_in_region:
x.SelectedCrop = CropType[0]
x.CHOICE[PRA].append(('Permanent crop (not in region, already in country)',len(CropType)))
break
elif data.plants[CropType[0]]['ratioADAPT'] < 0.15 :
# if the only edible permanent crop has an adaptation ratio lower than 15%, we chose it. Else, it is rejected.
x.SelectedCrop = CropType[0]
x.CHOICE[PRA].append(('Permanent crop (fruits, nuts)', len(CropType)))
break
else:
# if the crops has still never been chosen in the whole country :
x.SelectedCrop = CropType[0]
x.CHOICE[PRA].append(('Permanent crop (not in region)', len(CropType)))
break
else :
PRIORITYgeneral = lambda crop: data.plants[crop]['PRIORITYgeneral']
PRIORITYfruits = lambda crop: data.plants[crop]['PRIORITYfruits']
# considering only crops with the lower (better) priority index, so the lower potential edibility :
crops_priority = [PRIORITYgeneral(c) for c in CropType]
CropType = [c for c in CropType if PRIORITYgeneral(c) == min(crops_priority)]
unusedPermanentCrops = [c for c in CropType if c not in x.representativity]
if min(crops_priority) == 1 :
# if selected crops have a very low potential occurence in the country, we don't care if
# they are already in the region or not :
if unusedPermanentCrops != []:
# if there are permanent trees that have not been used until now :
if len(unusedPermanentCrops) == 1 :
x.SelectedCrop = unusedPermanentCrops[0]
x.CHOICE[PRA].append(('Permanent crop (not in country)', len(CropType)))
else:
#If there are several unused crops, we chose a crop according to the quality of the local
# water resources and the potential occurence of the crop in the country
priority = [( x.indexWR[c] * (1 - data.plants[c]['ratioADAPT']) )/2 for c in unusedPermanentCrops]
x.SelectedCrop = [c for c in unusedPermanentCrops if (x.indexWR[c] * (1 - data.plants[c]['ratioADAPT']) )/2 == max(priority)][0]
x.CHOICE[PRA].append(('Permanent crop (not in country, greater rarity and water indexes)', len(CropType)))
#---------------------------------------------------------------------------------------
else:
# if every crop are in x.representativity (if they have all been used once in the country)
if [ c for c in x.representativity if ( c in CropType and x.representativity[c] < MaxPermaCrops )] != []:
CropType = [ c for c in x.representativity if ( c in CropType and x.representativity[c] < MaxPermaCrops )]
crops_representativity = [x.representativity[c] for c in CropType]
# The selected crop is the one with the lower representativity and the better WaterResources-index:
if min(crops_representativity) < MaxPermaCrops:
lessRepresentedCrops = [c for c in CropType if x.representativity[c] == min(crops_representativity)]
priority = [(x.indexWR[c] * (1 - data.plants[c]['ratioADAPT'])) / 2 for c in lessRepresentedCrops]
x.SelectedCrop = [c for c in lessRepresentedCrops if (
x.indexWR[c] * (1 - data.plants[c]['ratioADAPT'])) / 2 == max(priority)][0]
x.CHOICE[PRA].append((
'Permanent crop (not in country, greater rarity and water indexes',
len(CropType)))
break
#-------------------------------------------------------------------------------------------
elif [c for c in CropType if c not in crops_in_region] != []:
# if selected crops have a medium or good potential occurence and if some of them are still not used in the region:
CropType = [c for c in CropType if c not in crops_in_region]
both_CropType_AND_representativity = [c for c in CropType if c in x.representativity]
# Selecting already crops which have been chosen less than 'MaxPermaCrops'-times (to avoid too much permanent crops and also a loss of biodiversity):
both_CropType_AND_representativity = [c for c in both_CropType_AND_representativity if x.representativity[c] < MaxPermaCrops]
crops_representativity = [x.representativity[c] for c in both_CropType_AND_representativity]
#=======================================================================================
#AVOIDING DOUBLE OLIVE TREES IN REGIONS (mature and green)
olives_already_in_region = [c for c in crops_in_region if 'OLV' in c] != []
only_olives_are_available = [c for c in CropType if 'OLV' not in c] == []
if olives_already_in_region and only_olives_are_available:
break
else:
# =======================================================================================
if len(both_CropType_AND_representativity) == len(CropType) :
# if all crops have already been used
# The selected crop is the one with the lower representativity (in all case lower than MaxPermaCrops) :
lowestRepresentativity = [c for c in both_CropType_AND_representativity if (x.representativity[c] == min(crops_representativity) and x.representativity[c] < MaxPermaCrops )]
if lowestRepresentativity != [] :
priority = [(x.indexWR[c] * (1 - data.plants[c]['ratioADAPT'])) / 2 for c in
lowestRepresentativity]
x.SelectedCrop = [c for c in lowestRepresentativity if (
x.indexWR[c] * (1 - data.plants[c]['ratioADAPT'])) / 2 == max(priority) ][0]
x.CHOICE[PRA].append(('Permanent crop (not in region, already in country)', len(CropType)))
else: # if there are not enough representativity data
# we select only crops which have still never been chosen (not in x.representativity)
CropType = [c for c in CropType if c not in x.representativity]
priorityIndex = [PRIORITYfruits(c) for c in CropType if PRIORITYfruits(c) > 0]
#--> if there is an error in the priority assessment, it may be trees with a 0 priority....
RareCrops = [c for c in CropType if PRIORITYfruits(c) == min(priorityIndex)]
if len(RareCrops) > 1 :
priority = [(x.indexWR[c] * (1 - data.plants[c]['ratioADAPT'])) / 2 for c in
unusedPermanentCrops]
x.SelectedCrop = [c for c in CropType if (
x.indexWR[c] * (1 - data.plants[c]['ratioADAPT'])) / 2 == max(priority)][0]
x.CHOICE[PRA].append(
('Permanent crop (not in country, PRIORITYfruits + Water Resources)', len(CropType)))
elif len(RareCrops) > 0:
x.SelectedCrop = RareCrops[0]
x.CHOICE[PRA].append(('Permanent crop (not in country, PRIORITYfruits)', len(CropType)))
# else, these are crops with a good occurence possibility or they are already in the region:
# we pass, evaluate the next list and if there are no interesting crop, it will be a "normal rotation".
def ASSESS_SeedingDate(PRA, x):
"""INPUT :
* PRA is the ID of a current PRA. It allows to call the related data in the 'environment' dictionary.
* x is the class that contains all self variables used in all VegAu's functions
FUNCTION:
Fulfil the list 'edibleCrops' by keeping only the x.edible crops (from 'edibleCropsID') for which the
earliest seeding date is before the latest end of the previous crop's GS.
If no crop can be planted before or while the last month of the previous crop's GS (x.EndPreviousCrop_later),
the crops are sorted by the time between their seed_from(crop) and the x.EndPreviousCrop_later. Only the 2 shortest
durations are allowed for the crop to be added in the list 'edibleCrops'.
OUTPUT:
'edibleCrops' list with the x.edible crops (from 'edibleCropsID') for which the earliest seeding date
is before the latest end of the previous crop's GS
"""
# For the moment, excluding rhubarb (kind of permanent crop, complicated to use) and Orchardgrass
x.edibleCrops_init = [c for c in x.edibleCropsID[PRA] if c != 'RBRB' and c != 'CC-GRASSorchard' and prodCAT(c) != 1 and prodCAT(c) != 2]
# there is already a crop in the rotation AND that it is a NOT cover crop :
if 0 < len(x.rotat[PRA]) <= 2:
if prodCAT(x.rotat[PRA][1][0]) != 0 :
x.edibleCrops_init = [c for c in x.edibleCrops_init if prodCAT(c) != 1 and prodCAT(c) != 2]
# if there is already a selected crop which is no cover crop: --> [0] start, [1] first crop
elif len(x.rotat[PRA]) > 2:
x.edibleCrops_init = [c for c in x.edibleCrops_init if prodCAT(c) != 1 and prodCAT(c) != 2]
x.edibleCrops = list(x.edibleCrops_init)
#------------------------------------------------------------------------------------------------------------
x.GSstart = {}
x.indexDelay = {}
for crop in x.edibleCrops:
# assessing if the current crop can be planted before the end of the previous crop's GS.
# Else, it is deleted from the list 'edibleCrops'.
# ============================================================================
if prodCAT(crop) == 1 and prodCAT(crop) == 2:
# If the crop is a permanent crop, there is no "seeding date" so it is not deleted.
x.GSstart[crop] = int(seed_from(crop))
else:
earlierPlantingMonth = x.EndPreviousCrop_earlier % 12
laterPlantingMonth = x.EndPreviousCrop_later % 12
if earlierPlantingMonth == 0:
earlierPlantingMonth = 12
if laterPlantingMonth == 0:
laterPlantingMonth = 12
# ----------------------------------------------------------------------------
if laterPlantingMonth < earlierPlantingMonth:
no_plantation_delay = earlierPlantingMonth <= seed_from(crop) % 12 <= laterPlantingMonth + 12
else:
no_plantation_delay = earlierPlantingMonth <= seed_from(crop) % 12 <= laterPlantingMonth
# =============================================================================
seeding_season_starts_before_the_end_of_the_previous_crop = (earlierPlantingMonth) > seed_from(crop)
ends_after_its_shorter_GS_duration = earlierPlantingMonth <= round(seed_to(crop)) <= laterPlantingMonth
if no_plantation_delay :
x.indexDelay[crop] = 1
if earlierPlantingMonth < seed_from(crop):
x.GSstart[crop] = x.EndPreviousCrop_earlier + (seed_from(crop) - earlierPlantingMonth)
elif earlierPlantingMonth == seed_from(crop):
x.GSstart[crop] = x.EndPreviousCrop_earlier + 1
elif seeding_season_starts_before_the_end_of_the_previous_crop and ends_after_its_shorter_GS_duration :
x.indexDelay[crop] = 1
x.GSstart[crop] = x.EndPreviousCrop_earlier + 1
else:
# if there are permanent crops in the x.edibleCrops list, it does not matter when they begin to build leaves:
if prodCAT(crop) == 1 or prodCAT(crop) == 2:
x.indexDelay[crop] = 1
x.GSstart[crop] = seed_from(crop)
else:
x.edibleCrops = [x for x in x.edibleCrops if x != crop]
#=========================================================================================
# If there are no x.edible crops for which the earliest planting date do not match with no one of the earliest
# and latest end of the previous crop's GS, the 'edibleCrops' list is empty. -> every crop have been deleted.
if x.edibleCrops == [] or len(x.edibleCrops) < 3:
if x.edibleCrops == [] :
print(""" No seeding date of any edible crop matches exactly with the end of the previous crop:
Looking for the shortest delay among seeding dates of the PRA's edible crops...""")
else:
print(""" Only {} crops could be planted without a delay ({}).
Looking for the shortest delay among seeding dates of other PRA's edible crops...""".format(len(x.edibleCrops), x.edibleCrops))
# restoring the 'edibleCrops' list
x.laterCrops = list(x.edibleCrops_init)
# creating a new dictionary which bounds the crops indexes to the duration btw their earliest seeding date
# and the earliest end date of the previous crop's GS:
SelectEarlierPlanting = {}
for crop in x.laterCrops:
SelectEarlierPlanting[crop] = x.EndPreviousCrop_earlier + abs( seed_from(crop) - laterPlantingMonth )
# selecting the both earliest planting dates among the crops from the 'SelectEarlierPlanting' dictionary:
PlantingDate_1 = sorted(list(set(SelectEarlierPlanting.values())))[0]
PlantingDate_2 = sorted(list(set(SelectEarlierPlanting.values())))[1]
delay1 = [c for c in x.laterCrops if SelectEarlierPlanting[c] == PlantingDate_1 and c not in x.edibleCrops]
delay2 = [c for c in x.laterCrops if SelectEarlierPlanting[c] == PlantingDate_2 and c not in x.edibleCrops]
print("delay1 = ", delay1)
print("delay2 = ", delay2)
x.laterCrops = [] # this dict will be used in SELECT_CashCrop --> priority to crops that are not in this dict (no delay).
for crop in SelectEarlierPlanting.keys():
# adding ID of the crops for which the earliest planting date are the earliest ones among the other PRA's x.edible crops:
if len(delay1) + len(x.edibleCrops) > 5 and crop in delay1:
x.laterCrops.append( (crop, PlantingDate_1) )
x.GSstart[crop] = PlantingDate_1
if PlantingDate_1 > x.EndPreviousCrop_later:
x.indexDelay[crop] = 1 - ((PlantingDate_1 - x.EndPreviousCrop_later%12) / 12)
else :
x.indexDelay[crop] = 1 - ((PlantingDate_1 + 12 - x.EndPreviousCrop_later%12) / 12)
# if there are less than 5 crops in x.edibleCrops + delay1, we add delay2
if len(delay2) + len(x.edibleCrops) < 5 and crop in delay2:
x.laterCrops.append((crop, PlantingDate_2))
x.GSstart[crop] = PlantingDate_2
if PlantingDate_1 > x.EndPreviousCrop_later:
x.indexDelay[crop] = 1 - ((PlantingDate_2 - x.EndPreviousCrop_later%12) / 12)
else :
x.indexDelay[crop] = 1 - ((PlantingDate_2 + 12 - x.EndPreviousCrop_later%12) / 12)
# if there was no edible crop for this season, we add delayed crops
if x.edibleCrops == []:
x.rotat[PRA].append( ('Plantation delay', None, PlantingDate_1) )
x.edibleCrops = [c for (c, delay) in x.laterCrops]
# if there was just 1 or 2 crop in x.edibleCrops, we add delayed crops to x.edibleCrops
else :
for crop in [c for (c, delay) in x.laterCrops if c not in x.edibleCrops] :
# 'not in x.edibleCrops' avoids duplicates which create errors in the ASSESS_Nutrient function.
x.edibleCrops.append(crop)
print(x.laterCrops)
print(""" {} edible crops can be planted without any delay : {}""".format(len(x.edibleCrops),
x.edibleCrops))
print(""" Next later crops are : {}""".format ([c for (c, delay) in x.laterCrops]))
else:
print(""" {} edible crops can be planted without any delay : {}""". format( len(x.edibleCrops), x.edibleCrops))
#================================================================================================================
#================================================================================================================
def ASSESS_WaterResources(PRA, x):
"""INPUT :
* PRA is the ID of a current PRA. It allows to call the related data in the 'environment' dictionary.
* x is the class that contains all self variables used in all VegAu's functions
---> x.edibleCrops (list), x.GSstart[crop] (int), x.GSstart (int), x.TOLERdrought/-flood (int)
FUNCTION:
This function compares the quality of the Water Resources provided by the current PRA for each x.edible crop.
If the PRA's monthly minimum Temperatures are lower than Tmin(crop) while at least one month in GSmin(crop), the crop is eliminated.
OUTPUT:
x.indexWR (dict) with a standardized WReval index: the lower the index value, the worse the WaterResources conditions.
"""
x.TOLERdrought = 0
x.TOLERflood = 0
x.indexWR = {}
#=============================================================================================================
# loop for the Tmin(crop) Assessement (deletes all crops for which the PRA's Temperature is too cold for at least one month in GS_min :
for crop in x.edibleCrops:
month = int(x.GSstart[crop])
CORR_TOLERdf(crop, x) # cf 'Functions_step1.py' --> converting the TOLERdrought(crop)/flood into percentages of ETc
last_month_of_the_shortest_growing_season = x.GSstart[crop] + GSmin(crop)
while month <= last_month_of_the_shortest_growing_season: # while the month of rotation is in the potential GS of the current crop
if TminMOY(month, PRA) < Tmin(crop):
x.edibleCrops = [x for x in x.edibleCrops if x != crop]
month += 1
if x.edibleCrops == []:
# taking x.edibleCrops_init as a reference for the initial x.edibleCrops selection (without trees and other crops)
# updating the earlier and later dates for the begin of a new growing season with a later crop :
later_dates = [abs(seed_from(c) - x.EndPreviousCrop_later % 12) for c in x.edibleCrops_init if abs(seed_from(c) - x.EndPreviousCrop_later % 12) != 0]
delay = sorted(list(set(later_dates)))[0]
x.EndPreviousCrop_earlier = int(x.EndPreviousCrop_later + delay)
x.EndPreviousCrop_later = int(x.EndPreviousCrop_earlier + 2)
x.rotat[PRA].append(('Cold season', None, x.EndPreviousCrop_earlier))
x.no_delay_because_of_T_or_water = False
# raise ColdSeason("The rotation must be delayed: Temperature does not match with the edible PRAs.")
else:
print("[{}][{}] The PRA's Tmin matches the crop's one for following crops : {}".format(PRA, x.EndPreviousCrop_later, x.edibleCrops))
# =============================================================================================================
# loop for the Water Requirement Assessement :
for crop in x.edibleCrops:
month = int(x.GSstart[crop])
last_month_of_the_shortest_growing_season = x.GSstart[crop] + GSmin(crop) - 1
x.indexWR[crop] = 0
while month <= last_month_of_the_shortest_growing_season: # while the month of rotation is in the potential GS of the current crop
if round(WRmargin_GSmin(crop, month, x, PRA), 2) > 0:
x.indexWR[crop] += round(WRmargin_GSmin(crop, month, x, PRA), 3) # summing the margins
else: # if the PRA's WR are lower than or equal to the drought threshold
# -> this crop is deleted from the dictionary
x.edibleCrops = [x for x in x.edibleCrops if x != crop]
del x.indexWR[crop]
break # close the loop for this crop -> back to the 'for-loop' without incrementing 'month'
month += 1
# for crop in x.indexWR:
# x.indexWR[crop] = round(x.indexWR[crop] / GSmin(crop), 3)
# if round(x.indexWR[crop], 2) == 0.00:
# x.edibleCrops = [x for x in x.edibleCrops if x != crop]
# new iteration to avoid the error : RuntimeError: dictionary changed size during iteration
# deleted_crops = [c for c in x.indexWR if crop not in x.edibleCrops]
# for crop in deleted_crops:
# del x.indexWR[crop]
# ===============================================================================================================
# Standardization : dividing all crop's values by this max to get an index between 0 and 1 with 1 = best conditions.
# /!\ STANDARDIZATION FALSIFIED THE YIELD CALCULATION ACC. TO WATER RESOURCES !!!
# indexWR must stay as caluclated in the original function !!
for crop in x.indexWR:
x.indexWR[crop] = round(x.indexWR[crop] / max(x.indexWR.values()), 4)
print(""" Standardization of the Water Resources indices [OK]""")
# at the end of these both loops, we get an 'indexWR' dictionary with a "WaterResources evaluation" (WReval)
# for each edible crop. -> helps to compare, at the end, with the remaining crops.
print("[{}][{}] Water Resources verified. Following crops remain : {}.".format(PRA, x.EndPreviousCrop_later, x.edibleCrops))
if x.edibleCrops == []:
# taking x.edibleCrops_init as reference list and looking for later crops:
later_dates = [abs(seed_from(c) - x.EndPreviousCrop_later%12) for c in x.edibleCrops_init]
if sorted(list(set(later_dates)))[0] != 0:
delay = sorted(list(set(later_dates)))[0]
else:
delay = sorted(list(set(later_dates)))[1]
x.EndPreviousCrop_earlier = x.EndPreviousCrop_later + delay # nearest date from the previous 'x.EndPreviousCrop_later'.
x.EndPreviousCrop_later = int( x.EndPreviousCrop_earlier + 2)
# there is a hole in the rotation:
x.rotat[PRA].append( ('Dry season', None, x.EndPreviousCrop_earlier) )
x.no_delay_because_of_T_or_water = False
# raise DrySeason("The rotation must be delayed: the season is too dry.")
#================================================================================================================
#================================================================================================================
def VERIF_lastCrops_not_CC(x, PRA, nutrient):
"""Verifying if the last 4 crops of the rotation (cf x.rotat) are not Cover Crops.
Else, the rotation is stoped."""
# If there is already 4 crops in the rotation, if all edible cash crops are removed and
# the 3 last crops are already cover crops, the limiting factor has been reached :
if nutrient == None:
nutrient = 'Last 4 crops are Cover Crops'
if len(x.rotat[PRA]) > 5:
last_crops = [c for (c, companion, date) in x.rotat[PRA] if (c != 'start' and c != 'Limiting factor' and c != 'Dry season' and c != 'Plantation delay' and 'season' not in c)]
if len(last_crops) >= 4:
i = int(len(last_crops))
# Selecting the last 4 crops of the rotation :
last_crops = [ last_crops[i - 4], last_crops[i - 3], last_crops[i - 2], last_crops[i-1] ]
# if they are all Cover Crops, their "production Category" will be 0, so their average will also be 0:
last_crops_are_CC = (((prodCAT(last_crops[0]) + prodCAT(last_crops[1]) + prodCAT(
last_crops[2]) + prodCAT(last_crops[3])) / 4)) == 0
if last_crops_are_CC:
# if the only crops in the x.edibleCrops list are CoverCrops, rotation is broken.
if [c for c in x.edibleCrops if prodCAT(c) != 0] != [] :
x.rotat[PRA].append(('Limiting factor', nutrient, x.edibleCrops))
x.LimitingFactorReached = True
# if the only crops in the x.edibleCrops list are CoverCrops, rotation is broken too.
# (we assume that only other cover crops will follow)
else:
x.rotat[PRA].append(('Limiting factor', 'Last 4 crops are Cover Crops', x.edibleCrops))
x.LimitingFactorReached = True
#================================================================================================================
def ASSESS_NutrientsMargin(PRA, x):
"""INPUT :
* crop is the ID of a current crop. It allows to call the related data in the 'plants' dictionary.
* PRA is the ID of a current PRA. It allows to call the related data in the 'environment' dictionary.
* x is the class that contains all self variables used in all VegAu's functions
FUNCTION:
This function uses the dictionary x.decomposition_month. It contains the nutrient amount which should be released
to the soil after the death of each crop of the rotation. Its keys correspond to the duration (in month) after
which the nutrients are released, that is to say added to the PRA soil resources. For each month of the rotation,
the values of each month are switched to the key "month - 1" and the nutrients that was referenced in the key 1
are added to the PRA soil nutrients.
It returns a list with the nutrient margin of each crop from x.edibleCrops that have ONLY POSITIVE
nutrient margins.
It deletes also from x.edibleCrops every crop that would require more nutrients than the soil can provide.
OUTPUT:
* x.indexNutrients
* updated x.edibleCrops (only edible crops)
"""
for crop in x.edibleCrops:
x.indexNutrients[crop] = {}
removed = {}
#---------------------------------------------------
#-- Setting up the dict 'removed' :
removed = {"N": removedN(crop), "P": removedP(crop), "K": removedK(crop), "Na": removedNa(crop),
"Mg": removedMg(crop), "Ca": removedCa(crop), "Mn": removedMn(crop), "Fe": removedFe(crop),
"Cu": removedCu(crop)}# Organic Matter ('OM' cannot be removed !)
nutrients = [x for x in sorted( removed.keys() )]
#---------------------------------------------------
for nutrient in nutrients:
x.indexNutrients[crop][nutrient]=[]
#-------------------------------------------------------------------------------------------------------------
monthInGS = 1
while monthInGS <= GSmin(crop):
#---------------------------------------------------------------------------------------------------------
# if the nutrient amount in the PRA's soil is not sufficient while GSmin(crop),
# the Selected Companion Crop is not considered as x.edible anymore:
if nutrient == 'N' :
margin = x.ActualStand[PRA][nutrient] + x.decomposition_month[monthInGS][nutrient] - ((removed[nutrient] - fixedN(crop)) /GSmin(crop) )
else :
margin = x.ActualStand[PRA][nutrient] + x.decomposition_month[monthInGS][nutrient] - (removed[nutrient]/GSmin(crop) )
# ---------------------------------------------------------------------------------------------------------
if margin < 0 :
print(" /!\ Not enough {} to grow {}. [DELETED FROM x.edibleCrops]".format(nutrient, prod_EN(crop)))
if nutrient not in x.LimitingFactor[PRA]:
x.LimitingFactor[PRA].append(nutrient)
if crop not in x.LimitingFactor_crops:
x.LimitingFactor_crops[crop] = []
else :
x.LimitingFactor_crops[crop].append(nutrient)
del x.indexNutrients[crop]
x.edibleCrops = [x for x in x.edibleCrops if x!= crop]
if x.edibleCrops == []:
x.rotat[PRA].append(('Limiting factor', x.LimitingFactor[PRA], x.EndPreviousCrop_later))
x.LimitingFactorReached = True
else:
# Verifying if the 4 last crops are not cover crops (if they are, the rotation is closed) :
VERIF_lastCrops_not_CC(x, PRA, nutrient)
break
# ---------------------------------------------------------------------------------------------------------
else:
# average removed and fixed nutrient for one month of GSmin(crop):
x.indexNutrients[crop][nutrient].append(margin)
#END if (margin not negative) --------------------------------------------------------------------------
monthInGS += 1
#END while (end of GSmin) ----------------------------------------------------------------------------------
if crop in x.indexNutrients.keys():
x.indexNutrients[crop][nutrient] = round( sum(x.indexNutrients[crop][nutrient]) / len(x.indexNutrients[crop][nutrient]), 4)
else:
break
#END for (nutrient in x.indexNutrients[crop].keys())-------------------------------------------------------------
#END for (crop in x.edibleCrops)--------------------------------------------------------------------------------------
print(" Nutrient Margin Assessment [OK]")
#================================================================================================================
def ASSESS_Nutrients(x, PRA):
"""INPUT :
* crop is the ID of a current crop. It allows to call the related data in the 'plants' dictionary.
* x is the class that contains all self variables used in all VegAu's functions
* PRA is the ID of a current PRA. It allows to call the related data in the 'environment' dictionary.
FUNCTION:
This function selects the crops for which there is enough nutrients in the PRA (updates the 'edibleCrops' list).
It updates also the 'edibleCropsSN' dictionary ('SN' for 'Soil Nutrients') with:
* keys = prodID
* values = standardized nutrients margin: the lower the index value, the less nutrients in the soil after the crop.
"""
x.indexNutrients = {}
ASSESS_NutrientsMargin(PRA, x)
# after this function, x.indexNutrients contains a key for each crop of edibleCrop for which there is POSITIVE nutrient margins
# x.edibleCrops is also updated (crops with negative nutrient margins have been deleted.
# Margin standardization, first step (finding the maximum NutrientMargin for each nutrient
# -> dividing all by max and get a percentage with 1 the higher value):
if x.LimitingFactorReached == False :
MAXvalue = {'N': [], 'P': [], 'K': [], 'Na': [], 'Mg': [], 'Ca': [], 'Mn': [], 'Fe': [], 'Cu': []}
for crop in x.indexNutrients:
#----------------------------------------------------------------------------------------------
for nutrient in MAXvalue.keys():
MAXvalue[nutrient].append( x.indexNutrients[crop][nutrient] )
#END for --------------------------------------------------------------------------------------
for nutrient in MAXvalue.keys(): MAXvalue[nutrient] = max(MAXvalue[nutrient])
# Margin standardization, second step (dividing by the MAXvalue to get a percentage):
for crop in x.indexNutrients:
#----------------------------------------------------------------------------------------------
for nutrient in x.indexNutrients[crop].keys():
x.indexNutrients[crop][nutrient] /= MAXvalue[nutrient]
#END for ----------------------------------------------------------------------------------
# calculating the average of all standardized nutrient margin to get an index between 0 and 1 :
x.indexNutrients[crop] = round( sum( list(x.indexNutrients[crop].values()) ) / len(x.indexNutrients[crop]), 3)
#END for --------------------------------------------------------------------------------------
# END if (x.LimitingFactorReached == False )
#================================================================================================================
#================================================================================================================
def ASSESS_PestDiseases(x, PRA):
"""INPUT :
* crop is the ID of a current crop. It allows to call the related data in the 'plants' dictionary.
* x is the class that contains all self variables used in all VegAu's functions
OUTPUT:
Index according to the risks of pests and diseases relative to a too short period between several crops
of a same botanic family.
"""
x.indexPnD = {} # cleaning up the dict from previous calculations
for crop in x.edibleCrops:
if prodBOT(crop) not in x.VERIFprodBOT.keys():
x.indexPnD[crop] = 1
elif int(period(crop)) == 0: # if prodBOT == 0, ZeroDivisionError : cover crops and trees have no return period
x.indexPnD[crop] = 1
else:
#-----------------------------------------------------------------------------------------------------------
try:
# x.rotat[-2][-1] should correspond to the beginning of the GS of the previously selected crop,
# or most exactly the end of the previously selected crop
growing_season_of_the_last_crop = int(x.GSstart[crop] - x.rotat[PRA][-2][-1])
except :
if x.SelectedCrop != None :
growing_season_of_the_last_crop = GSmax(x.SelectedCrop)
else:
growing_season_of_the_last_crop = 0
# -----------------------------------------------------------------------------------------------------------
duration_since_previous_crop = x.VERIFprodBOT[prodBOT(crop)]['Duration since previous crop'] + growing_season_of_the_last_crop
# Notice that duration_since_previous_crop is in months when period is in years :
x.indexPnD[crop] = (duration_since_previous_crop /12 )/ period(crop)
# theoritically, if x.indexPnD[prodBOT(crop)] >= 1, no risk -> the highest the ratio, the better the conditions (like the other indexes, important)
if x.indexPnD[crop] > 1 :
x.indexPnD[crop] = 1
if max(x.indexPnD.values()) >= 0.4:
for crop in set(x.edibleCrops): # --> set to avoid bogs because of double entries
# if the pests and diseases risk is too high (60% of yield reduction), the crop is deleted from the list :
if x.indexPnD[crop] <= 0.4 :
del x.indexPnD[crop]
x.edibleCrops = [c for c in x.edibleCrops if c != crop]
else:
for crop in set(x.edibleCrops): # --> set to avoid bogs because of double entries
# if the pests and diseases risk is too high (60% of yield reduction), the crop is deleted from the list :
if x.indexPnD[crop] < max(x.indexPnD.values()) :
del x.indexPnD[crop]
x.edibleCrops = [c for c in x.edibleCrops if c != crop]
# Standardization :
for crop in x.indexPnD:
if x.indexPnD[crop] != 1: # x.indexPnD[crop] has already been set to 1 because there have no Pests and
# Diseases risks to rotation occurrence (trees, shrubs and CC: no rotation !)
try:
x.indexPnD[crop] = x.indexPnD[crop] / max(x.indexPnD.values())
except ZeroDivisionError: # if the bigger value of x.indexPnD, it means that there is no risk
x.indexPnD[crop] = 1
#the obtained values are yet all comprised between 1 and 0, with 1 the very best one.
#================================================================================================================
#================================================================================================================
def SELECT_CashCrop(x, PRA, data):
"""INPUT:
* x is the class that contains all self variables used in all VegAu's functions
FUNCTION :
Selecting a crop among the 'x.edibleCrops' list according to the indices from the lists :
' x.indexWR', 'edibleCropsSN' and 'indexPnD'
OUTPUT :
* crop ID of the Selected crop
* the list 'x.edibleCompanionCrops' with theses from the edible crops that are cover crops or companion crops
"""
Final_Edibility_Index = {}
edibleCashCrops = []
x.edibleCompanionCrops = []
edibleCoverCrops = [] # not a self variable because a cover crop is only selected in this function
# IF the N resources are too low (< 120 kg/ha) AND if there are available cover
# crops for this season. In this only case, x.edibleCrops becomes edibleCoverCrops.
# Crossing the Water Requirement, Soil Nutrients and Pest&Diseases indexes (between 0 and 1 with 1 corresponding to the best conditions)
# Because de Pest&Diseases assessment did not suppress any crop of the list but because it has a critical biological importance,
# this index is ponderated by 2 against 1 for the others.
SelectionDone = False
x.edibleCompanionCrops = [c for c in x.edibleCrops if prodTYP(crop)=='Companion crop']
x.edibleCoverCrops = [c for c in x.edibleCrops if prodTYP(crop) == 'Companion crop' or prodTYP(crop) == 'Cover crop']
x.edibleCashCrops = [c for c in x.edibleCrops if prodTYP(crop) != 'Companion crop' and prodTYP(crop) != 'Cover crop']
#===================================================================================================================
#== FIRST PART : SELECTING THE TYPE OF CROP ACCORDING TO THE SOIL SUPPLIES IN N AND THEIR OCCURENCE IN THE =========
#== ROTATION(S) AT LOCAL AND COUNTRY SCALE ============================================================
#===================================================================================================================
if x.ActualStand[PRA]['N'] < 120 and len(edibleCoverCrops) > 0: # if N-ressources are lower than 120 kg/ha
if len(edibleCoverCrops) == 1:
x.SelectedCrop = edibleCoverCrops[0]
SelectionDone = True
elif len(edibleCoverCrops) >= 1:
x.SelectedCrop = edibleCoverCrops[0]
x.CHOICE[PRA].append(('Cover crop (insufficient nutrients)', len(edibleCoverCrops)))
#===================================================================================================================
# Selecting the best(s) crop for following the preceding one:
else:
edible_permanent_crops = [c for c in x.edibleCrops if prodCAT(c) == 1 or prodCAT(c) == 2]
if edible_permanent_crops != []:
VERIF_TreesInRegion(PRA, x, data)
if x.SelectedCrop != None:
if prodCAT(x.SelectedCrop) == 1 or prodCAT(x.SelectedCrop) == 2:
# if the x.SelectedCrop is a tree or a shrub, it has been selected in the previous step
SelectionDone = True
if SelectionDone == False:
# deleting permanent crops from x.edibleCrops:
x.edibleCrops = [c for c in x.edibleCrops if prodCAT(c) != 1 or prodCAT(c) != 2]
rotation = [c for (c, companion, date) in x.rotat[PRA] if (c != 'start' and c != 'Limiting factor' and 'delay' not in c and 'season' not in c)]
# Note : 'delay' not in c allows to add further informations about de delay in rotat.
delay = [c for (c, delay) in x.laterCrops if ( prodCAT(c) != 1 and prodCAT(c) != 2 )]
unusedCrops = [c for c in edibleCashCrops if ( c not in rotation and prodCAT(c) != 1 and prodCAT(c) != 2 )]
unusedCrops_countryScale = [c for c in unusedCrops if c not in x.totalYields["TOTAL"] and c not in delay]
# ------------------------------------------------------------------------------------------
# if there is only one edible crop left, it is the selected crop
if len(x.edibleCrops) == 1:
x.CHOICE[PRA].append(('x.edibleCrops', len(x.edibleCrops))) # Theoretically, ('x.edibleCrops', 1)
x.SelectedCrop = x.edibleCrops[0]
SelectionDone = True
# else, if there are in the selection some unused crops at the local AND country scale (without delay),
# they have priority
elif unusedCrops_countryScale != []:
x.CHOICE[PRA].append(('unusedCrops_countryScale', len(unusedCrops_countryScale)))
if len(unusedCrops_countryScale) == 1:
x.SelectedCrop = unusedCrops_countryScale[0]
SelectionDone = True
else:
x.edibleCrops = unusedCrops_countryScale
# else, if there are some unused crops in the selection, they have priority
elif unusedCrops != [] :
# Giving priority to unused crops :
if len(unusedCrops) == 1:
x.CHOICE[PRA].append(('unusedCrops', 1))
x.SelectedCrop = unusedCrops[0]
SelectionDone = True
else:
# x.edibleCrops = unusedCrops
unusedCrops_without_delay = [c for c in unusedCrops if c not in delay]
unusedCrops_countryScale_delay = [c for c in unusedCrops if c not in x.totalYields["TOTAL"]]
unusedCashCrops = [c for c in edibleCashCrops if c in unusedCrops]
# if there are unused crops without delay, they have priority
if unusedCrops_without_delay != []:
x.CHOICE[PRA].append( ('unusedCrops_without_delay', len(unusedCrops_without_delay)) )
if len(unusedCrops_without_delay) == 1:
x.SelectedCrop = unusedCrops_without_delay[0]
SelectionDone = True
else:
x.edibleCrops = unusedCrops_without_delay
else:
# if there are no unused crop without delay, delayed crops that have
# not been used at the country scale have priority:
if unusedCrops_countryScale_delay != []:
x.CHOICE[PRA].append( ('unusedCrops_countryScale', len(unusedCrops_countryScale_delay)) )
if len(unusedCrops_countryScale) == 1:
x.SelectedCrop = unusedCrops_countryScale_delay[0]
SelectionDone = True
else:
x.edibleCrops = unusedCrops_countryScale_delay
# if all edible crops have already been used at the country scale,
# we consider all unused cash crops (at the local scale).
elif unusedCashCrops != [] :
x.CHOICE[PRA].append( ('unusedCashCrops', len(unusedCashCrops)) )
if len(unusedCashCrops) == 1:
x.SelectedCrop = unusedCashCrops[0]
SelectionDone = True
else:
x.edibleCrops = unusedCashCrops
# if there is more than one crop in the edible crop list but that they are all already in the rotation
# cash crops have priority
elif edibleCashCrops != []:
x.CHOICE[PRA].append(('edibleCashCrops', len(edibleCashCrops)))
if len(edibleCashCrops) == 1:
x.SelectedCrop = edibleCashCrops[0]
SelectionDone = True
else:
x.edibleCrops = edibleCashCrops
# if all edible crops have already been used and that there is no cash crop,
# there are only cover crops left.
else:
x.CHOICE[PRA].append(('edibleCoverCrops', len(edibleCoverCrops)))
if len(edibleCoverCrops) == 1:
x.SelectedCrop = edibleCoverCrops[0]
SelectionDone = True
else:
x.edibleCrops = edibleCoverCrops # theoretically, at this point, x.edibleCrops is already the same as edibleCoverCrops
#===================================================================================================================
#= SECOND PART : IF WE HAVE TO CHOSE AMONG SEVERAL CROPS, WE USE THE PREVIOUSLY CALCULATED INDICES =================
# ==================================================================================================================
if SelectionDone == False:
#====================================================================================================
# Excluding crops with the highest representativity to keep a relative equilibrium
if [c for c in x.edibleCrops if c not in x.representativity] != [] :
# if there are still crops that have not been used at a country scale, making sure that they are the only one to stay:
x.edibleCrops = [c for c in x.edibleCrops if c not in x.representativity]
else:
# if all crops have been used at the country scale, chosing the one(s) with the lowest occurence:
CropsUsedAtCountryScale = [x.representativity[c] for c in x.representativity if c in x.edibleCrops]
if CropsUsedAtCountryScale != []:
minRepresentativity = min([x.representativity[c] for c in x.representativity if c in x.edibleCrops])
x.edibleCrops = [c for c in x.edibleCrops if x.representativity[c] == minRepresentativity]
# ====================================================================================================
if len(x.edibleCrops) == 1 :
x.SelectedCrop = x.edibleCrops[0]
else:
for crop in x.edibleCrops :
# to be comparable to the other indexes, the higher value must be the better one: the lower the
# territorial representativity, the bigger the chance for the crop to be chosen
# ----------------------------------------------------------------------------------------------------------
Final_Edibility_Index[crop] = round(((x.indexDelay[crop] + x.indexWR[
crop] + 0.5 * x.indexNutrients[crop] + 2 * x.indexPnD[crop]) / 4.5), 2)
# ----------------------------------------------------------------------------------------------------------
FinalSelection = []
# =======================================================================================================
for crop in x.edibleCrops:
if Final_Edibility_Index[crop] == max( Final_Edibility_Index.values() ):
FinalSelection.append(crop)
if len(FinalSelection) == 1:
x.SelectedCrop = FinalSelection[0]
#-----------------------------------------------
else: # considering the general priority index :
priority = [ data.plants[crop]['PRIORITYgeneral'] for crop in FinalSelection ]
FinalSelection = [x for x in FinalSelection if data.plants[x]['PRIORITYgeneral'] == max(priority)]
if len(FinalSelection) == 1:
x.SelectedCrop = FinalSelection[0]
# -----------------------------------------------
else: # considering the priority index for fruits :
priority = [data.plants[crop]['PRIORITYfruits'] for crop in FinalSelection]
FinalSelection = [x for x in FinalSelection if data.plants[x]['PRIORITYfruits'] == max(priority)]
if len(FinalSelection) == 1:
x.SelectedCrop = FinalSelection[0]
# -----------------------------------------------
else: # considering the priority index for textiles :
priority = [data.plants[crop]['PRIORITYtextile'] for crop in FinalSelection]
FinalSelection = [x for x in FinalSelection if data.plants[x]['PRIORITYtextile'] == max(priority)]
if len(FinalSelection) == 1:
x.SelectedCrop = FinalSelection[0]
# -----------------------------------------------
else:
# At least, choosing the crop with the lowest ratioADAPT (crop which have the lesser geographic adaptability in the study area, here France)
priority = [data.plants[crop]['ratioADAPT'] for crop in FinalSelection]
FinalSelection = [x for x in FinalSelection if data.plants[x]['ratioADAPT'] == min(priority)]
x.SelectedCrop = min(FinalSelection)
print(" The selected crop is : {} ({}).".format(prod_EN(x.SelectedCrop), prodID(x.SelectedCrop)))
if x.SelectedCrop not in x.representativity:
x.representativity[x.SelectedCrop] = 1
else:
x.representativity[x.SelectedCrop] += 1
# =========================================================================================================
# UPDATING TEMPORAL AND ANALYTICAL VARIABLES :
# x.GSstart becomes an integer and corresponds to the optimal seeding date of the SelectedCrop
if x.SelectedCrop in x.GSstart : # is the crop is a permanent one, it is not in GSstart
x.GSstart = int( x.GSstart[x.SelectedCrop] )
else:
x.GSstart = int( seed_from(x.SelectedCrop) )
UPDATE_VERIFprodBOT_and_PestsDiseases_in_rotation(PRA, x)
# /!\ THIS FUNCTION NEEDS THE x.EndPreviousCrop_earlier/later from the actual PREVIOUS crop !
# --> TO DO BEFORE UPDATING THESE VALUES !
# This function creates an entry in x.VERIFprodBOT for the newly selected crop if there is no one in the dictionary
# and verifies if the minimum return period is respected.
# * If respected : no Pest and Diseases malus
# * If not respected : +1 for this crop in the dict 'PestsDiseases_in_rotation'.
# In both cases, the 'Duration since previous crop' returns to 0 (because it is yet the 'previous crop' for the potential next ones).()
# indicating the earlier and the later month for the end of this crop for the next one:
# this is calculated before the next crop to capture the GStot values of this crop and not the next one.
# (for the next loop, the crop search occurs from the earlier to the later end of the SelectedCrop's GS)
if prodCAT(x.SelectedCrop) != 1 and prodCAT(x.SelectedCrop) != 2:
x.EndPreviousCrop_earlier = int(x.GSstart + GSmin(x.SelectedCrop))
x.EndPreviousCrop_later = int(x.GSstart + GSmax(x.SelectedCrop))
else :
x.EndPreviousCrop_earlier += 12
x.EndPreviousCrop_later += 12
# =========================================================================================================
# UPDATING x.rotat :
if x.PreviouslySelectedCrop != None:
if x.SelectedCrop not in [c for (c, delay) in x.laterCrops]:
# each time that a crop is selected, the given harvesting date is it's later one because
# we don't know when the next crop will be planted.
# ---> Before to add the actual crop's informations, we put right the previous one.
del x.rotat[PRA][-1]
x.rotat[PRA].append(
(x.PreviouslySelectedCrop, x.SelectedCC, x.GSstart - 1))
# GSstart is the first month of the actual crop's GS, so the previous one ends one month before.
# Now, we add: (actually selected crop, still no companion crop selected, later end of the GS)
x.rotat[PRA].append(
(x.SelectedCrop, None, x.EndPreviousCrop_later))
else: # If we have a delay to take into account :
# the later end of its GS stays the later one, actually : (Previous Crop, None, x.EndPreviousCrop_later))
#--> no change for the last entry
# the delay is specified:
x.rotat[PRA].append(('Plantation delay', None, x.GSstart - 1))
# And than we add the new crop:
x.rotat[PRA].append(
(x.SelectedCrop, None, x.EndPreviousCrop_later))
else:
x.rotat[PRA].append(('start', None, 3))
x.rotat[PRA].append( (x.SelectedCrop, None, x.EndPreviousCrop_later) )
# =========================================================================================================
# == YIELD CALCULATION =====================================================================================
print("[{}][{}] Calculating the yield for the Selected Crop...".format(PRA, x.EndPreviousCrop_later))
# adapting the surface of the study area
YIELD = expYIELD(x.SelectedCrop) * PRAsurface(PRA)
# adapting the yield proportionnaly to the Water Resources quality and to the Pests and Diseases risks (ratio).
YIELD *= x.indexWR[x.SelectedCrop] * x.indexPnD[x.SelectedCrop]
#----------------------------------------------------------------------------------------------------------
if x.SelectedCrop in x.totalYields[PRA]:
x.totalYields[PRA][x.SelectedCrop] += round(YIELD, 3) # rounding with 3 decimals to lighten the dict
else:
x.totalYields[PRA][x.SelectedCrop] = round(YIELD, 3)
# Notice: Yields are in tons, not in t/ha anymore ! -> preparation for the assessment of the nutritional requirements
# =======================================================================================================
# =======================================================================================================
# Taking into account the WaterRequirement on the GS and the Next Crop -> adapting x.EndPreviousCrop_later
print("[{}][{}] Adapting the later selected crop's harvest date (actually {}) according to the PRA's water ressources...".format(PRA, x.EndPreviousCrop_later, MonthID(x.EndPreviousCrop_later)))
month = int(x.EndPreviousCrop_earlier)
CORR_TOLERdf(x.SelectedCrop, x)
while month < x.EndPreviousCrop_later:
#----------------------------------------------------------------------------------------------------
water_resources_are_sufficient = ETc_GSmax(x.SelectedCrop, month, x, PRA) * x.TOLERdrought < WaterResources(
month, x.GSstart, PRA, x.SelectedCrop, x)
# ----------------------------------------------------------------------------------------------------
if water_resources_are_sufficient:
pass
else:
x.EndPreviousCrop_later = int(month) -1
break
month += 1
print("[{}][{}] The later harvesting date is in {}.".format(PRA, x.EndPreviousCrop_later, MonthID(month)))
#================================================================================================================
#================================================================================================================
def ASSESS_Water_CompanionCrop(x, PRA):
"""INPUT :
* crop is the ID of a current crop. It allows to call the related data in the 'plants' dictionary.
* x is the class that contains all self variables used in all VegAu's functions
* data is the class that contains the original 'plants' and 'environment' data bases
from 'input[COUNTRY].py' (e.g. 'inputFR.py' for France).
FUNCTION:
Assessing if the x.SelectedCrop tolerates a CompanionCrop according to its WaterRequirement and the PRA's WaterResources"""
if x.edibleCompanionCrops != [] :
print("[{}][{}] Looking for an eventual Companion Crop...".format(PRA, x.EndPreviousCrop_later))
# WaterResources_are_Sufficient = True
maximum_growing_season_duration = GSmax(x.SelectedCrop)
GS1_4 = round(maximum_growing_season_duration * 0.25 )
GS2_4 = round(maximum_growing_season_duration * 0.50 )
GS3_4 = round(maximum_growing_season_duration * 0.75 )
# while WaterResources_are_Sufficient:
if prodCAT(x.SelectedCrop) != 0 or prodCAT(x.SelectedCrop) != 5: # If the Selected Crop is neither a CoverCrop nor a vegetable Legume,
for crop in x.edibleCompanionCrops:
CurrentMonth = x.GSstart % 12
i = 1
while i <= GSmax(crop):
#= Determining the current stage of the GS =============================================
if i <= GS1_4 :
# print("Assessing the Water Resources for the 1st quarter of the growing season...")
ETc = Kc1_4(crop) * ETPmoy(CurrentMonth, PRA) # ETc = Kc (index) ETPmoy (in mm)
ETc_SelectedCrop = Kc1_4(x.SelectedCrop) * ETPmoy(CurrentMonth, PRA)
elif i <= GS2_4 :
ETc = Kc2_4(crop) * ETPmoy(CurrentMonth, PRA)
ETc_SelectedCrop = Kc2_4(x.SelectedCrop) * ETPmoy(CurrentMonth, PRA)
# print("Assessing the Water Resources for the 2nd quarter of the growing season...")
elif i <= GS3_4 :
ETc = Kc3_4(crop)*ETPmoy(CurrentMonth, PRA)
ETc_SelectedCrop = Kc3_4(x.SelectedCrop) * ETPmoy(CurrentMonth, PRA)
# print("Assessing the Water Resources for the 3rd quarter of the growing season...")
else:
ETc = Kc4_4(crop)*ETPmoy(CurrentMonth, PRA)
ETc_SelectedCrop = Kc4_4(x.SelectedCrop) * ETPmoy(CurrentMonth, PRA)
# print("Assessing the Water Resources for the 4th quarter of the growing season...")
#========================================================================================
# ATTENTION: CompanionCrops with a Cash Crop do not have the same density as if they were alone: 50% * ETc
CurrentMonth = x.GSstart
if CurrentMonth % 12 == 0:
if ETc*0.5 + ETc_SelectedCrop <= WaterResources(12, x.GSstart, PRA, crop, x) :
pass
else:
x.edibleCompanionCrops = [x for x in x.edibleCompanionCrops if x != crop] # deleting the current crop from the list
elif CurrentMonth % 12 != 0:
if ETc*0.5 + ETc_SelectedCrop <= WaterResources(CurrentMonth%12, x.GSstart, PRA, crop, x) :
pass
else:
x.edibleCompanionCrops = [x for x in x.edibleCompanionCrops if x != crop] # deleting the current crop from the list
CurrentMonth += 1
i += 1
#END while (i <= GSmax)
#END for (crop in edibleCompanionCrops)
# Output at the end of this loop: an updated 'edibleCompanionCrops' list with only the companion Crops
# that can grow with the x.SelectedCrop
# ATTENTION: CompanionCrops with a Cash Crop do not have the same density as if they were alone: 50%
###############################
if len(x.edibleCompanionCrops) == 1:
x.SelectedCC = x.edibleCompanionCrops[0]
else: # if the list x.edibleCompanionCrops is empty
x.SelectedCC = None
#----------------------------------------------------------------------------------------------------------------
def ASSESS_Nutrients_CompanionCrop(x, PRA):
"""INPUT :
* x is the class that contains all self variables used in all VegAu's functions
FUNCTION:
If the SelectedCrop tolerates a CompanionCrop (acc. to Water Requirement and Resources), cheking if there is enough nutrients for it to grow:
Nfix(crop) - Nremoved(crop) > x.ActualStand[PRA]['N']
OUTPUT:
Average margin for each nutrient and for each 'edibleCrops' and associates this value with indexNutrients[crop][nutrient].
If a margin is negative while GSmin(x.SelectedCC), this SelectedCC is deleted from the dict 'indexNutrients' and 'edibleCrops'.
Takes the decomposition of previous crops into account.
"""
try:
print("[{}][{}] Calculation nutrient margins for edible Companion Crops...".format(PRA, x.EndPreviousCrop_later))
for crop in x.edibleCompanionCrops :
x.indexNutrients[crop] = {'N': [], 'P': [], 'K': [], 'Na': [],'Mg': [], 'Ca': [], 'Mn': [], 'Fe': [], 'Cu': []}
#--------------------------------------
print(" Testing {}...".format( prod_EN(crop) ))
# ---------------------------------------------------------------------------------------------------------
# if the nutrient amount in the PRA's soil is not sufficient while GSmin(crop),
# the Selected Companion Crop is not considered as x.edible anymore:
removed = {"N": removedN(crop) + removedN(x.SelectedCrop),
"P": removedP(crop) + removedP(x.SelectedCrop),
"K": removedK(crop) + removedK(x.SelectedCrop),
"Na": removedNa(crop) + removedNa(x.SelectedCrop),
"Mg": removedMg(crop) + removedMg(x.SelectedCrop),
"Ca": removedCa(crop) + removedCa(x.SelectedCrop),
"Mn": removedMn(crop) + removedMn(x.SelectedCrop),
"Fe": removedFe(crop) + removedFe(x.SelectedCrop),
"Cu": removedCu(crop) + removedCu(x.SelectedCrop)}
for nutrient in x.indexNutrients[crop].keys():
assert crop in x.indexNutrients
x.indexNutrients[crop][nutrient] = []
monthInGS = 1
actual_stand = dict(x.ActualStand[PRA])
end_GSmin = monthInGS + GSmin(crop)
# -------------------------------------------------------------------------------------------------------------
# Simulation according to the ActualStand values for each GS month
# as long as the crop has not been deleted because of the lack soil resources :
while monthInGS <= end_GSmin and crop in x.indexNutrients :
if nutrient == 'N': # N can be fixed : separated margin assessment
margin = (actual_stand[nutrient] + x.decomposition_month[monthInGS][
nutrient] - ((removed[nutrient] - fixedN(crop)) / GSmin(crop)))
else:
margin = (actual_stand[nutrient] + x.decomposition_month[monthInGS][
nutrient] - (removed[nutrient] / GSmin(crop)))
# NOTICE : divison by GSmin assuming that the nutrient absorption is stable while GSmin
# (monthly assessment)
# ---------------------------------------------------------------------------------------------------------
if margin < 0 :
print(" Not enough {} for growing {}...".format(nutrient, prod_EN(crop)))
x.edibleCompanionCrops = [CC for CC in x.edibleCompanionCrops if CC != crop]
try:
del x.indexNutrients[crop]
break
except KeyError:
# the origin of this error was still not found...
print("KeyError : x.indexNutrients['{}'] does not exists.".format(crop))
# breaking the loop to assess the nutrient margins for this new crop :
assert x.edibleCompanionCrops != []
# If there are still x.edible Companion Crops, another one is chosen according to its Water Requirements: #
# for crop in x.edibleCompanionCrops : # state for the function before the replacement
# if x.CCwater[crop] == max(x.CCwater.values()): # by the next line (we consider all edible ComppanionCrops anyway !...?)
# crop = str(crop)
else:
# average removed and fixed nutrient for one month of GSmin(crop):
if nutrient == 'N':
x.indexNutrients[crop][nutrient].append(
x.ActualStand[PRA][nutrient] + x.decomposition_month[monthInGS][nutrient] - (
(removed[nutrient] - fixedN(crop)) / GSmin(crop)))
else:
x.indexNutrients[crop][nutrient].append(
x.ActualStand[PRA][nutrient] + x.decomposition_month[monthInGS][nutrient] - (
removed[nutrient] / GSmin(crop)))
monthInGS += 1
# END while (monthInGS < endGSmin)
if crop in x.indexNutrients:
# the crop may have been deleted from the dict if there was no nutrients enough for it in the soil
x.indexNutrients[crop][nutrient] = sum(x.indexNutrients[crop][nutrient]) / len(
x.indexNutrients[crop][nutrient])
# else there are still indexes from the crop selection which can be used to compare remaining crops.
# END for (nutrient) ----------------------------------------------------------------------------------------------------
# END for (crop in x.edibleCompanionCrops)-----------------------------------------------------------------------------------
#===============================================================================================================
# Standardization
if x.edibleCompanionCrops != [] :
MAXvalue = {'N': [], 'P': [], 'K': [], 'Na': [], 'Mg': [], 'Ca': [], 'Mn': [], 'Fe': [], 'Cu': []}
for crop in x.edibleCompanionCrops:
# ----------------------------------------------------------------------------------------------
for nutrient in MAXvalue.keys():
MAXvalue[nutrient].append(x.indexNutrients[crop][nutrient])
# END for --------------------------------------------------------------------------------------
for nutrient in MAXvalue.keys():
MAXvalue[nutrient] = max(MAXvalue[nutrient])
# Margin standardization, second step (dividing by the MAXvalue to get a percentage):
for crop in x.edibleCompanionCrops:
# ----------------------------------------------------------------------------------------------
for nutrient in x.indexNutrients[crop].keys():
x.indexNutrients[crop][nutrient] /= MAXvalue[nutrient]
# END for ----------------------------------------------------------------------------------
# calculating the average of all standardized nutrient margin to get an index between 0 and 1 :
x.indexNutrients[crop] = round(
sum(list(x.indexNutrients[crop].values())) / len(x.indexNutrients[crop]), 3)
except AssertionError: # crop == None, x.edibleCompanionCrops is empty
x.SelectedCC = None
print(" No companion crop available.")
#----------------------------------------------------------------------------------------------------------------
def SELECT_CompanionCrop(x, PRA):
"""Selecting a companion crop for the x.SelectedCrop."""
x.SelectedCC = None
if prodCAT(x.SelectedCrop) != 0 : # if prodCAT == 0, the Selected crop is already a cover/companion crop !
#=========================================================================================================
ASSESS_Water_CompanionCrop(x, PRA)
# This function gives back a dict x.CCwater with standardized indices for the WR quality
ASSESS_Nutrients_CompanionCrop(x, PRA)
# This function updates the x.indexNutrients dict with standardized indices for nutrients availability
if x.edibleCompanionCrops != []:
# if there are still edible CC and none of them has already been chosen:
selection = {}
for crop in x.edibleCompanionCrops:
selection[crop] = (x.indexWR[crop] + x.indexNutrients[crop]) / 2
# Choosing the higher index :
selection = [crop for crop in x.edibleCompanionCrops if selection[crop] == max(selection.values())]
x.SelectedCC = selection[0]
print("Companion crop selected: {}".format(prod_EN(x.SelectedCC)))
else:
print("[{}][{}] There are no edible Companion Crop for the currently Selected Crop.".format(PRA, x.EndPreviousCrop_later))
x.SelectedCC = None
x.rotat[PRA][-1] = (x.rotat[PRA][-1][0], x.SelectedCC, x.rotat[PRA][-1][2])
#================================================================================================================
#================================================================================================================
def APPLY_ResiduesDecomposition_of_PreviousCrops(PRA, x):
"""INPUT :
* crop is the ID of a current crop. It allows to call the related data in the 'plants' dictionary.
* PRA is the ID of a current PRA. It allows to call the related data in the 'environment' dictionary.
* x is the class that contains all self variables used in all VegAu's functions
OUTPUT:
Residues that are RETURNED BY THE PREVIOUS CROP while the GS of the newly x.SelectedCrop.
"""
print("[{}][{}] Calculating the nutrients that are returned by the previous crops...".format(PRA, x.EndPreviousCrop_later))
monthInGS = 1
# Calculated the nutrients that are returned by the previous crop by the GS of the newly x.SelectedCrop:
while monthInGS <= GSmin(x.SelectedCrop):
for nutrient in x.ActualStand[PRA].keys():
if monthInGS not in x.decomposition_month.keys():
x.decomposition_month[monthInGS] = {}
x.decomposition_month[monthInGS][nutrient] = 0
if monthInGS == 1: # minerals that still had 1 month delay before mineralization
x.ActualStand[PRA][nutrient] += x.decomposition_month[1][nutrient] # at x.GSstart-1 are yet available to x.SelectedCrop
monthInGS += 1
for i in sorted(x.decomposition_month.keys()) :
try:
x.decomposition_month[i] = x.decomposition_month[i+1] # updating mineralization delay: mineralization delay reduces each month by 1 month (while GS)
except KeyError: # the last month (i) has no following month (i+1)
# --> rebuilding the dict acc. to the initial NutrientList (allows to add nutrients in the list and be sure
# that the change will be taken into account in the whole script
for nutrient in x.ActualStand:
x.decomposition_month[i][nutrient] = 0
#================================================================================================================
#================================================================================================================
def APPLY_ResiduesDecomposition_of_CompanionCrop(x):
"""INPUT :
* x is the class that contains all self variables used in all VegAu's functions
FUNCTION:
Calculating the residues that remain after the x.SelectedCC according to the functions from the STICS model
and the parameter described in Justes et al. (2009).
OUTPUT:
updated 'x.decomposition_month' dictionary
"""
Residues = {"N": returnedN(x.SelectedCC), "P": returnedP(x.SelectedCC), "K": returnedK(x.SelectedCC), "OM": returnedOM(x.SelectedCC)}
print(" Simulating the Residues Decomposition for the selected Companion Crop...")
i = 1
# to avoid an infinite number of months in the dict 'month', the loop stops automatically after 8 years (96 months):
while i < 97:
for nutrient in Residues.keys():
mineralizedN_amount = mineralizedN(x.SelectedCC, i) - mineralizedN(x.SelectedCC, i - 1)
mineralizedCPK_amount = mineralizedCPK(x.SelectedCC, i) - mineralizedCPK(x.SelectedCC, i - 1)
# the functions mineralizedCPK(crop, month) and mineralizedN(crop, month) give the mineralized stuff amount at month[i]
# to get the mineralized amount while month[i] only --> month[i] - month [i-1]
if mineralizedN_amount > 0 or mineralizedCPK_amount > 0:
if nutrient == 'N':
x.decomposition_month[i][nutrient] += mineralizedN_amount/2 # The density of a CompanionCrop can't be as high as if it were grown alone -> divided by 2
else:
x.decomposition_month[i][nutrient] += mineralizedCPK_amount/2
# decomposition of each nutrient have been assessed for month i
i += 1 # switching to next month
#----------------------------------------------------------------------------------------------------------------
def APPLY_SelectedCC_Kill(PRA, x):
"""INPUT :
* PRA is the ID of a current PRA. It allows to call the related data in the 'environment' dictionary.
* x is the class that contains all self variables used in all VegAu's functions
OUTPUT:
Amounts of removed nutrients by the Selected Companion Crop while the current crop's GS.
It takes into account the decomposition of previous crop thanks the dictionary x.decomposition_month in the
function APPLY_ResiduesDecomposition_of_CompanionCrop(x).
"""
if x.SelectedCC != None:
# Calculated the nutrients that are returned by the previous crop by the GS of the newly x.SelectedCC:
removed = {"N": removedN(x.SelectedCC), "P": removedP(x.SelectedCC), "K": removedK(x.SelectedCC), "Na": removedNa(x.SelectedCC),
"Mg": removedMg(x.SelectedCC), "Ca": removedCa(x.SelectedCC), "Mn": removedMn(x.SelectedCC), "Fe": removedFe(x.SelectedCC),
"Cu": removedCu(x.SelectedCC)} # Organic Matter ('OM' cannot be removed !)
for nutrient in removed:
monthInGS = 1
while monthInGS <= GSmin(x.SelectedCC):
x.ActualStand[PRA][nutrient] -= round(removed[nutrient] / 2, 3)
monthInGS += 1
print("[{}][{}]".format(PRA, x.EndPreviousCrop_later), """ Simulating the residues decomposition of the Selected Companion Crop...""")
APPLY_ResiduesDecomposition_of_CompanionCrop(x)
print(" OK")
#END if
#================================================================================================================
#================================================================================================================
def APPLY_ResiduesDecomposition_of_SelectedCrop(x):
"""INPUT :
* x is the class that contains all self variables used in all VegAu's functions
OUTPUT:
Residues that remain after the x.SelectedCrop according to the functions from the STICS model
and the parameter described in Justes et al. (2009).
"""
Residues = {"N": returnedN(x.SelectedCrop), "P": returnedP(x.SelectedCrop), "K": returnedK(x.SelectedCrop), "OM": returnedOM(x.SelectedCrop)}
try:
i = 1
while i < 97: # to avoid an infinite number of months in the dict 'x.decomposition_month', the loop stops automatically after 8 years
mineralizedN_percentage = mineralizedN(x.SelectedCrop, i)
mineralizedN_percentageDiff = mineralizedN(x.SelectedCrop, i) - mineralizedN(x.SelectedCrop, i - 1)
mineralizedCPK_percentage = mineralizedCPK(x.SelectedCrop, i) - mineralizedCPK(x.SelectedCrop, i - 1)
# the functions mineralizedCPK(crop, month) and mineralizedN(crop, month) give the mineralized stuff amount at month[i]
# to get the mineralized amount while x.decomposition_month[i] only --> x.decomposition_month[i] - x.decomposition_month [i-1]
for nutrient in Residues.keys():
# if (nutrient == 'N' and mineralizedN_amount > 0) or ( nutrient != 'N' and mineralizedCPK_amount > 0) :
if nutrient == 'N':
if mineralizedN_percentageDiff > 0 :
# The first mineralization amoount may be negative, especially for cereals (bacteria feed on their N to decompose their C)
x.decomposition_month[i][nutrient] += mineralizedN_percentageDiff * Residues['N']
else:
x.decomposition_month[i][nutrient] += mineralizedN_percentage * Residues['N']
else:
x.decomposition_month[i][nutrient] += mineralizedCPK_percentage * Residues[nutrient]
# decomposition of each nutrient have been assessed for month i
#print(mineralizedCPK_percentage," | ", mineralizedN_percentage)
i += 1 # switching to next month
except TypeError:
print("TypeError: 'int' object is not subscriptable")
print("[{}][{}]".format(PRA, x.EndPreviousCrop_later), """ "Type of :
x.decomposition_month --> {}""".format(type(x.decomposition_month)))
#----------------------------------------------------------------------------------------------------------------
def APPLY_SelectedCrop_Harvest(PRA, x):
"""INPUT :
* crop is the ID of a current crop. It allows to call the related data in the 'plants' dictionary.
* PRA is the ID of a current PRA. It allows to call the related data in the 'environment' dictionary.
* x is the class that contains all self variables used in all VegAu's functions
OUTPUT:
Amounts of removed nutrients while the current crop GS.
It takes into account the decomposition of previous crop thanks the dictionary x.decomposition_month.
"""
removed = {"N": removedN(x.SelectedCrop), "P": removedP(x.SelectedCrop), "K": removedK(x.SelectedCrop), "Na": removedNa(x.SelectedCrop),
"Mg": removedMg(x.SelectedCrop), "Ca": removedCa(x.SelectedCrop), "Mn": removedMn(x.SelectedCrop), "Fe": removedFe(x.SelectedCrop),
"Cu": removedCu(x.SelectedCrop)} # Organic Matter ('OM' cannot be removed !)
end_of_the_shortest_GS = GSmin(x.SelectedCrop)
# Calculating the nutrients that are REMOVED while the GS of the newly x.SelectedCrop:
print(" Simulating the nutrient uptakes by the Selected Crop...")
for nutrient in removed :
monthInGS = 1
while monthInGS <= end_of_the_shortest_GS:
x.ActualStand[PRA][nutrient] -= removed[nutrient]
monthInGS += 1
# The following function calculates the residues that remain after the x.SelectedCrop:
APPLY_ResiduesDecomposition_of_SelectedCrop(x)
#================================================================================================================
#================================================================================================================
def UPDATE_VERIFprodBOT_and_PestsDiseases_in_rotation(PRA, x):
"""INPUT :
* PRA is the ID of a current PRA. It allows to call the related data in the 'environment' dictionary.
* x is the class that contains all self variables used in all VegAu's functions
FUNCTION:
If there is no one in the dictionary and verifies if the minimum return period is respected.
If respected : no Pest and Diseases malus
If not respected : +1 for this crop in the dict 'PestsDiseases_in_rotation'.
In both cases, the 'Duration since previous crop' returns to 0.
OUTPUT:
* updated 'x.VERIFprodBOT' dictionary
* updated 'PestsDiseases_in_rotation' dictionary
"""
print(" Verifying if the minimum return period is respected...")
# ==================================================
# Updating VERIFprodBOT : 'Duration since previous crop'
if x.EndPreviousCrop_earlier <= x.GSstart <= x.EndPreviousCrop_later:
for BotanicFamily in x.VERIFprodBOT.keys():
x.VERIFprodBOT[BotanicFamily]['Duration since previous crop'] += GSmin(x.SelectedCrop) + x.GSstart - x.EndPreviousCrop_earlier
elif x.GSstart > x.EndPreviousCrop_later:
for BotanicFamily in x.VERIFprodBOT.keys():
x.VERIFprodBOT[BotanicFamily]['Duration since previous crop'] += GSmin(x.SelectedCrop) + x.GSstart - x.EndPreviousCrop_later
else :
print("ERROR ! Can't increment the VERIFprodBOT dict...")
# WORKAROUND :
#TODO : understand why the both first conditions are not sufficient
for BotanicFamily in x.VERIFprodBOT.keys():
x.VERIFprodBOT[BotanicFamily]['Duration since previous crop'] += GSmin(x.SelectedCrop)
# ==================================================
# Updating VERIFprodBOT : new entry if the crop's botanic family is not in the dict:
#TODO : verify if x.VERIFprodBOT[prodBOT(x.SelectedCrop)]['Crops in Rotation'] is really used somewhere.
if prodBOT(x.SelectedCrop) not in x.VERIFprodBOT :
x.VERIFprodBOT[prodBOT(x.SelectedCrop)] = {'Duration since previous crop': 0, 'Crops in Rotation': 1}
# ==================================================
# x.SelectedCrop is added to the list of the crops added to the rotation
# (if the "duration since the previous crop" is 0, it has just been added) :
if x.VERIFprodBOT[prodBOT(x.SelectedCrop)]['Duration since previous crop']//12 < period(x.SelectedCrop) and x.VERIFprodBOT[prodBOT(x.SelectedCrop)]['Duration since previous crop']//12 != 0:
# if the minimum return period is not respected:
#------------------------------------------------------------------------------------
# adding an entry to the dict 'PestsDiseases_in_rotation' for this crop or incrementing its already existing value:
if PRA in x.PestsDiseases_in_rotation.keys():
x.PestsDiseases_in_rotation[PRA] += 1
else:
x.PestsDiseases_in_rotation[PRA] = 1
print("""
There is already a crop from the same botanic family of the x.SelectedCrop in the rotation...
Incrementing the 'PestsDiseases_in_rotation' index for the botanic family of this crop (PestsDiseases_in_rotation = {}).""".format(
x.PestsDiseases_in_rotation[PRA]))
#END if (min return period not respected)--------------------------------------------
# END if (prodBOT(SelectedCrop in VERIFprodBOT)--------------------------------------------