From 249ea6a49b2d8ae17a55c290d092298e7d6c299a Mon Sep 17 00:00:00 2001
From: Le Roux Erwan <erwan.le-roux@irstea.fr>
Date: Wed, 29 Jul 2020 16:35:43 +0200
Subject: [PATCH] [contrasting] add cubic models

---
 .../gev_altitudinal_models.py                  | 16 +++++++++++++++-
 .../gumbel_altitudinal_models.py               |  2 +-
 .../polynomial_margin_model.py                 |  4 ++++
 .../spatio_temporal_polynomial_model.py        |  2 +-
 .../polynomial_margin_model/utils.py           | 17 ++++++++++++++++-
 .../model/result_from_model_fit/utils.py       |  3 ---
 .../altitudes_fit/main_altitudes_studies.py    | 16 ++++++++++------
 ...ies_visualizer_for_non_stationary_models.py |  8 ++++----
 .../one_fold_analysis/one_fold_fit.py          |  3 ++-
 .../main_result_trends_and_return_levels.py    |  2 +-
 .../plot_uncertainty_histogram.py              | 18 +++++++++++-------
 11 files changed, 65 insertions(+), 26 deletions(-)

diff --git a/extreme_fit/model/margin_model/polynomial_margin_model/gev_altitudinal_models.py b/extreme_fit/model/margin_model/polynomial_margin_model/gev_altitudinal_models.py
index 218b2780..8d8068f7 100644
--- a/extreme_fit/model/margin_model/polynomial_margin_model/gev_altitudinal_models.py
+++ b/extreme_fit/model/margin_model/polynomial_margin_model/gev_altitudinal_models.py
@@ -36,7 +36,7 @@ class AbstractAltitudinalModel(AbstractSpatioTemporalPolynomialModel):
         name += self.dim_to_str_number(GevParams.LOC, self.coordinates.idx_temporal_coordinates)
         name += self.dim_to_str_number(GevParams.SCALE, self.coordinates.idx_temporal_coordinates)
         if isinstance(self, AbstractAddCrossTermForLocation):
-            name += 'l'
+            name += 'x'
         if isinstance(self, AbstractAddCrossTermForScale):
             name += 's'
         return name
@@ -81,6 +81,15 @@ class NonStationaryAltitudinalLocationQuadratic(AbstractAltitudinalModel):
             GevParams.SCALE: [(self.coordinates.idx_x_coordinates, 1)]
         }
 
+class NonStationaryAltitudinalLocationCubic(AbstractAltitudinalModel):
+
+    @property
+    def param_name_to_list_dim_and_degree(self):
+        return {
+            GevParams.LOC: [(self.coordinates.idx_x_coordinates, 1), (self.coordinates.idx_temporal_coordinates, 3)],
+            GevParams.SCALE: [(self.coordinates.idx_x_coordinates, 1)]
+        }
+
 
 class NonStationaryAltitudinalLocationLinearScaleLinear(AbstractAltitudinalModel):
 
@@ -141,6 +150,10 @@ class NonStationaryAltitudinalLocationQuadraticCrossTermForLocation(AbstractAddC
                                                                     NonStationaryAltitudinalLocationQuadratic):
     pass
 
+class NonStationaryAltitudinalLocationCubicCrossTermForLocation(AbstractAddCrossTermForLocation,
+                                                                NonStationaryAltitudinalLocationCubic,
+                                                                ):
+    pass
 
 class NonStationaryAltitudinalLocationLinearScaleLinearCrossTermForLocation(AbstractAddCrossTermForLocation,
                                                                             NonStationaryAltitudinalLocationLinearScaleLinear):
