#! /usr/bin/env python #-*- coding: utf-8 -*- """ 11-07-2023 adapted from modspa-parcel code @author: jeremy auclair Classes to load and store SAMIR parameters. """ from pandas import read_csv # to read csv parameter files import numpy as np # vectorised math class samir_parameters: """ Load all parameters for multiples classes in one object. Attributes ---------- 1. .self.table: ``pd.DataFrame`` ``pandas DataFrame`` with all paramters (rows) for all classes (columns) It also contains : - a ``scale_factor`` column (first column) that allows to convert all parameters to integer values for reduced memory usage - a ``Default`` column (second column) that contains default values to fill in missing values Example of the parameter self.table: .. code-block:: python ClassName scale_factor Default no_sim straw_cereal oilseed soy sunflower corn ClassNumber 1 0.000 1.000 2.000 3.000 4.000 5.000 6.000 NDVIsol 1 0.150 0.150 0.150 0.150 0.150 0.150 0.150 NDVImax 1 0.850 0.850 0.850 0.850 0.850 0.850 0.850 FCmax 1000 1.000 0.000 1.000 1.000 1.000 1.000 1.000 Fslope 1000 1.400 0.000 1.180 1.220 1.400 1.400 1.400 Foffset 1000 -0.075 0.000 -0.165 -0.170 -0.075 -0.075 -0.075 Kcbmax 1000 1.100 0.000 1.100 0.700 1.100 1.100 1.100 Kslope 1000 1.200 0.000 1.470 1.350 1.200 1.200 1.200 Koffset 1000 -0.240 0.000 -0.170 -0.160 -0.240 -0.240 -0.240 Kcmax, 1000 1.150 1.150 1.150 1.150 1.150 1.150 1.150 Zsoil 1 2000.000 2000.000 2000.000 2000.000 2000.000 2000.000 2000.000 Ze 1 300.000 300.000 125.000 125.000 300.000 300.000 300.000 Init_RU 1000 0.870 0.870 0.500 0.500 0.870 0.870 0.870 DiffE 1 1.000 0.000 0.000 0.000 1.000 1.000 1.000 DiffR 1 5.000 0.000 0.000 0.000 5.000 5.000 5.000 REW 1 0.000 0.000 9.000 9.000 0.000 0.000 0.000 minZr 1 150.000 0.000 125.000 125.000 150.000 150.000 150.000 maxZr 1 600.000 0.000 1250.000 1450.000 600.000 600.000 600.000 p 1000 0.550 0.000 0.550 0.650 0.550 0.550 0.550 FW 1000 1.000 0.000 1.000 1.000 1.000 1.000 1.000 Irrig_auto 1 1.000 0.000 1.000 1.000 1.000 1.000 1.000 Irrig_man 1 0.000 0.000 0.000 0.000 0.000 0.000 0.000 Lame_max 1 50.00 0.000 50.00 50.00 50.00 50.00 50.00 Lame_min 1 0.000 0.000 0.000 0.000 0.000 0.000 0.000 Kcb_min_start_irrig 1 0.100 0.000 0.100 0.100 0.100 0.100 0.100 frac_Kcb_stop_irrig 1 0.500 0.000 0.500 0.500 0.500 0.500 0.500 frac_TAW 1 0.650 0.000 0.650 0.650 0.650 0.650 0.650 Methods ------- 1. test_samir_parameter(min_max_param_file: ``str``): Test the values of the SAMIR parameters to check if they are in the correct range (defined the the csv param_range file) and automatically calculate the Fcover and Kcb slope and offset (from the NDVIsol and NDVImax values) if their value is equal to -9999. Arguments ========= 1. min_max_param_file: ``str`` samir parameter maximum and minimum values file Returns ======= 1. table: ``pd.DataFrame`` updated samir parameter dataframe """ def __init__(self, paramFile: str) -> None: """ Create pandas self.table from the csv parameter file. Arguments ========= 1. paramFile: ``str`` path to csv parameter file """ # Read csv file with Pandas csvFile = read_csv(paramFile) # Index file for correct conversion to dictionnary csvFile.index = csvFile.iloc[:,0] csvFile.drop(columns = ['ClassName'], inplace = True) # Replace missing parameters by their default values csvFile[csvFile.columns[1:]] = csvFile[csvFile.columns[1:]].astype('float32') for col in csvFile.columns[2:]: mask = csvFile[col].isna() csvFile.loc[mask, col] = csvFile.loc[mask, 'Default'] # Store dataframe in attribute table self.table = csvFile # Define method to test parameters def test_samir_parameter(self, min_max_param_file: str): """ Test the values of the SAMIR parameters to check if they are in the correct range (defined the the csv param_range file) and automatically calculate the Fcover and Kcb slope and offset (from the NDVIsol and NDVImax values) if their value is equal to -9999. Arguments ========= 1. min_max_param_file: ``str`` samir parameter maximum and minimum values file Returns ======= 1. table: ``pd.DataFrame`` updated samir parameter dataframe """ # Automatically calculate the FC and Kcb slopes and offsets if necessary for class_name in self.table.columns[1:]: if self.table.at['Fslope', class_name] == -9999: # Equation: Fslope = FCmax / (NDVImax - NDVIsol) self.table.at['Fslope', class_name] = np.round(self.table.at['FCmax', class_name] / (self.table.at['NDVImax', class_name] - self.table.at['NDVIsol', class_name]), decimals = 3) if self.table.at['Foffset', class_name] == -9999: # Equation: Foffset = - NDVIsol * Fslope self.table.at['Foffset', class_name] = - np.round(self.table.at['NDVIsol', class_name] * self.table.at['Fslope', class_name], decimals = 3) if self.table.at['Kslope', class_name] == -9999: # Equation: Kslope = Kcbmax / (NDVImax - NDVIsol) self.table.at['Kslope', class_name] = np.round(self.table.at['Kcbmax', class_name] / (self.table.at['NDVImax', class_name] - self.table.at['NDVIsol', class_name]), decimals = 3) if self.table.at['Koffset', class_name] == -9999: # Equation: Koffset = - NDVIsol * Kslope self.table.at['Koffset', class_name] = - np.round(self.table.at['NDVIsol', class_name] * self.table.at['Kslope', class_name], decimals = 3) if self.table.at['maxZr', class_name] > self.table.at['Zsoil', class_name]: print(f'\nmaxZr is higher than Zsoil for class {class_name}') # Set boolean to true to exit script out_of_range_param = True if self.table.at['minZr', class_name] > self.table.at['maxZr', class_name]: print(f'\minZr is higher than maxZr for class {class_name}') # Set boolean to true to exit script out_of_range_param = True # Test values of the parameters min_max_table = read_csv(min_max_param_file, index_col = 0) # Boolean set to true if a parameter is out of range out_of_range_param = False # Loop through parameter values for all the classes for parameter in min_max_table.index: for class_name in self.table.columns[1:]: # Test if parameter is out of range if self.table.at[parameter, class_name] > min_max_table.at[parameter, 'max_value'] or self.table.at[parameter, class_name] < min_max_table.at[parameter, 'min_value']: # If parameter is out of range, print which parameter and for which class print(f'\nParameter {parameter} is out of range for class {class_name}') # Set boolean to true to exit script out_of_range_param = True # Return 0 if param is out of range if out_of_range_param: self.table = 0 else: # Remove unecessary rows self.table.drop(['NDVIsol', 'NDVImax'], inplace = True)