File size: 10,135 Bytes
db3f094
6933122
a36fb9e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6933122
 
a36fb9e
7f03fb1
 
 
 
a36fb9e
 
 
2bfe827
a36fb9e
 
6933122
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a36fb9e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6933122
a36fb9e
 
 
 
db3f094
 
 
6933122
 
 
 
 
 
 
db3f094
a36fb9e
 
 
eb5bdd2
 
6933122
a36fb9e
7f03fb1
 
 
 
 
 
 
 
 
 
 
 
a36fb9e
 
6933122
a36fb9e
 
 
7f03fb1
 
a36fb9e
 
f98e2d7
db3f094
 
6933122
 
 
 
 
 
 
db3f094
a36fb9e
6933122
a36fb9e
2bfe827
f98e2d7
a36fb9e
2bfe827
f98e2d7
 
 
5a6b20f
2bfe827
 
 
 
 
a36fb9e
 
f98e2d7
 
5bf21e4
b6d0119
a36fb9e
 
 
 
f98e2d7
a36fb9e
 
 
 
db3f094
 
6933122
 
 
 
 
 
 
 
db3f094
a36fb9e
 
da36a6d
6933122
a36fb9e
 
7f03fb1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eb5bdd2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# Functions to generate dataframes from run results (Runs dict results are part of simu output pkl file)

# PKL STRUCTURE FILE AS INPUT THAT IS THE SAVED OPTIMISATION -> CONTAINS:
# SIMU DICT : {'config':self.config, 'runs':pareto_optimal_runs, 'computation_time':computation_time}
# and ech element in runs :
#             {'general_metrics':general_metrics,
#'df_weekly_blocks':df_weekly_blocks,
#'df_daily_pv_energy':df_daily_pv_energy,
#'list_dssat_outputs_per_row':list_dssat_outputs_per_row}
# and each list_dssat_outputs_per_row: CONTAINS {'index_row', 'x_row', 'dssat_summary', 'df_dssat_daily'}
# and each dssat_summary': """{'RUN': '1','TRT': '1','FLO': '213','MAT': '248','TOPWT': '3876','HARWT': '3239','RAIN': '644','TIRR': '0','CET': '431','PESW': '169','TNUP': '0','TNLF': '-99','TSON': '0','TSOC': '100'},"""

"""And general_metrics :
        general_metrics = {'run_id':self.id,
                    'average_agri_yield':self.get_average_agri_yield(),
                    'average_pv_energy':self.get_average_pv_energy(),
                    'max_dssat_end_time':self.max_dssat_end_time} 
                    'normalized_ac_collected_energy','normalized_agri_yield'}"""


# REMEMBER THAT ALL THOSE METHODS GENERATE DATASETS FOR A SINGLE RUN !


import datetime

import pandas as pd
import numpy as np

import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)

from WattFieldsCommon.constants import DSSAT_OUTPUT_KEYS

THRESHOLD_WFGD_WATER_STRESS = 0.7

def generate_run_df_summary(
    dict_run: dict,
    ref_agri_yield: float,
    ref_pv_energy: float,
    surface_panel: float,
    pv_module_pnom: float,
    planting_date: datetime.datetime,
) -> pd.DataFrame:
    """Based on a dict run, it generates dataframe with key metrics.

    Args:
        dict_run (dict): input dict run with keys (general_metrics, df_weekly_blocks, df_daily_pv_energy, list_dssat_outputs_per_row)
        ref_agri_yield (float): Ref agri yield
        ref_pv_energy (float): Ref pv energy
        surface_panel (float): Surface of the panel
        pv_module_pnom (float): PV module nominal power
        planting_date (datetime.datetime): Date of plantation

    Returns:
        pd.DataFrame: Return dataaframe with key metrics
    """

    df_recap = pd.Series()

    # AGRI SCORES
    df_recap["agri_average_yield"] = dict_run["general_metrics"]["average_agri_yield"]
    df_recap["normalized_agri_average_yield"] = int(
        df_recap["agri_average_yield"] / ref_agri_yield * 100
    )

    # PV SCORES
    df_recap["collected_ac_energy"] = dict_run["general_metrics"]["average_pv_energy"]
    df_recap["normalized_collected_ac_energy"] = int(
        df_recap["collected_ac_energy"] / ref_pv_energy * 100
    )
    df_recap["collected_ac_energy_per_meter2"] = int(
        df_recap["collected_ac_energy"] / surface_panel
    )
    df_recap["collected_ac_energy_per_peak_power"] = int(
        df_recap["collected_ac_energy"] / (pv_module_pnom / 1000)
    )  # in kW

    # OTHERS
    df_recap["run_duration"] = int(
        (dict_run["general_metrics"]["max_dssat_end_time"] - planting_date).days
    )
    weekly_choices = dict_run["df_weekly_blocks"]["weekly_strategy_choice"]
    df_recap["nb_block_anti-tracking"] = weekly_choices[weekly_choices == "anti-tracking"].count()

    return df_recap