@@ -155,3 +168,4 @@ class NonStationaryAltitudinalLocationQuadraticScaleLinearCrossTermForLocation(A
 class NonStationaryAltitudinalScaleLinearCrossTermForLocation(AbstractAddCrossTermForLocation,
                                                               NonStationaryAltitudinalScaleLinear):
     pass
+
diff --git a/extreme_fit/model/margin_model/polynomial_margin_model/gumbel_altitudinal_models.py b/extreme_fit/model/margin_model/polynomial_margin_model/gumbel_altitudinal_models.py
index 6ff8e3b6..e2a6e7dc 100644
--- a/extreme_fit/model/margin_model/polynomial_margin_model/gumbel_altitudinal_models.py
+++ b/extreme_fit/model/margin_model/polynomial_margin_model/gumbel_altitudinal_models.py
@@ -20,7 +20,7 @@ class AbstractGumbelAltitudinalModel(AbstractAltitudinalModel):
 
     def __init__(self, coordinates: AbstractCoordinates, params_user=None, starting_point=None,
                  fit_method=MarginFitMethod.extremes_fevd_mle, nb_iterations_for_bayesian_fit=5000,
-                 params_initial_fit_bayesian=None, params_class=GevParams, max_degree=2):
+                 params_initial_fit_bayesian=None, params_class=GevParams, max_degree=3):
         super().__init__(coordinates, params_user, starting_point, fit_method, nb_iterations_for_bayesian_fit,
                          params_initial_fit_bayesian, "Gumbel", params_class, max_degree)
 
diff --git a/extreme_fit/model/margin_model/polynomial_margin_model/polynomial_margin_model.py b/extreme_fit/model/margin_model/polynomial_margin_model/polynomial_margin_model.py
index 7d08ad61..0f044d8b 100644
--- a/extreme_fit/model/margin_model/polynomial_margin_model/polynomial_margin_model.py
+++ b/extreme_fit/model/margin_model/polynomial_margin_model/polynomial_margin_model.py
@@ -40,6 +40,10 @@ class PolynomialMarginModel(AbstractTemporalLinearMarginModel):
                 assert dims.index(self.coordinates.idx_x_coordinates) == 0
             if self.coordinates.has_temporal_coordinates and self.coordinates.idx_temporal_coordinates in dims:
                 assert dims.index(self.coordinates.idx_temporal_coordinates) == len(dims) - 1
+        # Assert that the degree are inferior to the max degree
+        for list_dim_and_degree in param_name_to_list_dim_and_degree.values():
+            for _, max_degree in list_dim_and_degree:
+                assert max_degree <= self.max_degree, 'Max degree (={}) specified is too high'.format(max_degree)
         # Load param_name_to_polynomial_all_coef
         param_name_to_polynomial_all_coef = self.param_name_to_polynomial_all_coef(
             param_name_to_list_dim_and_degree=param_name_to_list_dim_and_degree,
diff --git a/extreme_fit/model/margin_model/polynomial_margin_model/spatio_temporal_polynomial_model.py b/extreme_fit/model/margin_model/polynomial_margin_model/spatio_temporal_polynomial_model.py
index fefeb13b..4de87b47 100644
--- a/extreme_fit/model/margin_model/polynomial_margin_model/spatio_temporal_polynomial_model.py
+++ b/extreme_fit/model/margin_model/polynomial_margin_model/spatio_temporal_polynomial_model.py
@@ -8,7 +8,7 @@ class AbstractSpatioTemporalPolynomialModel(PolynomialMarginModel):
 
     def __init__(self, coordinates: AbstractCoordinates, params_user=None, starting_point=None,
                  fit_method=MarginFitMethod.extremes_fevd_mle, nb_iterations_for_bayesian_fit=5000,
-                 params_initial_fit_bayesian=None, type_for_MLE="GEV", params_class=GevParams, max_degree=2):
+                 params_initial_fit_bayesian=None, type_for_MLE="GEV", params_class=GevParams, max_degree=3):
         super().__init__(coordinates, params_user, starting_point, fit_method, nb_iterations_for_bayesian_fit,
                          params_initial_fit_bayesian, type_for_MLE, params_class, max_degree)
         self.drop_duplicates = False
