11"""
22Compare the model 2005-019 sea surface temperature trends from OISST and GLORYS.
33How to use:
4- python sst_trends.py /archive/acr/fre/NWA/2023_04/NWA12_COBALT_2023_04_kpo4-coastatten-physics/gfdl.ncrc5-intel22-prod
4+ python sst_trends.py -p /archive/acr/fre/NWA/2023_04/NWA12_COBALT_2023_04_kpo4-coastatten-physics/gfdl.ncrc5-intel22-prod -c config.yaml
55"""
66import cartopy .crs as ccrs
77from cartopy .mpl .geoaxes import GeoAxes
1010from mpl_toolkits .axes_grid1 import AxesGrid
1111import numpy as np
1212import xarray
13- import xesmf
13+ import logging
1414
15- from plot_common import autoextend_colorbar , corners , get_map_norm , annotate_skill , open_var , save_figure
15+ from plot_common import ( autoextend_colorbar , corners , get_map_norm ,
16+ annotate_skill , open_var , save_figure , load_config ,
17+ process_glorys , process_oisst )
1618
17- PC = ccrs .PlateCarree ()
19+ # Configure logging for sst_eval
20+ logger = logging .getLogger (__name__ )
21+ logging .basicConfig (filename = "sst_trends.log" , format = '%(asctime)s %(levelname)s:%(name)s: %(message)s' ,level = logging .INFO )
1822
19-
20- def get_3d_trends (x , y ):
21- x = np .array (x )
23+ def get_3d_trends (y ):
24+ x = np .array ( y ['time.year' ] )
2225 y2 = np .array (y ).reshape ((len (x ), - 1 ))
2326 coefs = np .polyfit (x , y2 , 1 )
24- trends = coefs [0 , :].reshape (y .shape [1 :])
27+ trends = coefs [0 , :].reshape (y .shape [1 :]) * 10 # -> C / decade
28+
2529 return trends
2630
2731
28- def plot_sst_trends (pp_root , label ):
32+ def plot_sst_trends (pp_root , label , config ):
2933 model = (
3034 open_var (pp_root , 'ocean_monthly' , 'tos' )
31- .sel (time = slice ('2005' , '2019' ))
35+ .sel (time = slice (config [ 'start_year' ], config [ 'end_year' ] ))
3236 .resample (time = '1AS' )
3337 .mean ('time' )
3438 .load ()
3539 )
36- model_grid = xarray .open_dataset ('../data/geography/ocean_static.nc' )
40+ logger .info ("MODEL: %s" ,model )
41+ model_grid = xarray .open_dataset ( config ['model_grid' ])
42+ logger .info ("MODEL_GRID: %s" ,model_grid )
43+
44+ # Verify that xh/yh are set as coordinates, then make sure model coordinates match grid data
45+ model_grid = model_grid .assign_coords ( {'xh' :model_grid .xh , 'yh' :model_grid .yh } )
46+ model = xarray .align (model_grid , model , join = 'override' , exclude = 'time' )[1 ]
47+ logger .info ("Successfully modified coordinates of model grid, and aligned model coordinates to grid coordinates" )
3748
38- model_trend = get_3d_trends (model ['time.year' ], model ) * 10 # -> C / decade
49+ model_trend = get_3d_trends (model )
50+ # Convert to Data Array, since xskillscore expects dataarrays to calculate skill metrics
3951 model_trend = xarray .DataArray (model_trend , dims = ['yh' , 'xh' ], coords = {'yh' : model .yh , 'xh' : model .xh })
52+ logger .info ("MODEL_TREND: %s" , model_trend )
4053
41- oisst = (
42- xarray .open_mfdataset ([f'/work/acr/oisstv2/sst.month.mean.{ y } .nc' for y in range (2005 , 2020 )])
43- .sst
44- .sel (lat = slice (0 , 60 ), lon = slice (360 - 100 , 360 - 30 ))
45- .resample (time = '1AS' )
46- .mean ('time' )
47- .load ()
48- )
49- oisst_trend = get_3d_trends (oisst ['time.year' ], oisst ) * 10 # -> C / decade
54+ target_grid = model_grid [ config ['rename_map' ].keys () ].rename ( config ['rename_map' ] )
5055
51- glorys = (
52- xarray .open_dataset ('/work/acr/mom6/diagnostics/glorys/glorys_sfc.nc' )
53- ['thetao' ]
54- .sel (time = slice ('2005' , '2019' ))
55- .resample (time = '1AS' )
56- .mean ('time' )
57- )
58- glorys_trend = get_3d_trends (glorys ['time.year' ], glorys ) * 10 # -> C / decade
56+ # Process OISST and get trend
57+ mom_rg , oisst , oisst_lonc , oisst_latc = process_oisst (config , target_grid , model_trend , start = int (config ['start_year' ]),
58+ end = int (config ['end_year' ])+ 1 , resamp_freq = '1AS' )
59+ logger .info ("OISST: %s" , oisst )
60+ oisst_trend = get_3d_trends (oisst )
61+ oisst_trend = xarray .DataArray (oisst_trend , dims = ['lat' ,'lon' ], coords = {'lat' :oisst .lat ,'lon' :oisst .lon } )
62+ logger .info ("OISST_TREND: %s" ,oisst_trend )
5963
60- oisst_lonc , oisst_latc = corners ( oisst . lon , oisst . lat )
61- oisst_lonc -= 360
62- oisst_to_mom = xesmf . Regridder ({ 'lat' : oisst . lat , 'lon' : oisst . lon }, model_grid [[ 'geolon' , 'geolat' ]]. rename ({ 'geolon' : 'lon' , 'geolat' : 'lat' }), method = 'bilinear' )
64+ oisst_delta = mom_rg - oisst_trend
65+ logger . info ( "MOM_RG: %s" , mom_rg )
66+ logger . info ( "OISST_DELTA: %s" , oisst_delta )
6367
64- glorys_lonc , glorys_latc = corners (glorys .lon , glorys .lat )
65- glorys_to_mom = xesmf .Regridder (glorys , model_grid [['geolon' , 'geolat' ]].rename ({'geolon' : 'lon' , 'geolat' : 'lat' }), method = 'bilinear' )
68+ # Process Glorys and get trend
69+ # NOTE: Glorys_ave is glorys_trends because we call get_3d_trends on it.
70+ glorys_rg , glorys_trend , glorys_lonc , glorys_latc = process_glorys (config , target_grid , 'thetao' ,
71+ sel_time = slice (config ['start_year' ], config ['end_year' ]),
72+ resamp_freq = '1AS' , preprocess_regrid = get_3d_trends )
73+ logger .info ("GLORYS_TREND: %s" ,glorys_trend )
6674
67- glorys_rg = glorys_to_mom (glorys_trend )
6875 glorys_rg = xarray .DataArray (glorys_rg , dims = ['yh' , 'xh' ], coords = {'yh' : model .yh , 'xh' : model .xh })
6976 glorys_delta = model_trend - glorys_rg
77+ logger .info ("GLORYS_RG: %s" ,glorys_rg )
78+ logger .info ("GLORYS_DELTA: %s" ,glorys_delta )
7079
71- oisst_rg = oisst_to_mom (oisst_trend )
72- oisst_rg = xarray .DataArray (oisst_rg , dims = ['yh' , 'xh' ], coords = {'yh' : model .yh , 'xh' : model .xh })
73- oisst_delta = model_trend - oisst_rg
80+ # Set projection of each grid in the plot
81+ # For now, sst_eval.py will only support a projection for the arctic and a projection for all other domains
82+ if config ['projection_grid' ] == 'NorthPolarStereo' :
83+ p = ccrs .NorthPolarStereo ()
84+ else :
85+ p = ccrs .PlateCarree ()
7486
7587 fig = plt .figure (figsize = (10 , 14 ))
7688 grid = AxesGrid (fig , 111 ,
7789 nrows_ncols = (2 , 3 ),
78- axes_class = (GeoAxes , dict (projection = PC )),
90+ axes_class = (GeoAxes , dict (projection = p )),
7991 axes_pad = 0.3 ,
8092 cbar_location = 'bottom' ,
8193 cbar_mode = 'edge' ,
8294 cbar_pad = 0.2 ,
8395 cbar_size = '15%' ,
84- label_mode = ''
96+ label_mode = 'keep '
8597 )
98+ logger .info ("Successfully created grid" )
8699
87- cmap , norm = get_map_norm ('cet_CET_D1' , np .arange (- 2 , 2.1 , .25 ), no_offset = True )
100+ cmap , norm = get_map_norm ('cet_CET_D1' , np .arange (config [ 'bias_min' ], config [ 'bias_max' ], config [ 'bias_step' ] ), no_offset = True )
88101 common = dict (cmap = cmap , norm = norm )
89102
90- bias_cmap , bias_norm = get_map_norm ('RdBu_r' , np .arange (- 1.5 , 1.51 , .25 ), no_offset = True )
103+ bias_cmap , bias_norm = get_map_norm ('RdBu_r' , np .arange (config [ 'bias_min_trends' ], config [ 'bias_max_trends' ], config [ 'bias_step' ] ), no_offset = True )
91104 bias_common = dict (cmap = bias_cmap , norm = bias_norm )
92105
93- p0 = grid [0 ].pcolormesh (model_grid .geolon_c , model_grid .geolat_c , model_trend , ** common )
106+ # Set projection of input data files so that data is correctly tranformed when plotting
107+ # For now, sst_eval.py will only support a projection for the arctic and a projection for all other domains
108+ if config ['projection_data' ] == 'NorthPolarStereo' :
109+ proj = ccrs .NorthPolarStereo ()
110+ else :
111+ proj = ccrs .PlateCarree ()
112+
113+ # MODEL
114+ p0 = grid [0 ].pcolormesh (model_grid .geolon_c , model_grid .geolat_c , model_trend , transform = proj , ** common )
94115 grid [0 ].set_title ('(a) Model' )
95116 cbar0 = autoextend_colorbar (grid .cbar_axes [0 ], p0 )
96117 cbar0 .ax .set_xlabel ('SST trend (°C / decade)' )
97- cbar0 .set_ticks ([- 2 , - 1 , 0 , 1 , 2 ])
98- cbar0 .set_ticklabels ([- 2 , - 1 , 0 , 1 , 2 ])
118+ cbar0 .set_ticks ( config ['ticks' ] )
119+ cbar0 .set_ticklabels ( config ['ticks' ] )
120+ logger .info ("Successfully plotted model data" )
99121
100- p1 = grid [1 ].pcolormesh (oisst_lonc , oisst_latc , oisst_trend , ** common )
122+ # OISST
123+ p1 = grid [1 ].pcolormesh (oisst_lonc , oisst_latc , oisst_trend , transform = proj , ** common )
101124 grid [1 ].set_title ('(b) OISST' )
125+ logger .info ("Successfully plotted oisst" )
102126
103- grid [2 ].pcolormesh (model_grid .geolon_c , model_grid .geolat_c , oisst_delta , ** bias_common )
127+ # MODEL - OISST
128+ grid [2 ].pcolormesh (oisst_lonc , oisst_latc , oisst_delta , transform = proj , ** bias_common )
104129 grid [2 ].set_title ('(c) Model - OISST' )
105- annotate_skill (model_trend , oisst_rg , grid [2 ], weights = model_grid .areacello )
130+ # NOTE: Oisst dims are [lat,lon], so dim argument is needed. Must use mom_rg though, since oisst also contains
131+ # an extra time dimension that changes output of xskillscore functions and leads to error when annotating plot
132+ annotate_skill (mom_rg , oisst_trend , grid [2 ], dim = list (mom_rg .dims ), x0 = config ['text_x' ], y0 = config ['text_y' ], xint = config ['text_xint' ], plot_lat = config ['plot_lat' ])
133+ logger .info ("Successfully plotted difference between model and oisst" )
106134
107- grid [4 ].pcolormesh (glorys_lonc , glorys_latc , glorys_trend , ** common )
135+ # GLORYS
136+ grid [4 ].pcolormesh (glorys_lonc , glorys_latc , glorys_trend , transform = proj , ** common )
108137 grid [4 ].set_title ('(d) GLORYS12' )
109138 cbar1 = autoextend_colorbar (grid .cbar_axes [1 ], p1 )
110139 cbar1 .ax .set_xlabel ('SST trend (°C / decade)' )
111- cbar1 .set_ticks ([- 2 , - 1 , 0 , 1 , 2 ])
112- cbar1 .set_ticklabels ([- 2 , - 1 , 0 , 1 , 2 ])
140+ cbar1 .set_ticks ( config ['ticks' ] )
141+ cbar1 .set_ticklabels ( config ['ticks' ] )
142+ logger .info ("Successfully plotted glorys" )
113143
114- p2 = grid [5 ].pcolormesh (model_grid .geolon_c , model_grid .geolat_c , glorys_delta , ** bias_common )
144+ # MODEL - GLORYS
145+ p2 = grid [5 ].pcolormesh (model_grid .geolon_c , model_grid .geolat_c , glorys_delta , transform = proj , ** bias_common )
115146 grid [5 ].set_title ('(e) Model - GLORYS12' )
116147 cbar2 = autoextend_colorbar (grid .cbar_axes [2 ], p2 )
117148 cbar2 .ax .set_xlabel ('SST trend difference (°C / decade)' )
118- annotate_skill (model_trend , glorys_rg , grid [5 ], weights = model_grid .areacello )
149+ annotate_skill (model_trend , glorys_rg , grid [5 ], weights = model_grid .areacello , x0 = config ['text_x' ], y0 = config ['text_y' ], xint = config ['text_xint' ], plot_lat = config ['plot_lat' ])
150+ logger .info ("Successfully plotted difference between glorys and model" )
119151
120152 for i , ax in enumerate (grid ):
121- ax .set_xlim (- 99 , - 35 )
122- ax .set_ylim (4 , 59 )
153+ ax .set_extent ([ config ['x' ]['min' ], config ['x' ]['max' ], config ['y' ]['min' ], config ['y' ]['max' ] ], crs = proj )
123154 ax .set_xticks ([])
124155 ax .set_yticks ([])
125156 ax .set_xticklabels ([])
@@ -128,14 +159,18 @@ def plot_sst_trends(pp_root, label):
128159 ax .set_facecolor ('#bbbbbb' )
129160 for s in ax .spines .values ():
130161 s .set_visible (False )
162+ logger .info ("Successfully set extent of each axis" )
131163
132164 save_figure ('sst_trends' , label = label )
165+ logger .info ("Successfully saved figure" )
133166
134167
135168if __name__ == '__main__' :
136169 from argparse import ArgumentParser
137170 parser = ArgumentParser ()
138- parser .add_argument ('pp_root' , help = 'Path to postprocessed data (up to but not including /pp/)' )
171+ parser .add_argument ('-p' ,'--pp_root' , help = 'Path to postprocessed data (up to but not including /pp/)' , required = True )
172+ parser .add_argument ('-c' ,'--config' , help = 'Path to yaml config file' , required = True )
139173 parser .add_argument ('-l' , '--label' , help = 'Label to add to figure file names' , type = str , default = '' )
140174 args = parser .parse_args ()
141- plot_sst_trends (args .pp_root , args .label )
175+ config = load_config (args .config )
176+ plot_sst_trends (args .pp_root , args .label , config )
0 commit comments