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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# 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 = round(np.log10(self.table.at['Fslope', 'scale_factor'])))
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 = round(np.log10(self.table.at['Foffset', 'scale_factor'])))
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 = round(np.log10(self.table.at['Kslope', 'scale_factor'])))
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 = round(np.log10(self.table.at['Koffset', 'scale_factor'])))

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)