diff --git a/extreme_fit/model/margin_model/polynomial_margin_model/utils.py b/extreme_fit/model/margin_model/polynomial_margin_model/utils.py
index ae5b147d..5906a9a7 100644
--- a/extreme_fit/model/margin_model/polynomial_margin_model/utils.py
+++ b/extreme_fit/model/margin_model/polynomial_margin_model/utils.py
@@ -5,7 +5,8 @@ from extreme_fit.model.margin_model.polynomial_margin_model.gev_altitudinal_mode
     NonStationaryAltitudinalLocationQuadraticCrossTermForLocation, \
     NonStationaryAltitudinalLocationLinearScaleLinearCrossTermForLocation, \
     NonStationaryAltitudinalLocationQuadraticScaleLinearCrossTermForLocation, NonStationaryAltitudinalScaleLinear, \
-    NonStationaryAltitudinalScaleLinearCrossTermForLocation
+    NonStationaryAltitudinalScaleLinearCrossTermForLocation, NonStationaryAltitudinalLocationCubicCrossTermForLocation, \
+    NonStationaryAltitudinalLocationCubic
 from extreme_fit.model.margin_model.polynomial_margin_model.gev_altitudinal_models_cross_term_in_scale import \
     NonStationaryAltitudinalLocationLinearScaleQuadraticCrossTermForScale, \
     NonStationaryAltitudinalLocationQuadraticScaleQuadraticCrossTermForScale, \
@@ -35,6 +36,20 @@ from extreme_fit.model.margin_model.polynomial_margin_model.spatio_temporal_poly
     NonStationaryLocationSpatioTemporalLinearityModelAssertError3, NonStationaryLocationSpatioTemporalLinearityModel6
 
 
