diff --git a/.idea/ENSO_metrics.iml b/.idea/ENSO_metrics.iml index e5b25bf..e8b9551 100644 --- a/.idea/ENSO_metrics.iml +++ b/.idea/ENSO_metrics.iml @@ -2,7 +2,7 @@ - + diff --git a/.idea/misc.xml b/.idea/misc.xml index 9a8a99c..1c30470 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/build/lib/EnsoMetrics/EnsoCollectionsLib.py b/build/lib/EnsoMetrics/EnsoCollectionsLib.py new file mode 100644 index 0000000..1dc8198 --- /dev/null +++ b/build/lib/EnsoMetrics/EnsoCollectionsLib.py @@ -0,0 +1,995 @@ +# -*- coding:UTF-8 -*- +# +# Define ENSO metrics collections as a function of science question/realm +# +# Draft version +# + + +# Define metrics collections +def defCollection(mc=True): + # Name, list of metrics + metrics_collection = { + 'ENSO_perf': { + 'long_name': 'Metrics Collection for ENSO performance', + 'metrics_list': { + 'BiasPrLatRmse': { + 'variables': ['pr'], + 'regions': {'pr': 'nino3_LatExt'}, + 'obs_name': {'pr': ['ERA-Interim', 'GPCPv2.3']}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + }, + 'BiasPrLonRmse': { + 'variables': ['pr'], + 'regions': {'pr': 'equatorial_pacific'}, + 'obs_name': {'pr': ['ERA-Interim', 'GPCPv2.3']}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + }, + 'BiasSshLatRmse': { + 'variables': ['ssh'], + 'regions': {'ssh': 'nino3_LatExt'}, + 'obs_name': {'ssh': ['AVISO']}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + }, + 'BiasSshLonRmse': { + 'variables': ['ssh'], + 'regions': {'ssh': 'equatorial_pacific'}, + 'obs_name': {'ssh': ['AVISO']}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + }, + 'BiasSstLatRmse': { + 'variables': ['sst'], + 'regions': {'sst': 'nino3_LatExt'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + }, + 'BiasSstLonRmse': { + 'variables': ['sst'], + 'regions': {'sst': 'equatorial_pacific'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + }, + 'BiasTauxLatRmse': { + 'variables': ['taux'], + 'regions': {'taux': 'equatorial_pacific_LatExt'}, + 'obs_name': {'taux': ['ERA-Interim', 'Tropflux']}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + }, + 'BiasTauxLonRmse': { + 'variables': ['taux'], + 'regions': {'taux': 'equatorial_pacific'}, + 'obs_name': {'taux': ['ERA-Interim', 'Tropflux']}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + }, + 'SeasonalPrLatRmse': { + 'variables': ['pr'], + 'regions': {'pr': 'nino3_LatExt'}, + 'obs_name': {'pr': ['ERA-Interim', 'GPCPv2.3']}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + }, + 'SeasonalPrLonRmse': { + 'variables': ['pr'], + 'regions': {'pr': 'equatorial_pacific'}, + 'obs_name': {'pr': ['ERA-Interim', 'GPCPv2.3']}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + }, + 'SeasonalSshLatRmse': { + 'variables': ['ssh'], + 'regions': {'ssh': 'nino3_LatExt'}, + 'obs_name': {'ssh': ['AVISO']}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + }, + 'SeasonalSshLonRmse': { + 'variables': ['ssh'], + 'regions': {'ssh': 'equatorial_pacific'}, + 'obs_name': {'ssh': ['AVISO']}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + }, + 'SeasonalSstLatRmse': { + 'variables': ['sst'], + 'regions': {'sst': 'nino3_LatExt'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + }, + 'SeasonalSstLonRmse': { + 'variables': ['sst'], + 'regions': {'sst': 'equatorial_pacific'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + }, + 'SeasonalTauxLatRmse': { + 'variables': ['taux'], + 'regions': {'taux': 'equatorial_pacific_LatExt'}, + 'obs_name': {'taux': ['ERA-Interim', 'Tropflux']}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + }, + 'SeasonalTauxLonRmse': { + 'variables': ['taux'], + 'regions': {'taux': 'equatorial_pacific'}, + 'obs_name': {'sst': ['ERA-Interim', 'Tropflux']}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + }, + 'EnsoAmpl': { + 'variables': ['sst'], + 'regions': {'sst': 'nino3.4'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + 'metric_computation': 'abs_relative_difference', + 'regridding': {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'}, + }, + 'EnsoDuration': { + 'variables': ['sst'], + 'regions': {'sst': 'nino3.4'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + 'normalization': True}, + 'nbr_years_window': 6, + 'smoothing': {'window': 5, 'method': 'triangle'}, + 'metric_computation': 'abs_relative_difference', + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + }, + 'EnsoSeasonality': { + 'variables': ['sst'], + 'regions': {'sst': 'nino3.4'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + 'metric_computation': 'abs_relative_difference', + 'regridding': {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'}, + }, + 'EnsoSstLonRmse': { + 'variables': ['sst'], + 'regions': {'sst': 'equatorial_pacific'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + 'normalization': True}, + 'smoothing': {'window': 5, 'method': 'triangle'}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + }, + 'EnsoSstDiversity_1': { + 'variables': ['sst'], + 'regions': {'sst': 'equatorial_pacific'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + 'normalization': False}, + 'smoothing': {'window': 5, 'method': 'triangle'}, + 'regridding': {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'}, + 'metric_computation': 'abs_relative_difference', + }, + 'EnsoSstDiversity_2': { + 'variables': ['sst'], + 'regions': {'sst': 'equatorial_pacific'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + 'normalization': True}, + 'smoothing': {'window': 5, 'method': 'triangle'}, + 'regridding': {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'}, + 'metric_computation': 'abs_relative_difference', + }, + 'EnsoSstSkew': { + 'variables': ['sst'], + 'regions': {'sst': 'nino3.4'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + 'metric_computation': 'abs_relative_difference', + 'regridding': {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'}, + }, + # 'EnsoPrTsRmse': { + # 'variables': ['sst', 'pr'], + # 'regions': {'sst': 'nino3.4', 'pr': 'nino3'}, + # 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux'], 'pr': ['ERA-Interim', 'GPCPv2.3']}, + # 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + # 'normalization': True}, + # 'nbr_years_window': 6, + # 'smoothing': {'window': 5, 'method': 'triangle'}, + # 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + # 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + # }, + 'EnsoSstTsRmse': { + 'variables': ['sst'], + 'regions': {'sst': 'nino3.4'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + 'normalization': True}, + 'nbr_years_window': 6, + 'smoothing': {'window': 5, 'method': 'triangle'}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + }, + # 'EnsoTauxTsRmse': { + # 'variables': ['sst', 'taux'], + # 'regions': {'sst': 'nino3.4', 'taux': 'nino4'}, + # 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux'], 'taux': ['ERA-Interim', 'Tropflux']}, + # 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + # 'normalization': True}, + # 'nbr_years_window': 6, + # 'smoothing': {'window': 5, 'method': 'triangle'}, + # 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + # 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + # }, + 'NinoSstDiversity_1': { + 'variables': ['sst'], + 'regions': {'sst': 'equatorial_pacific'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + 'normalization': False}, + 'smoothing': {'window': 5, 'method': 'triangle'}, + 'regridding': {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'}, + 'metric_computation': 'abs_relative_difference', + }, + 'NinoSstDiversity_2': { + 'variables': ['sst'], + 'regions': {'sst': 'equatorial_pacific'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + 'normalization': True}, + 'smoothing': {'window': 5, 'method': 'triangle'}, + 'regridding': {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'}, + 'metric_computation': 'abs_relative_difference', + }, + }, + 'common_collection_parameters': { + 'detrending': {'method': 'linear'}, + 'frequency': 'monthly', + 'min_time_steps': 204, + 'normalization': False, + 'project_interpreter': 'CMIP', + 'observed_period': ('1850-01-01 00:00:00', '2018-12-31 23:59:60.0'), + 'modeled_period': ('1850-01-01 00:00:00', '2015-12-31 23:59:60.0'), + }, + 'plot_order': ['BiasPrLatRmse', 'BiasPrLonRmse', 'BiasSstLonRmse', 'BiasTauxLonRmse', + 'SeasonalPrLatRmse', 'SeasonalPrLonRmse', 'SeasonalSstLonRmse', 'SeasonalTauxLonRmse', + 'EnsoSstLonRmse', 'EnsoSstTsRmse', 'EnsoAmpl', 'EnsoSeasonality', 'EnsoSstSkew', + 'EnsodDuration', 'EnsoSstDiversity_2'], + 'description': 'Describe which science question this collection is about', + }, + 'ENSO_tel': { + 'long_name': 'Metrics Collection for ENSO teleconnections', + 'metrics_list': { + 'EnsoAmpl': { + 'variables': ['sst'], + 'regions': {'sst': 'nino3.4'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + 'metric_computation': 'abs_relative_difference', + }, + 'EnsoSeasonality': { + 'variables': ['sst'], + 'regions': {'sst': 'nino3.4'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + 'metric_computation': 'abs_relative_difference', + 'regridding': {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'}, + }, + 'EnsoSstLonRmse': { + 'variables': ['sst'], + 'regions': {'sst': 'equatorial_pacific'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + 'normalization': True}, + 'smoothing': {'window': 5, 'method': 'triangle'}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + }, + 'EnsoPrMapDjf': { + 'variables': ['sst', 'pr'], + 'regions': {'pr': 'global', 'sst': 'nino3.4'}, + 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + 'normalization': False}, + 'smoothing': {'window': 5, 'method': 'triangle'}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + 'obs_name': {'pr': ['ERA-Interim', 'GPCPv2.3'], 'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + }, + 'EnsoPrMapJja': { + 'variables': ['sst', 'pr'], + 'regions': {'pr': 'global', 'sst': 'nino3.4'}, + 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + 'normalization': False}, + 'smoothing': {'window': 5, 'method': 'triangle'}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + 'obs_name': {'pr': ['ERA-Interim', 'GPCPv2.3'], 'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + }, + 'EnsoSlpMapDjf': { + 'variables': ['sst', 'slp'], + 'regions': {'slp': 'global', 'sst': 'nino3.4'}, + 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + 'normalization': False}, + 'smoothing': {'window': 5, 'method': 'triangle'}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + 'obs_name': {'slp': ['AVISO'], 'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + }, + 'EnsoSlpMapJja': { + 'variables': ['sst', 'slp'], + 'regions': {'slp': 'global', 'sst': 'nino3.4'}, + 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + 'normalization': False}, + 'smoothing': {'window': 5, 'method': 'triangle'}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + 'obs_name': {'slp': ['AVISO'], 'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + }, + 'EnsoSstMapDjf': { + 'variables': ['sst'], + 'regions': {'sst': 'global'}, + 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + 'normalization': False}, + 'smoothing': {'window': 5, 'method': 'triangle'}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + }, + 'EnsoSstMapJja': { + 'variables': ['sst'], + 'regions': {'sst': 'global'}, + 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + 'normalization': False}, + 'smoothing': {'window': 5, 'method': 'triangle'}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + }, + }, + 'common_collection_parameters': { + 'detrending': {'method': 'linear'}, + 'frequency': 'monthly', + 'min_time_steps': 204, + 'normalization': False, + 'project_interpreter': 'CMIP', + 'observed_period': ('1850-01-01 00:00:00', '2018-12-31 23:59:60.0'), + 'modeled_period': ('1850-01-01 00:00:00', '2015-12-31 23:59:60.0'), + }, + 'plot_order': ['EnsoSstLonRmse', 'EnsoAmpl', 'EnsoSeasonality', 'EnsoPrMapDjfRmse', 'EnsoPrMapJjaRmse', + 'EnsoSstMapDjfRmse', 'EnsoSstMapJjaRmse'], + 'description': 'Describe which science question this collection is about', + }, + 'ENSO_proc': { + 'long_name': 'Metrics Collection for ENSO processes', + 'metrics_list': { + 'BiasSstLonRmse': { + 'variables': ['sst'], + 'regions': {'sst': 'equatorial_pacific'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + }, + 'BiasTauxLonRmse': { + 'variables': ['taux'], + 'regions': {'taux': 'equatorial_pacific'}, + 'obs_name': {'taux': ['ERA-Interim', 'Tropflux']}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + }, + 'EnsoAmpl': { + 'variables': ['sst'], + 'regions': {'sst': 'nino3.4'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + 'metric_computation': 'abs_relative_difference', + }, + 'EnsoSstLonRmse': { + 'variables': ['sst'], + 'regions': {'sst': 'equatorial_pacific'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + 'normalization': True}, + 'smoothing': {'window': 5, 'method': 'triangle'}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + }, + 'EnsoSeasonality': { + 'variables': ['sst'], + 'regions': {'sst': 'nino3.4'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + 'metric_computation': 'abs_relative_difference', + 'regridding': {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'}, + }, + 'EnsoSstSkew': { + 'variables': ['sst'], + 'regions': {'sst': 'nino3.4'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + 'metric_computation': 'abs_relative_difference', + 'regridding': {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'}, + }, + 'EnsodSstOce_1': { + 'variables': ['sst', 'thf'], + 'regions': {'sst': 'nino3', 'thf': 'nino3'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux'], 'thf': ['ERA-Interim', 'Tropflux']}, + 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + 'normalization': False}, + 'smoothing': {'window': 5, 'method': 'triangle'}, + 'regridding': {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'}, + 'metric_computation': 'abs_relative_difference', + }, + 'EnsodSstOce_2': { + 'variables': ['sst', 'thf'], + 'regions': {'sst': 'nino3', 'thf': 'nino3'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux'], 'thf': ['ERA-Interim', 'Tropflux']}, + 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + 'normalization': True}, + 'smoothing': {'window': 5, 'method': 'triangle'}, + 'regridding': {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'}, + 'metric_computation': 'abs_relative_difference', + }, + 'EnsoFbSshSst': { + 'variables': ['sst', 'ssh'], + 'regions': {'sst': 'nino3', 'ssh': 'nino3'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux'], 'ssh': ['AVISO']}, + 'regridding': {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'}, + 'metric_computation': 'abs_relative_difference', + }, + 'EnsoFbSstLhf': { + 'variables': ['sst', 'lhf'], + 'regions': {'sst': 'nino3', 'lhf': 'nino3'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux'], 'lhf': ['ERA-Interim', 'Tropflux']}, + 'regridding': {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'}, + 'metric_computation': 'abs_relative_difference', + }, + 'EnsoFbSstLwr': { + 'variables': ['sst', 'lwr'], + 'regions': {'sst': 'nino3', 'lwr': 'nino3'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux'], 'lwr': ['ERA-Interim', 'Tropflux']}, + 'regridding': {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'}, + 'metric_computation': 'abs_relative_difference', + }, + 'EnsoFbSstShf': { + 'variables': ['sst', 'shf'], + 'regions': {'sst': 'nino3', 'shf': 'nino3'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux'], 'shf': ['ERA-Interim', 'Tropflux']}, + 'regridding': {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'}, + 'metric_computation': 'abs_relative_difference', + }, + 'EnsoFbSstSwr': { + 'variables': ['sst', 'swr'], + 'regions': {'sst': 'nino3', 'swr': 'nino3'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux'], 'swr': ['ERA-Interim', 'Tropflux']}, + 'regridding': {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'}, + 'metric_computation': 'abs_relative_difference', + }, + 'EnsoFbSstTaux': { + 'variables': ['sst', 'taux'], + 'regions': {'sst': 'nino3', 'taux': 'nino4'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux'], 'taux': ['ERA-Interim', 'Tropflux']}, + 'regridding': {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'}, + 'metric_computation': 'abs_relative_difference', + }, + 'EnsoFbSstThf': { + 'variables': ['sst', 'thf'], + 'regions': {'sst': 'nino3', 'thf': 'nino3'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux'], 'thf': ['ERA-Interim', 'Tropflux']}, + 'regridding': {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'}, + 'metric_computation': 'abs_relative_difference', + }, + 'EnsoFbTauxSsh': { + 'variables': ['taux', 'ssh'], + 'regions': {'ssh': 'nino3', 'taux': 'nino4'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux'], 'ssh': ['AVISO']}, + 'regridding': {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'}, + 'metric_computation': 'abs_relative_difference', + }, + }, + 'common_collection_parameters': { + 'detrending': {'method': 'linear'}, + 'frequency': 'monthly', + 'min_time_steps': 204, + 'normalization': False, + 'project_interpreter': 'CMIP', + 'observed_period': ('1850-01-01 00:00:00', '2018-12-31 23:59:60.0'), + 'modeled_period': ('1850-01-01 00:00:00', '2015-12-31 23:59:60.0'), + }, + 'plot_order': ['BiasSstLonRmse', 'BiasTauxLonRmse', 'EnsoSstLonRmse', 'EnsoAmpl', 'EnsoSeasonality', + 'EnsoSstSkew', 'EnsodSstOce_2', 'EnsoFbSstThf', 'EnsoFbSstTaux', 'EnsoFbTauxSsh', + 'EnsoFbSshSst'], + 'description': 'Describe which science question this collection is about', + }, + 'test_tel': { + 'long_name': 'Metrics Collection for ENSO teleconnections', + 'metrics_list': { + 'EnsoPrMapDjf': { + 'variables': ['sst', 'pr'], + 'regions': {'pr': 'global', 'sst': 'nino3.4'}, + 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + 'normalization': False}, + 'smoothing': {'window': 5, 'method': 'triangle'}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + 'obs_name': {'pr': ['ERA-Interim', 'GPCPv2.3'], 'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + }, + 'EnsoPrMapJja': { + 'variables': ['sst', 'pr'], + 'regions': {'pr': 'global', 'sst': 'nino3.4'}, + 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + 'normalization': False}, + 'smoothing': {'window': 5, 'method': 'triangle'}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + 'obs_name': {'pr': ['ERA-Interim', 'GPCPv2.3'], 'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + }, + 'EnsoSlpMapDjf': { + 'variables': ['sst', 'slp'], + 'regions': {'slp': 'global', 'sst': 'nino3.4'}, + 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + 'normalization': False}, + 'smoothing': {'window': 5, 'method': 'triangle'}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + 'obs_name': {'slp': ['AVISO'], 'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + }, + 'EnsoSlpMapJja': { + 'variables': ['sst', 'slp'], + 'regions': {'slp': 'global', 'sst': 'nino3.4'}, + 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + 'normalization': False}, + 'smoothing': {'window': 5, 'method': 'triangle'}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + 'obs_name': {'slp': ['AVISO'], 'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + }, + 'EnsoSstMapDjf': { + 'variables': ['sst'], + 'regions': {'sst': 'global'}, + 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + 'normalization': False}, + 'smoothing': {'window': 5, 'method': 'triangle'}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + }, + 'EnsoSstMapJja': { + 'variables': ['sst'], + 'regions': {'sst': 'global'}, + 'event_definition': {'region_ev': 'nino3.4', 'season_ev': 'DEC', 'threshold': 0.75, + 'normalization': False}, + 'smoothing': {'window': 5, 'method': 'triangle'}, + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'}, + 'obs_name': {'sst': ['ERA-Interim', 'HadISST', 'Tropflux']}, + }, + }, + 'common_collection_parameters': { + 'detrending': {'method': 'linear'}, + 'frequency': 'monthly', + 'min_time_steps': 204, + 'normalization': False, + 'project_interpreter': 'CMIP', + 'observed_period': ('1850-01-01 00:00:00', '2018-12-31 23:59:60.0'), + 'modeled_period': ('1850-01-01 00:00:00', '2015-12-31 23:59:60.0'), + }, + 'plot_order': ['EnsoSstLonRmse', 'EnsoAmpl', 'EnsoSeasonality', 'EnsoPrMapDjfRmse', 'EnsoPrMapJjaRmse', + 'EnsoSstMapDjfRmse', 'EnsoSstMapJjaRmse'], + 'description': 'Describe which science question this collection is about', + }, + } + if mc is True: + return metrics_collection + else: + return metrics_collection[mc] + + +# List of reference observations for each variables +def ReferenceObservations(dataset=True): + dict_ref_obs = { + '20CRv2': { + 'website': 'https://www.esrl.noaa.gov/psd/data/gridded/data.20thC_ReanV2.monolevel.mm.html', + 'file_name': '' + '*.mon.mean.nc', + 'variable_name_in_file': { + 'landmask': {'var_name': 'land'}, + 'lhf': {'var_name': 'lhtfl'}, + # longwave radiation computed from these variables IN THAT ORDER (on ocean grid or ocean points only) + # lwr = dlwrf - ulwrf + 'lwr': {'var_name': ['dlwrf', 'ulwrf'], 'algebric_calculation': ['plus', 'minus']}, + 'pr': {'var_name': 'prate'}, + 'slp': {'var_name': 'press'}, + 'shf': {'var_name': 'shtfl'}, + 'sst': {'var_name': 'air'}, + # shortwave radiation computed from these variables IN THAT ORDER (on ocean grid or ocean points only) + # swr = dswrf - uswrf + 'swr': {'var_name': ['dswrf', 'uswrf'], 'algebric_calculation': ['plus', 'minus']}, + 'taux': {'var_name': 'uflx'}, + 'tauy': {'var_name': 'vflx'}, + # total heat flux computed from these variables IN THAT ORDER (on ocean grid or ocean points only) + # tfh = lhtfl + shtfl + dlwrf - ulwrf + dswrf - uswrf + 'thf': { + 'var_name': ['lhtfl', 'shtfl', 'dlwrf', 'ulwrf', 'dswrf', 'uswrf'], + 'algebric_calculation': ['plus', 'plus', 'plus', 'minus', 'plus', 'minus'], + }, + }, + }, + '20CRv3': { + 'website': 'https://www.esrl.noaa.gov/psd/data/gridded/data.20thC_ReanV3.monolevel.html', + 'file_name': '' + '*.mon.mean.nc', + 'variable_name_in_file': { + 'landmask': {'var_name': 'land'}, + 'lhf': {'var_name': 'lhtfl'}, + # longwave radiation computed from these variables IN THAT ORDER (on ocean grid or ocean points only) + # lwr = dlwrf - ulwrf + 'lwr': {'var_name': ['dlwrf', 'ulwrf'], 'algebric_calculation': ['plus', 'minus']}, + 'pr': {'var_name': 'prate'}, + 'slp': {'var_name': 'prmsl'}, + 'shf': {'var_name': 'shtfl'}, + 'sst': {'var_name': 'skt'}, + # shortwave radiation computed from these variables IN THAT ORDER (on ocean grid or ocean points only) + # swr = dswrf - uswrf + 'swr': {'var_name': ['dswrf', 'uswrf'], 'algebric_calculation': ['plus', 'minus']}, + # total heat flux computed from these variables IN THAT ORDER (on ocean grid or ocean points only) + # tfh = lhtfl + shtfl + dlwrf - ulwrf + dswrf - uswrf + 'thf': { + 'var_name': ['lhtfl', 'shtfl', 'dlwrf', 'ulwrf', 'dswrf', 'uswrf'], + 'algebric_calculation': ['plus', 'plus', 'plus', 'minus', 'plus', 'minus'], + }, + }, + }, + 'AVISO': { + 'website': 'https://www.aviso.altimetry.fr/en/data/products/sea-surface-height-products/global.html', + 'file_name': 'dt_global_allsat_msla_h_y????_m??.nc', + 'variable_name_in_file': {'ssh': {'var_name': 'sla'}, }, + }, + 'CFSR': { + 'website': 'see https://esgf.nccs.nasa.gov/search/create-ip/', + 'file_name': '' + '_Omon_reanalysis_CFSR_*.nc', + 'variable_name_in_file': { + 'ssh': {'var_name': 'zos'}, + 'so': {'var_name': 'so'}, + 'thetao': {'var_name': 'thetao'}, + 'thf': {'var_name': 'hfds'}, # I'm not sure yet if it is the total heat flux + 'uo': {'var_name': 'uo'}, + 'vo': {'var_name': 'vo'}, + }, + }, + 'CMAP': { + 'website': 'https://www.esrl.noaa.gov/psd/data/gridded/data.cmap.html', + 'file_name': '' + '.mon.mean.nc', + 'variable_name_in_file': { + 'pr': {'var_name': 'precip'}, + }, + }, + 'ERA-Interim': { + 'website': 'see https://esgf.nccs.nasa.gov/search/create-ip/', + 'file_name': '' + '_Amon_reanalysis_IFS-Cy31r2_*.nc', + 'variable_name_in_file': { + 'landmask': {'var_name': 'lsmask'}, + 'lhf': {'var_name': 'hfls'}, + # longwave radiation computed from these variables IN THAT ORDER (on ocean grid or ocean points only) + # lwr = rlds - rlus + # sometimes lwr is included in the datasets in a variable called 'rls' + 'lwr': {'var_name': ['rlds', 'rlus'], 'algebric_calculation': ['plus', 'minus']}, + 'pr': {'var_name': 'pr'}, + 'slp': {'var_name': 'psl'}, + 'shf': {'var_name': 'hfss'}, + 'sst': {'var_name': 'ts'}, + # shortwave radiation computed from these variables IN THAT ORDER (on ocean grid or ocean points only) + # swr = rsds - rsus + # sometimes swr is included in the datasets in a variable called 'rss' + 'swr': {'var_name': ['rsds', 'rsus'], 'algebric_calculation': ['plus', 'minus']}, + 'taux': {'var_name': 'tauu'}, + 'tauy': {'var_name': 'tauv'}, + # total heat flux computed from these variables IN THAT ORDER (on ocean grid or ocean points only) + # tfh = hfls + hfss + rlds - rlus + rsds - rsus + # sometimes rls = rlds - rlus and rss = rsds - rsus + # sometimes thf is included in the datasets in a variable called 'hfds', 'netflux', 'thflx',... + 'thf': { + 'var_name': ['hfls', 'hfss', 'rlds', 'rlus', 'rsds', 'rsus'], + 'algebric_calculation': ['plus', 'plus', 'plus', 'minus', 'plus', 'minus'], + }, + 'uas': {'var_name': 'uas'}, + 'vas': {'var_name': 'vas'}, + }, + }, + 'ERSSTv5': { + 'website': 'see https://www1.ncdc.noaa.gov/pub/data/cmb/ersst/v5/netcdf/', + 'file_name': 'ersst.v5.' + '' + '.nc', + 'variable_name_in_file': { + 'sst': {'var_name': 'sst'}, + }, + }, + 'GODAS': { + 'website': 'https://www.esrl.noaa.gov/psd/data/gridded/data.godas.html', + 'file_name': '' + '_YYYY.nc', + 'variable_name_in_file': { + 'ssh': {'var_name': 'sshg'}, + 'taux': {'var_name': 'uflx'}, + 'tauy': {'var_name': 'vflx'}, + 'thf': {'var_name': 'thflx'}, + }, + }, + 'GPCPv2.3': { + 'website': 'see https://www.esrl.noaa.gov/psd/cgi-bin/db_search/DBSearch.pl?Dataset=GPCP+Version+2.3+' + + 'Combined+Precipitation+Dataset&group=0&submit=Search', + 'file_name': 'precip.mon.mean.nc', + 'variable_name_in_file': { + 'landmask': {'var_name': 'lsmask'}, + 'pr': {'var_name': 'precip'}, + }, + }, + 'HadISST': { + 'website': 'see https://www.metoffice.gov.uk/hadobs/hadisst/data/download.html', + 'file_name': 'HadISST_' + '' + '.nc', + 'variable_name_in_file': { + 'sst': {'var_name': 'sst'}, + }, + }, + 'NCEP2': { + 'website': 'see https://www.esrl.noaa.gov/psd/data/gridded/data.ncep.reanalysis2.gaussian.html', + 'file_name': '' + '.sfc.mon.mean.nc', + 'variable_name_in_file': { + 'landmask': {'var_name': 'land'}, + 'lhf': {'var_name': 'lhtfl'}, + # longwave radiation computed from these variables IN THAT ORDER (on ocean grid or ocean points only) + # lwr = rlds - rlus + 'lwr': {'var_name': ['dlwrf', 'ulwrf'], 'algebric_calculation': ['plus', 'minus']}, + 'pr': {'var_name': 'prate'}, + 'shf': {'var_name': 'shtfl'}, + 'slp': {'var_name': 'pres'}, + 'sst': {'var_name': 'skt'}, + # shortwave radiation computed from these variables IN THAT ORDER (on ocean grid or ocean points only) + # swr = rsds - rsus + 'swr': {'var_name': ['dswrf', 'uswrf'], 'algebric_calculation': ['plus', 'minus']}, + 'taux': {'var_name': 'uflx'}, + 'tauy': {'var_name': 'vflx'}, + 'thf': { + 'var_name': ['lhtfl', 'shtfl', 'dlwrf', 'ulwrf', 'dswrf', 'uswrf'], + 'algebric_calculation': ['plus', 'plus', 'plus', 'minus', 'plus', 'minus'], + }, + }, + }, + 'OAFlux': { + 'website': 'see ftp://ftp.whoi.edu/pub/science/oaflux/data_v3/monthly/turbulence/', + 'file_name': '' + '_oaflux_*.nc', + 'variable_name_in_file': { + 'lhf': {'var_name': 'lhtfl'}, + 'shf': {'var_name': 'shtfl'}, + 'sst': {'var_name': 'tmpsf'}, + }, + }, + 'OISST': { + 'website': 'see https://www.earthsystemcog.org/search/obs4mips/?template=obs4mips&limit=200', + 'file_name': '' + '_OISST_L4_AVHRR-only-v2_*-*.nc', + 'variable_name_in_file': { + 'sst': {'var_name': 'sst'}, + }, + }, + 'ORAS4': { + 'website': 'see https://esgf.nccs.nasa.gov/search/create-ip/', + 'file_name': '' + '_Omon_ORAreanalysis_ORAS4_*.nc', + 'variable_name_in_file': { + 'so': {'var_name': 'so'}, + 'thetao': {'var_name': 'thetao'}, + 'uo': {'var_name': 'uo'}, + 'vo': {'var_name': 'vo'}, + }, + }, + 'SODA3.4.2': { + 'website': 'see https://www.atmos.umd.edu/~ocean/index_files/soda3.4.2_mn_download_b.htm', + 'file_name': 'soda3.4.2_mn_ocean_reg_????.nc', + 'variable_name_in_file': { + 'so': {'var_name': 'salt'}, + 'ssh': {'var_name': 'ssh'}, + 'taux': {'var_name': 'taux'}, + 'thetao': {'var_name': 'temp'}, + 'thf': {'var_name': 'net_heating'}, + 'uo': {'var_name': 'u'}, + 'vo': {'var_name': 'v'}, + }, + }, + 'Tropflux': { + 'website': 'see https://incois.gov.in/tropflux/tf_products.jsp', + 'file_name': '' + '_tropflux_1m_*.nc', + 'variable_name_in_file': { + 'lhf': {'var_name': 'lhf'}, + 'lwr': {'var_name': 'lwr'}, + 'shf': {'var_name': 'shf'}, + 'sst': {'var_name': 'sst'}, + 'swr': {'var_name': 'swr'}, + 'taux': {'var_name': 'taux'}, + 'thf': {'var_name': 'netflux'}, + }, + }, + } + if dataset is True: + return dict_ref_obs + else: + return dict_ref_obs[dataset] + + +def ReferenceRegions(region=True): + dict_reference_regions = { + 'global': {'long_name': 'Global 60S-60N', 'latitude': (-60., 60.), 'longitude': (0., 360.)}, + 'global2': {'long_name': 'Global', 'latitude': (-90., 90.), 'longitude': (0., 360.)}, + 'tropical_pacific': { + 'long_name': 'Tropical Pacific (TP)', 'latitude': (-30., 30.), 'longitude': (120., 280.), + }, + 'equatorial_pacific': { + 'long_name': 'Equatorial Pacific (EP)', 'latitude': (-5., 5.), 'longitude': (150., 270.), + }, + 'equatorial_pacific_LatExt': { + 'long_name': 'Equatorial Pacific (EP)', 'latitude': (-15., 15.), 'longitude': (150., 270.), + }, + 'equatorial_pacific_LatExt2': { + 'long_name': 'Equatorial Pacific extended in latitude', 'latitude': (-15., 15.), 'longitude': (120., 285.), + }, + 'eastern_equatorial_pacific': { + 'long_name': 'Western Equatorial Pacific (WEP)', 'latitude': (-5., 5.), 'longitude': (205., 280.), + }, + 'western_equatorial_pacific': { + 'long_name': 'Eastern Equatorial Pacific (EEP)', 'latitude': (-5., 5.), 'longitude': (120., 205.), + }, + 'nino1+2': {'long_name': 'Niño 1+2', 'latitude': (-10., 0.), 'longitude': (270., 280.)}, + 'nino3': {'long_name': 'Niño 3', 'latitude': (-5., 5.), 'longitude': (210., 270.)}, + 'nino3_LatExt': { + 'long_name': 'Niño 3 extended in latitude', 'latitude': (-15., 15.), 'longitude': (210., 270.) + }, + 'nino3.4': {'long_name': 'Niño 3.4', 'latitude': (-5., 5.), 'longitude': (190., 240.)}, + 'nino4': {'long_name': 'Niño 4', 'latitude': (-5., 5.), 'longitude': (160., 210.)}, + # AR5 reference regions + 'ALA': {'long_name': 'Alaska/N.W. Canada', 'latitude': (60., 72.6), 'longitude': (192., 255.), + 'maskland': False, 'maskocean': True}, + # 'AMZ': {'long_name': 'Amazon', 'polygon shaped region, I do not know how to select it'}, + # 'CAM': {'long_name': 'Central America/Mexico', 'polygon shaped region, I do not know how to select it'}, + 'CAS': {'long_name': 'Central Asia', 'latitude': (30., 50.), 'longitude': (60., 75.), 'maskland': False, + 'maskocean': True}, + # 'CEU': {'long_name': 'Central Europe', 'polygon shaped region, I do not know how to select it'}, + 'CGI': {'long_name': 'Canada/Greenland/Iceland', 'latitude': (50., 85.), 'longitude': (255., 350.), + 'maskland': False, 'maskocean': True}, + 'CNA': {'long_name': 'Central North America', 'latitude': (28.6, 50.), 'longitude': (255., 275.), + 'maskland': False, 'maskocean': True}, + 'EAF': {'long_name': 'East Africa', 'latitude': (-11.4, 15.), 'longitude': (25., 52.), 'maskland': False, + 'maskocean': True}, + 'EAS': {'long_name': 'East Asia', 'latitude': (20., 50.), 'longitude': (100., 145.), 'maskland': False, + 'maskocean': True}, + 'ENA': {'long_name': 'East North America', 'latitude': (25., 50.), 'longitude': (275., 300.), 'maskland': False, + 'maskocean': True}, + 'MED': {'long_name': 'South Europe/Mediterranean', 'latitude': (30., 45.), 'longitude': (350., 400.), + 'maskland': False, 'maskocean': True}, + 'NAS': {'long_name': 'North Asia', 'latitude': (50., 70.), 'longitude': (40., 180.), 'maskland': False, + 'maskocean': True}, + 'NAU': {'long_name': 'North Australia', 'latitude': (-30., -10.), 'longitude': (110., 155.), 'maskland': False, + 'maskocean': True}, + 'NEB': {'long_name': 'North-East Brazil', 'latitude': (-20., 0.), 'longitude': (310., 326.), 'maskland': False, + 'maskocean': True}, + # 'NEU': {'long_name': 'North Europe', 'polygon shaped region, I do not know how to select it'}, + 'SAF': {'long_name': 'Southern Africa', 'latitude': (-35., -11.4), 'longitude': (350., 412.), 'maskland': False, + 'maskocean': True}, + 'SAH': {'long_name': 'Sahara', 'latitude': (15., 30.), 'longitude': (340., 400.), 'maskland': False, + 'maskocean': True}, + # 'SAS': {'long_name': 'South Asia', 'polygon shaped region, I do not know how to select it'}, + 'SAU': {'long_name': 'South Australia/New Zealand', 'latitude': (-50., -30.), 'longitude': (110., 180.), + 'maskland': False, 'maskocean': True}, + 'SEA': {'long_name': 'Southeast Asia', 'latitude': (-10., 20.), 'longitude': (95., 155.), 'maskland': False, + 'maskocean': False}, + # 'SSA': {'long_name': 'Southeastern South America', 'polygon shaped region, I do not know how to select it'}, + 'TIB': {'long_name': 'Tibetan Plateau', 'latitude': (30., 50.), 'longitude': (75., 100.), 'maskland': False, + 'maskocean': True}, + 'WAF': {'long_name': 'West Africa', 'latitude': (-11.4, 15.), 'longitude': (340., 385.), 'maskland': False, + 'maskocean': True}, + 'WAS': {'long_name': 'West Asia', 'latitude': (15., 50.), 'longitude': (40., 60.), 'maskland': False, + 'maskocean': True}, + 'WNA': {'long_name': 'West North America', 'latitude': (28.6, 60.), 'longitude': (230., 255.), + 'maskland': False, 'maskocean': True}, + # 'WSA': {'long_name': 'West Coast South America', 'polygon shaped region, I do not know how to select it'}, + # non-SREX reference regions + 'ANT': {'long_name': 'Antarctica', 'latitude': (-90., -50.), 'longitude': (0., 360.), 'maskland': False, + 'maskocean': False}, + 'ARC': {'long_name': 'Arctic', 'latitude': (67.5, 90.), 'longitude': (0., 360.), 'maskland': False, + 'maskocean': False}, + # 'CAR': { + # 'long_name': 'Caribbean', 'polygon shaped region, I do not know how to select it' + # }, + 'NTP': {'long_name': 'Northern Tropical Pacific', 'latitude': (5., 25.), 'longitude': (155., 210.)}, + 'STP': {'long_name': 'Southern Topical Pacific', 'latitude': (-25., -5.), 'longitude': (155., 230.)}, + 'ETP': {'long_name': 'Equatorial Tropical Pacific', 'latitude': (-5., 5.), 'longitude': (155., 210.)}, + 'WIO': {'long_name': 'West Indian Ocean', 'latitude': (-25., 5.), 'longitude': (52., 75.), 'maskland': False, + 'maskocean': False}, + # Power and Delage's (2018) oceanic regions + 'CEP': {'long_name': 'Central Equatorial Pacific', 'latitude': (-5., 5.), 'longitude': (180., 220.), + 'maskland': True, 'maskocean': False}, + 'CNP': {'long_name': 'Central Northern Tropical Pacific', 'latitude': (5., 15.), 'longitude': (180., 220.), + 'maskland': True, 'maskocean': False}, + 'CSP': {'long_name': 'Central Southern Tropical Pacific', 'latitude': (-15., -5.), 'longitude': (180., 220.), + 'maskland': True, 'maskocean': False}, + 'INO': {'long_name': 'Indian Ocean', 'latitude': (-25., 0.), 'longitude': (55., 95.), 'maskland': True, + 'maskocean': False}, + # YYP regions + 'africaSE': {'long_name': 'South and East Africa', 'latitude': (-40, 15.), 'longitude': (0., 55.), + 'maskland': False, 'maskocean': True}, + 'americaN': {'long_name': 'North America', 'latitude': (10., 60.), 'longitude': (235., 300.), + 'maskland': False, 'maskocean': True}, + 'americaS': {'long_name': 'South America', 'latitude': (-60., 15.), 'longitude': (275., 330.), + 'maskland': False, 'maskocean': True}, + 'asiaS': {'long_name': 'South Asia', 'latitude': (-10., 30.), 'longitude': (65., 130.), + 'maskland': False, 'maskocean': True}, + 'oceania': {'long_name': 'oceania', 'latitude': (-50., 0.), 'longitude': (110., 180.), 'maskland': False, + 'maskocean': True}, + } + if region is True: + return dict_reference_regions + else: + return dict_reference_regions[region] + + +def CmipVariables(): + dict_cmip_variables = { + 'reference': 'http://cfconventions.org/Data/cf-standard-names/46/build/cf-standard-name-table.html', + 'variable_name_in_file': { + # line keys: + # '':{'var_name':'','cf_name':, + # 'cf_unit':''} + # areacell + 'areacell': {'var_name': 'areacella', 'cf_name': 'cell_area', 'cf_units': 'm2'}, + # landmask + 'landmask': {'var_name': 'sftlf', 'cf_name': 'cell_area', 'cf_units': '1'}, + # latent heat flux + 'lhf': {'var_name': 'hfls', 'cf_name': 'land_area_fraction', 'cf_units': 'W m-2'}, + # longwave radiation computed from these variables IN THAT ORDER + # lwr = rlds - rlus + # sometimes lwr is included in the datasets in a variable called 'rls' + 'lwr': { + 'var_name': ['rlds', 'rlus'], + 'cf_name': ['surface_downwelling_longwave_flux_in_air', 'surface_upwelling_longwave_flux_in_air'], + 'cf_units': 'W m-2', 'algebric_calculation': ['plus', 'minus']}, + # Rainfall Flux + 'pr': {'var_name': 'pr', 'cf_name': 'rainfall_flux', 'cf_units': 'kg m-2 s-1'}, + # Sea Level Pressure + 'slp': {'var_name': 'psl', 'cf_name': 'air_pressure_at_mean_sea_level', 'cf_units': 'Pa'}, + # sensible heat flux (on ocean grid or ocean points only) + 'shf': {'var_name': 'hfss', 'cf_name': 'surface_upward_sensible_heat_flux', 'cf_units': 'W m-2'}, + # sea surface height + 'ssh': {'var_name': 'zos', 'cf_name': 'sea_surface_height_above_geoid', 'cf_units': 'm'}, + # sea surface temperature + 'sst': {'var_name': 'ts', 'cf_name': 'sea_surface_temperature', 'cf_units': 'K'}, + # shortwave radiation computed from these variables IN THAT ORDER + # swr = rsds - rsus + # sometimes swr is included in the datasets in a variable called 'rss' + 'swr': { + 'var_name': ['rsds', 'rsus'], + 'cf_name': ['surface_downwelling_shortwave_flux_in_air', 'surface_upwelling_shortwave_flux_in_air'], + 'cf_units': 'W m-2', 'algebric_calculation': ['plus', 'minus'] + }, + # zonal surface wind stress + 'taux': {'var_name': 'tauu', 'cf_name': 'surface_downward_eastward_stress', 'cf_units': 'Pa'}, + # total heat flux computed from these variables IN THAT ORDER + # tfh = hfls + hfss + rlds - rlus + rsds - rsus + # sometimes rls = rlds - rlus and rss = rsds - rsus + # sometimes thf is included in the datasets in a variable called 'hfds', 'netflux', 'thflx',... + 'thf': { + 'var_name': ['hfls', 'hfss', 'rlds', 'rlus', 'rsds', 'rsus'], + 'cf_name': ['surface_upward_latent_heat_flux', 'surface_upward_sensible_heat_flux', + 'surface_downwelling_longwave_flux_in_air', 'surface_upwelling_longwave_flux_in_air', + 'surface_downwelling_shortwave_flux_in_air', 'surface_upwelling_shortwave_flux_in_air'], + 'cf_units': 'W m-2', 'algebric_calculation': ['plus', 'plus', 'plus', 'minus', 'plus', 'minus'] + }, + }, + } + return dict_cmip_variables diff --git a/build/lib/EnsoMetrics/EnsoComputeMetricsLib.py b/build/lib/EnsoMetrics/EnsoComputeMetricsLib.py new file mode 100644 index 0000000..d4b0039 --- /dev/null +++ b/build/lib/EnsoMetrics/EnsoComputeMetricsLib.py @@ -0,0 +1,1094 @@ +# -*- coding:UTF-8 -*- +from copy import deepcopy +from glob import iglob as GLOBiglob +from inspect import stack as INSPECTstack +import json +from os import remove as OSremove + +# ENSO_metrics package functions: +from .EnsoCollectionsLib import defCollection, ReferenceObservations +from . import EnsoErrorsWarnings +from .EnsoMetricsLib import BiasPrLatRmse, BiasPrLonRmse, BiasPrRmse, BiasSshLatRmse, BiasSshLonRmse, BiasSshRmse,\ + BiasSstLatRmse, BiasSstLonRmse, BiasSstSkLonRmse, BiasSstRmse, BiasTauxLatRmse, BiasTauxLonRmse, BiasTauxRmse,\ + EnsoAmpl, EnsoDiversity, EnsodSstOce, EnsoDuration, EnsoFbSshSst, EnsoFbSstLhf, EnsoFbSstLwr, EnsoFbSstShf,\ + EnsoFbSstSwr, EnsoFbSstTaux, EnsoFbSstThf, EnsoFbTauxSsh, EnsoPrMap, EnsoPrMapDjf, EnsoPrMapJja, EnsoPrDjfTel,\ + EnsoPrJjaTel, EnsoPrTsRmse, EnsoSeasonality, EnsoSlpMap, EnsoSlpMapDjf, EnsoSlpMapJja, EnsoSstDiversity,\ + EnsoSstLonRmse, EnsoSstMap, EnsoSstMapDjf, EnsoSstMapJja, EnsoSstSkew, EnsoSstTsRmse, EnsoTauxTsRmse, NinaPrMap,\ + NinaSlpMap, NinaSstDiv, NinaSstDivRmse, NinaSstDur, NinaSstLonRmse, NinaSstMap, NinaSstTsRmse, NinoPrMap,\ + NinoSlpMap, NinoSstDiv, NinoSstDiversity, NinoSstDivRmse, NinoSstDur, NinoSstLonRmse, NinoSstMap, NinoSstTsRmse,\ + SeasonalPrLatRmse, SeasonalPrLonRmse, SeasonalSshLatRmse, SeasonalSshLonRmse, SeasonalSstLatRmse,\ + SeasonalSstLonRmse, SeasonalTauxLatRmse, SeasonalTauxLonRmse +from .EnsoToolsLib import math_metric_computation +from .KeyArgLib import default_arg_values + + +# sst only datasets (not good for surface temperature teleconnection) +sst_only = ["CFSR", "ERSSTv5", "GODAS", "HadISST", "OISST", "ORAS4", "SODA3.4.2", "Tropflux"] + + +# ---------------------------------------------------------------------------------------------------------------------# +# +# Computation of the metric collection +# +def ComputeCollection(metricCollection, dictDatasets, modelName, user_regridding={}, debug=False, dive_down=False, + netcdf=False, netcdf_name='', observed_fyear=None, observed_lyear=None, modeled_fyear=None, + modeled_lyear=None): + """ + The ComputeCollection() function computes all the diagnostics / metrics associated with the given Metric Collection + + Inputs: + ------ + :param MetricCollection: string + name of a Metric Collection, must be defined in EnsoCollectionsLib.defCollection() + :param dictDatasets: dict + dictionary containing all information needed to compute the Metric Collection for one model and observations + it must be like: + dictDatasets = { + 'model': { + 'modelName': { + 'variable1': {'path + filename': 'path_to_file/filename', 'varname': 'variable_name_in_file'}, + 'variable2': {'path + filename': 'path_to_file/filename', 'varname': 'variable_name_in_file'}, + ... + }, + }, + 'observations': { + # obsName1 can be 'HadISST' if all variables come from HadISST, or it could be 'ERA-Interim + HadISST' + # if the variables come from ERA-Interim AND HadISST,... + 'obsName1': { + 'variable1': {'path + filename': 'path_to_file/filename', 'varname': 'variable_name_in_file'}, + 'variable2': {'path + filename': 'path_to_file/filename', 'varname': 'variable_name_in_file'}, + ... + 'variable n': { + 'path + filename': ['path_to_file1/filename1', 'path_to_file2/filename2', ...], + 'varname': ['variable_name_in_file1', 'variable_name_in_file2', ...], + # this last one is not compulsory if 'obsName1' is defined in + # EnsoCollectionsLib.ReferenceObservations() + 'algebric_calculation': [+1,-1,..., '/'], + + }, + }, + 'obsName2': { + 'variable1': {'path + filename': 'path_to_file/filename', 'varname': 'variable_name_in_file'}, + 'variable2': {'path + filename': 'path_to_file/filename', 'varname': 'variable_name_in_file'}, + ... + }, + }, + } + :param user_regridding: dict, optional + regridding parameters selected by the user and not the "climate experts" (i.e. the program) + to see parameters: + help(EnsoUvcdatToolsLib.TwoVarRegrid) + help(EnsoUvcdatToolsLib.Regrid) + e.g.: + user_regridding = { + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic 1x1deg'}, + } + :param debug: boolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param dive_down: boolean, optional + default value = False dive_down are not saved in a dictionary + If you want to save the dive down diagnostics set it to True + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' root name of the saved NetCDFs + the name of a metric will be append at the end of the root name + e.g., netcdf_name='USER_DATE_METRICCOLLECTION_MODEL' + + :return: MCvalues: dict + name of the Metric Collection, Metrics, value, value_error, units, ... + MCvalues = { + 'MetricCollection': { + 'information about the MetricCollection': 'descriptions', + 'metrics': { + 'metric1': { + 'metric_values': { + 'obsName1': { + 'value': 'value of the metric', + 'value_error': 'estimation of the error on the metric', + }, + 'obsName2': {'value': val, 'value_error': err}, + ... + }, + 'information about the metric': 'description of how this metric if computed from model and + observations values', + 'raw_values': { + 'model': { + 'value': 'model value of the diagnostic', + 'value_error': 'estimation of the error on the diagnostic', + 'nyears': 'number of years used for the computation of the diagnostic', + 'time_period': 'period used for the computation of the diagnostic', + }, + 'observations': { + 'obsName1': {'value': val, 'value_error': err, 'nyears': ny, 'time_period': bnds}, + 'obsName2': {'value': val, 'value_error': err, 'nyears': ny, 'time_period': bnds}, + ... + }, + 'units': 'units of the diagnostic', 'method': 'method used to compute the diagnostic', + 'time_frequency': 'data frequency used to compute the diagnostic', + 'ref': 'reference paper', ... + }, + }, + 'metric2': { + ... + }, + ... + }, + }, + } + """ + dict_mc = defCollection(metricCollection) + dict_col_meta = { + 'name': dict_mc['long_name'], 'description_of_the_collection': dict_mc['description'], 'metrics': {}, + } + dict_col_dd_meta = { + 'name': dict_mc['long_name'], 'description_of_the_collection': dict_mc['description'], 'metrics': {}, + } + dict_col_valu = dict() + dict_col_dd_valu = dict() + dict_m = dict_mc['metrics_list'] + list_metrics = sorted(list(dict_m.keys()), key=lambda v: v.upper()) + for metric in list_metrics: + try: # try per metric + print('\033[94m' + str().ljust(5) + "ComputeCollection: metric = " + str(metric) + '\033[0m') + # sets arguments for this metric + list_variables = dict_m[metric]['variables'] + dict_regions = dict_m[metric]['regions'] + # model name, file, variable name in file + try: + modelFile1 = dictDatasets['model'][modelName][list_variables[0]]['path + filename'] + except: + modelFile1 = '' + try: + modelVarName1 = dictDatasets['model'][modelName][list_variables[0]]['varname'] + except: + modelVarName1 = '' + try: + modelFileArea1 = dictDatasets['model'][modelName][list_variables[0]]['path + filename_area'] + except: + modelFileArea1, modelAreaName1 = None, None + else: + modelAreaName1 = dictDatasets['model'][modelName][list_variables[0]]['areaname'] + try: + modelFileLandmask1 = dictDatasets['model'][modelName][list_variables[0]]['path + filename_landmask'] + except: + modelFileLandmask1, modelLandmaskName1 = None, None + else: + modelLandmaskName1 = dictDatasets['model'][modelName][list_variables[0]]['landmaskname'] + # observations name(s), file(s), variable(s) name in file(s) + obsNameVar1, obsFile1, obsVarName1, obsFileArea1, obsAreaName1 = list(), list(), list(), list(), list() + obsFileLandmask1, obsLandmaskName1 = list(), list() + for obs in sorted(list(dictDatasets['observations'].keys()), key=lambda v: v.upper()): + try: dictDatasets['observations'][obs][list_variables[0]] + except: pass + else: + obsNameVar1.append(obs) + obsFile1.append(dictDatasets['observations'][obs][list_variables[0]]['path + filename']) + obsVarName1.append(dictDatasets['observations'][obs][list_variables[0]]['varname']) + try: + obsFileArea1.append(dictDatasets['observations'][obs][list_variables[0]]['path + filename_area']) + except: + obsFileArea1.append(None) + obsAreaName1.append(None) + else: + obsAreaName1.append(dictDatasets['observations'][obs][list_variables[0]]['areaname']) + try: + obsFileLandmask1.append( + dictDatasets['observations'][obs][list_variables[0]]['path + filename_landmask']) + except: + obsFileLandmask1.append(None) + obsLandmaskName1.append(None) + else: + obsLandmaskName1.append(dictDatasets['observations'][obs][list_variables[0]]['landmaskname']) + # same if a second variable is needed + # this time in the form of a keyarg dictionary + arg_var2 = {'modelFileArea1': modelFileArea1, 'modelAreaName1': modelAreaName1, + 'modelFileLandmask1': modelFileLandmask1, 'modelLandmaskName1': modelLandmaskName1, + 'obsFileArea1': obsFileArea1, 'obsAreaName1': obsAreaName1, 'obsFileLandmask1': obsFileLandmask1, + 'obsLandmaskName1': obsLandmaskName1, 'observed_fyear': observed_fyear, + 'observed_lyear': observed_lyear, 'modeled_fyear': modeled_fyear, 'modeled_lyear': modeled_lyear} + if len(list_variables) > 1: + try: + arg_var2['modelFile2'] = dictDatasets['model'][modelName][list_variables[1]]['path + filename'] + except: + arg_var2['modelFile2'] = '' + try: + arg_var2['modelVarName2'] = dictDatasets['model'][modelName][list_variables[1]]['varname'] + except: + arg_var2['modelVarName2'] = '' + arg_var2['regionVar2'] = dict_regions[list_variables[1]] + try: + arg_var2['modelFileArea2'] = dictDatasets['model'][modelName][list_variables[1]]['path + filename_area'] + except: + arg_var2['modelFileArea2'], arg_var2['modelAreaName2'] = None, None + else: + arg_var2['modelAreaName2'] = dictDatasets['model'][modelName][list_variables[1]]['areaname'] + try: + arg_var2['modelFileLandmask2'] = \ + dictDatasets['model'][modelName][list_variables[1]]['path + filename_landmask'] + except: + arg_var2['modelFileLandmask2'], arg_var2['modelLandmaskName2'] = None, None + else: + arg_var2['modelLandmaskName2'] = dictDatasets['model'][modelName][list_variables[1]]['landmaskname'] + obsNameVar2, obsFile2, obsVarName2, obsFileArea2, obsAreaName2 = list(), list(), list(), list(), list() + obsFileLandmask2, obsLandmaskName2 = list(), list() + for obs in sorted(list(dictDatasets['observations'].keys()), key=lambda v: v.upper()): + try: dictDatasets['observations'][obs][list_variables[1]] + except: pass + else: + obsNameVar2.append(obs) + obsFile2.append(dictDatasets['observations'][obs][list_variables[1]]['path + filename']) + obsVarName2.append(dictDatasets['observations'][obs][list_variables[1]]['varname']) + try: + obsFileArea2.append( + dictDatasets['observations'][obs][list_variables[1]]['path + filename_area']) + except: + obsFileArea2.append(None) + obsAreaName2.append(None) + else: + obsAreaName2.append(dictDatasets['observations'][obs][list_variables[1]]['areaname']) + try: + obsFileLandmask2.append( + dictDatasets['observations'][obs][list_variables[1]]['path + filename_landmask']) + except: + obsFileLandmask2.append(None) + obsLandmaskName2.append(None) + else: + obsLandmaskName2.append(dictDatasets['observations'][obs][list_variables[1]]['landmaskname']) + arg_var2['obsNameVar2'] = obsNameVar2 + arg_var2['obsFile2'] = obsFile2 + arg_var2['obsVarName2'] = obsVarName2 + arg_var2['obsFileArea2'] = obsFileArea2 + arg_var2['obsAreaName2'] = obsAreaName2 + arg_var2['obsFileLandmask2'] = obsFileLandmask2 + arg_var2['obsLandmaskName2'] = obsLandmaskName2 + # computes the metric + if modelFile1 is None or len(modelFile1) == 0 or (isinstance(modelFile1, list) and None in modelFile1) or\ + (len(list_variables) > 1 and + (arg_var2['modelFile2'] is None or len(arg_var2['modelFile2']) == 0 or + (isinstance(arg_var2['modelFile2'], list) and None in arg_var2['modelFile2']))): + print('\033[94m' + str().ljust(5) + "ComputeCollection: " + str(metricCollection) + ", metric " + + str(metric) + " not computed" + '\033[0m') + print('\033[94m' + str().ljust(10) + "reason(s):" + '\033[0m') + if modelFile1 is None or len(modelFile1) == 0: + print('\033[94m' + str().ljust(11) + "no modeled " + list_variables[0] + " given" + '\033[0m') + if isinstance(modelFile1, list) and None in modelFile1: + for ff, vv in zip(modelFile1, modelVarName1): + if ff is None or vv is None: + print('\033[94m' + str().ljust(11) + "no modeled " + str(vv) + " given" + '\033[0m') + if (len(list_variables) > 1 and arg_var2['modelFile2'] is None) or\ + (len(list_variables) > 1 and len(arg_var2['modelFile2']) == 0): + print('\033[94m' + str().ljust(11) + "no modeled " + list_variables[1] + " given" + '\033[0m') + if isinstance(arg_var2['modelFile2'], list) and None in arg_var2['modelFile2']: + for ff, vv in zip(arg_var2['modelFile2'], arg_var2['modelVarName2']): + if ff is None or vv is None: + print('\033[94m' + str().ljust(11) + "no modeled " + str(vv) + " given" + '\033[0m') + elif obsFile1 is None or len(obsFile1) == 0 or (isinstance(obsFile1, list) and None in obsFile1) or\ + (len(list_variables) > 1 and + (arg_var2['obsFile2'] is None or len(arg_var2['obsFile2']) == 0 or + (isinstance(arg_var2['obsFile2'], list) and None in arg_var2['obsFile2']))): + print('\033[94m' + str().ljust(5) + "ComputeCollection: " + str(metricCollection) + ", metric " + + str(metric) + " not computed" + '\033[0m') + print('\033[94m' + str().ljust(10) + "reason(s):" + '\033[0m') + if obsFile1 is None or len(obsFile1) == 0: + print('\033[94m' + str().ljust(11) + "no observed " + list_variables[0] + " given" + '\033[0m') + if isinstance(obsFile1, list) and None in obsFile1: + for ff, vv in zip(obsFile1, obsVarName1): + if ff is None or vv is None: + print('\033[94m' + str().ljust(11) + "no observed " + str(vv) + " given" + '\033[0m') + if (len(list_variables) > 1 and arg_var2['obsFile2'] is None) or\ + (len(list_variables) > 1 and len(arg_var2['obsFile2']) == 0): + print('\033[94m' + str().ljust(11) + "no observed " + list_variables[1] + " given" + '\033[0m') + if isinstance(arg_var2['obsFile2'], list) and None in arg_var2['obsFile2']: + for ff, vv in zip(arg_var2['obsFile2'], arg_var2['obsVarName2']): + if ff is None or vv is None: + print('\033[94m' + str().ljust(11) + "no observed " + str(vv) + " given" + '\033[0m') + else: + valu, vame, dive, dime = ComputeMetric( + metricCollection, metric, modelName, modelFile1, modelVarName1, obsNameVar1, obsFile1, obsVarName1, + dict_regions[list_variables[0]], user_regridding=user_regridding, debug=debug, netcdf=netcdf, + netcdf_name=netcdf_name, **arg_var2) + keys1 = list(valu.keys()) + keys2 = list(set([kk.replace('value', '').replace('__', '').replace('_error', '') + for ll in list(valu[keys1[0]].keys()) for kk in list(valu[keys1[0]][ll].keys())])) + if len(keys2) > 1: + for kk in keys2: + mm, dd = dict(), dict() + keys3 = list(valu['metric'].keys()) + for ll in keys3: + mm[ll] = {'value': valu['metric'][ll][kk + '__value'], + 'value_error': valu['metric'][ll][kk + '__value_error']} + keys3 = list(valu['diagnostic'].keys()) + for ll in keys3: + dd[ll] = {'value': valu['diagnostic'][ll][kk + '__value'], + 'value_error': valu['diagnostic'][ll][kk + '__value_error']} + dict_col_valu[metric + kk] = {'metric': mm, 'diagnostic': dd} + mm = dict((ii, vame['metric'][ii]) for ii in list(vame['metric'].keys()) if 'units' not in ii) + mm['units'] = vame['metric'][kk + '__units'] + dict_col_meta['metrics'][metric + kk] = {'metric': mm, 'diagnostic': vame['diagnostic']} + dict_col_dd_valu[metric + kk], dict_col_dd_meta['metrics'][metric + kk] = dive, dime + del mm, dd + else: + dict_col_valu[metric], dict_col_meta['metrics'][metric] = valu, vame + dict_col_dd_valu[metric], dict_col_dd_meta['metrics'][metric] = dive, dime + except Exception as e: + print(e) + pass + + if dive_down is True: + return {'value': dict_col_valu, 'metadata': dict_col_meta},\ + {'value': dict_col_dd_valu, 'metadata': dict_col_dd_meta} + else: + return {'value': dict_col_valu, 'metadata': dict_col_meta}, {} + + +def group_json_obs(pattern, json_name_out, metric_name): + list_files = sorted(list(GLOBiglob(pattern)), key=lambda v: v.upper()) + for file1 in list_files: + with open(file1) as ff: + data = json.load(ff) + ff.close() + data = data["RESULTS"]["model"] + for dataset in sorted(list(data.keys()), key=lambda v: v.upper()): + try: dict_out + except: dict_out = deepcopy(data) + else: + if dataset in list(dict_out.keys()): + if metric_name == "all": + for met in sorted(list(data[dataset]["r1i1p1"]["value"].keys()), key=lambda v: v.upper()): + if met not in list(dict_out[dataset]["r1i1p1"]["value"].keys()): + dict_out[dataset]["r1i1p1"]["value"][met] = data[dataset]["r1i1p1"]["value"][met] + else: + dict_out[dataset]["r1i1p1"]["value"][metric_name] = data[dataset]["r1i1p1"]["value"][ + metric_name] + else: + dict_out[dataset] = data[dataset] + # OSremove(file1) + del data, dataset, ff + save_json_obs(dict_out, json_name_out) + return + + +def save_json_obs(dict_in, json_name): + dict_out = {"RESULTS": {"model": dict_in}} + # save as json file + with open(json_name + '.json', 'w') as outfile: + json.dump(dict_out, outfile, sort_keys=True) + return + + +def ComputeCollection_ObsOnly(metricCollection, dictDatasets, user_regridding={}, debug=False, dive_down=False, + netcdf=False, netcdf_name='', observed_fyear=None, observed_lyear=None, + modeled_fyear=None, modeled_lyear=None): + dict_mc = defCollection(metricCollection) + dict_col_meta = { + 'name': dict_mc['long_name'], 'description_of_the_collection': dict_mc['description'], 'metrics': {}, + } + dict_col_dd_meta = { + 'name': dict_mc['long_name'], 'description_of_the_collection': dict_mc['description'], 'metrics': {}, + } + dict_col_valu = dict() + dict_col_dd_valu = dict() + dict_m = dict_mc['metrics_list'] + list_metrics = sorted(list(dict_m.keys()), key=lambda v: v.upper()) + for metric in list_metrics: + print('\033[94m' + str().ljust(5) + "ComputeCollection: metric = " + str(metric) + '\033[0m') + # sets arguments for this metric + list_variables = dict_m[metric]['variables'] + dict_regions = dict_m[metric]['regions'] + # observations name(s), file(s), variable(s) name in file(s) + obsNameVar1, obsFile1, obsVarName1, obsFileArea1, obsAreaName1 = list(), list(), list(), list(), list() + obsFileLandmask1, obsLandmaskName1 = list(), list() + for obs in sorted(list(dictDatasets['observations'].keys()), key=lambda v: v.upper()): + try: dictDatasets['observations'][obs][list_variables[0]] + except: pass + else: + obsNameVar1.append(obs) + obsFile1.append(dictDatasets['observations'][obs][list_variables[0]]['path + filename']) + obsVarName1.append(dictDatasets['observations'][obs][list_variables[0]]['varname']) + try: + obsFileArea1.append(dictDatasets['observations'][obs][list_variables[0]]['path + filename_area']) + except: + obsFileArea1.append(None) + obsAreaName1.append(None) + else: + obsAreaName1.append(dictDatasets['observations'][obs][list_variables[0]]['areaname']) + try: + obsFileLandmask1.append( + dictDatasets['observations'][obs][list_variables[0]]['path + filename_landmask']) + except: + obsFileLandmask1.append(None) + obsLandmaskName1.append(None) + else: + obsLandmaskName1.append(dictDatasets['observations'][obs][list_variables[0]]['landmaskname']) + # same if a second variable is needed + obsNameVar2, obsFile2, obsVarName2, obsFileArea2, obsAreaName2 = list(), list(), list(), list(), list() + obsFileLandmask2, obsLandmaskName2 = list(), list() + if len(list_variables) > 1: + for obs in sorted(list(dictDatasets['observations'].keys()), key=lambda v: v.upper()): + try: + dictDatasets['observations'][obs][list_variables[1]] + except: + pass + else: + obsNameVar2.append(obs) + obsFile2.append(dictDatasets['observations'][obs][list_variables[1]]['path + filename']) + obsVarName2.append(dictDatasets['observations'][obs][list_variables[1]]['varname']) + try: + obsFileArea2.append( + dictDatasets['observations'][obs][list_variables[1]]['path + filename_area']) + except: + obsFileArea2.append(None) + obsAreaName2.append(None) + else: + obsAreaName2.append(dictDatasets['observations'][obs][list_variables[1]]['areaname']) + try: + obsFileLandmask2.append( + dictDatasets['observations'][obs][list_variables[1]]['path + filename_landmask']) + except: + obsFileLandmask2.append(None) + obsLandmaskName2.append(None) + else: + obsLandmaskName2.append( + dictDatasets['observations'][obs][list_variables[1]]['landmaskname']) + # observations as model + print(obsNameVar1) + print(obsNameVar2) + for ii in range(len(obsFileArea1)): + modelName = obsNameVar1[ii] + modelFile1 = obsFile1[ii] + modelVarName1 = obsVarName1[ii] + modelFileArea1 = obsFileArea1[ii] + modelAreaName1 = obsAreaName1[ii] + modelFileLandmask1 = obsFileLandmask1[ii] + modelLandmaskName1 = obsLandmaskName1[ii] + arg_var2 = {'modelFileArea1': modelFileArea1, 'modelAreaName1': modelAreaName1, + 'modelFileLandmask1': modelFileLandmask1, 'modelLandmaskName1': modelLandmaskName1, + 'obsFileArea1': obsFileArea1, 'obsAreaName1': obsAreaName1, + 'obsFileLandmask1': obsFileLandmask1, 'obsLandmaskName1': obsLandmaskName1, + 'observed_fyear': observed_fyear, 'observed_lyear': observed_lyear, + 'modeled_fyear': modeled_fyear, 'modeled_lyear': modeled_lyear} + if len(list_variables) == 1: + nbr = 1 + else: + nbr = len(obsNameVar2) + for jj in range(nbr): + try: + obsNameVar2[jj] + except: + modelName2 = deepcopy(modelName) + else: + modelName2 = modelName + "_" + obsNameVar2[jj] + arg_var2['modelFile2'] = obsFile2[jj] + arg_var2['modelVarName2'] = obsVarName2[jj] + arg_var2['modelFileArea2'] = obsFileArea2[jj] + arg_var2['modelAreaName2'] = obsAreaName2[jj] + arg_var2['modelFileLandmask2'] = obsFileLandmask2[jj] + arg_var2['modelLandmaskName2'] = obsLandmaskName2[jj] + arg_var2['regionVar2'] = dict_regions[list_variables[1]] + arg_var2['obsNameVar2'] = obsNameVar2 + arg_var2['obsFile2'] = obsFile2 + arg_var2['obsVarName2'] = obsVarName2 + arg_var2['obsFileArea2'] = obsFileArea2 + arg_var2['obsAreaName2'] = obsAreaName2 + arg_var2['obsFileLandmask2'] = obsFileLandmask2 + arg_var2['obsLandmaskName2'] = obsLandmaskName2 + if netcdf is True: + netcdf_name_out = netcdf_name.replace("OBSNAME", modelName2) + else: + netcdf_name_out = "" + json_name_out = netcdf_name.replace("OBSNAME", "tmp1_" + modelName2 + "_" + metric) + if ("EnsoSstMap" in metric and modelName2 in sst_only) or len( + list(GLOBiglob(json_name_out + ".json"))) == 1: + pass + else: + print(modelName2 + "_as_model") + valu, vame, dive, dime = ComputeMetric( + metricCollection, metric, modelName2, modelFile1, modelVarName1, obsNameVar1, + obsFile1, obsVarName1, dict_regions[list_variables[0]], user_regridding=user_regridding, + debug=debug, netcdf=netcdf, netcdf_name=netcdf_name_out, **arg_var2) + keys1 = list(valu.keys()) + keys2 = list(set([kk.replace('value', '').replace('__', '').replace('_error', '') + for ll in list(valu[keys1[0]].keys()) for kk in list(valu[keys1[0]][ll].keys())])) + if len(keys2) > 1: + for kk in keys2: + mm1, dd1 = dict(), dict() + keys3 = list(valu['metric'].keys()) + for ll in keys3: + mm1[ll] = {'value': valu['metric'][ll][kk + '__value'], + 'value_error': valu['metric'][ll][kk + '__value_error']} + keys3 = list(valu['diagnostic'].keys()) + for ll in keys3: + dd1[ll] = {'value': valu['diagnostic'][ll][kk + '__value'], + 'value_error': valu['diagnostic'][ll][kk + '__value_error']} + mm2 = dict((ll, vame['metric'][ll]) for ll in list(vame['metric'].keys()) if 'units' not in ll) + mm2['units'] = vame['metric'][kk + '__units'] + dict1 = {'metric': mm1, 'diagnostic': dd1} + dict2 = {'metric': mm2, 'diagnostic': vame['diagnostic']} + try: + dict_col_valu[modelName2] + except: + dict_col_valu[modelName2] = {metric + kk: dict1} + dict_col_meta[modelName2] = {'metrics': {metric + kk: dict2}} + dict_col_dd_valu[modelName2] = {metric + kk: dive} + dict_col_dd_meta[modelName2] = {'metrics': {metric + kk: dime}} + else: + dict_col_valu[modelName2][metric + kk] = dict1 + dict_col_meta[modelName2]['metrics'][metric + kk] = dict2 + dict_col_dd_valu[modelName2][metric + kk] = dive + dict_col_dd_meta[modelName2]['metrics'][metric + kk] = dime + del dd1, dict1, dict2, keys3, mm1, mm2 + else: + try: + dict_col_valu[modelName2] + except: + dict_col_valu[modelName2] = {metric: valu} + dict_col_meta[modelName2] = {'metrics': {metric: vame}} + dict_col_dd_valu[modelName2] = {metric: dive} + dict_col_dd_meta[modelName2] = {'metrics': {metric: dime}} + else: + dict_col_valu[modelName2][metric] = valu + dict_col_meta[modelName2]['metrics'][metric] = vame + dict_col_dd_valu[modelName2][metric] = dive + dict_col_dd_meta[modelName2]['metrics'][metric] = dime + # save json + dict_out = {modelName2: { + 'r1i1p1': {'value': dict_col_valu[modelName2], 'metadata': dict_col_meta[modelName2]}}} + save_json_obs(dict_out, json_name_out) + del dict_out, dime, dive, keys1, keys2, valu + del json_name_out, modelName2, netcdf_name_out + del arg_var2, modelName, modelFile1, modelVarName1, modelFileArea1, modelAreaName1, modelFileLandmask1, \ + modelLandmaskName1, nbr + # read all jsons and group them + pattern = netcdf_name.replace("OBSNAME", "tmp1_*_" + metric + ".json") + json_name_out = netcdf_name.replace("OBSNAME", "tmp2_" + metric) + group_json_obs(pattern, json_name_out, metric) + del dict_regions, json_name_out, list_variables, obsAreaName1, obsAreaName2, obsFile1, obsFile2, obsFileArea1, \ + obsFileArea2, obsFileLandmask1, obsFileLandmask2, obsLandmaskName1, obsLandmaskName2, obsNameVar1, \ + obsNameVar2, obsVarName1, obsVarName2, pattern + # read all jsons and group them + pattern = netcdf_name.replace("OBSNAME", "tmp2_*.json") + json_name_out = netcdf_name.replace("OBSNAME", "observation") + group_json_obs(pattern, json_name_out, "all") + return + +# ---------------------------------------------------------------------------------------------------------------------# + + +# ---------------------------------------------------------------------------------------------------------------------# +# +# Computation of the metric +# +dict_oneVar_modelAndObs = { + 'BiasPrLatRmse': BiasPrLatRmse, 'BiasPrLonRmse': BiasPrLonRmse, 'BiasPrRmse': BiasPrRmse, + 'BiasSshLatRmse': BiasSshLatRmse, 'BiasSshLonRmse': BiasSshLonRmse, 'BiasSshRmse': BiasSshRmse, + 'BiasSstLatRmse': BiasSstLatRmse, 'BiasSstLonRmse': BiasSstLonRmse, 'BiasSstRmse': BiasSstRmse, + 'BiasTauxLatRmse': BiasTauxLatRmse, 'BiasTauxLonRmse': BiasTauxLonRmse, 'BiasTauxRmse': BiasTauxRmse, + 'BiasSstSkLonRmse': BiasSstSkLonRmse, + 'EnsoSstLonRmse': EnsoSstLonRmse, 'NinaSstLonRmse': NinaSstLonRmse, 'NinoSstTsRmse': NinoSstTsRmse, + 'EnsoSstMap': EnsoSstMap, 'EnsoSstMapDjf': EnsoSstMapDjf, 'EnsoSstMapJja': EnsoSstMapJja, 'NinaSstMap': NinaSstMap, + 'NinoSstMap': NinoSstMap, + 'EnsoSstTsRmse': EnsoSstTsRmse, 'NinaSstTsRmse': NinaSstTsRmse, 'NinoSstLonRmse': NinoSstLonRmse, + 'NinaSstDivRmse': NinaSstDivRmse, 'NinoSstDivRmse': NinoSstDivRmse, + 'SeasonalPrLatRmse': SeasonalPrLatRmse, 'SeasonalPrLonRmse': SeasonalPrLonRmse, + 'SeasonalSshLatRmse': SeasonalSshLatRmse, 'SeasonalSshLonRmse': SeasonalSshLonRmse, + 'SeasonalSstLatRmse': SeasonalSstLatRmse, 'SeasonalSstLonRmse': SeasonalSstLonRmse, + 'SeasonalTauxLatRmse': SeasonalTauxLatRmse, 'SeasonalTauxLonRmse': SeasonalTauxLonRmse, +} + +dict_twoVar_modelAndObs = { + 'EnsoPrMap': EnsoPrMap, 'EnsoPrMapDjf': EnsoPrMapDjf, 'EnsoPrMapJja': EnsoPrMapJja, + 'EnsoPrDjfTel': EnsoPrDjfTel, 'EnsoPrJjaTel': EnsoPrJjaTel, + 'EnsoSlpMap': EnsoSlpMap, 'EnsoSlpMapDjf': EnsoSlpMapDjf, 'EnsoSlpMapJja': EnsoSlpMapJja, + 'NinaPrMap': NinaPrMap, 'NinaSlpMap': NinaSlpMap, 'NinoPrMap': NinoPrMap, 'NinoSlpMap': NinoSlpMap, + 'EnsoPrTsRmse': EnsoPrTsRmse, 'EnsoTauxTsRmse': EnsoTauxTsRmse, +} + +dict_oneVar = { + 'EnsoAmpl': EnsoAmpl, 'EnsoDuration': EnsoDuration, 'EnsoDiversity': EnsoDiversity, + 'EnsoSeasonality': EnsoSeasonality, 'EnsoSstDiversity': EnsoSstDiversity, 'EnsoSstSkew': EnsoSstSkew, + 'NinaSstDiv': NinaSstDiv, 'NinaSstDur': NinaSstDur, 'NinoSstDiv': NinoSstDiv, 'NinoSstDiversity': NinoSstDiversity, + 'NinoSstDur': NinoSstDur, +} + +dict_twoVar = { + 'EnsoFbSshSst': EnsoFbSshSst, 'EnsoFbSstLhf': EnsoFbSstLhf, 'EnsoFbSstLwr': EnsoFbSstLwr, + 'EnsoFbSstShf': EnsoFbSstShf, 'EnsoFbSstSwr': EnsoFbSstSwr, 'EnsoFbSstTaux': EnsoFbSstTaux, + 'EnsoFbSstThf': EnsoFbSstThf, 'EnsoFbTauxSsh': EnsoFbTauxSsh, 'EnsodSstOce': EnsodSstOce, +} + + +def ComputeMetric(metricCollection, metric, modelName, modelFile1, modelVarName1, obsNameVar1, obsFile1, obsVarName1, + regionVar1, modelFileArea1='', modelAreaName1='', modelFileLandmask1='', modelLandmaskName1='', + obsFileArea1='', obsAreaName1='', obsFileLandmask1='', obsLandmaskName1='', + modelFile2='', modelVarName2='', modelFileArea2='', modelAreaName2='', modelFileLandmask2='', + modelLandmaskName2='', obsNameVar2='', obsFile2='', obsVarName2='', obsFileArea2='', obsAreaName2='', + obsFileLandmask2='', obsLandmaskName2='', regionVar2='', user_regridding={}, debug=False, + netcdf=False, netcdf_name='', observed_fyear=None, observed_lyear=None, modeled_fyear=None, + modeled_lyear=None): + """ + :param metricCollection: string + name of a Metric Collection, must be defined in EnsoCollectionsLib.defCollection() + :param metric: string + name of a Metric, must be defined in EnsoCollectionsLib.defCollection() + :param modelName: string + path_to/filename of a model + :param modelFile1: string or list of strings + model file number 1 + e.g.: 'model_file.nc' or ['model_file1.nc','model_file2.nc'] + a list of files is given if a variable is the result of more than one CMIP variable (lwr = rlds - rlus) + :param modelVarName1: string or list of strings + model variable number 1, must be defined in EnsoCollectionsLib.CmipVariables() + e.g.: 'lwr' or ['rlds', 'rlus'] + :param obsNameVar1: string + path_to/filename of the observations for variable number 1, must be defined in + EnsoCollectionsLib.ReferenceObservations() + :param obsFile1: string or list of strings + observations file number 1 + e.g.: 'observations_file.nc' or ['observations_file1.nc','observations_file2.nc'] + a list of files is given if a variable is the result of more than one CMIP variable (lwr = rlds - rlus) + :param obsVarName1: string or list of strings + observations variable number 1, must be defined in EnsoCollectionsLib.ReferenceObservations() + e.g.: 'lwr' or ['rlds', 'rlus'] + :param regionVar1: string + name of box ('tropical_pacific') for variable number 1, must be defined in EnsoCollectionsLib.ReferenceRegions() + :param modelFileArea1: string, optional + path_to/filename of the model areacell for variable number 1 + :param modelAreaName1: string, optional + name the model areacell (e.g. 'areacella' or 'areacello') for variable number 1 + :param modelFileLandmask1: string, optional + path_to/filename of the model landmask for variable number 1 + :param modelLandmaskName1: string, optional + name the model landmask (e.g. 'landmask' or 'sftlf') for variable number 1 + :param obsFileArea1: string, optional + path_to/filename of the observations areacell for variable number 1 + :param obsAreaName1: string, optional + name the observations areacell (e.g. 'areacella' or 'areacello') for variable number 1 + :param obsFileLandmask1: string, optional + path_to/filename of the observations landmask for variable number 1 + :param obsLandmaskName1: string, optional + name the observations landmask (e.g. 'landmask' or 'sftlf') for variable number 1 + :param modelFile2: string or list of strings, optional + model file number 2 + e.g.: 'model_file.nc' or ['model_file1.nc','model_file2.nc'] + a list of files is given if a variable is the result of more than one CMIP variable (lwr = rlds - rlus) + :param modelVarName2: string or list of strings, optional + model variable number 2, must be defined in EnsoCollectionsLib.CmipVariables() + e.g.: 'lwr' or ['rlds', 'rlus'] + :param modelFileArea2: string, optional + path_to/filename of the model areacell for variable number 2 + :param modelAreaName2: string, optional + name the model areacell (e.g. 'areacella' or 'areacello') for variable number 2 + :param modelFileLandmask2: string, optional + path_to/filename of the model landmask for variable number 2 + :param modelLandmaskName2: string, optional + name the model landmask (e.g. 'landmask' or 'sftlf') for variable number 2 + :param obsNameVar2: string, optional + path_to/filename of the observations for variable number 2, must be defined in + EnsoCollectionsLib.ReferenceObservations() + :param obsFile2: string or list of strings, optional + observations file number 2 + e.g.: 'observations_file.nc' or ['observations_file1.nc','observations_file2.nc'] + a list of files is given if a variable is the result of more than one CMIP variable (lwr = rlds - rlus) + :param obsVarName2: string or list of strings, optional + observations variable number 2, must be defined in EnsoCollectionsLib.ReferenceObservations() + e.g.: 'lwr' or ['rlds', 'rlus'] + :param obsFileArea2: string, optional + path_to/filename of the observations areacell for variable number 2 + :param obsAreaName2: string, optional + name the observations areacell (e.g. 'areacella' or 'areacello') for variable number 2 + :param obsFileLandmask2: string, optional + path_to/filename of the observations landmask for variable number 2 + :param obsLandmaskName2: string, optional + name the observations landmask (e.g. 'landmask' or 'sftlf') for variable number 2 + :param regionVar2: string + name of box ('tropical_pacific') for variable number 2, must be defined in EnsoCollectionsLib.ReferenceRegions() + :param user_regridding: dict, optional + regridding parameters selected by the user and not the "climate experts" (i.e. the program) + to see parameters: + help(EnsoUvcdatToolsLib.TwoVarRegrid) + help(EnsoUvcdatToolsLib.Regrid) + e.g.: + user_regridding = { + 'regridding': {'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic 1x1deg'}, + } + :param debug: boolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' root name of the saved NetCDFs + the name of a metric will be append at the end of the root name + e.g., netcdf_name='USER_DATE_METRICCOLLECTION_MODEL' + + :return: + """ + tmp_metric = deepcopy(metric) + metric = metric.replace('_1', '').replace('_2', '').replace('_3', '').replace('_4', '').replace('_5', '') + # retrieving keyargs from EnsoCollectionsLib.defCollection + dict_mc = defCollection(metricCollection) + list_obs = sorted(list(ReferenceObservations().keys()), key=lambda v: v.upper()) + + # common_collection_parameters + keyarg = dict() + for arg in list(dict_mc['common_collection_parameters'].keys()): + keyarg[arg] = dict_mc['common_collection_parameters'][arg] + for arg in list(dict_mc['metrics_list'][tmp_metric].keys()): + keyarg[arg] = dict_mc['metrics_list'][tmp_metric][arg] + # if 'metric_computation' is not defined for this metric (in EnsoCollectionsLib.defCollection), sets it to its + # default value + try: + keyarg['metric_computation'] + except: + keyarg['metric_computation'] = default_arg_values('metric_computation') + # if 'modeled_period' is not defined for this metric (in EnsoCollectionsLib.defCollection), sets it to its default + # value + try: + keyarg['time_bounds_mod'] = keyarg['modeled_period'] + except: + keyarg['time_bounds_mod'] = default_arg_values('time_bounds_mod') + # YYP !!! experimental period defined bt user !!! + if isinstance(modeled_fyear, int) is True and isinstance(modeled_lyear, int) is True: + keyarg['time_bounds_mod'] = (str(modeled_fyear) + "-01-01 00:00:00", str(modeled_lyear) + "-12-31 23:59:60.0") + # if 'modeled_period' is not defined for this metric (in EnsoCollectionsLib.defCollection), sets it to its default + # value + try: + keyarg['time_bounds_obs'] = keyarg['observed_period'] + except: + keyarg['time_bounds_obs'] = default_arg_values('time_bounds_obs') + # YYP !!! experimental period defined bt user !!! + if isinstance(observed_fyear, int) is True and isinstance(observed_lyear, int) is True: + keyarg['time_bounds_obs'] = (str(observed_fyear) + "-01-01 00:00:00", str(observed_lyear) + "-12-31 23:59:60.0") + # if the user gave a specific regridding Tool / method, use it + if tmp_metric in list(user_regridding.keys()): + keyarg['regridding'] = user_regridding[tmp_metric] + elif 'regridding' in list(user_regridding.keys()): + keyarg['regridding'] = user_regridding['regridding'] + + # if model is an observation + if modelName.split("_")[0] in list_obs: + keyarg['time_bounds_mod'] = deepcopy(keyarg['time_bounds_obs']) + keyarg['project_interpreter_mod_var1'] = modelName.split("_")[0] + if modelFile2 != '': + keyarg['project_interpreter_mod_var2'] = modelName.split("_")[1] + else: + keyarg['project_interpreter_mod_var1'] = keyarg['project_interpreter'] + if modelFile2 != '': + keyarg['project_interpreter_mod_var2'] = keyarg['project_interpreter'] + + # obsName could be a list if the user wants to compare the model with a set of observations + # if obsName is just a name (string) it is put in a list + if isinstance(obsNameVar1, str) is True or isinstance(obsNameVar1, str) is True: + obsNameVar1 = [obsNameVar1] + if isinstance(obsFile1, str) is True or isinstance(obsFile1, str) is True: + obsFile1 = [obsFile1] + if isinstance(obsVarName1, str) is True or isinstance(obsVarName1, str) is True: + obsVarName1 = [obsVarName1] + # Var2, do the same as for Var1 + if isinstance(obsNameVar2, str) is True or isinstance(obsNameVar2, str) is True: + obsNameVar2 = [obsNameVar2] + if isinstance(obsFile2, str) is True or isinstance(obsFile2, str) is True: + obsFile2 = [obsFile2] + if isinstance(obsVarName2, str) is True or isinstance(obsVarName2, str) is True: + obsVarName2 = [obsVarName2] + + dict_metric_val = dict() + dict_diagnostic = dict() + dict_diagnostic_metadata = dict() + dict_dive_down = dict() + dict_dive_down_metadata = dict() + + multimetric = False + + # test files + if isinstance(modelFile1, list): + noerror = all(isinstance(file1, str) is True for file1 in modelFile1) + else: + noerror = True if isinstance(modelFile1, str) is True else False + if noerror is False: + if isinstance(modelFile1, list): + tmp = str([modelVarName1[kk] for kk, file1 in enumerate(modelFile1) if isinstance(file1, str) is False]) + else: + tmp = str(modelVarName1) + tmperr = "model var1 (" + tmp + ") not given" + if metric in list(dict_twoVar.keys())+list(dict_twoVar_modelAndObs.keys()): + if isinstance(modelFile2, list): + noerror2 = all(isinstance(file1, str) is True for file1 in modelFile2) + else: + noerror2 = True if isinstance(modelFile2, str) is True else False + if noerror2 is False: + if isinstance(modelFile2, list): + tmp = str([modelVarName2[kk] for kk, file1 in enumerate(modelFile2) if isinstance(file1, str) is False]) + else: + tmp = str(modelVarName2) + try: + tmperr + except: + tmperr = "model var2(" + tmp + ") not given" + else: + tmperr = tmperr + " ; model var2(" + tmp + ") not given" + noerror = False if noerror is False or noerror2 is False else True + if noerror is False: + dict_metrics = { + 'metric': {'value': None, 'value_error': None}, 'diagnostic': {'value': None, 'value_error': None}} + dict_metadata = { + 'metric': {'name': metric, 'method': None, 'datasets': modelName, 'units': None}, + 'diagnostic': {{modelName: {'method': None, 'name': None, 'ref': None, 'time_frequency': None, + 'units': None, 'keyerror': tmperr}}}, + } + dict_dive_down, dict_dive_down_metadata = {}, {} + else: + if metric in list(dict_oneVar_modelAndObs.keys()) or metric in list(dict_twoVar_modelAndObs.keys()): + # + # this part regroups all diagnostics comparing model and obs (rmse) + # so the diagnostic is the metric + # + description_metric = "The metric is the statistical value between the model and the observations" + diagnostic1 = dict() + for ii in range(len(obsNameVar1)): + # computes the diagnostic/metric + if metric in list(dict_oneVar_modelAndObs.keys()): + output_name = obsNameVar1[ii] + if output_name != modelName: + if "EnsoSstMap" in metric and output_name in sst_only: + pass + else: + print('\033[94m' + str().ljust(5) + "ComputeMetric: oneVarRMSmetric, " + metric + " = " \ + + modelName + " and " + output_name + '\033[0m') + diagnostic1[output_name] = dict_oneVar_modelAndObs[metric]( + modelFile1, modelVarName1, modelFileArea1, modelAreaName1, modelFileLandmask1, + modelLandmaskName1, obsFile1[ii], obsVarName1[ii], obsFileArea1[ii], obsAreaName1[ii], + obsFileLandmask1[ii], obsLandmaskName1[ii], regionVar1, dataset1=modelName, + dataset2=output_name, debug=debug, netcdf=netcdf, netcdf_name=netcdf_name, + metname=tmp_metric, **keyarg) + elif metric in list(dict_twoVar_modelAndObs.keys()): + for jj in range(len(obsNameVar2)): + output_name = obsNameVar1[ii] + '_' + obsNameVar2[jj] + if output_name != modelName: + print('\033[94m' + str().ljust(5) + "ComputeMetric: twoVarRMSmetric, " + metric + " = " + + modelName + " and " + output_name + '\033[0m') + diagnostic1[output_name] = dict_twoVar_modelAndObs[metric]( + modelFile1, modelVarName1, modelFileArea1, modelAreaName1, modelFileLandmask1, + modelLandmaskName1, modelFile2, modelVarName2, modelFileArea2, modelAreaName2, + modelFileLandmask2, modelLandmaskName2, obsFile1[ii], obsVarName1[ii], obsFileArea1[ii], + obsAreaName1[ii], obsFileLandmask1[ii], obsLandmaskName1[ii], obsFile2[jj], + obsVarName2[jj], obsFileArea2[jj], obsAreaName2[jj], obsFileLandmask2[jj], + obsLandmaskName2[jj], regionVar1, regionVar2, dataset1=modelName, dataset2=output_name, + debug=debug, netcdf=netcdf, netcdf_name=netcdf_name, metname=tmp_metric, **keyarg) + for obs in list(diagnostic1.keys()): + # puts metric values in its proper dictionary + if 'value' in list(diagnostic1[obs].keys()): + dict_metric_val[obs] = { + 'value': diagnostic1[obs]['value'], 'value_error': diagnostic1[obs]['value_error']} + dict_diagnostic[modelName] = {'value': None, 'value_error': None} + dict_diagnostic[obs] = {'value': None, 'value_error': None} + else: + multimetric = True + lkeys = list(set([key.split('__')[0] for key in list(diagnostic1[obs].keys()) if 'value' in key])) + for key in lkeys: + try: + dict_metric_val[obs] + except: + dict_metric_val[obs] = {key + '__value': diagnostic1[obs][key + '__value'], + key + '__value_error': diagnostic1[obs][key + '__value_error']} + dict_diagnostic[modelName] = {key + '__value': None, key + '__value_error': None} + dict_diagnostic[obs] = {key + '__value': None, key + '__value_error': None} + else: + dict_metric_val[obs][key + '__value'] = diagnostic1[obs][key + '__value'] + dict_metric_val[obs][key + '__value_error'] = diagnostic1[obs][key + '__value_error'] + dict_diagnostic[modelName][key + '__value'] = None + dict_diagnostic[modelName][key + '__value_error'] = None + dict_diagnostic[obs][key + '__value'] = None + dict_diagnostic[obs][key + '__value_error'] = None + if 'dive_down_diag' in list(diagnostic1[obs].keys()): + dict_dive_down[modelName] = diagnostic1[obs]['dive_down_diag']['model'] + dict_dive_down[obs] = diagnostic1[obs]['dive_down_diag']['observations'] + dict1 = {} + for elt in list(diagnostic1[obs]['dive_down_diag'].keys()): + if elt not in ['model', 'observations']: + dict1[elt] = diagnostic1[obs]['dive_down_diag'][elt] + dict_dive_down_metadata[obs] = dict1 + del dict1 + # puts diagnostic metadata in its proper dictionary + dict_diagnostic_metadata[modelName] = {'name': modelName, 'nyears': diagnostic1[obs]['nyears_model'], + 'time_period': diagnostic1[obs]['time_period_model']} + dict_diagnostic_metadata[obs] = {'name': obs, 'nyears': diagnostic1[obs]['nyears_observations'], + 'time_period': diagnostic1[obs]['time_period_observations']} + if 'events_model' in list(diagnostic1[obs].keys()): + dict_diagnostic_metadata[modelName]['events'] = diagnostic1[obs]['events_model'] + dict_diagnostic_metadata[obs]['events'] = diagnostic1[obs]['events_observations'] + if 'keyerror' in list(diagnostic1[obs].keys()): + dict_diagnostic_metadata[modelName]['keyerror'] = diagnostic1[obs]['keyerror'] + units = diagnostic1[obs]['units'] + diagnostic1 = diagnostic1[obs] + else: + # + # model diagnostic + # + keyarg['time_bounds'] = keyarg['time_bounds_mod'] + keyarg['project_interpreter_var1'] = keyarg['project_interpreter_mod_var1'] + if metric in list(dict_oneVar.keys()): + # computes diagnostic that needs only one variable + print('\033[94m' + str().ljust(5) + "ComputeMetric: oneVarmetric = " + str(modelName) + '\033[0m') + diagnostic1 = dict_oneVar[metric]( + modelFile1, modelVarName1, modelFileArea1, modelAreaName1, modelFileLandmask1, modelLandmaskName1, + regionVar1, dataset=modelName, debug=debug, netcdf=netcdf, netcdf_name=netcdf_name, + metname=tmp_metric, **keyarg) + elif metric in list(dict_twoVar.keys()): + # computes diagnostic that needs two variables + print('\033[94m' + str().ljust(5) + "ComputeMetric: twoVarmetric = " + str(modelName) + '\033[0m') + keyarg['project_interpreter_var2'] = keyarg['project_interpreter_mod_var2'] + diagnostic1 = dict_twoVar[metric]( + modelFile1, modelVarName1, modelFileArea1, modelAreaName1, modelFileLandmask1, modelLandmaskName1, + regionVar1, modelFile2, modelVarName2, modelFileArea2, modelAreaName2, modelFileLandmask2, + modelLandmaskName2, regionVar2, dataset=modelName, debug=debug, netcdf=netcdf, + netcdf_name=netcdf_name, metname=tmp_metric, **keyarg) + else: + diagnostic1 = None + list_strings = ["ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": metric", + str().ljust(5) + "unknown metric name: " + str(metric)] + EnsoErrorsWarnings.my_error(list_strings) + # puts metric / diagnostic values in its proper dictionary + dict_diagnostic[modelName] = {'value': diagnostic1['value'], 'value_error': diagnostic1['value_error']} + if 'nonlinearity' in list(diagnostic1.keys()): + dict_diagnostic[modelName]['nonlinearity'] = diagnostic1['nonlinearity'] + dict_diagnostic[modelName]['nonlinearity_error'] = diagnostic1['nonlinearity_error'] + if 'dive_down_diag' in list(diagnostic1.keys()): + dict_dive_down[modelName] = diagnostic1['dive_down_diag']['value'] + for elt in list(diagnostic1['dive_down_diag'].keys()): + if elt not in ['value']: + try: + dict_dive_down_metadata[modelName] + except: + dict_dive_down_metadata[modelName] = {elt: diagnostic1['dive_down_diag'][elt]} + else: + dict_dive_down_metadata[modelName][elt] = diagnostic1['dive_down_diag'][elt] + # puts diagnostic metadata in its proper dictionary + dict_diagnostic_metadata[modelName] = { + 'name': modelName, 'nyears': diagnostic1['nyears'], 'time_period': diagnostic1['time_period'], + } + if 'events_model' in list(diagnostic1.keys()): + dict_diagnostic_metadata[modelName]['events'] = diagnostic1['events'] + if 'keyerror' in list(diagnostic1.keys()): + dict_diagnostic_metadata[modelName]['keyerror'] = diagnostic1['keyerror'] + # + # observations diag + # + diag_obs = dict() + keyarg['time_bounds'] = keyarg['time_bounds_obs'] + for ii in range(len(obsNameVar1)): + # sets observations + obs1 = obsNameVar1[ii] + keyarg['project_interpreter_var1'] = obs1 + if metric in list(dict_oneVar.keys()): + output_name = obs1 + if output_name != modelName: + print('\033[94m' + str().ljust(5) + "ComputeMetric: oneVarmetric = " + str(output_name) + + '\033[0m') + diag_obs[output_name] = dict_oneVar[metric]( + obsFile1[ii], obsVarName1[ii], obsFileArea1[ii], obsAreaName1[ii], obsFileLandmask1[ii], + obsLandmaskName1[ii], regionVar1, dataset=output_name, debug=debug, netcdf=netcdf, + netcdf_name=netcdf_name, metname=tmp_metric, **keyarg) + elif metric in list(dict_twoVar.keys()): + for jj in range(len(obsNameVar2)): + obs2 = obsNameVar2[jj] + output_name = obs1 + '_' + obs2 + keyarg['project_interpreter_var2'] = obs2 + if output_name != modelName: + print('\033[94m' + str().ljust(5) + "ComputeMetric: twoVarmetric = " + str(output_name) + + '\033[0m') + diag_obs[output_name] = dict_twoVar[metric]( + obsFile1[ii], obsVarName1[ii], obsFileArea1[ii], obsAreaName1[ii], obsFileLandmask1[ii], + obsLandmaskName1[ii], regionVar1, obsFile2[jj], obsVarName2[jj], obsFileArea2[jj], + obsAreaName2[jj], obsFileLandmask2[jj], obsLandmaskName2[jj], regionVar2, + dataset=output_name, debug=debug, netcdf=netcdf, netcdf_name=netcdf_name, + metname=tmp_metric, **keyarg) + for obs in list(diag_obs.keys()): + # computes the metric + metric_val, metric_err, description_metric = math_metric_computation( + diagnostic1['value'], diagnostic1['value_error'], obs=diag_obs[obs]['value'], + obs_err=diag_obs[obs]['value_error'], keyword=keyarg['metric_computation']) + dict_metric_val[obs] = {'value': metric_val, 'value_error': metric_err} + # puts metric / diagnostic values in its proper dictionary + dict_diagnostic[obs] = {'value': diag_obs[obs]['value'], 'value_error': diag_obs[obs]['value_error']} + if 'nonlinearity' in list(diag_obs[obs].keys()): + dict_diagnostic[obs]['nonlinearity'] = diag_obs[obs]['nonlinearity'] + dict_diagnostic[obs]['nonlinearity_error'] = diag_obs[obs]['nonlinearity_error'] + if 'dive_down_diag' in list(diag_obs[obs].keys()): + dict_dive_down[obs] = diag_obs[obs]['dive_down_diag']['value'] + for elt in list(diag_obs[obs]['dive_down_diag'].keys()): + if elt not in ['value']: + try: + dict_dive_down_metadata[obs] + except: + dict_dive_down_metadata[obs] = {elt: diag_obs[obs]['dive_down_diag'][elt]} + else: + dict_dive_down_metadata[obs][elt] = diag_obs[obs]['dive_down_diag'][elt] + # puts diagnostic metadata in its proper dictionary + dict_diagnostic_metadata[obs] = { + 'name': obs, 'nyears': diag_obs[obs]['nyears'], 'time_period': diag_obs[obs]['time_period'], + } + if 'events_model' in list(diag_obs[obs].keys()): + dict_diagnostic_metadata[obs]['events'] = diag_obs[obs]['events'] + if 'keyerror' in list(diag_obs[obs].keys()): + dict_diagnostic_metadata[obs]['keyerror'] = diag_obs[obs]['keyerror'] + if keyarg['metric_computation'] in ['ratio', 'relative_difference']: + units = diagnostic1['units'] + ' / ' + diagnostic1['units'] + elif keyarg['metric_computation'] in ['abs_relative_difference']: + units = "%" + else: + units = diagnostic1['units'] + # finishes to fill the diagnostic dictionary + list_keys = ['method', 'name', 'ref', 'time_frequency', 'units'] + for key in list_keys: + if key == 'method': + dict_diagnostic_metadata[key] = diagnostic1['method'] + dict_dive_down_metadata[key] = diagnostic1['method'] + try: + diagnostic1['nonlinearity'] + except: + pass + else: + dict_diagnostic_metadata['method_nonlinearity'] = diagnostic1['method_nonlinearity'] + else: + dict_diagnostic_metadata[key] = diagnostic1[key] + dict_dive_down_metadata[key] = diagnostic1[key] + # creates the output dictionaries + dict_metrics = {'metric': dict_metric_val, 'diagnostic': dict_diagnostic} + datasets = modelName + "; " + for ii in range(len(obsNameVar1)): + datasets = datasets + obsNameVar1[ii] + "'s " + obsVarName1[ii] + if ii != len(obsNameVar1) - 1: + datasets = datasets + " & " + else: + datasets = datasets + "; " + if len(obsNameVar2) > 0: + for ii in range(len(obsNameVar2)): + if isinstance(obsVarName2[ii], list): + datasets = datasets + obsNameVar2[ii] + "'s " + for jj in range(len(obsVarName2[ii])): + datasets += obsVarName2[ii][jj] + if jj != len(obsVarName2[ii])-1: + datasets += " & " + else: + datasets = datasets + obsNameVar2[ii] + "'s " + obsVarName2[ii] + if ii != len(obsNameVar2) - 1: + datasets = datasets + " & " + if multimetric is True: + tmp = {'name': metric, 'method': description_metric, 'datasets': datasets} + for key in lkeys: + tmp[key + '__units'] = diagnostic1[key + '__units'] + dict_metadata = {'metric': tmp, 'diagnostic': dict_diagnostic_metadata} + else: + dict_metadata = { + 'metric': {'name': metric, 'method': description_metric, 'datasets': datasets, 'units': units}, + 'diagnostic': dict_diagnostic_metadata, + } + return dict_metrics, dict_metadata, dict_dive_down, dict_dive_down_metadata +# ---------------------------------------------------------------------------------------------------------------------# diff --git a/build/lib/EnsoMetrics/EnsoErrorsWarnings.py b/build/lib/EnsoMetrics/EnsoErrorsWarnings.py new file mode 100644 index 0000000..1be574c --- /dev/null +++ b/build/lib/EnsoMetrics/EnsoErrorsWarnings.py @@ -0,0 +1,483 @@ +# -*- coding:UTF-8 -*- + +from sys import exit as sys_exit + + +# ---------------------------------------------------# +# colors for printing +class bcolors: + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + + +# ---------------------------------------------------------------------------------------------------------------------# +# +# Set of defined errors and warning functions used in EnsoUvcdatToolsLib.py +# +def plus_comma_space(string): + """ + ################################################################################# + Description: + Adds a comma and a space if the string is not empty or if the string is not composed of space only + ################################################################################# + + :param string: string + string to which comma_space could be added + + :return string: string + given string+', ' if applicable + + Examples + ---------- + string = string('') + print string + '' + # or + string = string(' ') + print string + ' ' + # or + string = string('Where there’s a will') + print string + 'Where there’s a will, ' + """ + if string.isspace(): + return string + elif not string: + return string + else: + return string+', ' + + +def message_formating(inspect_stack): + """ + ################################################################################# + Description: + Formats inspect.stack() as ' File filename, line n, in module' + ################################################################################# + + :param inspect_stack: array + list of information about the program/module/line,... created using inspect.stack() + + :return string: string + formatted inspect.stack() in a string (using PlusCommaSpace) + + Examples + ---------- + string = message_formating(inspect_stack) + print string + ' File filename, line n, in module' + """ + string = ' ' + # adds file's name + if inspect_stack[0][1] != '': + string = plus_comma_space(string) + 'File ' + str(inspect_stack[0][1]) + # adds line number + string = plus_comma_space(string) + 'line ' + str(inspect_stack[0][2]) + # adds module's name + if inspect_stack[0][3] != '': + string = plus_comma_space(string) + 'in ' + str(inspect_stack[0][3]) + return string + + +def my_warning(list_strings): + """ + ################################################################################# + Description: + Prints the strings in 'list_strings' and continues + ################################################################################# + + :param list_strings: list + list of strings to print + :return: + """ + for ii in range(2): + print(bcolors.WARNING + "") + print(str().ljust(5) + "%%%%% ----- %%%%%") + for string in list_strings: + print(str().ljust(5) + str(string)) + print(str().ljust(5) + "%%%%% ----- %%%%%") + for ii in range(2): + print("" + bcolors.ENDC) + return + + +# ---------------------------------------------------------------------------------------------------------------------# +# +# ERRORS +# +def my_error(list_strings): + """ + ################################################################################# + Description: + Prints the strings in 'list_strings' and exits + ################################################################################# + + :param list_strings: list + list of strings to print + :return: + """ + for ii in range(2): + print(bcolors.FAIL + "") + print(str().ljust(5) + "%%%%% ----- %%%%%") + for string in list_strings: + print(str().ljust(5) + str(string)) + print(str().ljust(5) + "%%%%% ----- %%%%%") + for ii in range(2): + print("" + bcolors.ENDC) + sys_exit("") + return + + +def mismatch_shapes_error(tab1, tab2, inspect_stack): + """ + ################################################################################# + Description: + Function 'my_error' in the case of array shape error + Prints strings and exits + ################################################################################# + + :param tab1: masked_array + :param tab2: masked_array + :param inspect_stack: array + list of information about the program/module/line,... created using inspect.stack() + :return: + """ + try: name1 = tab1.name + except: name1 = 'no_name' + try: name2 = tab2.name + except: name2 = 'no_name' + list_strings = ["ERROR " + message_formating(inspect_stack) + ": array shape", + str().ljust(5) + "arrays shapes mismatch: " + str(name1) + " = " + str(tab1.shape) + "', and " + + str(name2) + " = " + str(tab2.shape)] + my_warning(list_strings) + return + + +def object_type_error(parameter_name, type_parameter, type_parameter_should_be, inspect_stack): + """ + ################################################################################# + Description: + Function 'my_error' in the case of object type error + Prints strings and exits + ################################################################################# + + :param parameter_name: string + name of a parameter from which the error comes from + :param type_parameter: string + parameter's type + :param type_parameter_should_be: string + what the parameter's type should be + :param inspect_stack: array + list of information about the program/module/line,... created using inspect.stack() + :return: + """ + list_strings = ["ERROR " + message_formating(inspect_stack) + ": object type", + str().ljust(5) + str(parameter_name) + ": should be '" + str(type_parameter_should_be) + "', not '" + + str(type_parameter) + "'"] + my_warning(list_strings) + return + + +def too_short_time_period(metric_name, length, minimum_length, inspect_stack): + """ + ################################################################################# + Description: + Function 'my_warning' in the case of a too short time-period + Prints strings and exits + ################################################################################# + + :param metric_name: string + name of the metric from which the error comes from + :param length: integer + length of the time axis of the variable + :param minimum_length: integer + minimum length of the time axis for the metric to make sens (defined in the metrics collection) + :param inspect_stack: array + list of information about the program/module/line,... created using inspect.stack() + :return: + """ + list_strings = ["ERROR " + message_formating(inspect_stack) + ": too short time-period", + str().ljust(5) + str(metric_name) + ": the time-period is too short: " + str(length) + + " (minimum time-period: " + str(minimum_length) + ")"] + my_warning(list_strings) + return + + +def unlikely_units(var_name, name_in_file, units, min_max, inspect_stack): + """ + ################################################################################# + Description: + Function 'my_warning' in the case of unlikely units + Prints strings and exits + ################################################################################# + + :param var_name: string + generic name of the variable that has unlikely units + :param name_in_file: string + name of the variable in the file (usually the short_name) that has unlikely units + :param units: string + units of the variable + :param min_max: list + minimum and maximum values of 'var_name' + :param inspect_stack: array + list of information about the program/module/line,... created using inspect.stack() + :return: + """ + list_strings = ["ERROR " + message_formating(inspect_stack) + ": units", + str().ljust(5) + "the file says that " + str(var_name) + " (" + str(name_in_file) + + ") is in " + str(units) + " but it seems unlikely (" + str(min_max) + ")"] + my_warning(list_strings) + return + + +def unknown_averaging(average, known_average, inspect_stack): + """ + ################################################################################# + Description: + Function 'my_error' in the case of unknown frequency + Prints strings + ################################################################################# + + :param average: string + averaging method (axis) (should by horizontal, meridional, temporal or zonal) + :param known_average: string + list of defined averaging method (axis) + :param inspect_stack: array + list of information about the program/module/line,... created using inspect.stack() + :return: + """ + list_strings = ["ERROR" + message_formating(inspect_stack) + ": averaging method", + str().ljust(5) + "unkwown averaging method (axis): " + str(average), + str().ljust(10) + "known averaging method: " + str(sorted(known_average))] + my_error(list_strings) + return + + +def unknown_frequency(frequency, inspect_stack): + """ + ################################################################################# + Description: + Function 'my_error' in the case of unknown frequency + Prints strings + ################################################################################# + + :param frequency: string + frequency of a dataset (should by daily, monthly or yearly) + :param inspect_stack: array + list of information about the program/module/line,... created using inspect.stack() + :return: + """ + list_strings = ["ERROR" + message_formating(inspect_stack) + ": frequency", + str().ljust(5) + "unknown frequency: " + str(frequency)] + my_error(list_strings) + return + + +def unknown_key_arg(arg, inspect_stack): + """ + ################################################################################# + Description: + Function 'my_error' in the case of unknown argument + Prints strings + ################################################################################# + + :param arg: string + argument of a function + :param inspect_stack: array + list of information about the program/module/line,... created using inspect.stack() + :return: + """ + list_strings = ["ERROR" + message_formating(inspect_stack) + ": argument", + str().ljust(5) + "unknown argument(s): " + str(arg)] + my_warning(list_strings) + return + + +def unknown_units(var_name, name_in_file, units, inspect_stack): + """ + ################################################################################# + Description: + Function 'MyError' in the case of unknown units + Prints strings and exits + ################################################################################# + + :param var_name: string + generic name of the variable that has unlikely units + :param name_in_file: string + name of the variable in the file (usually the short_name) that has unlikely units + :param units: string + units of the variable + :param inspect_stack: array + list of information about the program/module/line,... created using inspect.stack() + :return: + """ + list_strings = ["ERROR" + message_formating(inspect_stack) + ": units", + str().ljust(5) + "unknown units: " + str(var_name) + " (" + str(name_in_file) + + ") is in " + str(units)] + my_warning(list_strings) + return +# ---------------------------------------------------------------------------------------------------------------------# + + +# ---------------------------------------------------------------------------------------------------------------------# +# Just prints +def debug_mode(color, title, nbr_spaces, axes1='', axes2='', axes3='', axes4='', file1='', file2='', file3='', file4='', + line1='', line2='', line3='', line4='', nina1='', nina2='', nina3='', nina4='', nino1='', nino2='', + nino3='', nino4='', shape1='', shape2='', shape3='', shape4='', time1='', time2='', time3='', time4='', + var1='', var2='', var3='', var4=''): + """ + ################################################################################# + Description: + Prints strings to ease debugging + ################################################################################# + + :param color: string + color code (e.g. '\033[94m' is blue, '\033[92m' is green) + :param title: string + name of the section that is printed + :param nbr_spaces: int + number of leading spaces before printing the title + :param axes1: string, optional + axis list of variable 1 + :param axes2: string, optional + axis list of variable 2 + :param axes3: string, optional + axis list of variable 3 + :param axes4: string, optional + axis list of variable 4 + :param file1: string, optional + file name of variable 1 + :param file2: string, optional + file name of variable 2 + :param file3: string, optional + file name of variable 3 + :param file4: string, optional + file name of variable 4 + :param line1: string, optional + just a line to print 1 + :param line2: string, optional + just a line to print 2 + :param line3: string, optional + just a line to print 3 + :param line4: string, optional + just a line to print 4 + :param nina1: string, optional + list of nina years 1 + :param nina2: string, optional + list of nina years 2 + :param nina3: string, optional + list of nina years 3 + :param nina4: string, optional + list of nina years 4 + :param nino1: string, optional + list of nino years 1 + :param nino2: string, optional + list of nino years 2 + :param nino3: string, optional + list of nino years 3 + :param nino4: string, optional + list of nino years 4 + :param shape1: string, optional + shape of the array containing variable 1 + :param shape2: string, optional + shape of the array containing variable 2 + :param shape3: string, optional + shape of the array containing variable 3 + :param shape4: string, optional + shape of the array containing variable 4 + :param time1: string, optional + time bounds of variable 1 + :param time2: string, optional + time bounds of variable 2 + :param time3: string, optional + time bounds of variable 3 + :param time4: string, optional + time bounds of variable 4 + :param var1: string, optional + variable name 1 + :param var2: string, optional + variable name 2 + :param var3: string, optional + variable name 3 + :param var4: string, optional + variable name 4 + :return: + """ + # first variable + print(color + str().ljust(nbr_spaces) + title + bcolors.ENDC) + if file1: + print(color + str().ljust(nbr_spaces+5) + 'file name 1: ' + file1 + bcolors.ENDC) + if var1: + print(color + str().ljust(nbr_spaces+5) + 'variable name 1: ' + var1 + bcolors.ENDC) + if axes1: + print(color + str().ljust(nbr_spaces+5) + 'axes list 1: ' + axes1 + bcolors.ENDC) + if time1: + print(color + str().ljust(nbr_spaces+5) + 'time bounds 1: ' + time1 + bcolors.ENDC) + if shape1: + print(color + str().ljust(nbr_spaces+5) + 'shape 1: ' + shape1 + bcolors.ENDC) + if nina1: + print(color + str().ljust(nbr_spaces+5) + 'nina year 1: ' + nina1 + bcolors.ENDC) + if nino1: + print(color + str().ljust(nbr_spaces+5) + 'nino year 1: ' + nino1 + bcolors.ENDC) + if line1: + print(color + str().ljust(nbr_spaces+5) + line1 + bcolors.ENDC) + # second variable + if file2: + print(color + str().ljust(nbr_spaces+5) + 'file name 2: ' + file2 + bcolors.ENDC) + if var2: + print(color + str().ljust(nbr_spaces+5) + 'variable name 2: ' + var2 + bcolors.ENDC) + if axes2: + print(color + str().ljust(nbr_spaces+5) + 'axes list 2: ' + axes2 + bcolors.ENDC) + if time2: + print(color + str().ljust(nbr_spaces+5) + 'time bounds 2: ' + time2 + bcolors.ENDC) + if shape2: + print(color + str().ljust(nbr_spaces+5) + 'shape 2: ' + shape2 + bcolors.ENDC) + if nina2: + print(color + str().ljust(nbr_spaces+5) + 'nina year 2: ' + nina2 + bcolors.ENDC) + if nino2: + print(color + str().ljust(nbr_spaces+5) + 'nino year 2: ' + nino2 + bcolors.ENDC) + if line2: + print(color + str().ljust(nbr_spaces+5) + line2 + bcolors.ENDC) + # third variable + if file3: + print(color + str().ljust(nbr_spaces + 5) + 'file name 3: ' + file3 + bcolors.ENDC) + if var3: + print(color + str().ljust(nbr_spaces + 5) + 'variable name 3: ' + var3 + bcolors.ENDC) + if axes3: + print(color + str().ljust(nbr_spaces + 5) + 'axes list 3: ' + axes3 + bcolors.ENDC) + if time3: + print(color + str().ljust(nbr_spaces + 5) + 'time bounds 3: ' + time3 + bcolors.ENDC) + if shape3: + print(color + str().ljust(nbr_spaces + 5) + 'shape 3: ' + shape3 + bcolors.ENDC) + if nina3: + print(color + str().ljust(nbr_spaces + 5) + 'nina year 3: ' + nina3 + bcolors.ENDC) + if nino3: + print(color + str().ljust(nbr_spaces + 5) + 'nino year 3: ' + nino3 + bcolors.ENDC) + if line3: + print(color + str().ljust(nbr_spaces + 5) + line3 + bcolors.ENDC) + # fourth variable + if file4: + print(color + str().ljust(nbr_spaces + 5) + 'file name 4: ' + file4 + bcolors.ENDC) + if var4: + print(color + str().ljust(nbr_spaces + 5) + 'variable name 4: ' + var4 + bcolors.ENDC) + if axes4: + print(color + str().ljust(nbr_spaces + 5) + 'axes list 4: ' + axes4 + bcolors.ENDC) + if time4: + print(color + str().ljust(nbr_spaces + 5) + 'time bounds 4: ' + time4 + bcolors.ENDC) + if shape4: + print(color + str().ljust(nbr_spaces + 5) + 'shape 4: ' + shape4 + bcolors.ENDC) + if nina4: + print(color + str().ljust(nbr_spaces + 5) + 'nina year 4: ' + nina4 + bcolors.ENDC) + if nino4: + print(color + str().ljust(nbr_spaces + 5) + 'nino year 4: ' + nino4 + bcolors.ENDC) + if line4: + print(color + str().ljust(nbr_spaces + 5) + line4 + bcolors.ENDC) + return +# ---------------------------------------------------------------------------------------------------------------------# diff --git a/build/lib/EnsoMetrics/EnsoMetricsLib.py b/build/lib/EnsoMetrics/EnsoMetricsLib.py new file mode 100644 index 0000000..3167fd8 --- /dev/null +++ b/build/lib/EnsoMetrics/EnsoMetricsLib.py @@ -0,0 +1,23019 @@ +# -*- coding:UTF-8 -*- +from copy import deepcopy +from inspect import stack as INSPECTstack +from numpy import sign as NUMPYsign +from numpy import sqrt as NUMPYsqrt +from numpy import square as NUMPYsquare + +# ENSO_metrics package functions: +from .EnsoCollectionsLib import ReferenceRegions +from . import EnsoErrorsWarnings +from .EnsoToolsLib import add_up_errors, percentage_val_eastward, statistical_dispersion +from .EnsoUvcdatToolsLib import ArrayListAx, ArrayToList, AverageMeridional, AverageZonal, BasinMask, CheckTime,\ + Composite, ComputeInterannualAnomalies, ComputePDF, Concatenate, Correlation, DetectEvents, DurationAllEvent,\ + DurationEvent, Event_selection, fill_dict_teleconnection, FindXYMinMaxInTs, get_year_by_year,\ + LinearRegressionAndNonlinearity, LinearRegressionTsAgainstMap, LinearRegressionTsAgainstTs, MinMax, MyEmpty,\ + OperationMultiply, PreProcessTS, Read_data_mask_area, Read_data_mask_area_multifile, Regrid, RmsAxis,\ + RmsHorizontal, RmsMeridional, RmsZonal, SaveNetcdf, SeasonalMean, SkewnessTemporal, SlabOcean, Smoothing, Std,\ + StdMonthly, TimeBounds, TsToMap, TwoVarRegrid +from .KeyArgLib import default_arg_values + + +# ---------------------------------------------------------------------------------------------------------------------# +# +# Library to compute ENSO metrics +# These functions have file names and variable names as inputs and metric as output +# +def BiasPrRmse(prfilemod, prnamemod, prareafilemod, prareanamemod, prlandmaskfilemod, prlandmasknamemod, + prfileobs, prnameobs, prareafileobs, prareanameobs, prlandmaskfileobs, prlandmasknameobs, box, + centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, netcdf_name='', + metname='', **kwargs): + """ + The BiasPrRmse() function computes the PR spatial root mean square error (RMSE) in a 'box' (usually the tropical + Pacific) + + Inputs: + ------ + :param prfilemod: string + path_to/filename of the file (NetCDF) of the modeled PR + :param prnamemod: string + name of PR variable (pr, precip) in 'prfilemod' + :param prareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for PR + :param prareanamemod: string + name of areacell variable (areacella, areacello) in 'prareafilemod' + :param prlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for PR + :param prlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'prlandmaskfilemod' + :param prfileobs: string + path_to/filename of the file (NetCDF) of the observed PR + :param prnameobs: string + name of PR variable (pr, prec) in 'prfileobs' + :param prareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for PR + :param prareanameobs: string + name of areacell variable (areacella, areacello) in 'prareafileobs' + :param prlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for PR + :param prlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'prlandmaskfileobs' + :param box: string + name of box ('tropical_pacific') for PR + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If you want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param regridding: dict, optional + see EnsoUvcdatToolsLib.TwoVarRegrid and EnsoUvcdatToolsLib.Regrid for options + the aim if to specify if the model is regridded toward the observations or vice versa, of if both model and + observations are regridded toward another grid + interpolation tool and method can be specified + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled PR file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed PR file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return rmseMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_model, + time_period_observations, ref, keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + Notes: + ----- + TODO: add error calculation to rmse (function of nyears) + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'regridding', 'smoothing', + 'time_bounds_mod', 'time_bounds_obs'] + for arg in needed_kwarg: + try: kwargs[arg] + except: kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'pr RMSE' + Units = 'mm/day' + Method = 'Spatial root mean square error of ' + box + ' pr' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = "BiasPrRmse" + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + pr_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + prfilemod, prnamemod, 'precipitations', metric, box, file_area=prareafilemod, name_area=prareanamemod, + file_mask=prlandmaskfilemod, name_mask=prlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + pr_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + prfileobs, prnameobs, 'precipitations', metric, box, file_area=prareafileobs, name_area=prareanameobs, + file_mask=prlandmaskfileobs, name_mask=prlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(pr_mod.shape[0] / 12)) + yearN_obs = int(round(pr_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(pr_mod) + actualtimebounds_obs = TimeBounds(pr_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + prRmse, prRmseErr = None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + # here only the detrending (if applicable) and time averaging are performed + pr_mod, Method, keyerror_mod = PreProcessTS( + pr_mod, Method, areacell=mod_areacell, average='time', compute_anom=False, region=box, **kwargs) + pr_obs, _, keyerror_obs = PreProcessTS( + pr_obs, '', areacell=obs_areacell, average='time', compute_anom=False, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + prRmse, prRmseErr = None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + pr_mod, pr_obs, Method = TwoVarRegrid(pr_mod, pr_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Computes the root mean square difference + prRmse, keyerror = RmsHorizontal(pr_mod, pr_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + prRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + + if netcdf is True: + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, 'time_period': str(actualtimebounds_mod)} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, 'time_period': str(actualtimebounds_obs)} + dict3 = {'metric_name': Name, 'metric_value_' + dataset2: prRmse, + 'metric_value_error_' + dataset2: prRmseErr, 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + SaveNetcdf(file_name, var1=pr_mod, var1_attributes=dict1, var1_name='pr_map__' + dataset1, var2=pr_obs, + var2_attributes=dict2, var2_name='pr_map__' + dataset2, global_attributes=dict3) + del dict1, dict2, dict3 + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(prRmse), 'line2': 'metric value_error: ' + str(prRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + rmseMetric = { + 'name': Name, 'value': prRmse, 'value_error': prRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag, + } + return rmseMetric + + +def BiasPrLatRmse(prfilemod, prnamemod, prareafilemod, prareanamemod, prlandmaskfilemod, prlandmasknamemod, + prfileobs, prnameobs, prareafileobs, prareanameobs, prlandmaskfileobs, prlandmasknameobs, box, + centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, netcdf_name='', + metname='', **kwargs): + """ + The BiasPrLatRmse() function computes the PR meridional (latitude) root mean square error (RMSE) in a 'box' + (usually 'nino3_LatExt') + + Inputs: + ------ + :param prfilemod: string + path_to/filename of the file (NetCDF) of the modeled PR + :param prnamemod: string + name of PR variable (pr, precip) in 'prfilemod' + :param prareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for PR + :param prareanamemod: string + name of areacell variable (areacella, areacello) in 'prareafilemod' + :param prlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for PR + :param prlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'prlandmaskfilemod' + :param prfileobs: string + path_to/filename of the file (NetCDF) of the observed PR + :param prnameobs: string + name of PR variable (pr, prec) in 'prfileobs' + :param prareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for PR + :param prareanameobs: string + name of areacell variable (areacella, areacello) in 'prareafileobs' + :param prlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for PR + :param prlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'prlandmaskfileobs' + :param box: string + name of box ('nino3_LatExt') for PR + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param regridding: dict, optional + see EnsoUvcdatToolsLib.TwoVarRegrid and EnsoUvcdatToolsLib.Regrid for options + the aim if to specify if the model is regridded toward the observations or vice versa, of if both model and + observations are regridded toward another grid + interpolation tool and method can be specified + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled PR file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed PR file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return LatRmseMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_model, + time_period_observations, ref, keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + Notes: + ----- + TODO: add error calculation to rmse (function of nyears) + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'regridding', 'smoothing', + 'time_bounds_mod', 'time_bounds_obs'] + for arg in needed_kwarg: + try: kwargs[arg] + except: kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'pr Meridional RMSE' + Units = 'mm/day' + Method = 'Meridional root mean square error of ' + box + ' pr' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = "BiasPrLatRmse" + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + pr_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + prfilemod, prnamemod, 'precipitations', metric, box, file_area=prareafilemod, name_area=prareanamemod, + file_mask=prlandmaskfilemod, name_mask=prlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + pr_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + prfileobs, prnameobs, 'precipitations', metric, box, file_area=prareafileobs, name_area=prareanameobs, + file_mask=prlandmaskfileobs, name_mask=prlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(pr_mod.shape[0] / 12)) + yearN_obs = int(round(pr_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(pr_mod) + actualtimebounds_obs = TimeBounds(pr_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + prRmse, prRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + # here only the detrending (if applicable) and time averaging are performed + pr_mod, Method, keyerror_mod = PreProcessTS( + pr_mod, Method, areacell=mod_areacell, average='time', compute_anom=False, region=box, **kwargs) + pr_obs, _, keyerror_obs = PreProcessTS( + pr_obs, '', areacell=obs_areacell, average='time', compute_anom=False, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + prRmse, prRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + pr_mod, pr_obs, Method = TwoVarRegrid(pr_mod, pr_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Zonal average + pr_mod, keyerror_mod = AverageZonal(pr_mod) + pr_obs, keyerror_obs = AverageZonal(pr_obs) + if keyerror_mod is not None or keyerror_obs is not None: + prRmse, prRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageZonal', 15, **dict_debug) + + # Computes the root mean square difference + prRmse, keyerror = RmsMeridional(pr_mod, pr_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + prRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(pr_mod), 'observations': ArrayToList(pr_obs), + 'axis': list(pr_mod.getAxis(0)[:])} + if netcdf is True: + map_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + prfilemod, prnamemod, 'precipitations', metric, 'equatorial_pacific_LatExt2', + file_area=prareafilemod, name_area=prareanamemod, file_mask=prlandmaskfilemod, + name_mask=prlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + map_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + prfileobs, prnameobs, 'precipitations', metric, 'equatorial_pacific_LatExt2', + file_area=prareafileobs, name_area=prareanameobs, file_mask=prlandmaskfileobs, + name_mask=prlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + map_mod, _, keyerror_mod = PreProcessTS( + map_mod, '', areacell=mod_areacell, average='time', compute_anom=False, + region="equatorial_pacific_LatExt2", **kwargs) + map_obs, _, keyerror_obs = PreProcessTS( + map_obs, '', areacell=obs_areacell, average='time', compute_anom=False, + region="equatorial_pacific_LatExt2", **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + # Regridding + if isinstance(kwargs['regridding'], dict): + map_mod, map_obs, _ = TwoVarRegrid( + map_mod, map_obs, '', region='equatorial_pacific_LatExt2', **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in map_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in map_obs.getAxisList()]), + 'shape1': '(mod) ' + str(map_mod.shape), + 'shape2': '(obs) ' + str(map_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after TwoVarRegrid: netcdf', 15, **dict_debug) + # change units + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod)} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs)} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod)} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs)} + dict5 = {'metric_name': Name, 'metric_value_' + dataset2: prRmse, + 'metric_value_error_' + dataset2: prRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=pr_mod, var1_attributes=dict1, var1_name='pr_lat__' + dataset1, + var2=pr_obs, var2_attributes=dict2, var2_name='pr_lat__' + dataset2, var3=map_mod, + var3_attributes=dict3, var3_name='pr_map__' + dataset1, var4=map_obs, + var4_attributes=dict4, var4_name='pr_map__' + dataset2, global_attributes=dict5) + del dict1, dict2, dict3, dict4, dict5 + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(prRmse), 'line2': 'metric value_error: ' + str(prRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + LatRmseMetric = { + 'name': Name, 'value': prRmse, 'value_error': prRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag, + } + return LatRmseMetric + + +def BiasPrLonRmse(prfilemod, prnamemod, prareafilemod, prareanamemod, prlandmaskfilemod, prlandmasknamemod, prfileobs, + prnameobs, prareafileobs, prareanameobs, prlandmaskfileobs, prlandmasknameobs, box, centered_rmse=0, + biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, netcdf_name='', metname='', + **kwargs): + """ + The BiasPrLonRmse() function computes the PR zonal (longitude) root mean square error (RMSE) in a 'box' + (usually the Equatorial Pacific) + + Inputs: + ------ + :param prfilemod: string + path_to/filename of the file (NetCDF) of the modeled PR + :param prnamemod: string + name of PR variable (pr, precip) in 'prfilemod' + :param prareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for PR + :param prareanamemod: string + name of areacell variable (areacella, areacello) in 'prareafilemod' + :param prlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for PR + :param prlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'prlandmaskfilemod' + :param prfileobs: string + path_to/filename of the file (NetCDF) of the observed PR + :param prnameobs: string + name of PR variable (pr, prec) in 'prfileobs' + :param prareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for PR + :param prareanameobs: string + name of areacell variable (areacella, areacello) in 'prareafileobs' + :param prlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for PR + :param prlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'prlandmaskfileobs' + :param box: string + name of box ('equatorial_pacific') for PR + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param regridding: dict, optional + see EnsoUvcdatToolsLib.TwoVarRegrid and EnsoUvcdatToolsLib.Regrid for options + the aim if to specify if the model is regridded toward the observations or vice versa, of if both model and + observations are regridded toward another grid + interpolation tool and method can be specified + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled PR file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed PR file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return LonRmseMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_model, + time_period_observations, ref, keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + Notes: + ----- + TODO: add error calculation to rmse (function of nyears) + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'regridding', 'smoothing', + 'time_bounds_mod', 'time_bounds_obs'] + for arg in needed_kwarg: + try: kwargs[arg] + except: kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'pr Zonal RMSE' + Units = 'mm/day' + Method = 'Zonal root mean square error of ' + box + ' pr' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = "BiasPrLonRmse" + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + pr_mod, mod_areacell, keyerror_mod = \ + Read_data_mask_area(prfilemod, prnamemod, 'precipitations', metric, box, file_area=prareafilemod, + name_area=prareanamemod, file_mask=prlandmaskfilemod, name_mask=prlandmasknamemod, + maskland=True, maskocean=False, time_bounds=kwargs['time_bounds_mod'], debug=debug, + **kwargs) + pr_obs, obs_areacell, keyerror_obs = \ + Read_data_mask_area(prfileobs, prnameobs, 'precipitations', metric, box, file_area=prareafileobs, + name_area=prareanameobs, file_mask=prlandmaskfileobs, name_mask=prlandmasknameobs, + maskland=True, maskocean=False, time_bounds=kwargs['time_bounds_obs'], debug=debug, + **kwargs) + + # Number of years + yearN_mod = int(round(pr_mod.shape[0] / 12)) + yearN_obs = int(round(pr_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(pr_mod) + actualtimebounds_obs = TimeBounds(pr_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + prRmse, prRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + # here only the detrending (if applicable) and time averaging are performed + pr_mod, Method, keyerror_mod = PreProcessTS( + pr_mod, Method, areacell=mod_areacell, average='time', compute_anom=False, region=box, **kwargs) + pr_obs, _, keyerror_obs = PreProcessTS( + pr_obs, '', areacell=obs_areacell, average='time', compute_anom=False, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + prRmse, prRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + pr_mod, pr_obs, Method = TwoVarRegrid(pr_mod, pr_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Meridional average + pr_mod, keyerror_mod = AverageMeridional(pr_mod) + pr_obs, keyerror_obs = AverageMeridional(pr_obs) + if keyerror_mod is not None or keyerror_obs is not None: + prRmse, prRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + + # Computes the root mean square difference + prRmse, keyerror = RmsZonal(pr_mod, pr_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + prRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(pr_mod), 'observations': ArrayToList(pr_obs), + 'axis': list(pr_mod.getAxis(0)[:])} + if netcdf is True: + map_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + prfilemod, prnamemod, 'precipitations', metric, 'equatorial_pacific_LatExt2', + file_area=prareafilemod, name_area=prareanamemod, file_mask=prlandmaskfilemod, + name_mask=prlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + map_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + prfileobs, prnameobs, 'precipitations', metric, 'equatorial_pacific_LatExt2', + file_area=prareafileobs, name_area=prareanameobs, file_mask=prlandmaskfileobs, + name_mask=prlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + map_mod, _, keyerror_mod = PreProcessTS( + map_mod, '', areacell=mod_areacell, average='time', compute_anom=False, + region="equatorial_pacific_LatExt2", **kwargs) + map_obs, _, keyerror_obs = PreProcessTS( + map_obs, '', areacell=obs_areacell, average='time', compute_anom=False, + region="equatorial_pacific_LatExt2", **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + # Regridding + if isinstance(kwargs['regridding'], dict): + map_mod, map_obs, _ = TwoVarRegrid( + map_mod, map_obs, '', region='equatorial_pacific_LatExt2', **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in map_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in map_obs.getAxisList()]), + 'shape1': '(mod) ' + str(map_mod.shape), + 'shape2': '(obs) ' + str(map_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after TwoVarRegrid: netcdf', 15, **dict_debug) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod)} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs)} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod)} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs)} + dict5 = {'metric_name': Name, 'metric_value_' + dataset2: prRmse, + 'metric_value_error_' + dataset2: prRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=pr_mod, var1_attributes=dict1, var1_name='pr_lon__' + dataset1, + var2=pr_obs, var2_attributes=dict2, var2_name='pr_lon__' + dataset2, var3=map_mod, + var3_attributes=dict3, var3_name='pr_map__' + dataset1, var4=map_obs, + var4_attributes=dict4, var4_name='pr_map__' + dataset2, global_attributes=dict5) + del dict1, dict2, dict3, dict4, dict5 + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(prRmse), 'line2': 'metric value_error: ' + str(prRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + LonRmseMetric = { + 'name': Name, 'value': prRmse, 'value_error': prRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return LonRmseMetric + + +def BiasSshRmse(sshfilemod, sshnamemod, sshareafilemod, sshareanamemod, sshlandmaskfilemod, sshlandmasknamemod, + sshfileobs, sshnameobs, sshareafileobs, sshareanameobs, sshlandmaskfileobs, sshlandmasknameobs, box, + centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, netcdf_name='', + metname='', **kwargs): + """ + The BiasSshRmse() function computes the SSH spatial root mean square error (RMSE) in a 'box' (usually the tropical + Pacific) + + Inputs: + ------ + :param sshfilemod: string + path_to/filename of the file (NetCDF) of the modeled SSH + :param sshnamemod: string + name of SSH variable (ssh, sshg, zos) in 'sshfilemod' + :param sshareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for SSH + :param sshareanamemod: string + name of areacell variable (areacella, areacello) in 'sshareafilemod' + :param sshlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for SSH + :param sshlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'sshlandmaskfilemod' + :param sshfileobs: string + path_to/filename of the file (NetCDF) of the observed SSH + :param sshnameobs: string + name of SSH variable (ssh, sshg, zos) in 'sshfileobs' + :param sshareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for SSH + :param sshareanameobs: string + name of areacell variable (areacella, areacello) in 'sshareafileobs' + :param sshlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for SSH + :param sshlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'sshlandmaskfileobs' + :param box: string + name of box ('tropical_pacific') for SSH + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If you want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'AVISO',...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param regridding: dict, optional + see EnsoUvcdatToolsLib.TwoVarRegrid and EnsoUvcdatToolsLib.Regrid for options + the aim if to specify if the model is regridded toward the observations or vice versa, of if both model and + observations are regridded toward another grid + interpolation tool and method can be specified + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SSH file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SSH file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return rmseMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_model, + time_period_observations, ref, keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + Notes: + ----- + TODO: add error calculation to rmse (function of nyears) + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'regridding', 'smoothing', + 'time_bounds_mod', 'time_bounds_obs'] + for arg in needed_kwarg: + try: kwargs[arg] + except: kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'ssh RMSE' + Units = 'cm' + Method = 'Spatial root mean square error of ' + box + ' ssh' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = "BiasSshRmse" + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + ssh_mod, mod_areacell, keyerror_mod = \ + Read_data_mask_area(sshfilemod, sshnamemod, 'sea surface height', metric, box, file_area=sshareafilemod, + name_area=sshareanamemod, file_mask=sshlandmaskfilemod, name_mask=sshlandmasknamemod, + maskland=True, maskocean=False, time_bounds=kwargs['time_bounds_mod'], debug=debug, + **kwargs) + ssh_obs, obs_areacell, keyerror_obs = \ + Read_data_mask_area(sshfileobs, sshnameobs, 'sea surface height', metric, box, file_area=sshareafileobs, + name_area=sshareanameobs, file_mask=sshlandmaskfileobs, name_mask=sshlandmasknameobs, + maskland=True, maskocean=False, time_bounds=kwargs['time_bounds_obs'], debug=debug, + **kwargs) + + # Number of years + yearN_mod = int(round(ssh_mod.shape[0] / 12)) + yearN_obs = int(round(ssh_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(ssh_mod) + actualtimebounds_obs = TimeBounds(ssh_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + sshRmse, sshRmseErr = None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + # here only the detrending (if applicable) and time averaging are performed + ssh_mod, Method, keyerror_mod = PreProcessTS( + ssh_mod, Method, areacell=mod_areacell, average='time', compute_anom=False, region=box, **kwargs) + ssh_obs, _, keyerror_obs = PreProcessTS( + ssh_obs, '', areacell=obs_areacell, average='time', compute_anom=False, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + sshRmse, sshRmseErr = None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ssh_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ssh_obs.getAxisList()]), + 'shape1': '(mod) ' + str(ssh_mod.shape), 'shape2': '(obs) ' + str(ssh_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + ssh_mod, ssh_obs, Method = TwoVarRegrid(ssh_mod, ssh_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ssh_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ssh_obs.getAxisList()]), + 'shape1': '(mod) ' + str(ssh_mod.shape), 'shape2': '(obs) ' + str(ssh_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # change units + ssh_mod = ssh_mod * 1e2 + ssh_obs = ssh_obs * 1e2 + + # Computes the root mean square difference + sshRmse, keyerror = RmsHorizontal(ssh_mod, ssh_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + sshRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + + if netcdf is True: + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, 'time_period': str(actualtimebounds_mod)} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, 'time_period': str(actualtimebounds_obs)} + dict3 = {'metric_name': Name, 'metric_value_' + dataset2: sshRmse, + 'metric_value_error_' + dataset2: sshRmseErr, 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=ssh_mod, var1_attributes=dict1, var1_name='ssh_map__' + dataset1, var2=ssh_obs, + var2_attributes=dict2, var2_name='ssh_map__' + dataset2, global_attributes=dict3) + del dict1, dict2, dict3 + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(sshRmse), 'line2': 'metric value_error: ' + str(sshRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + rmseMetric = { + 'name': Name, 'value': sshRmse, 'value_error': sshRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return rmseMetric + + +def BiasSshLatRmse(sshfilemod, sshnamemod, sshareafilemod, sshareanamemod, sshlandmaskfilemod, sshlandmasknamemod, + sshfileobs, sshnameobs, sshareafileobs, sshareanameobs, sshlandmaskfileobs, sshlandmasknameobs, box, + centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, netcdf_name='', + metname='', **kwargs): + """ + The BiasSshLatRmse() function computes the SSH meridional (latitude) root mean square error (RMSE) in a 'box' + (usually 'nino3_LatExt') + + Inputs: + ------ + :param sshfilemod: string + path_to/filename of the file (NetCDF) of the modeled SSH + :param sshnamemod: string + name of SSH variable (ssh, sshg, zos) in 'sshfilemod' + :param sshareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for SSH + :param sshareanamemod: string + name of areacell variable (areacella, areacello) in 'sshareafilemod' + :param sshlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for SSH + :param sshlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'sshlandmaskfilemod' + :param sshfileobs: string + path_to/filename of the file (NetCDF) of the observed SSH + :param sshnameobs: string + name of SSH variable (ssh, sshg, zos) in 'sshfileobs' + :param sshareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for SSH + :param sshareanameobs: string + name of areacell variable (areacella, areacello) in 'sshareafileobs' + :param sshlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for SSH + :param sshlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'sshlandmaskfileobs' + :param box: string + name of box ('nino3_LatExt') for SSH + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'AVISO',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param regridding: dict, optional + see EnsoUvcdatToolsLib.TwoVarRegrid and EnsoUvcdatToolsLib.Regrid for options + the aim if to specify if the model is regridded toward the observations or vice versa, of if both model and + observations are regridded toward another grid + interpolation tool and method can be specified + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SSH file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SSH file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return LatRmseMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_model, + time_period_observations, ref, keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + Notes: + ----- + TODO: add error calculation to rmse (function of nyears) + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'regridding', 'smoothing', + 'time_bounds_mod', 'time_bounds_obs'] + for arg in needed_kwarg: + try: kwargs[arg] + except: kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'ssh Meridional RMSE' + Units = 'cm' + Method = 'Meridional root mean square error of ' + box + ' ssh' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = "BiasSshLatRmse" + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + ssh_mod, mod_areacell, keyerror_mod = \ + Read_data_mask_area(sshfilemod, sshnamemod, 'sea surface height', metric, box, file_area=sshareafilemod, + name_area=sshareanamemod, file_mask=sshlandmaskfilemod, name_mask=sshlandmasknamemod, + maskland=True, maskocean=False, time_bounds=kwargs['time_bounds_mod'], debug=debug, + **kwargs) + ssh_obs, obs_areacell, keyerror_obs = \ + Read_data_mask_area(sshfileobs, sshnameobs, 'sea surface height', metric, box, file_area=sshareafileobs, + name_area=sshareanameobs, file_mask=sshlandmaskfileobs, name_mask=sshlandmasknameobs, + maskland=True, maskocean=False, time_bounds=kwargs['time_bounds_obs'], debug=debug, + **kwargs) + + # Number of years + yearN_mod = int(round(ssh_mod.shape[0] / 12)) + yearN_obs = int(round(ssh_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(ssh_mod) + actualtimebounds_obs = TimeBounds(ssh_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + sshRmse, sshRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + keyerror = None + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + # here only the detrending (if applicable) and time averaging are performed + ssh_mod, Method, keyerror_mod = PreProcessTS( + ssh_mod, Method, areacell=mod_areacell, average='time', compute_anom=False, region=box, **kwargs) + ssh_obs, _, keyerror_obs = PreProcessTS( + ssh_obs, '', areacell=obs_areacell, average='time', compute_anom=False, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + sshRmse, sshRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ssh_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ssh_obs.getAxisList()]), + 'shape1': '(mod) ' + str(ssh_mod.shape), 'shape2': '(obs) ' + str(ssh_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + ssh_mod, ssh_obs, Method = TwoVarRegrid(ssh_mod, ssh_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ssh_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ssh_obs.getAxisList()]), + 'shape1': '(mod) ' + str(ssh_mod.shape), 'shape2': '(obs) ' + str(ssh_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Zonal average + ssh_mod, keyerror_mod = AverageZonal(ssh_mod) + ssh_obs, keyerror_obs = AverageZonal(ssh_obs) + if keyerror_mod is not None or keyerror_obs is not None: + sshRmse, sshRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + ssh_mod = ssh_mod * 1e2 + ssh_obs = ssh_obs * 1e2 + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ssh_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ssh_obs.getAxisList()]), + 'shape1': '(mod) ' + str(ssh_mod.shape), 'shape2': '(obs) ' + str(ssh_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageZonal', 15, **dict_debug) + + # Computes the root mean square difference + sshRmse, keyerror = RmsMeridional(ssh_mod, ssh_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + sshRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(ssh_mod), 'observations': ArrayToList(ssh_obs), + 'axis': list(ssh_mod.getAxis(0)[:])} + if netcdf is True: + map_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sshfilemod, sshnamemod, 'sea surface height', metric, 'equatorial_pacific_LatExt2', + file_area=sshareafilemod, name_area=sshareanamemod, file_mask=sshlandmaskfilemod, + name_mask=sshlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + map_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sshfileobs, sshnameobs, 'sea surface height', metric, 'equatorial_pacific_LatExt2', + file_area=sshareafileobs, name_area=sshareanameobs, file_mask=sshlandmaskfileobs, + name_mask=sshlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + map_mod, _, keyerror_mod = PreProcessTS( + map_mod, '', areacell=mod_areacell, average='time', compute_anom=False, + region="equatorial_pacific_LatExt2", **kwargs) + map_obs, _, keyerror_obs = PreProcessTS( + map_obs, '', areacell=obs_areacell, average='time', compute_anom=False, + region="equatorial_pacific_LatExt2", **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + # Regridding + if isinstance(kwargs['regridding'], dict): + map_mod, map_obs, _ = TwoVarRegrid( + map_mod, map_obs, '', region='equatorial_pacific_LatExt2', **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in map_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in map_obs.getAxisList()]), + 'shape1': '(mod) ' + str(map_mod.shape), + 'shape2': '(obs) ' + str(map_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after TwoVarRegrid: netcdf', 15, **dict_debug) + # change units + map_mod = map_mod * 1e2 + map_obs = map_obs * 1e2 + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod)} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs)} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod)} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs)} + dict5 = {'metric_name': Name, 'metric_value_' + dataset2: sshRmse, + 'metric_value_error_' + dataset2: sshRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=ssh_mod, var1_attributes=dict1, var1_name='ssh_lat__' + dataset1, + var2=ssh_obs, var2_attributes=dict2, var2_name='ssh_lat__' + dataset2, var3=map_mod, + var3_attributes=dict3, var3_name='ssh_map__' + dataset1, var4=map_obs, + var4_attributes=dict4, var4_name='ssh_map__' + dataset2, global_attributes=dict5) + del dict1, dict2, dict3, dict4, dict5 + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(sshRmse), 'line2': 'metric value_error: ' + str(sshRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + LatRmseMetric = { + 'name': Name, 'value': sshRmse, 'value_error': sshRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return LatRmseMetric + + +def BiasSshLonRmse(sshfilemod, sshnamemod, sshareafilemod, sshareanamemod, sshlandmaskfilemod, + sshlandmasknamemod, sshfileobs, sshnameobs, sshareafileobs, sshareanameobs, sshlandmaskfileobs, + sshlandmasknameobs, box, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, + netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The BiasSshLonRmse() function computes the SSH zonal (longitude) root mean square error (RMSE) in a 'box' + (usually the Equatorial Pacific) + + Inputs: + ------ + :param sshfilemod: string + path_to/filename of the file (NetCDF) of the modeled SSH + :param sshnamemod: string + name of SSH variable (ssh, sshg, zos) in 'sshfilemod' + :param sshareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for SSH + :param sshareanamemod: string + name of areacell variable (areacella, areacello) in 'sshareafilemod' + :param sshlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for SSH + :param sshlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'sshlandmaskfilemod' + :param sshfileobs: string + path_to/filename of the file (NetCDF) of the observed SSH + :param sshnameobs: string + name of SSH variable (ssh, sshg, zos) in 'sshfileobs' + :param sshareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for SSH + :param sshareanameobs: string + name of areacell variable (areacella, areacello) in 'sshareafileobs' + :param sshlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for SSH + :param sshlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'sshlandmaskfileobs' + :param box: string + name of box ('equatorial_pacific') for SSH + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'AVISO',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param regridding: dict, optional + see EnsoUvcdatToolsLib.TwoVarRegrid and EnsoUvcdatToolsLib.Regrid for options + the aim if to specify if the model is regridded toward the observations or vice versa, of if both model and + observations are regridded toward another grid + interpolation tool and method can be specified + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SSH file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SSH file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return LonRmseMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_model, + time_period_observations, ref, keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + Notes: + ----- + TODO: add error calculation to rmse (function of nyears) + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'regridding', 'smoothing', + 'time_bounds_mod', 'time_bounds_obs'] + for arg in needed_kwarg: + try: kwargs[arg] + except: kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'ssh Zonal RMSE' + Units = 'cm' + Method = 'Zonal root mean square error of ' + box + ' ssh' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = "BiasSshLonRmse" + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + ssh_mod, mod_areacell, keyerror_mod = \ + Read_data_mask_area(sshfilemod, sshnamemod, 'sea surface height', metric, box, file_area=sshareafilemod, + name_area=sshareanamemod, file_mask=sshlandmaskfilemod, name_mask=sshlandmasknamemod, + maskland=True, maskocean=False, time_bounds=kwargs['time_bounds_mod'], debug=debug, + **kwargs) + ssh_obs, obs_areacell, keyerror_obs = \ + Read_data_mask_area(sshfileobs, sshnameobs, 'sea surface height', metric, box, file_area=sshareafileobs, + name_area=sshareanameobs, file_mask=sshlandmaskfileobs, name_mask=sshlandmasknameobs, + maskland=True, maskocean=False, time_bounds=kwargs['time_bounds_obs'], debug=debug, + **kwargs) + + # Number of years + yearN_mod = int(round(ssh_mod.shape[0] / 12)) + yearN_obs = int(round(ssh_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(ssh_mod) + actualtimebounds_obs = TimeBounds(ssh_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + sshRmse, sshRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + # here only the detrending (if applicable) and time averaging are performed + ssh_mod, Method, keyerror_mod = PreProcessTS( + ssh_mod, Method, areacell=mod_areacell, average='time', compute_anom=False, region=box, **kwargs) + ssh_obs, _, keyerror_obs = PreProcessTS( + ssh_obs, '', areacell=obs_areacell, average='time', compute_anom=False, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + sshRmse, sshRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ssh_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ssh_obs.getAxisList()]), + 'shape1': '(mod) ' + str(ssh_mod.shape), 'shape2': '(obs) ' + str(ssh_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + ssh_mod, ssh_obs, Method = TwoVarRegrid(ssh_mod, ssh_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ssh_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ssh_obs.getAxisList()]), + 'shape1': '(mod) ' + str(ssh_mod.shape), 'shape2': '(obs) ' + str(ssh_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Meridional average + ssh_mod, keyerror_mod = AverageMeridional(ssh_mod) + ssh_obs, keyerror_obs = AverageMeridional(ssh_obs) + if keyerror_mod is not None or keyerror_obs is not None: + sshRmse, sshRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + ssh_mod = ssh_mod * 1e2 + ssh_obs = ssh_obs * 1e2 + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ssh_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ssh_obs.getAxisList()]), + 'shape1': '(mod) ' + str(ssh_mod.shape), 'shape2': '(obs) ' + str(ssh_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + + # Computes the root mean square difference + sshRmse, keyerror = RmsZonal(ssh_mod, ssh_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + sshRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(ssh_mod), 'observations': ArrayToList(ssh_obs), + 'axis': list(ssh_mod.getAxis(0)[:])} + if netcdf is True: + map_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sshfilemod, sshnamemod, 'sea surface height', metric, 'equatorial_pacific_LatExt2', + file_area=sshareafilemod, name_area=sshareanamemod, file_mask=sshlandmaskfilemod, + name_mask=sshlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + map_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sshfileobs, sshnameobs, 'sea surface height', metric, 'equatorial_pacific_LatExt2', + file_area=sshareafileobs, name_area=sshareanameobs, file_mask=sshlandmaskfileobs, + name_mask=sshlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + map_mod, _, keyerror_mod = PreProcessTS( + map_mod, '', areacell=mod_areacell, average='time', compute_anom=False, + region="equatorial_pacific_LatExt2", **kwargs) + map_obs, _, keyerror_obs = PreProcessTS( + map_obs, '', areacell=obs_areacell, average='time', compute_anom=False, + region="equatorial_pacific_LatExt2", **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Regridding + if isinstance(kwargs['regridding'], dict): + map_mod, map_obs, _ = TwoVarRegrid( + map_mod, map_obs, '', region='equatorial_pacific_LatExt2', **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in map_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in map_obs.getAxisList()]), + 'shape1': '(mod) ' + str(map_mod.shape), + 'shape2': '(obs) ' + str(map_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after TwoVarRegrid: netcdf', 15, **dict_debug) + # change units + map_mod = map_mod * 1e2 + map_obs = map_obs * 1e2 + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod)} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs)} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod)} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs)} + dict5 = {'metric_name': Name, 'metric_value_' + dataset2: sshRmse, + 'metric_value_error_' + dataset2: sshRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=ssh_mod, var1_attributes=dict1, var1_name='ssh_lon__' + dataset1, + var2=ssh_obs, var2_attributes=dict2, var2_name='ssh_lon__' + dataset2, var3=map_mod, + var3_attributes=dict3, var3_name='ssh_map__' + dataset1, var4=map_obs, + var4_attributes=dict4, var4_name='ssh_map__' + dataset2, global_attributes=dict5) + del dict1, dict2, dict3, dict4, dict5 + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(sshRmse), 'line2': 'metric value_error: ' + str(sshRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + LonRmseMetric = { + 'name': Name, 'value': sshRmse, 'value_error': sshRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return LonRmseMetric + + +def BiasSstRmse(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, box, + centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, netcdf_name='', + metname='', **kwargs): + """ + The BiasSstRmse() function computes the SST spatial root mean square error (RMSE) in a 'box' (usually the tropical + Pacific) + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for SST + :param sstareanamemod: string + name of areacell variable (areacella, areacello) in 'sstareafilemod' + :param sstlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for SST + :param sstlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for SST + :param sstareanameobs: string + name of areacell variable (areacella, areacello) in 'sstareafileobs' + :param sstlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for SST + :param sstlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfileobs' + :param box: string + name of box ('tropical_pacific') for SST + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If you want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param regridding: dict, optional + see EnsoUvcdatToolsLib.TwoVarRegrid and EnsoUvcdatToolsLib.Regrid for options + the aim if to specify if the model is regridded toward the observations or vice versa, of if both model and + observations are regridded toward another grid + interpolation tool and method can be specified + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return rmseMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_model, + time_period_observations, ref, keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + Notes: + ----- + TODO: add error calculation to rmse (function of nyears) + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'regridding', 'smoothing', + 'time_bounds_mod', 'time_bounds_obs'] + for arg in needed_kwarg: + try: kwargs[arg] + except: kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'sst RMSE' + Units = 'C' + Method = 'Spatial root mean square error of ' + box + ' sst' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = "BiasSstRmse" + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod = \ + Read_data_mask_area(sstfilemod, sstnamemod, 'temperature', metric, box, file_area=sstareafilemod, + name_area=sstareanamemod, file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, + maskland=True, maskocean=False, time_bounds=kwargs['time_bounds_mod'], debug=debug, + **kwargs) + sst_obs, obs_areacell, keyerror_obs = \ + Read_data_mask_area(sstfileobs, sstnameobs, 'temperature', metric, box, file_area=sstareafileobs, + name_area=sstareanameobs, file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, + maskland=True, maskocean=False, time_bounds=kwargs['time_bounds_obs'], debug=debug, + **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + sstRmse, sstRmseErr = None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + # here only the detrending (if applicable) and time averaging are performed + sst_mod, Method, keyerror_mod = PreProcessTS( + sst_mod, Method, areacell=mod_areacell, average='time', compute_anom=False, region=box, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average='time', compute_anom=False, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + sstRmse, sstRmseErr = None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + sst_mod, sst_obs, Method = TwoVarRegrid(sst_mod, sst_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Computes the root mean square difference + sstRmse, keyerror = RmsHorizontal(sst_mod, sst_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + sstRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + + if netcdf is True: + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, 'time_period': str(actualtimebounds_mod)} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, 'time_period': str(actualtimebounds_obs)} + dict3 = {'metric_name': Name, 'metric_value_' + dataset2: sstRmse, + 'metric_value_error_' + dataset2: sstRmseErr, 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=sst_mod, var1_attributes=dict1, var1_name='sst_map__' + dataset1, var2=sst_obs, + var2_attributes=dict2, var2_name='sst_map__' + dataset2, global_attributes=dict3) + del dict1, dict2, dict3 + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(sstRmse), 'line2': 'metric value_error: ' + str(sstRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + rmseMetric = { + 'name': Name, 'value': sstRmse, 'value_error': sstRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return rmseMetric + + +def BiasSstLatRmse(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, box, + centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, netcdf_name='', + metname='', **kwargs): + """ + The BiasSstLatRmse() function computes the SST meridional (latitude) root mean square error (RMSE) in a 'box' + (usually 'nino3_LatExt') + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for SST + :param sstareanamemod: string + name of areacell variable (areacella, areacello) in 'sstareafilemod' + :param sstlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for SST + :param sstlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for SST + :param sstareanameobs: string + name of areacell variable (areacella, areacello) in 'sstareafileobs' + :param sstlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for SST + :param sstlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfileobs' + :param box: string + name of box ('nino3_LatExt') for SST + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param regridding: dict, optional + see EnsoUvcdatToolsLib.TwoVarRegrid and EnsoUvcdatToolsLib.Regrid for options + the aim if to specify if the model is regridded toward the observations or vice versa, of if both model and + observations are regridded toward another grid + interpolation tool and method can be specified + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return LatRmseMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_model, + time_period_observations, ref, keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + Notes: + ----- + TODO: add error calculation to rmse (function of nyears) + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'regridding', 'smoothing', + 'time_bounds_mod', 'time_bounds_obs'] + for arg in needed_kwarg: + try: kwargs[arg] + except: kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'sst Meridional RMSE' + Units = 'C' + Method = 'Meridional root mean square error of ' + box + ' sst' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = "BiasSstLatRmse" + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod = \ + Read_data_mask_area(sstfilemod, sstnamemod, 'temperature', metric, box, file_area=sstareafilemod, + name_area=sstareanamemod, file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, + maskland=True, maskocean=False, time_bounds=kwargs['time_bounds_mod'], debug=debug, + **kwargs) + sst_obs, obs_areacell, keyerror_obs = \ + Read_data_mask_area(sstfileobs, sstnameobs, 'temperature', metric, box, file_area=sstareafileobs, + name_area=sstareanameobs, file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, + maskland=True, maskocean=False, time_bounds=kwargs['time_bounds_obs'], debug=debug, + **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + sstRmse, sstRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + # here only the detrending (if applicable) and time averaging are performed + sst_mod, Method, keyerror_mod = PreProcessTS( + sst_mod, Method, areacell=mod_areacell, average='time', compute_anom=False, region=box, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average='time', compute_anom=False, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + sstRmse, sstRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + sst_mod, sst_obs, Method = TwoVarRegrid(sst_mod, sst_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Zonal average + sst_mod, keyerror_mod = AverageZonal(sst_mod) + sst_obs, keyerror_obs = AverageZonal(sst_obs) + if keyerror_mod is not None or keyerror_obs is not None: + sstRmse, sstRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageZonal', 15, **dict_debug) + + # Computes the root mean square difference + sstRmse, keyerror = RmsMeridional(sst_mod, sst_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + sstRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(sst_mod), 'observations': ArrayToList(sst_obs), + 'axis': list(sst_mod.getAxis(0)[:])} + if netcdf is True: + map_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, 'equatorial_pacific_LatExt2', + file_area=sstareafilemod, name_area=sstareanamemod, file_mask=sstlandmaskfilemod, + name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + map_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, 'equatorial_pacific_LatExt2', + file_area=sstareafileobs, name_area=sstareanameobs, file_mask=sstlandmaskfileobs, + name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + map_mod, _, keyerror_mod = PreProcessTS( + map_mod, '', areacell=mod_areacell, average='time', compute_anom=False, + region="equatorial_pacific_LatExt2", **kwargs) + map_obs, _, keyerror_obs = PreProcessTS( + map_obs, '', areacell=obs_areacell, average='time', compute_anom=False, + region="equatorial_pacific_LatExt2", **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Regridding + if isinstance(kwargs['regridding'], dict): + map_mod, map_obs, _ = TwoVarRegrid( + map_mod, map_obs, '', region='equatorial_pacific_LatExt2', **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in map_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in map_obs.getAxisList()]), + 'shape1': '(mod) ' + str(map_mod.shape), + 'shape2': '(obs) ' + str(map_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after TwoVarRegrid: netcdf', 15, **dict_debug) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod)} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs)} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod)} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs)} + dict5 = {'metric_name': Name, 'metric_value_' + dataset2: sstRmse, + 'metric_value_error_' + dataset2: sstRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=sst_mod, var1_attributes=dict1, var1_name='sst_lat__' + dataset1, + var2=sst_obs, var2_attributes=dict2, var2_name='sst_lat__' + dataset2, var3=map_mod, + var3_attributes=dict3, var3_name='sst_map__' + dataset1, var4=map_obs, + var4_attributes=dict4, var4_name='sst_map__' + dataset2, global_attributes=dict5) + del dict1, dict2, dict3, dict4, dict5 + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(sstRmse), 'line2': 'metric value_error: ' + str(sstRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + LatRmseMetric = { + 'name': Name, 'value': sstRmse, 'value_error': sstRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return LatRmseMetric + + +def BiasSstLonRmse(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, + sstlandmasknamemod, sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, + sstlandmasknameobs, box, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, + netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The BiasSstLonRmse() function computes the SST zonal (longitude) root mean square error (RMSE) in a 'box' + (usually the Equatorial Pacific) + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for SST + :param sstareanamemod: string + name of areacell variable (areacella, areacello) in 'sstareafilemod' + :param sstlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for SST + :param sstlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for SST + :param sstareanameobs: string + name of areacell variable (areacella, areacello) in 'sstareafileobs' + :param sstlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for SST + :param sstlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfileobs' + :param box: string + name of box ('equatorial_pacific') for SST + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param regridding: dict, optional + see EnsoUvcdatToolsLib.TwoVarRegrid and EnsoUvcdatToolsLib.Regrid for options + the aim if to specify if the model is regridded toward the observations or vice versa, of if both model and + observations are regridded toward another grid + interpolation tool and method can be specified + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return LonRmseMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_model, + time_period_observations, ref, keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + Notes: + ----- + TODO: add error calculation to rmse (function of nyears) + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'regridding', 'smoothing', + 'time_bounds_mod', 'time_bounds_obs'] + for arg in needed_kwarg: + try: kwargs[arg] + except: kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'sst Zonal RMSE' + Units = 'C' + Method = 'Zonal root mean square error of ' + box + ' sst' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = "BiasSstLonRmse" + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod = \ + Read_data_mask_area(sstfilemod, sstnamemod, 'temperature', metric, box, file_area=sstareafilemod, + name_area=sstareanamemod, file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, + maskland=True, maskocean=False, time_bounds=kwargs['time_bounds_mod'], debug=debug, + **kwargs) + sst_obs, obs_areacell, keyerror_obs = \ + Read_data_mask_area(sstfileobs, sstnameobs, 'temperature', metric, box, file_area=sstareafileobs, + name_area=sstareanameobs, file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, + maskland=True, maskocean=False, time_bounds=kwargs['time_bounds_obs'], debug=debug, + **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + sstRmse, sstRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + # here only the detrending (if applicable) and time averaging are performed + sst_mod, Method, keyerror_mod = PreProcessTS( + sst_mod, Method, areacell=mod_areacell, average='time', compute_anom=False, region=box, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average='time', compute_anom=False, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + sstRmse, sstRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + sst_mod, sst_obs, Method = TwoVarRegrid(sst_mod, sst_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Meridional average + sst_mod, keyerror_mod = AverageMeridional(sst_mod) + sst_obs, keyerror_obs = AverageMeridional(sst_obs) + if keyerror_mod is not None or keyerror_obs is not None: + sstRmse, sstRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + tmp = [keyerror_mod, keyerror_obs] + keyerror = add_up_errors(tmp) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + + # Computes the root mean square difference + sstRmse, keyerror = RmsZonal(sst_mod, sst_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + sstRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(sst_mod), 'observations': ArrayToList(sst_obs), + 'axis': list(sst_mod.getAxis(0)[:])} + if netcdf is True: + map_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, 'equatorial_pacific_LatExt2', + file_area=sstareafilemod, name_area=sstareanamemod, file_mask=sstlandmaskfilemod, + name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + map_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, 'equatorial_pacific_LatExt2', + file_area=sstareafileobs, name_area=sstareanameobs, file_mask=sstlandmaskfileobs, + name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + map_mod, _, keyerror_mod = PreProcessTS( + map_mod, '', areacell=mod_areacell, average='time', compute_anom=False, + region="equatorial_pacific_LatExt2", **kwargs) + map_obs, _, keyerror_obs = PreProcessTS( + map_obs, '', areacell=obs_areacell, average='time', compute_anom=False, + region="equatorial_pacific_LatExt2", **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Regridding + if isinstance(kwargs['regridding'], dict): + map_mod, map_obs, _ = TwoVarRegrid( + map_mod, map_obs, '', region='equatorial_pacific_LatExt2', **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in map_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in map_obs.getAxisList()]), + 'shape1': '(mod) ' + str(map_mod.shape), + 'shape2': '(obs) ' + str(map_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after TwoVarRegrid: netcdf', 15, **dict_debug) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod)} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs)} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod)} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs)} + dict5 = {'metric_name': Name, 'metric_value_' + dataset2: sstRmse, + 'metric_value_error_' + dataset2: sstRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=sst_mod, var1_attributes=dict1, var1_name='sst_lon__' + dataset1, + var2=sst_obs, var2_attributes=dict2, var2_name='sst_lon__' + dataset2, var3=map_mod, + var3_attributes=dict3, var3_name='sst_map__' + dataset1, var4=map_obs, + var4_attributes=dict4, var4_name='sst_map__' + dataset2, global_attributes=dict5) + del dict1, dict2, dict3, dict4, dict5 + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(sstRmse), 'line2': 'metric value_error: ' + str(sstRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + LonRmseMetric = { + 'name': Name, 'value': sstRmse, 'value_error': sstRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return LonRmseMetric + + +def BiasSstSkLonRmse(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, + box, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, + netcdf_name='', metname='', **kwargs): + """ + The BiasSstSkLonRmse() function computes the SST zonal (longitude) skewness and then its root mean square error + (RMSE) in a 'box' (usually the Equatorial Pacific) + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for SST + :param sstareanamemod: string + name of areacell variable (areacella, areacello) in 'sstareafilemod' + :param sstlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for SST + :param sstlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for SST + :param sstareanameobs: string + name of areacell variable (areacella, areacello) in 'sstareafileobs' + :param sstlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for SST + :param sstlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfileobs' + :param box: string + name of box ('equatorial_pacific') for SST + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param regridding: dict, optional + see EnsoUvcdatToolsLib.TwoVarRegrid and EnsoUvcdatToolsLib.Regrid for options + the aim if to specify if the model is regridded toward the observations or vice versa, of if both model and + observations are regridded toward another grid + interpolation tool and method can be specified + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return LonRmseMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_model, + time_period_observations, ref, keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + Notes: + ----- + TODO: add error calculation to rmse (function of nyears) + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'regridding', 'smoothing', + 'time_bounds_mod', 'time_bounds_obs'] + for arg in needed_kwarg: + try: kwargs[arg] + except: kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'sstA Skewness Zonal RMSE' + if kwargs['normalization']: + Units = '' + else: + Units = 'C' + Method = 'Zonal root mean square error of ' + box + ' sstA skewness' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = "BiasSstSkLonRmse" + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod = \ + Read_data_mask_area(sstfilemod, sstnamemod, 'temperature', metric, box, file_area=sstareafilemod, + name_area=sstareanamemod, file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, + maskland=True, maskocean=False, time_bounds=kwargs['time_bounds_mod'], debug=debug, + **kwargs) + sst_obs, obs_areacell, keyerror_obs = \ + Read_data_mask_area(sstfileobs, sstnameobs, 'temperature', metric, box, file_area=sstareafileobs, + name_area=sstareanameobs, file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, + maskland=True, maskocean=False, time_bounds=kwargs['time_bounds_obs'], debug=debug, + **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + skeRmse, skeRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axi': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + # here only the detrending (if applicable) and time averaging are performed + sst_mod, Method, keyerror_mod = PreProcessTS( + sst_mod, Method, areacell=mod_areacell, average=False, compute_anom=True, region=box, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average=False, compute_anom=True, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + skeRmse, skeRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axi': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + sst_mod, sst_obs, Method = TwoVarRegrid(sst_mod, sst_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Meridional average + sst_mod, keyerror_mod = AverageMeridional(sst_mod) + sst_obs, keyerror_obs = AverageMeridional(sst_obs) + if keyerror_mod is not None or keyerror_obs is not None: + skeRmse, skeRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axi': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + + # skewness + sst_mod = SkewnessTemporal(sst_mod) + sst_obs = SkewnessTemporal(sst_obs) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SkewnessTemporal', 15, **dict_debug) + + # Computes the root mean square difference + skeRmse, keyerror = RmsZonal(sst_mod, sst_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + skeRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(sst_mod), 'observations': ArrayToList(sst_obs), + 'axis': list(sst_mod.getAxis(0)[:])} + if netcdf is True: + map_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, 'equatorial_pacific_LatExt2', + file_area=sstareafilemod, name_area=sstareanamemod, file_mask=sstlandmaskfilemod, + name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + map_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, 'equatorial_pacific_LatExt2', + file_area=sstareafileobs, name_area=sstareanameobs, file_mask=sstlandmaskfileobs, + name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + map_mod, _, keyerror_mod = PreProcessTS( + map_mod, '', areacell=mod_areacell, average=False, compute_anom=True, + region="equatorial_pacific_LatExt2", **kwargs) + map_obs, _, keyerror_obs = PreProcessTS( + map_obs, '', areacell=obs_areacell, average=False, compute_anom=True, + region="equatorial_pacific_LatExt2", **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in map_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in map_obs.getAxisList()]), + 'line1': '(mod) minmax' + str(MinMax(map_mod)), + 'line2': '(obs) minmax' + str(MinMax(map_obs)), + 'shape1': '(mod) ' + str(map_mod.shape), + 'shape2': '(obs) ' + str(map_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after PreProcessTS: netcdf', 15, **dict_debug) + # skewness + ske_map_mod = SkewnessTemporal(map_mod) + ske_map_obs = SkewnessTemporal(map_obs) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ske_map_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ske_map_obs.getAxisList()]), + 'line1': '(mod) minmax' + str(MinMax(ske_map_mod)), + 'line2': '(obs) minmax' + str(MinMax(ske_map_obs)), + 'shape1': '(mod) ' + str(ske_map_mod.shape), + 'shape2': '(obs) ' + str(ske_map_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after SkewnessTemporal: netcdf', 15, **dict_debug) + # Regridding + if isinstance(kwargs['regridding'], dict): + ske_map_mod, ske_map_obs, _ = TwoVarRegrid( + ske_map_mod, ske_map_obs, '', region='equatorial_pacific_LatExt2', + **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ske_map_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ske_map_obs.getAxisList()]), + 'line1': '(mod) minmax' + str(MinMax(ske_map_mod)), + 'line2': '(obs) minmax' + str(MinMax(ske_map_obs)), + 'shape1': '(mod) ' + str(ske_map_mod.shape), + 'shape2': '(obs) ' + str(ske_map_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after TwoVarRegrid: netcdf', 15, **dict_debug) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod)} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs)} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod)} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs)} + dict5 = {'metric_name': Name, 'metric_value_' + dataset2: skeRmse, + 'metric_value_error_' + dataset2: skeRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=sst_mod, var1_attributes=dict1, var1_name='sstSke_lon__' + dataset1, + var2=sst_obs, var2_attributes=dict2, var2_name='sstSke_lon__' + dataset2, + var3=ske_map_mod, var3_attributes=dict3, var3_name='sstSke_map__' + dataset1, + var4=ske_map_obs, var4_attributes=dict4, var4_name='sstSke_map__' + dataset2, + global_attributes=dict5) + del dict1, dict2, dict3, dict4, dict5 + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(skeRmse), 'line2': 'metric value_error: ' + str(skeRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + LonRmseMetric = { + 'name': Name, 'value': skeRmse, 'value_error': skeRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return LonRmseMetric + + +def BiasTauxRmse(tauxfilemod, tauxnamemod, tauxareafilemod, tauxareanamemod, tauxlandmaskfilemod, tauxlandmasknamemod, + tauxfileobs, tauxnameobs, tauxareafileobs, tauxareanameobs, tauxlandmaskfileobs, tauxlandmasknameobs, + box, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, + netcdf_name='', metname='', **kwargs): + """ + The BiasTauxRmse() function computes the TAUX spatial root mean square error (RMSE) in a 'box' (usually the tropical + Pacific) + + Inputs: + ------ + :param tauxfilemod: string + path_to/filename of the file (NetCDF) of the modeled TAUX + :param tauxnamemod: string + name of TAUX variable (taux, tauu) in 'tauxfilemod' + :param tauxareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for TAUX + :param tauxareanamemod: string + name of areacell variable (areacella, areacello) in 'tauxareafilemod' + :param tauxlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for TAUX + :param tauxlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'tauxlandmaskfilemod' + :param tauxfileobs: string + path_to/filename of the file (NetCDF) of the observed TAUX + :param tauxnameobs: string + name of TAUX variable (taux, tauu) in 'tauxfileobs' + :param tauxareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for TAUX + :param tauxareanameobs: string + name of areacell variable (areacella, areacello) in 'tauxareafileobs' + :param tauxlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for TAUX + :param tauxlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'tauxlandmaskfileobs' + :param box: string + name of box ('tropical_pacific') for TAUX + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If you want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param regridding: dict, optional + see EnsoUvcdatToolsLib.TwoVarRegrid and EnsoUvcdatToolsLib.Regrid for options + the aim if to specify if the model is regridded toward the observations or vice versa, of if both model and + observations are regridded toward another grid + interpolation tool and method can be specified + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled TAUX file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed TAUX file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return rmseMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_model, + time_period_observations, ref, keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + Notes: + ----- + TODO: add error calculation to rmse (function of nyears) + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'regridding', 'smoothing', + 'time_bounds_mod', 'time_bounds_obs'] + for arg in needed_kwarg: + try: kwargs[arg] + except: kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'taux RMSE' + Units = '1e-3 N/m2' + Method = 'Spatial root mean square error of ' + box + ' taux' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = "BiasTauxRmse" + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + taux_mod, mod_areacell, keyerror_mod = \ + Read_data_mask_area(tauxfilemod, tauxnamemod, 'wind stress', metric, box, file_area=tauxareafilemod, + name_area=tauxareanamemod, file_mask=tauxlandmaskfilemod, name_mask=tauxlandmasknamemod, + maskland=True, maskocean=False, time_bounds=kwargs['time_bounds_mod'], debug=debug, + **kwargs) + taux_obs, obs_areacell, keyerror_obs = \ + Read_data_mask_area(tauxfileobs, tauxnameobs, 'wind stress', metric, box, file_area=tauxareafileobs, + name_area=tauxareanameobs, file_mask=tauxlandmaskfileobs, name_mask=tauxlandmasknameobs, + maskland=True, maskocean=False, time_bounds=kwargs['time_bounds_obs'], debug=debug, + **kwargs) + + # Number of years + yearN_mod = int(round(taux_mod.shape[0] / 12)) + yearN_obs = int(round(taux_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(taux_mod) + actualtimebounds_obs = TimeBounds(taux_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + tauxRmse, tauxRmseErr = None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + # here only the detrending (if applicable) and time averaging are performed + taux_mod, Method, keyerror_mod = PreProcessTS( + taux_mod, Method, areacell=mod_areacell, average='time', compute_anom=False, region=box, **kwargs) + taux_obs, _, keyerror_obs = PreProcessTS( + taux_obs, '', areacell=obs_areacell, average='time', compute_anom=False, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + tauxRmse, tauxRmseErr = None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in taux_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in taux_obs.getAxisList()]), + 'shape1': '(mod) ' + str(taux_mod.shape), 'shape2': '(obs) ' + str(taux_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + taux_mod, taux_obs, Method = TwoVarRegrid( + taux_mod, taux_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in taux_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in taux_obs.getAxisList()]), + 'shape1': '(mod) ' + str(taux_mod.shape), 'shape2': '(obs) ' + str(taux_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # change units + taux_mod = taux_mod * 1e3 + taux_obs = taux_obs * 1e3 + + # Computes the root mean square difference + tauxRmse, keyerror = RmsHorizontal(taux_mod, taux_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + tauxRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + + if netcdf is True: + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, 'time_period': str(actualtimebounds_mod)} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, 'time_period': str(actualtimebounds_obs)} + dict3 = {'metric_name': Name, 'metric_value_' + dataset2: tauxRmse, + 'metric_value_error_' + dataset2: tauxRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=taux_mod, var1_attributes=dict1, var1_name='taux_map__' + dataset1, var2=taux_obs, + var2_attributes=dict2, var2_name='taux_map__' + dataset2, global_attributes=dict3) + del dict1, dict2, dict3 + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(tauxRmse), 'line2': 'metric value_error: ' + str(tauxRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + rmseMetric = { + 'name': Name, 'value': tauxRmse, 'value_error': tauxRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return rmseMetric + + +def BiasTauxLatRmse(tauxfilemod, tauxnamemod, tauxareafilemod, tauxareanamemod, tauxlandmaskfilemod, + tauxlandmasknamemod, tauxfileobs, tauxnameobs, tauxareafileobs, tauxareanameobs, + tauxlandmaskfileobs, tauxlandmasknameobs, box, centered_rmse=0, biased_rmse=1, dataset1='', + dataset2='', debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The BiasTauxLatRmse() function computes the TAUX meridional (latitude) root mean square error (RMSE) in a 'box' + (usually 'equatorial_pacific_LatExt') + + Inputs: + ------ + :param tauxfilemod: string + path_to/filename of the file (NetCDF) of the modeled TAUX + :param tauxnamemod: string + name of TAUX variable (taux, tauu) in 'tauxfilemod' + :param tauxareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for TAUX + :param tauxareanamemod: string + name of areacell variable (areacella, areacello) in 'tauxareafilemod' + :param tauxlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for TAUX + :param tauxlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'tauxlandmaskfilemod' + :param tauxfileobs: string + path_to/filename of the file (NetCDF) of the observed TAUX + :param tauxnameobs: string + name of TAUX variable (taux, tauu) in 'tauxfileobs' + :param tauxareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for TAUX + :param tauxareanameobs: string + name of areacell variable (areacella, areacello) in 'tauxareafileobs' + :param tauxlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for TAUX + :param tauxlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'tauxlandmaskfileobs' + :param box: string + name of box ('equatorial_pacific_LatExt') for TAUX + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param regridding: dict, optional + see EnsoUvcdatToolsLib.TwoVarRegrid and EnsoUvcdatToolsLib.Regrid for options + the aim if to specify if the model is regridded toward the observations or vice versa, of if both model and + observations are regridded toward another grid + interpolation tool and method can be specified + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled TAUX file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed TAUX file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return LatRmseMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_model, + time_period_observations, ref, keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + Notes: + ----- + TODO: add error calculation to rmse (function of nyears) + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'regridding', 'smoothing', + 'time_bounds_mod', 'time_bounds_obs'] + for arg in needed_kwarg: + try: kwargs[arg] + except: kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'taux Meridional RMSE' + Units = '1e-3 N/m2' + Method = 'Meridional root mean square error of ' + box + ' taux' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = "BiasTauxLatRmse" + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + taux_mod, mod_areacell, keyerror_mod = \ + Read_data_mask_area(tauxfilemod, tauxnamemod, 'wind stress', metric, box, file_area=tauxareafilemod, + name_area=tauxareanamemod, file_mask=tauxlandmaskfilemod, name_mask=tauxlandmasknamemod, + maskland=True, maskocean=False, time_bounds=kwargs['time_bounds_mod'], debug=debug, + **kwargs) + taux_obs, obs_areacell, keyerror_obs = \ + Read_data_mask_area(tauxfileobs, tauxnameobs, 'wind stress', metric, box, file_area=tauxareafileobs, + name_area=tauxareanameobs, file_mask=tauxlandmaskfileobs, name_mask=tauxlandmasknameobs, + maskland=True, maskocean=False, time_bounds=kwargs['time_bounds_obs'], debug=debug, + **kwargs) + + # Number of years + yearN_mod = int(round(taux_mod.shape[0] / 12)) + yearN_obs = int(round(taux_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(taux_mod) + actualtimebounds_obs = TimeBounds(taux_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + tauxRmse, tauxRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + # here only the detrending (if applicable) and time averaging are performed + taux_mod, Method, keyerror_mod = PreProcessTS( + taux_mod, Method, areacell=mod_areacell, average='time', compute_anom=False, region=box, **kwargs) + taux_obs, _, keyerror_obs = PreProcessTS( + taux_obs, '', areacell=obs_areacell, average='time', compute_anom=False, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + tauxRmse, tauxRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in taux_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in taux_obs.getAxisList()]), + 'shape1': '(mod) ' + str(taux_mod.shape), 'shape2': '(obs) ' + str(taux_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + taux_mod, taux_obs, Method = TwoVarRegrid( + taux_mod, taux_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in taux_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in taux_obs.getAxisList()]), + 'shape1': '(mod) ' + str(taux_mod.shape), 'shape2': '(obs) ' + str(taux_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Zonal average + taux_mod, keyerror_mod = AverageZonal(taux_mod) + taux_obs, keyerror_obs = AverageZonal(taux_obs) + if keyerror_mod is not None or keyerror_obs is not None: + tauxRmse, tauxRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + taux_mod = taux_mod * 1e3 + taux_obs = taux_obs * 1e3 + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in taux_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in taux_obs.getAxisList()]), + 'shape1': '(mod) ' + str(taux_mod.shape), 'shape2': '(obs) ' + str(taux_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageZonal', 15, **dict_debug) + + # Computes the root mean square difference + tauxRmse, keyerror = RmsMeridional(taux_mod, taux_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + tauxRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(taux_mod), 'observations': ArrayToList(taux_obs), + 'axis': list(taux_mod.getAxis(0)[:])} + if netcdf is True: + map_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + tauxfilemod, tauxnamemod, 'wind stress', metric, 'equatorial_pacific_LatExt2', + file_area=tauxareafilemod, name_area=tauxareanamemod, file_mask=tauxlandmaskfilemod, + name_mask=tauxlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + map_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + tauxfileobs, tauxnameobs, 'wind stress', metric, 'equatorial_pacific_LatExt2', + file_area=tauxareafileobs, name_area=tauxareanameobs, file_mask=tauxlandmaskfileobs, + name_mask=tauxlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + map_mod, _, keyerror_mod = PreProcessTS( + map_mod, '', areacell=mod_areacell, average='time', compute_anom=False, + region="equatorial_pacific_LatExt2", **kwargs) + map_obs, _, keyerror_obs = PreProcessTS( + map_obs, '', areacell=obs_areacell, average='time', compute_anom=False, + region="equatorial_pacific_LatExt2", **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Regridding + if isinstance(kwargs['regridding'], dict): + map_mod, map_obs, _ = TwoVarRegrid( + map_mod, map_obs, '', region='equatorial_pacific_LatExt2', **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in map_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in map_obs.getAxisList()]), + 'shape1': '(mod) ' + str(map_mod.shape), + 'shape2': '(obs) ' + str(map_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after TwoVarRegrid: netcdf', 15, **dict_debug) + # change units + map_mod = map_mod * 1e3 + map_obs = map_obs * 1e3 + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod)} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs)} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod)} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs)} + dict5 = {'metric_name': Name, 'metric_value_' + dataset2: tauxRmse, + 'metric_value_error_' + dataset2: tauxRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=taux_mod, var1_attributes=dict1, var1_name='taux_lat__' + dataset1, + var2=taux_obs, var2_attributes=dict2, var2_name='taux_lat__' + dataset2, var3=map_mod, + var3_attributes=dict3, var3_name='taux_map__' + dataset1, var4=map_obs, + var4_attributes=dict4, var4_name='taux_map__' + dataset2, global_attributes=dict5) + del dict1, dict2, dict3, dict4, dict5 + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(tauxRmse), 'line2': 'metric value_error: ' + str(tauxRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + LatRmseMetric = { + 'name': Name, 'value': tauxRmse, 'value_error': tauxRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return LatRmseMetric + + +def BiasTauxLonRmse(tauxfilemod, tauxnamemod, tauxareafilemod, tauxareanamemod, tauxlandmaskfilemod, + tauxlandmasknamemod, tauxfileobs, tauxnameobs, tauxareafileobs, tauxareanameobs, + tauxlandmaskfileobs, tauxlandmasknameobs, box, centered_rmse=0, biased_rmse=1, dataset1='', + dataset2='', debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The BiasTauxLonRmse() function computes the TAUX zonal (longitude) root mean square error (RMSE) in a 'box' + (usually the Equatorial Pacific) + + Inputs: + ------ + :param tauxfilemod: string + path_to/filename of the file (NetCDF) of the modeled TAUX + :param tauxnamemod: string + name of TAUX variable (taux, tauu) in 'tauxfilemod' + :param tauxareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for TAUX + :param tauxareanamemod: string + name of areacell variable (areacella, areacello) in 'tauxareafilemod' + :param tauxlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for TAUX + :param tauxlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'tauxlandmaskfilemod' + :param tauxfileobs: string + path_to/filename of the file (NetCDF) of the observed TAUX + :param tauxnameobs: string + name of TAUX variable (taux, tauu) in 'tauxfileobs' + :param tauxareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for TAUX + :param tauxareanameobs: string + name of areacell variable (areacella, areacello) in 'tauxareafileobs' + :param tauxlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for TAUX + :param tauxlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'tauxlandmaskfileobs' + :param box: string + name of box ('equatorial_pacific') for TAUX + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param regridding: dict, optional + see EnsoUvcdatToolsLib.TwoVarRegrid and EnsoUvcdatToolsLib.Regrid for options + the aim if to specify if the model is regridded toward the observations or vice versa, of if both model and + observations are regridded toward another grid + interpolation tool and method can be specified + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled TAUX file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed TAUX file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return LonRmseMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_model, + time_period_observations, ref, keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + Notes: + ----- + TODO: add error calculation to rmse (function of nyears) + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'regridding', 'smoothing', + 'time_bounds_mod', 'time_bounds_obs'] + for arg in needed_kwarg: + try: kwargs[arg] + except: kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'taux Zonal RMSE' + Units = '1e-3 N/m2' + Method = 'Zonal root mean square error of ' + box + ' taux' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = "BiasTauxLonRmse" + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + taux_mod, mod_areacell, keyerror_mod = \ + Read_data_mask_area(tauxfilemod, tauxnamemod, 'wind stress', metric, box, file_area=tauxareafilemod, + name_area=tauxareanamemod, file_mask=tauxlandmaskfilemod, name_mask=tauxlandmasknamemod, + maskland=True, maskocean=False, time_bounds=kwargs['time_bounds_mod'], debug=debug, + **kwargs) + taux_obs, obs_areacell, keyerror_obs = \ + Read_data_mask_area(tauxfileobs, tauxnameobs, 'wind stress', metric, box, file_area=tauxareafileobs, + name_area=tauxareanameobs, file_mask=tauxlandmaskfileobs, name_mask=tauxlandmasknameobs, + maskland=True, maskocean=False, time_bounds=kwargs['time_bounds_obs'], debug=debug, + **kwargs) + + # Number of years + yearN_mod = int(round(taux_mod.shape[0] / 12)) + yearN_obs = int(round(taux_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(taux_mod) + actualtimebounds_obs = TimeBounds(taux_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + tauxRmse, tauxRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + # here only the detrending (if applicable) and time averaging are performed + taux_mod, Method, keyerror_mod = PreProcessTS( + taux_mod, Method, areacell=mod_areacell, average='time', compute_anom=False, region=box, **kwargs) + taux_obs, _, keyerror_obs = PreProcessTS( + taux_obs, '', areacell=obs_areacell, average='time', compute_anom=False, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + tauxRmse, tauxRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in taux_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in taux_obs.getAxisList()]), + 'shape1': '(mod) ' + str(taux_mod.shape), 'shape2': '(obs) ' + str(taux_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + taux_mod, taux_obs, Method = TwoVarRegrid( + taux_mod, taux_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in taux_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in taux_obs.getAxisList()]), + 'shape1': '(mod) ' + str(taux_mod.shape), 'shape2': '(obs) ' + str(taux_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Meridional average + taux_mod, keyerror_mod = AverageMeridional(taux_mod) + taux_obs, keyerror_obs = AverageMeridional(taux_obs) + if keyerror_mod is not None or keyerror_obs is not None: + tauxRmse, tauxRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + taux_mod = taux_mod * 1e3 + taux_obs = taux_obs * 1e3 + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in taux_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in taux_obs.getAxisList()]), + 'shape1': '(mod) ' + str(taux_mod.shape), 'shape2': '(obs) ' + str(taux_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + + # Computes the root mean square difference + tauxRmse, keyerror = RmsZonal(taux_mod, taux_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + tauxRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(taux_mod), 'observations': ArrayToList(taux_obs), + 'axis': list(taux_mod.getAxis(0)[:])} + if netcdf is True: + map_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + tauxfilemod, tauxnamemod, 'wind stress', metric, 'equatorial_pacific_LatExt2', + file_area=tauxareafilemod, name_area=tauxareanamemod, file_mask=tauxlandmaskfilemod, + name_mask=tauxlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + map_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + tauxfileobs, tauxnameobs, 'wind stress', metric, 'equatorial_pacific_LatExt2', + file_area=tauxareafileobs, name_area=tauxareanameobs, file_mask=tauxlandmaskfileobs, + name_mask=tauxlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + map_mod, _, keyerror_mod = PreProcessTS( + map_mod, '', areacell=mod_areacell, average='time', compute_anom=False, + region="equatorial_pacific_LatExt2", **kwargs) + map_obs, _, keyerror_obs = PreProcessTS( + map_obs, '', areacell=obs_areacell, average='time', compute_anom=False, + region="equatorial_pacific_LatExt2", **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Regridding + if isinstance(kwargs['regridding'], dict): + map_mod, map_obs, _ = TwoVarRegrid( + map_mod, map_obs, '', region='equatorial_pacific_LatExt2', **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in map_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in map_obs.getAxisList()]), + 'shape1': '(mod) ' + str(map_mod.shape), + 'shape2': '(obs) ' + str(map_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after TwoVarRegrid: netcdf', 15, **dict_debug) + # change units + map_mod = map_mod * 1e3 + map_obs = map_obs * 1e3 + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod)} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs)} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod)} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs)} + dict5 = {'metric_name': Name, 'metric_value_' + dataset2: tauxRmse, + 'metric_value_error_' + dataset2: tauxRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=taux_mod, var1_attributes=dict1, var1_name='taux_lon__' + dataset1, + var2=taux_obs, var2_attributes=dict2, var2_name='taux_lon__' + dataset2, var3=map_mod, + var3_attributes=dict3, var3_name='taux_map__' + dataset1, var4=map_obs, + var4_attributes=dict4, var4_name='taux_map__' + dataset2, global_attributes=dict5) + del dict1, dict2, dict3, dict4, dict5 + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(tauxRmse), 'line2': 'metric value_error: ' + str(tauxRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + LonRmseMetric = { + 'name': Name, 'value': tauxRmse, 'value_error': tauxRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return LonRmseMetric + + +def EnsoFbSstLhf(sstfile, sstname, sstareafile, sstareaname, sstlandmaskfile, sstlandmaskname, sstbox, lhffile, lhfname, + lhfareafile, lhfareaname, lhflandmaskfile, lhflandmaskname, lhfbox, dataset='', debug=False, + netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The EnsoFbSstLhf() function computes the regression of 'lhfbox' lhfA (latent heat flux anomalies) over 'sstbox' sstA + (usually the regression of nino3 lhfA over nino3 sstA) + + Author: Yann Planton : yann.planton@locean-ipsl.upmc.fr + Co-author: + + Created on Thu Oct 5 2017 + + Inputs: + ------ + :param sstfile: string + path_to/filename of the file (NetCDF) of SST + :param sstname: string + name of SST variable (tos, ts) in 'sstfile' + :param sstareafile: string + path_to/filename of the file (NetCDF) of the areacell for SST + :param sstareaname: string + name of areacell variable (areacella, areacello) in 'sstareafile' + :param sstlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for SST + :param sstlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfile' + :param sstbox: string + name of box (nino3') for SST + :param lhffile: string + path_to/filename of the file (NetCDF) of LHF + :param lhfname: string + name of LHF variable (lhf, hfls) in 'lhffile' + :param lhfareafile: string + path_to/filename of the file (NetCDF) of the areacell for LHF + :param lhfareaname: string + name of areacell variable (areacella, areacello) in 'lhfareafile' + :param lhflandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for LHF + :param lhflandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'lhflandmaskfile' + :param lhfbox: string + name of box (nino3') for LHF + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return alphaLhfMetric: dict + name, value, value_error, units, method, nyears, time_frequency, time_period, ref, nonlinearity, + nonlinearity_error + + Method: + ------- + uses tools from uvcdat library + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'Lhf-Sst feedback (alpha_lh)' + Units = 'W/m2/C' + Method = 'Regression of ' + lhfbox + ' lhfA over ' + sstbox + ' sstA' + Method_NL = 'The nonlinearity is the regression computed when sstA<0 minus the regression computed when sstA>0' + Ref = 'Using CDAT regression calculation' + metric = 'EnsoFbSstLhf' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst, sst_areacell, keyerror1 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, sstbox, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + lhf, lhf_areacell, keyerror2 = Read_data_mask_area( + lhffile, lhfname, 'heat flux', metric, lhfbox, file_area=lhfareafile, name_area=lhfareaname, + file_mask=lhflandmaskfile, name_mask=lhflandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst, lhf, keyerror3 = CheckTime(sst, lhf, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN = int(round(sst.shape[0] / 12)) + + # Time period + actualtimebounds = TimeBounds(sst) + + keyerror = '' + if keyerror1 is not None or keyerror2 is not None or keyerror3 is not None: + alphaLhf, alphaLhfPos, alphaLhfNeg, nl1, nl2 = [None, None], [None, None], [None, None], None, None + dive_down_diag = {'value': None, 'axis': None} + keyerror = add_up_errors([keyerror1, keyerror2, keyerror3]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smooths TS, averages horizontally) + sst, Method, keyerror1 = PreProcessTS( + sst, Method, areacell=sst_areacell, average='horizontal', compute_anom=True, region=sstbox, **kwargs) + lhf, _, keyerror2 = PreProcessTS( + lhf, '', areacell=lhf_areacell, average='horizontal', compute_anom=True, region=lhfbox, **kwargs) + del sst_areacell, lhf_areacell + if keyerror1 is not None or keyerror2 is not None: + alphaLhf, alphaLhfPos, alphaLhfNeg, nl1, nl2 = [None, None], [None, None], [None, None], None, None + dive_down_diag = {'value': None, 'axis': None} + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'axes2': '(lhf) ' + str([ax.id for ax in lhf.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape), 'shape2': '(lhf) ' + str(lhf.shape), + 'time1': '(sst) ' + str(TimeBounds(sst)), 'time2': '(lhf) ' + str(TimeBounds(lhf))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Computes the linear regression for all points, for SSTA >=0 and for SSTA<=0 + alphaLhf, alphaLhfPos, alphaLhfNeg = LinearRegressionAndNonlinearity( + lhf, sst, return_stderr=True, return_intercept=True) + # Non linearities + nl1 = alphaLhfNeg[0] - alphaLhfPos[0] + nl2 = alphaLhfNeg[1] + alphaLhfPos[1] + # Dive down diagnostic + dive_down_diag = {'value': None, 'axis': None} + if netcdf is True: + sst_map, sst_map_areacell, keyerror1 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, 'equatorial_pacific', file_area=sstareafile, + name_area=sstareaname, file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, + maskocean=False, debug=debug, **kwargs) + lhf_map, lhf_map_areacell, keyerror2 = Read_data_mask_area( + lhffile, lhfname, 'heat flux', metric, 'equatorial_pacific', file_area=lhfareafile, + name_area=lhfareaname, file_mask=lhflandmaskfile, name_mask=lhflandmaskname, maskland=True, + maskocean=False, debug=debug, **kwargs) + # Checks if the same time period is used for both variables + sst_map, lhf_map, keyerror3 = CheckTime(sst_map, lhf_map, metric_name=metric, debug=debug, **kwargs) + if keyerror1 is not None or keyerror2 is not None or keyerror3 is not None: + keyerror = add_up_errors([keyerror1, keyerror2, keyerror3]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smooths TS, ...) + sst_map, _, keyerror1 = PreProcessTS( + sst_map, '', areacell=sst_map_areacell, average=False, compute_anom=True, + region="equatorial_pacific", **kwargs) + lhf_map, _, keyerror2 = PreProcessTS( + lhf_map, '', areacell=lhf_map_areacell, average=False, compute_anom=True, + region="equatorial_pacific", **kwargs) + del lhf_map_areacell, sst_map_areacell + if keyerror1 is not None or keyerror2 is not None: + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + # Regridding + if 'regridding' not in list(kwargs.keys()): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'} + else: + if not isinstance(kwargs['regridding'], dict): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + sst_map = Regrid(sst_map, None, region='equatorial_pacific', **kwargs['regridding']) + lhf_map = Regrid(lhf_map, None, region='equatorial_pacific', **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(lhf) ' + str([ax.id for ax in lhf_map.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(lhf) ' + str(lhf_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Regrid', 15, **dict_debug) + # Meridional average + sst_map, keyerror1 = AverageMeridional(sst_map) + lhf_map, keyerror2 = AverageMeridional(lhf_map) + if keyerror1 is not None or keyerror2 is not None: + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(lhf) ' + str([ax.id for ax in lhf_map.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(lhf) ' + str(lhf_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + # Zonal smoothing + sst_map, _ = Smoothing(sst_map, '', axis=1, window=31, method='square') + lhf_map, _ = Smoothing(lhf_map, '', axis=1, window=31, method='square') + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(lhf) ' + str([ax.id for ax in lhf_map.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(lhf) ' + str(lhf_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Smoothing', 15, **dict_debug) + # Array year by year + sst_yby = get_year_by_year(sst_map, frequency=kwargs['frequency']) + lhf_yby = get_year_by_year(lhf_map, frequency=kwargs['frequency']) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(lhf) ' + str([ax.id for ax in lhf_yby.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(lhf) ' + str(lhf_yby.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after get_year_by_year', 15, **dict_debug) + # Computes the linear regression for all points, for SSTA >=0 and for SSTA<=0 + curAlpha, curAlphaPos, curAlphaNeg = LinearRegressionAndNonlinearity( + lhf_map, sst_map, return_stderr=False, return_intercept=False) + hovAlpha, hovAlphaPos, hovAlphaNeg = LinearRegressionAndNonlinearity( + lhf_yby, sst_yby, return_stderr=False, return_intercept=False) + if debug is True: + dict_debug = {'axes1': '(zonal alpha) ' + str([ax.id for ax in curAlpha.getAxisList()]), + 'axes2': '(hovtx alpha) ' + str([ax.id for ax in hovAlpha.getAxisList()]), + 'shape1': '(zonal alpha) ' + str(curAlpha.shape), + 'shape2': '(hovtx alpha) ' + str(hovAlpha.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after LinearRegressionAndNonlinearity', 15, **dict_debug) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': 'C', 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s " + sstbox + " sstA", 'diagnostic_value': alphaLhf[0], + 'diagnostic_value_error': alphaLhf[1], 'slope': alphaLhf[0], + 'intercept': alphaLhf[2], + 'slope_neg': alphaLhfNeg[0], 'intercept_neg': alphaLhfNeg[2], + 'slope_pos': alphaLhfPos[0], 'intercept_pos': alphaLhfPos[2]} + dict2 = {'units': 'W/m2', 'number_of_years_used': yearN, + 'time_period': str(actualtimebounds), + 'description': dataset + "'s " + lhfbox + " lhfA", 'diagnostic_value': alphaLhf[0], + 'diagnostic_value_error': alphaLhf[1], 'slope': alphaLhf[0], + 'intercept': alphaLhf[2], + 'slope_neg': alphaLhfNeg[0], 'intercept_neg': alphaLhfNeg[2], + 'slope_pos': alphaLhfPos[0], 'intercept_pos': alphaLhfPos[2]} + dict3 = {'units': Units, 'number_of_years_used': yearN, + 'time_period': str(actualtimebounds), + 'description': + dataset + "'s zonal equatorial_pacific regression of lhfA over sstA", + 'diagnostic_value': alphaLhf[0], 'diagnostic_value_error': alphaLhf[1], + 'slope': alphaLhf[0], 'intercept': alphaLhf[2], 'slope_neg': alphaLhfNeg[0], + 'intercept_neg': alphaLhfNeg[2], 'slope_pos': alphaLhfPos[0], + 'intercept_pos': alphaLhfPos[2]} + dict4 = {'units': Units, 'number_of_years_used': yearN, + 'time_period': str(actualtimebounds), + 'description': + dataset + "'s zonal equatorial_pacific regression of lhfA over sstA>0", + 'diagnostic_value': alphaLhf[0], 'diagnostic_value_error': alphaLhf[1], + 'slope': alphaLhf[0], 'intercept': alphaLhf[2], 'slope_neg': alphaLhfNeg[0], + 'intercept_neg': alphaLhfNeg[2], 'slope_pos': alphaLhfPos[0], + 'intercept_pos': alphaLhfPos[2]} + dict5 = {'units': Units, 'number_of_years_used': yearN, + 'time_period': str(actualtimebounds), + 'description': + dataset + "'s zonal equatorial_pacific regression of lhfA over sstA<0", + 'diagnostic_value': alphaLhf[0], 'diagnostic_value_error': alphaLhf[1], + 'slope': alphaLhf[0], 'intercept': alphaLhf[2], 'slope_neg': alphaLhfNeg[0], + 'intercept_neg': alphaLhfNeg[2], 'slope_pos': alphaLhfPos[0], + 'intercept_pos': alphaLhfPos[2]} + dict6 = {'units': Units, 'number_of_years_used': yearN, + 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal monthly of equatorial_pacific regression of " + + "lhfA over sstA", + 'diagnostic_value': alphaLhf[0], 'diagnostic_value_error': alphaLhf[1], + 'slope': alphaLhf[0], 'intercept': alphaLhf[2], 'slope_neg': alphaLhfNeg[0], + 'intercept_neg': alphaLhfNeg[2], 'slope_pos': alphaLhfPos[0], + 'intercept_pos': alphaLhfPos[2]} + dict7 = {'units': Units, 'number_of_years_used': yearN, + 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal monthly of equatorial_pacific regression of " + + "lhfA over sstA>0", + 'diagnostic_value': alphaLhf[0], 'diagnostic_value_error': alphaLhf[1], + 'slope': alphaLhf[0], 'intercept': alphaLhf[2], 'slope_neg': alphaLhfNeg[0], + 'intercept_neg': alphaLhfNeg[2], 'slope_pos': alphaLhfPos[0], + 'intercept_pos': alphaLhfPos[2]} + dict8 = {'units': Units, 'number_of_years_used': yearN, + 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal monthly of equatorial_pacific regression of " + + "lhfA over sstA<0", + 'diagnostic_value': alphaLhf[0], 'diagnostic_value_error': alphaLhf[1], + 'slope': alphaLhf[0], 'intercept': alphaLhf[2], 'slope_neg': alphaLhfNeg[0], + 'intercept_neg': alphaLhfNeg[2], 'slope_pos': alphaLhfPos[0], + 'intercept_pos': alphaLhfPos[2]} + dict9 = {'metric_name': Name, 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=sst, var1_attributes=dict1, var1_name='sst__' + dataset, + var1_time_name='months_' + dataset, var2=lhf, var2_attributes=dict2, + var2_name='lhf__' + dataset, var2_time_name='months_' + dataset, + var3=curAlpha, var3_attributes=dict3, var3_name='reg_lhf_over_sst_lon__' + dataset, + var4=curAlphaPos, var4_attributes=dict4, + var4_name='reg_lhf_over_POSsst_lon__' + dataset, var5=curAlphaNeg, + var5_attributes=dict5, var5_name='reg_lhf_over_NEGsst_lon__' + dataset, var6=hovAlpha, + var6_attributes=dict6, var6_name='reg_lhf_over_sst_hov__' + dataset, var7=hovAlphaPos, + var7_attributes=dict7, var7_name='reg_lhf_over_POSsst_hov__' + dataset, + var8=hovAlphaNeg, var8_attributes=dict8, + var8_name='reg_lhf_over_NEGsst_hov__' + dataset, frequency=kwargs['frequency'], + global_attributes=dict9) + del dict1, dict2, dict3, dict4, dict5, dict6, dict7, dict8, dict9 + # Create output + alphaLhfMetric = { + 'name': Name, 'value': alphaLhf[0], 'value_error': alphaLhf[1], 'units': Units, 'method': Method, + 'method_nonlinearity': Method_NL, 'nyears': yearN, 'time_frequency': kwargs['frequency'], + 'time_period': actualtimebounds, 'ref': Ref, 'nonlinearity': nl1, 'nonlinearity_error': nl2, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return alphaLhfMetric + + +def EnsoFbSstLwr(sstfile, sstname, sstareafile, sstareaname, sstlandmaskfile, sstlandmaskname, sstbox, lwrfile, lwrname, + lwrareafile, lwrareaname, lwrlandmaskfile, lwrlandmaskname, lwrbox, dataset='', debug=False, + netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The EnsoFbSstLwr() function computes the regression of 'lwrbox' lwrA (net surface longwave radiation anomalies) over + 'sstbox' sstA (usually the regression of nino3 lwrA over nino3 sstA) + + Author: Yann Planton : yann.planton@locean-ipsl.upmc.fr + Co-author: + + Created on Thu Oct 5 2017 + + Inputs: + ------ + :param sstfile: string + path_to/filename of the file (NetCDF) of SST + :param sstname: string + name of SST variable (tos, ts) in 'sstfile' + :param sstareafile: string + path_to/filename of the file (NetCDF) of the areacell for SST + :param sstareaname: string + name of areacell variable (areacella, areacello) in 'sstareafile' + :param sstlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for SST + :param sstlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfile' + :param sstbox: string + name of box (nino3') for SST + :param lwrfile: string + path_to/filename of the file (NetCDF) of LWR + :param lwrname: string + name of LWR variable (lwr, rlds - rlus) in 'lwrfile' + :param lwrareafile: string + path_to/filename of the file (NetCDF) of the areacell for LWR + :param lwrareaname: string + name of areacell variable (areacella, areacello) in 'lwrareafile' + :param lwrlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for LWR + :param lwrlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'lwrlandmaskfile' + :param lwrbox: string + name of box (nino3') for LWR + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return alphaLwrMetric: dict + name, value, value_error, units, method, nyears, time_frequency, time_period, ref, nonlinearity, + nonlinearity_error + + Method: + ------- + uses tools from uvcdat library + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'Lwr-Sst feedback (alpha_lh)' + Units = 'W/m2/C' + Method = 'Regression of ' + lwrbox + ' lwrA over ' + sstbox + ' sstA' + Method_NL = 'The nonlinearity is the regression computed when sstA<0 minus the regression computed when sstA>0' + Ref = 'Using CDAT regression calculation' + metric = 'EnsoFbSstLwr' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst, sst_areacell, keyerror1 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, sstbox, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + lwr, lwr_areacell, keyerror2 = Read_data_mask_area_multifile( + lwrfile, lwrname, 'heat flux', 'lwr', metric, lwrbox, file_area=lwrareafile, name_area=lwrareaname, + file_mask=lwrlandmaskfile, name_mask=lwrlandmaskname, maskland=True, maskocean=False, debug=debug, + interpreter='project_interpreter_var2', **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst, lwr, keyerror3 = CheckTime(sst, lwr, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN = int(round(sst.shape[0] / 12)) + + # Time period + actualtimebounds = TimeBounds(sst) + + keyerror = '' + if keyerror1 is not None or keyerror2 is not None or keyerror3 is not None: + alphaLwr, alphaLwrPos, alphaLwrNeg, nl1, nl2 = [None, None], [None, None], [None, None], None, None + dive_down_diag = {'value': None, 'axis': None} + keyerror = add_up_errors([keyerror1, keyerror2, keyerror3]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smooths TS, averages horizontally) + sst, Method, keyerror1 = PreProcessTS( + sst, Method, areacell=sst_areacell, average='horizontal', compute_anom=True, region=sstbox, **kwargs) + lwr, _, keyerror2 = PreProcessTS( + lwr, '', areacell=lwr_areacell, average='horizontal', compute_anom=True, region=lwrbox, **kwargs) + del sst_areacell, lwr_areacell + if keyerror1 is not None or keyerror2 is not None: + alphaLwr, alphaLwrPos, alphaLwrNeg, nl1, nl2 = [None, None], [None, None], [None, None], None, None + dive_down_diag = {'value': None, 'axis': None} + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'axes2': '(lwr) ' + str([ax.id for ax in lwr.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape), 'shape2': '(lwr) ' + str(lwr.shape), + 'time1': '(sst) ' + str(TimeBounds(sst)), 'time2': '(lwr) ' + str(TimeBounds(lwr))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Computes the linear regression for all points, for SSTA >=0 and for SSTA<=0 + alphaLwr, alphaLwrPos, alphaLwrNeg = LinearRegressionAndNonlinearity( + lwr, sst, return_stderr=True, return_intercept=True) + # Non linearities + nl1 = alphaLwrNeg[0] - alphaLwrPos[0] + nl2 = alphaLwrNeg[1] + alphaLwrPos[1] + # Dive down diagnostic + dive_down_diag = {'value': None, 'axis': None} + if netcdf is True: + sst_map, sst_map_areacell, keyerror1 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, 'equatorial_pacific', file_area=sstareafile, + name_area=sstareaname, file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, + maskocean=False, debug=debug, **kwargs) + lwr_map, lwr_map_areacell, keyerror2 = Read_data_mask_area_multifile( + lwrfile, lwrname, 'heat flux', 'lwr', metric, 'equatorial_pacific', file_area=lwrareafile, + name_area=lwrareaname, file_mask=lwrlandmaskfile, name_mask=lwrlandmaskname, maskland=True, + maskocean=False, debug=debug, interpreter='project_interpreter_var2', **kwargs) + # Checks if the same time period is used for both variables + sst_map, lwr_map, keyerror3 = CheckTime(sst_map, lwr_map, metric_name=metric, debug=debug, **kwargs) + if keyerror1 is not None or keyerror2 is not None or keyerror3 is not None: + keyerror = add_up_errors([keyerror1, keyerror2, keyerror3]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smooths TS, ...) + sst_map, _, keyerror1 = PreProcessTS( + sst_map, '', areacell=sst_map_areacell, average=False, compute_anom=True, + region="equatorial_pacific", **kwargs) + lwr_map, _, keyerror2 = PreProcessTS( + lwr_map, '', areacell=lwr_map_areacell, average=False, compute_anom=True, + region="equatorial_pacific", **kwargs) + del lwr_map_areacell, sst_map_areacell + if keyerror1 is not None or keyerror2 is not None: + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + # Regridding + if 'regridding' not in list(kwargs.keys()): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'} + else: + if not isinstance(kwargs['regridding'], dict): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + sst_map = Regrid(sst_map, None, region='equatorial_pacific', **kwargs['regridding']) + lwr_map = Regrid(lwr_map, None, region='equatorial_pacific', **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(lwr) ' + str([ax.id for ax in lwr_map.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(lwr) ' + str(lwr_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Regrid', 15, **dict_debug) + # Meridional average + sst_map, keyerror1 = AverageMeridional(sst_map) + lwr_map, keyerror2 = AverageMeridional(lwr_map) + if keyerror1 is not None or keyerror2 is not None: + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(lwr) ' + str([ax.id for ax in lwr_map.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(lwr) ' + str(lwr_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + # Zonal smoothing + sst_map, _ = Smoothing(sst_map, '', axis=1, window=31, method='square') + lwr_map, _ = Smoothing(lwr_map, '', axis=1, window=31, method='square') + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(lwr) ' + str([ax.id for ax in lwr_map.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(lwr) ' + str(lwr_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Smoothing', 15, **dict_debug) + # Array year by year + sst_yby = get_year_by_year(sst_map, frequency=kwargs['frequency']) + lwr_yby = get_year_by_year(lwr_map, frequency=kwargs['frequency']) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(lwr) ' + str([ax.id for ax in lwr_yby.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(lwr) ' + str(lwr_yby.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after get_year_by_year', 15, **dict_debug) + # Computes the linear regression for all points, for SSTA >=0 and for SSTA<=0 + curAlpha, curAlphaPos, curAlphaNeg = LinearRegressionAndNonlinearity( + lwr_map, sst_map, return_stderr=False, return_intercept=False) + hovAlpha, hovAlphaPos, hovAlphaNeg = LinearRegressionAndNonlinearity( + lwr_yby, sst_yby, return_stderr=False, return_intercept=False) + if debug is True: + dict_debug = {'axes1': '(zonal alpha) ' + str([ax.id for ax in curAlpha.getAxisList()]), + 'axes2': '(hovtx alpha) ' + str([ax.id for ax in hovAlpha.getAxisList()]), + 'shape1': '(zonal alpha) ' + str(curAlpha.shape), + 'shape2': '(hovtx alpha) ' + str(hovAlpha.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after LinearRegressionAndNonlinearity', 15, **dict_debug) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': 'C', 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s " + sstbox + " sstA", 'diagnostic_value': alphaLwr[0], + 'diagnostic_value_error': alphaLwr[1], 'slope': alphaLwr[0], + 'intercept': alphaLwr[2], + 'slope_neg': alphaLwrNeg[0], 'intercept_neg': alphaLwrNeg[2], + 'slope_pos': alphaLwrPos[0], 'intercept_pos': alphaLwrPos[2]} + dict2 = {'units': 'W/m2', 'number_of_years_used': yearN, + 'time_period': str(actualtimebounds), + 'description': dataset + "'s " + lwrbox + " lwrA", 'diagnostic_value': alphaLwr[0], + 'diagnostic_value_error': alphaLwr[1], 'slope': alphaLwr[0], + 'intercept': alphaLwr[2], + 'slope_neg': alphaLwrNeg[0], 'intercept_neg': alphaLwrNeg[2], + 'slope_pos': alphaLwrPos[0], 'intercept_pos': alphaLwrPos[2]} + dict3 = {'units': Units, 'number_of_years_used': yearN, + 'time_period': str(actualtimebounds), + 'description': + dataset + "'s zonal equatorial_pacific regression of lwrA over sstA", + 'diagnostic_value': alphaLwr[0], 'diagnostic_value_error': alphaLwr[1], + 'slope': alphaLwr[0], 'intercept': alphaLwr[2], 'slope_neg': alphaLwrNeg[0], + 'intercept_neg': alphaLwrNeg[2], 'slope_pos': alphaLwrPos[0], + 'intercept_pos': alphaLwrPos[2]} + dict4 = {'units': Units, 'number_of_years_used': yearN, + 'time_period': str(actualtimebounds), + 'description': + dataset + "'s zonal equatorial_pacific regression of lwrA over sstA>0", + 'diagnostic_value': alphaLwr[0], 'diagnostic_value_error': alphaLwr[1], + 'slope': alphaLwr[0], 'intercept': alphaLwr[2], 'slope_neg': alphaLwrNeg[0], + 'intercept_neg': alphaLwrNeg[2], 'slope_pos': alphaLwrPos[0], + 'intercept_pos': alphaLwrPos[2]} + dict5 = {'units': Units, 'number_of_years_used': yearN, + 'time_period': str(actualtimebounds), + 'description': + dataset + "'s zonal equatorial_pacific regression of lwrA over sstA<0", + 'diagnostic_value': alphaLwr[0], 'diagnostic_value_error': alphaLwr[1], + 'slope': alphaLwr[0], 'intercept': alphaLwr[2], 'slope_neg': alphaLwrNeg[0], + 'intercept_neg': alphaLwrNeg[2], 'slope_pos': alphaLwrPos[0], + 'intercept_pos': alphaLwrPos[2]} + dict6 = {'units': Units, 'number_of_years_used': yearN, + 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal monthly of equatorial_pacific regression of " + + "lwrA over sstA", + 'diagnostic_value': alphaLwr[0], 'diagnostic_value_error': alphaLwr[1], + 'slope': alphaLwr[0], 'intercept': alphaLwr[2], 'slope_neg': alphaLwrNeg[0], + 'intercept_neg': alphaLwrNeg[2], 'slope_pos': alphaLwrPos[0], + 'intercept_pos': alphaLwrPos[2]} + dict7 = {'units': Units, 'number_of_years_used': yearN, + 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal monthly of equatorial_pacific regression of " + + "lwrA over sstA>0", + 'diagnostic_value': alphaLwr[0], 'diagnostic_value_error': alphaLwr[1], + 'slope': alphaLwr[0], 'intercept': alphaLwr[2], 'slope_neg': alphaLwrNeg[0], + 'intercept_neg': alphaLwrNeg[2], 'slope_pos': alphaLwrPos[0], + 'intercept_pos': alphaLwrPos[2]} + dict8 = {'units': Units, 'number_of_years_used': yearN, + 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal monthly of equatorial_pacific regression of " + + "lwrA over sstA<0", + 'diagnostic_value': alphaLwr[0], 'diagnostic_value_error': alphaLwr[1], + 'slope': alphaLwr[0], 'intercept': alphaLwr[2], 'slope_neg': alphaLwrNeg[0], + 'intercept_neg': alphaLwrNeg[2], 'slope_pos': alphaLwrPos[0], + 'intercept_pos': alphaLwrPos[2]} + dict9 = {'metric_name': Name, 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=sst, var1_attributes=dict1, var1_name='sst__' + dataset, + var1_time_name='months_' + dataset, var2=lwr, var2_attributes=dict2, + var2_name='lwr__' + dataset, var2_time_name='months_' + dataset, + var3=curAlpha, var3_attributes=dict3, var3_name='reg_lwr_over_sst_lon__' + dataset, + var4=curAlphaPos, var4_attributes=dict4, + var4_name='reg_lwr_over_POSsst_lon__' + dataset, var5=curAlphaNeg, + var5_attributes=dict5, var5_name='reg_lwr_over_NEGsst_lon__' + dataset, var6=hovAlpha, + var6_attributes=dict6, var6_name='reg_lwr_over_sst_hov__' + dataset, var7=hovAlphaPos, + var7_attributes=dict7, var7_name='reg_lwr_over_POSsst_hov__' + dataset, + var8=hovAlphaNeg, var8_attributes=dict8, + var8_name='reg_lwr_over_NEGsst_hov__' + dataset, frequency=kwargs['frequency'], + global_attributes=dict9) + del dict1, dict2, dict3, dict4, dict5, dict6, dict7, dict8, dict9 + # Create output + alphaLwrMetric = { + 'name': Name, 'value': alphaLwr[0], 'value_error': alphaLwr[1], 'units': Units, 'method': Method, + 'method_nonlinearity': Method_NL, 'nyears': yearN, 'time_frequency': kwargs['frequency'], + 'time_period': actualtimebounds, 'ref': Ref, 'nonlinearity': nl1, 'nonlinearity_error': nl2, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return alphaLwrMetric + + +def EnsoFbSstShf(sstfile, sstname, sstareafile, sstareaname, sstlandmaskfile, sstlandmaskname, sstbox, shffile, shfname, + shfareafile, shfareaname, shflandmaskfile, shflandmaskname, shfbox, dataset='', debug=False, + netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The EnsoFbSstShf() function computes the regression of 'shfbox' shfA (sensible heat flux anomalies) over 'sstbox' + sstA (usually the regression of nino3 shfA over nino3 sstA) + + Author: Yann Planton : yann.planton@locean-ipsl.upmc.fr + Co-author: + + Created on Thu Oct 5 2017 + + Inputs: + ------ + :param sstfile: string + path_to/filename of the file (NetCDF) of SST + :param sstname: string + name of SST variable (tos, ts) in 'sstfile' + :param sstareafile: string + path_to/filename of the file (NetCDF) of the areacell for SST + :param sstareaname: string + name of areacell variable (areacella, areacello) in 'sstareafile' + :param sstlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for SST + :param sstlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfile' + :param sstbox: string + name of box (nino3') for SST + :param shffile: string + path_to/filename of the file (NetCDF) of SHF + :param shfname: string + name of SHF variable (shf, hfss) in 'shffile' + :param shfareafile: string + path_to/filename of the file (NetCDF) of the areacell for SHF + :param shfareaname: string + name of areacell variable (areacella, areacello) in 'shfareafile' + :param shflandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for SHF + :param shflandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'shflandmaskfile' + :param shfbox: string + name of box (nino3') for SHF + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return alphaShfMetric: dict + name, value, value_error, units, method, nyears, time_frequency, time_period, ref, nonlinearity, + nonlinearity_error + + Method: + ------- + uses tools from uvcdat library + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'Shf-Sst feedback (alpha_lh)' + Units = 'W/m2/C' + Method = 'Regression of ' + shfbox + ' shfA over ' + sstbox + ' sstA' + Method_NL = 'The nonlinearity is the regression computed when sstA<0 minus the regression computed when sstA>0' + Ref = 'Using CDAT regression calculation' + metric = 'EnsoFbSstShf' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst, sst_areacell, keyerror1 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, sstbox, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + shf, shf_areacell, keyerror2 = Read_data_mask_area( + shffile, shfname, 'heat flux', metric, shfbox, file_area=shfareafile, name_area=shfareaname, + file_mask=shflandmaskfile, name_mask=shflandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst, shf, keyerror3 = CheckTime(sst, shf, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN = int(round(sst.shape[0] / 12)) + + # Time period + actualtimebounds = TimeBounds(sst) + + keyerror = '' + if keyerror1 is not None or keyerror2 is not None or keyerror3 is not None: + alphaShf, alphaShfPos, alphaShfNeg, nl1, nl2 = [None, None], [None, None], [None, None], None, None + dive_down_diag = {'value': None, 'axis': None} + keyerror = add_up_errors([keyerror1, keyerror2, keyerror3]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smooths TS, averages horizontally) + sst, Method, keyerror1 = PreProcessTS( + sst, Method, areacell=sst_areacell, average='horizontal', compute_anom=True, region=sstbox, **kwargs) + shf, _, keyerror2 = PreProcessTS( + shf, '', areacell=shf_areacell, average='horizontal', compute_anom=True, region=shfbox, **kwargs) + del sst_areacell, shf_areacell + if keyerror1 is not None or keyerror2 is not None: + alphaShf, alphaShfPos, alphaShfNeg, nl1, nl2 = [None, None], [None, None], [None, None], None, None + dive_down_diag = {'value': None, 'axis': None} + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'axes2': '(shf) ' + str([ax.id for ax in shf.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape), 'shape2': '(shf) ' + str(shf.shape), + 'time1': '(sst) ' + str(TimeBounds(sst)), 'time2': '(shf) ' + str(TimeBounds(shf))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Computes the linear regression for all points, for SSTA >=0 and for SSTA<=0 + alphaShf, alphaShfPos, alphaShfNeg = LinearRegressionAndNonlinearity( + shf, sst, return_stderr=True, return_intercept=True) + # Non linearities + nl1 = alphaShfNeg[0] - alphaShfPos[0] + nl2 = alphaShfNeg[1] + alphaShfPos[1] + # Dive down diagnostic + dive_down_diag = {'value': None, 'axis': None} + if netcdf is True: + sst_map, sst_map_areacell, keyerror1 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, 'equatorial_pacific', file_area=sstareafile, + name_area=sstareaname, file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, + maskocean=False, debug=debug, **kwargs) + shf_map, shf_map_areacell, keyerror2 = Read_data_mask_area( + shffile, shfname, 'heat flux', metric, 'equatorial_pacific', file_area=shfareafile, + name_area=shfareaname, file_mask=shflandmaskfile, name_mask=shflandmaskname, maskland=True, + maskocean=False, debug=debug, **kwargs) + # Checks if the same time period is used for both variables + sst_map, shf_map, keyerror3 = CheckTime(sst_map, shf_map, metric_name=metric, debug=debug, **kwargs) + if keyerror1 is not None or keyerror2 is not None or keyerror3 is not None: + keyerror = add_up_errors([keyerror1, keyerror2, keyerror3]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smooths TS, ...) + sst_map, _, keyerror1 = PreProcessTS( + sst_map, '', areacell=sst_map_areacell, average=False, compute_anom=True, + region="equatorial_pacific", **kwargs) + shf_map, _, keyerror2 = PreProcessTS( + shf_map, '', areacell=shf_map_areacell, average=False, compute_anom=True, + region="equatorial_pacific", **kwargs) + del shf_map_areacell, sst_map_areacell + if keyerror1 is not None or keyerror2 is not None: + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + # Regridding + if 'regridding' not in list(kwargs.keys()): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'} + else: + if not isinstance(kwargs['regridding'], dict): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + sst_map = Regrid(sst_map, None, region='equatorial_pacific', **kwargs['regridding']) + shf_map = Regrid(shf_map, None, region='equatorial_pacific', **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(shf) ' + str([ax.id for ax in shf_map.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(shf) ' + str(shf_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Regrid', 15, **dict_debug) + # Meridional average + sst_map, keyerror1 = AverageMeridional(sst_map) + shf_map, keyerror2 = AverageMeridional(shf_map) + if keyerror1 is not None or keyerror2 is not None: + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(shf) ' + str([ax.id for ax in shf_map.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(shf) ' + str(shf_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + # Zonal smoothing + sst_map, _ = Smoothing(sst_map, '', axis=1, window=31, method='square') + shf_map, _ = Smoothing(shf_map, '', axis=1, window=31, method='square') + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(shf) ' + str([ax.id for ax in shf_map.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(shf) ' + str(shf_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Smoothing', 15, **dict_debug) + # Array year by year + sst_yby = get_year_by_year(sst_map, frequency=kwargs['frequency']) + shf_yby = get_year_by_year(shf_map, frequency=kwargs['frequency']) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(shf) ' + str([ax.id for ax in shf_yby.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(shf) ' + str(shf_yby.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after get_year_by_year', 15, **dict_debug) + # Computes the linear regression for all points, for SSTA >=0 and for SSTA<=0 + curAlpha, curAlphaPos, curAlphaNeg = LinearRegressionAndNonlinearity( + shf_map, sst_map, return_stderr=False, return_intercept=False) + hovAlpha, hovAlphaPos, hovAlphaNeg = LinearRegressionAndNonlinearity( + shf_yby, sst_yby, return_stderr=False, return_intercept=False) + if debug is True: + dict_debug = {'axes1': '(zonal alpha) ' + str([ax.id for ax in curAlpha.getAxisList()]), + 'axes2': '(hovtx alpha) ' + str([ax.id for ax in hovAlpha.getAxisList()]), + 'shape1': '(zonal alpha) ' + str(curAlpha.shape), + 'shape2': '(hovtx alpha) ' + str(hovAlpha.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after LinearRegressionAndNonlinearity', 15, **dict_debug) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = { + 'units': 'C', 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s " + sstbox + " sstA", 'diagnostic_value': alphaShf[0], + 'diagnostic_value_error': alphaShf[1], 'slope': alphaShf[0], 'intercept': alphaShf[2], + 'slope_neg': alphaShfNeg[0], 'intercept_neg': alphaShfNeg[2], + 'slope_pos': alphaShfPos[0], 'intercept_pos': alphaShfPos[2]} + dict2 = { + 'units': 'W/m2', 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s " + shfbox + " shfA", 'diagnostic_value': alphaShf[0], + 'diagnostic_value_error': alphaShf[1], 'slope': alphaShf[0], 'intercept': alphaShf[2], + 'slope_neg': alphaShfNeg[0], 'intercept_neg': alphaShfNeg[2], + 'slope_pos': alphaShfPos[0], 'intercept_pos': alphaShfPos[2]} + dict3 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal equatorial_pacific regression of shfA over sstA", + 'diagnostic_value': alphaShf[0], 'diagnostic_value_error': alphaShf[1], + 'slope': alphaShf[0], 'intercept': alphaShf[2], 'slope_neg': alphaShfNeg[0], + 'intercept_neg': alphaShfNeg[2], 'slope_pos': alphaShfPos[0], + 'intercept_pos': alphaShfPos[2]} + dict4 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal equatorial_pacific regression of shfA over sstA>0", + 'diagnostic_value': alphaShf[0], 'diagnostic_value_error': alphaShf[1], + 'slope': alphaShf[0], 'intercept': alphaShf[2], 'slope_neg': alphaShfNeg[0], + 'intercept_neg': alphaShfNeg[2], 'slope_pos': alphaShfPos[0], + 'intercept_pos': alphaShfPos[2]} + dict5 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal equatorial_pacific regression of shfA over sstA<0", + 'diagnostic_value': alphaShf[0], 'diagnostic_value_error': alphaShf[1], + 'slope': alphaShf[0], 'intercept': alphaShf[2], 'slope_neg': alphaShfNeg[0], + 'intercept_neg': alphaShfNeg[2], 'slope_pos': alphaShfPos[0], + 'intercept_pos': alphaShfPos[2]} + dict6 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + + "'s zonal monthly of equatorial_pacific regression of shfA over sstA", + 'diagnostic_value': alphaShf[0], 'diagnostic_value_error': alphaShf[1], + 'slope': alphaShf[0], 'intercept': alphaShf[2], 'slope_neg': alphaShfNeg[0], + 'intercept_neg': alphaShfNeg[2], 'slope_pos': alphaShfPos[0], + 'intercept_pos': alphaShfPos[2]} + dict7 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + + "'s zonal monthly of equatorial_pacific regression of shfA over sstA>0", + 'diagnostic_value': alphaShf[0], 'diagnostic_value_error': alphaShf[1], + 'slope': alphaShf[0], 'intercept': alphaShf[2], 'slope_neg': alphaShfNeg[0], + 'intercept_neg': alphaShfNeg[2], 'slope_pos': alphaShfPos[0], + 'intercept_pos': alphaShfPos[2]} + dict8 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + + "'s zonal monthly of equatorial_pacific regression of shfA over sstA<0", + 'diagnostic_value': alphaShf[0], 'diagnostic_value_error': alphaShf[1], + 'slope': alphaShf[0], 'intercept': alphaShf[2], 'slope_neg': alphaShfNeg[0], + 'intercept_neg': alphaShfNeg[2], 'slope_pos': alphaShfPos[0], + 'intercept_pos': alphaShfPos[2]} + dict9 = {'metric_name': Name, 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=sst, var1_attributes=dict1, var1_name='sst__' + dataset, + var1_time_name='months_' + dataset, var2=shf, var2_attributes=dict2, + var2_name='shf__' + dataset, var2_time_name='months_' + dataset, + var3=curAlpha, var3_attributes=dict3, var3_name='reg_shf_over_sst_lon__' + dataset, + var4=curAlphaPos, var4_attributes=dict4, + var4_name='reg_shf_over_POSsst_lon__' + dataset, var5=curAlphaNeg, + var5_attributes=dict5, var5_name='reg_shf_over_NEGsst_lon__' + dataset, var6=hovAlpha, + var6_attributes=dict6, var6_name='reg_shf_over_sst_hov__' + dataset, var7=hovAlphaPos, + var7_attributes=dict7, var7_name='reg_shf_over_POSsst_hov__' + dataset, + var8=hovAlphaNeg, var8_attributes=dict8, + var8_name='reg_shf_over_NEGsst_hov__' + dataset, frequency=kwargs['frequency'], + global_attributes=dict9) + del dict1, dict2, dict3, dict4, dict5, dict6, dict7, dict8, dict9 + # Create output + alphaShfMetric = { + 'name': Name, 'value': alphaShf[0], 'value_error': alphaShf[1], 'units': Units, 'method': Method, + 'method_nonlinearity': Method_NL, 'nyears': yearN, 'time_frequency': kwargs['frequency'], + 'time_period': actualtimebounds, 'ref': Ref, 'nonlinearity': nl1, 'nonlinearity_error': nl2, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return alphaShfMetric + + +def EnsoFbSstSwr(sstfile, sstname, sstareafile, sstareaname, sstlandmaskfile, sstlandmaskname, sstbox, swrfile, swrname, + swrareafile, swrareaname, swrlandmaskfile, swrlandmaskname, swrbox, dataset='', debug=False, + netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The EnsoFbSstSwr() function computes the regression of 'swrbox' swrA (net surface shortwave radiation anomalies) + over 'sstbox' sstA (usually the regression of nino3 swrA over nino3 sstA) + + Author: Yann Planton : yann.planton@locean-ipsl.upmc.fr + Co-author: + + Created on Thu Oct 5 2017 + + Inputs: + ------ + :param sstfile: string + path_to/filename of the file (NetCDF) of SST + :param sstname: string + name of SST variable (tos, ts) in 'sstfile' + :param sstareafile: string + path_to/filename of the file (NetCDF) of the areacell for SST + :param sstareaname: string + name of areacell variable (areacella, areacello) in 'sstareafile' + :param sstlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for SST + :param sstlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfile' + :param sstbox: string + name of box (nino3') for SST + :param swrfile: string + path_to/filename of the file (NetCDF) of SWR + :param swrname: string + name of SWR variable (swr, rsds - rsus) in 'swrfile' + :param swrareafile: string + path_to/filename of the file (NetCDF) of the areacell for SWR + :param swrareaname: string + name of areacell variable (areacella, areacello) in 'swrareafile' + :param swrlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for SWR + :param swrlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'swrlandmaskfile' + :param swrbox: string + name of box (nino3') for SWR + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return alphaSwrMetric: dict + name, value, value_error, units, method, nyears, time_frequency, time_period, ref, nonlinearity, + nonlinearity_error + + Method: + ------- + uses tools from uvcdat library + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'Swr-Sst feedback (alpha_lh)' + Units = 'W/m2/C' + Method = 'Regression of ' + swrbox + ' swrA over ' + sstbox + ' sstA' + Method_NL = 'The nonlinearity is the regression computed when sstA<0 minus the regression computed when sstA>0' + Ref = 'Using CDAT regression calculation' + metric = 'EnsoFbSstSwr' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst, sst_areacell, keyerror1 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, sstbox, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + swr, swr_areacell, keyerror2 = Read_data_mask_area_multifile( + swrfile, swrname, 'heat flux', 'swr', metric, swrbox, file_area=swrareafile, name_area=swrareaname, + file_mask=swrlandmaskfile, name_mask=swrlandmaskname, maskland=True, maskocean=False, debug=debug, + interpreter='project_interpreter_var2', **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst, swr, keyerror3 = CheckTime(sst, swr, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN = int(round(sst.shape[0] / 12)) + + # Time period + actualtimebounds = TimeBounds(sst) + + keyerror = '' + if keyerror1 is not None or keyerror2 is not None or keyerror3 is not None: + alphaSwr, alphaSwrPos, alphaSwrNeg, nl1, nl2 = [None, None], [None, None], [None, None], None, None + dive_down_diag = {'value': None, 'axis': None} + keyerror = add_up_errors([keyerror1, keyerror2, keyerror3]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smooths TS, averages horizontally) + sst, Method, keyerror1 = PreProcessTS( + sst, Method, areacell=sst_areacell, average='horizontal', compute_anom=True, region=sstbox, **kwargs) + swr, _, keyerror2 = PreProcessTS( + swr, '', areacell=swr_areacell, average='horizontal', compute_anom=True, region=swrbox, **kwargs) + del sst_areacell, swr_areacell + if keyerror1 is not None or keyerror2 is not None: + alphaSwr, alphaSwrPos, alphaSwrNeg, nl1, nl2 = [None, None], [None, None], [None, None], None, None + dive_down_diag = {'value': None, 'axis': None} + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'axes2': '(swr) ' + str([ax.id for ax in swr.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape), 'shape2': '(swr) ' + str(swr.shape), + 'time1': '(sst) ' + str(TimeBounds(sst)), 'time2': '(swr) ' + str(TimeBounds(swr))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Computes the linear regression for all points, for SSTA >=0 and for SSTA<=0 + alphaSwr, alphaSwrPos, alphaSwrNeg = LinearRegressionAndNonlinearity( + swr, sst, return_stderr=True, return_intercept=True) + # Non linearities + nl1 = alphaSwrNeg[0] - alphaSwrPos[0] + nl2 = alphaSwrNeg[1] + alphaSwrPos[1] + # Dive down diagnostic + dive_down_diag = {'value': None, 'axis': None} + if netcdf is True: + sst_map, sst_map_areacell, keyerror1 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, 'equatorial_pacific', file_area=sstareafile, + name_area=sstareaname, file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, + maskocean=False, debug=debug, **kwargs) + swr_map, swr_map_areacell, keyerror2 = Read_data_mask_area_multifile( + swrfile, swrname, 'heat flux', 'swr', metric, 'equatorial_pacific', file_area=swrareafile, + name_area=swrareaname, file_mask=swrlandmaskfile, name_mask=swrlandmaskname, maskland=True, + maskocean=False, debug=debug, interpreter='project_interpreter_var2', **kwargs) + # Checks if the same time period is used for both variables + sst_map, swr_map, keyerror3 = CheckTime(sst_map, swr_map, metric_name=metric, debug=debug, **kwargs) + if keyerror1 is not None or keyerror2 is not None or keyerror3 is not None: + keyerror = add_up_errors([keyerror1, keyerror2, keyerror3]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smooths TS,...) + sst_map, _, keyerror1 = PreProcessTS( + sst_map, '', areacell=sst_map_areacell, average=False, compute_anom=True, + region="equatorial_pacific", **kwargs) + swr_map, _, keyerror2 = PreProcessTS( + swr_map, '', areacell=swr_map_areacell, average=False, compute_anom=True, + region="equatorial_pacific", **kwargs) + del swr_map_areacell, sst_map_areacell + if keyerror1 is not None or keyerror2 is not None: + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + # Regridding + if 'regridding' not in list(kwargs.keys()): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'} + else: + if not isinstance(kwargs['regridding'], dict): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + sst_map = Regrid(sst_map, None, region='equatorial_pacific', **kwargs['regridding']) + swr_map = Regrid(swr_map, None, region='equatorial_pacific', **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(swr) ' + str([ax.id for ax in swr_map.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(swr) ' + str(swr_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Regrid', 15, **dict_debug) + # Meridional average + sst_map, keyerror1 = AverageMeridional(sst_map) + swr_map, keyerror2 = AverageMeridional(swr_map) + if keyerror1 is not None or keyerror2 is not None: + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(swr) ' + str([ax.id for ax in swr_map.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(swr) ' + str(swr_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + # Zonal smoothing + sst_map, _ = Smoothing(sst_map, '', axis=1, window=31, method='square') + swr_map, _ = Smoothing(swr_map, '', axis=1, window=31, method='square') + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(swr) ' + str([ax.id for ax in swr_map.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(swr) ' + str(swr_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Smoothing', 15, **dict_debug) + # Array year by year + sst_yby = get_year_by_year(sst_map, frequency=kwargs['frequency']) + swr_yby = get_year_by_year(swr_map, frequency=kwargs['frequency']) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(swr) ' + str([ax.id for ax in swr_yby.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(swr) ' + str(swr_yby.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after get_year_by_year', 15, **dict_debug) + # Computes the linear regression for all points, for SSTA >=0 and for SSTA<=0 + curAlpha, curAlphaPos, curAlphaNeg = LinearRegressionAndNonlinearity( + swr_map, sst_map, return_stderr=False, return_intercept=False) + hovAlpha, hovAlphaPos, hovAlphaNeg = LinearRegressionAndNonlinearity( + swr_yby, sst_yby, return_stderr=False, return_intercept=False) + if debug is True: + dict_debug = {'axes1': '(zonal alpha) ' + str([ax.id for ax in curAlpha.getAxisList()]), + 'axes2': '(hovtx alpha) ' + str([ax.id for ax in hovAlpha.getAxisList()]), + 'shape1': '(zonal alpha) ' + str(curAlpha.shape), + 'shape2': '(hovtx alpha) ' + str(hovAlpha.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after LinearRegressionAndNonlinearity', 15, **dict_debug) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = { + 'units': 'C', 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s " + sstbox + " sstA", 'diagnostic_value': alphaSwr[0], + 'diagnostic_value_error': alphaSwr[1], 'slope': alphaSwr[0], 'intercept': alphaSwr[2], + 'slope_neg': alphaSwrNeg[0], 'intercept_neg': alphaSwrNeg[2], + 'slope_pos': alphaSwrPos[0], 'intercept_pos': alphaSwrPos[2]} + dict2 = { + 'units': 'W/m2', 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s " + swrbox + " swrA", 'diagnostic_value': alphaSwr[0], + 'diagnostic_value_error': alphaSwr[1], 'slope': alphaSwr[0], 'intercept': alphaSwr[2], + 'slope_neg': alphaSwrNeg[0], 'intercept_neg': alphaSwrNeg[2], + 'slope_pos': alphaSwrPos[0], 'intercept_pos': alphaSwrPos[2]} + dict3 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal equatorial_pacific regression of swrA over sstA", + 'diagnostic_value': alphaSwr[0], 'diagnostic_value_error': alphaSwr[1], + 'slope': alphaSwr[0], 'intercept': alphaSwr[2], 'slope_neg': alphaSwrNeg[0], + 'intercept_neg': alphaSwrNeg[2], 'slope_pos': alphaSwrPos[0], + 'intercept_pos': alphaSwrPos[2]} + dict4 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal equatorial_pacific regression of swrA over sstA>0", + 'diagnostic_value': alphaSwr[0], 'diagnostic_value_error': alphaSwr[1], + 'slope': alphaSwr[0], 'intercept': alphaSwr[2], 'slope_neg': alphaSwrNeg[0], + 'intercept_neg': alphaSwrNeg[2], 'slope_pos': alphaSwrPos[0], + 'intercept_pos': alphaSwrPos[2]} + dict5 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal equatorial_pacific regression of swrA over sstA<0", + 'diagnostic_value': alphaSwr[0], 'diagnostic_value_error': alphaSwr[1], + 'slope': alphaSwr[0], 'intercept': alphaSwr[2], 'slope_neg': alphaSwrNeg[0], + 'intercept_neg': alphaSwrNeg[2], 'slope_pos': alphaSwrPos[0], + 'intercept_pos': alphaSwrPos[2]} + dict6 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + + "'s zonal monthly of equatorial_pacific regression of swrA over sstA", + 'diagnostic_value': alphaSwr[0], 'diagnostic_value_error': alphaSwr[1], + 'slope': alphaSwr[0], 'intercept': alphaSwr[2], 'slope_neg': alphaSwrNeg[0], + 'intercept_neg': alphaSwrNeg[2], 'slope_pos': alphaSwrPos[0], + 'intercept_pos': alphaSwrPos[2]} + dict7 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + + "'s zonal monthly of equatorial_pacific regression of swrA over sstA>0", + 'diagnostic_value': alphaSwr[0], 'diagnostic_value_error': alphaSwr[1], + 'slope': alphaSwr[0], 'intercept': alphaSwr[2], 'slope_neg': alphaSwrNeg[0], + 'intercept_neg': alphaSwrNeg[2], 'slope_pos': alphaSwrPos[0], + 'intercept_pos': alphaSwrPos[2]} + dict8 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + + "'s zonal monthly of equatorial_pacific regression of swrA over sstA<0", + 'diagnostic_value': alphaSwr[0], 'diagnostic_value_error': alphaSwr[1], + 'slope': alphaSwr[0], 'intercept': alphaSwr[2], 'slope_neg': alphaSwrNeg[0], + 'intercept_neg': alphaSwrNeg[2], 'slope_pos': alphaSwrPos[0], + 'intercept_pos': alphaSwrPos[2]} + dict9 = {'metric_name': Name, 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=sst, var1_attributes=dict1, var1_name='sst__' + dataset, + var1_time_name='months_' + dataset, var2=swr, var2_attributes=dict2, + var2_name='swr__' + dataset, var2_time_name='months_' + dataset, + var3=curAlpha, var3_attributes=dict3, var3_name='reg_swr_over_sst_lon__' + dataset, + var4=curAlphaPos, var4_attributes=dict4, + var4_name='reg_swr_over_POSsst_lon__' + dataset, var5=curAlphaNeg, + var5_attributes=dict5, var5_name='reg_swr_over_NEGsst_lon__' + dataset, var6=hovAlpha, + var6_attributes=dict6, var6_name='reg_swr_over_sst_hov__' + dataset, var7=hovAlphaPos, + var7_attributes=dict7, var7_name='reg_swr_over_POSsst_hov__' + dataset, + var8=hovAlphaNeg, var8_attributes=dict8, + var8_name='reg_swr_over_NEGsst_hov__' + dataset, frequency=kwargs['frequency'], + global_attributes=dict9) + del dict1, dict2, dict3, dict4, dict5, dict6, dict7, dict8, dict9 + # Create output + alphaSwrMetric = { + 'name': Name, 'value': alphaSwr[0], 'value_error': alphaSwr[1], 'units': Units, 'method': Method, + 'method_nonlinearity': Method_NL, 'nyears': yearN, 'time_frequency': kwargs['frequency'], + 'time_period': actualtimebounds, 'ref': Ref, 'nonlinearity': nl1, 'nonlinearity_error': nl2, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return alphaSwrMetric + + +def EnsoFbSstThf(sstfile, sstname, sstareafile, sstareaname, sstlandmaskfile, sstlandmaskname, sstbox, thffile, thfname, + thfareafile, thfareaname, thflandmaskfile, thflandmaskname, thfbox, dataset='', debug=False, + netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The EnsoFbSstThf() function computes the regression of 'thfbox' thfA (total heat flux anomalies) over 'sstbox' sstA + (usually the regression of nino3 thfA over nino3 sstA) + The total heat flux is the sum of four term: + - net surface shortwave radiation, + - net surface longwave radiation, + - latent heat flux, + - sensible heat flux + + The total heat flux is not always available is models or observations. + Either the user computes it and sends the filename and the varname or he feeds into thffile and thfname of this + function a list() of the four needed files and variable names (CMIP: rsds-rsus, rlds-rlus, hfls, hfss) + + Author: Yann Planton : yann.planton@locean-ipsl.upmc.fr + Co-author: + + Created on Thu Oct 5 2017 + + Inputs: + ------ + :param sstfile: string + path_to/filename of the file (NetCDF) of SST + :param sstname: string + name of SST variable (tos, ts) in 'sstfile' + :param sstareafile: string + path_to/filename of the file (NetCDF) of the areacell for SST + :param sstareaname: string + name of areacell variable (areacella, areacello) in 'sstareafile' + :param sstlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for SST + :param sstlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfile' + :param sstbox: string + name of box (nino3') for SST + :param thffile: string + path_to/filename of the file (NetCDF) of THF + :param thfname: string + name of THF variable (thf, netflux, thflx, thf + lwr + lhf + shf) (may be a list of variables) in 'thffile' + :param thfareafile: string + path_to/filename of the file (NetCDF) of the areacell for THF + :param thfareaname: string + name of areacell variable (areacella, areacello) in 'thfareafile' + :param thflandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for THF + :param thflandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'thflandmaskfile' + :param thfbox: string + name of box (nino3') for THF + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return alphaMetric: dict + name, value, value_error, units, method, nyears, time_frequency, time_period, ref, nonlinearity, + nonlinearity_error + + Method: + ------- + uses tools from uvcdat library + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'Thf-Sst feedback (alpha)' + Units = 'W/m2/C' + Method = 'Regression of ' + thfbox + ' thfA over ' + sstbox + ' sstA' + Method_NL = 'The nonlinearity is the regression computed when sstA<0 minus the regression computed when sstA>0' + Ref = 'Using CDAT regression calculation' + metric = 'EnsoFbSstThf' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst, sst_areacell, keyerror1 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, sstbox, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + thf, thf_areacell, keyerror2 = Read_data_mask_area_multifile( + thffile, thfname, 'heat flux', 'thf', metric, thfbox, file_area=thfareafile, name_area=thfareaname, + file_mask=thflandmaskfile, name_mask=thflandmaskname, maskland=True, maskocean=False, debug=debug, + interpreter='project_interpreter_var2', **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst, thf, keyerror3 = CheckTime(sst, thf, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN = int(round(sst.shape[0] / 12)) + + # Time period + actualtimebounds = TimeBounds(sst) + + keyerror = '' + if keyerror1 is not None or keyerror2 is not None or keyerror3 is not None: + alphaThf, alphaThfPos, alphaThfNeg, nl1, nl2 = [None, None], [None, None], [None, None], None, None + dive_down_diag = {'value': None, 'axis': None} + keyerror = add_up_errors([keyerror1, keyerror2, keyerror3]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smooths TS, averages horizontally) + sst, Method, keyerror1 = PreProcessTS( + sst, Method, areacell=sst_areacell, average='horizontal', compute_anom=True, region=sstbox, **kwargs) + thf, _, keyerror2 = PreProcessTS( + thf, '', areacell=thf_areacell, average='horizontal', compute_anom=True, region=thfbox, **kwargs) + del sst_areacell, thf_areacell + if keyerror1 is not None or keyerror2 is not None: + alphaThf, alphaThfPos, alphaThfNeg, nl1, nl2 = [None, None], [None, None], [None, None], None, None + dive_down_diag = {'value': None, 'axis': None} + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'axes2': '(thf) ' + str([ax.id for ax in thf.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape), 'shape2': '(thf) ' + str(thf.shape), + 'time1': '(sst) ' + str(TimeBounds(sst)), 'time2': '(thf) ' + str(TimeBounds(thf))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Computes the linear regression for all points, for SSTA >=0 and for SSTA<=0 + alphaThf, alphaThfPos, alphaThfNeg = LinearRegressionAndNonlinearity( + thf, sst, return_stderr=True, return_intercept=True) + # Non linearities + nl1 = alphaThfNeg[0] - alphaThfPos[0] + nl2 = alphaThfNeg[1] + alphaThfPos[1] + # Dive down diagnostic + dive_down_diag = {'value': None, 'axis': None} + if netcdf is True: + sst_map, sst_map_areacell, keyerror1 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, 'equatorial_pacific', file_area=sstareafile, + name_area=sstareaname, file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, + maskocean=False, debug=debug, **kwargs) + thf_map, thf_map_areacell, keyerror2 = Read_data_mask_area_multifile( + thffile, thfname, 'heat flux', 'thf', metric, 'equatorial_pacific', file_area=thfareafile, + name_area=thfareaname, file_mask=thflandmaskfile, name_mask=thflandmaskname, maskland=True, + maskocean=False, debug=debug, interpreter='project_interpreter_var2', **kwargs) + # Checks if the same time period is used for both variables + sst_map, thf_map, keyerror3 = CheckTime(sst_map, thf_map, metric_name=metric, debug=debug, **kwargs) + if keyerror1 is not None or keyerror2 is not None or keyerror3 is not None: + keyerror = add_up_errors([keyerror1, keyerror2, keyerror3]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smooths TS, ...) + sst_map, _, keyerror1 = PreProcessTS( + sst_map, '', areacell=sst_map_areacell, average=False, compute_anom=True, + region="equatorial_pacific", **kwargs) + thf_map, _, keyerror2 = PreProcessTS( + thf_map, '', areacell=thf_map_areacell, average=False, compute_anom=True, + region="equatorial_pacific", **kwargs) + del thf_map_areacell, sst_map_areacell + if keyerror1 is not None or keyerror2 is not None: + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + # Regridding + if 'regridding' not in list(kwargs.keys()): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'} + else: + if not isinstance(kwargs['regridding'], dict): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + sst_map = Regrid(sst_map, None, region='equatorial_pacific', **kwargs['regridding']) + thf_map = Regrid(thf_map, None, region='equatorial_pacific', **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(thf) ' + str([ax.id for ax in thf_map.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(thf) ' + str(thf_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Regrid', 15, **dict_debug) + # Meridional average + sst_map, keyerror1 = AverageMeridional(sst_map) + thf_map, keyerror2 = AverageMeridional(thf_map) + if keyerror1 is not None or keyerror2 is not None: + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(thf) ' + str([ax.id for ax in thf_map.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(thf) ' + str(thf_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + # Zonal smoothing + sst_map, _ = Smoothing(sst_map, '', axis=1, window=31, method='square') + thf_map, _ = Smoothing(thf_map, '', axis=1, window=31, method='square') + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(thf) ' + str([ax.id for ax in thf_map.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(thf) ' + str(thf_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Smoothing', 15, **dict_debug) + # Array year by year + sst_yby = get_year_by_year(sst_map, frequency=kwargs['frequency']) + thf_yby = get_year_by_year(thf_map, frequency=kwargs['frequency']) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(thf) ' + str([ax.id for ax in thf_yby.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(thf) ' + str(thf_yby.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after get_year_by_year', 15, **dict_debug) + # Computes the linear regression for all points, for SSTA >=0 and for SSTA<=0 + curAlpha, curAlphaPos, curAlphaNeg = LinearRegressionAndNonlinearity( + thf_map, sst_map, return_stderr=False, return_intercept=False) + hovAlpha, hovAlphaPos, hovAlphaNeg = LinearRegressionAndNonlinearity( + thf_yby, sst_yby, return_stderr=False, return_intercept=False) + if debug is True: + dict_debug = {'axes1': '(zonal alpha) ' + str([ax.id for ax in curAlpha.getAxisList()]), + 'axes2': '(hovtx alpha) ' + str([ax.id for ax in hovAlpha.getAxisList()]), + 'shape1': '(zonal alpha) ' + str(curAlpha.shape), + 'shape2': '(hovtx alpha) ' + str(hovAlpha.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after LinearRegressionAndNonlinearity', 15, **dict_debug) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = { + 'units': 'C', 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s " + sstbox + " sstA", 'diagnostic_value': alphaThf[0], + 'diagnostic_value_error': alphaThf[1], 'slope': alphaThf[0], 'intercept': alphaThf[2], + 'slope_neg': alphaThfNeg[0], 'intercept_neg': alphaThfNeg[2], + 'slope_pos': alphaThfPos[0], 'intercept_pos': alphaThfPos[2]} + dict2 = { + 'units': 'W/m2', 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s " + thfbox + " thfA", 'diagnostic_value': alphaThf[0], + 'diagnostic_value_error': alphaThf[1], 'slope': alphaThf[0], 'intercept': alphaThf[2], + 'slope_neg': alphaThfNeg[0], 'intercept_neg': alphaThfNeg[2], + 'slope_pos': alphaThfPos[0], 'intercept_pos': alphaThfPos[2]} + dict3 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal equatorial_pacific regression of thfA over sstA", + 'diagnostic_value': alphaThf[0], 'diagnostic_value_error': alphaThf[1], + 'slope': alphaThf[0], 'intercept': alphaThf[2], 'slope_neg': alphaThfNeg[0], + 'intercept_neg': alphaThfNeg[2], 'slope_pos': alphaThfPos[0], + 'intercept_pos': alphaThfPos[2]} + dict4 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal equatorial_pacific regression of thfA over sstA>0", + 'diagnostic_value': alphaThf[0], 'diagnostic_value_error': alphaThf[1], + 'slope': alphaThf[0], 'intercept': alphaThf[2], 'slope_neg': alphaThfNeg[0], + 'intercept_neg': alphaThfNeg[2], 'slope_pos': alphaThfPos[0], + 'intercept_pos': alphaThfPos[2]} + dict5 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal equatorial_pacific regression of thfA over sstA<0", + 'diagnostic_value': alphaThf[0], 'diagnostic_value_error': alphaThf[1], + 'slope': alphaThf[0], 'intercept': alphaThf[2], 'slope_neg': alphaThfNeg[0], + 'intercept_neg': alphaThfNeg[2], 'slope_pos': alphaThfPos[0], + 'intercept_pos': alphaThfPos[2]} + dict6 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + + "'s zonal monthly of equatorial_pacific regression of thfA over sstA", + 'diagnostic_value': alphaThf[0], 'diagnostic_value_error': alphaThf[1], + 'slope': alphaThf[0], 'intercept': alphaThf[2], 'slope_neg': alphaThfNeg[0], + 'intercept_neg': alphaThfNeg[2], 'slope_pos': alphaThfPos[0], + 'intercept_pos': alphaThfPos[2]} + dict7 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + + "'s zonal monthly of equatorial_pacific regression of thfA over sstA>0", + 'diagnostic_value': alphaThf[0], 'diagnostic_value_error': alphaThf[1], + 'slope': alphaThf[0], 'intercept': alphaThf[2], 'slope_neg': alphaThfNeg[0], + 'intercept_neg': alphaThfNeg[2], 'slope_pos': alphaThfPos[0], + 'intercept_pos': alphaThfPos[2]} + dict8 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + + "'s zonal monthly of equatorial_pacific regression of thfA over sstA<0", + 'diagnostic_value': alphaThf[0], 'diagnostic_value_error': alphaThf[1], + 'slope': alphaThf[0], 'intercept': alphaThf[2], 'slope_neg': alphaThfNeg[0], + 'intercept_neg': alphaThfNeg[2], 'slope_pos': alphaThfPos[0], + 'intercept_pos': alphaThfPos[2]} + dict9 = {'metric_name': Name, 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=sst, var1_attributes=dict1, var1_name='sst__' + dataset, + var1_time_name='months_' + dataset, var2=thf, var2_attributes=dict2, + var2_name='thf__' + dataset, var2_time_name='months_' + dataset, var3=curAlpha, + var3_attributes=dict3, var3_name='reg_thf_over_sst_lon__' + dataset, var4=curAlphaPos, + var4_attributes=dict4, var4_name='reg_thf_over_POSsst_lon__' + dataset, + var5=curAlphaNeg, var5_attributes=dict5, + var5_name='reg_thf_over_NEGsst_lon__' + dataset, var6=hovAlpha, var6_attributes=dict6, + var6_name='reg_thf_over_sst_hov__' + dataset, var7=hovAlphaPos, var7_attributes=dict7, + var7_name='reg_thf_over_POSsst_hov__' + dataset, + var8=hovAlphaNeg, var8_attributes=dict8, + var8_name='reg_thf_over_NEGsst_hov__' + dataset, frequency=kwargs['frequency'], + global_attributes=dict9) + del dict1, dict2, dict3, dict4, dict5, dict6, dict7, dict8, dict9 + # Create output + alphaThfMetric = { + 'name': Name, 'value': alphaThf[0], 'value_error': alphaThf[1], 'units': Units, 'method': Method, + 'method_nonlinearity': Method_NL, 'nyears': yearN, 'time_frequency': kwargs['frequency'], + 'time_period': actualtimebounds, 'ref': Ref, 'nonlinearity': nl1, 'nonlinearity_error': nl2, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return alphaThfMetric + + +def EnsoAmpl(sstfile, sstname, sstareafile, sstareaname, sstlandmaskfile, sstlandmaskname, sstbox, dataset='', + debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The EnsoAmpl() function computes the standard deviation of 'sstbox' sstA (usually the standard deviation of nino3 + sstA) + + Author: Eric Guilyardi : Eric.Guilyardi@locean-ipsl.upmc.fr + Co-author: Yann Planton : yann.planton@locean-ipsl.upmc.fr + + Created on Mon Jan 9 11:05:18 CET 2017 + + Inputs: + ------ + :param sstfile: string + path_to/filename of the file (NetCDF) of SST + :param sstname: string + name of SST variable (tos, ts) in 'sstfile' + :param sstareafile: string + path_to/filename of the file (NetCDF) of the areacell for SST + :param sstareaname: string + name of areacell variable (areacella, areacello) in 'sstareafile' + :param sstlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for SST + :param sstlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfile' + :param sstbox: string + name of box (nino3') for SST + :param dataset: string, optional + name of current dataset (e.g., 'model', 'obs', ...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return amplMetric: dict + name, value, value_error, units, method, nyears, time_frequency, time_period, ref, keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'ENSO amplitude' + Units = 'C' + Method = 'Standard deviation of ' + sstbox + ' sstA' + Ref = 'Using CDAT regression calculation' + metric = 'EnsoAmpl' + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst, sst_areacell, keyerror = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, sstbox, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + + # Number of years + yearN = int(round(sst.shape[0] / 12)) + + # Time period + actualtimebounds = TimeBounds(sst) + + if keyerror is not None: + sstStd, sstStdErr, dive_down_diag = None, None, {'value': None, 'axis': None} + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst, Method, keyerror = PreProcessTS( + sst, Method, areacell=sst_areacell, average='horizontal', compute_anom=True, region=sstbox, **kwargs) + del sst_areacell + if keyerror is not None: + sstStd, sstStdErr, dive_down_diag = None, None, {'value': None, 'axis': None} + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape), 'time1': '(sst) ' + str(TimeBounds(sst))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Computes the standard deviation + sstStd = float(Std(sst)) + + # Standard Error of the Standard Deviation (function of nyears) + sstStdErr = sstStd / NUMPYsqrt(yearN) + + # Dive down diagnostic + dive_down_diag = {'value': None, 'axis': None} + if netcdf is True: + # additional diagnostic + # Read file and select the right region + sst1, sst_areacell1, keyerror1 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, 'equatorial_pacific_LatExt2', file_area=sstareafile, + name_area=sstareaname, file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, + maskocean=False, debug=debug, **kwargs) + sst2, sst_areacell2, keyerror2 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, 'equatorial_pacific', file_area=sstareafile, + name_area=sstareaname, file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, + maskocean=False, debug=debug, **kwargs) + if keyerror1 is not None or keyerror2 is not None: + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, ...) + sst1, _, keyerror1 = PreProcessTS( + sst1, '', areacell=sst_areacell1, compute_anom=True, region="equatorial_pacific_LatExt2", + **kwargs) + sst2, _, keyerror2 = PreProcessTS( + sst2, '', areacell=sst_areacell2, compute_anom=True, region="equatorial_pacific", **kwargs) + del sst_areacell1, sst_areacell2 + if keyerror1 is not None or keyerror2 is not None: + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + if debug is True: + dict_debug = {'axes1': '(sst1) ' + str([ax.id for ax in sst1.getAxisList()]), + 'axes2': '(sst2) ' + str([ax.id for ax in sst2.getAxisList()]), + 'shape1': '(sst1) ' + str(sst1.shape), 'shape2': '(sst2) ' + str(sst2.shape), + 'time1': '(sst1) ' + str(TimeBounds(sst1)), + 'time2': '(sst2) ' + str(TimeBounds(sst2))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 10, **dict_debug) + # std + sst1 = Std(sst1) + sst2 = Std(sst2) + # Regridding + if 'regridding' not in list(kwargs.keys()): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'} + else: + if not isinstance(kwargs['regridding'], dict): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + sst1 = Regrid(sst1, None, region='equatorial_pacific_LatExt2', **kwargs['regridding']) + sst2 = Regrid(sst2, None, region='equatorial_pacific', **kwargs['regridding']) + # Meridional average + sst2, keyerror = AverageMeridional(sst2) + if keyerror is not None: + dive_down_diag = {'value': None, 'axis': None} + else: + # Dive down diagnostic + dive_down_diag = {'value': ArrayToList(sst2), 'axis': list(sst2.getAxis(0)[:])} + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': "zonal standard deviation of equatorial_pacific sstA", + 'diagnostic_value': sstStd, 'diagnostic_value_error': sstStdErr} + dict2 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': "standard deviation of equatorial_pacific sstA"} + dict3 = {'metric_name': Name, 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + SaveNetcdf(file_name, var1=sst2, var1_attributes=dict1, var1_name='sstStd_lon__' + dataset, + var2=sst1, var2_attributes=dict2, var2_name='sstStd_map__' + dataset, + global_attributes=dict3) + del dict1, dict2, dict3 + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(sstStd), 'line2': 'metric value_error: ' + str(sstStdErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + amplMetric = { + 'name': Name, 'value': sstStd, 'value_error': sstStdErr, 'units': Units, 'method': Method, 'nyears': yearN, + 'time_frequency': kwargs['frequency'], 'time_period': actualtimebounds, 'ref': Ref, 'keyerror': keyerror, + 'dive_down_diag': dive_down_diag} + return amplMetric + + +def EnsoDiversity(sstfile, sstname, sstareafile, sstareaname, sstlandmaskfile, sstlandmaskname, box, event_definition, + dataset='', debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The EnsoDiversity() function computes a zonal composite of El Nino and La Nina events during the peak of the event. + 1.) detect events + 1.1) SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + 1.2) SSTA > (<) 'threshold' during 'season' are considered as El Nino (La Nina) events + 2.) diversity of the zonal location of the maximum (minimum) SSTA + 2.1) zonal SSTA at the peak of the event is computed for each selected event + 2.2) find the zonal position of the maximum (minimum) SSTA for each selected event + 2.3) compute the percentage of EP events (maximum/minimum SSTA eastward of the given threshold) + 2.4) compute the ratio EP events during La Nina divided by EP events during El Nino + + Inputs: + ------ + :param sstfile: string + path_to/filename of the file (NetCDF) of the SST + :param sstname: string + name of SST variable (tos, ts) in 'sstfile' + :param sstareafile: string + path_to/filename of the file (NetCDF) of the areacell for SST + :param sstareaname: string + name of areacell variable (areacella, areacello) in 'sstareafile' + :param sstlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for SST + :param sstlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfile' + :param box: string + name of box ('nino3') for SST + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': 0.75} + :param nbr_years_window: integer + number of years used to compute the composite (e.g. 6) + :param dataset: string, optional + name of current dataset (e.g., 'model', 'obs', ...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param treshold_ep_ev: float, optional + see EnsoToolsLib.percentage_val_eastward + longitude, in degree east, of the westward boundary of eastern Pacific event + default value is -140°E (i.e., 140°W) + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return NinaDivMetric: dict + name, value, value_error, units, method, nyears, events, time_frequency, time_period, ref, keyerror, + dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'treshold_ep_ev', + 'time_bounds'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'ENSO Diversity (percentage of eastern Pacific El Nino / La Nina)' + lat = ReferenceRegions(box)['latitude'] + lon = ReferenceRegions(box)['longitude'] + Method = 'Nino (Nina) events = ' + region_ev + ' sstA > (<) ' + str(threshold) + ' during ' + season_ev +\ + ', zonal SSTA ' + '(meridional averaged [' + str(lat[0]) + ' ; ' + str(lat[1]) +\ + ']), westward boundary of EP events ' + str(kwargs['treshold_ep_ev']) + 'E' + Units = '%' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = 'EnsoDiversity' + if metname == '': + metname = deepcopy(metric) + + # ------------------------------------------------ + # 1. detect events + # ------------------------------------------------ + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst, areacell, keyerror = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, region_ev, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + + # Number of years + yearN = int(round(sst.shape[0] / 12)) + + # Time period + actualtimebounds = TimeBounds(sst) + + if keyerror is not None: + ratioEP, StdErr, nino_years, nina_years = None, None, None, None + dive_down_diag = {'value': None, 'axis': None} + else: + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst, _, keyerror = PreProcessTS( + sst, '', areacell=areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + del areacell + if keyerror is not None: + ratioEP, StdErr, nino_years, nina_years = None, None, None, None + dive_down_diag = {'value': None, 'axis': None} + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape), 'time1': '(sst) ' + str(TimeBounds(sst))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 SSTA > (<) 'threshold' during 'season' are considered as El Nino (La Nina) events + # Lists event years + nino_years = DetectEvents(sst, season_ev, threshold, normalization=normalize, nino=True) + nina_years = DetectEvents(sst, season_ev, -threshold, normalization=normalize, nino=False) + if debug is True: + dict_debug = {'nino1': 'nbr(' + str(len(nino_years)) + '): ' + str(nino_years), + 'nina1': 'nbr(' + str(len(nina_years)) + '): ' + str(nina_years)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + + # ------------------------------------------------ + # 2. diversity of the zonal location of the minimum SSTA + # ------------------------------------------------ + # Read file and select the right region + sst, areacell, keyerror = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, box, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskfile, maskland=True, maskocean=False, debug=debug, + **kwargs) + if keyerror is not None: + ratioEP, StdErr, nino_years, nina_years = None, None, None, None + dive_down_diag = {'value': None, 'axis': None} + else: + # 2.1 zonal SSTA at the peak of the event is computed for each selected event + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst, Method, keyerror = PreProcessTS( + sst, Method, areacell=areacell, average=False, compute_anom=False, region=box, **kwargs) + del areacell + if keyerror is not None: + ratioEP, StdErr, nino_years, nina_years = None, None, None, None + dive_down_diag = {'value': None, 'axis': None} + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape), 'time1': '(sst) ' + str(TimeBounds(sst))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Seasonal mean + sst = SeasonalMean(sst, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', 'regridTool', + 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + sst = Regrid(sst, None, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Meridional average + sst, keyerror = AverageMeridional(sst) + if keyerror is not None: + ratioEP, StdErr, dive_down_diag = None, None, {'value': None, 'axis': None} + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + + # samples + sample_nino = Event_selection(sst, kwargs['frequency'], list_event_years=nino_years) + sample_nina = Event_selection(sst, kwargs['frequency'], list_event_years=nina_years) + + # 2.2 find the zonal position of the maximum/minimum SSTA for each selected event + lon_sstmax = FindXYMinMaxInTs( + sample_nino, return_val='maxi', smooth=True, axis=0, window=5, method='triangle') + lon_sstmin = FindXYMinMaxInTs( + sample_nina, return_val='mini', smooth=True, axis=0, window=5, method='triangle') + if debug is True: + dict_debug = {'line1': 'longitude of the maximum SSTA (nino): ' + str(lon_sstmax), + 'line2': 'longitude of the minimum SSTA (nina): ' + str(lon_sstmin)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after FindXYMinMaxInTs', 15, **dict_debug) + + # 2.3 compute the percentage of EP events (maximum/minimum SSTA eastward of the given threshold) + ep_event_nino, keyerror_nino = percentage_val_eastward( + lon_sstmax, metric, box, threshold=kwargs['treshold_ep_ev']) + ep_event_nina, keyerror_nina = percentage_val_eastward( + lon_sstmin, metric, box, threshold=kwargs['treshold_ep_ev']) + + if keyerror_nino is not None or keyerror_nina is not None: + ratioEP, StdErr, dive_down_diag = None, None, {'value': None, 'axis': None} + keyerror = add_up_errors([keyerror_nino, keyerror_nina]) + else: + if debug is True: + dict_debug = {'nino1': 'percentage of EP event + ' + str(ep_event_nino), + 'nina1': 'percentage of EP event + ' + str(ep_event_nina)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + + # 2.4 compute the ratio EP events during La Nina divided by EP events during El Nino + ratioEP = float(ep_event_nina / ep_event_nino) + # Standard Error of the Standard Deviation (function of nyears) + StdErr = None + + # Dive down diagnostic + dive_down_diag = {'value': ArrayToList(lon_sstmax), 'axis': list(lon_sstmax.getAxis(0)[:])} + if netcdf is True: + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': 'longitude (E)', 'number_of_years_used': yearN, + 'time_period': str(actualtimebounds), 'nino_years': str(nino_years), + 'diagnostic_value_' + dataset: ratioEP, + 'diagnostic_value_error_' + dataset: StdErr} + dict2 = {'units': 'longitude (E)', 'number_of_years_used': yearN, + 'time_period': str(actualtimebounds), 'nina_years': str(nina_years), + 'diagnostic_value_' + dataset: ratioEP, + 'diagnostic_value_error_' + dataset: StdErr} + dict3 = {'metric_name': Name, 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + SaveNetcdf(file_name, var1=lon_sstmax, var1_attributes=dict1, + var1_name='Nino_lon_pos_maxSSTA__' + dataset, var2=lon_sstmin, + var2_attributes=dict2, var2_name='Nina_lon_pos_minSSTA__' + dataset, + global_attributes=dict3) + del dict1, dict2, dict3 + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(ratioEP), 'line2': 'metric value_error: ' + str(StdErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + EnsoDivMetric = { + 'name': Name, 'value': ratioEP, 'value_error': StdErr, 'units': Units, 'method': Method, 'nyears': yearN, + 'events': nino_years+nina_years, 'time_frequency': kwargs['frequency'], 'time_period': actualtimebounds, + 'ref': Ref, 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return EnsoDivMetric + + +def EnsoDuration(sstfile, sstname, sstareafile, sstareaname, sstlandmaskfile, sstlandmaskname, box, event_definition, + nbr_years_window, dataset='', debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The EnsoDuration() function computes sea surface temperature anomalies life cycle associated with ENSO in a 'box' + (usually the nino3.4) with a window of 'nbr_years_window' centered on ENSO (nbr_years_window/2 leading and lagging + ENSO), the duration is then the period, around the peak, during which the life cycle is above 0.25. + + Inputs: + ------ + :param sstfile: string + path_to/filename of the file (NetCDF) of the SST + :param sstname: string + name of SST variable (tos, ts) in 'sstfile' + :param sstareafile: string + path_to/filename of the file (NetCDF) of the areacell for SST + :param sstareaname: string + name of areacell variable (areacella, areacello) in 'sstareafile' + :param sstlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for SST + :param sstlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfile' + :param box: string + name of box (e.g. 'nino3.4') for SST + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': -0.75} + :param nbr_years_window: integer + number of years used to compute the composite (e.g. 6) + :param dataset: string, optional + name of current dataset (e.g., 'model', 'obs', ...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return EnsoDurMetric: dict + name, value, value_error, units, method, nyears, time_frequency, time_period, ref, dive_down_diag + + Method: + ------- + uses tools from CDAT library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = "ENSO Duration based on life cyle SSTA pattern" + Method = region_ev + " SSTA during " + season_ev + " regressed against " + region_ev + " SSTA during " +\ + str(nbr_years_window) + " years (centered on ENSO), the duration is the number of consecutive months " +\ + "during which the regression is above 0.25" + Units = 'months' + Ref = 'Using CDAT' + metric = 'EnsoDuration' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst, areacell, keyerror = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, region_ev, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + + # Number of years + yearN = int(round(sst.shape[0] / 12)) + + # Time period + actualtimebounds = TimeBounds(sst) + + if keyerror is not None: + duration, duration_err = None, None + dive_down_diag = {'model': None, 'axis': None} + else: + # ------------------------------------------------ + # 1. box SSTA + # ------------------------------------------------ + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + enso, _, keyerror = PreProcessTS( + sst, '', areacell=areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + if keyerror is not None: + duration, duration_err = None, None + dive_down_diag = {'model': None, 'axis': None} + else: + if debug is True: + dict_debug = {'axes1': str([ax.id for ax in enso.getAxisList()]), 'shape1': str(enso.shape), + 'time1': str(TimeBounds(enso))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 Seasonal mean and anomalies + enso = SeasonalMean(enso, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': str([ax.id for ax in enso.getAxisList()]), + 'shape1': str(enso.shape), 'time1': str(TimeBounds(enso))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # ------------------------------------------------ + # 2. temporal SSTA + # ------------------------------------------------ + # 2.1 SSTA in 'box' are normalized / detrended / smoothed (running average) if applicable + # Preprocess ts (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst, Method, keyerror = PreProcessTS( + sst, Method, areacell=areacell, average='horizontal', compute_anom=True, region=region_ev, **kwargs) + del areacell + if keyerror is not None: + duration, duration_err = None, None + dive_down_diag = {'model': None, 'axis': None} + else: + if debug is True: + dict_debug = {'axes1': str([ax.id for ax in sst.getAxisList()]), 'shape1': str(sst.shape), + 'time1': str(TimeBounds(sst))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # ------------------------------------------------ + # 3. Regression map + # ------------------------------------------------ + sstts = LinearRegressionTsAgainstTs( + sst, enso, nbr_years_window, return_stderr=False, frequency=kwargs['frequency'], debug=debug) + if debug is True: + dict_debug = {'axes1': str([ax.id for ax in sstts.getAxisList()]), 'shape1': str(sstts.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after LinearRegressionTsAgainstTs', 15, **dict_debug) + + # ------------------------------------------------ + # 4. Duration + # ------------------------------------------------ + # 4.1 count the number of consecutive month above a threshold + duration = float(DurationEvent(sstts, 0.25, nino=True, debug=debug)) + + # Error on the metric + duration_err = None + + # Dive down diagnostic + dive_down_diag = {'value': ArrayToList(sstts), 'axis': list(sstts.getAxis(0)[:])} + + if netcdf is True: + # Lists event years + nina_years = DetectEvents( + enso, season_ev, -threshold, normalization=normalize, nino=False, compute_season=False) + nino_years = DetectEvents( + enso, season_ev, threshold, normalization=normalize, nino=True, compute_season=False) + if debug is True: + dict_debug = {'nina1': 'nbr(' + str(len(nina_years)) + '): ' + str(nina_years), + 'nino1': 'nbr(' + str(len(nino_years)) + '): ' + str(nino_years)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + # composites + if len(nina_years) > 0: + sample = Event_selection( + sst, kwargs['frequency'], nbr_years_window=nbr_years_window, list_event_years=nina_years) + # count the number of consecutive month bellow a threshold + if normalize is True: + nina_dur_all = DurationAllEvent(sample, -0.5 * float(Std(enso)), nino=False, debug=debug) + else: + nina_dur_all = DurationAllEvent(sample, -0.5, nino=False, debug=debug) + nina_dur_mean = float(nina_dur_all.mean()) + nina_dur_err = float(Std(nina_dur_all) / NUMPYsqrt(len(nina_dur_all))) + else: + nina_dur_all = MyEmpty(sst[:5], time=True, time_id='years') + nina_dur_mean = None + nina_dur_err = None + if len(nino_years) > 0: + sample = Event_selection( + sst, kwargs['frequency'], nbr_years_window=nbr_years_window, list_event_years=nino_years) + # count the number of consecutive month above a threshold + if normalize is True: + nino_dur_all = DurationAllEvent(sample, 0.5 * float(Std(enso)), nino=True, debug=debug) + else: + nino_dur_all = DurationAllEvent(sample, 0.5, nino=True, debug=debug) + nino_dur_mean = float(nino_dur_all.mean()) + nino_dur_err = float(Std(nino_dur_all) / NUMPYsqrt(len(nino_dur_all))) + else: + nino_dur_all = MyEmpty(sst[:5], time=True, time_id='years') + nino_dur_mean = None + nino_dur_err = None + if debug is True: + dict_debug = {'nina1': 'mean(' + str(nina_dur_mean) + '): ' + str(nina_dur_all), + 'nino1': 'mean(' + str(nino_dur_mean) + '): ' + str(nino_dur_all)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DurationAllEvent', 15, **dict_debug) + # save + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + my_thresh = 'std' if normalize is True else 'C' + dict1 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': "time series of " + region_ev + " SSTA " + season_ev + " regressed against " + + region_ev + " SSTA during " + str(nbr_years_window) + + " years (centered on ENSO)", + 'diagnostic_value': duration, 'diagnostic_value_error': duration_err} + dict2 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'nina_years': str(nina_years), + 'description': "Nina events = " + region_ev + " SSTA < -" + str(threshold) + my_thresh + + " during " + season_ev + ", duration is the number of consecutive months " + + "during which SSTA < -0.5" + my_thresh, + 'diagnostic_value': nina_dur_mean, 'diagnostic_value_error': nina_dur_err} + dict3 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'nino_years': str(nino_years), + 'description': "Nino events = " + region_ev + " SSTA > " + str(threshold) + my_thresh + + " during " + season_ev + ", duration is the number of consecutive months " + + "during which SSTA > 0.5" + my_thresh, + 'diagnostic_value': nino_dur_mean, 'diagnostic_value_error': nino_dur_err} + dict4 = {'metric_name': Name, 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + if debug is True: + dict_debug = {'axes1': str([ax.id for ax in sstts.getAxisList()]), 'shape1': str(sstts.shape), + 'var1': 'sst_against_sst_ts__' + dataset, + 'axes2': str([ax.id for ax in nina_dur_all.getAxisList()]), + 'nina2': str(len(str(nina_years)))+" "+str(nina_years), + 'shape2': str(nina_dur_all.shape), 'var2': 'Nina_duration__' + dataset, + 'axes3': str([ax.id for ax in nino_dur_all.getAxisList()]), + 'nino3': str(len(str(nino_years)))+" "+str(nina_years), + 'shape3': str(nino_dur_all.shape), 'var3': 'Nino_duration__' + dataset} + EnsoErrorsWarnings.debug_mode('\033[92m', 'before SaveNetcdf', 15, **dict_debug) + SaveNetcdf( + file_name, var1=sstts, var1_attributes=dict1, var1_name='sst_against_sst_ts__' + dataset, + var2=nina_dur_all, var2_attributes=dict2, var2_name='Nina_duration__' + dataset, + var3=nino_dur_all, var3_attributes=dict3, var3_name='Nino_duration__' + dataset, + global_attributes=dict4) + del dict1, dict2, dict3, dict4 + # Create output + EnsoDurMetric = { + 'name': Name, 'value': duration, 'value_error': duration_err, 'units': Units, 'method': Method, 'nyears': yearN, + 'time_frequency': kwargs['frequency'], 'time_period': actualtimebounds, 'ref': Ref, 'keyerror': keyerror, + 'dive_down_diag': dive_down_diag} + return EnsoDurMetric + + +def EnsodSstOce(sstfile, sstname, sstareafile, sstareaname, sstlandmaskfile, sstlandmaskname, sstbox, + thffile, thfname, thfareafile, thfareaname, thflandmaskfile, thflandmaskname, thfbox, + event_definition, dataset='', debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The EnsodSstOce() function computes an estimation of the SST change caused by an anomalous ocean circulation + (usually in nino3) + For this, the (THF) total heat flux is integrated from June to December (representing SST change driven by heat + fluxes) and subtracted to the SST change during this period. dSSToce = dSST - dSSTthf + The total heat flux is the sum of four term: + - net surface shortwave radiation, + - net surface longwave radiation, + - latent heat flux, + - sensible heat flux + + The total heat flux is not always available is models or observations. + Either the user computes it and sends the filename and the varname or he feeds into thffile and thfname of this + function a list() of the four needed files and variable names (CMIP: rsds-rsus, rlds-rlus, hfls, hfss) + + Author: Yann Planton : yann.planton@locean-ipsl.upmc.fr + Co-author: + + Created on Thu Jul 18 2019 + + Based on: + Bayr, T., C. Wengel, M. Latif, D. Dommenget, J. Lübbecke, W. Park (2018) Error compensation of ENSO atmospheric + feedbacks in climate models and its influence on simulated ENSO dynamics. Clim. Dyn., doi:10.1007/s00382-018-4575-7 + + Inputs: + ------ + :param sstfile: string + path_to/filename of the file (NetCDF) of SST + :param sstname: string + name of SST variable (tos, ts) in 'sstfile' + :param sstareafile: string + path_to/filename of the file (NetCDF) of the areacell for SST + :param sstareaname: string + name of areacell variable (areacella, areacello) in 'sstareafile' + :param sstlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for SST + :param sstlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfile' + :param sstbox: string + name of box (nino3') for SST + :param thffile: string + path_to/filename of the file (NetCDF) of THF + :param thfname: string + name of THF variable (thf, netflux, thflx, thf + lwr + lhf + shf) (may be a list of variables) in 'thffile' + :param thfareafile: string + path_to/filename of the file (NetCDF) of the areacell for THF + :param thfareaname: string + name of areacell variable (areacella, areacello) in 'thfareafile' + :param thflandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for THF + :param thflandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'thflandmaskfile' + :param thfbox: string + name of box (nino3') for THF + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': -0.75} + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return SlabOceanMetric: dict + name, value, value_error, units, method, nyears, time_frequency, time_period, ref, nonlinearity, + nonlinearity_error + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'SST change caused by an anomalous ocean circulation (dSSToce)' + Units = 'C/C' + Method = 'Nino (Nina) events = ' + region_ev + ' sstA > ' + str(threshold) + ' (< -' + str(threshold) + ') during '\ + + season_ev + ', dSSToce = dSST - dSSTthf during ENSO events (relative difference between ' + sstbox +\ + ' SST change and heat flux-driven ' + thfbox + ' SST change in ' + Ref = 'Using CDAT' + metric = 'EnsodSstOce' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + enso, enso_areacell, keyerror1 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, region_ev, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + sst, sst_areacell, keyerror2 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, sstbox, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + thf, thf_areacell, keyerror3 = Read_data_mask_area_multifile( + thffile, thfname, 'heat flux', 'thf', metric, thfbox, file_area=thfareafile, name_area=thfareaname, + file_mask=thflandmaskfile, name_mask=thflandmaskname, maskland=True, maskocean=False, debug=debug, + interpreter='project_interpreter_var2', **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst, thf, keyerror4 = CheckTime(sst, thf, metric_name=metric, debug=debug, **kwargs) + sst, enso, _ = CheckTime(sst, enso, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN = int(round(sst.shape[0] / 12)) + + # Time period + actualtimebounds = TimeBounds(sst) + + if keyerror1 is not None or keyerror2 is not None or keyerror3 is not None or keyerror4 is not None: + metric, metricErr, dive_down_diag = None, None, {'value': None, 'value2': None, 'value3': None, 'axis': None} + nina_years, nino_years = list(), list() + keyerror = add_up_errors([keyerror1, keyerror2, keyerror3, keyerror4]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smooths TS, averages horizontally) + enso, _, keyerror1 = PreProcessTS( + enso, '', areacell=enso_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + # if 'smoothing' in kwargs.keys(): + # smooth = deepcopy(kwargs['smoothing']) + # kwargs['smoothing'] = False + sst, Method, keyerror2 = PreProcessTS( + sst, Method, areacell=sst_areacell, average='horizontal', compute_anom=True, region=sstbox, **kwargs) + thf, _, keyerror3 = PreProcessTS( + thf, '', areacell=thf_areacell, average='horizontal', compute_anom=True, region=thfbox, **kwargs) + del enso_areacell, sst_areacell, thf_areacell + if keyerror1 is not None or keyerror2 is not None or keyerror3 is not None: + metric, metricErr = None, None, + dive_down_diag = {'value': None, 'value2': None, 'value3': None, 'axis': None} + nina_years, nino_years = list(), list() + keyerror = add_up_errors([keyerror1, keyerror2, keyerror3]) + else: + keyerror = None + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'axes2': '(thf) ' + str([ax.id for ax in thf.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape), 'shape2': '(thf) ' + str(thf.shape), + 'time1': '(sst) ' + str(TimeBounds(sst)), 'time2': '(thf) ' + str(TimeBounds(thf))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + # Lists event years + nina_years = DetectEvents( + enso, season_ev, -threshold, normalization=normalize, nino=False, compute_season=True) + nino_years = DetectEvents( + enso, season_ev, threshold, normalization=normalize, nino=True, compute_season=True) + if debug is True: + dict_debug = {'nina1': str(nina_years), 'nino1': str(nino_years)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + # SST change + dSST, dSSTthf, dSSToce = SlabOcean( + sst, thf, 'JUN', 'DEC', nina_years + nino_years, frequency=kwargs['frequency'], debug=debug) + # Mean SST change caused by an anomalous ocean circulation during ENSO events + metric = float(dSSToce[-1]) + metricErr = None + # Dive down diagnostic + dive_down_diag = {'value': ArrayToList(dSST), 'value2': ArrayToList(dSSTthf), + 'value3': ArrayToList(dSSToce), 'axis': list(dSST.getAxis(0)[:])} + if netcdf is True: + sst_map, sst_map_areacell, keyerror1 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, 'equatorial_pacific', file_area=sstareafile, + name_area=sstareaname, file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, + maskocean=False, debug=debug, **kwargs) + thf_map, thf_map_areacell, keyerror2 = Read_data_mask_area_multifile( + thffile, thfname, 'heat flux', 'thf', metric, 'equatorial_pacific', file_area=thfareafile, + name_area=thfareaname, file_mask=thflandmaskfile, name_mask=thflandmaskname, maskland=True, + maskocean=False, debug=debug, interpreter='project_interpreter_var2', **kwargs) + # Checks if the same time period is used for both variables + sst_map, thf_map, keyerror3 = CheckTime(sst_map, thf_map, metric_name=metric, debug=debug, **kwargs) + if keyerror1 is not None or keyerror2 is not None or keyerror3 is not None: + keyerror = add_up_errors([keyerror1, keyerror2, keyerror3]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smooths TS, ...) + sst_map, _, keyerror1 = PreProcessTS( + sst_map, '', areacell=sst_map_areacell, average=False, compute_anom=True, + region="equatorial_pacific", **kwargs) + thf_map, _, keyerror2 = PreProcessTS( + thf_map, '', areacell=thf_map_areacell, average=False, compute_anom=True, + region="equatorial_pacific", **kwargs) + del thf_map_areacell, sst_map_areacell + if keyerror1 is not None or keyerror2 is not None: + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + # Regridding + if 'regridding' not in list(kwargs.keys()): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'} + else: + if not isinstance(kwargs['regridding'], dict): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + sst_map = Regrid(sst_map, None, region='equatorial_pacific', **kwargs['regridding']) + thf_map = Regrid(thf_map, None, region='equatorial_pacific', **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(thf) ' + str([ax.id for ax in thf_map.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), 'shape2': '(thf) ' + str(thf_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Regrid', 15, **dict_debug) + # Meridional average + sst_map, keyerror1 = AverageMeridional(sst_map) + thf_map, keyerror2 = AverageMeridional(thf_map) + if keyerror1 is not None or keyerror2 is not None: + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(thf) ' + str([ax.id for ax in thf_map.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(thf) ' + str(thf_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + # Zonal smoothing + sst_map, _ = Smoothing(sst_map, '', axis=1, window=51, method='square') + thf_map, _ = Smoothing(thf_map, '', axis=1, window=51, method='square') + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(thf) ' + str([ax.id for ax in thf_map.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(thf) ' + str(thf_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Smoothing', 15, **dict_debug) + # SST change + hovdSST, hovdSSTthf, hovdSSToce = SlabOcean( + sst_map, thf_map, 'JUN', 'DEC', nina_years + nino_years, frequency=kwargs['frequency'], + debug=debug) + curdSSTthf, curdSSToce = hovdSSTthf[-1], hovdSSToce[-1] + if debug is True: + dict_debug = { + 'axes1': '(zonal curdSSTthf) ' +str([ax.id for ax in curdSSTthf.getAxisList()]), + 'axes2': '(hovtx hovdSST) ' + str([ax.id for ax in hovdSST.getAxisList()]), + 'shape1': '(zonal curdSSTthf) ' + str(curdSSTthf.shape), + 'shape2': '(hovtx hovdSST) ' + str(hovdSST.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SlabOcean', 15, **dict_debug) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s SST change in " + sstbox, 'diagnostic_value': metric, + 'diagnostic_value_error': metricErr, 'nina_years': str(nina_years), + 'nino_years': str(nino_years)} + dict2 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s heat flux-driven SST change in " + thfbox, + 'diagnostic_value': metric, 'diagnostic_value_error': metricErr, + 'nina_years': str(nina_years), 'nino_years': str(nino_years)} + dict3 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s SST change caused by an anomalous ocean circulation in " + + thfbox, + 'diagnostic_value': metric, 'diagnostic_value_error': metricErr, + 'nina_years': str(nina_years), 'nino_years': str(nino_years)} + dict4 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal heat flux-driven SST change", + 'diagnostic_value': metric, 'diagnostic_value_error': metricErr, + 'nina_years': str(nina_years), 'nino_years': str(nino_years)} + dict5 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal SST change caused by an anomalous ocean circulation", + 'diagnostic_value': metric, 'diagnostic_value_error': metricErr, + 'nina_years': str(nina_years), 'nino_years': str(nino_years)} + dict6 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal monthly SST change", 'diagnostic_value': metric, + 'diagnostic_value_error': metricErr, 'nina_years': str(nina_years), + 'nino_years': str(nino_years)} + dict7 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal monthly heat flux-driven SST change", + 'diagnostic_value': metric, 'diagnostic_value_error': metricErr, + 'nina_years': str(nina_years), 'nino_years': str(nino_years)} + dict8 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + + "'s zonal monthly SST change caused by an anomalous ocean circulation", + 'diagnostic_value': metric, 'diagnostic_value_error': metricErr, + 'nina_years': str(nina_years), 'nino_years': str(nino_years)} + dict9 = {'metric_name': Name, 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=dSST, var1_attributes=dict1, var1_name='dSST_ts__' + dataset, + var2=dSSTthf, var2_attributes=dict2, var2_name='dSSTthf_ts__' + dataset, var3=dSSToce, + var3_attributes=dict3, var3_name='dSSToce_ts__' + dataset, var4=curdSSTthf, + var4_attributes=dict4, var4_name='dSSTthf_lon__' + dataset, var5=curdSSToce, + var5_attributes=dict5, var5_name='dSSToce_lon__' + dataset, var6=hovdSST, + var6_attributes=dict6, var6_name='dSST_hov__' + dataset, var7=hovdSSTthf, + var7_attributes=dict7, var7_name='dSSTthf_hov__' + dataset, var8=hovdSSToce, + var8_attributes=dict8, var8_name='dSSToce_hov__' + dataset, + frequency=kwargs['frequency'], global_attributes=dict9) + del dict1, dict2, dict3, dict4, dict5, dict6, dict7, dict8, dict9 + # if 'smoothing' in kwargs.keys(): + # kwargs['smoothing'] = smooth + # del smooth + # Create output + SlabOceanMetric = { + 'name': Name, 'value': metric, 'value_error': metricErr, 'units': Units, 'method': Method, 'nyears': yearN, + 'events': sorted(nina_years + nino_years), 'time_frequency': kwargs['frequency'], + 'time_period': actualtimebounds, 'ref': Ref, 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return SlabOceanMetric + + +def EnsoFbSstTaux(sstfile, sstname, sstareafile, sstareaname, sstlandmaskfile, sstlandmaskname, sstbox, tauxfile, + tauxname, tauxareafile, tauxareaname, tauxlandmaskfile, tauxlandmaskname, tauxbox, dataset='', + debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The EnsoFbSstTaux() function computes the regression of 'tauxbox' tauxA (surface downward zonal stress anomalies) + over 'sstbox' sstA (usually the regression of nino4 tauxA over nino3 sstA) + + Author: Eric Guilyardi : Eric.Guilyardi@locean-ipsl.upmc.fr + Co-author: Yann Planton : yann.planton@locean-ipsl.upmc.fr + + Created on Mon Jan 9 11:05:18 CET 2017 + + Inputs: + ------ + :param sstfile: string + path_to/filename of the file (NetCDF) of SST + :param sstname: string + name of SST variable (tos, ts) in 'sstfile' + :param sstareafile: string + path_to/filename of the file (NetCDF) of the areacell for SST + :param sstareaname: string + name of areacell variable (areacella, areacello) in 'sstareafile' + :param sstlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for SST + :param sstlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfile' + :param sstbox: string + name of box ('nino3') for SST + :param tauxfile: string + path_to/filename of the file (NetCDF) of TAUX + :param tauxname: string + name of TAUX variable (taux, tauu) in 'tauxfile' + :param tauxareafile: string + path_to/filename of the file (NetCDF) of the areacell for TAUX + :param tauxareaname: string + name of areacell variable (areacella, areacello) in 'tauxareafile' + :param tauxlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for TAUX + :param tauxlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'tauxlandmaskfile' + :param tauxbox: string + name of box ('nino4') for TAUX + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return muMetric: dict + name, value, value_error, units, method, nyears, time_frequency, time_period, ref, nonlinearity, + nonlinearity_error + + Method: + ------- + uses tools from uvcdat library + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'Taux-Sst feedback (mu)' + Units = '1e-3 N/m2/C' + Method = 'Regression of ' + tauxbox + ' tauxA over ' + sstbox + ' sstA' + Method_NL = 'The nonlinearity is the regression computed when sstA<0 minus the regression computed when sstA>0' + Ref = 'Using CDAT regression calculation' + metric = 'EnsoFbSstTaux' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst, sst_areacell, keyerror1 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, sstbox, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + taux, taux_areacell, keyerror2 = Read_data_mask_area( + tauxfile, tauxname, 'wind stress', metric, tauxbox, file_area=tauxareafile, name_area=tauxareaname, + file_mask=tauxlandmaskfile, name_mask=tauxlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst, taux, keyerror3 = CheckTime(sst, taux, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN = int(round(sst.shape[0] / 12)) + + # Time period + actualtimebounds = TimeBounds(sst) + + keyerror = add_up_errors([keyerror1, keyerror2, keyerror3]) + if keyerror is not None: + mu, muPos, muNeg, nl1, nl2 = [None, None], [None, None], [None, None], None, None + dive_down_diag = {'value': None, 'axis': None} + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smooths TS, averages horizontally) + sst, Method, keyerror1 = PreProcessTS( + sst, Method, areacell=sst_areacell, average='horizontal', compute_anom=True, region=sstbox, **kwargs) + taux, _, keyerror2 = PreProcessTS( + taux, '', areacell=taux_areacell, average='horizontal', compute_anom=True, region=tauxbox, **kwargs) + del sst_areacell, taux_areacell + if keyerror1 is not None or keyerror2 is not None: + mu, muPos, muNeg, nl1, nl2 = [None, None], [None, None], [None, None], None, None + dive_down_diag = {'value': None, 'axis': None} + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'axes2': '(taux) ' + str([ax.id for ax in taux.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape), 'shape2': '(taux) ' + str(taux.shape), + 'time1': '(sst) ' + str(TimeBounds(sst)), 'time2': '(taux) ' + str(TimeBounds(taux))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + # Change units + taux = taux * 1e3 + # Computes the linear regression for all points, for SSTA >=0 and for SSTA<=0 + mu, muPos, muNeg = LinearRegressionAndNonlinearity(taux, sst, return_stderr=True, return_intercept=True) + # Non linearities + nl1 = muNeg[0] - muPos[0] + nl2 = muNeg[1] + muPos[1] + # Dive down diagnostic + dive_down_diag = {'value': None, 'axis': None} + if netcdf is True: + taux_map, taux_map_areacell, keyerror1 = Read_data_mask_area( + tauxfile, tauxname, 'wind stress', metric, 'equatorial_pacific', file_area=tauxareafile, + name_area=tauxareaname, file_mask=tauxlandmaskfile, name_mask=tauxlandmaskname, maskland=True, + maskocean=False, debug=debug, **kwargs) + # Checks if the same time period is used for both variables + sst, taux_map, keyerror2 = CheckTime(sst, taux_map, metric_name=metric, debug=debug, **kwargs) + if keyerror1 is not None or keyerror2 is not None: + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smooths TS, ...) + taux_map, _, keyerror = PreProcessTS( + taux_map, '', areacell=taux_map_areacell, average=False, compute_anom=True, + region="equatorial_pacific", **kwargs) + del taux_map_areacell + if keyerror is None: + # Regridding + if 'regridding' not in list(kwargs.keys()): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'} + else: + if not isinstance(kwargs['regridding'], dict): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + taux_map = Regrid(taux_map, None, region='equatorial_pacific', **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(taux) ' + str([ax.id for ax in taux_map.getAxisList()]), + 'shape1': '(taux) ' + str(taux_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Regrid', 15, **dict_debug) + # Meridional average + taux_map, keyerror = AverageMeridional(taux_map) + if keyerror is None: + if debug is True: + dict_debug = {'axes1': '(taux) ' + str([ax.id for ax in taux_map.getAxisList()]), + 'shape2': '(taux) ' + str(taux_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + # Zonal smoothing + taux_map, _ = Smoothing(taux_map, '', axis=1, window=31, method='square') + if debug is True: + dict_debug = {'axes1': '(taux) ' + str([ax.id for ax in taux_map.getAxisList()]), + 'shape1': '(taux) ' + str(taux_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Smoothing', 15, **dict_debug) + # Change units + taux_map = taux_map * 1e3 + # Sst to map + sst_map = TsToMap(sst, taux_map) + # Array year by year + sst_yby = get_year_by_year(sst_map, frequency=kwargs['frequency']) + taux_yby = get_year_by_year(taux_map, frequency=kwargs['frequency']) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(taux) ' + str([ax.id for ax in taux_yby.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(taux) ' + str(taux_yby.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after get_year_by_year', 15, **dict_debug) + # Computes the linear regression for all points, for SSTA >=0 and for SSTA<=0 + curMu, curMuPos, curMuNeg = LinearRegressionAndNonlinearity( + taux_map, sst_map, return_stderr=False, return_intercept=False) + hovMu, hovMuPos, hovMuNeg = LinearRegressionAndNonlinearity( + taux_yby, sst_yby, return_stderr=False, return_intercept=False) + if debug is True: + dict_debug = {'axes1': '(zonal alpha) ' + str([ax.id for ax in curMuPos.getAxisList()]), + 'axes2': '(hovtx alpha) ' + str([ax.id for ax in hovMuPos.getAxisList()]), + 'shape1': '(zonal alpha) ' + str(curMuPos.shape), + 'shape2': '(hovtx alpha) ' + str(hovMuPos.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after LinearRegressionAndNonlinearity', 15, **dict_debug) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = { + 'units': 'C', 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s " + sstbox + " sstA", 'diagnostic_value': mu[0], + 'diagnostic_value_error': mu[1], 'slope': mu[0], 'intercept': mu[2], + 'slope_neg': muNeg[0], 'intercept_neg': muNeg[2], 'slope_pos': muPos[0], + 'intercept_pos': muPos[2]} + dict2 = { + 'units': '1e-3 N/m2', 'number_of_years_used': yearN, + 'time_period': str(actualtimebounds), + 'description': dataset + "'s " + tauxbox + " tauxA", 'diagnostic_value': mu[0], + 'diagnostic_value_error': mu[1], 'slope': mu[0], 'intercept': mu[2], + 'slope_neg': muNeg[0], 'intercept_neg': muNeg[2], 'slope_pos': muPos[0], + 'intercept_pos': muPos[2]} + dict3 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal equatorial_pacific regression of " + tauxbox + + " tauxA over sstA", + 'diagnostic_value': mu[0], 'diagnostic_value_error': mu[1], 'slope': mu[0], + 'intercept': mu[2], 'slope_neg': muNeg[0], 'intercept_neg': muNeg[2], + 'slope_pos': muPos[0], 'intercept_pos': muPos[2]} + dict4 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal equatorial_pacific regression of " + tauxbox + + " tauxA over sstA>0", + 'diagnostic_value': mu[0], 'diagnostic_value_error': mu[1], 'slope': mu[0], + 'intercept': mu[2], 'slope_neg': muNeg[0], 'intercept_neg': muNeg[2], + 'slope_pos': muPos[0], 'intercept_pos': muPos[2]} + dict5 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal equatorial_pacific regression of " + tauxbox + + " tauxA over sstA<0", + 'diagnostic_value': mu[0], 'diagnostic_value_error': mu[1], 'slope': mu[0], + 'intercept': mu[2], 'slope_neg': muNeg[0], 'intercept_neg': muNeg[2], + 'slope_pos': muPos[0], 'intercept_pos': muPos[2]} + dict6 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal monthly of equatorial_pacific regression of " + + tauxbox + " tauxA over sstA", 'diagnostic_value': mu[0], + 'diagnostic_value_error': mu[1], 'slope': mu[0], 'intercept': mu[2], + 'slope_neg': muNeg[0], 'intercept_neg': muNeg[2], 'slope_pos': muPos[0], + 'intercept_pos': muPos[2]} + dict7 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal monthly of equatorial_pacific regression of " + + tauxbox + " tauxA over sstA>0", 'diagnostic_value': mu[0], + 'diagnostic_value_error': mu[1], 'slope': mu[0], 'intercept': mu[2], + 'slope_neg': muNeg[0], 'intercept_neg': muNeg[2], 'slope_pos': muPos[0], + 'intercept_pos': muPos[2]} + dict8 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal monthly of equatorial_pacific regression of " + + tauxbox + " tauxA over sstA<0", + 'diagnostic_value': mu[0], 'diagnostic_value_error': mu[1], 'slope': mu[0], + 'intercept': mu[2], 'slope_neg': muNeg[0], 'intercept_neg': muNeg[2], + 'slope_pos': muPos[0], 'intercept_pos': muPos[2]} + dict9 = {'metric_name': Name, 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=sst, var1_attributes=dict1, var1_name='sst__' + dataset, + var1_time_name='months_' + dataset, var2=taux, var2_attributes=dict2, + var2_name='taux__' + dataset, var2_time_name='months_' + dataset, var3=curMu, + var3_attributes=dict3, var3_name='reg_taux_over_sst_lon__' + dataset, var4=curMuPos, + var4_attributes=dict4, var4_name='reg_taux_over_POSsst_lon__' + dataset, var5=curMuNeg, + var5_attributes=dict5, var5_name='reg_taux_over_NEGsst_lon__' + dataset, var6=hovMu, + var6_attributes=dict6, var6_name='reg_taux_over_sst_hov__' + dataset, var7=hovMuPos, + var7_attributes=dict7, var7_name='reg_taux_over_POSsst_hov__' + dataset, var8=hovMuNeg, + var8_attributes=dict8, var8_name='reg_taux_over_NEGsst_hov__' + dataset, + frequency=kwargs['frequency'], global_attributes=dict9) + del dict1, dict2, dict3, dict4, dict5, dict6, dict7, dict8, dict9 + # Create output + muMetric = { + 'name': Name, 'value': mu[0], 'value_error': mu[1], 'units': Units, 'method': Method, + 'method_nonlinearity': Method_NL, 'nyears': yearN, 'time_frequency': kwargs['frequency'], + 'time_period': actualtimebounds, 'ref': Ref, 'nonlinearity': nl1, 'nonlinearity_error': nl2, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return muMetric + + +def EnsoFbSshSst(sstfile, sstname, sstareafile, sstareaname, sstlandmaskfile, sstlandmaskname, sstbox, sshfile, sshname, + sshareafile, sshareaname, sshlandmaskfile, sshlandmaskname, sshbox, dataset='', debug=False, + netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The EnsoFbSshSst() function computes the regression of 'sstbox' sstA over 'sshbox' sshA (sea surface height + anomalies) (usually the regression of nino3 sstA over nino3 sshA) + + Author: Yann Planton : yann.planton@locean-ipsl.upmc.fr + Co-author: + + Created on Thu Oct 5 2017 + + Inputs: + ------ + :param sstfile: string + path_to/filename of the file (NetCDF) of SST + :param sstname: string + name of SST variable (tos, ts) in 'sstfile' + :param sstareafile: string + path_to/filename of the file (NetCDF) of the areacell for SST + :param sstareaname: string + name of areacell variable (areacella, areacello) in 'sstareafile' + :param sstlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for SST + :param sstlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfile' + :param sstbox: string + name of box ('nino3') for SST + :param sshfile: string + path_to/filename of the file (NetCDF) of SSH + :param sshname: string + name of SSH variable (ssh, sshg, zos) in 'sshfile' + :param sshareafile: string + path_to/filename of the file (NetCDF) of the areacell for SSH + :param sshareaname: string + name of areacell variable (areacella, areacello) in 'sshareafile' + :param sshlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for SSH + :param sshlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'sshlandmaskfile' + :param sshbox: string + name of box ('nino3') for SSH + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return SshMetric: dict + name, value, value_error, units, method, nyears, time_frequency, time_period, ref, nonlinearity, + nonlinearity_error + + Method: + ------- + uses tools from uvcdat library + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'Sst-Ssh feedback' + Units = 'C/cm' + Method = 'Regression of ' + sstbox + ' sstA over ' + sshbox + ' sshA' + Method_NL = 'The nonlinearity is the regression computed when sshA<0 minus the regression computed when sshA>0' + Ref = 'Using CDAT regression calculation' + metric = 'EnsoFbSshSst' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst, sst_areacell, keyerror1 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, sstbox, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + ssh, ssh_areacell, keyerror2 = Read_data_mask_area( + sshfile, sshname, 'sea surface height', metric, sshbox, file_area=sshareafile, name_area=sshareaname, + file_mask=sshlandmaskfile, name_mask=sshlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst, ssh, keyerror3 = CheckTime(sst, ssh, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN = int(round(sst.shape[0] / 12)) + + # Time period + actualtimebounds = TimeBounds(sst) + + keyerror = add_up_errors([keyerror1, keyerror2, keyerror3]) + if keyerror is not None: + ThermoFb, ThermoFbPos, ThermoFbNeg, nl1, nl2 = [None, None], [None, None], [None, None], None, None + dive_down_diag = {'value': None, 'axis': None} + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smooths TS, averages horizontally) + sst, Method, keyerror1 = PreProcessTS( + sst, Method, areacell=sst_areacell, average='horizontal', compute_anom=True, region=sstbox, **kwargs) + ssh, _, keyerror2 = PreProcessTS( + ssh, '', areacell=ssh_areacell, average='horizontal', compute_anom=True, region=sshbox, **kwargs) + del sst_areacell, ssh_areacell + if keyerror1 is not None or keyerror2 is not None or keyerror3 is not None: + ThermoFb, ThermoFbPos, ThermoFbNeg, nl1, nl2 = [None, None], [None, None], [None, None], None, None + dive_down_diag = {'value': None, 'axis': None} + keyerror = add_up_errors([keyerror1, keyerror2, keyerror3]) + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'axes2': '(ssh) ' + str([ax.id for ax in ssh.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape), 'shape2': '(ssh) ' + str(ssh.shape), + 'time1': '(ssh) ' + str(TimeBounds(sst)), 'time2': '(ssh) ' + str(TimeBounds(ssh))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + # Change units + ssh = ssh * 1e2 + # Computes the linear regression for all points, for SSHA >=0 and for SSHA<=0 + ThermoFb, ThermoFbPos, ThermoFbNeg = LinearRegressionAndNonlinearity( + sst, ssh, return_stderr=True, return_intercept=True) + # Non linearities + nl1 = ThermoFbNeg[0] - ThermoFbPos[0] + nl2 = ThermoFbNeg[1] + ThermoFbPos[1] + # Dive down diagnostic + dive_down_diag = {'value': None, 'axis': None} + if netcdf is True: + sst_map, sst_map_areacell, keyerror1 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, 'equatorial_pacific', file_area=sstareafile, + name_area=sstareaname, file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, + maskocean=False, debug=debug, **kwargs) + ssh_map, ssh_map_areacell, keyerror2 = Read_data_mask_area( + sshfile, sshname, 'sea surface height', metric, 'equatorial_pacific', file_area=sshareafile, + name_area=sshareaname, file_mask=sshlandmaskfile, name_mask=sshlandmaskname, maskland=True, + maskocean=False, debug=debug, **kwargs) + # Checks if the same time period is used for both variables + sst_map, ssh_map, keyerror3 = CheckTime(sst_map, ssh_map, metric_name=metric, debug=debug, **kwargs) + if keyerror1 is not None or keyerror2 is not None or keyerror3 is not None: + keyerror = add_up_errors([keyerror1, keyerror2, keyerror3]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smooths TS, ...) + sst_map, _, keyerror1 = PreProcessTS( + sst_map, '', areacell=sst_map_areacell, average=False, compute_anom=True, + region="equatorial_pacific", **kwargs) + ssh_map, _, keyerror2 = PreProcessTS( + ssh_map, '', areacell=ssh_map_areacell, average=False, compute_anom=True, + region="equatorial_pacific", **kwargs) + del ssh_map_areacell, sst_map_areacell + if keyerror1 is not None or keyerror2 is not None: + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + # Regridding + if 'regridding' not in list(kwargs.keys()): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'} + else: + if not isinstance(kwargs['regridding'], dict): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + sst_map = Regrid(sst_map, None, region='equatorial_pacific', **kwargs['regridding']) + ssh_map = Regrid(ssh_map, None, region='equatorial_pacific', **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(ssh) ' + str([ax.id for ax in ssh_map.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(ssh) ' + str(ssh_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Regrid', 15, **dict_debug) + # Meridional average + sst_map, keyerror1 = AverageMeridional(sst_map) + ssh_map, keyerror2 = AverageMeridional(ssh_map) + if keyerror1 is not None or keyerror2 is not None: + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(ssh) ' + str([ax.id for ax in ssh_map.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(ssh) ' + str(ssh_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + # Zonal smoothing + sst_map, _ = Smoothing(sst_map, '', axis=1, window=31, method='square') + ssh_map, _ = Smoothing(ssh_map, '', axis=1, window=31, method='square') + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(ssh) ' + str([ax.id for ax in ssh_map.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(ssh) ' + str(ssh_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Smoothing', 15, **dict_debug) + # Change units + ssh_map = ssh_map * 1e2 + # Array year by year + sst_yby = get_year_by_year(sst_map, frequency=kwargs['frequency']) + ssh_yby = get_year_by_year(ssh_map, frequency=kwargs['frequency']) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_map.getAxisList()]), + 'axes2': '(ssh) ' + str([ax.id for ax in ssh_yby.getAxisList()]), + 'shape1': '(sst) ' + str(sst_map.shape), + 'shape2': '(ssh) ' + str(ssh_yby.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after get_year_by_year', 15, **dict_debug) + # Computes the linear regression for all points, for SSHA >=0 and for SSHA<=0 + curThermoFb, curThermoFbPos, curThermoFbNeg = LinearRegressionAndNonlinearity( + sst_map, ssh_map, return_stderr=False, return_intercept=False) + hovThermoFb, hovThermoFbPos, hovThermoFbNeg = LinearRegressionAndNonlinearity( + sst_yby, ssh_yby, return_stderr=False, return_intercept=False) + if debug is True: + dict_debug = { + 'axes1': '(zonal Thermo Fb) ' +str([ax.id for ax in curThermoFb.getAxisList()]), + 'axes2': '(hovtx Thermo Fb) ' + str([ax.id for ax in hovThermoFb.getAxisList()]), + 'shape1': '(zonal Thermo Fb) ' + str(curThermoFb.shape), + 'shape2': '(hovtx Thermo Fb) ' + str(hovThermoFb.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after LinearRegressionAndNonlinearity', 15, **dict_debug) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': 'C', 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s " + sstbox + " sstA", + 'diagnostic_value': ThermoFb[0], 'diagnostic_value_error': ThermoFb[1], + 'slope': ThermoFb[0], 'intercept': ThermoFb[2], 'slope_neg': ThermoFbNeg[0], + 'intercept_neg': ThermoFbNeg[2], 'slope_pos': ThermoFbPos[0], + 'intercept_pos': ThermoFbPos[2]} + dict2 = {'units': 'cm', 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s " + sshbox + " sshA", + 'diagnostic_value': ThermoFb[0], 'diagnostic_value_error': ThermoFb[1], + 'slope': ThermoFb[0], 'intercept': ThermoFb[2], 'slope_neg': ThermoFbNeg[0], + 'intercept_neg': ThermoFbNeg[2], 'slope_pos': ThermoFbPos[0], + 'intercept_pos': ThermoFbPos[2]} + dict3 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal equatorial_pacific regression of sstA over sshA", + 'diagnostic_value': ThermoFb[0], 'diagnostic_value_error': ThermoFb[1], + 'slope': ThermoFb[0], 'intercept': ThermoFb[2], 'slope_neg': ThermoFbNeg[0], + 'intercept_neg': ThermoFbNeg[2], 'slope_pos': ThermoFbPos[0], + 'intercept_pos': ThermoFbPos[2]} + dict4 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal equatorial_pacific regression of sstA over sshA>0", + 'diagnostic_value': ThermoFb[0], 'diagnostic_value_error': ThermoFb[1], + 'slope': ThermoFb[0], 'intercept': ThermoFb[2], 'slope_neg': ThermoFbNeg[0], + 'intercept_neg': ThermoFbNeg[2], 'slope_pos': ThermoFbPos[0], + 'intercept_pos': ThermoFbPos[2]} + dict5 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal equatorial_pacific regression of sstA over sshA<0", + 'diagnostic_value': ThermoFb[0], 'diagnostic_value_error': ThermoFb[1], + 'slope': ThermoFb[0], 'intercept': ThermoFb[2], 'slope_neg': ThermoFbNeg[0], + 'intercept_neg': ThermoFbNeg[2], 'slope_pos': ThermoFbPos[0], + 'intercept_pos': ThermoFbPos[2]} + dict6 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + + "'s zonal monthly of equatorial_pacific regression of sstA over sshA", + 'diagnostic_value': ThermoFb[0], 'diagnostic_value_error': ThermoFb[1], + 'slope': ThermoFb[0], 'intercept': ThermoFb[2], 'slope_neg': ThermoFbNeg[0], + 'intercept_neg': ThermoFbNeg[2], 'slope_pos': ThermoFbPos[0], + 'intercept_pos': ThermoFbPos[2]} + dict7 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + + "'s zonal monthly of equatorial_pacific regression of sstA over sshA>0", + 'diagnostic_value': ThermoFb[0], 'diagnostic_value_error': ThermoFb[1], + 'slope': ThermoFb[0], 'intercept': ThermoFb[2], 'slope_neg': ThermoFbNeg[0], + 'intercept_neg': ThermoFbNeg[2], 'slope_pos': ThermoFbPos[0], + 'intercept_pos': ThermoFbPos[2]} + dict8 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + + "'s zonal monthly of equatorial_pacific regression of sstA over sshA<0", + 'diagnostic_value': ThermoFb[0], 'diagnostic_value_error': ThermoFb[1], + 'slope': ThermoFb[0], 'intercept': ThermoFb[2], 'slope_neg': ThermoFbNeg[0], + 'intercept_neg': ThermoFbNeg[2], 'slope_pos': ThermoFbPos[0], + 'intercept_pos': ThermoFbPos[2]} + dict9 = {'metric_name': Name, 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=sst, var1_attributes=dict1, var1_name='sst__' + dataset, + var1_time_name='months_' + dataset, var2=ssh, var2_attributes=dict2, + var2_name='ssh__' + dataset, var2_time_name='months_' + dataset, var3=curThermoFb, + var3_attributes=dict3, var3_name='reg_sst_over_ssh_lon__' + dataset, + var4=curThermoFbPos, var4_attributes=dict4, + var4_name='reg_sst_over_POSssh_lon__' + dataset, var5=curThermoFbNeg, + var5_attributes=dict5, var5_name='reg_sst_over_NEGssh_lon__' + dataset, + var6=hovThermoFb, var6_attributes=dict6, var6_name='reg_sst_over_ssh_hov__' + dataset, + var7=hovThermoFbPos, var7_attributes=dict7, + var7_name='reg_sst_over_POSssh_hov__' + dataset, var8=hovThermoFbNeg, + var8_attributes=dict8, var8_name='reg_sst_over_NEGssh_hov__' + dataset, + frequency=kwargs['frequency'], global_attributes=dict9) + del dict1, dict2, dict3, dict4, dict5, dict6, dict7, dict8, dict9 + # Create output + SshMetric = { + 'name': Name, 'value': ThermoFb[0], 'value_error': ThermoFb[1], 'units': Units, 'method': Method, + 'method_nonlinearity': Method_NL, 'nyears': yearN, 'time_frequency': kwargs['frequency'], + 'time_period': actualtimebounds, 'ref': Ref, 'nonlinearity': nl1, 'nonlinearity_error': nl2, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return SshMetric + + +def EnsoFbTauxSsh(tauxfile, tauxname, tauxareafile, tauxareaname, tauxlandmaskfile, tauxlandmaskname, tauxbox, + sshfile, sshname, sshareafile, sshareaname, sshlandmaskfile, sshlandmaskname, sshbox, dataset='', + debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The EnsoFbTauxSsh() function computes the regression of 'sshbox' sshA (sea surface height anomalies) over 'tauxbox' + tauxA (surface downward zonal stress anomalies) (usually the regression of nino3 sshA over nino4 tauxA) + + Author: Eric Guilyardi : Eric.Guilyardi@locean-ipsl.upmc.fr + Co-author: Yann Planton : yann.planton@locean-ipsl.upmc.fr + + Created on Mon Jan 9 11:05:18 CET 2017 + + Inputs: + ------ + :param tauxfile: string + path_to/filename of the file (NetCDF) of TAUX + :param tauxname: string + name of TAUX variable (taux, tauu) in 'tauxfile' + :param tauxareafile: string + path_to/filename of the file (NetCDF) of the areacell for TAUX + :param tauxareaname: string + name of areacell variable (areacella, areacello) in 'tauxareafile' + :param tauxlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for TAUX + :param tauxlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'tauxlandmaskfile' + :param tauxbox: string + name of box ('nino4') for TAUX + :param sshfile: string + path_to/filename of the file (NetCDF) of SSH + :param sshname: string + name of SSH variable (ssh, sshg, zos) in 'sshfile' + :param sshareafile: string + path_to/filename of the file (NetCDF) of the areacell for SSH + :param sshareaname: string + name of areacell variable (areacella, areacello) in 'sshareafile' + :param sshlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for SSH + :param sshlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'sshlandmaskfile' + :param sshbox: string + name of box ('nino3') for SSH + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return fbMetric: dict + name, value, value_error, units, method, nyears, time_frequency, time_period, ref, nonlinearity, + nonlinearity_error + + Method: + ------- + uses tools from uvcdat library + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'Ssh-Taux feedback' + Units = '1e3 cm/N/m2' + Method = 'Regression of ' + sshbox + ' sshA over ' + tauxbox + ' tauxA' + Method_NL = 'The nonlinearity is the regression computed when tauxA<0 minus the regression computed when tauxA>0' + Ref = 'Using CDAT regression calculation' + metric = 'EnsoFbTauxSsh' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + ssh, ssh_areacell, keyerror1 = Read_data_mask_area( + sshfile, sshname, 'sea surface height', metric, sshbox, file_area=sshareafile, name_area=sshareaname, + file_mask=sshlandmaskfile, name_mask=sshlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + taux, taux_areacell, keyerror2 = Read_data_mask_area( + tauxfile, tauxname, 'wind stress', metric, tauxbox, file_area=tauxareafile, name_area=tauxareaname, + file_mask=tauxlandmaskfile, name_mask=tauxlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + ssh, taux, keyerror3 = CheckTime(ssh, taux, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN = int(round(ssh.shape[0] / 12)) + + # Time period + actualtimebounds = TimeBounds(ssh) + + keyerror = add_up_errors([keyerror1, keyerror2, keyerror3]) + if keyerror is not None: + fb, fbPos, fbNeg, nl1, nl2 = [None, None], [None, None], [None, None], None, None + dive_down_diag = {'value': None, 'axis': None} + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smooths TS, averages horizontally) + ssh, Method, keyerror1 = PreProcessTS( + ssh, Method, areacell=ssh_areacell, average='horizontal', compute_anom=True, region=sshbox, **kwargs) + taux, _, keyerror3 = PreProcessTS( + taux, '', areacell=taux_areacell, average='horizontal', compute_anom=True, region=tauxbox, **kwargs) + del ssh_areacell, taux_areacell + if keyerror1 is not None or keyerror2 is not None: + fb, fbPos, fbNeg, nl1, nl2 = [None, None], [None, None], [None, None], None, None + dive_down_diag = {'value': None, 'axis': None} + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + if debug is True: + dict_debug = {'axes1': '(ssh) ' + str([ax.id for ax in ssh.getAxisList()]), + 'axes2': '(taux) ' + str([ax.id for ax in taux.getAxisList()]), + 'shape1': '(ssh) ' + str(ssh.shape), 'shape2': '(taux) ' + str(taux.shape), + 'time1': '(ssh) ' + str(TimeBounds(ssh)), 'time2': '(taux) ' + str(TimeBounds(taux))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + # Change units + ssh = ssh * 1e2 + taux = taux * 1e3 + # Computes the linear regression for all points, for SSHA >=0 and for SSHA<=0 + fb, fbPos, fbNeg = LinearRegressionAndNonlinearity(ssh, taux, return_stderr=True, return_intercept=True) + # Non linearities + nl1 = fbNeg[0] - fbPos[0] + nl2 = fbNeg[1] + fbPos[1] + # Dive down diagnostic + dive_down_diag = {'value': None, 'axis': None} + if netcdf is True: + ssh_map, ssh_map_areacell, keyerror1 = Read_data_mask_area( + sshfile, sshname, 'sea surface height', metric, 'equatorial_pacific', file_area=sshareafile, + name_area=sshareaname, file_mask=sshlandmaskfile, name_mask=sshlandmaskname, maskland=True, + maskocean=False, debug=debug, **kwargs) + # Checks if the same time period is used for both variables + ssh_map, taux, keyerror2 = CheckTime(ssh_map, taux, metric_name=metric, debug=debug, **kwargs) + if keyerror1 is not None or keyerror2 is not None: + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smooths TS, ...) + ssh_map, _, keyerror = PreProcessTS( + ssh_map, '', areacell=ssh_map_areacell, average=False, compute_anom=True, + region="equatorial_pacific", **kwargs) + del ssh_map_areacell + if keyerror is None: + # Regridding + if 'regridding' not in list(kwargs.keys()): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'} + else: + if not isinstance(kwargs['regridding'], dict): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + ssh_map = Regrid(ssh_map, None, region='equatorial_pacific', **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(ssh) ' + str([ax.id for ax in ssh_map.getAxisList()]), + 'shape1': '(ssh) ' + str(ssh_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Regrid', 15, **dict_debug) + # Meridional average + ssh_map, keyerror = AverageMeridional(ssh_map) + if keyerror is None: + if debug is True: + dict_debug = {'axes1': '(ssh) ' + str([ax.id for ax in ssh_map.getAxisList()]), + 'shape2': '(ssh) ' + str(ssh_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + # Zonal smoothing + ssh_map, _ = Smoothing(ssh_map, '', axis=1, window=31, method='square') + if debug is True: + dict_debug = {'axes1': '(ssh) ' + str([ax.id for ax in ssh_map.getAxisList()]), + 'shape1': '(ssh) ' + str(ssh_map.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Smoothing', 15, **dict_debug) + # Change units + ssh_map = ssh_map * 1e2 + # Ssh to map + taux_map = TsToMap(taux, ssh_map) + # Array year by year + ssh_yby = get_year_by_year(ssh_map, frequency=kwargs['frequency']) + taux_yby = get_year_by_year(taux_map, frequency=kwargs['frequency']) + if debug is True: + dict_debug = {'axes1': '(ssh) ' + str([ax.id for ax in ssh_map.getAxisList()]), + 'axes2': '(taux) ' + str([ax.id for ax in taux_yby.getAxisList()]), + 'shape1': '(ssh) ' + str(ssh_map.shape), + 'shape2': '(taux) ' + str(taux_yby.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after get_year_by_year', 15, **dict_debug) + # Computes the linear regression for all points, for SSHA >=0 and for SSHA<=0 + curFb, curFbPos, curFbNeg = LinearRegressionAndNonlinearity( + ssh_map, taux_map, return_stderr=False, return_intercept=False) + hovFb, hovFbPos, hovFbNeg = LinearRegressionAndNonlinearity( + ssh_yby, taux_yby, return_stderr=False, return_intercept=False) + if debug is True: + dict_debug = {'axes1': '(zonal alpha) ' + str([ax.id for ax in curFbPos.getAxisList()]), + 'axes2': '(hovtx alpha) ' + str([ax.id for ax in hovFbPos.getAxisList()]), + 'shape1': '(zonal alpha) ' + str(curFbPos.shape), + 'shape2': '(hovtx alpha) ' + str(hovFbPos.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after LinearRegressionAndNonlinearity', 15, **dict_debug) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = { + 'units': 'cm', 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s " + sshbox + " sshA", 'diagnostic_value': fb[0], + 'diagnostic_value_error': fb[1], 'slope': fb[0], 'intercept': fb[2], + 'slope_neg': fbNeg[0], 'intercept_neg': fbNeg[2], 'slope_pos': fbPos[0], + 'intercept_pos': fbPos[2]} + dict2 = { + 'units': '1e-3 N/m2', 'number_of_years_used': yearN, + 'time_period': str(actualtimebounds), + 'description': dataset + "'s " + tauxbox + " tauxA", 'diagnostic_value': fb[0], + 'diagnostic_value_error': fb[1], 'slope': fb[0], 'intercept': fb[2], + 'slope_neg': fbNeg[0], 'intercept_neg': fbNeg[2], 'slope_pos': fbPos[0], + 'intercept_pos': fbPos[2]} + dict3 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal equatorial_pacific regression of sshA over " + + tauxbox + " tauxA", + 'diagnostic_value': fb[0], 'diagnostic_value_error': fb[1], 'slope': fb[0], + 'intercept': fb[2], 'slope_neg': fbNeg[0], 'intercept_neg': fbNeg[2], + 'slope_pos': fbPos[0], 'intercept_pos': fbPos[2]} + dict4 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal equatorial_pacific regression of sshA over " + + tauxbox + " tauxA>0", 'diagnostic_value': fb[0], + 'diagnostic_value_error': fb[1], 'slope': fb[0], 'intercept': fb[2], + 'slope_neg': fbNeg[0], 'intercept_neg': fbNeg[2], 'slope_pos': fbPos[0], + 'intercept_pos': fbPos[2]} + dict5 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + "'s zonal equatorial_pacific regression of sshA over " + + tauxbox + " tauxA<0", + 'diagnostic_value': fb[0], 'diagnostic_value_error': fb[1], 'slope': fb[0], + 'intercept': fb[2], 'slope_neg': fbNeg[0], 'intercept_neg': fbNeg[2], + 'slope_pos': fbPos[0], 'intercept_pos': fbPos[2]} + dict6 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + + "'s zonal monthly of equatorial_pacific regression of sshA over " + + tauxbox + " tauxA", + 'diagnostic_value': fb[0], 'diagnostic_value_error': fb[1], 'slope': fb[0], + 'intercept': fb[2], 'slope_neg': fbNeg[0], 'intercept_neg': fbNeg[2], + 'slope_pos': fbPos[0], 'intercept_pos': fbPos[2]} + dict7 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + + "'s zonal monthly of equatorial_pacific regression of sshA over " + + tauxbox + " tauxA>0", + 'diagnostic_value': fb[0], 'diagnostic_value_error': fb[1], 'slope': fb[0], + 'intercept': fb[2], 'slope_neg': fbNeg[0], 'intercept_neg': fbNeg[2], + 'slope_pos': fbPos[0], 'intercept_pos': fbPos[2]} + dict8 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': dataset + + "'s zonal monthly of equatorial_pacific regression of sshA over " + + tauxbox + " tauxA<0", 'diagnostic_value': fb[0], + 'diagnostic_value_error': fb[1], 'slope': fb[0], 'intercept': fb[2], + 'slope_neg': fbNeg[0], 'intercept_neg': fbNeg[2], 'slope_pos': fbPos[0], + 'intercept_pos': fbPos[2]} + dict9 = {'metric_name': Name, 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=ssh, var1_attributes=dict1, var1_name='ssh__' + dataset, + var1_time_name='months_' + dataset, var2=taux, var2_attributes=dict2, + var2_name='taux__' + dataset, var2_time_name='months_' + dataset, var3=curFb, + var3_attributes=dict3, var3_name='reg_ssh_over_taux_lon__' + dataset, var4=curFbPos, + var4_attributes=dict4, var4_name='reg_ssh_over_POStaux_lon__' + dataset, var5=curFbNeg, + var5_attributes=dict5, var5_name='reg_ssh_over_NEGtaux_lon__' + dataset, var6=hovFb, + var6_attributes=dict6, var6_name='reg_ssh_over_taux_hov__' + dataset, var7=hovFbPos, + var7_attributes=dict7, var7_name='reg_ssh_over_POStaux_hov__' + dataset, var8=hovFbNeg, + var8_attributes=dict8, var8_name='reg_ssh_over_NEGtaux_hov__' + dataset, + frequency=kwargs['frequency'], global_attributes=dict9) + del dict1, dict2, dict3, dict4, dict5, dict6, dict7, dict8, dict9 + # Create output + fbMetric = { + 'name': Name, 'value': fb[0], 'value_error': fb[1], 'units': Units, 'method': Method, + 'method_nonlinearity': Method_NL, 'nyears': yearN, 'time_frequency': kwargs['frequency'], + 'time_period': actualtimebounds, 'ref': Ref, 'nonlinearity': nl1, 'nonlinearity_error': nl2, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return fbMetric + + +def EnsoPrMap(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, prfilemod, + prnamemod, prareafilemod, prareanamemod, prlandmaskfilemod, prlandmasknamemod, sstfileobs, sstnameobs, + sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, prfileobs, prnameobs, + prareafileobs, prareanameobs, prlandmaskfileobs, prlandmasknameobs, sstbox, prbox, event_definition, + centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, netcdf_name='', + metname='', **kwargs): + """ + The EnsoPrMap() function computes precipitation anomalies pattern associated with ENSO on the globe. + First metric: rmse(observations vs model). + Second metric: correlation(observations vs model). + Third metric: std(model)/std(observations) + These metrics can be used to compute a Taylor diagram. + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST areacell + :param sstareanamemod: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafilemod' + :param sstlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST landmask + :param sstlandmasknamemod: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfilemod' + :param prfilemod: string + path_to/filename of the file (NetCDF) of the modeled PR + :param prnamemod: string + name of PR variable (pr) in 'prfilemod' + :param prareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled PR areacell + :param prareanamemod: string, optional + name of areacell for the PR variable (areacella, areacello,...) in 'prareafilemod' + :param prlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled PR landmask + :param prlandmasknamemod: string, optional + name of landmask for the PR variable (sftlf,...) in 'prlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST areacell + :param sstareanameobs: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafileobs' + :param sstlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST landmask + :param sstlandmasknameobs: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfileobs' + :param prfileobs: string + path_to/filename of the file (NetCDF) of the observed PR + :param prnameobs: string + name of PR variable (pr, precip) in 'prfileobs' + :param prareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed PR areacell + :param prareanameobs: string, optional + name of areacell for the PR variable (areacella, areacello,...) in 'prareafileobs' + :param prlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed PR landmask + :param prlandmasknameobs: string, optional + name of landmask for the PR variable (sftlf,...) in 'prlandmaskfileobs' + :param sstbox: string + name of box (e.g. 'global') for SST + :param prbox: string + name of box (e.g. 'global') for PR + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': -0.75} + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return EnsoPrMapMetric: dict + name, value (rms [obs;model]), value_error, units, method, value2 (corr [obs;model]), + value_error2, units2, value3 (std_model / std_obs), value_error3, units3, nyears_model, nyears_observations, + time_frequency, time_period_mod, time_period_obs, ref, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'ENSO PRA pattern' + Method = region_ev + 'SSTA during ' + season_ev + ' regressed against precipitation anomalies in ' + prbox + if kwargs['normalization']: + Units = '' + else: + Units = 'mm/day/C' + Ref = 'Using CDAT regridding, correlation (centered and biased), std (centered and biased) and ' + \ + 'rms (uncentered and biased) calculation' + metric = 'EnsoPrMap' + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod1 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs1 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + pr_mod, pr_mod_areacell, keyerror_mod2 = Read_data_mask_area( + prfilemod, prnamemod, 'precipitations', metric, prbox,file_area=prareafilemod, name_area=prareanamemod, + file_mask=prlandmaskfilemod, name_mask=prlandmasknamemod, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + pr_obs, pr_obs_areacell, keyerror_obs2 = Read_data_mask_area( + prfileobs, prnameobs, 'precipitations', metric, prbox, file_area=prareafileobs, name_area=prareanameobs, + file_mask=prlandmaskfileobs, name_mask=prlandmasknameobs, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst_mod, pr_mod, keyerror_mod3 = CheckTime(sst_mod, pr_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs, pr_obs, keyerror_obs3 = CheckTime(sst_obs, pr_obs, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if (keyerror_mod1 is not None or keyerror_obs1 is not None or keyerror_mod2 is not None) or \ + (keyerror_obs2 is not None or keyerror_mod3 is not None or keyerror_obs3 is not None): + prCorr, prCorrErr, prRmse, prRmseErr, prStd, prStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + tmp = [keyerror_mod1, keyerror_mod2, keyerror_mod3, keyerror_obs1, keyerror_obs2, keyerror_obs3] + keyerror = add_up_errors(tmp) + else: + # ------------------------------------------------ + # 1. SSTA + # ------------------------------------------------ + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, _, keyerror_mod = PreProcessTS( + sst_mod, '', areacell=mod_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + prCorr, prCorrErr, prRmse, prRmseErr, prStd, prStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sst_mod)), + 'time2': '(obs) ' + str(TimeBounds(sst_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 Seasonal mean and anomalies + enso_mod = SeasonalMean(sst_mod, season_ev, compute_anom=True) + enso_obs = SeasonalMean(sst_obs, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in enso_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(enso_mod.shape), 'shape2': '(obs) ' + str(enso_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(enso_mod)), + 'time2': '(obs) ' + str(TimeBounds(enso_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # ------------------------------------------------ + # 2. PRA + # ------------------------------------------------ + # 2.1 PRA in 'prbox' are normalized / detrended / smoothed (running average) if applicable + # Preprocess pr (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + pr_mod, Method, keyerror_mod = PreProcessTS( + pr_mod, Method, areacell=pr_mod_areacell, compute_anom=False, region=prbox, **kwargs) + pr_obs, _, keyerror_obs = PreProcessTS( + pr_obs, '', areacell=pr_obs_areacell, compute_anom=False, region=prbox, **kwargs) + del pr_mod_areacell, pr_obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + prCorr, prCorrErr, prRmse, prRmseErr, prStd, prStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(pr_mod)), + 'time2': '(obs) ' + str(TimeBounds(pr_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 2.2 Seasonal mean and anomalies + pr_mod = SeasonalMean(pr_mod, season_ev, compute_anom=True) + pr_obs = SeasonalMean(pr_obs, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(pr_mod)), + 'time2': '(obs) ' + str(TimeBounds(pr_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # ------------------------------------------------ + # 3. Regression map + # ------------------------------------------------ + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + pr_mod, pr_obs, Method = TwoVarRegrid(pr_mod, pr_obs, Method, region=prbox, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # regression + pr_mod_slope = LinearRegressionTsAgainstMap(pr_mod, enso_mod, return_stderr=False) + pr_obs_slope = LinearRegressionTsAgainstMap(pr_obs, enso_obs, return_stderr=False) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs_slope.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod_slope.shape), + 'shape2': '(obs) ' + str(pr_obs_slope.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after LinearRegressionTsAgainstMap', 15, **dict_debug) + + # mask Pacific + pr_mod_slope, keyerror_mod = BasinMask( + pr_mod_slope, 'pacific', box=prbox, lat1=-15, lat2=15, latkey='between', debug=debug) + pr_obs_slope, keyerror_obs = BasinMask( + pr_obs_slope, 'pacific', box=prbox, lat1=-15, lat2=15, latkey='between', debug=debug) + if keyerror_mod is not None or keyerror_obs is not None: + prCorr, prCorrErr, prRmse, prRmseErr, prStd, prStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs_slope.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod_slope.shape), + 'shape2': '(obs) ' + str(pr_obs_slope.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after BasinMask', 15, **dict_debug) + + # Metric 1 + prRmse, keyerror = RmsAxis( + pr_mod_slope, pr_obs_slope, axis='xy', centered=centered_rmse, biased=biased_rmse) + prRmseErr = None + # Metric 2 + prCorr = float(Correlation(pr_mod_slope, pr_obs_slope, axis='xy', centered=1, biased=1)) + prCorrErr = None + # Metric 3 + std_mod = Std(pr_mod_slope, weights=None, axis='xy', centered=1, biased=1) + std_obs = Std(pr_obs_slope, weights=None, axis='xy', centered=1, biased=1) + prStd = float(std_mod) / float(std_obs) + prStdErr = None + + # Dive down diagnostic + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + + if netcdf is True: + # read + pr_mod, pr_mod_areacell, keyerror_mod1 = Read_data_mask_area( + prfilemod, prnamemod, 'precipitations', metric, prbox, file_area=prareafilemod, + name_area=prareanamemod, file_mask=prlandmaskfilemod, name_mask=prlandmasknamemod, + maskland=False, maskocean=True, time_bounds=kwargs['time_bounds_mod'], debug=debug, + **kwargs) + pr_obs, pr_obs_areacell, keyerror_obs1 = Read_data_mask_area( + prfileobs, prnameobs, 'precipitations', metric, prbox, file_area=prareafileobs, + name_area=prareanameobs, file_mask=prlandmaskfileobs, name_mask=prlandmasknameobs, + maskland=False, maskocean=True, time_bounds=kwargs['time_bounds_obs'], debug=debug, + **kwargs) + sst_mod, pr_mod, keyerror_mod2 = CheckTime( + sst_mod, pr_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs, pr_obs, keyerror_obs2 = CheckTime( + sst_obs, pr_obs, metric_name=metric, debug=debug, **kwargs) + if keyerror_mod1 is not None or keyerror_obs1 is not None or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + # process + pr_mod, _, keyerror_mod = PreProcessTS( + pr_mod, '', areacell=pr_mod_areacell, compute_anom=False, region=prbox, **kwargs) + pr_obs, _, keyerror_obs = PreProcessTS( + pr_obs, '', areacell=pr_obs_areacell, compute_anom=False, region=prbox, **kwargs) + del pr_mod_areacell, pr_obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), + 'shape2': '(obs) ' + str(pr_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(pr_mod)), + 'time2': '(obs) ' + str(TimeBounds(pr_obs))} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after PreProcessTS', 15, **dict_debug) + # anomalies + pr_mod = SeasonalMean(pr_mod, season_ev, compute_anom=True) + pr_obs = SeasonalMean(pr_obs, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), + 'shape2': '(obs) ' + str(pr_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(pr_mod)), + 'time2': '(obs) ' + str(TimeBounds(pr_obs))} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after SeasonalMean', 15, **dict_debug) + # regridding + if isinstance(kwargs['regridding'], dict): + pr_mod, pr_obs, _ = TwoVarRegrid( + pr_mod, pr_obs, '', region=prbox, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), + 'shape2': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after TwoVarRegrid', 15, **dict_debug) + # regression + pr_mod = LinearRegressionTsAgainstMap(pr_mod, enso_mod, return_stderr=False) + pr_obs = LinearRegressionTsAgainstMap(pr_obs, enso_obs, return_stderr=False) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), + 'shape2': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after LinearRegressionTsAgainstMap', 15, **dict_debug) + list_region = ["africaSE", "americaN", "americaS", "asiaS", "oceania"] + list_met_name = ["RMSE_" + dataset2, "RMSE_error_" + dataset2, "CORR_" + dataset2, + "CORR_error_" + dataset2, "STD_" + dataset2, "STD_error_" + dataset2] + # Metrics ENSO regression regional + dict_metric, dict_nc = dict(), dict() + nbr = 3 + for ii, reg in enumerate(list_region): + # select region + dictreg = ReferenceRegions(reg) + tmp1 = pr_mod(longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + tmp2 = pr_obs(longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + dict_metric, dict_nc = fill_dict_teleconnection( + tmp1, tmp2, dataset1, dataset2, actualtimebounds_mod, actualtimebounds_obs, + yearN_mod, yearN_obs, nbr, "reg_pr_over_sst_djf_map_" + reg + "__", reg, Units, + centered_rmse=centered_rmse, biased_rmse=biased_rmse, dict_metric=dict_metric, + dict_nc=dict_nc) + nbr += 2 + del dictreg, tmp1, tmp2 + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), 'spatialSTD_' + dataset1: std_mod} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), 'spatialSTD_' + dataset2: std_obs} + dict3 = {'metric_name': Name, 'metric_valueRMSE_' + dataset2: prRmse, + 'metric_valueRMSE_error_' + dataset2: prRmseErr, + 'metric_valueCORR_' + dataset2: prCorr, + 'metric_valueCORR_error_' + dataset2: prCorrErr, + 'metric_valueSTD_' + dataset2: prStd, + 'metric_valueSTD_error_' + dataset2: prStdErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + dict3.update(dict_metric) + SaveNetcdf( + file_name, var1=pr_mod_slope, var1_attributes=dict1, + var1_name='reg_pr_over_sst_map__' + dataset1, var2=pr_obs_slope, + var2_attributes=dict2, var2_name='reg_pr_over_sst_map__' + dataset2, + global_attributes=dict3, **dict_nc) + del dict1, dict2, dict3, dict_metric, dict_nc, file_name, list_met_name, list_region + if prCorr is not None: + prCorr = 1 - prCorr + # Create output + EnsoPrMapMetric = { + 'name': Name, 'Rmse__value': prRmse, 'Rmse__value_error': prRmseErr, 'Rmse__units': Units, 'method': Method, + 'Corr__value': prCorr, 'Corr__value_error': prCorrErr, 'Corr__units': '', 'Std__value': prStd, + 'Std__value_error': prStdErr, 'Std__units': Units + ' / ' + Units, 'nyears_model': yearN_mod, + 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag, 'units': ''} + return EnsoPrMapMetric + + +def EnsoPrMapDjf(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + prfilemod, prnamemod, prareafilemod, prareanamemod, prlandmaskfilemod, prlandmasknamemod, sstfileobs, + sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, prfileobs, + prnameobs, prareafileobs, prareanameobs, prlandmaskfileobs, prlandmasknameobs, sstbox, prbox, + event_definition, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, + netcdf_name='', metname='', **kwargs): + """ + The EnsoPrMapDjf() function computes precipitation anomalies pattern associated with ENSO on the globe. + First metric: rmse(observations vs model). + Second metric: correlation(observations vs model). + Third metric: std(model)/std(observations) + These metrics can be used to compute a Taylor diagram. + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST areacell + :param sstareanamemod: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafilemod' + :param sstlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST landmask + :param sstlandmasknamemod: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfilemod' + :param prfilemod: string + path_to/filename of the file (NetCDF) of the modeled PR + :param prnamemod: string + name of PR variable (pr) in 'prfilemod' + :param prareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled PR areacell + :param prareanamemod: string, optional + name of areacell for the PR variable (areacella, areacello,...) in 'prareafilemod' + :param prlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled PR landmask + :param prlandmasknamemod: string, optional + name of landmask for the PR variable (sftlf,...) in 'prlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST areacell + :param sstareanameobs: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafileobs' + :param sstlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST landmask + :param sstlandmasknameobs: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfileobs' + :param prfileobs: string + path_to/filename of the file (NetCDF) of the observed PR + :param prnameobs: string + name of PR variable (pr, precip) in 'prfileobs' + :param prareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed PR areacell + :param prareanameobs: string, optional + name of areacell for the PR variable (areacella, areacello,...) in 'prareafileobs' + :param prlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed PR landmask + :param prlandmasknameobs: string, optional + name of landmask for the PR variable (sftlf,...) in 'prlandmaskfileobs' + :param sstbox: string + name of box (e.g. 'global') for SST + :param prbox: string + name of box (e.g. 'global') for PR + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': -0.75} + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return EnsoPrMapMetric: dict + name, value (rms [obs;model]), value_error, units, method, value2 (corr [obs;model]), + value_error2, units2, value3 (std_model / std_obs), value_error3, units3, nyears_model, nyears_observations, + time_frequency, time_period_mod, time_period_obs, ref, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = "ENSO DJF PRA pattern" + Method = region_ev + " SSTA regressed against precipitation anomalies in " + prbox + " during DJF" + if kwargs['normalization']: + Units = "" + else: + Units = "mm/day/C" + Ref = 'Using CDAT regridding, correlation (centered and biased), std (centered and biased) and ' + \ + 'rms (uncentered and biased) calculation' + metric = "EnsoPrMapDjf" + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod_box, mod_areacell, keyerror_mod1 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs_box, obs_areacell, keyerror_obs1 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + pr_mod, pr_mod_areacell, keyerror_mod2 = Read_data_mask_area( + prfilemod, prnamemod, 'precipitations', metric, prbox, file_area=prareafilemod, name_area=prareanamemod, + file_mask=prlandmaskfilemod, name_mask=prlandmasknamemod, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + pr_obs, pr_obs_areacell, keyerror_obs2 = Read_data_mask_area( + prfileobs, prnameobs, 'precipitations', metric, prbox, file_area=prareafileobs, name_area=prareanameobs, + file_mask=prlandmaskfileobs, name_mask=prlandmasknameobs, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst_mod_box, pr_mod, keyerror_mod3 = CheckTime(sst_mod_box, pr_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs_box, pr_obs, keyerror_obs3 = CheckTime(sst_obs_box, pr_obs, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod_box.shape[0] / 12)) + yearN_obs = int(round(sst_obs_box.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod_box) + actualtimebounds_obs = TimeBounds(sst_obs_box) + + if (keyerror_mod1 is not None or keyerror_obs1 is not None or keyerror_mod2 is not None) or \ + (keyerror_obs2 is not None or keyerror_mod3 is not None or keyerror_obs3 is not None): + prCorr, prCorrErr, prRmse, prRmseErr, prStd, prStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + tmp = [keyerror_mod1, keyerror_mod2, keyerror_mod3, keyerror_obs1, keyerror_obs2, keyerror_obs3] + keyerror = add_up_errors(tmp) + else: + # smoothing is not applied + if 'smoothing' in list(kwargs.keys()): + smooth = deepcopy(kwargs['smoothing']) + kwargs['smoothing'] = False + else: + smooth = False + # ------------------------------------------------ + # 1. SSTA + # ------------------------------------------------ + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, _, keyerror_mod = PreProcessTS( + sst_mod_box, '', areacell=mod_areacell, average='horizontal', compute_anom=False, region=region_ev, + **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs_box, '', areacell=obs_areacell, average='horizontal', compute_anom=False, region=region_ev, + **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + prCorr, prCorrErr, prRmse, prRmseErr, prStd, prStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sst_mod)), + 'time2': '(obs) ' + str(TimeBounds(sst_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 Seasonal mean and anomalies + enso_mod = SeasonalMean(sst_mod, "DJF", compute_anom=True) + enso_obs = SeasonalMean(sst_obs, "DJF", compute_anom=True) + if debug is True: + dict_debug = { + 'axes1': '(mod) ' + str([ax.id for ax in enso_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(enso_mod.shape), 'shape2': '(obs) ' + str(enso_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(enso_mod)), 'time2': '(obs) ' + str(TimeBounds(enso_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # ------------------------------------------------ + # 2. PRA + # ------------------------------------------------ + # 2.1 PRA in 'prbox' are normalized / detrended / smoothed (running average) if applicable + # Preprocess pr (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + pr_mod, Method, keyerror_mod = PreProcessTS( + pr_mod, Method, areacell=pr_mod_areacell, compute_anom=False, region=prbox, **kwargs) + pr_obs, _, keyerror_obs = PreProcessTS( + pr_obs, '', areacell=pr_obs_areacell, compute_anom=False, region=prbox, **kwargs) + del pr_mod_areacell, pr_obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + prCorr, prCorrErr, prRmse, prRmseErr, prStd, prStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(pr_mod)), + 'time2': '(obs) ' + str(TimeBounds(pr_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 2.2 Seasonal mean and anomalies + pr_mod = SeasonalMean(pr_mod, "DJF", compute_anom=True) + pr_obs = SeasonalMean(pr_obs, "DJF", compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(pr_mod)), + 'time2': '(obs) ' + str(TimeBounds(pr_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # ------------------------------------------------ + # 3. Regression map + # ------------------------------------------------ + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + pr_mod, pr_obs, Method = TwoVarRegrid(pr_mod, pr_obs, Method, region=prbox, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # regression + pr_mod_slope = LinearRegressionTsAgainstMap(pr_mod, enso_mod, return_stderr=False) + pr_obs_slope = LinearRegressionTsAgainstMap(pr_obs, enso_obs, return_stderr=False) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs_slope.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod_slope.shape), + 'shape2': '(obs) ' + str(pr_obs_slope.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after LinearRegressionTsAgainstMap', 15, **dict_debug) + + # mask Pacific + pr_mod_slope, keyerror_mod = BasinMask( + pr_mod_slope, 'pacific', box=prbox, lat1=-15, lat2=15, latkey='between', debug=debug) + pr_obs_slope, keyerror_obs = BasinMask( + pr_obs_slope, 'pacific', box=prbox, lat1=-15, lat2=15, latkey='between', debug=debug) + if keyerror_mod is not None or keyerror_obs is not None: + prCorr, prCorrErr, prRmse, prRmseErr, prStd, prStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs_slope.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod_slope.shape), + 'shape2': '(obs) ' + str(pr_obs_slope.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after BasinMask', 15, **dict_debug) + + # Metric 1 + prRmse, keyerror = RmsAxis( + pr_mod_slope, pr_obs_slope, axis='xy', centered=centered_rmse, biased=biased_rmse) + prRmseErr = None + # Metric 2 + prCorr = float(Correlation(pr_mod_slope, pr_obs_slope, axis='xy', centered=1, biased=1)) + prCorrErr = None + # Metric 3 + std_mod = Std(pr_mod_slope, weights=None, axis='xy', centered=1, biased=1) + std_obs = Std(pr_obs_slope, weights=None, axis='xy', centered=1, biased=1) + prStd = float(std_mod) / float(std_obs) + prStdErr = None + + # Dive down diagnostic + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + + if netcdf is True: + # read + pr_mod_land, pr_mod_areacell, keyerror_mod1 = Read_data_mask_area( + prfilemod, prnamemod, 'precipitations', metric, prbox, file_area=prareafilemod, + name_area=prareanamemod, file_mask=prlandmaskfilemod, name_mask=prlandmasknamemod, + maskland=False, maskocean=True, time_bounds=kwargs['time_bounds_mod'], debug=debug, + **kwargs) + pr_obs_land, pr_obs_areacell, keyerror_obs1 = Read_data_mask_area( + prfileobs, prnameobs, 'precipitations', metric, prbox, file_area=prareafileobs, + name_area=prareanameobs, file_mask=prlandmaskfileobs, name_mask=prlandmasknameobs, + maskland=False, maskocean=True, time_bounds=kwargs['time_bounds_obs'], debug=debug, + **kwargs) + sst_mod, pr_mod_land, keyerror_mod1 = CheckTime( + sst_mod, pr_mod_land, metric_name=metric, debug=debug, **kwargs) + sst_obs, pr_obs_land, keyerror_obs2 = CheckTime( + sst_obs, pr_obs_land, metric_name=metric, debug=debug, **kwargs) + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + # process + pr_mod_land, _, keyerror_mod = PreProcessTS( + pr_mod_land, '', areacell=pr_mod_areacell, compute_anom=False, region=prbox, **kwargs) + pr_obs_land, _, keyerror_obs = PreProcessTS( + pr_obs_land, '', areacell=pr_obs_areacell, compute_anom=False, region=prbox, **kwargs) + del pr_mod_areacell, pr_obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = { + 'axes1': '(mod) ' + str([ax.id for ax in pr_mod_land.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs_land.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod_land.shape), + 'shape2': '(obs) ' + str(pr_obs_land.shape), + 'time1': '(mod) ' + str(TimeBounds(pr_mod_land)), + 'time2': '(obs) ' + str(TimeBounds(pr_obs_land))} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after PreProcessTS', 15, **dict_debug) + # anomalies + pr_mod_land = SeasonalMean(pr_mod_land, "DJF", compute_anom=True) + pr_obs_land = SeasonalMean(pr_obs_land, "DJF", compute_anom=True) + if debug is True: + dict_debug = { + 'axes1': '(mod) ' + str([ax.id for ax in pr_mod_land.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs_land.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod_land.shape), + 'shape2': '(obs) ' + str(pr_obs_land.shape), + 'time1': '(mod) ' + str(TimeBounds(pr_mod_land)), + 'time2': '(obs) ' + str(TimeBounds(pr_obs_land))} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after SeasonalMean', 15, **dict_debug) + # regridding + if isinstance(kwargs['regridding'], dict): + pr_mod_land, pr_obs_land, _ = TwoVarRegrid( + pr_mod_land, pr_obs_land, '', region=prbox, **kwargs['regridding']) + if debug is True: + dict_debug = { + 'axes1': '(mod) ' + str([ax.id for ax in pr_mod_land.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs_land.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod_land.shape), + 'shape2': '(obs) ' + str(pr_obs_land.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after TwoVarRegrid', 15, **dict_debug) + # regression + pr_mod_land_slope = LinearRegressionTsAgainstMap( + pr_mod_land, enso_mod, return_stderr=False) + pr_obs_land_slope = LinearRegressionTsAgainstMap( + pr_obs_land, enso_obs, return_stderr=False) + if debug is True: + dict_debug = { + 'axes1': '(mod) ' + str([ax.id for ax in pr_mod_land_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs_land_slope.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod_land_slope.shape), + 'shape2': '(obs) ' + str(pr_obs_land_slope.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after LinearRegressionTsAgainstMap', 15, **dict_debug) + # ENSO events: SSTA > (<) 'threshold' during 'season' are considered as El Nino + # (La Nina) events + if 'smoothing' in list(kwargs.keys()): + kwargs['smoothing'] = smooth + del smooth + sst_mod, _, keyerror_mod = PreProcessTS( + sst_mod_box, '', areacell=mod_areacell, average='horizontal', compute_anom=False, + region=region_ev, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs_box, '', areacell=obs_areacell, average='horizontal', compute_anom=False, + region=region_ev, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Lists event years + nino_mod_years = DetectEvents( + sst_mod, season_ev, threshold, normalization=normalize, nino=True) + nina_mod_years = DetectEvents( + sst_mod, season_ev, -threshold, normalization=normalize, nino=False) + nino_obs_years = DetectEvents( + sst_obs, season_ev, threshold, normalization=normalize, nino=True) + nina_obs_years = DetectEvents( + sst_obs, season_ev, -threshold, normalization=normalize, nino=False) + if debug is True: + dict_debug = {'nino1': '(mod) nbr(' + str(len(nino_mod_years)) + '): ' + + str(nino_mod_years), + 'nina1': '(mod) nbr(' + str(len(nina_mod_years)) + '): ' + + str(nina_mod_years), + 'nino2': '(obs) nbr(' + str(len(nino_obs_years)) + '): ' + + str(nino_obs_years), + 'nina2': '(obs) nbr(' + str(len(nina_obs_years)) + '): ' + + str(nina_obs_years)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after DetectEvents', 15, **dict_debug) + # samples + nino_mod = Composite(pr_mod, nino_mod_years, kwargs['frequency']) + nina_mod = Composite(pr_mod, nina_mod_years, kwargs['frequency']) + nino_obs = Composite(pr_obs, nino_obs_years, kwargs['frequency']) + nina_obs = Composite(pr_obs, nina_obs_years, kwargs['frequency']) + nino_mod_land = Composite(pr_mod_land, nino_mod_years, kwargs['frequency']) + nina_mod_land = Composite(pr_mod_land, nina_mod_years, kwargs['frequency']) + nino_obs_land = Composite(pr_obs_land, nino_obs_years, kwargs['frequency']) + nina_obs_land = Composite(pr_obs_land, nina_obs_years, kwargs['frequency']) + # mask Pacific + nino_mod, keyerror_mod1 = BasinMask( + nino_mod, 'pacific', box=prbox, lat1=-15, lat2=15, latkey='between', + debug=debug) + nina_mod, keyerror_mod2 = BasinMask( + nina_mod, 'pacific', box=prbox, lat1=-15, lat2=15, latkey='between', + debug=debug) + nino_obs, keyerror_obs1 = BasinMask( + nino_obs, 'pacific', box=prbox, lat1=-15, lat2=15, latkey='between', + debug=debug) + nina_obs, keyerror_obs2 = BasinMask( + nina_obs, 'pacific', box=prbox, lat1=-15, lat2=15, latkey='between', + debug=debug) + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + keyerror = add_up_errors( + [keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + # Metrics ENSO events global + dict_metric, dict_nc = dict(), dict() + nbr = 3 + for jj, (evname, tab1, tab2, ev1, ev2) in enumerate( + zip(["nino", "nina"], [nino_mod, nina_mod], [nino_obs, nina_obs], + [nino_mod_years, nina_mod_years], + [nino_obs_years, nina_obs_years])): + dict_metric, dict_nc = fill_dict_teleconnection( + tab1, tab2, dataset1, dataset2, actualtimebounds_mod, + actualtimebounds_obs, yearN_mod, yearN_obs, nbr, + "pr_" + evname + "_djf_map__", evname, "mm/day", + centered_rmse=centered_rmse, biased_rmse=biased_rmse, + dict_metric=dict_metric, dict_nc=dict_nc, ev_name=evname, events1=ev1, + events2=ev2) + nbr += 2 + list_region = ["africaSE", "americaN", "americaS", "asiaS", "oceania"] + for ii, reg in enumerate(list_region): + # select region + dictreg = ReferenceRegions(reg) + tmp1 = pr_mod_land_slope( + longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + tmp2 = pr_obs_land_slope( + longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + dict_metric, dict_nc = fill_dict_teleconnection( + tmp1, tmp2, dataset1, dataset2, actualtimebounds_mod, + actualtimebounds_obs, yearN_mod, yearN_obs, nbr, + "reg_pr_over_sst_djf_map_" + reg + "__", reg, Units, + centered_rmse=centered_rmse, biased_rmse=biased_rmse, + dict_metric=dict_metric, dict_nc=dict_nc) + nbr += 2 + for jj, (evname, tab1, tab2, ev1, ev2) in enumerate( + zip(["nino", "nina"], [nino_mod_land, nina_mod_land], + [nino_obs_land, nina_obs_land], + [nino_mod_years, nina_mod_years], + [nino_obs_years, nina_obs_years])): + tmp1 = tab1( + longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + tmp2 = tab2( + longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + dict_metric, dict_nc = fill_dict_teleconnection( + tmp1, tmp2, dataset1, dataset2, actualtimebounds_mod, + actualtimebounds_obs, yearN_mod, yearN_obs, nbr, + "pr_" + evname + "_djf_map_" + reg + "__", evname + "_" + reg, + "mm/day", centered_rmse=centered_rmse, biased_rmse=biased_rmse, + dict_metric=dict_metric, dict_nc=dict_nc, ev_name=evname, + events1=ev1, events2=ev2) + nbr += 2 + del dictreg, tmp1, tmp2 + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'spatialSTD_' + dataset1: std_mod} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'spatialSTD_' + dataset2: std_obs} + dict3 = {'metric_name': Name, 'metric_valueRMSE_' + dataset2: prRmse, + 'metric_valueRMSE_error_' + dataset2: prRmseErr, + 'metric_valueCORR_' + dataset2: prCorr, + 'metric_valueCORR_error_' + dataset2: prCorrErr, + 'metric_valueSTD_' + dataset2: prStd, + 'metric_valueSTD_error_' + dataset2: prStdErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + dict3.update(dict_metric) + SaveNetcdf( + file_name, var1=pr_mod_slope, var1_attributes=dict1, + var1_name='reg_pr_over_sst_djf_map__' + dataset1, var2=pr_obs_slope, + var2_attributes=dict2, var2_name='reg_pr_over_sst_djf_map__' + dataset2, + global_attributes=dict3, **dict_nc) + del dict1, dict2, dict3, dict_metric, dict_nc, file_name, list_region + if prCorr is not None: + prCorr = 1 - prCorr + # Create output + EnsoPrMapMetric = { + 'name': Name, 'Rmse__value': prRmse, 'Rmse__value_error': prRmseErr, 'Rmse__units': Units, 'method': Method, + 'Corr__value': prCorr, 'Corr__value_error': prCorrErr, 'Corr__units': '', 'Std__value': prStd, + 'Std__value_error': prStdErr, 'Std__units': Units + ' / ' + Units, 'nyears_model': yearN_mod, + 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag, 'units': ''} + return EnsoPrMapMetric + + +def EnsoPrMapJja(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + prfilemod, prnamemod, prareafilemod, prareanamemod, prlandmaskfilemod, prlandmasknamemod, sstfileobs, + sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, prfileobs, + prnameobs, prareafileobs, prareanameobs, prlandmaskfileobs, prlandmasknameobs, sstbox, prbox, + event_definition, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, + netcdf_name='', metname='', **kwargs): + """ + The EnsoPrMapJja() function computes precipitation anomalies pattern associated with ENSO on the globe. + First metric: rmse(observations vs model). + Second metric: correlation(observations vs model). + Third metric: std(model)/std(observations) + These metrics can be used to compute a Taylor diagram. + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST areacell + :param sstareanamemod: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafilemod' + :param sstlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST landmask + :param sstlandmasknamemod: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfilemod' + :param prfilemod: string + path_to/filename of the file (NetCDF) of the modeled PR + :param prnamemod: string + name of PR variable (pr) in 'prfilemod' + :param prareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled PR areacell + :param prareanamemod: string, optional + name of areacell for the PR variable (areacella, areacello,...) in 'prareafilemod' + :param prlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled PR landmask + :param prlandmasknamemod: string, optional + name of landmask for the PR variable (sftlf,...) in 'prlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST areacell + :param sstareanameobs: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafileobs' + :param sstlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST landmask + :param sstlandmasknameobs: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfileobs' + :param prfileobs: string + path_to/filename of the file (NetCDF) of the observed PR + :param prnameobs: string + name of PR variable (pr, precip) in 'prfileobs' + :param prareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed PR areacell + :param prareanameobs: string, optional + name of areacell for the PR variable (areacella, areacello,...) in 'prareafileobs' + :param prlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed PR landmask + :param prlandmasknameobs: string, optional + name of landmask for the PR variable (sftlf,...) in 'prlandmaskfileobs' + :param sstbox: string + name of box (e.g. 'global') for SST + :param prbox: string + name of box (e.g. 'global') for PR + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': -0.75} + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return EnsoPrMapMetric: dict + name, value (rms [obs;model]), value_error, units, method, value2 (corr [obs;model]), + value_error2, units2, value3 (std_model / std_obs), value_error3, units3, nyears_model, nyears_observations, + time_frequency, time_period_mod, time_period_obs, ref, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = "ENSO JJA PRA pattern" + Method = region_ev + " SSTA regressed against precipitation anomalies in " + prbox + " during JJA" + if kwargs['normalization']: + Units = "" + else: + Units = "mm/day/C" + Ref = 'Using CDAT regridding, correlation (centered and biased), std (centered and biased) and ' + \ + 'rms (uncentered and biased) calculation' + metric = "EnsoPrMapJja" + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod_box, mod_areacell, keyerror_mod1 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs_box, obs_areacell, keyerror_obs1 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + pr_mod, pr_mod_areacell, keyerror_mod2 = Read_data_mask_area( + prfilemod, prnamemod, 'precipitations', metric, prbox, file_area=prareafilemod, name_area=prareanamemod, + file_mask=prlandmaskfilemod, name_mask=prlandmasknamemod, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + pr_obs, pr_obs_areacell, keyerror_obs2 = Read_data_mask_area( + prfileobs, prnameobs, 'precipitations', metric, prbox, file_area=prareafileobs, name_area=prareanameobs, + file_mask=prlandmaskfileobs, name_mask=prlandmasknameobs, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst_mod_box, pr_mod, keyerror_mod3 = CheckTime(sst_mod_box, pr_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs_box, pr_obs, keyerror_obs3 = CheckTime(sst_obs_box, pr_obs, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod_box.shape[0] / 12)) + yearN_obs = int(round(sst_obs_box.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod_box) + actualtimebounds_obs = TimeBounds(sst_obs_box) + + if (keyerror_mod1 is not None or keyerror_obs1 is not None or keyerror_mod2 is not None) or \ + (keyerror_obs2 is not None or keyerror_mod3 is not None or keyerror_obs3 is not None): + prCorr, prCorrErr, prRmse, prRmseErr, prStd, prStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + tmp = [keyerror_mod1, keyerror_mod2, keyerror_mod3, keyerror_obs1, keyerror_obs2, keyerror_obs3] + keyerror = add_up_errors(tmp) + else: + # smoothing is not applied + if 'smoothing' in list(kwargs.keys()): + smooth = deepcopy(kwargs['smoothing']) + kwargs['smoothing'] = False + else: + smooth = False + # ------------------------------------------------ + # 1. SSTA + # ------------------------------------------------ + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, _, keyerror_mod = PreProcessTS( + sst_mod_box, '', areacell=mod_areacell, average='horizontal', compute_anom=False, region=region_ev, + **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs_box, '', areacell=obs_areacell, average='horizontal', compute_anom=False, region=region_ev, + **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + prCorr, prCorrErr, prRmse, prRmseErr, prStd, prStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sst_mod)), + 'time2': '(obs) ' + str(TimeBounds(sst_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 Seasonal mean and anomalies + enso_mod = SeasonalMean(sst_mod, "JJA", compute_anom=True) + enso_obs = SeasonalMean(sst_obs, "JJA", compute_anom=True) + if debug is True: + dict_debug = { + 'axes1': '(mod) ' + str([ax.id for ax in enso_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(enso_mod.shape), 'shape2': '(obs) ' + str(enso_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(enso_mod)), 'time2': '(obs) ' + str(TimeBounds(enso_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # ------------------------------------------------ + # 2. PRA + # ------------------------------------------------ + # 2.1 PRA in 'prbox' are normalized / detrended / smoothed (running average) if applicable + # Preprocess pr (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + pr_mod, Method, keyerror_mod = PreProcessTS( + pr_mod, Method, areacell=pr_mod_areacell, compute_anom=False, region=prbox, **kwargs) + pr_obs, _, keyerror_obs = PreProcessTS( + pr_obs, '', areacell=pr_obs_areacell, compute_anom=False, region=prbox, **kwargs) + del pr_mod_areacell, pr_obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + prCorr, prCorrErr, prRmse, prRmseErr, prStd, prStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(pr_mod)), + 'time2': '(obs) ' + str(TimeBounds(pr_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 2.2 Seasonal mean and anomalies + pr_mod = SeasonalMean(pr_mod, "JJA", compute_anom=True) + pr_obs = SeasonalMean(pr_obs, "JJA", compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(pr_mod)), + 'time2': '(obs) ' + str(TimeBounds(pr_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # ------------------------------------------------ + # 3. Regression map + # ------------------------------------------------ + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + pr_mod, pr_obs, Method = TwoVarRegrid(pr_mod, pr_obs, Method, region=prbox, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # regression + pr_mod_slope = LinearRegressionTsAgainstMap(pr_mod, enso_mod, return_stderr=False) + pr_obs_slope = LinearRegressionTsAgainstMap(pr_obs, enso_obs, return_stderr=False) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs_slope.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod_slope.shape), + 'shape2': '(obs) ' + str(pr_obs_slope.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after LinearRegressionTsAgainstMap', 15, **dict_debug) + + # mask Pacific + pr_mod_slope, keyerror_mod = BasinMask( + pr_mod_slope, 'pacific', box=prbox, lat1=-15, lat2=15, latkey='between', debug=debug) + pr_obs_slope, keyerror_obs = BasinMask( + pr_obs_slope, 'pacific', box=prbox, lat1=-15, lat2=15, latkey='between', debug=debug) + if keyerror_mod is not None or keyerror_obs is not None: + prCorr, prCorrErr, prRmse, prRmseErr, prStd, prStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs_slope.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod_slope.shape), + 'shape2': '(obs) ' + str(pr_obs_slope.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after BasinMask', 15, **dict_debug) + + # Metric 1 + prRmse, keyerror = RmsAxis( + pr_mod_slope, pr_obs_slope, axis='xy', centered=centered_rmse, biased=biased_rmse) + prRmseErr = None + # Metric 2 + prCorr = float(Correlation(pr_mod_slope, pr_obs_slope, axis='xy', centered=1, biased=1)) + prCorrErr = None + # Metric 3 + std_mod = Std(pr_mod_slope, weights=None, axis='xy', centered=1, biased=1) + std_obs = Std(pr_obs_slope, weights=None, axis='xy', centered=1, biased=1) + prStd = float(std_mod) / float(std_obs) + prStdErr = None + + # Dive down diagnostic + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + + if netcdf is True: + # read + pr_mod_land, pr_mod_areacell, keyerror_mod1 = Read_data_mask_area( + prfilemod, prnamemod, 'precipitations', metric, prbox, file_area=prareafilemod, + name_area=prareanamemod, file_mask=prlandmaskfilemod, name_mask=prlandmasknamemod, + maskland=False, maskocean=True, time_bounds=kwargs['time_bounds_mod'], debug=debug, + **kwargs) + pr_obs_land, pr_obs_areacell, keyerror_obs1 = Read_data_mask_area( + prfileobs, prnameobs, 'precipitations', metric, prbox, file_area=prareafileobs, + name_area=prareanameobs, file_mask=prlandmaskfileobs, name_mask=prlandmasknameobs, + maskland=False, maskocean=True, time_bounds=kwargs['time_bounds_obs'], debug=debug, + **kwargs) + sst_mod, pr_mod_land, keyerror_mod1 = CheckTime( + sst_mod, pr_mod_land, metric_name=metric, debug=debug, **kwargs) + sst_obs, pr_obs_land, keyerror_obs2 = CheckTime( + sst_obs, pr_obs_land, metric_name=metric, debug=debug, **kwargs) + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + # process + pr_mod_land, _, keyerror_mod = PreProcessTS( + pr_mod_land, '', areacell=pr_mod_areacell, compute_anom=False, region=prbox, **kwargs) + pr_obs_land, _, keyerror_obs = PreProcessTS( + pr_obs_land, '', areacell=pr_obs_areacell, compute_anom=False, region=prbox, **kwargs) + del pr_mod_areacell, pr_obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = { + 'axes1': '(mod) ' + str([ax.id for ax in pr_mod_land.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs_land.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod_land.shape), + 'shape2': '(obs) ' + str(pr_obs_land.shape), + 'time1': '(mod) ' + str(TimeBounds(pr_mod_land)), + 'time2': '(obs) ' + str(TimeBounds(pr_obs_land))} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after PreProcessTS', 15, **dict_debug) + # anomalies + pr_mod_land = SeasonalMean(pr_mod_land, "JJA", compute_anom=True) + pr_obs_land = SeasonalMean(pr_obs_land, "JJA", compute_anom=True) + if debug is True: + dict_debug = { + 'axes1': '(mod) ' + str([ax.id for ax in pr_mod_land.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs_land.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod_land.shape), + 'shape2': '(obs) ' + str(pr_obs_land.shape), + 'time1': '(mod) ' + str(TimeBounds(pr_mod_land)), + 'time2': '(obs) ' + str(TimeBounds(pr_obs_land))} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after SeasonalMean', 15, **dict_debug) + # regridding + if isinstance(kwargs['regridding'], dict): + pr_mod_land, pr_obs_land, _ = TwoVarRegrid( + pr_mod_land, pr_obs_land, '', region=prbox, **kwargs['regridding']) + if debug is True: + dict_debug = { + 'axes1': '(mod) ' + str([ax.id for ax in pr_mod_land.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs_land.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod_land.shape), + 'shape2': '(obs) ' + str(pr_obs_land.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after TwoVarRegrid', 15, **dict_debug) + # regression + pr_mod_land_slope = LinearRegressionTsAgainstMap( + pr_mod_land, enso_mod, return_stderr=False) + pr_obs_land_slope = LinearRegressionTsAgainstMap( + pr_obs_land, enso_obs, return_stderr=False) + if debug is True: + dict_debug = { + 'axes1': '(mod) ' + str([ax.id for ax in pr_mod_land_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs_land_slope.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod_land_slope.shape), + 'shape2': '(obs) ' + str(pr_obs_land_slope.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after LinearRegressionTsAgainstMap', 15, **dict_debug) + # ENSO events: SSTA > (<) 'threshold' during 'season' are considered as El Nino + # (La Nina) events + if 'smoothing' in list(kwargs.keys()): + kwargs['smoothing'] = smooth + del smooth + sst_mod, _, keyerror_mod = PreProcessTS( + sst_mod_box, '', areacell=mod_areacell, average='horizontal', compute_anom=False, + region=region_ev, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs_box, '', areacell=obs_areacell, average='horizontal', compute_anom=False, + region=region_ev, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Lists event years + nino_mod_years = DetectEvents( + sst_mod, season_ev, threshold, normalization=normalize, nino=True) + nina_mod_years = DetectEvents( + sst_mod, season_ev, -threshold, normalization=normalize, nino=False) + nino_obs_years = DetectEvents( + sst_obs, season_ev, threshold, normalization=normalize, nino=True) + nina_obs_years = DetectEvents( + sst_obs, season_ev, -threshold, normalization=normalize, nino=False) + if debug is True: + dict_debug = {'nino1': '(mod) nbr(' + str(len(nino_mod_years)) + '): ' + + str(nino_mod_years), + 'nina1': '(mod) nbr(' + str(len(nina_mod_years)) + '): ' + + str(nina_mod_years), + 'nino2': '(obs) nbr(' + str(len(nino_obs_years)) + '): ' + + str(nino_obs_years), + 'nina2': '(obs) nbr(' + str(len(nina_obs_years)) + '): ' + + str(nina_obs_years)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after DetectEvents', 15, **dict_debug) + # samples + nino_mod = Composite(pr_mod, nino_mod_years, kwargs['frequency']) + nina_mod = Composite(pr_mod, nina_mod_years, kwargs['frequency']) + nino_obs = Composite(pr_obs, nino_obs_years, kwargs['frequency']) + nina_obs = Composite(pr_obs, nina_obs_years, kwargs['frequency']) + nino_mod_land = Composite(pr_mod_land, nino_mod_years, kwargs['frequency']) + nina_mod_land = Composite(pr_mod_land, nina_mod_years, kwargs['frequency']) + nino_obs_land = Composite(pr_obs_land, nino_obs_years, kwargs['frequency']) + nina_obs_land = Composite(pr_obs_land, nina_obs_years, kwargs['frequency']) + # mask Pacific + nino_mod, keyerror_mod1 = BasinMask( + nino_mod, 'pacific', box=prbox, lat1=-15, lat2=15, latkey='between', + debug=debug) + nina_mod, keyerror_mod2 = BasinMask( + nina_mod, 'pacific', box=prbox, lat1=-15, lat2=15, latkey='between', + debug=debug) + nino_obs, keyerror_obs1 = BasinMask( + nino_obs, 'pacific', box=prbox, lat1=-15, lat2=15, latkey='between', + debug=debug) + nina_obs, keyerror_obs2 = BasinMask( + nina_obs, 'pacific', box=prbox, lat1=-15, lat2=15, latkey='between', + debug=debug) + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + keyerror = add_up_errors( + [keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + # Metrics ENSO events global + dict_metric, dict_nc = dict(), dict() + nbr = 3 + for jj, (evname, tab1, tab2, ev1, ev2) in enumerate( + zip(["nino", "nina"], [nino_mod, nina_mod], [nino_obs, nina_obs], + [nino_mod_years, nina_mod_years], + [nino_obs_years, nina_obs_years])): + dict_metric, dict_nc = fill_dict_teleconnection( + tab1, tab2, dataset1, dataset2, actualtimebounds_mod, + actualtimebounds_obs, yearN_mod, yearN_obs, nbr, + "pr_" + evname + "_jja_map__", evname, "mm/day", + centered_rmse=centered_rmse, biased_rmse=biased_rmse, + dict_metric=dict_metric, dict_nc=dict_nc, ev_name=evname, events1=ev1, + events2=ev2) + nbr += 2 + list_region = ["africaSE", "americaN", "americaS", "asiaS", "oceania"] + for ii, reg in enumerate(list_region): + # select region + dictreg = ReferenceRegions(reg) + tmp1 = pr_mod_land_slope( + longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + tmp2 = pr_obs_land_slope( + longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + dict_metric, dict_nc = fill_dict_teleconnection( + tmp1, tmp2, dataset1, dataset2, actualtimebounds_mod, + actualtimebounds_obs, yearN_mod, yearN_obs, nbr, + "reg_pr_over_sst_jja_map_" + reg + "__", reg, Units, + centered_rmse=centered_rmse, biased_rmse=biased_rmse, + dict_metric=dict_metric, dict_nc=dict_nc) + nbr += 2 + for jj, (evname, tab1, tab2, ev1, ev2) in enumerate( + zip(["nino", "nina"], [nino_mod_land, nina_mod_land], + [nino_obs_land, nina_obs_land], + [nino_mod_years, nina_mod_years], + [nino_obs_years, nina_obs_years])): + tmp1 = tab1( + longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + tmp2 = tab2( + longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + dict_metric, dict_nc = fill_dict_teleconnection( + tmp1, tmp2, dataset1, dataset2, actualtimebounds_mod, + actualtimebounds_obs, yearN_mod, yearN_obs, nbr, + "pr_" + evname + "_jja_map_" + reg + "__", evname + "_" + reg, + "mm/day", centered_rmse=centered_rmse, biased_rmse=biased_rmse, + dict_metric=dict_metric, dict_nc=dict_nc, ev_name=evname, + events1=ev1, events2=ev2) + nbr += 2 + del dictreg, tmp1, tmp2 + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'spatialSTD_' + dataset1: std_mod} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'spatialSTD_' + dataset2: std_obs} + dict3 = {'metric_name': Name, 'metric_valueRMSE_' + dataset2: prRmse, + 'metric_valueRMSE_error_' + dataset2: prRmseErr, + 'metric_valueCORR_' + dataset2: prCorr, + 'metric_valueCORR_error_' + dataset2: prCorrErr, + 'metric_valueSTD_' + dataset2: prStd, + 'metric_valueSTD_error_' + dataset2: prStdErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + dict3.update(dict_metric) + SaveNetcdf( + file_name, var1=pr_mod_slope, var1_attributes=dict1, + var1_name='reg_pr_over_sst_jja_map__' + dataset1, var2=pr_obs_slope, + var2_attributes=dict2, var2_name='reg_pr_over_sst_jja_map__' + dataset2, + global_attributes=dict3, **dict_nc) + del dict1, dict2, dict3, dict_metric, dict_nc, file_name, list_region + if prCorr is not None: + prCorr = 1 - prCorr + # Create output + EnsoPrMapMetric = { + 'name': Name, 'Rmse__value': prRmse, 'Rmse__value_error': prRmseErr, 'Rmse__units': Units, 'method': Method, + 'Corr__value': prCorr, 'Corr__value_error': prCorrErr, 'Corr__units': '', 'Std__value': prStd, + 'Std__value_error': prStdErr, 'Std__units': Units + ' / ' + Units, 'nyears_model': yearN_mod, + 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag, 'units': ''} + return EnsoPrMapMetric + + +def EnsoPrDjfTel(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + prfilemod, prnamemod, prareafilemod, prareanamemod, prlandmaskfilemod, prlandmasknamemod, sstfileobs, + sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, prfileobs, + prnameobs, prareafileobs, prareanameobs, prlandmaskfileobs, prlandmasknameobs, sstbox, prbox, + event_definition, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, + netcdf_name='', metname='', **kwargs): + """ + The EnsoPrDjfTel() function computes precipitations anomalies associated with El Nino and La Nina events in many AR5 + reference regions, then precipitations during DJF preceding the events are composited for each selected event + and the difference (El Nino PR - La Nina PR) is computed in each region. + The first rmse(observations vs model) is the metric. + The second metric is the number of regions where observations and models agree on the sign of the teleconnection + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param prfilemod: string + path_to/filename of the file (NetCDF) of the modeled PR + :param prnamemod: string + name of PR variable (pr) in 'prfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param prfileobs: string + path_to/filename of the file (NetCDF) of the observed PR + :param prnameobs: string + name of PR variable (pr, precip) in 'prfileobs' + :param box: string + name of box (e.g. 'nino3') for SST + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': 0.75} + :param sstareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST areacell + :param sstareanamemod: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafilemod' + :param sstlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST landmask + :param sstlandmasknamemod: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfilemod' + :param prareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled PR areacell + :param prareanamemod: string, optional + name of areacell for the PR variable (areacella, areacello,...) in 'prareafilemod' + :param prlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled PR landmask + :param prlandmasknamemod: string, optional + name of landmask for the PR variable (sftlf,...) in 'prlandmaskfilemod' + :param sstareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST areacell + :param sstareanameobs: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafileobs' + :param sstlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST landmask + :param sstlandmasknameobs: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfileobs' + :param prareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed PR areacell + :param prareanameobs: string, optional + name of areacell for the PR variable (areacella, areacello,...) in 'prareafileobs' + :param prlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed PR landmask + :param prlandmasknameobs: string, optional + name of landmask for the PR variable (sftlf,...) in 'prlandmaskfileobs' + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return EnsoPrTelMetric: dict + name, Rmse__value (rms [NinoPr-NinaPr]), Rmse__value_error, Rmse__units, method, + SignAgree__value (sign agreement [NinoPr-NinaPr]), SignAgree__value_error, SignAgree__units, nyears_model, + nyears_observations, nina_model, nino_model, nina_observations, nino_observations, time_frequency, + time_period_model, time_period_observations, ref, keyerror, dive_down_diag, units + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'Nino composite minus Nina composite during DJF preceeding the events in each region' + Method = "Nino events = " + region_ev + " sstA > " + str(threshold) + ", Nina events = " + region_ev + " sstA < -" \ + + str(threshold) + " during " + season_ev + "; Precipitations associated with El Nino/La Nina events " + \ + " during the preceeding DJF are composited and the difference (El Nino PR - La Nina PR) is computed in" + \ + " each region" + if kwargs['normalization']: + Units = '' + else: + Units = 'mm/day' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = 'EnsoPrDjfTel' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod1 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs1 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if not isinstance(prbox, list): + prbox = [prbox] + prbox = sorted(prbox, key=str.lower) + prmap_mod, _, keyerror_mod2 = Read_data_mask_area( + prfilemod, prnamemod, 'precipitations', metric, prbox[0], file_area=prareafilemod, name_area=prareanamemod, + file_mask=prlandmaskfilemod, name_mask=prlandmasknamemod, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + prmap_obs, _, keyerror_obs2 = Read_data_mask_area( + prfileobs, prnameobs, 'precipitations', metric, prbox[0], file_area=prareafileobs, name_area=prareanameobs, + file_mask=prlandmaskfileobs, name_mask=prlandmasknameobs, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst_mod, prmap_mod, keyerror_mod3 = CheckTime(sst_mod, prmap_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs, prmap_obs, keyerror_obs3 = CheckTime(sst_obs, prmap_obs, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if (keyerror_mod1 is not None or keyerror_obs1 is not None or keyerror_mod2 is not None) or \ + (keyerror_obs2 is not None or keyerror_mod3 is not None or keyerror_obs3 is not None): + compRmse, compRmseErr, signAgreement, signAgreementErr = None, None, None, None + nina_years_mod, nina_years_obs, nino_years_mod, nino_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + tmp = [keyerror_mod1, keyerror_mod2, keyerror_mod3, keyerror_obs1, keyerror_obs2, keyerror_obs3] + keyerror = add_up_errors(tmp) + else: + # ------------------------------------------------ + # 1. detect events + # ------------------------------------------------ + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, _, keyerror_mod = PreProcessTS( + sst_mod, '', areacell=mod_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs: + compRmse, compRmseErr, signAgreement, signAgreementErr = None, None, None, None + nina_years_mod, nina_years_obs, nino_years_mod, nino_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sst_mod)), + 'time2': '(obs) ' + str(TimeBounds(sst_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 SSTA < 'threshold' (SSTA > 'threshold') during 'season' are considered as La Nina (El Nino) events + # Lists event years + nina_years_mod = DetectEvents(sst_mod, season_ev, -threshold, normalization=normalize, nino=False) + nino_years_mod = DetectEvents(sst_mod, season_ev, threshold, normalization=normalize, nino=True) + nina_years_obs = DetectEvents(sst_obs, season_ev, -threshold, normalization=normalize, nino=False) + nino_years_obs = DetectEvents(sst_obs, season_ev, threshold, normalization=normalize, nino=True) + if debug is True: + dict_debug = {'nina1': '(mod) ' + str(nina_years_mod), 'nina2': '(obs) ' + str(nina_years_obs), + 'nino1': '(mod) ' + str(nino_years_mod), 'nino2': '(obs) ' + str(nino_years_obs)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + + # ------------------------------------------------ + # 2. compute composite + # ------------------------------------------------ + # smoothing is not applied + if 'smoothing' in list(kwargs.keys()): + smooth = deepcopy(kwargs['smoothing']) + kwargs['smoothing'] = False + else: + smooth = False + list_composite_mod, list_composite_obs = list(), list() + loop_keyerror = None + loop_box = list() + for reg in prbox: + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', 'region = ' + str(reg), 15) + # Read if the given region is defined as a land region, an oceanic region, or both + dict_reg = ReferenceRegions(reg) + maskland = dict_reg['maskland'] if 'maskland' in list(dict_reg.keys()) else False + maskoce = dict_reg['maskocean'] if 'maskocean' in list(dict_reg.keys()) else False + pr_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + prfilemod, prnamemod, 'precipitations', metric, reg, file_area=prareafilemod, + name_area=prareanamemod, file_mask=prlandmaskfilemod, name_mask=prlandmasknamemod, + maskland=maskland, maskocean=maskoce, time_bounds=kwargs['time_bounds_mod'], debug=debug, + **kwargs) + pr_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + prfileobs, prnameobs, 'precipitations', metric, reg, file_area=prareafileobs, + name_area=prareanameobs, file_mask=prlandmaskfileobs, name_mask=prlandmasknameobs, + maskland=maskland, maskocean=maskoce, time_bounds=kwargs['time_bounds_obs'], debug=debug, + **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + loop_keyerror = add_up_errors([loop_keyerror, keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(pr_mod)), + 'time2': '(obs) ' + str(TimeBounds(pr_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Read_data_mask_area', 20, **dict_debug) + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + pr_mod, Method, keyerror_mod = PreProcessTS( + pr_mod, Method, areacell=mod_areacell, average='horizontal', compute_anom=False, region=reg, + **kwargs) + pr_obs, _, keyerror_obs = PreProcessTS( + pr_obs, '', areacell=obs_areacell, average='horizontal', compute_anom=False, region=reg, + **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + loop_keyerror = add_up_errors([loop_keyerror, keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), + 'shape2': '(obs) ' + str(pr_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(pr_mod)), + 'time2': '(obs) ' + str(TimeBounds(pr_obs))} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after PreProcessTS ' + str(reg), 20, **dict_debug) + + # Seasonal mean + pr_mod = SeasonalMean(pr_mod, 'DJF', compute_anom=False) + pr_obs = SeasonalMean(pr_obs, 'DJF', compute_anom=False) + + # composites + composite_nina_mod = Composite(pr_mod, nina_years_mod, kwargs['frequency']) + composite_nino_mod = Composite(pr_mod, nino_years_mod, kwargs['frequency']) + composite_nina_obs = Composite(pr_obs, nina_years_obs, kwargs['frequency']) + composite_nino_obs = Composite(pr_obs, nino_years_obs, kwargs['frequency']) + + # list composites + list_composite_mod.append(float(composite_nino_mod - composite_nina_mod)) + list_composite_obs.append(float(composite_nino_obs - composite_nina_obs)) + loop_box.append(reg) + del composite_nina_mod, composite_nina_obs, composite_nino_mod, composite_nino_obs + del dict_reg, keyerror_mod, keyerror_obs, maskland, maskoce, mod_areacell, obs_areacell, pr_mod, pr_obs + + # create arrays + ar5 = 'AR5 reference regions' + ref = 'https://www.ipcc-data.org/guidelines/pages/ar5_regions.html' + list_composite_mod = ArrayListAx( + list_composite_mod, loop_box, ax_name_ax='box', ax_long_name=ar5, ax_ref=ref) + list_composite_obs = ArrayListAx( + list_composite_obs, loop_box, ax_name_ax='box', ax_long_name=ar5, ax_ref=ref) + + if 'smoothing' in list(kwargs.keys()): + kwargs['smoothing'] = smooth + del smooth + + # Computes the root mean square difference + compRmse, keyerror = RmsAxis( + list_composite_mod, list_composite_obs, centered=centered_rmse, biased=biased_rmse) + compRmseErr = None + + # Computes the percentage of regions where observations and model agree on the sign of the teleconnection + signAgreement = sum([1. for vmod, vobs in zip(list_composite_mod, list_composite_obs) + if NUMPYsign(vmod) == NUMPYsign(vobs)]) / len(list_composite_mod) + signAgreementErr = NUMPYsqrt(signAgreement * (1 - signAgreement) / len(list_composite_mod)) * 1.65 + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(list_composite_mod), 'observations': ArrayToList(list_composite_obs), + 'axis': loop_box} + if keyerror is not None or loop_keyerror is not None: + keyerror = add_up_errors([keyerror, loop_keyerror]) + + if netcdf is True: + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, 'time_period': str(actualtimebounds_mod), + 'nina_years': str(nina_years_mod), 'nino_years': str(nino_years_mod)} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, 'time_period': str(actualtimebounds_obs), + 'nina_years': str(nina_years_obs), 'nino_years': str(nino_years_obs)} + dict3 = { + 'metric_name': Name, 'metric_valueRMSE_' + dataset2: compRmse, + 'metric_valueRMSE_error_' + dataset2: compRmseErr, + 'metric_valueSignAgree_' + dataset2: signAgreement, + 'metric_valueSignAgree_error_' + dataset2: signAgreementErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf(file_name, var1=list_composite_mod, var1_attributes=dict1, + var1_name='prComp_box__' + dataset1, var2=list_composite_obs, var2_attributes=dict2, + var2_name='prComp_box__' + dataset2, global_attributes=dict3) + del dict1, dict2, dict3, file_name + + # Create output + EnsoPrTelMetric = { + 'name': Name, 'Rmse__value': compRmse, 'Rmse__value_error': signAgreement, 'Rmse__units': Units, + 'method': Method, 'SignAgree__value': signAgreement, 'SignAgree__value_error': signAgreementErr, + 'SignAgree__units': '%', 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, + 'nina_model': nina_years_mod, 'nino_model': nino_years_mod, 'nina_observations': nina_years_obs, + 'nino_observations': nino_years_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag, 'units': ''} + return EnsoPrTelMetric + + +def EnsoPrJjaTel(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + prfilemod, prnamemod, prareafilemod, prareanamemod, prlandmaskfilemod, prlandmasknamemod, sstfileobs, + sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, prfileobs, + prnameobs, prareafileobs, prareanameobs, prlandmaskfileobs, prlandmasknameobs, sstbox, prbox, + event_definition, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, + netcdf_name='', metname='', **kwargs): + """ + The EnsoPrJjaTel() function computes precipitations anomalies associated with El Nino and La Nina events in many AR5 + reference regions, then precipitations during JJA preceding the events are composited for each selected event + and the difference (El Nino PR - La Nina PR) is computed in each region. + The first rmse(observations vs model) is the metric. + The second metric is the number of regions where observations and models agree on the sign of the teleconnection + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param prfilemod: string + path_to/filename of the file (NetCDF) of the modeled PR + :param prnamemod: string + name of PR variable (pr) in 'prfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param prfileobs: string + path_to/filename of the file (NetCDF) of the observed PR + :param prnameobs: string + name of PR variable (pr, precip) in 'prfileobs' + :param box: string + name of box (e.g. 'nino3') for SST + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': 0.75} + :param sstareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST areacell + :param sstareanamemod: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafilemod' + :param sstlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST landmask + :param sstlandmasknamemod: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfilemod' + :param prareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled PR areacell + :param prareanamemod: string, optional + name of areacell for the PR variable (areacella, areacello,...) in 'prareafilemod' + :param prlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled PR landmask + :param prlandmasknamemod: string, optional + name of landmask for the PR variable (sftlf,...) in 'prlandmaskfilemod' + :param sstareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST areacell + :param sstareanameobs: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafileobs' + :param sstlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST landmask + :param sstlandmasknameobs: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfileobs' + :param prareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed PR areacell + :param prareanameobs: string, optional + name of areacell for the PR variable (areacella, areacello,...) in 'prareafileobs' + :param prlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed PR landmask + :param prlandmasknameobs: string, optional + name of landmask for the PR variable (sftlf,...) in 'prlandmaskfileobs' + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return EnsoPrTelMetric: dict + name, Rmse__value (rms [NinoPr-NinaPr]), Rmse__value_error, Rmse__units, method, + SignAgree__value (sign agreement [NinoPr-NinaPr]), SignAgree__value_error, SignAgree__units, nyears_model, + nyears_observations, nina_model, nino_model, nina_observations, nino_observations, time_frequency, + time_period_model, time_period_observations, ref, keyerror, dive_down_diag, units + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'Nino composite minus Nina composite during JJA preceeding the events in each region' + Method = "Nino events = " + region_ev + " sstA > " + str(threshold) + ", Nina events = " + region_ev + " sstA < -"\ + + str(threshold) + " during " + season_ev + "; Precipitations associated with El Nino/La Nina events " + \ + " during the preceeding JJA are composited and the difference (El Nino PR - La Nina PR) is computed in" + \ + " each region" + if kwargs['normalization']: + Units = '' + else: + Units = 'mm/day' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = 'EnsoPrJjaTel' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod1 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs1 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if not isinstance(prbox, list): + prbox = [prbox] + prbox = sorted(prbox, key=str.lower) + prmap_mod, _, keyerror_mod2 = Read_data_mask_area( + prfilemod, prnamemod, 'precipitations', metric, prbox[0], file_area=prareafilemod, name_area=prareanamemod, + file_mask=prlandmaskfilemod, name_mask=prlandmasknamemod, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + prmap_obs, _, keyerror_obs2 = Read_data_mask_area( + prfileobs, prnameobs, 'precipitations', metric, prbox[0], file_area=prareafileobs, name_area=prareanameobs, + file_mask=prlandmaskfileobs, name_mask=prlandmasknameobs, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst_mod, prmap_mod, keyerror_mod3 = CheckTime(sst_mod, prmap_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs, prmap_obs, keyerror_obs3 = CheckTime(sst_obs, prmap_obs, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if (keyerror_mod1 is not None or keyerror_obs1 is not None or keyerror_mod2 is not None) or \ + (keyerror_obs2 is not None or keyerror_mod3 is not None or keyerror_obs3 is not None): + compRmse, compRmseErr, signAgreement, signAgreementErr = None, None, None, None + nina_years_mod, nina_years_obs, nino_years_mod, nino_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + tmp = [keyerror_mod1, keyerror_mod2, keyerror_mod3, keyerror_obs1, keyerror_obs2, keyerror_obs3] + keyerror = add_up_errors(tmp) + else: + # ------------------------------------------------ + # 1. detect events + # ------------------------------------------------ + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, _, keyerror_mod = PreProcessTS( + sst_mod, '', areacell=mod_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs: + compRmse, compRmseErr, signAgreement, signAgreementErr = None, None, None, None + nina_years_mod, nina_years_obs, nino_years_mod, nino_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sst_mod)), + 'time2': '(obs) ' + str(TimeBounds(sst_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 SSTA < 'threshold' (SSTA > 'threshold') during 'season' are considered as La Nina (El Nino) events + # Lists event years + nina_years_mod = DetectEvents(sst_mod, season_ev, -threshold, normalization=normalize, nino=False) + nino_years_mod = DetectEvents(sst_mod, season_ev, threshold, normalization=normalize, nino=True) + nina_years_obs = DetectEvents(sst_obs, season_ev, -threshold, normalization=normalize, nino=False) + nino_years_obs = DetectEvents(sst_obs, season_ev, threshold, normalization=normalize, nino=True) + if debug is True: + dict_debug = {'nina1': '(mod) ' + str(nina_years_mod), 'nina2': '(obs) ' + str(nina_years_obs), + 'nino1': '(mod) ' + str(nino_years_mod), 'nino2': '(obs) ' + str(nino_years_obs)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + + # ------------------------------------------------ + # 2. compute composite + # ------------------------------------------------ + # smoothing is not applied + if 'smoothing' in list(kwargs.keys()): + smooth = deepcopy(kwargs['smoothing']) + kwargs['smoothing'] = False + else: + smooth = False + list_composite_mod, list_composite_obs = list(), list() + loop_keyerror = None + loop_box = list() + for reg in prbox: + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', 'region = ' + str(reg), 15) + # Read if the given region is defined as a land region, an oceanic region, or both + dict_reg = ReferenceRegions(reg) + maskland = dict_reg['maskland'] if 'maskland' in list(dict_reg.keys()) else False + maskoce = dict_reg['maskocean'] if 'maskocean' in list(dict_reg.keys()) else False + pr_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + prfilemod, prnamemod, 'precipitations', metric, reg, file_area=prareafilemod, + name_area=prareanamemod, file_mask=prlandmaskfilemod, name_mask=prlandmasknamemod, + maskland=maskland, maskocean=maskoce, time_bounds=kwargs['time_bounds_mod'], debug=debug, + **kwargs) + pr_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + prfileobs, prnameobs, 'precipitations', metric, reg, file_area=prareafileobs, + name_area=prareanameobs, file_mask=prlandmaskfileobs, name_mask=prlandmasknameobs, + maskland=maskland, maskocean=maskoce, time_bounds=kwargs['time_bounds_obs'], debug=debug, + **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + loop_keyerror = add_up_errors([loop_keyerror, keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(pr_mod)), + 'time2': '(obs) ' + str(TimeBounds(pr_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Read_data_mask_area', 20, **dict_debug) + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + pr_mod, Method, keyerror_mod = PreProcessTS( + pr_mod, Method, areacell=mod_areacell, average='horizontal', compute_anom=False, region=reg, + **kwargs) + pr_obs, _, keyerror_obs = PreProcessTS( + pr_obs, '', areacell=obs_areacell, average='horizontal', compute_anom=False, region=reg, + **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + loop_keyerror = add_up_errors([loop_keyerror, keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), + 'shape2': '(obs) ' + str(pr_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(pr_mod)), + 'time2': '(obs) ' + str(TimeBounds(pr_obs))} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after PreProcessTS ' + str(reg), 20, **dict_debug) + + # Seasonal mean + pr_mod = SeasonalMean(pr_mod, 'JJA', compute_anom=False) + pr_obs = SeasonalMean(pr_obs, 'JJA', compute_anom=False) + + # composites + composite_nina_mod = Composite(pr_mod, nina_years_mod, kwargs['frequency']) + composite_nino_mod = Composite(pr_mod, nino_years_mod, kwargs['frequency']) + composite_nina_obs = Composite(pr_obs, nina_years_obs, kwargs['frequency']) + composite_nino_obs = Composite(pr_obs, nino_years_obs, kwargs['frequency']) + + # list composites + list_composite_mod.append(float(composite_nino_mod-composite_nina_mod)) + list_composite_obs.append(float(composite_nino_obs-composite_nina_obs)) + loop_box.append(reg) + del composite_nina_mod, composite_nina_obs, composite_nino_mod, composite_nino_obs + del dict_reg, keyerror_mod, keyerror_obs, maskland, maskoce, mod_areacell, obs_areacell, pr_mod, pr_obs + + # create arrays + ar5 = 'AR5 reference regions' + ref = 'https://www.ipcc-data.org/guidelines/pages/ar5_regions.html' + list_composite_mod = ArrayListAx( + list_composite_mod, loop_box, ax_name_ax='box', ax_long_name=ar5, ax_ref=ref) + list_composite_obs = ArrayListAx( + list_composite_obs, loop_box, ax_name_ax='box', ax_long_name=ar5, ax_ref=ref) + + if 'smoothing' in list(kwargs.keys()): + kwargs['smoothing'] = smooth + del smooth + + # Computes the root mean square difference + compRmse, keyerror = RmsAxis( + list_composite_mod, list_composite_obs, centered=centered_rmse, biased=biased_rmse) + compRmseErr = None + + # Computes the percentage of regions where observations and model agree on the sign of the teleconnection + signAgreement = sum([1. for vmod,vobs in zip(list_composite_mod, list_composite_obs) + if NUMPYsign(vmod) == NUMPYsign(vobs)]) / len(list_composite_mod) + signAgreementErr = NUMPYsqrt(signAgreement * (1 - signAgreement) / len(list_composite_mod)) * 1.65 + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(list_composite_mod), 'observations': ArrayToList(list_composite_obs), + 'axis': loop_box} + if keyerror is not None or loop_keyerror is not None: + keyerror = add_up_errors([keyerror, loop_keyerror]) + + if netcdf is True: + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, 'time_period': str(actualtimebounds_mod), + 'nina_years': str(nina_years_mod), 'nino_years': str(nino_years_mod)} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, 'time_period': str(actualtimebounds_obs), + 'nina_years': str(nina_years_obs), 'nino_years': str(nino_years_obs)} + dict3 = { + 'metric_name': Name, 'metric_valueRMSE_' + dataset2: compRmse, + 'metric_valueRMSE_error_' + dataset2: compRmseErr, + 'metric_valueSignAgree_' + dataset2: signAgreement, + 'metric_valueSignAgree_error_' + dataset2: signAgreementErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf(file_name, var1=list_composite_mod, var1_attributes=dict1, + var1_name='prComp_box__' + dataset1, var2=list_composite_obs, var2_attributes=dict2, + var2_name='prComp_box__' + dataset2, global_attributes=dict3) + del dict1, dict2, dict3, file_name + + # Create output + EnsoPrTelMetric = { + 'name': Name, 'Rmse__value': compRmse, 'Rmse__value_error': signAgreement, 'Rmse__units': Units, + 'method': Method, 'SignAgree__value': signAgreement, 'SignAgree__value_error': signAgreementErr, + 'SignAgree__units': '%', 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, + 'nina_model': nina_years_mod, 'nino_model': nino_years_mod, 'nina_observations': nina_years_obs, + 'nino_observations': nino_years_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag, 'units': ''} + return EnsoPrTelMetric + + +def EnsoSeasonality(sstfile, sstname, sstareafile, sstareaname, sstlandmaskfile, sstlandmaskname, sstbox, dataset='', + debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The EnsoSeasonality() function computes ratio between the November-December-January (NDJ) and March-April-May (MAM) + average standard deviation of 'sstbox' sstA (usually nino3 sstA) + + Inputs: + ------ + :param sstfile: string + path_to/filename of the file (NetCDF) of SST + :param sstname: string + name of SST variable (tos, ts) in 'sstfile' + :param sstareafile: string + path_to/filename of the file (NetCDF) of the areacell for SST + :param sstareaname: string + name of areacell variable (areacella, areacello) in 'sstareafile' + :param sstlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for SST + :param sstlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfile' + :param sstbox: string + name of box ('nino3') for SST + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return SeaMetric: dict + name, value, value_error, units, method, nyears, time_frequency, time_period, ref, keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'ENSO seasonality' + Units = '' if kwargs['normalization'] else 'C/C' + Method = 'Ratio between NDJ and MAM standard deviation ' + sstbox + ' sstA' + Ref = 'Using CDAT std dev calculation' + metric = 'EnsoSeasonality' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst, sst_areacell, keyerror = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, sstbox, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + + # Number of years + yearN = int(round(sst.shape[0] / 12)) + + # Time period + actualtimebounds = TimeBounds(sst) + + if keyerror is not None: + ratioStd, ratio_std_err, dive_down_diag = None, None, {'value': None, 'axis': None} + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_ts, Method, keyerror = PreProcessTS( + sst, Method, areacell=sst_areacell, average='horizontal', compute_anom=False, region=sstbox, **kwargs) + if keyerror is not None: + ratioStd, ratio_std_err, dive_down_diag = None, None, {'value': None, 'axis': None} + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst_ts.getAxisList()]), + 'shape1': '(sst) ' + str(sst_ts.shape), 'time1': '(sst) ' + str(TimeBounds(sst_ts))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Seasonal mean + sst_NDJ = SeasonalMean(sst_ts, 'NDJ', compute_anom=True) + sst_MAM = SeasonalMean(sst_ts, 'MAM', compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(sst_NDJ) ' + str([ax.id for ax in sst_NDJ.getAxisList()]), + 'axes2': '(sst_NDJ) ' + str([ax.id for ax in sst_MAM.getAxisList()]), + 'shape1': '(sst_NDJ) ' + str(sst_NDJ.shape), 'shape2': '(sst_NDJ) ' + str(sst_MAM.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # Compute std dev and ratio + sst_NDJ_std = Std(sst_NDJ) + sst_MAM_std = Std(sst_MAM) + ratioStd = float(sst_NDJ_std / sst_MAM_std) + + # Standard Error of the Standard Deviation (function of nyears) + sst_NDJ_std_err = sst_NDJ_std / NUMPYsqrt(yearN - 1) + sst_MAM_std_err = sst_MAM_std / NUMPYsqrt(yearN) + # The error (dy) on ratio ('y = x/z'): dy = (z*dx + x*dz) / z2 + ratio_std_err =\ + float((sst_MAM_std * sst_NDJ_std_err + sst_NDJ_std * sst_MAM_std_err) / NUMPYsquare(sst_MAM_std)) + + # Dive down diagnostic + sst, _, keyerror = PreProcessTS( + sst, '', areacell=sst_areacell, average='horizontal', compute_anom=True, region=sstbox, **kwargs) + del sst_areacell + if keyerror is not None: + dive_down_diag = {'value': None, 'axis': None} + else: + sstStd_monthly = StdMonthly(sst) + dive_down_diag = {'value': ArrayToList(sstStd_monthly), 'axis': list(sstStd_monthly.getAxis(0)[:])} + if netcdf is True: + # additional diagnostic + # Read file and select the right region + sst1, sst_areacell1, keyerror1 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, 'equatorial_pacific_LatExt2', file_area=sstareafile, + name_area=sstareaname, file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, + maskocean=False, debug=debug, **kwargs) + sst2, sst_areacell2, keyerror2 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, 'equatorial_pacific', file_area=sstareafile, + name_area=sstareaname, file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, + maskocean=False, debug=debug, **kwargs) + if keyerror1 is not None or keyerror2 is not None: + keyerror = add_up_errors([keyerror, keyerror1, keyerror2]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, ...) + sst1, _, keyerror1 = PreProcessTS(sst1, '', areacell=sst_areacell1, compute_anom=False, + region="equatorial_pacific_LatExt2", **kwargs) + sst3, _, keyerror2 = PreProcessTS(sst2, '', areacell=sst_areacell2, compute_anom=False, + region="equatorial_pacific", **kwargs) + sst2, _, keyerror3 = PreProcessTS(sst2, '', areacell=sst_areacell2, compute_anom=True, + region="equatorial_pacific", **kwargs) + del sst_areacell1, sst_areacell2 + if keyerror1 is not None or keyerror2 is not None or keyerror3 is not None: + keyerror = add_up_errors([keyerror, keyerror1, keyerror2, keyerror3]) + else: + if debug is True: + dict_debug = { + 'axes1': '(sst1) ' + str([ax.id for ax in sst1.getAxisList()]), + 'axes2': '(sst2) ' + str([ax.id for ax in sst2.getAxisList()]), + 'axes3': '(sst3) ' + str([ax.id for ax in sst3.getAxisList()]), + 'shape1': '(sst1) ' + str(sst1.shape), 'shape2': '(sst2) ' + str(sst2.shape), + 'shape3': '(sst3) ' + str(sst3.shape), 'time1': '(sst1) ' + str(TimeBounds(sst1)), + 'time2': '(sst2) ' + str(TimeBounds(sst2)), 'time3': '(sst3) ' + str(TimeBounds(sst3))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS: netcdf', 10, **dict_debug) + # Seasonal mean + sst1_NDJ = SeasonalMean(sst1, 'NDJ', compute_anom=True) + sst1_MAM = SeasonalMean(sst1, 'MAM', compute_anom=True) + sst3_NDJ = SeasonalMean(sst3, 'NDJ', compute_anom=True) + sst3_MAM = SeasonalMean(sst3, 'MAM', compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(sst1_NDJ) ' + str([ax.id for ax in sst1_NDJ.getAxisList()]), + 'axes2': '(sst1_MAM) ' + str([ax.id for ax in sst1_MAM.getAxisList()]), + 'axes3': '(sst3_NDJ) ' + str([ax.id for ax in sst3_NDJ.getAxisList()]), + 'axes4': '(sst3_MAM) ' + str([ax.id for ax in sst3_MAM.getAxisList()]), + 'shape1': '(sst1_NDJ) ' + str(sst1_NDJ.shape), + 'shape2': '(sst1_MAM) ' + str(sst1_MAM.shape), + 'shape3': '(sst3_NDJ) ' + str(sst3_NDJ.shape), + 'shape4': '(sst3_MAM) ' + str(sst3_MAM.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean: netcdf', 15, **dict_debug) + # Compute std dev + sst1_NDJ = Std(sst1_NDJ) + sst1_MAM = Std(sst1_MAM) + sst2 = StdMonthly(sst2) + sst3_NDJ = Std(sst3_NDJ) + sst3_MAM = Std(sst3_MAM) + # Regridding + if 'regridding' not in list(kwargs.keys()): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'} + else: + if not isinstance(kwargs['regridding'], dict): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + sst1_NDJ = Regrid(sst1_NDJ, None, region='equatorial_pacific_LatExt2', **kwargs['regridding']) + sst1_MAM = Regrid(sst1_MAM, None, region='equatorial_pacific_LatExt2', **kwargs['regridding']) + sst2 = Regrid(sst2, None, region='equatorial_pacific', **kwargs['regridding']) + sst3_NDJ = Regrid(sst3_NDJ, None, region='equatorial_pacific', **kwargs['regridding']) + sst3_MAM = Regrid(sst3_MAM, None, region='equatorial_pacific', **kwargs['regridding']) + # Meridional average + sst2, keyerror1 = AverageMeridional(sst2) + sst3_NDJ, keyerror2 = AverageMeridional(sst3_NDJ) + sst3_MAM, keyerror3 = AverageMeridional(sst3_MAM) + if keyerror1 is not None or keyerror2 is not None or keyerror3 is not None: + keyerror = add_up_errors([keyerror, keyerror1, keyerror2, keyerror3]) + else: + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + my_units = '' if kwargs['normalization'] is True else 'C' + dict1 = { + 'units': my_units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': "monthly standard deviation of " + sstbox + " sstA", + 'diagnostic_value': ratioStd, 'diagnostic_value_error': ratio_std_err} + dict2 = { + 'units': my_units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': "zonal monthly standard deviation of equatorial_pacific sstA"} + dict3 = { + 'units': my_units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': "zonal standard deviation of equatorial_pacific sstA (during NDJ)"} + dict4 = { + 'units': my_units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': "zonal standard deviation of equatorial_pacific sstA (during MAM)"} + dict5 = { + 'units': my_units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': "standard deviation of equatorial_pacific sstA (during NDJ)"} + dict6 = { + 'units': my_units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': "standard deviation of equatorial_pacific sstA (during MAM)"} + dict7 = {'metric_name': Name, 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + SaveNetcdf(file_name, var1=sstStd_monthly, var1_attributes=dict1, + var1_name='sstStd_monthly__' + dataset, var2=sst2, var2_attributes=dict2, + var2_name='sstStd_hov__' + dataset, var3=sst3_NDJ, var3_attributes=dict3, + var3_name='sstStd_NDJ_lon__' + dataset, var4=sst3_MAM, var4_attributes=dict4, + var4_name='sstStd_MAM_lon__' + dataset, var5=sst1_NDJ, var5_attributes=dict5, + var5_name='sstStd_NDJ_map__' + dataset, var6=sst1_MAM, var6_attributes=dict6, + var6_name='sstStd_MAM_map__' + dataset, global_attributes=dict7) + del dict1, dict2, dict3, dict4, dict5, dict6, dict7 + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(ratioStd), 'line2': 'metric value_error: ' + str(ratio_std_err)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + seaMetric = { + 'name': Name, 'value': ratioStd, 'value_error': ratio_std_err, 'units': Units, 'method': Method, + 'nyears': yearN, 'time_frequency': kwargs['frequency'], 'time_period': actualtimebounds, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return seaMetric + + +def EnsoSstDiversity(sstfile, sstname, sstareafile, sstareaname, sstlandmaskfile, sstlandmaskname, box, + event_definition, dataset='', debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The EnsoSstDiversity() function computes a zonal composite of El Nino and La Nina events during the peak of the + event. + 1.) detect events + 1.1) SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + 1.2) SSTA > 'threshold' (SSTA < -'threshold') during 'season' are considered as El Nino (La Nina) events + 2.) diversity of the zonal location of the maximum SSTA + 2.1) zonal SSTA at the peak of the event is computed for each selected event + 2.2) find the zonal position of the maximum SSTA for each selected event + 2.3) compute the spread of the distribution + + Inputs: + ------ + :param sstfile: string + path_to/filename of the file (NetCDF) of the SST + :param sstname: string + name of SST variable (tos, ts) in 'sstfile' + :param sstareafile: string + path_to/filename of the file (NetCDF) of the areacell for SST + :param sstareaname: string + name of areacell variable (areacella, areacello) in 'sstareafile' + :param sstlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for SST + :param sstlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfile' + :param box: string + name of box ('nino3') for SST + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': 0.75} + :param dataset: string, optional + name of current dataset (e.g., 'model', 'obs', ...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param treshold_ep_ev: float, optional + see EnsoToolsLib.percentage_val_eastward + longitude, in degree east, of the westward boundary of eastern Pacific event + default value is -140°E (i.e., 140°W) + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return EnsoDivMetric: dict + name, value, value_error, units, method, nyears, events, time_frequency, time_period, ref, keyerror, + dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + my_thresh = 'std' if normalize is True else 'C' + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'treshold_ep_ev', + 'time_bounds'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = "ENSO Diversity (interquartile range)" + lat = ReferenceRegions(box)['latitude'] + lon = ReferenceRegions(box)['longitude'] + + Method = "Nino (Nina) events = " + region_ev + " sstA > " + str(threshold) + " (< -" + str(threshold) + ") during "\ + + season_ev + ", zonal SSTA (meridional averaged [" + str(lat[0]) + " ; " + str(lat[1])\ + + "]), the zonal SSTA maximum (minimum) is located for each event, the diversity is the interquartile "\ + + "range (IQR = Q3 - Q1)" + Units = 'long' + Ref = 'Using CDAT regridding' + metric = 'EnsoSstDiversity' + if metname == '': + metname = deepcopy(metric) + + # ------------------------------------------------ + # 1. detect events + # ------------------------------------------------ + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst, sst_areacell, keyerror1 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, region_ev, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + sstmap, sstmap_areacell, keyerror2 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, box, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + + # Number of years + yearN = int(round(sst.shape[0] / 12)) + + # Time period + actualtimebounds = TimeBounds(sst) + + if keyerror1 is not None or keyerror2 is not None: + dispersion1, dispersion1_err, dispersion2, dispersion2_err, event_years = None, None, None, None, None + dive_down_diag = None, None, {'value': None, 'axis': None} + keyerror = add_up_errors([keyerror1, keyerror2]) + nina_years, nino_years = [], [] + else: + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + enso, _, keyerror1 = PreProcessTS( + sst, '', areacell=sst_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + sstmap, Method, keyerror2 = PreProcessTS( + sstmap, Method, areacell=sstmap_areacell, average=False, compute_anom=False, region=box, **kwargs) + del sst_areacell, sstmap_areacell + if keyerror1 is not None or keyerror2 is not None: + dispersion1, dispersion1_err, dispersion2, dispersion2_err, event_years = None, None, None, None, None + dive_down_diag = None, None, {'value': None, 'axis': None} + keyerror = add_up_errors([keyerror1, keyerror2]) + nina_years, nino_years = [], [] + else: + if debug is True: + dict_debug = {'axes1': '(sst ts) ' + str([ax.id for ax in enso.getAxisList()]), + 'axes2': '(sst map) ' + str([ax.id for ax in sstmap.getAxisList()]), + 'shape1': '(sst ts) ' + str(enso.shape), 'shape2': '(sst map) ' + str(sstmap.shape), + 'time1': '(sst ts) ' + str(TimeBounds(enso)), + 'time2': '(sst map) ' + str(TimeBounds(sstmap))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 SSTA > 'threshold' (SSTA < -'threshold') during 'season' are considered as El Nino (La Nina) events + # Lists event years + nina_years = DetectEvents( + enso, season_ev, -threshold, normalization=normalize, nino=False, compute_season=True) + nino_years = DetectEvents( + enso, season_ev, threshold, normalization=normalize, nino=True, compute_season=True) + if debug is True: + dict_debug = {"nina1": "nbr(" + str(len(nina_years)) + "): " + str(nina_years), + "nino1": "nbr(" + str(len(nino_years)) + "): " + str(nino_years)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + + # ------------------------------------------------ + # 2. diversity of the zonal location of the maximum SSTA + # ------------------------------------------------ + # 2.1 zonal SSTA at the peak of the event is computed for each selected event + # Seasonal mean + sstmap = SeasonalMean(sstmap, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sstmap.getAxisList()]), + 'shape1': '(sst) ' + str(sstmap.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', 'regridTool', + 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + sstmap = Regrid(sstmap, None, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sstmap.getAxisList()]), + 'shape1': '(sst) ' + str(sstmap.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Meridional average + sstlon, keyerror = AverageMeridional(sstmap) + if keyerror is not None: + dispersion1, dispersion1_err, dispersion2, dispersion2_err, event_years = None, None, None, None, None + dive_down_diag = None, None, {'value': None, 'axis': None} + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sstlon.getAxisList()]), + 'shape1': '(sst) ' + str(sstlon.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + + if len(nina_years) > 0: + # samples + sample_ln = Event_selection(sstlon, kwargs['frequency'], list_event_years=nina_years) + + # 2.2 find the zonal position of the minimum SSTA for each selected event + lon_ln =\ + FindXYMinMaxInTs(sample_ln, return_val='mini', smooth=True, axis=0, window=5, method='triangle') + if debug is True: + dict_debug = {'line1': 'longitude of the minimum SSTA: ' + str(lon_ln)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after FindXYMinMaxInTs', 15, **dict_debug) + else: + sample_ln = MyEmpty(sstlon[:5, 0], time=True, time_id='years') + lon_ln = MyEmpty(sstlon[:5, 0], time=True, time_id='years') + + if len(nino_years) > 0: + # samples + sample_en = Event_selection(sstlon, kwargs['frequency'], list_event_years=nino_years) + + # 2.2 find the zonal position of the maximum SSTA for each selected event + lon_en =\ + FindXYMinMaxInTs(sample_en, return_val='maxi', smooth=True, axis=0, window=5, method='triangle') + if debug is True: + dict_debug = {'line1': 'longitude of the maximum SSTA: ' + str(lon_en)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after FindXYMinMaxInTs', 15, **dict_debug) + else: + sample_en = MyEmpty(sstlon[:5, 0], time=True, time_id='years') + lon_en = MyEmpty(sstlon[:5, 0], time=True, time_id='years') + + if len(nina_years) > 0 or len(nino_years) > 0: + sample_lnen = Concatenate(-1 * sample_ln, sample_en, events1=nina_years, events2=nino_years) + lon_lnen = Concatenate(lon_ln, lon_en, events1=nina_years, events2=nino_years) + # 2.3 compute the spread of the distribution + dispersion1 = statistical_dispersion(lon_lnen, method='IQR') + dispersion2 = statistical_dispersion(lon_lnen, method='MAD') + dispersion1_err, dispersion2_err = None, None + if len(nina_years) > 0: + dispersion3 = statistical_dispersion(lon_ln, method='IQR') + dispersion4 = statistical_dispersion(lon_ln, method='MAD') + dispersion3_err, dispersion4_err = None, None + else: + dispersion3, dispersion4, dispersion3_err, dispersion4_err = None, None, None, None + if len(nino_years) > 0: + dispersion5 = statistical_dispersion(lon_en, method='IQR') + dispersion6 = statistical_dispersion(lon_en, method='MAD') + dispersion5_err, dispersion6_err = None, None + else: + dispersion5, dispersion6, dispersion5_err, dispersion6_err = None, None, None, None + else: + if len(nina_years) > 0: + sample_lnen = -1 * deepcopy(sample_ln) + lon_lnen = deepcopy(lon_ln) + else: + sample_lnen = deepcopy(sample_en) + lon_lnen = deepcopy(lon_en) + dispersion1, dispersion2, dispersion3, dispersion4, dispersion5 = None, None, None, None, None + dispersion6, dispersion1_err, dispersion2_err, dispersion3_err = None, None, None, None + dispersion4_err, dispersion5_err, dispersion6_err = None, None, None + + # Dive down diagnostic + dive_down_diag = {'value': ArrayToList(lon_lnen), 'axis': list(lon_lnen.getAxis(0)[:])} + + if netcdf is True: + # save + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'nina_years': str(nina_years), 'nino_years': str(nino_years), + 'description': + "Nino (Nina) events = " + region_ev + " SSTA > " + str(threshold) + my_thresh + " (SSTA < -" + + str(threshold) + my_thresh + ") during " + season_ev + + ", zonal SSTA (meridional averaged [" + str(lat[0]) + " ; " + str(lat[1]) + + "]), the zonal SSTA maximum (minimum) is located for each event, the diversity is the " + + "interquartile range (IQR = Q3 - Q1), second value is the median absolute deviation " + + "(MAD = median([Xi - median(tab)]))", + 'diagnostic_value_' + dataset: dispersion1, + 'diagnostic_value_error_' + dataset: dispersion1_err, + 'diagnostic_value2_' + dataset: dispersion2, + 'diagnostic_value_error2_' + dataset: dispersion2_err} + dict2 = { + 'units': "C", 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'nina_years': str(nina_years), 'nino_years': str(nino_years), + 'description': + "Nino (Nina) events = " + region_ev + " SSTA > " + str(threshold) + my_thresh + " (SSTA < -" + + str(threshold) + my_thresh + ") during " + season_ev + " (Nina * -1)"} + dict3 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'nina_years': str(nina_years), + 'description': + "Nina events = " + region_ev + " SSTA < -" + str(threshold) + my_thresh + " during " + + season_ev + ", zonal SSTA " + "(meridional averaged [" + str(lat[0]) + " ; " + str(lat[1]) + + "]), the zonal SSTA minimum is located for each event, the diversity is the interquartile" + + " range (IQR = Q3 - Q1), second value is the median absolute deviation " + + "(MAD = median([Xi - median(tab)]))", + 'diagnostic_value_' + dataset: dispersion3, + 'diagnostic_value_error_' + dataset: dispersion3_err, + 'diagnostic_value2_' + dataset: dispersion4, + 'diagnostic_value_error2_' + dataset: dispersion4_err} + dict4 = { + 'units': "C", 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'nina_years': str(nina_years), + 'description': "Nina events = " + region_ev + " SSTA < -" + str(threshold) + my_thresh + + " during " + season_ev} + dict5 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'nino_years': str(nino_years), + 'description': + "Nino events = " + region_ev + " SSTA > " + str(threshold) + my_thresh + " during " + + season_ev + ", zonal SSTA " + "(meridional averaged [" + str(lat[0]) + " ; " + str(lat[1]) + + "]), the zonal SSTA maximum is located for each event, the diversity is the interquartile" + + " range (IQR = Q3 - Q1), second value is the median absolute deviation " + + "(MAD = median([Xi - median(tab)]))", + 'diagnostic_value_' + dataset: dispersion5, + 'diagnostic_value_error_' + dataset: dispersion5_err, + 'diagnostic_value2_' + dataset: dispersion6, + 'diagnostic_value_error2_' + dataset: dispersion6_err} + dict6 = { + 'units': "C", 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'nino_years': str(nino_years), + 'description': "Nino events = " + region_ev + " SSTA > " + str(threshold) + my_thresh + + " during " + season_ev} + dict7 = {'metric_name': Name, 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + SaveNetcdf(file_name, var1=lon_lnen, var1_attributes=dict1, + var1_name='Enso_lon_pos_maxSSTA__' + dataset, var2=sample_lnen, var2_attributes=dict2, + var2_name='Enso_sst_lon__' + dataset, var3=lon_ln, var3_attributes=dict3, + var3_name='Nina_lon_pos_minSSTA__' + dataset, var4=sample_ln, var4_attributes=dict4, + var4_name='Nina_sst_lon__' + dataset, var5=lon_en, var5_attributes=dict5, + var5_name='Nino_lon_pos_maxSSTA__' + dataset, var6=sample_en, var6_attributes=dict6, + var6_name='Nino_sst_lon__' + dataset, global_attributes=dict7) + del dict1, dict2, dict3, dict4, dict5, dict6, dict7 + + # metric value + if debug is True: + dict_debug = { + 'line1': 'diagnostic (IQR) value: ' + str(dispersion1), + 'line2': 'diagnostic (IQR) value_error: ' + str(dispersion1_err), + 'line3': 'diagnostic (MAD) value: ' + str(dispersion2), + 'line4': 'diagnostic (MAD) value_error: ' + str(dispersion2_err)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + EnsoDivMetric = { + 'name': Name, 'value': dispersion1, 'value_error': dispersion1_err, 'value2': dispersion2, + 'value_error2': dispersion2_err, 'units': Units, 'method': Method, 'nyears': yearN, + 'events': nina_years + nino_years, 'time_frequency': kwargs['frequency'], 'time_period': actualtimebounds, + 'ref': Ref, 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return EnsoDivMetric + + +def EnsoSstSkew(sstfile, sstname, sstareafile, sstareaname, sstlandmaskfile, sstlandmaskname, sstbox, dataset='', + debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The EnsoSstSkew() function computes the skewness of 'sstbox' sstA (usually the skewness of nino3 sstA) + + Author: Eric Guilyardi : Eric.Guilyardi@locean-ipsl.upmc.fr + Co-author: Yann Planton : yann.planton@locean-ipsl.upmc.fr + + Created on Mon Jan 9 11:05:18 CET 2017 + + Inputs: + ------ + :param sstfile: string + path_to/filename of the file (NetCDF) of SST + :param sstname: string + name of SST variable (tos, ts) in 'sstfile' + :param sstareafile: string + path_to/filename of the file (NetCDF) of the areacell for SST + :param sstareaname: string + name of areacell variable (areacella, areacello) in 'sstareafile' + :param sstlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for SST + :param sstlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfile' + :param sstbox: string + name of box ('nino3') for SST + :param dataset: string, optional + name of current dataset (e.g., 'model', 'obs', ...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return skewMetric: dict + name, value, value_error, units, method, nyears, time_frequency, time_period, ref, keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'ENSO skewness' + Units = '' if kwargs['normalization'] else 'C' + Method = 'Standard deviation of ' + sstbox + ' sstA' + Ref = 'Using CDAT regression calculation' + metric = 'EnsoSstSkew' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst, sst_areacell, keyerror = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, sstbox, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + + # Number of years + yearN = int(round(sst.shape[0] / 12)) + + # Time period + actualtimebounds = TimeBounds(sst) + + if keyerror is not None: + sstSke, sstSkeErr, dive_down_diag = None, None, {'value': None, 'axis': None} + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst, Method, keyerror = PreProcessTS( + sst, Method, areacell=sst_areacell, average='horizontal', compute_anom=True, region=sstbox, **kwargs) + del sst_areacell + if keyerror is not None: + sstSke, sstSkeErr, dive_down_diag = None, None, {'value': None, 'axis': None} + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape), 'time1': '(sst) ' + str(TimeBounds(sst))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Computes the skewness + sstSke = float(SkewnessTemporal(sst)) + + # Standard Error of the skewness (function of nyears) + sstSkeErr = sstSke / NUMPYsqrt(yearN) + + # Dive down diagnostic + dive_down_diag = {'value': None, 'axis': None} + + if netcdf is True: + # additional diagnostic + # Read file and select the right region + sst1, sst_areacell1, keyerror1 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, 'equatorial_pacific_LatExt2', file_area=sstareafile, + name_area=sstareaname, file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, + maskocean=False, debug=debug, **kwargs) + sst2, sst_areacell2, keyerror2 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, 'equatorial_pacific', file_area=sstareafile, + name_area=sstareaname, file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, + maskocean=False, debug=debug, **kwargs) + if keyerror1 is not None or keyerror2 is not None: + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, ...) + sst1, _, keyerror1 = PreProcessTS(sst1, '', areacell=sst_areacell1, compute_anom=True, + region="equatorial_pacific_LatExt2", **kwargs) + sst2, _, keyerror2 = PreProcessTS(sst2, '', areacell=sst_areacell2, compute_anom=True, + region="equatorial_pacific", **kwargs) + del sst_areacell1, sst_areacell2 + if keyerror1 is not None or keyerror2 is not None: + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + if debug is True: + dict_debug = {'axes1': '(sst1) ' + str([ax.id for ax in sst1.getAxisList()]), + 'axes2': '(sst2) ' + str([ax.id for ax in sst2.getAxisList()]), + 'shape1': '(sst1) ' + str(sst1.shape), 'shape2': '(sst2) ' + str(sst2.shape), + 'time1': '(sst1) ' + str(TimeBounds(sst1)), + 'time2': '(sst2) ' + str(TimeBounds(sst2))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 10, **dict_debug) + # std + sst1 = SkewnessTemporal(sst1) + sst2 = SkewnessTemporal(sst2) + # Regridding + if 'regridding' not in list(kwargs.keys()): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'} + else: + if not isinstance(kwargs['regridding'], dict): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + sst1 = Regrid(sst1, None, region='equatorial_pacific_LatExt2', **kwargs['regridding']) + sst2 = Regrid(sst2, None, region='equatorial_pacific', **kwargs['regridding']) + # Meridional average + sst2, keyerror = AverageMeridional(sst2) + if keyerror is None: + # Dive down diagnostic + dive_down_diag = {'value': ArrayToList(sst2), 'axis': list(sst2.getAxis(0)[:])} + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': "zonal skewness of equatorial_pacific sstA", 'diagnostic_value': sstSke, + 'diagnostic_value_error': sstSkeErr} + dict2 = { + 'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'description': "skewness of equatorial_pacific sstA"} + dict3 = {'metric_name': Name, 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + SaveNetcdf(file_name, var1=sst2, var1_attributes=dict1, var1_name='sstSke_lon__' + dataset, + var2=sst1, var2_attributes=dict2, var2_name='sstSke_map__' + dataset, + global_attributes=dict3) + del dict1, dict2, dict3 + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(sstSke), 'line2': 'metric value_error: ' + str(sstSkeErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + skewMetric = { + 'name': Name, 'value': sstSke, 'value_error': sstSkeErr, 'units': Units, 'method': Method, 'nyears': yearN, + 'time_frequency': kwargs['frequency'], 'time_period': actualtimebounds, 'ref': Ref, 'keyerror': keyerror, + 'dive_down_diag': dive_down_diag} + return skewMetric + + +def EnsoSlpMap(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + slpfilemod, slpnamemod, slpareafilemod, slpareanamemod, slplandmaskfilemod, slplandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, + slpfileobs, slpnameobs, slpareafileobs, slpareanameobs, slplandmaskfileobs, slplandmasknameobs, sstbox, + slpbox, event_definition, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, + netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The EnsoSlpMap() function computes sea level pressure anomalies pattern associated with ENSO on the globe. + First metric: rmse(observations vs model). + Second metric: correlation(observations vs model). + Third metric: std(model)/std(observations) + These metrics can be used to compute a Taylor diagram. + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST areacell + :param sstareanamemod: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafilemod' + :param sstlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST landmask + :param sstlandmasknamemod: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfilemod' + :param slpfilemod: string + path_to/filename of the file (NetCDF) of the modeled SLP + :param slpnamemod: string + name of SLP variable (psl) in 'slpfilemod' + :param slpareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SLP areacell + :param slpareanamemod: string, optional + name of areacell for the SLP variable (areacella, areacello,...) in 'slpareafilemod' + :param slplandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SLP landmask + :param slplandmasknamemod: string, optional + name of landmask for the SLP variable (sftlf,...) in 'slplandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST areacell + :param sstareanameobs: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafileobs' + :param sstlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST landmask + :param sstlandmasknameobs: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfileobs' + :param slpfileobs: string + path_to/filename of the file (NetCDF) of the observed SLP + :param slpnameobs: string + name of SLP variable (slp) in 'slpfileobs' + :param slpareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SLP areacell + :param slpareanameobs: string, optional + name of areacell for the SLP variable (areacella, areacello,...) in 'slpareafileobs' + :param slplandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SLP landmask + :param slplandmasknameobs: string, optional + name of landmask for the SLP variable (sftlf,...) in 'slplandmaskfileobs' + :param sstbox: string + name of box (e.g. 'global') for SST + :param slpbox: string + name of box (e.g. 'global') for SLP + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': -0.75} + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return EnsoSlpMapMetric: dict + name, value (rms [obs;model]), value_error, units, method, value2 (corr [obs;model]), + value_error2, units2, value3 (std_model / std_obs), value_error3, units3, nyears_model, nyears_observations, + time_frequency, time_period_mod, time_period_obs, ref, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'ENSO SLPA pattern' + Method = region_ev + 'SSTA during ' + season_ev + ' regressed against sea level pressure anomalies in ' + slpbox + Units = '' if kwargs['normalization'] else 'hPa/C' + Ref = 'Using CDAT regridding, correlation (centered and biased), std (centered and biased) and ' + \ + 'rms (uncentered and biased) calculation' + metric = 'EnsoSlpMap' + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod1 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs1 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + slp_mod, slp_mod_areacell, keyerror_mod2 = Read_data_mask_area( + slpfilemod, slpnamemod, 'pressure', metric, slpbox,file_area=slpareafilemod, name_area=slpareanamemod, + file_mask=slplandmaskfilemod, name_mask=slplandmasknamemod, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + slp_obs, slp_obs_areacell, keyerror_obs2 = Read_data_mask_area( + slpfileobs, slpnameobs, 'pressure', metric, slpbox, file_area=slpareafileobs, name_area=slpareanameobs, + file_mask=slplandmaskfileobs, name_mask=slplandmasknameobs, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst_mod, slp_mod, keyerror_mod3 = CheckTime(sst_mod, slp_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs, slp_obs, keyerror_obs3 = CheckTime(sst_obs, slp_obs, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if (keyerror_mod1 is not None or keyerror_obs1 is not None or keyerror_mod2 is not None) or \ + (keyerror_obs2 is not None or keyerror_mod3 is not None or keyerror_obs3 is not None): + slpCorr, slpCorrErr, slpRmse, slpRmseErr, slpStd, slpStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + tmp = [keyerror_mod1, keyerror_mod2, keyerror_mod3, keyerror_obs1, keyerror_obs2, keyerror_obs3] + keyerror = add_up_errors(tmp) + else: + # ------------------------------------------------ + # 1. SSTA + # ------------------------------------------------ + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, _, keyerror_mod = PreProcessTS( + sst_mod, '', areacell=mod_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + slpCorr, slpCorrErr, slpRmse, slpRmseErr, slpStd, slpStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sst_mod)), + 'time2': '(obs) ' + str(TimeBounds(sst_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 Seasonal mean and anomalies + enso_mod = SeasonalMean(sst_mod, season_ev, compute_anom=True) + enso_obs = SeasonalMean(sst_obs, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in enso_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(enso_mod.shape), 'shape2': '(obs) ' + str(enso_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(enso_mod)), + 'time2': '(obs) ' + str(TimeBounds(enso_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # ------------------------------------------------ + # 2. SLPA + # ------------------------------------------------ + # 2.1 SLPA in 'slpbox' are normalized / detrended / smoothed (running average) if applicable + # Preprocess slp (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + slp_mod, Method, keyerror_mod = PreProcessTS( + slp_mod, Method, areacell=slp_mod_areacell, compute_anom=False, region=slpbox, **kwargs) + slp_obs, _, keyerror_obs = PreProcessTS( + slp_obs, '', areacell=slp_obs_areacell, compute_anom=False, region=slpbox, **kwargs) + del slp_mod_areacell, slp_obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + slpCorr, slpCorrErr, slpRmse, slpRmseErr, slpStd, slpStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in slp_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod.shape), 'shape2': '(obs) ' + str(slp_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(slp_mod)), + 'time2': '(obs) ' + str(TimeBounds(slp_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 2.2 Seasonal mean and anomalies + slp_mod = SeasonalMean(slp_mod, season_ev, compute_anom=True) * 1e-2 + slp_obs = SeasonalMean(slp_obs, season_ev, compute_anom=True) * 1e-2 + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in slp_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod.shape), 'shape2': '(obs) ' + str(slp_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(slp_mod)), + 'time2': '(obs) ' + str(TimeBounds(slp_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # ------------------------------------------------ + # 3. Regression map + # ------------------------------------------------ + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + slp_mod, slp_obs, Method = TwoVarRegrid( + slp_mod, slp_obs, Method, region=slpbox, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in slp_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod.shape), 'shape2': '(obs) ' + str(slp_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # regression + slp_mod_slope = LinearRegressionTsAgainstMap(slp_mod, enso_mod, return_stderr=False) + slp_obs_slope = LinearRegressionTsAgainstMap(slp_obs, enso_obs, return_stderr=False) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in slp_mod_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs_slope.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod_slope.shape), + 'shape2': '(obs) ' + str(slp_obs_slope.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after LinearRegressionTsAgainstMap', 15, **dict_debug) + + # mask Pacific + slp_mod_slope, keyerror_mod = BasinMask( + slp_mod_slope, 'pacific', box=slpbox, lat1=-15, lat2=15, latkey='between', debug=debug) + slp_obs_slope, keyerror_obs = BasinMask( + slp_obs_slope, 'pacific', box=slpbox, lat1=-15, lat2=15, latkey='between', debug=debug) + if keyerror_mod is not None or keyerror_obs is not None: + slpCorr, slpCorrErr, slpRmse, slpRmseErr, slpStd, slpStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in slp_mod_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs_slope.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod_slope.shape), + 'shape2': '(obs) ' + str(slp_obs_slope.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after BasinMask', 15, **dict_debug) + + # Metric 1 + slpRmse, keyerror = RmsAxis( + slp_mod_slope, slp_obs_slope, axis='xy', centered=centered_rmse, biased=biased_rmse) + slpRmseErr = None + # Metric 2 + slpCorr = float(Correlation(slp_mod_slope, slp_obs_slope, axis='xy', centered=1, biased=1)) + slpCorrErr = None + # Metric 3 + std_mod = Std(slp_mod_slope, weights=None, axis='xy', centered=1, biased=1) + std_obs = Std(slp_obs_slope, weights=None, axis='xy', centered=1, biased=1) + slpStd = float(std_mod) / float(std_obs) + slpStdErr = None + + # Dive down diagnostic + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + + if netcdf is True: + # read + slp_mod, slp_mod_areacell, keyerror_mod1 = Read_data_mask_area( + slpfilemod, slpnamemod, 'pressure', metric, slpbox, file_area=slpareafilemod, + name_area=slpareanamemod, file_mask=slplandmaskfilemod, name_mask=slplandmasknamemod, + maskland=False, maskocean=True, time_bounds=kwargs['time_bounds_mod'], debug=debug, + **kwargs) + slp_obs, slp_obs_areacell, keyerror_obs1 = Read_data_mask_area( + slpfileobs, slpnameobs, 'pressure', metric, slpbox, file_area=slpareafileobs, + name_area=slpareanameobs, file_mask=slplandmaskfileobs, name_mask=slplandmasknameobs, + maskland=False, maskocean=True, time_bounds=kwargs['time_bounds_obs'], debug=debug, + **kwargs) + sst_mod, slp_mod, keyerror_mod2 = CheckTime( + sst_mod, slp_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs, slp_obs, keyerror_obs2 = CheckTime( + sst_obs, slp_obs, metric_name=metric, debug=debug, **kwargs) + if keyerror_mod1 is not None or keyerror_obs1 is not None or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + # process + slp_mod, _, keyerror_mod = PreProcessTS( + slp_mod, '', areacell=slp_mod_areacell, compute_anom=False, region=slpbox, **kwargs) + slp_obs, _, keyerror_obs = PreProcessTS( + slp_obs, '', areacell=slp_obs_areacell, compute_anom=False, region=slpbox, **kwargs) + del slp_mod_areacell, slp_obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in slp_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod.shape), + 'shape2': '(obs) ' + str(slp_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(slp_mod)), + 'time2': '(obs) ' + str(TimeBounds(slp_obs))} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after PreProcessTS', 15, **dict_debug) + # anomalies + slp_mod = SeasonalMean(slp_mod, season_ev, compute_anom=True) * 1e-2 + slp_obs = SeasonalMean(slp_obs, season_ev, compute_anom=True) * 1e-2 + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in slp_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod.shape), + 'shape2': '(obs) ' + str(slp_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(slp_mod)), + 'time2': '(obs) ' + str(TimeBounds(slp_obs))} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after SeasonalMean', 15, **dict_debug) + # regridding + if isinstance(kwargs['regridding'], dict): + slp_mod, slp_obs, _ = TwoVarRegrid( + slp_mod, slp_obs, '', region=slpbox, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in slp_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod.shape), + 'shape2': '(obs) ' + str(slp_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after TwoVarRegrid', 15, **dict_debug) + # regression + slp_mod = LinearRegressionTsAgainstMap(slp_mod, enso_mod, return_stderr=False) + slp_obs = LinearRegressionTsAgainstMap(slp_obs, enso_obs, return_stderr=False) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in slp_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod.shape), + 'shape2': '(obs) ' + str(slp_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after LinearRegressionTsAgainstMap', 15, **dict_debug) + list_region = ["africaSE", "americaN", "americaS", "asiaS", "oceania"] + # Metrics ENSO regression regional + dict_metric, dict_nc = dict(), dict() + nbr = 3 + for ii, reg in enumerate(list_region): + # select region + dictreg = ReferenceRegions(reg) + tmp1 = slp_mod(longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + tmp2 = slp_obs(longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + dict_metric, dict_nc = fill_dict_teleconnection( + tmp1, tmp2, dataset1, dataset2, actualtimebounds_mod, actualtimebounds_obs, + yearN_mod, yearN_obs, nbr, "reg_slp_over_sst_djf_map_" + reg + "__", reg, Units, + centered_rmse=centered_rmse, biased_rmse=biased_rmse, dict_metric=dict_metric, + dict_nc=dict_nc) + nbr += 2 + del dictreg, tmp1, tmp2 + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), 'spatialSTD_' + dataset1: std_mod} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), 'spatialSTD_' + dataset2: std_obs} + dict3 = {'metric_name': Name, 'metric_valueRMSE_' + dataset2: slpRmse, + 'metric_valueRMSE_error_' + dataset2: slpRmseErr, + 'metric_valueCORR_' + dataset2: slpCorr, + 'metric_valueCORR_error_' + dataset2: slpCorrErr, + 'metric_valueSTD_' + dataset2: slpStd, + 'metric_valueSTD_error_' + dataset2: slpStdErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + dict3.update(dict_metric) + SaveNetcdf( + file_name, var1=slp_mod_slope, var1_attributes=dict1, + var1_name='reg_slp_over_sst_map__' + dataset1, var2=slp_obs_slope, + var2_attributes=dict2, var2_name='reg_slp_over_sst_map__' + dataset2, + global_attributes=dict3, **dict_nc) + del dict1, dict2, dict3, dict_metric, dict_nc, file_name, list_region + if slpCorr is not None: + slpCorr = 1 - slpCorr + # Create output + EnsoSlpMapMetric = { + 'name': Name, 'Rmse__value': slpRmse, 'Rmse__value_error': slpRmseErr, 'Rmse__units': Units, 'method': Method, + 'Corr__value': slpCorr, 'Corr__value_error': slpCorrErr, 'Corr__units': '', 'Std__value': slpStd, + 'Std__value_error': slpStdErr, 'Std__units': Units + ' / ' + Units, 'nyears_model': yearN_mod, + 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag, 'units': ''} + return EnsoSlpMapMetric + + +def EnsoSlpMapDjf(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + slpfilemod, slpnamemod, slpareafilemod, slpareanamemod, slplandmaskfilemod, slplandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, + slpfileobs, slpnameobs, slpareafileobs, slpareanameobs, slplandmaskfileobs, slplandmasknameobs, + sstbox, slpbox, event_definition, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', + debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The EnsoSlpMapDjf() function computes sea level pressure anomalies pattern associated with ENSO on the globe. + First metric: rmse(observations vs model). + Second metric: correlation(observations vs model). + Third metric: std(model)/std(observations) + These metrics can be used to compute a Taylor diagram. + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST areacell + :param sstareanamemod: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafilemod' + :param sstlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST landmask + :param sstlandmasknamemod: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfilemod' + :param slpfilemod: string + path_to/filename of the file (NetCDF) of the modeled SLP + :param slpnamemod: string + name of SLP variable (psl) in 'slpfilemod' + :param slpareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SLP areacell + :param slpareanamemod: string, optional + name of areacell for the SLP variable (areacella, areacello,...) in 'slpareafilemod' + :param slplandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SLP landmask + :param slplandmasknamemod: string, optional + name of landmask for the SLP variable (sftlf,...) in 'slplandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST areacell + :param sstareanameobs: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafileobs' + :param sstlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST landmask + :param sstlandmasknameobs: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfileobs' + :param slpfileobs: string + path_to/filename of the file (NetCDF) of the observed SLP + :param slpnameobs: string + name of SLP variable (slp) in 'slpfileobs' + :param slpareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SLP areacell + :param slpareanameobs: string, optional + name of areacell for the SLP variable (areacella, areacello,...) in 'slpareafileobs' + :param slplandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SLP landmask + :param slplandmasknameobs: string, optional + name of landmask for the SLP variable (sftlf,...) in 'slplandmaskfileobs' + :param sstbox: string + name of box (e.g. 'global') for SST + :param slpbox: string + name of box (e.g. 'global') for SLP + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': -0.75} + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return EnsoSlpMapMetric: dict + name, value (rms [obs;model]), value_error, units, method, value2 (corr [obs;model]), + value_error2, units2, value3 (std_model / std_obs), value_error3, units3, nyears_model, nyears_observations, + time_frequency, time_period_mod, time_period_obs, ref, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = "ENSO DJF SLPA pattern" + Method = region_ev + " SSTA regressed against sea level pressure anomalies in " + slpbox + " during DJF" + Units = "" if kwargs['normalization'] else "hPa/C" + Ref = 'Using CDAT regridding, correlation (centered and biased), std (centered and biased) and ' + \ + 'rms (uncentered and biased) calculation' + metric = "EnsoSlpMapDjf" + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod_box, mod_areacell, keyerror_mod1 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs_box, obs_areacell, keyerror_obs1 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + slp_mod, slp_mod_areacell, keyerror_mod2 = Read_data_mask_area( + slpfilemod, slpnamemod, 'pressure', metric, slpbox, file_area=slpareafilemod, name_area=slpareanamemod, + file_mask=slplandmaskfilemod, name_mask=slplandmasknamemod, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + slp_obs, slp_obs_areacell, keyerror_obs2 = Read_data_mask_area( + slpfileobs, slpnameobs, 'pressure', metric, slpbox, file_area=slpareafileobs, name_area=slpareanameobs, + file_mask=slplandmaskfileobs, name_mask=slplandmasknameobs, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst_mod_box, slp_mod, keyerror_mod3 = CheckTime(sst_mod_box, slp_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs_box, slp_obs, keyerror_obs3 = CheckTime(sst_obs_box, slp_obs, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod_box.shape[0] / 12)) + yearN_obs = int(round(sst_obs_box.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod_box) + actualtimebounds_obs = TimeBounds(sst_obs_box) + + if (keyerror_mod1 is not None or keyerror_obs1 is not None or keyerror_mod2 is not None) or \ + (keyerror_obs2 is not None or keyerror_mod3 is not None or keyerror_obs3 is not None): + slpCorr, slpCorrErr, slpRmse, slpRmseErr, slpStd, slpStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + tmp = [keyerror_mod1, keyerror_mod2, keyerror_mod3, keyerror_obs1, keyerror_obs2, keyerror_obs3] + keyerror = add_up_errors(tmp) + else: + # smoothing is not applied + if 'smoothing' in list(kwargs.keys()): + smooth = deepcopy(kwargs['smoothing']) + kwargs['smoothing'] = False + else: + smooth = False + # ------------------------------------------------ + # 1. SSTA + # ------------------------------------------------ + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, _, keyerror_mod = PreProcessTS( + sst_mod_box, '', areacell=mod_areacell, average='horizontal', compute_anom=False, region=region_ev, + **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs_box, '', areacell=obs_areacell, average='horizontal', compute_anom=False, region=region_ev, + **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + slpCorr, slpCorrErr, slpRmse, slpRmseErr, slpStd, slpStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sst_mod)), + 'time2': '(obs) ' + str(TimeBounds(sst_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 Seasonal mean and anomalies + enso_mod = SeasonalMean(sst_mod, "DJF", compute_anom=True) + enso_obs = SeasonalMean(sst_obs, "DJF", compute_anom=True) + if debug is True: + dict_debug = { + 'axes1': '(mod) ' + str([ax.id for ax in enso_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(enso_mod.shape), 'shape2': '(obs) ' + str(enso_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(enso_mod)), 'time2': '(obs) ' + str(TimeBounds(enso_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # ------------------------------------------------ + # 2. SLPA + # ------------------------------------------------ + # 2.1 SLPA in 'slpbox' are normalized / detrended / smoothed (running average) if applicable + # Preprocess slp (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + slp_mod, Method, keyerror_mod = PreProcessTS( + slp_mod, Method, areacell=slp_mod_areacell, compute_anom=False, region=slpbox, **kwargs) + slp_obs, _, keyerror_obs = PreProcessTS( + slp_obs, '', areacell=slp_obs_areacell, compute_anom=False, region=slpbox, **kwargs) + del slp_mod_areacell, slp_obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + slpCorr, slpCorrErr, slpRmse, slpRmseErr, slpStd, slpStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in slp_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod.shape), 'shape2': '(obs) ' + str(slp_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(slp_mod)), + 'time2': '(obs) ' + str(TimeBounds(slp_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 2.2 Seasonal mean and anomalies + slp_mod = SeasonalMean(slp_mod, "DJF", compute_anom=True) * 1e-2 + slp_obs = SeasonalMean(slp_obs, "DJF", compute_anom=True) * 1e-2 + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in slp_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod.shape), 'shape2': '(obs) ' + str(slp_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(slp_mod)), + 'time2': '(obs) ' + str(TimeBounds(slp_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # ------------------------------------------------ + # 3. Regression map + # ------------------------------------------------ + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + slp_mod, slp_obs, Method = TwoVarRegrid( + slp_mod, slp_obs, Method, region=slpbox, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in slp_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod.shape), 'shape2': '(obs) ' + str(slp_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # regression + slp_mod_slope = LinearRegressionTsAgainstMap(slp_mod, enso_mod, return_stderr=False) + slp_obs_slope = LinearRegressionTsAgainstMap(slp_obs, enso_obs, return_stderr=False) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in slp_mod_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs_slope.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod_slope.shape), + 'shape2': '(obs) ' + str(slp_obs_slope.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after LinearRegressionTsAgainstMap', 15, **dict_debug) + + # mask Pacific + slp_mod_slope, keyerror_mod = BasinMask( + slp_mod_slope, 'pacific', box=slpbox, lat1=-15, lat2=15, latkey='between', debug=debug) + slp_obs_slope, keyerror_obs = BasinMask( + slp_obs_slope, 'pacific', box=slpbox, lat1=-15, lat2=15, latkey='between', debug=debug) + if keyerror_mod is not None or keyerror_obs is not None: + slpCorr, slpCorrErr, slpRmse, slpRmseErr, slpStd, slpStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in slp_mod_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs_slope.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod_slope.shape), + 'shape2': '(obs) ' + str(slp_obs_slope.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after BasinMask', 15, **dict_debug) + + # Metric 1 + slpRmse, keyerror = RmsAxis( + slp_mod_slope, slp_obs_slope, axis='xy', centered=centered_rmse, biased=biased_rmse) + slpRmseErr = None + # Metric 2 + slpCorr = float(Correlation(slp_mod_slope, slp_obs_slope, axis='xy', centered=1, biased=1)) + slpCorrErr = None + # Metric 3 + std_mod = Std(slp_mod_slope, weights=None, axis='xy', centered=1, biased=1) + std_obs = Std(slp_obs_slope, weights=None, axis='xy', centered=1, biased=1) + slpStd = float(std_mod) / float(std_obs) + slpStdErr = None + + # Dive down diagnostic + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + + if netcdf is True: + # read + slp_mod_land, slp_mod_areacell, keyerror_mod1 = Read_data_mask_area( + slpfilemod, slpnamemod, 'pressure', metric, slpbox, file_area=slpareafilemod, + name_area=slpareanamemod, file_mask=slplandmaskfilemod, name_mask=slplandmasknamemod, + maskland=False, maskocean=True, time_bounds=kwargs['time_bounds_mod'], debug=debug, + **kwargs) + slp_obs_land, slp_obs_areacell, keyerror_obs1 = Read_data_mask_area( + slpfileobs, slpnameobs, 'pressure', metric, slpbox, file_area=slpareafileobs, + name_area=slpareanameobs, file_mask=slplandmaskfileobs, name_mask=slplandmasknameobs, + maskland=False, maskocean=True, time_bounds=kwargs['time_bounds_obs'], debug=debug, + **kwargs) + sst_mod, slp_mod_land, keyerror_mod1 = CheckTime( + sst_mod, slp_mod_land, metric_name=metric, debug=debug, **kwargs) + sst_obs, slp_obs_land, keyerror_obs2 = CheckTime( + sst_obs, slp_obs_land, metric_name=metric, debug=debug, **kwargs) + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + # process + slp_mod_land, _, keyerror_mod = PreProcessTS(slp_mod_land, '', areacell=slp_mod_areacell, + compute_anom=False, region=slpbox, **kwargs) + slp_obs_land, _, keyerror_obs = PreProcessTS(slp_obs_land, '', areacell=slp_obs_areacell, + compute_anom=False, region=slpbox, **kwargs) + del slp_mod_areacell, slp_obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = { + 'axes1': '(mod) ' + str([ax.id for ax in slp_mod_land.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs_land.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod_land.shape), + 'shape2': '(obs) ' + str(slp_obs_land.shape), + 'time1': '(mod) ' + str(TimeBounds(slp_mod_land)), + 'time2': '(obs) ' + str(TimeBounds(slp_obs_land))} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after PreProcessTS', 15, **dict_debug) + # anomalies + slp_mod_land = SeasonalMean(slp_mod_land, "DJF", compute_anom=True) * 1e-2 + slp_obs_land = SeasonalMean(slp_obs_land, "DJF", compute_anom=True) * 1e-2 + if debug is True: + dict_debug = { + 'axes1': '(mod) ' + str([ax.id for ax in slp_mod_land.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs_land.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod_land.shape), + 'shape2': '(obs) ' + str(slp_obs_land.shape), + 'time1': '(mod) ' + str(TimeBounds(slp_mod_land)), + 'time2': '(obs) ' + str(TimeBounds(slp_obs_land))} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after SeasonalMean', 15, **dict_debug) + # regridding + if isinstance(kwargs['regridding'], dict): + slp_mod_land, slp_obs_land, _ = TwoVarRegrid( + slp_mod_land, slp_obs_land, '', region=slpbox, **kwargs['regridding']) + if debug is True: + dict_debug = { + 'axes1': '(mod) ' + str([ax.id for ax in slp_mod_land.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs_land.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod_land.shape), + 'shape2': '(obs) ' + str(slp_obs_land.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after TwoVarRegrid', 15, **dict_debug) + # regression + slp_mod_land_slope = LinearRegressionTsAgainstMap( + slp_mod_land, enso_mod, return_stderr=False) + slp_obs_land_slope = LinearRegressionTsAgainstMap( + slp_obs_land, enso_obs, return_stderr=False) + if debug is True: + dict_debug = { + 'axes1': '(mod) ' + str([ax.id for ax in slp_mod_land_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs_land_slope.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod_land_slope.shape), + 'shape2': '(obs) ' + str(slp_obs_land_slope.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after LinearRegressionTsAgainstMap', 15, **dict_debug) + # ENSO events: SSTA > (<) 'threshold' during 'season' are considered as El Nino + # (La Nina) events + if 'smoothing' in list(kwargs.keys()): + kwargs['smoothing'] = smooth + del smooth + sst_mod, _, keyerror_mod = PreProcessTS( + sst_mod_box, '', areacell=mod_areacell, average='horizontal', compute_anom=False, + region=region_ev, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs_box, '', areacell=obs_areacell, average='horizontal', compute_anom=False, + region=region_ev, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Lists event years + nino_mod_years = DetectEvents( + sst_mod, season_ev, threshold, normalization=normalize, nino=True) + nina_mod_years = DetectEvents( + sst_mod, season_ev, -threshold, normalization=normalize, nino=False) + nino_obs_years = DetectEvents( + sst_obs, season_ev, threshold, normalization=normalize, nino=True) + nina_obs_years = DetectEvents( + sst_obs, season_ev, -threshold, normalization=normalize, nino=False) + if debug is True: + dict_debug = {'nino1': '(mod) nbr(' + str(len(nino_mod_years)) + '): ' + + str(nino_mod_years), + 'nina1': '(mod) nbr(' + str(len(nina_mod_years)) + '): ' + + str(nina_mod_years), + 'nino2': '(obs) nbr(' + str(len(nino_obs_years)) + '): ' + + str(nino_obs_years), + 'nina2': '(obs) nbr(' + str(len(nina_obs_years)) + '): ' + + str(nina_obs_years)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after DetectEvents', 15, **dict_debug) + # samples + nino_mod = Composite(slp_mod, nino_mod_years, kwargs['frequency']) + nina_mod = Composite(slp_mod, nina_mod_years, kwargs['frequency']) + nino_obs = Composite(slp_obs, nino_obs_years, kwargs['frequency']) + nina_obs = Composite(slp_obs, nina_obs_years, kwargs['frequency']) + nino_mod_land = Composite(slp_mod_land, nino_mod_years, kwargs['frequency']) + nina_mod_land = Composite(slp_mod_land, nina_mod_years, kwargs['frequency']) + nino_obs_land = Composite(slp_obs_land, nino_obs_years, kwargs['frequency']) + nina_obs_land = Composite(slp_obs_land, nina_obs_years, kwargs['frequency']) + # mask Pacific + nino_mod, keyerror_mod1 = BasinMask( + nino_mod, 'pacific', box=slpbox, lat1=-15, lat2=15, latkey='between', + debug=debug) + nina_mod, keyerror_mod2 = BasinMask( + nina_mod, 'pacific', box=slpbox, lat1=-15, lat2=15, latkey='between', + debug=debug) + nino_obs, keyerror_obs1 = BasinMask( + nino_obs, 'pacific', box=slpbox, lat1=-15, lat2=15, latkey='between', + debug=debug) + nina_obs, keyerror_obs2 = BasinMask( + nina_obs, 'pacific', box=slpbox, lat1=-15, lat2=15, latkey='between', + debug=debug) + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + keyerror = add_up_errors( + [keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + # Metrics ENSO events global + dict_metric, dict_nc = dict(), dict() + nbr = 3 + for jj, (evname, tab1, tab2, ev1, ev2) in enumerate( + zip(["nino", "nina"], [nino_mod, nina_mod], [nino_obs, nina_obs], + [nino_mod_years, nina_mod_years], + [nino_obs_years, nina_obs_years])): + dict_metric, dict_nc = fill_dict_teleconnection( + tab1, tab2, dataset1, dataset2, actualtimebounds_mod, + actualtimebounds_obs, yearN_mod, yearN_obs, nbr, + "slp_" + evname + "_djf_map__", evname, "hPa", + centered_rmse=centered_rmse, biased_rmse=biased_rmse, + dict_metric=dict_metric, dict_nc=dict_nc, ev_name=evname, events1=ev1, + events2=ev2) + nbr += 2 + list_region = ["africaSE", "americaN", "americaS", "asiaS", "oceania"] + for ii, reg in enumerate(list_region): + # select region + dictreg = ReferenceRegions(reg) + tmp1 = slp_mod_land_slope( + longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + tmp2 = slp_obs_land_slope( + longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + dict_metric, dict_nc = fill_dict_teleconnection( + tmp1, tmp2, dataset1, dataset2, actualtimebounds_mod, + actualtimebounds_obs, yearN_mod, yearN_obs, nbr, + "reg_slp_over_sst_djf_map_" + reg + "__", reg, Units, + centered_rmse=centered_rmse, biased_rmse=biased_rmse, + dict_metric=dict_metric, dict_nc=dict_nc) + nbr += 2 + for jj, (evname, tab1, tab2, ev1, ev2) in enumerate( + zip(["nino", "nina"], [nino_mod_land, nina_mod_land], + [nino_obs_land, nina_obs_land], + [nino_mod_years, nina_mod_years], + [nino_obs_years, nina_obs_years])): + tmp1 = tab1( + longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + tmp2 = tab2( + longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + dict_metric, dict_nc = fill_dict_teleconnection( + tmp1, tmp2, dataset1, dataset2, actualtimebounds_mod, + actualtimebounds_obs, yearN_mod, yearN_obs, nbr, + "slp_" + evname + "_djf_map_" + reg + "__", evname + "_" + reg, + "hPa/day", centered_rmse=centered_rmse, biased_rmse=biased_rmse, + dict_metric=dict_metric, dict_nc=dict_nc, ev_name=evname, + events1=ev1, events2=ev2) + nbr += 2 + del dictreg, tmp1, tmp2 + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'spatialSTD_' + dataset1: std_mod} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'spatialSTD_' + dataset2: std_obs} + dict3 = {'metric_name': Name, 'metric_valueRMSE_' + dataset2: slpRmse, + 'metric_valueRMSE_error_' + dataset2: slpRmseErr, + 'metric_valueCORR_' + dataset2: slpCorr, + 'metric_valueCORR_error_' + dataset2: slpCorrErr, + 'metric_valueSTD_' + dataset2: slpStd, + 'metric_valueSTD_error_' + dataset2: slpStdErr, + 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + dict3.update(dict_metric) + SaveNetcdf( + file_name, var1=slp_mod_slope, var1_attributes=dict1, + var1_name='reg_slp_over_sst_djf_map__' + dataset1, var2=slp_obs_slope, + var2_attributes=dict2, var2_name='reg_slp_over_sst_djf_map__' + dataset2, + global_attributes=dict3, **dict_nc) + del dict1, dict2, dict3, dict_metric, dict_nc, file_name, list_region + if slpCorr is not None: + slpCorr = 1 - slpCorr + # Create output + EnsoSlpMapMetric = { + 'name': Name, 'Rmse__value': slpRmse, 'Rmse__value_error': slpRmseErr, 'Rmse__units': Units, 'method': Method, + 'Corr__value': slpCorr, 'Corr__value_error': slpCorrErr, 'Corr__units': '', 'Std__value': slpStd, + 'Std__value_error': slpStdErr, 'Std__units': Units + ' / ' + Units, 'nyears_model': yearN_mod, + 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag, 'units': ''} + return EnsoSlpMapMetric + + +def EnsoSlpMapJja(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + slpfilemod, slpnamemod, slpareafilemod, slpareanamemod, slplandmaskfilemod, slplandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, + slpfileobs, slpnameobs, slpareafileobs, slpareanameobs, slplandmaskfileobs, slplandmasknameobs, + sstbox, slpbox, event_definition, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', + debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The EnsoSlpMapJja() function computes sea level pressure anomalies pattern associated with ENSO on the globe. + First metric: rmse(observations vs model). + Second metric: correlation(observations vs model). + Third metric: std(model)/std(observations) + These metrics can be used to compute a Taylor diagram. + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST areacell + :param sstareanamemod: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafilemod' + :param sstlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST landmask + :param sstlandmasknamemod: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfilemod' + :param slpfilemod: string + path_to/filename of the file (NetCDF) of the modeled SLP + :param slpnamemod: string + name of SLP variable (psl) in 'slpfilemod' + :param slpareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SLP areacell + :param slpareanamemod: string, optional + name of areacell for the SLP variable (areacella, areacello,...) in 'slpareafilemod' + :param slplandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SLP landmask + :param slplandmasknamemod: string, optional + name of landmask for the SLP variable (sftlf,...) in 'slplandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST areacell + :param sstareanameobs: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafileobs' + :param sstlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST landmask + :param sstlandmasknameobs: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfileobs' + :param slpfileobs: string + path_to/filename of the file (NetCDF) of the observed SLP + :param slpnameobs: string + name of SLP variable (slp) in 'slpfileobs' + :param slpareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SLP areacell + :param slpareanameobs: string, optional + name of areacell for the SLP variable (areacella, areacello,...) in 'slpareafileobs' + :param slplandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SLP landmask + :param slplandmasknameobs: string, optional + name of landmask for the SLP variable (sftlf,...) in 'slplandmaskfileobs' + :param sstbox: string + name of box (e.g. 'global') for SST + :param slpbox: string + name of box (e.g. 'global') for SLP + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': -0.75} + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return EnsoSlpMapMetric: dict + name, value (rms [obs;model]), value_error, units, method, value2 (corr [obs;model]), + value_error2, units2, value3 (std_model / std_obs), value_error3, units3, nyears_model, nyears_observations, + time_frequency, time_period_mod, time_period_obs, ref, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = "ENSO JJA SLPA pattern" + Method = region_ev + " SSTA regressed against sea level pressure anomalies in " + slpbox + " during JJA" + Units = "" if kwargs['normalization'] else "hPa/C" + Ref = 'Using CDAT regridding, correlation (centered and biased), std (centered and biased) and ' + \ + 'rms (uncentered and biased) calculation' + metric = "EnsoSlpMapJja" + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod_box, mod_areacell, keyerror_mod1 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs_box, obs_areacell, keyerror_obs1 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + slp_mod, slp_mod_areacell, keyerror_mod2 = Read_data_mask_area( + slpfilemod, slpnamemod, 'pressure', metric, slpbox, file_area=slpareafilemod, name_area=slpareanamemod, + file_mask=slplandmaskfilemod, name_mask=slplandmasknamemod, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + slp_obs, slp_obs_areacell, keyerror_obs2 = Read_data_mask_area( + slpfileobs, slpnameobs, 'pressure', metric, slpbox, file_area=slpareafileobs, name_area=slpareanameobs, + file_mask=slplandmaskfileobs, name_mask=slplandmasknameobs, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst_mod_box, slp_mod, keyerror_mod3 = CheckTime(sst_mod_box, slp_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs_box, slp_obs, keyerror_obs3 = CheckTime(sst_obs_box, slp_obs, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod_box.shape[0] / 12)) + yearN_obs = int(round(sst_obs_box.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod_box) + actualtimebounds_obs = TimeBounds(sst_obs_box) + + if (keyerror_mod1 is not None or keyerror_obs1 is not None or keyerror_mod2 is not None) or \ + (keyerror_obs2 is not None or keyerror_mod3 is not None or keyerror_obs3 is not None): + slpCorr, slpCorrErr, slpRmse, slpRmseErr, slpStd, slpStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + tmp = [keyerror_mod1, keyerror_mod2, keyerror_mod3, keyerror_obs1, keyerror_obs2, keyerror_obs3] + keyerror = add_up_errors(tmp) + else: + # smoothing is not applied + if 'smoothing' in list(kwargs.keys()): + smooth = deepcopy(kwargs['smoothing']) + kwargs['smoothing'] = False + else: + smooth = False + # ------------------------------------------------ + # 1. SSTA + # ------------------------------------------------ + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, _, keyerror_mod = PreProcessTS(sst_mod_box, '', areacell=mod_areacell, average='horizontal', + compute_anom=False, region=region_ev, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS(sst_obs_box, '', areacell=obs_areacell, average='horizontal', + compute_anom=False, region=region_ev, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + slpCorr, slpCorrErr, slpRmse, slpRmseErr, slpStd, slpStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sst_mod)), + 'time2': '(obs) ' + str(TimeBounds(sst_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 Seasonal mean and anomalies + enso_mod = SeasonalMean(sst_mod, "JJA", compute_anom=True) + enso_obs = SeasonalMean(sst_obs, "JJA", compute_anom=True) + if debug is True: + dict_debug = { + 'axes1': '(mod) ' + str([ax.id for ax in enso_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(enso_mod.shape), 'shape2': '(obs) ' + str(enso_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(enso_mod)), 'time2': '(obs) ' + str(TimeBounds(enso_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # ------------------------------------------------ + # 2. SLPA + # ------------------------------------------------ + # 2.1 SLPA in 'slpbox' are normalized / detrended / smoothed (running average) if applicable + # Preprocess slp (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + slp_mod, Method, keyerror_mod = PreProcessTS( + slp_mod, Method, areacell=slp_mod_areacell, compute_anom=False, region=slpbox, **kwargs) + slp_obs, _, keyerror_obs = PreProcessTS( + slp_obs, '', areacell=slp_obs_areacell, compute_anom=False, region=slpbox, **kwargs) + del slp_mod_areacell, slp_obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + slpCorr, slpCorrErr, slpRmse, slpRmseErr, slpStd, slpStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in slp_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod.shape), 'shape2': '(obs) ' + str(slp_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(slp_mod)), + 'time2': '(obs) ' + str(TimeBounds(slp_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 2.2 Seasonal mean and anomalies + slp_mod = SeasonalMean(slp_mod, "JJA", compute_anom=True) * 1e-2 + slp_obs = SeasonalMean(slp_obs, "JJA", compute_anom=True) * 1e-2 + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in slp_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod.shape), 'shape2': '(obs) ' + str(slp_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(slp_mod)), + 'time2': '(obs) ' + str(TimeBounds(slp_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # ------------------------------------------------ + # 3. Regression map + # ------------------------------------------------ + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + slp_mod, slp_obs, Method = TwoVarRegrid( + slp_mod, slp_obs, Method, region=slpbox, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in slp_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod.shape), 'shape2': '(obs) ' + str(slp_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # regression + slp_mod_slope = LinearRegressionTsAgainstMap(slp_mod, enso_mod, return_stderr=False) + slp_obs_slope = LinearRegressionTsAgainstMap(slp_obs, enso_obs, return_stderr=False) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in slp_mod_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs_slope.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod_slope.shape), + 'shape2': '(obs) ' + str(slp_obs_slope.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after LinearRegressionTsAgainstMap', 15, **dict_debug) + + # mask Pacific + slp_mod_slope, keyerror_mod = BasinMask( + slp_mod_slope, 'pacific', box=slpbox, lat1=-15, lat2=15, latkey='between', debug=debug) + slp_obs_slope, keyerror_obs = BasinMask( + slp_obs_slope, 'pacific', box=slpbox, lat1=-15, lat2=15, latkey='between', debug=debug) + if keyerror_mod is not None or keyerror_obs is not None: + slpCorr, slpCorrErr, slpRmse, slpRmseErr, slpStd, slpStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in slp_mod_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs_slope.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod_slope.shape), + 'shape2': '(obs) ' + str(slp_obs_slope.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after BasinMask', 15, **dict_debug) + + # Metric 1 + slpRmse, keyerror = RmsAxis( + slp_mod_slope, slp_obs_slope, axis='xy', centered=centered_rmse, biased=biased_rmse) + slpRmseErr = None + # Metric 2 + slpCorr = float(Correlation(slp_mod_slope, slp_obs_slope, axis='xy', centered=1, biased=1)) + slpCorrErr = None + # Metric 3 + std_mod = Std(slp_mod_slope, weights=None, axis='xy', centered=1, biased=1) + std_obs = Std(slp_obs_slope, weights=None, axis='xy', centered=1, biased=1) + slpStd = float(std_mod) / float(std_obs) + slpStdErr = None + + # Dive down diagnostic + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + + if netcdf is True: + # read + slp_mod_land, slp_mod_areacell, keyerror_mod1 = Read_data_mask_area( + slpfilemod, slpnamemod, 'pressure', metric, slpbox, file_area=slpareafilemod, + name_area=slpareanamemod, file_mask=slplandmaskfilemod, name_mask=slplandmasknamemod, + maskland=False, maskocean=True, time_bounds=kwargs['time_bounds_mod'], debug=debug, + **kwargs) + slp_obs_land, slp_obs_areacell, keyerror_obs1 = Read_data_mask_area( + slpfileobs, slpnameobs, 'pressure', metric, slpbox, file_area=slpareafileobs, + name_area=slpareanameobs, file_mask=slplandmaskfileobs, name_mask=slplandmasknameobs, + maskland=False, maskocean=True, time_bounds=kwargs['time_bounds_obs'], debug=debug, + **kwargs) + sst_mod, slp_mod_land, keyerror_mod1 = CheckTime( + sst_mod, slp_mod_land, metric_name=metric, debug=debug, **kwargs) + sst_obs, slp_obs_land, keyerror_obs2 = CheckTime( + sst_obs, slp_obs_land, metric_name=metric, debug=debug, **kwargs) + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + # process + slp_mod_land, _, keyerror_mod = PreProcessTS(slp_mod_land, '', areacell=slp_mod_areacell, + compute_anom=False, region=slpbox, **kwargs) + slp_obs_land, _, keyerror_obs = PreProcessTS(slp_obs_land, '', areacell=slp_obs_areacell, + compute_anom=False, region=slpbox, **kwargs) + del slp_mod_areacell, slp_obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = { + 'axes1': '(mod) ' + str([ax.id for ax in slp_mod_land.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs_land.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod_land.shape), + 'shape2': '(obs) ' + str(slp_obs_land.shape), + 'time1': '(mod) ' + str(TimeBounds(slp_mod_land)), + 'time2': '(obs) ' + str(TimeBounds(slp_obs_land))} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after PreProcessTS', 15, **dict_debug) + # anomalies + slp_mod_land = SeasonalMean(slp_mod_land, "JJA", compute_anom=True) * 1e-2 + slp_obs_land = SeasonalMean(slp_obs_land, "JJA", compute_anom=True) * 1e-2 + if debug is True: + dict_debug = { + 'axes1': '(mod) ' + str([ax.id for ax in slp_mod_land.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs_land.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod_land.shape), + 'shape2': '(obs) ' + str(slp_obs_land.shape), + 'time1': '(mod) ' + str(TimeBounds(slp_mod_land)), + 'time2': '(obs) ' + str(TimeBounds(slp_obs_land))} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after SeasonalMean', 15, **dict_debug) + # regridding + if isinstance(kwargs['regridding'], dict): + slp_mod_land, slp_obs_land, _ = TwoVarRegrid( + slp_mod_land, slp_obs_land, '', region=slpbox, **kwargs['regridding']) + if debug is True: + dict_debug = { + 'axes1': '(mod) ' + str([ax.id for ax in slp_mod_land.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs_land.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod_land.shape), + 'shape2': '(obs) ' + str(slp_obs_land.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after TwoVarRegrid', 15, **dict_debug) + # regression + slp_mod_land_slope = LinearRegressionTsAgainstMap( + slp_mod_land, enso_mod, return_stderr=False) + slp_obs_land_slope = LinearRegressionTsAgainstMap( + slp_obs_land, enso_obs, return_stderr=False) + if debug is True: + dict_debug = { + 'axes1': '(mod) ' + str([ax.id for ax in slp_mod_land_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs_land_slope.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod_land_slope.shape), + 'shape2': '(obs) ' + str(slp_obs_land_slope.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after LinearRegressionTsAgainstMap', 15, **dict_debug) + # ENSO events: SSTA > (<) 'threshold' during 'season' are considered as El Nino + # (La Nina) events + if 'smoothing' in list(kwargs.keys()): + kwargs['smoothing'] = smooth + del smooth + sst_mod, _, keyerror_mod = PreProcessTS( + sst_mod_box, '', areacell=mod_areacell, average='horizontal', compute_anom=False, + region=region_ev, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs_box, '', areacell=obs_areacell, average='horizontal', compute_anom=False, + region=region_ev, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Lists event years + nino_mod_years = DetectEvents( + sst_mod, season_ev, threshold, normalization=normalize, nino=True) + nina_mod_years = DetectEvents( + sst_mod, season_ev, -threshold, normalization=normalize, nino=False) + nino_obs_years = DetectEvents( + sst_obs, season_ev, threshold, normalization=normalize, nino=True) + nina_obs_years = DetectEvents( + sst_obs, season_ev, -threshold, normalization=normalize, nino=False) + if debug is True: + dict_debug = {'nino1': '(mod) nbr(' + str(len(nino_mod_years)) + '): ' + + str(nino_mod_years), + 'nina1': '(mod) nbr(' + str(len(nina_mod_years)) + '): ' + + str(nina_mod_years), + 'nino2': '(obs) nbr(' + str(len(nino_obs_years)) + '): ' + + str(nino_obs_years), + 'nina2': '(obs) nbr(' + str(len(nina_obs_years)) + '): ' + + str(nina_obs_years)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after DetectEvents', 15, **dict_debug) + # samples + nino_mod = Composite(slp_mod, nino_mod_years, kwargs['frequency']) + nina_mod = Composite(slp_mod, nina_mod_years, kwargs['frequency']) + nino_obs = Composite(slp_obs, nino_obs_years, kwargs['frequency']) + nina_obs = Composite(slp_obs, nina_obs_years, kwargs['frequency']) + nino_mod_land = Composite(slp_mod_land, nino_mod_years, kwargs['frequency']) + nina_mod_land = Composite(slp_mod_land, nina_mod_years, kwargs['frequency']) + nino_obs_land = Composite(slp_obs_land, nino_obs_years, kwargs['frequency']) + nina_obs_land = Composite(slp_obs_land, nina_obs_years, kwargs['frequency']) + # mask Pacific + nino_mod, keyerror_mod1 = BasinMask(nino_mod, 'pacific', box=slpbox, lat1=-15, + lat2=15, latkey='between', debug=debug) + nina_mod, keyerror_mod2 = BasinMask(nina_mod, 'pacific', box=slpbox, lat1=-15, + lat2=15, latkey='between', debug=debug) + nino_obs, keyerror_obs1 = BasinMask(nino_obs, 'pacific', box=slpbox, lat1=-15, + lat2=15, latkey='between', debug=debug) + nina_obs, keyerror_obs2 = BasinMask(nina_obs, 'pacific', box=slpbox, lat1=-15, + lat2=15, latkey='between', debug=debug) + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + keyerror = add_up_errors( + [keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + # Metrics ENSO events global + dict_metric, dict_nc = dict(), dict() + nbr = 3 + for jj, (evname, tab1, tab2, ev1, ev2) in enumerate( + zip(["nino", "nina"], [nino_mod, nina_mod], [nino_obs, nina_obs], + [nino_mod_years, nina_mod_years], + [nino_obs_years, nina_obs_years])): + dict_metric, dict_nc = fill_dict_teleconnection( + tab1, tab2, dataset1, dataset2, actualtimebounds_mod, + actualtimebounds_obs, yearN_mod, yearN_obs, nbr, + "slp_" + evname + "_jja_map__", evname, "hPa", + centered_rmse=centered_rmse, biased_rmse=biased_rmse, + dict_metric=dict_metric, dict_nc=dict_nc, ev_name=evname, events1=ev1, + events2=ev2) + nbr += 2 + list_region = ["africaSE", "americaN", "americaS", "asiaS", "oceania"] + for ii, reg in enumerate(list_region): + # select region + dictreg = ReferenceRegions(reg) + tmp1 = slp_mod_land_slope( + longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + tmp2 = slp_obs_land_slope( + longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + dict_metric, dict_nc = fill_dict_teleconnection( + tmp1, tmp2, dataset1, dataset2, actualtimebounds_mod, + actualtimebounds_obs, yearN_mod, yearN_obs, nbr, + "reg_slp_over_sst_jja_map_" + reg + "__", reg, Units, + centered_rmse=centered_rmse, biased_rmse=biased_rmse, + dict_metric=dict_metric, dict_nc=dict_nc) + nbr += 2 + for jj, (evname, tab1, tab2, ev1, ev2) in enumerate( + zip(["nino", "nina"], [nino_mod_land, nina_mod_land], + [nino_obs_land, nina_obs_land], + [nino_mod_years, nina_mod_years], + [nino_obs_years, nina_obs_years])): + tmp1 = tab1( + longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + tmp2 = tab2( + longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + dict_metric, dict_nc = fill_dict_teleconnection( + tmp1, tmp2, dataset1, dataset2, actualtimebounds_mod, + actualtimebounds_obs, yearN_mod, yearN_obs, nbr, + "slp_" + evname + "_jja_map_" + reg + "__", evname + "_" + reg, + "hPa", centered_rmse=centered_rmse, biased_rmse=biased_rmse, + dict_metric=dict_metric, dict_nc=dict_nc, ev_name=evname, + events1=ev1, events2=ev2) + nbr += 2 + del dictreg, tmp1, tmp2 + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'spatialSTD_' + dataset1: std_mod} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'spatialSTD_' + dataset2: std_obs} + dict3 = {'metric_name': Name, 'metric_valueRMSE_' + dataset2: slpRmse, + 'metric_valueRMSE_error_' + dataset2: slpRmseErr, + 'metric_valueCORR_' + dataset2: slpCorr, + 'metric_valueCORR_error_' + dataset2: slpCorrErr, + 'metric_valueSTD_' + dataset2: slpStd, + 'metric_valueSTD_error_' + dataset2: slpStdErr, + 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + dict3.update(dict_metric) + SaveNetcdf( + file_name, var1=slp_mod_slope, var1_attributes=dict1, + var1_name='reg_slp_over_sst_jja_map__' + dataset1, var2=slp_obs_slope, + var2_attributes=dict2, var2_name='reg_slp_over_sst_jja_map__' + dataset2, + global_attributes=dict3, **dict_nc) + del dict1, dict2, dict3, dict_metric, dict_nc, file_name, list_region + if slpCorr is not None: + slpCorr = 1 - slpCorr + # Create output + EnsoSlpMapMetric = { + 'name': Name, 'Rmse__value': slpRmse, 'Rmse__value_error': slpRmseErr, 'Rmse__units': Units, 'method': Method, + 'Corr__value': slpCorr, 'Corr__value_error': slpCorrErr, 'Corr__units': '', 'Std__value': slpStd, + 'Std__value_error': slpStdErr, 'Std__units': Units + ' / ' + Units, 'nyears_model': yearN_mod, + 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag, 'units': ''} + return EnsoSlpMapMetric + + + +def EnsoSstLonRmse(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, + box, event_definition, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, + netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The EnsoSstLonRmse() function computes sea surface temperature anomalies pattern associated with ENSO in a 'box' + (usually the equatorial_pacific) + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST areacell + :param sstareanamemod: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafilemod' + :param sstlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST landmask + :param sstlandmasknamemod: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST areacell + :param sstareanameobs: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafileobs' + :param sstlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST landmask + :param sstlandmasknameobs: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfileobs' + :param box: string + name of box (e.g. 'equatorial_pacific') for SST + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': -0.75} + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return EnsoSstMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_mod, + time_period_obs, ref, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = "ENSO Zonal SSTA pattern" + Method = region_ev + " SSTA during " + season_ev + " regressed against " + box + " SSTA" + Units = '' if kwargs['normalization'] else 'C/C' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = 'EnsoSstLonRmse' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod1 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs1 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + sstmap_mod, sstmap_mod_areacell, keyerror_mod2 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, box, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sstmap_obs, sstmap_obs_areacell, keyerror_obs2 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, box, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst_mod, sstmap_mod, keyerror_mod3 = CheckTime(sst_mod, sstmap_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs, sstmap_obs, keyerror_obs3 = CheckTime(sst_obs, sstmap_obs, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if (keyerror_mod1 is not None or keyerror_obs1 is not None or keyerror_mod2 is not None) or \ + (keyerror_obs2 is not None or keyerror_mod3 is not None or keyerror_obs3 is not None): + sstRmse, sstRmseErr = None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + tmp = [keyerror_mod1, keyerror_obs1, keyerror_mod2, keyerror_obs2, keyerror_mod3, keyerror_obs3] + keyerror = add_up_errors(tmp) + else: + # ------------------------------------------------ + # 1. box SSTA + # ------------------------------------------------ + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, _, keyerror_mod1 = PreProcessTS( + sst_mod, '', areacell=mod_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + sst_obs, _, keyerror_obs1 = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + sstmap_mod, Method, keyerror_mod1 = PreProcessTS( + sstmap_mod, Method, areacell=sstmap_mod_areacell, compute_anom=False, region=box, **kwargs) + sstmap_obs, _, keyerror_obs2 = PreProcessTS( + sstmap_obs, '', areacell=sstmap_obs_areacell, compute_anom=False, region=box, **kwargs) + del mod_areacell, obs_areacell, sstmap_mod_areacell, sstmap_obs_areacell + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + sstRmse, sstRmseErr = None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + if debug is True: + dict_debug = { + 'axes1': '(mod ts) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs ts) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'axes3': '(mod map) ' + str([ax.id for ax in sstmap_mod.getAxisList()]), + 'axes4': '(obs map) ' + str([ax.id for ax in sstmap_obs.getAxisList()]), + 'shape1': '(mod ts) ' + str(sst_mod.shape), 'shape2': '(obs ts) ' + str(sst_obs.shape), + 'shape3': '(mod map) ' + str(sstmap_mod.shape), 'shape4': '(obs map) ' + str(sstmap_obs.shape), + 'time1': '(mod ts) ' + str(TimeBounds(sst_mod)), 'time2': '(obs ts) ' + str(TimeBounds(sst_obs)), + 'time3': '(mod ts) ' + str(TimeBounds(sstmap_mod)), + 'time4': '(obs ts) ' + str(TimeBounds(sstmap_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 Seasonal mean and anomalies + sst_mod = SeasonalMean(sst_mod, season_ev, compute_anom=True) + sst_obs = SeasonalMean(sst_obs, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sst_mod)), + 'time2': '(obs) ' + str(TimeBounds(sst_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # ------------------------------------------------ + # 2. spatial SSTA + # ------------------------------------------------ + # 2.2 Seasonal mean and anomalies + sstmap_mod = SeasonalMean(sstmap_mod, season_ev, compute_anom=True) + sstmap_obs = SeasonalMean(sstmap_obs, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sstmap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sstmap_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sstmap_mod.shape), 'shape2': '(obs) ' + str(sstmap_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sstmap_mod)), + 'time2': '(obs) ' + str(TimeBounds(sstmap_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # ------------------------------------------------ + # 3. Regression map + # ------------------------------------------------ + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + sstmap_mod, sstmap_obs, Method = TwoVarRegrid( + sstmap_mod, sstmap_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sstmap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sstmap_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sstmap_mod.shape), + 'shape2': '(obs) ' + str(sstmap_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Meridional average + sstlon_mod, keyerror_mod = AverageMeridional(sstmap_mod) + sstlon_obs, keyerror_obs = AverageMeridional(sstmap_obs) + if keyerror_mod is not None or keyerror_obs is not None: + sstRmse, sstRmseErr = None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sstlon_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sstlon_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sstlon_mod.shape), + 'shape2': '(obs) ' + str(sstlon_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + + # regression + sst_mod_slope = LinearRegressionTsAgainstMap(sstlon_mod, sst_mod, return_stderr=False) + sst_obs_slope = LinearRegressionTsAgainstMap(sstlon_obs, sst_obs, return_stderr=False) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs_slope.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod_slope.shape), + 'shape2': '(obs) ' + str(sst_obs_slope.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after LinearRegressionTsAgainstMap', 15, **dict_debug) + + # Computes the root mean square difference + sstRmse, keyerror = RmsZonal(sst_mod_slope, sst_obs_slope, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + sstRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(sst_mod_slope), 'observations': ArrayToList(sst_obs_slope), + 'axis': list(sst_mod_slope.getAxis(0)[:])} + + if netcdf is True: + # Read file and select the right region + sstmap_mod, sstmap_mod_areacell, keyerror_mod = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, 'equatorial_pacific_LatExt2', + file_area=sstareafilemod, name_area=sstareanamemod, file_mask=sstlandmaskfilemod, + name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sstmap_obs, sstmap_obs_areacell, keyerror_obs = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, 'equatorial_pacific_LatExt2', + file_area=sstareafileobs, name_area=sstareanameobs, file_mask=sstlandmaskfileobs, + name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Preprocess ts (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sstmap_mod, _, keyerror_mod = PreProcessTS( + sstmap_mod, '', areacell=sstmap_mod_areacell, compute_anom=False, + region="equatorial_pacific_LatExt2", **kwargs) + sstmap_obs, _, keyerror_obs = PreProcessTS( + sstmap_obs, '', areacell=sstmap_obs_areacell, compute_anom=False, + region="equatorial_pacific_LatExt2", **kwargs) + del sstmap_mod_areacell, sstmap_obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sstmap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sstmap_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sstmap_mod.shape), + 'shape2': '(obs) ' + str(sstmap_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sstmap_mod)), + 'time2': '(obs) ' + str(TimeBounds(sstmap_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + # Seasonal mean and anomalies + sstmap_mod = SeasonalMean(sstmap_mod, season_ev, compute_anom=True) + sstmap_obs = SeasonalMean(sstmap_obs, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sstmap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sstmap_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sstmap_mod.shape), + 'shape2': '(obs) ' + str(sstmap_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sstmap_mod)), + 'time2': '(obs) ' + str(TimeBounds(sstmap_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', + 'regridder', 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + sstmap_mod, sstmap_obs, _ = TwoVarRegrid( + sstmap_mod, sstmap_obs, '', region='equatorial_pacific_LatExt2', + **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sstmap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sstmap_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sstmap_mod.shape), + 'shape2': '(obs) ' + str(sstmap_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + # Linear regression + sstmap_mod_slope = LinearRegressionTsAgainstMap(sstmap_mod, sst_mod, return_stderr=False) + sstmap_obs_slope = LinearRegressionTsAgainstMap(sstmap_obs, sst_obs, return_stderr=False) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sstmap_mod_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sstmap_obs_slope.getAxisList()]), + 'shape1': '(mod) ' + str(sstmap_mod_slope.shape), + 'shape2': '(obs) ' + str(sstmap_obs_slope.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after LinearRegressionTsAgainstMap', 15, **dict_debug) + # Lists event years + nina_years_mod = DetectEvents(sst_mod, season_ev, -threshold, normalization=normalize, + nino=False, compute_season=False) + nino_years_mod = DetectEvents(sst_mod, season_ev, threshold, normalization=normalize, + nino=True, compute_season=False) + nina_years_obs = DetectEvents(sst_obs, season_ev, -threshold, normalization=normalize, + nino=False, compute_season=False) + nino_years_obs = DetectEvents(sst_obs, season_ev, threshold, normalization=normalize, + nino=True, compute_season=False) + if debug is True: + dict_debug = {'nina1': '(mod) nbr(' + str(len(nina_years_mod)) + '): ' + + str(nina_years_mod), + 'nina2': '(obs) nbr(' + str(len(nina_years_obs)) + '): ' + + str(nina_years_obs), + 'nino1': '(mod) nbr(' + str(len(nino_years_mod)) + '): ' + + str(nino_years_mod), + 'nino2': '(obs) nbr(' + str(len(nino_years_obs)) + '): ' + + str(nino_years_obs)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + # composites + if len(nina_years_mod) > 0: + nina_sstlon_mod = Composite(sstlon_mod, nina_years_mod, kwargs['frequency']) + nina_sstmap_mod = Composite(sstmap_mod, nina_years_mod, kwargs['frequency']) + else: + nina_sstlon_mod = MyEmpty(sstlon_mod[0], time=False) + nina_sstmap_mod = MyEmpty(sstmap_mod[0], time=False) + if len(nino_years_mod) > 0: + nino_sstlon_mod = Composite(sstlon_mod, nino_years_mod, kwargs['frequency']) + nino_sstmap_mod = Composite(sstmap_mod, nino_years_mod, kwargs['frequency']) + else: + nino_sstlon_mod = MyEmpty(sstlon_mod[0], time=False) + nino_sstmap_mod = MyEmpty(sstmap_mod[0], time=False) + if len(nina_years_obs) > 0: + nina_sstlon_obs = Composite(sstlon_obs, nina_years_obs, kwargs['frequency']) + nina_sstmap_obs = Composite(sstmap_obs, nina_years_obs, kwargs['frequency']) + else: + nina_sstlon_obs = MyEmpty(sstlon_obs[0], time=False) + nina_sstmap_obs = MyEmpty(sstmap_obs[0], time=False) + if len(nino_years_obs) > 0: + nino_sstlon_obs = Composite(sstlon_obs, nino_years_obs, kwargs['frequency']) + nino_sstmap_obs = Composite(sstmap_obs, nino_years_obs, kwargs['frequency']) + else: + nino_sstlon_obs = MyEmpty(sstlon_obs[0], time=False) + nino_sstmap_obs = MyEmpty(sstmap_obs[0], time=False) + # save + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + my_thresh = 'std' if normalize is True else 'C' + my_units = '' if kwargs['normalization'] is True else 'C' + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': region_ev + " SSTA during " + season_ev + " regressed against " + + box + " SSTA"} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': region_ev + " SSTA during " + season_ev + " regressed against " + + box + " SSTA"} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': region_ev + " SSTA during " + season_ev + + " regressed against tropical Pacific SSTA"} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': region_ev + " SSTA during " + season_ev + + " regressed against tropical Pacific SSTA"} + dict5 = {'units': my_units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), 'nina_years': str(nina_years_mod), + 'description': + "Nina events = " + region_ev + " SSTA < -" + str(threshold) + my_thresh + + " during " + season_ev + ", this is the composite of La Nina events"} + dict6 = {'units': my_units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), 'nino_years': str(nino_years_mod), + 'description': + "Nino events = " + region_ev + " SSTA > " + str(threshold) + my_thresh + + " during " + season_ev + ", this is the composite of El Nino events"} + dict7 = {'units': my_units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), 'nina_years': str(nina_years_obs), + 'description': + "Nina events = " + region_ev + " SSTA < -" + str(threshold) + my_thresh + + " during " + season_ev + ", this is the composite of La Nina events"} + dict8 = {'units': my_units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), 'nino_years': str(nino_years_obs), + 'description': + "Nino events = " + region_ev + " SSTA > " + str(threshold) + my_thresh + + " during " + season_ev + ", this is the composite of El Nino events"} + dict9 = {'metric_name': Name, 'metric_value_' + dataset2: sstRmse, + 'metric_value_error_' + dataset2: sstRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=sst_mod_slope, var1_attributes=dict1, + var1_name='sst_against_sst_lon__' + dataset1, var2=sst_obs_slope, var2_attributes=dict2, + var2_name='sst_against_sst_lon__' + dataset2, var3=sstmap_mod_slope, + var3_attributes=dict3, var3_name='sst_against_sst_map__' + dataset1, + var4=sstmap_obs_slope, var4_attributes=dict4, + var4_name='sst_against_sst_map__' + dataset2, var5=nina_sstlon_mod, + var5_attributes=dict5, var5_name='Nina_sst_lon__' + dataset1, var6=nina_sstmap_mod, + var6_attributes=dict5, var6_name='Nina_sst_map__' + dataset1, var7=nino_sstlon_mod, + var7_attributes=dict6, var7_name='Nino_sst_lon__' + dataset1, var8=nino_sstmap_mod, + var8_attributes=dict6, var8_name='Nino_sst_map__' + dataset1, var9=nina_sstlon_obs, + var9_attributes=dict7, var9_name='Nina_sst_lon__' + dataset2, var10=nina_sstmap_obs, + var10_attributes=dict7, var10_name='Nina_sst_map__' + dataset2, var11=nino_sstlon_obs, + var11_attributes=dict8, var11_name='Nino_sst_lon__' + dataset2, var12=nino_sstmap_obs, + var12_attributes=dict8, var12_name='Nino_sst_map__' + dataset2, global_attributes=dict9) + del dict1, dict2, dict3, dict4, dict5, dict6, dict7, dict8, dict9 + + # Create output + EnsoSstMetric = { + 'name': Name, 'value': sstRmse, 'value_error': sstRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return EnsoSstMetric + + +def EnsoSstMap(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, tsbox, + event_definition, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, + netcdf_name='', metname='', **kwargs): + """ + The EnsoSstMap() function computes surface temperature anomalies pattern associated with ENSO on the globe. + First metric: rmse(observations vs model). + Second metric: correlation(observations vs model). + Third metric: std(model)/std(observations) + These metrics can be used to compute a Taylor diagram. + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST areacell + :param sstareanamemod: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafilemod' + :param sstlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST landmask + :param sstlandmasknamemod: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST areacell + :param sstareanameobs: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafileobs' + :param sstlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST landmask + :param sstlandmasknameobs: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfileobs' + :param tsbox: string + name of box (e.g. 'global') for TS + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': -0.75} + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return EnsoSstMapMetric: dict + name, value (rms [obs;model]), value_error, units, method, value2 (corr [obs;model]), + value_error2, units2, value3 (std_model / std_obs), value_error3, units3, nyears_model, nyears_observations, + time_frequency, time_period_mod, time_period_obs, ref, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = "ENSO TSA pattern" + Method = region_ev + " SSTA during " + season_ev + " regressed against " + tsbox + " surface temperature anomalies" + Units = '' if kwargs['normalization'] else 'C/C' + Ref = 'Using CDAT regridding, correlation (centered and biased), std (centered and biased) and ' + \ + 'rms (uncentered and biased) calculation' + metric = 'EnsoSstMap' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod1 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs1 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + tsmap_mod, tsmap_mod_areacell, keyerror_mod2 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, tsbox, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + tsmap_obs, tsmap_obs_areacell, keyerror_obs2 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, tsbox, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst_mod, tsmap_mod, keyerror_mod3 = CheckTime(sst_mod, tsmap_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs, tsmap_obs, keyerror_obs3 = CheckTime(sst_obs, tsmap_obs, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if (keyerror_mod1 is not None or keyerror_obs1 is not None or keyerror_mod2 is not None) or \ + (keyerror_obs2 is not None or keyerror_mod3 is not None or keyerror_obs3 is not None): + tsCorr, tsCorrErr, tsRmse, tsRmseErr, tsStd, tsStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + tmp = [keyerror_mod1, keyerror_mod2, keyerror_mod3, keyerror_obs1, keyerror_obs2, keyerror_obs3] + keyerror = add_up_errors(tmp) + else: + # ------------------------------------------------ + # 1. SSTA + # ------------------------------------------------ + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, _, keyerror_mod1 = PreProcessTS( + sst_mod, '', areacell=mod_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + sst_obs, _, keyerror_obs1 = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + tsmap_mod, Method, keyerror_mod2 = PreProcessTS( + tsmap_mod, Method, areacell=tsmap_mod_areacell, compute_anom=False, region=tsbox, **kwargs) + tsmap_obs, _, keyerror_obs2 = PreProcessTS( + tsmap_obs, '', areacell=tsmap_obs_areacell, compute_anom=False, region=tsbox, **kwargs) + del mod_areacell, obs_areacell, tsmap_mod_areacell, tsmap_obs_areacell + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + tsCorr, tsCorrErr, tsRmse, tsRmseErr, tsStd, tsStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + if debug is True: + dict_debug = { + 'axes1': '(mod ts) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs ts) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'axes3': '(mod map) ' + str([ax.id for ax in tsmap_mod.getAxisList()]), + 'axes4': '(obs map) ' + str([ax.id for ax in tsmap_obs.getAxisList()]), + 'shape1': '(mod ts) ' + str(sst_mod.shape), 'shape2': '(obs ts) ' + str(sst_obs.shape), + 'shape3': '(mod map) ' + str(tsmap_mod.shape), 'shape4': '(obs map) ' + str(tsmap_obs.shape), + 'time1': '(mod ts) ' + str(TimeBounds(sst_mod)), 'time2': '(obs ts) ' + str(TimeBounds(sst_obs)), + 'time3': '(mod map) ' + str(TimeBounds(tsmap_mod)), + 'time4': '(obs map) ' + str(TimeBounds(tsmap_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 Seasonal mean and anomalies + enso_mod = SeasonalMean(sst_mod, season_ev, compute_anom=True) + enso_obs = SeasonalMean(sst_obs, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in enso_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in enso_obs.getAxisList()]), + 'shape1': '(mod) ' + str(enso_mod.shape), 'shape2': '(obs) ' + str(enso_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(enso_mod)), + 'time2': '(obs) ' + str(TimeBounds(enso_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # ------------------------------------------------ + # 2. TSA + # ------------------------------------------------ + # 2.2 Seasonal mean and anomalies + tsmap_mod = SeasonalMean(tsmap_mod, season_ev, compute_anom=True) + tsmap_obs = SeasonalMean(tsmap_obs, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in tsmap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in tsmap_obs.getAxisList()]), + 'shape1': '(mod) ' + str(tsmap_mod.shape), 'shape2': '(obs) ' + str(tsmap_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(tsmap_mod)), + 'time2': '(obs) ' + str(TimeBounds(tsmap_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # ------------------------------------------------ + # 3. Regression map + # ------------------------------------------------ + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + tsmap_mod, tsmap_obs, Method = TwoVarRegrid( + tsmap_mod, tsmap_obs, Method, region=tsbox, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in tsmap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in tsmap_obs.getAxisList()]), + 'shape1': '(mod) ' + str(tsmap_mod.shape), 'shape2': '(obs) ' + str(tsmap_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # regression + ts_mod_slope, ts_mod_stderr = LinearRegressionTsAgainstMap(tsmap_mod, enso_mod, return_stderr=True) + ts_obs_slope, ts_obs_stderr = LinearRegressionTsAgainstMap(tsmap_obs, enso_obs, return_stderr=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ts_mod_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ts_obs_slope.getAxisList()]), + 'shape1': '(mod) ' + str(ts_mod_slope.shape), + 'shape2': '(obs) ' + str(ts_obs_slope.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after LinearRegressionTsAgainstMap', 15, **dict_debug) + + # mask Pacific + ts_mod_slope, keyerror_mod = BasinMask( + ts_mod_slope, 'pacific', box=tsbox, lat1=-15, lat2=15, latkey='between', debug=debug) + ts_obs_slope, keyerror_obs = BasinMask( + ts_obs_slope, 'pacific', box=tsbox, lat1=-15, lat2=15, latkey='between', debug=debug) + if keyerror_mod is not None or keyerror_obs is not None: + tsCorr, tsCorrErr, tsRmse, tsRmseErr, tsStd, tsStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ts_mod_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ts_obs_slope.getAxisList()]), + 'shape1': '(mod) ' + str(ts_mod_slope.shape), + 'shape2': '(obs) ' + str(ts_obs_slope.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after BasinMask', 15, **dict_debug) + + # Metric 1 + tsRmse, keyerror = RmsAxis( + ts_mod_slope, ts_obs_slope, axis='xy', centered=centered_rmse, biased=biased_rmse) + tsRmseErr = None + # Metric 2 + tsCorr = float(Correlation(ts_mod_slope, ts_obs_slope, axis='xy', centered=1, biased=1)) + tsCorrErr = None + # Metric 3 + std_mod = Std(ts_mod_slope, weights=None, axis='xy', centered=1, biased=1) + std_obs = Std(ts_obs_slope, weights=None, axis='xy', centered=1, biased=1) + tsStd = float(std_mod) / float(std_obs) + tsStdErr = None + + # Dive down diagnostic + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + + if netcdf is True: + # read + ts_mod, ts_mod_areacell, keyerror_mod1 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, tsbox, file_area=sstareafilemod, + name_area=sstareanamemod, file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, + maskland=False, maskocean=True, time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + ts_obs, ts_obs_areacell, keyerror_obs1 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, tsbox, file_area=sstareafileobs, + name_area=sstareanameobs, file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, + maskland=False, maskocean=True, time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + sst_mod, ts_mod, keyerror_mod2 = CheckTime( + sst_mod, ts_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs, ts_obs, keyerror_obs2 = CheckTime( + sst_obs, ts_obs, metric_name=metric, debug=debug, **kwargs) + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + # process + ts_mod, _, keyerror_mod = PreProcessTS( + ts_mod, '', areacell=ts_mod_areacell, compute_anom=False, region=tsbox, **kwargs) + ts_obs, _, keyerror_obs = PreProcessTS( + ts_obs, '', areacell=ts_obs_areacell, compute_anom=False, region=tsbox, **kwargs) + del ts_mod_areacell, ts_obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ts_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ts_obs.getAxisList()]), + 'shape1': '(mod) ' + str(ts_mod.shape), + 'shape2': '(obs) ' + str(ts_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(ts_mod)), + 'time2': '(obs) ' + str(TimeBounds(ts_obs))} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after PreProcessTS', 15, **dict_debug) + # anomalies + ts_mod = SeasonalMean(ts_mod, season_ev, compute_anom=True) + ts_obs = SeasonalMean(ts_obs, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ts_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ts_obs.getAxisList()]), + 'shape1': '(mod) ' + str(ts_mod.shape), + 'shape2': '(obs) ' + str(ts_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(ts_mod)), + 'time2': '(obs) ' + str(TimeBounds(ts_obs))} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after SeasonalMean', 15, **dict_debug) + # regridding + if isinstance(kwargs['regridding'], dict): + ts_mod, ts_obs, _ = TwoVarRegrid( + ts_mod, ts_obs, '', region=tsbox, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ts_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ts_obs.getAxisList()]), + 'shape1': '(mod) ' + str(ts_mod.shape), + 'shape2': '(obs) ' + str(ts_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after TwoVarRegrid', 15, **dict_debug) + # regression + ts_mod = LinearRegressionTsAgainstMap(ts_mod, enso_mod, return_stderr=False) + ts_obs = LinearRegressionTsAgainstMap(ts_obs, enso_obs, return_stderr=False) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ts_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ts_obs.getAxisList()]), + 'shape1': '(mod) ' + str(ts_mod.shape), + 'shape2': '(obs) ' + str(ts_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after LinearRegressionTsAgainstMap', 15, **dict_debug) + list_region = ["africaSE", "americaN", "americaS", "asiaS", "oceania"] + # Metrics ENSO regression regional + dict_metric, dict_nc = dict(), dict() + nbr = 3 + for ii, reg in enumerate(list_region): + # select region + dictreg = ReferenceRegions(reg) + tmp1 = ts_mod(longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + tmp2 = ts_obs(longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + dict_metric, dict_nc = fill_dict_teleconnection( + tmp1, tmp2, dataset1, dataset2, actualtimebounds_mod, actualtimebounds_obs, + yearN_mod, yearN_obs, nbr, "reg_ts_over_sst_djf_map_" + reg + "__", reg, Units, + centered_rmse=centered_rmse, biased_rmse=biased_rmse, dict_metric=dict_metric, + dict_nc=dict_nc) + nbr += 2 + del dictreg, tmp1, tmp2 + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), 'spatialSTD_' + dataset1: std_mod} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), 'spatialSTD_' + dataset2: std_obs} + dict3 = {'metric_name': Name, 'metric_valueRMSE_' + dataset2: tsRmse, + 'metric_valueRMSE_error_' + dataset2: tsRmseErr, + 'metric_valueCORR_' + dataset2: tsCorr, + 'metric_valueCORR_error_' + dataset2: tsCorrErr, + 'metric_valueSTD_' + dataset2: tsStd, + 'metric_valueSTD_error_' + dataset2: tsStdErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + dict3.update(dict_metric) + SaveNetcdf( + file_name, var1=ts_mod_slope, var1_attributes=dict1, + var1_name='reg_ts_over_sst_map__' + dataset1, var2=ts_obs_slope, + var2_attributes=dict2, var2_name='reg_ts_over_sst_map__' + dataset2, + global_attributes=dict3, **dict_nc) + del dict1, dict2, dict3, dict_metric, dict_nc, file_name, list_region + if tsCorr is not None: + tsCorr = 1 - tsCorr + # Create output + EnsoSstMapMetric = { + 'name': Name, 'Rmse__value': tsRmse, 'Rmse__value_error': tsRmseErr, 'Rmse__units': Units, 'method': Method, + 'Corr__value': tsCorr, 'Corr__value_error': tsCorrErr, 'Corr__units': '', 'Std__value': tsStd, + 'Std__value_error': tsStdErr, 'Std__units': Units + ' / ' + Units, 'nyears_model': yearN_mod, + 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag, 'units': ''} + return EnsoSstMapMetric + + +def EnsoSstMapDjf(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, tsbox, + event_definition, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, + netcdf_name='', metname='', **kwargs): + """ + The EnsoSstMapDjf() function computes surface temperature anomalies pattern associated with ENSO on the globe. + First metric: rmse(observations vs model). + Second metric: correlation(observations vs model). + Third metric: std(model)/std(observations) + These metrics can be used to compute a Taylor diagram. + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST areacell + :param sstareanamemod: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafilemod' + :param sstlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST landmask + :param sstlandmasknamemod: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST areacell + :param sstareanameobs: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafileobs' + :param sstlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST landmask + :param sstlandmasknameobs: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfileobs' + :param tsbox: string + name of box (e.g. 'global') for TS + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': -0.75} + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return EnsoSstMapMetric: dict + name, value (rms [obs;model]), value_error, units, method, value2 (corr [obs;model]), + value_error2, units2, value3 (std_model / std_obs), value_error3, units3, nyears_model, nyears_observations, + time_frequency, time_period_mod, time_period_obs, ref, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = "ENSO DJF TSA pattern" + Method = region_ev + " SSTA regressed against surface temperature anomalies in " + tsbox + " during DJF" + Units = '' if kwargs['normalization'] else 'C/C' + Ref = 'Using CDAT regridding, correlation (centered and biased), std (centered and biased) and ' + \ + 'rms (uncentered and biased) calculation' + metric = "EnsoSstMapDjf" + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod_box, mod_areacell, keyerror_mod1 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs_box, obs_areacell, keyerror_obs1 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + ts_mod, ts_mod_areacell, keyerror_mod2 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, tsbox, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + ts_obs, ts_obs_areacell, keyerror_obs2 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, tsbox, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst_mod_box, ts_mod, keyerror_mod3 = CheckTime(sst_mod_box, ts_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs_box, ts_obs, keyerror_obs3 = CheckTime(sst_obs_box, ts_obs, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod_box.shape[0] / 12)) + yearN_obs = int(round(sst_obs_box.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod_box) + actualtimebounds_obs = TimeBounds(sst_obs_box) + + if (keyerror_mod1 is not None or keyerror_obs1 is not None or keyerror_mod2 is not None) or \ + (keyerror_obs2 is not None or keyerror_mod3 is not None or keyerror_obs3 is not None): + tsCorr, tsCorrErr, tsRmse, tsRmseErr, tsStd, tsStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + tmp = [keyerror_mod1, keyerror_mod2, keyerror_mod3, keyerror_obs1, keyerror_obs2, keyerror_obs3] + keyerror = add_up_errors(tmp) + else: + # smoothing is not applied + if 'smoothing' in list(kwargs.keys()): + smooth = deepcopy(kwargs['smoothing']) + kwargs['smoothing'] = False + else: + smooth = False + # ------------------------------------------------ + # 1. SSTA + # ------------------------------------------------ + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, _, keyerror_mod1 = PreProcessTS(sst_mod_box, '', areacell=mod_areacell, average='horizontal', + compute_anom=False, region=region_ev, **kwargs) + sst_obs, _, keyerror_obs1 = PreProcessTS(sst_obs_box, '', areacell=obs_areacell, average='horizontal', + compute_anom=False, region=region_ev, **kwargs) + ts_mod, Method, keyerror_mod2 = PreProcessTS(ts_mod, Method, areacell=ts_mod_areacell, compute_anom=False, + region=tsbox, **kwargs) + ts_obs, _, keyerror_obs2 = PreProcessTS(ts_obs, '', areacell=ts_obs_areacell, compute_anom=False, region=tsbox, + **kwargs) + del ts_mod_areacell, ts_obs_areacell + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + tsCorr, tsCorrErr, tsRmse, tsRmseErr, tsStd, tsStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + if debug is True: + dict_debug = { + 'axes1': '(mod ts) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs ts) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'axes3': '(mod map) ' + str([ax.id for ax in ts_mod.getAxisList()]), + 'axes4': '(obs map) ' + str([ax.id for ax in ts_obs.getAxisList()]), + 'shape1': '(mod ts) ' + str(sst_mod.shape), 'shape2': '(obs ts) ' + str(sst_obs.shape), + 'shape3': '(mod map) ' + str(ts_mod.shape), 'shape4': '(obs map) ' + str(ts_obs.shape), + 'time1': '(mod ts) ' + str(TimeBounds(sst_mod)), 'time2': '(obs ts) ' + str(TimeBounds(sst_obs)), + 'time3': '(mod map) ' + str(TimeBounds(ts_mod)), 'time4': '(obs map) ' + str(TimeBounds(ts_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 Seasonal mean and anomalies + enso_mod = SeasonalMean(sst_mod, "DJF", compute_anom=True) + enso_obs = SeasonalMean(sst_obs, "DJF", compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in enso_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in enso_obs.getAxisList()]), + 'shape1': '(mod) ' + str(enso_mod.shape), 'shape2': '(obs) ' + str(enso_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(enso_mod)), + 'time2': '(obs) ' + str(TimeBounds(enso_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # ------------------------------------------------ + # 2. TSA + # ------------------------------------------------ + # 2.2 Seasonal mean and anomalies + ts_mod = SeasonalMean(ts_mod, "DJF", compute_anom=True) + ts_obs = SeasonalMean(ts_obs, "DJF", compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ts_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ts_obs.getAxisList()]), + 'shape1': '(mod) ' + str(ts_mod.shape), 'shape2': '(obs) ' + str(ts_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(ts_mod)), 'time2': '(obs) ' + str(TimeBounds(ts_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # ------------------------------------------------ + # 3. Regression map + # ------------------------------------------------ + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + ts_mod, ts_obs, Method = TwoVarRegrid(ts_mod, ts_obs, Method, region=tsbox, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ts_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ts_obs.getAxisList()]), + 'shape1': '(mod) ' + str(ts_mod.shape), 'shape2': '(obs) ' + str(ts_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # regression + ts_mod_slope, ts_mod_stderr = LinearRegressionTsAgainstMap(ts_mod, enso_mod, return_stderr=True) + ts_obs_slope, ts_obs_stderr = LinearRegressionTsAgainstMap(ts_obs, enso_obs, return_stderr=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ts_mod_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ts_obs_slope.getAxisList()]), + 'shape1': '(mod) ' + str(ts_mod_slope.shape), + 'shape2': '(obs) ' + str(ts_obs_slope.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after LinearRegressionTsAgainstMap', 15, **dict_debug) + + # mask Pacific + ts_mod_slope, keyerror_mod = BasinMask( + ts_mod_slope, 'pacific', box=tsbox, lat1=-15, lat2=15, latkey='between', debug=debug) + ts_obs_slope, keyerror_obs = BasinMask( + ts_obs_slope, 'pacific', box=tsbox, lat1=-15, lat2=15, latkey='between', debug=debug) + if keyerror_mod is not None or keyerror_obs is not None: + tsCorr, tsCorrErr, tsRmse, tsRmseErr, tsStd, tsStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ts_mod_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ts_obs_slope.getAxisList()]), + 'shape1': '(mod) ' + str(ts_mod_slope.shape), + 'shape2': '(obs) ' + str(ts_obs_slope.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after BasinMask', 15, **dict_debug) + + # Metric 1 + tsRmse, keyerror = RmsAxis( + ts_mod_slope, ts_obs_slope, axis='xy', centered=centered_rmse, biased=biased_rmse) + tsRmseErr = None + # Metric 2 + tsCorr = float(Correlation(ts_mod_slope, ts_obs_slope, axis='xy', centered=1, biased=1)) + tsCorrErr = None + # Metric 3 + std_mod = Std(ts_mod_slope, weights=None, axis='xy', centered=1, biased=1) + std_obs = Std(ts_obs_slope, weights=None, axis='xy', centered=1, biased=1) + tsStd = float(std_mod) / float(std_obs) + tsStdErr = None + + # Dive down diagnostic + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + + if netcdf is True: + # read + ts_mod_land, ts_mod_areacell, keyerror_mod1 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, tsbox, file_area=sstareafilemod, + name_area=sstareanamemod, file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, + maskland=False, maskocean=True, time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + ts_obs_land, ts_obs_areacell, keyerror_obs1 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, tsbox, file_area=sstareafileobs, + name_area=sstareanameobs, file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, + maskland=False, maskocean=True, time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + sst_mod, ts_mod_land, keyerror_mod2 = CheckTime( + sst_mod, ts_mod_land, metric_name=metric, debug=debug, **kwargs) + sst_obs, ts_obs_land, keyerror_obs2 = CheckTime( + sst_obs, ts_obs_land, metric_name=metric, debug=debug, **kwargs) + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + # process + ts_mod_land, _, keyerror_mod = PreProcessTS( + ts_mod_land, '', areacell=ts_mod_areacell, compute_anom=False, region=tsbox, **kwargs) + ts_obs_land, _, keyerror_obs = PreProcessTS( + ts_obs_land, '', areacell=ts_obs_areacell, compute_anom=False, region=tsbox, **kwargs) + del ts_mod_areacell, ts_obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ts_mod_land.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ts_obs_land.getAxisList()]), + 'shape1': '(mod) ' + str(ts_mod_land.shape), + 'shape2': '(obs) ' + str(ts_obs_land.shape), + 'time1': '(mod) ' + str(TimeBounds(ts_mod_land)), + 'time2': '(obs) ' + str(TimeBounds(ts_obs_land))} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after PreProcessTS', 15, **dict_debug) + # anomalies + ts_mod_land = SeasonalMean(ts_mod_land, "DJF", compute_anom=True) + ts_obs_land = SeasonalMean(ts_obs_land, "DJF", compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ts_mod_land.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ts_obs_land.getAxisList()]), + 'shape1': '(mod) ' + str(ts_mod_land.shape), + 'shape2': '(obs) ' + str(ts_obs_land.shape), + 'time1': '(mod) ' + str(TimeBounds(ts_mod_land)), + 'time2': '(obs) ' + str(TimeBounds(ts_obs_land))} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after SeasonalMean', 15, **dict_debug) + # regridding + if isinstance(kwargs['regridding'], dict): + ts_mod_land, ts_obs_land, _ = TwoVarRegrid( + ts_mod_land, ts_obs_land, '', region=tsbox, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ts_mod_land.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ts_obs_land.getAxisList()]), + 'shape1': '(mod) ' + str(ts_mod_land.shape), + 'shape2': '(obs) ' + str(ts_obs_land.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after TwoVarRegrid', 15, **dict_debug) + # regression + ts_mod_land_slope = LinearRegressionTsAgainstMap(ts_mod_land, enso_mod, return_stderr=False) + ts_obs_land_slope = LinearRegressionTsAgainstMap(ts_obs_land, enso_obs, return_stderr=False) + if debug is True: + dict_debug = { + 'axes1': '(mod) ' + str([ax.id for ax in ts_mod_land_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ts_obs_land_slope.getAxisList()]), + 'shape1': '(mod) ' + str(ts_mod_land_slope.shape), + 'shape2': '(obs) ' + str(ts_obs_land_slope.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after LinearRegressionTsAgainstMap', 15, **dict_debug) + # ENSO events: SSTA > (<) 'threshold' during 'season' are considered as El Nino + # (La Nina) events + if 'smoothing' in list(kwargs.keys()): + kwargs['smoothing'] = smooth + del smooth + sst_mod, _, keyerror_mod = PreProcessTS( + sst_mod_box, '', areacell=mod_areacell, average='horizontal', compute_anom=False, + region=region_ev, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs_box, '', areacell=obs_areacell, average='horizontal', compute_anom=False, + region=region_ev, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Lists event years + nino_mod_years = DetectEvents( + sst_mod, season_ev, threshold, normalization=normalize, nino=True) + nina_mod_years = DetectEvents( + sst_mod, season_ev, -threshold, normalization=normalize, nino=False) + nino_obs_years = DetectEvents( + sst_obs, season_ev, threshold, normalization=normalize, nino=True) + nina_obs_years = DetectEvents( + sst_obs, season_ev, -threshold, normalization=normalize, nino=False) + if debug is True: + dict_debug = {'nino1': '(mod) nbr(' + str(len(nino_mod_years)) + '): ' + + str(nino_mod_years), + 'nina1': '(mod) nbr(' + str(len(nina_mod_years)) + '): ' + + str(nina_mod_years), + 'nino2': '(obs) nbr(' + str(len(nino_obs_years)) + '): ' + + str(nino_obs_years), + 'nina2': '(obs) nbr(' + str(len(nina_obs_years)) + '): ' + + str(nina_obs_years)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after DetectEvents', 15, **dict_debug) + # samples + nino_mod = Composite(ts_mod, nino_mod_years, kwargs['frequency']) + nina_mod = Composite(ts_mod, nina_mod_years, kwargs['frequency']) + nino_obs = Composite(ts_obs, nino_obs_years, kwargs['frequency']) + nina_obs = Composite(ts_obs, nina_obs_years, kwargs['frequency']) + nino_mod_land = Composite(ts_mod_land, nino_mod_years, kwargs['frequency']) + nina_mod_land = Composite(ts_mod_land, nina_mod_years, kwargs['frequency']) + nino_obs_land = Composite(ts_obs_land, nino_obs_years, kwargs['frequency']) + nina_obs_land = Composite(ts_obs_land, nina_obs_years, kwargs['frequency']) + # mask Pacific + nino_mod, keyerror_mod1 = BasinMask( + nino_mod, 'pacific', box=tsbox, lat1=-15, lat2=15, latkey='between', + debug=debug) + nina_mod, keyerror_mod2 = BasinMask( + nina_mod, 'pacific', box=tsbox, lat1=-15, lat2=15, latkey='between', + debug=debug) + nino_obs, keyerror_obs1 = BasinMask( + nino_obs, 'pacific', box=tsbox, lat1=-15, lat2=15, latkey='between', + debug=debug) + nina_obs, keyerror_obs2 = BasinMask( + nina_obs, 'pacific', box=tsbox, lat1=-15, lat2=15, latkey='between', + debug=debug) + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + keyerror = add_up_errors( + [keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + # Metrics ENSO events global + dict_metric, dict_nc = dict(), dict() + nbr = 3 + for jj, (evname, tab1, tab2, ev1, ev2) in enumerate( + zip(["nino", "nina"], [nino_mod, nina_mod], [nino_obs, nina_obs], + [nino_mod_years, nina_mod_years], + [nino_obs_years, nina_obs_years])): + dict_metric, dict_nc = fill_dict_teleconnection( + tab1, tab2, dataset1, dataset2, actualtimebounds_mod, + actualtimebounds_obs, yearN_mod, yearN_obs, nbr, + "ts_" + evname + "_djf_map__", evname, "C", + centered_rmse=centered_rmse, biased_rmse=biased_rmse, + dict_metric=dict_metric, dict_nc=dict_nc, ev_name=evname, events1=ev1, + events2=ev2) + nbr += 2 + list_region = ["africaSE", "americaN", "americaS", "asiaS", "oceania"] + for ii, reg in enumerate(list_region): + # select region + dictreg = ReferenceRegions(reg) + tmp1 = ts_mod_land_slope( + longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + tmp2 = ts_obs_land_slope( + longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + dict_metric, dict_nc = fill_dict_teleconnection( + tmp1, tmp2, dataset1, dataset2, actualtimebounds_mod, + actualtimebounds_obs, yearN_mod, yearN_obs, nbr, + "reg_ts_over_sst_djf_map_" + reg + "__", reg, Units, + centered_rmse=centered_rmse, biased_rmse=biased_rmse, + dict_metric=dict_metric, dict_nc=dict_nc) + nbr += 2 + for jj, (evname, tab1, tab2, ev1, ev2) in enumerate( + zip(["nino", "nina"], [nino_mod_land, nina_mod_land], + [nino_obs_land, nina_obs_land], + [nino_mod_years, nina_mod_years], + [nino_obs_years, nina_obs_years])): + tmp1 = tab1( + longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + tmp2 = tab2( + longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + dict_metric, dict_nc = fill_dict_teleconnection( + tmp1, tmp2, dataset1, dataset2, actualtimebounds_mod, + actualtimebounds_obs, yearN_mod, yearN_obs, nbr, + "ts_" + evname + "_djf_map_" + reg + "__", evname + "_" + reg, + "C", centered_rmse=centered_rmse, biased_rmse=biased_rmse, + dict_metric=dict_metric, dict_nc=dict_nc, ev_name=evname, + events1=ev1, events2=ev2) + nbr += 2 + del dictreg, tmp1, tmp2 + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'spatialSTD_' + dataset1: std_mod} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'spatialSTD_' + dataset2: std_obs} + dict3 = {'metric_name': Name, 'metric_valueRMSE_' + dataset2: tsRmse, + 'metric_valueRMSE_error_' + dataset2: tsRmseErr, + 'metric_valueCORR_' + dataset2: tsCorr, + 'metric_valueCORR_error_' + dataset2: tsCorrErr, + 'metric_valueSTD_' + dataset2: tsStd, + 'metric_valueSTD_error_' + dataset2: tsStdErr, + 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + dict3.update(dict_metric) + SaveNetcdf( + file_name, var1=ts_mod_slope, var1_attributes=dict1, + var1_name='reg_ts_over_sst_djf_map__' + dataset1, var2=ts_obs_slope, + var2_attributes=dict2, var2_name='reg_ts_over_sst_djf_map__' + dataset2, + global_attributes=dict3, **dict_nc) + del dict1, dict2, dict3, dict_metric, dict_nc, file_name, list_region + if tsCorr is not None: + tsCorr = 1 - tsCorr + # Create output + EnsoSstMapMetric = { + 'name': Name, 'Rmse__value': tsRmse, 'Rmse__value_error': tsRmseErr, 'Rmse__units': Units, 'method': Method, + 'Corr__value': tsCorr, 'Corr__value_error': tsCorrErr, 'Corr__units': '', 'Std__value': tsStd, + 'Std__value_error': tsStdErr, 'Std__units': Units + ' / ' + Units, 'nyears_model': yearN_mod, + 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag, 'units': ''} + return EnsoSstMapMetric + + +def EnsoSstMapJja(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, tsbox, + event_definition, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, + netcdf_name='', metname='', **kwargs): + """ + The EnsoSstMapJja() function computes surface temperature anomalies pattern associated with ENSO on the globe. + First metric: rmse(observations vs model). + Second metric: correlation(observations vs model). + Third metric: std(model)/std(observations) + These metrics can be used to compute a Taylor diagram. + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST areacell + :param sstareanamemod: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafilemod' + :param sstlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST landmask + :param sstlandmasknamemod: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST areacell + :param sstareanameobs: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafileobs' + :param sstlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST landmask + :param sstlandmasknameobs: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfileobs' + :param tsbox: string + name of box (e.g. 'global') for TS + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': -0.75} + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return EnsoSstMapMetric: dict + name, value (rms [obs;model]), value_error, units, method, value2 (corr [obs;model]), + value_error2, units2, value3 (std_model / std_obs), value_error3, units3, nyears_model, nyears_observations, + time_frequency, time_period_mod, time_period_obs, ref, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = "ENSO JJA TSA pattern" + Method = region_ev + " SSTA regressed against surface temperature anomalies in " + tsbox + " during JJA" + Units = '' if kwargs['normalization'] else 'C/C' + Ref = 'Using CDAT regridding, correlation (centered and biased), std (centered and biased) and ' + \ + 'rms (uncentered and biased) calculation' + metric = "EnsoSstMapJja" + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod_box, mod_areacell, keyerror_mod1 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs_box, obs_areacell, keyerror_obs1 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + ts_mod, ts_mod_areacell, keyerror_mod2 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, tsbox, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + ts_obs, ts_obs_areacell, keyerror_obs2 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, tsbox, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst_mod_box, ts_mod, keyerror_mod3 = CheckTime(sst_mod_box, ts_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs_box, ts_obs, keyerror_obs3 = CheckTime(sst_obs_box, ts_obs, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod_box.shape[0] / 12)) + yearN_obs = int(round(sst_obs_box.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod_box) + actualtimebounds_obs = TimeBounds(sst_obs_box) + + if (keyerror_mod1 is not None or keyerror_obs1 is not None or keyerror_mod2 is not None) or \ + (keyerror_obs2 is not None or keyerror_mod3 is not None or keyerror_obs3 is not None): + tsCorr, tsCorrErr, tsRmse, tsRmseErr, tsStd, tsStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + tmp = [keyerror_mod1, keyerror_mod2, keyerror_mod3, keyerror_obs1, keyerror_obs2, keyerror_obs3] + keyerror = add_up_errors(tmp) + else: + # smoothing is not applied + if 'smoothing' in list(kwargs.keys()): + smooth = deepcopy(kwargs['smoothing']) + kwargs['smoothing'] = False + else: + smooth = False + # ------------------------------------------------ + # 1. SSTA + # ------------------------------------------------ + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, _, keyerror_mod1 = PreProcessTS(sst_mod_box, '', areacell=mod_areacell, average='horizontal', + compute_anom=False, region=region_ev, **kwargs) + sst_obs, _, keyerror_obs1 = PreProcessTS(sst_obs_box, '', areacell=obs_areacell, average='horizontal', + compute_anom=False, region=region_ev, **kwargs) + ts_mod, Method, keyerror_mod2 = PreProcessTS(ts_mod, Method, areacell=ts_mod_areacell, compute_anom=False, + region=tsbox, **kwargs) + ts_obs, _, keyerror_obs2 = PreProcessTS(ts_obs, '', areacell=ts_obs_areacell, compute_anom=False, region=tsbox, + **kwargs) + del ts_mod_areacell, ts_obs_areacell + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + tsCorr, tsCorrErr, tsRmse, tsRmseErr, tsStd, tsStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + if debug is True: + dict_debug = { + 'axes1': '(mod ts) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs ts) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'axes3': '(mod map) ' + str([ax.id for ax in ts_mod.getAxisList()]), + 'axes4': '(obs map) ' + str([ax.id for ax in ts_obs.getAxisList()]), + 'shape1': '(mod ts) ' + str(sst_mod.shape), 'shape2': '(obs ts) ' + str(sst_obs.shape), + 'shape3': '(mod map) ' + str(ts_mod.shape), 'shape4': '(obs map) ' + str(ts_obs.shape), + 'time1': '(mod ts) ' + str(TimeBounds(sst_mod)), 'time2': '(obs ts) ' + str(TimeBounds(sst_obs)), + 'time3': '(mod map) ' + str(TimeBounds(ts_mod)), 'time4': '(obs map) ' + str(TimeBounds(ts_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 Seasonal mean and anomalies + enso_mod = SeasonalMean(sst_mod, "JJA", compute_anom=True) + enso_obs = SeasonalMean(sst_obs, "JJA", compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in enso_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in enso_obs.getAxisList()]), + 'shape1': '(mod) ' + str(enso_mod.shape), 'shape2': '(obs) ' + str(enso_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(enso_mod)), + 'time2': '(obs) ' + str(TimeBounds(enso_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # ------------------------------------------------ + # 2. TSA + # ------------------------------------------------ + # 2.2 Seasonal mean and anomalies + ts_mod = SeasonalMean(ts_mod, "JJA", compute_anom=True) + ts_obs = SeasonalMean(ts_obs, "JJA", compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ts_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ts_obs.getAxisList()]), + 'shape1': '(mod) ' + str(ts_mod.shape), 'shape2': '(obs) ' + str(ts_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(ts_mod)), 'time2': '(obs) ' + str(TimeBounds(ts_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # ------------------------------------------------ + # 3. Regression map + # ------------------------------------------------ + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + ts_mod, ts_obs, Method = TwoVarRegrid(ts_mod, ts_obs, Method, region=tsbox, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ts_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ts_obs.getAxisList()]), + 'shape1': '(mod) ' + str(ts_mod.shape), 'shape2': '(obs) ' + str(ts_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # regression + ts_mod_slope, ts_mod_stderr = LinearRegressionTsAgainstMap(ts_mod, enso_mod, return_stderr=True) + ts_obs_slope, ts_obs_stderr = LinearRegressionTsAgainstMap(ts_obs, enso_obs, return_stderr=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ts_mod_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ts_obs_slope.getAxisList()]), + 'shape1': '(mod) ' + str(ts_mod_slope.shape), + 'shape2': '(obs) ' + str(ts_obs_slope.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after LinearRegressionTsAgainstMap', 15, **dict_debug) + + # mask Pacific + ts_mod_slope, keyerror_mod = BasinMask( + ts_mod_slope, 'pacific', box=tsbox, lat1=-15, lat2=15, latkey='between', debug=debug) + ts_obs_slope, keyerror_obs = BasinMask( + ts_obs_slope, 'pacific', box=tsbox, lat1=-15, lat2=15, latkey='between', debug=debug) + if keyerror_mod is not None or keyerror_obs is not None: + tsCorr, tsCorrErr, tsRmse, tsRmseErr, tsStd, tsStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ts_mod_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ts_obs_slope.getAxisList()]), + 'shape1': '(mod) ' + str(ts_mod_slope.shape), + 'shape2': '(obs) ' + str(ts_obs_slope.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after BasinMask', 15, **dict_debug) + + # Metric 1 + tsRmse, keyerror = RmsAxis( + ts_mod_slope, ts_obs_slope, axis='xy', centered=centered_rmse, biased=biased_rmse) + tsRmseErr = None + # Metric 2 + tsCorr = float(Correlation(ts_mod_slope, ts_obs_slope, axis='xy', centered=1, biased=1)) + tsCorrErr = None + # Metric 3 + std_mod = Std(ts_mod_slope, weights=None, axis='xy', centered=1, biased=1) + std_obs = Std(ts_obs_slope, weights=None, axis='xy', centered=1, biased=1) + tsStd = float(std_mod) / float(std_obs) + tsStdErr = None + + # Dive down diagnostic + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + + if netcdf is True: + # read + ts_mod_land, ts_mod_areacell, keyerror_mod1 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, tsbox, file_area=sstareafilemod, + name_area=sstareanamemod, file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, + maskland=False, maskocean=True, time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + ts_obs_land, ts_obs_areacell, keyerror_obs1 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, tsbox, file_area=sstareafileobs, + name_area=sstareanameobs, file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, + maskland=False, maskocean=True, time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + sst_mod, ts_mod_land, keyerror_mod2 = CheckTime( + sst_mod, ts_mod_land, metric_name=metric, debug=debug, **kwargs) + sst_obs, ts_obs_land, keyerror_obs2 = CheckTime( + sst_obs, ts_obs_land, metric_name=metric, debug=debug, **kwargs) + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + # process + ts_mod_land, _, keyerror_mod = PreProcessTS( + ts_mod_land, '', areacell=ts_mod_areacell, compute_anom=False, region=tsbox, **kwargs) + ts_obs_land, _, keyerror_obs = PreProcessTS( + ts_obs_land, '', areacell=ts_obs_areacell, compute_anom=False, region=tsbox, **kwargs) + del ts_mod_areacell, ts_obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ts_mod_land.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ts_obs_land.getAxisList()]), + 'shape1': '(mod) ' + str(ts_mod_land.shape), + 'shape2': '(obs) ' + str(ts_obs_land.shape), + 'time1': '(mod) ' + str(TimeBounds(ts_mod_land)), + 'time2': '(obs) ' + str(TimeBounds(ts_obs_land))} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after PreProcessTS', 15, **dict_debug) + # anomalies + ts_mod_land = SeasonalMean(ts_mod_land, "JJA", compute_anom=True) + ts_obs_land = SeasonalMean(ts_obs_land, "JJA", compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ts_mod_land.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ts_obs_land.getAxisList()]), + 'shape1': '(mod) ' + str(ts_mod_land.shape), + 'shape2': '(obs) ' + str(ts_obs_land.shape), + 'time1': '(mod) ' + str(TimeBounds(ts_mod_land)), + 'time2': '(obs) ' + str(TimeBounds(ts_obs_land))} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after SeasonalMean', 15, **dict_debug) + # regridding + if isinstance(kwargs['regridding'], dict): + ts_mod_land, ts_obs_land, _ = TwoVarRegrid( + ts_mod_land, ts_obs_land, '', region=tsbox, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ts_mod_land.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ts_obs_land.getAxisList()]), + 'shape1': '(mod) ' + str(ts_mod_land.shape), + 'shape2': '(obs) ' + str(ts_obs_land.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after TwoVarRegrid', 15, **dict_debug) + # regression + ts_mod_land_slope = LinearRegressionTsAgainstMap(ts_mod_land, enso_mod, return_stderr=False) + ts_obs_land_slope = LinearRegressionTsAgainstMap(ts_obs_land, enso_obs, return_stderr=False) + if debug is True: + dict_debug = { + 'axes1': '(mod) ' + str([ax.id for ax in ts_mod_land_slope.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ts_obs_land_slope.getAxisList()]), + 'shape1': '(mod) ' + str(ts_mod_land_slope.shape), + 'shape2': '(obs) ' + str(ts_obs_land_slope.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'divedown after LinearRegressionTsAgainstMap', 15, **dict_debug) + # ENSO events: SSTA > (<) 'threshold' during 'season' are considered as El Nino + # (La Nina) events + if 'smoothing' in list(kwargs.keys()): + kwargs['smoothing'] = smooth + del smooth + sst_mod, _, keyerror_mod = PreProcessTS( + sst_mod_box, '', areacell=mod_areacell, average='horizontal', compute_anom=False, + region=region_ev, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs_box, '', areacell=obs_areacell, average='horizontal', compute_anom=False, + region=region_ev, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Lists event years + nino_mod_years = DetectEvents( + sst_mod, season_ev, threshold, normalization=normalize, nino=True) + nina_mod_years = DetectEvents( + sst_mod, season_ev, -threshold, normalization=normalize, nino=False) + nino_obs_years = DetectEvents( + sst_obs, season_ev, threshold, normalization=normalize, nino=True) + nina_obs_years = DetectEvents( + sst_obs, season_ev, -threshold, normalization=normalize, nino=False) + if debug is True: + dict_debug = {'nino1': '(mod) nbr(' + str(len(nino_mod_years)) + '): ' + + str(nino_mod_years), + 'nina1': '(mod) nbr(' + str(len(nina_mod_years)) + '): ' + + str(nina_mod_years), + 'nino2': '(obs) nbr(' + str(len(nino_obs_years)) + '): ' + + str(nino_obs_years), + 'nina2': '(obs) nbr(' + str(len(nina_obs_years)) + '): ' + + str(nina_obs_years)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after DetectEvents', 15, **dict_debug) + # samples + nino_mod = Composite(ts_mod, nino_mod_years, kwargs['frequency']) + nina_mod = Composite(ts_mod, nina_mod_years, kwargs['frequency']) + nino_obs = Composite(ts_obs, nino_obs_years, kwargs['frequency']) + nina_obs = Composite(ts_obs, nina_obs_years, kwargs['frequency']) + nino_mod_land = Composite(ts_mod_land, nino_mod_years, kwargs['frequency']) + nina_mod_land = Composite(ts_mod_land, nina_mod_years, kwargs['frequency']) + nino_obs_land = Composite(ts_obs_land, nino_obs_years, kwargs['frequency']) + nina_obs_land = Composite(ts_obs_land, nina_obs_years, kwargs['frequency']) + # mask Pacific + nino_mod, keyerror_mod1 = BasinMask( + nino_mod, 'pacific', box=tsbox, lat1=-15, lat2=15, latkey='between', + debug=debug) + nina_mod, keyerror_mod2 = BasinMask( + nina_mod, 'pacific', box=tsbox, lat1=-15, lat2=15, latkey='between', + debug=debug) + nino_obs, keyerror_obs1 = BasinMask( + nino_obs, 'pacific', box=tsbox, lat1=-15, lat2=15, latkey='between', + debug=debug) + nina_obs, keyerror_obs2 = BasinMask( + nina_obs, 'pacific', box=tsbox, lat1=-15, lat2=15, latkey='between', + debug=debug) + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + keyerror = add_up_errors( + [keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + # Metrics ENSO events global + dict_metric, dict_nc = dict(), dict() + nbr = 3 + for jj, (evname, tab1, tab2, ev1, ev2) in enumerate( + zip(["nino", "nina"], [nino_mod, nina_mod], [nino_obs, nina_obs], + [nino_mod_years, nina_mod_years], + [nino_obs_years, nina_obs_years])): + dict_metric, dict_nc = fill_dict_teleconnection( + tab1, tab2, dataset1, dataset2, actualtimebounds_mod, + actualtimebounds_obs, yearN_mod, yearN_obs, nbr, + "ts_" + evname + "_jja_map__", evname, "C", + centered_rmse=centered_rmse, biased_rmse=biased_rmse, + dict_metric=dict_metric, dict_nc=dict_nc, ev_name=evname, events1=ev1, + events2=ev2) + nbr += 2 + list_region = ["africaSE", "americaN", "americaS", "asiaS", "oceania"] + for ii, reg in enumerate(list_region): + # select region + dictreg = ReferenceRegions(reg) + tmp1 = ts_mod_land_slope( + longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + tmp2 = ts_obs_land_slope( + longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + dict_metric, dict_nc = fill_dict_teleconnection( + tmp1, tmp2, dataset1, dataset2, actualtimebounds_mod, + actualtimebounds_obs, yearN_mod, yearN_obs, nbr, + "reg_ts_over_sst_jja_map_" + reg + "__", reg, Units, + centered_rmse=centered_rmse, biased_rmse=biased_rmse, + dict_metric=dict_metric, dict_nc=dict_nc) + nbr += 2 + for jj, (evname, tab1, tab2, ev1, ev2) in enumerate( + zip(["nino", "nina"], [nino_mod_land, nina_mod_land], + [nino_obs_land, nina_obs_land], + [nino_mod_years, nina_mod_years], + [nino_obs_years, nina_obs_years])): + tmp1 = tab1( + longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + tmp2 = tab2( + longitude=dictreg["longitude"], latitude=dictreg["latitude"]) + dict_metric, dict_nc = fill_dict_teleconnection( + tmp1, tmp2, dataset1, dataset2, actualtimebounds_mod, + actualtimebounds_obs, yearN_mod, yearN_obs, nbr, + "ts_" + evname + "_jja_map_" + reg + "__", evname + "_" + reg, + "C", centered_rmse=centered_rmse, biased_rmse=biased_rmse, + dict_metric=dict_metric, dict_nc=dict_nc, ev_name=evname, + events1=ev1, events2=ev2) + nbr += 2 + del dictreg, tmp1, tmp2 + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'spatialSTD_' + dataset1: std_mod} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'spatialSTD_' + dataset2: std_obs} + dict3 = {'metric_name': Name, 'metric_valueRMSE_' + dataset2: tsRmse, + 'metric_valueRMSE_error_' + dataset2: tsRmseErr, + 'metric_valueCORR_' + dataset2: tsCorr, + 'metric_valueCORR_error_' + dataset2: tsCorrErr, + 'metric_valueSTD_' + dataset2: tsStd, + 'metric_valueSTD_error_' + dataset2: tsStdErr, + 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + dict3.update(dict_metric) + SaveNetcdf( + file_name, var1=ts_mod_slope, var1_attributes=dict1, + var1_name='reg_ts_over_sst_jja_map__' + dataset1, var2=ts_obs_slope, + var2_attributes=dict2, var2_name='reg_ts_over_sst_jja_map__' + dataset2, + global_attributes=dict3, **dict_nc) + del dict1, dict2, dict3, dict_metric, dict_nc, file_name, list_region + if tsCorr is not None: + tsCorr = 1 - tsCorr + # Create output + EnsoSstMapMetric = { + 'name': Name, 'Rmse__value': tsRmse, 'Rmse__value_error': tsRmseErr, 'Rmse__units': Units, 'method': Method, + 'Corr__value': tsCorr, 'Corr__value_error': tsCorrErr, 'Corr__units': '', 'Std__value': tsStd, + 'Std__value_error': tsStdErr, 'Std__units': Units + ' / ' + Units, 'nyears_model': yearN_mod, + 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag, 'units': ''} + return EnsoSstMapMetric + + +def EnsoPrTsRmse(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + prfilemod, prnamemod, prareafilemod, prareanamemod, prlandmaskfilemod, prlandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, + prfileobs, prnameobs, prareafileobs, prareanameobs, prlandmaskfileobs, prlandmasknameobs, sstbox, + prbox, event_definition, nbr_years_window, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', + debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The EnsoPrTsRmse() function computes precipitations anomalies life cycle associated with ENSO in a 'prbox' + (usually the nino3) with a window of 'nbr_years_window' centered on ENSO (nbr_years_window/2 leading and lagging + ENSO) + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST areacell + :param sstareanamemod: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafilemod' + :param sstlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST landmask + :param sstlandmasknamemod: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfilemod' + :param prfilemod: string + path_to/filename of the file (NetCDF) of the modeled PR + :param prnamemod: string + name of PR variable (pr) in 'prfilemod' + :param prareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled PR areacell + :param prareanamemod: string, optional + name of areacell for the PR variable (areacella, areacello,...) in 'prareafilemod' + :param prlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled PR landmask + :param prlandmasknamemod: string, optional + name of landmask for the PR variable (sftlf,...) in 'prlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST areacell + :param sstareanameobs: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafileobs' + :param sstlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST landmask + :param sstlandmasknameobs: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfileobs' + :param prfileobs: string + path_to/filename of the file (NetCDF) of the observed PR + :param prnameobs: string + name of PR variable (pr, precip) in 'prfileobs' + :param prareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed PR areacell + :param prareanameobs: string, optional + name of areacell for the PR variable (areacella, areacello,...) in 'prareafileobs' + :param prlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed PR landmask + :param prlandmasknameobs: string, optional + name of landmask for the PR variable (sftlf,...) in 'prlandmaskfileobs' + :param sstbox: string + name of box (e.g. 'nino3.4') for SST + :param prbox: string + name of box (e.g. 'nino3') for PR + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': -0.75} + :param nbr_years_window: integer + number of years used to compute the composite (e.g. 6) + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return EnsoPrMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_mod, + time_period_obs, ref, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = "ENSO life cyle PRA pattern" + Method = region_ev + " SSTA during " + season_ev + " regressed against " + prbox + " PRA during " + \ + str(nbr_years_window) + " years (centered on ENSO)" + Units = '' if kwargs['normalization'] else 'mm/day/C' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = 'EnsoPrTsRmse' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, sst_mod_areacell, keyerror_mod1 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, sst_obs_areacell, keyerror_obs1 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + pr_mod, pr_mod_areacell, keyerror_mod2 = Read_data_mask_area( + prfilemod, prnamemod, 'precipitations', metric, prbox, file_area=prareafilemod, name_area=prareanamemod, + file_mask=prlandmaskfilemod, name_mask=prlandmasknamemod, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + pr_obs, pr_obs_areacell, keyerror_obs2 = Read_data_mask_area( + prfileobs, prnameobs, 'precipitations', metric, prbox, file_area=prareafileobs, name_area=prareanameobs, + file_mask=prlandmaskfileobs, name_mask=prlandmasknameobs, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst_mod, pr_mod, keyerror_mod3 = CheckTime(sst_mod, pr_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs, pr_obs, keyerror_obs3 = CheckTime(sst_obs, pr_obs, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if (keyerror_mod1 is not None or keyerror_obs1 is not None or keyerror_mod2 is not None) or \ + (keyerror_obs2 is not None or keyerror_mod3 is not None or keyerror_obs3 is not None): + prRmse, prRmseErr = None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + tmp = [keyerror_mod1, keyerror_mod2, keyerror_mod3, keyerror_obs1, keyerror_obs2, keyerror_obs3] + keyerror = add_up_errors(tmp) + else: + # ------------------------------------------------ + # 1. box SSTA + # ------------------------------------------------ + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + enso_mod, _, keyerror_mod1 = PreProcessTS(sst_mod, '', areacell=sst_mod_areacell, average='horizontal', + compute_anom=False, region=region_ev, **kwargs) + enso_obs, _, keyerror_obs1 = PreProcessTS(sst_obs, '', areacell=sst_obs_areacell, average='horizontal', + compute_anom=False, region=region_ev, **kwargs) + pr_mod, Method, keyerror_mod2 = PreProcessTS(pr_mod, Method, areacell=pr_mod_areacell, average='horizontal', + compute_anom=True, region=prbox, **kwargs) + pr_obs, _, keyerror_obs2 = PreProcessTS(pr_obs, '', areacell=pr_obs_areacell, average='horizontal', + compute_anom=True, region=prbox, **kwargs) + del pr_mod_areacell, pr_obs_areacell, sst_mod_areacell, sst_obs_areacell + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + prRmse, prRmseErr = None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + if debug is True: + dict_debug = { + 'axes1': '(mod sst) ' + str([ax.id for ax in enso_mod.getAxisList()]), + 'axes2': '(obs sst) ' + str([ax.id for ax in enso_obs.getAxisList()]), + 'axes3': '(mod pr) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes4': '(obs pr) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod sst) ' + str(enso_mod.shape), 'shape2': '(obs sst) ' + str(enso_obs.shape), + 'shape3': '(mod pr) ' + str(pr_mod.shape), 'shape4': '(obs pr) ' + str(pr_obs.shape), + 'time1': '(mod sst) ' + str(TimeBounds(enso_mod)), + 'time2': '(obs sst) ' + str(TimeBounds(enso_obs)), + 'time3': '(mod pr) ' + str(TimeBounds(pr_mod)), 'time4': '(obs pr) ' + str(TimeBounds(pr_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 Seasonal mean and anomalies + enso_mod = SeasonalMean(enso_mod, season_ev, compute_anom=True) + enso_obs = SeasonalMean(enso_obs, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in enso_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in enso_obs.getAxisList()]), + 'shape1': '(mod) ' + str(enso_mod.shape), 'shape2': '(obs) ' + str(enso_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(enso_mod)), + 'time2': '(obs) ' + str(TimeBounds(enso_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # ------------------------------------------------ + # 2. Regression map + # ------------------------------------------------ + prts_mod = LinearRegressionTsAgainstTs( + pr_mod, enso_mod, nbr_years_window, return_stderr=False, frequency=kwargs['frequency'], debug=debug) + prts_obs = LinearRegressionTsAgainstTs( + pr_obs, enso_obs, nbr_years_window, return_stderr=False, frequency=kwargs['frequency'], debug=debug) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in prts_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in prts_obs.getAxisList()]), + 'shape1': '(mod) ' + str(prts_mod.shape), 'shape2': '(obs) ' + str(prts_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after LinearRegressionTsAgainstTs', 15, **dict_debug) + + # Computes the root mean square difference + prRmse, keyerror = RmsAxis(prts_mod, prts_obs, axis=0, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + prRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(prts_mod), 'observations': ArrayToList(prts_obs), + 'axis': list(prts_mod.getAxis(0)[:])} + + if netcdf is True: + # Read file and select the right region + prmap_mod, prmap_mod_areacell, keyerror_mod1 = Read_data_mask_area( + prfilemod, prnamemod, 'precipitations', metric, 'equatorial_pacific', file_area=prareafilemod, + name_area=prareanamemod, file_mask=prlandmaskfilemod, name_mask=prlandmasknamemod, maskland=True, + maskocean=False, time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + prmap_obs, prmap_obs_areacell, keyerror_obs1 = Read_data_mask_area( + prfileobs, prnameobs, 'precipitations', metric, 'equatorial_pacific', file_area=prareafileobs, + name_area=prareanameobs, file_mask=prlandmaskfileobs, name_mask=prlandmasknameobs, maskland=True, + maskocean=False, time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + # Checks if the same time period is used for both variables + sst_mod, prmap_mod, keyerror_mod2 = CheckTime( + sst_mod, prmap_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs, prmap_obs, keyerror_obs2 = CheckTime( + sst_obs, prmap_obs, metric_name=metric, debug=debug, **kwargs) + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + # Preprocess ts (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + prmap_mod, _, keyerror_mod = PreProcessTS(prmap_mod, '', areacell=prmap_mod_areacell, + compute_anom=True, region="equatorial_pacific", **kwargs) + prmap_obs, _, keyerror_obs = PreProcessTS(prmap_obs, '', areacell=prmap_obs_areacell, + compute_anom=True, region="equatorial_pacific", **kwargs) + del prmap_mod_areacell, prmap_obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in prmap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in prmap_obs.getAxisList()]), + 'shape1': '(mod) ' + str(prmap_mod.shape), + 'shape2': '(obs) ' + str(prmap_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(prmap_mod)), + 'time2': '(obs) ' + str(TimeBounds(prmap_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', + 'regridder', 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + prmap_mod, prmap_obs, _ = TwoVarRegrid( + prmap_mod, prmap_obs, '', region='equatorial_pacific', **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in prmap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in prmap_obs.getAxisList()]), + 'shape1': '(mod) ' + str(prmap_mod.shape), + 'shape2': '(obs) ' + str(prmap_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + # Meridional average + prhov_mod, keyerror_mod = AverageMeridional(prmap_mod) + prhov_obs, keyerror_obs = AverageMeridional(prmap_obs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in prhov_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in prhov_obs.getAxisList()]), + 'shape1': '(mod) ' + str(prhov_mod.shape), + 'shape2': '(obs) ' + str(prhov_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + # Linear regression + regprhov_mod = LinearRegressionTsAgainstTs( + prhov_mod, enso_mod, nbr_years_window, return_stderr=False, + frequency=kwargs['frequency'], debug=debug) + regprhov_obs = LinearRegressionTsAgainstTs( + prhov_obs, enso_obs, nbr_years_window, return_stderr=False, + frequency=kwargs['frequency'], debug=debug) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in regprhov_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in regprhov_obs.getAxisList()]), + 'shape1': '(mod) ' + str(regprhov_mod.shape), + 'shape2': '(obs) ' + str(regprhov_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after LinearRegressionTsAgainstTs', 15, **dict_debug) + # Lists event years + nina_years_mod = DetectEvents(enso_mod, season_ev, -threshold, normalization=normalize, + nino=False, compute_season=False) + nino_years_mod = DetectEvents(enso_mod, season_ev, threshold, normalization=normalize, + nino=True, compute_season=False) + nina_years_obs = DetectEvents(enso_obs, season_ev, -threshold, normalization=normalize, + nino=False, compute_season=False) + nino_years_obs = DetectEvents(enso_obs, season_ev, threshold, normalization=normalize, + nino=True, compute_season=False) + if debug is True: + dict_debug = { + 'nina1': '(mod) nbr(' + str(len(nina_years_mod)) + '): ' + str(nina_years_mod), + 'nina2': '(obs) nbr(' + str(len(nina_years_obs)) + '): ' + str(nina_years_obs), + 'nino1': '(mod) nbr(' + str(len(nino_years_mod)) + '): ' + str(nino_years_mod), + 'nino2': '(obs) nbr(' + str(len(nino_years_obs)) + '): ' + str(nino_years_obs)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + # composites + if len(nina_years_mod) > 0: + nina_pr_mod = Composite( + pr_mod, nina_years_mod, kwargs['frequency'], nbr_years_window=nbr_years_window) + nina_prhov_mod = Composite( + prhov_mod, nina_years_mod, kwargs['frequency'], nbr_years_window=nbr_years_window) + else: + nina_pr_mod = MyEmpty(pr_mod[:12 * nbr_years_window], time=True, time_id='months') + nina_prhov_mod = MyEmpty(prhov_mod[:12 * nbr_years_window], time=True, time_id='months') + if len(nino_years_mod) > 0: + nino_pr_mod = Composite( + pr_mod, nino_years_mod, kwargs['frequency'], nbr_years_window=nbr_years_window) + nino_prhov_mod = Composite( + prhov_mod, nino_years_mod, kwargs['frequency'], nbr_years_window=nbr_years_window) + else: + nino_pr_mod = MyEmpty(pr_mod[:12 * nbr_years_window], time=True, time_id='months') + nino_prhov_mod = MyEmpty(prhov_mod[:12 * nbr_years_window], time=True, time_id='months') + if len(nina_years_obs) > 0: + nina_pr_obs = Composite( + pr_obs, nina_years_obs, kwargs['frequency'], nbr_years_window=nbr_years_window) + nina_prhov_obs = Composite( + prhov_obs, nina_years_obs, kwargs['frequency'], nbr_years_window=nbr_years_window) + else: + nina_pr_obs = MyEmpty(pr_obs[:12 * nbr_years_window], time=True, time_id='months') + nina_prhov_obs = MyEmpty(prhov_obs[:12 * nbr_years_window], time=True, time_id='months') + if len(nino_years_obs) > 0: + nino_pr_obs = Composite( + pr_obs, nino_years_obs, kwargs['frequency'], nbr_years_window=nbr_years_window) + nino_prhov_obs = Composite( + prhov_obs, nino_years_obs, kwargs['frequency'], nbr_years_window=nbr_years_window) + else: + nino_pr_obs = MyEmpty(pr_obs[:12 * nbr_years_window], time=True, time_id='months') + nino_prhov_obs = MyEmpty(prhov_obs[:12 * nbr_years_window], time=True, time_id='months') + # save + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + my_thresh = 'std' if normalize is True else 'C' + my_units = '' if kwargs['normalization'] is True else 'mm/day' + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "time series of " + region_ev + " SSTA " + season_ev + + " regressed against " + prbox + " PRA during " + str(nbr_years_window) + + " years (centered on ENSO)"} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "time series of " + region_ev + " SSTA " + season_ev + + " regressed against " + prbox + " PRA during " + + str(nbr_years_window) + " years (centered on ENSO)"} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "hovmoeller (time - longitude) of " + region_ev + " SSTA " + + season_ev + " regressed against equatorial Pacific PRA during " + + str(nbr_years_window) + " years (centered on ENSO)"} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "hovmoeller (time - longitude) of " + region_ev + " SSTA " + + season_ev + " regressed against equatorial Pacific PRA during " + + str(nbr_years_window) + " years (centered on ENSO)"} + dict5 = {'units': my_units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), 'nina_years': str(nina_years_mod), + 'description': "Nina events = " + region_ev + " SSTA < -" + str(threshold) + + my_thresh + " during " + season_ev + + ", PRA composited during La Nina events"} + dict6 = {'units': my_units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), 'nino_years': str(nino_years_mod), + 'description': "Nino events = " + region_ev + " SSTA > " + str(threshold) + + my_thresh + " during " + season_ev + + ", PRA composited during El Nino events"} + dict7 = {'units': my_units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), 'nina_years': str(nina_years_obs), + 'description': "Nina events = " + region_ev + " SSTA < -" + str(threshold) + + my_thresh + " during " + season_ev + + ", PRA composited during La Nina events"} + dict8 = {'units': my_units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), 'nino_years': str(nino_years_obs), + 'description': "Nino events = " + region_ev + " SSTA > " + str(threshold) + + my_thresh + " during " + season_ev + + ", PRA composited during El Nino events"} + dict9 = {'metric_name': Name, 'metric_value_' + dataset2: prRmse, + 'metric_value_error_' + dataset2: prRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=prts_mod, var1_attributes=dict1, + var1_name='sst_against_pr_ts__' + dataset1, var2=prts_obs, var2_attributes=dict2, + var2_name='sst_against_pr_ts__' + dataset2, var3=regprhov_mod, var3_attributes=dict3, + var3_name='sst_against_pr_hov__' + dataset1, var4=regprhov_obs, var4_attributes=dict4, + var4_name='sst_against_pr_hov__' + dataset2, var5=nina_pr_mod, var5_attributes=dict5, + var5_name='Nina_pr_ts__' + dataset1, var6=nina_prhov_mod, var6_attributes=dict5, + var6_name='Nina_pr_hov__' + dataset1, var7=nino_pr_mod, var7_attributes=dict6, + var7_name='Nino_pr_ts__' + dataset1, var8=nino_prhov_mod, var8_attributes=dict6, + var8_name='Nino_pr_hov__' + dataset1, var9=nina_pr_obs, var9_attributes=dict7, + var9_name='Nina_pr_ts__' + dataset2, var10=nina_prhov_obs, var10_attributes=dict7, + var10_name='Nina_pr_hov__' + dataset2, var11=nino_pr_obs, var11_attributes=dict8, + var11_name='Nino_pr_ts__' + dataset2, var12=nino_prhov_obs, var12_attributes=dict8, + var12_name='Nino_pr_hov__' + dataset2, global_attributes=dict9) + del dict1, dict2, dict3, dict4, dict5, dict6, dict7, dict8, dict9 + # Create output + EnsoPrMetric = { + 'name': Name, 'value': prRmse, 'value_error': prRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return EnsoPrMetric + + +def EnsoSstTsRmse(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, + box, event_definition, nbr_years_window, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', + debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The EnsoSstTsRmse() function computes sea surface temperature anomalies life cycle associated with ENSO in a 'box' + (usually the nino3.4) with a window of 'nbr_years_window' centered on ENSO (nbr_years_window/2 leading and lagging + ENSO) + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST areacell + :param sstareanamemod: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafilemod' + :param sstlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST landmask + :param sstlandmasknamemod: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST areacell + :param sstareanameobs: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafileobs' + :param sstlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST landmask + :param sstlandmasknameobs: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfileobs' + :param box: string + name of box (e.g. 'nino3.4') for SST + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': -0.75} + :param nbr_years_window: integer + number of years used to compute the composite (e.g. 6) + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return EnsoSstMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_mod, + time_period_obs, ref, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = "ENSO life cyle SSTA pattern" + Method = region_ev + " SSTA during " + season_ev + " regressed against " + region_ev + " SSTA during " +\ + str(nbr_years_window) + " years (centered on ENSO)" + Units = '' if kwargs['normalization'] else 'C/C' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = 'EnsoSstTsRmse' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + sstRmse, sstRmseErr = None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # ------------------------------------------------ + # 1. box SSTA + # ------------------------------------------------ + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + enso_mod, _, keyerror_mod1 = PreProcessTS(sst_mod, '', areacell=mod_areacell, average='horizontal', + compute_anom=False, region=region_ev, **kwargs) + enso_obs, _, keyerror_obs1 = PreProcessTS(sst_obs, '', areacell=obs_areacell, average='horizontal', + compute_anom=False, region=region_ev, **kwargs) + sst_mod, Method, keyerror_mod2 = PreProcessTS(sst_mod, Method, areacell=mod_areacell, average='horizontal', + compute_anom=True, region=region_ev, **kwargs) + sst_obs, _, keyerror_obs2 = PreProcessTS(sst_obs, '', areacell=obs_areacell, average='horizontal', + compute_anom=True, region=region_ev, **kwargs) + del mod_areacell, obs_areacell + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + sstRmse, sstRmseErr = None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + if debug is True: + dict_debug = { + 'axes1': '(mod enso) ' + str([ax.id for ax in enso_mod.getAxisList()]), + 'axes2': '(obs enso) ' + str([ax.id for ax in enso_obs.getAxisList()]), + 'axes3': '(mod sst) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes4': '(obs sst) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod enso) ' + str(enso_mod.shape), 'shape2': '(obs enso) ' + str(enso_obs.shape), + 'shape3': '(mod sst) ' + str(sst_mod.shape), 'shape4': '(obs sst) ' + str(sst_obs.shape), + 'time1': '(mod enso) ' + str(TimeBounds(enso_mod)), + 'time2': '(obs enso) ' + str(TimeBounds(enso_obs)), + 'time3': '(mod sst) ' + str(TimeBounds(sst_mod)), 'time4': '(obs sst) ' + str(TimeBounds(sst_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 Seasonal mean and anomalies + enso_mod = SeasonalMean(enso_mod, season_ev, compute_anom=True) + enso_obs = SeasonalMean(enso_obs, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in enso_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in enso_obs.getAxisList()]), + 'shape1': '(mod) ' + str(enso_mod.shape), 'shape2': '(obs) ' + str(enso_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(enso_mod)), + 'time2': '(obs) ' + str(TimeBounds(enso_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # ------------------------------------------------ + # 2. Regression map + # ------------------------------------------------ + sstts_mod = LinearRegressionTsAgainstTs( + sst_mod, enso_mod, nbr_years_window, return_stderr=False, frequency=kwargs['frequency'], debug=debug) + sstts_obs = LinearRegressionTsAgainstTs( + sst_obs, enso_obs, nbr_years_window, return_stderr=False, frequency=kwargs['frequency'], debug=debug) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sstts_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sstts_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sstts_mod.shape), 'shape2': '(obs) ' + str(sstts_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after LinearRegressionTsAgainstTs', 15, **dict_debug) + + # Computes the root mean square difference + sstRmse, keyerror = RmsAxis(sstts_mod, sstts_obs, axis=0, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + sstRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(sstts_mod), 'observations': ArrayToList(sstts_obs), + 'axis': list(sstts_mod.getAxis(0)[:])} + + if netcdf is True: + # Read file and select the right region + sstmap_mod, sstmap_mod_areacell, keyerror_mod1 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, 'equatorial_pacific', file_area=sstareafilemod, + name_area=sstareanamemod, file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, + maskocean=False, time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sstmap_obs, sstmap_obs_areacell, keyerror_obs1 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, 'equatorial_pacific', file_area=sstareafileobs, + name_area=sstareanameobs, file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, + maskocean=False, time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + # Checks if the same time period is used for both variables + sst_mod, sstmap_mod, keyerror_mod2 = CheckTime( + sst_mod, sstmap_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs, sstmap_obs, keyerror_obs2 = CheckTime( + sst_obs, sstmap_obs, metric_name=metric, debug=debug, **kwargs) + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + # Preprocess ts (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sstmap_mod, _, keyerror_mod = PreProcessTS(sstmap_mod, '', areacell=sstmap_mod_areacell, + compute_anom=True, region="equatorial_pacific", **kwargs) + sstmap_obs, _, keyerror_obs = PreProcessTS(sstmap_obs, '', areacell=sstmap_obs_areacell, + compute_anom=True, region="equatorial_pacific", **kwargs) + del sstmap_mod_areacell, sstmap_obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sstmap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sstmap_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sstmap_mod.shape), + 'shape2': '(obs) ' + str(sstmap_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sstmap_mod)), + 'time2': '(obs) ' + str(TimeBounds(sstmap_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', + 'regridder', 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + sstmap_mod, sstmap_obs, _ = TwoVarRegrid( + sstmap_mod, sstmap_obs, '', region='equatorial_pacific', **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sstmap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sstmap_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sstmap_mod.shape), + 'shape2': '(obs) ' + str(sstmap_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + # Meridional average + ssthov_mod, keyerror_mod = AverageMeridional(sstmap_mod) + ssthov_obs, keyerror_obs = AverageMeridional(sstmap_obs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ssthov_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ssthov_obs.getAxisList()]), + 'shape1': '(mod) ' + str(ssthov_mod.shape), + 'shape2': '(obs) ' + str(ssthov_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + # Linear regression + regssthov_mod = LinearRegressionTsAgainstTs( + ssthov_mod, enso_mod, nbr_years_window, return_stderr=False, + frequency=kwargs['frequency'], debug=debug) + regssthov_obs = LinearRegressionTsAgainstTs( + ssthov_obs, enso_obs, nbr_years_window, return_stderr=False, + frequency=kwargs['frequency'], debug=debug) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ssthov_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ssthov_obs.getAxisList()]), + 'shape1': '(mod) ' + str(ssthov_mod.shape), + 'shape2': '(obs) ' + str(ssthov_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after LinearRegressionTsAgainstTs', 15, **dict_debug) + # Lists event years + nina_years_mod = DetectEvents(enso_mod, season_ev, -threshold, normalization=normalize, + nino=False, compute_season=False) + nino_years_mod = DetectEvents(enso_mod, season_ev, threshold, normalization=normalize, + nino=True, compute_season=False) + nina_years_obs = DetectEvents(enso_obs, season_ev, -threshold, normalization=normalize, + nino=False, compute_season=False) + nino_years_obs = DetectEvents(enso_obs, season_ev, threshold, normalization=normalize, + nino=True, compute_season=False) + if debug is True: + dict_debug = {'nina1': '(mod) nbr(' + str(len(nina_years_mod)) + '): ' + + str(nina_years_mod), + 'nina2': '(obs) nbr(' + str(len(nina_years_obs)) + '): ' + + str(nina_years_obs), + 'nino1': '(mod) nbr(' + str(len(nino_years_mod)) + '): ' + + str(nino_years_mod), + 'nino2': '(obs) nbr(' + str(len(nino_years_obs)) + '): ' + + str(nino_years_obs)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + # composites + if len(nina_years_mod) > 0: + nina_sst_mod = Composite( + sst_mod, nina_years_mod, kwargs['frequency'], nbr_years_window=nbr_years_window) + nina_ssthov_mod = Composite( + ssthov_mod, nina_years_mod, kwargs['frequency'], nbr_years_window=nbr_years_window) + else: + nina_sst_mod = MyEmpty(sst_mod[:12 * nbr_years_window], time=True, time_id='months') + nina_ssthov_mod = MyEmpty( + ssthov_mod[:12 * nbr_years_window], time=True, time_id='months') + if len(nino_years_mod) > 0: + nino_sst_mod = Composite( + sst_mod, nino_years_mod, kwargs['frequency'], nbr_years_window=nbr_years_window) + nino_ssthov_mod = Composite( + ssthov_mod, nino_years_mod, kwargs['frequency'], nbr_years_window=nbr_years_window) + else: + nino_sst_mod = MyEmpty(sst_mod[:12 * nbr_years_window], time=True, time_id='months') + nino_ssthov_mod = MyEmpty( + ssthov_mod[:12 * nbr_years_window], time=True, time_id='months') + if len(nina_years_obs) > 0: + nina_sst_obs = Composite( + sst_obs, nina_years_obs, kwargs['frequency'], nbr_years_window=nbr_years_window) + nina_ssthov_obs = Composite( + ssthov_obs, nina_years_obs, kwargs['frequency'], nbr_years_window=nbr_years_window) + else: + nina_sst_obs = MyEmpty(sst_obs[:12 * nbr_years_window], time=True, time_id='months') + nina_ssthov_obs = MyEmpty( + ssthov_obs[:12 * nbr_years_window], time=True, time_id='months') + if len(nino_years_obs) > 0: + nino_sst_obs = Composite( + sst_obs, nino_years_obs, kwargs['frequency'], nbr_years_window=nbr_years_window) + nino_ssthov_obs = Composite( + ssthov_obs, nino_years_obs, kwargs['frequency'], nbr_years_window=nbr_years_window) + else: + nino_sst_obs = MyEmpty(sst_obs[:12 * nbr_years_window], time=True, time_id='months') + nino_ssthov_obs = MyEmpty( + ssthov_obs[:12 * nbr_years_window], time=True, time_id='months') + # save + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + my_thresh = 'std' if normalize is True else 'C' + my_units = '' if kwargs['normalization'] is True else 'C' + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "time series of " + region_ev + " SSTA " + season_ev + + " regressed against " + region_ev + " SSTA during " + + str(nbr_years_window) + " years (centered on ENSO)"} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "time series of " + region_ev + " SSTA " + season_ev + + " regressed against " + region_ev + " SSTA during " + + str(nbr_years_window) + " years (centered on ENSO)"} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "hovmoeller (time - longitude) of " + region_ev + " SSTA " + + season_ev + " regressed against equatorial Pacific SSTA during " + + str(nbr_years_window) + " years (centered on ENSO)"} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "hovmoeller (time - longitude) of " + region_ev + " SSTA " + + season_ev + " regressed against equatorial Pacific SSTA during " + + str(nbr_years_window) + " years (centered on ENSO)"} + dict5 = {'units': my_units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), 'nina_years': str(nina_years_mod), + 'description': "Nina events = " + region_ev + " SSTA < -" + str(threshold) + + my_thresh + " during " + season_ev + + ", this is the composite of La Nina events"} + dict6 = {'units': my_units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), 'nino_years': str(nino_years_mod), + 'description': "Nino events = " + region_ev + " SSTA > " + str(threshold) + + my_thresh + " during " + season_ev + + ", this is the composite of El Nino events"} + dict7 = {'units': my_units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), 'nina_years': str(nina_years_obs), + 'description': "Nina events = " + region_ev + " SSTA < -" + str(threshold) + + my_thresh + " during " + season_ev + + ", this is the composite of La Nina events"} + dict8 = {'units': my_units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), 'nino_years': str(nino_years_obs), + 'description': "Nino events = " + region_ev + " SSTA > " + str(threshold) + + my_thresh + " during " + season_ev + + ", this is the composite of El Nino events"} + dict9 = {'metric_name': Name, 'metric_value_' + dataset2: sstRmse, + 'metric_value_error_' + dataset2: sstRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=sstts_mod, var1_attributes=dict1, + var1_name='sst_against_sst_ts__' + dataset1, var2=sstts_obs, var2_attributes=dict2, + var2_name='sst_against_sst_ts__' + dataset2, var3=regssthov_mod, var3_attributes=dict3, + var3_name='sst_against_sst_hov__' + dataset1, var4=regssthov_obs, var4_attributes=dict4, + var4_name='sst_against_sst_hov__' + dataset2, var5=nina_sst_mod, var5_attributes=dict5, + var5_name='Nina_sst_ts__' + dataset1, var6=nina_ssthov_mod, var6_attributes=dict5, + var6_name='Nina_sst_hov__' + dataset1, var7=nino_sst_mod, var7_attributes=dict6, + var7_name='Nino_sst_ts__' + dataset1, var8=nino_ssthov_mod, var8_attributes=dict6, + var8_name='Nino_sst_hov__' + dataset1, var9=nina_sst_obs, var9_attributes=dict7, + var9_name='Nina_sst_ts__' + dataset2, var10=nina_ssthov_obs, var10_attributes=dict7, + var10_name='Nina_sst_hov__' + dataset2, var11=nino_sst_obs, var11_attributes=dict8, + var11_name='Nino_sst_ts__' + dataset2, var12=nino_ssthov_obs, var12_attributes=dict8, + var12_name='Nino_sst_hov__' + dataset2, global_attributes=dict9) + del dict1, dict2, dict3, dict4, dict5, dict6, dict7, dict8, dict9 + # Create output + EnsoSstMetric = { + 'name': Name, 'value': sstRmse, 'value_error': sstRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return EnsoSstMetric + + +def EnsoTauxTsRmse(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + tauxfilemod, tauxnamemod, tauxareafilemod, tauxareanamemod, tauxlandmaskfilemod, tauxlandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, + tauxfileobs, tauxnameobs, tauxareafileobs, tauxareanameobs, tauxlandmaskfileobs, tauxlandmasknameobs, + sstbox, tauxbox, event_definition, nbr_years_window, centered_rmse=0, biased_rmse=1, dataset1='', + dataset2='', debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The EnsoTauxTsRmse() function computes zonal wind stress anomalies life cycle associated with ENSO in a 'tauxbox' + (usually the nino3) with a window of 'nbr_years_window' centered on ENSO (nbr_years_window/2 leading and lagging + ENSO) + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST areacell + :param sstareanamemod: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafilemod' + :param sstlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST landmask + :param sstlandmasknamemod: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfilemod' + :param tauxfilemod: string + path_to/filename of the file (NetCDF) of the modeled TAUX + :param tauxnamemod: string + name of TAUX variable (tauu, tauuo) in 'tauxfilemod' + :param tauxareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled TAUX areacell + :param tauxareanamemod: string, optional + name of areacell for the TAUX variable (areacella, areacello,...) in 'tauxareafilemod' + :param tauxlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled TAUX landmask + :param tauxlandmasknamemod: string, optional + name of landmask for the TAUX variable (sftlf,...) in 'tauxlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST areacell + :param sstareanameobs: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafileobs' + :param sstlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST landmask + :param sstlandmasknameobs: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfileobs' + :param tauxfileobs: string + path_to/filename of the file (NetCDF) of the observed TAUX + :param tauxnameobs: string + name of TAUX variable (taux) in 'tauxfileobs' + :param tauxareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed TAUX areacell + :param tauxareanameobs: string, optional + name of areacell for the TAUX variable (areacella, areacello,...) in 'tauxareafileobs' + :param tauxlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed TAUX landmask + :param tauxlandmasknameobs: string, optional + name of landmask for the TAUX variable (sftlf,...) in 'tauxlandmaskfileobs' + :param sstbox: string + name of box (e.g. 'nino3.4') for SST + :param tauxbox: string + name of box (e.g. 'nino3') for TAUX + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': -0.75} + :param nbr_years_window: integer + number of years used to compute the composite (e.g. 6) + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return EnsoTauxMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_mod, + time_period_obs, ref, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = "ENSO life cyle TAUXA pattern" + Method = region_ev + " SSTA during " + season_ev + " regressed against " + tauxbox + " TAUXA during " + \ + str(nbr_years_window) + " years (centered on ENSO)" + Units = '' if kwargs['normalization'] else '1e-3 N/m2/C' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = 'EnsoTauxTsRmse' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, sst_mod_areacell, keyerror_mod1 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, sst_obs_areacell, keyerror_obs1 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + taux_mod, taux_mod_areacell, keyerror_mod2 = Read_data_mask_area( + tauxfilemod, tauxnamemod, 'wind stress', metric, tauxbox, file_area=tauxareafilemod, name_area=tauxareanamemod, + file_mask=tauxlandmaskfilemod, name_mask=tauxlandmasknamemod, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + taux_obs, taux_obs_areacell, keyerror_obs2 = Read_data_mask_area( + tauxfileobs, tauxnameobs, 'wind stress', metric, tauxbox, file_area=tauxareafileobs, name_area=tauxareanameobs, + file_mask=tauxlandmaskfileobs, name_mask=tauxlandmasknameobs, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst_mod, taux_mod, keyerror_mod3 = CheckTime(sst_mod, taux_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs, taux_obs, keyerror_obs3 = CheckTime(sst_obs, taux_obs, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if (keyerror_mod1 is not None or keyerror_obs1 is not None or keyerror_mod2 is not None) or \ + (keyerror_obs2 is not None or keyerror_mod3 is not None or keyerror_obs3 is not None): + tauxRmse, tauxRmseErr = None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + tmp = [keyerror_mod1, keyerror_mod2, keyerror_mod3, keyerror_obs1, keyerror_obs2, keyerror_obs3] + keyerror = add_up_errors(tmp) + else: + # ------------------------------------------------ + # 1. box SSTA + # ------------------------------------------------ + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + enso_mod, _, keyerror_mod1 = PreProcessTS(sst_mod, '', areacell=sst_mod_areacell, average='horizontal', + compute_anom=False, region=region_ev, **kwargs) + enso_obs, _, keyerror_obs1 = PreProcessTS(sst_obs, '', areacell=sst_obs_areacell, average='horizontal', + compute_anom=False, region=region_ev, **kwargs) + taux_mod, Method, keyerror_mod2 = PreProcessTS( + taux_mod, Method, areacell=taux_mod_areacell, average='horizontal', compute_anom=True, region=tauxbox, + **kwargs) + taux_obs, _, keyerror_obs2 = PreProcessTS( + taux_obs, '', areacell=taux_obs_areacell, average='horizontal', compute_anom=True, region=tauxbox, **kwargs) + del taux_mod_areacell, taux_obs_areacell, sst_mod_areacell, sst_obs_areacell + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + tauxRmse, tauxRmseErr = None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + if debug is True: + dict_debug = { + 'axes1': '(mod sst) ' + str([ax.id for ax in enso_mod.getAxisList()]), + 'axes2': '(obs sst) ' + str([ax.id for ax in enso_obs.getAxisList()]), + 'axes3': '(mod taux) ' + str([ax.id for ax in taux_mod.getAxisList()]), + 'axes4': '(obs taux) ' + str([ax.id for ax in taux_obs.getAxisList()]), + 'shape1': '(mod sst) ' + str(enso_mod.shape), 'shape2': '(obs sst) ' + str(enso_obs.shape), + 'shape3': '(mod taux) ' + str(taux_mod.shape), 'shape4': '(obs taux) ' + str(taux_obs.shape), + 'time1': '(mod sst) ' + str(TimeBounds(enso_mod)), + 'time2': '(obs sst) ' + str(TimeBounds(enso_obs)), + 'time3': '(mod taux) ' + str(TimeBounds(taux_mod)), + 'time4': '(obs taux) ' + str(TimeBounds(taux_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 Seasonal mean and anomalies + enso_mod = SeasonalMean(enso_mod, season_ev, compute_anom=True) + enso_obs = SeasonalMean(enso_obs, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in enso_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in enso_obs.getAxisList()]), + 'shape1': '(mod) ' + str(enso_mod.shape), 'shape2': '(obs) ' + str(enso_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(enso_mod)), + 'time2': '(obs) ' + str(TimeBounds(enso_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # ------------------------------------------------ + # 2. Regression map + # ------------------------------------------------ + tauxts_mod = LinearRegressionTsAgainstTs( + taux_mod, enso_mod, nbr_years_window, return_stderr=False, frequency=kwargs['frequency'], debug=debug) + tauxts_obs = LinearRegressionTsAgainstTs( + taux_obs, enso_obs, nbr_years_window, return_stderr=False, frequency=kwargs['frequency'], debug=debug) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in tauxts_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in tauxts_obs.getAxisList()]), + 'shape1': '(mod) ' + str(tauxts_mod.shape), 'shape2': '(obs) ' + str(tauxts_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after LinearRegressionTsAgainstTs', 15, **dict_debug) + + # Computes the root mean square difference + tauxRmse, keyerror = RmsAxis(tauxts_mod, tauxts_obs, axis=0, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + tauxRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(tauxts_mod), 'observations': ArrayToList(tauxts_obs), + 'axis': list(tauxts_mod.getAxis(0)[:])} + + if netcdf is True: + # Read file and select the right region + tauxmap_mod, tauxmap_mod_areacell, keyerror_mod1 = Read_data_mask_area( + tauxfilemod, tauxnamemod, 'wind stress', metric, 'equatorial_pacific', file_area=tauxareafilemod, + name_area=tauxareanamemod, file_mask=tauxlandmaskfilemod, name_mask=tauxlandmasknamemod, + maskland=True, maskocean=False, time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + tauxmap_obs, tauxmap_obs_areacell, keyerror_obs1 = Read_data_mask_area( + tauxfileobs, tauxnameobs, 'wind stress', metric, 'equatorial_pacific', file_area=tauxareafileobs, + name_area=tauxareanameobs, file_mask=tauxlandmaskfileobs, name_mask=tauxlandmasknameobs, + maskland=True, maskocean=False, time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + # Checks if the same time period is used for both variables + sst_mod, tauxmap_mod, keyerror_mod2 = CheckTime( + sst_mod, tauxmap_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs, tauxmap_obs, keyerror_obs2 = CheckTime( + sst_obs, tauxmap_obs, metric_name=metric, debug=debug, **kwargs) + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + # Preprocess ts (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + tauxmap_mod, _, keyerror_mod = PreProcessTS( + tauxmap_mod, '', areacell=tauxmap_mod_areacell, compute_anom=True, region="equatorial_pacific", + **kwargs) + tauxmap_obs, _, keyerror_obs = PreProcessTS( + tauxmap_obs, '', areacell=tauxmap_obs_areacell, compute_anom=True, region="equatorial_pacific", + **kwargs) + del tauxmap_mod_areacell, tauxmap_obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in tauxmap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in tauxmap_obs.getAxisList()]), + 'shape1': '(mod) ' + str(tauxmap_mod.shape), + 'shape2': '(obs) ' + str(tauxmap_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(tauxmap_mod)), + 'time2': '(obs) ' + str(TimeBounds(tauxmap_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', + 'regridder', 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + tauxmap_mod, tauxmap_obs, _ = TwoVarRegrid( + tauxmap_mod, tauxmap_obs, '', region='equatorial_pacific', **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in tauxmap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in tauxmap_obs.getAxisList()]), + 'shape1': '(mod) ' + str(tauxmap_mod.shape), + 'shape2': '(obs) ' + str(tauxmap_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + # Meridional average + tauxhov_mod, keyerror_mod = AverageMeridional(tauxmap_mod) + tauxhov_obs, keyerror_obs = AverageMeridional(tauxmap_obs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in tauxhov_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in tauxhov_obs.getAxisList()]), + 'shape1': '(mod) ' + str(tauxhov_mod.shape), + 'shape2': '(obs) ' + str(tauxhov_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + # Linear regression + regtauxhov_mod = LinearRegressionTsAgainstTs( + tauxhov_mod, enso_mod, nbr_years_window, return_stderr=False, + frequency=kwargs['frequency'], debug=debug) + regtauxhov_obs = LinearRegressionTsAgainstTs( + tauxhov_obs, enso_obs, nbr_years_window, return_stderr=False, + frequency=kwargs['frequency'], debug=debug) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in regtauxhov_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in regtauxhov_obs.getAxisList()]), + 'shape1': '(mod) ' + str(regtauxhov_mod.shape), + 'shape2': '(obs) ' + str(regtauxhov_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after LinearRegressionTsAgainstTs', 15, **dict_debug) + # Lists event years + nina_years_mod = DetectEvents(enso_mod, season_ev, -threshold, normalization=normalize, + nino=False, compute_season=False) + nino_years_mod = DetectEvents(enso_mod, season_ev, threshold, normalization=normalize, + nino=True, compute_season=False) + nina_years_obs = DetectEvents(enso_obs, season_ev, -threshold, normalization=normalize, + nino=False, compute_season=False) + nino_years_obs = DetectEvents(enso_obs, season_ev, threshold, normalization=normalize, + nino=True, compute_season=False) + if debug is True: + dict_debug = { + 'nina1': '(mod) nbr(' + str(len(nina_years_mod)) + '): ' + str(nina_years_mod), + 'nina2': '(obs) nbr(' + str(len(nina_years_obs)) + '): ' + str(nina_years_obs), + 'nino1': '(mod) nbr(' + str(len(nino_years_mod)) + '): ' + str(nino_years_mod), + 'nino2': '(obs) nbr(' + str(len(nino_years_obs)) + '): ' + str(nino_years_obs)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + # composites + if len(nina_years_mod) > 0: + nina_taux_mod = Composite( + taux_mod, nina_years_mod, kwargs['frequency'], nbr_years_window=nbr_years_window) + nina_tauxhov_mod = Composite( + tauxhov_mod, nina_years_mod, kwargs['frequency'], nbr_years_window=nbr_years_window) + else: + nina_taux_mod = MyEmpty(taux_mod[:12 * nbr_years_window], time=True, time_id='months') + nina_tauxhov_mod = MyEmpty( + tauxhov_mod[:12 * nbr_years_window], time=True, time_id='months') + if len(nino_years_mod) > 0: + nino_taux_mod = Composite( + taux_mod, nino_years_mod, kwargs['frequency'], nbr_years_window=nbr_years_window) + nino_tauxhov_mod = Composite( + tauxhov_mod, nino_years_mod, kwargs['frequency'], nbr_years_window=nbr_years_window) + else: + nino_taux_mod = MyEmpty(taux_mod[:12 * nbr_years_window], time=True, time_id='months') + nino_tauxhov_mod = MyEmpty( + tauxhov_mod[:12 * nbr_years_window], time=True, time_id='months') + if len(nina_years_obs) > 0: + nina_taux_obs = Composite( + taux_obs, nina_years_obs, kwargs['frequency'], nbr_years_window=nbr_years_window) + nina_tauxhov_obs = Composite( + tauxhov_obs, nina_years_obs, kwargs['frequency'], nbr_years_window=nbr_years_window) + else: + nina_taux_obs = MyEmpty(taux_obs[:12 * nbr_years_window], time=True, time_id='months') + nina_tauxhov_obs = MyEmpty( + tauxhov_obs[:12 * nbr_years_window], time=True, time_id='months') + if len(nino_years_obs) > 0: + nino_taux_obs = Composite( + taux_obs, nino_years_obs, kwargs['frequency'], nbr_years_window=nbr_years_window) + nino_tauxhov_obs = Composite( + tauxhov_obs, nino_years_obs, kwargs['frequency'], nbr_years_window=nbr_years_window) + else: + nino_taux_obs = MyEmpty(taux_obs[:12 * nbr_years_window], time=True, time_id='months') + nino_tauxhov_obs = MyEmpty( + tauxhov_obs[:12 * nbr_years_window], time=True, time_id='months') + # save + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + my_thresh = 'std' if normalize is True else 'C' + my_units = '' if kwargs['normalization'] is True else '1e-3 N/m2' + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "time series of " + region_ev + " SSTA " + season_ev + + " regressed against " + tauxbox + " TAUXA during " + str(nbr_years_window) + + " years (centered on ENSO)"} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "time series of " + region_ev + " SSTA " + season_ev + + " regressed against " + tauxbox + " TAUXA during " + + str(nbr_years_window) + " years (centered on ENSO)"} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "hovmoeller (time - longitude) of " + region_ev + " SSTA " + + season_ev + " regressed against equatorial Pacific TAUXA during " + + str(nbr_years_window) + " years (centered on ENSO)"} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "hovmoeller (time - longitude) of " + region_ev + " SSTA " + + season_ev + " regressed against equatorial Pacific TAUXA during " + + str(nbr_years_window) + " years (centered on ENSO)"} + dict5 = {'units': my_units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), 'nina_years': str(nina_years_mod), + 'description': "Nina events = " + region_ev + " SSTA < -" + str(threshold) + + my_thresh + " during " + season_ev + + ", TAUXA composited during La Nina events"} + dict6 = {'units': my_units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), 'nino_years': str(nino_years_mod), + 'description': "Nino events = " + region_ev + " SSTA > " + str(threshold) + + my_thresh + " during " + season_ev + + ", TAUXA composited during El Nino events"} + dict7 = {'units': my_units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), 'nina_years': str(nina_years_obs), + 'description': "Nina events = " + region_ev + " SSTA < -" + str(threshold) + + my_thresh + " during " + season_ev + + ", TAUXA composited during La Nina events"} + dict8 = {'units': my_units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), 'nino_years': str(nino_years_obs), + 'description': "Nino events = " + region_ev + " SSTA > " + str(threshold) + + my_thresh + " during " + season_ev + + ", TAUXA composited during El Nino events"} + dict9 = {'metric_name': Name, 'metric_value_' + dataset2: tauxRmse, + 'metric_value_error_' + dataset2: tauxRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=tauxts_mod, var1_attributes=dict1, + var1_name='sst_against_taux_ts__' + dataset1, var2=tauxts_obs, var2_attributes=dict2, + var2_name='sst_against_taux_ts__' + dataset2, var3=regtauxhov_mod, + var3_attributes=dict3, var3_name='sst_against_taux_hov__' + dataset1, + var4=regtauxhov_obs, var4_attributes=dict4, + var4_name='sst_against_taux_hov__' + dataset2, var5=nina_taux_mod, + var5_attributes=dict5, var5_name='Nina_taux_ts__' + dataset1, var6=nina_tauxhov_mod, + var6_attributes=dict5, var6_name='Nina_taux_hov__' + dataset1, var7=nino_taux_mod, + var7_attributes=dict6, var7_name='Nino_taux_ts__' + dataset1, var8=nino_tauxhov_mod, + var8_attributes=dict6, var8_name='Nino_taux_hov__' + dataset1, var9=nina_taux_obs, + var9_attributes=dict7, var9_name='Nina_taux_ts__' + dataset2, var10=nina_tauxhov_obs, + var10_attributes=dict7, var10_name='Nina_taux_hov__' + dataset2, var11=nino_taux_obs, + var11_attributes=dict8, var11_name='Nino_taux_ts__' + dataset2, var12=nino_tauxhov_obs, + var12_attributes=dict8, var12_name='Nino_taux_hov__' + dataset2, + global_attributes=dict9) + del dict1, dict2, dict3, dict4, dict5, dict6, dict7, dict8, dict9 + # Create output + EnsoTauxMetric = { + 'name': Name, 'value': tauxRmse, 'value_error': tauxRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return EnsoTauxMetric + + +def NinaPrMap(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, prfilemod, + prnamemod, prareafilemod, prareanamemod, prlandmaskfilemod, prlandmasknamemod, sstfileobs, sstnameobs, + sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, prfileobs, prnameobs, + prareafileobs, prareanameobs, prlandmaskfileobs, prlandmasknameobs, sstbox, prbox, event_definition, + centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, netcdf_name='', + metname='', **kwargs): + """ + The NinaPrMap() function computes a precipitation anomalies composite of during the peak of La Nina events + SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + Then SSTA < 'threshold' during 'season' are considered as La Nina events + Then the PRA at the peak of the event is composited for each selected event + First metric: rmse(observations vs model). + Second metric: correlation(observations vs model). + Third metric: std(model)/std(observations) + These metrics can be used to compute a Taylor diagram. + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST areacell + :param sstareanamemod: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafilemod' + :param sstlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST landmask + :param sstlandmasknamemod: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfilemod' + :param prfilemod: string + path_to/filename of the file (NetCDF) of the modeled PR + :param prnamemod: string + name of PR variable (pr) in 'prfilemod' + :param prareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled PR areacell + :param prareanamemod: string, optional + name of areacell for the PR variable (areacella, areacello,...) in 'prareafilemod' + :param prlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled PR landmask + :param prlandmasknamemod: string, optional + name of landmask for the PR variable (sftlf,...) in 'prlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST areacell + :param sstareanameobs: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafileobs' + :param sstlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST landmask + :param sstlandmasknameobs: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfileobs' + :param prfileobs: string + path_to/filename of the file (NetCDF) of the observed PR + :param prnameobs: string + name of PR variable (pr, precip) in 'prfileobs' + :param prareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed PR areacell + :param prareanameobs: string, optional + name of areacell for the PR variable (areacella, areacello,...) in 'prareafileobs' + :param prlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed PR landmask + :param prlandmasknameobs: string, optional + name of landmask for the PR variable (sftlf,...) in 'prlandmaskfileobs' + :param sstbox: string + name of box (e.g. 'global') for SST + :param prbox: string + name of box (e.g. 'global') for PR + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': -0.75} + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return NinaPrMapMetric: dict + name, Rmse__value (rms [obs;model]), Rmse__value_error, Rmse__units, method, Corr__value (corr [obs;model]), + Corr__value_error, Corr__units, Std__value (std_model / std_obs), Std__value_error, Std__units, nyears_model, + nyears_observations, time_frequency, time_period_mod, time_period_obs, ref, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'La Nina PRA Composite' + Method = 'Nina events = ' + region_ev + ' sstA < ' + str(threshold) + ' during ' + season_ev +\ + ', Nina PRA Composited' + Units = '' if kwargs['normalization'] else 'mm/day' + Ref = 'Using CDAT regridding, correlation (centered and biased), std (centered and biased) and ' + \ + 'rms (uncentered and biased) calculation' + metric = 'NinaPrMap' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod1 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs1 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + pr_mod, pr_mod_areacell, keyerror_mod2 = Read_data_mask_area( + prfilemod, prnamemod, 'precipitations', metric, prbox, file_area=prareafilemod, name_area=prareanamemod, + file_mask=prlandmaskfilemod, name_mask=prlandmasknamemod, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + pr_obs, pr_obs_areacell, keyerror_obs2 = Read_data_mask_area( + prfileobs, prnameobs, 'precipitations', metric, prbox, file_area=prareafileobs, name_area=prareanameobs, + file_mask=prlandmaskfileobs, name_mask=prlandmasknameobs, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst_mod, pr_mod, keyerror_mod3 = CheckTime(sst_mod, pr_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs, pr_obs, keyerror_obs3 = CheckTime(sst_obs, pr_obs, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if (keyerror_mod1 is not None or keyerror_obs1 is not None or keyerror_mod2 is not None) or \ + (keyerror_obs2 is not None or keyerror_mod3 is not None or keyerror_obs3 is not None): + prCorr, prCorrErr, prRmse, prRmseErr, prStd, prStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + tmp = [keyerror_mod1, keyerror_mod2, keyerror_mod3, keyerror_obs1, keyerror_obs2, keyerror_obs3] + keyerror = add_up_errors(tmp) + else: + # ------------------------------------------------ + # 1. detect events + # ------------------------------------------------ + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, _, keyerror_mod1 = PreProcessTS( + sst_mod, '', areacell=mod_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + sst_obs, _, keyerror_obs1 = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + pr_mod, Method, keyerror_mod2 = PreProcessTS( + pr_mod, Method, areacell=pr_mod_areacell, compute_anom=False, region=prbox, **kwargs) + pr_obs, _, keyerror_obs2 = PreProcessTS( + pr_obs, '', areacell=pr_obs_areacell, compute_anom=False, region=prbox, **kwargs) + del mod_areacell, obs_areacell, pr_mod_areacell, pr_obs_areacell + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + prCorr, prCorrErr, prRmse, prRmseErr, prStd, prStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + if debug is True: + dict_debug = { + 'axes1': '(mod sst) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs sst) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'axes3': '(mod pr) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes4': '(obs pr) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'shape1': '(mod sst) ' + str(sst_mod.shape), 'shape2': '(obs sst) ' + str(sst_obs.shape), + 'shape3': '(mod pr) ' + str(pr_mod.shape), 'shape4': '(obs pr) ' + str(pr_obs.shape), + 'time1': '(mod sst) ' + str(TimeBounds(sst_mod)), 'time2': '(obs sst) ' + str(TimeBounds(sst_obs)), + 'time3': '(mod pr) ' + str(TimeBounds(pr_mod)), 'time4': '(obs pr) ' + str(TimeBounds(pr_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 SSTA < 'threshold' during 'season' are considered as La Nina events + # Lists event years + event_years_mod = DetectEvents(sst_mod, season_ev, threshold, normalization=normalize, nino=False) + event_years_obs = DetectEvents(sst_obs, season_ev, threshold, normalization=normalize, nino=False) + if debug is True: + dict_debug = {'nina1': '(mod) ' + str(event_years_mod), 'nina2': '(obs) ' + str(event_years_obs)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + + # ------------------------------------------------ + # 2. composite PRA + # ------------------------------------------------ + # 2.2 Seasonal mean and anomalies + pr_mod = SeasonalMean(pr_mod, season_ev, compute_anom=True) + pr_obs = SeasonalMean(pr_obs, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(pr_mod)), 'time2': '(obs) ' + str(TimeBounds(pr_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + pr_mod, pr_obs, Method = TwoVarRegrid(pr_mod, pr_obs, Method, region=prbox, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # 2.3 Composites + pr_mod = Composite(pr_mod, event_years_mod, kwargs['frequency']) + pr_obs = Composite(pr_obs, event_years_obs, kwargs['frequency']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Composite', 15, **dict_debug) + + # mask Pacific + pr_mod, keyerror_mod = BasinMask( + pr_mod, 'pacific', box=prbox, lat1=-15, lat2=15, latkey='between', debug=debug) + pr_obs, keyerror_obs = BasinMask( + pr_obs, 'pacific', box=prbox, lat1=-15, lat2=15, latkey='between', debug=debug) + if keyerror_mod is not None or keyerror_obs is not None: + prCorr, prCorrErr, prRmse, prRmseErr, prStd, prStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after BasinMask', 15, **dict_debug) + + # Metric 1 + prRmse, keyerror = RmsAxis(pr_mod, pr_obs, axis='xy', centered=centered_rmse, biased=biased_rmse) + prRmseErr = None + # Metric 2 + prCorr = float(Correlation(pr_mod, pr_obs, axis='xy', centered=1, biased=1)) + prCorrErr = None + # Metric 3 + std_mod = Std(pr_mod, weights=None, axis='xy', centered=1, biased=1) + std_obs = Std(pr_obs, weights=None, axis='xy', centered=1, biased=1) + prStd = float(std_mod) / float(std_obs) + prStdErr = None + + # Dive down diagnostic + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + + if netcdf is True: + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = { + 'units': Units, 'number_of_years_used': yearN_mod, 'time_period': str(actualtimebounds_mod), + 'nina_years': str(event_years_mod)} + dict2 = { + 'units': Units, 'number_of_years_used': yearN_obs, 'time_period': str(actualtimebounds_obs), + 'nina_years': str(event_years_obs)} + dict3 = { + 'metric_name': Name, 'metric_valueRMSE_' + dataset2: prRmse, + 'metric_valueRMSE_error_' + dataset2: prRmseErr, 'metric_valueCORR_' + dataset2: prCorr, + 'metric_valueCORR_error_' + dataset2: prCorrErr, 'metric_valueSTD_' + dataset2: prStd, + 'metric_valueCORR_error_' + dataset2: prStdErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=pr_mod, var1_attributes=dict1, var1_name='prComp_map__' + dataset1, var2=pr_obs, + var2_attributes=dict2, var2_name='prComp_map__' + dataset2, global_attributes=dict3) + del dict1, dict2, dict3, file_name + if prCorr is not None: + prCorr = 1 - prCorr + # Create output + NinaPrMapMetric = { + 'name': Name, 'Rmse__value': prRmse, 'Rmse__value_error': prRmseErr, 'Rmse__units': Units, 'method': Method, + 'Corr__value': prCorr, 'Corr__value_error': prCorrErr, 'Corr__units': '', 'Std__value': prStd, + 'Std__value_error': prStdErr, 'Std__units': Units + ' / ' + Units, 'nyears_model': yearN_mod, + 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag, 'units': ''} + return NinaPrMapMetric + + +def NinaSstDiv(sstfile, sstname, sstareafile, sstareaname, sstlandmaskfile, sstlandmaskname, box, event_definition, + dataset='', debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The NinaSstDiv() function computes a zonal composite of La Nina events during the peak of the event. + 1.) detect events + 1.1) SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + 1.2) SSTA < 'threshold' during 'season' are considered as La Nina events + 2.) diversity of the zonal location of the minimum SSTA + 2.1) zonal SSTA at the peak of the event is computed for each selected event + 2.2) find the zonal position of the minimum SSTA for each selected event + 2.3) compute the percentage of EP events (minimum SSTA eastward of the given threshold) + + Inputs: + ------ + :param sstfile: string + path_to/filename of the file (NetCDF) of the SST + :param sstname: string + name of SST variable (tos, ts) in 'sstfile' + :param sstareafile: string + path_to/filename of the file (NetCDF) of the areacell for SST + :param sstareaname: string + name of areacell variable (areacella, areacello) in 'sstareafile' + :param sstlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for SST + :param sstlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfile' + :param box: string + name of box ('nino3') for SST + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': 0.75} + :param nbr_years_window: integer + number of years used to compute the composite (e.g. 6) + :param dataset: string, optional + name of current dataset (e.g., 'model', 'obs', ...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param treshold_ep_ev: float, optional + see EnsoToolsLib.percentage_val_eastward + longitude, in degree east, of the westward boundary of eastern Pacific event + default value is -140°E (i.e., 140°W) + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return NinaDivMetric: dict + name, value, value_error, units, method, nyears, events, time_frequency, time_period, ref, keyerror, + dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'treshold_ep_ev', + 'time_bounds'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'La Nina Diversity (percentage of eastern Pacific La Nina)' + lat = ReferenceRegions(box)['latitude'] + lon = ReferenceRegions(box)['longitude'] + Method = 'Nina events = ' + region_ev + ' sstA < ' + str(threshold) + ' during ' + season_ev + ', zonal SSTA ' +\ + '(meridional averaged [' + str(lat[0]) + ' ; ' + str(lat[1]) + ']), westward boundary of EP events' +\ + str(kwargs['treshold_ep_ev']) + 'E' + Units = '%' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = 'NinaSstDiv' + if metname == '': + metname = deepcopy(metric) + + # ------------------------------------------------ + # 1. detect events + # ------------------------------------------------ + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst, areacell, keyerror = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, region_ev, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + + # Number of years + yearN = int(round(sst.shape[0] / 12)) + + # Time period + actualtimebounds = TimeBounds(sst) + + if keyerror is not None: + ep_event, StdErr, dive_down_diag, event_years = None, None, {'value': None, 'axis': None}, None + else: + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst, _, keyerror = PreProcessTS( + sst, '', areacell=areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + del areacell + if keyerror is not None: + ep_event, StdErr, dive_down_diag, event_years = None, None, {'value': None, 'axis': None}, None + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape), 'time1': '(sst) ' + str(TimeBounds(sst))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 SSTA < 'threshold' during 'season' are considered as La Nina events + # Lists event years + event_years = DetectEvents(sst, season_ev, threshold, normalization=normalize, nino=False) + if debug is True: + dict_debug = {'nina1': 'nbr(' + str(len(event_years)) + '): ' + str(event_years)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + + # ------------------------------------------------ + # 2. diversity of the zonal location of the minimum SSTA + # ------------------------------------------------ + # Read file and select the right region + sst, areacell, keyerror = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, box, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskfile, maskland=True, maskocean=False, debug=debug, + **kwargs) + if keyerror is not None: + ep_event, StdErr, dive_down_diag, event_years = None, None, {'value': None, 'axis': None}, None + else: + # 2.1 zonal SSTA at the peak of the event is computed for each selected event + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst, Method, keyerror = PreProcessTS( + sst, Method, areacell=areacell, average=False, compute_anom=False, region=box, **kwargs) + del areacell + if keyerror is not None: + ep_event, StdErr, dive_down_diag, event_years = None, None, {'value': None, 'axis': None}, None + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape), 'time1': '(sst) ' + str(TimeBounds(sst))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Seasonal mean + sst = SeasonalMean(sst, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', 'regridTool', + 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + sst = Regrid(sst, None, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Meridional average + sst, keyerror = AverageMeridional(sst) + if keyerror is not None: + ep_event, StdErr, dive_down_diag, event_years = None, None, {'value': None, 'axis': None}, None + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + + # samples + sample = Event_selection(sst, kwargs['frequency'], list_event_years=event_years) + + # 2.2 find the zonal position of the minimum SSTA for each selected event + lon_sstmax = FindXYMinMaxInTs( + sample, return_val='mini', smooth=True, axis=0, window=5, method='triangle') + if debug is True: + dict_debug = {'line1': 'longitude of the minimum SSTA: ' + str(lon_sstmax)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after FindXYMinMaxInTs', 15, **dict_debug) + + # 2.3 compute the percentage of EP events (minimum SSTA eastward of the given threshold) + ep_event, keyerror = percentage_val_eastward( + lon_sstmax, metric, box, threshold=kwargs['treshold_ep_ev']) + ep_event = float(ep_event) + + if keyerror is not None: + StdErr, dive_down_diag = None, {'value': None, 'axis': None} + else: + # Standard Error of the Standard Deviation (function of nyears) + StdErr = None + + # Dive down diagnostic + dive_down_diag = {'value': ArrayToList(lon_sstmax), 'axis': list(lon_sstmax.getAxis(0)[:])} + + if netcdf is True: + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': 'longitude (E)', 'number_of_years_used': yearN, + 'time_period': str(actualtimebounds), 'nina_years': str(event_years), + 'diagnostic_value_' + dataset: ep_event, + 'diagnostic_value_error_' + dataset: StdErr} + dict2 = {'metric_name': Name, 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + SaveNetcdf(file_name, var1=lon_sstmax, var1_attributes=dict1, + var1_name='Nina_lon_pos_minSSTA__' + dataset, global_attributes=dict2) + del dict1, dict2 + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(ep_event), 'line2': 'metric value_error: ' + str(StdErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + NinaDivMetric = { + 'name': Name, 'value': ep_event, 'value_error': StdErr, 'units': Units, 'method': Method, 'nyears': yearN, + 'events': event_years, 'time_frequency': kwargs['frequency'], 'time_period': actualtimebounds, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return NinaDivMetric + + +def NinaSstDivRmse(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, box, + event_definition, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, + netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The NinaSstDivRmse() function computes a zonal minimum of La Nina events during the peak of the event. + 1.) detect events + 1.1) SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + 1.2) SSTA < 'threshold' during 'season' are considered as La Nina events + 2.) diversity of the zonal location of the minimum SSTA + 2.1) zonal SSTA at the peak of the event is computed for each selected event + 2.2) find the zonal position of the minimum SSTA for each selected event and compute a pdf + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for SST + :param sstareanamemod: string + name of areacell variable (areacella, areacello) in 'sstareafilemod' + :param sstlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for SST + :param sstlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for SST + :param sstareanameobs: string + name of areacell variable (areacella, areacello) in 'sstareafileobs' + :param sstlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for SST + :param sstlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfileobs' + :param box: string + name of box ('nino3') for SST + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': -0.75} + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return NinaDivMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, events_model, events_observations, + time_frequency, time_period_model, time_period_observations, ref, keyword, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'PDF of zonal min(SSTA) during La Nina' + lat = ReferenceRegions(box)['latitude'] + lon = ReferenceRegions(box)['longitude'] + Method = 'Nina events = ' + region_ev + ' sstA < ' + str(threshold) + ' during ' + season_ev + ', zonal SSTA '\ + + '(meridional averaged [' + str(lat[0]) + ' ; ' + str(lat[1]) + ']' + Units = 'density' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = 'NinaSstDivRmse' + if metname == '': + metname = deepcopy(metric) + + # ------------------------------------------------ + # 1. detect events + # ------------------------------------------------ + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + pdfRmse, pdfRmseErr, event_years_mod, event_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, _, keyerror_mod = PreProcessTS( + sst_mod, '', areacell=mod_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + pdfRmse, pdfRmseErr, event_years_mod, event_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sst_mod)), + 'time2': '(obs) ' + str(TimeBounds(sst_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 SSTA < 'threshold' during 'season' are considered as La Nina events + # Lists event years + event_years_mod = DetectEvents(sst_mod, season_ev, threshold, normalization=normalize, nino=False) + event_years_obs = DetectEvents(sst_obs, season_ev, threshold, normalization=normalize, nino=False) + if debug is True: + dict_debug = {'nina1': '(mod) ' + str(event_years_mod), 'nina2': '(obs) ' + str(event_years_obs)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + + # ------------------------------------------------ + # 2. diversity of the zonal location of the minimum SSTA + # ------------------------------------------------ + # Read file and select the right region + sst_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, box, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmaskfilemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, box, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmaskfileobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + pdfRmse, pdfRmseErr, event_years_mod, event_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # 2.1 zonal SSTA at the peak of the event is computed for each selected event + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, Method, keyerror_mod = PreProcessTS( + sst_mod, Method, areacell=mod_areacell, average=False, compute_anom=False, region=box, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average=False, compute_anom=False, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + pdfRmse, pdfRmseErr, event_years_mod, event_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sst_mod)), + 'time2': '(obs) ' + str(TimeBounds(sst_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Seasonal mean + sst_mod = SeasonalMean(sst_mod, season_ev, compute_anom=True) + sst_obs = SeasonalMean(sst_obs, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', + 'regridder', 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + sst_mod, sst_obs, Method = TwoVarRegrid( + sst_mod, sst_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), + 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Meridional average + sst_mod, keyerror_mod = AverageMeridional(sst_mod) + sst_obs, keyerror_obs = AverageMeridional(sst_obs) + if keyerror_mod is not None or keyerror_obs is not None: + pdfRmse, pdfRmseErr, event_years_mod, event_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), + 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + + # samples + sample_mod = Event_selection(sst_mod, kwargs['frequency'], list_event_years=event_years_mod) + sample_obs = Event_selection(sst_obs, kwargs['frequency'], list_event_years=event_years_obs) + + # 2.2 find the zonal position of the minimum SSTA for each selected event and compute a pdf + # longitude of the minimum SSTA for each selected event + lon_min_mod = FindXYMinMaxInTs( + sample_mod, return_val='mini', smooth=True, axis=0, window=5, method='triangle') + lon_min_obs = FindXYMinMaxInTs( + sample_obs, return_val='mini', smooth=True, axis=0, window=5, method='triangle') + if debug is True: + dict_debug = {'line1': '(mod) longitude of the minimum SSTA: ' + str(lon_min_mod), + 'line2': '(obs) longitude of the minimum SSTA: ' + str(lon_min_obs)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after FindXYMinMaxInTs', 15, **dict_debug) + + # compute PDFs + if debug is True: + dict_debug = {'line1': 'lon ' + str(lon) + ' ; nbr_bins old = ' + + str((lon[1] - lon[0]) / 10) + ' ; nbr_bins new = ' + + str(int(round((lon[1] - lon[0]) / 10)))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'before ComputePDF', 15, **dict_debug) + pdf_mod = ComputePDF(lon_min_mod, nbr_bins=int(round((lon[1] - lon[0]) / 10)), interval=lon, + axis_name='longitude') + pdf_obs = ComputePDF(lon_min_obs, nbr_bins=int(round((lon[1] - lon[0]) / 10)), interval=lon, + axis_name='longitude') + + # Computes the root mean square difference + pdfRmse, keyerror = RmsZonal(pdf_mod, pdf_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + pdfRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(pdf_mod), 'observations': ArrayToList(pdf_obs), + 'axis': list(pdf_mod.getAxis(0)[:])} + if netcdf is True: + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), 'nina_years': str(event_years_mod)} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), 'nina_years': str(event_years_obs)} + dict3 = {'metric_name': Name, 'metric_value_' + dataset2: pdfRmse, + 'metric_value_error_' + dataset2: pdfRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf(file_name, var1=pdf_mod, var1_attributes=dict1, var1_name='pdf__' + dataset1, + var2=pdf_obs, var2_attributes=dict2, var2_name='pdf__' + dataset2, + global_attributes=dict3) + del dict1, dict2, dict3, file_name + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(pdfRmse), 'line2': 'metric value_error: ' + str(pdfRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + NinaDivMetric = { + 'name': Name, 'value': pdfRmse, 'value_error': pdfRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'events_model': event_years_mod, + 'events_observations': event_years_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return NinaDivMetric + + +def NinaSstDur(sstfile, sstname, sstareafile, sstareaname, sstlandmaskfile, sstlandmaskname, box, event_definition, + nbr_years_window, dataset='', debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The NinaSstDurRmse() function computes a duration of La Nina events. + 1.) detect events + 1.1) SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + 1.2) SSTA < 'threshold' during 'season' are considered as La Nina events + 2.) La Nina duration + 2.1) get a time series of 2 years before and 2 years after the La Nina peak (4 years time series) + 2.2) count the number of consecutive month below a threshold + + Inputs: + ------ + :param sstfile: string + path_to/filename of the file (NetCDF) of the SST + :param sstname: string + name of SST variable (tos, ts) in 'sstfile' + :param sstareafile: string + path_to/filename of the file (NetCDF) of the areacell for SST + :param sstareaname: string + name of areacell variable (areacella, areacello) in 'sstareafile' + :param sstlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for SST + :param sstlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfile' + :param box: string + name of box ('nino3') for SST + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': -0.75} + :param nbr_years_window: integer + number of years used to compute the composite (e.g. 6) + :param dataset: string, optional + name of current dataset (e.g., 'model', 'obs', ...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return NinaDurMetric: dict + name, value, value_error, units, method, nyears, events, time_frequency, time_period, time_period, ref, + keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'La Nina Duration' + Units = 'months' + Method = 'Nina events = ' + region_ev + ' sstA < ' + str(threshold) + ' during ' + season_ev + \ + ', number of consecutive months when sstA < -0.5' + Units + Ref = 'Using CDAT' + metric = 'NinaSstDur' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst, sst_areacell, keyerror = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, region_ev, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + + # Number of years + yearN = int(round(sst.shape[0] / 12)) + + # Time period + actualtimebounds = TimeBounds(sst) + + if keyerror is not None: + duration_mean, duration_err, dive_down_diag, event_years = None, None, {'value': None, 'axis': None}, None + else: + # ------------------------------------------------ + # 1. detect events + # ------------------------------------------------ + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst, Method, keyerror = PreProcessTS( + sst, Method, areacell=sst_areacell, average='horizontal', compute_anom=True, region=region_ev, **kwargs) + del sst_areacell + if keyerror is not None: + duration_mean, duration_err, dive_down_diag, event_years = None, None, {'value': None, 'axis': None}, None + else: + if debug is True: + dict_debug = {'axes1': str([ax.id for ax in sst.getAxisList()]), 'shape1': str(sst.shape), + 'time1': str(TimeBounds(sst))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 SSTA < 'threshold' during 'season' are considered as La Nina events + # Lists event years + event_years = DetectEvents(sst, season_ev, threshold, normalization=normalize, nino=False) + if debug is True: + dict_debug = {'nina1': str(event_years)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + + # ------------------------------------------------ + # 2. La Nina duration + # ------------------------------------------------ + # 2.1 get a time series of 2 years before and 2 years after the La Nina peak (4 years time series) + # composites + sample = Event_selection( + sst, kwargs['frequency'], nbr_years_window=nbr_years_window, list_event_years=event_years) + if debug is True: + dict_debug = {'axes1': str([ax.id for ax in sample.getAxisList()]), 'shape1': str(sample.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Event_selection', 15, **dict_debug) + + # 2.2 count the number of consecutive month below a threshold + if normalize is True: + duration = DurationAllEvent(sample, -0.5 * float(Std(sst)), nino=False, debug=debug) + else: + duration = DurationAllEvent(sample, -0.5, nino=False, debug=debug) + + duration_err = float(Std(duration) / NUMPYsqrt(len(duration))) + duration_mean = float(duration.mean()) + + # Dive down diagnostic + dive_down_diag = {'value': ArrayToList(duration), 'axis': list(duration.getAxis(0)[:])} + + if netcdf is True: + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'nina_years': str(event_years), 'description': "La duration of Nina events", + 'diagnostic_value': duration_mean, 'diagnostic_value_error': duration_err} + dict2 = {'metric_name': Name, 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + SaveNetcdf(file_name, var1=duration, var1_attributes=dict1, var1_name='Nina_duration__' + dataset, + global_attributes=dict2) + del dict1, dict2, file_name + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(duration_mean), + 'line2': 'metric value_error: ' + str(duration_err)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + NinaDurMetric = { + 'name': Name, 'value': duration_mean, 'value_error': duration_err, 'units': Units, 'method': Method, + 'nyears': yearN, 'events': event_years, 'time_frequency': kwargs['frequency'], 'time_period': actualtimebounds, + 'ref': Ref, 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return NinaDurMetric + + +def NinaSstLonRmse(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, box, + event_definition, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, + netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The NinaSstLonRmse() function computes a zonal composite of La Nina events during the peak of the event + SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + Then SSTA < 'threshold' during 'season' are considered as La Nina events + Then the zonal SSTA at the peak of the event is composited for each selected event + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for SST + :param sstareanamemod: string + name of areacell variable (areacella, areacello) in 'sstareafilemod' + :param sstlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for SST + :param sstlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for SST + :param sstareanameobs: string + name of areacell variable (areacella, areacello) in 'sstareafileobs' + :param sstlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for SST + :param sstlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfileobs' + :param box: string + name of box ('equatorial_pacific') for SST + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': -0.75} + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return NinaLonMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, events_model, events_observations, + time_frequency, time_period_model, time_period_observations, ref, keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'La Nina Zonal Composite' + lat = ReferenceRegions(box)['latitude'] + Method = 'Nina events = ' + region_ev + ' sstA < ' + str(threshold) + ' during ' + season_ev + ', zonal SSTA '\ + + '(meridional averaged [' + str(lat[0]) + ' ; ' + str(lat[1]) + ']' + Units = '' if kwargs['normalization'] else 'C' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = 'NinaSstLonRmse' + if metname == '': + metname = deepcopy(metric) + + # ------------------------------------------------ + # detect events + # ------------------------------------------------ + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + compRmse, compRmseErr, event_years_mod, event_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, _, keyerror_mod = PreProcessTS( + sst_mod, '', areacell=mod_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + compRmse, compRmseErr, event_years_mod, event_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sst_mod)), + 'time2': '(obs) ' + str(TimeBounds(sst_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 SSTA < 'threshold' during 'season' are considered as La Nina events + # Lists event years + event_years_mod = DetectEvents(sst_mod, season_ev, threshold, normalization=normalize, nino=False) + event_years_obs = DetectEvents(sst_obs, season_ev, threshold, normalization=normalize, nino=False) + if debug is True: + dict_debug = {'nina1': '(mod) ' + str(event_years_mod), 'nina2': '(obs) ' + str(event_years_obs)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + + # ------------------------------------------------ + # 2. zonal composite of SSTA + # ------------------------------------------------ + # Read file and select the right region + sst_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, box, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmaskfilemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, box, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmaskfileobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + compRmse, compRmseErr, event_years_mod, event_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # 2.1 zonal SSTA at the peak of the event is computed for each selected event + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, Method, keyerror_mod = PreProcessTS( + sst_mod, Method, areacell=mod_areacell, average=False, compute_anom=False, region=box, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average=False, compute_anom=False, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + compRmse, compRmseErr, event_years_mod, event_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sst_mod)), + 'time2': '(obs) ' + str(TimeBounds(sst_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Seasonal mean + sst_mod = SeasonalMean(sst_mod, season_ev, compute_anom=True) + sst_obs = SeasonalMean(sst_obs, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', + 'regridder', 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + sst_mod, sst_obs, Method = TwoVarRegrid( + sst_mod, sst_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), + 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Meridional average + sst_mod, keyerror_mod = AverageMeridional(sst_mod) + sst_obs, keyerror_obs = AverageMeridional(sst_obs) + if keyerror_mod is not None or keyerror_obs is not None: + compRmse, compRmseErr, event_years_mod, event_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), + 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + + # samples + sst_mod = Composite(sst_mod, event_years_mod, kwargs['frequency']) + sst_obs = Composite(sst_obs, event_years_obs, kwargs['frequency']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), + 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Composite', 15, **dict_debug) + + # Computes the root mean square difference + compRmse, keyerror = RmsZonal(sst_mod, sst_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + compRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(sst_mod), 'observations': ArrayToList(sst_obs), + 'axis': list(sst_mod.getAxis(0)[:])} + if netcdf is True: + map_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, 'equatorial_pacific_LatExt2', + file_area=sstareafilemod, name_area=sstareanamemod, file_mask=sstlandmaskfilemod, + name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + map_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, 'equatorial_pacific_LatExt2', + file_area=sstareafileobs, name_area=sstareanameobs, file_mask=sstlandmaskfileobs, + name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + map_mod, _, keyerror_mod = PreProcessTS( + map_mod, '', areacell=mod_areacell, average=False, compute_anom=False, + region="equatorial_pacific_LatExt2", **kwargs) + map_obs, _, keyerror_obs = PreProcessTS( + map_obs, '', areacell=obs_areacell, average=False, compute_anom=False, + region="equatorial_pacific_LatExt2", **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in map_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in map_obs.getAxisList()]), + 'shape1': '(mod) ' + str(map_mod.shape), + 'shape2': '(obs) ' + str(map_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(map_mod)), + 'time2': '(obs) ' + str(TimeBounds(map_obs))} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after PreProcessTS: netcdf', 15, **dict_debug) + # Seasonal mean + map_mod = SeasonalMean(map_mod, season_ev, compute_anom=True) + map_obs = SeasonalMean(map_obs, season_ev, compute_anom=True) + # Regridding + if isinstance(kwargs['regridding'], dict): + map_mod, map_obs, _ = TwoVarRegrid( + map_mod, map_obs, '', region='equatorial_pacific_LatExt2', + **kwargs['regridding']) + if debug is True: + dict_debug = { + 'axes1': '(mod) ' + str([ax.id for ax in map_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in map_obs.getAxisList()]), + 'shape1': '(mod) ' + str(map_mod.shape), + 'shape2': '(obs) ' + str(map_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after TwoVarRegrid: netcdf', 15, **dict_debug) + # samples + map_mod = Composite(map_mod, event_years_mod, kwargs['frequency']) + map_obs = Composite(map_obs, event_years_obs, kwargs['frequency']) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'nina_years': str(event_years_mod)} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'nina_years': str(event_years_obs)} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'nina_years': str(event_years_mod)} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'nina_years': str(event_years_obs)} + dict5 = {'metric_name': Name, 'metric_value_' + dataset2: compRmse, + 'metric_value_error_' + dataset2: compRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=sst_mod, var1_attributes=dict1, + var1_name='sst_lon__' + dataset1, var2=sst_obs, var2_attributes=dict2, + var2_name='sst_lon__' + dataset2, var3=map_mod, var3_attributes=dict3, + var3_name='sst_map__' + dataset1, var4=map_obs, var4_attributes=dict4, + var4_name='sst_map__' + dataset2, global_attributes=dict5) + del dict1, dict2, dict3, dict4, dict5, file_name + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(compRmse), 'line2': 'metric value_error: ' + str(compRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + NinaLonMetric = { + 'name': Name, 'value': compRmse, 'value_error': compRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'events_model': event_years_mod, + 'events_observations': event_years_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return NinaLonMetric + + +def NinaSlpMap(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + slpfilemod, slpnamemod, slpareafilemod, slpareanamemod, slplandmaskfilemod, slplandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, + slpfileobs, slpnameobs, slpareafileobs, slpareanameobs, slplandmaskfileobs, slplandmasknameobs, sstbox, + slpbox, event_definition, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, + netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The NinaSlpMap() function computes a sea level pressure anomalies composite of during the peak of La Nina events + SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + Then SSTA < 'threshold' during 'season' are considered as La Nina events + Then the SLPA at the peak of the event is composited for each selected event + First metric: rmse(observations vs model). + Second metric: correlation(observations vs model). + Third metric: std(model)/std(observations) + These metrics can be used to compute a Taylor diagram. + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST areacell + :param sstareanamemod: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafilemod' + :param sstlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST landmask + :param sstlandmasknamemod: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfilemod' + :param slpfilemod: string + path_to/filename of the file (NetCDF) of the modeled SLP + :param slpnamemod: string + name of SLP variable (slp) in 'slpfilemod' + :param slpareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SLP areacell + :param slpareanamemod: string, optional + name of areacell for the SLP variable (areacella, areacello,...) in 'slpareafilemod' + :param slplandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SLP landmask + :param slplandmasknamemod: string, optional + name of landmask for the SLP variable (sftlf,...) in 'slplandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST areacell + :param sstareanameobs: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafileobs' + :param sstlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST landmask + :param sstlandmasknameobs: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfileobs' + :param slpfileobs: string + path_to/filename of the file (NetCDF) of the observed SLP + :param slpnameobs: string + name of SLP variable (slp) in 'slpfileobs' + :param slpareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SLP areacell + :param slpareanameobs: string, optional + name of areacell for the SLP variable (areacella, areacello,...) in 'slpareafileobs' + :param slplandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SLP landmask + :param slplandmasknameobs: string, optional + name of landmask for the SLP variable (sftlf,...) in 'slplandmaskfileobs' + :param sstbox: string + name of box (e.g. 'global') for SST + :param slpbox: string + name of box (e.g. 'global') for SLP + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': -0.75} + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return NinaSlpMapMetric: dict + name, Rmse__value (rms [obs;model]), Rmse__value_error, Rmse__units, method, Corr__value (corr [obs;model]), + Corr__value_error, Corr__units, Std__value (std_model / std_obs), Std__value_error, Std__units, nyears_model, + nyears_observations, time_frequency, time_period_mod, time_period_obs, ref, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'La Nina SLPA Composite' + Method = 'Nina events = ' + region_ev + ' sstA < ' + str(threshold) + ' during ' + season_ev +\ + ', Nina SLPA Composited' + Units = '' if kwargs['normalization'] else 'hPa' + Ref = 'Using CDAT regridding, correlation (centered and biased), std (centered and biased) and ' + \ + 'rms (uncentered and biased) calculation' + metric = 'NinaSlpMap' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod1 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs1 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + slp_mod, slp_mod_areacell, keyerror_mod2 = Read_data_mask_area( + slpfilemod, slpnamemod, 'pressure', metric, slpbox, file_area=slpareafilemod, name_area=slpareanamemod, + file_mask=slplandmaskfilemod, name_mask=slplandmasknamemod, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + slp_obs, slp_obs_areacell, keyerror_obs2 = Read_data_mask_area( + slpfileobs, slpnameobs, 'pressure', metric, slpbox, file_area=slpareafileobs, name_area=slpareanameobs, + file_mask=slplandmaskfileobs, name_mask=slplandmasknameobs, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst_mod, slp_mod, keyerror_mod3 = CheckTime(sst_mod, slp_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs, slp_obs, keyerror_obs3 = CheckTime(sst_obs, slp_obs, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if (keyerror_mod1 is not None or keyerror_obs1 is not None or keyerror_mod2 is not None) or \ + (keyerror_obs2 is not None or keyerror_mod3 is not None or keyerror_obs3 is not None): + slpCorr, slpCorrErr, slpRmse, slpRmseErr, slpStd, slpStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + tmp = [keyerror_mod1, keyerror_mod2, keyerror_mod3, keyerror_obs1, keyerror_obs2, keyerror_obs3] + keyerror = add_up_errors(tmp) + else: + # ------------------------------------------------ + # 1. detect events + # ------------------------------------------------ + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, _, keyerror_mod1 = PreProcessTS( + sst_mod, '', areacell=mod_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + sst_obs, _, keyerror_obs1 = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + slp_mod, Method, keyerror_mod2 = PreProcessTS( + slp_mod, Method, areacell=slp_mod_areacell, compute_anom=False, region=slpbox, **kwargs) + slp_obs, _, keyerror_obs2 = PreProcessTS( + slp_obs, '', areacell=slp_obs_areacell, compute_anom=False, region=slpbox, **kwargs) + del mod_areacell, obs_areacell, slp_mod_areacell, slp_obs_areacell + if (keyerror_mod1 is not None or keyerror_obs1) or \ + (keyerror_mod2 is not None or keyerror_obs2): + slpCorr, slpCorrErr, slpRmse, slpRmseErr, slpStd, slpStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + if debug is True: + dict_debug = { + 'axes1': '(mod sst) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs sst) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'axes3': '(mod slp) ' + str([ax.id for ax in slp_mod.getAxisList()]), + 'axes4': '(obs slp) ' + str([ax.id for ax in slp_obs.getAxisList()]), + 'shape1': '(mod sst) ' + str(sst_mod.shape), 'shape2': '(obs sst) ' + str(sst_obs.shape), + 'shape3': '(mod slp) ' + str(slp_mod.shape), 'shape4': '(obs slp) ' + str(slp_obs.shape), + 'time1': '(mod sst) ' + str(TimeBounds(sst_mod)), 'time2': '(obs sst) ' + str(TimeBounds(sst_obs)), + 'time3': '(mod slp) ' + str(TimeBounds(slp_mod)), 'time4': '(obs slp) ' + str(TimeBounds(slp_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 SSTA < 'threshold' during 'season' are considered as La Nina events + # Lists event years + event_years_mod = DetectEvents(sst_mod, season_ev, threshold, normalization=normalize, nino=False) + event_years_obs = DetectEvents(sst_obs, season_ev, threshold, normalization=normalize, nino=False) + if debug is True: + dict_debug = {'nina1': '(mod) ' + str(event_years_mod), 'nina2': '(obs) ' + str(event_years_obs)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + + # ------------------------------------------------ + # 2. composite SLPA + # ------------------------------------------------ + # 2.2 Seasonal mean and anomalies + slp_mod = SeasonalMean(slp_mod, season_ev, compute_anom=True) * 1e-2 + slp_obs = SeasonalMean(slp_obs, season_ev, compute_anom=True) * 1e-2 + if debug is True: + dict_debug = {'axes1': '(mod slp) ' + str([ax.id for ax in slp_mod.getAxisList()]), + 'axes2': '(obs slp) ' + str([ax.id for ax in slp_obs.getAxisList()]), + 'shape1': '(mod slp) ' + str(slp_mod.shape), 'shape2': '(obs slp) ' + str(slp_obs.shape), + 'time1': '(mod slp) ' + str(TimeBounds(slp_mod)), + 'time2': '(obs slp) ' + str(TimeBounds(slp_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + slp_mod, slp_obs, Method = TwoVarRegrid(slp_mod, slp_obs, Method, region=slpbox, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod slp) ' + str([ax.id for ax in slp_mod.getAxisList()]), + 'axes2': '(obs slp) ' + str([ax.id for ax in slp_obs.getAxisList()]), + 'shape1': '(mod slp) ' + str(slp_mod.shape), + 'shape2': '(obs slp) ' + str(slp_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # 2.3 Composites + slp_mod = Composite(slp_mod, event_years_mod, kwargs['frequency']) + slp_obs = Composite(slp_obs, event_years_obs, kwargs['frequency']) + if debug is True: + dict_debug = {'axes1': '(mod slp) ' + str([ax.id for ax in slp_mod.getAxisList()]), + 'axes2': '(obs slp) ' + str([ax.id for ax in slp_obs.getAxisList()]), + 'shape1': '(mod slp) ' + str(slp_mod.shape), 'shape2': '(obs slp) ' + str(slp_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Composite', 15, **dict_debug) + + # mask Pacific + slp_mod, keyerror_mod = BasinMask( + slp_mod, 'pacific', box=slpbox, lat1=-15, lat2=15, latkey='between', debug=debug) + slp_obs, keyerror_obs = BasinMask( + slp_obs, 'pacific', box=slpbox, lat1=-15, lat2=15, latkey='between', debug=debug) + if keyerror_mod is not None or keyerror_obs is not None: + slpCorr, slpCorrErr, slpRmse, slpRmseErr, slpStd, slpStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in slp_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod.shape), 'shape2': '(obs) ' + str(slp_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after BasinMask', 15, **dict_debug) + + # Metric 1 + slpRmse, keyerror = RmsAxis(slp_mod, slp_obs, axis='xy', centered=centered_rmse, biased=biased_rmse) + slpRmseErr = None + # Metric 2 + slpCorr = float(Correlation(slp_mod, slp_obs, axis='xy', centered=1, biased=1)) + slpCorrErr = None + # Metric 3 + std_mod = Std(slp_mod, weights=None, axis='xy', centered=1, biased=1) + std_obs = Std(slp_obs, weights=None, axis='xy', centered=1, biased=1) + slpStd = float(std_mod) / float(std_obs) + slpStdErr = None + + # Dive down diagnostic + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + if netcdf is True: + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'nina_years': str(event_years_mod)} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'nina_years': str(event_years_obs)} + dict3 = {'metric_name': Name, 'metric_valueRMSE_' + dataset2: slpRmse, + 'metric_valueRMSE_error_' + dataset2: slpRmseErr, 'metric_valueCORR_' + dataset2: slpCorr, + 'metric_valueCORR_error_' + dataset2: slpCorrErr, 'metric_valueSTD_' + dataset2: slpStd, + 'metric_valueCORR_error_' + dataset2: slpStdErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf(file_name, var1=slp_mod, var1_attributes=dict1, var1_name='slp_map__' + dataset1, + var2=slp_obs, var2_attributes=dict2, var2_name='slp_map__' + dataset2, + global_attributes=dict3) + del dict1, dict2, dict3, file_name + if slpCorr is not None: + slpCorr = 1 - slpCorr + # Create output + NinaSlpMapMetric = { + 'name': Name, 'Rmse__value': slpRmse, 'Rmse__value_error': slpRmseErr, 'Rmse__units': Units, 'method': Method, + 'Corr__value': slpCorr, 'Corr__value_error': slpCorrErr, 'Corr__units': '', 'Std__value': slpStd, + 'Std__value_error': slpStdErr, 'Std__units': Units + ' / ' + Units, 'nyears_model': yearN_mod, + 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag, 'units': ''} + return NinaSlpMapMetric + + +def NinaSstMap(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, tsbox, + event_definition, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, + netcdf_name='', metname='', **kwargs): + """ + The NinaSstMap() function computes a surface temperature anomalies composite of during the peak of La Nina events + SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + Then SSTA < 'threshold' during 'season' are considered as La Nina events + Then the TSA at the peak of the event is composited for each selected event + First metric: rmse(observations vs model). + Second metric: correlation(observations vs model). + Third metric: std(model)/std(observations) + These metrics can be used to compute a Taylor diagram. + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST areacell + :param sstareanamemod: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafilemod' + :param sstlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST landmask + :param sstlandmasknamemod: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST areacell + :param sstareanameobs: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafileobs' + :param sstlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST landmask + :param sstlandmasknameobs: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfileobs' + :param sstbox: string + name of box (e.g. 'global') for SST + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': -0.75} + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return NinaSstMapMetric: dict + name, Rmse__value (rms [obs;model]), Rmse__value_error, Rmse__units, method, Corr__value (corr [obs;model]), + Corr__value_error, Corr__units, Std__value (std_model / std_obs), Std__value_error, Std__units, nyears_model, + nyears_observations, time_frequency, time_period_mod, time_period_obs, ref, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'La Nina TSA Composite' + Method = 'Nina events = ' + region_ev + ' sstA < ' + str(threshold) + ' during ' + season_ev +\ + ', Nina TSA Composited' + Units = '' if kwargs['normalization'] else 'mm/day' + Ref = 'Using CDAT regridding, correlation (centered and biased), std (centered and biased) and ' + \ + 'rms (uncentered and biased) calculation' + metric = 'NinaSstMap' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod1 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs1 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + tsmap_mod, tsmap_mod_areacell, keyerror_mod2 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, tsbox, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + tsmap_obs, tsmap_obs_areacell, keyerror_obs2 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, tsbox, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst_mod, tsmap_mod, keyerror_mod3 = CheckTime(sst_mod, tsmap_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs, tsmap_obs, keyerror_obs3 = CheckTime(sst_obs, tsmap_obs, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if (keyerror_mod1 is not None or keyerror_obs1 is not None or keyerror_mod2 is not None) or \ + (keyerror_obs2 is not None or keyerror_mod3 is not None or keyerror_obs3 is not None): + tsCorr, tsCorrErr, tsRmse, tsRmseErr, tsStd, tsStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + tmp = [keyerror_mod1, keyerror_mod2, keyerror_mod3, keyerror_obs1, keyerror_obs2, keyerror_obs3] + keyerror = add_up_errors(tmp) + else: + # ------------------------------------------------ + # 1. detect events + # ------------------------------------------------ + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, _, keyerror_mod1 = PreProcessTS( + sst_mod, '', areacell=mod_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + sst_obs, _, keyerror_obs1 = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + tsmap_mod, Method, keyerror_mod2 = PreProcessTS( + tsmap_mod, Method, areacell=tsmap_mod_areacell, compute_anom=False, region=tsbox, **kwargs) + tsmap_obs, _, keyerror_obs2 = PreProcessTS( + tsmap_obs, '', areacell=tsmap_obs_areacell, compute_anom=False, region=tsbox, **kwargs) + del mod_areacell, obs_areacell, tsmap_mod_areacell, tsmap_obs_areacell + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_obs2 is not None or keyerror_mod2 is not None): + tsCorr, tsCorrErr, tsRmse, tsRmseErr, tsStd, tsStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + if debug is True: + dict_debug = { + 'axes1': '(mod sst) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs sst) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'axes3': '(mod ts) ' + str([ax.id for ax in tsmap_mod.getAxisList()]), + 'axes4': '(obs ts) ' + str([ax.id for ax in tsmap_obs.getAxisList()]), + 'shape1': '(mod sst) ' + str(sst_mod.shape), 'shape2': '(obs sst) ' + str(sst_obs.shape), + 'shape3': '(mod ts) ' + str(tsmap_mod.shape), 'shape4': '(obs ts) ' + str(tsmap_obs.shape), + 'time1': '(mod sst) ' + str(TimeBounds(sst_mod)), 'time2': '(obs sst) ' + str(TimeBounds(sst_obs)), + 'time3': '(mod ts) ' + str(TimeBounds(tsmap_mod)), + 'time4': '(obs ts) ' + str(TimeBounds(tsmap_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 SSTA < 'threshold' during 'season' are considered as La Nina events + # Lists event years + event_years_mod = DetectEvents(sst_mod, season_ev, threshold, normalization=normalize, nino=False) + event_years_obs = DetectEvents(sst_obs, season_ev, threshold, normalization=normalize, nino=False) + if debug is True: + dict_debug = {'nina1': '(mod) ' + str(event_years_mod), 'nina2': '(obs) ' + str(event_years_obs)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + + # ------------------------------------------------ + # 2. composite TSA + # ------------------------------------------------ + # 2.2 Seasonal mean and anomalies + tsmap_mod = SeasonalMean(tsmap_mod, season_ev, compute_anom=True) + tsmap_obs = SeasonalMean(tsmap_obs, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod ts) ' + str([ax.id for ax in tsmap_mod.getAxisList()]), + 'axes2': '(obs ts) ' + str([ax.id for ax in tsmap_obs.getAxisList()]), + 'shape1': '(mod ts) ' + str(tsmap_mod.shape), + 'shape2': '(obs ts) ' + str(tsmap_obs.shape), + 'time1': '(mod ts) ' + str(TimeBounds(tsmap_mod)), + 'time2': '(obs ts) ' + str(TimeBounds(tsmap_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + tsmap_mod, tsmap_obs, Method = TwoVarRegrid( + tsmap_mod, tsmap_obs, Method, region=tsbox, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod ts) ' + str([ax.id for ax in tsmap_mod.getAxisList()]), + 'axes2': '(obs ts) ' + str([ax.id for ax in tsmap_obs.getAxisList()]), + 'shape1': '(mod ts) ' + str(tsmap_mod.shape), + 'shape2': '(obs ts) ' + str(tsmap_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # 2.3 Composites + tsmap_mod = Composite(tsmap_mod, event_years_mod, kwargs['frequency']) + tsmap_obs = Composite(tsmap_obs, event_years_obs, kwargs['frequency']) + if debug is True: + dict_debug = {'axes1': '(mod ts) ' + str([ax.id for ax in tsmap_mod.getAxisList()]), + 'axes2': '(obs ts) ' + str([ax.id for ax in tsmap_obs.getAxisList()]), + 'shape1': '(mod ts) ' + str(tsmap_mod.shape), + 'shape2': '(obs ts) ' + str(tsmap_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Composite', 15, **dict_debug) + + # mask Pacific + tsmap_mod, keyerror_mod = BasinMask( + tsmap_mod, 'pacific', box=tsbox, lat1=-15, lat2=15, latkey='between', debug=debug) + tsmap_obs, keyerror_obs = BasinMask( + tsmap_obs, 'pacific', box=tsbox, lat1=-15, lat2=15, latkey='between', debug=debug) + if keyerror_mod is not None or keyerror_obs is not None: + tsCorr, tsCorrErr, tsRmse, tsRmseErr, tsStd, tsStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod ts) ' + str([ax.id for ax in tsmap_mod.getAxisList()]), + 'axes2': '(obs ts) ' + str([ax.id for ax in tsmap_obs.getAxisList()]), + 'shape1': '(mod ts) ' + str(tsmap_mod.shape), + 'shape2': '(obs ts) ' + str(tsmap_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after BasinMask', 15, **dict_debug) + + # Metric 1 + tsRmse, keyerror = RmsAxis(tsmap_mod, tsmap_obs, axis='xy', centered=centered_rmse, biased=biased_rmse) + tsRmseErr = None + # Metric 2 + tsCorr = float(Correlation(tsmap_mod, tsmap_obs, axis='xy', centered=1, biased=1)) + tsCorrErr = None + # Metric 3 + std_mod = Std(tsmap_mod, weights=None, axis='xy', centered=1, biased=1) + std_obs = Std(tsmap_obs, weights=None, axis='xy', centered=1, biased=1) + tsStd = float(std_mod) / float(std_obs) + tsStdErr = None + + # Dive down diagnostic + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + if netcdf is True: + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), 'nina_years': str(event_years_mod)} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), 'nina_years': str(event_years_obs)} + dict3 = {'metric_name': Name, 'metric_valueRMSE_' + dataset2: tsRmse, + 'metric_valueRMSE_error_' + dataset2: tsRmseErr, 'metric_valueCORR_' + dataset2: tsCorr, + 'metric_valueCORR_error_' + dataset2: tsCorrErr, 'metric_valueSTD_' + dataset2: tsStd, + 'metric_valueCORR_error_' + dataset2: tsStdErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf(file_name, var1=tsmap_mod, var1_attributes=dict1, var1_name='ts_map__' + dataset1, + var2=tsmap_obs, var2_attributes=dict2, var2_name='ts_map__' + dataset2, + global_attributes=dict3) + del dict1, dict2, dict3, file_name + if tsCorr is not None: + tsCorr = 1 - tsCorr + # Create output + NinaSstMapMetric = { + 'name': Name, 'Rmse__value': tsRmse, 'Rmse__value_error': tsRmseErr, 'Rmse__units': Units, 'method': Method, + 'Corr__value': tsCorr, 'Corr__value_error': tsCorrErr, 'Corr__units': '', 'Std__value': tsStd, + 'Std__value_error': tsStdErr, 'Std__units': Units + ' / ' + Units, 'nyears_model': yearN_mod, + 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag, 'units': ''} + return NinaSstMapMetric + + +def NinaSstTsRmse(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, + box, event_definition, nbr_years_window, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', + debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The NinaSstTsRmse() function computes a time composite of La Nina events + SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + Then SSTA < 'threshold' during 'season' are considered as La Nina events + Then a 'nbr_years_window' long time series centered on selected events is composited for each selected event + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for SST + :param sstareanamemod: string + name of areacell variable (areacella, areacello) in 'sstareafilemod' + :param sstlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for SST + :param sstlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for SST + :param sstareanameobs: string + name of areacell variable (areacella, areacello) in 'sstareafileobs' + :param sstlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for SST + :param sstlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfileobs' + :param box: string + name of box ('nino3') for SST + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': -0.75} + :param nbr_years_window: integer + number of years used to compute the composite (e.g. 6) + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return NinaTsMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, events_model, events_observations, + time_frequency, time_period_model, time_period_observations, ref, keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'La Nina Composite Time Series' + Method = 'Nina events = ' + region_ev + ' sstA < ' + str(threshold) + ' during ' + season_ev + ', time series of '\ + + str(nbr_years_window) + ' years (centered on events)' + Units = '' if kwargs['normalization'] else 'C' + Ref = 'Using CDAT rms (uncentered and biased) calculation' + metric = 'NinaSstTsRmse' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + compRmse, compRmseErr, event_years_mod, event_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, _, keyerror_mod = PreProcessTS( + sst_mod, '', areacell=mod_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + compRmse, compRmseErr, event_years_mod, event_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sst_mod)), 'time2': '(obs) ' + str(TimeBounds(sst_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 SSTA < 'threshold' during 'season' are considered as La Nina events + # Lists event years + event_years_mod = DetectEvents(sst_mod, season_ev, threshold, normalization=normalize, nino=False) + event_years_obs = DetectEvents(sst_obs, season_ev, threshold, normalization=normalize, nino=False) + if debug is True: + dict_debug = {'nina1': '(mod) ' + str(event_years_mod), 'nina2': '(obs) ' + str(event_years_obs)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + + # ------------------------------------------------ + # 2. temporal composite of SSTA + # ------------------------------------------------ + # interannual anomalies + sst_mod = ComputeInterannualAnomalies(sst_mod) + sst_obs = ComputeInterannualAnomalies(sst_obs) + + # composites + composite_mod = Composite(sst_mod, event_years_mod, kwargs['frequency'], nbr_years_window=nbr_years_window) + composite_obs = Composite(sst_obs, event_years_obs, kwargs['frequency'], nbr_years_window=nbr_years_window) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in composite_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in composite_obs.getAxisList()]), + 'shape1': '(mod) ' + str(composite_mod.shape), + 'shape2': '(obs) ' + str(composite_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Composite', 15, **dict_debug) + + # Computes the root mean square difference + compRmse, keyerror = RmsAxis( + composite_mod, composite_obs, axis=0, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + compRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(composite_mod), 'observations': ArrayToList(composite_obs), + 'axis': list(composite_mod.getAxis(0)[:])} + if netcdf is True: + # Read file and select the right region + sst_hov_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, 'equatorial_pacific', file_area=sstareafilemod, + name_area=sstareanamemod, file_mask=sstlandmaskfilemod, name_mask=sstlandmaskfilemod, maskland=True, + maskocean=False, time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_hov_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, 'equatorial_pacific', file_area=sstareafileobs, + name_area=sstareanameobs, file_mask=sstlandmaskfileobs, name_mask=sstlandmaskfileobs, maskland=True, + maskocean=False, time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_hov_mod, _, keyerror_mod = PreProcessTS( + sst_hov_mod, Method, areacell=mod_areacell, average=False, compute_anom=True, + region="equatorial_pacific", **kwargs) + sst_hov_obs, _, keyerror_obs = PreProcessTS( + sst_hov_obs, '', areacell=obs_areacell, average=False, compute_anom=True, + region="equatorial_pacific", **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_hov_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_hov_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_hov_mod.shape), + 'shape2': '(obs) ' + str(sst_hov_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sst_hov_mod)), + 'time2': '(obs) ' + str(TimeBounds(sst_hov_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + # Regridding + if 'regridding' not in list(kwargs.keys()): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'} + else: + if not isinstance(kwargs['regridding'], dict): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + sst_hov_mod, sst_hov_obs, Method = TwoVarRegrid( + sst_hov_mod, sst_hov_obs, Method, region='equatorial_pacific', **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_hov_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_hov_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_hov_mod.shape), + 'shape2': '(obs) ' + str(sst_hov_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + # Meridional average + sst_hov_mod, keyerror_mod = AverageMeridional(sst_hov_mod) + sst_hov_obs, keyerror_obs = AverageMeridional(sst_hov_obs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_hov_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_hov_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_hov_mod.shape), + 'shape2': '(obs) ' + str(sst_hov_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + # samples + sst_hov_mod = Composite( + sst_hov_mod, event_years_mod, kwargs['frequency'], nbr_years_window=nbr_years_window) + sst_hov_obs = Composite( + sst_hov_obs, event_years_obs, kwargs['frequency'], nbr_years_window=nbr_years_window) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_hov_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_hov_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_hov_mod.shape), + 'shape2': '(obs) ' + str(sst_hov_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Composite', 15, **dict_debug) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), 'nina_years': str(event_years_mod), + 'description': "time series of " + box + " sstA centered on La Nina peak"} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), 'nina_years': str(event_years_obs), + 'description': "time series of " + box + " sstA centered on La Nina peak"} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), 'nina_years': str(event_years_mod), + 'description': "zonal monthly of equatorial_pacific sstA centered on La Nina peak"} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), 'nina_years': str(event_years_obs), + 'description': "zonal monthly of equatorial_pacific sstA centered on La Nina peak"} + dict5 = {'metric_name': Name, 'metric_value_' + dataset2: compRmse, + 'metric_value_error_' + dataset2: compRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=composite_mod, var1_attributes=dict1, var1_name='sst_ts__' + dataset1, + var2=composite_obs, var2_attributes=dict2, var2_name='sst_ts__' + dataset2, + var3=sst_hov_mod, var3_attributes=dict3, var3_name='sst_hov__' + dataset1, + var4=sst_hov_obs, var4_attributes=dict4, var4_name='sst_hov__' + dataset2, + global_attributes=dict5) + del dict1, dict2, dict3, dict4, dict5, file_name + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(compRmse), 'line2': 'metric value_error: ' + str(compRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + NinaTsMetric = { + 'name': Name, 'value': compRmse, 'value_error': compRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'events_model': event_years_mod, + 'events_observations': event_years_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return NinaTsMetric + + +def NinoPrMap(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, prfilemod, + prnamemod, prareafilemod, prareanamemod, prlandmaskfilemod, prlandmasknamemod, sstfileobs, sstnameobs, + sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, prfileobs, prnameobs, + prareafileobs, prareanameobs, prlandmaskfileobs, prlandmasknameobs, sstbox, prbox, event_definition, + centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, netcdf_name='', + metname='', **kwargs): + """ + The NinoPrMap() function computes a precipitation anomalies composite of during the peak of El Nino events + SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + Then SSTA > 'threshold' during 'season' are considered as El Nino events + Then the PRA at the peak of the event is composited for each selected event + First metric: rmse(observations vs model). + Second metric: correlation(observations vs model). + Third metric: std(model)/std(observations) + These metrics can be used to compute a Taylor diagram. + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST areacell + :param sstareanamemod: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafilemod' + :param sstlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST landmask + :param sstlandmasknamemod: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfilemod' + :param prfilemod: string + path_to/filename of the file (NetCDF) of the modeled PR + :param prnamemod: string + name of PR variable (pr) in 'prfilemod' + :param prareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled PR areacell + :param prareanamemod: string, optional + name of areacell for the PR variable (areacella, areacello,...) in 'prareafilemod' + :param prlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled PR landmask + :param prlandmasknamemod: string, optional + name of landmask for the PR variable (sftlf,...) in 'prlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST areacell + :param sstareanameobs: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafileobs' + :param sstlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST landmask + :param sstlandmasknameobs: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfileobs' + :param prfileobs: string + path_to/filename of the file (NetCDF) of the observed PR + :param prnameobs: string + name of PR variable (pr, precip) in 'prfileobs' + :param prareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed PR areacell + :param prareanameobs: string, optional + name of areacell for the PR variable (areacella, areacello,...) in 'prareafileobs' + :param prlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed PR landmask + :param prlandmasknameobs: string, optional + name of landmask for the PR variable (sftlf,...) in 'prlandmaskfileobs' + :param sstbox: string + name of box (e.g. 'global') for SST + :param prbox: string + name of box (e.g. 'global') for PR + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': 0.75} + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return NinoPrMapMetric: dict + name, Rmse__value (rms [obs;model]), Rmse__value_error, Rmse__units, method, Corr__value (corr [obs;model]), + Corr__value_error, Corr__units, Std__value (std_model / std_obs), Std__value_error, Std__units, nyears_model, + nyears_observations, time_frequency, time_period_mod, time_period_obs, ref, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'El Nino PRA Composite' + Method = 'Nino events = ' + region_ev + ' sstA > ' + str(threshold) + ' during ' + season_ev +\ + ', Nino PRA Composited' + Units = '' if kwargs['normalization'] else 'mm/day' + Ref = 'Using CDAT regridding, correlation (centered and biased), std (centered and biased) and ' + \ + 'rms (uncentered and biased) calculation' + metric = 'NinoPrMap' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod1 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs1 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + pr_mod, pr_mod_areacell, keyerror_mod2 = Read_data_mask_area( + prfilemod, prnamemod, 'precipitations', metric, prbox, file_area=prareafilemod, name_area=prareanamemod, + file_mask=prlandmaskfilemod, name_mask=prlandmasknamemod, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + pr_obs, pr_obs_areacell, keyerror_obs2 = Read_data_mask_area( + prfileobs, prnameobs, 'precipitations', metric, prbox, file_area=prareafileobs, name_area=prareanameobs, + file_mask=prlandmaskfileobs, name_mask=prlandmasknameobs, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst_mod, pr_mod, keyerror_mod3 = CheckTime(sst_mod, pr_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs, pr_obs, keyerror_obs3 = CheckTime(sst_obs, pr_obs, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if (keyerror_mod1 is not None or keyerror_obs1 is not None or keyerror_mod2 is not None) or \ + (keyerror_obs2 is not None or keyerror_mod3 is not None or keyerror_obs3 is not None): + prCorr, prCorrErr, prRmse, prRmseErr, prStd, prStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + tmp = [keyerror_mod1, keyerror_mod2, keyerror_mod3, keyerror_obs1, keyerror_obs2, keyerror_obs3] + keyerror = add_up_errors(tmp) + else: + # ------------------------------------------------ + # 1. detect events + # ------------------------------------------------ + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, _, keyerror_mod1 = PreProcessTS( + sst_mod, '', areacell=mod_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + sst_obs, _, keyerror_obs1 = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + pr_mod, Method, keyerror_mod2 = PreProcessTS( + pr_mod, Method, areacell=pr_mod_areacell, compute_anom=False, region=prbox, **kwargs) + pr_obs, _, keyerror_obs2 = PreProcessTS( + pr_obs, '', areacell=pr_obs_areacell, compute_anom=False, region=prbox, **kwargs) + del mod_areacell, obs_areacell, pr_mod_areacell, pr_obs_areacell + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_mod2 is not None or keyerror_obs2 is not None): + prCorr, prCorrErr, prRmse, prRmseErr, prStd, prStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + if debug is True: + dict_debug = { + 'axes1': '(mod sst) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs sst) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'axes3': '(mod pr) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes4': '(obs pr) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'shape1': '(mod sst) ' + str(sst_mod.shape), 'shape2': '(obs sst) ' + str(sst_obs.shape), + 'shape3': '(mod pr) ' + str(pr_mod.shape), 'shape4': '(obs pr) ' + str(pr_obs.shape), + 'time1': '(mod sst) ' + str(TimeBounds(sst_mod)), 'time2': '(obs sst) ' + str(TimeBounds(sst_obs)), + 'time3': '(mod pr) ' + str(TimeBounds(pr_mod)), 'time4': '(obs pr) ' + str(TimeBounds(pr_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 SSTA > 'threshold' during 'season' are considered as El Nino events + # Lists event years + event_years_mod = DetectEvents(sst_mod, season_ev, threshold, normalization=normalize, nino=True) + event_years_obs = DetectEvents(sst_obs, season_ev, threshold, normalization=normalize, nino=True) + if debug is True: + dict_debug = {'nino1': '(mod) ' + str(event_years_mod), 'nino2': '(obs) ' + str(event_years_obs)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + + # ------------------------------------------------ + # 2. composite PRA + # ------------------------------------------------ + # 2.2 Seasonal mean and anomalies + pr_mod = SeasonalMean(pr_mod, season_ev, compute_anom=True) + pr_obs = SeasonalMean(pr_obs, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(pr_mod)), 'time2': '(obs) ' + str(TimeBounds(pr_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + pr_mod, pr_obs, Method = TwoVarRegrid(pr_mod, pr_obs, Method, region=prbox, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # 2.3 Composites + pr_mod = Composite(pr_mod, event_years_mod, kwargs['frequency']) + pr_obs = Composite(pr_obs, event_years_obs, kwargs['frequency']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Composite', 15, **dict_debug) + + # mask Pacific + pr_mod, keyerror_mod = BasinMask( + pr_mod, 'pacific', box=prbox, lat1=-15, lat2=15, latkey='between', debug=debug) + pr_obs, keyerror_obs = BasinMask( + pr_obs, 'pacific', box=prbox, lat1=-15, lat2=15, latkey='between', debug=debug) + if keyerror_mod is not None or keyerror_obs is not None: + prCorr, prCorrErr, prRmse, prRmseErr, prStd, prStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after BasinMask', 15, **dict_debug) + + # Metric 1 + prRmse, keyerror = RmsAxis(pr_mod, pr_obs, axis='xy', centered=centered_rmse, biased=biased_rmse) + prRmseErr = None + # Metric 2 + prCorr = float(Correlation(pr_mod, pr_obs, axis='xy', centered=1, biased=1)) + prCorrErr = None + # Metric 3 + std_mod = Std(pr_mod, weights=None, axis='xy', centered=1, biased=1) + std_obs = Std(pr_obs, weights=None, axis='xy', centered=1, biased=1) + prStd = float(std_mod) / float(std_obs) + prStdErr = None + + # Dive down diagnostic + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + + if netcdf is True: + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = { + 'units': Units, 'number_of_years_used': yearN_mod, 'time_period': str(actualtimebounds_mod), + 'nino_years': str(event_years_mod)} + dict2 = { + 'units': Units, 'number_of_years_used': yearN_obs, 'time_period': str(actualtimebounds_obs), + 'nino_years': str(event_years_obs)} + dict3 = { + 'metric_name': Name, 'metric_valueRMSE_' + dataset2: prRmse, + 'metric_valueRMSE_error_' + dataset2: prRmseErr, 'metric_valueCORR_' + dataset2: prCorr, + 'metric_valueCORR_error_' + dataset2: prCorrErr, 'metric_valueSTD_' + dataset2: prStd, + 'metric_valueCORR_error_' + dataset2: prStdErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=pr_mod, var1_attributes=dict1, var1_name='prComp_map__' + dataset1, var2=pr_obs, + var2_attributes=dict2, var2_name='prComp_map__' + dataset2, global_attributes=dict3) + del dict1, dict2, dict3, file_name + if prCorr is not None: + prCorr = 1 - prCorr + # Create output + NinoPrMapMetric = { + 'name': Name, 'Rmse__value': prRmse, 'Rmse__value_error': prRmseErr, 'Rmse__units': Units, 'method': Method, + 'Corr__value': prCorr, 'Corr__value_error': prCorrErr, 'Corr__units': '', 'Std__value': prStd, + 'Std__value_error': prStdErr, 'Std__units': Units + ' / ' + Units, 'nyears_model': yearN_mod, + 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag, 'units': ''} + return NinoPrMapMetric + + +def NinoSstDiv(sstfile, sstname, sstareafile, sstareaname, sstlandmaskfile, sstlandmaskname, box, event_definition, + dataset='', debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The NinoSstDiv() function computes a zonal composite of El Nino events during the peak of the event. + 1.) detect events + 1.1) SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + 1.2) SSTA > 'threshold' during 'season' are considered as El Nino events + 2.) diversity of the zonal location of the maximum SSTA + 2.1) zonal SSTA at the peak of the event is computed for each selected event + 2.2) find the zonal position of the maximum SSTA for each selected event + 2.3) compute the percentage of EP events (maximum SSTA eastward of the given threshold) + + Inputs: + ------ + :param sstfile: string + path_to/filename of the file (NetCDF) of the SST + :param sstname: string + name of SST variable (tos, ts) in 'sstfile' + :param sstareafile: string + path_to/filename of the file (NetCDF) of the areacell for SST + :param sstareaname: string + name of areacell variable (areacella, areacello) in 'sstareafile' + :param sstlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for SST + :param sstlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfile' + :param box: string + name of box ('nino3') for SST + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': 0.75} + :param nbr_years_window: integer + number of years used to compute the composite (e.g. 6) + :param dataset: string, optional + name of current dataset (e.g., 'model', 'obs', ...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param treshold_ep_ev: float, optional + see EnsoToolsLib.percentage_val_eastward + longitude, in degree east, of the westward boundary of eastern Pacific event + default value is -140°E (i.e., 140°W) + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return NinoDivMetric: dict + name, value, value_error, units, method, nyears, events, time_frequency, time_period, ref, keyerror, + dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'treshold_ep_ev', + 'time_bounds'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'El Nino Diversity (percentage of eastern Pacific El Nino)' + lat = ReferenceRegions(box)['latitude'] + lon = ReferenceRegions(box)['longitude'] + Method = 'Nino events = ' + region_ev + ' sstA > ' + str(threshold) + ' during ' + season_ev + ', zonal SSTA ' +\ + '(meridional averaged [' + str(lat[0]) + ' ; ' + str(lat[1]) + ']), westward boundary of EP events' +\ + str(kwargs['treshold_ep_ev']) + 'E' + Units = '%' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = 'NinoSstDiv' + if metname == '': + metname = deepcopy(metric) + + # ------------------------------------------------ + # 1. detect events + # ------------------------------------------------ + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst, areacell, keyerror = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, region_ev, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + + # Number of years + yearN = int(round(sst.shape[0] / 12)) + + # Time period + actualtimebounds = TimeBounds(sst) + + if keyerror is not None: + ep_event, StdErr, dive_down_diag, event_years = None, None, {'value': None, 'axis': None}, None + else: + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst, _, keyerror = PreProcessTS( + sst, '', areacell=areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + del areacell + if keyerror is not None: + ep_event, StdErr, dive_down_diag, event_years = None, None, {'value': None, 'axis': None}, None + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape), 'time1': '(sst) ' + str(TimeBounds(sst))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 SSTA > 'threshold' during 'season' are considered as El Nino events + # Lists event years + event_years = DetectEvents(sst, season_ev, threshold, normalization=normalize, nino=True) + if debug is True: + dict_debug = {'nino1': 'nbr(' + str(len(event_years)) + '): ' + str(event_years)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + + # ------------------------------------------------ + # 2. diversity of the zonal location of the maximum SSTA + # ------------------------------------------------ + # Read file and select the right region + sst, areacell, keyerror = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, box, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskfile, maskland=True, maskocean=False, debug=debug, + **kwargs) + if keyerror is not None: + ep_event, StdErr, dive_down_diag, event_years = None, None, {'value': None, 'axis': None}, None + else: + # 2.1 zonal SSTA at the peak of the event is computed for each selected event + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst, Method, keyerror = PreProcessTS( + sst, Method, areacell=areacell, average=False, compute_anom=False, region=box, **kwargs) + del areacell + if keyerror is not None: + ep_event, StdErr, dive_down_diag, event_years = None, None, {'value': None, 'axis': None}, None + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape), 'time1': '(sst) ' + str(TimeBounds(sst))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Seasonal mean + sst = SeasonalMean(sst, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', 'regridTool', + 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + sst = Regrid(sst, None, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Meridional average + sst, keyerror = AverageMeridional(sst) + if keyerror is not None: + ep_event, StdErr, dive_down_diag, event_years = None, None, {'value': None, 'axis': None}, None + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sst.getAxisList()]), + 'shape1': '(sst) ' + str(sst.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + + # samples + sample = Event_selection(sst, kwargs['frequency'], list_event_years=event_years) + + # 2.2 find the zonal position of the maximum SSTA for each selected event + lon_sstmax = FindXYMinMaxInTs( + sample, return_val='maxi', smooth=True, axis=0, window=5, method='triangle') + if debug is True: + dict_debug = {'line1': 'longitude of the maximum SSTA: ' + str(lon_sstmax)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after FindXYMinMaxInTs', 15, **dict_debug) + + # 2.3 compute the percentage of EP events (maximum SSTA eastward of the given threshold) + ep_event, keyerror = percentage_val_eastward( + lon_sstmax, metric, box, threshold=kwargs['treshold_ep_ev']) + ep_event = float(ep_event) + + if keyerror is not None: + StdErr, dive_down_diag = None, {'value': None, 'axis': None} + else: + # Standard Error of the Standard Deviation (function of nyears) + StdErr = None + + # Dive down diagnostic + dive_down_diag = {'value': ArrayToList(lon_sstmax), 'axis': list(lon_sstmax.getAxis(0)[:])} + + if netcdf is True: + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': 'longitude (E)', 'number_of_years_used': yearN, + 'time_period': str(actualtimebounds), 'nino_years': str(event_years), + 'diagnostic_value_' + dataset: ep_event, + 'diagnostic_value_error_' + dataset: StdErr} + dict2 = {'metric_name': Name, 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + SaveNetcdf(file_name, var1=lon_sstmax, var1_attributes=dict1, + var1_name='Nino_lon_pos_minSSTA__' + dataset, global_attributes=dict2) + del dict1, dict2 + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(ep_event), 'line2': 'metric value_error: ' + str(StdErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + NinoDivMetric = { + 'name': Name, 'value': ep_event, 'value_error': StdErr, 'units': Units, 'method': Method, 'nyears': yearN, + 'events': event_years, 'time_frequency': kwargs['frequency'], 'time_period': actualtimebounds, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return NinoDivMetric + + +def NinoSstDivRmse(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, box, + event_definition, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, + netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The NinoSstDivRmse() function computes a zonal maximum of El Nino events during the peak of the event. + 1.) detect events + 1.1) SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + 1.2) SSTA > 'threshold' during 'season' are considered as El Nino events + 2.) diversity of the zonal location of the maximum SSTA + 2.1) zonal SSTA at the peak of the event is computed for each selected event + 2.2) find the zonal position of the maximum SSTA for each selected event and compute a pdf + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for SST + :param sstareanamemod: string + name of areacell variable (areacella, areacello) in 'sstareafilemod' + :param sstlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for SST + :param sstlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for SST + :param sstareanameobs: string + name of areacell variable (areacella, areacello) in 'sstareafileobs' + :param sstlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for SST + :param sstlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfileobs' + :param box: string + name of box ('nino3') for SST + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': 0.75} + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return NinoDivMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, events_model, events_observations, + time_frequency, time_period_model, time_period_observations, ref, keyword, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'PDF of zonal min(SSTA) during El Nino' + lat = ReferenceRegions(box)['latitude'] + lon = ReferenceRegions(box)['longitude'] + Method = 'Nino events = ' + region_ev + ' sstA > ' + str(threshold) + ' during ' + season_ev + ', zonal SSTA '\ + + '(meridional averaged [' + str(lat[0]) + ' ; ' + str(lat[1]) + ']' + Units = 'density' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = 'NinoSstDivRmse' + if metname == '': + metname = deepcopy(metric) + + # ------------------------------------------------ + # 1. detect events + # ------------------------------------------------ + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + pdfRmse, pdfRmseErr, event_years_mod, event_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, _, keyerror_mod = PreProcessTS( + sst_mod, '', areacell=mod_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + pdfRmse, pdfRmseErr, event_years_mod, event_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sst_mod)), + 'time2': '(obs) ' + str(TimeBounds(sst_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 SSTA > 'threshold' during 'season' are considered as El Nino events + # Lists event years + event_years_mod = DetectEvents(sst_mod, season_ev, threshold, normalization=normalize, nino=True) + event_years_obs = DetectEvents(sst_obs, season_ev, threshold, normalization=normalize, nino=True) + if debug is True: + dict_debug = {'nino1': '(mod) ' + str(event_years_mod), 'nino2': '(obs) ' + str(event_years_obs)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + + # ------------------------------------------------ + # 2. diversity of the zonal location of the maximum SSTA + # ------------------------------------------------ + # Read file and select the right region + sst_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, box, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmaskfilemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, box, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmaskfileobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + pdfRmse, pdfRmseErr, event_years_mod, event_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # 2.1 zonal SSTA at the peak of the event is computed for each selected event + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, Method, keyerror_mod = PreProcessTS( + sst_mod, Method, areacell=mod_areacell, average=False, compute_anom=False, region=box, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average=False, compute_anom=False, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + pdfRmse, pdfRmseErr, event_years_mod, event_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sst_mod)), + 'time2': '(obs) ' + str(TimeBounds(sst_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Seasonal mean + sst_mod = SeasonalMean(sst_mod, season_ev, compute_anom=True) + sst_obs = SeasonalMean(sst_obs, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', + 'regridder', 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + sst_mod, sst_obs, Method = TwoVarRegrid( + sst_mod, sst_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), + 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Meridional average + sst_mod, keyerror_mod = AverageMeridional(sst_mod) + sst_obs, keyerror_obs = AverageMeridional(sst_obs) + if keyerror_mod is not None or keyerror_obs is not None: + pdfRmse, pdfRmseErr, event_years_mod, event_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), + 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + + # samples + sample_mod = Event_selection(sst_mod, kwargs['frequency'], list_event_years=event_years_mod) + sample_obs = Event_selection(sst_obs, kwargs['frequency'], list_event_years=event_years_obs) + + # 2.2 find the zonal position of the maximum SSTA for each selected event and compute a pdf + # longitude of the maximum SSTA for each selected event + lon_min_mod = FindXYMinMaxInTs( + sample_mod, return_val='maxi', smooth=True, axis=0, window=5, method='triangle') + lon_min_obs = FindXYMinMaxInTs( + sample_obs, return_val='maxi', smooth=True, axis=0, window=5, method='triangle') + if debug is True: + dict_debug = {'line1': '(mod) longitude of the maximum SSTA: ' + str(lon_min_mod), + 'line2': '(obs) longitude of the maximum SSTA: ' + str(lon_min_obs)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after FindXYMinMaxInTs', 15, **dict_debug) + + # compute PDFs + if debug is True: + dict_debug = {'line1': 'lon ' + str(lon) + ' ; nbr_bins old = ' + + str((lon[1] - lon[0]) / 10) + ' ; nbr_bins new = ' + + str(int(round((lon[1] - lon[0]) / 10)))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'before ComputePDF', 15, **dict_debug) + pdf_mod = ComputePDF(lon_min_mod, nbr_bins=int(round((lon[1] - lon[0]) / 10)), interval=lon, + axis_name='longitude') + pdf_obs = ComputePDF(lon_min_obs, nbr_bins=int(round((lon[1] - lon[0]) / 10)), interval=lon, + axis_name='longitude') + + # Computes the root mean square difference + pdfRmse, keyerror = RmsZonal(pdf_mod, pdf_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + pdfRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(pdf_mod), 'observations': ArrayToList(pdf_obs), + 'axis': list(pdf_mod.getAxis(0)[:])} + if netcdf is True: + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), 'nino_years': str(event_years_mod)} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), 'nino_years': str(event_years_obs)} + dict3 = {'metric_name': Name, 'metric_value_' + dataset2: pdfRmse, + 'metric_value_error_' + dataset2: pdfRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf(file_name, var1=pdf_mod, var1_attributes=dict1, var1_name='pdf__' + dataset1, + var2=pdf_obs, var2_attributes=dict2, var2_name='pdf__' + dataset2, + global_attributes=dict3) + del dict1, dict2, dict3, file_name + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(pdfRmse), 'line2': 'metric value_error: ' + str(pdfRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + NinoDivMetric = { + 'name': Name, 'value': pdfRmse, 'value_error': pdfRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'events_model': event_years_mod, + 'events_observations': event_years_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return NinoDivMetric + + +def NinoSstDur(sstfile, sstname, sstareafile, sstareaname, sstlandmaskfile, sstlandmaskname, box, event_definition, + nbr_years_window, dataset='', debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The NinoSstDurRmse() function computes a duration of El Nino events. + 1.) detect events + 1.1) SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + 1.2) SSTA > 'threshold' during 'season' are considered as El Nino events + 2.) El Nino duration + 2.1) get a time series of 2 years before and 2 years after the El Nino peak (4 years time series) + 2.2) count the number of consecutive month above a threshold + + Inputs: + ------ + :param sstfile: string + path_to/filename of the file (NetCDF) of the SST + :param sstname: string + name of SST variable (tos, ts) in 'sstfile' + :param sstareafile: string + path_to/filename of the file (NetCDF) of the areacell for SST + :param sstareaname: string + name of areacell variable (areacella, areacello) in 'sstareafile' + :param sstlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for SST + :param sstlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfile' + :param box: string + name of box ('nino3') for SST + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': 0.75} + :param nbr_years_window: integer + number of years used to compute the composite (e.g. 6) + :param dataset: string, optional + name of current dataset (e.g., 'model', 'obs', ...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return NinoDurMetric: dict + name, value, value_error, units, method, nyears, events, time_frequency, time_period, time_period, ref, + keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'El Nino Duration' + Units = 'months' + Method = 'Nino events = ' + region_ev + ' sstA > ' + str(threshold) + ' during ' + season_ev + \ + ', number of consecutive months when sstA > 0.5' + Units + Ref = 'Using CDAT' + metric = 'NinoSstDur' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst, sst_areacell, keyerror = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, region_ev, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + + # Number of years + yearN = int(round(sst.shape[0] / 12)) + + # Time period + actualtimebounds = TimeBounds(sst) + + if keyerror is not None: + duration_mean, duration_err, dive_down_diag, event_years = None, None, {'value': None, 'axis': None}, None + else: + # ------------------------------------------------ + # 1. detect events + # ------------------------------------------------ + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst, Method, keyerror = PreProcessTS( + sst, Method, areacell=sst_areacell, average='horizontal', compute_anom=True, region=region_ev, **kwargs) + del sst_areacell + if keyerror is not None: + duration_mean, duration_err, dive_down_diag, event_years = None, None, {'value': None, 'axis': None}, None + else: + if debug is True: + dict_debug = {'axes1': str([ax.id for ax in sst.getAxisList()]), 'shape1': str(sst.shape), + 'time1': str(TimeBounds(sst))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 SSTA > 'threshold' during 'season' are considered as El Nino events + # Lists event years + event_years = DetectEvents(sst, season_ev, threshold, normalization=normalize, nino=True) + if debug is True: + dict_debug = {'nino1': str(event_years)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + + # ------------------------------------------------ + # 2. El Nino duration + # ------------------------------------------------ + # 2.1 get a time series of 2 years before and 2 years after the El Nino peak (4 years time series) + # composites + sample = Event_selection( + sst, kwargs['frequency'], nbr_years_window=nbr_years_window, list_event_years=event_years) + if debug is True: + dict_debug = {'axes1': str([ax.id for ax in sample.getAxisList()]), 'shape1': str(sample.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Event_selection', 15, **dict_debug) + + # 2.2 count the number of consecutive month above a threshold + if normalize is True: + duration = DurationAllEvent(sample, 0.5 * float(Std(sst)), nino=True, debug=debug) + else: + duration = DurationAllEvent(sample, 0.5, nino=True, debug=debug) + + duration_err = float(Std(duration) / NUMPYsqrt(len(duration))) + duration_mean = float(duration.mean()) + + # Dive down diagnostic + dive_down_diag = {'value': ArrayToList(duration), 'axis': list(duration.getAxis(0)[:])} + + if netcdf is True: + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'nino_years': str(event_years), 'description': "duration of El Nino events", + 'diagnostic_value': duration_mean, 'diagnostic_value_error': duration_err} + dict2 = {'metric_name': Name, 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + SaveNetcdf(file_name, var1=duration, var1_attributes=dict1, var1_name='Nino_duration__' + dataset, + global_attributes=dict2) + del dict1, dict2, file_name + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(duration_mean), + 'line2': 'metric value_error: ' + str(duration_err)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + NinoDurMetric = { + 'name': Name, 'value': duration_mean, 'value_error': duration_err, 'units': Units, 'method': Method, + 'nyears': yearN, 'events': event_years, 'time_frequency': kwargs['frequency'], 'time_period': actualtimebounds, + 'ref': Ref, 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return NinoDurMetric + + +def NinoSstLonRmse(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, box, + event_definition, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, + netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The NinoSstLonRmse() function computes a zonal composite of El Nino events during the peak of the event + SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + Then SSTA > 'threshold' during 'season' are considered as El Nino events + Then the zonal SSTA at the peak of the event is composited for each selected event + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for SST + :param sstareanamemod: string + name of areacell variable (areacella, areacello) in 'sstareafilemod' + :param sstlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for SST + :param sstlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for SST + :param sstareanameobs: string + name of areacell variable (areacella, areacello) in 'sstareafileobs' + :param sstlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for SST + :param sstlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfileobs' + :param box: string + name of box ('equatorial_pacific') for SST + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': 0.75} + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return NinoLonMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, events_model, events_observations, + time_frequency, time_period_model, time_period_observations, ref, keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'El Nino Zonal Composite' + lat = ReferenceRegions(box)['latitude'] + Method = 'Nino events = ' + region_ev + ' sstA > ' + str(threshold) + ' during ' + season_ev + ', zonal SSTA '\ + + '(meridional averaged [' + str(lat[0]) + ' ; ' + str(lat[1]) + ']' + Units = '' if kwargs['normalization'] else 'C' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = 'NinoSstLonRmse' + if metname == '': + metname = deepcopy(metric) + + # ------------------------------------------------ + # detect events + # ------------------------------------------------ + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + compRmse, compRmseErr, event_years_mod, event_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, _, keyerror_mod = PreProcessTS( + sst_mod, '', areacell=mod_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + compRmse, compRmseErr, event_years_mod, event_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sst_mod)), + 'time2': '(obs) ' + str(TimeBounds(sst_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 SSTA > 'threshold' during 'season' are considered as El Nino events + # Lists event years + event_years_mod = DetectEvents(sst_mod, season_ev, threshold, normalization=normalize, nino=True) + event_years_obs = DetectEvents(sst_obs, season_ev, threshold, normalization=normalize, nino=True) + if debug is True: + dict_debug = {'nino1': '(mod) ' + str(event_years_mod), 'nino2': '(obs) ' + str(event_years_obs)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + + # ------------------------------------------------ + # 2. zonal composite of SSTA + # ------------------------------------------------ + # Read file and select the right region + sst_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, box, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmaskfilemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, box, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmaskfileobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + compRmse, compRmseErr, event_years_mod, event_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # 2.1 zonal SSTA at the peak of the event is computed for each selected event + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, Method, keyerror_mod = PreProcessTS( + sst_mod, Method, areacell=mod_areacell, average=False, compute_anom=False, region=box, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average=False, compute_anom=False, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + compRmse, compRmseErr, event_years_mod, event_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sst_mod)), + 'time2': '(obs) ' + str(TimeBounds(sst_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # Seasonal mean + sst_mod = SeasonalMean(sst_mod, season_ev, compute_anom=True) + sst_obs = SeasonalMean(sst_obs, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', + 'regridder', 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + sst_mod, sst_obs, Method = TwoVarRegrid( + sst_mod, sst_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), + 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Meridional average + sst_mod, keyerror_mod = AverageMeridional(sst_mod) + sst_obs, keyerror_obs = AverageMeridional(sst_obs) + if keyerror_mod is not None or keyerror_obs is not None: + compRmse, compRmseErr, event_years_mod, event_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), + 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + + # samples + sst_mod = Composite(sst_mod, event_years_mod, kwargs['frequency']) + sst_obs = Composite(sst_obs, event_years_obs, kwargs['frequency']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), + 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Composite', 15, **dict_debug) + + # Computes the root mean square difference + compRmse, keyerror = RmsZonal(sst_mod, sst_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + compRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(sst_mod), 'observations': ArrayToList(sst_obs), + 'axis': list(sst_mod.getAxis(0)[:])} + if netcdf is True: + map_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, 'equatorial_pacific_LatExt2', + file_area=sstareafilemod, name_area=sstareanamemod, file_mask=sstlandmaskfilemod, + name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + map_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, 'equatorial_pacific_LatExt2', + file_area=sstareafileobs, name_area=sstareanameobs, file_mask=sstlandmaskfileobs, + name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + map_mod, _, keyerror_mod = PreProcessTS( + map_mod, '', areacell=mod_areacell, average=False, compute_anom=False, + region="equatorial_pacific_LatExt2", **kwargs) + map_obs, _, keyerror_obs = PreProcessTS( + map_obs, '', areacell=obs_areacell, average=False, compute_anom=False, + region="equatorial_pacific_LatExt2", **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in map_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in map_obs.getAxisList()]), + 'shape1': '(mod) ' + str(map_mod.shape), + 'shape2': '(obs) ' + str(map_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(map_mod)), + 'time2': '(obs) ' + str(TimeBounds(map_obs))} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after PreProcessTS: netcdf', 15, **dict_debug) + # Seasonal mean + map_mod = SeasonalMean(map_mod, season_ev, compute_anom=True) + map_obs = SeasonalMean(map_obs, season_ev, compute_anom=True) + # Regridding + if isinstance(kwargs['regridding'], dict): + map_mod, map_obs, _ = TwoVarRegrid( + map_mod, map_obs, '', region='equatorial_pacific_LatExt2', + **kwargs['regridding']) + if debug is True: + dict_debug = { + 'axes1': '(mod) ' + str([ax.id for ax in map_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in map_obs.getAxisList()]), + 'shape1': '(mod) ' + str(map_mod.shape), + 'shape2': '(obs) ' + str(map_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after TwoVarRegrid: netcdf', 15, **dict_debug) + # samples + map_mod = Composite(map_mod, event_years_mod, kwargs['frequency']) + map_obs = Composite(map_obs, event_years_obs, kwargs['frequency']) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'nino_years': str(event_years_mod)} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'nino_years': str(event_years_obs)} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'nino_years': str(event_years_mod)} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'nino_years': str(event_years_obs)} + dict5 = {'metric_name': Name, 'metric_value_' + dataset2: compRmse, + 'metric_value_error_' + dataset2: compRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=sst_mod, var1_attributes=dict1, + var1_name='sst_lon__' + dataset1, var2=sst_obs, var2_attributes=dict2, + var2_name='sst_lon__' + dataset2, var3=map_mod, var3_attributes=dict3, + var3_name='sst_map__' + dataset1, var4=map_obs, var4_attributes=dict4, + var4_name='sst_map__' + dataset2, global_attributes=dict5) + del dict1, dict2, dict3, dict4, dict5, file_name + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(compRmse), 'line2': 'metric value_error: ' + str(compRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + NinoLonMetric = { + 'name': Name, 'value': compRmse, 'value_error': compRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'events_model': event_years_mod, + 'events_observations': event_years_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return NinoLonMetric + + +def NinoSlpMap(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + slpfilemod, slpnamemod, slpareafilemod, slpareanamemod, slplandmaskfilemod, slplandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, + slpfileobs, slpnameobs, slpareafileobs, slpareanameobs, slplandmaskfileobs, slplandmasknameobs, sstbox, + slpbox, event_definition, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, + netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The NinoSlpMap() function computes a sea level pressure anomalies composite of during the peak of El Nino events + SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + Then SSTA > 'threshold' during 'season' are considered as El Nino events + Then the SLPA at the peak of the event is composited for each selected event + First metric: rmse(observations vs model). + Second metric: correlation(observations vs model). + Third metric: std(model)/std(observations) + These metrics can be used to compute a Taylor diagram. + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST areacell + :param sstareanamemod: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafilemod' + :param sstlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST landmask + :param sstlandmasknamemod: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfilemod' + :param slpfilemod: string + path_to/filename of the file (NetCDF) of the modeled SLP + :param slpnamemod: string + name of SLP variable (slp) in 'slpfilemod' + :param slpareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SLP areacell + :param slpareanamemod: string, optional + name of areacell for the SLP variable (areacella, areacello,...) in 'slpareafilemod' + :param slplandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SLP landmask + :param slplandmasknamemod: string, optional + name of landmask for the SLP variable (sftlf,...) in 'slplandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST areacell + :param sstareanameobs: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafileobs' + :param sstlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST landmask + :param sstlandmasknameobs: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfileobs' + :param slpfileobs: string + path_to/filename of the file (NetCDF) of the observed SLP + :param slpnameobs: string + name of SLP variable (slp) in 'slpfileobs' + :param slpareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SLP areacell + :param slpareanameobs: string, optional + name of areacell for the SLP variable (areacella, areacello,...) in 'slpareafileobs' + :param slplandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SLP landmask + :param slplandmasknameobs: string, optional + name of landmask for the SLP variable (sftlf,...) in 'slplandmaskfileobs' + :param sstbox: string + name of box (e.g. 'global') for SST + :param slpbox: string + name of box (e.g. 'global') for SLP + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': 0.75} + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return NinoSlpMapMetric: dict + name, Rmse__value (rms [obs;model]), Rmse__value_error, Rmse__units, method, Corr__value (corr [obs;model]), + Corr__value_error, Corr__units, Std__value (std_model / std_obs), Std__value_error, Std__units, nyears_model, + nyears_observations, time_frequency, time_period_mod, time_period_obs, ref, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'El Nino SLPA Composite' + Method = 'Nino events = ' + region_ev + ' sstA > ' + str(threshold) + ' during ' + season_ev +\ + ', Nino SLPA Composited' + Units = '' if kwargs['normalization'] else 'hPa' + Ref = 'Using CDAT regridding, correlation (centered and biased), std (centered and biased) and ' + \ + 'rms (uncentered and biased) calculation' + metric = 'NinoSlpMap' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod1 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs1 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + slp_mod, slp_mod_areacell, keyerror_mod2 = Read_data_mask_area( + slpfilemod, slpnamemod, 'pressure', metric, slpbox, file_area=slpareafilemod, name_area=slpareanamemod, + file_mask=slplandmaskfilemod, name_mask=slplandmasknamemod, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + slp_obs, slp_obs_areacell, keyerror_obs2 = Read_data_mask_area( + slpfileobs, slpnameobs, 'pressure', metric, slpbox, file_area=slpareafileobs, name_area=slpareanameobs, + file_mask=slplandmaskfileobs, name_mask=slplandmasknameobs, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst_mod, slp_mod, keyerror_mod3 = CheckTime(sst_mod, slp_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs, slp_obs, keyerror_obs3 = CheckTime(sst_obs, slp_obs, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if (keyerror_mod1 is not None or keyerror_obs1 is not None or keyerror_mod2 is not None) or \ + (keyerror_obs2 is not None or keyerror_mod3 is not None or keyerror_obs3 is not None): + slpCorr, slpCorrErr, slpRmse, slpRmseErr, slpStd, slpStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + tmp = [keyerror_mod1, keyerror_mod2, keyerror_mod3, keyerror_obs1, keyerror_obs2, keyerror_obs3] + keyerror = add_up_errors(tmp) + else: + # ------------------------------------------------ + # 1. detect events + # ------------------------------------------------ + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, _, keyerror_mod1 = PreProcessTS( + sst_mod, '', areacell=mod_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + sst_obs, _, keyerror_obs1 = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + slp_mod, Method, keyerror_mod2 = PreProcessTS( + slp_mod, Method, areacell=slp_mod_areacell, compute_anom=False, region=slpbox, **kwargs) + slp_obs, _, keyerror_obs2 = PreProcessTS( + slp_obs, '', areacell=slp_obs_areacell, compute_anom=False, region=slpbox, **kwargs) + del mod_areacell, obs_areacell, slp_mod_areacell, slp_obs_areacell + if (keyerror_mod1 is not None or keyerror_obs1) or \ + (keyerror_mod2 is not None or keyerror_obs2): + slpCorr, slpCorrErr, slpRmse, slpRmseErr, slpStd, slpStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + if debug is True: + dict_debug = { + 'axes1': '(mod sst) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs sst) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'axes3': '(mod slp) ' + str([ax.id for ax in slp_mod.getAxisList()]), + 'axes4': '(obs slp) ' + str([ax.id for ax in slp_obs.getAxisList()]), + 'shape1': '(mod sst) ' + str(sst_mod.shape), 'shape2': '(obs sst) ' + str(sst_obs.shape), + 'shape3': '(mod slp) ' + str(slp_mod.shape), 'shape4': '(obs slp) ' + str(slp_obs.shape), + 'time1': '(mod sst) ' + str(TimeBounds(sst_mod)), 'time2': '(obs sst) ' + str(TimeBounds(sst_obs)), + 'time3': '(mod slp) ' + str(TimeBounds(slp_mod)), 'time4': '(obs slp) ' + str(TimeBounds(slp_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 SSTA > 'threshold' during 'season' are considered as El Nino events + # Lists event years + event_years_mod = DetectEvents(sst_mod, season_ev, threshold, normalization=normalize, nino=True) + event_years_obs = DetectEvents(sst_obs, season_ev, threshold, normalization=normalize, nino=True) + if debug is True: + dict_debug = {'nino1': '(mod) ' + str(event_years_mod), 'nino2': '(obs) ' + str(event_years_obs)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + + # ------------------------------------------------ + # 2. composite SLPA + # ------------------------------------------------ + # 2.2 Seasonal mean and anomalies + slp_mod = SeasonalMean(slp_mod, season_ev, compute_anom=True) * 1e-2 + slp_obs = SeasonalMean(slp_obs, season_ev, compute_anom=True) * 1e-2 + if debug is True: + dict_debug = {'axes1': '(mod slp) ' + str([ax.id for ax in slp_mod.getAxisList()]), + 'axes2': '(obs slp) ' + str([ax.id for ax in slp_obs.getAxisList()]), + 'shape1': '(mod slp) ' + str(slp_mod.shape), 'shape2': '(obs slp) ' + str(slp_obs.shape), + 'time1': '(mod slp) ' + str(TimeBounds(slp_mod)), + 'time2': '(obs slp) ' + str(TimeBounds(slp_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + slp_mod, slp_obs, Method = TwoVarRegrid(slp_mod, slp_obs, Method, region=slpbox, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod slp) ' + str([ax.id for ax in slp_mod.getAxisList()]), + 'axes2': '(obs slp) ' + str([ax.id for ax in slp_obs.getAxisList()]), + 'shape1': '(mod slp) ' + str(slp_mod.shape), + 'shape2': '(obs slp) ' + str(slp_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # 2.3 Composites + slp_mod = Composite(slp_mod, event_years_mod, kwargs['frequency']) + slp_obs = Composite(slp_obs, event_years_obs, kwargs['frequency']) + if debug is True: + dict_debug = {'axes1': '(mod slp) ' + str([ax.id for ax in slp_mod.getAxisList()]), + 'axes2': '(obs slp) ' + str([ax.id for ax in slp_obs.getAxisList()]), + 'shape1': '(mod slp) ' + str(slp_mod.shape), 'shape2': '(obs slp) ' + str(slp_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Composite', 15, **dict_debug) + + # mask Pacific + slp_mod, keyerror_mod = BasinMask( + slp_mod, 'pacific', box=slpbox, lat1=-15, lat2=15, latkey='between', debug=debug) + slp_obs, keyerror_obs = BasinMask( + slp_obs, 'pacific', box=slpbox, lat1=-15, lat2=15, latkey='between', debug=debug) + if keyerror_mod is not None or keyerror_obs is not None: + slpCorr, slpCorrErr, slpRmse, slpRmseErr, slpStd, slpStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in slp_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in slp_obs.getAxisList()]), + 'shape1': '(mod) ' + str(slp_mod.shape), 'shape2': '(obs) ' + str(slp_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after BasinMask', 15, **dict_debug) + + # Metric 1 + slpRmse, keyerror = RmsAxis(slp_mod, slp_obs, axis='xy', centered=centered_rmse, biased=biased_rmse) + slpRmseErr = None + # Metric 2 + slpCorr = float(Correlation(slp_mod, slp_obs, axis='xy', centered=1, biased=1)) + slpCorrErr = None + # Metric 3 + std_mod = Std(slp_mod, weights=None, axis='xy', centered=1, biased=1) + std_obs = Std(slp_obs, weights=None, axis='xy', centered=1, biased=1) + slpStd = float(std_mod) / float(std_obs) + slpStdErr = None + + # Dive down diagnostic + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + if netcdf is True: + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'nino_years': str(event_years_mod)} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'nino_years': str(event_years_obs)} + dict3 = {'metric_name': Name, 'metric_valueRMSE_' + dataset2: slpRmse, + 'metric_valueRMSE_error_' + dataset2: slpRmseErr, 'metric_valueCORR_' + dataset2: slpCorr, + 'metric_valueCORR_error_' + dataset2: slpCorrErr, 'metric_valueSTD_' + dataset2: slpStd, + 'metric_valueCORR_error_' + dataset2: slpStdErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf(file_name, var1=slp_mod, var1_attributes=dict1, var1_name='slp_map__' + dataset1, + var2=slp_obs, var2_attributes=dict2, var2_name='slp_map__' + dataset2, + global_attributes=dict3) + del dict1, dict2, dict3, file_name + if slpCorr is not None: + slpCorr = 1 - slpCorr + # Create output + NinoSlpMapMetric = { + 'name': Name, 'Rmse__value': slpRmse, 'Rmse__value_error': slpRmseErr, 'Rmse__units': Units, 'method': Method, + 'Corr__value': slpCorr, 'Corr__value_error': slpCorrErr, 'Corr__units': '', 'Std__value': slpStd, + 'Std__value_error': slpStdErr, 'Std__units': Units + ' / ' + Units, 'nyears_model': yearN_mod, + 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag, 'units': ''} + return NinoSlpMapMetric + + +def NinoSstDiversity(sstfile, sstname, sstareafile, sstareaname, sstlandmaskfile, sstlandmaskname, box, + event_definition, dataset='', debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The NinoSstDiversity() function computes a zonal composite of El Nino events during the peak of the event. + 1.) detect events + 1.1) SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + 1.2) SSTA > 'threshold' during 'season' are considered as El Nino events + 2.) diversity of the zonal location of the maximum SSTA + 2.1) zonal SSTA at the peak of the event is computed for each selected event + 2.2) find the zonal position of the maximum SSTA for each selected event + 2.3) compute the spread of the distribution + + Inputs: + ------ + :param sstfile: string + path_to/filename of the file (NetCDF) of the SST + :param sstname: string + name of SST variable (tos, ts) in 'sstfile' + :param sstareafile: string + path_to/filename of the file (NetCDF) of the areacell for SST + :param sstareaname: string + name of areacell variable (areacella, areacello) in 'sstareafile' + :param sstlandmaskfile: string + path_to/filename of the file (NetCDF) of the landmask for SST + :param sstlandmaskname: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfile' + :param box: string + name of box ('nino3') for SST + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': 0.75} + :param dataset: string, optional + name of current dataset (e.g., 'model', 'obs', ...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param treshold_ep_ev: float, optional + see EnsoToolsLib.percentage_val_eastward + longitude, in degree east, of the westward boundary of eastern Pacific event + default value is -140°E (i.e., 140°W) + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return NinoDivMetric: dict + name, value, value_error, units, method, nyears, events, time_frequency, time_period, ref, keyerror, + dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + my_thresh = 'std' if normalize is True else 'C' + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'treshold_ep_ev', + 'time_bounds'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = "El Nino Diversity (interquartile range)" + lat = ReferenceRegions(box)['latitude'] + lon = ReferenceRegions(box)['longitude'] + + Method = "Nino events = " + region_ev + " SSTA > " + str(threshold) + my_thresh + " during " + season_ev + \ + ", zonal SSTA (meridional averaged [" + str(lat[0]) + " ; " + str(lat[1]) + "]), the zonal SSTA " +\ + "maximum is located for each event, the diversity is the interquartile range (IQR = Q3 - Q1)" + Units = 'long' + Ref = 'Using CDAT regridding' + metric = 'NinoSstDiversity' + if metname == '': + metname = deepcopy(metric) + + # ------------------------------------------------ + # 1. detect events + # ------------------------------------------------ + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst, sst_areacell, keyerror1 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, region_ev, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + sstmap, sstmap_areacell, keyerror2 = Read_data_mask_area( + sstfile, sstname, 'temperature', metric, box, file_area=sstareafile, name_area=sstareaname, + file_mask=sstlandmaskfile, name_mask=sstlandmaskname, maskland=True, maskocean=False, debug=debug, **kwargs) + + # Number of years + yearN = int(round(sst.shape[0] / 12)) + + # Time period + actualtimebounds = TimeBounds(sst) + + keyerror = add_up_errors([keyerror1, keyerror2]) + if keyerror is not None: + dispersion1, dispersion1_err, dispersion2, dispersion2_err, event_years = None, None, None, None, None + dive_down_diag = None, None, {'value': None, 'axis': None} + else: + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + enso, _, keyerror1 = PreProcessTS( + sst, '', areacell=sst_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + sstmap, Method, keyerror2 = PreProcessTS( + sstmap, Method, areacell=sstmap_areacell, average=False, compute_anom=False, region=box, **kwargs) + del sst_areacell, sstmap_areacell + if keyerror1 is not None or keyerror2 is not None: + dispersion1, dispersion1_err, dispersion2, dispersion2_err, event_years = None, None, None, None, None + dive_down_diag = None, None, {'value': None, 'axis': None} + else: + if debug is True: + dict_debug = {'axes1': '(sst enso) ' + str([ax.id for ax in enso.getAxisList()]), + 'axes2': '(sst map) ' + str([ax.id for ax in sstmap.getAxisList()]), + 'shape1': '(sst enso) ' + str(enso.shape), 'shape2': '(sst map) ' + str(sstmap.shape), + 'time1': '(sst enso) ' + str(TimeBounds(enso)), + 'time2': '(sst map) ' + str(TimeBounds(sstmap))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 SSTA > 'threshold' during 'season' are considered as El Nino events + # Lists event years + event_years = DetectEvents(enso, season_ev, threshold, normalization=normalize, nino=True) + if debug is True: + dict_debug = {'nino1': 'nbr(' + str(len(event_years)) + '): ' + str(event_years)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + + # ------------------------------------------------ + # 2. diversity of the zonal location of the maximum SSTA + # ------------------------------------------------ + # 2.1 zonal SSTA at the peak of the event is computed for each selected event + # Seasonal mean + sstmap = SeasonalMean(sstmap, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sstmap.getAxisList()]), + 'shape1': '(sst) ' + str(sstmap.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', 'regridTool', + 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + sstmap = Regrid(sstmap, None, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sstmap.getAxisList()]), + 'shape1': '(sst) ' + str(sstmap.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Meridional average + sstlon, keyerror = AverageMeridional(sstmap) + if keyerror is not None: + dispersion1, dispersion1_err, dispersion2, dispersion2_err = None, None, None, None + dive_down_diag = None, None, {'value': None, 'axis': None} + else: + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sstlon.getAxisList()]), + 'shape1': '(sst) ' + str(sstlon.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + + if len(event_years) > 0: + # samples + sample = Event_selection(sstlon, kwargs['frequency'], list_event_years=event_years) + + # 2.2 find the zonal position of the maximum SSTA for each selected event + lon_sstmax = FindXYMinMaxInTs( + sample, return_val='maxi', smooth=True, axis=0, window=5, method='triangle') + if debug is True: + dict_debug = {'line1': 'longitude of the maximum SSTA: ' + str(lon_sstmax)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after FindXYMinMaxInTs', 15, **dict_debug) + + # 2.3 compute the spread of the distribution + dispersion1 = statistical_dispersion(lon_sstmax, method='IQR') + dispersion2 = statistical_dispersion(lon_sstmax, method='MAD') + dispersion1_err = None + dispersion2_err = None + else: + lon_sstmax = MyEmpty(sstlon[:5, 0], time=True, time_id='years') + dispersion1 = None + dispersion2 = None + dispersion1_err = None + dispersion2_err = None + + # Dive down diagnostic + dive_down_diag = {'value': ArrayToList(lon_sstmax), 'axis': list(lon_sstmax.getAxis(0)[:])} + if netcdf is True: + # nina events + nina_years = DetectEvents(enso, season_ev, -threshold, normalization=normalize, nino=False) + if len(event_years) > 0: + # samples + sample = Event_selection(sstlon, kwargs['frequency'], list_event_years=nina_years) + # 2.2 find the zonal position of the maximum SSTA for each selected event + lon_sstmin = FindXYMinMaxInTs( + sample, return_val='mini', smooth=True, axis=0, window=5, method='triangle') + if debug is True: + dict_debug = {'line1': 'longitude of the minimum SSTA: ' + str(lon_sstmax)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after FindXYMinMaxInTs', 15, **dict_debug) + # 2.3 compute the spread of the distribution + nina_disp1 = statistical_dispersion(lon_sstmin, method='IQR') + nina_disp2 = statistical_dispersion(lon_sstmin, method='MAD') + nina_disp1_err = None + nina_disp2_err = None + else: + lon_sstmin = MyEmpty(sstlon[:5, 0], time=True, time_id='years') + nina_disp1 = None + nina_disp2 = None + nina_disp1_err = None + nina_disp2_err = None + # save + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'nino_years': str(event_years), + 'description': + "Nino events = " + region_ev + " SSTA > " + str(threshold) + my_thresh + " during " + + season_ev + ", zonal SSTA " + "(meridional averaged [" + str(lat[0]) + " ; " + + str(lat[1]) + "]), the zonal SSTA maximum is located for each event, the diversity " + + "is the interquartile range (IQR = Q3 - Q1), second value is the median absolute " + + "deviation (MAD = median([Xi - median(tab)]))", + 'diagnostic_value_' + dataset: dispersion1, + 'diagnostic_value_error_' + dataset: dispersion1_err, + 'diagnostic_value2_' + dataset: dispersion2, + 'diagnostic_value_error2_' + dataset: dispersion2_err} + dict2 = {'units': Units, 'number_of_years_used': yearN, 'time_period': str(actualtimebounds), + 'nino_years': str(nina_years), + 'description': + "Nina events = " + region_ev + " SSTA < -" + str(threshold) + my_thresh + " during " + + season_ev + ", zonal SSTA " + "(meridional averaged [" + str(lat[0]) + " ; " + + str(lat[1]) + "]), the zonal SSTA minimum is located for each event, the diversity " + + "is the interquartile range (IQR = Q3 - Q1), second value is the median absolute " + + "deviation (MAD = median([Xi - median(tab)]))", + 'diagnostic_value_' + dataset: nina_disp1, + 'diagnostic_value_error_' + dataset: nina_disp1_err, + 'diagnostic_value2_' + dataset: nina_disp2, + 'diagnostic_value_error2_' + dataset: nina_disp2_err} + dict3 = {'metric_name': Name, 'metric_method': Method, 'metric_reference': Ref, + 'frequency': kwargs['frequency']} + SaveNetcdf(file_name, var1=lon_sstmax, var1_attributes=dict1, + var1_name='Nino_lon_pos_maxSSTA__' + dataset, var2=lon_sstmin, var2_attributes=dict2, + var2_name='Nina_lon_pos_minSSTA__' + dataset, global_attributes=dict3) + del dict1, dict2, dict3, file_name + # metric value + if debug is True: + dict_debug = {'line1': 'diagnostic (IQR) value: ' + str(dispersion1), + 'line2': 'diagnostic (IQR) value_error: ' + str(dispersion1_err), + 'line3': 'diagnostic (MAD) value: ' + str(dispersion2), + 'line4': 'diagnostic (MAD) value_error: ' + str(dispersion2_err)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + NinoDivMetric = { + 'name': Name, 'value': dispersion1, 'value_error': dispersion1_err, 'value2': dispersion2, + 'value_error2': dispersion2_err, 'units': Units, 'method': Method, 'nyears': yearN, 'events': event_years, + 'time_frequency': kwargs['frequency'], 'time_period': actualtimebounds, 'ref': Ref, 'keyerror': keyerror, + 'dive_down_diag': dive_down_diag} + return NinoDivMetric + + +def NinoSstMap(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, tsbox, + event_definition, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, + netcdf_name='', metname='', **kwargs): + """ + The NinoSstMap() function computes a surface temperature anomalies composite of during the peak of El Nino events + SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + Then SSTA > 'threshold' during 'season' are considered as El Nino events + Then the TSA at the peak of the event is composited for each selected event + First metric: rmse(observations vs model). + Second metric: correlation(observations vs model). + Third metric: std(model)/std(observations) + These metrics can be used to compute a Taylor diagram. + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST areacell + :param sstareanamemod: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafilemod' + :param sstlandmaskfilemod: string, optional + path_to/filename of the file (NetCDF) of the modeled SST landmask + :param sstlandmasknamemod: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST areacell + :param sstareanameobs: string, optional + name of areacell for the SST variable (areacella, areacello,...) in 'sstareafileobs' + :param sstlandmaskfileobs: string, optional + path_to/filename of the file (NetCDF) of the observed SST landmask + :param sstlandmasknameobs: string, optional + name of landmask for the SST variable (sftlf,...) in 'sstlandmaskfileobs' + :param sstbox: string + name of box (e.g. 'global') for SST + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': 0.75} + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return NinoSstMapMetric: dict + name, Rmse__value (rms [obs;model]), Rmse__value_error, Rmse__units, method, Corr__value (corr [obs;model]), + Corr__value_error, Corr__units, Std__value (std_model / std_obs), Std__value_error, Std__units, nyears_model, + nyears_observations, time_frequency, time_period_mod, time_period_obs, ref, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'El Nino TSA Composite' + Method = 'Nino events = ' + region_ev + ' sstA > ' + str(threshold) + ' during ' + season_ev +\ + ', Nino TSA Composited' + Units = '' if kwargs['normalization'] else 'mm/day' + Ref = 'Using CDAT regridding, correlation (centered and biased), std (centered and biased) and ' + \ + 'rms (uncentered and biased) calculation' + metric = 'NinoSstMap' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod1 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs1 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + tsmap_mod, tsmap_mod_areacell, keyerror_mod2 = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, tsbox, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + tsmap_obs, tsmap_obs_areacell, keyerror_obs2 = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, tsbox, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=False, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Checks if the same time period is used for both variables and if the minimum number of time steps is respected + sst_mod, tsmap_mod, keyerror_mod3 = CheckTime(sst_mod, tsmap_mod, metric_name=metric, debug=debug, **kwargs) + sst_obs, tsmap_obs, keyerror_obs3 = CheckTime(sst_obs, tsmap_obs, metric_name=metric, debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if (keyerror_mod1 is not None or keyerror_obs1 is not None or keyerror_mod2 is not None) or \ + (keyerror_obs2 is not None or keyerror_mod3 is not None or keyerror_obs3 is not None): + tsCorr, tsCorrErr, tsRmse, tsRmseErr, tsStd, tsStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + tmp = [keyerror_mod1, keyerror_mod2, keyerror_mod3, keyerror_obs1, keyerror_obs2, keyerror_obs3] + keyerror = add_up_errors(tmp) + else: + # ------------------------------------------------ + # 1. detect events + # ------------------------------------------------ + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, _, keyerror_mod1 = PreProcessTS( + sst_mod, '', areacell=mod_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + sst_obs, _, keyerror_obs1 = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + tsmap_mod, Method, keyerror_mod2 = PreProcessTS( + tsmap_mod, Method, areacell=tsmap_mod_areacell, compute_anom=False, region=tsbox, **kwargs) + tsmap_obs, _, keyerror_obs2 = PreProcessTS( + tsmap_obs, '', areacell=tsmap_obs_areacell, compute_anom=False, region=tsbox, **kwargs) + del mod_areacell, obs_areacell, tsmap_mod_areacell, tsmap_obs_areacell + if (keyerror_mod1 is not None or keyerror_obs1 is not None) or \ + (keyerror_obs2 is not None or keyerror_mod2 is not None): + tsCorr, tsCorrErr, tsRmse, tsRmseErr, tsStd, tsStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod1, keyerror_mod2, keyerror_obs1, keyerror_obs2]) + else: + if debug is True: + dict_debug = { + 'axes1': '(mod sst) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs sst) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'axes3': '(mod ts) ' + str([ax.id for ax in tsmap_mod.getAxisList()]), + 'axes4': '(obs ts) ' + str([ax.id for ax in tsmap_obs.getAxisList()]), + 'shape1': '(mod sst) ' + str(sst_mod.shape), 'shape2': '(obs sst) ' + str(sst_obs.shape), + 'shape3': '(mod ts) ' + str(tsmap_mod.shape), 'shape4': '(obs ts) ' + str(tsmap_obs.shape), + 'time1': '(mod sst) ' + str(TimeBounds(sst_mod)), 'time2': '(obs sst) ' + str(TimeBounds(sst_obs)), + 'time3': '(mod ts) ' + str(TimeBounds(tsmap_mod)), + 'time4': '(obs ts) ' + str(TimeBounds(tsmap_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 SSTA > 'threshold' during 'season' are considered as El Nino events + # Lists event years + event_years_mod = DetectEvents(sst_mod, season_ev, threshold, normalization=normalize, nino=True) + event_years_obs = DetectEvents(sst_obs, season_ev, threshold, normalization=normalize, nino=True) + if debug is True: + dict_debug = {'nino1': '(mod) ' + str(event_years_mod), 'nino2': '(obs) ' + str(event_years_obs)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + + # ------------------------------------------------ + # 2. composite TSA + # ------------------------------------------------ + # 2.2 Seasonal mean and anomalies + tsmap_mod = SeasonalMean(tsmap_mod, season_ev, compute_anom=True) + tsmap_obs = SeasonalMean(tsmap_obs, season_ev, compute_anom=True) + if debug is True: + dict_debug = {'axes1': '(mod ts) ' + str([ax.id for ax in tsmap_mod.getAxisList()]), + 'axes2': '(obs ts) ' + str([ax.id for ax in tsmap_obs.getAxisList()]), + 'shape1': '(mod ts) ' + str(tsmap_mod.shape), + 'shape2': '(obs ts) ' + str(tsmap_obs.shape), + 'time1': '(mod ts) ' + str(TimeBounds(tsmap_mod)), + 'time2': '(obs ts) ' + str(TimeBounds(tsmap_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after SeasonalMean', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + tsmap_mod, tsmap_obs, Method = TwoVarRegrid( + tsmap_mod, tsmap_obs, Method, region=tsbox, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod ts) ' + str([ax.id for ax in tsmap_mod.getAxisList()]), + 'axes2': '(obs ts) ' + str([ax.id for ax in tsmap_obs.getAxisList()]), + 'shape1': '(mod ts) ' + str(tsmap_mod.shape), + 'shape2': '(obs ts) ' + str(tsmap_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # 2.3 Composites + tsmap_mod = Composite(tsmap_mod, event_years_mod, kwargs['frequency']) + tsmap_obs = Composite(tsmap_obs, event_years_obs, kwargs['frequency']) + if debug is True: + dict_debug = {'axes1': '(mod ts) ' + str([ax.id for ax in tsmap_mod.getAxisList()]), + 'axes2': '(obs ts) ' + str([ax.id for ax in tsmap_obs.getAxisList()]), + 'shape1': '(mod ts) ' + str(tsmap_mod.shape), + 'shape2': '(obs ts) ' + str(tsmap_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Composite', 15, **dict_debug) + + # mask Pacific + tsmap_mod, keyerror_mod = BasinMask( + tsmap_mod, 'pacific', box=tsbox, lat1=-15, lat2=15, latkey='between', debug=debug) + tsmap_obs, keyerror_obs = BasinMask( + tsmap_obs, 'pacific', box=tsbox, lat1=-15, lat2=15, latkey='between', debug=debug) + if keyerror_mod is not None or keyerror_obs is not None: + tsCorr, tsCorrErr, tsRmse, tsRmseErr, tsStd, tsStdErr = None, None, None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod ts) ' + str([ax.id for ax in tsmap_mod.getAxisList()]), + 'axes2': '(obs ts) ' + str([ax.id for ax in tsmap_obs.getAxisList()]), + 'shape1': '(mod ts) ' + str(tsmap_mod.shape), + 'shape2': '(obs ts) ' + str(tsmap_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after BasinMask', 15, **dict_debug) + + # Metric 1 + tsRmse, keyerror = RmsAxis(tsmap_mod, tsmap_obs, axis='xy', centered=centered_rmse, biased=biased_rmse) + tsRmseErr = None + # Metric 2 + tsCorr = float(Correlation(tsmap_mod, tsmap_obs, axis='xy', centered=1, biased=1)) + tsCorrErr = None + # Metric 3 + std_mod = Std(tsmap_mod, weights=None, axis='xy', centered=1, biased=1) + std_obs = Std(tsmap_obs, weights=None, axis='xy', centered=1, biased=1) + tsStd = float(std_mod) / float(std_obs) + tsStdErr = None + + # Dive down diagnostic + dive_down_diag = {'model': None, 'observations': None, 'axisLat': None, 'axisLon': None} + if netcdf is True: + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), 'nino_years': str(event_years_mod)} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), 'nino_years': str(event_years_obs)} + dict3 = {'metric_name': Name, 'metric_valueRMSE_' + dataset2: tsRmse, + 'metric_valueRMSE_error_' + dataset2: tsRmseErr, 'metric_valueCORR_' + dataset2: tsCorr, + 'metric_valueCORR_error_' + dataset2: tsCorrErr, 'metric_valueSTD_' + dataset2: tsStd, + 'metric_valueCORR_error_' + dataset2: tsStdErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf(file_name, var1=tsmap_mod, var1_attributes=dict1, var1_name='ts_map__' + dataset1, + var2=tsmap_obs, var2_attributes=dict2, var2_name='ts_map__' + dataset2, + global_attributes=dict3) + del dict1, dict2, dict3, file_name + if tsCorr is not None: + tsCorr = 1 - tsCorr + # Create output + NinoSstMapMetric = { + 'name': Name, 'Rmse__value': tsRmse, 'Rmse__value_error': tsRmseErr, 'Rmse__units': Units, 'method': Method, + 'Corr__value': tsCorr, 'Corr__value_error': tsCorrErr, 'Corr__units': '', 'Std__value': tsStd, + 'Std__value_error': tsStdErr, 'Std__units': Units + ' / ' + Units, 'nyears_model': yearN_mod, + 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag, 'units': ''} + return NinoSstMapMetric + + +def NinoSstTsRmse(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, + box, event_definition, nbr_years_window, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', + debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The NinoSstTsRmse() function computes a time composite of El Nino events + SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + Then SSTA > 'threshold' during 'season' are considered as El Nino events + Then a 'nbr_years_window' long time series centered on selected events is composited for each selected event + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for SST + :param sstareanamemod: string + name of areacell variable (areacella, areacello) in 'sstareafilemod' + :param sstlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for SST + :param sstlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for SST + :param sstareanameobs: string + name of areacell variable (areacella, areacello) in 'sstareafileobs' + :param sstlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for SST + :param sstlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfileobs' + :param box: string + name of box ('nino3') for SST + :param event_definition: dict + dictionary providing the necessary information to detect ENSO events (region_ev, season_ev, threshold) + e.g., event_definition = {'region_ev': 'nino3', 'season_ev': 'DEC', 'threshold': 0.75} + :param nbr_years_window: integer + number of years used to compute the composite (e.g. 6) + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'HadISST',...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return NinoTsMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, events_model, events_observations, + time_frequency, time_period_model, time_period_observations, ref, keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + """ + # setting variables + region_ev = event_definition['region_ev'] + season_ev = event_definition['season_ev'] + threshold = event_definition['threshold'] + normalize = event_definition['normalization'] + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'smoothing', 'time_bounds_mod', + 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'El Nino Composite Time Series' + Method = 'Nino events = ' + region_ev + ' sstA > ' + str(threshold) + ' during ' + season_ev + ', time series of '\ + + str(nbr_years_window) + ' years (centered on events)' + Units = '' if kwargs['normalization'] else 'C' + Ref = 'Using CDAT rms (uncentered and biased) calculation' + metric = 'NinoSstTsRmse' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, region_ev, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, region_ev, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + compRmse, compRmseErr, event_years_mod, event_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # 1.1 SSTA averaged in 'region_ev' are normalized / detrended / smoothed (running average) if applicable + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_mod, _, keyerror_mod = PreProcessTS( + sst_mod, '', areacell=mod_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS( + sst_obs, '', areacell=obs_areacell, average='horizontal', compute_anom=False, region=region_ev, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + compRmse, compRmseErr, event_years_mod, event_years_obs = None, None, None, None + dive_down_diag = {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sst_mod)), 'time2': '(obs) ' + str(TimeBounds(sst_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # 1.2 SSTA > 'threshold' during 'season' are considered as El Nino events + # Lists event years + event_years_mod = DetectEvents(sst_mod, season_ev, threshold, normalization=normalize, nino=True) + event_years_obs = DetectEvents(sst_obs, season_ev, threshold, normalization=normalize, nino=True) + if debug is True: + dict_debug = {'nino1': '(mod) ' + str(event_years_mod), 'nino2': '(obs) ' + str(event_years_obs)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after DetectEvents', 15, **dict_debug) + + # ------------------------------------------------ + # 2. temporal composite of SSTA + # ------------------------------------------------ + # interannual anomalies + sst_mod = ComputeInterannualAnomalies(sst_mod) + sst_obs = ComputeInterannualAnomalies(sst_obs) + + # composites + composite_mod = Composite(sst_mod, event_years_mod, kwargs['frequency'], nbr_years_window=nbr_years_window) + composite_obs = Composite(sst_obs, event_years_obs, kwargs['frequency'], nbr_years_window=nbr_years_window) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in composite_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in composite_obs.getAxisList()]), + 'shape1': '(mod) ' + str(composite_mod.shape), + 'shape2': '(obs) ' + str(composite_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Composite', 15, **dict_debug) + + # Computes the root mean square difference + compRmse, keyerror = RmsAxis( + composite_mod, composite_obs, axis=0, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + compRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(composite_mod), 'observations': ArrayToList(composite_obs), + 'axis': list(composite_mod.getAxis(0)[:])} + if netcdf is True: + # Read file and select the right region + sst_hov_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, 'equatorial_pacific', file_area=sstareafilemod, + name_area=sstareanamemod, file_mask=sstlandmaskfilemod, name_mask=sstlandmaskfilemod, maskland=True, + maskocean=False, time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_hov_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, 'equatorial_pacific', file_area=sstareafileobs, + name_area=sstareanameobs, file_mask=sstlandmaskfileobs, name_mask=sstlandmaskfileobs, maskland=True, + maskocean=False, time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + sst_hov_mod, _, keyerror_mod = PreProcessTS( + sst_hov_mod, Method, areacell=mod_areacell, average=False, compute_anom=True, + region="equatorial_pacific", **kwargs) + sst_hov_obs, _, keyerror_obs = PreProcessTS( + sst_hov_obs, '', areacell=obs_areacell, average=False, compute_anom=True, + region="equatorial_pacific", **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_hov_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_hov_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_hov_mod.shape), + 'shape2': '(obs) ' + str(sst_hov_obs.shape), + 'time1': '(mod) ' + str(TimeBounds(sst_hov_mod)), + 'time2': '(obs) ' + str(TimeBounds(sst_hov_obs))} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + # Regridding + if 'regridding' not in list(kwargs.keys()): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'} + else: + if not isinstance(kwargs['regridding'], dict): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + sst_hov_mod, sst_hov_obs, Method = TwoVarRegrid( + sst_hov_mod, sst_hov_obs, Method, region='equatorial_pacific', **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_hov_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_hov_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_hov_mod.shape), + 'shape2': '(obs) ' + str(sst_hov_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + # Meridional average + sst_hov_mod, keyerror_mod = AverageMeridional(sst_hov_mod) + sst_hov_obs, keyerror_obs = AverageMeridional(sst_hov_obs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_hov_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_hov_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_hov_mod.shape), + 'shape2': '(obs) ' + str(sst_hov_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + # samples + sst_hov_mod = Composite( + sst_hov_mod, event_years_mod, kwargs['frequency'], nbr_years_window=nbr_years_window) + sst_hov_obs = Composite( + sst_hov_obs, event_years_obs, kwargs['frequency'], nbr_years_window=nbr_years_window) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_hov_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_hov_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_hov_mod.shape), + 'shape2': '(obs) ' + str(sst_hov_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Composite', 15, **dict_debug) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), 'nino_years': str(event_years_mod), + 'description': "time series of " + box + " sstA centered on El Nino peak"} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), 'nino_years': str(event_years_obs), + 'description': "time series of " + box + " sstA centered on El Nino peak"} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), 'nino_years': str(event_years_mod), + 'description': "zonal monthly of equatorial_pacific sstA centered on El Nino peak"} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), 'nino_years': str(event_years_obs), + 'description': "zonal monthly of equatorial_pacific sstA centered on El Nino peak"} + dict5 = {'metric_name': Name, 'metric_value_' + dataset2: compRmse, + 'metric_value_error_' + dataset2: compRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=composite_mod, var1_attributes=dict1, var1_name='sst_ts__' + dataset1, + var2=composite_obs, var2_attributes=dict2, var2_name='sst_ts__' + dataset2, + var3=sst_hov_mod, var3_attributes=dict3, var3_name='sst_hov__' + dataset1, + var4=sst_hov_obs, var4_attributes=dict4, var4_name='sst_hov__' + dataset2, + global_attributes=dict5) + del dict1, dict2, dict3, dict4, dict5, file_name + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(compRmse), 'line2': 'metric value_error: ' + str(compRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + NinoTsMetric = { + 'name': Name, 'value': compRmse, 'value_error': compRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'events_model': event_years_mod, + 'events_observations': event_years_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return NinoTsMetric + + +def SeasonalPrLatRmse(prfilemod, prnamemod, prareafilemod, prareanamemod, prlandmaskfilemod, prlandmasknamemod, + prfileobs, prnameobs, prareafileobs, prareanameobs, prlandmaskfileobs, prlandmasknameobs, + box, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, + netcdf_name='', metname='', **kwargs): + """ + The SeasonalPrLatRmse() function computes the climatological (12 months) PR meridional (latitude) standard + deviation root mean square error (RMSE) in a 'box' (usually the equatorial_pacific_LatExt) + + Inputs: + ------ + :param prfilemod: string + path_to/filename of the file (NetCDF) of the modeled PR + :param prnamemod: string + name of PR variable (pr) in 'prfilemod' + :param prareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for PR + :param prareanamemod: string + name of areacell variable (areacella, areacello) in 'prareafilemod' + :param prlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for PR + :param prlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'prlandmaskfilemod' + :param prfileobs: string + path_to/filename of the file (NetCDF) of the observed PR + :param prnameobs: string + name of PR variable (pr, precip) in 'prfileobs' + :param prareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for PR + :param prareanameobs: string + name of areacell variable (areacella, areacello) in 'prareafileobs' + :param prlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for PR + :param prlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'prlandmaskfileobs' + :param box: string + name of box ('equatorial_pacific_LatExt') for PR + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'Tropflux',...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param regridding: dict, optional + see EnsoUvcdatToolsLib.TwoVarRegrid and EnsoUvcdatToolsLib.Regrid for options + the aim if to specify if the model is regridded toward the observations or vice versa, of if both model and + observations are regridded toward another grid + interpolation tool and method can be specified + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled PR file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed PR file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return LatRmseMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_model, + time_period_observations, ref + + Method: + ------- + uses tools from uvcdat library + + Notes: + ----- + TODO: add error calculation to rmse (function of nyears) + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'regridding', 'smoothing', + 'time_bounds_mod', 'time_bounds_obs'] + for arg in needed_kwarg: + try: kwargs[arg] + except: kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'pr meridional seasonality RMSE' + Units = 'mm/day' + Method = 'Meridional root mean square error of ' + box + ' climatological pr STD' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = 'SeasonalPrLatRmse' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + pr_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + prfilemod, prnamemod, 'precipitations', metric, box, file_area=prareafilemod, name_area=prareanamemod, + file_mask=prlandmaskfilemod, name_mask=prlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + pr_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + prfileobs, prnameobs, 'precipitations', metric, box, file_area=prareafileobs, name_area=prareanameobs, + file_mask=prlandmaskfileobs, name_mask=prlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(pr_mod.shape[0] / 12)) + yearN_obs = int(round(pr_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(pr_mod) + actualtimebounds_obs = TimeBounds(pr_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + prRmse, prRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + # here only the detrending (if applicable) and time averaging are performed + pr_mod, Method, keyerror_mod = PreProcessTS(pr_mod, Method, compute_sea_cycle=True, region=box, **kwargs) + pr_obs, _, keyerror_obs = PreProcessTS(pr_obs, '', compute_sea_cycle=True, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + prRmse, prRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # standard deviation computation + prStd_mod = Std(pr_mod) + prStd_obs = Std(pr_obs) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in prStd_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in prStd_obs.getAxisList()]), + 'shape1': '(mod) ' + str(prStd_mod.shape), 'shape2': '(obs) ' + str(prStd_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Std', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + prStd_mod, prStd_obs, Method = TwoVarRegrid( + prStd_mod, prStd_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in prStd_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in prStd_obs.getAxisList()]), + 'shape1': '(mod) ' + str(prStd_mod.shape), + 'shape2': '(obs) ' + str(prStd_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Zonal average + prStdLat_mod, keyerror_mod = AverageZonal(prStd_mod) + prStdLat_obs, keyerror_obs = AverageZonal(prStd_obs) + if keyerror_mod is not None or keyerror_obs is not None: + prRmse, prRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in prStdLat_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in prStdLat_obs.getAxisList()]), + 'shape1': '(mod) ' + str(prStdLat_mod.shape), + 'shape2': '(obs) ' + str(prStdLat_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageZonal', 15, **dict_debug) + + # Computes the root mean square difference + prRmse, keyerror = RmsMeridional( + prStdLat_mod, prStdLat_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + prRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(prStdLat_mod), 'observations': ArrayToList(prStdLat_obs), + 'axis': list(prStdLat_mod.getAxis(0)[:])} + if netcdf is True: + # Read file and select the right region + prMap_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + prfilemod, prnamemod, 'precipitations', metric, 'equatorial_pacific_LatExt2', + file_area=prareafilemod, name_area=prareanamemod, file_mask=prlandmaskfilemod, + name_mask=prlandmaskfilemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + prMap_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + prfileobs, prnameobs, 'precipitations', metric, 'equatorial_pacific_LatExt2', + file_area=prareafileobs, name_area=prareanameobs, file_mask=prlandmaskfileobs, + name_mask=prlandmaskfileobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Preprocess pr (computes anomalies, normalizes, detrends TS, smoothes TS, ...) + prMap_mod, _, keyerror_mod = PreProcessTS(prMap_mod, '', compute_sea_cycle=True, + region="equatorial_pacific_LatExt2", **kwargs) + prMap_obs, _, keyerror_obs = PreProcessTS(prMap_obs, '', compute_sea_cycle=True, + region="equatorial_pacific_LatExt2", **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in prMap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in prMap_obs.getAxisList()]), + 'shape1': '(mod) ' + str(prMap_mod.shape), + 'shape2': '(obs) ' + str(prMap_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + # standard deviation computation + prMap_mod = Std(prMap_mod) + prMap_obs = Std(prMap_obs) + # Regridding + if 'regridding' not in list(kwargs.keys()): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + else: + if not isinstance(kwargs['regridding'], dict): + kwargs['regridding'] = { + 'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + prMap_mod, prMap_obs, _ = TwoVarRegrid( + prMap_mod, prMap_obs, '', region='equatorial_pacific_LatExt2', **kwargs['regridding']) + pr_mod, pr_obs, _ = TwoVarRegrid(pr_mod, pr_obs, '', region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in prMap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in prMap_obs.getAxisList()]), + 'axes3': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes4': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(prMap_mod.shape), + 'shape2': '(obs) ' + str(prMap_obs.shape), + 'shape3': '(mod) ' + str(pr_mod.shape), + 'shape4': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + # Zonal average + pr_mod, keyerror_mod = AverageZonal(pr_mod) + pr_obs, keyerror_obs = AverageZonal(pr_obs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), + 'shape2': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageZonal', 15, **dict_debug) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "meridional climatological standard deviation of EEP pr"} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "meridional climatological standard deviation of EEP pr"} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "climatological standard deviation of equatorial_pacific pr"} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "climatological standard deviation of equatorial_pacific pr"} + dict5 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "meridional mean annual cycle of EEP pr"} + dict6 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "meridional mean annual cycle of EEP pr"} + dict7 = {'metric_name': Name, 'metric_value_' + dataset2: prRmse, + 'metric_value_error_' + dataset2: prRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=prStdLat_mod, var1_attributes=dict1, + var1_name='pr_lat__' + dataset1, var2=prStdLat_obs, var2_attributes=dict2, + var2_name='pr_lat__' + dataset2, var3=prMap_mod, var3_attributes=dict3, + var3_name='pr_map__' + dataset1, var4=prMap_obs, var4_attributes=dict4, + var4_name='pr_map__' + dataset2, var5=pr_mod, var5_attributes=dict5, + var5_name='prMac_hov__' + dataset1, var6=pr_obs, var6_attributes=dict6, + var6_name='prMac_hov__' + dataset2, global_attributes=dict7) + del dict1, dict2, dict3, dict4, dict5, dict6, dict7, file_name + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(prRmse), 'line2': 'metric value_error: ' + str(prRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + LatRmseMetric = { + 'name': Name, 'value': prRmse, 'value_error': prRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return LatRmseMetric + + +def SeasonalPrLonRmse(prfilemod, prnamemod, prareafilemod, prareanamemod, prlandmaskfilemod, prlandmasknamemod, + prfileobs, prnameobs, prareafileobs, prareanameobs, prlandmaskfileobs, prlandmasknameobs, + box, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, + netcdf_name='', metname='', **kwargs): + """ + The SeasonalPrLonRmse() function computes the climatological (12 months) PR zonal (longitude) standard + deviation root mean square error (RMSE) in a 'box' (usually the Equatorial Pacific) + + Inputs: + ------ + :param prfilemod: string + path_to/filename of the file (NetCDF) of the modeled PR + :param prnamemod: string + name of PR variable (pr) in 'prfilemod' + :param prareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for PR + :param prareanamemod: string + name of areacell variable (areacella, areacello) in 'prareafilemod' + :param prlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for PR + :param prlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'prlandmaskfilemod' + :param prfileobs: string + path_to/filename of the file (NetCDF) of the observed PR + :param prnameobs: string + name of PR variable (pr, precip) in 'prfileobs' + :param prareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for PR + :param prareanameobs: string + name of areacell variable (areacella, areacello) in 'prareafileobs' + :param prlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for PR + :param prlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'prlandmaskfileobs' + :param box: string + name of box ('equatorial_pacific') for PR + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'Tropflux',...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param regridding: dict, optional + see EnsoUvcdatToolsLib.TwoVarRegrid and EnsoUvcdatToolsLib.Regrid for options + the aim if to specify if the model is regridded toward the observations or vice versa, of if both model and + observations are regridded toward another grid + interpolation tool and method can be specified + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled PR file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed PR file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return LonRmseMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_model, + time_period_observations, ref, keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + Notes: + ----- + TODO: add error calculation to rmse (function of nyears) + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'regridding', 'smoothing', + 'time_bounds_mod', 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'pr zonal seasonality RMSE' + Units = 'mm/day' + Method = 'Zonal root mean square error of ' + box + ' climatological pr STD' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = 'SeasonalPrLonRmse' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + pr_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + prfilemod, prnamemod, 'precipitations', metric, box, file_area=prareafilemod, name_area=prareanamemod, + file_mask=prlandmaskfilemod, name_mask=prlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + pr_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + prfileobs, prnameobs, 'precipitations', metric, box, file_area=prareafileobs, name_area=prareanameobs, + file_mask=prlandmaskfileobs, name_mask=prlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(pr_mod.shape[0] / 12)) + yearN_obs = int(round(pr_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(pr_mod) + actualtimebounds_obs = TimeBounds(pr_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + prRmse, prRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + # here only the detrending (if applicable) and time averaging are performed + pr_mod, Method, keyerror_mod = PreProcessTS(pr_mod, Method, compute_sea_cycle=True, region=box, **kwargs) + pr_obs, _, keyerror_obs = PreProcessTS(pr_obs, '', compute_sea_cycle=True, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + prRmse, prRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), 'shape2': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # standard deviation computation + prStd_mod = Std(pr_mod) + prStd_obs = Std(pr_obs) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in prStd_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in prStd_obs.getAxisList()]), + 'shape1': '(mod) ' + str(prStd_mod.shape), 'shape2': '(obs) ' + str(prStd_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Std', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + prStd_mod, prStd_obs, Method = TwoVarRegrid( + prStd_mod, prStd_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in prStd_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in prStd_obs.getAxisList()]), + 'shape1': '(mod) ' + str(prStd_mod.shape), + 'shape2': '(obs) ' + str(prStd_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Meridional average + prStdLon_mod, keyerror_mod = AverageMeridional(prStd_mod) + prStdLon_obs, keyerror_obs = AverageMeridional(prStd_obs) + if keyerror_mod is not None or keyerror_obs is not None: + prRmse, prRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in prStdLon_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in prStdLon_obs.getAxisList()]), + 'shape1': '(mod) ' + str(prStdLon_mod.shape), + 'shape2': '(obs) ' + str(prStdLon_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + + # Computes the root mean square difference + prRmse, keyerror = RmsZonal(prStdLon_mod, prStdLon_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + prRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(prStdLon_mod), 'observations': ArrayToList(prStdLon_obs), + 'axis': list(prStdLon_mod.getAxis(0)[:])} + if netcdf is True: + # Read file and select the right region + prMap_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + prfilemod, prnamemod, 'precipitations', metric, 'equatorial_pacific_LatExt2', + file_area=prareafilemod, name_area=prareanamemod, file_mask=prlandmaskfilemod, + name_mask=prlandmaskfilemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + prMap_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + prfileobs, prnameobs, 'precipitations', metric, 'equatorial_pacific_LatExt2', + file_area=prareafileobs, name_area=prareanameobs, file_mask=prlandmaskfileobs, + name_mask=prlandmaskfileobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Preprocess pr (computes anomalies, normalizes, detrends TS, smoothes TS, ...) + prMap_mod, _, keyerror_mod = PreProcessTS( + prMap_mod, '', compute_sea_cycle=True, region="equatorial_pacific_LatExt2", **kwargs) + prMap_obs, _, keyerror_obs = PreProcessTS( + prMap_obs, '', compute_sea_cycle=True, region="equatorial_pacific_LatExt2", **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in prMap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in prMap_obs.getAxisList()]), + 'shape1': '(mod) ' + str(prMap_mod.shape), + 'shape2': '(obs) ' + str(prMap_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + # standard deviation computation + prMap_mod = Std(prMap_mod) + prMap_obs = Std(prMap_obs) + # Regridding + if 'regridding' not in list(kwargs.keys()): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + else: + if not isinstance(kwargs['regridding'], dict): + kwargs['regridding'] = { + 'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + prMap_mod, prMap_obs, _ = TwoVarRegrid( + prMap_mod, prMap_obs, '', region='equatorial_pacific_LatExt2', **kwargs['regridding']) + pr_mod, pr_obs, _ = TwoVarRegrid(pr_mod, pr_obs, '', region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in prMap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in prMap_obs.getAxisList()]), + 'axes3': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes4': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(prMap_mod.shape), + 'shape2': '(obs) ' + str(prMap_obs.shape), + 'shape3': '(mod) ' + str(pr_mod.shape), + 'shape4': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + # Meridional average + pr_mod, keyerror_mod = AverageMeridional(pr_mod) + pr_obs, keyerror_obs = AverageMeridional(pr_obs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in pr_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in pr_obs.getAxisList()]), + 'shape1': '(mod) ' + str(pr_mod.shape), + 'shape2': '(obs) ' + str(pr_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after AverageMeridional', 15, **dict_debug) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = { + 'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "zonal climatological standard deviation of equatorial_pacific pr"} + dict2 = { + 'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "zonal climatological standard deviation of equatorial_pacific pr"} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "climatological standard deviation of equatorial_pacific pr"} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "climatological standard deviation of equatorial_pacific pr"} + dict5 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "zonal mean annual cycle of equatorial_pacific pr"} + dict6 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "zonal mean annual cycle of equatorial_pacific pr"} + dict7 = {'metric_name': Name, 'metric_value_' + dataset2: prRmse, + 'metric_value_error_' + dataset2: prRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=prStdLon_mod, var1_attributes=dict1, + var1_name='pr_lon__' + dataset1, var2=prStdLon_obs, var2_attributes=dict2, + var2_name='pr_lon__' + dataset2, var3=prMap_mod, var3_attributes=dict3, + var3_name='pr_map__' + dataset1, var4=prMap_obs, var4_attributes=dict4, + var4_name='pr_map__' + dataset2, var5=pr_mod, var5_attributes=dict5, + var5_name='prMac_hov__' + dataset1, var6=pr_obs, var6_attributes=dict6, + var6_name='prMac_hov__' + dataset2, global_attributes=dict7) + del dict1, dict2, dict3, dict4, dict5, dict6, dict7, file_name + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(prRmse), 'line2': 'metric value_error: ' + str(prRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + LonRmseMetric = { + 'name': Name, 'value': prRmse, 'value_error': prRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return LonRmseMetric + + +def SeasonalSshLatRmse(sshfilemod, sshnamemod, sshareafilemod, sshareanamemod, sshlandmaskfilemod, sshlandmasknamemod, + sshfileobs, sshnameobs, sshareafileobs, sshareanameobs, sshlandmaskfileobs, sshlandmasknameobs, + box, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, + netcdf_name='', metname='', **kwargs): + """ + The SeasonalSshLatRmse() function computes the climatological (12 months) SSH meridional (latitude) standard + deviation root mean square error (RMSE) in a 'box' (usually the nino3_LatExt) + + Inputs: + ------ + :param sshfilemod: string + path_to/filename of the file (NetCDF) of the modeled SSH + :param sshnamemod: string + name of SSH variable (ssh, sshg, zos) in 'sshfilemod' + :param sshareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for SSH + :param sshareanamemod: string + name of areacell variable (areacella, areacello) in 'sshareafilemod' + :param sshlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for SSH + :param sshlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'sshlandmaskfilemod' + :param sshfileobs: string + path_to/filename of the file (NetCDF) of the observed SSH + :param sshnameobs: string + name of SSH variable (ssh, sshg, zos) in 'sshfileobs' + :param sshareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for SSH + :param sshareanameobs: string + name of areacell variable (areacella, areacello) in 'sshareafileobs' + :param sshlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for SSH + :param sshlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'sshlandmaskfileobs' + :param box: string + name of box ('equatorial_pacific_LatExt') for SSH + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'AVISO',...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param regridding: dict, optional + see EnsoUvcdatToolsLib.TwoVarRegrid and EnsoUvcdatToolsLib.Regrid for options + the aim if to specify if the model is regridded toward the observations or vice versa, of if both model and + observations are regridded toward another grid + interpolation tool and method can be specified + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SSH file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SSH file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return LatRmseMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_model, + time_period_observations, ref + + Method: + ------- + uses tools from uvcdat library + + Notes: + ----- + TODO: add error calculation to rmse (function of nyears) + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'regridding', 'smoothing', + 'time_bounds_mod', 'time_bounds_obs'] + for arg in needed_kwarg: + try: kwargs[arg] + except: kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'ssh meridional seasonality RMSE' + Units = 'cm' + Method = 'Meridional root mean square error of ' + box + ' climatological ssh STD' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = 'SeasonalSshLatRmse' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + ssh_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sshfilemod, sshnamemod, 'sea surface height', metric, box, file_area=sshareafilemod, name_area=sshareanamemod, + file_mask=sshlandmaskfilemod, name_mask=sshlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + ssh_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sshfileobs, sshnameobs, 'sea surface height', metric, box, file_area=sshareafileobs, name_area=sshareanameobs, + file_mask=sshlandmaskfileobs, name_mask=sshlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(ssh_mod.shape[0] / 12)) + yearN_obs = int(round(ssh_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(ssh_mod) + actualtimebounds_obs = TimeBounds(ssh_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + sshRmse, sshRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + # here only the detrending (if applicable) and time averaging are performed + ssh_mod, Method, keyerror_mod = PreProcessTS(ssh_mod, Method, compute_sea_cycle=True, region=box, **kwargs) + ssh_obs, _, keyerror_obs = PreProcessTS(ssh_obs, '', compute_sea_cycle=True, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + sshRmse, sshRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ssh_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ssh_obs.getAxisList()]), + 'shape1': '(mod) ' + str(ssh_mod.shape), 'shape2': '(obs) ' + str(ssh_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # standard deviation computation + sshStd_mod = Std(ssh_mod) + sshStd_obs = Std(ssh_obs) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sshStd_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sshStd_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sshStd_mod.shape), 'shape2': '(obs) ' + str(sshStd_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Std', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + sshStd_mod, sshStd_obs, Method = TwoVarRegrid( + sshStd_mod, sshStd_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sshStd_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sshStd_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sshStd_mod.shape), + 'shape2': '(obs) ' + str(sshStd_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Zonal average + sshStdLat_mod, keyerror_mod = AverageZonal(sshStd_mod) + sshStdLat_obs, keyerror_obs = AverageZonal(sshStd_obs) + if keyerror_mod is not None or keyerror_obs is not None: + sshRmse, sshRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + sshStdLat_mod = sshStdLat_mod * 1e2 + sshStdLat_obs = sshStdLat_obs * 1e2 + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sshStdLat_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sshStdLat_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sshStdLat_mod.shape), + 'shape2': '(obs) ' + str(sshStdLat_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageZonal', 15, **dict_debug) + + # Computes the root mean square difference + sshRmse, keyerror = RmsMeridional( + sshStdLat_mod, sshStdLat_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + sshRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(sshStdLat_mod), 'observations': ArrayToList(sshStdLat_obs), + 'axis': list(sshStdLat_mod.getAxis(0)[:])} + if netcdf is True: + # Read file and select the right region + sshMap_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sshfilemod, sshnamemod, 'sea surface height', metric, 'equatorial_pacific_LatExt2', + file_area=sshareafilemod, name_area=sshareanamemod, file_mask=sshlandmaskfilemod, + name_mask=sshlandmaskfilemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sshMap_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sshfileobs, sshnameobs, 'sea surface height', metric, 'equatorial_pacific_LatExt2', + file_area=sshareafileobs, name_area=sshareanameobs, file_mask=sshlandmaskfileobs, + name_mask=sshlandmaskfileobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Preprocess ssh (computes anomalies, normalizes, detrends TS, smoothes TS, ...) + sshMap_mod, _, keyerror_mod = PreProcessTS(sshMap_mod, '', compute_sea_cycle=True, + region="equatorial_pacific_LatExt2", **kwargs) + sshMap_obs, _, keyerror_obs = PreProcessTS(sshMap_obs, '', compute_sea_cycle=True, + region="equatorial_pacific_LatExt2", **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sshMap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sshMap_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sshMap_mod.shape), + 'shape2': '(obs) ' + str(sshMap_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + # standard deviation computation + sshMap_mod = Std(sshMap_mod) + sshMap_mod = OperationMultiply(sshMap_mod, 1e2) + sshMap_obs = Std(sshMap_obs) + sshMap_obs = OperationMultiply(sshMap_obs, 1e2) + # Regridding + if 'regridding' not in list(kwargs.keys()): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + else: + if not isinstance(kwargs['regridding'], dict): + kwargs['regridding'] = { + 'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + sshMap_mod, sshMap_obs, _ = TwoVarRegrid( + sshMap_mod, sshMap_obs, '', region='equatorial_pacific_LatExt2', **kwargs['regridding']) + ssh_mod, ssh_obs, _ = TwoVarRegrid(ssh_mod, ssh_obs, '', region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sshMap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sshMap_obs.getAxisList()]), + 'axes3': '(mod) ' + str([ax.id for ax in ssh_mod.getAxisList()]), + 'axes4': '(obs) ' + str([ax.id for ax in ssh_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sshMap_mod.shape), + 'shape2': '(obs) ' + str(sshMap_obs.shape), + 'shape3': '(mod) ' + str(ssh_mod.shape), + 'shape4': '(obs) ' + str(ssh_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + # Zonal average + ssh_mod, keyerror_mod = AverageZonal(ssh_mod) + ssh_obs, keyerror_obs = AverageZonal(ssh_obs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + ssh_mod = ssh_mod * 1e2 + ssh_obs = ssh_obs * 1e2 + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ssh_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ssh_obs.getAxisList()]), + 'shape1': '(mod) ' + str(ssh_mod.shape), + 'shape2': '(obs) ' + str(ssh_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageZonal', 15, **dict_debug) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "meridional climatological standard deviation of EEP ssh"} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "meridional climatological standard deviation of EEP ssh"} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "climatological standard deviation of equatorial_pacific ssh"} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "climatological standard deviation of equatorial_pacific ssh"} + dict5 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "meridional mean annual cycle of EEP ssh"} + dict6 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "meridional mean annual cycle of EEP ssh"} + dict7 = {'metric_name': Name, 'metric_value_' + dataset2: sshRmse, + 'metric_value_error_' + dataset2: sshRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=sshStdLat_mod, var1_attributes=dict1, + var1_name='ssh_lat__' + dataset1, var2=sshStdLat_obs, var2_attributes=dict2, + var2_name='ssh_lat__' + dataset2, var3=sshMap_mod, var3_attributes=dict3, + var3_name='ssh_map__' + dataset1, var4=sshMap_obs, var4_attributes=dict4, + var4_name='ssh_map__' + dataset2, var5=ssh_mod, var5_attributes=dict5, + var5_name='sshMac_hov__' + dataset1, var6=ssh_obs, var6_attributes=dict6, + var6_name='sshMac_hov__' + dataset2, global_attributes=dict7) + del dict1, dict2, dict3, dict4, dict5, dict6, dict7, file_name + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(sshRmse), 'line2': 'metric value_error: ' + str(sshRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + LatRmseMetric = { + 'name': Name, 'value': sshRmse, 'value_error': sshRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return LatRmseMetric + + +def SeasonalSshLonRmse(sshfilemod, sshnamemod, sshareafilemod, sshareanamemod, sshlandmaskfilemod, sshlandmasknamemod, + sshfileobs, sshnameobs, sshareafileobs, sshareanameobs, sshlandmaskfileobs, sshlandmasknameobs, + box, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, + netcdf_name='', metname='', **kwargs): + """ + The SeasonalSshLonRmse() function computes the climatological (12 months) SSH zonal (longitude) standard + deviation root mean square error (RMSE) in a 'box' (usually the Equatorial Pacific) + + Inputs: + ------ + :param sshfilemod: string + path_to/filename of the file (NetCDF) of the modeled SSH + :param sshnamemod: string + name of SSH variable (ssh, tauu) in 'sshfilemod' + :param sshareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for SSH + :param sshareanamemod: string + name of areacell variable (areacella, areacello) in 'sshareafilemod' + :param sshlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for SSH + :param sshlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'sshlandmaskfilemod' + :param sshfileobs: string + path_to/filename of the file (NetCDF) of the observed SSH + :param sshnameobs: string + name of SSH variable (ssh, tauu) in 'sshfileobs' + :param sshareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for SSH + :param sshareanameobs: string + name of areacell variable (areacella, areacello) in 'sshareafileobs' + :param sshlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for SSH + :param sshlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'sshlandmaskfileobs' + :param box: string + name of box ('equatorial_pacific') for SSH + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'AVISO',...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param regridding: dict, optional + see EnsoUvcdatToolsLib.TwoVarRegrid and EnsoUvcdatToolsLib.Regrid for options + the aim if to specify if the model is regridded toward the observations or vice versa, of if both model and + observations are regridded toward another grid + interpolation tool and method can be specified + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SSH file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SSH file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return LonRmseMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_model, + time_period_observations, ref, keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + Notes: + ----- + TODO: add error calculation to rmse (function of nyears) + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'regridding', 'smoothing', + 'time_bounds_mod', 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'ssh zonal seasonality RMSE' + Units = 'cm' + Method = 'Zonal root mean square error of ' + box + ' climatological ssh STD' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = 'SeasonalSshLonRmse' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + ssh_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sshfilemod, sshnamemod, 'sea surface height', metric, box, file_area=sshareafilemod, name_area=sshareanamemod, + file_mask=sshlandmaskfilemod, name_mask=sshlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + ssh_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sshfileobs, sshnameobs, 'sea surface height', metric, box, file_area=sshareafileobs, name_area=sshareanameobs, + file_mask=sshlandmaskfileobs, name_mask=sshlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(ssh_mod.shape[0] / 12)) + yearN_obs = int(round(ssh_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(ssh_mod) + actualtimebounds_obs = TimeBounds(ssh_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + sshRmse, sshRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + # here only the detrending (if applicable) and time averaging are performed + ssh_mod, Method, keyerror_mod = PreProcessTS(ssh_mod, Method, compute_sea_cycle=True, region=box, **kwargs) + ssh_obs, _, keyerror_obs = PreProcessTS(ssh_obs, '', compute_sea_cycle=True, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + sshRmse, sshRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ssh_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ssh_obs.getAxisList()]), + 'shape1': '(mod) ' + str(ssh_mod.shape), 'shape2': '(obs) ' + str(ssh_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # standard deviation computation + sshStd_mod = Std(ssh_mod) + sshStd_obs = Std(ssh_obs) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sshStd_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sshStd_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sshStd_mod.shape), 'shape2': '(obs) ' + str(sshStd_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Std', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + sshStd_mod, sshStd_obs, Method = TwoVarRegrid( + sshStd_mod, sshStd_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sshStd_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sshStd_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sshStd_mod.shape), + 'shape2': '(obs) ' + str(sshStd_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Meridional average + sshStdLon_mod, keyerror_mod = AverageMeridional(sshStd_mod) + sshStdLon_obs, keyerror_obs = AverageMeridional(sshStd_obs) + if keyerror_mod is not None or keyerror_obs is not None: + sshRmse, sshRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + sshStdLon_mod = sshStdLon_mod * 1e2 + sshStdLon_obs = sshStdLon_obs * 1e2 + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sshStdLon_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sshStdLon_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sshStdLon_mod.shape), + 'shape2': '(obs) ' + str(sshStdLon_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + + # Computes the root mean square difference + sshRmse, keyerror = RmsZonal(sshStdLon_mod, sshStdLon_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + sshRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(sshStdLon_mod), 'observations': ArrayToList(sshStdLon_obs), + 'axis': list(sshStdLon_mod.getAxis(0)[:])} + if netcdf is True: + # Read file and select the right region + sshMap_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sshfilemod, sshnamemod, 'sea surface height', metric, 'equatorial_pacific_LatExt2', + file_area=sshareafilemod, name_area=sshareanamemod, file_mask=sshlandmaskfilemod, + name_mask=sshlandmaskfilemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sshMap_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sshfileobs, sshnameobs, 'sea surface height', metric, 'equatorial_pacific_LatExt2', + file_area=sshareafileobs, name_area=sshareanameobs, file_mask=sshlandmaskfileobs, + name_mask=sshlandmaskfileobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Preprocess ssh (computes anomalies, normalizes, detrends TS, smoothes TS, ...) + sshMap_mod, _, keyerror_mod = PreProcessTS( + sshMap_mod, '', compute_sea_cycle=True, region="equatorial_pacific_LatExt2", **kwargs) + sshMap_obs, _, keyerror_obs = PreProcessTS( + sshMap_obs, '', compute_sea_cycle=True, region="equatorial_pacific_LatExt2", **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sshMap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sshMap_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sshMap_mod.shape), + 'shape2': '(obs) ' + str(sshMap_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + # standard deviation computation + sshMap_mod = Std(sshMap_mod) + sshMap_mod = OperationMultiply(sshMap_mod, 1e2) + sshMap_obs = Std(sshMap_obs) + sshMap_obs = OperationMultiply(sshMap_obs, 1e2) + # Regridding + if 'regridding' not in list(kwargs.keys()): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + else: + if not isinstance(kwargs['regridding'], dict): + kwargs['regridding'] = { + 'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + sshMap_mod, sshMap_obs, _ = TwoVarRegrid( + sshMap_mod, sshMap_obs, '', region='equatorial_pacific_LatExt2', **kwargs['regridding']) + ssh_mod, ssh_obs, _ = TwoVarRegrid(ssh_mod, ssh_obs, '', region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sshMap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sshMap_obs.getAxisList()]), + 'axes3': '(mod) ' + str([ax.id for ax in ssh_mod.getAxisList()]), + 'axes4': '(obs) ' + str([ax.id for ax in ssh_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sshMap_mod.shape), + 'shape2': '(obs) ' + str(sshMap_obs.shape), + 'shape3': '(mod) ' + str(ssh_mod.shape), + 'shape4': '(obs) ' + str(ssh_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + # Meridional average + ssh_mod, keyerror_mod = AverageMeridional(ssh_mod) + ssh_obs, keyerror_obs = AverageMeridional(ssh_obs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + ssh_mod = ssh_mod * 1e2 + ssh_obs = ssh_obs * 1e2 + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in ssh_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in ssh_obs.getAxisList()]), + 'shape1': '(mod) ' + str(ssh_mod.shape), + 'shape2': '(obs) ' + str(ssh_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after AverageMeridional', 15, **dict_debug) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = { + 'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "zonal climatological standard deviation of equatorial_pacific ssh"} + dict2 = { + 'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "zonal climatological standard deviation of equatorial_pacific ssh"} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "climatological standard deviation of equatorial_pacific ssh"} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "climatological standard deviation of equatorial_pacific ssh"} + dict5 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "zonal mean annual cycle of equatorial_pacific ssh"} + dict6 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "zonal mean annual cycle of equatorial_pacific ssh"} + dict7 = {'metric_name': Name, 'metric_value_' + dataset2: sshRmse, + 'metric_value_error_' + dataset2: sshRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=sshStdLon_mod, var1_attributes=dict1, + var1_name='ssh_lon__' + dataset1, var2=sshStdLon_obs, var2_attributes=dict2, + var2_name='ssh_lon__' + dataset2, var3=sshMap_mod, var3_attributes=dict3, + var3_name='ssh_map__' + dataset1, var4=sshMap_obs, var4_attributes=dict4, + var4_name='ssh_map__' + dataset2, var5=ssh_mod, var5_attributes=dict5, + var5_name='sshMac_hov__' + dataset1, var6=ssh_obs, var6_attributes=dict6, + var6_name='sshMac_hov__' + dataset2, global_attributes=dict7) + del dict1, dict2, dict3, dict4, dict5, dict6, dict7, file_name + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(sshRmse), 'line2': 'metric value_error: ' + str(sshRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + LonRmseMetric = { + 'name': Name, 'value': sshRmse, 'value_error': sshRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return LonRmseMetric + + +def SeasonalSstLatRmse(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, + box, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, + netcdf_name='', metname='', **kwargs): + """ + The SeasonalSstLatRmse() function computes the climatological (12 months) SST meridional (latitude) standard + deviation root mean square error (RMSE) in a 'box' (usually the equatorial_pacific_LatExt) + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for SST + :param sstareanamemod: string + name of areacell variable (areacella, areacello) in 'sstareafilemod' + :param sstlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for SST + :param sstlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for SST + :param sstareanameobs: string + name of areacell variable (areacella, areacello) in 'sstareafileobs' + :param sstlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for SST + :param sstlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfileobs' + :param box: string + name of box ('equatorial_pacific_LatExt') for SST + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'Tropflux',...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param regridding: dict, optional + see EnsoUvcdatToolsLib.TwoVarRegrid and EnsoUvcdatToolsLib.Regrid for options + the aim if to specify if the model is regridded toward the observations or vice versa, of if both model and + observations are regridded toward another grid + interpolation tool and method can be specified + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return LatRmseMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_model, + time_period_observations, ref + + Method: + ------- + uses tools from uvcdat library + + Notes: + ----- + TODO: add error calculation to rmse (function of nyears) + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'regridding', 'smoothing', + 'time_bounds_mod', 'time_bounds_obs'] + for arg in needed_kwarg: + try: kwargs[arg] + except: kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'sst meridional seasonality RMSE' + Units = 'C' + Method = 'Meridional root mean square error of ' + box + ' climatological sst STD' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = 'SeasonalSstLatRmse' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, box, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, box, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + sstRmse, sstRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + # here only the detrending (if applicable) and time averaging are performed + sst_mod, Method, keyerror_mod = PreProcessTS(sst_mod, Method, compute_sea_cycle=True, region=box, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS(sst_obs, '', compute_sea_cycle=True, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + sstRmse, sstRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # standard deviation computation + sstStd_mod = Std(sst_mod) + sstStd_obs = Std(sst_obs) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sstStd_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sstStd_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sstStd_mod.shape), 'shape2': '(obs) ' + str(sstStd_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Std', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + sstStd_mod, sstStd_obs, Method = TwoVarRegrid( + sstStd_mod, sstStd_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sstStd_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sstStd_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sstStd_mod.shape), + 'shape2': '(obs) ' + str(sstStd_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Zonal average + sstStdLat_mod, keyerror_mod = AverageZonal(sstStd_mod) + sstStdLat_obs, keyerror_obs = AverageZonal(sstStd_obs) + if keyerror_mod is not None or keyerror_obs is not None: + sstRmse, sstRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sstStdLat_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sstStdLat_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sstStdLat_mod.shape), + 'shape2': '(obs) ' + str(sstStdLat_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageZonal', 15, **dict_debug) + + # Computes the root mean square difference + sstRmse, keyerror = RmsMeridional( + sstStdLat_mod, sstStdLat_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + sstRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(sstStdLat_mod), 'observations': ArrayToList(sstStdLat_obs), + 'axis': list(sstStdLat_mod.getAxis(0)[:])} + if netcdf is True: + # Read file and select the right region + sstMap_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, 'equatorial_pacific_LatExt2', + file_area=sstareafilemod, name_area=sstareanamemod, file_mask=sstlandmaskfilemod, + name_mask=sstlandmaskfilemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sstMap_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, 'equatorial_pacific_LatExt2', + file_area=sstareafileobs, name_area=sstareanameobs, file_mask=sstlandmaskfileobs, + name_mask=sstlandmaskfileobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, ...) + sstMap_mod, _, keyerror_mod = PreProcessTS(sstMap_mod, '', compute_sea_cycle=True, + region="equatorial_pacific_LatExt2", **kwargs) + sstMap_obs, _, keyerror_obs = PreProcessTS(sstMap_obs, '', compute_sea_cycle=True, + region="equatorial_pacific_LatExt2", **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sstMap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sstMap_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sstMap_mod.shape), + 'shape2': '(obs) ' + str(sstMap_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + # standard deviation computation + sstMap_mod = Std(sstMap_mod) + sstMap_obs = Std(sstMap_obs) + # Regridding + if 'regridding' not in list(kwargs.keys()): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + else: + if not isinstance(kwargs['regridding'], dict): + kwargs['regridding'] = { + 'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + sstMap_mod, sstMap_obs, _ = TwoVarRegrid( + sstMap_mod, sstMap_obs, '', region='equatorial_pacific_LatExt2', **kwargs['regridding']) + sst_mod, sst_obs, _ = TwoVarRegrid(sst_mod, sst_obs, '', region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sstMap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sstMap_obs.getAxisList()]), + 'axes3': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes4': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sstMap_mod.shape), + 'shape2': '(obs) ' + str(sstMap_obs.shape), + 'shape3': '(mod) ' + str(sst_mod.shape), + 'shape4': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + # Zonal average + sst_mod, keyerror_mod = AverageZonal(sst_mod) + sst_obs, keyerror_obs = AverageZonal(sst_obs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), + 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageZonal', 15, **dict_debug) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "meridional climatological standard deviation of EEP sst"} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "meridional climatological standard deviation of EEP sst"} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "climatological standard deviation of equatorial_pacific sst"} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "climatological standard deviation of equatorial_pacific sst"} + dict5 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "meridional mean annual cycle of EEP sst"} + dict6 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "meridional mean annual cycle of EEP sst"} + dict7 = {'metric_name': Name, 'metric_value_' + dataset2: sstRmse, + 'metric_value_error_' + dataset2: sstRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=sstStdLat_mod, var1_attributes=dict1, + var1_name='sst_lat__' + dataset1, var2=sstStdLat_obs, var2_attributes=dict2, + var2_name='sst_lat__' + dataset2, var3=sstMap_mod, var3_attributes=dict3, + var3_name='sst_map__' + dataset1, var4=sstMap_obs, var4_attributes=dict4, + var4_name='sst_map__' + dataset2, var5=sst_mod, var5_attributes=dict5, + var5_name='sstMac_hov__' + dataset1, var6=sst_obs, var6_attributes=dict6, + var6_name='sstMac_hov__' + dataset2, global_attributes=dict7) + del dict1, dict2, dict3, dict4, dict5, dict6, dict7, file_name + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(sstRmse), 'line2': 'metric value_error: ' + str(sstRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + LatRmseMetric = { + 'name': Name, 'value': sstRmse, 'value_error': sstRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return LatRmseMetric + + +def SeasonalSstLonRmse(sstfilemod, sstnamemod, sstareafilemod, sstareanamemod, sstlandmaskfilemod, sstlandmasknamemod, + sstfileobs, sstnameobs, sstareafileobs, sstareanameobs, sstlandmaskfileobs, sstlandmasknameobs, + box, centered_rmse=0, biased_rmse=1, dataset1='', dataset2='', debug=False, netcdf=False, + netcdf_name='', metname='', **kwargs): + """ + The SeasonalSstLonRmse() function computes the climatological (12 months) SST zonal (longitude) standard + deviation root mean square error (RMSE) in a 'box' (usually the Equatorial Pacific) + + Inputs: + ------ + :param sstfilemod: string + path_to/filename of the file (NetCDF) of the modeled SST + :param sstnamemod: string + name of SST variable (tos, ts) in 'sstfilemod' + :param sstareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for SST + :param sstareanamemod: string + name of areacell variable (areacella, areacello) in 'sstareafilemod' + :param sstlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for SST + :param sstlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfilemod' + :param sstfileobs: string + path_to/filename of the file (NetCDF) of the observed SST + :param sstnameobs: string + name of SST variable (tos, ts) in 'sstfileobs' + :param sstareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for SST + :param sstareanameobs: string + name of areacell variable (areacella, areacello) in 'sstareafileobs' + :param sstlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for SST + :param sstlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'sstlandmaskfileobs' + :param box: string + name of box ('equatorial_pacific') for SST + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'Tropflux',...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param regridding: dict, optional + see EnsoUvcdatToolsLib.TwoVarRegrid and EnsoUvcdatToolsLib.Regrid for options + the aim if to specify if the model is regridded toward the observations or vice versa, of if both model and + observations are regridded toward another grid + interpolation tool and method can be specified + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed SST file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return LonRmseMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_model, + time_period_observations, ref, keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + Notes: + ----- + TODO: add error calculation to rmse (function of nyears) + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'regridding', 'smoothing', + 'time_bounds_mod', 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'sst zonal seasonality RMSE' + Units = 'C' + Method = 'Zonal root mean square error of ' + box + ' climatological sst STD' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = 'SeasonalSstLonRmse' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + sst_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, box, file_area=sstareafilemod, name_area=sstareanamemod, + file_mask=sstlandmaskfilemod, name_mask=sstlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sst_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, box, file_area=sstareafileobs, name_area=sstareanameobs, + file_mask=sstlandmaskfileobs, name_mask=sstlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(sst_mod.shape[0] / 12)) + yearN_obs = int(round(sst_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(sst_mod) + actualtimebounds_obs = TimeBounds(sst_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + sstRmse, sstRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + # here only the detrending (if applicable) and time averaging are performed + sst_mod, Method, keyerror_mod = PreProcessTS(sst_mod, Method, compute_sea_cycle=True, region=box, **kwargs) + sst_obs, _, keyerror_obs = PreProcessTS(sst_obs, '', compute_sea_cycle=True, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + sstRmse, sstRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # standard deviation computation + sstStd_mod = Std(sst_mod) + sstStd_obs = Std(sst_obs) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sstStd_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sstStd_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sstStd_mod.shape), 'shape2': '(obs) ' + str(sstStd_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Std', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + sstStd_mod, sstStd_obs, Method = TwoVarRegrid( + sstStd_mod, sstStd_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sstStd_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sstStd_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sstStd_mod.shape), + 'shape2': '(obs) ' + str(sstStd_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Meridional average + sstStdLon_mod, keyerror_mod = AverageMeridional(sstStd_mod) + sstStdLon_obs, keyerror_obs = AverageMeridional(sstStd_obs) + if keyerror_mod is not None or keyerror_obs is not None: + sstRmse, sstRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sstStdLon_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sstStdLon_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sstStdLon_mod.shape), + 'shape2': '(obs) ' + str(sstStdLon_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + + # Computes the root mean square difference + sstRmse, keyerror = RmsZonal(sstStdLon_mod, sstStdLon_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + sstRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(sstStdLon_mod), 'observations': ArrayToList(sstStdLon_obs), + 'axis': list(sstStdLon_mod.getAxis(0)[:])} + if netcdf is True: + # Read file and select the right region + sstMap_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + sstfilemod, sstnamemod, 'temperature', metric, 'equatorial_pacific_LatExt2', + file_area=sstareafilemod, name_area=sstareanamemod, file_mask=sstlandmaskfilemod, + name_mask=sstlandmaskfilemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + sstMap_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + sstfileobs, sstnameobs, 'temperature', metric, 'equatorial_pacific_LatExt2', + file_area=sstareafileobs, name_area=sstareanameobs, file_mask=sstlandmaskfileobs, + name_mask=sstlandmaskfileobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Preprocess sst (computes anomalies, normalizes, detrends TS, smoothes TS, ...) + sstMap_mod, _, keyerror_mod = PreProcessTS( + sstMap_mod, '', compute_sea_cycle=True, region="equatorial_pacific_LatExt2", **kwargs) + sstMap_obs, _, keyerror_obs = PreProcessTS( + sstMap_obs, '', compute_sea_cycle=True, region="equatorial_pacific_LatExt2", **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sstMap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sstMap_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sstMap_mod.shape), + 'shape2': '(obs) ' + str(sstMap_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + # standard deviation computation + sstMap_mod = Std(sstMap_mod) + sstMap_obs = Std(sstMap_obs) + # Regridding + if 'regridding' not in list(kwargs.keys()): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + else: + if not isinstance(kwargs['regridding'], dict): + kwargs['regridding'] = { + 'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + sstMap_mod, sstMap_obs, _ = TwoVarRegrid( + sstMap_mod, sstMap_obs, '', region='equatorial_pacific_LatExt2', **kwargs['regridding']) + sst_mod, sst_obs, _ = TwoVarRegrid(sst_mod, sst_obs, '', region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sstMap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sstMap_obs.getAxisList()]), + 'axes3': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes4': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sstMap_mod.shape), + 'shape2': '(obs) ' + str(sstMap_obs.shape), + 'shape3': '(mod) ' + str(sst_mod.shape), + 'shape4': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + # Meridional average + sst_mod, keyerror_mod = AverageMeridional(sst_mod) + sst_obs, keyerror_obs = AverageMeridional(sst_obs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in sst_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in sst_obs.getAxisList()]), + 'shape1': '(mod) ' + str(sst_mod.shape), + 'shape2': '(obs) ' + str(sst_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after AverageMeridional', 15, **dict_debug) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = { + 'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "zonal climatological standard deviation of equatorial_pacific sst"} + dict2 = { + 'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "zonal climatological standard deviation of equatorial_pacific sst"} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "climatological standard deviation of equatorial_pacific sst"} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "climatological standard deviation of equatorial_pacific sst"} + dict5 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "zonal mean annual cycle of equatorial_pacific sst"} + dict6 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "zonal mean annual cycle of equatorial_pacific sst"} + dict7 = {'metric_name': Name, 'metric_value_' + dataset2: sstRmse, + 'metric_value_error_' + dataset2: sstRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=sstStdLon_mod, var1_attributes=dict1, + var1_name='sst_lon__' + dataset1, var2=sstStdLon_obs, var2_attributes=dict2, + var2_name='sst_lon__' + dataset2, var3=sstMap_mod, var3_attributes=dict3, + var3_name='sst_map__' + dataset1, var4=sstMap_obs, var4_attributes=dict4, + var4_name='sst_map__' + dataset2, var5=sst_mod, var5_attributes=dict5, + var5_name='sstMac_hov__' + dataset1, var6=sst_obs, var6_attributes=dict6, + var6_name='sstMac_hov__' + dataset2, global_attributes=dict7) + del dict1, dict2, dict3, dict4, dict5, dict6, dict7, file_name + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(sstRmse), 'line2': 'metric value_error: ' + str(sstRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + LonRmseMetric = { + 'name': Name, 'value': sstRmse, 'value_error': sstRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return LonRmseMetric + + +def SeasonalTauxLatRmse(tauxfilemod, tauxnamemod, tauxareafilemod, tauxareanamemod, tauxlandmaskfilemod, + tauxlandmasknamemod, tauxfileobs, tauxnameobs, tauxareafileobs, tauxareanameobs, + tauxlandmaskfileobs, tauxlandmasknameobs, box, centered_rmse=0, biased_rmse=1, dataset1='', + dataset2='', debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The SeasonalTauxLatRmse() function computes the climatological (12 months) TAUX meridional (latitude) standard + deviation root mean square error (RMSE) in a 'box' (usually the equatorial_pacific_LatExt) + + Inputs: + ------ + :param tauxfilemod: string + path_to/filename of the file (NetCDF) of the modeled TAUX + :param tauxnamemod: string + name of TAUX variable (tauu, tauuo) in 'tauxfilemod' + :param tauxareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for TAUX + :param tauxareanamemod: string + name of areacell variable (areacella, areacello) in 'tauxareafilemod' + :param tauxlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for TAUX + :param tauxlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'tauxlandmaskfilemod' + :param tauxfileobs: string + path_to/filename of the file (NetCDF) of the observed TAUX + :param tauxnameobs: string + name of TAUX variable (taux, tauu) in 'tauxfileobs' + :param tauxareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for TAUX + :param tauxareanameobs: string + name of areacell variable (areacella, areacello) in 'tauxareafileobs' + :param tauxlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for TAUX + :param tauxlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'tauxlandmaskfileobs' + :param box: string + name of box ('equatorial_pacific_LatExt') for TAUX + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'Tropflux',...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param regridding: dict, optional + see EnsoUvcdatToolsLib.TwoVarRegrid and EnsoUvcdatToolsLib.Regrid for options + the aim if to specify if the model is regridded toward the observations or vice versa, of if both model and + observations are regridded toward another grid + interpolation tool and method can be specified + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled TAUX file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed TAUX file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return LatRmseMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_model, + time_period_observations, ref + + Method: + ------- + uses tools from uvcdat library + + Notes: + ----- + TODO: add error calculation to rmse (function of nyears) + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'regridding', 'smoothing', + 'time_bounds_mod', 'time_bounds_obs'] + for arg in needed_kwarg: + try: kwargs[arg] + except: kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'taux meridional seasonality RMSE' + Units = '1e-3 N/m2' + Method = 'Meridional root mean square error of ' + box + ' climatological taux STD' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = 'SeasonalTauxLatRmse' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + taux_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + tauxfilemod, tauxnamemod, 'wind stress', metric, box, file_area=tauxareafilemod, name_area=tauxareanamemod, + file_mask=tauxlandmaskfilemod, name_mask=tauxlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + taux_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + tauxfileobs, tauxnameobs, 'wind stress', metric, box, file_area=tauxareafileobs, name_area=tauxareanameobs, + file_mask=tauxlandmaskfileobs, name_mask=tauxlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(taux_mod.shape[0] / 12)) + yearN_obs = int(round(taux_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(taux_mod) + actualtimebounds_obs = TimeBounds(taux_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + tauxRmse, tauxRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + # here only the detrending (if applicable) and time averaging are performed + taux_mod, Method, keyerror_mod = PreProcessTS(taux_mod, Method, compute_sea_cycle=True, region=box, **kwargs) + taux_obs, _, keyerror_obs = PreProcessTS(taux_obs, '', compute_sea_cycle=True, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + tauxRmse, tauxRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in taux_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in taux_obs.getAxisList()]), + 'shape1': '(mod) ' + str(taux_mod.shape), 'shape2': '(obs) ' + str(taux_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # standard deviation computation + tauxStd_mod = Std(taux_mod) + tauxStd_obs = Std(taux_obs) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in tauxStd_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in tauxStd_obs.getAxisList()]), + 'shape1': '(mod) ' + str(tauxStd_mod.shape), 'shape2': '(obs) ' + str(tauxStd_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Std', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + tauxStd_mod, tauxStd_obs, Method = TwoVarRegrid( + tauxStd_mod, tauxStd_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in tauxStd_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in tauxStd_obs.getAxisList()]), + 'shape1': '(mod) ' + str(tauxStd_mod.shape), + 'shape2': '(obs) ' + str(tauxStd_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Zonal average + tauxStdLat_mod, keyerror_mod = AverageZonal(tauxStd_mod) + tauxStdLat_obs, keyerror_obs = AverageZonal(tauxStd_obs) + if keyerror_mod is not None or keyerror_obs is not None: + tauxRmse, tauxRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + tauxStdLat_mod = tauxStdLat_mod * 1e3 + tauxStdLat_obs = tauxStdLat_obs * 1e3 + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in tauxStdLat_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in tauxStdLat_obs.getAxisList()]), + 'shape1': '(mod) ' + str(tauxStdLat_mod.shape), + 'shape2': '(obs) ' + str(tauxStdLat_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageZonal', 15, **dict_debug) + + # Computes the root mean square difference + tauxRmse, keyerror = RmsMeridional( + tauxStdLat_mod, tauxStdLat_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + tauxRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(tauxStdLat_mod), 'observations': ArrayToList(tauxStdLat_obs), + 'axis': list(tauxStdLat_mod.getAxis(0)[:])} + if netcdf is True: + # Read file and select the right region + tauxMap_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + tauxfilemod, tauxnamemod, 'wind stress', metric, 'equatorial_pacific_LatExt2', + file_area=tauxareafilemod, name_area=tauxareanamemod, file_mask=tauxlandmaskfilemod, + name_mask=tauxlandmaskfilemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + tauxMap_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + tauxfileobs, tauxnameobs, 'wind stress', metric, 'equatorial_pacific_LatExt2', + file_area=tauxareafileobs, name_area=tauxareanameobs, file_mask=tauxlandmaskfileobs, + name_mask=tauxlandmaskfileobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Preprocess taux (computes anomalies, normalizes, detrends TS, smoothes TS, ...) + tauxMap_mod, _, keyerror_mod = PreProcessTS(tauxMap_mod, '', compute_sea_cycle=True, + region="equatorial_pacific_LatExt2", **kwargs) + tauxMap_obs, _, keyerror_obs = PreProcessTS(tauxMap_obs, '', compute_sea_cycle=True, + region="equatorial_pacific_LatExt2", **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in tauxMap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in tauxMap_obs.getAxisList()]), + 'shape1': '(mod) ' + str(tauxMap_mod.shape), + 'shape2': '(obs) ' + str(tauxMap_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + # standard deviation computation + tauxMap_mod = Std(tauxMap_mod) + tauxMap_mod = OperationMultiply(tauxMap_mod, 1e3) + tauxMap_obs = Std(tauxMap_obs) + tauxMap_obs = OperationMultiply(tauxMap_obs, 1e3) + # Regridding + if 'regridding' not in list(kwargs.keys()): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + else: + if not isinstance(kwargs['regridding'], dict): + kwargs['regridding'] = { + 'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + tauxMap_mod, tauxMap_obs, _ = TwoVarRegrid( + tauxMap_mod, tauxMap_obs, '', region='equatorial_pacific_LatExt2', + **kwargs['regridding']) + taux_mod, taux_obs, _ = TwoVarRegrid( + taux_mod, taux_obs, '', region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in tauxMap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in tauxMap_obs.getAxisList()]), + 'axes3': '(mod) ' + str([ax.id for ax in taux_mod.getAxisList()]), + 'axes4': '(obs) ' + str([ax.id for ax in taux_obs.getAxisList()]), + 'shape1': '(mod) ' + str(tauxMap_mod.shape), + 'shape2': '(obs) ' + str(tauxMap_obs.shape), + 'shape3': '(mod) ' + str(taux_mod.shape), + 'shape4': '(obs) ' + str(taux_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + # Zonal average + taux_mod, keyerror_mod = AverageZonal(taux_mod) + taux_obs, keyerror_obs = AverageZonal(taux_obs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + taux_mod = taux_mod * 1e3 + taux_obs = taux_obs * 1e3 + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in taux_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in taux_obs.getAxisList()]), + 'shape1': '(mod) ' + str(taux_mod.shape), + 'shape2': '(obs) ' + str(taux_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageZonal', 15, **dict_debug) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "meridional climatological standard deviation of EEP taux"} + dict2 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "meridional climatological standard deviation of EEP taux"} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "climatological standard deviation of equatorial_pacific taux"} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "climatological standard deviation of equatorial_pacific taux"} + dict5 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "meridional mean annual cycle of EEP taux"} + dict6 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "meridional mean annual cycle of EEP taux"} + dict7 = {'metric_name': Name, 'metric_value_' + dataset2: tauxRmse, + 'metric_value_error_' + dataset2: tauxRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=tauxStdLat_mod, var1_attributes=dict1, + var1_name='taux_lat__' + dataset1, var2=tauxStdLat_obs, var2_attributes=dict2, + var2_name='taux_lat__' + dataset2, var3=tauxMap_mod, var3_attributes=dict3, + var3_name='taux_map__' + dataset1, var4=tauxMap_obs, var4_attributes=dict4, + var4_name='taux_map__' + dataset2, var5=taux_mod, var5_attributes=dict5, + var5_name='tauxMac_hov__' + dataset1, var6=taux_obs, var6_attributes=dict6, + var6_name='tauxMac_hov__' + dataset2, global_attributes=dict7) + del dict1, dict2, dict3, dict4, dict5, dict6, dict7, file_name + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(tauxRmse), 'line2': 'metric value_error: ' + str(tauxRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + LatRmseMetric = { + 'name': Name, 'value': tauxRmse, 'value_error': tauxRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return LatRmseMetric + + +def SeasonalTauxLonRmse(tauxfilemod, tauxnamemod, tauxareafilemod, tauxareanamemod, tauxlandmaskfilemod, + tauxlandmasknamemod, tauxfileobs, tauxnameobs, tauxareafileobs, tauxareanameobs, + tauxlandmaskfileobs, tauxlandmasknameobs, box, centered_rmse=0, biased_rmse=1, dataset1='', + dataset2='', debug=False, netcdf=False, netcdf_name='', metname='', **kwargs): + """ + The SeasonalTauxLonRmse() function computes the climatological (12 months) TAUX zonal (longitude) standard + deviation root mean square error (RMSE) in a 'box' (usually the Equatorial Pacific) + + Inputs: + ------ + :param tauxfilemod: string + path_to/filename of the file (NetCDF) of the modeled TAUX + :param tauxnamemod: string + name of TAUX variable (tauu, tauuo) in 'tauxfilemod' + :param tauxareafilemod: string + path_to/filename of the file (NetCDF) of the model areacell for TAUX + :param tauxareanamemod: string + name of areacell variable (areacella, areacello) in 'tauxareafilemod' + :param tauxlandmaskfilemod: string + path_to/filename of the file (NetCDF) of the model landmask for TAUX + :param tauxlandmasknamemod: string + name of landmask variable (sftlf, lsmask, landmask) in 'tauxlandmaskfilemod' + :param tauxfileobs: string + path_to/filename of the file (NetCDF) of the observed TAUX + :param tauxnameobs: string + name of TAUX variable (taux, tauu) in 'tauxfileobs' + :param tauxareafileobs: string + path_to/filename of the file (NetCDF) of the observations areacell for TAUX + :param tauxareanameobs: string + name of areacell variable (areacella, areacello) in 'tauxareafileobs' + :param tauxlandmaskfileobs: string + path_to/filename of the file (NetCDF) of the observations landmask for TAUX + :param tauxlandmasknameobs: string + name of landmask variable (sftlf, lsmask, landmask) in 'tauxlandmaskfileobs' + :param box: string + name of box ('equatorial_pacific') for TAUX + :param centered_rmse: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased_rmse: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :param dataset1: string, optional + name of model dataset (e.g., 'model', 'ACCESS1-0', ...) + :param dataset2: string, optional + name of observational dataset (e.g., 'obs', 'Tropflux',...) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :param netcdf: boolean, optional + default value = False dive_down are not saved in NetCDFs + If you want to save the dive down diagnostics set it to True + :param netcdf_name: string, optional + default value = '' NetCDFs are saved where the program is ran without a root name + the name of a metric will be append at the end of the root name + e.g., netcdf_name='/path/to/directory/USER_DATE_METRICCOLLECTION_MODEL' + usual kwargs: + :param detrending: dict, optional + see EnsoUvcdatToolsLib.Detrend for options + the aim if to specify if the trend must be removed + detrending method can be specified + default value is False + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + default value is None + :param normalization: boolean, optional + True to normalize by the standard deviation (needs the frequency to be defined), if you don't want it pass + anything but true + default value is False + :param regridding: dict, optional + see EnsoUvcdatToolsLib.TwoVarRegrid and EnsoUvcdatToolsLib.Regrid for options + the aim if to specify if the model is regridded toward the observations or vice versa, of if both model and + observations are regridded toward another grid + interpolation tool and method can be specified + default value is False + :param smoothing: dict, optional + see EnsoUvcdatToolsLib.Smoothing for options + the aim if to specify if variables are smoothed (running mean) + smoothing axis, window and method can be specified + default value is False + :param time_bounds_mod: tuple, optional + tuple of the first and last dates to extract from the modeled TAUX file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param time_bounds_obs: tuple, optional + tuple of the first and last dates to extract from the observed TAUX file (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + + Output: + ------ + :return LonRmseMetric: dict + name, value, value_error, units, method, nyears_model, nyears_observations, time_frequency, time_period_model, + time_period_observations, ref, keyerror, dive_down_diag + + Method: + ------- + uses tools from uvcdat library + + Notes: + ----- + TODO: add error calculation to rmse (function of nyears) + + """ + # test given kwargs + needed_kwarg = ['detrending', 'frequency', 'min_time_steps', 'normalization', 'regridding', 'smoothing', + 'time_bounds_mod', 'time_bounds_obs'] + for arg in needed_kwarg: + try: + kwargs[arg] + except: + kwargs[arg] = default_arg_values(arg) + + # Define metric attributes + Name = 'taux zonal seasonality RMSE' + Units = '1e-3 N/m2' + Method = 'Zonal root mean square error of ' + box + ' climatological taux STD' + Ref = 'Using CDAT regridding and rms (uncentered and biased) calculation' + metric = 'SeasonalTauxLonRmse' + if metname == '': + metname = deepcopy(metric) + + # Read file and select the right region + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[92m', metric, 10) + taux_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + tauxfilemod, tauxnamemod, 'wind stress', metric, box, file_area=tauxareafilemod, name_area=tauxareanamemod, + file_mask=tauxlandmaskfilemod, name_mask=tauxlandmasknamemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + taux_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + tauxfileobs, tauxnameobs, 'wind stress', metric, box, file_area=tauxareafileobs, name_area=tauxareanameobs, + file_mask=tauxlandmaskfileobs, name_mask=tauxlandmasknameobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + + # Number of years + yearN_mod = int(round(taux_mod.shape[0] / 12)) + yearN_obs = int(round(taux_obs.shape[0] / 12)) + + # Time period + actualtimebounds_mod = TimeBounds(taux_mod) + actualtimebounds_obs = TimeBounds(taux_obs) + + if keyerror_mod is not None or keyerror_obs is not None: + tauxRmse, tauxRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Preprocess variables (computes anomalies, normalizes, detrends TS, smoothes TS, averages horizontally) + # here only the detrending (if applicable) and time averaging are performed + taux_mod, Method, keyerror_mod = PreProcessTS(taux_mod, Method, compute_sea_cycle=True, region=box, **kwargs) + taux_obs, _, keyerror_obs = PreProcessTS(taux_obs, '', compute_sea_cycle=True, region=box, **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + tauxRmse, tauxRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in taux_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in taux_obs.getAxisList()]), + 'shape1': '(mod) ' + str(taux_mod.shape), 'shape2': '(obs) ' + str(taux_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + + # standard deviation computation + tauxStd_mod = Std(taux_mod) + tauxStd_obs = Std(taux_obs) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in tauxStd_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in tauxStd_obs.getAxisList()]), + 'shape1': '(mod) ' + str(tauxStd_mod.shape), 'shape2': '(obs) ' + str(tauxStd_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after Std', 15, **dict_debug) + + # Regridding + if isinstance(kwargs['regridding'], dict): + known_args = {'model_orand_obs', 'newgrid', 'missing', 'order', 'mask', 'newgrid_name', 'regridder', + 'regridTool', 'regridMethod'} + extra_args = set(kwargs['regridding']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + tauxStd_mod, tauxStd_obs, Method = TwoVarRegrid( + tauxStd_mod, tauxStd_obs, Method, region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in tauxStd_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in tauxStd_obs.getAxisList()]), + 'shape1': '(mod) ' + str(tauxStd_mod.shape), + 'shape2': '(obs) ' + str(tauxStd_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + + # Meridional average + tauxStdLon_mod, keyerror_mod = AverageMeridional(tauxStd_mod) + tauxStdLon_obs, keyerror_obs = AverageMeridional(tauxStd_obs) + if keyerror_mod is not None or keyerror_obs is not None: + tauxRmse, tauxRmseErr, dive_down_diag = None, None, {'model': None, 'observations': None, 'axis': None} + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + tauxStdLon_mod = tauxStdLon_mod * 1e3 + tauxStdLon_obs = tauxStdLon_obs * 1e3 + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in tauxStdLon_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in tauxStdLon_obs.getAxisList()]), + 'shape1': '(mod) ' + str(tauxStdLon_mod.shape), + 'shape2': '(obs) ' + str(tauxStdLon_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after AverageMeridional', 15, **dict_debug) + + # Computes the root mean square difference + tauxRmse, keyerror = RmsZonal( + tauxStdLon_mod, tauxStdLon_obs, centered=centered_rmse, biased=biased_rmse) + + # Error on the metric + tauxRmseErr = None + + # Dive down diagnostic + dive_down_diag = {'model': ArrayToList(tauxStdLon_mod), 'observations': ArrayToList(tauxStdLon_obs), + 'axis': list(tauxStdLon_mod.getAxis(0)[:])} + if netcdf is True: + # Read file and select the right region + tauxMap_mod, mod_areacell, keyerror_mod = Read_data_mask_area( + tauxfilemod, tauxnamemod, 'wind stress', metric, 'equatorial_pacific_LatExt2', + file_area=tauxareafilemod, name_area=tauxareanamemod, file_mask=tauxlandmaskfilemod, + name_mask=tauxlandmaskfilemod, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_mod'], debug=debug, **kwargs) + tauxMap_obs, obs_areacell, keyerror_obs = Read_data_mask_area( + tauxfileobs, tauxnameobs, 'wind stress', metric, 'equatorial_pacific_LatExt2', + file_area=tauxareafileobs, name_area=tauxareanameobs, file_mask=tauxlandmaskfileobs, + name_mask=tauxlandmaskfileobs, maskland=True, maskocean=False, + time_bounds=kwargs['time_bounds_obs'], debug=debug, **kwargs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + # Preprocess taux (computes anomalies, normalizes, detrends TS, smoothes TS, ...) + tauxMap_mod, _, keyerror_mod = PreProcessTS( + tauxMap_mod, '', compute_sea_cycle=True, region="equatorial_pacific_LatExt2", **kwargs) + tauxMap_obs, _, keyerror_obs = PreProcessTS( + tauxMap_obs, '', compute_sea_cycle=True, region="equatorial_pacific_LatExt2", **kwargs) + del mod_areacell, obs_areacell + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in tauxMap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in tauxMap_obs.getAxisList()]), + 'shape1': '(mod) ' + str(tauxMap_mod.shape), + 'shape2': '(obs) ' + str(tauxMap_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after PreProcessTS', 15, **dict_debug) + # standard deviation computation + tauxMap_mod = Std(tauxMap_mod) + tauxMap_mod = OperationMultiply(tauxMap_mod, 1e3) + tauxMap_obs = Std(tauxMap_obs) + tauxMap_obs = OperationMultiply(tauxMap_obs, 1e3) + # Regridding + if 'regridding' not in list(kwargs.keys()): + kwargs['regridding'] = {'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + else: + if not isinstance(kwargs['regridding'], dict): + kwargs['regridding'] = { + 'model_orand_obs': 2, 'regridder': 'cdms', 'regridTool': 'esmf', + 'regridMethod': 'linear', 'newgrid_name': 'generic_1x1deg'} + tauxMap_mod, tauxMap_obs, _ = TwoVarRegrid( + tauxMap_mod, tauxMap_obs, '', region='equatorial_pacific_LatExt2', + **kwargs['regridding']) + taux_mod, taux_obs, _ = TwoVarRegrid( + taux_mod, taux_obs, '', region=box, **kwargs['regridding']) + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in tauxMap_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in tauxMap_obs.getAxisList()]), + 'axes3': '(mod) ' + str([ax.id for ax in taux_mod.getAxisList()]), + 'axes4': '(obs) ' + str([ax.id for ax in taux_obs.getAxisList()]), + 'shape1': '(mod) ' + str(tauxMap_mod.shape), + 'shape2': '(obs) ' + str(tauxMap_obs.shape), + 'shape3': '(mod) ' + str(taux_mod.shape), + 'shape4': '(obs) ' + str(taux_obs.shape)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'after TwoVarRegrid', 15, **dict_debug) + # Meridional average + taux_mod, keyerror_mod = AverageMeridional(taux_mod) + taux_obs, keyerror_obs = AverageMeridional(taux_obs) + if keyerror_mod is not None or keyerror_obs is not None: + keyerror = add_up_errors([keyerror_mod, keyerror_obs]) + else: + taux_mod = taux_mod * 1e3 + taux_obs = taux_obs * 1e3 + if debug is True: + dict_debug = {'axes1': '(mod) ' + str([ax.id for ax in taux_mod.getAxisList()]), + 'axes2': '(obs) ' + str([ax.id for ax in taux_obs.getAxisList()]), + 'shape1': '(mod) ' + str(taux_mod.shape), + 'shape2': '(obs) ' + str(taux_obs.shape)} + EnsoErrorsWarnings.debug_mode( + '\033[92m', 'after AverageMeridional', 15, **dict_debug) + if ".nc" in netcdf_name: + file_name = deepcopy(netcdf_name).replace(".nc", "_" + metname + ".nc") + else: + file_name = deepcopy(netcdf_name) + "_" + metname + ".nc" + dict1 = { + 'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "zonal climatological standard deviation of equatorial_pacific taux"} + dict2 = { + 'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "zonal climatological standard deviation of equatorial_pacific taux"} + dict3 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "climatological standard deviation of equatorial_pacific taux"} + dict4 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "climatological standard deviation of equatorial_pacific taux"} + dict5 = {'units': Units, 'number_of_years_used': yearN_mod, + 'time_period': str(actualtimebounds_mod), + 'description': "zonal mean annual cycle of equatorial_pacific taux"} + dict6 = {'units': Units, 'number_of_years_used': yearN_obs, + 'time_period': str(actualtimebounds_obs), + 'description': "zonal mean annual cycle of equatorial_pacific taux"} + dict7 = {'metric_name': Name, 'metric_value_' + dataset2: tauxRmse, + 'metric_value_error_' + dataset2: tauxRmseErr, 'metric_method': Method, + 'metric_reference': Ref, 'frequency': kwargs['frequency']} + SaveNetcdf( + file_name, var1=tauxStdLon_mod, var1_attributes=dict1, + var1_name='taux_lon__' + dataset1, var2=tauxStdLon_obs, var2_attributes=dict2, + var2_name='taux_lon__' + dataset2, var3=tauxMap_mod, var3_attributes=dict3, + var3_name='taux_map__' + dataset1, var4=tauxMap_obs, var4_attributes=dict4, + var4_name='taux_map__' + dataset2, var5=taux_mod, var5_attributes=dict5, + var5_name='tauxMac_hov__' + dataset1, var6=taux_obs, var6_attributes=dict6, + var6_name='tauxMac_hov__' + dataset2, global_attributes=dict7) + del dict1, dict2, dict3, dict4, dict5, dict6, dict7, file_name + # metric value + if debug is True: + dict_debug = {'line1': 'metric value: ' + str(tauxRmse), 'line2': 'metric value_error: ' + str(tauxRmseErr)} + EnsoErrorsWarnings.debug_mode('\033[92m', 'end of ' + metric, 10, **dict_debug) + # Create output + LonRmseMetric = { + 'name': Name, 'value': tauxRmse, 'value_error': tauxRmseErr, 'units': Units, 'method': Method, + 'nyears_model': yearN_mod, 'nyears_observations': yearN_obs, 'time_frequency': kwargs['frequency'], + 'time_period_model': actualtimebounds_mod, 'time_period_observations': actualtimebounds_obs, 'ref': Ref, + 'keyerror': keyerror, 'dive_down_diag': dive_down_diag} + return LonRmseMetric +# ---------------------------------------------------------------------------------------------------------------------# diff --git a/build/lib/EnsoMetrics/EnsoPlotLib.py b/build/lib/EnsoMetrics/EnsoPlotLib.py new file mode 100644 index 0000000..665e35d --- /dev/null +++ b/build/lib/EnsoMetrics/EnsoPlotLib.py @@ -0,0 +1,3379 @@ +# -*- coding:UTF-8 -*- +# +# Define ENSO metrics plots +# +from copy import deepcopy +from numpy import arange as NUMPYarange +# ENSO_metrics functions +from .EnsoCollectionsLib import defCollection +from .KeyArgLib import default_arg_values + + +dict_colorbar = { + "amplitude": "amp", + "anomalies": "balance", + "PR": "rain", + "SST": "thermal", +} + +dict_label = { + "amplitude": [round(ii, 1) for ii in NUMPYarange(0, 2.1, 0.5)], + "amplitude5": list(range(0, 6, 1)), + "amplitude10": [round(ii, 1) for ii in NUMPYarange(0, 10.1, 2.5)], + "amplitude15": list(range(0, 16, 5)), + "amplitude60": list(range(0, 61, 20)), + "PR": list(range(0, 13, 4)), + "PRA": [round(ii, 1) for ii in NUMPYarange(-1, 1.1, 0.5)], + "REG03": [round(ii, 1) for ii in NUMPYarange(-0.3, 0.35, 0.1)], + "REG05": [round(ii, 2) for ii in NUMPYarange(-0.5, 0.55, 0.25)], + "REG12": [round(ii, 1) for ii in NUMPYarange(-1.2, 1.4, 0.6)], + "REG2": list(range(-2, 3, 1)), + "REG25": [round(ii, 1) for ii in NUMPYarange(-2.5, 2.6, 1.0)], + "REG3": list(range(-3, 4, 1)), + "REG4": list(range(-4, 5, 1)), + "REG5": [round(ii, 1) for ii in NUMPYarange(-5, 6, 2.5)], + "REG20": list(range(-20, 25, 10)), + "REG30": list(range(-30, 35, 15)), + "REG50": list(range(-50, 55, 25)), + "REG60": list(range(-60, 65, 30)), + "REG80": list(range(-80, 85, 40)), + "SKEW": [round(ii, 1) for ii in NUMPYarange(-1.5, 1.6, 0.5)], + "dSST": list(range(-2, 3, 1)), + "SST": list(range(21, 31, 3)), + "SSTA": [round(ii, 1) for ii in NUMPYarange(-1, 1.1, 0.5)], + "TAUX": list(range(-100, 110, 50)), +} + +plot_parameters = { + "BiasPrLatRmse": { + "netcdf_variables": ["pr_lat__", "pr_map__"], + "diagnostic": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "Mean PR", # "a) Mean meridional PR", # + "varpattern": "pr_lat__", + "xname": "latitude", + "yname": "PR", + "method": "1) Linearly detrended\n2) Temporal averaged\n3) Regridded to 1°x1°\n" + + "4) Zonal averaged (see box)\n\nMetric: RMSE$_y$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["PR"], + "label": dict_label["PR"], + "maskland": True, + "title": ["Mean PR", "Mean PR"], + "varpattern": "pr_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "PR", + "method": "1) Linearly detrended\n2) Temporal averaged\n3) Regridded to 1°x1°", + }, + }, + "BiasPrLonRmse": { + "netcdf_variables": ["pr_lon__", "pr_map__"], + "diagnostic": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "Mean PR", + "varpattern": "pr_lon__", + "xname": "longitude", + "yname": "PR", + "method": "1) Linearly detrended\n2) Temporal averaged\n3) Regridded to 1°x1°\n" + + "4) Meridional averaged (see box)\n\nMetric: RMSE$_x$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["PR"], + "label": dict_label["PR"], + "maskland": True, + "title": ["Mean PR", "Mean PR"], + "varpattern": "pr_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "PR", + "method": "1) Linearly detrended\n2) Temporal averaged\n3) Regridded to 1°x1°", + }, + }, + "BiasPrRmse": { + "netcdf_variables": ["pr_map__"], + "diagnostic": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["PR"], + "label": dict_label["PR"], + "maskland": True, + "title": ["Mean PR", "Mean PR"], + "varpattern": "pr_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "PR", + "method": "1) Linearly detrended\n2) Temporal averaged\n3) Regridded to 1°x1°\n\nMetric: RMSE$_{xy}$", + }, + }, + "BiasSshLatRmse": { + "netcdf_variables": ["ssh_lat__", "ssh_map__"], + "diagnostic": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "Mean SSH", + "varpattern": "ssh_lat__", + "xname": "latitude", + "yname": "SSH", + "method": "1) Linearly detrended\n2) Temporal averaged\n3) Regridded to 1°x1°\n" + + "4) Zonal averaged (see box)\n\nMetric: RMSE$_y$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["SST"], # YYP: I do not know yet the colobar / label needed + "label": dict_label["SST"], + "maskland": True, + "title": ["Mean SSH", "Mean SSH"], + "varpattern": "ssh_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "SSH", + "method": "1) Linearly detrended\n2) Temporal averaged\n3) Regridded to 1°x1°", + }, + }, + "BiasSshLonRmse": { + "netcdf_variables": ["ssh_lon__", "ssh_map__"], + "diagnostic": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "Mean SSH", + "varpattern": "ssh_lon__", + "xname": "longitude", + "yname": "SSH", + "method": "1) Linearly detrended\n2) Temporal averaged\n3) Regridded to 1°x1°\n" + + "4) Meridional averaged (see box)\n\nMetric: RMSE$_x$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["SST"], # YYP: I do not know yet the colobar / label needed + "label": dict_label["SST"], + "maskland": True, + "title": ["Mean SSH", "Mean SSH"], + "varpattern": "ssh_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "SSH", + "method": "1) Linearly detrended\n2) Temporal averaged\n3) Regridded to 1°x1°", + }, + }, + "BiasSshRmse": { + "netcdf_variables": ["ssh_map__"], + "diagnostic": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["SST"], # YYP: I do not know yet the colobar / label needed + "label": dict_label["SST"], + "maskland": True, + "title": ["Mean SSH", "Mean SSH"], + "varpattern": "ssh_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "SSH", + "method": "1) Linearly detrended\n2) Temporal averaged\n3) Regridded to 1°x1°\n\nMetric: RMSE$_{xy}$", + }, + }, + "BiasSstLatRmse": { + "netcdf_variables": ["sst_lat__", "sst_map__"], + "diagnostic": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "Mean SST", + "varpattern": "sst_lat__", + "xname": "latitude", + "yname": "SST", + "method": "1) Linearly detrended\n2) Temporal averaged\n3) Regridded to 1°x1°\n" + + "4) Zonal averaged (see box)\n\nMetric: RMSE$_y$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["SST"], + "label": dict_label["SST"], + "maskland": True, + "title": ["Mean SST", "Mean SST"], + "varpattern": "sst_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "SST", + "method": "1) Linearly detrended\n2) Temporal averaged\n3) Regridded to 1°x1°", + }, + }, + "BiasSstLonRmse": { + "netcdf_variables": ["sst_lon__", "sst_map__"], + "diagnostic": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "Mean SST", + "varpattern": "sst_lon__", + "xname": "longitude", + "yname": "SST", + "method": "1) Linearly detrended\n2) Temporal averaged\n3) Regridded to 1°x1°\n" + + "4) Meridional averaged (see box)\n\nMetric: RMSE$_x$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["SST"], + "label": dict_label["SST"], + "maskland": True, + "title": ["Mean SST", "Mean SST"], + "varpattern": "sst_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "SST", + "method": "1) Linearly detrended\n2) Temporal averaged\n3) Regridded to 1°x1°", + }, + }, + "BiasSstRmse": { + "netcdf_variables": ["sst_map__"], + "diagnostic": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["SST"], + "label": dict_label["SST"], + "maskland": True, + "title": ["Mean SST", "Mean SST"], + "varpattern": "sst_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "SST", + "method": "1) Linearly detrended\n2) Temporal averaged\n3) Regridded to 1°x1°\n\nMetric: RMSE$_{xy}$", + }, + }, + "BiasTauxLatRmse": { + "netcdf_variables": ["taux_lat__", "taux_map__"], + "diagnostic": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "Mean TAUX", + "varpattern": "taux_lat__", + "xname": "latitude", + "yname": "TAUX", + "method": "1) Linearly detrended\n2) Temporal averaged\n3) Regridded to 1°x1°\n" + + "4) Zonal averaged (see box)\n\nMetric: RMSE$_y$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["TAUX"], + "maskland": True, + "title": ["Mean TAUX", "Mean TAUX"], + "varpattern": "taux_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "TAUX", + "method": "1) Linearly detrended\n2) Temporal averaged\n3) Regridded to 1°x1°", + }, + }, + "BiasTauxLonRmse": { + "netcdf_variables": ["taux_lon__", "taux_map__"], + "diagnostic": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "Mean TAUX", + "varpattern": "taux_lon__", + "xname": "longitude", + "yname": "TAUX", + "method": "1) Linearly detrended\n2) Temporal averaged\n3) Regridded to 1°x1°\n" + + "4) Meridional averaged (see box)\n\nMetric: RMSE$_x$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["TAUX"], + "maskland": True, + "title": ["Mean TAUX", "Mean TAUX"], + "varpattern": "taux_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "TAUX", + "method": "1) Linearly detrended\n2) Temporal averaged\n3) Regridded to 1°x1°", + }, + }, + "BiasTauxRmse": { + "netcdf_variables": ["taux_map__"], + "diagnostic": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["TAUX"], + "maskland": True, + "title": ["Mean TAUX", "Mean TAUX"], + "varpattern": "taux_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "TAUX", + "method": "1) Linearly detrended\n2) Temporal averaged\n3) Regridded to 1°x1°\n\nMetric: RMSE$_{xy}$", + }, + }, + "EnsoAmpl": { + "netcdf_variables": ["sstStd_lon__", "sstStd_map__"], + "diagnostic": { + "plot_type": "dot", + "nbr_panel": 1, + "title": "ENSO amplitude", + "varpattern": "diagnostic", + "yname": "SSTA std", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) REGION1 averaged\n" + + "4) Standard deviation\n\nMetric: abs((STD$_{mod}$-STD$_{ref}$)/STD$_{ref}$)*100", + + }, + "dive_down01": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "SSTA standard deviation", + "varpattern": "sstStd_lon__", + "xname": "longitude", + "yname": "SSTA std", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) Standard deviation\n" + + "4) Regridded to 1°x1°\n5) 5S-5N meridional averaged", + }, + "dive_down02": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["amplitude"], + "label": dict_label["amplitude"], + "maskland": True, + "title": ["SSTA standard deviation", "SSTA standard deviation"], + "varpattern": "sstStd_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "SSTA std", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) Standard deviation\n" + + "4) Regridded to 1°x1°", + }, + }, + "EnsodSstOce": { + "netcdf_variables": ["dSST_ts__", "dSSTthf_ts__", "dSSToce_ts__", "dSSTthf_lon__", "dSSToce_lon__", + "dSST_hov__", "dSSTthf_hov__", "dSSToce_hov__"], + "diagnostic": { + "plot_type": "dot", + "nbr_panel": 1, + "title": "ENSO ocean-driven SST change", + "varpattern": "diagnostic", + "yname": "normalized dSSToce", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) dSST = REGION1 SSTA Dec. - Jul.\n3) REGION1 NHFA summed from Jul. to Dec.\n" + + "4) dSST = dSST/dSST and dSSTnhf = dSSTnhf/dSST\n" + + "5) dSSToce = dSST - dSSTnhf\n6) Mean dSSToce both El Nino and La Nina\n\n" + + "Metric: abs((dSSToce$_{mod}$-dSSToce$_{ref}$)/dSSToce$_{ref}$)*100", + }, + "dive_down01": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "ENSO SST change", + "varpattern": ["dSST_ts__", "dSSTthf_ts__", "dSSToce_ts__"], + "colors": {"model": ["black", "red", "blue"], "reference": ["black", "red", "blue"]}, + "linestyles": {"model": ["-", "-", "-"], "reference": ["-.", "-.", "-."]}, + "legend": ["dSST", "dSSTnhf", "dSSToce"], + "xname": "months", + "yname": "normalized dSST", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) dSST$_i$ = REGION1 SSTA M$_i$ - M$_{i-1}$\n" + + "3) dSSTnhf$_i$ = REGION1 NHFA summed from M$_0$ to M$_i$\n" + + "4) dt = dSST$_{dec}$-dSST$_{jul}$,\n dSST$_i$ = dSST$_i$/dt,\n " + + "dSSTnhf$_i$ = dSSTnhf$_i$/dt\n" + + "5) dSSToce$_i$ = dSST$_i$ - dSSTnhf$_i$\n6) Mean dSSToce both El Nino and La Nina", + }, + "dive_down02": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "ENSO SST change", + "varpattern": ["dSSTthf_lon__", "dSSToce_lon__"], + "colors": {"model": ["red", "blue"], "reference": ["red", "blue"]}, + "linestyles": {"model": ["-", "-"], "reference": ["-.", "-."]}, + "legend": ["dSSTnhf", "dSSToce"], + "xname": "longitude", + "yname": "normalized dSST", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) dSST = REGION1 SSTA Dec. - Jul.\n3) REGION1 NHFA summed from Jul. to Dec.\n" + + "4) dSST = dSST/dSST and dSSTnhf = dSSTnhf/dSST\n" + + "5) dSSToce = dSST - dSSTnhf\n6) Mean dSSToce both El Nino and La Nina", + }, + "dive_down03": { + "plot_type": "hovmoeller", + "nbr_panel": 6, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["dSST"], + "title": ["ENSO dSST", "ENSO heat flux dSST", "ENSO ocean dSST"], + "varpattern": ["dSST_hov__", "dSSTthf_hov__", "dSSToce_hov__"], + "xname": "longitude", + "yname": "months", + "zname": "normalized dSST", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) dSST$_i$ = REGION1 SSTA M$_i$ - M$_{i-1}$\n" + + "3) dSSTnhf$_i$ = REGION1 NHFA summed from M$_0$ to M$_i$\n" + + "4) dt = dSST$_{dec}$-dSST$_{jul}$,\n dSST$_i$ = dSST$_i$/dt,\n " + + "dSSTnhf$_i$ = dSSTnhf$_i$/dt\n" + + "5) dSSToce$_i$ = dSST$_i$ - dSSTnhf$_i$\n6) Mean dSSToce both El Nino and La Nina", + }, + + }, + "EnsoDuration": { + "netcdf_variables": ["sst_against_sst_ts__", "Nina_duration__", "Nino_duration__"], + "diagnostic": { + "plot_type": "dot", + "nbr_panel": 1, + "title": "ENSO duration", + "varpattern": "diagnostic", + "yname": "duration (reg>0.25)", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) REGION1 averaged\n5) Dec. REGION1 SSTA regressed onto REGION1 SSTA\n" + + "6) Duration = nbr months > 0.25\n\nMetric: abs((DUR$_{mod}$-DUR$_{ref}$)/DUR$_{ref}$)*100", + }, + "dive_down01": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "ENSO life-cycle", + "varpattern": "sst_against_sst_ts__", #"sst_over_sst_ts__", + "xname": "months", + "yname": "reg(SSTA, SSTA)", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) REGION1 averaged\n5) Dec. REGION1 SSTA regressed onto REGION1 SSTA", + }, + "dive_down02": { + "plot_type": "boxplot", + "nbr_panel": 2, + "title": ["La Nina duration", "El Nino duration"], + "varpattern": ["Nina_duration__", "Nino_duration__"], + "yname": ["duration (SSTA<-0.5)", "duration (SSTA>0.5)"], + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Seasonal cycle removed\n3) Linearly detrended\n4) 5-month triangular running ave.\n" + + "5) REGION1 averaged\n6) Duration = nbr months > 0.5 ENSO STD", + }, + }, + "EnsoFbSshSst": { + "netcdf_variables": ["ssh__", "sst__", "ssh_over_sst_lon__", "sshPOS_over_sst_lon__", "sshNEG_over_sst_lon__", + "ssh_over_sst_hov__", "sshPOS_over_sst_hov__", "sshNEG_over_sst_hov__"], + "diagnostic": { + "plot_type": "scatterplot", + "nbr_panel": 1, + "title": "SSH-to-SST coupling", + "varpattern": ["ssh__", "sst__"], + "xname": "SSHA", + "yname": "SSTA", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) REGION1 averaged\n" + + "4) REGION1 SSHA regressed onto REGION1 SSTA\n\n" + + "Metric: abs((Slope$_{mod}$-Slope$_{ref}$)/Slope$_{ref}$)*100", + }, + "dive_down01": { + "plot_type": "scatterplot", + "nbr_panel": 2, + "title": "nonlinarity", + "varpattern": ["ssh__", "sst__"], + "xname": "SSHA", + "yname": "SSTA", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) REGION1 averaged\n" + + "4) REGION1 SSHA>0 (SSHA<0) regressed onto REGION1 SSTA", + }, + "dive_down02": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "Thermocline feedback", + #"varpattern": ["ssh_over_sst_lon__", "sshPOS_over_sst_lon__", "sshNEG_over_sst_lon__"], + "varpattern": ["reg_sst_over_ssh_lon__", "reg_sst_over_POSssh_lon__", "reg_sst_over_NEGssh_lon__"], + "colors": {"model": ["black", "red", "blue"], "reference": ["black", "red", "blue"]}, + "linestyles": {"model": ["-", "-", "-"], "reference": ["-.", "-.", "-."]}, + "legend": ["All", "SSHA>0", "SSHA<0"], + "xname": "longitude", + "yname": "reg(SSHA, SSTA)", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5S-5N meridional averaged\n" + + "4) 30° zonal running ave.\n5) SSHA or SSHA>0 or SSHA<0 regressed onto SSTA", + }, + "dive_down03": { + "plot_type": "hovmoeller", + "nbr_panel": 6, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG03"], + "title": ["reg(SSHA, SSTA)", "reg(SSHA>0, SSTA)", "reg(SSHA<0, SSTA)"], + #"varpattern": ["ssh_over_sst_hov__", "sshPOS_over_sst_hov__", "sshNEG_over_sst_hov__"], + "varpattern": ["reg_sst_over_ssh_hov__", "reg_sst_over_POSssh_hov__", "reg_sst_over_NEGssh_hov__"], + "xname": "longitude", + "yname": "months", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5S-5N meridional averaged\n" + + "4) 30° zonal running ave.\n" + + "5) For each calendar month:\n SSHA or SSHA>0 or SSHA<0 regressed onto SSTA", + }, + }, + "EnsoFbSstLhf": { + "netcdf_variables": ["sst__", "lhf__", "sst_over_lhf_lon__", "sstPOS_over_lhf_lon__", "sstNEG_over_lhf_lon__", + "sst_over_lhf_hov__", "sstPOS_over_lhf_hov__", "sstNEG_over_lhf_hov__"], + "diagnostic": { + "plot_type": "scatterplot", + "nbr_panel": 1, + "title": "Latent heat feedback", + "varpattern": ["sst__", "lhf__"], + "xname": "SSTA", + "yname": "LHFA", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) REGION1 averaged\n" + + "4) REGION1 SSTA regressed onto REGION1 LHFA\n\n" + + "Metric: abs((Slope$_{mod}$-Slope$_{ref}$)/Slope$_{ref}$)*100", + }, + "dive_down01": { + "plot_type": "scatterplot", + "nbr_panel": 2, + "title": "nonlinarity", + "varpattern": ["sst__", "lhf__"], + "xname": "SSTA", + "yname": "LHFA", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) REGION1 averaged\n" + + "4) REGION1 SSTA>0 (SSTA<0) regressed onto REGION1 LHFA", + }, + "dive_down02": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "Latent heat feedback", + #"varpattern": ["sst_over_lhf_lon__", "sstPOS_over_lhf_lon__", "sstNEG_over_lhf_lon__"], + "varpattern": ["reg_lhf_over_sst_lon__", "reg_lhf_over_POSsst_lon__", "reg_lhf_over_NEGsst_lon__"], + "colors": {"model": ["black", "red", "blue"], "reference": ["black", "red", "blue"]}, + "linestyles": {"model": ["-", "-", "-"], "reference": ["-.", "-.", "-."]}, + "legend": ["All", "SSTA>0", "SSTA<0"], + "xname": "longitude", + "yname": "reg(SSTA, LHFA)", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5S-5N meridional averaged\n" + + "4) 30° zonal running ave.\n5) SSTA or SSTA>0 or SSTA<0 regressed onto LHFA", + }, + "dive_down03": { + "plot_type": "hovmoeller", + "nbr_panel": 6, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG20"], + "title": ["reg(SSTA, LHFA)", "reg(SSTA>0, LHFA)", "reg(SSTA<0, LHFA)"], + #"varpattern": ["sst_over_lhf_hov__", "sstPOS_over_lhf_hov__", "sstNEG_over_lhf_hov__"], + "varpattern": ["reg_lhf_over_sst_hov__", "reg_lhf_over_POSsst_hov__", "reg_lhf_over_NEGsst_hov__"], + "xname": "longitude", + "yname": "months", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5S-5N meridional averaged\n" + + "4) 30° zonal running ave.\n" + + "5) For each calendar month:\n SSTA or SSTA>0 or SSTA<0 regressed onto LHFA", + }, + }, + "EnsoFbSstLwr": { + "netcdf_variables": ["sst__", "lwr__", "sst_over_lwr_lon__", "sstPOS_over_lwr_lon__", "sstNEG_over_lwr_lon__", + "sst_over_lwr_hov__", "sstPOS_over_lwr_hov__", "sstNEG_over_lwr_hov__"], + "diagnostic": { + "plot_type": "scatterplot", + "nbr_panel": 1, + "title": "Longwave feedback", + "varpattern": ["sst__", "lwr__"], + "xname": "SSTA", + "yname": "LWRA", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) REGION1 averaged\n" + + "4) REGION1 SSTA regressed onto REGION1 LWRA\n\n" + + "Metric: abs((Slope$_{mod}$-Slope$_{ref}$)/Slope$_{ref}$)*100", + }, + "dive_down01": { + "plot_type": "scatterplot", + "nbr_panel": 2, + "title": "nonlinarity", + "varpattern": ["sst__", "lwr__"], + "xname": "SSTA", + "yname": "LWRA", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) REGION1 averaged\n" + + "4) REGION1 SSTA>0 (SSTA<0) regressed onto REGION1 LWRA", + }, + "dive_down02": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "Longwave feedback", + #"varpattern": ["sst_over_lwr_lon__", "sstPOS_over_lwr_lon__", "sstNEG_over_lwr_lon__"], + "varpattern": ["reg_lwr_over_sst_lon__", "reg_lwr_over_POSsst_lon__", "reg_lwr_over_NEGsst_lon__"], + "colors": {"model": ["black", "red", "blue"], "reference": ["black", "red", "blue"]}, + "linestyles": {"model": ["-", "-", "-"], "reference": ["-.", "-.", "-."]}, + "legend": ["All", "SSTA>0", "SSTA<0"], + "xname": "longitude", + "yname": "reg(SSTA, LWRA)", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5S-5N meridional averaged\n" + + "4) 30° zonal running ave.\n5) SSTA or SSTA>0 or SSTA<0 regressed onto LWRA", + }, + "dive_down03": { + "plot_type": "hovmoeller", + "nbr_panel": 6, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG20"], + "title": ["reg(SSTA, LWRA)", "reg(SSTA>0, LWRA)", "reg(SSTA<0, LWRA)"], + #"varpattern": ["sst_over_lwr_hov__", "sstPOS_over_lwr_hov__", "sstNEG_over_lwr_hov__"], + "varpattern": ["reg_lwr_over_sst_hov__", "reg_lwr_over_POSsst_hov__", "reg_lwr_over_NEGsst_hov__"], + "xname": "longitude", + "yname": "months", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5S-5N meridional averaged\n" + + "4) 30° zonal running ave.\n" + + "5) For each calendar month:\n SSTA or SSTA>0 or SSTA<0 regressed onto LWRA", + }, + }, + "EnsoFbSstShf": { + "netcdf_variables": ["sst__", "shf__", "sst_over_shf_lon__", "sstPOS_over_shf_lon__", "sstNEG_over_shf_lon__", + "sst_over_shf_hov__", "sstPOS_over_shf_hov__", "sstNEG_over_shf_hov__"], + "diagnostic": { + "plot_type": "scatterplot", + "nbr_panel": 1, + "title": "Sensible heat feedback", + "varpattern": ["sst__", "shf__"], + "xname": "SSTA", + "yname": "SHFA", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) REGION1 averaged\n" + + "4) REGION1 SSTA regressed onto REGION1 SHFA\n\n" + + "Metric: abs((Slope$_{mod}$-Slope$_{ref}$)/Slope$_{ref}$)*100", + }, + "dive_down01": { + "plot_type": "scatterplot", + "nbr_panel": 2, + "title": "nonlinarity", + "varpattern": ["sst__", "shf__"], + "xname": "SSTA", + "yname": "SHFA", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) REGION1 averaged\n" + + "4) REGION1 SSTA>0 (SSTA<0) regressed onto REGION1 SHFA", + }, + "dive_down02": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "Sensible heat feedback", + #"varpattern": ["sst_over_shf_lon__", "sstPOS_over_shf_lon__", "sstNEG_over_shf_lon__"], + "varpattern": ["reg_shf_over_sst_lon__", "reg_shf_over_POSsst_lon__", "reg_shf_over_NEGsst_lon__"], + "colors": {"model": ["black", "red", "blue"], "reference": ["black", "red", "blue"]}, + "linestyles": {"model": ["-", "-", "-"], "reference": ["-.", "-.", "-."]}, + "legend": ["All", "SSTA>0", "SSTA<0"], + "xname": "longitude", + "yname": "reg(SSTA, SHFA)", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5S-5N meridional averaged\n" + + "4) 30° zonal running ave.\n5) SSTA or SSTA>0 or SSTA<0 regressed onto SHFA", + }, + "dive_down03": { + "plot_type": "hovmoeller", + "nbr_panel": 6, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG5"], + "title": ["reg(SSTA, SHFA)", "reg(SSTA>0, SHFA)", "reg(SSTA<0, SHFA)"], + #"varpattern": ["sst_over_shf_hov__", "sstPOS_over_shf_hov__", "sstNEG_over_shf_hov__"], + "varpattern": ["reg_shf_over_sst_hov__", "reg_shf_over_POSsst_hov__", "reg_shf_over_NEGsst_hov__"], + "xname": "longitude", + "yname": "months", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5S-5N meridional averaged\n" + + "4) 30° zonal running ave.\n" + + "5) For each calendar month:\n SSTA or SSTA>0 or SSTA<0 regressed onto SHFA", + }, + }, + "EnsoFbSstSwr": { + "netcdf_variables": ["sst__", "swr__", "sst_over_swr_lon__", "sstPOS_over_swr_lon__", "sstNEG_over_swr_lon__", + "sst_over_swr_hov__", "sstPOS_over_swr_hov__", "sstNEG_over_swr_hov__"], + "diagnostic": { + "plot_type": "scatterplot", + "nbr_panel": 1, + "title": "Shortwave feedback", + "varpattern": ["sst__", "swr__"], + "xname": "SSTA", + "yname": "SWRA", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) REGION1 averaged\n" + + "4) REGION1 SSTA regressed onto REGION1 SWRA\n\n" + + "Metric: abs((Slope$_{mod}$-Slope$_{ref}$)/Slope$_{ref}$)*100", + }, + "dive_down01": { + "plot_type": "scatterplot", + "nbr_panel": 2, + "title": "nonlinarity", + "varpattern": ["sst__", "swr__"], + "xname": "SSTA", + "yname": "SWRA", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) REGION1 averaged\n" + + "4) REGION1 SSTA>0 (SSTA<0) regressed onto REGION1 SWRA", + }, + "dive_down02": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "Shortwave feedback", + #"varpattern": ["sst_over_swr_lon__", "sstPOS_over_swr_lon__", "sstNEG_over_swr_lon__"], + "varpattern": ["reg_swr_over_sst_lon__", "reg_swr_over_POSsst_lon__", "reg_swr_over_NEGsst_lon__"], + "colors": {"model": ["black", "red", "blue"], "reference": ["black", "red", "blue"]}, + "linestyles": {"model": ["-", "-", "-"], "reference": ["-.", "-.", "-."]}, + "legend": ["All", "SSTA>0", "SSTA<0"], + "xname": "longitude", + "yname": "reg(SSTA, SWRA)", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5S-5N meridional averaged\n" + + "4) 30° zonal running ave.\n5) SSTA or SSTA>0 or SSTA<0 regressed onto SWRA", + }, + "dive_down03": { + "plot_type": "hovmoeller", + "nbr_panel": 6, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG50"], + "title": ["reg(SSTA, SWRA)", "reg(SSTA>0, SWRA)", "reg(SSTA<0, SWRA)"], + #"varpattern": ["sst_over_swr_hov__", "sstPOS_over_swr_hov__", "sstNEG_over_swr_hov__"], + "varpattern": ["reg_swr_over_sst_hov__", "reg_swr_over_POSsst_hov__", "reg_swr_over_NEGsst_hov__"], + "xname": "longitude", + "yname": "months", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5S-5N meridional averaged\n" + + "4) 30° zonal running ave.\n" + + "5) For each calendar month:\n SSTA or SSTA>0 or SSTA<0 regressed onto SWRA", + }, + }, + "EnsoFbSstTaux": { + "netcdf_variables": [ + "sst__", "taux__", "sst_over_taux_lon__", "sstPOS_over_taux_lon__", "sstNEG_over_taux_lon__", + "sst_over_taux_hov__", "sstPOS_over_taux_hov__", "sstNEG_over_taux_hov__"], + "diagnostic": { + "plot_type": "scatterplot", + "nbr_panel": 1, + "title": "SST-to-Taux coupling", + "varpattern": ["sst__", "taux__"], + "xname": "SSTA", + "yname": "TAUXA", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) Horizontal averaged\n" + + "4) REGION1 SSTA regressed onto REGION2 TAUXA\n\n" + + "Metric: abs((Slope$_{mod}$-Slope$_{ref}$)/Slope$_{ref}$)*100", + }, + "dive_down01": { + "plot_type": "scatterplot", + "nbr_panel": 2, + "title": "nonlinarity", + "varpattern": ["sst__", "taux__"], + "xname": "SSTA", + "yname": "TAUXA", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) Horizontal averaged\n" + + "4) REGION1 SSTA>0 (SSTA<0) regressed onto REGION2 TAUXA", + }, + "dive_down02": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "Wind stress feedback", + #"varpattern": ["sst_over_taux_lon__", "sstPOS_over_taux_lon__", "sstNEG_over_taux_lon__"], + "varpattern": ["reg_taux_over_sst_lon__", "reg_taux_over_POSsst_lon__", "reg_taux_over_NEGsst_lon__"], + "colors": {"model": ["black", "red", "blue"], "reference": ["black", "red", "blue"]}, + "linestyles": {"model": ["-", "-", "-"], "reference": ["-.", "-.", "-."]}, + "legend": ["All", "SSTA>0", "SSTA<0"], + "xname": "longitude", + "yname": "reg(SSTA, TAUXA)", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) REGION1 SSTA averaged\n" + + "4) TAUXA 5S-5N meridional averaged\n5) TAUXA 30° zonal running ave.\n" + + "6) REGION1 SSTA or SSTA>0 or SSTA<0 regressed onto TAUXA", + }, + "dive_down03": { + "plot_type": "hovmoeller", + "nbr_panel": 6, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG20"], + "title": ["reg(SSTA, TAUXA)", "reg(SSTA>0, TAUXA)", "reg(SSTA<0, TAUXA)"], + #"varpattern": ["sst_over_taux_hov__", "sstPOS_over_taux_hov__", "sstNEG_over_taux_hov__"], + "varpattern": ["reg_taux_over_sst_hov__", "reg_taux_over_POSsst_hov__", "reg_taux_over_NEGsst_hov__"], + "xname": "longitude", + "yname": "months", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) REGION1 SSTA averaged\n" + + "4) TAUXA 5S-5N meridional averaged\n5) TAUXA 30° zonal running ave.\n" + + "6) For each calendar month:\n REGION1 SSTA or SSTA>0 or SSTA<0 regressed onto TAUXA", + }, + }, + "EnsoFbSstThf": { + "netcdf_variables": ["sst__", "thf__", "sst_over_thf_lon__", "sstPOS_over_thf_lon__", "sstNEG_over_thf_lon__", + "sst_over_thf_hov__", "sstPOS_over_thf_hov__", "sstNEG_over_thf_hov__"], + "diagnostic": { + "plot_type": "scatterplot", + "nbr_panel": 1, + "title": "Net heat flux feedback", + "varpattern": ["sst__", "thf__"], + "xname": "SSTA", + "yname": "NHFA", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) REGION1 averaged\n" + + "4) REGION1 SSTA regressed onto REGION1 NHFA\n\n" + + "Metric: abs((Slope$_{mod}$-Slope$_{ref}$)/Slope$_{ref}$)*100", + }, + "dive_down01": { + "plot_type": "scatterplot", + "nbr_panel": 2, + "title": "nonlinarity", + "varpattern": ["sst__", "thf__"], + "xname": "SSTA", + "yname": "NHFA", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) REGION1 averaged\n" + + "4) REGION1 SSTA>0 (SSTA<0) regressed onto REGION1 NHFA", + }, + "dive_down02": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "Net heat flux feedback", + #"varpattern": ["sst_over_thf_lon__", "sstPOS_over_thf_lon__", "sstNEG_over_thf_lon__"], + "varpattern": ["reg_thf_over_sst_lon__", "reg_thf_over_POSsst_lon__", "reg_thf_over_NEGsst_lon__"], + "colors": {"model": ["black", "red", "blue"], "reference": ["black", "red", "blue"]}, + "linestyles": {"model": ["-", "-", "-"], "reference": ["-.", "-.", "-."]}, + "legend": ["All", "SSTA>0", "SSTA<0"], + "xname": "longitude", + "yname": "reg(SSTA, NHFA)", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5S-5N meridional averaged\n" + + "4) 30° zonal running ave.\n5) SSTA or SSTA>0 or SSTA<0 regressed onto NHFA", + }, + "dive_down03": { + "plot_type": "hovmoeller", + "nbr_panel": 6, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG50"], + "title": ["reg(SSTA, NHFA)", "reg(SSTA>0, NHFA)", "reg(SSTA<0, NHFA)"], + #"varpattern": ["sst_over_thf_hov__", "sstPOS_over_thf_hov__", "sstNEG_over_thf_hov__"], + "varpattern": ["reg_thf_over_sst_hov__", "reg_thf_over_POSsst_hov__", "reg_thf_over_NEGsst_hov__"], + "xname": "longitude", + "yname": "months", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5S-5N meridional averaged\n" + + "4) 30° zonal running ave.\n" + + "5) For each calendar month:\n SSTA or SSTA>0 or SSTA<0 regressed onto NHFA", + }, + }, + "EnsoFbTauxSsh": { + "netcdf_variables": [ + "taux__", "ssh__", "taux_over_ssh_lon__", "tauxPOS_over_ssh_lon__", "tauxNEG_over_ssh_lon__", + "taux_over_ssh_hov__", "tauxPOS_over_ssh_hov__", "tauxNEG_over_ssh_hov__"], + "diagnostic": { + "plot_type": "scatterplot", + "nbr_panel": 1, + "title": "Taux-to-SSH coupling", + "varpattern": ["taux__", "ssh__"], + "xname": "TAUXA", + "yname": "SSHA", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) Horizontal averaged\n" + + "4) REGION1 TAUXA regressed onto REGION2 SSHA\n\n" + + "Metric: abs((Slope$_{mod}$-Slope$_{ref}$)/Slope$_{ref}$)*100", + }, + "dive_down01": { + "plot_type": "scatterplot", + "nbr_panel": 2, + "title": "nonlinarity", + "varpattern": ["taux__", "ssh__"], + "xname": "TAUXA", + "yname": "SSHA", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) Horizontal averaged\n" + + "4) REGION1 TAUXA>0 (TAUXA<0) regressed onto REGION2 SSHA", + }, + "dive_down02": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "SSH-Wind feedback", + #"varpattern": ["taux_over_ssh_lon__", "tauxPOS_over_ssh_lon__", "tauxNEG_over_ssh_lon__"], + "varpattern": ["reg_ssh_over_taux_lon__", "reg_ssh_over_POStaux_lon__", "reg_ssh_over_NEGtaux_lon__"], + "colors": {"model": ["black", "red", "blue"], "reference": ["black", "red", "blue"]}, + "linestyles": {"model": ["-", "-", "-"], "reference": ["-.", "-.", "-."]}, + "legend": ["All", "TAUXA>0", "TAUXA<0"], + "xname": "longitude", + "yname": "reg(TAUXA, SSHA)", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) REGION1 TAUXA averaged\n" + + "4) SSHA 5S-5N meridional averaged\n5) SSHA 30° zonal running ave.\n" + + "6) REGION1 TAUXA or TAUXA>0 or TAUXA<0 regressed onto SSHA", + }, + "dive_down03": { + "plot_type": "hovmoeller", + "nbr_panel": 6, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG05"], + "title": ["reg(TAUXA, SSHA)", "reg(TAUXA>0, SSHA)", "reg(TAUXA<0, SSHA)"], + #"varpattern": ["taux_over_ssh_hov__", "tauxPOS_over_ssh_hov__", "tauxNEG_over_ssh_hov__"], + "varpattern": ["reg_ssh_over_taux_hov__", "reg_ssh_over_POStaux_hov__", "reg_ssh_over_NEGtaux_hov__"], + "xname": "longitude", + "yname": "months", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) REGION1 TAUXA averaged\n" + + "4) SSHA 5S-5N meridional averaged\n5) SSHA 30° zonal running ave.\n" + + "6) For each calendar month:\n REGION1 TAUXA or TAUXA>0 or TAUXA<0 regressed onto SSHA", + }, + }, + "EnsoPrMap": { + "netcdf_variables": ["reg_pr_over_sst_map__", "reg_pr_over_sst_map__"], + "diagnostic": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "title": ["reg(ENSO SSTA, PRA)", "reg(ENSO SSTA, PRA)"], + #"varpattern": "sst_over_sst_map__", + "varpattern": "reg_pr_over_sst_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) N3.4 SSTA averaged\n5) PRA regridded to 1°x1°\n6) Dec. N3.4 SSTA regressed onto Dec. PRA\n" + + "7) Equatorial Pacific masked\n\nMetric: RMSE$_{xy}$", + }, + # ["africaSE", "americaN", "americaS", "asiaS", "oceania"] + "dive_down01": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, PRA)", "reg(ENSO SSTA, PRA)"], + "varpattern": "reg_pr_over_sst_map_africaSE__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) N3.4 SSTA averaged\n5) PRA regridded to 1°x1°\n6) Dec. N3.4 SSTA regressed onto Dec. PRA\n" + + "7) Mask ocean", + }, + "dive_down02": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, PRA)", "reg(ENSO SSTA, PRA)"], + "varpattern": "reg_pr_over_sst_map_americaN__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) N3.4 SSTA averaged\n5) PRA regridded to 1°x1°\n6) Dec. N3.4 SSTA regressed onto Dec. PRA\n" + + "7) Mask ocean", + }, + "dive_down03": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, PRA)", "reg(ENSO SSTA, PRA)"], + "varpattern": "reg_pr_over_sst_map_americaS__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) N3.4 SSTA averaged\n5) PRA regridded to 1°x1°\n6) Dec. N3.4 SSTA regressed onto Dec. PRA\n" + + "7) Mask ocean", + }, + "dive_down04": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, PRA)", "reg(ENSO SSTA, PRA)"], + "varpattern": "reg_pr_over_sst_map_asiaS__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) N3.4 SSTA averaged\n5) PRA regridded to 1°x1°\n6) Dec. N3.4 SSTA regressed onto Dec. PRA\n" + + "7) Mask ocean", + }, + "dive_down05": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, PRA)", "reg(ENSO SSTA, PRA)"], + "varpattern": "reg_pr_over_sst_map_oceania__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) N3.4 SSTA averaged\n5) PRA regridded to 1°x1°\n6) Dec. N3.4 SSTA regressed onto Dec. PRA\n" + + "7) Mask ocean", + }, + }, + "EnsoPrMapDjf": { + "netcdf_variables": ["reg_pr_over_sst_djf_map__", "reg_pr_over_sst_djf_map__"], + "diagnostic": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "title": ["reg(ENSO SSTA, PRA) DJF", "reg(ENSO SSTA, PRA) DJF"], + #"varpattern": "sst_over_sst_map__", + "varpattern": "reg_pr_over_sst_djf_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) DJF averaged\n4) Temporal mean removed\n" + + "5) PRA regridded to 1°x1°\n6) DJF N3.4 SSTA regressed onto DJF PRA\n" + + "7) Equatorial Pacific masked\n\nMetric: RMSE$_{xy}$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "title": ["La Nina PRA DJF", "El Nino PRA DJF"], + "varpattern": ["pr_nina_djf_map__", "pr_nino_djf_map__"], + "xname": "longitude", + "yname": "latitude", + "zname": "PRA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) DJF averaged PR\n4) Temporal mean PR removed\n" + + "5) PRA regridded to 1°x1°\n6) El Nino and La Nina DJF PRA composited\n" + + "7) Equatorial Pacific masked", + }, + # ["africaSE", "americaN", "americaS", "asiaS", "oceania"] + "dive_down02": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, PRA) DJF", "reg(ENSO SSTA, PRA) DJF"], + "varpattern": "reg_pr_over_sst_djf_map_africaSE__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) DJF averaged\n4) Temporal mean removed\n" + + "5) PRA regridded to 1°x1°\n6) DJF N3.4 SSTA regressed onto DJF PRA\n7) Mask ocean", + }, + "dive_down03": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, PRA) DJF", "reg(ENSO SSTA, PRA) DJF"], + "varpattern": "reg_pr_over_sst_djf_map_americaN__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) DJF averaged\n4) Temporal mean removed\n" + + "5) PRA regridded to 1°x1°\n6) DJF N3.4 SSTA regressed onto DJF PRA\n7) Mask ocean", + }, + "dive_down04": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, PRA) DJF", "reg(ENSO SSTA, PRA) DJF"], + "varpattern": "reg_pr_over_sst_djf_map_americaS__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) DJF averaged\n4) Temporal mean removed\n" + + "5) PRA regridded to 1°x1°\n6) DJF N3.4 SSTA regressed onto DJF PRA\n7) Mask ocean", + }, + "dive_down05": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, PRA) DJF", "reg(ENSO SSTA, PRA) DJF"], + "varpattern": "reg_pr_over_sst_djf_map_asiaS__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) DJF averaged\n4) Temporal mean removed\n" + + "5) PRA regridded to 1°x1°\n6) DJF N3.4 SSTA regressed onto DJF PRA\n7) Mask ocean", + }, + "dive_down06": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, PRA) DJF", "reg(ENSO SSTA, PRA) DJF"], + "varpattern": "reg_pr_over_sst_djf_map_oceania__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) DJF averaged\n4) Temporal mean removed\n" + + "5) PRA regridded to 1°x1°\n6) DJF N3.4 SSTA regressed onto DJF PRA\n7) Mask ocean", + }, + "dive_down07": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["La Nina PRA DJF", "El Nino PRA DJF"], + "varpattern": ["pr_nina_djf_map_africaSE__", "pr_nino_djf_map_africaSE__"], + "xname": "longitude", + "yname": "latitude", + "zname": "PRA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) DJF averaged PR\n4) Temporal mean PR removed\n" + + "5) PRA regridded to 1°x1°\n6) El Nino and La Nina DJF PRA composited\n7) Mask ocean", + }, + "dive_down08": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["La Nina PRA DJF", "El Nino PRA DJF"], + "varpattern": ["pr_nina_djf_map_americaN__", "pr_nino_djf_map_americaN__"], + "xname": "longitude", + "yname": "latitude", + "zname": "PRA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) DJF averaged PR\n4) Temporal mean PR removed\n" + + "5) PRA regridded to 1°x1°\n6) El Nino and La Nina DJF PRA composited\n7) Mask ocean", + }, + "dive_down09": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["La Nina PRA DJF", "El Nino PRA DJF"], + "varpattern": ["pr_nina_djf_map_americaS__", "pr_nino_djf_map_americaS__"], + "xname": "longitude", + "yname": "latitude", + "zname": "PRA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) DJF averaged PR\n4) Temporal mean PR removed\n" + + "5) PRA regridded to 1°x1°\n6) El Nino and La Nina DJF PRA composited\n7) Mask ocean", + }, + "dive_down10": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["La Nina PRA DJF", "El Nino PRA DJF"], + "varpattern": ["pr_nina_djf_map_asiaS__", "pr_nino_djf_map_asiaS__"], + "xname": "longitude", + "yname": "latitude", + "zname": "PRA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) DJF averaged PR\n4) Temporal mean PR removed\n" + + "5) PRA regridded to 1°x1°\n6) El Nino and La Nina DJF PRA composited\n7) Mask ocean", + }, + "dive_down11": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["La Nina PRA DJF", "El Nino PRA DJF"], + "varpattern": ["pr_nina_djf_map_oceania__", "pr_nino_djf_map_oceania__"], + "xname": "longitude", + "yname": "latitude", + "zname": "PRA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) DJF averaged PR\n4) Temporal mean PR removed\n" + + "5) PRA regridded to 1°x1°\n6) El Nino and La Nina DJF PRA composited\n7) Mask ocean", + }, + }, + "EnsoPrMapJja": { + "netcdf_variables": ["reg_pr_over_sst_jja_map__", "reg_pr_over_sst_jja_map__"], + "diagnostic": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "title": ["reg(ENSO SSTA, PRA) JJA", "reg(ENSO SSTA, PRA) JJA"], + #"varpattern": "sst_over_sst_map__", + "varpattern": "reg_pr_over_sst_jja_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) JJA averaged\n4) Temporal mean removed\n" + + "5) PRA regridded to 1°x1°\n6) JJA N3.4 SSTA regressed onto JJA PRA\n" + + "7) Equatorial Pacific masked\n\nMetric: RMSE$_{xy}$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["SKEW"], + "maskland": False, + "title": ["La Nina PRA JJA", "El Nino PRA JJA"], + "varpattern": ["pr_nina_jja_map__", "pr_nino_jja_map__"], + "xname": "longitude", + "yname": "latitude", + "zname": "PRA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) JJA averaged PR\n4) Temporal mean PR removed\n" + + "5) PRA regridded to 1°x1°\n6) El Nino and La Nina JJA PRA composited\n" + + "7) Equatorial Pacific masked", + }, + # ["africaSE", "americaN", "americaS", "asiaS", "oceania"] + "dive_down02": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, PRA) JJA", "reg(ENSO SSTA, PRA) JJA"], + "varpattern": "reg_pr_over_sst_jja_map_africaSE__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) JJA averaged\n4) Temporal mean removed\n" + + "5) PRA regridded to 1°x1°\n6) JJA N3.4 SSTA regressed onto JJA PRA\n7) Mask ocean", + }, + "dive_down03": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, PRA) JJA", "reg(ENSO SSTA, PRA) JJA"], + "varpattern": "reg_pr_over_sst_jja_map_americaN__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) JJA averaged\n4) Temporal mean removed\n" + + "5) PRA regridded to 1°x1°\n6) JJA N3.4 SSTA regressed onto JJA PRA\n7) Mask ocean", + }, + "dive_down04": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, PRA) JJA", "reg(ENSO SSTA, PRA) JJA"], + "varpattern": "reg_pr_over_sst_jja_map_americaS__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) JJA averaged\n4) Temporal mean removed\n" + + "5) PRA regridded to 1°x1°\n6) JJA N3.4 SSTA regressed onto JJA PRA\n7) Mask ocean", + }, + "dive_down05": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, PRA) JJA", "reg(ENSO SSTA, PRA) JJA"], + "varpattern": "reg_pr_over_sst_jja_map_asiaS__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) JJA averaged\n4) Temporal mean removed\n" + + "5) PRA regridded to 1°x1°\n6) JJA N3.4 SSTA regressed onto JJA PRA\n7) Mask ocean", + }, + "dive_down06": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, PRA) JJA", "reg(ENSO SSTA, PRA) JJA"], + "varpattern": "reg_pr_over_sst_jja_map_oceania__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) JJA averaged\n4) Temporal mean removed\n" + + "5) PRA regridded to 1°x1°\n6) JJA N3.4 SSTA regressed onto JJA PRA\n7) Mask ocean", + }, + "dive_down07": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["SKEW"], + "maskland": False, + "maskocean": True, + "title": ["La Nina PRA JJA", "El Nino PRA JJA"], + "varpattern": ["pr_nina_jja_map_africaSE__", "pr_nino_jja_map_africaSE__"], + "xname": "longitude", + "yname": "latitude", + "zname": "PRA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) JJA averaged PR\n4) Temporal mean PR removed\n" + + "5) PRA regridded to 1°x1°\n6) El Nino and La Nina JJA PRA composited\n7) Mask ocean", + }, + "dive_down08": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["SKEW"], + "maskland": False, + "maskocean": True, + "title": ["La Nina PRA JJA", "El Nino PRA JJA"], + "varpattern": ["pr_nina_jja_map_americaN__", "pr_nino_jja_map_americaN__"], + "xname": "longitude", + "yname": "latitude", + "zname": "PRA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) JJA averaged PR\n4) Temporal mean PR removed\n" + + "5) PRA regridded to 1°x1°\n6) El Nino and La Nina JJA PRA composited\n7) Mask ocean", + }, + "dive_down09": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["SKEW"], + "maskland": False, + "maskocean": True, + "title": ["La Nina PRA JJA", "El Nino PRA JJA"], + "varpattern": ["pr_nina_jja_map_americaS__", "pr_nino_jja_map_americaS__"], + "xname": "longitude", + "yname": "latitude", + "zname": "PRA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) JJA averaged PR\n4) Temporal mean PR removed\n" + + "5) PRA regridded to 1°x1°\n6) El Nino and La Nina JJA PRA composited\n7) Mask ocean", + }, + "dive_down10": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["SKEW"], + "maskland": False, + "maskocean": True, + "title": ["La Nina PRA JJA", "El Nino PRA JJA"], + "varpattern": ["pr_nina_jja_map_asiaS__", "pr_nino_jja_map_asiaS__"], + "xname": "longitude", + "yname": "latitude", + "zname": "PRA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) JJA averaged PR\n4) Temporal mean PR removed\n" + + "5) PRA regridded to 1°x1°\n6) El Nino and La Nina JJA PRA composited\n7) Mask ocean", + }, + "dive_down11": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["SKEW"], + "maskland": False, + "maskocean": True, + "title": ["La Nina PRA JJA", "El Nino PRA JJA"], + "varpattern": ["pr_nina_jja_map_oceania__", "pr_nino_jja_map_oceania__"], + "xname": "longitude", + "yname": "latitude", + "zname": "PRA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) JJA averaged PR\n4) Temporal mean PR removed\n" + + "5) PRA regridded to 1°x1°\n6) El Nino and La Nina JJA PRA composited\n7) Mask ocean", + }, + }, + "EnsoPrTsRmse": { + "netcdf_variables": ["pr_over_sst_ts__", "pr_over_sst_hov__", "Nina_pr_ts__", "Nino_pr_ts__", "Nina_pr_hov__", + "Nino_pr_hov__"], + "diagnostic": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "ENSO's PRA life-cycle", + #"varpattern": "pr_over_sst_ts__", + "varpattern": "sst_against_pr_ts__", + "xname": "months", + "yname": "reg(ENSO SSTA, PRA)", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) Horizontal averaged\n5) Dec. N3.4 SSTA regressed onto REGION1 PRA\n\nMetric: RMSE$_{t}$", + }, + "dive_down01": { + "plot_type": "hovmoeller", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG3"], + "title": ["reg(ENSO SSTA, PRA)", "reg(ENSO SSTA, PRA)"], + #"varpattern": "pr_over_sst_hov__", + "varpattern": "sst_against_pr_hov__", + "xname": "longitude", + "yname": "months", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) N3.4 SSTA averaged\n5) PRA regridded to 1°x1°\n" + + "6) 5S-5N meridional PRA average\n7) Dec. N3.4 SSTA regressed onto PRA", + }, + "dive_down02": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "ENSO's PRA life-cycle", + "varpattern": ["Nina_pr_ts__", "Nino_pr_ts__"], + "colors": {"model": ["blue", "red"], "reference": ["blue", "red"]}, + "linestyles": {"model": ["-", "-"], "reference": ["-.", "-."]}, + "legend": ["La Nina", "El Nino"], + "xname": "months", + "yname": "ENSO PRA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Seasonal cycle removed\n3) Linearly detrended\n4) 5-month triangular running ave.\n" + + "5) REGION1 PRA averaged\n6) El Nino and La Nina PRA composited", + }, + "dive_down03": { + "plot_type": "hovmoeller", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG5"], + "title": ["La Nina PRA", "El Nino PRA"], + "varpattern": ["Nina_pr_hov__", "Nino_pr_hov__"], + "xname": "longitude", + "yname": "months", + "zname": "PRA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Seasonal cycle removed\n3) Linearly detrended\n4) 5-month triangular running ave.\n" + + "5) PRA regridded to 1°x1°\n6) 5S-5N meridional PRA average\n" + + "7) El Nino and La Nina PRA composited", + }, + }, + "EnsoSlpMap": { + "netcdf_variables": ["reg_slp_over_sst_map__", "reg_slp_over_sst_map__"], + "diagnostic": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "title": ["reg(ENSO SSTA, SLPA)", "reg(ENSO SSTA, SLPA)"], + "varpattern": "reg_slp_over_sst_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) N3.4 SSTA averaged\n5) SLPA regridded to 1°x1°\n" + + "6) Dec. N3.4 SSTA regressed onto Dec. SLPA\n7) Equatorial Pacific masked\n\nMetric: RMSE$_{xy}$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, SLPA)", "reg(ENSO SSTA, SLPA)"], + "varpattern": "reg_slp_over_sst_map_africaSE__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) N3.4 SSTA averaged\n5) SLPA regridded to 1°x1°\n" + + "6) Dec. N3.4 SSTA regressed onto Dec. SLPA\n7) Mask ocean", + }, + "dive_down02": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, SLPA)", "reg(ENSO SSTA, SLPA)"], + "varpattern": "reg_slp_over_sst_map_americaN__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) N3.4 SSTA averaged\n5) SLPA regridded to 1°x1°\n" + + "6) Dec. N3.4 SSTA regressed onto Dec. SLPA\n7) Mask ocean", + }, + "dive_down03": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, SLPA)", "reg(ENSO SSTA, SLPA)"], + "varpattern": "reg_slp_over_sst_map_americaS__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) N3.4 SSTA averaged\n5) SLPA regridded to 1°x1°\n" + + "6) Dec. N3.4 SSTA regressed onto Dec. SLPA\n7) Mask ocean", + }, + "dive_down04": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, SLPA)", "reg(ENSO SSTA, SLPA)"], + "varpattern": "reg_slp_over_sst_map_asiaS__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) N3.4 SSTA averaged\n5) SLPA regridded to 1°x1°\n" + + "6) Dec. N3.4 SSTA regressed onto Dec. SLPA\n7) Mask ocean", + }, + "dive_down05": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, SLPA)", "reg(ENSO SSTA, SLPA)"], + "varpattern": "reg_slp_over_sst_map_oceania__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) N3.4 SSTA averaged\n5) SLPA regridded to 1°x1°\n" + + "6) Dec. N3.4 SSTA regressed onto Dec. SLPA\n7) Mask ocean", + }, + }, + "EnsoSlpMapDjf": { + "netcdf_variables": ["reg_slp_over_sst_djf_map__", "reg_slp_over_sst_djf_map__"], + "diagnostic": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "title": ["reg(ENSO SSTA, SLPA) DJF", "reg(ENSO SSTA, SLPA) DJF"], + # "varpattern": "sst_over_sst_map__", + "varpattern": "reg_slp_over_sst_djf_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) DJF averaged\n4) Temporal mean removed\n" + + "5) SLPA regridded to 1°x1°\n6) DJF N3.4 SSTA regressed onto DJF SLPA\n" + + "7) Equatorial Pacific masked\n\nMetric: RMSE$_{xy}$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG3"], + "maskland": False, + "title": ["La Nina SLPA DJF", "El Nino SLPA DJF"], + "varpattern": ["slp_nina_djf_map__", "slp_nino_djf_map__"], + "xname": "longitude", + "yname": "latitude", + "zname": "SLPA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) DJF averaged SLP\n4) Temporal mean SLP removed\n" + + "5) SLPA regridded to 1°x1°\n6) El Nino and La Nina DJF SLPA composited\n" + + "7) Equatorial Pacific masked", + }, + # ["africaSE", "americaN", "americaS", "asiaS", "oceania"] + "dive_down02": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, SLPA) DJF", "reg(ENSO SSTA, SLPA) DJF"], + "varpattern": "reg_slp_over_sst_djf_map_africaSE__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) DJF averaged\n4) Temporal mean removed\n" + + "5) SLPA regridded to 1°x1°\n6) DJF N3.4 SSTA regressed onto DJF SLPA\n7) Mask ocean", + }, + "dive_down03": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, SLPA) DJF", "reg(ENSO SSTA, SLPA) DJF"], + "varpattern": "reg_slp_over_sst_djf_map_americaN__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) DJF averaged\n4) Temporal mean removed\n" + + "5) SLPA regridded to 1°x1°\n6) DJF N3.4 SSTA regressed onto DJF SLPA\n7) Mask ocean", + }, + "dive_down04": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, SLPA) DJF", "reg(ENSO SSTA, SLPA) DJF"], + "varpattern": "reg_slp_over_sst_djf_map_americaS__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) DJF averaged\n4) Temporal mean removed\n" + + "5) SLPA regridded to 1°x1°\n6) DJF N3.4 SSTA regressed onto DJF SLPA\n7) Mask ocean", + }, + "dive_down05": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, SLPA) DJF", "reg(ENSO SSTA, SLPA) DJF"], + "varpattern": "reg_slp_over_sst_djf_map_asiaS__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) DJF averaged\n4) Temporal mean removed\n" + + "5) SLPA regridded to 1°x1°\n6) DJF N3.4 SSTA regressed onto DJF SLPA\n7) Mask ocean", + }, + "dive_down06": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, SLPA) DJF", "reg(ENSO SSTA, SLPA) DJF"], + "varpattern": "reg_slp_over_sst_djf_map_oceania__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) DJF averaged\n4) Temporal mean removed\n" + + "5) SLPA regridded to 1°x1°\n6) DJF N3.4 SSTA regressed onto DJF SLPA\n7) Mask ocean", + }, + "dive_down07": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG3"], + "maskland": False, + "maskocean": True, + "title": ["La Nina SLPA DJF", "El Nino SLPA DJF"], + "varpattern": ["slp_nina_djf_map_africaSE__", "slp_nino_djf_map_africaSE__"], + "xname": "longitude", + "yname": "latitude", + "zname": "SLPA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) DJF averaged SLP\n4) Temporal mean SLP removed\n" + + "5) SLPA regridded to 1°x1°\n6) El Nino and La Nina DJF SLPA composited\n7) Mask ocean", + }, + "dive_down08": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG3"], + "maskland": False, + "maskocean": True, + "title": ["La Nina SLPA DJF", "El Nino SLPA DJF"], + "varpattern": ["slp_nina_djf_map_americaN__", "slp_nino_djf_map_americaN__"], + "xname": "longitude", + "yname": "latitude", + "zname": "SLPA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) DJF averaged SLP\n4) Temporal mean SLP removed\n" + + "5) SLPA regridded to 1°x1°\n6) El Nino and La Nina DJF SLPA composited\n7) Mask ocean", + }, + "dive_down09": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG3"], + "maskland": False, + "maskocean": True, + "title": ["La Nina SLPA DJF", "El Nino SLPA DJF"], + "varpattern": ["slp_nina_djf_map_americaS__", "slp_nino_djf_map_americaS__"], + "xname": "longitude", + "yname": "latitude", + "zname": "SLPA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) DJF averaged SLP\n4) Temporal mean SLP removed\n" + + "5) SLPA regridded to 1°x1°\n6) El Nino and La Nina DJF SLPA composited\n7) Mask ocean", + }, + "dive_down10": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG3"], + "maskland": False, + "maskocean": True, + "title": ["La Nina SLPA DJF", "El Nino SLPA DJF"], + "varpattern": ["slp_nina_djf_map_asiaS__", "slp_nino_djf_map_asiaS__"], + "xname": "longitude", + "yname": "latitude", + "zname": "SLPA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) DJF averaged SLP\n4) Temporal mean SLP removed\n" + + "5) SLPA regridded to 1°x1°\n6) El Nino and La Nina DJF SLPA composited\n7) Mask ocean", + }, + "dive_down11": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG3"], + "maskland": False, + "maskocean": True, + "title": ["La Nina SLPA DJF", "El Nino SLPA DJF"], + "varpattern": ["slp_nina_djf_map_oceania__", "slp_nino_djf_map_oceania__"], + "xname": "longitude", + "yname": "latitude", + "zname": "SLPA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) DJF averaged SLP\n4) Temporal mean SLP removed\n" + + "5) SLPA regridded to 1°x1°\n6) El Nino and La Nina DJF SLPA composited\n7) Mask ocean", + }, + }, + "EnsoSlpMapJja": { + "netcdf_variables": ["reg_slp_over_sst_jja_map__", "reg_slp_over_sst_jja_map__"], + "diagnostic": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "title": ["reg(ENSO SSTA, SLPA) JJA", "reg(ENSO SSTA, SLPA) JJA"], + # "varpattern": "sst_over_sst_map__", + "varpattern": "reg_slp_over_sst_jja_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) JJA averaged\n4) Temporal mean removed\n" + + "5) SLPA regridded to 1°x1°\n6) JJA N3.4 SSTA regressed onto JJA SLPA\n" + + "7) Equatorial Pacific masked\n\nMetric: RMSE$_{xy}$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "title": ["La Nina SLPA JJA", "El Nino SLPA JJA"], + "varpattern": ["slp_nina_jja_map__", "slp_nino_jja_map__"], + "xname": "longitude", + "yname": "latitude", + "zname": "SLPA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) JJA averaged SLP\n4) Temporal mean SLP removed\n" + + "5) SLPA regridded to 1°x1°\n6) El Nino and La Nina JJA SLPA composited\n" + + "7) Equatorial Pacific masked", + }, + # ["africaSE", "americaN", "americaS", "asiaS", "oceania"] + "dive_down02": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, SLPA) JJA", "reg(ENSO SSTA, SLPA) JJA"], + "varpattern": "reg_slp_over_sst_jja_map_africaSE__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) JJA averaged\n4) Temporal mean removed\n" + + "5) SLPA regridded to 1°x1°\n6) JJA N3.4 SSTA regressed onto JJA SLPA\n7) Mask ocean", + }, + "dive_down03": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, SLPA) JJA", "reg(ENSO SSTA, SLPA) JJA"], + "varpattern": "reg_slp_over_sst_jja_map_americaN__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) JJA averaged\n4) Temporal mean removed\n" + + "5) SLPA regridded to 1°x1°\n6) JJA N3.4 SSTA regressed onto JJA SLPA\n7) Mask ocean", + }, + "dive_down04": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, SLPA) JJA", "reg(ENSO SSTA, SLPA) JJA"], + "varpattern": "reg_slp_over_sst_jja_map_americaS__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) JJA averaged\n4) Temporal mean removed\n" + + "5) SLPA regridded to 1°x1°\n6) JJA N3.4 SSTA regressed onto JJA SLPA\n7) Mask ocean", + }, + "dive_down05": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, SLPA) JJA", "reg(ENSO SSTA, SLPA) JJA"], + "varpattern": "reg_slp_over_sst_jja_map_asiaS__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) JJA averaged\n4) Temporal mean removed\n" + + "5) SLPA regridded to 1°x1°\n6) JJA N3.4 SSTA regressed onto JJA SLPA\n7) Mask ocean", + }, + "dive_down06": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, SLPA) JJA", "reg(ENSO SSTA, SLPA) JJA"], + "varpattern": "reg_slp_over_sst_jja_map_oceania__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) JJA averaged\n4) Temporal mean removed\n" + + "5) SLPA regridded to 1°x1°\n6) JJA N3.4 SSTA regressed onto JJA SLPA\n7) Mask ocean", + }, + "dive_down07": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["La Nina SLPA JJA", "El Nino SLPA JJA"], + "varpattern": ["slp_nina_jja_map_africaSE__", "slp_nino_jja_map_africaSE__"], + "xname": "longitude", + "yname": "latitude", + "zname": "SLPA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) JJA averaged SLP\n4) Temporal mean SLP removed\n" + + "5) SLPA regridded to 1°x1°\n6) El Nino and La Nina JJA SLPA composited\n7) Mask ocean", + }, + "dive_down08": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["La Nina SLPA JJA", "El Nino SLPA JJA"], + "varpattern": ["slp_nina_jja_map_americaN__", "slp_nino_jja_map_americaN__"], + "xname": "longitude", + "yname": "latitude", + "zname": "SLPA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) JJA averaged SLP\n4) Temporal mean SLP removed\n" + + "5) SLPA regridded to 1°x1°\n6) El Nino and La Nina JJA SLPA composited\n7) Mask ocean", + }, + "dive_down09": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["La Nina SLPA JJA", "El Nino SLPA JJA"], + "varpattern": ["slp_nina_jja_map_americaS__", "slp_nino_jja_map_americaS__"], + "xname": "longitude", + "yname": "latitude", + "zname": "SLPA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) JJA averaged SLP\n4) Temporal mean SLP removed\n" + + "5) SLPA regridded to 1°x1°\n6) El Nino and La Nina JJA SLPA composited\n7) Mask ocean", + }, + "dive_down10": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["La Nina SLPA JJA", "El Nino SLPA JJA"], + "varpattern": ["slp_nina_jja_map_asiaS__", "slp_nino_jja_map_asiaS__"], + "xname": "longitude", + "yname": "latitude", + "zname": "SLPA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) JJA averaged SLP\n4) Temporal mean SLP removed\n" + + "5) SLPA regridded to 1°x1°\n6) El Nino and La Nina JJA SLPA composited\n7) Mask ocean", + }, + "dive_down11": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["La Nina SLPA JJA", "El Nino SLPA JJA"], + "varpattern": ["slp_nina_jja_map_oceania__", "slp_nino_jja_map_oceania__"], + "xname": "longitude", + "yname": "latitude", + "zname": "SLPA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) JJA averaged SLP\n4) Temporal mean SLP removed\n" + + "5) SLPA regridded to 1°x1°\n6) El Nino and La Nina JJA SLPA composited\n7) Mask ocean", + }, + }, + "EnsoSstLonRmse": { + "netcdf_variables": ["sst_over_sst_lon__", "sst_over_sst_map__", "Nina_sst_lon__", "Nino_sst_lon__", + "Nina_sst_map__", "Nino_sst_map__"], + "diagnostic": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "ENSO pattern", + #"varpattern": "sst_over_sst_lon__", + "varpattern": "sst_against_sst_lon__", + "xname": "longitude", + "yname": "reg(ENSO SSTA, SSTA)", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) N3.4 SSTA averaged\n5) SSTA regridded to 1°x1°\n6) 5S-5N meridional SSTA averaged\n" + + "7) Dec. N3.4 SSTA regressed onto Dec. SSTA\n\nMetric: RMSE$_x$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["SKEW"], + "maskland": True, + "title": ["reg(ENSO SSTA, SSTA)", "reg(ENSO SSTA, SSTA)"], + #"varpattern": "sst_over_sst_map__", + "varpattern": "sst_against_sst_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) N3.4 SSTA averaged\n5) SSTA regridded to 1°x1°\n6) Dec. N3.4 SSTA regressed onto Dec. SSTA", + }, + "dive_down02": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "ENSO's SSTA pattern", + "varpattern": ["Nina_sst_lon__", "Nino_sst_lon__"], + "colors": {"model": ["blue", "red"], "reference": ["blue", "red"]}, + "linestyles": {"model": ["-", "-"], "reference": ["-.", "-."]}, + "legend": ["La Nina", "El Nino"], + "xname": "longitude", + "yname": "ENSO SSTA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Seasonal cycle removed\n3) Linearly detrended\n4) 5-month triangular running ave.\n" + + "5) SSTA regridded to 1°x1°\n6) 5S-5N meridional SSTA averaged\n" + + "7) El Nino and La Nina Dec. SSTA composited", + }, + "dive_down03": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG25"], + "maskland": True, + "title": ["La Nina SSTA", "El Nino SSTA"], + "varpattern": ["Nina_sst_map__", "Nino_sst_map__"], + "xname": "longitude", + "yname": "latitude", + "zname": "SSTA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Seasonal cycle removed\n3) Linearly detrended\n4) 5-month triangular running ave.\n" + + "5) SSTA regridded to 1°x1°\n6) El Nino and La Nina Dec. SSTA composited", + }, + }, + "EnsoSstMap": { + "netcdf_variables": ["reg_ts_over_sst_map__", "reg_ts_over_sst_map__"], + "diagnostic": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "title": ["reg(ENSO SSTA, TSA)", "reg(ENSO SSTA, TSA)"], + "varpattern": "reg_ts_over_sst_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) N3.4 SSTA averaged\n5) TSA regridded to 1°x1°\n6) Dec. N3.4 SSTA regressed onto Dec. TSA\n" + + "7) Equatorial Pacific masked\n\nMetric: RMSE$_{xy}$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, TSA)", "reg(ENSO SSTA, TSA)"], + "varpattern": "reg_ts_over_sst_map_africaSE__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) N3.4 SSTA averaged\n5) TSA regridded to 1°x1°\n6) Dec. N3.4 SSTA regressed onto Dec. TSA\n" + + "7) Mask ocean", + }, + "dive_down02": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, TSA)", "reg(ENSO SSTA, TSA)"], + "varpattern": "reg_ts_over_sst_map_americaN__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) N3.4 SSTA averaged\n5) TSA regridded to 1°x1°\n6) Dec. N3.4 SSTA regressed onto Dec. TSA\n" + + "7) Mask ocean", + }, + "dive_down03": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, TSA)", "reg(ENSO SSTA, TSA)"], + "varpattern": "reg_ts_over_sst_map_americaS__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) N3.4 SSTA averaged\n5) TSA regridded to 1°x1°\n6) Dec. N3.4 SSTA regressed onto Dec. TSA\n" + + "7) Mask ocean", + }, + "dive_down04": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, TSA)", "reg(ENSO SSTA, TSA)"], + "varpattern": "reg_ts_over_sst_map_asiaS__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) N3.4 SSTA averaged\n5) TSA regridded to 1°x1°\n6) Dec. N3.4 SSTA regressed onto Dec. TSA\n" + + "7) Mask ocean", + }, + "dive_down05": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, TSA)", "reg(ENSO SSTA, TSA)"], + "varpattern": "reg_ts_over_sst_map_oceania__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) N3.4 SSTA averaged\n5) TSA regridded to 1°x1°\n6) Dec. N3.4 SSTA regressed onto Dec. TSA\n" + + "7) Mask ocean", + }, + }, + "EnsoSstMapDjf": { + "netcdf_variables": ["reg_ts_over_sst_djf_map__", "reg_ts_over_sst_djf_map__"], + "diagnostic": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "title": ["reg(ENSO SSTA, TSA) DJF", "reg(ENSO SSTA, TSA) DJF"], + # "varpattern": "sst_over_sst_map__", + "varpattern": "reg_ts_over_sst_djf_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) DJF averaged\n4) Temporal mean removed\n" + + "5) TSA regridded to 1°x1°\n6) DJF N3.4 SSTA regressed onto DJF TSA\n" + + "7) Equatorial Pacific masked\n\nMetric: RMSE$_{xy}$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "title": ["La Nina TSA DJF", "El Nino TSA DJF"], + "varpattern": ["ts_nina_djf_map__", "ts_nino_djf_map__"], + "xname": "longitude", + "yname": "latitude", + "zname": "TSA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) DJF averaged TS\n4) Temporal mean TS removed\n" + + "5) TSA regridded to 1°x1°\n6) El Nino and La Nina DJF TSA composited\n" + + "7) Equatorial Pacific masked", + }, + # ["africaSE", "americaN", "americaS", "asiaS", "oceania"] + "dive_down02": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, TSA) DJF", "reg(ENSO SSTA, TSA) DJF"], + "varpattern": "reg_ts_over_sst_djf_map_africaSE__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) DJF averaged\n4) Temporal mean removed\n" + + "5) TSA regridded to 1°x1°\n6) DJF N3.4 SSTA regressed onto DJF TSA\n7) Mask ocean", + }, + "dive_down03": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, TSA) DJF", "reg(ENSO SSTA, TSA) DJF"], + "varpattern": "reg_ts_over_sst_djf_map_americaN__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) DJF averaged\n4) Temporal mean removed\n" + + "5) TSA regridded to 1°x1°\n6) DJF N3.4 SSTA regressed onto DJF TSA\n7) Mask ocean", + }, + "dive_down04": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, TSA) DJF", "reg(ENSO SSTA, TSA) DJF"], + "varpattern": "reg_ts_over_sst_djf_map_americaS__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) DJF averaged\n4) Temporal mean removed\n" + + "5) TSA regridded to 1°x1°\n6) DJF N3.4 SSTA regressed onto DJF TSA\n7) Mask ocean", + }, + "dive_down05": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, TSA) DJF", "reg(ENSO SSTA, TSA) DJF"], + "varpattern": "reg_ts_over_sst_djf_map_asiaS__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) DJF averaged\n4) Temporal mean removed\n" + + "5) TSA regridded to 1°x1°\n6) DJF N3.4 SSTA regressed onto DJF TSA\n7) Mask ocean", + }, + "dive_down06": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, TSA) DJF", "reg(ENSO SSTA, TSA) DJF"], + "varpattern": "reg_ts_over_sst_djf_map_oceania__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) DJF averaged\n4) Temporal mean removed\n" + + "5) TSA regridded to 1°x1°\n6) DJF N3.4 SSTA regressed onto DJF TSA\n7) Mask ocean", + }, + "dive_down07": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["La Nina TSA DJF", "El Nino TSA DJF"], + "varpattern": ["ts_nina_djf_map_africaSE__", "ts_nino_djf_map_africaSE__"], + "xname": "longitude", + "yname": "latitude", + "zname": "TSA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) DJF averaged TS\n4) Temporal mean TS removed\n" + + "5) TSA regridded to 1°x1°\n6) El Nino and La Nina DJF TSA composited\n7) Mask ocean", + }, + "dive_down08": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["La Nina TSA DJF", "El Nino TSA DJF"], + "varpattern": ["ts_nina_djf_map_americaN__", "ts_nino_djf_map_americaN__"], + "xname": "longitude", + "yname": "latitude", + "zname": "TSA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) DJF averaged TS\n4) Temporal mean TS removed\n" + + "5) TSA regridded to 1°x1°\n6) El Nino and La Nina DJF TSA composited\n7) Mask ocean", + }, + "dive_down09": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["La Nina TSA DJF", "El Nino TSA DJF"], + "varpattern": ["ts_nina_djf_map_americaS__", "ts_nino_djf_map_americaS__"], + "xname": "longitude", + "yname": "latitude", + "zname": "TSA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) DJF averaged TS\n4) Temporal mean TS removed\n" + + "5) TSA regridded to 1°x1°\n6) El Nino and La Nina DJF TSA composited\n7) Mask ocean", + }, + "dive_down10": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["La Nina TSA DJF", "El Nino TSA DJF"], + "varpattern": ["ts_nina_djf_map_asiaS__", "ts_nino_djf_map_asiaS__"], + "xname": "longitude", + "yname": "latitude", + "zname": "TSA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) DJF averaged TS\n4) Temporal mean TS removed\n" + + "5) TSA regridded to 1°x1°\n6) El Nino and La Nina DJF TSA composited\n7) Mask ocean", + }, + "dive_down11": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "maskocean": True, + "title": ["La Nina TSA DJF", "El Nino TSA DJF"], + "varpattern": ["ts_nina_djf_map_oceania__", "ts_nino_djf_map_oceania__"], + "xname": "longitude", + "yname": "latitude", + "zname": "TSA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) DJF averaged TS\n4) Temporal mean TS removed\n" + + "5) TSA regridded to 1°x1°\n6) El Nino and La Nina DJF TSA composited\n7) Mask ocean", + }, + }, + "EnsoSstMapJja": { + "netcdf_variables": ["reg_ts_over_sst_jja_map__", "reg_ts_over_sst_jja_map__"], + "diagnostic": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "title": ["reg(ENSO SSTA, TSA) JJA", "reg(ENSO SSTA, TSA) JJA"], + # "varpattern": "sst_over_sst_map__", + "varpattern": "reg_ts_over_sst_jja_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) JJA averaged\n4) Temporal mean removed\n" + + "5) TSA regridded to 1°x1°\n6) JJA N3.4 SSTA regressed onto JJA TSA\n" + + "7) Equatorial Pacific masked\n\nMetric: RMSE$_{xy}$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "title": ["La Nina TSA JJA", "El Nino TSA JJA"], + "varpattern": ["ts_nina_jja_map__", "ts_nino_jja_map__"], + "xname": "longitude", + "yname": "latitude", + "zname": "TSA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) JJA averaged TS\n4) Temporal mean TS removed\n" + + "5) TSA regridded to 1°x1°\n6) El Nino and La Nina JJA TSA composited\n" + + "7) Equatorial Pacific masked", + }, + "dive_down02": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, TSA) JJA", "reg(ENSO SSTA, TSA) JJA"], + "varpattern": "reg_ts_over_sst_jja_map_africaSE__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) JJA averaged\n4) Temporal mean removed\n" + + "5) TSA regridded to 1°x1°\n6) JJA N3.4 SSTA regressed onto JJA TSA\n7) Mask ocean", + }, + "dive_down03": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, TSA) JJA", "reg(ENSO SSTA, TSA) JJA"], + "varpattern": "reg_ts_over_sst_jja_map_americaN__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) JJA averaged\n4) Temporal mean removed\n" + + "5) TSA regridded to 1°x1°\n6) JJA N3.4 SSTA regressed onto JJA TSA\n7) Mask ocean", + }, + "dive_down04": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, TSA) JJA", "reg(ENSO SSTA, TSA) JJA"], + "varpattern": "reg_ts_over_sst_jja_map_americaS__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) JJA averaged\n4) Temporal mean removed\n" + + "5) TSA regridded to 1°x1°\n6) JJA N3.4 SSTA regressed onto JJA TSA\n7) Mask ocean", + }, + "dive_down05": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, TSA) JJA", "reg(ENSO SSTA, TSA) JJA"], + "varpattern": "reg_ts_over_sst_jja_map_asiaS__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) JJA averaged\n4) Temporal mean removed\n" + + "5) TSA regridded to 1°x1°\n6) JJA N3.4 SSTA regressed onto JJA TSA\n7) Mask ocean", + }, + "dive_down06": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["reg(ENSO SSTA, TSA) JJA", "reg(ENSO SSTA, TSA) JJA"], + "varpattern": "reg_ts_over_sst_jja_map_oceania__", + "xname": "longitude", + "yname": "latitude", + "zname": "regression", + "method": "1) Linearly detrended\n2) N3.4 SST averaged\n3) JJA averaged\n4) Temporal mean removed\n" + + "5) TSA regridded to 1°x1°\n6) JJA N3.4 SSTA regressed onto JJA TSA\n7) Mask ocean", + }, + "dive_down07": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["La Nina TSA JJA", "El Nino TSA JJA"], + "varpattern": ["ts_nina_jja_map_africaSE__", "ts_nino_jja_map_africaSE__"], + "xname": "longitude", + "yname": "latitude", + "zname": "TSA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) JJA averaged TS\n4) Temporal mean TS removed\n" + + "5) TSA regridded to 1°x1°\n6) El Nino and La Nina JJA TSA composited\n7) Mask ocean", + }, + "dive_down08": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["La Nina TSA JJA", "El Nino TSA JJA"], + "varpattern": ["ts_nina_jja_map_americaN__", "ts_nino_jja_map_americaN__"], + "xname": "longitude", + "yname": "latitude", + "zname": "TSA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) JJA averaged TS\n4) Temporal mean TS removed\n" + + "5) TSA regridded to 1°x1°\n6) El Nino and La Nina JJA TSA composited\n7) Mask ocean", + }, + "dive_down09": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["La Nina TSA JJA", "El Nino TSA JJA"], + "varpattern": ["ts_nina_jja_map_americaS__", "ts_nino_jja_map_americaS__"], + "xname": "longitude", + "yname": "latitude", + "zname": "TSA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) JJA averaged TS\n4) Temporal mean TS removed\n" + + "5) TSA regridded to 1°x1°\n6) El Nino and La Nina JJA TSA composited\n7) Mask ocean", + }, + "dive_down10": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["La Nina TSA JJA", "El Nino TSA JJA"], + "varpattern": ["ts_nina_jja_map_asiaS__", "ts_nino_jja_map_asiaS__"], + "xname": "longitude", + "yname": "latitude", + "zname": "TSA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) JJA averaged TS\n4) Temporal mean TS removed\n" + + "5) TSA regridded to 1°x1°\n6) El Nino and La Nina JJA TSA composited\n7) Mask ocean", + }, + "dive_down11": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["PRA"], + "maskland": False, + "maskocean": True, + "title": ["La Nina TSA JJA", "El Nino TSA JJA"], + "varpattern": ["ts_nina_jja_map_oceania__", "ts_nino_jja_map_oceania__"], + "xname": "longitude", + "yname": "latitude", + "zname": "TSA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Linearly detrended\n3) JJA averaged TS\n4) Temporal mean TS removed\n" + + "5) TSA regridded to 1°x1°\n6) El Nino and La Nina JJA TSA composited\n7) Mask ocean", + }, + }, + "EnsoSstTsRmse": { + "netcdf_variables": ["sst_over_sst_ts__", "sst_over_sst_hov__", "Nina_sst_ts__", "Nino_sst_ts__", + "Nina_sst_hov__", "Nino_sst_hov__"], + "diagnostic": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "ENSO life-cycle", + #"varpattern": "sst_over_sst_ts__", + "varpattern": "sst_against_sst_ts__", + "xname": "months", + "yname": "reg(ENSO SSTA, SSTA)", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) Horizontal averaged\n5) Dec. N3.4 SSTA regressed onto REGION1 SSTA\n\nMetric: RMSE$_{t}$", + }, + "dive_down01": { + "plot_type": "hovmoeller", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG12"], + "title": ["reg(ENSO SSTA, SSTA)", "reg(ENSO SSTA, SSTA)"], + #"varpattern": "sst_over_sst_hov__", + "varpattern": "sst_against_sst_hov__", + "xname": "longitude", + "yname": "months", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) N3.4 SSTA averaged\n5) SSTA regridded to 1°x1°\n" + + "6) 5S-5N meridional SSTA average\n7) Dec. N3.4 SSTA regressed onto Dec. SSTA", + }, + "dive_down02": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "ENSO's SSTA life-cycle", + "varpattern": ["Nina_sst_ts__", "Nino_sst_ts__"], + "colors": {"model": ["blue", "red"], "reference": ["blue", "red"]}, + "linestyles": {"model": ["-", "-"], "reference": ["-.", "-."]}, + "legend": ["La Nina", "El Nino"], + "xname": "months", + "yname": "ENSO SSTA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Seasonal cycle removed\n3) Linearly detrended\n4) 5-month triangular running ave.\n" + + "5) REGION1 SSTA averaged\n6) El Nino and La Nina SSTA composited", + }, + "dive_down03": { + "plot_type": "hovmoeller", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "title": ["La Nina SSTA", "El Nino SSTA"], + "varpattern": ["Nina_sst_hov__", "Nino_sst_hov__"], + "xname": "longitude", + "yname": "months", + "zname": "SSTA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Seasonal cycle removed\n3) Linearly detrended\n4) 5-month triangular running ave.\n" + + "5) SSTA regridded to 1°x1°\n6) 5S-5N meridional SSTA average\n" + + "7) El Nino and La Nina SSTA composited", + }, + }, + "EnsoTauxTsRmse": { + "netcdf_variables": ["taux_over_sst_ts__", "taux_over_sst_hov__", "Nina_taux_ts__", "Nino_taux_ts__", + "Nina_taux_hov__", "Nino_taux_hov__"], + "diagnostic": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "ENSO life-cycle", + #"varpattern": "taux_over_sst_ts__", + "varpattern": "sst_against_taux_ts__", + "xname": "months", + "yname": "reg(ENSO SSTA, TAUXA)", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) Horizontal averaged\n5) Dec. N3.4 SSTA regressed onto REGION1 TAUXA\n\nMetric: RMSE$_{t}$", + }, + "dive_down01": { + "plot_type": "hovmoeller", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG20"], + "title": ["reg(ENSO SSTA, TAUXA)", "reg(ENSO SSTA, TAUXA)"], + #"varpattern": "taux_over_sst_hov__", + "varpattern": "sst_against_taux_hov__", + "xname": "longitude", + "yname": "months", + "zname": "regression", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) 5-month triangular running ave.\n" + + "4) N3.4 SSTA averaged\n5) TAUXA regridded to 1°x1°\n" + + "6) 5S-5N meridional TAUXA average\n7) Dec. N3.4 SSTA regressed onto TAUXA", + }, + "dive_down02": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "ENSO's TAUXA life-cycle", + "varpattern": ["Nina_taux_ts__", "Nino_taux_ts__"], + "colors": {"model": ["blue", "red"], "reference": ["blue", "red"]}, + "linestyles": {"model": ["-", "-"], "reference": ["-.", "-."]}, + "legend": ["La Nina", "El Nino"], + "xname": "months", + "yname": "ENSO TAUXA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Seasonal cycle removed\n3) Linearly detrended\n4) 5-month triangular running ave.\n" + + "5) REGION1 TAUXA averaged\n6) El Nino and La Nina TAUXA composited", + }, + "dive_down03": { + "plot_type": "hovmoeller", + "nbr_panel": 4, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG30"], + "title": ["La Nina TAUXA", "El Nino TAUXA"], + "varpattern": ["Nina_taux_hov__", "Nino_taux_hov__"], + "xname": "longitude", + "yname": "months", + "zname": "TAUXA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Seasonal cycle removed\n3) Linearly detrended\n4) 5-month triangular running ave.\n" + + "5) TAUXA regridded to 1°x1°\n6) 5S-5N meridional TAUXA average\n" + + "7) El Nino and La Nina TAUXA composited", + }, + }, + "EnsoSeasonality": { + "netcdf_variables": ["sstStd_monthly__", "sstStd_hov__", "sstStd_NDJ_lon__", "sstStd_MAM_lon__", + "sstStd_NDJ_map__", "sstStd_MAM_map__"], + "diagnostic": { + "plot_type": "dot", + "nbr_panel": 1, + "title": "ENSO Seasonality", + "varpattern": "diagnostic", + "yname": "SSTA std (NDJ/MAM)", + "method": "1) Linearly detrended\n2) REGION1 averaged\n3) NDJ and MAM averaged\n" + + "4) Temporal mean removed\n5) Standard deviation\n6) ratio = STD$_{NDJ}$/STD$_{MAM}$\n\n" + + "Metric: abs((Ratio$_{mod}$-Ratio$_{ref}$)/Ratio$_{ref}$)*100", + }, + "dive_down01": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "SSTA standard deviation", + "varpattern": "sstStd_monthly__", + "xname": "months", + "yname": "SSTA std", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) REGION1 averaged\n" + + "4) Standard deviation for each calendar month", + }, + "dive_down02": { + "plot_type": "hovmoeller", + "nbr_panel": 2, + "colorbar": dict_colorbar["amplitude"], + "label": dict_label["amplitude"], + "title": ["SSTA standard deviation", "SSTA standard deviation"], + "varpattern": "sstStd_hov__", + "xname": "longitude", + "yname": "months", + "zname": "SSTA std", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n" + + "3) Standard deviation for each calendar month\n4) SSTA regridded to 1°x1°\n" + + "5) 5S-5N meridional averaged", + }, + "dive_down03": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "SSTA standard deviation", + "varpattern": ["sstStd_NDJ_lon__", "sstStd_MAM_lon__"], + "colors": {"model": ["red", "blue"], "reference": ["red", "blue"]}, + "linestyles": {"model": ["-", "-"], "reference": ["-.", "-."]}, + "legend": ["NDJ", "MAM"], + "xname": "longitude", + "yname": "SSTA std", + "method": "1) Linearly detrended\n2) REGION1 averaged\n3) NDJ and MAM averaged\n" + + "4) Temporal mean removed\n5) Standard deviation\n6) Regridded to 1°x1°\n" + + "7) 5S-5N meridional averaged", + }, + "dive_down04": { + "plot_type": "map", + "nbr_panel": 4, + "colorbar": dict_colorbar["amplitude"], + "label": dict_label["amplitude"], + "maskland": True, + "title": ["NDJ", "MAM"], #["SSTA std NDJ", "SSTA std MAM"], + "varpattern": ["sstStd_NDJ_map__", "sstStd_MAM_map__"], + "xname": "longitude", + "yname": "latitude", + "zname": "monthly SSTA std", + "method": "1) Linearly detrended\n2) REGION1 averaged\n3) NDJ and MAM averaged\n" + + "4) Temporal mean removed\n5) Standard deviation\n6) Regridded to 1°x1°", + }, + }, + "EnsoSstDiversity": { + "netcdf_variables": ["Enso_lon_pos_maxSSTA__", "Nina_lon_pos_minSSTA__", "Nino_lon_pos_maxSSTA__"], + "diagnostic": { + "plot_type": "dot", + "nbr_panel": 1, + "title": "ENSO diversity", + "varpattern": "diagnostic", + "yname": "IQR of min/max SSTA", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Seasonal cycle removed\n3) Linearly detrended\n4) 5-month triangular running ave.\n" + + "5) SSTA regridded to 1°x1°\n6) 5S-5N meridional SSTA averaged\n7) 5° triangular running ave.\n" + + "8) Find zonal location of El Nino max(SSTA) and La Nina min(SSTA)\n" + + "7) IQR El Nino max(SSTA) with La Nina min(SSTA)\n\n" + + "Metric: abs((IQR$_{mod}$-IQR$_{ref}$)/IQR$_{ref}$)*100", + }, + "dive_down01": { + "plot_type": "boxplot", + "nbr_panel": 3, + "title": ["ENSO diversity", "La Nina diversity", "El Nino diversity"], + "varpattern": ["Enso_lon_pos_maxSSTA__", "Nina_lon_pos_minSSTA__", "Nino_lon_pos_maxSSTA__"], + "yname": ["longitude of min/max SSTA", "longitude of min SSTA", "longitude of max SSTA"], + "custom_label": "longitude", + "method": "1) Detect El Nino and La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n" + + "2) Seasonal cycle removed\n3) Linearly detrended\n4) 5-month triangular running ave.\n" + + "5) SSTA regridded to 1°x1°\n6) 5S-5N meridional SSTA averaged\n7) 5° triangular running ave.\n" + + "8) Find zonal location of El Nino max(SSTA) and La Nina min(SSTA)\n" + + "7) IQR El Nino max(SSTA) with La Nina min(SSTA)\nand IQR El Nino and IQR La Nina\n\n" + + "Metric: abs((IQR$_{mod}$-IQR$_{ref}$)/IQR$_{ref}$)*100", + }, + }, + "EnsoSstSkew": { + "netcdf_variables": ["sstSke_lon__", "sstSke_map__"], + "diagnostic": { + "plot_type": "dot", + "nbr_panel": 1, + "title": "ENSO skewness", + "varpattern": "diagnostic", + "yname": "SSTA skewness", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) REGION1 averaged\n4) Skewness\n\n" + + "Metric: abs((SKE$_{mod}$-SKE$_{ref}$)/SKE$_{ref}$)*100", + }, + "dive_down01": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "SSTA skewness", + "varpattern": "sstSke_lon__", + "xname": "longitude", + "yname": "SSTA skew", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) Skewness\n4) Regridded to 1°x1°\n" + + "5) 5S-5N meridional averaged", + }, + "dive_down02": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["SKEW"], + "maskland": True, + "title": ["SSTA skew", "SSTA skew"], + "varpattern": "sstSke_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "SSTA skew", + "method": "1) Seasonal cycle removed\n2) Linearly detrended\n3) Skewness\n4) Regridded to 1°x1°", + }, + }, + "NinaPrMap": { + "netcdf_variables": ["prComp_map__", "prComp_map__"], + "diagnostic": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG2"], + "maskland": False, + "title": ["La Nina composite", "La Nina composite"], + #"varpattern": "sst_over_sst_map__", + "varpattern": "prComp_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "PRA", + "method": "1) Detect La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA < -0.75 STD)\n" + + "2) Seasonal cycle removed\n3) Linearly detrended\n4) 5-month triangular running ave.\n" + + "5) PRA regridded to 1°x1°6) La Nina Dec. PRA composited\n\nMetric: RMSE$_{xy}$", + }, + }, + "NinaSlpMap": { + "netcdf_variables": ["slp_map__", "slp_map__"], + "diagnostic": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG3"], + "maskland": False, + "title": ["La Nina composite", "La Nina composite"], + #"varpattern": "sst_over_sst_map__", + "varpattern": "slp_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "SLPA", + "method": "1) Detect La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA < -0.75 STD)\n" + + "2) Seasonal cycle removed\n3) Linearly detrended\n4) 5-month triangular running ave.\n" + + "5) SLPA regridded to 1°x1°6) La Nina Dec. SLPA composited\n\nMetric: RMSE$_{xy}$", + }, + }, + "NinaSstMap": { + "netcdf_variables": ["ts_map__", "ts_map__"], + "diagnostic": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["SKEW"], + "maskland": False, + "title": ["La Nina composite", "La Nina composite"], + #"varpattern": "sst_over_sst_map__", + "varpattern": "ts_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "TSA", + "method": "1) Detect La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA < -0.75 STD)\n" + + "2) Seasonal cycle removed\n3) Linearly detrended\n4) 5-month triangular running ave.\n" + + "5) TSA regridded to 1°x1°6) La Nina Dec. TSA composited\n\nMetric: RMSE$_{xy}$", + }, + }, + "NinaSstDur": { + "netcdf_variables": ["Nina_duration__"], + "diagnostic": { + "plot_type": "dot", + "nbr_panel": 1, + "title": "La Nina duration", + "varpattern": "diagnostic", + "yname": "duration (SSTA<-0.5)", + "method": "1) Detect La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA < -0.75 STD)\n" + + "2) Seasonal cycle removed\n3) Linearly detrended\n4) 5-month triangular running ave.\n" + + "5) REGION1 averaged\n6) Duration = nbr months < -0.5 ENSO STD\n7) Mean La Nina duration\n\n" + + "Metric: abs((DUR$_{mod}$-DUR$_{ref}$)/DUR$_{ref}$)*100", + }, + "dive_down01": { + "plot_type": "boxplot", + "nbr_panel": 1, + "title": "La Nina duration", + "varpattern": "Nina_duration__", + "yname": "duration (SSTA<-0.5)", + "method": "1) Detect La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA < -0.75 STD)\n" + + "2) Seasonal cycle removed\n3) Linearly detrended\n4) 5-month triangular running ave.\n" + + "5) REGION1 averaged\n6) Duration = nbr months < -0.5 ENSO STD", + }, + }, + "NinaSstLonRmse": { + "netcdf_variables": ["sst_lon__", "sst_map__"], + "diagnostic": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "La Nina pattern", + "varpattern": "sst_lon__", + "xname": "longitude", + "yname": "SSTA", + "method": "1) Detect La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA < -0.75 STD)\n" + + "2) Seasonal cycle removed\n3) Linearly detrended\n4) 5-month triangular running ave.\n" + + "5) SSTA regridded to 1°x1°\n6) 5S-5N meridional SSTA averaged\n" + + "7) La Nina Dec. SSTA composited\n\nMetric: RMSE$_x$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["dSST"], + "maskland": True, + "title": "La Nina SSTA", + "varpattern": "sst_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "SSTA", + "method": "1) Detect La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA < -0.75 STD)\n" + + "2) Seasonal cycle removed\n3) Linearly detrended\n4) 5-month triangular running ave.\n" + + "5) SSTA regridded to 1°x1°\n6) La Nina Dec. SSTA composited", + }, + }, + "NinaSstTsRmse": { + "netcdf_variables": ["sst_ts__", "sst_hov__"], + "diagnostic": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "La Nina life-cycle", + "varpattern": "sst_ts__", + "xname": "months", + "yname": "SSTA", + "method": "1) Detect La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA < -0.75 STD)\n" + + "2) Seasonal cycle removed\n3) Linearly detrended\n4) 5-month triangular running ave.\n" + + "5) REGION1 SSTA averaged\n6) La Nina SSTA composited\n\nMetric: RMSE$_{t}$", + }, + "dive_down01": { + "plot_type": "hovmoeller", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["SKEW"], + "title": "La Nina SSTA", + "varpattern": "sst_hov__", + "xname": "longitude", + "yname": "months", + "zname": "SSTA", + "method": "1) Detect La Nina\n (5-m. tri. ave. Dec. N3.4 SSTA < -0.75 STD)\n" + + "2) Seasonal cycle removed\n3) Linearly detrended\n4) 5-month triangular running ave.\n" + + "5) SSTA regridded to 1°x1°\n6) 5S-5N meridional SSTA average\n7) La Nina SSTA composited", + }, + }, + "NinoPrMap": { + "netcdf_variables": ["pr_map__", "pr_map__"], + "diagnostic": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["SKEW"], + "maskland": False, + "title": ["El Nino composite", "El Nino composite"], + #"varpattern": "sst_over_sst_map__", + "varpattern": "pr_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "PRA", + "method": "1) Detect El Nino\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n2) Seasonal cycle removed\n" + + "3) Linearly detrended\n4) 5-month triangular running ave.\n5) PRA regridded to 1°x1°" + + "6) El Nino Dec. PRA composited\n\nMetric: RMSE$_{xy}$", + }, + }, + "NinoSlpMap": { + "netcdf_variables": ["slp_map__", "slp_map__"], + "diagnostic": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG3"], + "maskland": False, + "title": ["El Nino composite", "El Nino composite"], + #"varpattern": "sst_over_sst_map__", + "varpattern": "slp_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "SLPA", + "method": "1) Detect El Nino\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n2) Seasonal cycle removed\n" + + "3) Linearly detrended\n4) 5-month triangular running ave.\n5) SLPA regridded to 1°x1°" + + "6) El Nino Dec. SLPA composited\n\nMetric: RMSE$_{xy}$", + }, + }, + "NinoSstMap": { + "netcdf_variables": ["ts_map__", "ts_map__"], + "diagnostic": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["SKEW"], + "maskland": False, + "title": ["El Nino composite", "El Nino composite"], + #"varpattern": "sst_over_sst_map__", + "varpattern": "ts_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "SSTA", + "method": "1) Detect El Nino\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n2) Seasonal cycle removed\n" + + "3) Linearly detrended\n4) 5-month triangular running ave.\n5) SSTA regridded to 1°x1°" + + "6) El Nino Dec. SSTA composited\n\nMetric: RMSE$_{xy}$", + }, + }, + "NinoSstDiversity": { + "netcdf_variables": ["Nina_lon_pos_minSSTA__", "Nino_lon_pos_maxSSTA__"], + "diagnostic": { + "plot_type": "dot", + "nbr_panel": 1, + "title": "El Nino diversity", + "varpattern": "diagnostic", + "yname": "IQR of max SSTA", + "method": "1) Detect El Nino\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n2) Seasonal cycle removed\n" + + "3) Linearly detrended\n4) 5-month triangular running ave.\n5) SSTA regridded to 1°x1°\n" + + "6) 5S-5N meridional SSTA averaged\n7) 5° triangular running ave.\n" + + "8) Find zonal location of max(SSTA)\n7) IQR El Nino max(SSTA)\n\n" + + "Metric: abs((IQR$_{mod}$-IQR$_{ref}$)/IQR$_{ref}$)*100", + }, + "dive_down01": { + "plot_type": "boxplot", + "nbr_panel": 2, + "title": ["La Nina diversity", "El Nino diversity"], + "varpattern": ["Nina_lon_pos_minSSTA__", "Nino_lon_pos_maxSSTA__"], + "yname": ["longitude of min SSTA", "longitude of max SSTA"], + "custom_label": "longitude", + "method": "1) Detect El Nino\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n2) Seasonal cycle removed\n" + + "3) Linearly detrended\n4) 5-month triangular running ave.\n5) SSTA regridded to 1°x1°\n" + + "6) 5S-5N meridional SSTA averaged\n7) 5° triangular running ave.\n" + + "8) Find zonal location of max(SSTA)", + }, + }, + "NinoSstDur": { + "netcdf_variables": ["Nino_duration__"], + "diagnostic": { + "plot_type": "dot", + "nbr_panel": 1, + "title": "El Nino duration", + "varpattern": "diagnostic", + "yname": "duration (SSTA>0.5)", + "method": "1) Detect El Nino\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n2) Seasonal cycle removed\n" + + "3) Linearly detrended\n4) 5-month triangular running ave.\n5) REGION1 averaged\n" + + "6) Duration = nbr months > 0.5 ENSO STD\n7) Mean El Nino duration\n\n" + + "Metric: abs((DUR$_{mod}$-DUR$_{ref}$)/DUR$_{ref}$)*100", + }, + "dive_down01": { + "plot_type": "boxplot", + "nbr_panel": 1, + "title": "El Nino duration", + "varpattern": "Nino_duration__", + "yname": "duration (SSTA>0.5)", + "method": "1) Detect El Nino\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n2) Seasonal cycle removed\n" + + "3) Linearly detrended\n4) 5-month triangular running ave.\n5) REGION1 averaged\n" + + "6) Duration = nbr months > 0.5 ENSO STD", + }, + }, + "NinoSstLonRmse": { + "netcdf_variables": ["sst_lon__", "sst_map__"], + "diagnostic": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "El Nino pattern", + "varpattern": "sst_lon__", + "xname": "longitude", + "yname": "SSTA", + "method": "1) Detect El Nino\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n2) Seasonal cycle removed\n" + + "3) Linearly detrended\n4) 5-month triangular running ave.\n5) SSTA regridded to 1°x1°\n" + + "6) 5S-5N meridional SSTA averaged\n7) El Nino Dec. SSTA composited\n\nMetric: RMSE$_x$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["dSST"], + "maskland": True, + "title": "El Nino SSTA", + "varpattern": "sst_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "SSTA", + "method": "1) Detect El Nino\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n2) Seasonal cycle removed\n" + + "3) Linearly detrended\n4) 5-month triangular running ave.\n5) SSTA regridded to 1°x1°\n" + + "6) El Nino Dec. SSTA composited", + }, + }, + "NinoSstTsRmse": { + "netcdf_variables": ["sst_ts__", "sst_hov__"], + "diagnostic": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "El Nino life-cycle", + "varpattern": "sst_ts__", + "xname": "months", + "yname": "SSTA", + "method": "1) Detect El Nino\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n2) Seasonal cycle removed\n" + + "3) Linearly detrended\n4) 5-month triangular running ave.\n5) REGION1 SSTA averaged\n" + + "6) El Nino SSTA composited\n\nMetric: RMSE$_{t}$", + }, + "dive_down01": { + "plot_type": "hovmoeller", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["SKEW"], + "title": "El Nino SSTA", + "varpattern": "sst_hov__", + "xname": "longitude", + "yname": "months", + "zname": "SSTA", + "method": "1) Detect El Nino\n (5-m. tri. ave. Dec. N3.4 SSTA > 0.75 STD)\n2) Seasonal cycle removed\n" + + "3) Linearly detrended\n4) 5-month triangular running ave.\n5) SSTA regridded to 1°x1°\n" + + "6) 5S-5N meridional SSTA average\n7) El Nino SSTA composited", + }, + }, + "SeasonalPrLatRmse": { + "netcdf_variables": ["pr_lat__", "pr_map__", "prMac_hov__"], + "diagnostic": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "PR seasonal cycle std", + "varpattern": "pr_lat__", + "xname": "latitude", + "yname": "PR std", + "method": "1) Linearly detrended\n2) Seasonal cycle computed\n3) Standard deviation\n" + + "4) Regridded to 1°x1°\n5) Zonal averaged (see box)\n\nMetric: RMSE$_y$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["amplitude"], + "label": dict_label["amplitude5"], + "maskland": True, + "title": ["PR seasonal cycle std"], + "varpattern": "pr_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "PR std", + "method": "1) Linearly detrended\n2) Seasonal cycle computed\n3) Standard deviation\n" + + "4) Regridded to 1°x1°", + }, + "dive_down02": { + "plot_type": "hovmoeller", + "nbr_panel": 2, + "colorbar": dict_colorbar["PR"], + "label": dict_label["amplitude15"], + "title": ["PR seasonal cycle", "PR seasonal cycle"], + "varpattern": "prMac_hov__", + "xname": "latitude", + "yname": "months", + "zname": "PR", + "method": "1) Linearly detrended\n2) Seasonal cycle computed\n3) Regridded to 1°x1°\n" + + "5) Zonal averaged (see box)", + }, + }, + "SeasonalPrLonRmse": { + "netcdf_variables": ["pr_lon__", "pr_map__", "prMac_hov__"], + "diagnostic": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "PR seasonal cycle std", + "varpattern": "pr_lon__", + "xname": "longitude", + "yname": "PR std", + "method": "1) Linearly detrended\n2) Seasonal cycle computed\n3) Standard deviation\n" + + "4) Regridded to 1°x1°\n5) Meridional averaged (see box)\n\nMetric: RMSE$_x$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["amplitude"], + "label": dict_label["amplitude5"], + "maskland": True, + "title": ["PR seasonal cycle std"], + "varpattern": "pr_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "PR std", + "method": "1) Linearly detrended\n2) Seasonal cycle computed\n3) Standard deviation\n" + + "4) Regridded to 1°x1°", + }, + "dive_down02": { + "plot_type": "hovmoeller", + "nbr_panel": 2, + "colorbar": dict_colorbar["PR"], + "label": dict_label["amplitude10"], + "title": ["PR seasonal cycle", "PR seasonal cycle"], + "varpattern": "prMac_hov__", + "xname": "longitude", + "yname": "months", + "zname": "PR", + "method": "1) Linearly detrended\n2) Seasonal cycle computed\n3) Regridded to 1°x1°\n" + + "5) Meridional averaged (see box)", + }, + }, + "SeasonalSshLatRmse": { + "netcdf_variables": ["ssh_lat__", "ssh_map__", "sshMac_hov__"], + "diagnostic": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "SSH seasonal cycle std", + "varpattern": "ssh_lat__", + "xname": "latitude", + "yname": "SSH std", + "method": "1) Linearly detrended\n2) Seasonal cycle computed\n3) Standard deviation\n" + + "4) Regridded to 1°x1°\n5) Zonal averaged (see box)\n\nMetric: RMSE$_y$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["amplitude"], # YYP: I do not know yet the colobar / label needed + "label": dict_label["amplitude"], + "maskland": True, + "title": ["SSH seasonal cycle std"], + "varpattern": "ssh_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "SSH std", + "method": "1) Linearly detrended\n2) Seasonal cycle computed\n3) Standard deviation\n" + + "4) Regridded to 1°x1°", + }, + "dive_down02": { + "plot_type": "hovmoeller", + "nbr_panel": 2, + "colorbar": dict_colorbar["SST"], # YYP: I do not know yet the colobar / label needed + "label": dict_label["SST"], + "title": ["SSH seasonal cycle", "SSH seasonal cycle"], + "varpattern": "sshMac_hov__", + "xname": "latitude", + "yname": "months", + "zname": "SSH", + "method": "1) Linearly detrended\n2) Seasonal cycle computed\n3) Regridded to 1°x1°\n" + + "5) Zonal averaged (see box)", + }, + }, + "SeasonalSshLonRmse": { + "netcdf_variables": ["ssh_lon__", "ssh_map__", "sshMac_hov__"], + "diagnostic": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "SSH seasonal cycle std", + "varpattern": "ssh_lon__", + "xname": "longitude", + "yname": "SSH std", + "method": "1) Linearly detrended\n2) Seasonal cycle computed\n3) Standard deviation\n" + + "4) Regridded to 1°x1°\n5) Meridional averaged (see box)\n\nMetric: RMSE$_x$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["amplitude"], # YYP: I do not know yet the colobar / label needed + "label": dict_label["amplitude"], + "maskland": True, + "title": ["SSH seasonal cycle std"], + "varpattern": "ssh_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "SSH std", + "method": "1) Linearly detrended\n2) Seasonal cycle computed\n3) Standard deviation\n" + + "4) Regridded to 1°x1°", + }, + "dive_down02": { + "plot_type": "hovmoeller", + "nbr_panel": 2, + "colorbar": dict_colorbar["SST"], # YYP: I do not know yet the colobar / label needed + "label": dict_label["SST"], + "title": ["SSH seasonal cycle", "SSH seasonal cycle"], + "varpattern": "sshMac_hov__", + "xname": "longitude", + "yname": "months", + "zname": "SSH", + "method": "1) Linearly detrended\n2) Seasonal cycle computed\n3) Regridded to 1°x1°\n" + + "5) Meridional averaged (see box)", + }, + }, + "SeasonalSstLatRmse": { + "netcdf_variables": ["sst_lat__", "sst_map__", "sstMac_hov__"], + "diagnostic": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "SST seasonal cycle std", + "varpattern": "sst_lat__", + "xname": "latitude", + "yname": "SST std", + "method": "1) Linearly detrended\n2) Seasonal cycle computed\n3) Standard deviation\n" + + "4) Regridded to 1°x1°\n5) Zonal averaged (see box)\n\nMetric: RMSE$_y$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["amplitude"], + "label": dict_label["amplitude"], + "maskland": True, + "title": ["SST seasonal cycle std"], + "varpattern": "sst_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "SST std", + "method": "1) Linearly detrended\n2) Seasonal cycle computed\n3) Standard deviation\n" + + "4) Regridded to 1°x1°", + }, + "dive_down02": { + "plot_type": "hovmoeller", + "nbr_panel": 2, + "colorbar": dict_colorbar["SST"], + "label": dict_label["SST"], + "title": ["SST seasonal cycle", "SST seasonal cycle"], + "varpattern": "sstMac_hov__", + "xname": "latitude", + "yname": "months", + "zname": "SST", + "method": "1) Linearly detrended\n2) Seasonal cycle computed\n3) Regridded to 1°x1°\n" + + "5) Zonal averaged (see box)", + }, + }, + "SeasonalSstLonRmse": { + "netcdf_variables": ["sst_lon__", "sst_map__", "sstMac_hov__"], + "diagnostic": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "SST seasonal cycle std", + "varpattern": "sst_lon__", + "xname": "longitude", + "yname": "SST std", + "method": "1) Linearly detrended\n2) Seasonal cycle computed\n3) Standard deviation\n" + + "4) Regridded to 1°x1°\n5) Meridional averaged (see box)\n\nMetric: RMSE$_x$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["amplitude"], + "label": dict_label["amplitude"], + "maskland": True, + "title": ["SST seasonal cycle std"], + "varpattern": "sst_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "SST std", + "method": "1) Linearly detrended\n2) Seasonal cycle computed\n3) Standard deviation\n" + + "4) Regridded to 1°x1°", + }, + "dive_down02": { + "plot_type": "hovmoeller", + "nbr_panel": 2, + "colorbar": dict_colorbar["SST"], + "label": dict_label["SST"], + "title": ["SST seasonal cycle", "SST seasonal cycle"], + "varpattern": "sstMac_hov__", + "xname": "longitude", + "yname": "months", + "zname": "SST", + "method": "1) Linearly detrended\n2) Seasonal cycle computed\n3) Regridded to 1°x1°\n" + + "5) Meridional averaged (see box)", + }, + }, + "SeasonalTauxLatRmse": { + "netcdf_variables": ["taux_lat__", "taux_map__", "tauxMac_hov__"], + "diagnostic": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "TAUX seasonal cycle std", + "varpattern": "taux_lat__", + "xname": "latitude", + "yname": "TAUX std", + "method": "1) Linearly detrended\n2) Seasonal cycle computed\n3) Standard deviation\n" + + "4) Regridded to 1°x1°\n5) Zonal averaged (see box)\n\nMetric: RMSE$_y$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["amplitude"], + "label": dict_label["amplitude60"], + "maskland": True, + "title": ["TAUX seasonal cycle std"], + "varpattern": "taux_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "TAUX std", + "method": "1) Linearly detrended\n2) Seasonal cycle computed\n3) Standard deviation\n" + + "4) Regridded to 1°x1°", + }, + "dive_down02": { + "plot_type": "hovmoeller", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["TAUX"], + "title": ["TAUX seasonal cycle", "TAUX seasonal cycle"], + "varpattern": "tauxMac_hov__", + "xname": "latitude", + "yname": "months", + "zname": "TAUX", + "method": "1) Linearly detrended\n2) Seasonal cycle computed\n3) Regridded to 1°x1°\n" + + "5) Zonal averaged (see box)", + + }, + }, + "SeasonalTauxLonRmse": { + "netcdf_variables": ["taux_lon__", "taux_map__", "tauxMac_hov__"], + "diagnostic": { + "plot_type": "curve", + "nbr_panel": 1, + "title": "TAUX seasonal cycle std", + "varpattern": "taux_lon__", + "xname": "longitude", + "yname": "TAUX std", + "method": "1) Linearly detrended\n2) Seasonal cycle computed\n3) Standard deviation\n" + + "4) Regridded to 1°x1°\n5) Meridional averaged (see box)\n\nMetric: RMSE$_x$", + }, + "dive_down01": { + "plot_type": "map", + "nbr_panel": 2, + "colorbar": dict_colorbar["amplitude"], + "label": dict_label["amplitude60"], + "maskland": True, + "title": ["TAUX seasonal cycle std"], + "varpattern": "taux_map__", + "xname": "longitude", + "yname": "latitude", + "zname": "TAUX std", + "method": "1) Linearly detrended\n2) Seasonal cycle computed\n3) Standard deviation\n" + + "4) Regridded to 1°x1°", + }, + "dive_down02": { + "plot_type": "hovmoeller", + "nbr_panel": 2, + "colorbar": dict_colorbar["anomalies"], + "label": dict_label["REG80"], + "title": ["TAUX seasonal cycle", "TAUX seasonal cycle"], + "varpattern": "tauxMac_hov__", + "xname": "longitude", + "yname": "months", + "zname": "TAUX", + "method": "1) Linearly detrended\n2) Seasonal cycle computed\n3) Regridded to 1°x1°\n" + + "5) Meridional averaged (see box)", + }, + }, + +} + + +reference_observations = { + "ssh": "AVISO", "pr": "GPCPv2.3", "sst": "Tropflux", "lhf": "Tropflux", "lwr": "Tropflux", "shf": "Tropflux", + "slp": "ERA-Interim", "swr": "Tropflux", "taux": "Tropflux", "thf": "Tropflux" +} + + +def plot_param(metric_collection, metric): + dict_MC = defCollection(metric_collection) + dict_MCm = dict_MC["metrics_list"][metric] + # get plot parameters + dict_out = plot_parameters[metric.replace("_1", "").replace("_2", "").replace("_3", "")] + # get metric computation + if metric_collection in ["ENSO_tel", "test_tel"] and "Map" in metric: + computation = ["CORR", "RMSE"] + elif "rmse" in metric.lower(): + computation = "RMSE" + elif "corr" in metric.lower(): + computation = "CORR" + else: + try: + computation = dict_MCm["metric_computation"] + except: + try: + computation = dict_MC["common_collection_parameters"]["metric_computation"] + except: + computation = default_arg_values("metric_computation") + dict_out["metric_computation"] = computation + # get metric variables + variables = dict_MCm["variables"] + dict_out["metric_variables"] = variables + # get metric reference + references = dict((var, reference_observations[var]) for var in variables) + if "sst" in variables and len(variables) > 1: + references["sst"] = "Tropflux" + refname = references[variables[0]] + for var in variables[1:]: + refname = refname + "_" + references[var] + # if metric_collection == "ENSO_tel" and metric in ["EnsoPrMap", "EnsoSlpMap"]: + # refname = "ERA-Interim_ERA-Interim" + # elif metric_collection == "ENSO_tel" and metric in ["EnsoSstMap", "NinaSstMap", "NinoSstMap"]: + # refname = "ERA-Interim" + if metric_collection in ["ENSO_tel", "test_tel"] and metric in\ + ["EnsoSstMap", "NinaSstMap", "NinoSstMap", "EnsoSstMapDjf", "NinoSstMapDjf", "NinaSstMapDjf", + "EnsoSstMapJja", "NinoSstMapJja", "NinaSstMapJja"]: + refname = "ERA-Interim" + dict_out["metric_reference"] = refname + # get variable regions + dict_out["metric_regions"] = dict_MCm["regions"] + return dict_out diff --git a/build/lib/EnsoMetrics/EnsoToolsLib.py b/build/lib/EnsoMetrics/EnsoToolsLib.py new file mode 100644 index 0000000..47de61f --- /dev/null +++ b/build/lib/EnsoMetrics/EnsoToolsLib.py @@ -0,0 +1,278 @@ +# -*- coding:UTF-8 -*- +from copy import deepcopy +from inspect import stack as INSPECTstack +from numpy import array as NUMPYarray +from numpy import square as NUMPYsquare +from numpy import unravel_index as NUMPYunravel_index +from scipy.stats import scoreatpercentile as SCIPYstats__scoreatpercentile +# ENSO_metrics package functions: +from . import EnsoErrorsWarnings + + +# ---------------------------------------------------------------------------------------------------------------------# +# +# Set of functions without CDAT +# +def add_up_errors(list_keyerror): + """ + ################################################################################# + Description: + Adds in one string the given keyerrors from the list of keyerrors + ################################################################################# + + :param list_keyerror: list of string + list of keyerrors (strings or None) listing encountered errors + + :return keyerror: string + string of all encountered errors concatenated + """ + keyerror = '' + for key in list_keyerror: + if len(keyerror) > 0 and key is not None: + keyerror += " ; " + if key is not None: + keyerror += str(key) + if keyerror == '': + keyerror = None + return keyerror + + +def find_xy_min_max(tab, return_val='both'): + """ + ################################################################################# + Description: + Finds in tab the position (t,x,y,z) of the minimum (return_val='mini') or the maximum (return_val='maxi') or both + values (if return_val is neither 'mini' nor 'maxi') + Returned position(s) are not the position in tab but in the (t,x,y,z) space defined by tab axes + ################################################################################# + + :param tab: masked_array + array for which you would like to know the position (t,x,y,z) of the minimum and/or the maximum values + :param return_val: string, optional + 'mini' to return the position of the minimum value + 'maxi' to return the position of the maximum value + to return both minimum and maximum values, pass anything else + default value = 'both', returns both minimum and maximum values + + :return: minimum/maximum position or both minimum and maximum positions, int, float or list + position(s) in the (t,x,y,z) space defined by tab axes of the minimum and/or maximum values of tab + """ + mini = NUMPYunravel_index(tab.argmin(), tab.shape) + maxi = NUMPYunravel_index(tab.argmax(), tab.shape) + list_ax = NUMPYarray([tab.getAxis(ii)[:] for ii in range(len(mini))]) + axis_min = [list_ax[ii][mini[ii]] for ii in range(len(mini))] + axis_max = [list_ax[ii][maxi[ii]] for ii in range(len(mini))] + if return_val == 'mini': + if len(axis_min) == 1: + tab_out = axis_min[0] + else: + tab_out = axis_min + elif return_val == 'maxi': + if len(axis_max) == 1: + tab_out = axis_max[0] + else: + tab_out = axis_max + else: + if len(axis_min) == 1 and len(axis_max) == 1: + tab_out = [axis_min[0], axis_max[0]] + else: + tab_out = [axis_min, axis_max] + return tab_out + + +def math_metric_computation(model, model_err, obs=None, obs_err=None, keyword='difference'): + """ + ################################################################################# + Description: + Computes the metric value, i.e., distance between a model and an observational dataset + ################################################################################# + + :param model: float or None + scalar value computed with a model + :param model_err: float or None + error value on the scalar value computed with a model + :param obs: float or None, optional + scalar value computed with an observational dataset + default value is None + :param obs_err: float or None, optional + error value on the scalar value computed with an observational dataset + default value is None + :param keyword: string, optional + name of a mathematical method to apply to compute the metric value (distance between a model and an + observational dataset): 'difference', 'ratio', 'relative_difference', 'abs_relative_difference' + default value is 'difference' + :return metric: float or None + distance between a model and an observational dataset or None if 'model' and/or 'obs' is/are None + :return metric_err: float or None + error on the distance between a model and an observational dataset or None if 'model' and/or 'obs' and/or + 'metric_err' and/or 'obs_err' is/are None + :return description_metric: string + description of the mathematical method used to compute the metric value + """ + if keyword not in ['difference', 'ratio', 'relative_difference', 'abs_relative_difference']: + metric, metric_err, description_metric = \ + None, None, "unknown keyword for the mathematical computation of the metric: " + str(keyword) + list_strings = ["ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": keyword", + str().ljust(5) + description_metric] + EnsoErrorsWarnings.my_warning(list_strings) + else: + if model is not None and obs is not None: + if keyword == 'difference': + description_metric = \ + "The metric is the difference between model and observations values (M = model - obs)" + metric = model - obs + elif keyword == 'ratio': + description_metric = "The metric is the ratio between model and observations values (M = model / obs)" + metric = model / float(obs) + elif keyword == 'relative_difference': + description_metric = "The metric is the relative difference between model and observations values " + \ + "(M = [model-obs] / obs)" + metric = (model - obs) / float(obs) + else: + description_metric = "The metric is the absolute value of the relative difference between model " + \ + "and observations values (M = 100 * abs[[model-obs] / obs])" + metric = 100. * abs((model - obs) / float(obs)) + else: + metric, description_metric = None, '' + if model_err is not None or obs_err is not None: + if keyword == 'difference': + # mathematical definition of the error on addition / subtraction + metric_err = model_err + obs_err + elif keyword == 'ratio': + # mathematical definition of the error on division + if model is not None and obs is not None: + metric_err = float((obs * model_err + model * obs_err) / NUMPYsquare(obs)) + else: + metric_err = None + else: + # mathematical definition of the error on division + if model is not None and obs is not None: + metric_err = float((obs * (model_err + obs_err) + (model - obs) * obs_err) / NUMPYsquare(obs)) + if keyword == 'abs_relative_difference': + metric_err = 100. * metric_err + else: + metric_err = None + else: + metric_err = None + return metric, metric_err, description_metric + + +def percentage_val_eastward(val_longitude, metric_name, region, threshold=-140): + """ + ################################################################################# + Description: + Computes the percentage of given values (longitude in val_longitude) eastward of 'threshold' + ################################################################################# + + :param val_longitude: list + list of longitude + :param metric_name: string, optional + name of the metric calling the function + :param region: string + name of a region to select, must be defined in EnsoCollectionsLib.ReferenceRegions + :param threshold: float, optional + threshold to define the westward boundary of the region + + :return ep: float + percentage of given values eastward of 'threshold' + """ + keyerror = None + pos_lon = 'pos' if all(ii >= 0 for ii in val_longitude) is True else \ + ('neg' if all(ii <= 0 for ii in val_longitude) is True else False) + if pos_lon is False: + keyerror = "longitude in lon_axis are neither all positive nor all negative" + list_strings = ["ERROR " + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": unknown longitude", + str().ljust(5) + metric_name + ": " + str(region) + ": " + keyerror + ": " + str(val_longitude)] + EnsoErrorsWarnings.my_warning(list_strings) + ep_event = None + else: + if pos_lon == 'pos': + ep_event = [1 for val in val_longitude if val > 360 + threshold] + else: + ep_event = [1 for val in val_longitude if val > threshold] + ep_event = sum(ep_event) * 100. / len(val_longitude) + return ep_event, keyerror + + +def statistical_dispersion(tab, method='IQR'): + """ + ################################################################################# + Description: + Computes the statistical dispersion of the distribution + ################################################################################# + + :param tab: list or `cdms2` variable + A list or a `cdms2` variable containing the data to be analysed + :param method: string, optional + method to compute the statistical dispersion + 'IQR': interquartile range, IQR = Q3 - Q1 + 'MAD': median absolute deviation, MAD = median([Xi - median(tab)]) + Default is 'IQR' + + :return stat_disp: float + statistical_dispersion + """ + known_methods = sorted(['IQR', 'MAD']) + if method not in known_methods: + # if 'method' is not defined -> raise error + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": unknown method", + str().ljust(5) + "method " + str(method) + " is not defined", + str().ljust(10) + "known methods: " + str(known_methods) + ] + EnsoErrorsWarnings.my_error(list_strings) + if method == 'IQR': + stat_disp = abs(float(SCIPYstats__scoreatpercentile(tab, 75) - SCIPYstats__scoreatpercentile(tab, 25))) + else: + med = float(SCIPYstats__scoreatpercentile(tab, 50)) + stat_disp = float(SCIPYstats__scoreatpercentile([abs(ii - med) for ii in tab], 50)) + return stat_disp + + +def string_in_dict(string_or_list, dictionary, inspect_stack): + """ + ################################################################################# + Description: + Tests if 'string_or_list' is in the given 'dictionary' + ################################################################################# + + :param string_or_list: string or list + key or list of keys to look for in 'dictionary' + :param dictionary: dict + dictionary in which the given keys are looked for + :param inspect_stack: array + list of information about the program/module/line,... created using inspect.stack() + :return: + """ + # test input parameters + if not isinstance(dictionary, dict): + EnsoErrorsWarnings.object_type_error('dictionary', 'dictionary', type(dictionary), INSPECTstack()) + if isinstance(string_or_list, str): + # 'string_or_list' is a string + if string_or_list not in list(dictionary.keys()): + # key 'string_or_list' is not in 'dictionary' -> raise error + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(inspect_stack) + ": item not included", + str().ljust(5) + " key: " + str(string_or_list) + " is not in the given dictionary", + str().ljust(10) + "key(s) in the dictionary: " + str(sorted(dictionary.keys())) + ] + EnsoErrorsWarnings.my_error(list_strings) + elif isinstance(string_or_list, list): + # 'string_or_list' is a list + key_not_included = list() + for key in string_or_list: + if key not in list(dictionary.keys()): + # lists keys that are not in 'dictionary' + key_not_included.append(key) + if key_not_included: + # if 'key_not_included' is not empty -> raise error + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(inspect_stack) + ": item not included", + str().ljust(5) + " key(s): " + str(key_not_included) + " are (is) not in the given dictionary", + str().ljust(10) + "key(s) in the dictionary: " + str(sorted(dictionary.keys())) + ] + EnsoErrorsWarnings.my_error(list_strings) + else: + # 'string_or_list' is neither a string nor a list -> raise error + EnsoErrorsWarnings.object_type_error('string_or_list', '[string, list]', type(string_or_list), INSPECTstack()) + return diff --git a/build/lib/EnsoMetrics/EnsoUvcdatToolsLib.py b/build/lib/EnsoMetrics/EnsoUvcdatToolsLib.py new file mode 100644 index 0000000..3411bba --- /dev/null +++ b/build/lib/EnsoMetrics/EnsoUvcdatToolsLib.py @@ -0,0 +1,3852 @@ +# -*- coding:UTF-8 -*- + +from calendar import monthrange +from copy import deepcopy +import copy +from datetime import date +from inspect import stack as INSPECTstack +import ntpath +from numpy import array as NParray +from numpy import exp as NPexp +from numpy import histogram as NPhistogram +from numpy import isnan as NPisnan +from numpy import nan as NPnan +from numpy import nonzero as NPnonzero +from numpy import ones as NPones +from numpy import product as NPproduct +from numpy import where as NPwhere +from numpy.ma.core import MaskedArray as NPma__core__MaskedArray +from os.path import isdir as OSpath_isdir +from os.path import isfile as OSpath__isfile +from os.path import join as OSpath__join +from os.path import split as OSpath__split +from scipy.signal import detrend as SCIPYsignal_detrend +from scipy.stats import skew as SCIPYstats__skew +from sys import prefix as SYS_prefix + +# ENSO_metrics package functions: +from .EnsoCollectionsLib import CmipVariables +from .EnsoCollectionsLib import ReferenceObservations +from .EnsoCollectionsLib import ReferenceRegions +from . import EnsoErrorsWarnings +from .EnsoToolsLib import add_up_errors, find_xy_min_max, string_in_dict + +# uvcdat based functions: +from cdms2 import createAxis as CDMS2createAxis +from cdms2 import createRectGrid as CDMS2createRectGrid +from cdms2 import createUniformLatitudeAxis as CDMS2createUniformLatitudeAxis +from cdms2 import createUniformLongitudeAxis as CDMS2createUniformLongitudeAxis +from cdms2 import createVariable as CDMS2createVariable +from cdms2 import setAutoBounds as CDMS2setAutoBounds +from cdms2 import open as CDMS2open +from cdtime import comptime as CDTIMEcomptime +import cdutil +from genutil.statistics import correlation as GENUTILcorrelation +from genutil.statistics import linearregression as GENUTILlinearregression +from genutil.statistics import rms as GENUTILrms +from genutil.statistics import std as GENUTILstd +from MV2 import add as MV2add +from MV2 import arange as MV2arange +from MV2 import array as MV2array +from MV2 import average as MV2average +from MV2 import compress as MV2compress +from MV2 import concatenate as MV2concatenate +from MV2 import divide as MV2divide +from MV2 import masked_where as MV2masked_where +from MV2 import maximum as MV2maximum +from MV2 import minimum as MV2minimum +from MV2 import multiply as MV2multiply +from MV2 import ones as MV2ones +from MV2 import subtract as MV2subtract +from MV2 import sum as MV2sum +from MV2 import take as MV2take +from MV2 import where as MV2where +from MV2 import zeros as MV2zeros +from regrid2.horizontal import Horizontal as REGRID2horizontal__Horizontal + + +# ---------------------------------------------------------------------------------------------------------------------# +# +# Set of simple uvcdat functions used in EnsoMetricsLib.py +# +def ArrayOnes(tab, id='new_variable_ones'): + """ + ################################################################################# + Description: + Create a masked_array filled with ones with the same properties as tab (shape, axes, grid, mask) + ################################################################################# + + for more information: + import MV2 + help(MV2.ones) + """ + return CDMS2createVariable(MV2ones(tab.shape), axes=tab.getAxisList(), grid=tab.getGrid(), mask=tab.mask, id=id) + + +def ArrayZeros(tab, id='new_variable_zeros'): + """ + ################################################################################# + Description: + Create a masked_array filled with zeros with the same properties as tab (shape, axes, grid, mask) + ################################################################################# + + for more information: + import MV2 + help(MV2.zeros) + """ + return CDMS2createVariable(MV2zeros(tab.shape), axes=tab.getAxisList(), grid=tab.getGrid(), mask=tab.mask, id=id) + + +def AverageHorizontal(tab, areacell=None, region=None, **kwargs): + """ + ################################################################################# + Description: + Averages along 'xy' axis + ################################################################################# + + for more information: + import cdutil + help(cdutil.averager) + """ + keyerror = None + lat_num = get_num_axis(tab, 'latitude') + lon_num = get_num_axis(tab, 'longitude') + snum = str(lat_num) + str(lon_num) + if areacell is None or tab.getGrid().shape != areacell.getGrid().shape: + print("\033[93m" + str().ljust(15) + "EnsoUvcdatToolsLib AverageHorizontal" + "\033[0m") + if areacell is not None and tab.getGrid().shape != areacell.getGrid().shape: + print("\033[93m" + str().ljust(25) + "tab.grid " + str(tab.getGrid().shape) + + " is not the same as areacell.grid " + str(areacell.getGrid().shape) + " \033[0m") + try: averaged_tab = cdutil.averager(tab, axis='xy', weights='weighted', action='average') + except: + try: averaged_tab = cdutil.averager(tab, axis=snum, weights='weighted', action='average') + except: + if 'regridding' not in list(kwargs.keys()) or isinstance(kwargs['regridding'], dict) is False: + kwargs2 = {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'} + else: + kwargs2 = kwargs['regridding'] + kwargs2["newgrid_name"] =\ + closest_grid(region, len(tab.getAxis(lat_num)[:]), len(tab.getAxis(lon_num)[:])) + print("\033[93m" + str().ljust(25) + "need to regrid to = " + str(kwargs2["newgrid_name"]) + + " to perform average \033[0m") + tmp = Regrid(tab, None, region=region, **kwargs2) + try: averaged_tab = cdutil.averager(tmp, axis=snum, weights='weighted', action='average') + except: + keyerror = "cannot perform horizontal average" + averaged_tab = None + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": horizontal average", + str().ljust(5) + "cdutil.averager cannot perform horizontal average"] + EnsoErrorsWarnings.my_warning(list_strings) + else: + averaged_tab = MV2multiply(tab, areacell) + for elt in snum[::-1]: + averaged_tab = MV2sum(averaged_tab, axis=int(elt)) + averaged_tab = averaged_tab / float(MV2sum(areacell)) + return averaged_tab, keyerror + + +def AverageMeridional(tab, areacell=None, region=None, **kwargs): + """ + ################################################################################# + Description: + Averages along 'y' axis + ################################################################################# + + for more information: + import cdutil + help(cdutil.averager) + """ + keyerror = None + lat_num = get_num_axis(tab, 'latitude') + lon_num = get_num_axis(tab, 'longitude') + snum = str(lat_num) + if areacell is None or tab.getGrid().shape != areacell.getGrid().shape: + print("\033[93m" + str().ljust(15) + "EnsoUvcdatToolsLib AverageMeridional" + "\033[0m") + if areacell is not None and tab.getGrid().shape != areacell.getGrid().shape: + print("\033[93m" + str().ljust(25) + "tab.grid " + str(tab.getGrid().shape) + + " is not the same as areacell.grid " + str(areacell.getGrid().shape) + " \033[0m") + try: averaged_tab = cdutil.averager(tab, axis='y', weights='weighted', action='average') + except: + try: averaged_tab = cdutil.averager(tab, axis=snum, weights='weighted', action='average') + except: + if 'regridding' not in list(kwargs.keys()) or isinstance(kwargs['regridding'], dict) is False: + kwargs2 = {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'} + else: + kwargs2 = kwargs['regridding'] + kwargs2["newgrid_name"] = \ + closest_grid(region, len(tab.getAxis(lat_num)[:]), len(tab.getAxis(lon_num)[:])) + print("\033[93m" + str().ljust(25) + "need to regrid to = " + str(kwargs2["newgrid_name"]) + + " to perform average \033[0m") + tmp = Regrid(tab, None, region=region, **kwargs2) + try: averaged_tab = cdutil.averager(tmp, axis=snum, weights='weighted', action='average') + except: + keyerror = "cannot perform meridional average" + averaged_tab = None + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": meridional average", + str().ljust(5) + "cdutil.averager cannot perform meridional average"] + EnsoErrorsWarnings.my_warning(list_strings) + else: + lat_num_area = get_num_axis(areacell, 'latitude') + averaged_tab = MV2multiply(tab, areacell) + averaged_tab = MV2sum(averaged_tab, axis=lat_num) / MV2sum(areacell, axis=lat_num_area) + if averaged_tab is not None: + lon = tab.getLongitude() + if len(lon.shape) > 1: + lonn = CDMS2createAxis(MV2array(lon[0, :]), id='longitude') + lonn.units = lon.units + lon_num = get_num_axis(tab, 'longitude') + try: + averaged_tab.setAxis(lon_num, lonn) + except: + averaged_tab.setAxis(lon_num - 1, lonn) + return averaged_tab, keyerror + + +def AverageTemporal(tab, areacell=None, **kwargs): + """ + ################################################################################# + Description: + Averages along 't' axis + ################################################################################# + + for more information: + import cdutil + help(cdutil.averager) + """ + keyerror = None + try: averaged_tab = cdutil.averager(tab, axis='t') + except: + time_num = get_num_axis(tab, 'time') + try: averaged_tab = cdutil.averager(tab, axis=str(time_num)) + except: + keyerror = "cannot perform temporal average" + averaged_tab = None + list_strings = ["ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": temporal average", + str().ljust(5) + "cannot perform temporal average"] + EnsoErrorsWarnings.my_warning(list_strings) + return averaged_tab, keyerror + + +def AverageZonal(tab, areacell=None, region=None, **kwargs): + """ + ################################################################################# + Description: + Averages along 'x' axis + ################################################################################# + + for more information: + import cdutil + help(cdutil.averager) + """ + keyerror = None + lat_num = get_num_axis(tab, 'latitude') + lon_num = get_num_axis(tab, 'longitude') + snum = str(lon_num) + if areacell is None or tab.getGrid().shape != areacell.getGrid().shape: + print("\033[93m" + str().ljust(15) + "EnsoUvcdatToolsLib AverageZonal" + "\033[0m") + if areacell is not None and tab.getGrid().shape != areacell.getGrid().shape: + print("\033[93m" + str().ljust(25) + "tab.grid " + str(tab.getGrid().shape) + + " is not the same as areacell.grid " + str(areacell.getGrid().shape) + " \033[0m") + try: averaged_tab = cdutil.averager(tab, axis='x', weights='weighted', action='average') + except: + try: averaged_tab = cdutil.averager(tab, axis=snum, weights='weighted', action='average') + except: + if 'regridding' not in list(kwargs.keys()) or isinstance(kwargs['regridding'], dict) is False: + kwargs2 = {'regridder': 'cdms', 'regridTool': 'esmf', 'regridMethod': 'linear', + 'newgrid_name': 'generic_1x1deg'} + else: + kwargs2 = kwargs['regridding'] + kwargs2["newgrid_name"] = \ + closest_grid(region, len(tab.getAxis(lat_num)[:]), len(tab.getAxis(lon_num)[:])) + print("\033[93m" + str().ljust(25) + "need to regrid to = " + str(kwargs2["newgrid_name"]) + + " to perform average \033[0m") + tmp = Regrid(tab, None, region=region, **kwargs2) + try: averaged_tab = cdutil.averager(tmp, axis=snum, weights='weighted', action='average') + except: + keyerror = "cannot perform zonal average" + averaged_tab = None + list_strings = ["ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": zonal average", + str().ljust(5) + "cdutil.averager cannot perform zonal average"] + EnsoErrorsWarnings.my_warning(list_strings) + else: + lon_num_area = get_num_axis(areacell, 'longitude') + averaged_tab = MV2multiply(tab, areacell) + averaged_tab = MV2sum(averaged_tab, axis=lon_num) / MV2sum(areacell, axis=lon_num_area) + if averaged_tab is not None: + lat = tab.getLatitude() + if len(lat.shape) > 1: + latn = CDMS2createAxis(MV2array(lat[:, 0]), id='latitude') + latn.units = lat.units + lat_num = get_num_axis(tab, 'latitude') + try: + averaged_tab.setAxis(lat_num, latn) + except: + averaged_tab.setAxis(lat_num - 1, latn) + return averaged_tab, keyerror + + +# Dictionary of averaging methods +dict_average = {'horizontal': AverageHorizontal, 'meridional': AverageMeridional, 'time': AverageTemporal, + 'zonal': AverageZonal} + + +def Concatenate(tab1, tab2, events1=[], events2=[]): + my_events = events1 + events2 + if len(my_events) > 0: + my_events_sort = sorted(my_events) + for yy in my_events_sort: + try: + tab_out + except: + if yy in events1: + tab_out = MV2array([tab1[events1.index(yy)]]) + else: + tab_out = MV2array([tab2[events2.index(yy)]]) + else: + if yy in events1: + tab_out = MV2concatenate((tab_out, MV2array([tab1[events1.index(yy)]]))) + else: + tab_out = MV2concatenate((tab_out, MV2array([tab2[events2.index(yy)]]))) + axes = CDMS2createAxis(MV2array(my_events_sort, dtype='int32'), id='years') + if len(events1): + tmp = copy.copy(tab1) + else: + tmp = copy.copy(tab2) + att = tmp.attributes + if len(tmp.shape) > 1: + mask = tmp[0].mask + mask2 = MV2zeros(tab_out.shape) + mask2[:] = mask + dictvar = {"axes": [axes] + tab1[0].getAxisList(), "mask": mask2, "grid": tmp.getGrid(), "attributes": att} + else: + dictvar = {"axes": [axes], "attributes": att} + tab_out = CDMS2createVariable(tab_out, **dictvar) + else: + tab_out = MyEmpty(tab1[:5, 0], time=True, time_id='years') + return tab_out + + +def closest_grid(region, nlat, nlon): + res = [0.25, 0.50, 0.75, 1.00, 1.25, 1.50, 1.75, 2.00, 2.25, 2.50, 2.75] + region_ref = ReferenceRegions(region) + lats = region_ref['latitude'] + dy = float(abs(max(lats) - min(lats))) / nlat + lyy = [abs(dy - ii) for ii in res] + lyy = res[lyy.index(min(lyy))] + lons = region_ref['longitude'] + dx = float(abs(max(lons) - min(lons))) / nlon + lxx = [abs(dx - ii) for ii in res] + lxx = res[lxx.index(min(lxx))] + if lxx == lyy: + grid = "generic_" + str(lxx) + "x" + str(lxx) + "deg" + else: + dx = abs(lxx + lyy) / 2. + lxx = [abs(dx - ii) for ii in res] + lxx = res[lxx.index(min(lxx))] + grid = "generic_" + str(lxx) + "x" + str(lxx) + "deg" + return grid + + +def ComputeInterannualAnomalies(tab): + """ + ################################################################################# + Description: + Computes interannual anomalies + ################################################################################# + + for more information: + import cdutil + help(cdutil.ANNUALCYCLE.departures) + """ + return cdutil.ANNUALCYCLE.departures(tab) + + +def Correlation(tab, ref, weights=None, axis=0, centered=1, biased=1): + """ + ################################################################################# + Description: + Computes correlation + ################################################################################# + + for more information: + import genutil + help(genutil.statistics.correlation) + """ + return GENUTILcorrelation(tab, ref, weights=weights, axis=axis, centered=centered, biased=biased) + + +def OperationAdd(tab, number_or_tab): + """ + ################################################################################# + Description: + Adds every elements of 'tab' by 'number_or_tab' + If 'number_or_tab' is an array it must have the same shape as tab + ################################################################################# + + for more information: + import MV2 + help(MV2.add) + """ + if not isinstance(number_or_tab, int) and not isinstance(number_or_tab, float): + if tab.shape != number_or_tab.shape: + EnsoErrorsWarnings.mismatch_shapes_error(tab, number_or_tab, INSPECTstack()) + return MV2add(tab, number_or_tab) + + +def OperationDivide(tab, number_or_tab): + """ + ################################################################################# + Description: + Divides every elements of 'tab' by 'number_or_tab' + ################################################################################# + + for more information: + import MV2 + help(MV2.divide) + """ + if not isinstance(number_or_tab, int) and not isinstance(number_or_tab, float): + if tab.shape != number_or_tab.shape: + EnsoErrorsWarnings.mismatch_shapes_error(tab, number_or_tab, INSPECTstack()) + return MV2divide(tab, number_or_tab) + + +def OperationMultiply(tab, number_or_tab): + """ + ################################################################################# + Description: + Multiplies every elements of 'tab' by 'number_or_tab' + ################################################################################# + + for more information: + import MV2 + help(MV2.multiply) + """ + if not isinstance(number_or_tab, int) and not isinstance(number_or_tab, float): + if tab.shape != number_or_tab.shape: + EnsoErrorsWarnings.mismatch_shapes_error(tab, number_or_tab, INSPECTstack()) + tab_out = MV2multiply(tab, number_or_tab) + axes = tab.getAxisList() + att = tab.attributes + if len(tab.shape) > 1: + mask = tab.mask + dictvar = {"axes": axes, "mask": tab.mask, "grid": tab.getGrid(), "attributes": att} + else: + dictvar = {"axes": axes, "attributes": att} + tab_out = CDMS2createVariable(tab_out, **dictvar) + return tab_out + + +def OperationSubtract(tab, number_or_tab): + """ + ################################################################################# + Description: + Subtracts every elements of 'tab' by 'number_or_tab' + ################################################################################# + + for more information: + import MV2 + help(MV2.subtract) + """ + if not isinstance(number_or_tab, int) and not isinstance(number_or_tab, float): + if tab.shape != number_or_tab.shape: + EnsoErrorsWarnings.mismatch_shapes_error(tab, number_or_tab, INSPECTstack()) + return MV2subtract(tab, number_or_tab) + + +# Dictionary of operations +dict_operations = {'divide': OperationDivide, 'minus': OperationSubtract, 'multiply': OperationMultiply, + 'plus': OperationAdd} + + +def RmsAxis(tab, ref, weights=None, axis=0, centered=0, biased=1): + """ + ################################################################################# + Description: + genutil.rms applied on two arrays + + Computes the root mean square difference between tab and ref on the given axis + + Uses uvcdat + ################################################################################# + + :param tab: masked_array + masked_array (uvcdat cdms2) containing a variable, with many attributes attached (short_name, units,...) + usually it is the modeled variable + :param ref: masked_array + masked_array (uvcdat cdms2) containing a variable, with many attributes attached (short_name, units,...) + usually it is the observed variable + :param weights: masked_array, optional + weights applied to each grid point + default value = None returns equally weighted statistic + If you want to compute the weighted statistic, provide weights here + :param axis: int or string, optional + name ('t', 'x', 'y', 'xy',...) or number (0, 1, 2,...) of the axis over which you want to compute the rms + default value = 0 returns statistic computed over the first axis + :param centered: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :param biased: int, optional + default value = 1 returns biased statistic (number of elements along given axis) + If want to compute an unbiased variance pass anything but 1 (number of elements along given axis minus 1) + :return rmse: float + value of root mean square difference + """ + keyerror = None + # Computes the root mean square difference + try: + rmse = GENUTILrms(tab, ref, weights=weights, axis=axis, centered=centered, biased=biased) + except: + keyerror = "cannot perform RMS along given axis: tab (" + str(tab.shape) + ") and ref (" + str(ref.shape) +\ + ") are not on the same grid" + list_strings = ["ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": RMS over axis " + str(axis), + str().ljust(5) + "cannot perform RMS along given axis", + str().ljust(10) + "axes may not be in the same order in 'ref' and 'tab'", + str().ljust(15) + "order: ref = " + str(ref.getOrder()) + ", tab = " + str(tab.getOrder()), + str().ljust(15) + "axes: ref = " + str(ref.getAxisList()) + ", tab = " + str(tab.getAxisList())] + EnsoErrorsWarnings.my_warning(list_strings) + try: + rmse = float(rmse) + except: + rmse = None + return rmse, keyerror + + +def RmsHorizontal(tab, ref, centered=0, biased=1): + """ + ################################################################################# + Description: + genutil.rms applied on two masked_arrays that are on the same grid + + Computes the root mean square difference between tab and ref on horizontal axes (lat and lon) + + Uses uvcdat + ################################################################################# + + :param tab: masked_array + masked_array (uvcdat cdms2) containing a variable, with many attributes attached (short_name, units,...) + usually it is the modeled variable + :param ref: masked_array + masked_array (uvcdat cdms2) containing a variable, with many attributes attached (short_name, units,...) + usually it is the observed variable + :param centered: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :return rmse: float + value of root mean square difference + """ + keyerror = None + # Computes the root mean square difference + try: + rmse = GENUTILrms(tab, ref, weights='weighted', axis='xy', centered=centered, biased=biased) + except: + lat_num = get_num_axis(tab, 'latitude') + lon_num = get_num_axis(tab, 'longitude') + try: + rmse = GENUTILrms(tab, ref, weights='weighted', axis=str(lat_num)+str(lon_num), centered=centered, + biased=biased) + except: + keyerror = "cannot perform horizontal RMS (x=" + str(lon_num) + ", y=" + str(lat_num) + "): tab (" +\ + str(tab.shape) + ") and ref (" + str(ref.shape) + ") are not on the same grid" + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": horizontal RMS", + str().ljust(5) + "cannot perform horizontal RMS", + str().ljust(10) + "either lat and lon cannot be found in 'ref' / 'tab'", + str().ljust(10) + "or lat and lon are not in the same order in 'ref' and 'tab'", + str().ljust(15) + "order: ref = " + str(ref.getOrder()) + ", tab = " + str(tab.getOrder()), + str().ljust(15) + "axes: ref = " + str(ref.getAxisList()) + ", tab = " + str(tab.getAxisList())] + EnsoErrorsWarnings.my_warning(list_strings) + try: + rmse = float(rmse) + except: + rmse = None + return rmse, keyerror + + +def RmsMeridional(tab, ref, centered=0, biased=1): + """ + ################################################################################# + Description: + genutil.rms applied on two masked_arrays that are on the same grid + + Computes the root mean square difference between tab and ref on meridional axis (lat) + + Uses uvcdat + ################################################################################# + + :param tab: masked_array + masked_array (uvcdat cdms2) containing a variable, with many attributes attached (short_name, units,...) + usually it is the modeled variable + :param ref: masked_array + masked_array (uvcdat cdms2) containing a variable, with many attributes attached (short_name, units,...) + usually it is the observed variable + :param centered: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :return rmse: float + value of root mean square difference + """ + keyerror = None + # Computes the root mean square difference + try: + rmse = GENUTILrms(tab, ref, axis='y', centered=centered, biased=biased) + except: + lat_num = get_num_axis(tab, 'latitude') + try: + rmse = GENUTILrms(tab, ref, axis=str(lat_num), centered=centered, biased=biased) + except: + keyerror = "cannot perform meridional RMS (y=" + str(lat_num) + "): tab (" + str(tab.shape) +\ + ") and ref (" + str(ref.shape) + ") are not on the same grid" + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": meridional RMS", + str().ljust(5) + "cannot perform meridional RMS", + str().ljust(10) + "lat cannot be found in 'ref' / 'tab'", + str().ljust(10) + "or lat is not in the same order in 'ref' and 'tab'", + str().ljust(15) + "order: ref = " + str(ref.getOrder()) + ", tab = " + str(tab.getOrder()), + str().ljust(15) + "axes: ref = " + str(ref.getAxisList()) + ", tab = " + str(tab.getAxisList())] + EnsoErrorsWarnings.my_warning(list_strings) + try: + rmse = float(rmse) + except: + rmse = None + return rmse, keyerror + + +def RmsTemporal(tab, ref, centered=0, biased=1): + """ + ################################################################################# + Description: + genutil.rms applied on two masked_arrays that are horizontally averaged + + Computes the root mean square difference between tab and ref along time axis + + Uses uvcdat + ################################################################################# + + :param tab: masked_array + masked_array (uvcdat cdms2) containing a variable, with many attributes attached (short_name, units,...) + usually it is the modeled variable + :param ref: masked_array + masked_array (uvcdat cdms2) containing a variable, with many attributes attached (short_name, units,...) + usually it is the observed variable + :param centered: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :return rmse: float + value of root mean square difference + """ + keyerror = None + # Computes the root mean square difference + try: + rmse = GENUTILrms(tab, ref, axis='t', centered=centered, biased=biased) + except: + time_num = get_num_axis(tab, 'time') + try: + rmse = GENUTILrms(tab, ref, axis=str(time_num), centered=centered, biased=biased) + except: + keyerror = "cannot perform temporal RMS (t=" + str(time_num) + "): tab (" + str(tab.shape) + \ + ") and ref (" + str(ref.shape) + ") are not on the same grid" + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": temporal RMS", + str().ljust(5) + "cannot perform temporal RMS", + str().ljust(10) + "time cannot be found in 'ref' / 'tab'", + str().ljust(10) + "or time is not in the same order in 'ref' and 'tab'", + str().ljust(15) + "order: ref = " + str(ref.getOrder()) + ", tab = " + str(tab.getOrder()), + str().ljust(15) + "axes: ref = " + str(ref.getAxisList()) + ", tab = " + str(tab.getAxisList())] + EnsoErrorsWarnings.my_warning(list_strings) + try: + rmse = float(rmse) + except: + rmse = None + return rmse, keyerror + + +def RmsZonal(tab, ref, centered=0, biased=1): + """ + ################################################################################# + Description: + genutil.rms applied on two masked_arrays that are on the same grid + + Computes the root mean square difference between tab and ref on zonal axis (lon) + + Uses uvcdat + ################################################################################# + + :param tab: masked_array + masked_array (uvcdat cdms2) containing a variable, with many attributes attached (short_name, units,...) + usually it is the modeled variable + :param ref: masked_array + masked_array (uvcdat cdms2) containing a variable, with many attributes attached (short_name, units,...) + usually it is the observed variable + :param centered: int, optional + default value = 0 returns uncentered statistic (same as None). To remove the mean first (i.e centered statistic) + set to 1. NOTE: Most other statistic functions return a centered statistic by default + :return rmse: float + value of root mean square difference + """ + keyerror = None + # Computes the root mean square difference + try: + rmse = GENUTILrms(tab, ref, axis='x', centered=centered, biased=biased) + except: + lon_num = get_num_axis(tab, 'longitude') + try: + rmse = GENUTILrms(tab, ref, axis=str(lon_num), centered=centered, biased=biased) + except: + keyerror = "cannot perform zonal RMS (t=" + str(lon_num) + "): tab (" + str(tab.shape) + \ + ") and ref (" + str(ref.shape) + ") are not on the same grid" + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": zonal RMS", + str().ljust(5) + "cannot perform zonal RMS", + str().ljust(10) + "lon cannot be found in 'ref' / 'tab'", + str().ljust(10) + "or lon is not in the same order in 'ref' and 'tab'", + str().ljust(15) + "order: ref = " + str(ref.getOrder()) + ", tab = " + str(tab.getOrder()), + str().ljust(15) + "axes: ref = " + str(ref.getAxisList()) + ", tab = " + str(tab.getAxisList())] + EnsoErrorsWarnings.my_warning(list_strings) + try: + rmse = float(rmse) + except: + rmse = None + return rmse, keyerror + + +# Dictionary of RMS methods +dict_rms = {'axis': RmsAxis, 'horizontal': RmsHorizontal, 'meridional': RmsMeridional, 'time': RmsTemporal, + 'zonal': RmsZonal} + + +def Std(tab, weights=None, axis=0, centered=1, biased=1): + """ + ################################################################################# + Description: + Computes standard deviation + ################################################################################# + + for more information: + import genutil + help(genutil.statistics.std) + """ + tmp = GENUTILstd(tab, weights=weights, axis=axis, centered=centered, biased=biased) + try: + tmp.setGrid(tab.getGrid()) + except: + pass + return tmp + + +def SumAxis(tab, axis=None, fill_value=0, dtype=None): + """ + ################################################################################# + Description: + MV2.sum applied on tab + + Sum of elements along a certain axis using fill_value for missing + + Uses CDAT + ################################################################################# + + :param tab: masked_array + masked_array (uvcdat cdms2) containing a variable, with many attributes attached (short_name, units,...) + :param axis: int or string, optional + name ('t', 'x', 'y', 'xy',...) or number (0, 1, 2,...) of the axis over which you want to compute the rms + default value = 0 returns statistic computed over the first axis + :param fill_value: int or float, optional + fill_value used for missing (masked) data + :param dtype : None or dtype, optional + Determines the type of the returned array and of the accumulator where the elements are summed. If dtype has the + value None and the type of tab is an integer type of precision less than the default platform integer, then the + default platform integer precision is used. Otherwise, the dtype is the same as that of tab + :return sum_along_axis: MaskedArray or scalar + An array with the same shape as tab, with the specified axis removed. If tab is a 0-d array, or if axis is None, + a scalar is returned. + """ + keyerror = None + try: + sum_along_axis = MV2sum(tab, axis=axis, fill_value=fill_value, dtype=dtype) + except: + keyerror = "cannot sum along given axis (" + str(axis) + "): tab (" + str(tab.shape) + ")" + sum_along_axis = None + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": sum over axis " + str(axis), + str().ljust(5) + "cannot perform sum along given axis", + str().ljust(10) + "axes: " + str(tab.getAxisList()), + str().ljust(15) + "axis = " + str(axis) + " ; fill_value = " + str(fill_value) + " ; dtype = " + str(dtype)] + EnsoErrorsWarnings.my_warning(list_strings) + return sum_along_axis, keyerror + + +def TimeBounds(tab): + """ + ################################################################################# + Description: + Finds first and last dates of tab's time axis, tab must be a uvcdat masked_array + ################################################################################# + + Returns a tuple of strings: e.g., ('1979-1-1 11:59:60.0', '2016-12-31 11:59:60.0') + """ + time = tab.getTime().asComponentTime() + return (str(time[0]), str(time[-1])) +# ---------------------------------------------------------------------------------------------------------------------# + + +# ---------------------------------------------------------------------------------------------------------------------# +# +# Set of more complex functions (based on uvcdat) used in EnsoMetricsLib.py +# +def annualcycle(tab): + """ + ################################################################################# + Description: + Computes the annual cycle (climatological value of each calendar month) of tab + ################################################################################# + + :param tab: masked_array + :return: tab: array + array of the monthly annual cycle + """ + initorder = tab.getOrder() + tab = tab.reorder('t...') + axes = tab.getAxisList() + time_ax = tab.getTime().asComponentTime() + months = MV2array(list(tt.month for tt in time_ax)) + cyc = [] + for ii in range(12): + ids = MV2compress(months == (ii + 1), list(range(len(tab)))) + tmp = MV2take(tab, ids, axis=0) + # tmp = tab.compress(months == (ii + 1), axis=0) + tmp = MV2average(tmp, axis=0) + cyc.append(tmp) + del tmp + time = CDMS2createAxis(list(range(12)), id='time') + moy = CDMS2createVariable(MV2array(cyc), axes=[time] + axes[1:], grid=tab.getGrid(), attributes=tab.attributes) + moy = moy.reorder(initorder) + time = CDMS2createAxis(list(range(12)), id='months') + moy.setAxis(get_num_axis(moy, 'time'), time) + return moy + + +def ApplyLandmask(tab, landmask, maskland=True, maskocean=False): + """ + ################################################################################# + Description: + Applies the landmask on the given tab + if maskland is True, mask where landmask==100 + if maskocean is True, mask where landmask==0 + ################################################################################# + + :param tab: masked_array + :param landmask: masked_array + :param maskland: boolean, optional + masks land points + default value is True + :param maskocean: boolean, optional + masks ocean points + default value is False + + :return: tab: masked_array + masked_array where land points and/or ocean points are masked + """ + keyerror = None + if maskland is True or maskocean is True: + if tab.getGrid().shape != landmask.getGrid().shape: + keyerror = "tab (" + str(tab.getGrid().shape) + ") and landmask (" + str(landmask.getGrid().shape) +\ + ") are not on the same grid" + list_strings = ["ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": applying landmask", + str().ljust(5) + keyerror, str().ljust(5) + "cannot apply landmask", + str().ljust(5) + "this metric will be skipped"] + EnsoErrorsWarnings.my_warning(list_strings) + else: + landmask_nd = MV2zeros(tab.shape) + if landmask_nd.shape == landmask.shape: + landmask_nd = copy.copy(landmask) + else: + try: + landmask_nd[:] = landmask + except: + try: + landmask_nd[:, :] = landmask + except: + keyerror = "ApplyLandmask: tab must be more than 4D and this is not taken into account yet (" +\ + str(tab.shape) + ") and landmask (" + str(landmask.shape) + ")" + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": landmask shape", + str().ljust(5) + keyerror, str().ljust(5) + "cannot reshape landmask"] + EnsoErrorsWarnings.my_warning(list_strings) + if keyerror is None: + tab = MV2masked_where(landmask_nd.mask, tab) + # if land = 100 instead of 1, divides landmask by 100 + if MV2minimum(landmask_nd) == 0 and MV2maximum(landmask_nd) == 100: + landmask_nd = landmask_nd / 100. + if maskland is True: + tab = MV2masked_where(landmask_nd == 1, tab) + if maskocean is True: + tab = MV2masked_where(landmask_nd == 0, tab) + return tab, keyerror + + +def ApplyLandmaskToArea(area, landmask, maskland=True, maskocean=False): + """ + ################################################################################# + Description: + Applies the landmask on the given tab + if maskland is True, mask where landmask==1 and area=area*(1-landmask) (to weight island and coastal points) + if maskocean is True, mask where landmask==0 and area=area*landmask (to weight island and coastal points) + ################################################################################# + + :param area: masked_array + areacell + :param landmask: masked_array + :param maskland: boolean, optional + masks land points and weights island and coastal points + default value is True + :param maskocean: boolean, optional + masks ocean points and weights island and coastal points + default value is False + + :return: tab: masked_array + masked_array where land points and/or ocean points are masked + """ + keyerror = None + if maskland is True or maskocean is True: + if area.getGrid().shape != landmask.getGrid().shape: + keyerror = "ApplyLandmaskToArea: area (" + str(area.getGrid().shape) + ") and landmask (" +\ + str(landmask.getGrid().shape) + ") are not on the same grid" + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": applying landmask to areacell", + str().ljust(5) + keyerror, str().ljust(5) + "cannot apply landmask to areacell"] + EnsoErrorsWarnings.my_warning(list_strings) + if keyerror is None: + # if land = 100 instead of 1, divides landmask by 100 + if MV2minimum(landmask) == 0 and MV2maximum(landmask) == 100: + landmask = landmask / 100. + area = MV2masked_where(landmask.mask, area) + if maskland: + area = MV2masked_where(landmask == 1, area) + area = MV2multiply(area, 1-landmask) + if maskocean: + area = MV2masked_where(landmask == 0, area) + area = MV2multiply(area, landmask) + return area, keyerror + + +def ArrayListAx(tab, list1, ax_name_ax='', ax_long_name='', ax_ref=''): + tab_out = MV2array(tab) + ax = CDMS2createAxis(list(range(len(list1))), id=ax_name_ax) + ax.regions = str(list1) + if len(ax_long_name) > 0: + ax.long_name = ax_long_name + if len(ax_ref) > 0: + ax.reference = ax_ref + tab_out.setAxis(0, ax) + return tab_out + + +def ArrayToList(tab): + try: + len(tab.mask) + except: + tmp_mask = [tab.mask] + else: + tmp_mask = tab.mask + if all(ii is False for ii in tmp_mask) is True or all(ii == False for ii in tmp_mask) == True: + tmp = NParray(tab) + else: + tmp = NParray(MV2where(tab.mask, 1e20, tab)) + if len(tab.shape) == 1: + tab_out = list(tmp) + elif len(tab.shape) == 2: + tab_out = [list(tmp[ii]) for ii in range(len(tab))] + else: + tab_out = [None] + list_strings = ["ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": bad shape", + str().ljust(5) + "cannot transform this array to a list", + str().ljust(10) + "the length (" + str(len(tab.shape)) + ") of the shape (" + str(tab.shape) + + ") is too large", + str().ljust(10) + "it is not programed yet"] + EnsoErrorsWarnings.my_error(list_strings) + return tab_out + + +def BasinMask(tab_in, region_mask, box=None, lat1=None, lat2=None, latkey='', lon1=None, lon2=None, lonkey='', + debug=False): + keyerror = None + keys = ['between', 'outside'] + # temp corrections for cdms2 to find the right axis + CDMS2setAutoBounds('on') + # open file + this_dir, this_filename = OSpath__split(__file__) + # check basin file + basin_generic_ncfile = OSpath__join(this_dir, '../share/EnsoMetrics/basin_generic_1x1deg.nc') + if not OSpath__isfile(basin_generic_ncfile): + basin_generic_ncfile = OSpath__join(SYS_prefix, 'share', 'EnsoMetrics', 'basin_generic_1x1deg.nc') + if debug is True: + dict_debug = {'line1': '(path) ' + str(this_dir), 'line2': '(file) ' + str(this_filename), + 'line3': '(basin) ' + str(basin_generic_ncfile)} + EnsoErrorsWarnings.debug_mode('\033[93m', 'OSpath__split', 20, **dict_debug) + ff = CDMS2open(basin_generic_ncfile) + # read basins + if box is not None: + region_ref = ReferenceRegions(box) + basin = ff('basin', latitude=region_ref['latitude'], longitude=region_ref['longitude']) + else: + basin = ff('basin') + if debug is True: + dict_debug = {'axes1': str([ax.id for ax in basin.getAxisList()]), 'shape1': str(basin.shape), + 'line1': 'order = ' + str(basin.getOrder())} + EnsoErrorsWarnings.debug_mode('\033[93m', 'in BasinMask', 20, **dict_debug) + # choose basin + keybasin = {'atlantic': 1, 'pacific': 2, 'indian': 3, 'antarctic': 10, 'arctic': 11} + mask = MV2zeros(basin.shape) + if region_mask.lower() not in list(keybasin.keys()): + keyerror = "unknown region: " + region_mask + " (basin_generic_1x1deg.nc, regrided file from NOAA NODC" + \ + "WOA09 Masks basin Data Files)" + list_strings = ["WARNING" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": region", + str().ljust(5) + keyerror, + str().ljust(5) + "https://iridl.ldeo.columbia.edu/SOURCES/.NOAA/.NODC/.WOA09/.Masks/.basin/" + + "datafiles.html"] + EnsoErrorsWarnings.my_warning(list_strings) + else: + mask = MV2where(basin == keybasin[region_mask], 1, mask) + mask = MV2where(basin.mask, 0, mask) + # basin mask is selected only between or outside lat1 and lat2 + if latkey in keys and lat1 is not None and lat2 is not None: + lat2d = MV2zeros(basin.shape) + lat2d = lat2d.reorder('10') + lat2d[:] = basin.getLatitude() + lat2d = lat2d.reorder('10') + tmp = MV2where(lat2d > lat1, 1, 0) + MV2where(lat2d < lat2, 1, 0) + if latkey == 'between': + mask = MV2where(tmp != 2, 0, mask) + else: + mask = MV2where(tmp == 2, 0, mask) + # basin mask is selected only between or outside lon1 and lon2 + if lonkey in keys and lat1 is not None and lat2 is not None: + lon2d = MV2zeros(basin.shape) + lon2d[:] = basin.getLongitude() + tmp = MV2where(lon2d > lon1, 1, 0) + MV2where(lon2d < lon2, 1, 0) + if latkey == 'between': + mask = MV2where(tmp != 2, 0, mask) + else: + mask = MV2where(tmp == 2, 0, mask) + # apply mask + tab_out = MV2masked_where(mask == 1, tab_in) + tab_out = CDMS2createVariable(tab_out, axes=tab_in.getAxisList(), grid=tab_in.getGrid(), mask=tab_in.mask, + attributes=tab_in.attributes, id=tab_in.id) + return tab_out, keyerror + + +def CheckTime(tab1, tab2, frequency='monthly', min_time_steps=None, metric_name='', debug=False, **kwargs): + """ + ################################################################################# + Description: + Checks if tab1 and tab2 cover the same time period and adjust if not + Checks if the minimum_length of the time period criterion if fulfilled + ################################################################################# + + :param tab1: masked_array + :param tab2: masked_array + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + :param min_time_steps: int, optional + minimum number of time steps for the metric to make sens + e.g., for 30 years of monthly data mintimesteps=360 + :param metric_name: string, optional + name of the metric calling the function + :return: + """ + if debug is True: + # dict_debug = {'shape1': 'tab1.shape = ' + str(tab1.shape), 'shape2': 'tab2.shape = ' + str(tab2.shape), + # 'time1': 'tab1.time = ' + str(TimeBounds(tab1)), + # 'time2': 'tab2.time = ' + str(TimeBounds(tab2))} + dict_debug = {'shape1': 'tab1.shape = ' + str(tab1.shape), 'shape2': 'tab2.shape = ' + str(tab2.shape)} + EnsoErrorsWarnings.debug_mode('\033[93m', 'in CheckTime (input)', 20, **dict_debug) + # gets dates of the first and last the time steps of tab1 + stime1 = tab1.getTime().asComponentTime()[0] + etime1 = tab1.getTime().asComponentTime()[-1] + + # gets dates of the first and last the time steps of tab2 + stime2 = tab2.getTime().asComponentTime()[0] + etime2 = tab2.getTime().asComponentTime()[-1] + + # retains only the latest start date and the earliest end date + if stime1.year > stime2.year: + stime = stime1 + elif stime1.year < stime2.year: + stime = stime2 + else: + if stime1.month > stime2.month: + stime = stime1 + elif stime1.month < stime2.month: + stime = stime2 + else: + if stime1.day > stime2.day: + stime = stime1 + elif stime1.day < stime2.day: + stime = stime2 + else: + stime = max(stime1, stime2) + if etime1.year < etime2.year: + etime = etime1 + elif etime1.year > etime2.year: + etime = etime2 + else: + if etime1.month < etime2.month: + etime = etime1 + elif etime1.month > etime2.month: + etime = etime2 + else: + if etime1.day < etime2.day: + etime = etime1 + elif etime1.day > etime2.day: + etime = etime2 + else: + etime = min(etime1, etime2) + # stime = max(stime1, stime2) + # etime = min(etime1, etime2) + + # defines the period between the two dates + if frequency == 'daily': + stime_adjust = CDTIMEcomptime(stime.year, stime.month, stime.day, 0, 0, 0.0) + etime_adjust = CDTIMEcomptime(etime.year, etime.month, etime.day, 23, 59, 0) + elif frequency == 'monthly': + etime_day = monthrange(etime.year, etime.month)[-1] + stime_adjust = CDTIMEcomptime(stime.year, stime.month, 1, 0, 0, 0.0) + etime_adjust = CDTIMEcomptime(etime.year, etime.month, etime_day, 23, 59, 0) + elif frequency == 'yearly': + stime_adjust = CDTIMEcomptime(stime.year, 1, 1, 0, 0, 0.0) + etime_adjust = CDTIMEcomptime(etime.year, 12, 31, 23, 59, 0) + else: + EnsoErrorsWarnings.unknown_frequency(frequency, INSPECTstack()) + + # retains only the time-period common to both tab1 and tab2 + tab1_sliced = tab1(time=(stime_adjust, etime_adjust)) + tab2_sliced = tab2(time=(stime_adjust, etime_adjust)) + if debug is True: + # dict_debug = {'shape1': 'tab1.shape = ' + str(tab1_sliced.shape), + # 'shape2': 'tab2.shape = ' + str(tab2_sliced.shape), + # 'time1': 'tab1.time = ' + str(TimeBounds(tab1_sliced)), + # 'time2': 'tab1.time = ' + str(tab1_sliced.getTime().asComponentTime()[:]), + # 'time3': 'tab2.time = ' + str(TimeBounds(tab2_sliced)), + # 'time4': 'tab2.time = ' + str(tab2_sliced.getTime().asComponentTime()[:])} + dict_debug = {'shape1': 'tab1.shape = ' + str(tab1_sliced.shape), + 'shape2': 'tab2.shape = ' + str(tab2_sliced.shape)} + EnsoErrorsWarnings.debug_mode('\033[93m', 'in CheckTime (output)', 20, **dict_debug) + if len(tab1_sliced.getTime()[:]) != len(tab2_sliced.getTime()[:]): + keyerror1 = "missing time step within the given period" + else: + keyerror1 = None + tab2_sliced.setAxis(0, tab1_sliced.getTime()) + + # checks if the remaining time-period fulfills the minimum length criterion + if min_time_steps is not None: + if len(tab1_sliced) < min_time_steps or len(tab2_sliced) < min_time_steps: + shortest = min(len(tab1_sliced), len(tab2_sliced)) + EnsoErrorsWarnings.too_short_time_period(metric_name, shortest, min_time_steps, INSPECTstack()) + keyerror2 = "too short time period (variable1:" + str(len(tab1_sliced)) + " ; variable2:" +\ + str(len(tab2_sliced)) + ")" + else: + keyerror2 = None + else: + keyerror2 = None + + # errors + if keyerror1 is not None or keyerror2 is not None: + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + keyerror = None + return tab1_sliced, tab2_sliced, keyerror + + +def CheckUnits(tab, var_name, name_in_file, units, return_tab_only=True, **kwargs): + """ + ################################################################################# + Description: + Checks the units of the variable and changes it if necessary + Works for current/wind velocities, heat flux, precipitation, pressure, temperature, wind stress + + Uses MV2 (uvcdat) to find the minimum value, to multiply and to subtract + ################################################################################# + + :param tab: array + array containing 'var_name' + :param var_name: string + name of the variable included in 'tab' + :param name_in_file: string + name of the variable in the file (usually the short_name) + :param units: string + units of the variable included in 'tab' + :param return_tab_only: boolean, optional + default value = True, only the tab is returned + True if you want only the tab, if you want the new units also pass anything but true + :return tab: array + array with new units (if applicable) + """ + keyerror = None + if var_name in ['temperature']: + if units in ['K', 'Kelvin', 'Kelvins', 'degree K', 'degree Kelvin', 'degree Kelvins', 'degree_K', + 'degree_Kelvin', 'degree_Kelvins', 'degreeK', 'degreeKelvin', 'degreeKelvins', 'degrees K', + 'degrees Kelvin', 'degrees Kelvins', 'degrees_K', 'degrees_Kelvin', 'degrees_Kelvins', 'degreesK', + 'degreesKelvin', 'degreesKelvins', 'deg K', 'deg Kelvin', 'deg Kelvins', 'deg_K', 'deg_Kelvin', + 'deg_Kelvins', 'degK', 'degKelvin', 'degKelvins', 'deg. K', 'deg. Kelvin', 'deg. Kelvins']: + # check if the temperature units is really K + if float(MV2minimum(tab)) > 150: + # unit change of the temperature: from K to degC + tab = dict_operations['minus'](tab, 273.15) + else: + minmax = [MV2minimum(tab), MV2maximum(tab)] + EnsoErrorsWarnings.unlikely_units(var_name, name_in_file, units, minmax, INSPECTstack()) + keyerror = "unlikely units: " + str(units) + "(" + str(minmax) + ")" + elif units in ['C', 'celsius', 'Celsius', 'degree C', 'degree celsius', 'degree Celsius', 'degree_C', + 'degree_celsius', 'degree_Celsius', 'degreeC', 'degreecelsius', 'degreeCelsius', 'degrees C', + 'degrees celsius', 'degrees Celsius', 'degrees_C', 'degrees_celsius', 'degrees_Celsius', + 'degreesC', 'degreescelsius', 'degreesCelsius', 'deg C', 'deg celsius', 'deg Celsius', 'deg_C', + 'deg_celsius', 'deg_Celsius', 'degC', 'degcelsius', 'degCelsius', 'deg. C', 'deg. celsius', + 'deg. Celsius']: + # check if the temperature units is really degC + if float(MV2minimum(tab)) > 50: + minmax = [MV2minimum(tab), MV2maximum(tab)] + EnsoErrorsWarnings.unlikely_units(var_name, name_in_file, minmax, units, INSPECTstack()) + keyerror = "unlikely units: " + str(units) + "(" + str(minmax) + ")" + else: + EnsoErrorsWarnings.unknown_units(var_name, name_in_file, units, INSPECTstack()) + keyerror = "unknown units: " + str(units) + "(as " + str(var_name) + ")" + units = "degC" + elif var_name in ['precipitations']: + if units in ["kg/m2/s", "kg m-2 s-1", "kg/m^2/s", "kg/m**2/s", "kg m**-2 s**-1", "Kg/m2/s", "Kg m-2 s-1", + "Kg/m^2/s", "Kg/m**2/s", "Kg m**-2 s**-1"]: + # changes units of the precipitation flux: from kg/(m2.s) to mm/day + # it must be divided by the density of water = 1000 kg/m3 + # and multiplied by 1000 (m to mm) and by 60*60*24 (s to day) + tab = dict_operations['multiply'](tab, 86400) + elif units in ["mm/day", "mm day-1", 'mm day**-1', "mm/d", "mm d-1", 'mm d**-1']: + pass + else: + EnsoErrorsWarnings.unknown_units(var_name, name_in_file, units, INSPECTstack()) + keyerror = "unknown units: " + str(units) + "(as " + str(var_name) + ")" + units = "mm/day" + elif var_name in ['wind stress']: + if units not in ['N/m2', 'N m-2', 'N/m^2', 'N/m**2', 'N m**-2', 'Pa', 'pascal', 'pascals', 'Pascal', 'Pascals']: + EnsoErrorsWarnings.unknown_units(var_name, name_in_file, units, INSPECTstack()) + keyerror = "unknown units: " + str(units) + "(as " + str(var_name) + ")" + units = "N/m2" + elif var_name in ['velocity']: + if units in ['cm/s', 'cm s-1', 'cm s**-1', 'cm/sec', 'cm sec-1', 'cm sec**-1']: + # unit change of the velocity: from cm/s to m/s + tab = dict_operations['multiply'](tab, 1e-2) + elif units in ['m/s', 'm s-1', 'm s**-1', 'm/sec', 'm sec-1', 'm sec**-1']: + pass + else: + EnsoErrorsWarnings.unknown_units(var_name, name_in_file, units, INSPECTstack()) + keyerror = "unknown units: " + str(units) + "(as " + str(var_name) + ")" + units = "m/s" + elif var_name in ['heat flux']: + if units in ['W/m2', 'W m-2', 'W/m^2', 'W/m**2', 'W m**-2', "Watt/m2", "Watt m-2", "Watt/m^2", "Watt/m**2", + "Watt m**-2", "Watts/m2", "Watts m-2", "Watts/m^2", "Watts/m**2", "Watts m**-2"]: + pass + else: + EnsoErrorsWarnings.unknown_units(var_name, name_in_file, units, INSPECTstack()) + keyerror = "unknown units: " + str(units) + "(as " + str(var_name) + ")" + units = "W/m2" + elif var_name in ['pressure']: + if units in ['N/m2', 'N m-2', 'N/m^2', 'N/m**2', 'N m**-2', 'Pa', 'pascal', 'pascals', 'Pascal', 'Pascals']: + pass + else: + EnsoErrorsWarnings.unknown_units(var_name, name_in_file, units, INSPECTstack()) + keyerror = "unknown units: " + str(units) + "(as " + str(var_name) + ")" + units = "Pa" + elif var_name in ['sea surface height']: + if units in ['cm', 'centimeter']: + # unit change of the sea surface height: from cm to m + tab = dict_operations['multiply'](tab, 1e-2) + elif units in ['m', 'meter']: + pass + else: + EnsoErrorsWarnings.unknown_units(var_name, name_in_file, units, INSPECTstack()) + keyerror = "unknown units: " + str(units) + "(as " + str(var_name) + ")" + units = "m" + else: + list_strings = ["WARNING" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": variable name", + str().ljust(5) + "unknown variable name: " + var_name + " (" + name_in_file + ")"] + EnsoErrorsWarnings.my_warning(list_strings) + if return_tab_only is True: + return tab + else: + return tab, units, keyerror + + +def Event_selection(tab, frequency, nbr_years_window=None, list_event_years=[]): + if frequency not in ['daily', 'monthly', 'yearly']: + EnsoErrorsWarnings.unknown_frequency(frequency, INSPECTstack()) + if len(list_event_years) == 0: + tax = tab.getTime().asComponentTime() + list_event_years = sorted(list(set([tax[ii].year for ii in range(len(tax))]))) + else: + list_event_years = sorted(list_event_years) + # function to fill array with masked value where the data is not available + def fill_array(tab, units, freq): + y1 = tab.getTime().asComponentTime()[0].year + m1 = tab.getTime().asComponentTime()[0].month + d1 = tab.getTime().asComponentTime()[0].day + if len(tab.shape) == 1: + tab_out = MV2zeros(nbr_years_window * 12) + elif len(tab.shape) == 2: + tab_out = MV2zeros((nbr_years_window * 12, tab.shape[1])) + else: + tab_out = MV2zeros((nbr_years_window * 12, tab.shape[1], tab.shape[2])) + tab_out = MV2masked_where(tab_out == 0, tab_out) + axis = CDMS2createAxis(list(range(len(tab_out))), id='time') + axis.units = units + tab_out.setAxis(0, axis) + for ii in range(len(tab)): + y2 = tab.getTime().asComponentTime()[ii].year + m2 = tab.getTime().asComponentTime()[ii].month + d2 = tab.getTime().asComponentTime()[ii].day + if freq == 'yearly': + if y2 == y1: + tab_out[ii:ii + len(tab)] = copy.copy(tab) + break + elif freq == 'monthly': + if y2 == y1 and m2 == m1: + tab_out[ii:ii + len(tab)] = copy.copy(tab) + break + elif freq == 'daily': + if y2 == y1 and m2 == m1 and d2 == d1: + tab_out[ii:ii + len(tab)] = copy.copy(tab) + break + return tab_out + # compute composite + if nbr_years_window is not None: + composite = list() + for yy in list_event_years: + # first and last years of the window + yy1, yy2 = yy - (int(round(nbr_years_window / 2)) - 1), yy + int(round(nbr_years_window / 2)) + # create time bounds from 'first and last years of the window' + timebnds = (str(yy1) + '-01-01 00:00:00.0', str(yy2) + '-12-31 23:59:60.0') + # select the right time period in the given tab + tmp1 = tab(time=timebnds) + # sometimes there is some errors with 'time=timebnds' + # if the time slice selected has the right length: do nothing + # else: fill the beginning / end of the time series by masked values (done by the function 'fill_array') + if frequency == 'yearly': + length = nbr_years_window + units = 'years since ' + timebnds[0] + units_out = 'years since 0001-07-02 12:00:00' + elif frequency == 'monthly': + length = nbr_years_window * 12 + units = 'months since ' + timebnds[0] + units_out = 'months since 0001-01-15 12:00:00' + elif frequency == 'daily': + date1 = date(yy1, 1, 1) + date2 = date(yy2, 12, 31) + length = (date2 - date1).days + units = 'days since ' + timebnds[0] + units_out = 'days since 0001-01-01 12:00:00' + if len(tmp1) == length: + tmp2 = copy.copy(tmp1) + else: + tmp2 = fill_array(tmp1, units, frequency) + # save the selected time slice + composite.append(tmp2) + composite = MV2array(composite) + # axis list + axis0 = CDMS2createAxis(MV2array(list_event_years, dtype='int32'), id='years') + axis1 = CDMS2createAxis(list(range(len(composite[0]))), id='months') + axis1.units = units_out + axes = [axis0, axis1] + if len(tab.shape) > 1: + axes = axes + tab.getAxisList()[1:] + composite.setAxisList(axes) + else: + time_ax = tab.getTime().asComponentTime() # gets component time of tab + list_years = [yy.year for yy in time_ax[:]] # listing years in tab (from component time) + indices = MV2arange(tab.size) + # creates a tab of 'condition' where True is set when the event is found, False otherwise + try: + condition = [True if yy in list_event_years else False for yy in list_years] + except: + list_event_years = [str(yy) for yy in list_event_years] + condition = [True if str(yy) in list_event_years else False for yy in list_years] + ids = MV2compress(condition, indices) # gets indices of events + composite = MV2take(tab, ids, axis=0) # gets events + axis0 = CDMS2createAxis(MV2array(list_event_years, dtype='int32'), id='years') + composite.setAxis(0, axis0) + return composite + + +def Composite(tab, list_event_years, frequency, nbr_years_window=None): + return MV2average( + Event_selection(tab, frequency, nbr_years_window=nbr_years_window, list_event_years=list_event_years), axis=0) + + +def DetectEvents(tab, season, threshold, normalization=False, nino=True, compute_season=True): + """ + ################################################################################# + Description: + Detects Nina or Nino events + These events are detected when 'tab' anomalies during 'season' are above (less) then 'threshold' + The anomalies can be normalized + + Uses MV2 (uvcdat) to create an empty array, to create an array of indices, to define conditions, to select the + indices depending on the conditions and to select the years of the events + ################################################################################# + + :param tab: masked_array + masked_array containing a variable from which the events are detected. Most likely SST + :param season: string + one month (e.g, 'DEC'), two months (e.g., 'DJ'), three months (e.g., 'NDJ'), four months (e.g., 'NDJF'), period + when the events are detected + :param threshold: float + threshold to define the events (e.g., 0.75 for El Nino, -0.75 for La Nina) + :param normalization: boolean, optional + True if events are detected based on the standard deviation, if not pass anything but True + :param nino: boolean, optional + True if events are detected if above threshold (El Nino like), if not pass anything but True (La Nina like) + :return list_of_years: list + list of years including a detected event + """ + # Seasonal mean and anomalies + if compute_season is True: + tab = SeasonalMean(tab, season, compute_anom=True) + # Normalization ? + if normalization is True: + threshold = threshold * float(GENUTILstd(tab, weights=None, axis=0, centered=1, biased=1)) + # Initialization + tab_threshold = MV2zeros(tab.shape) + tab_threshold.fill(threshold) + list_years = [tab.getTime().asComponentTime()[yy].year for yy in range(len(tab))] + indices = MV2arange(len(tab)) + # Conditions + if nino is True: + condition = MV2where(tab > tab_threshold, True, False) + else: + condition = MV2where(tab < tab_threshold, True, False) + # Indices of the events + ids = MV2compress(condition, indices) + # Events years + return list(MV2take(list_years, ids, axis=0)) + + +def Detrend(tab, info, axis=0, method='linear', bp=0): + """ + ################################################################################# + Description: + Removes trend along 'axis' from 'tab' + ################################################################################# + + :param tab: array + tab of data to detrend + :param info: string + information about what is done to 'tab' + :param axis: int, optional + axis along which to detrend the data + default value is the first axis (0) + :param method: string, optional + detrending method: + 'constant': only the mean of 'tab' is subtracted + 'linear': the result of a linear least-squares fit to 'tab' is subtracted from 'tab' + :param bp: array of integer, optional + a sequence of break points. If given, an individual linear fit is performed for each part of 'tab' between two + break points + break points are specified as indices into 'tab' + :return new_tab: array + detrended data + """ + if method not in ['linear', 'constant']: + new_tab = None + keyerror = "cannot detrend: unknown method" + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": method", + str().ljust(5) + "unknown method: " + str(method) + ] + EnsoErrorsWarnings.my_warning(list_strings) + else: + axes = tab.getAxisList() + grid = tab.getGrid() + mask = tab.mask + mean, keyerror = AverageTemporal(tab) + new_tab = MV2array(SCIPYsignal_detrend(tab, axis=axis, type=method, bp=bp)) + new_tab = new_tab + mean + new_tab = MV2masked_where(mask, new_tab) + new_tab.setAxisList(axes) + new_tab.setGrid(grid) + if method == 'linear': + info = info + ', time series are linearly detrended' + else: + info = info + ', the mean value of the time series is subtracted' + return new_tab, info, keyerror + + +def DurationAllEvent(tab, threshold, nino=True, debug=False): + """ + ################################################################################# + Description: + Duration of Nina or Nino events + The duration is the number of consecutive timestep when tab < threshold for La Nina and tab > threshold for El Nino + + Uses CDMS2 (uvcdat) for axes + ################################################################################# + + :param tab: masked_array + masked_array containing a variable from which the events are detected. Most likely SST + :param threshold: float + threshold to define the events (e.g., 0.75 for El Nino, -0.75 for La Nina) + :param nino: boolean, optional + True if events are detected if above threshold (El Nino like), if not pass anything but True (La Nina like) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :return list_of_years: list + list of years including a detected event + """ + tmp = MV2array([DurationEvent(tab[tt], threshold, nino=nino, debug=debug) for tt in range(len(tab))]) + tmp.setAxis(0, tab.getAxis(0)) + return tmp + + +def DurationEvent(tab, threshold, nino=True, debug=False): + """ + ################################################################################# + Description: + Duration of Nina or Nino events + The duration is the number of consecutive timestep when tab < threshold for La Nina and tab > threshold for El Nino + + Uses MV2 (uvcdat) + ################################################################################# + + :param tab: masked_array + masked_array containing a variable from which the events are detected. Most likely SST + :param threshold: float + threshold to define the events (e.g., 0.75 for El Nino, -0.75 for La Nina) + :param nino: boolean, optional + True if events are detected if above threshold (El Nino like), if not pass anything but True (La Nina like) + :param debug: bolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :return list_of_years: list + list of years including a detected event + """ + try: + len(tab.mask) + except: + mask = [tab.mask] + else: + mask = tab.mask + # if debug is True: + # dict_debug = {'line1': 'threshold = ' + str(threshold) + ' ; nino = ' + str(nino) + # + ' ; len(tab) = ' + str(len(tab)), + # 'line2': 'tab = ' + str(tab) + '\nmask = ' + str(mask) + # } + # EnsoErrorsWarnings.DebugMode('\033[93m', 'in DurationEvent', 20, **dict_debug) + if all(ii is False for ii in mask) is True: + pass + else: + if nino is True: + tab = MV2where(tab.mask, -9999, tab) + else: + tab = MV2where(tab.mask, 9999, tab) + # if debug is True: + # dict_debug = {'line1': 'after unmasking', + # 'line2': 'tab = ' + str(tab)} + # EnsoErrorsWarnings.DebugMode('\033[93m', 'in DurationEvent', 20, **dict_debug) + tmp1 = list(reversed(tab[:int(round(len(tab) / 2))])) + tmp2 = list(tab[int(round(len(tab) / 2)):]) + if nino is True: + try: + nbr_before = next(x[0] for x in enumerate(tmp1) if x[1] <= threshold) + except: + if all(ii == -9999 for ii in tmp1): + nbr_before = 0 + elif all(ii > threshold for ii in tmp1): + nbr_before = len(tmp1) + try: + nbr_after = next(x[0] for x in enumerate(tmp2) if x[1] <= threshold) + except: + if all(ii == -9999 for ii in tmp2): + nbr_after = 0 + elif all(ii > threshold for ii in tmp2): + nbr_after = len(tmp2) + else: + try: + nbr_before = next(x[0] for x in enumerate(tmp1) if x[1] >= threshold) + except: + if all(ii == 9999 for ii in tmp1): + nbr_before = 0 + elif all(ii < threshold for ii in tmp1): + nbr_before = len(tmp1) + try: + nbr_after = next(x[0] for x in enumerate(tmp2) if x[1] >= threshold) + except: + if all(ii == 9999 for ii in tmp2): + nbr_after = 0 + elif all(ii < threshold for ii in tmp2): + nbr_after = len(tmp2) + duration = nbr_before + nbr_after + # if debug is True: + # dict_debug = {'line1': 'duration of the event = ' + str(duration)} + # EnsoErrorsWarnings.DebugMode('\033[93m', 'in DurationEvent', 20, **dict_debug) + return duration + + +def get_num_axis(tab, name_axis): + """ + ################################################################################# + Description: + Finds the number of the axis named "name_axis" + ################################################################################# + + :param tab: array + tab of data to normalize by the standard deviation + :param name_axis: string + name of an axis + e.g., name_axis='latitude' + :return number: int + position of the axis named "name_axis" + """ + num = None + if name_axis == 'depth': + axis_nick = 'lev' + axis_nicks = ['z', 'Z', 'st_ocean', 'sw_ocean'] + if name_axis == 'latitude': + axis_nick = 'lat' + axis_nicks = ['j', 'y', 'Y', 'yt_ocean', 'yu_ocean'] + elif name_axis == 'longitude': + axis_nick = 'lon' + axis_nicks = ['i', 'x', 'X', 'xt_ocean', 'xu_ocean'] + elif name_axis == 'time': + axis_nick = 'time' + axis_nicks = ['t', 'T'] + for nn in range(len(tab.shape)): + if axis_nick in tab.getAxisList()[nn].id: + num = nn + break + if num is None: + for nn in range(len(tab.shape)): + for ax in axis_nicks: + if ax == tab.getAxisList()[nn].id: + num = nn + break + if num is None: + list_strings = ["ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": axis", + str().ljust(5) + "cannot find axis named: " + str(name_axis), + str().ljust(5) + "axes: " + str(tab.getAxisList())] + EnsoErrorsWarnings.my_error(list_strings) + return num + + +def get_year_by_year(tab, frequency='monthly'): + """ + ################################################################################# + Description: + Reshape array to a year by year array + ################################################################################# + + :param tab: masked_array + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + :return: tab: array + array of the year by year values + """ + tab = tab.reorder('t...') + time_ax = tab.getTime().asComponentTime() + myshape = [1] + [ss for ss in tab.shape[1:]] + zeros = MV2zeros(myshape) + zeros = MV2masked_where(zeros == 0, zeros) + if frequency == 'daily': + days = MV2array(list(tt.day for tt in time_ax)) + months = MV2array(list(tt.month for tt in time_ax)) + months = MV2array([(mm*100)+dd for dd, mm in zip(days, months)]) + tmm = CDMS2createAxis(list(range(365)), id='days') + m1 = time_ax[0].day + m2 = time_ax[-1].day + t2 = 365 + elif frequency == 'monthly': + months = MV2array(list(tt.month for tt in time_ax)) + tmm = CDMS2createAxis(list(range(12)), id='months') + m1 = time_ax[0].month + m2 = time_ax[-1].month + t2 = 12 + else: + EnsoErrorsWarnings.unknown_frequency(frequency, INSPECTstack()) + years = sorted(set(MV2array(list(tt.year for tt in time_ax)))) + tyy = CDMS2createAxis(MV2array(years, dtype='int32'), id='years') + axes = [tyy] + [tmm] + val = sorted(set(months)) + tab_out = list() + if frequency == 'daily': + val.remove(129) + for ii in val: + tmp = tab.compress(months == ii, axis=0) + if m1 != 1 and len(tmp) != len(years): + tmp = MV2concatenate((zeros, tmp)) + if m2 != t2 and len(tmp) != len(years): + tmp = MV2concatenate((tmp, zeros)) + tab_out.append(tmp) + tab_out = MV2array(tab_out) + tab_out = MV2masked_where(tab_out == 0, tab_out) + tab_out = tab_out.reorder('10') + if len(tab.shape) == 1: + tab_out = CDMS2createVariable(tab_out, axes=axes, attributes=tab.attributes, id=tab.id) + else: + axes = axes + tab.getAxisList()[1:] + grid = tab[0].getGrid() + mask = tab[0].mask + mask_out = MV2zeros(tab_out.shape) + mask_out[:, :] = mask + tab_out = CDMS2createVariable(tab_out, axes=axes, grid=grid, mask=mask_out, attributes=tab.attributes, + id=tab.id) + return tab_out + + +def MinMax(tab): + return [float(MV2minimum(tab)), float(MV2maximum(tab))] + + +def MyEmpty(tab, time=True, time_id=''): + tab_out = ArrayZeros(tab) + if time is True: + axis = CDMS2createAxis(MV2array(len(tab_out), dtype='int32'), id=time_id) + axes = [axis] + tab.getAxisList()[1:] + else: + axes = tab.getAxisList() + tab_out.setAxisList(axes) + return tab_out + + +def Normalize(tab, frequency): + """ + ################################################################################# + Description: + Removes trend along 'axis' from 'tab' + ################################################################################# + + :param tab: array + tab of data to normalize by the standard deviation + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + :return tab: masked_array + normalized data + """ + keyerror = None + axes = tab.getAxisList() + if frequency == 'daily': + time_steps_per_year = 365 + elif frequency == 'monthly': + time_steps_per_year = 12 + elif frequency == 'yearly': + time_steps_per_year = 1 + else: + keyerror = "unknown frequency" + time_steps_per_year = None + EnsoErrorsWarnings.unknown_frequency(frequency, INSPECTstack()) + if time_steps_per_year is not None: + if len(tab) % time_steps_per_year != 0: + tab_out = None + keyerror = "cannot perform normalization: the function can only handle full years (len(tab) = " +\ + str(len(tab)) + ")" + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": data length", + str().ljust(5) + "the normalization function can only handle full years: " + + str(len(tab) // time_steps_per_year) + " years " + str(len(tab) % time_steps_per_year), + str().ljust(10) + "frequency: " + str(frequency) + " (time steps per year = " + + str(time_steps_per_year) + "), len(dataset) = " + str(len(tab)) + ", so " + + str(len(tab) / float(time_steps_per_year)) + " years", + ] + EnsoErrorsWarnings.my_warning(list_strings) + else: + # reshape tab like [yy,nb] + new_tab = list() + for yy in range(len(tab)/time_steps_per_year): + new_tab.append(tab[yy * time_steps_per_year:(yy + 1) * time_steps_per_year]) + new_tab = MV2array(new_tab) + std = MV2zeros(new_tab[0].shape) + for dd in range(time_steps_per_year): + std[dd] = float(GENUTILstd(new_tab[:,dd], weights=None, axis=0, centered=1, biased=1)) + tab_out = copy.copy(tab) + for yy in range(len(tab) / time_steps_per_year): + tab_out[yy * time_steps_per_year:(yy + 1) * time_steps_per_year] = \ + tab_out[yy * time_steps_per_year:(yy + 1) * time_steps_per_year] / std + if len(tab.shape) == 1: + tab_out = CDMS2createVariable(tab_out, axes=axes, attributes=tab.attributes, id=tab.id) + else: + axes = axes + tab.getAxisList()[1:] + grid = tab.getGrid() + mask = tab.mask + tab_out = CDMS2createVariable(tab_out, axes=axes, grid=grid, mask=mask, attributes=tab.attributes, id=tab.id) + return tab_out, keyerror + + +def ReadAndSelectRegion(filename, varname, box=None, time_bounds=None, frequency=None, **kwargs): + """ + ################################################################################# + Description: + Reads the given 'varname' from the given 'filename' and selects the given 'box' + + Uses cdms2 (uvcdat) to read 'varname' from 'filename' and cdutil (uvcdat) to select the 'box' + ################################################################################# + + :param filename: string + string of the path to the file and name of the file to read + :param varname: string + name of the variable to read from 'filename' + :param box: string + name of a region to select, must be defined in EnsoCollectionsLib.ReferenceRegions + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + + :return tab: masked_array + masked_array containing 'varname' in 'box' + """ + # Temp corrections for cdms2 to find the right axis + CDMS2setAutoBounds('on') + # Open file and get time dimension + fi = CDMS2open(filename) + if box is None: # no box given + if time_bounds is None: # no time period given + # read file + tab = fi(varname) + else: # time period given by the user + # read file + tab = fi(varname, time=time_bounds) + else: # box given by the user + # define box + region_ref = ReferenceRegions(box) + if time_bounds is None: # no time period given + # read file + tab = fi(varname, latitude=region_ref['latitude'], longitude=region_ref['longitude']) + else: + # read file + tab = fi(varname, time=time_bounds, latitude=region_ref['latitude'], longitude=region_ref['longitude']) + # sign correction + try: + att1 = tab.attributes['standard_name'].lower().replace(" ", "_") + except: + att1 = '' + try: + att2 = tab.attributes['long_name'].lower().replace(" ", "_") + except: + att2 = '' + if "latent_heat" in att1 or "latent_heat" in att2 or "sensible_heat" in att1 or "sensible_heat" in att2 or\ + (varname in ["tauu", "tauuo", "tauv", "tauvo", "taux", "tauy", "uflx", "vflx"]): + if "upward" in att1 or "upward" in att2 or\ + (varname in ["tauu", "tauuo", "tauv", "tauvo", "taux", "tauy", "uflx", "vflx"] and + ("in_air" in att1 or "in_air" in att2)): + # I need to be in the ocean point of view so the heat fluxes must be downwards + tab = -1 * tab + if time_bounds is not None: + # sometimes the time boundaries are wrong, even with 'time=time_bounds' + # this section checks if one time step has not been included by error at the beginning or the end of the time + # series + if isinstance(time_bounds[0], str): + if str(tab.getTime().asComponentTime()[0]) < time_bounds[0]: + tab = tab[1:] + if str(tab.getTime().asComponentTime()[-1]) > time_bounds[1]: + tab = tab[:-1] + time_ax = tab.getTime() + time_units = "days since " + str(time_ax.asComponentTime()[0].year) + "-01-01 12:00:00" + time_ax.id = "time" + time_ax.toRelativeTime(time_units) + tab.setAxis(0, time_ax) + if frequency is None: # no frequency given + pass + elif frequency == 'daily': + cdutil.setTimeBoundsDaily(tab) + elif frequency == 'monthly': + cdutil.setTimeBoundsMonthly(tab) + elif frequency == 'yearly': + cdutil.setTimeBoundsYearly(tab) + else: + EnsoErrorsWarnings.unknown_frequency(frequency, INSPECTstack()) + # remove axis 'level' if its length is 1 + if tab.getLevel(): + if len(tab.getLevel()) == 1: + tab = tab(squeeze=1) + # HadISST has -1000 values... mask them + if 'HadISST' in filename or 'hadisst' in filename: + tab = MV2masked_where(tab == -1000, tab) + # check if the mask is constant through time + if len(NPwhere(tab[0].mask != tab[1:].mask)[0]) > 0: + # the mask is not constant -> make it constant + # sum mask through time + mask = MV2sum(tab.mask.astype('f'), axis=0) + # mask where at least one time step is masked + mask = MV2where(mask > 0, True, False) + # create a mask the same size as the original data + mask_nd = MV2zeros(tab.shape) + mask_nd[:] = mask + # apply mask to original data + tab = MV2masked_where(mask_nd, tab) + # check taux sign + if varname in ["taux", "tauu", "tauuo", "uflx"]: + # define box + region_ref = ReferenceRegions("nino4") + if time_bounds is None: # no time period given + # read file + taux = fi(varname, latitude=region_ref['latitude'], longitude=region_ref['longitude']) + else: + # read file + taux = fi(varname, time=time_bounds, latitude=region_ref['latitude'], longitude=region_ref['longitude']) + # horizontal average + taux, keyerror = AverageHorizontal(taux, region="nino4") + if keyerror is None: + taux, keyerror = AverageTemporal(taux) + if keyerror is None and float(taux) > 0: + print('\033[93m' + str().ljust(25) + "NOTE: taux sign reversed by the code (mean nino4 = " + + str(float(taux)) + ")" + '\033[0m') + tab = -1 * tab + fi.close() + return tab + + +def ReadAreaSelectRegion(filename, areaname='', box=None, **kwargs): + """ + ################################################################################# + Description: + Reads the given areacell from the given 'filename' and selects the given 'box' + + Uses cdms2 (uvcdat) to read areacell from 'filename' and cdutil (uvcdat) to select the 'box' + ################################################################################# + + :param filename: string + string of the path to the file and name of the file to read + :param areaname: string, optional + name of areacell (areacella, areacello,...) in 'filename' + :param box: string, optional + name of a region to select, must be defined in EnsoCollectionsLib.ReferenceRegions + + :return area: masked_array + masked_array containing areacell in 'box' + """ + # Temp corrections for cdms2 to find the right axis + CDMS2setAutoBounds('on') + # Open file and get time dimension + fi = CDMS2open(filename) + if box is None: # no box given + # read file + try: + areacell = fi(areaname) + except: + try: + areacell = fi('areacell') + except: + try: + areacell = fi('areacella') + except: + try: + areacell = fi('areacello') + except: + areacell = None + else: # box given by the user + # define box + region_ref = ReferenceRegions(box) + # read file + try: + areacell = fi(areaname, latitude=region_ref['latitude'], longitude=region_ref['longitude']) + except: + try: + areacell = fi('areacell', latitude=region_ref['latitude'], longitude=region_ref['longitude']) + except: + try: + areacell = fi('areacella', latitude=region_ref['latitude'], longitude=region_ref['longitude']) + except: + try: + areacell = fi('areacello', latitude=region_ref['latitude'], longitude=region_ref['longitude']) + except: + areacell = None + fi.close() + return areacell + + +def ReadLandmaskSelectRegion(tab, filename, landmaskname='', box=None, **kwargs): + """ + ################################################################################# + Description: + Reads the given landmask from the given 'filename' and selects the given 'box' + + Uses cdms2 (uvcdat) to read areacell from 'filename' and cdutil (uvcdat) to select the 'box' + ################################################################################# + + :param filename: string + string of the path to the file and name of the file to read + :param landmaskname: string, optional + name of landmask (sftlf, lsmask, landmask,...) in 'filename' + :param box: string, optional + name of a region to select, must be defined in EnsoCollectionsLib.ReferenceRegions + + :return area: masked_array + masked_array containing landmask in 'box' + """ + # Temp corrections for cdms2 to find the right axis + CDMS2setAutoBounds('on') + # Get landmask + if OSpath__isfile(filename): + # Open file and get time dimension + fi = CDMS2open(filename) + if box is None: # no box given + # read file + try: + landmask = fi(landmaskname) + except: + try: + landmask = fi('landmask') + except: + try: + landmask = fi('lsmask') + except: + try: + landmask = fi('sftlf') + except: + landmask = None + else: # box given by the user + # define box + region_ref = ReferenceRegions(box) + # read file + try: + landmask = fi(landmaskname, latitude=region_ref['latitude'], longitude=region_ref['longitude']) + except: + try: + landmask = fi('landmask', latitude=region_ref['latitude'], longitude=region_ref['longitude']) + except: + try: + landmask = fi('lsmask', latitude=region_ref['latitude'], longitude=region_ref['longitude']) + except: + try: + landmask = fi('sftlf', latitude=region_ref['latitude'], longitude=region_ref['longitude']) + except: + landmask = None + fi.close() + else: + landmask = None + if OSpath__isfile(filename) is False or landmask is None or tab.getGrid().shape != landmask.getGrid().shape: + # Estimate landmask + landmask = EstimateLandmask(tab) + if box is not None: + # define box + region_ref = ReferenceRegions(box) + # subset + landmask = landmask(latitude=region_ref['latitude'], longitude=region_ref['longitude']) + # Return + return landmask + + +def EstimateLandmask(d): + """ + ################################################################################# + Description: + Estimate landmask (when landmask was not given) + Uses cdutil (uvcdat) to create estimated landmask for model resolution + ################################################################################# + + :param d: array (CDMS) + model variable + + :return landmask: masked_array + masked_array containing landmask + """ + print('\033[93m' + str().ljust(25) + 'NOTE: Estimated landmask applied' + '\033[0m') + n = 1 + sft = cdutil.generateLandSeaMask(d(*(slice(0, 1),) * n)) * 100.0 + sft[:] = sft.filled(100.0) + lmsk = sft + lmsk.setAxis(0, d.getAxis(1)) + lmsk.setAxis(1, d.getAxis(2)) + lmsk.id = 'sftlf' + return lmsk + + +def Regrid(tab_to_regrid, newgrid, missing=None, order=None, mask=None, regridder='cdms', regridTool='esmf', + regridMethod='linear', **kwargs): + """ + ################################################################################# + Description: + Regrids 'tab_to_regrid' to 'togrid' + ################################################################################# + + for more information: + import cdms2 + help(cdms2.avariable) + + :param tab_to_regrid: masked_array + masked_array to regrid (must include a CDMS grid!) + :param newgrid: CDMS grid + destination grid + :param missing: float, optional + missing values (missing data value, if any) + :param order: string, optional + axis order (form "tzyx", "tyx", etc.) + :param mask: array of booleans, optional + mask of the new grid (either 2-D or the same shape as togrid) + :param regridder: string, optional + regridders (either 'cdms', 'cdmsHorizontal') + default value is 'cdms' + :param regridTool: string, optional + only if regrider is set to 'cdms' + regridding tools (either 'regrid2', 'esmf', 'libcf') + default value is 'esmf' + :param regridMethod: string, optional + regridding methods depend on regridder and regridTool + 'cdms' + regridTool='regrid2' -> 'linear' + regridTool='esmf' -> 'conserve', 'linear', 'patch' + regridTool='libcf' -> 'linear' + 'cdmsHorizontal' -> None + default value is 'linear' + + usual kwargs: + :param newgrid_name: string, optional + if 'newgrid' is not defined (is string) this will be used to create a grid: + this string must contain two keywords: the grid type and the resolution (same resolution in lon and lat) + grid type -> 'equalarea', 'gaussian', 'generic', 'uniform' + resolution -> '0.25x0.25deg', '0.5x0.5deg', '1x1deg', '2x2deg' + e.g., newgrid_name='gaussian 1x1deg' + default value is 'generic 1x1deg' + :param region: string, optional + if 'newgrid' is not defined (is string) this will be used to create a grid + name of a region, domain where the grid will be defined, must be defined in EnsoCollectionsLib.ReferenceRegions + + :return new_tab: masked_array + tab_to_regrid regridded on newgrid + """ + known_args = {"newgrid_name", "region"} + extra_args = set(kwargs) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + # test given arguments + known_regridder = ["cdms", "cdmsHorizontal"] + if regridder not in known_regridder: + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": regridder", + str().ljust(5) + "unknown regridder: " + str(regridder), + str().ljust(10) + "known regridder: " + str(known_regridder)] + EnsoErrorsWarnings.my_error(list_strings) + elif regridder == "cdms": + if regridTool in ["regrid2", "libcf"]: + list_method = [None, "linear"] + elif regridTool == "esmf": + list_method = [None, "conserve", "linear", "patch"] + if (regridTool is not None) and (regridTool not in ["regrid2", "esmf", "libcf"]): + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": regridTool", + str().ljust(5) + "unknown regridTool: " + str(regridTool), + str().ljust(10) + "known regridTool: " + str(["regrid2", "esmf", "libcf"])] + EnsoErrorsWarnings.my_error(list_strings) + elif regridMethod not in list_method: + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": regridMethod", + str().ljust(5) + "unknown regridMethod (" + str(regridMethod) + ") for this regridTool (" + + str(regridTool) + ")", + str().ljust(10) + "known regridMethod: " + str(list_method)] + EnsoErrorsWarnings.my_error(list_strings) + # test the given 'newgrid' + if isinstance(newgrid, str) or newgrid is None: + # + # newgrid is not a grid, so a grid will be created + # to do this, kwargs['newgrid_name'] and kwargs['region'] must be defined + # + # define the grid type + for gtype in ["equalarea", "gaussian", "generic", "uniform"]: + if gtype in kwargs['newgrid_name']: + GridType = gtype + break + try: + GridType + except: + GridType = "generic" + # define resolution (same resolution in lon and lat) + for res in ["0.25x0.25deg", "0.5x0.5deg", "0.75x0.75deg", "1x1deg", "1.25x1.25deg", "1.5x1.5deg", + "1.75x1.75deg", "2x2deg", "2.25x2.25deg", "2.5x2.5deg", "2.75x2.75deg"]: + if res in kwargs['newgrid_name']: + if res == "0.25x0.25deg": + GridRes = 0.25 + elif res == "0.5x0.5deg": + GridRes = 0.5 + elif res == "0.75x0.75deg": + GridRes = 0.75 + elif res == "1x1deg": + GridRes = 1. + elif res == "1.25x1.25deg": + GridRes = 1.25 + elif res == "1.5x1.5deg": + GridRes = 1.5 + elif res == "1.75x1.75deg": + GridRes = 1.75 + elif res == "2x2deg": + GridRes = 2. + elif res == "2.25x2.25deg": + GridRes = 2.25 + elif res == "2.5x2.5deg": + GridRes = 2.5 + else: + GridRes = 2.75 + break + try: + GridRes + except: + GridRes = 1. + # define bounds of 'region' + region_ref = ReferenceRegions(kwargs["region"]) + lat1, lat2 = region_ref["latitude"][0], region_ref["latitude"][1] + lon1, lon2 = region_ref["longitude"][0], region_ref["longitude"][1] + # create uniform axis + nlat = lat2 - lat1 + lat = CDMS2createUniformLatitudeAxis(lat1 + (GridRes / 2.), nlat, GridRes) + nlon = lon2 - lon1 + lon = CDMS2createUniformLongitudeAxis(lon1 + (GridRes / 2.), nlon, GridRes) + # create grid + newgrid = CDMS2createRectGrid(lat, lon, "yx", type=GridType, mask=None) + newgrid.id = kwargs["newgrid_name"] + # + # regrid + # + if regridder == "cdms": + axis = tab_to_regrid.getAxis(0) + idname = copy.copy(axis.id) + if len(tab_to_regrid.shape) == 3 and (axis.id == "months" or axis.id == "years"): + axis.id = "time" + tab_to_regrid.setAxis(0, axis) + new_tab = tab_to_regrid.regrid(newgrid, missing=missing, order=order, mask=mask, regridTool=regridTool, + regridMethod=regridMethod) + axis = tab_to_regrid.getAxis(0) + axis.id = idname + tab_to_regrid.setAxis(0, axis) + if tab_to_regrid.getGrid().shape == newgrid.shape: + new_tab = MV2masked_where(tab_to_regrid.mask, new_tab) + else: + regridFCT = REGRID2horizontal__Horizontal(tab_to_regrid.getGrid(), newgrid) + new_tab = regridFCT(tab_to_regrid) + return new_tab + + +def SaveNetcdf(netcdf_name, var1=None, var1_attributes={}, var1_name='', var1_time_name=None, var2=None, + var2_attributes={}, var2_name='', var2_time_name=None, var3=None, var3_attributes={}, var3_name='', + var3_time_name=None, var4=None, var4_attributes={}, var4_name='', var4_time_name=None, var5=None, + var5_attributes={}, var5_name='', var5_time_name=None, var6=None, var6_attributes={}, var6_name='', + var6_time_name=None, var7=None, var7_attributes={}, var7_name='', var7_time_name=None, var8=None, + var8_attributes={}, var8_name='', var8_time_name=None, var9=None, var9_attributes={}, var9_name='', + var9_time_name=None, var10=None, var10_attributes={}, var10_name='', var10_time_name=None, var11=None, + var11_attributes={}, var11_name='', var11_time_name=None, var12=None, var12_attributes={}, var12_name='', + var12_time_name=None, frequency="monthly", global_attributes={}, **kwargs): + if OSpath_isdir(ntpath.dirname(netcdf_name)) is not True: + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": given path does not exist", + str().ljust(5) + "netcdf_name = " + str(netcdf_name)] + EnsoErrorsWarnings.my_error(list_strings) + if OSpath__isfile(netcdf_name) is True: + o = CDMS2open(netcdf_name, "a") + else: + o = CDMS2open(netcdf_name, "w+") + if var1 is not None: + if var1_name == '': + var1_name = var1.id + if var1_time_name is not None: + var1 = TimeButNotTime(var1, var1_time_name, frequency) + o.write(var1, attributes=var1_attributes, dtype="float32", id=var1_name) + if var2 is not None: + if var2_name == '': + var2_name = var2.id + if var2_time_name is not None: + var2 = TimeButNotTime(var2, var2_time_name, frequency) + o.write(var2, attributes=var2_attributes, dtype="float32", id=var2_name) + if var3 is not None: + if var3_name == '': + var3_name = var3.id + if var3_time_name is not None: + var3 = TimeButNotTime(var3, var3_time_name, frequency) + o.write(var3, attributes=var3_attributes, dtype="float32", id=var3_name) + if var4 is not None: + if var4_name == '': + var4_name = var4.id + if var4_time_name is not None: + var4 = TimeButNotTime(var4, var4_time_name, frequency) + o.write(var4, attributes=var4_attributes, dtype="float32", id=var4_name) + if var5 is not None: + if var5_name == '': + var5_name = var5.id + if var5_time_name is not None: + var5 = TimeButNotTime(var5, var5_time_name, frequency) + o.write(var5, attributes=var5_attributes, dtype="float32", id=var5_name) + if var6 is not None: + if var6_name == '': + var6_name = var6.id + if var6_time_name is not None: + var6 = TimeButNotTime(var6, var6_time_name, frequency) + o.write(var6, attributes=var6_attributes, dtype="float32", id=var6_name) + if var7 is not None: + if var7_name == '': + var7_name = var7.id + if var7_time_name is not None: + var7 = TimeButNotTime(var7, var7_time_name, frequency) + o.write(var7, attributes=var7_attributes, dtype="float32", id=var7_name) + if var8 is not None: + if var8_name == '': + var8_name = var8.id + if var8_time_name is not None: + var8 = TimeButNotTime(var8, var8_time_name, frequency) + o.write(var8, attributes=var8_attributes, dtype="float32", id=var8_name) + if var9 is not None: + if var9_name == '': + var9_name = var9.id + if var9_time_name is not None: + var9 = TimeButNotTime(var9, var9_time_name, frequency) + o.write(var9, attributes=var9_attributes, dtype="float32", id=var9_name) + if var10 is not None: + if var10_name == '': + var10_name = var10.id + if var10_time_name is not None: + var10 = TimeButNotTime(var10, var10_time_name, frequency) + o.write(var10, attributes=var10_attributes, dtype="float32", id=var10_name) + if var11 is not None: + if var11_name == '': + var11_name = var11.id + if var11_time_name is not None: + var11 = TimeButNotTime(var11, var11_time_name, frequency) + o.write(var11, attributes=var11_attributes, dtype="float32", id=var11_name) + if var12 is not None: + if var12_name == '': + var12_name = var12.id + if var12_time_name is not None: + var12 = TimeButNotTime(var12, var12_time_name, frequency) + o.write(var12, attributes=var12_attributes, dtype="float32", id=var12_name) + my_keys = sorted([key for key in list(kwargs.keys()) if "var" in key and str(key.replace("var", "")).isdigit() is True], + key=lambda v: v.upper()) + for key in my_keys: + if kwargs[key] is not None: + if key + "_name" not in list(kwargs.keys()) or (key + "_name" in list(kwargs.keys()) and kwargs[key + "_name"] == ''): + kwargs[key + "_name"] = kwargs[key].id + if key + "_time_name" in list(kwargs.keys()) and kwargs[key + "_time_name"] is not None: + kwargs[key] = TimeButNotTime(kwargs[key], kwargs[key + "_time_name"], frequency) + if key + "_attributes" not in list(kwargs.keys()): + kwargs[key + "_attributes"] = {} + o.write(kwargs[key], attributes=kwargs[key + "_attributes"], dtype="float32", id=kwargs[key + "_name"]) + for att in list(global_attributes.keys()): + o.__setattr__(att, global_attributes[att]) + o.close() + return + + +def SkewnessTemporal(tab): + """ + ################################################################################# + Description: + Computes the skewness along the time axis + ################################################################################# + + :param tab: masked_array + :return: tab: array + array of the temporal skewness + """ + tab = tab.reorder('t...') + if len(tab.shape) > 4: + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": too many dimensions", + str().ljust(5) + "tab.shape = " + str(tab.shape)] + EnsoErrorsWarnings.my_error(list_strings) + if len(tab.shape) == 1: + skew = float(SCIPYstats__skew(tab)) + else: + if len(tab.shape) == 2: + skew = SCIPYstats__skew(tab, axis=0) + else: + # swith to numpy + dataset = NPma__core__MaskedArray(tab) + # masked values -> nan + dataset = dataset.filled(fill_value=NPnan) + # Store information about the shape/size of the input data + time_ax = dataset.shape[0] + spac_ax = dataset.shape[1:] + channels = NPproduct(spac_ax) + # Reshape to two dimensions (time, space) creating the design matrix + dataset = dataset.reshape([time_ax, channels]) + # Find the indices of values that are not missing in one row. All the rows will have missing values in the + # same places provided the array was centered. If it wasn't then it is possible that some missing values + # will be missed and the singular value decomposition will produce not a number for everything. + nonMissingIndex = NPwhere(NPisnan(dataset[0]) == False)[0] + # Remove missing values from the design matrix. + dataNoMissing = dataset[:, nonMissingIndex] + new_dataset = SCIPYstats__skew(dataNoMissing, axis=0) + flatE = NPones([channels], dtype=dataset.dtype) * NPnan + flatE = flatE.astype(dataset.dtype) + flatE[nonMissingIndex] = new_dataset + skew = flatE.reshape(spac_ax) + skew = MV2masked_where(NPisnan(skew), skew) + skew = CDMS2createVariable(MV2array(skew), axes=tab.getAxisList()[1:], grid=tab.getGrid(), mask=tab[0].mask, + attributes=tab.attributes, id='skewness') + return skew + + +def SmoothGaussian(tab, axis=0, window=5): + """ + ################################################################################# + Description: + Smooth 'tab' along 'axis' using gaussian moving window average + ################################################################################# + + :param tab: masked_array + masked_array to smooth + :param axis: integer, optional + axis along which to smooth the data + default value is the first axis (0) + :param window: odd integer, optional + number of points used for the triangle moving window average + default value is 5 + :return smoothed_tab: masked_array + smoothed data + """ + if window % 2 == 0: + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": smoothing window (running mean)", + str().ljust(5) + "the window of smoothing must be an odd number: " + str(window)] + EnsoErrorsWarnings.my_error(list_strings) + if axis > len(tab.shape) - 1: + list_strings = ["ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": axis", + str().ljust(5) + "axis number too big: " + str(axis)] + EnsoErrorsWarnings.my_error(list_strings) + # Reorder tab in order to put 'axis' in first position + indices = list(range(len(tab.shape))) + indices.remove(axis) + newOrder = str(axis) + for ii in indices: + newOrder = newOrder + str(ii) + new_tab = tab.reorder(newOrder) + + # degree + degree = int(round((window - 1) / 2.)) + + # Create the gaussian weight array + weightGauss = list() + for ii in range(window): + ii = ii - degree + 1 + frac = ii / float(window) + gauss = float(1 / (NPexp((4 * (frac)) ** 2))) + ww = MV2zeros(new_tab.shape[1:]) + ww.fill(gauss) + weightGauss.append(ww) + weightGauss = MV2array(weightGauss) + + # Smoothing + smoothed_tab = MV2zeros(new_tab.shape) + smoothed_tab = smoothed_tab[:len(new_tab) - window + 1] + sum_weight = MV2sum(weightGauss, axis=0) + for ii in range(len(smoothed_tab)): + smoothed_tab[ii] = MV2sum(MV2array(new_tab[ii:ii + window]) * weightGauss * weightGauss, axis=0) / sum_weight + + # Axes list + axes0 = new_tab[degree: len(new_tab) - degree].getAxisList()[0] + if len(tab.shape) > 1: + axes = [axes0] + new_tab.getAxisList()[1:] + else: + axes = [axes0] + smoothed_tab.setAxisList(axes) + + # Reorder to the input order + for ii in range(axis): + smoothed_tab = smoothed_tab.reorder(newOrder) + return smoothed_tab + + +def SmoothSquare(tab, axis=0, window=5): + """ + ################################################################################# + Description: + Smooth 'tab' along 'axis' using square moving window average + ################################################################################# + + :param tab: masked_array + masked_array to smooth + :param axis: integer, optional + axis along which to smooth the data + default value is the first axis (0) + :param window: odd integer, optional + number of points used for the square moving window average + default value is 5 + :return smoothed_tab: masked_array + smoothed data + """ + if window % 2 == 0: + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": smoothing window (running mean)", + str().ljust(5) + "the window of smoothing must be an odd number: " + str(window)] + EnsoErrorsWarnings.my_error(list_strings) + if axis > len(tab.shape)-1: + list_strings = ["ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": axis", + str().ljust(5) + "axis number too big: " + str(axis)] + EnsoErrorsWarnings.my_error(list_strings) + + # Reorder tab in order to put 'axis' in first position + indices = list(range(len(tab.shape))) + indices.remove(axis) + newOrder = str(axis) + for ii in indices: + newOrder = newOrder+str(ii) + new_tab = tab.reorder(newOrder) + + # degree + degree = int(round((window - 1) / 2.)) + + # Smoothing + smoothed_tab = MV2zeros(new_tab.shape) + smoothed_tab = smoothed_tab[:len(new_tab) - window + 1] + for ii in range(len(smoothed_tab)): + smoothed_tab[ii] = sum(new_tab[ii:ii + window]) / float(window) + + # Axes list + axes0 = new_tab[degree: len(new_tab) - degree].getAxisList()[0] + if len(tab.shape) > 1: + axes = [axes0] + new_tab.getAxisList()[1:] + else: + axes = [axes0] + smoothed_tab.setAxisList(axes) + + # Reorder to the input order + for ii in range(axis): + smoothed_tab = smoothed_tab.reorder(newOrder) + return smoothed_tab + + +def SmoothTriangle(tab, axis=0, window=5): + """ + ################################################################################# + Description: + Smooth 'tab' along 'axis' using triangle moving window average + ################################################################################# + + :param tab: masked_array + masked_array to smooth + :param axis: integer, optional + axis along which to smooth the data + default value is the first axis (0) + :param window: odd integer, optional + number of points used for the triangle moving window average + default value is 5 + :return smoothed_tab: masked_array + smoothed data + """ + if window % 2 == 0: + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": smoothing window (running mean)", + str().ljust(5) + "the window of smoothing must be an odd number: " + str(window)] + EnsoErrorsWarnings.my_error(list_strings) + if axis > len(tab.shape)-1: + list_strings = ["ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": axis", + str().ljust(5) + "axis number too big: " + str(axis)] + EnsoErrorsWarnings.my_error(list_strings) + + # Reorder tab in order to put 'axis' in first position + indices = list(range(len(tab.shape))) + indices.remove(axis) + newOrder = str(axis) + for ii in indices: + newOrder = newOrder+str(ii) + new_tab = tab.reorder(newOrder) + + # degree + degree = int(round((window - 1) / 2.)) + + # Create the weight array (triangle) + weight = list() + for ii in range(0, (2 * degree)+1): + ww = MV2zeros(new_tab.shape[1:]) + ww.fill(float(1 + degree - abs(degree - ii))) + weight.append(ww) + weight = MV2array(weight) + + # Smoothing + smoothed_tab = MV2zeros(new_tab.shape) + smoothed_tab = smoothed_tab[:len(new_tab) - window + 1] + sum_weight = MV2sum(weight, axis=0) + for ii in range(len(smoothed_tab)): + smoothed_tab[ii] = MV2sum(MV2array(new_tab[ii:ii + window]) * weight, axis=0) / sum_weight + + # Axes list + axes0 = new_tab[degree: len(new_tab) - degree].getAxisList()[0] + if len(tab.shape) > 1: + axes = [axes0] + new_tab.getAxisList()[1:] + else: + axes = [axes0] + smoothed_tab.setAxisList(axes) + if tab.getGrid(): + try: + smoothed_tab.setGrid(tab.getGrid()) + except: + pass + + # Reorder to the input order + for ii in range(axis): + smoothed_tab = smoothed_tab.reorder(newOrder) + return smoothed_tab + + +# Dictionary of seasons +sea_dict = dict(JAN=cdutil.JAN, FEB=cdutil.FEB, MAR=cdutil.MAR, APR=cdutil.APR, MAY=cdutil.MAY, JUN=cdutil.JUN, + JUL=cdutil.JUL, AUG=cdutil.AUG, SEP=cdutil.SEP, OCT=cdutil.OCT, NOV=cdutil.NOV, DEC=cdutil.DEC, + JF=cdutil.times.Seasons('JF'), FM=cdutil.times.Seasons('FM'), MA=cdutil.times.Seasons('MA'), + AM=cdutil.times.Seasons('AM'), MJ=cdutil.times.Seasons('MJ'), JJ=cdutil.times.Seasons('JJ'), + JA=cdutil.times.Seasons('JA'), AS=cdutil.times.Seasons('AS'), SO=cdutil.times.Seasons('SO'), + ON=cdutil.times.Seasons('ON'), ND=cdutil.times.Seasons('ND'), DJ=cdutil.times.Seasons('DJ'), + JFM=cdutil.times.Seasons('JFM'), FMA=cdutil.times.Seasons('FMA'), MAM=cdutil.MAM, + AMJ=cdutil.times.Seasons('AMJ'), MJJ=cdutil.times.Seasons('MJJ'), JJA=cdutil.JJA, + JAS=cdutil.times.Seasons('JAS'), ASO=cdutil.times.Seasons('ASO'), SON=cdutil.SON, + OND=cdutil.times.Seasons('OND'), NDJ=cdutil.times.Seasons('NDJ'), DJF=cdutil.DJF, + JFMA=cdutil.times.Seasons('JFMA'),FMAM=cdutil.times.Seasons('FMAM'),MAMJ=cdutil.times.Seasons('MAMJ'), + AMJJ=cdutil.times.Seasons('AMJJ'),MJJA=cdutil.times.Seasons('MJJA'),JJAS=cdutil.times.Seasons('JJAS'), + JASO=cdutil.times.Seasons('JASO'),ASON=cdutil.times.Seasons('ASON'),SOND=cdutil.times.Seasons('SOND'), + ONDJ=cdutil.times.Seasons('ONDJ'),NDJF=cdutil.times.Seasons('NDJF'),DJFM=cdutil.times.Seasons('DJFM')) + + +def SeasonalMean(tab, season, compute_anom=False): + """ + ################################################################################# + Description: + Creates a time series of the seasonal mean ('season') and computes the anomalies (difference from the mean value; if + applicable) + Improved cdutil seasonal mean (more seasons and incomplete seasons are removed) + + Uses cdutil (uvcdat) to select the 'season', to average it, and to compute the anomalies (if applicable) + ################################################################################# + + :param tab: masked_array + masked_array (uvcdat cdms2) containing a variable, with many attributes attached (short_name, units,...) + :param season: string + name of a season, must be defined in 'sea_dict' + :param compute_anom: boolean, optional + default value = True, computes anomalies (difference from the mean value) + True if you want to compute anomalies, if you don't want to compute anomalies pass anything but true + :return tab: masked_array + time series of the seasonal mean ('season') anomalies (if applicable) + """ + # Temp corrections for cdms2 to find the right axis + CDMS2setAutoBounds('on') + # Checks if the season has been defined + try: + sea_dict[season] + except: + list_strings = ["ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": season", + str().ljust(5) + "unknown season: " + str(season)] + EnsoErrorsWarnings.my_error(list_strings) + else: + if season in ['DJ', 'NDJ', 'DJF', 'ONDJ', 'NDJF', 'NDJF']: + # these 'seasons' are between two years + # if I don't custom 'tab' cdutil will compute half season mean + # (i.e., for NDJ the first element would be for J only and the last for ND only) + time_ax_comp = tab.getTime().asComponentTime() + ntime = len(time_ax_comp) + ii, jj = 0, 0 + if season == 'DJ': + for ii in range(ntime): + if time_ax_comp[ii].month == 12: break + for jj in range(ntime): + if time_ax_comp[ntime - 1 - jj].month == 1: break + elif season == 'NDJ': + for ii in range(ntime): + if time_ax_comp[ii].month == 11: break + for jj in range(ntime): + if time_ax_comp[ntime - 1 - jj].month == 1: break + elif season == 'DJF': + for ii in range(ntime): + if time_ax_comp[ii].month == 12: break + for jj in range(ntime): + if time_ax_comp[ntime - 1 - jj].month == 2: break + elif season == 'ONDJ': + for ii in range(ntime): + if time_ax_comp[ii].month == 10: break + for jj in range(ntime): + if time_ax_comp[ntime - 1 - jj].month == 1: break + elif season == 'NDJF': + for ii in range(ntime): + if time_ax_comp[ii].month == 11: break + for jj in range(ntime): + if time_ax_comp[ntime - 1 - jj].month == 2: break + elif season == 'DJFM': + for ii in range(ntime): + if time_ax_comp[ii].month == 12: break + for jj in range(ntime): + if time_ax_comp[ntime - 1 - jj].month == 3: break + tab = tab[ii:ntime - jj] + if compute_anom: + tab = sea_dict[season].departures(tab) # extracts 'season' seasonal anomalies (from climatology) + else: + tab = sea_dict[season](tab) # computes the 'season' climatology of a tab + if season == 'DJF': + time_ax = tab.getTime() + time_ax[:] = time_ax[:] - (time_ax[1] - time_ax[0]) + tab.setAxis(0, time_ax) + return tab + + +# Dictionary of smoothing methods +dict_smooth = {'gaussian': SmoothGaussian, 'square': SmoothSquare, 'triangle': SmoothTriangle} + + +def Smoothing(tab, info, axis=0, window=5, method='triangle'): + """ + ################################################################################# + Description: + Smooth 'tab' along 'axis' using moving window average based on 'method' + ################################################################################# + + :param tab: masked_array + masked_array to smooth + :param info: string + information about what was done on tab + :param axis: integer, optional + axis along which to smooth the data + default value is the first axis (0) + :param window: odd integer, optional + number of points used for the moving window average + default value is 5 + :param method: string, optional + smoothing method: + 'gaussian': gaussian shaped window + 'square': square shaped window + 'triangle': triangle shaped window + :return: smoothed_tab: masked_array + smoothed data + """ + try: dict_smooth[method] + except: + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": smoothing method (running mean)", + str().ljust(5) + "unkwown smoothing method: " + str(method), + str().ljust(10) + "known smoothing method: " + str(sorted(list(dict_smooth.keys()), key=lambda v: v.upper()))] + EnsoErrorsWarnings.my_error(list_strings) + info = info + ', smoothing using a ' + str(method) + ' shaped window of ' + str(window) + ' points' + return dict_smooth[method](tab, axis=axis, window=window), info + + +def SkewMonthly(tab): + """ + ################################################################################# + Description: + Computes the monthly standard deviation (value of each calendar month) of tab + ################################################################################# + + :param tab: masked_array + :return: tab: array + array of the monthly standard deviation + """ + initorder = tab.getOrder() + tab = tab.reorder('t...') + axes = tab.getAxisList() + time_ax = tab.getTime().asComponentTime() + months = MV2array(list(tt.month for tt in time_ax)) + cyc = [] + for ii in range(12): + tmp = tab.compress(months == (ii + 1), axis=0) + tmp = SCIPYstats__skew(tmp) + cyc.append(tmp) + del tmp + time = CDMS2createAxis(list(range(12)), id='time') + skew = CDMS2createVariable(MV2array(cyc), axes=[time] + axes[1:], grid=tab.getGrid(), attributes=tab.attributes) + skew = skew.reorder(initorder) + time = CDMS2createAxis(list(range(12)), id='months') + skew.setAxis(get_num_axis(skew, 'time'), time) + return skew + + +def StdMonthly(tab): + """ + ################################################################################# + Description: + Computes the monthly standard deviation (value of each calendar month) of tab + ################################################################################# + + :param tab: masked_array + :return: tab: array + array of the monthly standard deviation + """ + initorder = tab.getOrder() + tab = tab.reorder('t...') + axes = tab.getAxisList() + time_ax = tab.getTime().asComponentTime() + months = MV2array(list(tt.month for tt in time_ax)) + cyc = [] + for ii in range(12): + tmp = tab.compress(months == (ii + 1), axis=0) + tmp = Std(tmp, axis=0) + cyc.append(tmp) + del tmp + time = CDMS2createAxis(list(range(12)), id='time') + std = CDMS2createVariable(MV2array(cyc), axes=[time] + axes[1:], grid=tab.getGrid(), attributes=tab.attributes) + std = std.reorder(initorder) + time = CDMS2createAxis(list(range(12)), id='months') + std.setAxis(get_num_axis(std, 'time'), time) + return std + + +def TimeButNotTime(tab, new_time_name, frequency): + tab_out = copy.copy(tab) + time_num = get_num_axis(tab_out, 'time') + timeax = tab_out.getAxis(time_num).asComponentTime() + year1, month1, day1 = timeax[0].year, timeax[0].month, timeax[0].day + if frequency == 'daily': + freq = 'days' + elif frequency == 'monthly': + freq = 'months' + elif frequency == 'yearly': + freq = 'years' + else: + EnsoErrorsWarnings.unknown_frequency(frequency, INSPECTstack()) + axis = CDMS2createAxis(list(range(len(tab_out))), id=new_time_name) + axis.units = freq + " since " + str(year1) + "-" + str(month1) + "-" + str(day1) + axis.axis = freq + tab_out.setAxis(time_num, axis) + return tab_out +# ---------------------------------------------------------------------------------------------------------------------# + + +# ---------------------------------------------------------------------------------------------------------------------# +# +# Set of often used combinations of previous functions +# +def ComputePDF(tab, nbr_bins=10, interval=None, axis_name='axis'): + """ + ################################################################################# + Description: + Computes the PDF of tab based on numpy.histogram using nbr_bins in interval + Returns the density (sum of pdf=1.) in each bin + + Uses uvcdat for array and axis + ################################################################################# + + :param tab: masked_array + array for which you would like to compute the PDF + :param nbr_bins: int, optional + defines the number of equal-width bins in the given range + default value = 10 + :param interval: [float,float], optional + The lower and upper range of the bins. Values outside the range are ignored. The first element of the range must + be less than or equal to the second. + default value = [tab.min(), tab.max()] + :param axis_name: string, optional + name given to the axis of the pdf, e.g. 'longitude' + default value = 'axis' + + :return: pdf: masked_array + density (sum of pdf=1.) in each bin, with the center of each bin as axis + """ + tmp = NPhistogram(tab, bins=nbr_bins, range=interval) + axis = [(tmp[1][ii] + tmp[1][ii + 1]) / 2. for ii in range(len(tmp[1]) - 1)] + pdf = MV2array(tmp[0]) / float(len(tab)) + axis = CDMS2createAxis(MV2array(axis, dtype='f'), id=axis_name) + pdf.setAxis(0, axis) + return pdf + + +def CustomLinearRegression(y, x, sign_x=0, return_stderr=True, return_intercept=True): + """ + ################################################################################# + Description: + Custom version of genutil.linearregression + This function offers the possibility to compute the linear regression for all points, for values of x>=0, for values + of x<=0 + + Uses uvcdat + ################################################################################# + + :param y: masked_array + masked_array (uvcdat cdms2) containing a variable, with many attributes attached (short_name, units,...) + :param x: masked_array + masked_array (uvcdat cdms2) containing 'var_name', with many attributes attached (short_name, units,...) + :param sign_x: int, optional + default value = 0, computes the linear regression of y over x. You can pass -1 or 1 to compute the linear + regression of y over x for x >=0 or x<=0 respectively + :param return_stderr: boolean, optional + default value = True, returns the the unadjusted standard error + True if you want the unadjusted standard error, if you don't want it pass anything but true + :param return_intercept: boolean, optional + default value = True, returns the the interception value of the linear regression + True if you want the interception value, if you don't want it pass anything but true + :return slope, stderr: floats + slope of the linear regression of y over x + unadjusted standard error of the linear regression of y over x (if return_stderr=True) + """ + if sign_x != 0: + try: + len(y[0]) + except: + slope, intercept, stderr = CustomLinearRegression1d(y, x, sign_x=sign_x) + else: + if x.shape != y.shape: + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": array shape", + str().ljust(5) + "different array shape for x " + str(x.shape) + " and y " + str(y.shape)] + EnsoErrorsWarnings.my_error(list_strings) + slope, intercept, stderr = MV2zeros(y[0].shape), MV2zeros(y[0].shape), MV2zeros(y[0].shape) + for ii in range(len(y[0])): + try: + len(y[0, ii]) + except: + slope[ii], intercept[ii], stderr[ii] = CustomLinearRegression1d(y[:, ii], x[:, ii], sign_x=sign_x) + else: + for jj in range(len(y[0, ii])): + try: + len(y[0, ii, jj]) + except: + slope[ii, jj], intercept[ii, jj], stderr[ii, jj] = \ + CustomLinearRegression1d(y[:, ii, jj], x[:, ii, jj], sign_x=sign_x) + else: + for kk in range(len(y[0, ii, jj])): + try: + len(y[0, ii, jj, kk]) + except: + slope[ii, jj, kk], intercept[ii, jj, kk], stderr[ii, jj, kk] = \ + CustomLinearRegression1d(y[:, ii, jj, kk], x[:, ii, jj, kk], sign_x=sign_x) + else: + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + + ": array shape", + str().ljust(5) + str(x.shape) + " too many dimensions (not programmed)", + str().ljust(5) + "Please ckeck and modify the program if needed"] + EnsoErrorsWarnings.my_error(list_strings) + + else: + results = GENUTILlinearregression(y, x=x, error=1, nointercept=None) + slope, intercept, stderr = results[0][0], results[0][1], results[1][0] + try: + slope[0] + except: + slope, intercept, stderr = float(slope), float(intercept), float(stderr) + try: + len(slope) + except: + pass + else: + axes = y[0].getAxisList() + grid = y[0].getGrid() + mask = y[0].mask + slope = CDMS2createVariable(MV2array(slope), mask=mask, grid=grid, axes=axes, id='slope') + stderr = CDMS2createVariable(MV2array(stderr), mask=mask, grid=grid, axes=axes, id='standart_error') + intercept = CDMS2createVariable(MV2array(intercept), mask=mask, grid=grid, axes=axes, id='intercept') + if return_stderr is False and return_intercept is False: + tab = copy.copy(slope) + else: + tab = [slope] + if return_stderr is True: + tab.append(stderr) + if return_intercept is True: + tab.append(intercept) + return tab + + +def CustomLinearRegression1d(y, x, sign_x=1): + x = NParray(x) + y = NParray(y) + if sign_x == 1: + idx = NPnonzero(x > 0.) + elif sign_x == -1: + idx = NPnonzero(x < 0.) + if len(idx[0]) == 0: + slope, intercept, stderr = 0, 0, 0 + else: + results = GENUTILlinearregression(y[idx], x=x[idx], error=1, nointercept=None) + slope, intercept, stderr = float(results[0][0]), float(results[0][1]), float(results[1][0]) + return slope, intercept, stderr + + +def fill_dict_teleconnection(tab1, tab2, dataset1, dataset2, timebounds1, timebounds2, nyear1, nyear2, nbr, var_name, + add_name, units, centered_rmse=0, biased_rmse=1, dict_metric={}, dict_nc={}, ev_name=None, + events1=None, events2=None): + # Metric 1 + rmse_dive, keyerror = RmsAxis(tab1, tab2, axis="xy", centered=centered_rmse, biased=biased_rmse) + rmse_error_dive = None + # Metric 2 + corr_dive = float(Correlation(tab1, tab2, axis="xy", centered=1, biased=1)) + corr_error_dive = None + # Metric 3 + std_mod_dive = Std(tab1, weights=None, axis="xy", centered=1, biased=1) + std_obs_dive = Std(tab2, weights=None, axis="xy", centered=1, biased=1) + std_dive = float(std_mod_dive) / float(std_obs_dive) + std_error_dive = None + list_met_name = ["RMSE_" + dataset2, "RMSE_error_" + dataset2, "CORR_" + dataset2, "CORR_error_" + dataset2, + "STD_" + dataset2, "STD_error_" + dataset2] + list_metric_value = [rmse_dive, rmse_error_dive, corr_dive, corr_error_dive, std_dive, std_error_dive] + for tmp1, tmp2 in zip(list_met_name, list_metric_value): + dict_metric[tmp1 + "_" + add_name] = tmp2 + dict_nc["var" + str(nbr)] = tab1 + dict_dive = {"units": units, "number_of_years_used": nyear1, "time_period": str(timebounds1), + "spatialSTD_" + dataset1: std_mod_dive} + if isinstance(events1, list) is True: + dict_dive[ev_name + "_years"] = str(events1) + dict_nc["var" + str(nbr) + "_attributes"] = dict_dive + dict_nc["var" + str(nbr) + "_name"] = var_name + dataset1 + dict_dive = {"units": units, "number_of_years_used": nyear2, "time_period": str(timebounds2), + "spatialSTD_" + dataset2: std_obs_dive} + if isinstance(events2, list) is True: + dict_dive[ev_name + "_years"] = str(events2) + dict_nc["var" + str(nbr + 1)] = tab2 + dict_nc["var" + str(nbr + 1) + "_attributes"] = dict_dive + dict_nc["var" + str(nbr + 1) + "_name"] = var_name + dataset2 + return dict_metric, dict_nc + + +def FindXYMinMaxInTs(tab, return_val='both', smooth=False, axis=0, window=5, method='triangle'): + """ + ################################################################################# + Description: + Finds in tab in each time step the position (t,x,y,z) of the minimum (return_val='mini') or the maximum + (return_val='maxi') or both values (if return_val is neither 'mini' nor 'maxi') + Returned position(s) are not the position in tab but in the (t,x,y,z) space defined by tab axes + + Uses uvcdat for smoothing + ################################################################################# + + :param tab: masked_array + array for which you would like to know the position (t,x,y,z) of the minimum and/or the maximum values + :param return_val: string, optional + 'mini' to return the position of the minimum value + 'maxi' to return the position of the maximum value + to return both minimum and maximum values, pass anything else + default value = 'both', returns both minimum and maximum values + :param smooth: boolean, optional + True if you want to smooth tab, if you do not, pass anything but true + default value = False, tab is not smoothed + + See function EnsoUvcdatToolsLib.Smoothing + :param axis: integer, optional + axis along which to smooth the data + default value is the first axis (0) + :param window: odd integer, optional + number of points used for the moving window average + default value is 5 + :param method: string, optional + smoothing method: + 'gaussian': gaussian shaped window + 'square': square shaped window + 'triangle': triangle shaped window + + :return: minimum/maximum position or both minimum and maximum positions, int, float or list + position(s) in the (t,x,y,z) space defined by tab axes of the minimum and/or maximum values of tab + """ + tab_ts = list() + for tt in range(len(tab)): + if smooth is True: + tmp, unneeded = Smoothing(tab[tt], '', axis=axis, window=window, method=method) + else: + tmp = copy.copy(tab[tt]) + tab_ts.append(find_xy_min_max(tmp, return_val=return_val)) + tab_ts = MV2array(tab_ts) + tab_ts.setAxis(0, tab.getAxis(0)) + return tab_ts + + +def MyDerive(project, internal_variable_name, dict_var): + # get dictionary of observations + dict_obs = ReferenceObservations() + # test input parameters + keyerror1, keyerror2, keyerror3, keyerror4 = None, None, None, None + if not isinstance(project, str): + keyerror1 = "project is not well defined (" + str(project) + ")" + EnsoErrorsWarnings.object_type_error('project', 'string', type(project), INSPECTstack()) + if not isinstance(internal_variable_name, str): + keyerror2 = "internal_variable_name is not well defined (" + str(internal_variable_name) + ")" + EnsoErrorsWarnings.object_type_error('internal_variable_name', 'string', type(internal_variable_name), + INSPECTstack()) + if not isinstance(dict_var, dict): + keyerror3 = "dictionary of variable is not well defined (" + str(internal_variable_name) + ")" + EnsoErrorsWarnings.object_type_error('project', 'dictionary', type(dict_var), INSPECTstack()) + # wrong project? + if 'CMIP' not in project and project not in list(dict_obs.keys()) and keyerror1 is None: + keyerror4 = "project is not well defined (" + str(project) + ")" + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": project", + str().ljust(5) + "unknown 'project' (or observations dataset): " + str(project), + str().ljust(10) + "it must be either a 'CMIP' project or an observations dataset defined in " + + "EnsoCollectionsLib.ReferenceObservations", + str().ljust(10) + "known observations dataset: " + str(sorted(list(dict_obs.keys()), key=lambda v: v.upper()))] + EnsoErrorsWarnings.my_warning(list_strings) + + if (keyerror1 is not None or keyerror2 is not None or keyerror3 is not None or keyerror4 is not None): + outvar = None + keyerror = add_up_errors([keyerror1, keyerror2, keyerror3, keyerror4]) + else: + # compute 'internal_variable_name' in 'CMIP' case + if 'CMIP' in project: + # get dictionary of CMIP + dict_CMIP = CmipVariables()['variable_name_in_file'] + # test if 'internal_variable_name' is defined in EnsoCollectionsLib.CmipVariables + if internal_variable_name in list(dict_CMIP.keys()): + list_var = dict_CMIP[internal_variable_name]['var_name'] + outvar, keyerror = MyDeriveCompute( + list_var, dict_var, dict_att=dict_CMIP, variable=internal_variable_name, project=project) + else: + outvar = None + keyerror = "variable (" + str(internal_variable_name) + ") not defined in CmipVariables" + # compute 'internal_variable_name' in 'obs' case + else: + # 'project' is defined in EnsoCollectionsLib.ReferenceObservations + dict_obs_var = dict_obs[project]['variable_name_in_file'] + # test if 'internal_variable_name' is defined for this observations dataset + if internal_variable_name in list(dict_obs_var.keys()): + list_var = dict_obs_var[internal_variable_name]['var_name'] + outvar, keyerror = MyDeriveCompute( + list_var, dict_var, dict_att=dict_obs_var, variable=internal_variable_name, isObs=True) + else: + outvar = None + keyerror = "variable (" + str(internal_variable_name) + ") not defined in ReferenceObservations[" +\ + str(project) + "]" + return outvar, keyerror + + +def MyDeriveCompute(list_var, dict_var, dict_att={}, variable='', isObs=False, project=''): + # test if keys in list_var are in 'dict_var' + string_in_dict(list_var, dict_var, INSPECTstack()) + if isinstance(list_var, str): + # this 'internal_variable_name' is based on one variable + keyerror = None + outvar = dict_var[list_var] + else: + # this 'internal_variable_name' is based on several variables + list_operator = dict_att[variable]['algebric_calculation'] + if len(list_operator) != len(list_var): + outvar = None + keyerror = str(len(list_var)) + " variables are needed to compute " + str(variable) + " but " + \ + str(len(list_operator)) + " operator(s) are given" + if isObs is True: + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + + ": variable definition in EnsoCollectionsLib.ReferenceObservations(" + str(project) + ")", + str().ljust(5) + str(len(list_var)) + " variables are needed to compute " + + str(variable) + " but " + str(len(list_operator)) + " operator(s) are given"] + else: + list_strings = ["ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + + ": variable definition in EnsoCollectionsLib.CmipVariables", + str().ljust(5) + str(len(list_var)) + " variables are needed to compute " + + str(variable) + " but " + str(len(list_operator)) + " operator(s) are given"] + EnsoErrorsWarnings.my_warning(list_strings) + else: + keyerror = None + # compute the output variable + if list_operator[0] == 'minus': + outvar = -1 * dict_var[list_var[0]] + else: + outvar = dict_var[list_var[0]] + for ii in range(1, len(list_var)): + outvar = dict_operations[list_operator[ii]](outvar, dict_var[list_var[ii]]) + outvar.setAxisList(dict_var[list_var[0]].getAxisList()) + outvar = MV2masked_where(dict_var[list_var[0]].mask, outvar) + outvar.setGrid(dict_var[list_var[0]].getGrid()) + return outvar, keyerror + + +def LinearRegressionAndNonlinearity(y, x, return_stderr=True, return_intercept=True): + """ + ################################################################################# + Description: + CustomLinearRegression applied for all values of x, for values of x>=0, for values of x<=0 + + Uses uvcdat + ################################################################################# + + :param y: masked_array + masked_array (uvcdat cdms2) containing a variable, with many attributes attached (short_name, units,...) + :param x: masked_array + masked_array (uvcdat cdms2) containing a variable, with many attributes attached (short_name, units,...) + :param return_stderr: boolean, optional + default value = True, returns the the unadjusted standard error + True if you want the unadjusted standard error, if you don't want it pass anything but true + :param return_intercept: boolean, optional + default value = True, returns the the interception value of the linear regression + True if you want the interception value, if you don't want it pass anything but true + :return [slope_all_values, stderr_all_values], [slope_positive_values, stderr_positive_values], + [slope_negative_values, stderr_negative_values]: lists of floats + slope of the linear regression of y over x + unadjusted standard error of the linear regression of y over x (if return_stderr=True) + """ + # all points + all_values = CustomLinearRegression(y, x, 0, return_stderr=return_stderr, return_intercept=return_intercept) + # positive SSTA = El Nino + positive_values = CustomLinearRegression(y, x, 1, return_stderr=return_stderr, return_intercept=return_intercept) + # negative SSTA = La Nina + negative_values = CustomLinearRegression(y, x, -1, return_stderr=return_stderr, return_intercept=return_intercept) + return all_values, positive_values, negative_values + + +def LinearRegressionTsAgainstMap(y, x, return_stderr=True): + """ + ################################################################################# + Description: + Custom version of genutil.linearregression + This function offers the possibility to compute the linear regression of a time series against a map + + Uses uvcdat + ################################################################################# + + :param y: masked_array + masked_array (uvcdat cdms2) containing a variable, with many attributes attached (short_name, units,...) + :param x: masked_array + masked_array (uvcdat cdms2) containing 'var_name', with many attributes attached (short_name, units,...) + :param return_stderr: boolean, optional + default value = True, returns the the unadjusted standard error + True if you want the unadjusted standard error, if you don't want it pass anything but true + :return slope, stderr: floats + slope of the linear regression of y over x + unadjusted standard error of the linear regression of y over x (if return_stderr=True) + """ + tmp = MV2zeros(y.shape) + for ii in range(len(tmp)): + tmp[ii].fill(x[ii]) + tmp = CDMS2createVariable(tmp, mask=y.mask, grid=y.getGrid(), axes=y.getAxisList(), id=x.id) + slope, stderr = GENUTILlinearregression(y, x=tmp, error=1, nointercept=1) + if return_stderr: + return slope, stderr + else: + return slope + + +def LinearRegressionTsAgainstTs(y, x, nbr_years_window, return_stderr=True, frequency=None, debug=False): + """ + ################################################################################# + Description: + Custom version of genutil.linearregression + This function offers the possibility to compute the linear regression of a time series against a lead-lag time + series + + Uses uvcdat + ################################################################################# + + :param y: masked_array + masked_array (uvcdat cdms2) containing a variable, with many attributes attached (short_name, units,...) + :param x: masked_array + masked_array (uvcdat cdms2) containing 'var_name', with many attributes attached (short_name, units,...) + :param nbr_years_window: integer + number of years used to compute the composite (e.g. 6) + :param return_stderr: boolean, optional + default value = True, returns the the unadjusted standard error + True if you want the unadjusted standard error, if you don't want it pass anything but true + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param debug: boolean, optional + default value = False debug mode not activated + If you want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :return slope, stderr: floats + slope of the linear regression of y over x + unadjusted standard error of the linear regression of y over x (if return_stderr=True) + """ + if frequency == 'daily': + nbr_timestep = nbr_years_window * 365 + elif frequency == 'monthly': + nbr_timestep = nbr_years_window * 12 + elif frequency == 'yearly': + nbr_timestep = nbr_years_window + else: + EnsoErrorsWarnings.unknown_frequency(frequency, INSPECTstack()) + tab_yy_mm = Event_selection(y, frequency, nbr_years_window=nbr_years_window) + myshape = [nbr_timestep] + [ss for ss in y.shape[1:]] + tmp_ax = CDMS2createAxis(list(range(nbr_timestep)), id='months') + slope_out = MV2zeros(myshape) + slope_out.setAxisList([tmp_ax] + y.getAxisList()[1:]) + stderr_out = MV2zeros(myshape) + stderr_out.setAxisList([tmp_ax] + y.getAxisList()[1:]) + for ii in range(nbr_timestep): + tmp1 = tab_yy_mm[:, ii] + tmp2 = copy.copy(x) + yy1 = tab_yy_mm.getAxis(0)[0] + yy2 = tmp2.getTime().asComponentTime()[0].year + if yy1 == yy2: + tmp1 = tmp1[:len(tmp2)] + elif yy1 < yy2: + tmp1 = tmp1[yy2 - yy1:len(tmp2)] + else: + tmp2 = tmp2[yy2 - yy1:] + tmp1 = tmp1[:len(x)] + if len(tmp2) > len(tmp1): + tmp2 = tmp2[:len(tmp1)] + # if debug is True: + # yy1 = tmp1.getAxis(0)[0] + # yy2 = tmp2.getTime().asComponentTime()[0].year + # EnsoErrorsWarnings.DebugMode('\033[93m', "EnsoUvcdatToolsLib LinearRegressionTsAgainstTs", 20) + # dict_debug = {'axes1': str([ax.id for ax in tmp1.getAxisList()]), 'shape1': str(tmp1.shape), + # 'line1': "first year is " + str(yy1), + # 'axes2': str([ax.id for ax in tmp2.getAxisList()]), 'shape2': str(tmp2.shape), + # 'line2': "first year is " + str(yy2)} + # EnsoErrorsWarnings.DebugMode('\033[93m', str(x.id) + " regressed against " + str(y.id), 25, **dict_debug) + if tmp2.shape == tmp1.shape: + tmp3 = copy.copy(tmp2) + else: + tmp3 = MV2zeros(tmp1.shape) + for jj in range(len(tmp3)): + tmp3[jj].fill(tmp2[jj]) + tmp3 = CDMS2createVariable(tmp3, mask=tmp1.mask, grid=tmp1.getGrid(), axes=tmp1.getAxisList(), id=x.id) + slope, stderr = GENUTILlinearregression(tmp1, x=tmp3, error=1, nointercept=1) + slope_out[ii] = slope + stderr_out[ii] = stderr + del slope, stderr, tmp1, tmp2, tmp3, yy1, yy2 + if return_stderr: + return slope_out, stderr_out + else: + return slope_out + + +def PreProcessTS(tab, info, areacell=None, average=False, compute_anom=False, compute_sea_cycle=False, debug=False, + region=None, **kwargs): + keyerror = None + # removes annual cycle (anomalies with respect to the annual cycle) + if compute_anom is True: + tab = ComputeInterannualAnomalies(tab) + # Normalization of the anomalies + if kwargs['normalization']: + if kwargs['frequency'] is not None: + tab, keyerror = Normalize(tab, kwargs['frequency']) + info = info + ', normalized' + # Removing linear trend + if keyerror is None: + if isinstance(kwargs['detrending'], dict): + known_args = {'axis', 'method', 'bp'} + extra_args = set(kwargs['detrending']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + tab, info, keyerror = Detrend(tab, info, **kwargs['detrending']) + if keyerror is None: + # Smoothing time series + if isinstance(kwargs['smoothing'], dict): + known_args = {'axis', 'method', 'window'} + extra_args = set(kwargs['smoothing']) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + tab, info = Smoothing(tab, info, **kwargs['smoothing']) + # computes mean annual cycle + if compute_sea_cycle is True: + tab = annualcycle(tab) + # average + if average is not False: + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[93m', "EnsoUvcdatToolsLib PreProcessTS", 20) + dict_debug = {'axes1': str([ax.id for ax in tab.getAxisList()]), 'shape1': str(tab.shape)} + EnsoErrorsWarnings.debug_mode('\033[93m', "averaging to perform: " + str(average), 25, **dict_debug) + if isinstance(average, str): + try: dict_average[average] + except: + EnsoErrorsWarnings.unknown_averaging(average, list(dict_average.keys()), INSPECTstack()) + else: + tab, keyerror = dict_average[average](tab, areacell, region=region, **kwargs) + if keyerror is None: + if debug is True: + dict_debug = {'axes1': str([ax.id for ax in tab.getAxisList()]), 'shape1': str(tab.shape)} + EnsoErrorsWarnings.debug_mode('\033[93m', "performed " + str(average), 25, **dict_debug) + elif isinstance(average, list): + for av in average: + try: dict_average[av] + except: + EnsoErrorsWarnings.unknown_averaging(average, list(dict_average.keys()), INSPECTstack()) + else: + tab, keyerror = dict_average[av](tab, areacell, region=region, **kwargs) + if keyerror is None: + if debug is True: + dict_debug = {'axes1': str([ax.id for ax in tab.getAxisList()]), + 'shape1': str(tab.shape)} + EnsoErrorsWarnings.debug_mode('\033[93m', "performed " + str(av), 25, **dict_debug) + else: + break + else: + EnsoErrorsWarnings.unknown_averaging(average, list(dict_average.keys()), INSPECTstack()) + else: + tab = None + return tab, info, keyerror + + +def ReadSelectRegionCheckUnits(filename, varname, varfamily, box=None, time_bounds=None, frequency=None, **keyarg): + """ + ################################################################################# + Description: + Combines ReadAndSelectRegion and CheckUnits + Reads the given 'varname' from the given 'filename', selects the given 'box' and checks the 'varname''s units + depending on 'vartype' + + Uses uvcdat + ################################################################################# + + :param filename: string + string of the path to the file and name of the file to read + :param varname: string + name of the variable to read from 'filename' + :param varfamily: string + family of variable encompassing 'varname' (temperature, velocity,...) + :param box: string + name of a region to select, must be defined in EnsoCollectionsLib.ReferenceRegions + :param time_bounds: tuple, optional + tuple of the first and last dates to extract from the files (strings) + e.g., time_bounds=('1979-01-01T00:00:00', '2017-01-01T00:00:00') + default value is None + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + + :return tab: masked_array + masked_array containing 'varname' in 'box' + """ + tab = ReadAndSelectRegion(filename, varname, box=box, time_bounds=time_bounds, frequency=frequency) + tab, units, keyerror = CheckUnits(tab, varfamily, varname, tab.units, return_tab_only=False) + tab.name = varname + tab.units = units + return tab, keyerror + + +def Read_data_mask_area(file_data, name_data, type_data, metric, region, file_area='', name_area='', file_mask='', + name_mask='', maskland=False, maskocean=False, time_bounds=None, debug=False, **kwargs): + keyerror1, keyerror2, keyerror3 = None, None, None + # Read variable + if debug is True: + dict_debug = {'file1': '(' + type_data + ') ' + str(file_data), 'var1': '(' + type_data + ') ' + str(name_data)} + EnsoErrorsWarnings.debug_mode('\033[93m', 'Files', 20, **dict_debug) + variable, keyerror1 = ReadSelectRegionCheckUnits(file_data, name_data, type_data, box=region, + time_bounds=time_bounds, **kwargs) + if debug is True: + dict_debug = {'axes1': '(' + type_data + ') ' + str([ax.id for ax in variable.getAxisList()]), + 'shape1': '(' + type_data + ') ' + str(variable.shape), + 'time1': '(' + type_data + ') ' + str(TimeBounds(variable))} + EnsoErrorsWarnings.debug_mode('\033[93m', 'after ReadSelectRegionCheckUnits', 20, **dict_debug) + # checks if the time-period fulfills the minimum length criterion + if isinstance(kwargs['min_time_steps'], int): + if len(variable) < kwargs['min_time_steps']: + EnsoErrorsWarnings.too_short_time_period(metric, len(variable), kwargs['min_time_steps'], INSPECTstack()) + keyerror2 = "too short time period (" + str(len(variable)) + ")" + # Read areacell & mask + variable, areacell, keyerror3 = Read_mask_area( + variable, name_data, file_data, type_data, region, file_area=file_area, name_area=name_area, + file_mask=file_mask, name_mask=name_mask, maskland=maskland, maskocean=maskocean, debug=debug, **kwargs) + if keyerror1 is not None or keyerror2 is not None or keyerror3 is not None: + keyerror = add_up_errors([keyerror1, keyerror2, keyerror3]) + else: + keyerror = None + return variable, areacell, keyerror + + +def Read_data_mask_area_multifile(file_data, name_data, type_data, variable, metric, region, file_area='', name_area='', + file_mask='', name_mask='', maskland=False, maskocean=False, debug=False, + interpreter='', **kwargs): + dict_area, dict_keye, dict_var = dict(), dict(), dict() + if isinstance(file_data, str): + tab, areacell, keyerror = \ + Read_data_mask_area(file_data, name_data, type_data, metric, region, file_area=file_area, + name_area=name_area, file_mask=file_mask, name_mask=name_mask, maskland=maskland, + maskocean=maskocean, debug=debug, **kwargs) + dict_area[name_data], dict_keye[name_data], dict_var[name_data] = areacell, keyerror, tab + else: + for ii in range(len(file_data)): + try: + file_data[ii] + except: + ff1 = '' + else: + ff1 = file_data[ii] + try: + name_data[ii] + except: + nn1 = '' + else: + nn1 = name_data[ii] + try: + file_area[ii] + except: + fa1 = '' + else: + fa1 = file_area[ii] + try: + name_area[ii] + except: + an1 = '' + else: + an1 = name_area[ii] + try: + file_mask[ii] + except: + fl1 = '' + else: + fl1 = file_mask[ii] + try: + name_mask[ii] + except: + ln1 = '' + else: + ln1 = name_mask[ii] + tab, areacell, keyerror = \ + Read_data_mask_area(ff1, nn1, type_data, metric, region, file_area=fa1, name_area=an1, file_mask=fl1, + name_mask=ln1, maskland=maskland, maskocean=maskocean, debug=debug, **kwargs) + dict_area[nn1], dict_keye[nn1], dict_var[nn1] = areacell, keyerror, tab + keyerror = add_up_errors([dict_keye[ii] for ii in list(dict_keye.keys())]) + if keyerror is None: + list_var = sorted(dict_var.keys()) + if len(list_var) > 1: + for ii in range(2): + for var in list_var[1:]: + dict_var[list_var[0]], dict_var[var], keyerror =\ + CheckTime(dict_var[list_var[0]], dict_var[var], metric_name=metric, **kwargs) + if keyerror is not None: + break + if keyerror is not None: + tab, areacell = None, None + else: + tab, keyerror = MyDerive(kwargs[interpreter], variable, dict_var) + areacell = dict_area[list(dict_area.keys())[0]] + return tab, areacell, keyerror + + +def Read_mask_area(tab, name_data, file_data, type_data, region, file_area='', name_area='', file_mask='', name_mask='', + maskland=False, maskocean=False, debug=False, **kwargs): + tab_out = copy.copy(tab) + keyerror1, keyerror2 = None, None + # Read areacell + if file_area: + areacell = ReadAreaSelectRegion(file_area, areaname=name_area, box=region, **kwargs) + else: + areacell = ReadAreaSelectRegion(file_data, areaname=name_area, box=region, **kwargs) + if areacell is not None and tab.getGrid().shape != areacell.getGrid().shape: + areacell = None + if debug is True: + if areacell is not None: + dict_debug = {'axes1': '(' + type_data + ') ' + str([ax.id for ax in areacell.getAxisList()]), + 'shape1': '(' + type_data + ') ' + str(areacell.shape)} + EnsoErrorsWarnings.debug_mode('\033[93m', 'after ReadAreaSelectRegion', 20, **dict_debug) + else: + dict_debug = {'line1': 'areacell is None '} + EnsoErrorsWarnings.debug_mode('\033[93m', 'after ReadAreaSelectRegion', 20, **dict_debug) + # Read landmask + if name_data in ["msla", "sla", "sshg", "ssh", "sst", "taux", "tauuo", "tos", "zos"]: + landmask = None + elif file_mask: + landmask = ReadLandmaskSelectRegion(tab, file_mask, landmaskname=name_mask, box=region, **kwargs) + else: + landmask = ReadLandmaskSelectRegion(tab, file_data, landmaskname=name_mask, box=region, **kwargs) + if debug is True: + if landmask is not None: + dict_debug = {'axes1': '(' + type_data + ') ' + str([ax.id for ax in landmask.getAxisList()]), + 'shape1': '(' + type_data + ') ' + str(landmask.shape)} + EnsoErrorsWarnings.debug_mode('\033[93m', 'after ReadLandmaskSelectRegion', 20, **dict_debug) + else: + dict_debug = {'line1': 'landmask is None '} + EnsoErrorsWarnings.debug_mode('\033[93m', 'after ReadLandmaskSelectRegion', 20, **dict_debug) + # Apply landmask + if landmask is not None: + tab_out, keyerror1 = ApplyLandmask(tab_out, landmask, maskland=maskland, maskocean=maskocean) + if keyerror1 is None: + if areacell is None: + areacell = ArrayOnes(landmask, id='areacell') + areacell, keyerror2 = ApplyLandmaskToArea(areacell, landmask, maskland=maskland, maskocean=maskocean) + if keyerror1 is not None or keyerror2 is not None: + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + keyerror = None + return tab_out, areacell, keyerror + + +def SlabOcean(tab1, tab2, month1, month2, events, frequency=None, debug=False): + """ + ################################################################################# + Description: + Compute a simple slab ocean by integrating the total heat fluxes over time + + Based on: + Bayr, T., C. Wengel, M. Latif, D. Dommenget, J. Lübbecke, W. Park (2018) Error compensation of ENSO atmospheric + feedbacks in climate models and its influence on simulated ENSO dynamics. Clim. Dyn., doi:10.1007/s00382-018-4575-7 + + Uses CDAT + ################################################################################# + + :param tab1: masked_array + masked_array (uvcdat cdms2) containing SSTA, with many attributes attached (short_name, units,...) + :param tab2: masked_array + masked_array (uvcdat cdms2) containing THFA, with many attributes attached (short_name, units,...) + :param month1: string + first month of integration (e.g., 'JUN') + :param month2: string + last month of integration (e.g., 'DEC') + :param events: list of integer + list of the years considered as ENSO events to be selected + :param frequency: string, optional + time frequency of the datasets + e.g., frequency='monthly' + default value is None + :param debug: bolean, optional + default value = False debug mode not activated + If want to activate the debug mode set it to True (prints regularly to see the progress of the calculation) + :return dSST, dSSTthf, dSSToce: masked_array + normalized cumulative SST change (from 0 to 1; in C/C) + normalized cumulative heat flux-driven SST change (in C/C) + normalized cumulative SST change by an anomalous ocean circulation (in C/C) + """ + if debug is True: + EnsoErrorsWarnings.debug_mode('\033[93m', "EnsoUvcdatToolsLib SlabOcean", 20) + # months and associated position + list_months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'] + if month1 in list_months and month2 in list_months: + mm1 = list_months.index(month1) + mm2 = list_months.index(month2) + else: + list_strings = ["ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": month"] + if month1 not in list_months: + list_strings.append(str().ljust(5) + "unknown month1 : " + str(month1)) + if month2 not in list_months: + list_strings.append(str().ljust(5) + "unknown month2 : " + str(month2)) + EnsoErrorsWarnings.my_error(list_strings) + # sea water constants + cp = 4000 # J/(kg * K) (specific heat capacity at constant pressure of sea water) + rho = 1024 # kg/m3 (average density of sea water) + H = 50 # m (depth of the slab ocean) + fraction = 60 * 60 * 24 * 30.42 / (cp * rho * H) # W/m2 to C + # selecting events + sstA = Event_selection(tab1, frequency, nbr_years_window=2, list_event_years=events) + thfA = Event_selection(tab2, frequency, nbr_years_window=2, list_event_years=events) + if debug is True: + dict_debug = {'axes1': '(sst) ' + str([ax.id for ax in sstA.getAxisList()]), + 'axes2': '(thf) ' + str([ax.id for ax in thfA.getAxisList()]), + 'shape1': '(sst) ' + str(sstA.shape), 'shape2': '(thf) ' + str(thfA.shape)} + EnsoErrorsWarnings.debug_mode('\033[93m', 'after Event_selection', 25, **dict_debug) + # cumulative anomalies + myshape = [len(events), mm2-mm1+1] + [ss for ss in tab1.shape[1:]] + dSST = MV2zeros(myshape) + dSSTthf = MV2zeros(myshape) + for ii in range(mm1, mm2): + dSST[:, ii - mm1 + 1] = dSST[:, ii - mm1] + sstA[:, ii + 1] - sstA[:, ii] + dSSTthf[:, ii - mm1 + 1] = dSSTthf[:, ii - mm1] + thfA[:, ii + 1] + if debug is True: + dict_debug = {'shape1': '(dSST) ' + str(dSST.shape), 'shape2': '(dSSTthf) ' + str(dSSTthf.shape)} + EnsoErrorsWarnings.debug_mode('\033[93m', 'after cumulative_anomalies', 25, **dict_debug) + # normalized heat flux-driven SST change + dt = MV2zeros(dSSTthf.shape) + dt = dt.reorder('10') + dt[:] = dSST[:, -1] + dt = dt.reorder('10') + dt = MV2masked_where(abs(dt) < 0.1, dt) + dSSTthf[:] = fraction * dSSTthf[:] / dt + # normalized SST change + dSST[:] = dSST[:] / dt + # normalized SST change by an anomalous ocean circulation + dSSToce = dSST - dSSTthf + # averaging across events + dSST = MV2average(dSST, axis=0) + dSSTthf = MV2average(dSSTthf, axis=0) + dSSToce = MV2average(dSSToce, axis=0) + # axes + axes = [CDMS2createAxis(MV2array(list(range(12-len(dSST), 12))), id='months')] + if debug is True: + dict_debug = {'axes1': 'axes ' + str(axes[0]), 'axes2': 'axes[:] ' + str(axes[0][:]), + 'shape1': '(dSST) ' + str(dSST.shape), 'shape2': '(dSSTthf) ' + str(dSSTthf.shape), + 'shape3': '(dSSToce) ' + str(dSSToce.shape)} + EnsoErrorsWarnings.debug_mode('\033[93m', 'after mean dSST', 25, **dict_debug) + if len(tab1.shape) > 1: + axes = axes + tab1.getAxisList()[1:] + dSST.setAxisList(axes) + dSSTthf.setAxisList(axes) + dSSToce.setAxisList(axes) + if debug is True: + dict_debug = {'axes1': '(dSST) ' + str([ax.id for ax in dSST.getAxisList()]), + 'axes2': '(dSSTthf) ' + str([ax.id for ax in dSSTthf.getAxisList()]), + 'axes3': '(dSSToce) ' + str([ax.id for ax in dSSToce.getAxisList()]), + 'shape1': '(dSST) ' + str(dSST.shape), 'shape2': '(dSSTthf) ' + str(dSSTthf.shape), + 'shape3': '(dSSToce) ' + str(dSSToce.shape)} + EnsoErrorsWarnings.debug_mode('\033[93m', 'output', 25, **dict_debug) + return dSST, dSSTthf, dSSToce + + +def TimeAnomaliesLinearRegressionAndNonlinearity(tab2, tab1, return_stderr=True): + """ + ################################################################################# + Description: + LinearRegressionAndNonlinearity applied on two 'raw' masked_arrays (i.e., the annual cycle is not removed and the + spatial average is not computed) + The linear regression of tab2 on tab1 is computed for all values of tab1, for values of tab1>=0, for values of + tab1<=0 + + Uses uvcdat + ################################################################################# + + :param tab2: masked_array + masked_array (uvcdat cdms2) containing a variable, with many attributes attached (short_name, units,...) + :param tab1: masked_array + masked_array (uvcdat cdms2) containing a variable, with many attributes attached (short_name, units,...) + :param return_stderr: boolean, optional + default value = True, returns the the unadjusted standard error + True if you want the unadjusted standard error, if you don't want it pass anything but true + :return: [slope_all_values, stderr_all_values], [slope_positive_values, stderr_positive_values], + [slope_negative_values, stderr_negative_values]: lists of floats + slope of the linear regression of y over x + unadjusted standard error of the linear regression of y over x (if return_stderr=True) + """ + # horizontal average + tab1, keyerror1 = dict_average['horizontal'](tab1) + tab2, keyerror2 = dict_average['horizontal'](tab2) + if keyerror1 is not None or keyerror2 is not None: + lr, lrpos, lrneg = None, None, None + keyerror = add_up_errors([keyerror1, keyerror2]) + else: + keyerror = None + # removes annual cycle (anomalies with respect to the annual cycle) + tab1 = ComputeInterannualAnomalies(tab1) + tab2 = ComputeInterannualAnomalies(tab2) + # computes linear regression of tab2 on tab1 for all values of tab1, for values of tab1>=0, + # for values of tab1<=0 + lr, lrpos, lrneg = LinearRegressionAndNonlinearity(tab2, tab1, return_stderr=return_stderr) + return lr, lrpos, lrneg, keyerror + + +def TimeAnomaliesStd(tab): + """ + ################################################################################# + Description: + Combines cdutil.averager and genutil.std + Averages spatially and computes the standard deviation + + Uses uvcdat + ################################################################################# + + :param tab: masked_array + masked_array (uvcdat cdms2) containing a variable, with many attributes attached (short_name, units,...) + :return std: float + standard deviation (one value) of the masked_array averaged spatially and with the annual cycle removed + """ + # horizontal average + tab, keyerror = dict_average['horizontal'](tab) + if keyerror is not None: + std = None + else: + # computes standard deviation + std = float(GENUTILstd(tab, weights=None, axis=0, centered=1, biased=1)) + return std, keyerror + + +def TsToMap(tab, map_ref): + """ + ################################################################################# + Description: + Put a time series into a nD array according to the reference map + + Uses uvcdat + ################################################################################# + + :param tab: masked_array + masked_array (uvcdat cdms2) containing a variable, with many attributes attached (short_name, units,...) + :param map_ref: masked_array + masked_array (uvcdat cdms2) containing a variable, with many attributes attached (short_name, units,...) + :return map_out: masked_array + tab (1D) values set into map_ref shape (nD) + """ + if len(map_ref.shape) > 6: + list_strings = [ + "ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": too many dimensions", + str().ljust(5) + "map_ref.shape = " + str(map_ref.shape)] + EnsoErrorsWarnings.my_error(list_strings) + map_out = MV2zeros(map_ref.shape) + map_out = CDMS2createVariable(map_out, axes=map_ref.getAxisList(), grid=map_ref.getGrid(), mask=map_ref.mask, + attributes=map_ref.attributes, id=tab.id) + initorder = map_out.getOrder() + map_out = map_out.reorder('...t') + if len(map_ref.shape) == 2: + map_out[:] = tab + elif len(map_ref.shape) == 3: + map_out[:, :] = tab + elif len(map_ref.shape) == 4: + map_out[:, :, :] = tab + elif len(map_ref.shape) == 5: + map_out[:, :, :, :] = tab + else: + map_out[:, :, :, :, :] = tab + map_out = map_out.reorder(initorder) + return map_out + + +def TwoVarRegrid(model, obs, info, region=None, model_orand_obs=0, newgrid=None, **keyarg): + """ + ################################################################################# + Description: + Regrids 'model', 'obs' or both + + Uses uvcdat + ################################################################################# + + :param model: masked_array + model data + :param obs: masked_array + observations data + :param info: string + information about what was done to 'model' and 'obs' + :param region: string + name of a region to select, must be defined in EnsoCollectionsLib.ReferenceRegions + :param model_orand_obs: integer, optional + 0 if you want to regrid model data toward observations data + 1 if you want to regrid observations data toward model data + 2 if you want to regrid model AND observations data toward 'newgrid' + default value = 0 + :param newgrid: CDMS grid + grid toward which model data and observations data are regridded if model_orand_obs=2 + + usual kwargs: + :param newgrid_name: string, optional + generates 'newgrid' depending on the given string using cdms2 + 'newgrid_name' must contain a name of a grid type: + 'equalarea', 'gaussian', 'generic', 'uniform' + and a name of a grid resolution: + '0.25x0.25deg', '0.5x0.5deg', '1x1deg', '2x2deg' + default value = 'generic 1x1deg' + for more information: + import cdms2 + help(cdms2.createUniformLatitudeAxis) + help(cdms2.createUniformLongitudeAxis) + help(cdms2.createRectGrid) + see EnsoUvcdatToolsLib.Regrid for regridding options + + :return: model, obs, info + model and obs on the same grid, and information about what has been done to 'model' and 'obs' + """ + known_args = {'missing', 'order', 'mask', 'newgrid_name', 'regridder', 'regridTool', 'regridMethod'} + extra_args = set(keyarg) - known_args + if extra_args: + EnsoErrorsWarnings.unknown_key_arg(extra_args, INSPECTstack()) + grid_obs = obs.getGrid() + grid_model = model.getGrid() + # select case: + if model_orand_obs == 0: + model = Regrid(model, grid_obs, **keyarg) + info = info + ', model regridded to observations' + elif model_orand_obs == 1: + obs = Regrid(obs, grid_model, **keyarg) + info = info + ', observations regridded to model' + elif model_orand_obs == 2: + model = Regrid(model, newgrid, region=region, **keyarg) + obs = Regrid(obs, newgrid, region=region, **keyarg) + try: grid_name = newgrid.id + except: + try: grid_name = newgrid.name + except: + try: grid_name = keyarg['newgrid_name'] + except: grid_name = 'newgrid' + info = info + ', observations and model regridded to ' + str(grid_name) + else: + info = info + ', observations and model NOT regridded' + if model.shape == obs.shape: + if model.mask.shape != (): + mask = model.mask + if obs.mask.shape != (): + mask = MV2where(obs.mask, obs.mask, mask) + else: + if obs.mask.shape != (): + mask = obs.mask + else: + mask = MV2where(MV2zeros(model.shape)==0, False, True) + model = MV2masked_where(mask, model) + obs = MV2masked_where(mask, obs) + else: + if obs[0].mask.shape != (): + tab = MV2zeros(model.shape) + for tt in range(len(tab)): + tab[tt] = MV2masked_where(obs[0].mask, tab[tt]) + model = MV2masked_where(tab.mask, model) + if model[0].mask != (): + tab = MV2zeros(obs.shape) + for tt in range(len(tab)): + tab[tt] = MV2masked_where(model[0].mask, tab[tt]) + obs = MV2masked_where(tab.mask, obs) + return model, obs, info diff --git a/build/lib/EnsoMetrics/KeyArgLib.py b/build/lib/EnsoMetrics/KeyArgLib.py new file mode 100644 index 0000000..9b10b41 --- /dev/null +++ b/build/lib/EnsoMetrics/KeyArgLib.py @@ -0,0 +1,24 @@ +# -*- coding:UTF-8 -*- +from inspect import stack as INSPECTstack + +# ENSO_metrics package functions: +from .EnsoErrorsWarnings import unknown_key_arg + + +# ---------------------------------------------------------------------------------------------------------------------# +# +# Library to ENSO metrics arguments (arg parser) +# These functions analyses given arguments and sets some arguments to their default value +# +def default_arg_values(arg): + default = { + 'detrending': False, 'frequency': None, 'metric_computation': 'difference', 'min_time_steps': None, + 'normalization': False, 'project_interpreter': 'CMIP', 'regridding': False, 'smoothing': False, + 'treshold_ep_ev': -140, 'time_bounds': None, 'time_bounds_mod': None, 'time_bounds_obs': None, + } + try: + default[arg] + except: + unknown_key_arg(arg, INSPECTstack()) + return default[arg] +# ---------------------------------------------------------------------------------------------------------------------# diff --git a/build/lib/EnsoMetrics/__init__.py b/build/lib/EnsoMetrics/__init__.py new file mode 100644 index 0000000..d3127ab --- /dev/null +++ b/build/lib/EnsoMetrics/__init__.py @@ -0,0 +1,8 @@ +from .EnsoCollectionsLib import * +from .EnsoComputeMetricsLib import * +from .EnsoErrorsWarnings import * +from .EnsoMetricsLib import * +from .EnsoToolsLib import * +from .EnsoUvcdatToolsLib import * +from .EnsoPlotLib import * +from .KeyArgLib import * diff --git a/build/lib/EnsoMetrics/version.py b/build/lib/EnsoMetrics/version.py new file mode 100644 index 0000000..dbff009 --- /dev/null +++ b/build/lib/EnsoMetrics/version.py @@ -0,0 +1,3 @@ +__version__ = '1.0-2020' +__git_tag_describe__ = '1.0-2020' +__git_sha1__ = b'c626e9760e8b40061f741188c5226eb4df943e82' diff --git a/build/lib/EnsoPlots/EnsoMetricPlot.py b/build/lib/EnsoPlots/EnsoMetricPlot.py new file mode 100644 index 0000000..8485da3 --- /dev/null +++ b/build/lib/EnsoPlots/EnsoMetricPlot.py @@ -0,0 +1,144 @@ +# -*- coding:UTF-8 -*- + +from copy import deepcopy +from datetime import datetime +from os.path import join as OSpath__join +# ENSO_metrics functions +from EnsoMetrics.EnsoPlotLib import plot_param +from .EnsoPlotTemplate import cmip_boxplot, my_boxplot, my_curve, my_dotplot, my_dot_to_box, my_hovmoeller, my_map,\ + my_scatterplot + + +dict_plot = {"boxplot": my_boxplot, "curve": my_curve, "dot": my_dotplot, "dot_to_box": my_dot_to_box, + "hovmoeller": my_hovmoeller, "map": my_map, "scatterplot": my_scatterplot} + + +def cmip_plotter(metric_collection, metric, experiment, diagnostic_values, diagnostic_units, metric_values, + metric_units, multi_member_ave, figure_name): + """ + Organizes plots for CMIP ensembles + + Inputs: + ------ + :param metric_collection: string + name of the metric collection (e.g., "ENSO_perf") + :param metric: string + name of the metric (e.g., "EnsoAmpl") + :param diagnostic_values: dictionary + dictionary containing the diagnostic values + e.g., {"ref": {"ERA-Interim": 1, "Tropflux": 1.1}, "cmip5": [0.9, ...], "cmip6": [0.9, ...]} + :param diagnostic_units: string + diagnostic units (e.g., "C") + :param metric_values: dictionary + dictionary containing the metric values + e.g., {"cmip5": {"ERA-Interim": [10, ...], "Tropflux": [20, ...]}, + "cmip6": {"ERA-Interim": [10, ...], "Tropflux": [20, ...]}} + :param metric_units: string + metric units (e.g., "%") + :param multi_member_ave: boolean + True if multi-member mean is used, False otherwise + :param figure_name: string + path and name of the plots (e.g., "path/to/directory/file_name") + + Output: + ------ + :return: + """ + lmet = metric.replace("Corr", "").replace("Rmse", "").replace("Std", "") if "Map" in metric else deepcopy(metric) + dict_param = plot_param(metric_collection, lmet) + my_param = dict_param["diagnostic"] + reference = dict_param["metric_reference"] + diagnostic_units = diagnostic_units.replace("C", "$^\circ$C").replace("long", "$^\circ$long") + metric_units = metric_units.replace("C", "$^\circ$C").replace("long", "$^\circ$long") + if multi_member_ave is True: + info = "ensemble-mean\n" + experiment + " simulations" + else: + info = "only first\n" + experiment + " simulation" + # metric + cmip_boxplot(my_param, metric_values, metric_units, reference, "metric", info, figure_name + "_divedown01") + # diagnostic + cmip_boxplot(my_param, diagnostic_values, diagnostic_units, reference, "diagnostic", info, + figure_name + "_divedown02") + return + + +def main_plotter(metric_collection, metric, model, experiment, filename_nc, diagnostic_values, diagnostic_units, + metric_values, metric_units, member=None, path_png=None, name_png=None, models2=None, shading=False, + plot_ref=False): + dict_param = plot_param(metric_collection, metric) + list_var = dict_param['metric_variables'] + met_type = dict_param['metric_computation'] + reference = dict_param['metric_reference'] + dict_reg = dict_param['metric_regions'] + if isinstance(diagnostic_units, str) is True: + diagnostic_units = diagnostic_units.replace("C", "$^\circ$C").replace("long", "$^\circ$long") + elif isinstance(diagnostic_units, list) is True: + for ii, uni in enumerate(diagnostic_units): + diagnostic_units[ii] = uni.replace("C", "$^\circ$C").replace("long", "$^\circ$long") + if isinstance(metric_units, str) is True: + metric_units = metric_units.replace("C", "$^\circ$C").replace("long", "$^\circ$long") + elif isinstance(metric_units, list) is True: + for ii, uni in enumerate(metric_units): + metric_units[ii] = uni.replace("C", "$^\circ$C").replace("long", "$^\circ$long") + if isinstance(name_png, str) is False: + name_png = metric_collection + "_" + metric + "_" + experiment + "_" + if isinstance(model, str): + name_png = name_png + model + elif isinstance(model, list) is True and shading is True: + name_png = name_png + str(len(model)) + "projects" + else: + name_png = name_png + str(len(model)) + "models" + if member is not None: + if (isinstance(model, str) is True and member not in model) or isinstance(model, list) is True: + name_png = name_png + "_" + member + # diagnostic + dict_diag = dict_param['diagnostic'] + if isinstance(path_png, str): + fig_name = OSpath__join(path_png, name_png + "_diagnostic") + else: + fig_name = deepcopy(name_png) + plt_typ = dict_diag['plot_type'] + if plt_typ == "dot" and shading is True: + plt_typ = "dot_to_box" + t1 = datetime.now() + print(str().ljust(20) + plt_typ + " " + str(t1.hour).zfill(2) + ":" + str(t1.minute).zfill(2)) + dict_plot[plt_typ]( + model, filename_nc, dict_diag, reference, list_var, fig_name + "_divedown01", models2=models2, + member=member, metric_type=met_type, metric_values=metric_values, metric_units=metric_units, + diagnostic_values=diagnostic_values, diagnostic_units=diagnostic_units, regions=dict_reg, shading=shading) + if plot_ref is True: + model_new = model.replace("GPCPv2.3", "GPCPv23").replace("SODA3.4.2", "SODA342") + fig_name_ref = fig_name.replace(model_new, "reference") + dict_plot[plt_typ]( + model, filename_nc, dict_diag, reference, list_var, fig_name_ref + "_divedown01", models2=None, + member=None, metric_type=None, metric_values=metric_values, metric_units=metric_units, + diagnostic_values=diagnostic_values, diagnostic_units=diagnostic_units, regions=dict_reg, shading=False, + plot_ref=plot_ref) + dt = datetime.now() - t1 + dt = str(int(round(dt.seconds / 60.))) + print(str().ljust(30) + "took " + dt + " minute(s)") + # dive downs + list_dd = sorted([key for key in list(dict_param.keys()) if "dive_down" in key], key=lambda v: v.upper()) + for ii, dd in enumerate(list_dd): + dict_diag = dict_param[dd] + plt_typ = dict_diag['plot_type'] + t1 = datetime.now() + print(str().ljust(20) + plt_typ + " " + str(t1.hour).zfill(2) + ":" + str(t1.minute).zfill(2)) + if metric_collection in ["ENSO_tel", "test_tel"] and "Map" in metric: + metype = deepcopy(met_type) + else: + metype = None + dict_plot[plt_typ]( + model, filename_nc, dict_diag, reference, list_var, fig_name + "_divedown" + str(ii+2).zfill(2), + models2=models2, member=member, metric_type=metype, metric_values=metric_values, metric_units=metric_units, + diagnostic_values=diagnostic_values, diagnostic_units=diagnostic_units, regions=dict_reg, shading=shading) + if plot_ref is True: + dict_plot[plt_typ]( + model, filename_nc, dict_diag, reference, list_var, fig_name_ref + "_divedown" + str(ii + 2).zfill(2), + models2=None, member=None, metric_type=None, metric_values=metric_values, metric_units=metric_units, + diagnostic_values=diagnostic_values, diagnostic_units=diagnostic_units, regions=dict_reg, shading=False, + plot_ref=plot_ref) + dt = datetime.now() - t1 + dt = str(int(round(dt.seconds / 60.))) + print(str().ljust(30) + "took " + dt + " minute(s)") + diff --git a/build/lib/EnsoPlots/EnsoPlotTemplate.py b/build/lib/EnsoPlots/EnsoPlotTemplate.py new file mode 100644 index 0000000..8e88d25 --- /dev/null +++ b/build/lib/EnsoPlots/EnsoPlotTemplate.py @@ -0,0 +1,2338 @@ +# -*- coding:UTF-8 -*- +import cmocean +from copy import deepcopy +from math import ceil as MATHceil +from math import floor as MATHfloor +from matplotlib.colors import BoundaryNorm, ListedColormap +from matplotlib.gridspec import GridSpec +from matplotlib.lines import Line2D +from matplotlib.patches import Polygon +from matplotlib.ticker import MaxNLocator +import matplotlib.pyplot as plt +from mpl_toolkits.basemap import Basemap +from numpy import arange as NUMPYarange +from numpy import array as NUMPYarray +from numpy import linspace as NUMPYlinspace +from numpy import meshgrid as NUMPYmeshgrid +from numpy.ma import masked_where as NUMPYmasked_where + +# ENSO_metrics functions +from EnsoMetrics.EnsoCollectionsLib import ReferenceRegions +from .EnsoPlotToolsLib import create_labels, create_levels, format_metric, minimaxi, minmax_plot, my_average,\ + my_bootstrap, my_legend, my_mask, my_mask_map, read_diag, read_var, return_metrics_type, shading_levels + +colors_sup = ["r", "lime", "peru", "gold", "forestgreen", "sienna", "gold"] +dict_col = {"REF": "k", "CMIP": "forestgreen", "CMIP3": "orange", "CMIP5": "dodgerblue", "CMIP6": "r"} + +met_names = { + "BiasPrLatRmse": "double_ITCZ_bias", "BiasPrLonRmse": "eq_PR_bias", + "BiasSshLatRmse": "lat_SSH_bias", "BiasSshLonRmse": "eq_SSH_bias", + "BiasSstLatRmse": "lat_SST_bias", "BiasSstLonRmse": "eq_SST_bias", + "BiasTauxLatRmse": "lat_Taux_bias", "BiasTauxLonRmse": "eq_Taux_bias", + "SeasonalPrLatRmse": "double_ITCZ_sea_cycle", "SeasonalPrLonRmse": "eq_PR_sea_cycle", + "SeasonalSshLatRmse": "lat_SSH_sea_cycle", "SeasonalSshLonRmse": "eq_SSH_sea_cycle", + "SeasonalSstLatRmse": "lat_SST_sea_cycle", "SeasonalSstLonRmse": "eq_SST_sea_cycle", + "SeasonalTauxLatRmse": "lat_Taux_sea_cycle", "SeasonalTauxLonRmse": "eq_Taux_sea_cycle", + "EnsoPrLonRmse": "ENSO_pattern_PR", "EnsoSshLonRmse": "ENSO_pattern_SSH", "EnsoSstLonRmse": "ENSO_pattern", + "EnsoTauxLonRmse": "ENSO_pattern_Taux", "EnsoPrTsRmse": "ENSO_lifecycle_PR", "EnsoSshTsRmse": "ENSO_lifecycle_SSH", + "EnsoSstTsRmse": "ENSO_lifecycle", "EnsoTauxTsRmse": "ENSO_lifecycle_Taux", + "EnsoAmpl": "ENSO_amplitude", "EnsoSeasonality": "ENSO_seasonality", "EnsoSstSkew": "ENSO_asymmetry", + "EnsoDuration": "ENSO_duration", "EnsoSstDiversity": "ENSO_diversity", "EnsoSstDiversity_1": "ENSO_diversity", + "EnsoSstDiversity_2": "ENSO_diversity", "EnsoPrMapCorr": "Dec_PR_teleconnection_CORR", + "EnsoPrMapRmse": "Dec_PR_teleconnection", "EnsoPrMapStd": "Dec_PR_teleconnection_STD", + "EnsoPrMapDjfCorr": "DJF_PR_teleconnection_CORR", "EnsoPrMapDjfRmse": "DJF_PR_teleconnection", + "EnsoPrMapDjfStd": "DJF_PR_teleconnection_STD", "EnsoPrMapJjaCorr": "JJA_PR_teleconnection_CORR", + "EnsoPrMapJjaRmse": "JJA_PR_teleconnection", "EnsoPrMapJjaStd": "JJA_PR_teleconnection_STD", + "EnsoSlpMapRmse": "Dec_SLP_teleconnection", "EnsoSlpMapStd": "Dec_SLP_teleconnection_STD", + "EnsoSlpMapDjfCorr": "DJF_SLP_teleconnection_CORR", "EnsoSlpMapDjfRmse": "DJF_SLP_teleconnection", + "EnsoSlpMapDjfStd": "DJF_SLP_teleconnection_STD", "EnsoSlpMapJjaCorr": "JJA_SLP_teleconnection_CORR", + "EnsoSlpMapJjaRmse": "JJA_SLP_teleconnection", "EnsoSlpMapJjaStd": "JJA_SLP_teleconnection_STD", + "EnsoSstMapRmse": "Dec_TS_teleconnection", "EnsoSstMapStd": "Dec_TS_teleconnection_STD", + "EnsoSstMapDjfCorr": "DJF_TS_teleconnection_CORR", "EnsoSstMapDjfRmse": "DJF_TS_teleconnection", + "EnsoSstMapDjfStd": "DJF_TS_teleconnection_STD", "EnsoSstMapJjaCorr": "JJA_TS_teleconnection_CORR", + "EnsoSstMapJjaRmse": "JJA_TS_teleconnection", "EnsoSstMapJjaStd": "JJA_TS_teleconnection_STD", + "EnsoFbSstTaux": "SST-Taux_feedback", "EnsoFbTauxSsh": "Taux-SSH_feedback", "EnsoFbSshSst": "SSH-SST_feedback", + "EnsoFbSstThf": "SST-NHF_feedback", "EnsodSstOce": "ocean_driven_SST", "EnsodSstOce_1": "ocean_driven_SST", + "EnsodSstOce_2": "ocean_driven_SST"} + +mod_nicknames = ["CMIP5", "CMIP6"] + +article_fig = False # True +plot_for_wiki = False # True + + +def cmip_boxplot(dict_param, dict_values, units, reference, val_type, my_text, figure_name): + # concatenate every mips + keys = sorted([kk for kk in list(dict_values.keys()) if kk != "ref"], key=lambda v: v.upper()) + vall = list() + for kk in keys: + vall += dict_values[kk][reference] if val_type == "metric" else dict_values[kk] + if len(vall) > 0: + # add every mips in the list + legend = ["CMIP"] + [kk.upper() for kk in keys] + colors = [dict_col[kk] for kk in legend] + vall = [vall] + [dict_values[kk][reference] if val_type == "metric" else dict_values[kk] for kk in keys] + # number of 'columns' (boxplot, markers, etc) + nbrc = len(vall) + if val_type == "metric": + nbrc += int(round(len(keys)*(len(keys) - 1) / 2.)) + # plot param + title = "Metric values" if val_type == "metric" else "Scalar diagnostic values" + yname = dict_param["title"][0] if isinstance(dict_param["title"], list) is True else dict_param["title"] + if units != "": + uni = units.replace("1e2", "10$^2$").replace("1e3", "10$^3$") + uni = uni.replace("1e-2", "10$^{-2}$").replace("1e-3", "10$^{-3}$") + uni = uni.replace("m2", "m$^2$") + yname += " (" + uni + ")" + del uni + tmp = vall[0] + if val_type != "metric": + tmp += [dict_values["ref"][reference]] + tick_labels = minmax_plot(tmp) + mini, maxi = min(tick_labels), max(tick_labels) + fig, ax = plt.subplots(1, 1, figsize=(max(4, nbrc), 4), sharex="col", sharey="row") + # title + ax.set_title(title, fontsize=15, y=1.01, loc="left") + # # y axis + ax.set_yticks(tick_labels) + ax.set_ylim(ymin=mini, ymax=maxi) + ax.set_ylabel(yname, fontsize=15) + for tick in ax.yaxis.get_major_ticks(): + tick.label.set_fontsize(12) + # boxplots + for ii, (cc, tab) in enumerate(zip(colors, vall)): + boxproperties = { + "boxprops": dict(linestyle="-", linewidth=2, color=cc), + "capprops": dict(linestyle="-", linewidth=2, color=cc), + "flierprops": dict(marker="o", markersize=5.0, markeredgecolor=cc, markerfacecolor=cc, + markeredgewidth=0), + "meanprops": dict(marker="D", markersize=10.0, markeredgecolor=cc, markerfacecolor=cc, + markeredgewidth=0), + "medianprops": dict(linestyle="-", linewidth=2, color=cc), + "whiskerprops": dict(linestyle="-", linewidth=2, color=cc)} + tmp = [[1e20, 1e20]] * ii + [tab] + [[1e20, 1e20]] * (nbrc-1-ii) + ax.boxplot(tmp, whis=[5, 95], labels=[""] * len(tmp), showmeans=True, showfliers=True, **boxproperties) + del boxproperties, tmp + # bootstrap + x1, x2 = ax.get_xlim() + dx = (x2 - x1) / 100. + y1, y2 = ax.get_ylim() + dy = (y2 - y1) / 100. + if val_type == "metric": + for ii, tab1 in enumerate(vall[1:]): + for jj, tab2 in enumerate(vall[2+ii:]): + cc1, cc2 = colors[ii + 1], colors[ii + jj + 2] + bst1, bst2, mea1, mea2 = my_bootstrap(tab1, tab2) + fillstyle = "none" if (min(bst2) <= mea1 <= max(bst2)) or (min(bst1) <= mea2 <= max(bst1)) else\ + "full" + xxx = len(vall) + ii + 1 + ax.plot([xxx], [mea1], markersize=10, color=cc1, marker="D", fillstyle=fillstyle, + markeredgecolor=cc1, markeredgewidth=3, zorder=2) + ax.plot([xxx], [mea2], markersize=10, color=cc2, marker="D", fillstyle=fillstyle, + markeredgecolor=cc2, markeredgewidth=3, zorder=2) + ax.add_line(Line2D([xxx - 0.3, xxx + 0.3], [min(bst1), min(bst1)], c=cc1, lw=2, zorder=3)) + ax.add_line(Line2D([xxx - 0.3, xxx + 0.3], [max(bst1), max(bst1)], c=cc1, lw=2, zorder=3)) + ax.add_line(Line2D([xxx - 0.05, xxx - 0.05], [min(bst1), max(bst1)], c=cc1, lw=2, zorder=3)) + ax.add_line(Line2D([xxx - 0.3, xxx + 0.3], [min(bst2), min(bst2)], c=cc2, lw=2, zorder=3)) + ax.add_line(Line2D([xxx - 0.3, xxx + 0.3], [max(bst2), max(bst2)], c=cc2, lw=2, zorder=3)) + ax.add_line(Line2D([xxx + 0.05, xxx + 0.05], [min(bst2), max(bst2)], c=cc2, lw=2, zorder=3)) + del bst1, bst2, cc1, cc2, fillstyle, mea1, mea2, xxx + ax.plot([x2 + 5 * dx], [y1 + 50 * dy], markersize=8, color="k", marker="D", fillstyle="full", + markeredgecolor="k", markeredgewidth=2, clip_on=False, zorder=2) + ax.text(x2 + 10 * dx, y1 + 50 * dy, r'significantly $\neq$', fontsize=12, color="k", ha="left", va="center") + ax.plot([x2 + 5 * dx], [y1 + 43 * dy], markersize=8, color="k", marker="D", fillstyle="none", + markeredgecolor="k", markeredgewidth=2, clip_on=False, zorder=2) + ax.text(x2 + 10 * dx, y1 + 43 * dy, r'not $\neq$', fontsize=12, color="k", ha="left", va="center") + ax.text(x2 + 2 * dx, y1 + 34 * dy, "at the 95% confidence level\n(Monte Carlo sampling method)", + fontsize=10, color="k", ha="left", va="center") + # reference + if val_type != "metric": + ax.axhline(dict_values["ref"][reference], c="k", ls="-", lw=4, zorder=1) + # legend + if val_type == "metric": + legend = ["ref: " + reference + " = 0"] + legend + else: + legend = ["ref: " + reference] + legend + lines = [Line2D([0], [0], marker="D", color="w", mfc=cc, markersize=10) for cc in colors] + lines = [Line2D([0], [0], color=dict_col["REF"], ls="-", lw=4)] + lines + ax.legend(lines, legend, bbox_to_anchor=(1, 1), loc="upper left", ncol=1) + ax.text(x2 + 2 * dx, y1, my_text, fontsize=12, color="k", ha="left", va="bottom") + # grid + ax.grid(ls="--", lw=1, which="major", axis="y") + # save + plt.savefig(figure_name, bbox_inches="tight") + plt.close() + del ax, colors, fig, legend, lines, maxi, mini, nbrc, tick_labels, title, yname + else: + # plot param + title = "Metric values" if val_type == "metric" else "Scalar diagnostic values" + fig, ax = plt.subplots(1, 1, figsize=(4, 4), sharex="col", sharey="row") + ax.axes.xaxis.set_visible(False) + ax.axes.yaxis.set_visible(False) + # title + ax.set_title(title, fontsize=15, y=1.01, loc="left") + ax.set_xlim(xmin=0, xmax=1) + ax.set_ylim(ymin=0, ymax=1) + txt = "No metric\nvalues?" if val_type == "metric" else "No scalar diagnostic\nfor this metric" + ax.text(0.5, 0.5, txt, fontsize=20, color="k", ha="center", va="center") + # save + plt.savefig(figure_name, bbox_inches="tight") + plt.close() + del ax, fig, title, txt + return + + +def my_boxplot(model, filename_nc, dict_param, reference, metric_variables, figure_name, models2=None, member=None, + metric_type=None, metric_values=None, metric_units=None, diagnostic_values=None, diagnostic_units=None, + regions=None, shading=False, plot_ref=False): + # get data + variables = dict_param["varpattern"] + method = dict_param["method"] + if isinstance(metric_variables, list) is True and regions is not None: + for ii, vv in enumerate(metric_variables): + method = method.replace("REGION" + str(ii + 1), regions[vv].replace("nino", "N")) + if isinstance(variables, str) is True or isinstance(variables, str) is True: + nbr_val = 1 + else: + nbr_val = len(variables) + tab_mod, tab_obs, metval, obsname = \ + read_var(variables, filename_nc, model, reference, metric_variables, metric_values, models2=models2, + member=member, shading=shading) + if metric_type is not None: + plot_metric = True + else: + plot_metric = False + # figure initialization + nbr_panel = dict_param["nbr_panel"] + title = dict_param["title"] + if isinstance(title, str) is True or isinstance(title, str) is True: + title = [title] * nbr_panel + yname = dict_param["yname"] + if isinstance(yname, str) is True or isinstance(yname, str) is True: + yname = [yname] * nbr_panel + one_yaxis = True + else: + one_yaxis = False + if isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True: + units = tab_mod[0].units.replace("C", "$^\circ$C").replace("long", "$^\circ$lon") + elif isinstance(filename_nc, dict) is True and shading is True: + units = tab_mod[0][0][0].units.replace("C", "$^\circ$C").replace("long", "$^\circ$lon") + else: + units = tab_mod[0][0].units.replace("C", "$^\circ$C").replace("long", "$^\circ$lon") + if "legend" in list(dict_param.keys()): + legend = dict_param["legend"] + else: + legend = my_legend(model, obsname, filename_nc, models2=models2, member=member, plot_metric=plot_metric, + shading=shading) + if plot_for_wiki is True: + if "CNRM-CM5" in legend[0]: + legend[0] = "model" + elif "CNRM-CM5" in legend[1]: + legend[1] = "model" + if plot_ref is True: + legend = [legend[0]] + if "custom_label" in list(dict_param.keys()): + custom_label = dict_param["custom_label"] + else: + custom_label = None + nbrl = int(round(nbr_panel / 2.)) + nbrc = 1 if nbr_panel == 1 else 2 + fig, axes = plt.subplots(nbrl, nbrc, figsize=(4 * nbrc, 4 * nbrl), sharex="col", sharey="row") + legco = ["k", "dodgerblue"] + if isinstance(model, list) is True: + for ii in range(len(model)-1): + legco.append(colors_sup[ii]) + lines = [Line2D([0], [0], marker="o", c="w", markerfacecolor=cc, markersize=12) for cc in legco] + if shading is True: + tmp1 = list() + for ii in range(len(tab_mod)): + tmp2 = list() + for jj in range(nbr_val): + tmp3 = list() + for kk in range(len(tab_mod[ii])): + # tmp3.append(tab_mod[ii][kk][jj]) + tmp3 += list(tab_mod[ii][kk][jj]) + tmp2.append(tmp3) + tmp1.append(tmp2) + tab_mod = deepcopy(tmp1) + if isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True: + if plot_ref is True: + tmp = tab_obs + else: + tmp = tab_mod + tab_obs + else: + tmp = deepcopy(tab_obs) + if plot_ref is False: + for ii in range(len(tab_mod)): + tmp += tab_mod[ii] + if custom_label is None: + tick_labels = minmax_plot(tmp, metric=plot_metric) + mini, maxi = min(tick_labels), max(tick_labels) + else: + mini, maxi = minimaxi(tmp) + tick_labels = list(range(int(MATHfloor(mini)), int(MATHceil(maxi)) + 1)) + tick_labels, label = create_labels(custom_label, tick_labels) + for ii in range(nbr_panel): + if nbr_panel == 1: + ax = axes + elif nbrl == 1 and nbrc != 1: + ax = axes[ii % 2] + else: + ax = axes[int(round(ii / 2)), ii % 2] + # title + ax.set_title(title[ii], fontsize=15, y=1.01, loc="left") + # x axis + for tick in ax.xaxis.get_major_ticks(): + tick.label.set_fontsize(12) + # y axis + ax.set_yticks(tick_labels) + if custom_label is not None: + ax.set_yticklabels(label) + ax.set_ylim(ymin=mini, ymax=maxi) + if (one_yaxis is True and ii % 2 == 0) or one_yaxis is False: + ylabel = yname[ii] + if units != "": + ylabel = ylabel + " (" + units + ")" + ax.set_ylabel(ylabel, fontsize=15) + for tick in ax.yaxis.get_major_ticks(): + tick.label.set_fontsize(12) + # boxplots + boxproperties = { + "boxprops": dict(linestyle="-", linewidth=2, color=legco[0]), + "capprops": dict(linestyle="-", linewidth=2, color=legco[0]), + "flierprops": dict(marker="o", markersize=2.0, markeredgecolor=legco[0], markerfacecolor=legco[0], + markeredgewidth=0), + "meanprops": dict(marker="D", markersize=8.0, markeredgecolor=legco[0], markerfacecolor=legco[0], + markeredgewidth=0), + "medianprops": dict(linestyle="-", linewidth=2, color=legco[0]), + "whiskerprops": dict(linestyle="-", linewidth=2, color=legco[0])} + if isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True: + ax.boxplot([tab_obs[ii], [1e20, 1e20]], whis=[5, 95], labels=["", ""], showmeans=True, showfliers=False, + **boxproperties) + boxproperties = { + "boxprops": dict(linestyle="-", linewidth=2, color=legco[1]), + "capprops": dict(linestyle="-", linewidth=2, color=legco[1]), + "flierprops": dict(marker="o", markersize=2.0, markeredgecolor=legco[1], markerfacecolor=legco[1], + markeredgewidth=0), + "meanprops": dict(marker="D", markersize=8.0, markeredgecolor=legco[1], markerfacecolor=legco[1], + markeredgewidth=0), + "medianprops": dict(linestyle="-", linewidth=2, color=legco[1]), + "whiskerprops": dict(linestyle="-", linewidth=2, color=legco[1]), + } + if plot_ref is False: + ax.boxplot([[1e20, 1e20], tab_mod[ii]], whis=[5, 95], labels=["", ""], showmeans=True, showfliers=False, + **boxproperties) + # my text + if plot_metric is True: + # relative space + x1, x2 = ax.get_xlim() + dx = (x2 - x1) / 100. + y1, y2 = ax.get_ylim() + dy = (y2 - y1) / 100. + txt = format_metric(metric_type, metval, metric_units) + ax.text(x2 - (2 * dx), y2 - (6 * dy), txt, fontsize=12, color="k", horizontalalignment="right", + verticalalignment="center") + # legend + if (nbr_panel == 1 and ii == 0) or (nbr_panel != 1 and ii == 1): + ax.legend(lines, legend, bbox_to_anchor=(1, 1), loc="upper left", ncol=1) + else: + tmp = [tab_obs[ii]] + [1e20, 1e20] * len(tab_mod) + ax.boxplot(tmp, whis=[5, 95], labels=[""] * len(tmp), showmeans=True, showfliers=False, **boxproperties) + if plot_ref is False: + for kk in range(len(tab_mod)): + boxproperties = { + "boxprops": dict(linestyle="-", linewidth=2, color=legco[kk+1]), + "capprops": dict(linestyle="-", linewidth=2, color=legco[kk+1]), + "flierprops": dict(marker="o", markersize=2.0, markeredgecolor=legco[kk+1], + markerfacecolor=legco[kk+1], markeredgewidth=0), + "meanprops": dict(marker="D", markersize=8.0, markeredgecolor=legco[kk+1], + markerfacecolor=legco[kk+1], markeredgewidth=0), + "medianprops": dict(linestyle="-", linewidth=2, color=legco[kk+1]), + "whiskerprops": dict(linestyle="-", linewidth=2, color=legco[kk+1])} + tmp = [[1e20, 1e20]] * (kk + 1) + [tab_mod[kk][ii]] + [[1e20, 1e20]] * (len(tab_mod) - 1 - kk) + ax.boxplot(tmp, whis=[5, 95], labels=[""] * len(tmp), showmeans=True, showfliers=False, + **boxproperties) + # legend + if (nbr_panel == 1 and ii == 0) or (nbr_panel != 1 and ii == 1): + if plot_metric is True: + for jj in range(1, len(legend)): + legend[jj] = legend[jj] + " (" + "{0:.2f}".format(metval[jj-1]) + " " + metric_units + ")" + ax.legend(lines, legend, bbox_to_anchor=(1, 1), loc="upper left", ncol=1) + # grid + ax.grid(linestyle="--", linewidth=1, which="major", axis="y") + if ii == nbr_panel - 1 and plot_ref is True and (ii+1)%2 == 0 and plot_ref is True: + x1, x2 = ax.get_xlim() + dx = (x2 - x1) / 100. + y1, y2 = ax.get_ylim() + dy = (y2 - y1) / 100. + ax.text(x2 + 2 * dx, y2 - 20 * dy, str(method, "utf-8"), fontsize=12, color="k", ha="left", va="top") + if ii + 1 < (nbrc*nbrl): + if nbrl == 1 and nbrc != 1: + ax = axes[-1] + else: + ax = axes[-1, ii-1] + ax.axis("off") + if plot_ref is True: + x1, x2 = ax.get_xlim() + dx = (x2 - x1) / 100. + y1, y2 = ax.get_ylim() + ax.text(x1 - 18 * dx, y2, str(method, "utf-8"), fontsize=12, color="k", ha="left", va="top") + plt.savefig(figure_name, bbox_inches="tight") + plt.close() + return + + +def my_curve(model, filename_nc, dict_param, reference, metric_variables, figure_name, models2=None, member=None, + metric_type=None, metric_values=None, metric_units=None, diagnostic_values=None, diagnostic_units=None, + regions=None, shading=False, plot_ref=False): + # get data + variables = dict_param["varpattern"] + method = dict_param["method"] + if isinstance(metric_variables, list) is True and regions is not None: + for ii, vv in enumerate(metric_variables): + method = method.replace("REGION" + str(ii+1), regions[vv].replace("nino", "N")) + if isinstance(variables, str) is True or isinstance(variables, str) is True: + nbr_val = 1 + else: + nbr_val = len(variables) + tab_mod, tab_obs, metval, obsname =\ + read_var(deepcopy(variables), filename_nc, model, reference, metric_variables, metric_values, models2=models2, + member=member, shading=shading) + if metric_type is not None and (isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True): + plot_metric = True + else: + plot_metric = False + if isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True: + axis = list(NUMPYarray(tab_mod[0].coords[list(tab_mod[0].coords.keys())[0]])) + elif isinstance(filename_nc, dict) is True and shading is True: + axis = list(NUMPYarray(tab_mod[0][0][0].coords[list(tab_mod[0][0][0].coords.keys())[0]])) + else: + axis = list(NUMPYarray(tab_mod[0][0].coords[list(tab_mod[0][0].coords.keys())[0]])) + # figure initialization + title = dict_param["title"] + xname = dict_param["xname"] + yname = dict_param["yname"] + if isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True: + units = tab_mod[0].units.replace("C", "$^\circ$C").replace("long", "$^\circ$lon") + elif isinstance(filename_nc, dict) is True and shading is True: + units = tab_mod[0][0][0].units.replace("C", "$^\circ$C").replace("long", "$^\circ$lon") + else: + units = tab_mod[0][0].units.replace("C", "$^\circ$C").replace("long", "$^\circ$lon") + if units != "": + yname = yname + " (" + units + ")" + if "colors" in list(dict_param.keys()): + linecolors = dict_param["colors"] + else: + linecolors = {"model": ["dodgerblue"], "reference": ["k"]} + if "linestyles" in list(dict_param.keys()): + linestyles = dict_param["linestyles"] + else: + linestyles = {"model": ["-"], "reference": ["-"]} + if "legend" in list(dict_param.keys()): + legend = dict_param["legend"] + else: + legend = my_legend(model, obsname, filename_nc, models2=models2, member=member, plot_metric=plot_metric, + shading=shading) + if plot_for_wiki is True: + if "CNRM-CM5" in legend[0]: + legend[0] = "model" + elif "CNRM-CM5" in legend[1]: + legend[1] = "model" + nbr = len(tab_mod[0]) if isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True else\ + (len(tab_mod[0][0][0]) if isinstance(filename_nc, dict) is True and shading is True else len(tab_mod[0][0])) + if isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True: + tmp = tab_mod + tab_obs + else: + tmp = deepcopy(tab_obs) + for ii in range(len(tab_mod)): + tmp += tab_mod[ii] + ytick_labels = minmax_plot(tmp, metric=plot_metric) + # plot + if isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True: + if xname == "months" and nbr == 72: + fig, ax = plt.subplots(figsize=(8, 4)) + else: + fig, ax = plt.subplots(figsize=(4, 4)) + plot_curve(tab_mod, tab_obs, ax, title, axis, xname, yname, ytick_labels, linecolors, linestyles, metric_type, + metval, metric_units, model=model, member=member, obsname=obsname, legend=legend, multimodel=False, + plot_metric=plot_metric, shading=shading, plot_ref=plot_ref, method=method) + else: + if metric_type is not None: + plot_metric = True + if xname == "months" and nbr == 72: + nbrl = nbr_val + nbrc = 1 + fig, axes = plt.subplots(nbrl, nbrc, figsize=(8, 4 * nbrl), sharex="col", sharey="row") + else: + nbrl = int(round(nbr_val / 2.)) + nbrc = 1 if nbr_val == 1 else 2 + fig, axes = plt.subplots(nbrl, nbrc, figsize=(4 * nbrc, 4 * nbrl), sharex="col", sharey="row") + old_leg = deepcopy(legend) + for kk in range(len(tab_obs)): + if isinstance(filename_nc, dict) is True and shading is True: + tab_tmp = [[tab_mod[jj][ll][kk] for ll in range(len(tab_mod[jj]))] for jj in range(len(tab_mod))] + else: + tab_tmp = [tab_mod[jj][kk] for jj in range(len(tab_mod))] + if nbr_val == 1: + ax = axes + elif (nbrl == 1 and nbrc != 1) or (nbrl != 1 and nbrc == 1): + ax = axes[kk % 2] + else: + ax = axes[int(round(kk / 2)), kk % 2] + if "legend" in list(dict_param.keys()): + title_tmp = title + ": " + old_leg[kk] + else: + title_tmp = title + lcol = {"model": ["dodgerblue"], "reference": ["k"]} + lsty = {"model": ["-"], "reference": ["-"]} + for jj in range(len(filename_nc) - 1): + lcol["model"].append(colors_sup[jj]) + lsty["model"].append("-") + if (nbrc == 2 and kk == 1) or (nbrc == 1 and kk == 0) or nbr_val == 1: + plot_legend = True + else: + plot_legend = False + plot_curve(tab_tmp, [tab_obs[kk]], ax, title_tmp, axis, xname, yname, ytick_labels, lcol, lsty, metric_type, + metval, metric_units, model=model, member=member, obsname=obsname, legend=legend, + multimodel=True, plot_metric=plot_metric, plot_legend=plot_legend, shading=shading, + plot_ref=plot_ref, method=method) + plt.savefig(figure_name, bbox_inches="tight") + plt.close() + return + + +def my_dotplot(model, filename_nc, dict_param, reference, metric_variables, figure_name, models2=None, member=None, + metric_type=None, metric_values=None, metric_units=None, diagnostic_values=None, diagnostic_units=None, + regions=None, shading=False, plot_ref=False): + # get data + method = dict_param["method"] + if isinstance(metric_variables, list) is True and regions is not None: + for ii, vv in enumerate(metric_variables): + method = method.replace("REGION" + str(ii + 1), regions[vv].replace("nino", "N")) + diag_mod, diag_obs, metval, obsname =\ + read_diag(diagnostic_values, metric_values, model, reference, metric_variables, member=member) + if metric_type is not None: + plot_metric = True + else: + plot_metric = False + # figure initialization if isinstance(filename_nc, str) is True or isinstance(filename_nc, unicode) is True + title = dict_param["title"] + yname = dict_param["yname"] + if diagnostic_units != "": + yname = yname + " (" + diagnostic_units + ")" + if len(yname) < 20: + for kk in list(regions.keys()): + if kk in yname.lower(): + yname = regions[kk] + " " + yname + if "colors" in list(dict_param.keys()): + mcolors = dict_param["colors"] + else: + mcolors = ["k", "dodgerblue"] + if isinstance(filename_nc, list): + for ii in range(len(filename_nc) - 1): + mcolors.append(colors_sup[ii]) + if "markers" in list(dict_param.keys()): + markers = dict_param["markers"] + else: + markers = ["D", "o"] + if isinstance(filename_nc, list): + for ii in range(len(filename_nc) - 1): + markers.append("o") + if "legend" in list(dict_param.keys()): + legend = dict_param["legend"] + else: + legend = my_legend(model, obsname, filename_nc, models2=models2, member=member, plot_metric=plot_metric, + shading=shading) + if plot_for_wiki is True: + if "CNRM-CM5" in legend[0]: + legend[0] = "model" + elif "CNRM-CM5" in legend[1]: + legend[1] = "model" + if plot_ref is True: + legend = [legend[0]] + fig, ax = plt.subplots(figsize=(4, 4)) + if plot_ref is True: + tab = [diag_obs] + else: + if isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True: + tab = [diag_obs, diag_mod] + else: + tab = [diag_obs] + diag_mod + # title + ax.set_title(title, fontsize=15, y=1.01, loc="left") + # x axis + label_ticks = [-0.5] + list(range(len(tab))) + [len(tab) - 0.5] + label = [""] * len(label_ticks) + plt.xticks(label_ticks, label) + plt.xlim(min(label_ticks), max(label_ticks)) + for tick in ax.xaxis.get_major_ticks(): + tick.label.set_fontsize(12) + # y axis + tick_labels = minmax_plot(tab, metric=plot_metric) + plt.yticks(tick_labels, tick_labels) + plt.ylim(min(tick_labels), max(tick_labels)) + ax.set_ylabel(yname, fontsize=15) + for tick in ax.yaxis.get_major_ticks(): + tick.label.set_fontsize(12) + if min(tick_labels) < 0 and max(tick_labels) > 0: + ax.axhline(0, color='k', linestyle='-', linewidth=2) + # dots + for ii in range(len(tab)): + ax.scatter([ii], tab[ii], s=80, c=mcolors[ii], marker=markers[ii], clip_on=False) + x1, x2 = ax.get_xlim() + dx = (x2 - x1) / 100. + y1, y2 = ax.get_ylim() + dy = (y2 - y1) / 100. + if isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True: + # my text + if plot_metric is True: + txt = format_metric(metric_type, metval, metric_units) + ax.text(x2 - (2 * dx), y2 - (6 * dy), txt, fontsize=12, color='k', horizontalalignment='right', + verticalalignment='center') + # legend + lines = [Line2D([0], [0], marker=markers[kk], c="w", markerfacecolor=mcolors[kk], markersize=12) + for kk in range(len(mcolors))] + ax.legend(lines, legend, bbox_to_anchor=(1, 1), loc='upper left', ncol=1) + else: + # legend + lines = [Line2D([0], [0], marker=markers[kk], c="w", markerfacecolor=mcolors[kk], markersize=12) + for kk in range(len(mcolors))] + for jj in range(1, len(legend)): + legend[jj] = legend[jj] + " (" + "{0:.2f}".format(metval[jj - 1]) + " " + metric_units + ")" + ax.legend(lines, legend, bbox_to_anchor=(1, 1), loc='upper left', ncol=1) + if plot_ref is True: + ax.text(x2 + 2 * dx, y2 - 20 * dy, str(method, "utf-8"), fontsize=12, color="k", ha="left", va="top") + plt.grid(linestyle='--', linewidth=1, which='major') + plt.savefig(figure_name, bbox_inches='tight') + plt.close() + return + + +def my_dot_to_box(model, filename_nc, dict_param, reference, metric_variables, figure_name, models2=None, member=None, + metric_type=None, metric_values=None, metric_units=None, diagnostic_values=None, + diagnostic_units=None, regions=None, shading=False, plot_ref=False): + # get data + diag_mod, diag_obs, metval, obsname = \ + read_diag(diagnostic_values, metric_values, model, reference, metric_variables, shading=shading, member=member) + if metric_type is not None: + plot_metric = True + else: + plot_metric = False + # figure initialization + nbr_panel = dict_param["nbr_panel"] + title = dict_param["title"] + if isinstance(title, str) is True or isinstance(title, str) is True: + title = [title] * nbr_panel + yname = dict_param["yname"] + if diagnostic_units != "": + yname = yname + " (" + diagnostic_units + ")" + if len(yname) < 20: + for kk in list(regions.keys()): + if kk in yname.lower(): + yname = regions[kk] + " " + yname + if "colors" in list(dict_param.keys()): + mcolors = dict_param["colors"] + else: + mcolors = ["k", "dodgerblue"] + if isinstance(filename_nc, list) is True or isinstance(filename_nc, dict) is True: + for ii in range(len(filename_nc) - 1): + mcolors.append(colors_sup[ii]) + if "legend" in list(dict_param.keys()): + legend = dict_param["legend"] + else: + legend = my_legend(model, obsname, filename_nc, models2=models2, member=member, plot_metric=plot_metric, + shading=shading) + if plot_for_wiki is True: + legend[0] = "model" + fig, ax = plt.subplots(figsize=(4, 4)) + if isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True: + tab = [diag_mod] + else: + tab = diag_mod + lines = [Line2D([0], [0], marker="o", c="w", markerfacecolor=cc, markersize=12) for cc in mcolors] + # x axis + for tick in ax.xaxis.get_major_ticks(): + tick.label.set_fontsize(12) + # y axis + tmp = [diag_obs] + [min(my_mask(tt, remove_masked=True)) for tt in tab] +\ + [max(my_mask(tt, remove_masked=True)) for tt in tab] + tick_labels = minmax_plot(tmp, metric=plot_metric) + ax.set_yticks(tick_labels) + ax.set_ylim(ymin=min(tick_labels), ymax=max(tick_labels)) + ax.set_ylabel(yname, fontsize=15) + for tick in ax.yaxis.get_major_ticks(): + tick.label.set_fontsize(12) + # plot + ax.axhline(diag_obs, color=mcolors[0], linestyle='-', linewidth=2) + for ii in range(len(tab)): + # boxplots + boxproperties = { + "boxprops": dict(linestyle="-", linewidth=2, color=mcolors[ii+1]), + "capprops": dict(linestyle="-", linewidth=2, color=mcolors[ii+1]), + "flierprops": dict(marker="o", markersize=2.0, markeredgecolor=mcolors[ii+1], markerfacecolor=mcolors[ii+1], + markeredgewidth=0), + "meanprops": dict(marker="D", markersize=8.0, markeredgecolor=mcolors[ii+1], markerfacecolor=mcolors[ii+1], + markeredgewidth=0), + "medianprops": dict(linestyle="-", linewidth=2, color=mcolors[ii+1]), + "whiskerprops": dict(linestyle="-", linewidth=2, color=mcolors[ii+1]), + } + tmp = [[1e20, 1e20]] * ii + [my_mask(tab[ii], remove_masked=True)] + [[1e20, 1e20]] * (len(tab) - 1 - ii) + ax.boxplot(tmp, whis=[5, 95], labels=[""] * len(tmp), showmeans=True, showfliers=True, **boxproperties) + # legend + if plot_metric is True: + for jj in range(1, len(legend)): + legend[jj] = legend[jj] + " (" + "{0:.2f}".format(metval[jj-1]) + " " + metric_units + ")" + ax.legend(lines, legend, bbox_to_anchor=(1, 1), loc="upper left", ncol=1) + # grid + ax.grid(linestyle="--", linewidth=1, which="major", axis="y") + plt.savefig(figure_name, bbox_inches="tight") + plt.close() + return + + +def my_hovmoeller(model, filename_nc, dict_param, reference, metric_variables, figure_name, models2=None, member=None, + metric_type=None, metric_values=None, metric_units=None, diagnostic_values=None, + diagnostic_units=None, regions=None, shading=False, plot_ref=False): + # get data + variables = dict_param["varpattern"] + method = dict_param["method"] + if isinstance(metric_variables, list) is True and regions is not None: + for ii, vv in enumerate(metric_variables): + method = method.replace("REGION" + str(ii + 1), regions[vv].replace("nino", "N")) + if isinstance(variables, str) is True or isinstance(variables, str) is True: + nbr_val = 1 + else: + nbr_val = len(variables) + tab_mod, tab_obs, metval, obsname = \ + read_var(variables, filename_nc, model, reference, metric_variables, metric_values, models2=models2, + member=member, + shading=shading) + if isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True: + tim = list(NUMPYarray(tab_mod[0].coords[tab_mod[0].dims[0]])) + lon = list(NUMPYarray(tab_mod[0].coords[tab_mod[0].dims[1]])) + elif isinstance(filename_nc, dict) is True and shading is True: + tim = list(NUMPYarray(tab_mod[0][0][0].coords[tab_mod[0][0][0].dims[0]])) + lon = list(NUMPYarray(tab_mod[0][0][0].coords[tab_mod[0][0][0].dims[1]])) + else: + tim = list(NUMPYarray(tab_mod[0][0].coords[tab_mod[0][0].dims[0]])) + lon = list(NUMPYarray(tab_mod[0][0].coords[tab_mod[0][0].dims[1]])) + nbr_years = len(tim) / 12. + # figure initialization + nbr_panel = dict_param["nbr_panel"] + if isinstance(filename_nc, list) or (isinstance(filename_nc, dict) is True and shading is True): + nbr_panel = nbr_panel + ((len(tab_mod) - 1) * nbr_val) + if plot_ref is True: + nbr_panel = int(round(nbr_panel / 2.)) + title = dict_param["title"] + if isinstance(filename_nc, list): + if plot_ref is True: + title = [obsname] + else: + if isinstance(member, list) is True and len(member) == len(model): + title = [obsname] + [mod + mem for mod, mem in zip(model, member)] + else: + title = [obsname] + model + if nbr_val > 1: + title = title * nbr_val + elif isinstance(filename_nc, dict) is True: + if isinstance(member, list) is True and len(member) == len(model): + title = [obsname] + [mod.upper() + mem + " (" + str(len(models2[mod])) + ")" + for mod, mem in zip(model, member)] + else: + title = [obsname] + [mod.upper() + "(" + str(len(models2[mod])) + ")" for mod in model] + if nbr_val > 1: + title = title * nbr_val + xname = dict_param["xname"] + yname = dict_param["yname"] + zname = dict_param["zname"] + if isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True: + units = tab_mod[0].units.replace("C", "$^\circ$C").replace("long", "$^\circ$lon") + elif isinstance(filename_nc, dict) is True and shading is True: + units = tab_mod[0][0][0].units.replace("C", "$^\circ$C").replace("long", "$^\circ$lon") + else: + units = tab_mod[0][0].units.replace("C", "$^\circ$C").replace("long", "$^\circ$lon") + if units != "": + zname = zname + " (" + units + ")" + colorbar = "cmo." + dict_param["colorbar"] + labelbar = dict_param["label"] + if shading is True and len(model) + 1 == 3: + nbrl = int(round(nbr_panel / 3.)) + nbrc = 1 if nbr_panel == 1 else 3 + else: + nbrl = int(round(nbr_panel / 2.)) + nbrc = 1 if nbr_panel == 1 else 2 + if plot_ref is True: + nbrl = deepcopy(nbr_panel) + nbrc = 1 + fig, axes = plt.subplots(nbrl, nbrc, figsize=(4 * nbrc, 4 * nbr_years * nbrl), sharex="col", sharey="row") + hspa1 = 0.3 / nbr_years + hspa2 = 0.01 / nbr_years + plt.subplots_adjust(hspace=hspa1, wspace=0.1) + xlabel_ticks = list(range(int(MATHfloor(min(lon))), int(MATHceil(max(lon))) + 1)) + xlabel_ticks, xlabel = create_labels(xname, xlabel_ticks) + ylabel_ticks = list(range(int(MATHfloor(min(tim))), int(MATHceil(max(tim))) + 1)) + ylabel_ticks, ylabel = create_labels(yname, ylabel_ticks) + tab = list() + legend = my_legend(model, obsname, filename_nc, models2=models2, member=member, shading=shading) + if plot_for_wiki is True: + legend[1] = "model" + if plot_ref is True: + legend = [legend[0]] + if isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True: + for ii in range(len(tab_obs)): + tab.append(tab_obs[ii]) + if plot_ref is False: + tab.append(tab_mod[ii]) + elif isinstance(filename_nc, dict) is True and shading is True: + for ii in range(len(tab_obs)): + tab.append(tab_obs[ii]) + for kk in range(len(tab_mod)): + tmp = [tab_mod[kk][jj][ii] for jj in range(len(tab_mod[kk]))] + tab.append(my_average(tmp, axis=0)) + else: + for ii in range(len(tab_obs)): + tab.append(tab_obs[ii]) + if plot_ref is False: + for kk in range(len(tab_mod)): + tab.append(tab_mod[kk][ii]) + for ii in range(nbr_panel): + if nbr_panel == 1: + ax = axes + elif (nbrl == 1 and nbrc != 1) or (nbrl != 1 and nbrc == 1): + if nbrl == 1 and nbrc != 1: + ax = axes[ii % nbrc] + else: + ax = axes[ii % nbrl] + else: + ax = axes[ii / nbrc, ii % nbrc] + # title + if isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True: + tt = title[ii] if plot_ref is True else title[int(round(ii / 2))] + ax.set_title(tt, fontsize=15, y=1. + hspa2, loc="left") + if ii in [0, 1] and len(legend) - 1 >= ii % 2: + ax.text(0.5, 1. + hspa1, legend[ii % 2], fontsize=15, weight="bold", horizontalalignment="center", + verticalalignment="center", transform=ax.transAxes) + else: + if nbrc == 2: + if ii % nbrc == 0: + ax.set_title(title[ii], fontsize=15, y=1. + hspa2, loc="left") + else: + ax.set_title(title[ii], fontsize=15, y=1. + hspa2, loc="right") + else: + ax.set_title(title[ii], fontsize=15, y=1. + hspa2, loc="center") + if nbr_val > 1: + if nbrc == 2: + if ii % (len(filename_nc) + 1) == 0: + ttx2 = ax.get_position().x1 + elif (ii - 1) % (len(filename_nc) + 1) == 0: + ttx1 = ax.get_position().x0 + tty2 = ax.get_position().y1 + ax.text(ttx2 + (ttx1 - ttx2) / 2., tty2 + hspa2 / 2., + dict_param["title"][(ii - 1) / (len(filename_nc) + 1)], fontsize=15, weight="bold", + horizontalalignment="center", verticalalignment="center", transform=fig.transFigure) + else: + if ii % nbrc == 1: + ttx2 = ax.get_position().x1 + ttx1 = ax.get_position().x0 + tty2 = ax.get_position().y1 + ax.text(ttx2 + (ttx1 - ttx2) / 2., tty2 + (hspa2 * 4), + dict_param["title"][(ii - 1) / (len(filename_nc) + 1)], fontsize=15, weight="bold", + horizontalalignment="center", verticalalignment="center", transform=fig.transFigure) + # x axis + ax.set_xlim(xmin=min(lon), xmax=max(lon)) + if ii >= nbr_panel - nbrc: + ax.set_xticks(xlabel_ticks) + ax.set_xticklabels(xlabel) + ax.set_xlabel(xname, fontsize=15) + for tick in ax.xaxis.get_major_ticks(): + tick.label.set_fontsize(12) + # y axis + ax.set_ylim(ymin=min(tim), ymax=max(tim)) + if ii % nbrc == 0: + ax.set_yticks(ylabel_ticks) + ax.set_yticklabels(ylabel) + ax.set_ylabel(yname, fontsize=15) + for tick in ax.yaxis.get_major_ticks(): + tick.label.set_fontsize(12) + # hovmoeller + levels = create_levels(labelbar) + xx, yy = NUMPYmeshgrid(lon, tim) + cs = ax.contourf(xx, yy, tab[ii], levels=levels, extend="both", cmap=colorbar) + if ii == 0 and plot_ref is True: + tx1, tx2 = ax.get_xlim() + dx = (tx2 - tx1) / 100. + ty1, ty2 = ax.get_ylim() + ax.text(tx2 + 2 * dx, ty2, str(method, "utf-8"), fontsize=12, color="k", ha="left", va="top") + if ii == nbr_panel - nbrc: + x1 = ax.get_position().x0 + if ii == nbr_panel - 1: + x2 = ax.get_position().x1 + # add colorbar + if nbr_years == 1 and nbrl == 1: + cax = plt.axes([x1, -0.1, x2 - x1, 0.04]) + else: + if nbr_panel in [5, 6]: + if nbr_years == 6: + cax = plt.axes([x1, 0.09, x2 - x1, 0.005]) + else: + cax = plt.axes([x1, 0.03, x2 - x1, 0.02]) + elif nbrl == 2 and nbrc <= 2 and nbr_years == 6: + cax = plt.axes([x1, 0.09, x2 - x1, 0.006]) + elif nbr_panel in [7, 8] and nbr_years == 6: + cax = plt.axes([x1, 0.1, x2 - x1, 0.002]) + elif nbr_panel in [11, 12] and nbr_years == 6: + cax = plt.axes([x1, 0.1, x2 - x1, 0.002]) + elif nbr_panel in [17, 18]: + if nbr_years < 1: + cax = plt.axes([x1, 0.07, x2 - x1, 0.01]) + else: + cax = plt.axes([x1, 0.08, x2 - x1, 0.005]) + else: + cax = plt.axes([x1, 0.05, x2-x1, 0.015]) + cbar = plt.colorbar(cs, cax=cax, orientation="horizontal", ticks=labelbar, pad=0.35, extend="both") + cbar.set_label(zname, fontsize=15) + cbar.ax.tick_params(labelsize=12) + plt.savefig(figure_name, bbox_inches="tight") + plt.close() + return + + +def my_map(model, filename_nc, dict_param, reference, metric_variables, figure_name, models2=None, member=None, + metric_type=None, metric_values=None, metric_units=None, diagnostic_values=None, diagnostic_units=None, + regions=None, shading=False, plot_ref=False): + # get data + variables = dict_param["varpattern"] + method = dict_param["method"] + if isinstance(metric_variables, list) is True and regions is not None: + for ii, vv in enumerate(metric_variables): + method = method.replace("REGION" + str(ii + 1), regions[vv].replace("nino", "N")) + if isinstance(variables, str) is True or isinstance(variables, str) is True: + nbr_val = 1 + else: + nbr_val = len(variables) + if "africaSE" in variables or "america" in variables or "asiaS" in variables or "oceania" in variables: + met_in_file = True + my_reg = "africaSE" if "africaSE" in variables else ( + "americaN" if "americaN" in variables else ( + "americaS" if "americaS" in variables else ("asiaS" if "asiaS" in variables else "oceania"))) + elif isinstance(variables, list) is True and ( + "africaSE" in variables[0] or "america" in variables[0] or "asiaS" in variables[0] or + "oceania" in variables[0]): + met_in_file = True + my_reg = "africaSE" if "africaSE" in variables[0] else ( + "americaN" if "americaN" in variables[0] else ( + "americaS" if "americaS" in variables[0] else ("asiaS" if "asiaS" in variables[0] else "oceania"))) + elif isinstance(variables, list) is True and "_nina_" in variables[0] and "_nino_" in variables[1]: + met_in_file = True + my_reg = "" + else: + met_in_file = False + my_reg = None + tab_mod, tab_obs, metval, obsname = \ + read_var(variables, filename_nc, model, reference, metric_variables, metric_values, models2=models2, + member=member, shading=shading, met_in_file=met_in_file, met_type=metric_type, met_pattern=my_reg) + if metric_type is not None: + plot_metric = True + else: + plot_metric = False + if isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True: + lon = list(NUMPYarray(tab_mod[0].coords[tab_mod[0].dims[1]])) + lat = list(NUMPYarray(tab_mod[0].coords[tab_mod[0].dims[0]])) + elif isinstance(filename_nc, dict) is True and shading is True: + lon = list(NUMPYarray(tab_mod[0][0][0].coords[tab_mod[0][0][0].dims[1]])) + lat = list(NUMPYarray(tab_mod[0][0][0].coords[tab_mod[0][0][0].dims[0]])) + else: + lon = list(NUMPYarray(tab_mod[0][0].coords[tab_mod[0][0].dims[1]])) + lat = list(NUMPYarray(tab_mod[0][0].coords[tab_mod[0][0].dims[0]])) + # figure initialization + nbr_panel = dict_param["nbr_panel"] + if isinstance(filename_nc, list) is True or isinstance(filename_nc, dict) is True: + nbr_panel = nbr_panel + ((len(tab_mod) - 1) * nbr_val) + if plot_ref is True: + nbr_panel = int(round(nbr_panel / 2.)) + title = dict_param["title"] + xname = dict_param["xname"] + yname = dict_param["yname"] + zname = dict_param["zname"] + if isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True: + nbr_mod = 2 + else: + nbr_mod = len(model) + 1 + if isinstance(filename_nc, list) is True: + title = [obsname] + if plot_ref is False: + if isinstance(member, list) is True and len(member) == len(model): + title += [mod + mem for mod, mem in zip(model, member)] + else: + if plot_for_wiki is True: + title += ["model"] + else: + title += model + if nbr_val > 1: + title = title * nbr_val + elif isinstance(filename_nc, dict) is True: + # title = [obsname] + [mod.upper() + " (" + str(len(models2[mod])) + ")" for mod in model] + tmp_let = ["b) ", "c) ", "d) "] + title = ["a) ref: " + obsname] + if isinstance(member, list) is True and len(member) == len(model): + title += [tmp_let[ii] + mod.upper() + mem + " (" + str(len(models2[mod])) + ")" + for ii, (mod, mem) in enumerate(zip(model, member))] + else: + title += [tmp_let[ii] + mod.upper() + "(" + str(len(models2[mod])) + ")" for ii, mod in enumerate(model)] + if nbr_val > 1: + title = title * nbr_val + if isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True: + units = tab_mod[0].units.replace("C", "$^\circ$C").replace("long", "$^\circ$lon") + elif isinstance(filename_nc, dict) is True and shading is True: + units = tab_mod[0][0][0].units.replace("C", "$^\circ$C").replace("long", "$^\circ$lon") + else: + units = tab_mod[0][0].units.replace("C", "$^\circ$C").replace("long", "$^\circ$lon") + if units != "": + zname = zname + " (" + units + ")" + colorbar = "cmo." + dict_param["colorbar"] + labelbar = dict_param["label"] + if "maskland" in list(dict_param.keys()): + maskland = dict_param["maskland"] + else: + maskland = False + if "maskocean" in list(dict_param.keys()): + maskocean = dict_param["maskocean"] + else: + maskocean = False + if shading is True and len(model) + 1 == 3: + nbrl = int(round(nbr_panel / 3.)) + nbrc = 1 if nbr_panel == 1 else 3 + else: + nbrl = int(round(nbr_panel / 2.)) + nbrc = 1 if nbr_panel == 1 else 2 + if plot_ref is True: + nbrl = deepcopy(nbr_panel) + nbrc = 1 + if article_fig is True: + if "EnsoPrMap" not in figure_name: + nbrc = 1 + nbrl = 3 + if (isinstance(variables, str) is True and ( + "reg_pr_over_sst_map" in variables or "reg_slp_over_sst_map" in variables or + "reg_ts_over_sst_map" in variables or "djf_map__" in variables or "jja_map__" in variables)) or\ + (isinstance(variables, list) is True and ("djf_map__" in variables[0] or "jja_map__" in variables[0])): + fig, axes = plt.subplots(nbrl, nbrc, figsize=(6 * nbrc, 6 * nbrl), sharex="col", sharey="row") + else: + fig, axes = plt.subplots(nbrl, nbrc, figsize=(4 * nbrc, 4 * nbrl), sharex="col", sharey="row") + hspa1 = 0.1 + hspa2 = 0.01 + if ((nbrc == 2 and nbrl == 2) or (nbrc == 1 and plot_ref is True)) and isinstance(variables, list) is True and\ + my_reg in ["africaSE", "americaN", "americaS", "asiaS", "oceania"]: + if my_reg == "africaSE": + hspace = 0.3 + elif my_reg == "americaN": + hspace = 0.1 + elif my_reg == "americaS": + hspace = 0.4 + elif my_reg == "asiaS": + hspace = 0.1 + else: + hspace = 0.0 + plt.subplots_adjust(hspace=hspace, wspace=0.2) + elif ((nbrc == 2 and nbrl == 2 and plot_metric is True) or (nbrc == 1 and plot_ref is True)) and\ + isinstance(variables, list) is True and my_reg == "": + plt.subplots_adjust(hspace=-0.58, wspace=0.2) + elif nbr_panel / float(nbrc) <= 2: + if nbrc == 3 and nbr_val > 1: + plt.subplots_adjust(hspace=-0.70, wspace=0.2) + else: + plt.subplots_adjust(hspace=-0.75, wspace=0.2) + elif nbr_panel / float(nbrc) <= 3: + plt.subplots_adjust(hspace=-0.8, wspace=0.2) + elif nbr_panel / float(nbrc) <= 4: + plt.subplots_adjust(hspace=-0.85, wspace=0.2) + elif nbr_panel / float(nbrc) <= 6: + plt.subplots_adjust(hspace=-0.9, wspace=0.2) + else: + plt.subplots_adjust(hspace=hspa1, wspace=0.2) + if article_fig is True and nbrl == 3: + plt.subplots_adjust(hspace=-0.7, wspace=0.2) + if "BiasPrLatRmse" in figure_name and article_fig is True and nbrc == 1 and nbrl == 3: + plt.subplots_adjust(hspace=-0.80, wspace=0.2) + xlabel_ticks = list(range(int(MATHfloor(min(lon))), int(MATHceil(max(lon))) + 1)) + xlabel_ticks, xlabel = create_labels(xname, xlabel_ticks) + ylabel_ticks = list(range(int(MATHfloor(min(lat))), int(MATHceil(max(lat))) + 1)) + ylabel_ticks, ylabel = create_labels(yname, ylabel_ticks) + tab = list() + legend = my_legend(model, obsname, filename_nc, models2=models2, member=member, plot_metric=plot_metric, + shading=shading) + if plot_for_wiki is True: + if "CNRM-CM5" in legend[0]: + legend[0] = "model" + elif "CNRM-CM5" in legend[1]: + legend[1] = "model" + if plot_ref is True: + legend = [legend[0]] + if isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True: + for ii in range(len(tab_mod)): + tab.append(tab_obs[ii]) + if plot_ref is False: + tab.append(tab_mod[ii]) + elif isinstance(filename_nc, dict) is True and shading is True: + for ii in range(len(tab_obs)): + tab.append(tab_obs[ii]) + for kk in range(len(tab_mod)): + if "africaSE" in variables or "america" in variables or "asiaS" in variables or "oceania" in variables: + tmp = list() + for jj in range(len(tab_mod[kk])): + tmp.append(my_mask_map(tab_obs[ii], tab_mod[kk][jj][ii])) + else: + tmp = [tab_mod[kk][jj][ii] for jj in range(len(tab_mod[kk]))] + tab.append(my_average(tmp, axis=0)) + else: + for ii in range(len(tab_obs)): + tab.append(tab_obs[ii]) + if plot_ref is False: + for kk in range(len(tab_mod)): + tab.append(tab_mod[kk][ii]) + for ii in range(nbr_panel): + if nbr_panel == 1: + ax = axes + elif (nbrl == 1 and nbrc != 1) or (nbrl != 1 and nbrc == 1): + if nbrl == 1 and nbrc != 1: + ax = axes[ii % nbrc] + else: + ax = axes[ii % nbrl] + else: + ax = axes[ii / nbrc, ii % nbrc] + if isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True: + tt = title[ii] if plot_ref is True else title[int(round(ii / 2))] + ax.set_title(tt, fontsize=15, y=1., loc="left") + if ii in [0, 1] and len(legend) -1 >= ii % 2: + ax.text(0.5, 1. + (0.15 * (len(lon) + 10)) / len(lat), legend[ii % 2], fontsize=15, weight="bold", + horizontalalignment="center", verticalalignment="center", transform=ax.transAxes) + else: + if nbrc == 2 or plot_metric is True: + if ii == 0 or (ii % nbr_mod == 0): + location = "left" + else: + location = "left"#"right" + ax.set_title(title[ii], fontsize=15, y=1. + hspa2, loc=location) + del location + else: + if "BiasPrLatRmse" in figure_name and article_fig is True and nbrc == 1 and nbrl == 3: + ax.set_title(title[ii], fontsize=15, y=1. + hspa2, loc="left") + else: + ax.set_title(title[ii], fontsize=15, y=1. + hspa2, loc="center") + if nbr_val > 1: + if nbrc == 2: + if ii % nbrc == 0: + ax.text(1.1, 1. + 20 * hspa2, dict_param["title"][ii / nbrc], + fontsize=15, weight="bold", horizontalalignment="center", verticalalignment="center", + transform=ax.transAxes) + # if ii % (len(filename_nc) + 1) == 0: + # ttx2 = ax.get_position().x1 + # elif (ii - 1) % (len(filename_nc) + 1) == 0: + # ttx1 = ax.get_position().x0 + # tty2 = ax.get_position().y1 + # ax.text(ttx2 + (ttx1 - ttx2) / 2., tty2 + hspa2 / 2., + # dict_param["title"][(ii - 1) / (len(filename_nc) + 1)], fontsize=15, weight="bold", + # horizontalalignment="center", verticalalignment="center", transform=fig.transFigure) + else: + if ii % nbrc == 1: + ax.text(0.5, 1. + 60 * hspa2, dict_param["title"][(ii - 1) / (len(filename_nc) + 1)], + fontsize=15, weight="bold", horizontalalignment="center", verticalalignment="center", + transform=ax.transAxes) + # map + xx, yy = NUMPYmeshgrid(lon, lat) + if lat[-1] - lat[0] < 40: + locmap = Basemap(projection="cyl", llcrnrlat=lat[0] - 5, urcrnrlat=lat[-1] + 5, llcrnrlon=lon[0], + urcrnrlon=lon[-1], ax=ax) + else: + locmap = Basemap(projection="cyl", llcrnrlat=lat[0], urcrnrlat=lat[-1], llcrnrlon=lon[0], urcrnrlon=lon[-1], + ax=ax) + # draw coastlines + locmap.drawcoastlines() + # fill continents + if maskland is True: + locmap.fillcontinents(color="gainsboro") + if maskocean is True: + locmap.drawmapboundary(fill_color="white") + # draw parallels + locmap.drawparallels(ylabel_ticks, labels=[1, 0, 0, 0], fontsize=12, dashes=[3, 1], linewidth=1) + # draw meridians + locmap.drawmeridians(xlabel_ticks, labels=[0, 0, 0, 1], fontsize=12, dashes=[3, 1], linewidth=1) + #cs = locmap.pcolormesh(xx, yy, tab[ii], vmin=min(labelbar), vmax=max(labelbar), cmap=colorbar) + levels = create_levels(labelbar) + cs = locmap.contourf(xx, yy, tab[ii], levels=levels, extend="both", cmap=colorbar) + # my text + if (ii > 0 and plot_metric is True and isinstance(variables, list) is False) or\ + (isinstance(variables, list) is True and "nina" in variables[0] and "nino" in variables[1] and + (ii+1) % 2 == 0): + if isinstance(metric_type, str) is True or isinstance(metric_type, str) is True: + txt = format_metric(metric_type, my_average(metval[ii - 1], remove_masked=True), metric_units) + ax.text(0.5, 0.5, txt, fontsize=12, color="k", horizontalalignment="center", + verticalalignment="center") + elif isinstance(metric_type, list) is True: + for jj in range(len(metric_type)): + if shading is True: + tmp = [metval[ii - 1][kk][jj] for kk in range(len(metval[ii - 1]))] + tmp = my_average(tmp, remove_masked=True) + else: + if isinstance(metval[0], list) is True: + tmp = metval[int(round(ii / 2))][jj] + else: + tmp = metval[jj] + txt = format_metric(metric_type[jj], tmp, metric_units[jj]) + if my_reg in ["africaSE", "americaN", "americaS", "asiaS", "oceania"]: + if my_reg in ["africaSE"]: + xxx, yyy = 0.00, -0.05 - jj * 0.07 + elif my_reg in ["americaN"]: + xxx, yyy = 0.00, -0.14 - jj * 0.10 + elif my_reg in ["americaS"]: + xxx, yyy = 0.00, -0.12 - jj * 0.06 + elif my_reg in ["asiaS"]: + xxx, yyy = 0.00, -0.13 - jj * 0.09 + else: + xxx, yyy = 0.00, -0.15 - jj * 0.10 + elif "reg_pr_over_sst_map" in variables or "reg_slp_over_sst_map" in variables or\ + "reg_ts_over_sst_map" in variables or "reg_pr_over_sst_djf_map" in variables or\ + "reg_slp_over_sst_djf_map" in variables or "reg_ts_over_sst_djf_map" in variables or\ + "reg_pr_over_sst_jja_map" in variables or "reg_slp_over_sst_jja_map" in variables or\ + "reg_ts_over_sst_jja_map" in variables or (isinstance(variables, list) is True and ( + "djf_map__" in variables[0] or "jja_map__" in variables[0])): + xxx, yyy = 0.00, -0.30 - jj * 0.18 + else: + xxx, yyy = -0.12, 1.26 - jj * 0.16 + ax.text(xxx, yyy, txt, fontsize=11, color="k", horizontalalignment="left", + verticalalignment="center", transform=ax.transAxes) + if ii == 0 and plot_ref is True: + tx1, tx2 = ax.get_xlim() + dx = (tx2 - tx1) / 100. + ty1, ty2 = ax.get_ylim() + ax.text(tx2 + 2 * dx, ty2, str(method, "utf-8"), fontsize=12, color="k", ha="left", va="top") + if plot_ref is True: + if regions is not None: + if (isinstance(variables, list) is True and + ("djf_map__" in variables[0] or "jja_map__" in variables[0])) or ( + "djf_map__" in variables or "jja_map__" in variables): + lreg = ReferenceRegions("nino3.4") + else: + lreg = ReferenceRegions(regions[metric_variables[0]]) + lons = [lreg["longitude"][0]] * 2 + [lreg["longitude"][1]] * 2 + lats = list(lreg["latitude"]) + list(reversed(list(lreg["latitude"]))) + x, y = locmap(lons, lats) + ax.add_patch(Polygon(list(zip(x, y)), edgecolor="k", linewidth=3, linestyle="-", facecolor="none")) + # if ii == 0 and plot_metric is True: + # x1 = ax.get_position().x1 + # y2 = ax.get_position().y1 + # if nbrl == 1: + # ax2 = axes[(ii + 1) % 2] + # else: + # ax2 = axes[int(round((ii + 1) / 2)), (ii + 1) % 2] + # x2 = ax2.get_position().x0 + # txt = format_metric(metric_type, metval, metric_units) + # ax.text(x1 + ((x2 - x1) / 2.), y2 + 0.1, txt, fontsize=12, color="k", horizontalalignment="center", + # verticalalignment="center", transform=fig.transFigure) + if ii == 0: + x1 = ax.get_position().x0 + if ii == nbr_panel - 1: + x2 = ax.get_position().x1 + y1 = ax.get_position().y0 + # add colorbar + if my_reg in ["africaSE", "americaN", "americaS", "asiaS", "oceania"]: + if my_reg in ["africaSE"]: + if isinstance(variables, list) is True: + cax = plt.axes([x1, y1 - 0.08, x2 - x1, 0.02]) + else: + cax = plt.axes([x1, y1 - 0.15, x2 - x1, 0.04]) + elif my_reg in ["americaN"]: + if isinstance(variables, list) is True: + cax = plt.axes([x1, y1 - 0.07, x2 - x1, 0.02]) + else: + cax = plt.axes([x1, y1 - 0.10, x2 - x1, 0.03]) + elif my_reg in ["americaS"]: + if isinstance(variables, list) is True: + cax = plt.axes([x1 + 0.04, y1 - 0.12, x2 - x1 - 0.08, 0.025]) + else: + cax = plt.axes([x1 + 0.04, y1 - 0.23, x2 - x1 - 0.08, 0.035]) + elif my_reg in ["asiaS"]: + if isinstance(variables, list) is True: + cax = plt.axes([x1, y1 - 0.07, x2 - x1, 0.02]) + else: + cax = plt.axes([x1, y1 - 0.07, x2 - x1, 0.03]) + else: + if isinstance(variables, list) is True: + cax = plt.axes([x1, y1 - 0.05, x2 - x1, 0.02]) + else: + cax = plt.axes([x1, y1 - 0.05, x2 - x1, 0.03]) + elif nbrl == 2: + if isinstance(variables, list) is True and ("djf_map__" in variables[0] or "jja_map__" in variables[0]): + cax = plt.axes([x1, y1 + 0.11, x2 - x1, 0.018]) + else: + cax = plt.axes([x1, y1 + 0.2, x2 - x1, 0.015]) # cax = plt.axes([x1, y1 + 0.21, x2 - x1, 0.015]) + elif nbrl == 3: + if article_fig is True and nbrl == 3 and ( + "reg_pr_over_sst_map" in variables or "reg_pr_over_sst_djf_map" in variables or + "reg_pr_over_sst_jja_map" in variables): + cax = plt.axes([x1, y1 + 0.15, x2 - x1, 0.015]) + else: + cax = plt.axes([x1, y1 + 0.2, x2 - x1, 0.015]) + elif nbrl == 4: + cax = plt.axes([x1, y1 + 0.21, x2 - x1, 0.01]) + elif nbrl == 6: + cax = plt.axes([x1, y1 + 0.22, x2 - x1, 0.005]) + else: + if "reg_pr_over_sst_map" in variables or "reg_slp_over_sst_map" in variables or\ + "reg_ts_over_sst_map" in variables or "reg_pr_over_sst_djf_map" in variables or\ + "reg_slp_over_sst_djf_map" in variables or "reg_ts_over_sst_djf_map" in variables or\ + "reg_pr_over_sst_jja_map" in variables or "reg_slp_over_sst_jja_map" in variables or\ + "reg_ts_over_sst_jja_map" in variables: + cax = plt.axes([x1, y1 + 0.07, x2 - x1, 0.035]) + else: + cax = plt.axes([x1, y1 + 0.2, x2 - x1, 0.03]) + # cax = plt.axes([x1, y1 + 0.15, x2 - x1, 0.03]) + cbar = plt.colorbar(cs, cax=cax, orientation="horizontal", ticks=labelbar, pad=0.35, extend="both") + cbar.set_label(zname, fontsize=15) + cbar.ax.tick_params(labelsize=12) + plt.savefig(figure_name, bbox_inches="tight") + plt.close() + return + + +def my_scatterplot(model, filename_nc, dict_param, reference, metric_variables, figure_name, models2=None, member=None, + metric_type=None, metric_values=None, metric_units=None, diagnostic_values=None, + diagnostic_units=None, regions=None, shading=False, plot_ref=False): + # get data + variables = dict_param["varpattern"] + method = dict_param["method"] + if isinstance(metric_variables, list) is True and regions is not None: + for ii, vv in enumerate(metric_variables): + method = method.replace("REGION" + str(ii + 1), regions[vv].replace("nino", "N")) + if isinstance(variables, str) is True or isinstance(variables, str) is True: + nbr_val = 1 + else: + nbr_val = len(variables) + tab_mod, tab_obs, metval, obsname = \ + read_var(variables, filename_nc, model, reference, metric_variables, metric_values, models2=models2, + member=member, + shading=shading) + if metric_type is not None and (isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True): + plot_metric = True + else: + plot_metric = False + # figure initialization + nbr_panel = dict_param["nbr_panel"] + if (isinstance(filename_nc, list) is True or isinstance(filename_nc, dict) is True) and nbr_panel > 1: + nbr_panel = nbr_panel + len(filename_nc) - 1 + if plot_ref is True: + nbr_panel = int(round(nbr_panel / 2.)) + title = dict_param["title"] + if (isinstance(filename_nc, list) is True or isinstance(filename_nc, dict) is True) and nbr_panel > 1: + if isinstance(filename_nc, dict): + if "EnsoFbSstSwr" in figure_name and article_fig is True: + tmp_let = ["b) ", "c) ", "d) ", "e) "] + title = [tmp_let[0] + "ref: Tropflux"] + if isinstance(member, list) is True and len(member) == len(model): + title += [tmp_let[ii+1] + mod.upper() + mem + " (" + str(len(models2[mod])) + ")" + for ii, (mod, mem) in enumerate(zip(model, member))] + else: + title += [tmp_let[ii+1] + mod.upper() + "(" + str(len(models2[mod])) + ")" + for ii, mod in enumerate(model)] + else: + title = [obsname] + if plot_ref is False: + if isinstance(member, list) is True and len(member) == len(model): + title += [mod.upper() + mem + " (" + str(len(models2[mod])) + ")" + for mod, mem in zip(model, member)] + else: + title += [mod.upper() + "(" + str(len(models2[mod])) + ")" for mod in model] + else: + title = [obsname] + if plot_ref is False: + if isinstance(member, list) is True and len(member) == len(model): + title += [mod + mem for mod, mem in zip(model, member)] + else: + title += model + if isinstance(title, str) is True or isinstance(title, str) is True: + title = [title] * nbr_panel + xname = dict_param["xname"] + if isinstance(xname, str) is True or isinstance(xname, str) is True: + xname = [xname] * nbr_panel + one_xaxis = True + else: + one_xaxis = False + yname = dict_param["yname"] + if isinstance(yname, str) is True or isinstance(yname, str) is True: + yname = [yname] * nbr_panel + one_yaxis = True + else: + one_yaxis = False + if "colors" in list(dict_param.keys()): + mcolors = dict_param["colors"] + else: + mcolors = ["k", "dodgerblue"] + # if dict_param["nbr_panel"] == len(nbr_val): + # mcolors = list(reversed(mcolors)) + if isinstance(filename_nc, list) is True or isinstance(filename_nc, dict) is True: + for ii in range(len(filename_nc) - 1): + mcolors.append(colors_sup[ii]) + if plot_ref is True: + mcolors = ["k"] + if "markers" in list(dict_param.keys()): + markers = dict_param["markers"] + else: + markers = ["D", "."] + # if dict_param["nbr_panel"] == len(nbr_val): + # markers = list(reversed(markers)) + if isinstance(filename_nc, list) is True or isinstance(filename_nc, dict) is True: + for ii in range(len(filename_nc) - 1): + markers.append(".") + if plot_ref is True: + markers = ["D"] + if "legend" in list(dict_param.keys()): + legend = dict_param["legend"] + else: + legend = my_legend(model, obsname, filename_nc, models2=models2, member=member, plot_metric=plot_metric, + shading=shading) + if plot_for_wiki is True: + legend[1] = "model" + if plot_ref is True: + legend = [legend[0]] + keys1 = ["", "_neg", "_pos"] + keys2 = ["all", "x<0", "x>0"] + keys3 = [[None, None], [None, 0], [0, None]] + keys4 = ["black", "dodgerblue", "red"] + lines = [Line2D([0], [0], marker=markers[kk], c="w", markerfacecolor=mcolors[kk], markersize=12) + for kk in range(len(mcolors))] + if shading is True and nbr_panel == 3: + nbrl = int(round(nbr_panel / 3.)) + nbrc = 1 if nbr_panel == 1 else 3 + else: + nbrl = int(round(nbr_panel / 2.)) + nbrc = 1 if nbr_panel == 1 else 2 + if plot_ref is True: + nbrl = deepcopy(nbr_panel) + nbrc = 1 + fig, axes = plt.subplots(nbrl, nbrc, figsize=(4 * nbrc, 4 * nbrl), sharex="col", sharey="row") + plt.subplots_adjust(hspace=0.3, wspace=0.1) + if isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True: + tab1 = tab_obs[::2] + tab2 = tab_obs[1::2] + if plot_ref is False: + tab1 += tab_mod[::2] + tab2 += tab_mod[1::2] + elif isinstance(filename_nc, dict) is True and shading is True: + tab1 = tab_obs[::2] + tab2 = tab_obs[1::2] + for kk in range(len(tab_mod)): + tmp1, tmp2 = list(), list() + for jj in range(len(tab_mod[kk])): + tmp1 += list(tab_mod[kk][jj][::2][0]) + tmp2 += list(tab_mod[kk][jj][1::2][0]) + tab1.append(tmp1) + tab2.append(tmp2) + del tmp1, tmp2 + else: + tab1 = tab_obs[::2] + tab2 = tab_obs[1::2] + if plot_ref is False: + for ii in range(len(filename_nc)): + tab1 += tab_mod[ii][::2] + tab2 += tab_mod[ii][1::2] + xtick_labels = minmax_plot(tab1, metric=False) + ytick_labels = minmax_plot(tab2, metric=plot_metric) + if nbr_panel == nbr_val / 2.: + markers = list(reversed(markers)) + mcolors = list(reversed(mcolors)) + tab1 = list(reversed(tab1)) + tab2 = list(reversed(tab2)) + for ii in range(nbr_panel): + if nbr_panel == 1: + ax = axes + elif (nbrl == 1 and nbrc != 1) or (nbrc == 1 and nbrl != 1): + if nbrl == 1 and nbrc != 1: + ax = axes[ii % nbrc] + else: + ax = axes[ii % nbrl] + else: + ax = axes[ii / nbrc, ii % nbrc] + # title + if isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True: + ax.set_title(title[int(round(ii / 2))], fontsize=15, y=1.01, loc="left") + if nbr_panel == len(tab_mod) and ii in [0, 1]: + ax.text(0.5, 1.15, legend[ii % 2], fontsize=15, weight="bold", horizontalalignment="center", + verticalalignment="center", transform=ax.transAxes) + else: + ax.set_title(title[ii], fontsize=15, y=1.01, loc="left") + # x axis + if "EnsoFbSshSst" in figure_name and article_fig is True: + ax.set_xticks([-40, -20, 0, 20, 40], minor=False) + ax.set_xticklabels([-40, -20, 0, 20, 40]) + ax.set_xticks([-30, -10, 10, 30], minor=True) + ax.set_xlim([-40, 40]) + elif ("EnsoFbSstTaux" in figure_name or "EnsoFbSstSwr" in figure_name) and article_fig is True: + ax.set_xticks([-6, -3, 0, 3, 6], minor=False) + ax.set_xticklabels([-6, -3, 0, 3, 6]) + ax.set_xticks([-4.5, -1.5, 1.5, 4.5], minor=True) + ax.set_xlim([-6, 6]) + elif "EnsoFbTauxSsh" in figure_name and article_fig is True: + ax.set_xticks([-100, -50, 0, 50, 100], minor=False) + ax.set_xticklabels([-100, -50, 0, 50, 100]) + ax.set_xticks([-75, -25, 25, 75], minor=True) + ax.set_xlim([-125, 125]) + else: + ax.set_xticks(xtick_labels) + ax.set_xlim(xmin=min(xtick_labels), xmax=max(xtick_labels)) + if (one_xaxis is True and (ii >= (nbrc * nbrl) - nbrc)) or one_xaxis is False: + xlabel = xname[ii] + for kk in list(regions.keys()): + if kk in xlabel.lower(): + xlabel = regions[kk] + " " + xlabel + units = tab_obs[0].units.replace("C", "$^\circ$C").replace("long", "$^\circ$lon") + if units != "": + xlabel = xlabel + " (" + units + ")" + ax.set_xlabel(xlabel, fontsize=15) + for tick in ax.xaxis.get_major_ticks(): + tick.label.set_fontsize(12) + # y axis + if "EnsoFbSshSst" in figure_name and article_fig is True: + ax.set_yticks([-6, -3, 0, 3, 6], minor=False) + ax.set_yticklabels([-6, -3, 0, 3, 6]) + ax.set_yticks([-4.5, -1.5, 1.5, 4.5], minor=True) + ax.set_ylim([-6, 6]) + elif "EnsoFbSstTaux" in figure_name and article_fig is True: + ax.set_yticks([-100, -50, 0, 50, 100], minor=False) + ax.set_yticklabels([-100, -50, 0, 50, 100]) + ax.set_yticks([-75, -25, 25, 75], minor=True) + ax.set_ylim([-125, 125]) + elif "EnsoFbTauxSsh" in figure_name and article_fig is True: + ax.set_yticks([-30, -15, 0, 15, 30], minor=False) + ax.set_yticklabels([-30, -15, 0, 15, 30]) + ax.set_yticks([-22.5, -7.5, 0, 7.5, 22.5], minor=True) + ax.set_ylim([-37.5, 37.5]) + else: + ax.set_yticks(ytick_labels) + ax.set_ylim(ymin=min(ytick_labels), ymax=max(ytick_labels)) + # ax.set_yticks([-150, -75, 0, 75, 150], minor=False) + # ax.set_yticklabels([-150, -75, 0, 75, 150]) + # ax.set_yticks([-112.5, -37.5, 37.5, 112.5], minor=True) + # ax.set_ylim([-150, 150]) + # ax.set_yticks([-100, -50, 0, 50, 100], minor=False) + # ax.set_yticklabels([-100, -50, 0, 50, 100]) + # ax.set_yticks([-75, -25, 25, 75], minor=True) + # ax.set_ylim([-100, 100]) + if (one_yaxis is True and ii % nbrc == 0) or one_yaxis is False: + ylabel = yname[ii] + for kk in list(regions.keys()): + if kk in ylabel.lower(): + ylabel = regions[kk] + " " + ylabel + units = tab_obs[1].units.replace("C", "$^\circ$C").replace("long", "$^\circ$lon") + if units != "": + ylabel = ylabel + " (" + units + ")" + ax.set_ylabel(ylabel, fontsize=15) + for tick in ax.yaxis.get_major_ticks(): + tick.label.set_fontsize(12) + # scatterplots and slopes + x1, x2 = ax.get_xlim() + dx = (x2 - x1) / 100. + y1, y2 = ax.get_ylim() + dy = (y2 - y1) / 100. + if (nbr_panel > 1 and nbr_panel == nbr_val / 2.) or (nbr_panel == len(legend) and title[0] == "nonlinarity"): + # multiple panel + ax.scatter(tab1[ii], tab2[ii], s=10, c="k", marker=markers[ii]) + for jj in range(len(keys1)): + if isinstance(filename_nc, dict): + intercept, slope = list(), list() + if ii == 0: + if "slope" + keys1[jj] in list(tab_obs[0].attrs.keys()) and\ + "intercept" + keys1[jj] in list(tab_obs[0].attrs.keys()): + intercept.append(tab_obs[0].attrs["intercept" + keys1[jj]]) + slope.append(tab_obs[0].attrs["slope" + keys1[jj]]) + else: + for kk in range(len(tab_mod[ii-1])): + if "slope" + keys1[jj] in list(tab_mod[ii-1][kk][0].attrs.keys()) and\ + "intercept" + keys1[jj] in list(tab_mod[ii-1][kk][0].attrs.keys()): + intercept.append(tab_mod[ii-1][kk][0].attrs["intercept" + keys1[jj]]) + slope.append(tab_mod[ii-1][kk][0].attrs["slope" + keys1[jj]]) + if len(intercept) == 1: + intercept = intercept[0] + slope = slope[0] + else: + intercept = float(my_average(intercept, remove_masked=True)) + slope = float(my_average(slope, remove_masked=True)) + col = keys4[jj] + xx = keys3[jj] + if xx[0] is None: + xx[0] = x1 + if xx[1] is None: + xx[1] = x2 + yy = [kk * slope + intercept for kk in xx] + ax.plot(xx, yy, lw=2, c=col) + txt = "slope(" + keys2[jj] + ") = " + "{0:+.2f}".format(round(slope, 2)) + # ax.text(dx + x1, ((97 - 5 * jj) * dy) + y1, txt, fontsize=12, color=col, + # horizontalalignment="left", verticalalignment="center") + ax.text(2 * dx + x1, ((93 - 6 * jj) * dy) + y1, txt, fontsize=12, color=col, + horizontalalignment="left", verticalalignment="center") + else: + if "slope" + keys1[jj] in list(tab1[ii].attrs.keys()) and\ + "intercept" + keys1[jj] in list(tab1[ii].attrs.keys()): + col = keys4[jj] + slope = tab1[ii].attrs["slope" + keys1[jj]] + intercept = tab1[ii].attrs["intercept" + keys1[jj]] + xx = keys3[jj] + if xx[0] is None: + xx[0] = x1 + if xx[1] is None: + xx[1] = x2 + yy = [kk * slope + intercept for kk in xx] + ax.plot(xx, yy, lw=2, c=col) + txt = "slope(" + keys2[jj] + ") = " + "{0:+.2f}".format(round(slope, 2)) + # ax.text(dx + x1, ((93 - 6 * jj) * dy) + y1, txt, fontsize=12, color=col, + # horizontalalignment="left", verticalalignment="center") + ax.text(2 * dx + x1, ((93 - 6 * jj) * dy) + y1, txt, fontsize=12, color=col, + horizontalalignment="left", verticalalignment="center") + else: + tmp = list() + for jj in range(len(tab1)): + col = mcolors[jj] + ax.scatter(tab1[jj], tab2[jj], s=10, c=col, marker=markers[jj]) + if isinstance(filename_nc, dict): + intercept, slope = list(), list() + if jj == len(tab1) - 1: + if "slope" + keys1[0] in list(tab_obs[0].attrs.keys()) and\ + "intercept" + keys1[0] in list(tab_obs[0].attrs.keys()): + intercept.append(tab_obs[0].attrs["intercept" + keys1[0]]) + slope.append(tab_obs[0].attrs["slope" + keys1[0]]) + else: + for kk in range(len(tab_mod[len(tab_mod) - 1 - jj])): + if "slope" + keys1[0] in list(tab_mod[len(tab_mod) - 1 - jj][kk][0].attrs.keys()) and\ + "intercept" + keys1[0] in list(tab_mod[len(tab_mod) - 1 - jj][kk][0].attrs.keys()): + intercept.append(tab_mod[len(tab_mod) - 1 - jj][kk][0].attrs["intercept" + keys1[0]]) + slope.append(tab_mod[len(tab_mod) - 1 - jj][kk][0].attrs["slope" + keys1[0]]) + if len(intercept) == 1: + intercept = intercept[0] + slope = slope[0] + else: + intercept = float(my_average(intercept, remove_masked=True)) + slope = float(my_average(slope, remove_masked=True)) + xx = [x1, x2] + yy = [kk * slope + intercept for kk in xx] + if jj == 0: + ax.plot(xx, yy, lw=4, c=col) + else: + ax.plot(xx, yy, lw=2, c=col) + tmp.append("slope = " + "{0:+.2f}".format(round(slope, 2))) + else: + if "slope" in list(tab1[jj].attrs.keys()) and "intercept" in list(tab1[jj].attrs.keys()): + slope = tab1[jj].attrs["slope"] + intercept = tab1[jj].attrs["intercept"] + xx = [x1, x2] + yy = [kk * slope + intercept for kk in xx] + ax.plot(xx, yy, lw=2, c=col) + tmp.append("slope = " + "{0:+.2f}".format(round(slope, 2))) + for jj in range(len(tmp)): + col = mcolors[len(tmp) - 1 - jj] + ax.text(2 * dx + x1, ((93 - 6 * jj) * dy) + y1, tmp[len(tmp) - 1 - jj], fontsize=12, + color=col, horizontalalignment="left", verticalalignment="center") + # ax.text(2 * dx + x1, ((13 - 6 * jj) * dy) + y1, tmp[len(tmp) - 1 - jj], fontsize=12, + # color=col, horizontalalignment="left", verticalalignment="center") + if nbr_panel == 1 or ii == nbrc - 1: + if metric_type is not None: + for jj in range(1, len(legend)): + if isinstance(model, str) is True or isinstance(model, str) is True: + tmp = deepcopy(metval) + elif shading is True: + tmp = my_average(metval[jj - 1], remove_masked=True) + else: + tmp = metval[jj - 1] + # metric_units = "N/m2/$^\circ$C / N/m2/$^\circ$C" + # metric_units = "cm/N/m2 / cm/N/m2" + # legend[jj] = legend[jj] + " (" + "{0:.2f}".format(tmp) + " " + metric_units + ")" + if ("EnsoFbSshSst" in figure_name or "EnsoFbSstTaux" in figure_name or\ + "EnsoFbTauxSsh" in figure_name) and article_fig is True: + metric_units = "%" + legend[jj] = legend[jj] + " (" + "{0:}".format(int(round(tmp))) + " " + metric_units + ")" + if ("EnsoFbSshSst" in figure_name or "EnsoFbSstTaux" in figure_name or "EnsoFbTauxSsh" in figure_name) \ + and article_fig is True: + ax.legend(lines, legend, bbox_to_anchor=(1, 0), loc="lower right", ncol=1) + else: + ax.legend(lines, legend, bbox_to_anchor=(1, 1), loc="upper left", ncol=1) + # my text + if plot_metric is True: + txt = format_metric(metric_type, metval, metric_units) + ax.text(x2 - (2 * dx), y1 + (6 * dy), txt, fontsize=12, color="k", horizontalalignment="right", + verticalalignment="center") + if ii == 0 and plot_ref is True: + ax.text(x2 + 2 * dx, y2 - 20 * dy, str(method, "utf-8"), fontsize=12, color="k", ha="left", va="top") + # grid + ax.grid(linestyle="--", linewidth=1, which="major") + plt.savefig(figure_name, bbox_inches="tight") + plt.close() + return + + +def plot_curve(tab_mod, tab_obs, ax, title, axis, xname, yname, ytick_labels, linecolors, linestyles, metric_type, + metval, metric_units, model='', member=None, obsname='', legend=[], multimodel=False, plot_metric=False, + plot_legend=False, shading=False, plot_ref=False, method=""): + # title + ax.set_title(title, fontsize=15, y=1.01, loc="left") + # x axis + label_ticks = list(range(int(MATHfloor(min(axis))), int(MATHceil(max(axis))) + 1)) + label_ticks, label = create_labels(xname, label_ticks) + ax.set_xticks(label_ticks) + ax.set_xticklabels(label) + ax.set_xlim([min(axis), max(axis)]) + # ax.set_xlim([-13, 13]) + ax.set_xlabel(xname, fontsize=15) + for tick in ax.xaxis.get_major_ticks(): + tick.label.set_fontsize(12) + # y axis + ax.set_yticks(ytick_labels) + ax.set_yticklabels(ytick_labels) + ax.set_ylim([min(ytick_labels), max(ytick_labels)]) + # ax.set_yticks([0, 3, 6, 9], minor=False) + # ax.set_yticklabels([0, 3, 6, 9]) + # ax.set_yticks([1.5, 4.5, 7.5], minor=True) + # ax.set_ylim([0, 9.5]) + # ax.set_yticks([-1.0, -0.5, 0.0, 0.5, 1.0], minor=False) + # ax.set_yticklabels([-1.0, -0.5, 0.0, 0.5, 1.0]) + # ax.set_yticks([-0.75, -0.25, 0.25, 0.75], minor=True) + # ax.set_ylim([-1.1, 1.1]) + # ax.add_line(Line2D([29, 39], [0.25, 0.25], c="orange", lw=2)) + # ax.scatter([29], 0.25, s=80, c="orange", marker="<", zorder=10) + # ax.scatter([39], 0.25, s=80, c="orange", marker=">", zorder=10) + # ax.text(34, 0.1, "duration", fontsize=18, color="orange", horizontalalignment='center', + # verticalalignment='center') + ax.set_ylabel(yname, fontsize=15) + for tick in ax.yaxis.get_major_ticks(): + tick.label.set_fontsize(12) + if min(ytick_labels) < 0 and max(ytick_labels) > 0: + ax.axhline(0, color='k', linestyle='-', linewidth=2) + # plot curves + if len(tab_mod) + len(tab_obs) > 2: + lw = 4 # 2 # + else: + lw = 4 + if plot_ref is False: + for ii, tab in enumerate(tab_mod): + if shading is True: + tab_sh = shading_levels(tab, axis=0) + # # !!!!! temporary: start !!!!! + # if ii != len(tab_mod) - 1: + # ax.fill_between(axis, list(tab_sh[0]), list(tab_sh[3]), facecolor=linecolors["model"][ii], + # alpha=0.3) + # ax.fill_between(axis, list(tab_sh[1]), list(tab_sh[2]), facecolor=linecolors["model"][ii], + # alpha=0.4) + # # !!!!! temporary: end !!!!! + # ax.fill_between(axis, list(tab_sh[0]), list(tab_sh[3]), facecolor=linecolors["model"][ii], alpha=0.3) + # ax.fill_between(axis, list(tab_sh[1]), list(tab_sh[2]), facecolor=linecolors["model"][ii], alpha=0.4) + ax.plot(axis, list(tab_sh[4]), lw=lw, color=linecolors["model"][ii], ls=linestyles["model"][ii]) + else: + ax.plot(axis, list(tab), c=linecolors["model"][ii], lw=lw, ls=linestyles["model"][ii]) + for ii, tab in enumerate(tab_obs): + ax.plot(axis, list(tab), c=linecolors["reference"][ii], lw=lw, ls=linestyles["reference"][ii]) + # relative space + x1, x2 = ax.get_xlim() + dx = (x2 - x1) / 100. + y1, y2 = ax.get_ylim() + dy = (y2 - y1) / 100. + if multimodel is False: + # legend + if len(linecolors["model"]) == 1 and len(linecolors["reference"]) == 1: + if plot_ref is False: + legen = deepcopy(legend) + legco = [linecolors["reference"][0], linecolors["model"][0]] + else: + legen = [legend[0]] + legco = [linecolors["reference"][0]] + lines = [Line2D([0], [0], marker='o', c='w', markerfacecolor=cc, markersize=12) for cc in legco] + ax.legend(lines, legen, bbox_to_anchor=(1, 1), loc='upper left', ncol=1) + else: + legtxt = deepcopy(legend) + if plot_ref is False: + if isinstance(member, str) is True or isinstance(member, str) is True: + legtxt += [model + " " + member, obsname] + else: + legtxt += [model, obsname] + legls = [linestyles["model"][0], linestyles["reference"][0]] + else: + legtxt += [obsname] + legls = [linestyles["reference"][0]] + if plot_for_wiki is True: + legtxt = ["model" if "CNRM-CM5" in tt else tt for tt in legtxt] + lines = [Line2D([0], [0], marker='o', c='w', markerfacecolor=cc, markersize=12) for cc in + linecolors["model"]] + lines = lines + [Line2D([0], [0], c='k', lw=2, ls=ls) for ls in legls] + ax.legend(lines, legtxt, bbox_to_anchor=(1, 1), loc='upper left', ncol=1) + if plot_ref is True: + ax.text(x2 + 2 * dx, y2 - 50 * dy, str(method, "utf-8"), fontsize=12, color="k", ha="left", + va="top") + # my text + if plot_metric is True: + txt = format_metric(metric_type, metval, metric_units) + ax.text(x2 - (2 * dx), y2 - (6 * dy), txt, fontsize=12, color='k', ha='right', va='center') + else: + # legend + if plot_legend is True: + legco = linecolors["reference"] + linecolors["model"] + lines = [Line2D([0], [0], marker='o', c='w', markerfacecolor=cc, markersize=12) for cc in legco] + if plot_metric is True: + for jj in range(1, len(legend)): + if shading is True: + tmp = my_average(metval[jj - 1], remove_masked=True) + else: + tmp = metval[jj - 1] + legend[jj] = legend[jj] + " (" + "{0:.2f}".format(tmp) + " " + metric_units + ")" + # ax.legend(lines, legend, bbox_to_anchor=(0, 1), loc="upper left", ncol=1) + legend = ["model" if "CNRM-CM5" in tt else tt for tt in legend] + ax.legend(lines, legend, bbox_to_anchor=(1, 1), loc="upper left", ncol=1) + ax.grid(linestyle='--', linewidth=1, which='major') + return + + +def plot_metrics_correlations(tab_rval, figure_name, xy_names, tab_pval=None, write_corr=False, title="", cfram=False, + chigh=False, bold_names=[], save_eps=False): + """ + Plots the correlations matrix + + Inputs: + ------ + :param tab_rval: `cdms2` variable + A `cdms2` variable containing the correlation coefficients. + :param figure_name: string + Path to and name of the output plot. + :param xy_names: list of string + List of the names to put as x and y ticks. + **Optional arguments:** + :param tab_pval: `cdms2` variable, optional + A `cdms2` variable containing the p-value of the correlation coefficients. It is used to mask the correlations + that are not significant. + Default is None (all correlation coefficients are plotted). + :param write_corr: boolean, optional + True to write the correlation value in each box. + Default is False (correlation not written). + :param title: string, optional + Title of the plot. + Default is "" (no title). + :param chigh: boolean, optional + True to highlight labels in colors for each group of metrics. + Default is False (names written in black). + :param cfram: boolean, optional + True to color the frame for each group of metrics. + Default is False (frame is black). + :param bold_names: list, optional + List of names to write in bold (must be in xy_names). + Default is False (names written in normal font). + :param save_eps: boolean, optional + True to save the plot in eps format instead of png. + Default is False (plot saved in png format). + + Output: + ------ + """ + met_o1, met_o2, met_o3, met_o4 = return_metrics_type() + if tab_pval is not None: + mask1 = NUMPYmasked_where(tab_rval < tab_pval, tab_rval).mask.astype("f") + mask2 = NUMPYmasked_where(tab_rval > -tab_pval, tab_rval).mask.astype("f") + mask = mask1 + mask2 + tab_plot = NUMPYmasked_where(mask == 2, tab_rval) + else: + tab_plot = deepcopy(tab_rval) + fig, ax = plt.subplots(figsize=(0.5 * len(tab_rval), 0.5 * len(tab_rval))) + # shading & colorbar + levels = MaxNLocator(nbins=20).tick_values(-1, 1) + cmap = plt.get_cmap("cmo.balance") + norm = BoundaryNorm(levels, ncolors=cmap.N, clip=True) + cs = plt.pcolormesh(tab_plot, cmap=cmap, norm=norm) + # title + plt.title(title, fontsize=30, y=1.01, loc="center") + # x axis + xticks = [ii + 0.5 for ii in range(len(tab_plot))] + xlabel = [met for met in xy_names] + ax.set_xticks(xticks) + ax.set_xticklabels([""] * len(xticks)) + ax.tick_params(axis="x", labelsize=15, labelrotation=90) + for ll, txt in enumerate(xlabel): + cc = "yellowgreen" if txt in met_o1 else ("plum" if txt in met_o2 else + ("gold" if txt in met_o3 else "turquoise")) + weight = "bold" if txt in bold_names else "normal" + if chigh is True: + boxdict = dict(lw=0, facecolor=cc, pad=1, alpha=1) + ax.text(ll + 0.5, -0.2, met_names[txt], fontsize=18, ha="right", va="top", rotation=45, color="k", + weight=weight, bbox=boxdict) + ax.text(-0.4, ll + 0.5, met_names[txt], fontsize=18, ha="right", va="center", color="k", weight=weight, + bbox=boxdict) + else: + ax.text(ll + 0.5, -0.2, met_names[txt], fontsize=18, ha="right", va="top", rotation=45, color="k", + weight=weight) + ax.text(-0.4, ll + 0.5, met_names[txt], fontsize=18, ha="right", va="center", color="k", weight=weight) + if cfram is True: + tmp1 = [txt for ll, txt in enumerate(xlabel) if txt in met_o1] + n1 = len(tmp1) + tmp2 = [txt for ll, txt in enumerate(xlabel) if txt in met_o2] + n2 = n1 + len(tmp2) + tmp3 = [txt for ll, txt in enumerate(xlabel) if txt in met_o3] + n3 = n2 + len(tmp3) + tmp4 = [txt for ll, txt in enumerate(xlabel) if txt in met_o4] + n4 = n3 + len(tmp4) + lic, nc = list(), 0 + if len(tmp1) > 0: + lic += ["yellowgreen"] * 4 + nc += 2 + if len(tmp2) > 0: + lic += ["plum"] * 4 + nc += 2 + if len(tmp3) > 0: + lic += ["gold"] * 4 + nc += 2 + if len(tmp4) > 0: + lic += ["turquoise"] * 4 + nc += 2 + lic = ["k"] * nc + lic + lis = ["-"] * len(lic) + liw = [4] * nc + [10] * (len(lic) - nc) + # horizontal and vertical black lines + lix, liy = list(), list() + if len(tmp1) > 0: + lix += [[n1, n1], [0, n4]] + liy += [[0, n4], [n1, n1]] + if len(tmp2) > 0: + lix += [[n2, n2], [0, n4]] + liy += [[0, n4], [n2, n2]] + if len(tmp3) > 0: + lix += [[n3, n3], [0, n4]] + liy += [[0, n4], [n3, n3]] + if len(tmp4) > 0: + lix += [[n1, n1], [0, n4]] + liy += [[0, n4], [n1, n1]] + # horizontal and vertical colored frame + if len(tmp1) > 0: + lix += [[0, 0], [n4, n4], [0, n1], [0, n1]] + liy += [[0, n1], [0, n1], [0, 0], [n4, n4]] + if len(tmp2) > 0: + add = 0.18 if len(lix) > 0 else 0 + lix += [[0, 0], [n4, n4], [n1 + add, n2], [n1 + add, n2]] + liy += [[n1 + add, n2], [n1 + add, n2], [0, 0], [n4, n4]] + if len(tmp3) > 0: + add = 0.18 if len(lix) > 0 else 0 + lix += [[0, 0], [n4, n4], [n2 + add, n3], [n2 + add, n3]] + liy += [[n2 + add, n3], [n2 + add, n3], [0, 0], [n4, n4]] + if len(tmp4) > 0: + add = 0.18 if len(lix) > 0 else 0 + lix += [[0, 0], [n4, n4], [n3 + add, n4], [n3 + add, n4]] + liy += [[n3 + add, n4], [n3 + add, n4], [0, 0], [n4, n4]] + for lc, ls, lw, lx, ly in zip(lic, lis, liw, lix, liy): + line = Line2D(lx, ly, c=lc, lw=lw, ls=ls, zorder=10) + line.set_clip_on(False) + ax.add_line(line) + # y axis + ax.set_yticks(xticks) + ax.set_yticklabels([""] * len(xticks)) + ax.tick_params(axis="y", labelsize=15) + # text + if write_corr is True: + for ii in range(len(tab_plot)): + for jj in range(len(tab_plot)): + plt.text(ii + 0.5, jj + 0.5, str(round(tab_rval[ii, jj], 1)), fontsize=10, ha="center", va="center") + if tab_pval is not None: + ax.text(len(tab_plot) + 1, 0, "nbr corr < 0", fontsize=15, ha="right", va="top", rotation=45) + ax.text(len(tab_plot) + 2, 0, "nbr corr > 0", fontsize=15, ha="right", va="top", rotation=45) + for ii in range(len(tab_plot)): + nbr1 = str(sum([1 for jj in range(len(tab_plot[ii])) if tab_plot[ii][jj] < 0])) + nbr2 = str(sum([1 for jj in range(len(tab_plot[ii])) if tab_plot[ii][jj] > 0])).zfill(2) + ax.text(len(tab_plot) + 0.5, ii + 0.5, nbr1, fontsize=15, ha="center", va="center") + ax.text(len(tab_plot) + 1 + 0.5, ii + 0.5, nbr2, fontsize=15, ha="center", va="center") + # color bar + levels = [round(ii, 1) for ii in NUMPYarange(-1, 1.1, 0.5)] + x2 = ax.get_position().x1 + y1 = ax.get_position().y0 + y2 = ax.get_position().y1 + if tab_pval is not None: + cax = plt.axes([x2 + 0.09, y1, 0.02, y2 - y1]) + else: + cax = plt.axes([x2 + 0.02, y1, 0.02, y2 - y1]) + cbar = plt.colorbar(cs, cax=cax, orientation="vertical", ticks=levels, pad=0.05, extend="both", aspect=40) + cbar.ax.tick_params(labelsize=18) + # save fig + if save_eps is True: + plt.savefig(figure_name + ".eps", bbox_inches="tight", format="eps") + else: + plt.savefig(figure_name, bbox_inches="tight") + plt.close() + return + + +def my_colorbar(cmap, mini=-1., maxi=1., nbins=20): + """ + Modifies given colobar instance (removes the extremes on each side of the colorbar) + + Inputs: + ------ + :param cmap: colormap instance + Colormap instance as defined in matplotlib (see matplotlib.cm.get_cmap) + **Optional arguments:** + :param mini: float + Minimum value of the colorbar. + :param maxi: float + Maximum value of the colorbar. + :param nbins: integer + Number of interval in the colorbar. + + Outputs: + ------- + :return newcmp1: object + Colormap, baseclass for all scalar to RGBA mappings + :return norm: object + Normalize, a class which can normalize data into the [0.0, 1.0] interval. + """ + levels = MaxNLocator(nbins=nbins).tick_values(mini, maxi) + newcmp1 = cmap(NUMPYlinspace(0.15, 0.85, 256)) + newcmp2 = cmap(NUMPYlinspace(0.0, 1.0, 256)) + newcmp1 = ListedColormap(newcmp1) + newcmp1.set_over(newcmp2[-30]) + newcmp1.set_under(newcmp2[29]) + newcmp1.set_bad(color="k") # missing values in black + norm = BoundaryNorm(levels, ncolors=newcmp1.N) + return newcmp1, norm + + +def plot_portraitplot(tab, figure_name, xticklabel=[], yticklabel=[], title=[], write_metrics=False, my_text="", + levels=None, cfram=False, chigh=False, save_eps=False, nbr_space=2): + """ + Plot the portraitplot (as in BAMS paper) + + Inputs: + ------ + :param tab: list of masked_array + List of masked_array containing metric collections values. + :param figure_name: string + Name of the output figure. + **Optional arguments:** + :param xticklabel: list of string, optional + List of the names to put as x ticks (metric names). + Default is [] nothing will be written). + :param yticklabel: list of string, optional + List of the names to put as y ticks (model names). + Default is [] nothing will be written). + :param title: list of string, optional + List of metric collection's title. + Default is [], no title will be written). + :param write_metrics: boolean, optional + True to write the metric value in each box. + Default is False (value not written). + :param my_text: string, optional + Text to add at the bottom right of the plot (I use it to indicate how CMIP6 models are marked in the plot). + Default is "" (no text will be written). + :param levels: list of floats, optional + Levels of the colorbar, if None is given, colobar ranges from -1 to 1. + Default is None ([-1.0, -0.5, 0.0, 0.5, 1.0] will be used). + :param chigh: boolean, optional + True to highlight labels in colors for each group of metrics. + Default is False (names written in black). + :param cfram: boolean, optional + True to color the frame for each group of metrics. + Default is False (frame is black). + :param save_eps: boolean, optional + True to save the plot in eps format instead of png. + Default is False (plot saved in png format). + :param nbr_space: integer, optional + Number of blank space between two metric collections. + Default is 2. + + Output: + ------ + """ + met_o1, met_o2, met_o3, met_o4 = return_metrics_type() + if levels is None: + levels = [-1.0, -0.5, 0.0, 0.5, 1.0] + fontdict = {"fontsize": 40, "fontweight": "bold"} + # nbr of columns of the portraitplot + nbrc = sum([len(tab[ii][0]) for ii in range(len(tab))]) + (len(tab) - 1) * nbr_space + # figure definition + fig = plt.figure(0, figsize=(0.5 * nbrc, 0.5 * len(tab[0]))) + gs = GridSpec(1, nbrc) + # adapt the colorbar + cmap, norm = my_colorbar(plt.get_cmap("cmo.balance"), mini=min(levels), maxi=max(levels)) + # loop on metric collections + count = 0 + for kk, tmp in enumerate(tab): + ax = plt.subplot(gs[0, count: count + len(tmp[0])]) + # shading + cs = ax.pcolormesh(tmp, cmap=cmap, norm=norm) + # title + xx1, xx2 = ax.get_xlim() + dx = 0.5 / (xx2 - xx1) + yy1, yy2 = ax.get_ylim() + dy = 0.5 / (yy2 - yy1) + try: ax.set_title(title[kk], fontdict=fontdict, y=1+dy, loc="center") + except: pass + # x axis + ticks = [ii + 0.5 for ii in range(len(tmp[0]))] + ax.set_xticks(ticks) + ax.set_xticklabels([] * len(ticks)) + if len(xticklabel[kk]) == len(tmp[0]): + for ll, txt in enumerate(xticklabel[kk]): + cc = "yellowgreen" if txt in met_o1 else ("plum" if txt in met_o2 else + ("gold" if txt in met_o3 else "turquoise")) + if chigh is True: + boxdict = dict(lw=0, facecolor=cc, pad=1, alpha=1) + ax.text(ll + 0.5, -0.2, met_names[txt], fontsize=18, ha="right", va="top", rotation=45, color="k", + bbox=boxdict) + else: + ax.text(ll + 0.5, -0.2, met_names[txt], fontsize=18, ha="right", va="top", rotation=45, color="k") + if cfram is True: + tmp1 = [met_o1, met_o2, met_o3, met_o4] + # draw vertical black lines to separate metric types + nn = 0 + lix = [[0, 0]] + for tt in tmp1: + tmp2 = [txt for ll, txt in enumerate(xticklabel[kk]) if txt in tt] + nn += len(tmp2) + if len(tmp2) > 0: + lix += [[nn, nn]] + del tmp2 + liy = [[0, len(tab[0])]] * len(lix) + lic, lis = ["k"] * len(lix), ["-"] * len(lix) + for lc, ls, lx, ly in zip(lic, lis, lix, liy): + line = Line2D(lx, ly, c=lc, lw=7, ls=ls, zorder=10) + line.set_clip_on(False) + ax.add_line(line) + # draw horizontal colored lines to indicate metric types + nn = 0 + lic, lix = list(), list() + for uu, tt in enumerate(tmp1): + tmp2 = [txt for ll, txt in enumerate(xticklabel[kk]) if + txt in tt or txt + "_1" in tt or txt + "_2" in tt] + if len(tmp2) > 0: + cc = "yellowgreen" if uu == 0 else ("plum" if uu == 1 else ("gold" if uu == 2 else "turquoise")) + lic += [cc, cc] + if nn > 0: + lix += [[nn + 0.2, nn + len(tmp2)], [nn + 0.2, nn + len(tmp2)]] + else: + lix += [[nn, nn + len(tmp2)], [nn, nn + len(tmp2)]] + nn += len(tmp2) + del cc + del tmp2 + liy = [[len(tab[0]), len(tab[0])], [0, 0]] * int(round(len(lix) / 2.)) + lis = ["-"] * len(lix) + for mm, (lc, ls, lx, ly) in enumerate(zip(lic, lis, lix, liy)): + if mm < 2: + line = Line2D([lx[0] + 0.05, lx[1]], ly, c=lc, lw=10, ls=ls, zorder=10) + elif mm > len(lis) - 3: + line = Line2D([lx[0], lx[1] - 0.05], ly, c=lc, lw=10, ls=ls, zorder=10) + else: + line = Line2D(lx, ly, c=lc, lw=10, ls=ls, zorder=10) + line.set_clip_on(False) + ax.add_line(line) + # y axis + ticks = [ii + 0.5 for ii in range(len(tmp))] + ax.set_yticks(ticks) + if kk != 0 or len(yticklabel) != len(tmp): + ax.set_yticklabels([""] * len(ticks)) + else: + ax.text(-5 * dx, -1 * dy, my_text, fontsize=25, ha="right", va="top", transform=ax.transAxes) + ax.tick_params(axis="y", labelsize=20) + ax.set_yticklabels(yticklabel) + ax.yaxis.set_label_coords(-20 * dx, 0.5) + # grid (create squares around metric values) + for ii in range(1, len(tmp)): + ax.axhline(ii, color="k", linestyle="-", linewidth=1) + for ii in range(1, len(tmp[0])): + ax.axvline(ii, color="k", linestyle="-", linewidth=1) + # write metric value in each square (standardized value!) + if write_metrics is True: + for jj in range(len(tmp[0])): + for ii in range(len(tmp)): + if tmp.mask[ii, jj] == False: + plt.text(jj + 0.5, ii + 0.5, str(round(tmp[ii, jj], 1)), fontsize=10, ha="center", va="center") + if kk == len(tab) - 1: + x2 = ax.get_position().x1 + y1 = ax.get_position().y0 + y2 = ax.get_position().y1 + count += len(tmp[0]) + nbr_space + # color bar + cax = plt.axes([x2 + 0.03, y1, 0.02, y2 - y1]) + cbar = plt.colorbar(cs, cax=cax, orientation="vertical", ticks=levels, pad=0.05, extend="both", aspect=40) + cbar.ax.set_yticklabels(["-2 $\sigma$", "-1", "MMV", "1", "2 $\sigma$"], fontdict=fontdict) + dict_arrow = dict(facecolor="k", width=8, headwidth=40, headlength=40, shrink=0.0) + dict_txt = dict(fontsize=40, rotation="vertical", ha="center", weight="bold") + cax.annotate("", xy=(3.7, 0.06), xycoords="axes fraction", xytext=(3.7, 0.45), arrowprops=dict_arrow) + cax.text(5.2, 0.45, "closer to reference", va="top", **dict_txt) + cax.annotate("", xy=(3.7, 0.94), xycoords="axes fraction", xytext=(3.7, 0.55), arrowprops=dict_arrow) + cax.text(5.2, 0.55, "further from reference", va="bottom", **dict_txt) + # save fig + if save_eps is True: + plt.savefig(figure_name + ".eps", bbox_inches="tight", format="eps") + else: + plt.savefig(figure_name, bbox_inches="tight") + plt.close() + return + + +def plot_projects_comparison(tab_val, figure_name, title="", x_name="", y_name="", xticklabel=[], yticklabel="", + colors=None, tab_bst=None, legend=None, chigh=False, cfram=False, + bold_names=[], save_eps=False): + """ + Plots the projects comparison, markers for each metric, solid markers mean that the difference if significant at the + 95% confidence level + + Inputs: + ------ + :param tab_val: list + 2D array with metric values averaged by projects, projects in the first axis, metrics in the second. + :param figure_name: string + Path to and name of the output plot. + **Optional arguments:** + :param title: string, optional + Title of the plot. + Default is "" (no title). + :param x_name: string, optional + Names of x axis. + Default is "" (no name written). + :param y_name: string, optional + Names of y axis. + Default is "" (no name written). + :param xticklabel: list of string, optional + List of the names to put as x ticks. + Default is [] (numbers from 0 to len(tab_val[0]) will be written). + :param yticklabel: string, optional + Name of the group used to normalized plot. + Default is "" (no name written). + :param colors: list of string, optional + List of colors (e.g., "k", "r") must be the size of the first dimension of tab_val. + Default is None (every group will be plotted in black). + :param tab_bst: list, optional + 3D array with confidence interval on the metric values averaged by projects, projects in the first axis, metrics + in the second, confidence interval in the third. + Default is None (no confidence interval plotted). + :param legend: list of string, optional + Name of the groups to plot the legend. + Default is None (no legend plotted). + :param chigh: boolean, optional + True to highlight labels in colors for each group of metrics. + Default is False (names written in black). + :param cfram: boolean, optional + True to color the frame for each group of metrics. + Default is False (frame is black). + :param bold_names: list, optional + List of names to write in bold (must be in xy_names). + Default is False (names written in normal font). + :param save_eps: boolean, optional + True to save the plot in eps format instead of png. + Default is False (plot saved in png format). + + Output: + ------ + :return: + """ + met_o1, met_o2, met_o3, met_o4 = return_metrics_type() + fig, ax = plt.subplots(figsize=(0.5 * len(tab_val[0]), 4)) + # title + plt.title(title, fontsize=20, y=1.01, loc='left') + # x axis + axis = list(range(len(tab_val[0]))) + ax.set_xticks(axis) + if isinstance(xticklabel, list): + ax.set_xticklabels([""] * len(xticklabel)) + for ll, txt in enumerate(xticklabel): + cc = "yellowgreen" if txt in met_o1 else ("plum" if txt in met_o2 else + ("gold" if txt in met_o3 else "turquoise")) + if chigh is True: + boxdict = dict(lw=0, facecolor=cc, pad=1, alpha=1) + ax.text(ll + 0.5, -0.03, met_names[txt], fontsize=18, ha="right", va="top", rotation=45, color="k", + bbox=boxdict) + else: + ax.text(ll + 0.5, -0.03, met_names[txt], fontsize=18, ha="right", va="top", rotation=45, color="k") + else: + ax.set_xticklabels(axis) + if cfram is True: + nn = 0 + lic, lix = list(), list() + for cc, tmp1 in zip(["yellowgreen", "plum", "gold", "turquoise"], [met_o1, met_o2, met_o3, met_o4]): + tmp2 = [txt for ll, txt in enumerate(xticklabel) if txt in tmp1] + if len(tmp2) > 0: + lic += [cc, cc] + if nn == 0: + lix += [[-0.4, len(tmp2) + 0.5], [-0.4, len(tmp2) - 0.5]] + nn += len(tmp2) - 0.5 + elif nn + len(tmp2) > len(xticklabel) - 1: + lix += [[nn, nn + len(tmp2) - 0.1], [nn, nn + len(tmp2) - 0.1]] + else: + lix += [[nn, nn + len(tmp2)], [nn, nn + len(tmp2)]] + nn += len(tmp2) + lis = ["-"] * len(lic) + liw = [5] * len(lic) + liy = [[2, 2], [0, 0]] * int(round(len(lic) / 2.)) + for lc, ls, lw, lx, ly in zip(lic, lis, liw, lix, liy): + line = Line2D(lx, ly, c=lc, lw=lw, ls=ls, zorder=10) + line.set_clip_on(False) + ax.add_line(line) + ax.set_xlim([min(axis) - 0.5, max(axis) + 0.5]) + ax.tick_params(axis="x", labelsize=12, labelrotation=90) + ax.set_xlabel(x_name, fontsize=15) + # y axis + ax.set_yticks([0.5, 1.5], minor=True) + ax.set_yticks([0, 1, 2], minor=False) + ax.set_yticklabels(["reference", yticklabel, "2 * " + yticklabel], + fontdict={"fontsize": 12, "fontweight": "normal"}) + ax.set_ylim([0, 2]) + ax.set_ylabel(y_name, fontsize=15) + # plot marker + for ii in range(len(tab_val)): + col = colors[len(colors) - 1 - ii] if isinstance(colors, list) else "k" + ind = len(tab_val) - 1 - ii + if tab_bst is not None: + for jj in range(len(tab_bst[ind])): + tmp1, tmp2 = tab_val[ind][jj], tab_bst[ind][jj] + if ind == 0: + tmp3, tmp4 = tab_val[1][jj], tab_bst[1][jj] + else: + tmp3, tmp4 = tab_val[0][jj], tab_bst[0][jj] + if (min(tmp4) <= tmp1 <= max(tmp4)) or (min(tmp2) <= tmp3 <= max(tmp2)): + ax.plot([jj], [tmp1], markersize=13, color="none", marker="D", fillstyle="none", + markeredgecolor=col, markeredgewidth=3, zorder=2) + else: + ax.scatter(jj, tmp1, s=200, c=col, marker="D", zorder=2) + if tmp2[0] > 0 and tmp2[1] > 0: + ax.add_line(Line2D([jj - 0.3, jj + 0.3], [tmp2[0], tmp2[0]], c=col, lw=2, zorder=3)) + ax.add_line(Line2D([jj - 0.3, jj + 0.3], [tmp2[1], tmp2[1]], c=col, lw=2, zorder=3)) + tmpl = [jj - 0.05, jj - 0.05] if ii == 0 else [jj + 0.05, jj + 0.05] + ax.add_line(Line2D(tmpl, [tmp2[0], tmp2[1]], c=col, lw=2, zorder=3)) + del tmpl + del tmp1, tmp2, tmp3, tmp4 + else: + ax.scatter(axis, list(tab_val[ind]), s=200, c=col, marker="D", zorder=2) + del col + # grid + ax.grid(linestyle="--", linewidth=1, axis="y", which="both", zorder=1) + # text + if legend is not None: + x1, x2 = ax.get_xlim() + dx = (x2 - x1) / 100. + y1, y2 = ax.get_ylim() + dy = (y2 - y1) / 100. + for ii in range(len(legend)): + col = colors[len(colors) - 1 - ii] if isinstance(colors, list) else "k" + font = {"color": col, "weight": "bold", "size": 15} + ax.text(x2 - 2 * dx, y2 - (ii + 1) * 8 * dy, legend[len(legend) - 1 - ii], ha="right", va="center", + fontdict=font) + del col, font + xxx, ddx, yyy, ddy = x1 + (2 * dx), deepcopy(dx), 1.75, 0.2 + ax.add_line(Line2D([xxx - ddx, xxx + ddx], [yyy + ddy, yyy + ddy], c=colors[1], lw=2)) + ax.add_line(Line2D([xxx - ddx, xxx + ddx], [yyy - ddy, yyy - ddy], c=colors[1], lw=2)) + ax.add_line(Line2D([xxx, xxx], [yyy - ddy, yyy + ddy], c=colors[1], lw=2)) + dicttext = {"horizontalalignment": "left", "verticalalignment": "center", + "fontdict": {'weight': 'normal', 'size': 12}, "transform": ax.transData} + ax.text(xxx + dx, yyy, "95% confidence interval of MMM\n(Monte Carlo sampling method)", **dicttext) + arrowdict = dict(facecolor="k", width=2, headwidth=10, headlength=10, shrink=0.0) + ax.annotate("", xy=(-0.05, 0.05), xycoords='axes fraction', xytext=(-0.05, 0.42), fontsize=13, + rotation="vertical", ha="center", va='bottom', arrowprops=arrowdict) + ax.text(-0.07, 0.42, "improved", fontsize=13, rotation="vertical", ha="center", va='top', + transform=ax.transAxes) + ax.annotate("", xy=(-0.05, 0.95), xycoords='axes fraction', xytext=(-0.05, 0.58), fontsize=13, + rotation="vertical", ha="center", va='top', arrowprops=arrowdict) + ax.text(-0.07, 0.58, "worsened", fontsize=13, rotation="vertical", ha="center", va='bottom', + transform=ax.transAxes) + # save fig + if save_eps is True: + plt.savefig(figure_name + ".eps", bbox_inches="tight", format="eps") + else: + plt.savefig(figure_name, bbox_inches="tight") + plt.close() + return diff --git a/build/lib/EnsoPlots/EnsoPlotToolsLib.py b/build/lib/EnsoPlots/EnsoPlotToolsLib.py new file mode 100644 index 0000000..d052db9 --- /dev/null +++ b/build/lib/EnsoPlots/EnsoPlotToolsLib.py @@ -0,0 +1,756 @@ +# -*- coding:UTF-8 -*- +from copy import deepcopy +from inspect import stack as INSPECTstack +from math import ceil as MATHceil +from math import floor as MATHfloor +from numpy import arange as NUMPYarange +from numpy import around as NUMPYaround +from numpy import array as NUMPYarray +from numpy import isnan as NUMPYisnan +from numpy import mean as NUMPYmean +from numpy import nan as NUMPYnan +from numpy import sort as NUMPYsort +from numpy import where as NUMPYwhere +from numpy.ma import masked_invalid as NUMPYma__masked_invalid +from numpy.random import randint as NUMPYrandom__randint +from scipy.stats import scoreatpercentile as SCIPYstats__scoreatpercentile + +# xarray based functions +from xarray import open_dataset +from xarray import where as XARRAYwhere + +# ENSO_metrics functions +from EnsoMetrics.EnsoCollectionsLib import ReferenceObservations +from EnsoMetrics.EnsoPlotLib import plot_param +from EnsoMetrics import EnsoErrorsWarnings + + +calendar_months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'] +observations = sorted(list(ReferenceObservations().keys()), key=lambda v: v.upper()) + +# metrics order +metrics_background = [ + "BiasPrLatRmse", "BiasPrLonRmse", "BiasSshLatRmse", "BiasSshLonRmse", "BiasSstLatRmse", "BiasSstLonRmse", + "BiasTauxLatRmse", "BiasTauxLonRmse", "SeasonalPrLatRmse", "SeasonalPrLonRmse", "SeasonalSshLatRmse", + "SeasonalSshLonRmse", "SeasonalSstLatRmse", "SeasonalSstLonRmse", "SeasonalTauxLatRmse", "SeasonalTauxLonRmse"] +metrics_basic = [ + "EnsoSstLonRmse", "EnsoPrTsRmse", "EnsoSstTsRmse", "EnsoTauxTsRmse", "EnsoAmpl", "EnsoSeasonality", "EnsoSstSkew", + "EnsoDuration", "EnsoSstDiversity", "EnsoSstDiversity_1", "EnsoSstDiversity_2", "NinoSstDiversity", + "NinoSstDiversity_1", "NinoSstDiversity_2"] +metrics_teleconnection = [ + "EnsoPrMapCorr", "EnsoPrMapRmse", "EnsoPrMapStd", "EnsoPrMapDjfCorr", "EnsoPrMapDjfRmse", "EnsoPrMapDjfStd", + "EnsoPrMapJjaCorr", "EnsoPrMapJjaRmse", "EnsoPrMapJjaStd", "EnsoSlpMapCorr", "EnsoSlpMapRmse", "EnsoSlpMapStd", + "EnsoSlpMapDjfCorr", "EnsoSlpMapDjfRmse", "EnsoSlpMapDjfStd", "EnsoSlpMapJjaCorr", "EnsoSlpMapJjaRmse", + "EnsoSlpMapJjaStd", "EnsoSstMapCorr", "EnsoSstMapRmse", "EnsoSstMapStd", "EnsoSstMapDjfCorr", "EnsoSstMapDjfRmse", + "EnsoSstMapDjfStd", "EnsoSstMapJjaCorr", "EnsoSstMapJjaRmse", "EnsoSstMapJjaStd"] +metrics_process = [ + "EnsoFbSstTaux", "EnsoFbTauxSsh", "EnsoFbSshSst", "EnsoFbSstThf", "EnsoFbSstSwr", "EnsoFbSstLhf", "EnsoFbSstLwr", + "EnsoFbSstShf", "EnsodSstOce", "EnsodSstOce_1", "EnsodSstOce_2"] +# models order +models_order = [ + "ACCESS1-0", "ACCESS1-3", "ACCESS-CM2", "ACCESS-ESM1-5", "BCC-CSM1-1", "BCC-CSM1-1-M", "BCC-CSM2-MR", "BCC-ESM1", + "BNU-ESM", "CAMS-CSM1-0", "CanCM4", "CanESM2", "CanESM5", "CanESM5-CanOE", "CCSM4", "CESM1-BGC", "CESM1-CAM5", + "CESM2", "CESM2-FV2", "CESM1-FASTCHEM", "CESM1-WACCM", "CESM2-WACCM", "CESM2-WACCM-FV2", "CMCC-CESM", "CMCC-CM", + "CMCC-CMS", "CNRM-CM5", "CNRM-CM5-2", "CNRM-CM6-1", "CNRM-CM6-1-HR", "CNRM-ESM2-1", "CSIRO-Mk3-6-0", + "CSIRO-Mk3L-1-2", "E3SM-1-0", "E3SM-1-1", "EC-EARTH", "EC-Earth3", "EC-Earth3-Veg", "FGOALS-f3-L", "FGOALS-g2", + "FGOALS-s2", "FIO-ESM", "GFDL-CM2p1", "GFDL-CM3", "GFDL-CM4", "GFDL-ESM2G", "GFDL-ESM2M", "GFDL-ESM4", + "GISS-E2-1-G", "GISS-E2-1-G-CC", "GISS-E2-H", "GISS-E2-H-CC", "GISS-E2-1-H", "GISS-E2-R", "GISS-E2-R-CC", "HadCM3", + "HadGEM2-AO", "HadGEM2-CC", "HadGEM2-ES", "HadGEM3-GC31-LL", "INMCM4", "INM-CM4-8", "INM-CM5-0", "IPSL-CM5A-LR", + "IPSL-CM5A-MR", "IPSL-CM5B-LR", "IPSL-CM6A-LR", "KACE-1-0-G", "MIROC4h", "MIROC5", "MIROC6", "MIROC-ESM", + "MIROC-ESM-CHEM", "MIROC-ES2L", "MPI-ESM-LR", "MPI-ESM-MR", "MPI-ESM-P", "MPI-ESM-1-2-HAM", "MPI-ESM1-2-HR", + "MPI-ESM1-2-LR", "MRI-CGCM3", "MRI-ESM1", "MRI-ESM2-0", "NESM3", "NorESM1-M", "NorESM1-ME", "NorCPM1", "NorESM2-LM", + "NorESM2-MM", "SAM0-UNICON", "TaiESM1", "UKESM1-0-LL"] + + +def bootstrap(tab, num_samples=1000000, alpha=0.05, nech=None, statistic=NUMPYmean): + """Returns bootstrap estimate of 100.0*(1-alpha) CI for statistic.""" + n = len(tab) + if nech is None: + nech = deepcopy(n) + idx = NUMPYrandom__randint(0, n, (num_samples, nech)) + samples = tab[idx] + stat = NUMPYsort(statistic(samples, 1)) + return [stat[int(round((alpha / 2.)*num_samples))], stat[int(round((1 - alpha / 2.)*num_samples))]] + + +def create_labels(label_name, label_ticks): + if label_name == "months": + if len(label_ticks) > 40: + mult = 6 + elif len(label_ticks) > 10: + mult = 4 + else: + mult = 3 + label_ticks = [ii for ii in label_ticks if ii % mult == 0] + label = [calendar_months[ii % 12] for ii in label_ticks] + elif label_name == "latitude": + if len(label_ticks) < 40: + mult = 10 + else: + mult = 20 + label_ticks = [ii for ii in label_ticks if ii % mult == 0] + if min(label_ticks) < 0 and max(label_ticks) > 0 and 0 not in label_ticks: + label_ticks = NUMPYarray(label_ticks) + while 0 not in label_ticks: + label_ticks = label_ticks + 1 + label = [str(abs(int(ii))) + '$^\circ$S' if ii < 0 else (str(abs(int(ii))) + '$^\circ$N' if ii > 0 else 'eq') + for ii in label_ticks] + elif label_name == "longitude": + if len(label_ticks) < 200: + mult = 40 + else: + mult = 90 + label_ticks = [ii for ii in label_ticks if ii % mult == 0] + if min(label_ticks) < 180 and max(label_ticks) > 180 and 180 not in label_ticks: + label_ticks = NUMPYarray(label_ticks) + while 180 not in label_ticks: + label_ticks = label_ticks + 10 + label = [str(int(ii)) + "$^\circ$E" if ii < 180 else ( + str(abs(int(ii) - 360)) + "$^\circ$W" if ii > 180 else "180$^\circ$") for ii in label_ticks] + return label_ticks, label + + +def create_levels(labelbar): + diff = round(float(labelbar[1] - labelbar[0]), 2) + if diff in [0.3, 0.6, 0.9] or diff % 3 == 0: + mult = 3 + elif diff in [0.1, 0.2, 0.4, 0.8, 1.0, 2, 4, 8, 10, 20, 40, 60, 80, 100]: + mult = 4 + elif diff in [0.5, 5, 25]: + mult = 5 + else: + mult = 6 + delta = float(diff) / mult + return [round(kk + jj * delta, 2) for kk in labelbar[:-1] for jj in range(mult)] + [labelbar[-1]] + + +def format_metric(metric_type, metric_value, metric_units): + if metric_type in ["CORR", "RMSE"]: + mytext = deepcopy(metric_type) + else: + if metric_type == "difference": + mytext = "model-ref" + elif metric_type == "ratio": + mytext = r"$\frac{model}{ref}$" + elif metric_type == "relative_difference": + mytext = r"$\frac{model-ref}{ref}$" + else: + mytext = r"$abs\left(\frac{model-ref}{ref}\right)$" + if metric_value is not None: + return mytext + ": " + "{0:.2f}".format(metric_value) + " " + metric_units + else: + return None + + +def get_reference(metric_collection, metric): + if metric_collection in ["ENSO_tel"] and "Map" in metric: + my_met = metric.replace("Corr", "").replace("Rmse", "").replace("Std", "") + else: + my_met = deepcopy(metric) + return plot_param(metric_collection, my_met)['metric_reference'] + + +def minimaxi(tab): + tmp = [my_mask(tmp, remove_masked=True) for tmp in tab] + tmp = [tt.min() for tt in tmp] + [tt.max() for tt in tmp] + return min(tmp), max(tmp) + + +def minmax_plot(tab, metric=False): + # define minimum and maximum + mini, maxi = minimaxi(tab) + if mini == maxi or abs(maxi-mini)/float(abs(maxi+mini))<1e-2: + tt = max(abs(mini), abs(maxi)) / 10. + tmp = int(str("%.e" % tt)[3:]) + if mini == maxi or (mini < 0 and maxi > 0): + tmp = 10**-tmp if tt < 1 else 10**tmp + elif mini >= 0: + tmp = 10**-tmp if tt < 1 else 10**tmp + else: + tmp = 10**-tmp if tt < 1 else 10**tmp + mini = 0 if mini > 0 and (mini-tmp)<0 else mini - tmp + maxi = 0 if maxi < 0 and (maxi+tmp)>0 else maxi + tmp + if mini < 0 and maxi > 0: + locmaxi = max([abs(mini), abs(maxi)]) + locmini = -deepcopy(locmaxi) + else: + locmini, locmaxi = deepcopy(mini), deepcopy(maxi) + # find the power of ten to get an interval between 1 and 10 + mult = pow(10, int(str("%e" % abs(locmaxi - locmini)).split('e')[1])) + locmini, locmaxi = int(MATHfloor(float(locmini) / mult)), int(MATHceil(float(locmaxi) / mult)) + if locmaxi == 2 and maxi < 15 and mult == 10 and abs(locmini) != locmaxi: + locmini, locmaxi = 0, 15 + mult = 1. + scalmini, scalemaxi = mini / mult, maxi / mult + interval = locmaxi - locmini + listbase = list(NUMPYaround([ii*10**exp for exp in range(-1, 1) for ii in range(1, 6)], decimals=1)) + listbase = listbase + listbase + listmult = [3] * int(round(len(listbase) / 2.)) + [4] * int(round(len(listbase) / 2.)) + list1 = list(NUMPYaround([listbase[ii] * listmult[ii] for ii in range(len(listbase))], decimals=1)) + list2 = list(NUMPYaround([abs(ii - interval) for ii in list1], decimals=1)) + interval = list1[list2.index(min(list2))] + base = listbase[list1.index(interval)] + if base * 4.5 < interval: + ii = 1 + tmp = sorted(list2) + while base * 4.5 < interval: + interval = list1[list2.index(tmp[ii])] + base = listbase[list1.index(interval)] + ii += 1 + if abs(locmini) == locmaxi: + maxi_out = 2 * base + while maxi_out - base > locmaxi: + maxi_out -= base + if metric is True and maxi_out < scalemaxi + base * 0.4: + maxi_out += base + mini_out = -maxi_out + else: + if locmini < 0 and locmaxi <= 0: + locmini, locmaxi = abs(locmaxi), abs(locmini) + sign = -1 + else: + sign = 1 + half_int = int(round(interval / 2.)) + tmp_middle = locmini + half_int + mini_out = max([0, tmp_middle - half_int]) + while mini_out > locmini: + mini_out -= base + while mini_out + base < locmini: + mini_out += base + maxi_out = mini_out + 2 * base + while maxi_out < locmaxi: + maxi_out += base + while maxi_out - base > locmaxi: + maxi_out -= base + minmax = list(NUMPYaround(NUMPYarray([mini_out, maxi_out]) * sign, decimals=0).astype(int)) + mini_out, maxi_out = min(minmax), max(minmax) + if metric is True: + if maxi_out < scalemaxi + base * 0.4: + maxi_out += base + tick_labels = NUMPYarange(mini_out, maxi_out + base / 2., base) + tick_labels = list(NUMPYaround(tick_labels * mult, decimals=4)) + if all([True if int(ii) == float(ii) else False for ii in tick_labels]): + tick_labels = list(NUMPYaround(tick_labels, decimals=0).astype(int)) + if len(tick_labels) > 7: + list_strings = [ + "WARNING" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": too many ticks for axis", + str().ljust(5) + str(len(tick_labels)) + " ticks: " + str(tick_labels), + str().ljust(5) + "there should not be more than 7" + ] + EnsoErrorsWarnings.my_warning(list_strings) + if min(tick_labels) > mini or max(tick_labels) < maxi: + list_strings = ["WARNING" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + + ": wrong bounds in ticks for axis"] + if min(tick_labels) > mini: + list_strings += [str().ljust(5) + "ticks minimum (" + str(min(tick_labels)) + ") > tab minimum (" + + str(mini) + ")"] + if max(tick_labels) < maxi: + list_strings += [str().ljust(5) + "ticks maximum (" + str(max(tick_labels)) + ") > tab maximum (" + + str(maxi) + ")"] + EnsoErrorsWarnings.my_warning(list_strings) + return tick_labels + + +def my_average(tab, axis=None, remove_masked=False): + tmp = my_mask(tab, remove_masked=remove_masked) + return NUMPYmean(tmp, axis=axis) + + +def my_bootstrap(tab1, tab2): + mea1 = float(NUMPYarray(tab1).mean()) + mea2 = float(NUMPYarray(tab2).mean()) + bst1 = bootstrap(NUMPYarray(tab1), nech=len(tab2)) + bst2 = bootstrap(NUMPYarray(tab2), nech=len(tab1)) + return bst1, bst2, mea1, mea2 + + +def my_legend(modname, obsname, filename_nc, models2=None, member=None, plot_metric=True, shading=False): + legend = ["ref: " + obsname] + if isinstance(filename_nc, str) is True or isinstance(filename_nc, str) is True: + if isinstance(modname, str) is True or isinstance(modname, str) is True: + if isinstance(member, str) is True or isinstance(member, str) is True: + legend += [modname + " " + member] + else: + legend += [modname] + else: + legend += ["model"] + else: + if shading is True and (isinstance(filename_nc, dict) is True or plot_metric is False): + if isinstance(member, list) is True and len(member) == len(modname): + legend += [mod.upper() + mem + " (" + str(len(models2[mod])) + ")" + for mod, mem in zip(modname, member)] + else: + legend += [mod.upper() + "(" + str(len(models2[mod])) + ")" for mod in modname] + elif shading is True and plot_metric is True: + if isinstance(member, list) is True and len(member) == len(modname): + legend += [mod.upper() + mem for mod, mem in zip(modname, member)] + else: + legend += [mod.upper() for mod in modname] + else: + if isinstance(member, list) is True and len(member) == len(modname): + legend += [mod + mem for mod, mem in zip(modname, member)] + else: + legend += modname + return legend + + +def my_mask(tab, remove_masked=False): + tmp = NUMPYarray(tab, dtype=float) + tmp = NUMPYwhere(tmp == None, NUMPYnan, tmp) + tmp = NUMPYma__masked_invalid(tmp) + if remove_masked is True: + # tmp = tmp[~tmp.mask] + tmp = tmp.compressed() + return tmp + + +def my_mask_map(tab_ref, tab_mod): + return XARRAYwhere(NUMPYisnan(tab_ref), NUMPYnan, tab_mod) + + +def read_diag(dict_diag, dict_metric, model, reference, metric_variables, shading=False, member=None): + if member is not None: + modelKeyName = model + "_" + member + else: + modelKeyName = model + if isinstance(model, str): + diag_mod = dict_diag[modelKeyName] + else: + if shading is True: + diag_mod =\ + [[dict_diag[mod][mm] for mm in sorted(list(dict_diag[mod].keys()), key=lambda v: v.upper())] for mod in modelKeyName] + else: + diag_mod = [dict_diag[mod] for mod in modelKeyName] + if shading is True: + my_ref = list(dict_diag["obs"].keys()) + else: + my_ref = list(dict_diag.keys()) + if reference in my_ref: + obs = deepcopy(reference) + else: + if len(metric_variables) == 1: + for obs in observations: + if obs in my_ref: + break + else: + for obs1 in observations: + for obs2 in observations: + obs = obs1 + "_" + obs2 + if obs in my_ref: + break + if shading is True: + diag_obs = dict_diag["obs"][obs] + else: + diag_obs = dict_diag[obs] + if isinstance(model, str): + metric_value = dict_metric[obs][model] + else: + if shading is True: + metric_value = [ + my_average([dict_metric[mod][obs][mm] + for mm in sorted(list(dict_metric[mod][obs].keys()), key=lambda v: v.upper())], + remove_masked=True) + for mod in model] + else: + metric_value = [dict_metric[obs][mod] for mod in model] + # metric_value = dict_metric[obs] # ["ref_" + obs] + return diag_mod, diag_obs, metric_value, obs + + +def read_obs(xml, variables_in_xml, metric_variables, varname, dict_metric, model): + if len(metric_variables) == 1: + for obs in observations: + newvar = varname + obs + if newvar in variables_in_xml: + break + else: + my_break = False + for obs1 in observations: + for obs2 in observations: + obs = obs1 + "_" + obs2 + newvar = varname + obs + if newvar in variables_in_xml: + my_break = True + break + if my_break is True: + break + # if "_lon__" in newvar or "_hov__" in newvar: + # list_strings = [ + # "WARNING" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": reader", + # str().ljust(5) + str(newvar) + " trick: read only between 140E and 96W", + # str().ljust(5) + "this should not stay like that!!!" + # ] + # EnsoErrorsWarnings.my_warning(list_strings) + # tab_out = xml[newvar].sel(longitude=slice(140, 264)) + # else: + # tab_out = xml[newvar] + tab_out = xml[newvar] + metric_value = dict_metric[obs][model]#["ref_" + obs] + return tab_out, metric_value, obs + + +def reader(filename_nc, model, reference, var_to_read, metric_variables, dict_metric, member=None, met_in_file=False, met_type=None, + met_pattern=""): + ff = open_dataset(filename_nc, decode_times=False) + variables_in_file = sorted([var for var in list(ff.keys())], key=lambda v: v.upper()) + # read model + tab_mod = list() + for var in var_to_read: + # if "_lon__" in var or "_hov__" in var: + # list_strings = [ + # "WARNING" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": reader", + # str().ljust(5) + str(var) + " trick: read only between 140E and 96W", + # str().ljust(5) + "this should not stay like that!!!" + # ] + # EnsoErrorsWarnings.my_warning(list_strings) + # tab_mod.append(ff[var + model].sel(longitude=slice(140, 264))) + # else: + # tab_mod.append(ff[var + model]) + varName_in_nc = var + model + if member is not None: + varName_in_nc += "_" + member + #tab_mod.append(ff[var + model]) + tab_mod.append(ff[varName_in_nc]) + # reab obs + tab_obs = list() + for var in var_to_read: + varobs = var + reference + if varobs in variables_in_file: + # if "_lon__" in varobs or "_hov__" in varobs: + # list_strings = [ + # "WARNING" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": reader", + # str().ljust(5) + str(varobs) + " trick: read only between 140E and 96W", + # str().ljust(5) + "this should not stay like that!!!" + # ] + # EnsoErrorsWarnings.my_warning(list_strings) + # tab = ff[varobs].sel(longitude=slice(140, 264)) + # else: + # tab = ff[varobs] + tab = ff[varobs] + metval = dict_metric[reference][model] # ["ref_" + reference] + obs = deepcopy(reference) + else: + tab, metval, obs = read_obs(ff, variables_in_file, metric_variables, var, dict_metric, model) + tab_obs.append(tab) + if isinstance(var_to_read, list) is True and len(var_to_read) == 1: + if met_in_file is True: + if isinstance(met_type, str): + for key in list(ff.attrs.keys()): + if met_type + "_" + obs + "_" + met_pattern == key: + val = ff.attrs[key] + try: val + except: val = None + metval = deepcopy(val) + del val + elif isinstance(met_type, list): + metval = list() + for mety in met_type: + for key in list(ff.attrs.keys()): + if mety + "_" + obs + "_" + met_pattern == key or (met_pattern == "" and mety + "_" + obs == key): + val = ff.attrs[key] + try: val + except: val = None + metval.append(val) + del val + elif isinstance(var_to_read, list) is True and len(var_to_read) == 2 and\ + ("nina" in var_to_read[0] or "nino" in var_to_read[0]): + metval = list() + for var in var_to_read: + add = "nina" if "nina" in var else "nino" + if met_in_file is True: + if isinstance(met_type, str): + for key in list(ff.attrs.keys()): + if met_type + "_" + obs + "_" + add + "_" + met_pattern == key: + val = ff.attrs[key] + try: val + except: val = None + metval.append(val) + del val + elif isinstance(met_type, list): + tmpval = list() + for mety in met_type: + for key in list(ff.attrs.keys()): + if mety + "_" + obs + "_" + add + "_" + met_pattern == key or ( + met_pattern == "" and mety + "_" + obs + "_" + add == key): + val = ff.attrs[key] + try: val + except: val = None + tmpval.append(val) + del val + metval.append(tmpval) + del tmpval + del add + ff.close() + return tab_mod, tab_obs, metval, obs + + +def read_var(var_to_read, filename_nc, model, reference, metric_variables, dict_metric, models2=None, member=None, + shading=False, met_in_file=False, met_type=None, met_pattern=""): + if isinstance(var_to_read, str): + var_to_read = [var_to_read] + if isinstance(filename_nc, str): + tab_mod, tab_obs, metval, obs = reader(filename_nc, model, reference, var_to_read, metric_variables, + dict_metric, member=member, met_in_file=met_in_file, met_type=met_type, + met_pattern=met_pattern) + else: + tab_mod, metval, obs = list(), list(), list() + if shading is True: + for jj in range(len(model)): + tmp1, tmp2 = list(), list() + for ii in range(len(filename_nc[model[jj]])): + ttt1, tab_obs, ttt2, tmp3 = reader( + filename_nc[model[jj]][ii], models2[model[jj]][ii], reference, var_to_read, metric_variables, + dict_metric[model[jj]], member=member, met_in_file=met_in_file, met_type=met_type, met_pattern=met_pattern) + tmp1.append(ttt1) + tmp2.append(ttt2) + obs.append(tmp3) + tab_mod.append(tmp1) + metval.append(tmp2) + else: + for ii in range(len(filename_nc)): + tmp1, tab_obs, tmp2, tmp3 = reader(filename_nc[ii], model[ii], reference, var_to_read, metric_variables, + dict_metric, member=member) + tab_mod.append(tmp1) + metval.append(tmp2) + obs.append(tmp3) + obs = list(set(obs)) + if len(obs) > 1: + list_strings = ["ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": too many obs", + str().ljust(5) + "var_to_read = "+str(var_to_read), + str().ljust(5) + "filename_nc = " + str(filename_nc), + str().ljust(5) + "model = " + str(model), + str().ljust(5) + "reference = " + str(reference)] + EnsoErrorsWarnings.my_error(list_strings) + else: + obs = obs[0] + return tab_mod, tab_obs, metval, obs + + +def shading_levels(tab, lev=[5, 25, 75, 95], axis=None): + return [SCIPYstats__scoreatpercentile(tab, ll, axis=axis) for ll in lev] + [my_average(tab, axis=axis)] + + +def remove_metrics(metrics_in, metric_collection, reduced_set=False, portraitplot=False): + """ + Removes some metrics from given list + + Inputs: + ------ + :param metrics_in: list of string + List of metrics. + :param metric_collection: string + Name of a metric collection. + **Optional arguments:** + :param reduced_set: boolean, optional + True to remove extra metrics that are not in the final set chosen by CLIVAR PRP. + If set to False it removes metrics that are in more than one metric collection. + Default value is False + :param portraitplot: boolean, optional + True to remove extra metrics that are not in the final set chosen by CLIVAR PRP but keep metrics that are in + more than one metric collection. + If set to False it removes metrics that are in more than one metric collection. + Default value is False + + Output: + ------ + :return metrics_out: list of string + Input list of metrics minus some metrics depending on given metric collection. + """ + metrics_out = deepcopy(metrics_in) + if reduced_set is True: + if portraitplot is True: + if metric_collection == "ENSO_perf": + to_remove = [ + 'BiasSshLatRmse', 'BiasSshLonRmse', 'BiasSstLatRmse', 'BiasTauxLatRmse', 'EnsoPrTsRmse', + 'EnsoSstDiversity_1', 'EnsoTauxTsRmse', 'NinaSstDur', 'NinaSstDur_1', + 'NinaSstDur_2', 'NinaSstLonRmse', 'NinaSstLonRmse_1', 'NinaSstLonRmse_2', 'NinaSstTsRmse', + 'NinaSstTsRmse_1', 'NinaSstTsRmse_2', 'NinoSstDiversity', 'NinoSstDiversity_1', + 'NinoSstDiversity_2', 'NinoSstDur', 'NinoSstDur_1', 'NinoSstDur_2', 'NinoSstLonRmse', + 'NinoSstLonRmse_1', 'NinoSstLonRmse_2', 'NinoSstTsRmse', 'NinoSstTsRmse_1', 'NinoSstTsRmse_2', + "SeasonalSshLatRmse", "SeasonalSshLonRmse", "SeasonalSstLatRmse", "SeasonalTauxLatRmse"] + elif metric_collection == "ENSO_proc": + to_remove = [ + 'BiasSshLonRmse', 'EnsodSstOce_1', 'EnsoFbSstLhf', 'EnsoFbSstLwr', 'EnsoFbSstShf', 'EnsoFbSstSwr'] + else: + to_remove = [ + 'EnsoPrMapCorr', 'EnsoPrMapRmse', 'EnsoPrMapStd', + 'EnsoPrMapDjfCorr', 'EnsoPrMapDjfStd', 'EnsoPrMapJjaCorr', 'EnsoPrMapJjaStd', 'EnsoSlpMapCorr', + 'EnsoSlpMapRmse', 'EnsoSlpMapStd', 'EnsoSlpMapDjfCorr', 'EnsoSlpMapDjfRmse', 'EnsoSlpMapDjfStd', + 'EnsoSlpMapJjaCorr', 'EnsoSlpMapJjaRmse', 'EnsoSlpMapJjaStd', 'EnsoSstMapCorr', 'EnsoSstMapRmse', + 'EnsoSstMapStd', 'EnsoSstMapDjfCorr', 'EnsoSstMapDjfStd', 'EnsoSstMapJjaCorr', 'EnsoSstMapJjaStd', + 'NinaPrMapCorr', 'NinaPrMap_1Corr', 'NinaPrMap_2Corr', 'NinaPrMapRmse', 'NinaPrMap_1Rmse', + 'NinaPrMap_2Rmse', 'NinaPrMapStd', 'NinaPrMap_1Std', 'NinaPrMap_2Std', 'NinaSlpMapCorr', + 'NinaSlpMap_1Corr', 'NinaSlpMap_2Corr', 'NinaSlpMapRmse', 'NinaSlpMap_1Rmse', 'NinaSlpMap_2Rmse', + 'NinaSlpMapStd', 'NinaSlpMap_1Std', 'NinaSlpMap_2Std', 'NinaSstLonRmse', 'NinaSstLonRmse_1', + 'NinaSstLonRmse_2', 'NinaSstMapCorr', 'NinaSstMap_1Corr', 'NinaSstMap_2Corr', 'NinaSstMapRmse', + 'NinaSstMap_1Rmse', 'NinaSstMap_2Rmse', 'NinaSstMapStd', 'NinaSstMap_1Std', 'NinaSstMap_2Std', + 'NinoPrMapCorr', 'NinoPrMap_1Corr', 'NinoPrMap_2Corr', 'NinoPrMapRmse', 'NinoPrMap_1Rmse', + 'NinoPrMap_2Rmse', 'NinoPrMapStd', 'NinoPrMap_1Std', 'NinoPrMap_2Std', 'NinoSlpMapCorr', + 'NinoSlpMap_1Corr', 'NinoSlpMap_2Corr', 'NinoSlpMap_1Rmse', 'NinoSlpMap_2Rmse', 'NinoSlpMapStd', + 'NinoSlpMap_1Std', 'NinoSlpMap_2Std', 'NinoSstLonRmse', 'NinoSstLonRmse_1', 'NinoSstLonRmse_2', + 'NinoSstMapCorr', 'NinoSstMap_1Corr', 'NinoSstMap_2Corr', 'NinoSstMapRmse', 'NinoSstMap_1Rmse', + 'NinoSstMap_2Rmse', 'NinoSstMapStd', 'NinoSstMap_1Std', 'NinoSstMap_2Std'] + else: + if metric_collection == "ENSO_perf": + to_remove = [ + 'BiasSshLatRmse', 'BiasSshLonRmse', 'BiasSstLatRmse', 'BiasTauxLatRmse', 'EnsoPrTsRmse', + 'EnsoSstDiversity_1', 'EnsoTauxTsRmse', 'NinaSstDur', 'NinaSstDur_1', + 'NinaSstDur_2', 'NinaSstLonRmse', 'NinaSstLonRmse_1', 'NinaSstLonRmse_2', 'NinaSstTsRmse', + 'NinaSstTsRmse_1', 'NinaSstTsRmse_2', 'NinoSstDiversity', 'NinoSstDiversity_1', + 'NinoSstDiversity_2', 'NinoSstDur', 'NinoSstDur_1', 'NinoSstDur_2', 'NinoSstLonRmse', + 'NinoSstLonRmse_1', 'NinoSstLonRmse_2', 'NinoSstTsRmse', 'NinoSstTsRmse_1', 'NinoSstTsRmse_2', + "SeasonalSshLatRmse", "SeasonalSshLonRmse", "SeasonalSstLatRmse", "SeasonalTauxLatRmse"] + elif metric_collection == "ENSO_proc": + to_remove = [ + 'BiasSshLonRmse', 'BiasSstLonRmse', 'BiasTauxLonRmse', 'EnsoAmpl', 'EnsoSeasonality', + 'EnsoSstLonRmse', 'EnsoSstSkew', 'EnsodSstOce_1', 'EnsoFbSstLhf', 'EnsoFbSstLwr', 'EnsoFbSstShf', + 'EnsoFbSstSwr'] + else: + to_remove = [ + 'EnsoAmpl', 'EnsoSeasonality', 'EnsoSstLonRmse', 'EnsoPrMapCorr', 'EnsoPrMapRmse', 'EnsoPrMapStd', + 'EnsoPrMapDjfCorr', 'EnsoPrMapDjfStd', 'EnsoPrMapJjaCorr', 'EnsoPrMapJjaStd', 'EnsoSlpMapCorr', + 'EnsoSlpMapRmse', 'EnsoSlpMapStd', 'EnsoSlpMapDjfCorr', 'EnsoSlpMapDjfRmse', 'EnsoSlpMapDjfStd', + 'EnsoSlpMapJjaCorr', 'EnsoSlpMapJjaRmse', 'EnsoSlpMapJjaStd', 'EnsoSstMapCorr', 'EnsoSstMapRmse', + 'EnsoSstMapStd', 'EnsoSstMapDjfCorr', 'EnsoSstMapDjfStd', 'EnsoSstMapJjaCorr', 'EnsoSstMapJjaStd', + 'NinaPrMapCorr', 'NinaPrMap_1Corr', 'NinaPrMap_2Corr', 'NinaPrMapRmse', 'NinaPrMap_1Rmse', + 'NinaPrMap_2Rmse', 'NinaPrMapStd', 'NinaPrMap_1Std', 'NinaPrMap_2Std', 'NinaSlpMapCorr', + 'NinaSlpMap_1Corr', 'NinaSlpMap_2Corr', 'NinaSlpMapRmse', 'NinaSlpMap_1Rmse', 'NinaSlpMap_2Rmse', + 'NinaSlpMapStd', 'NinaSlpMap_1Std', 'NinaSlpMap_2Std', 'NinaSstLonRmse', 'NinaSstLonRmse_1', + 'NinaSstLonRmse_2', 'NinaSstMapCorr', 'NinaSstMap_1Corr', 'NinaSstMap_2Corr', 'NinaSstMapRmse', + 'NinaSstMap_1Rmse', 'NinaSstMap_2Rmse', 'NinaSstMapStd', 'NinaSstMap_1Std', 'NinaSstMap_2Std', + 'NinoPrMapCorr', 'NinoPrMap_1Corr', 'NinoPrMap_2Corr', 'NinoPrMapRmse', 'NinoPrMap_1Rmse', + 'NinoPrMap_2Rmse', 'NinoPrMapStd', 'NinoPrMap_1Std', 'NinoPrMap_2Std', 'NinoSlpMapCorr', + 'NinoSlpMap_1Corr', 'NinoSlpMap_2Corr', 'NinoSlpMap_1Rmse', 'NinoSlpMap_2Rmse', 'NinoSlpMapStd', + 'NinoSlpMap_1Std', 'NinoSlpMap_2Std', 'NinoSstLonRmse', 'NinoSstLonRmse_1', 'NinoSstLonRmse_2', + 'NinoSstMapCorr', 'NinoSstMap_1Corr', 'NinoSstMap_2Corr', 'NinoSstMapRmse', 'NinoSstMap_1Rmse', + 'NinoSstMap_2Rmse', 'NinoSstMapStd', 'NinoSstMap_1Std', 'NinoSstMap_2Std'] + else: + if portraitplot is True: + to_remove = [] + else: + if metric_collection == "ENSO_perf": + to_remove = ['BiasSshLatRmse', 'BiasSshLonRmse', "SeasonalSshLatRmse", "SeasonalSshLonRmse"] + elif metric_collection == "ENSO_proc": + to_remove = ['BiasSshLonRmse', 'BiasSstLonRmse', 'BiasTauxLonRmse', 'EnsoAmpl', 'EnsoSeasonality', + 'EnsoSstLonRmse', 'EnsoSstSkew'] + else: + to_remove = ['EnsoAmpl', 'EnsoSeasonality', 'EnsoSstLonRmse', 'NinaSstLonRmse', 'NinaSstLonRmse_1', + 'NinaSstLonRmse_2', 'NinoSstLonRmse', 'NinoSstLonRmse_1', 'NinoSstLonRmse_2'] + for met in to_remove: + while met in metrics_out: + metrics_out.remove(met) + return metrics_out + + +def find_first_member(members_in): + """ + Finds first member name + + Inputs: + ------ + :param members_in: list of string + list of member names (e.g., "r1i1p1", "r1i1p2") + + Output: + ------ + :return member_out: string + first member of the given list + """ + if "r1i1p1" in members_in: + member_out = "r1i1p1" + elif "r1i1p1f1" in members_in: + member_out = "r1i1p1f1" + elif "r1i1p1f2" in members_in: + member_out = "r1i1p1f2" + else: + member_out = sort_members(members_in)[0] + return member_out + + +def return_metrics_type(): + return metrics_background, metrics_basic, metrics_teleconnection, metrics_process + + +def sort_members(members_in): + """ + Finds first member name + + Inputs: + ------ + :param members_in: list of string + list of member names (e.g., "r1i1p1", "r1i1p2") + + Output: + ------ + :return members_out: list of string + given list of member names sorted + """ + members_tmp = list() + for mem in members_in: + mem2 = mem.replace("r1i", "r01i").replace("r2i", "r02i").replace("r3i", "r03i").replace("r4i", "r04i") + mem2 = mem2.replace("r5i", "r05i").replace("r6i", "r06i").replace("r7i", "r07i").replace("r8i", "r08i") + mem2 = mem2.replace("r9i", "r09i") + mem2 = mem2.replace("i1p", "i01p").replace("i2p", "i02p").replace("i3p", "i03p").replace("i4p", "i04p") + mem2 = mem2.replace("i5p", "i05p").replace("i6p", "i06p").replace("i7p", "i07p").replace("i8p", "i08p") + mem2 = mem2.replace("i9p", "i09p") + if "f" in mem2: + mem2 = mem2.replace("p1f", "p01f").replace("p2f", "p02f").replace("p3f", "p03f").replace("p4f", "p04f") + mem2 = mem2.replace("p5f", "p05f").replace("p6f", "p06f").replace("p7f", "p07f").replace("p8f", "p08f") + mem2 = mem2.replace("p9f", "p09f") + mem2 = mem2.replace("f1", "f01").replace("f2", "f02").replace("f3", "f03").replace("f4", "f04") + mem2 = mem2.replace("f5", "f05").replace("f6", "f06").replace("f7", "f07").replace("f8", "f08") + mem2 = mem2.replace("f9", "f09") + else: + mem2 = mem2.replace("p1", "p01").replace("p2", "p02").replace("p3", "p03").replace("p4", "p04") + mem2 = mem2.replace("p5", "p05").replace("p6", "p06").replace("p7", "p07").replace("p8", "p08") + mem2 = mem2.replace("p9", "p09") + members_tmp.append(mem2) + members_tmp = sorted(list(set(members_tmp)), key=lambda v: v.upper()) + members_out = list() + for mem in members_tmp: + mem2 = mem.replace("r0", "r").replace("i0", "i").replace("p0", "p").replace("f0", "f") + members_out.append(mem2) + return members_out + + +def sort_metrics(metrics_in): + """ + Puts given list of models in a certain order (putting together model generations) + + Input: + ----- + :param metrics_in: list of string + List of metrics. + + Output: + ------ + :return metrics_out: list of string + Input list of metrics reordered + """ + # metric type and order + met_order = metrics_background + metrics_basic + metrics_teleconnection + metrics_process + metrics_out = sorted(list(set(metrics_in)), key=lambda v: v.upper()) + metrics_out = [met for met in met_order if met in metrics_out] + metrics_out += sorted(list(set(metrics_in) - set(metrics_out)), key=lambda v: v.upper()) + return metrics_out + + +def sort_models(models_in): + """ + Puts given list of models in a certain order (putting together model generations) + + Input: + ----- + :param models_in: list of string + List of models. + + Output: + ------ + :return models_out: list of string + Input list of models reordered + """ + # model order + models_out = sorted(list(set(models_in)), key=lambda v: v.upper()) + models_out = [mod for mod in models_order if mod in models_out] + models_out += sorted(list(set(models_in) - set(models_out)), key=lambda v: v.upper()) + return models_out diff --git a/build/lib/EnsoPlots/__init__.py b/build/lib/EnsoPlots/__init__.py new file mode 100644 index 0000000..55714cf --- /dev/null +++ b/build/lib/EnsoPlots/__init__.py @@ -0,0 +1 @@ +from .EnsoMetricPlot import * diff --git a/build/scripts-3.7/driver_metric_collection.py b/build/scripts-3.7/driver_metric_collection.py new file mode 100755 index 0000000..4f2bd4f --- /dev/null +++ b/build/scripts-3.7/driver_metric_collection.py @@ -0,0 +1,176 @@ +# -*- coding:UTF-8 -*- + +from getpass import getuser as GETPASSgetuser +import json +from os.path import join as OSpath__join + +# ENSO_metrics package +from EnsoMetrics.EnsoCollectionsLib import CmipVariables, defCollection, ReferenceObservations +from EnsoMetrics.EnsoComputeMetricsLib import ComputeCollection + +# set of functions to find cmip/obs files and save a json file +# to be adapted/changed by users depending on their environments +from driver_tools_lib import find_members, find_xml_cmip, find_xml_obs, save_json + + +# user (get your user name for the paths and to save the files) +user_name = GETPASSgetuser() + + +# ---------------------------------------------------# +# +# param needed to compute +# +# path where to save data +path_netcdf = "/data/" + user_name + "/ENSO_metrics/v20200311" + +# metric collection +mc_name = "ENSO_perf" + +# project / models +project = "CMIP5" # "CMIP6" +experiment = "hist" +frequency = "mon" +realm = "A" +list_models = ['IPSL-CM5B-LR'] +first_member_only = True + +# list of observations +if mc_name == 'ENSO_perf': + list_obs = ['20CRv2', 'AVISO', 'CMAP', 'ERA-Interim', 'ERSSTv5', 'GODAS', 'GPCPv2.3', 'HadISST', 'NCEP2', + 'SODA3.4.2', 'Tropflux'] +elif mc_name == 'ENSO_tel': + list_obs = ['20CRv2', 'CMAP', 'ERA-Interim', 'ERSSTv5', 'GPCPv2.3', 'HadISST', 'NCEP2', 'SODA3.4.2', 'Tropflux'] +elif mc_name == 'ENSO_proc': + list_obs = ['20CRv2', 'AVISO', 'ERA-Interim', 'ERSSTv5', 'GODAS', 'HadISST', 'NCEP2', 'SODA3.4.2', 'Tropflux'] +else: + list_obs = ['20CRv2', 'AVISO', 'CMAP', 'ERA-Interim', 'ERSSTv5', 'GODAS', 'GPCPv2.3', 'HadISST', 'NCEP2', + 'SODA3.4.2', 'Tropflux'] +# ---------------------------------------------------# + + +# ---------------------------------------------------# +# Main +# ---------------------------------------------------# +dict_mc = defCollection(mc_name) +list_metric = sorted(dict_mc["metrics_list"].keys()) + + +# +# list of variables needed for the given metric collection +# +list_variables = list() +for metric in list_metric: + listvar = dict_mc["metrics_list"][metric]["variables"] + for var in listvar: + if var not in list_variables: + list_variables.append(var) +list_variables = sorted(list_variables) + + +# +# finding file and variable name in file for each observational dataset +# +dict_obs = dict() +for obs in list_obs: + # be sure that the datasets is defined in EnsoCollectionsLib.ReferenceObservations + dict_var = ReferenceObservations(obs)["variable_name_in_file"] + dict_obs[obs] = dict() + for var in list_variables: + # + # finding variable name in file + # + try: var_in_file = dict_var[var]["var_name"] + except: + print(str(var) + " is not available for " + str(obs) + " or unscripted") + else: + try: + areacell_in_file = dict_var["areacell"]["var_name"] + except: + areacell_in_file = None + try: + landmask_in_file = dict_var["landmask"]["var_name"] + except: + landmask_in_file = None + if isinstance(var_in_file, list): + list_areacell, list_files, list_landmask, list_name_area, list_name_land = \ + list(), list(), list(), list(), list() + for var1 in var_in_file: + file_name, file_areacell, file_landmask = find_xml_obs(obs, var1) + list_files.append(file_name) + list_areacell.append(file_areacell) + list_name_area.append(areacell_in_file) + list_landmask.append(file_landmask) + list_name_land.append(landmask_in_file) + else: + file_name, file_areacell, file_landmask = find_xml_obs(obs, var_in_file) + list_files = file_name + list_areacell = file_areacell + list_name_area = areacell_in_file + list_landmask = file_landmask + list_name_land = landmask_in_file + dict_obs[obs][var] = {"path + filename": list_files, "varname": var_in_file, + "path + filename_area": list_areacell, "areaname": list_name_area, + "path + filename_landmask": list_landmask, "landmaskname": list_name_land} + + +# +# finding file and variable name in file for each models +# +dict_metric, dict_dive = dict(), dict() +dict_var = CmipVariables()["variable_name_in_file"] +for mod in list_models: + list_ens = find_members(experiment, frequency, mod, project, realm, first_only=first_member_only) + pattern_out = OSpath__join(path_netcdf, user_name + "_" + mc_name + "_" + mod + "_" + experiment) + dict_ens, dict_ens_dive = dict(), dict() + for ens in list_ens: + dict_mod = {mod + '_' + ens: {}} + for var in list_variables: + # + # finding variable name in file + # + var_in_file = dict_var[var]["var_name"] + try: + areacell_in_file = dict_var["areacell"]["var_name"] + except: + areacell_in_file = None + try: + landmask_in_file = dict_var["landmask"]["var_name"] + except: + landmask_in_file = None + if isinstance(var_in_file, list): + list_areacell, list_files, list_landmask, list_name_area, list_name_land = \ + list(), list(), list(), list(), list() + for var1 in var_in_file: + file_name, file_areacell, file_landmask = \ + find_xml_cmip(experiment, frequency, mod, project, realm, ens, var1) + list_files.append(file_name) + list_areacell.append(file_areacell) + list_name_area.append(areacell_in_file) + list_landmask.append(file_landmask) + list_name_land.append(landmask_in_file) + else: + file_name, file_areacell, file_landmask = \ + find_xml_cmip(experiment, frequency, mod, project, realm, ens, var_in_file) + list_files = file_name + list_areacell = file_areacell + list_name_area = areacell_in_file + list_landmask = file_landmask + list_name_land = landmask_in_file + dict_mod[mod + '_' + ens][var] =\ + {"path + filename": list_files, "varname": var_in_file, "path + filename_area": list_areacell, + "areaname": list_name_area, "path + filename_landmask": list_landmask, "landmaskname": list_name_land} + del areacell_in_file, file_areacell, file_landmask, file_name, landmask_in_file, list_areacell, list_files,\ + list_landmask, list_name_area, list_name_land, var_in_file + dictDatasets = {"model": dict_mod, "observations": dict_obs} + # Computes the metric collection + netcdf = pattern_out + "_" + ens + dict_ens[mod + "_" + ens], dict_ens_dive[mod + "_" + ens] =\ + ComputeCollection(mc_name, dictDatasets, mod + "_" + ens, netcdf=True, netcdf_name=netcdf, debug=False) + # save json + save_json({mod + "_" + ens: dict_ens[mod + "_" + ens]}, netcdf, metric_only=True) + with open(netcdf + "_raw.json", "w") as outfile: + json.dump(dict_ens[mod + "_" + ens], outfile, sort_keys=True) + del dict_mod, dictDatasets, netcdf + dict_metric[mod], dict_dive[mod] = dict_ens, dict_ens_dive + del dict_ens, dict_ens_dive, list_ens, pattern_out diff --git a/build/scripts-3.7/driver_plot_correlation.py b/build/scripts-3.7/driver_plot_correlation.py new file mode 100755 index 0000000..a139afa --- /dev/null +++ b/build/scripts-3.7/driver_plot_correlation.py @@ -0,0 +1,165 @@ +# -*- coding:UTF-8 -*- +# ---------------------------------------------------# +# Aim of the program: +# Create plots of the correlation inter metrics or inter models +# FIG. 6 in Planton et al. 2020: Evaluating climate models with the CLIVAR 2020 ENSO metrics package. BAMS +# It uses the first available member of each model or all members of each model and averages them +# Updated json files (needed to create this plot) can be downloaded from the page "Summary statistics in Interactive +# Portrait Plots" at https://cmec.llnl.gov/results/enso/ +# ---------------------------------------------------# + + +# ---------------------------------------------------# +# Import the right packages +# ---------------------------------------------------# + +from numpy.ma import masked_invalid as NUMPYma__masked_invalid +from numpy.ma import masked_where as NUMPYmasked_where +from numpy.ma import zeros as NUMPYma__zeros +from os.path import join as OSpath__join +from scipy.stats import linregress as SCIPYstats__linregress + +# set of functions to find cmip/obs files and save a json file +# to be adapted/changed by users depending on their environments +from driver_tools_lib import get_metric_values, get_mod_mem_json + +# ENSO_metrics functions +from EnsoPlots.EnsoPlotTemplate import plot_metrics_correlations +from EnsoPlots.EnsoPlotToolsLib import sort_metrics + + +# ---------------------------------------------------# +# Arguments +# ---------------------------------------------------# +# metric collections to plot +list_metric_collections = ["ENSO_perf", "ENSO_proc", "ENSO_tel"] +# CMIP experiment +experiment = "historical" +# project to use, here both CMIP5 and CMIP6 models will be used +list_projects = ["CMIP6", "CMIP5"] +# True to use the set of metric in the BAMS paper +# More metric have been computed and tested but not kept +reduced_set = True # False # +# True to use the first available member only +# If set to False, all members will be used and the metric values computed for all members of each model will be +# averaged +first_member = True # False # +# computation version, 'v20200427' is provided with the package +version = "v20200427" +# json files +dict_json = { + "CMIP5": { + "ENSO_perf": "share/EnsoMetrics/cmip5_historical_ENSO_perf_" + version + "_allModels_allRuns.json", + "ENSO_proc": "share/EnsoMetrics/cmip5_historical_ENSO_proc_" + version + "_allModels_allRuns.json", + "ENSO_tel": "share/EnsoMetrics/cmip5_historical_ENSO_tel_" + version + "_allModels_allRuns.json"}, + "CMIP6": { + "ENSO_perf": "share/EnsoMetrics/cmip6_historical_ENSO_perf_" + version + "_allModels_allRuns.json", + "ENSO_proc": "share/EnsoMetrics/cmip6_historical_ENSO_proc_" + version + "_allModels_allRuns.json", + "ENSO_tel": "share/EnsoMetrics/cmip6_historical_ENSO_tel_" + version + "_allModels_allRuns.json"}, + "obs2obs": { + "ENSO_perf": "share/EnsoMetrics/obs2obs_ENSO_perf_" + version + ".json", + "ENSO_proc": "share/EnsoMetrics/obs2obs_ENSO_proc_" + version + ".json", + "ENSO_tel": "share/EnsoMetrics/obs2obs_ENSO_tel_" + version + ".json"}} +# figure name +path_out = "" +figure_name = "metrics_correlations_" + str(len(list_metric_collections)) + "metric_collections_" + version +if len(list_projects) == 1: + figure_name += "_" + str(list_projects[0]) +else: + figure_name += "_" + str(len(list_projects)) + "cmip" +if first_member is True: + figure_name += "_first_member" +else: + figure_name += "_members_averaged" +if reduced_set is False: + figure_name += "_all_metrics" +figure_name = OSpath__join(path_out, figure_name) +# ---------------------------------------------------# + + +# ---------------------------------------------------# +# Functions +# ---------------------------------------------------# +def compute_correlation(tab_in): + """ + Computes correlations + + Input: + ----- + :param tab_in: `cdms2` variable + A `cdms2` variable containing the data to be analysed. + + Outputs: + ------- + :return rval: `cdms2` variable + A `cdms2` variable containing the correlation coefficients between the values along the first axis. + :return pval: `cdms2` variable + A `cdms2` variable containing the two-sided p-value for a hypothesis test whose null hypothesis is that the + slope is zero, using Wald Test with t-distribution of the test statistic. I.e., if the absolute value of the + correlation is smaller than the p-value, it means that the correlation is not significant. + """ + pval = NUMPYma__zeros((len(tab_in), len(tab_in))) + rval = NUMPYma__zeros((len(tab_in), len(tab_in))) + for ii in range(len(tab_in)): + tmp1 = tab_in[ii] + for jj in range(len(tab_in)): + tmp2 = tab[jj] + tmpf1 = NUMPYmasked_where(tmp2.mask, tmp1) + tmpf2 = NUMPYmasked_where(tmpf1.mask, tmp2) + tmpf1 = tmpf1.flatten().compressed() + tmpf2 = tmpf2.flatten().compressed() + slope, intercept, r_value, p_value, std_err = SCIPYstats__linregress(tmpf1, tmpf2) + pval[ii, jj] = float(p_value) + rval[ii, jj] = float(r_value) + del intercept, p_value, r_value, slope, std_err, tmp2, tmpf1, tmpf2 + del tmp1 + return rval, pval +# ---------------------------------------------------# + + +# ---------------------------------------------------# +# Main +# ---------------------------------------------------# +# get members by model by project from json file +# only metrics from models/members chosen here will be used +# all metrics from models/members chosen here will be used (ensures that if a model/member is not available for one or +# several metric collections, the corresponding line will still be created in the portraitplot) +model_by_proj = get_mod_mem_json(list_projects, list_metric_collections, dict_json, first_only=first_member) +# read json file +dict_met = dict() +for proj in list_projects: + for mc in list_metric_collections: + dict1 = get_metric_values(proj, mc, dict_json, model_by_proj, reduced_set=reduced_set) + # save in common dictionary + for mod in list(dict1.keys()): + try: dict_met[mod] + except: dict_met[mod] = dict1[mod] + else: + for met in list(dict1[mod].keys()): + dict_met[mod][met] = dict1[mod][met] + del dict1 + + +# ---------------------------------------------------# +# Plot +# ---------------------------------------------------# +if ' ': + list_metrics = list() + for k1 in list(dict_met.keys()): + list_metrics += list(dict_met[k1].keys()) + list_metrics = sort_metrics(list(set(list_metrics))) + list_models = list(dict_met.keys()) + # fill 2D-array with metric values + tab = NUMPYma__zeros((len(list_metrics), len(list_models))) + for ii, met in enumerate(list_metrics): + for jj, mod in enumerate(list_models): + if met not in list(dict_met[mod].keys()) or dict_met[mod][met] is None: + tab[ii, jj] = 1e20 + else: + tab[ii, jj] = dict_met[mod][met] + tab = NUMPYma__masked_invalid(tab) + tab = NUMPYmasked_where(tab == 1e20, tab) + # compute inter model correlations + rval, pval = compute_correlation(tab) + # plot metrics correlations + plot_metrics_correlations(rval, figure_name, list_metrics, tab_pval=pval, cfram=True, chigh=True) diff --git a/build/scripts-3.7/driver_plot_divedowns.py b/build/scripts-3.7/driver_plot_divedowns.py new file mode 100755 index 0000000..f78099a --- /dev/null +++ b/build/scripts-3.7/driver_plot_divedowns.py @@ -0,0 +1,125 @@ +# -*- coding:UTF-8 -*- +# ---------------------------------------------------# +# Aim of the program: +# Create divedown plots for ENSO_metrics +# netCDF files are not provided in the package to do these plots +# Updated json files (needed to create this plot) can be downloaded from the page "Summary statistics in Interactive +# Portrait Plots" at https://cmec.llnl.gov/results/enso/ +# ---------------------------------------------------# + + +# ---------------------------------------------------# +# Import the right packages +# ---------------------------------------------------# + +from copy import deepcopy +from glob import iglob as GLOBiglob +import json +from os.path import join as OSpath__join + +# ENSO_metrics functions +from EnsoPlots.EnsoMetricPlot import main_plotter +from EnsoPlots.EnsoPlotToolsLib import remove_metrics + + +# ---------------------------------------------------# +# Arguments +# ---------------------------------------------------# +metric_collection = "ENSO_tel" +project = "CMIP5" # "obs2obs" # +model = "CNRM-CM5" # "ERA-Interim_SODA3.4.2" # "ERA-Interim_ERA-Interim" # "ERA-Interim" # +experiment = "historical" +member = "r1i1p1" +dataname = deepcopy(model) if project == "obs2obs" else model + "_" + member +plot_ref = True if project == "obs2obs" else False +# True to use the set of metric in the BAMS paper +# More metric have been computed and tested but not kept +reduced_set = True # False # +# computation version, 'v20200427' is provided with the package +version = "v20200427" +# json files +dict_json = { + "CMIP5": { + "ENSO_perf": "share/EnsoMetrics/cmip5_historical_ENSO_perf_" + version + "_allModels_allRuns.json", + "ENSO_proc": "share/EnsoMetrics/cmip5_historical_ENSO_proc_" + version + "_allModels_allRuns.json", + "ENSO_tel": "share/EnsoMetrics/cmip5_historical_ENSO_tel_" + version + "_allModels_allRuns.json"}, + "CMIP6": { + "ENSO_perf": "share/EnsoMetrics/cmip6_historical_ENSO_perf_" + version + "_allModels_allRuns.json", + "ENSO_proc": "share/EnsoMetrics/cmip6_historical_ENSO_proc_" + version + "_allModels_allRuns.json", + "ENSO_tel": "share/EnsoMetrics/cmip6_historical_ENSO_tel_" + version + "_allModels_allRuns.json"}, + "obs2obs": { + "ENSO_perf": "share/EnsoMetrics/obs2obs_ENSO_perf_" + version + ".json", + "ENSO_proc": "share/EnsoMetrics/obs2obs_ENSO_proc_" + version + ".json", + "ENSO_tel": "share/EnsoMetrics/obs2obs_ENSO_tel_" + version + ".json"}} + +path_main = "/Users/yannplanton/Documents/Yann/Fac/2016_2018_postdoc_LOCEAN/2018_06_ENSO_metrics/2020_05_report" +path_nc = OSpath__join(path_main, "Data/" + project.lower() + "/" + experiment + "/" + metric_collection) +# figure name +path_out = "" +dataname2 = dataname.replace("GPCPv2.3", "GPCPv23").replace("SODA3.4.2", "SODA342") +figure_name = project.lower() + "_" + experiment + "_" + metric_collection + "_" + dataname2 +# ---------------------------------------------------# + + +# ---------------------------------------------------# +# Main +# ---------------------------------------------------# +# read json file +with open(dict_json[project][metric_collection]) as ff: + data_json = json.load(ff)['RESULTS']['model'][model][member] +ff.close() +del ff +# get metric names +list_metrics = sorted(list(data_json["value"].keys()), key=lambda v: v.upper()) +if reduced_set is True: + metrics = remove_metrics(list_metrics, metric_collection, reduced_set=reduced_set) +# pattern of netCDF files +pattern = project.lower() + "_" + experiment + "_" + metric_collection + "_" + version + "_" + dataname +# +# Loop on metrics +# +for met in ["EnsoPrMapDjfRmse"]:#list_metrics: + print(met) + # get NetCDF file name + met2 = met.replace("Rmse", "") if metric_collection in ["ENSO_tel"] and "Map" in met else deepcopy(met) + filename_nc = pattern + "_" + met2 + ".nc" + filename_nc = list(GLOBiglob(OSpath__join(path_nc, filename_nc)))[0] + # get diagnostic values for the given model and observations + dict_dia = data_json["value"][met]["diagnostic"] + diagnostic_values = dict((key1, dict_dia[key1]["value"]) for key1 in list(dict_dia.keys())) + diagnostic_units = data_json["metadata"]["metrics"][met]["diagnostic"]["units"] + # get metric values computed with the given model and observations + if metric_collection in ["ENSO_tel"] and "Map" in met: + list1, list2 = [met.replace("Rmse", "Corr"), met], ["metric", "metric"] + dict_met = data_json["value"] + metric_values = dict((key1, {model: [1-dict_met[su][ty][key1]["value"] if "Corr" in su else + dict_met[su][ty][key1]["value"]for su, ty in zip(list1, list2)]}) + for key1 in list(dict_met[list1[0]]["metric"].keys())) + metric_units = [data_json["metadata"]["metrics"][su]["metric"]["units"] for su in list1] + del list1, list2 + else: + dict_met = data_json["value"][met]["metric"] + metric_values = dict((key1, {model: dict_met[key1]["value"]}) for key1 in list(dict_met.keys())) + metric_units = data_json["metadata"]["metrics"][met]["metric"]["units"] + # figure name + name_png = figure_name + "_" + met + # this function needs: + # - the name of the metric collection: metric_collection + # - the name of the metric: metric + # - the name of the model: model + # - name of the experiment: experiment + # - name of the netCDF file name and path: filename_nc + # - a dictionary containing the diagnostic values: diagnostic_values (e.g., {"ERA-Interim": 1, "Tropflux": 1.1, + # model: 1.5}) + # - the diagnostic units: diagnostic_units + # - a dictionary containing the metric values: metric_values (e.g., {"ERA-Interim": {model: 1.5}, + # "Tropflux": {model: 1.36}}) + # - the metric units: metric_units + # - (optional) the member name, not needed if project aims to compare observational datasets (obs2obs): member + # - (optional) the path where to save the plots: path_png + # - (optional) the name of the plots: name_png + # - (optional) if the project aims to compare observational datasets (obs2obs): plot_ref + main_plotter(metric_collection, met2, model, experiment, filename_nc, diagnostic_values, diagnostic_units, + metric_values, metric_units, member=member, path_png=path_out, name_png=figure_name, plot_ref=plot_ref) + del diagnostic_values, diagnostic_units, dict_dia, dict_met, filename_nc, met2, metric_values, metric_units, \ + name_png diff --git a/build/scripts-3.7/driver_plot_metric_comparison.py b/build/scripts-3.7/driver_plot_metric_comparison.py new file mode 100755 index 0000000..256aa4d --- /dev/null +++ b/build/scripts-3.7/driver_plot_metric_comparison.py @@ -0,0 +1,197 @@ +# -*- coding:UTF-8 -*- +# ---------------------------------------------------# +# Aim of the program: +# Create plots to compare groups of models +# FIG. 3 in Planton et al. 2020: Evaluating climate models with the CLIVAR 2020 ENSO metrics package. BAMS +# It uses the first available member of each model or all members of each model and averages them +# Updated json files (needed to create this plot) can be downloaded from the page "Summary statistics in Interactive +# Portrait Plots" at https://cmec.llnl.gov/results/enso/ +# ---------------------------------------------------# + + +# ---------------------------------------------------# +# Import the right packages +# ---------------------------------------------------# + +from copy import deepcopy +from numpy import array as NUMPYarray +from numpy import mean as NUMPYmean +from numpy import moveaxis as NUMPYmoveaxis +from numpy.ma import masked_invalid as NUMPYma__masked_invalid +from numpy.ma import masked_where as NUMPYma__masked_where +from os.path import join as OSpath__join + +# set of functions to find cmip/obs files and save a json file +# to be adapted/changed by users depending on their environments +from driver_tools_lib import get_metric_values, get_mod_mem_json + +# ENSO_metrics functions +from EnsoPlots.EnsoPlotTemplate import plot_projects_comparison +from EnsoPlots.EnsoPlotToolsLib import bootstrap, sort_metrics + + +# ---------------------------------------------------# +# Arguments +# ---------------------------------------------------# +# metric collections to plot +list_metric_collections = ["ENSO_perf", "ENSO_proc", "ENSO_tel"] +# CMIP experiment +experiment = "historical" +# project to use, here both CMIP5 and CMIP6 models will be used +list_projects = ["CMIP6", "CMIP5"] +# True to use the set of metric in the BAMS paper +# More metric have been computed and tested but not kept +reduced_set = True # False # +# False to projects defined in 'list_project' +# If set to True, all projects defined in 'list_project' will be used as one and will be compared to a given selection +# of models (see 'my_project' and 'my_selection') +big_ensemble = False # True # +# marker colors +if big_ensemble is False: + colors = ["r", "dodgerblue"] +else: + colors = ["orange", "forestgreen"] +# True to use the first available member only +# If set to False, all members will be used and the metric values computed for all members of each model will be +# averaged +first_member = True # False # +# If 'big_ensemble' is set to True, 'BAMS_teleconnection' will be compared to 'CMIP', all projects defined in +# 'list_project' used as one +my_project = ["BAMS_teleconnection", "CMIP"] +# Definition of selection to use if 'big_ensemble' is set to True +my_selection = { + "BAMS_teleconnection": [ + 'CESM2', 'CESM2-FV2', 'CESM2-WACCM', 'CESM2-WACCM-FV2', 'CMCC-CM', 'CNRM-CM5', 'CNRM-CM5-2', 'EC-Earth3', + 'EC-Earth3-Veg', 'FGOALS-f3-L', 'FGOALS-s2', 'GFDL-CM4', 'GFDL-ESM4', 'MIROC-ES2L', 'MIROC6', 'NESM3', + 'NorESM2-MM']} +# List of additional observations +# the reading part is very 'ad hoc', do not change the obs! +list_obs = ["20CRv2", "NCEP2", "ERA-Interim"] +# computation version, 'v20200427' is provided with the package +version = "v20200427" +# json files +dict_json = { + "CMIP5": { + "ENSO_perf": "share/EnsoMetrics/cmip5_historical_ENSO_perf_" + version + "_allModels_allRuns.json", + "ENSO_proc": "share/EnsoMetrics/cmip5_historical_ENSO_proc_" + version + "_allModels_allRuns.json", + "ENSO_tel": "share/EnsoMetrics/cmip5_historical_ENSO_tel_" + version + "_allModels_allRuns.json"}, + "CMIP6": { + "ENSO_perf": "share/EnsoMetrics/cmip6_historical_ENSO_perf_" + version + "_allModels_allRuns.json", + "ENSO_proc": "share/EnsoMetrics/cmip6_historical_ENSO_proc_" + version + "_allModels_allRuns.json", + "ENSO_tel": "share/EnsoMetrics/cmip6_historical_ENSO_tel_" + version + "_allModels_allRuns.json"}, + "obs2obs": { + "ENSO_perf": "share/EnsoMetrics/obs2obs_ENSO_perf_" + version + ".json", + "ENSO_proc": "share/EnsoMetrics/obs2obs_ENSO_proc_" + version + ".json", + "ENSO_tel": "share/EnsoMetrics/obs2obs_ENSO_tel_" + version + ".json"}} +# figure name +path_out = "" +figure_name = "metrics_intercomparison_" + str(len(list_metric_collections)) + "metric_collections_" + version +if len(list_projects) == 1: + figure_name += "_" + str(list_projects[0]) +else: + figure_name += "_" + str(len(list_projects)) + "cmip" +if big_ensemble is False: + figure_name += "_" + list_projects[1] + "_vs_" + list_projects[0] +else: + figure_name += "_" + my_project[1] + "_vs_" + my_project[0] +if first_member is True: + figure_name += "_first_member" +else: + figure_name += "_members_averaged" +if reduced_set is False: + figure_name += "_all_metrics" +figure_name = OSpath__join(path_out, figure_name) +# ---------------------------------------------------# + + +# ---------------------------------------------------# +# Functions +# ---------------------------------------------------# +def common_save(dict_in, dict_out={}): + for mod in list(dict_in.keys()): + try: dict_out[mod] + except: dict_out[mod] = dict_in[mod] + else: + for met in list(dict_in[mod].keys()): + dict_out[mod][met] = dict_in[mod][met] + return dict_out +# ---------------------------------------------------# + + +# ---------------------------------------------------# +# Main +# ---------------------------------------------------# +# get members by model by project from json file +# only metrics from models/members chosen here will be used +# all metrics from models/members chosen here will be used (ensures that if a model/member is not available for one or +# several metric collections, the corresponding line will still be created in the portraitplot) +model_by_proj = get_mod_mem_json(list_projects, list_metric_collections, dict_json, first_only=first_member) +# read json file +dict_met = dict() +if big_ensemble is False: + for proj in list_projects: + dict_mc = dict() + for mc in list_metric_collections: + dict1 = get_metric_values(proj, mc, dict_json, model_by_proj, reduced_set=reduced_set) + # save in common dictionary + dict_mc = common_save(dict1, dict_out=dict_mc) + dict_met[proj] = dict_mc + del dict_mc +else: + dict_mc = dict() + for proj in list_projects: + for mc in list_metric_collections: + dict1 = get_metric_values(proj, mc, dict_json, model_by_proj, reduced_set=reduced_set) + # save in common dictionary + dict_mc = common_save(dict1, dict_out=dict_mc) + dict_met["CMIP"] = dict_mc + # put the selected models in a separate key + for mod in my_selection[my_project[0]]: + try: dict_met[my_project[0]] + except: dict_met[my_project[0]] = {mod: dict_met["CMIP"][mod]} + else: dict_met[my_project[0]][mod] = dict_met["CMIP"][mod] + del dict_mc + + +# ---------------------------------------------------# +# Plot +# ---------------------------------------------------# +if ' ': + list_metrics = list() + for k1 in list(dict_met.keys()): + for k2 in list(dict_met[k1].keys()): + list_metrics += list(dict_met[k1][k2].keys()) + list_metrics = sort_metrics(list(set(list_metrics))) + opposed_groups = deepcopy(list_projects) if big_ensemble is False else deepcopy(my_project) + # mean metric evaluation + tab_bst, tab_val = list(), list() + for met in list_metrics: + tab_tmp = list() + for grp in opposed_groups: + tab = list() + for mod in list(dict_met[grp].keys()): + if met in list(dict_met[grp][mod].keys()): + if dict_met[grp][mod][met] is not None and dict_met[grp][mod][met] != 1e20: + tab.append(dict_met[grp][mod][met]) + tab = NUMPYarray(tab) + tab_tmp.append(NUMPYma__masked_invalid(tab).compressed()) + del tab + tab1, tab2 = list(), list() + for ii in range(len(tab_tmp)): + tab1.append(float(NUMPYmean(tab_tmp[ii]))) + nbr = nbr = len(tab_tmp[1]) if ii==0 else len(tab_tmp[0]) + bst = bootstrap(tab_tmp[ii], nech=nbr) + tab2.append(bst) + del bst, nbr + tab_bst.append(tab2) + tab_val.append(tab1) + tab_bst = NUMPYmoveaxis(NUMPYarray(tab_bst), 0, 1) + tab_bst = NUMPYma__masked_where(tab_bst == 1e20, tab_bst) + tab_val = NUMPYmoveaxis(NUMPYarray(tab_val), 0, -1) + tmp = NUMPYmoveaxis(NUMPYarray([tab_val[1], tab_val[1]]), 0, 1) + tab_bst = tab_bst / tmp + tab_val = tab_val / tab_val[1] + # plot project comparison + plot_projects_comparison(tab_val, figure_name, xticklabel=list_metrics, yticklabel=opposed_groups[1].upper(), + colors=colors, tab_bst=tab_bst, legend=opposed_groups, chigh=True, cfram=True) + del list_metrics, opposed_groups, tab_bst, tab_val, tmp diff --git a/build/scripts-3.7/driver_plot_portraitplot.py b/build/scripts-3.7/driver_plot_portraitplot.py new file mode 100755 index 0000000..2c8ff6d --- /dev/null +++ b/build/scripts-3.7/driver_plot_portraitplot.py @@ -0,0 +1,173 @@ +# -*- coding:UTF-8 -*- +# ---------------------------------------------------# +# Aim of the program: +# Create portraitplot +# FIG. 2 in Planton et al. 2020: Evaluating climate models with the CLIVAR 2020 ENSO metrics package. BAMS +# It uses the first available member of each model or all members of each model and averages them +# Updated json files (needed to create this plot) can be downloaded from the page "Summary statistics in Interactive +# Portrait Plots" at https://cmec.llnl.gov/results/enso/ +# ---------------------------------------------------# + + +# ---------------------------------------------------# +# Import the right packages +# ---------------------------------------------------# + +from numpy import mean as NUMPYmean +from numpy import std as NUMPYstd +from numpy.ma import array as NUMPYma__array +from numpy.ma import masked_invalid as NUMPYma__masked_invalid +from numpy.ma import masked_where as NUMPYmasked_where +from numpy.ma import zeros as NUMPYma__zeros +from os.path import join as OSpath__join +import string + +# set of functions to find cmip/obs files and save a json file +# to be adapted/changed by users depending on their environments +from driver_tools_lib import get_metric_values, get_metric_values_observations, get_mod_mem_json + +# ENSO_metrics functions +from EnsoPlots.EnsoPlotTemplate import plot_portraitplot +from EnsoPlots.EnsoPlotToolsLib import sort_metrics, sort_models + + +# ---------------------------------------------------# +# Arguments +# ---------------------------------------------------# +# metric collections to plot +list_metric_collections = ["ENSO_perf", "ENSO_tel", "ENSO_proc"] +# CMIP experiment +experiment = "historical" +# project to use, here both CMIP5 and CMIP6 models will be used +list_projects = ["CMIP6", "CMIP5"] +# list of additional observations +# the reading part is very 'ad hoc', do not change the obs! +list_observations = ["20CRv2", "NCEP2", "ERA-Interim"] +# True to use the set of metric in the BAMS paper +# More metric have been computed and tested but not kept +reduced_set = True # False # +# True to use the first available member only +# If set to False, all members will be used and the metric values computed for all members of each model will be +# averaged +first_member = True # False # +# computation version, 'v20200427' is provided with the package +version = "v20200427" +# json files +dict_json = { + "CMIP5": { + "ENSO_perf": "share/EnsoMetrics/cmip5_historical_ENSO_perf_" + version + "_allModels_allRuns.json", + "ENSO_proc": "share/EnsoMetrics/cmip5_historical_ENSO_proc_" + version + "_allModels_allRuns.json", + "ENSO_tel": "share/EnsoMetrics/cmip5_historical_ENSO_tel_" + version + "_allModels_allRuns.json"}, + "CMIP6": { + "ENSO_perf": "share/EnsoMetrics/cmip6_historical_ENSO_perf_" + version + "_allModels_allRuns.json", + "ENSO_proc": "share/EnsoMetrics/cmip6_historical_ENSO_proc_" + version + "_allModels_allRuns.json", + "ENSO_tel": "share/EnsoMetrics/cmip6_historical_ENSO_tel_" + version + "_allModels_allRuns.json"}, + "obs2obs": { + "ENSO_perf": "share/EnsoMetrics/obs2obs_ENSO_perf_" + version + ".json", + "ENSO_proc": "share/EnsoMetrics/obs2obs_ENSO_proc_" + version + ".json", + "ENSO_tel": "share/EnsoMetrics/obs2obs_ENSO_tel_" + version + ".json"}} +# figure name +path_out = "" +figure_name = "portraitplot_" + str(len(list_metric_collections)) + "metric_collections_" + version +if len(list_projects) == 1: + figure_name += "_" + str(list_projects[0]) +else: + figure_name += "_" + str(len(list_projects)) + "cmip" +if first_member is True: + figure_name += "_first_member" +else: + figure_name += "_members_averaged" +if reduced_set is False: + figure_name += "_all_metrics" +figure_name = OSpath__join(path_out, figure_name) +# Metric collection names on the figure +metric_collection_names_for_plot = {"ENSO_perf": "Performance", "ENSO_proc": "Processes", "ENSO_tel": "Telecon."} +# ---------------------------------------------------# + + +# ---------------------------------------------------# +# Main +# ---------------------------------------------------# +# get members by model by project from json file +# only metrics from models/members chosen here will be used +# all metrics from models/members chosen here will be used (ensures that if a model/member is not available for one or +# several metric collections, the corresponding line will still be created in the portraitplot) +model_by_proj = get_mod_mem_json(list_projects, list_metric_collections, dict_json, first_only=first_member) +# read json file +tab_all, tab_all_act, x_names = list(), list(), list() +for mc in list_metric_collections: + dict_met = dict() + for proj in list_projects: + dict1 = get_metric_values(proj, mc, dict_json, model_by_proj, reduced_set=reduced_set, portraitplot=True) + # save in common dictionary + for mod in list(dict1.keys()): + dict_met[mod] = dict1[mod] + del dict1 + # models and metrics + tmp_models = sorted([str(mod) for mod in list(dict_met.keys())], key=lambda v: v.upper()) + my_metrics = list() + for mod in tmp_models: + try: list(dict_met[mod].keys()) + except: pass + else: my_metrics += list(dict_met[mod].keys()) + my_metrics = sort_metrics(sorted(list(set(my_metrics)), key=lambda v: v.upper())) + my_models = list(reversed(sort_models(tmp_models))) + # read other observational datasets compared to the reference + dict_ref_met = get_metric_values_observations(dict_json["obs2obs"][mc], list_observations, my_metrics, mc) + # number of line to add to the array (CMIP mean, reference, other observational datasets,...) + plus = 3 + len(list_observations) + # fill array + tab = NUMPYma__zeros((len(my_models) + plus, len(my_metrics))) + for ii, mod in enumerate(my_models): + for jj, met in enumerate(my_metrics): + if met not in list(dict_met[mod].keys()) or dict_met[mod][met] is None: + tab[ii + plus, jj] = 1e20 + else: + tab[ii + plus, jj] = dict_met[mod][met] + tab = NUMPYma__masked_invalid(tab) + tab = NUMPYmasked_where(tab == 1e20, tab) + # add values to the array (CMIP mean, reference, other observational datasets,...) + for jj, met in enumerate(my_metrics): + tmp = tab[plus:, jj].compressed() + mea = float(NUMPYmean(tmp)) + std = float(NUMPYstd(tmp)) + del tmp + for ii, dd in enumerate(list_observations + ["reference"] + list_projects): + if dd in list_observations: + val = dict_ref_met[dd][met] + elif dd in list_projects: + tmp = [tab[kk + plus, jj] for kk, mod in enumerate(my_models) if mod in list(model_by_proj[dd].keys())] + tmp = NUMPYma__masked_invalid(NUMPYma__array(tmp)) + tmp = NUMPYmasked_where(tmp == 1e20, tmp).compressed() + val = float(NUMPYmean(tmp)) + del tmp + else: + val = 0 + tab[ii, jj] = val + del val + # normalize + tab[:, jj] = (tab[:, jj] - mea) / std + del mea, std + tab = NUMPYma__masked_invalid(tab) + tab = NUMPYmasked_where(tab > 1e3, tab) + tab_all.append(tab) + x_names.append(my_metrics) + if mc == list_metric_collections[0]: + y_names = ["("+dd+")" for dd in list_observations] + ["(reference)"] + list_projects +\ + ["* " + mod if mod in list(model_by_proj["CMIP6"].keys()) else mod for mod in my_models] + del dict_met, dict_ref_met, my_metrics, my_models, plus, tab, tmp_models + + +# ---------------------------------------------------# +# Plot +# ---------------------------------------------------# +if ' ': + numbering = [ii+") " for ii in list(string.ascii_lowercase)] + # plot + title = [numbering[ii] + metric_collection_names_for_plot[mc] + for ii, mc in enumerate(list_metric_collections)] + text = "* = CMIP6\nmodel" + levels = list(range(-2, 3)) + plot_portraitplot(tab_all, figure_name, xticklabel=x_names, yticklabel=y_names, title=title, my_text=text, + levels=levels, cfram=True, chigh=True) + del levels, numbering, text, title diff --git a/build/scripts-3.7/driver_tools_lib.py b/build/scripts-3.7/driver_tools_lib.py new file mode 100755 index 0000000..02573de --- /dev/null +++ b/build/scripts-3.7/driver_tools_lib.py @@ -0,0 +1,602 @@ +# -*- coding:UTF-8 -*- + +from copy import deepcopy +from getpass import getuser as GETPASSgetuser +from cdms2 import open as CDMS2open +from inspect import stack as INSPECTstack +import json +from numpy import array as NUMPYarray +from os import environ as OSenviron +from os.path import join as OSpath__join +from sys import exit as SYSexit +from sys import path as SYSpath + +# ENSO_metrics package +from EnsoMetrics.EnsoCollectionsLib import ReferenceObservations +from EnsoPlots.EnsoPlotToolsLib import find_first_member, get_reference, remove_metrics, sort_members + +# user (get your user name for the paths and to save the files) +user_name = GETPASSgetuser() +# path +xmldir = OSenviron['XMLDIR'] +path_obs = "/data/" + user_name + "/Obs" +path_netcdf = "/data/" + user_name + "/ENSO_metrics/v20200311" + +# My (YYP) package +# set new path where to find programs +# SYSpath.insert(0, "/home/yplanton/New_programs/lib_cmip_bash") +# from getfiles_sh_to_py import find_path_and_files +# from getfiles_sh_to_py import get_ensembles + + +# ---------------------------------------------------# +# colors for printing +class bcolors: + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' +# ---------------------------------------------------# + + +def find_members(experiment, frequency, model, project, realm, first_only=False): + """ + Finds member names + + Inputs: + ------ + :param experiment: string + experiment name (e.g., "historical", "piControl") + :param frequency: string + data frequency: "day" for daily, "mon" for monthly + :param model: string + model name (e.g., "CNRM-CM5", "IPSL-CM5A-LR") + :param project: string + project name (e.g., "CMIP5", "CMIP6") + :param realm: string + data realm: "A" for atmosphere, "O" for ocean + **Optional arguments:** + :param first_only: boolean, optional + True to return only the first member + + Output: + ------ + :return members: list + list of member(s) for the given information + """ + members = get_ensembles(exp=experiment, fre=frequency, mod=model, pro=project, rea=realm) + if first_only is True: + members = [find_first_member(members)] + return members + + +def find_fx(model, experiment='', project='', realm='', ensemble=''): + """ + Finds fixed variables, here areacell and sftlf (landmask) + + Inputs: + ------ + :param model: string + model name (e.g., "CNRM-CM5", "IPSL-CM5A-LR") + **Optional arguments:** + :param experiment: string, optional + experiment name (e.g., "historical", "piControl") + :param project: string, optional + project name (e.g., "CMIP5", "CMIP6") + :param realm: string, optional + data realm: "A" for atmosphere, "O" for ocean + :param ensemble: string, optional + ensemble name (e.g., "r1i1p1", "r1i1p1f1") + + Outputs: + ------- + :return file_area: string + Path and areacell file name corresponding to the given information (e.g., /path/to/file/areacell.xml) + Set to None if the file cannot be found + :return file_land: string + Path and landmask file name corresponding to the given information (e.g., /path/to/file/landmask.xml) + Set to None if the file cannot be found + """ + if project in ["CMIP5", "CMIP6"]: + if project in ['CMIP5']: + my_ens = "r0i0p0" + else: + my_ens = deepcopy(ensemble) + if realm == "A": + farea1, farea2 = find_path_and_files(ens=my_ens, exp=experiment, fre="fx", mod=model, pro=project, + rea=realm, var="areacella") + fland1, fland2 = find_path_and_files(ens=my_ens, exp=experiment, fre="fx", mod=model, pro=project, + rea=realm, var="sftlf") + file_land = OSpath__join(fland1, fland2[0]) + else: + farea1, farea2 = find_path_and_files(ens=my_ens, exp=experiment, fre="fx", mod=model, pro=project, + rea=realm, var="areacello") + file_land = None + file_area = OSpath__join(farea1, farea2[0]) + else: + file_area, file_land = find_xml_fx(model, project=project, experiment=experiment, realm=realm) + try: CDMS2open(file_area) + except: file_area = None + try: CDMS2open(file_land) + except: file_land = None + return file_area, file_land + + +def find_xml_cmip(experiment, frequency, model, project, realm, ensemble, variable): + """ + Finds cmip variable file, as well as corresponding areacell and landmask + + Inputs: + ------ + :param experiment: string + experiment name (e.g., "historical", "piControl") + :param frequency: string + data frequency: "day" for daily, "mon" for monthly + :param model: string + model name (e.g., "CNRM-CM5", "IPSL-CM5A-LR") + :param project: string + project name (e.g., "CMIP5", "CMIP6") + :param realm: string + data realm: "A" for atmosphere, "O" for ocean + :param ensemble: string + ensemble name (e.g., "r1i1p1", "r1i1p1f1") + :param variable: string + variable name (e.g., "pr", "tos") + + Outputs: + ------- + :return file_name: string + Path and file name corresponding to the given information (e.g., /path/to/file/filename.xml) + :return file_area: string + Path and areacell file name corresponding to the given information (e.g., /path/to/file/areacell.xml) + Set to None if the file cannot be found + :return file_land: string + Path and landmask file name corresponding to the given information (e.g., /path/to/file/landmask.xml) + Set to None if the file cannot be found + """ + try: pathnc, filenc = find_path_and_files(ens=ensemble, exp=experiment, fre=frequency, mod=model, pro=project, + rea=realm, var=variable) + except: + if realm == "O": + new_realm = "A" + else: + new_realm = "O" + # if var is not in realm 'O' (for ocean), look for it in realm 'A' (for atmosphere), and conversely + try: pathnc, filenc = find_path_and_files(ens=ensemble, exp=experiment, fre=frequency, mod=model, pro=project, + rea=new_realm, var=variable) + except: + pathnc, filenc = None, [None] + # given variable is neither in realm 'A' nor 'O' + print(bcolors.FAIL + "%%%%% ----- %%%%%") + print("ERROR: function: " + str(INSPECTstack()[0][3]) + ", line: " + str(INSPECTstack()[0][2])) + print("given variable cannot be found in either realm A or O: " + str(variable)) + print("param: " + str(model) + ", " + str(project) + ", " + str(experiment) + ", " + str(ensemble) + + ", " + str(frequency) + ", " + str(realm)) + print("%%%%% ----- %%%%%" + bcolors.ENDC) + SYSexit("") + file_area, file_land =\ + find_fx(model, project=project, experiment=experiment, ensemble=ensemble, realm=new_realm) + else: + file_area, file_land = find_fx(model, project=project, experiment=experiment, ensemble=ensemble, realm=realm) + file_name = OSpath__join(pathnc, str(filenc[0])) + return file_name, file_area, file_land + + +def find_xml_obs(dataset, variable): + """ + Finds observational variable file, as well as corresponding areacell and landmask + + Inputs: + ------ + :param dataset: string + model name (e.g., "CNRM-CM5", "IPSL-CM5A-LR") + :param variable: string + variable name (e.g., "pr", "tos") + + Outputs: + ------- + :return file_name: string + Path and file name corresponding to the given information (e.g., /path/to/file/filename.xml) + :return file_area: string + Path and areacell file name corresponding to the given information (e.g., /path/to/file/areacell.xml) + Set to None if the file cannot be found + :return file_land: string + Path and landmask file name corresponding to the given information (e.g., /path/to/file/landmask.xml) + Set to None if the file cannot be found + """ + file_name = OSpath__join(xmldir, "obs_ENSO_metrics_" + str(dataset) + ".xml") + xml = CDMS2open(file_name) + listvar1 = sorted(xml.listvariables()) + if variable not in listvar1: + print(bcolors.FAIL + "%%%%% ----- %%%%%") + print(str().ljust(5) + "obs var " + str(variable) + " cannot be found") + print(str().ljust(10) + "file_name = " + str(file_name)) + print(str().ljust(10) + "variables = " + str(listvar1)) + print("%%%%% ----- %%%%%" + bcolors.ENDC) + SYSexit("") + file_area, file_land = find_fx(dataset) + return file_name, file_area, file_land + + +def find_xml_fx(model, experiment='', project='', realm=''): + """ + Finds fixed variables, here areacell and sftlf (landmask), mostly used for observational dataset + + Inputs: + ------ + :param model: string + model name (e.g., "CNRM-CM5", "IPSL-CM5A-LR") + **Optional arguments:** + :param experiment: string, optional + experiment name (e.g., "historical", "piControl") + :param project: string, optional + project name (e.g., "CMIP5", "CMIP6") + :param realm: string, optional + data realm: "A" for atmosphere, "O" for ocean + + Outputs: + ------- + :return file_area: string + Path and areacell file name corresponding to the given information (e.g., /path/to/file/areacell.xml) + Set to None if the file cannot be found + :return file_land: string + Path and landmask file name corresponding to the given information (e.g., /path/to/file/landmask.xml) + Set to None if the file cannot be found + """ + list_obs = list(ReferenceObservations().keys()) + if model in list_obs: + if user_name == "yplanton": + file_area = None + if model in ["20CRv2", "NCEP2"]: + file_land = OSpath__join(path_obs, model + "/land.sfc.gauss.nc") + elif model == "ERSSTv5": + file_land = OSpath__join(path_obs, model + "/lsmask_ERSSTv5.nc") + elif model == "CMAP": + file_land = OSpath__join(path_obs, model + "/lsmask_fx_cmap.nc") + elif model == "GPCPv2.3": + file_land = OSpath__join(path_obs, model + "/lsmask_fx_gpcpv2.3.nc") + elif model == "OISSTv2": + file_land = OSpath__join(path_obs, model + "/lsmask_fx_oisstv2.nc") + else: + file_land = None + else: + file_area = None + if model in ["20CRv2", "CMAP", "ERA-Interim", "ERSSTv5", "GPCPv2.3", "NCEP2"]: + file_land = OSpath__join(path_obs, "lsmask_" + model + ".nc") + else: + file_land = None + else: + file_area = OSpath__join(xmldir, str(model) + "_" + str(project) + "_" + str(experiment) + "_r0i0p0_glob_fx_" + + str(realm) + "_areacell.xml") + file_land = OSpath__join(xmldir, str(model) + "_" + str(project) + "_" + str(experiment) + "_r0i0p0_glob_fx_" + + str(realm) + "_landmask.xml") + return file_area, file_land + + +def get_metric_values(project, metric_collection, dict_json, dict_mod_mem, reduced_set=True, portraitplot=False): + """ + Finds fixed variables, here areacell and sftlf (landmask), mostly used for observational dataset + + Inputs: + ------ + :param project: strings + project name (e.g., "CMIP5", "CMIP6") + :param metric_collection: strings + metric collection (e.g., "ENSO_perf", "ENSO_proc", "ENSO_tel") + :param dict_json: dictionary + Dictionary with path and name of json files output of the CLIVAR PRP ENSO metrics package. + :param dict_mod_mem: dictionary + Dictionary with every models available and members in the given json files, for the given projects and metric + collections. + **Optional arguments:** + :param reduced_set: boolean, optional + True to remove extra metrics that are not in the final set chosen by CLIVAR PRP. + If set to False it removes metrics that are in more than one metric collection. + Default value is True. + :param portraitplot: boolean, optional + True to remove extra metrics that are not in the final set chosen by CLIVAR PRP but keep metrics that are in + more than one metric collection. + If set to False it removes metrics that are in more than one metric collection. + Default value is False. + + Output: + ------ + :return dict_out: dictionary + Dictionary with every models available and metrics, member values averaged + """ + + # open and read json file + data_json = read_json(dict_json[project][metric_collection]) + list_models = list(dict_mod_mem[project].keys()) + dict_out = dict() + for mod in list_models: + list_members = sort_members(dict_mod_mem[project][mod]) + dict2 = dict() + for mem in list_members: + try: data_mod = data_json[mod][mem]["value"] + except: data_mod = None + list_metrics = list() + try: list(data_mod.keys()) + except: pass + else: list_metrics += list(data_mod.keys()) + list_metrics = remove_metrics(list_metrics, metric_collection, reduced_set=reduced_set, + portraitplot=portraitplot) + dict3 = dict() + if len(list_metrics) > 0: + for met in list_metrics: + dict3[met] = data_mod[met]["metric"][get_reference(metric_collection, met)]["value"] + dict2[mem] = dict3 + del data_mod, dict3, list_metrics + # models and metrics + list_metrics = list() + for mem in list_members: + try: list(dict2[mem].keys()) + except: pass + else: list_metrics += list(dict2[mem].keys()) + # average member values if there is more than one available + dict_met = dict() + for met in list_metrics: + tmp = [dict2[mem][met] for mem in list_members + if dict2[mem][met] is not None and dict2[mem][met] != 1e20] + if len(tmp) > 0: + dict_met[met] = float(tmp[0]) if len(tmp) == 1 else float(NUMPYarray(tmp).mean()) + del tmp + dict_out[mod] = dict_met + del dict2, dict_met, list_members, list_metrics + return dict_out + + +def get_metric_values_observations(filename_json, obsvation_names, list_met, metric_collection): + """ + Reads given json file (must have usual jiwoo's structure) and read given obs + + Inputs: + ------ + :param filename_json: string + Path and name of a json file output of the CLIVAR 2020 ENSO metrics package. + :param obsvation_names: list of string + Names of wanted additional observations for the portrait plot. + :param list_met: list of string + List of metrics. + :param metric_collection: string + Name of a metric collection. + + Output: + ------ + :return data: list + Dictionary output of additional observations metric values. + """ + data_json = read_json(filename_json) + dict_out = dict() + for obs in obsvation_names: + for met in list_met: + ref = get_reference(metric_collection, met) + if obs == "20CRv2": + if "Ssh" not in met: + try: + tab = data_json["20CRv2"]["r1i1p1"]["value"][met]["metric"] + except: + tab = data_json["20CRv2_20CRv2"]["r1i1p1"]["value"][met]["metric"] + elif obs == "NCEP2": + if "TauxSsh" in met or "SshSst" in met: + tab = data_json["NCEP2_GODAS"]["r1i1p1"]["value"][met]["metric"] + elif "Ssh" in met: + tab = data_json["GODAS"]["r1i1p1"]["value"][met]["metric"] + else: + try: + tab = data_json["NCEP2"]["r1i1p1"]["value"][met]["metric"] + except: + tab = data_json["NCEP2_NCEP2"]["r1i1p1"]["value"][met]["metric"] + elif obs == "ERA-Interim": + if "SstMap" in met: + tab = {ref: {"value": 0}} + elif "TauxSsh" in met or "SshSst" in met: + tab = data_json["ERA-Interim_SODA3.4.2"]["r1i1p1"]["value"][met]["metric"] + elif "Ssh" in met: + tab = data_json["SODA3.4.2"]["r1i1p1"]["value"][met]["metric"] + else: + try: + tab = data_json["ERA-Interim"]["r1i1p1"]["value"][met]["metric"] + except: + tab = data_json["ERA-Interim_ERA-Interim"]["r1i1p1"]["value"][met]["metric"] + try: + val = tab[ref]["value"] + except: + val = 1e20 + try: + dict_out[obs] + except: + dict_out[obs] = {met: val} + else: + dict_out[obs][met] = val + try: + del tab + except: + pass + del ref, val + return dict_out + + +def get_mod_mem_json(projects, metric_collections, dict_json, first_only=False): + """ + Creates a dictionary with every models available in the given json files, for the given projects and metric + collections. + Also provides a list of available members. If first_only is True, provides only the first available member. + + dict_out = {'project1': {'model1': ['member1', 'member2', ...], + 'model2': ['member1', 'member2', ...], + ...}, + 'project2': {'model1': ['member1', 'member2', ...], + 'model2': ['member1', 'member2', ...], + ...}, + } + + Inputs: + ------ + :param projects: list of strings + list project names (e.g., "CMIP5", "CMIP6") + :param metric_collections: list of strings + list of metric collections (e.g., "ENSO_perf", "ENSO_proc", "ENSO_tel") + :param dict_json: dictionary + Dictionary with path and name of json files output of the CLIVAR PRP ENSO metrics package. + **Optional arguments:** + :param first_only: boolean, optional + True to return only the first member + + Output: + ------ + :return model_by_proj: dictionary + Dictionary with every models available and members in the given json files, for the given projects and metric + collections. + If first_only is True, provides only the first available member. + """ + # get members by model by project from json file + # only metrics from models/members chosen here will be used + # all metrics from models/members chosen here will be used (ensures that if a model/member is not available for one + # or several metric collections, the corresponding line will still be created in the portraitplot) + model_by_proj = dict() + for proj in projects: + list_models = list() + dict_members = dict() + for mc in metric_collections: + # read json files + tmp = read_json(dict_json[proj][mc]) + # list models + list_models += list(tmp.keys()) + # members + for mod in list(tmp.keys()): + try: + dict_members[mod] + except: + dict_members[mod] = list(tmp[mod].keys()) + else: + dict_members[mod] += list(tmp[mod].keys()) + del tmp + list_models = sorted(list(set(list_models)), key=lambda v: v.upper()) + list_to_remove = ["EC-EARTH", "FIO-ESM", "GFDL-CM2p1", "HadGEM2-AO", "CIESM", "E3SM-1-1-ECA", "FGOALS-g3", + "MCM-UA-1-0"] + # EC-EARTH: incorrect time coordinate + # FIO-ESM, HadCM3: grid issues + # GFDL-CM2p1: hfls not published + # HadGEM2-AO: rlus and rsus not published + # E3SM-1-1-ECA: Experimental stage + # CIESM, FGOALS-g3: ??? + # MCM-UA-1-0: unit issue with pr + for mod in list_to_remove: + while mod in list_models: + list_models.remove(mod) + for mod in list_models: + list_members = sorted(list(set(dict_members[mod])), key=lambda v: v.upper()) + if first_only is True: + list_members = [find_first_member(list_members)] + try: + model_by_proj[proj] + except: + model_by_proj[proj] = {mod: list_members} + else: + try: + model_by_proj[proj][mod] + except: + model_by_proj[proj][mod] = list_members + else: + print("this model should not be here") + del list_members + del dict_members, list_models, list_to_remove + return model_by_proj + + +def read_json(filename_json): + """ + Reads given json file (must have usual jiwoo's structure) + + Input: + ----- + :param filename_json: string + Path and name of a json file output of the CLIVAR PRP ENSO metrics package. + + Output: + ------ + :return data: dictionary + Dictionary output of the CLIVAR PRP ENSO metrics package, first level is models, second is members. + """ + with open(filename_json) as ff: + data = json.load(ff) + ff.close() + data = data["RESULTS"]["model"] + return data + + +def save_json(dict_in, json_name, metric_only=True): + """ + Saves given dictionary under given name in a json file + + Inputs: + ------ + :param dict_in: dictionary + data to save in a json file + :param json_name: string + Path and file name where to save the given data (e.g., /path/to/file/jsonname.json) + **Optional arguments:** + :param metric_only: boolean, optional + True to save only the metric values + + Output: + ------ + :return: + """ + # reshape dictionary + liste = sorted(dict_in.keys()) + listm = sorted(dict_in[liste[0]]['value'].keys()) + dict_out = dict() + for met in listm: + dict1 = dict() + for ens in liste: + # metadata (nyears) + dict_meta = dict() + for key1 in list(dict_in[ens]['metadata']['metrics'][met]['diagnostic'].keys()): + if key1 not in ['time_frequency', 'ref', 'method', 'method_nonlinearity', 'name']: + if key1 == "units": + dict_meta[key1] = dict_in[ens]['metadata']['metrics'][met]['diagnostic'][key1] + else: + dict_meta[key1] = dict_in[ens]['metadata']['metrics'][met]['diagnostic'][key1]['nyears'] + units = dict_in[ens]['metadata']['metrics'][met]['metric']['units'] + if metric_only is True: + # metrics + dict2 = dict() + for key1 in list(dict_in[ens]['value'][met]['metric'].keys()): + tmp = dict_in[ens]['value'][met]['metric'][key1]['value'] + tmp_key = key1.replace("ref_", "") + dict2[tmp_key] = {'metric': tmp, 'nyears_obs': dict_meta[tmp_key], 'units': units} + del tmp, tmp_key + else: + # metrics + dict2 = {'metric': {}, 'diagnostic': {}} + for key1 in list(dict_in[ens]['value'][met]['metric'].keys()): + tmp = dict_in[ens]['value'][met]['metric'][key1]['value'] + tmp_key = key1.replace("ref_", "") + dict2['metric'][tmp_key] = {'value': tmp, 'nyears_obs': dict_meta[tmp_key], 'units': units} + del tmp, tmp_key + # dive down diagnostics + for key1 in list(dict_in[ens]['value'][met]['diagnostic'].keys()): + tmp = dict_in[ens]['value'][met]['diagnostic'][key1]['value'] + if key1 == 'model': + dict2['diagnostic'][ens] = \ + {'value': tmp, 'nyears': dict_meta[key1], 'units': dict_meta['units']} + else: + dict2['diagnostic'][key1] = \ + {'value': tmp, 'nyears': dict_meta[key1], 'units': dict_meta['units']} + del tmp + dict1[ens] = dict2 + del dict_meta, dict2 + dict_out[met] = dict1 + del dict1 + # save as json file + if ".json" not in json_name: + json_name += ".json" + with open(json_name, "w") as outfile: + json.dump(dict_out, outfile, sort_keys=True) + return diff --git a/lib/EnsoComputeMetricsLib.py b/lib/EnsoComputeMetricsLib.py index d4b0039..503d884 100644 --- a/lib/EnsoComputeMetricsLib.py +++ b/lib/EnsoComputeMetricsLib.py @@ -361,6 +361,9 @@ def group_json_obs(pattern, json_name_out, metric_name): for met in sorted(list(data[dataset]["r1i1p1"]["value"].keys()), key=lambda v: v.upper()): if met not in list(dict_out[dataset]["r1i1p1"]["value"].keys()): dict_out[dataset]["r1i1p1"]["value"][met] = data[dataset]["r1i1p1"]["value"][met] + if met not in list(dict_out[dataset]["r1i1p1"]["metadata"]["metrics"].keys()): + dict_out[dataset]["r1i1p1"]["metadata"]["metrics"][met] = \ + data[dataset]["r1i1p1"]["metadata"]["metrics"][met] else: dict_out[dataset]["r1i1p1"]["value"][metric_name] = data[dataset]["r1i1p1"]["value"][ metric_name] diff --git a/lib/EnsoPlotLib.py b/lib/EnsoPlotLib.py index 665e35d..0b098be 100644 --- a/lib/EnsoPlotLib.py +++ b/lib/EnsoPlotLib.py @@ -2,7 +2,6 @@ # # Define ENSO metrics plots # -from copy import deepcopy from numpy import arange as NUMPYarange # ENSO_metrics functions from .EnsoCollectionsLib import defCollection diff --git a/lib/EnsoToolsLib.py b/lib/EnsoToolsLib.py index 47de61f..354ce4c 100644 --- a/lib/EnsoToolsLib.py +++ b/lib/EnsoToolsLib.py @@ -1,5 +1,4 @@ # -*- coding:UTF-8 -*- -from copy import deepcopy from inspect import stack as INSPECTstack from numpy import array as NUMPYarray from numpy import square as NUMPYsquare diff --git a/lib/EnsoUvcdatToolsLib.py b/lib/EnsoUvcdatToolsLib.py index e9efd6b..5322666 100644 --- a/lib/EnsoUvcdatToolsLib.py +++ b/lib/EnsoUvcdatToolsLib.py @@ -1,7 +1,7 @@ # -*- coding:UTF-8 -*- from calendar import monthrange -from copy import deepcopy +import copy from datetime import date from inspect import stack as INSPECTstack import ntpath @@ -312,9 +312,9 @@ def Concatenate(tab1, tab2, events1=[], events2=[]): tab_out = MV2concatenate((tab_out, MV2array([tab2[events2.index(yy)]]))) axes = CDMS2createAxis(MV2array(my_events_sort, dtype='int32'), id='years') if len(events1): - tmp = deepcopy(tab1) + tmp = copy.copy(tab1) else: - tmp = deepcopy(tab2) + tmp = copy.copy(tab2) att = tmp.attributes if len(tmp.shape) > 1: mask = tmp[0].mask @@ -860,7 +860,7 @@ def ApplyLandmask(tab, landmask, maskland=True, maskocean=False): else: landmask_nd = MV2zeros(tab.shape) if landmask_nd.shape == landmask.shape: - landmask_nd = deepcopy(landmask) + landmask_nd = copy.copy(landmask) else: try: landmask_nd[:] = landmask @@ -1306,15 +1306,15 @@ def fill_array(tab, units, freq): d2 = tab.getTime().asComponentTime()[ii].day if freq == 'yearly': if y2 == y1: - tab_out[ii:ii + len(tab)] = deepcopy(tab) + tab_out[ii:ii + len(tab)] = copy.copy(tab) break elif freq == 'monthly': if y2 == y1 and m2 == m1: - tab_out[ii:ii + len(tab)] = deepcopy(tab) + tab_out[ii:ii + len(tab)] = copy.copy(tab) break elif freq == 'daily': if y2 == y1 and m2 == m1 and d2 == d1: - tab_out[ii:ii + len(tab)] = deepcopy(tab) + tab_out[ii:ii + len(tab)] = copy.copy(tab) break return tab_out # compute composite @@ -1345,7 +1345,7 @@ def fill_array(tab, units, freq): units = 'days since ' + timebnds[0] units_out = 'days since 0001-01-01 12:00:00' if len(tmp1) == length: - tmp2 = deepcopy(tmp1) + tmp2 = copy.copy(tmp1) else: tmp2 = fill_array(tmp1, units, frequency) # save the selected time slice @@ -1356,7 +1356,7 @@ def fill_array(tab, units, freq): axis1 = CDMS2createAxis(list(range(len(composite[0]))), id='months') axis1.units = units_out axes = [axis0, axis1] - if tab.shape > 1: + if len(tab.shape) > 1: axes = axes + tab.getAxisList()[1:] composite.setAxisList(axes) else: @@ -1766,7 +1766,7 @@ def Normalize(tab, frequency): std = MV2zeros(new_tab[0].shape) for dd in range(time_steps_per_year): std[dd] = float(GENUTILstd(new_tab[:,dd], weights=None, axis=0, centered=1, biased=1)) - tab_out = deepcopy(tab) + tab_out = copy.copy(tab) for yy in range(len(tab) / time_steps_per_year): tab_out[yy * time_steps_per_year:(yy + 1) * time_steps_per_year] = \ tab_out[yy * time_steps_per_year:(yy + 1) * time_steps_per_year] / std @@ -2210,7 +2210,7 @@ def Regrid(tab_to_regrid, newgrid, missing=None, order=None, mask=None, regridde # if regridder == "cdms": axis = tab_to_regrid.getAxis(0) - idname = deepcopy(axis.id) + idname = copy.copy(axis.id) if len(tab_to_regrid.shape) == 3 and (axis.id == "months" or axis.id == "years"): axis.id = "time" tab_to_regrid.setAxis(0, axis) @@ -2420,7 +2420,7 @@ def SmoothGaussian(tab, axis=0, window=5): new_tab = tab.reorder(newOrder) # degree - degree = int(round(window / 2)) + degree = int(round((window - 1) / 2.)) # Create the gaussian weight array weightGauss = list() @@ -2481,6 +2481,7 @@ def SmoothSquare(tab, axis=0, window=5): list_strings = ["ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": axis", str().ljust(5) + "axis number too big: " + str(axis)] EnsoErrorsWarnings.my_error(list_strings) + # Reorder tab in order to put 'axis' in first position indices = list(range(len(tab.shape))) indices.remove(axis) @@ -2489,6 +2490,9 @@ def SmoothSquare(tab, axis=0, window=5): newOrder = newOrder+str(ii) new_tab = tab.reorder(newOrder) + # degree + degree = int(round((window - 1) / 2.)) + # Smoothing smoothed_tab = MV2zeros(new_tab.shape) smoothed_tab = smoothed_tab[:len(new_tab) - window + 1] @@ -2496,7 +2500,7 @@ def SmoothSquare(tab, axis=0, window=5): smoothed_tab[ii] = sum(new_tab[ii:ii + window]) / float(window) # Axes list - axes0 = new_tab[int(round(window / 2)): len(new_tab) - int(round(window / 2))].getAxisList()[0] + axes0 = new_tab[degree: len(new_tab) - degree].getAxisList()[0] if len(tab.shape) > 1: axes = [axes0] + new_tab.getAxisList()[1:] else: @@ -2536,6 +2540,7 @@ def SmoothTriangle(tab, axis=0, window=5): list_strings = ["ERROR" + EnsoErrorsWarnings.message_formating(INSPECTstack()) + ": axis", str().ljust(5) + "axis number too big: " + str(axis)] EnsoErrorsWarnings.my_error(list_strings) + # Reorder tab in order to put 'axis' in first position indices = list(range(len(tab.shape))) indices.remove(axis) @@ -2545,7 +2550,7 @@ def SmoothTriangle(tab, axis=0, window=5): new_tab = tab.reorder(newOrder) # degree - degree = int(round(window / 2)) + degree = int(round((window - 1) / 2.)) # Create the weight array (triangle) weight = list() @@ -2779,7 +2784,7 @@ def StdMonthly(tab): def TimeButNotTime(tab, new_time_name, frequency): - tab_out = deepcopy(tab) + tab_out = copy.copy(tab) time_num = get_num_axis(tab_out, 'time') timeax = tab_out.getAxis(time_num).asComponentTime() year1, month1, day1 = timeax[0].year, timeax[0].month, timeax[0].day @@ -2923,7 +2928,7 @@ def CustomLinearRegression(y, x, sign_x=0, return_stderr=True, return_intercept= stderr = CDMS2createVariable(MV2array(stderr), mask=mask, grid=grid, axes=axes, id='standart_error') intercept = CDMS2createVariable(MV2array(intercept), mask=mask, grid=grid, axes=axes, id='intercept') if return_stderr is False and return_intercept is False: - tab = deepcopy(slope) + tab = copy.copy(slope) else: tab = [slope] if return_stderr is True: @@ -3027,7 +3032,7 @@ def FindXYMinMaxInTs(tab, return_val='both', smooth=False, axis=0, window=5, met if smooth is True: tmp, unneeded = Smoothing(tab[tt], '', axis=axis, window=window, method=method) else: - tmp = deepcopy(tab[tt]) + tmp = copy.copy(tab[tt]) tab_ts.append(find_xy_min_max(tmp, return_val=return_val)) tab_ts = MV2array(tab_ts) tab_ts.setAxis(0, tab.getAxis(0)) @@ -3246,7 +3251,7 @@ def LinearRegressionTsAgainstTs(y, x, nbr_years_window, return_stderr=True, freq stderr_out.setAxisList([tmp_ax] + y.getAxisList()[1:]) for ii in range(nbr_timestep): tmp1 = tab_yy_mm[:, ii] - tmp2 = deepcopy(x) + tmp2 = copy.copy(x) yy1 = tab_yy_mm.getAxis(0)[0] yy2 = tmp2.getTime().asComponentTime()[0].year if yy1 == yy2: @@ -3268,7 +3273,7 @@ def LinearRegressionTsAgainstTs(y, x, nbr_years_window, return_stderr=True, freq # 'line2': "first year is " + str(yy2)} # EnsoErrorsWarnings.DebugMode('\033[93m', str(x.id) + " regressed against " + str(y.id), 25, **dict_debug) if tmp2.shape == tmp1.shape: - tmp3 = deepcopy(tmp2) + tmp3 = copy.copy(tmp2) else: tmp3 = MV2zeros(tmp1.shape) for jj in range(len(tmp3)): @@ -3491,7 +3496,7 @@ def Read_data_mask_area_multifile(file_data, name_data, type_data, variable, met def Read_mask_area(tab, name_data, file_data, type_data, region, file_area='', name_area='', file_mask='', name_mask='', maskland=False, maskocean=False, debug=False, **kwargs): - tab_out = deepcopy(tab) + tab_out = copy.copy(tab) keyerror1, keyerror2 = None, None # Read areacell if file_area: diff --git a/lib/version.py b/lib/version.py index 220f803..dbff009 100644 --- a/lib/version.py +++ b/lib/version.py @@ -1,3 +1,3 @@ -__version__ = 'v1.0-2020' -__git_tag_describe__ = 'v1.0-2020-2-g82896d0' -__git_sha1__ = '82896d0cb96585c87ca144a1515465eaf08dd874' +__version__ = '1.0-2020' +__git_tag_describe__ = '1.0-2020' +__git_sha1__ = b'c626e9760e8b40061f741188c5226eb4df943e82' diff --git a/setup.py b/setup.py index e1cd269..7f4d398 100755 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -from __future__ import print_function from distutils.core import setup import subprocess import glob @@ -29,13 +28,13 @@ stdout=subprocess.PIPE, stderr=subprocess.PIPE) try: - commit = p.stdout.readlines()[0].split()[1] + commit = str(p.stdout.readlines()[0].split()[1]) except: commit = "" f = open("lib/version.py", "w") print("__version__ = '%s'" % Version, file=f) print("__git_tag_describe__ = '%s'" % descr, file=f) -print("__git_sha1__ = '%s'" % commit, file=f) +print("__git_sha1__ = %s" % commit, file=f) f.close() # data_files = (