def generate_df_temporal_analysis(dict_run: dict, dict_ref_agri_run: dict):
    """Generate a dataframe with temporal evolution of DSSAT variables One
    column for each dual (row, dssat_var)

    Args:
        dict_run (dict): Input run
        dict_ref_agri_run (dict): Ref run agri

    Returns:
        pd.DataFRame: dataframe with temporal evolution (one row per day)
    """
    all_dfs = []
    for row in dict_run["list_dssat_outputs_per_row"]:
        x_row = round(row["x_row"], 2)
        keys_to_select = list(set(DSSAT_OUTPUT_KEYS["ALL"].keys()).intersection(set(row["df_dssat_daily"].columns)))
        df_row = row["df_dssat_daily"][keys_to_select]
        df_row = df_row.rename(columns={col: f"x={x_row}_{col}" for col in df_row.columns})
        all_dfs.append(df_row)
    df_all_rows = pd.concat(all_dfs, axis=1)

    variables = list(set([col.split('_')[1] for col in df_row.columns]))

    # ADD MEAN AS EXTRA DF
    dict_mean_rows = {}
    for var in variables:
        df_selected_var_all_rows = df_all_rows.filter(like=var)
        dict_mean_rows[f'MOYENNE_{var}'] = list(df_selected_var_all_rows.mean(axis=1))
    df_mean_col = pd.DataFrame(dict_mean_rows)
    df_mean_col.loc[:, 'date'] = list(df_all_rows.index)
    df_mean_col = df_mean_col.set_index('date')

    # ADD REF AS EXTRA DF
    df_ref_dssat_ref = dict_ref_agri_run["list_dssat_outputs_per_row"][0]["df_dssat_daily"]
    df_ref_dssat_ref = df_ref_dssat_ref.rename(
        columns={col: f"AGRI_REF_{col}" for col in df_ref_dssat_ref.columns}
    )
    return pd.concat([df_all_rows, df_mean_col, df_ref_dssat_ref], axis=1)



def generate_df_spatial_analysis(dict_run: dict, dict_ref_agri_run: dict):
    """Generate a dataframe with spatial evolution of DSSAT variables (one var
    per dssat row, for example HWAT_row_x=2)

    Args:
        dict_run (dict): input run
        dict_ref_agri_run (dict): ref run agri

    Returns:
        pd.DataFRame: Dataframe where each column accounts for a row
    """
    column_names = [
        f'x_row={round(row["x_row"],2)}' for row in dict_run["list_dssat_outputs_per_row"]
    ]

    dicts_summary_per_row = [row["dssat_summary"] for row in dict_run["list_dssat_outputs_per_row"]]

    # FRACTION IRRAD
    sum_rad_ref_agri = dict_ref_agri_run["list_dssat_outputs_per_row"][0]['df_dssat_daily']['SRAD'].sum()
    for i in range(len(dicts_summary_per_row)):
        dicts_summary_per_row[i]['RATIO_IRRAD_AGRI_REF'] = round(dict_run["list_dssat_outputs_per_row"][i]['df_dssat_daily']['SRAD'].sum() / sum_rad_ref_agri, 3) * 100

    # NB DAYS WHERE HYDRIC STRESS
    for i in range(len(dicts_summary_per_row)):
        water_stress_level = dict_run["list_dssat_outputs_per_row"][i]['df_dssat_daily']['WFGD']
        dicts_summary_per_row[i]['NB_DAYS_WATER_STRESS'] = len(water_stress_level[water_stress_level>THRESHOLD_WFGD_WATER_STRESS])

    # ADD REF AS EXTRA ROW
    column_names.append("AGRI_REF_ROW")
    dicts_summary_per_row.append(dict_ref_agri_run["list_dssat_outputs_per_row"][0]["dssat_summary"])
    dicts_summary_per_row[-1]['RATIO_IRRAD_AGRI_REF'] = 100
    ref_agri_water_stress_level = dict_ref_agri_run["list_dssat_outputs_per_row"][0]['df_dssat_daily']['WFGD']
    dicts_summary_per_row[-1]['NB_DAYS_WATER_STRESS'] = len(ref_agri_water_stress_level[ref_agri_water_stress_level>THRESHOLD_WFGD_WATER_STRESS])

    # Compute total irradience per row
    # total_irrad = int(np.nanmean(row["df_dssat_daily"]["SRAD"]))

    df_all_rows = pd.DataFrame(dicts_summary_per_row).T
    df_all_rows.columns = column_names
    return df_all_rows