+ALTITUDINAL_GEV_MODELS_LOCATION = [
+    StationaryAltitudinal,
+    NonStationaryAltitudinalLocationLinear,
+    NonStationaryAltitudinalLocationQuadratic,
+    NonStationaryAltitudinalLocationCubic,
+    # First order cross term
+    NonStationaryCrossTermForLocation,
+    NonStationaryAltitudinalLocationLinearCrossTermForLocation,
+    NonStationaryAltitudinalLocationQuadraticCrossTermForLocation,
+    NonStationaryAltitudinalLocationCubicCrossTermForLocation
+
+]
+
+
 ALTITUDINAL_GEV_MODELS = [
                              StationaryAltitudinal,
                              NonStationaryAltitudinalScaleLinear,
diff --git a/extreme_fit/model/result_from_model_fit/utils.py b/extreme_fit/model/result_from_model_fit/utils.py
index b0ff5edc..8a51ceee 100644
--- a/extreme_fit/model/result_from_model_fit/utils.py
+++ b/extreme_fit/model/result_from_model_fit/utils.py
@@ -33,9 +33,6 @@ def get_margin_coef_ordered_dict(param_name_to_dims, mle_values, type_for_mle="G
                 coef_dict[coef_name] = mle_values[i]
                 i += 1
             else:
-                # We found (thanks to the test) that time was the first parameter when len(param_name_to_dims) == 1
-                # otherwise time is the second parameter in the order of the mle parameters
-                # inverted_dims = dims[::-1] if len(param_name_to_dims) == 1 else dims
                 for dim, max_degree in dims[:]:
                     coefficient_name = LinearCoef.coefficient_name(dim, dim_to_coordinate_name)
                     coef_template = LinearCoef.coef_template_str(param_name, coefficient_name)
diff --git a/projects/altitude_spatial_model/altitudes_fit/main_altitudes_studies.py b/projects/altitude_spatial_model/altitudes_fit/main_altitudes_studies.py
index ec41adb2..068f7154 100644
--- a/projects/altitude_spatial_model/altitudes_fit/main_altitudes_studies.py
+++ b/projects/altitude_spatial_model/altitudes_fit/main_altitudes_studies.py
@@ -2,7 +2,8 @@ import matplotlib as mpl
 import matplotlib.pyplot as plt
 import numpy as np
 
-from extreme_fit.model.margin_model.polynomial_margin_model.utils import ALTITUDINAL_GEV_MODELS
+from extreme_fit.model.margin_model.polynomial_margin_model.utils import ALTITUDINAL_GEV_MODELS, \
+    ALTITUDINAL_GEV_MODELS_LOCATION
 from projects.altitude_spatial_model.altitudes_fit.one_fold_analysis.plot_total_aic import plot_total_aic, \
     plot_individual_aic
 
@@ -33,7 +34,7 @@ def plot_moments(studies, massif_names=None):
 
 
 def plot_altitudinal_fit(studies, massif_names=None):
-    model_classes = ALTITUDINAL_GEV_MODELS
+    model_classes = ALTITUDINAL_GEV_MODELS_LOCATION
     # model_classes = ALTITUDINAL_GEV_MODELS_QUADRATIC
     visualizer = AltitudesStudiesVisualizerForNonStationaryModels(studies=studies,
                                                                   model_classes=model_classes,
@@ -42,7 +43,7 @@ def plot_altitudinal_fit(studies, massif_names=None):
     # Plot the results for the model that minimizes the individual aic
     plot_individual_aic(visualizer)
     # Plot the results for the model that minimizes the total aic
-    # plot_total_aic(model_classes, visualizer)
+    plot_total_aic(model_classes, visualizer)
 
 
 def main():
@@ -50,12 +51,15 @@ def main():
     # todo: l ecart  pour les saisons de l automne ou de sprint
     #  vient probablement de certains zéros pas pris en compte pour le fit,
     # mais pris en compte pour le calcul de mon aic
-    altitudes = [900, 1200, 1500, 1800, 2100, 2400, 2700, 3000, 3300, 3600, 3900][:]
+    altitudes = [300, 600, 900, 1200, 1500, 1800, 2100, 2400, 2700, 3000, 3300, 3600, 3900, 4200][3:]
+    altitudes = [300, 600, 900, 1200, 1500, 1800, 2100, 2400, 2700, 3000][:]
+    print(altitudes)
+    altitudes = [900, 1200, 1500][:]
     study_classes = [SafranSnowfall1Day, SafranSnowfall3Days, SafranSnowfall5Days, SafranSnowfall7Days][:2]
     study_classes = [SafranPrecipitation1Day, SafranPrecipitation3Days, SafranPrecipitation5Days,
                      SafranPrecipitation7Days][:]
-    study_classes = [SafranSnowfall1Day, SafranPrecipitation1Day,
-                     SafranSnowfall3Days, SafranPrecipitation3Days][:1]
+    study_classes = [SafranSnowfall1Day, SafranSnowfall3Days, SafranPrecipitation1Day
+                     , SafranPrecipitation3Days][:2]
     # seasons = [Season.automn, Season.winter, Season.spring][::-1]
     seasons = [Season.winter]
     # seasons = [Season.winter_extended]
diff --git a/projects/altitude_spatial_model/altitudes_fit/one_fold_analysis/altitudes_studies_visualizer_for_non_stationary_models.py b/projects/altitude_spatial_model/altitudes_fit/one_fold_analysis/altitudes_studies_visualizer_for_non_stationary_models.py
index 351f9c7c..b01b505b 100644
--- a/projects/altitude_spatial_model/altitudes_fit/one_fold_analysis/altitudes_studies_visualizer_for_non_stationary_models.py
+++ b/projects/altitude_spatial_model/altitudes_fit/one_fold_analysis/altitudes_studies_visualizer_for_non_stationary_models.py
@@ -32,7 +32,7 @@ class AltitudesStudiesVisualizerForNonStationaryModels(StudyVisualizer):
         self.non_stationary_models = model_classes
         self.fit_method = fit_method
         self.display_only_model_that_pass_anderson_test = display_only_model_that_pass_anderson_test
-        self.massif_names = massif_names if massif_names is not None else self.study.study_massif_names
+        self.massif_names = massif_names if massif_names is not None else self.study.all_massif_names()
         self.massif_name_to_massif_id = {m: i for i, m in enumerate(self.massif_names)}
         # Load one fold fit
         self._massif_name_to_one_fold_fit = {}
@@ -143,7 +143,7 @@ class AltitudesStudiesVisualizerForNonStationaryModels(StudyVisualizer):
         pass
 
     def plot_year_for_the_peak(self):
-        t_list = 1900 + np.arange(200)
+        t_list = 1800 + np.arange(400)
         for massif_name, one_fold_fit in self.massif_name_to_one_fold_fit.items():
             ax = plt.gca()
             # One plot for each altitude
@@ -162,7 +162,7 @@ class AltitudesStudiesVisualizerForNonStationaryModels(StudyVisualizer):
             ax.legend()
             ax.set_xlabel('Year')
             ax.set_xlabel('Mean annual maxima of ')
-            self.plot_name = 'Peak year for {}'.format(massif_name)
-            self.show_or_save_to_file(no_title=True)
+            plot_name = '{}/{}/Peak year for {}'.format(OneFoldFit.folder_for_plots, 'Peak year', massif_name.replace('_', ''))
+            self.studies.show_or_save_to_file(plot_name=plot_name, show=self.show)
             plt.close()
 
diff --git a/projects/altitude_spatial_model/altitudes_fit/one_fold_analysis/one_fold_fit.py b/projects/altitude_spatial_model/altitudes_fit/one_fold_analysis/one_fold_fit.py
index 56cf37f9..898f825f 100644
--- a/projects/altitude_spatial_model/altitudes_fit/one_fold_analysis/one_fold_fit.py
+++ b/projects/altitude_spatial_model/altitudes_fit/one_fold_analysis/one_fold_fit.py
@@ -116,7 +116,8 @@ class OneFoldFit(object):
 
     @property
     def best_shape(self):
-        # We take any altitude (altitude=1000 for instance) as the shape is constant w.r.t the altitude
+        # We take any altitude (altitude=1000 for instance) and any year
+        # as the shape is constant w.r.t the altitude and the year
         return self.get_gev_params(altitude=1000, year=2019).shape
 
     def best_coef(self, param_name, dim, degree):
diff --git a/projects/exceeding_snow_loads/section_results/main_result_trends_and_return_levels.py b/projects/exceeding_snow_loads/section_results/main_result_trends_and_return_levels.py
index 62055821..c77b8374 100644
--- a/projects/exceeding_snow_loads/section_results/main_result_trends_and_return_levels.py
+++ b/projects/exceeding_snow_loads/section_results/main_result_trends_and_return_levels.py
@@ -74,7 +74,7 @@ def intermediate_result(altitudes, massif_names=None,
         # Plots
         # plot_trend_map(altitude_to_visualizer)
         # plot_trend_curves(altitude_to_visualizer={a: v for a, v in altitude_to_visualizer.items() if a >= 900})
-        plot_uncertainty_massifs(altitude_to_visualizer)
+        # plot_uncertainty_massifs(altitude_to_visualizer)
         plot_uncertainty_histogram(altitude_to_visualizer)
         # plot_selection_curves(altitude_to_visualizer)
         # plot_intensity_against_gumbel_quantile_for_3_examples(altitude_to_visualizer)
diff --git a/projects/exceeding_snow_loads/section_results/plot_uncertainty_histogram.py b/projects/exceeding_snow_loads/section_results/plot_uncertainty_histogram.py
index 923e9211..88272fb2 100644
--- a/projects/exceeding_snow_loads/section_results/plot_uncertainty_histogram.py
+++ b/projects/exceeding_snow_loads/section_results/plot_uncertainty_histogram.py
@@ -1,4 +1,4 @@
-from typing import Dict
+from typing import Dict, Tuple
 import matplotlib.pyplot as plt
 import numpy as np
 
@@ -39,6 +39,7 @@ def plot_histogram(altitude_to_visualizer, model_subset_for_uncertainty):
     fontsize_label = 15
     legend_size = 15
     # Plot histogram
+    ylim = (0, 110)
     for j, ci_method in enumerate(visualizer.uncertainty_methods):
         if len(visualizer.uncertainty_methods) == 2:
             offset = -50 if j == 0 else 50
@@ -51,7 +52,7 @@ def plot_histogram(altitude_to_visualizer, model_subset_for_uncertainty):
         plot_histogram_ci_method(visualizers, model_subset_for_uncertainty, ci_method, ax, bincenters, width, legend_size)
 
         # Plot percentages of return level excess on the right axis
-        plot_percentage_of_excess(visualizers, model_subset_for_uncertainty, ci_method, ax.twinx(), fontsize_label, legend_size)
+        ylim = plot_percentage_of_excess(visualizers, model_subset_for_uncertainty, ci_method, ax.twinx(), fontsize_label, legend_size)
 
 
     ax.set_xticks(altitudes)
@@ -63,7 +64,7 @@ def plot_histogram(altitude_to_visualizer, model_subset_for_uncertainty):
     #               'exceeds French standards (\%)', fontsize=fontsize_label)
     ax.set_ylabel('Massifs exceeding French standards (\%)', fontsize=fontsize_label)
     ax.set_xlabel('Altitude (m)', fontsize=fontsize_label)
-    ax.set_ylim([0, 110])
+    ax.set_ylim(ylim)
     ax.yaxis.grid()
 
     ax_twiny = ax.twiny()
@@ -76,7 +77,8 @@ def plot_histogram(altitude_to_visualizer, model_subset_for_uncertainty):
     ax_twiny.set_xticklabels(nb_massif_names)
     ax_twiny.set_xlabel('Number of massifs at each altitude (for the percentage and the mean)', fontsize=fontsize_label)
 
-    ax.set_yticks([20 * i for i in range(6)])
+    nb_ticks =  1 + (ylim[1] - ylim[0]) // 20
+    ax.set_yticks([100 - 20 * i for i in range(nb_ticks)][::-1])
     visualizer.plot_name = 'Percentages of exceedance with {}'.format(
         get_display_name_from_object_type(model_subset_for_uncertainty))
     visualizer.show_or_save_to_file(no_title=True, dpi=dpi_paper1_figure, tight_layout=True)
@@ -109,7 +111,7 @@ def plot_histogram_ci_method(visualizers, model_subset_for_uncertainty, ci_metho
     ax.legend(loc='upper left', prop={'size': legend_size})
 
 
-def plot_percentage_of_excess(visualizers, model_subset_for_uncertainty, ci_method, ax, fontsize_label, legend_size=10):
+def plot_percentage_of_excess(visualizers, model_subset_for_uncertainty, ci_method, ax, fontsize_label, legend_size=10) -> Tuple[int, int]:
     l = [v.excess_metrics(ci_method, model_subset_for_uncertainty)[2] for v in visualizers]
     lower_bound, mean, upper_bound = list(zip(*l))
     other_mean = [e[1] for e in l]
@@ -121,7 +123,6 @@ def plot_percentage_of_excess(visualizers, model_subset_for_uncertainty, ci_meth
                       '50-year return levels and French standards (\%)'
 
     label_name = 'Mean relative difference'
-    print('mean relative difference', mean)
     ax.plot(altitudes, mean, linestyle='--', marker='o', color=color,
             label=label_name)
 
@@ -132,7 +133,10 @@ def plot_percentage_of_excess(visualizers, model_subset_for_uncertainty, ci_meth
     ax.tick_params(labelsize=fontsize_label)
     ax.legend(loc='upper right', prop={'size': legend_size})
     ax.set_ylabel(full_label_name, fontsize=fontsize_label)
-    ax.set_ylim([0, 110])
+
+    ylim = (-85, 110)
+    ax.set_ylim(ylim)
+    return ylim
 
 
 def get_label_confidence_interval(label_name):
-- 
GitLab