Newer
Older
Jeremy Auclair
committed
#! /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
Jeremy Auclair
committed
class samir_parameters:
"""
Jeremy Auclair
committed
Load all parameters for multiples classes in one object.
Attributes
----------
Jeremy Auclair
committed
1. .self.table: ``pd.DataFrame``
``pandas DataFrame`` with all paramters (rows) for all classes (columns)
Jeremy Auclair
committed
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:
Jeremy Auclair
committed
.. code-block:: python
Jeremy Auclair
committed
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
Jeremy Auclair
committed
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
Jeremy Auclair
committed
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
Jeremy Auclair
committed
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
Jeremy Auclair
committed
"""
def __init__(self, paramFile: str) -> None:
"""
Create pandas self.table from the csv parameter file.
Arguments
=========
1. paramFile: ``str``
path to csv parameter file
Jeremy Auclair
committed
# Read csv file with Pandas
Jeremy Auclair
committed
csvFile = read_csv(paramFile)
Jeremy Auclair
committed
# Index file for correct conversion to dictionnary
csvFile.index = csvFile.iloc[:,0]
Jeremy Auclair
committed
csvFile.drop(columns = ['ClassName'], inplace = True)
Jeremy Auclair
committed
Jeremy Auclair
committed
# 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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# 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)

Jérémy AUCLAIR
committed
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

Jérémy AUCLAIR
committed
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)

Jérémy AUCLAIR
committed
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

Jérémy AUCLAIR
committed
self.table.at['Koffset', class_name] = - np.round(self.table.at['NDVIsol', class_name] * self.table.at['Kslope', class_name], decimals = 3)

Jérémy AUCLAIR
committed
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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# 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)