def generate_df_block_temporal_analysis(dict_run: dict, ref_pv_run: dict, pv_module_pnom: float):
    """From run to PV energy temporal data.

    Args:
        dict_run (dict): Input run
        ref_pv_run (dict): Ref run PV (full backtracking)
        pv_module_pnom (float): Nominal Power of PV module

    Returns:
        pd.DataFrame: DataFrame with nb_rows = nb_blocks (account generally for one week)
    """
    df_block = dict_run["df_weekly_blocks"]
    nb_blocks = len(df_block)
    df_block["PV_REF_block_pv_power_per_peak_power"] = list(
        ref_pv_run["df_weekly_blocks"]["weekly_collected_ac_energy"] / pv_module_pnom
    )[:nb_blocks]
    return df_block

def generate_df_gstd_block(dict_run, df_temporal_analysis, dict_ref_agri_run):

    # GENERATE GSTD BINS
    selected_columns = [col for col in df_temporal_analysis.columns if 'GSTD' in col and 'x=' in col]
    gstd_all_rows = df_temporal_analysis[selected_columns]
    max_gstd = max(gstd_all_rows.max().values)
    #Range of std can vary a lot so different use cases...
    if max_gstd > 20:
        # Case of wheat for example
        bins = np.arange(0, max_gstd + 10, 10)
    else:
        # Others species
        bins = np.arange(0, max_gstd + 1, 1)
    #bins = [(a,b) for a,b in zip(bins[:-1, 1:])]
    labels = [f'{a} - {b}' for a,b in zip(bins[:-1],bins[1:])]

    x_row_cols = [col for col in df_temporal_analysis.columns if 'x=' in col]
    x_row_cols = [col for col in df_temporal_analysis.columns if (('GSTD' in col) or ('SRAD' in col))]
    df_temporal_analysis = df_temporal_analysis[x_row_cols]

    x_positions = [round(row["x_row"],2) for row in dict_run["list_dssat_outputs_per_row"]]
    average_outputs = []

    df_dssat_ref = dict_ref_agri_run["list_dssat_outputs_per_row"][0]['df_dssat_daily']
    df_dssat_ref.loc[:, 'GSTD_bin'] = pd.cut(df_dssat_ref['GSTD'], bins=bins, labels=labels, right=True)
    ref_bins_average = df_dssat_ref.groupby('GSTD_bin')['SRAD'].mean().reset_index()[['GSTD_bin', f'SRAD']]
    ref_bins_average = ref_bins_average.rename(columns={'SRAD':'REF_SRAD'})

    for x_pos in x_positions:
        df_x_pos = df_temporal_analysis[[f'x={x_pos}_GSTD', f'x={x_pos}_SRAD']]
        df_x_pos.loc[:, 'GSTD_bin'] = pd.cut(df_x_pos[f'x={x_pos}_GSTD'], bins=bins, labels=labels, right=True)
        x_pos_average = df_x_pos.groupby('GSTD_bin')[f'x={x_pos}_SRAD'].mean().reset_index()[['GSTD_bin', f'x={x_pos}_SRAD']]
        x_pos_average = x_pos_average.merge(ref_bins_average, on='GSTD_bin')
        x_pos_average[f'x={x_pos}_SRAD_ref_ratio'] = np.divide(
            100 * np.array(x_pos_average[f'x={x_pos}_SRAD']), #percentage
            np.array(x_pos_average['REF_SRAD']),
            out=np.full_like(np.array(x_pos_average[f'x={x_pos}_SRAD']), np.nan),  # Fill with NaN
            where=np.array(x_pos_average['REF_SRAD']) != 0  # Only divide where REF_SRAD is not zero
        )       
        average_outputs.append(x_pos_average[[f'x={x_pos}_SRAD_ref_ratio', 'GSTD_bin']].set_index('GSTD_bin'))

    df_row_srad_averages = pd.concat(average_outputs, axis=1).round(5)
    df_row_srad_averages['MOYENNE_SRAD_ref_ratio'] = df_row_srad_averages.mean(axis=1)

    return df_row_srad_averages