Back to home page

MITgcm

 
 

    


File indexing completed on 2021-04-23 05:12:38 UTC

view on githubraw file Latest commit ce0d9af5 on 2021-04-21 19:30:20 UTC
ce0d9af5ea Jeff*0001 # import python modules
                0002 import numpy as np
                0003 import matplotlib.pyplot as plt
                0004 import xarray as xr
                0005 
                0006 # python pkg xarray used to load netcdf files http://xarray.pydata.org
                0007 # xarray loads netcdf data and metadata into xarray structure Dataset
                0008 # (containing data in xarray DataArray) which includes information
                0009 # about dimensions, coordinates etc.
                0010 # Xarray simplifies some basic tasks, e.g. it is not necessary to
                0011 # provide axes arguments when plotting, but also more complex tasks
                0012 # such as broadcasting data to fill missing dimensions in DataArray
                0013 # arithmetic, for example.
                0014 
                0015 
                0016 ###########   load grid data   ########### 
                0017 
                0018 # Load grid variables; names correspond with MITgcm source code.
                0019 # (if using standard binary output instead of netcdf, these are dumped
                0020 # to individual files, e.g. 'RC.data' etc.)
                0021 # Assumes that output in multiple tiles has been concatenated into
                0022 # global files (used script utils/python/MITgcmutils/scripts/gluemncbig)
                0023 # and moved into the top directory;
                0024 # see MITgcm user manual section 4.2.4.1 
                0025 
                0026 # Using a spherical polar grid, all X,Y variables in lon,lat coordinates
                0027 # vertical grid provided in meters, area in m^2
                0028 
                0029 grid = xr.open_dataset('grid.nc')
                0030 
                0031 # We will be using the following fields from the grid file:
                0032 # 1-D fields
                0033 #   RC:     vertical grid, cell center locations (this is coordinate Z)
                0034 #   drF:    vertical spacing of grid cells (thickness of cells)
                0035 #   X:      1-D version of XC data
                0036 #   Y:      1-D version of YC data
                0037 #   Xp1:    x-location of gridcell lower left corner
                0038 #   Yp1:    y-location of gridcell lower left corner
                0039 #   Xp1 and Yp1 are effectively 1-D versions of grid variables XG,YG with
                0040 #   an extra data value at the eastern and northern ends of the domain.
                0041 # 2-D fields (y,x)
                0042 #   XC:     x-location of gridcell centers
                0043 #   YC:     y-location of gridcell centers
                0044 #   dyG:    grid spacing in y-dim (i.e. separation between corners)
                0045 #   rA:     surface area of gridcells
                0046 # 3-D fields (z,y,x)
                0047 #   HFacC:  vertical fraction of cell which is ocean
                0048 # See MITgcm users manual section 2.11 for additional MITgcm grid info
                0049 
                0050 # Number of gridcells in x,y for full domain:
                0051 Nx = grid.X.size
                0052 Ny = grid.Y.size
                0053 # out-of-the-box tutorial configuration is 62x62
                0054 
                0055 
                0056 ###########   load diagnostics   ########### 
                0057 
                0058 # unit for temperature is degrees Celsius, velocity in m/s,
                0059 # surface height in m, heat flux in W/m^2
                0060 # see run output file available_diagnostics.log
                0061    
                0062 # load statistical diagnostic output (set to monthly time-avg output)
                0063 # only one output region is defined: global (the default)
                0064 dynStDiag = xr.open_dataset('dynStDiag.nc')
                0065 dynStDiag = dynStDiag.rename({'Zmd000015':'Z'})
                0066 # includes diagnostics:
                0067 # TRELAX_ave:   (time, region, depth); region=0 (global), depth=0 (surf-only)
                0068 # THETA_lv_ave: (time, region, depth); region=0 (global)
                0069 # THETA_lv_std: (time, region, depth); region=0 (global)
                0070    
                0071 # load 2-D and 3-D variable diagnostic output, annual mean data
                0072 surfDiag = xr.open_dataset('surfDiag.nc')
                0073 # TRELAX:       (time, depth, y, x); depth=0 (surface-only)
                0074 # ETAN:         (time, depth, y, x); depth=0 (surface-only)
                0075 dynDiag = xr.open_dataset('dynDiag.nc')
                0076 dynDiag = dynDiag.rename({'Zmd000015':'Z'})
                0077 # UVEL:         (time, depth, y, x); x dim. is Nx+1, includes eastern edge
                0078 # VVEL:         (time, depth, y, x); y dim. is Ny+1, includes northern edge
                0079 # THETA:        (time, depth, y, x)
                0080 
                0081 # Note the Z dimension above has a different name, we rename to the
                0082 # standard name 'Z' which will make life easier when we do some
                0083 # calculations using the raw data. This is because pkg/diagnostics
                0084 # has an option to select specific levels of a 3-D variable to be output.
                0085 # If 'levels' are selected in data.diagnostics, the following
                0086 # additional statement would be needed:
                0087 #   dynDiag['Z'] = grid['Z'][dynDiag.diag_levels.astype(int)-1]
                0088 
                0089 
                0090 ###########   plot diagnostics   ########### 
                0091 
                0092 # figure 4.6 - time series of global mean TRELAX and THETA by level
                0093 #
                0094 # plot THETA at MITgcm k levels 1,5,15 (SST, -305 m, -1705 m)
                0095 klevs = [0, 4, 14]
                0096 
                0097 # MITgcm time unit (dim='T') is seconds, so create new coordinate
                0098 # for (dynStDiag) monthly time averages and convert into years.
                0099 # Note that the MITgcm time array values correspond to time at the end
                0100 # of the time-avg periods, i.e. subtract 1/24 to plot at mid-month.  
                0101 Tmon = (dynStDiag['T']/(86400*360) - 1/24).assign_attrs(units='years')
                0102 # and repeat to create time series for annual mean time output,
                0103 # subtract 0.5 to plot at mid-year.
                0104 Tann = (surfDiag['T']/(86400*360) - 0.5).assign_attrs(units='years')
                0105 
                0106 plt.figure(figsize=(16,10))
                0107 # global mean TRELAX
                0108 plt.subplot(221)
                0109 # we tell xarray to plot using this new abscissa Tmon (instead of T)
                0110 # xarray is smart enough to ignore the singleton dimensions
                0111 # for region and depth
                0112 dynStDiag.TRELAX_ave.assign_coords(T=Tmon).plot(color='b', linewidth=4)
                0113 plt.grid('both') 
                0114 plt.xlim(0, np.ceil(Tmon[-1]))
                0115 plt.ylim(-400, 0)
                0116 # Alternatively, a global mean area-weighted TRELAX (annual mean)
                0117 # could be computed as follows, using HfacC[0,:,:], i.e. HfacC in
                0118 # the surface layer, as a land-ocean mask, using xarray .where()
                0119 # First, compute total surface area of ocean points:
                0120 tot_ocean_area = (grid.rA.where(grid.HFacC[0,:,:]!=0)).sum(('Y', 'X'))
                0121 # broadcasting with xarray is more flexible than basic numpy
                0122 # because it matches axis name (not dependent on axis position)
                0123 # grid area is broadcast across the time dimension below
                0124 TRELAX_ave_ann = (surfDiag.TRELAX * grid.rA.where(grid.HFacC[0,:,:]!=0)
                0125                  ).sum(('Y', 'X')) / tot_ocean_area
                0126 TRELAX_ave_ann.assign_coords(T=Tann).plot(
                0127                  color='m', linewidth=4, linestyle='dashed')
                0128 plt.title('a) Net Heat Flux into Ocean (TRELAX_ave)')
                0129 
                0130 plt.subplot(223)
                0131 # mean THETA by level
                0132 # Here is an example of allowing xarray to do even more of the work
                0133 # and labeling for you, given selected levels, using 'hue' parameter
                0134 THETAmon = dynStDiag.THETA_lv_ave.assign_coords(T=Tmon, Z=grid.RC)
                0135 THETAmon.isel(Z=klevs).plot(hue='Z', linewidth=4)
                0136 plt.grid('both')
                0137 plt.title('b) Mean Potential Temp. by Level (THETA_lv_avg)')
                0138 plt.xlim(0, np.ceil(Tmon[-1]))
                0139 plt.ylim(0, 30);
                0140 # Note a legend (of Z depths) was included automatically.
                0141 # Specifying colors is not so simple however, either change
                0142 # the default a priori, e.g. ax.set_prop_cycle(color=['r','g','c'])
                0143 # or after the fact, e.g. lh[0].set_color('r') etc.
                0144 
                0145 plt.subplot(224)
                0146 # standard deviation of THETA by level
                0147 THETAmon = dynStDiag.THETA_lv_std.assign_coords(T=Tmon, Z=grid.RC)
                0148 THETAmon.isel(Z=klevs).plot(hue='Z', linewidth=4)
                0149 plt.grid('both')
                0150 plt.title('c) Std. Dev. Potential Temp. by Level (THETA_lv_std)')
                0151 plt.xlim(0, np.ceil(Tmon[-1]))
                0152 plt.ylim(0, 8)
                0153 plt.show()
                0154 
                0155 # figure 4.7 - 2-D plot of TRELAX and contours of free surface height
                0156 # (ETAN) at simulation end ( endTime = 3110400000. is t=100 yrs).
                0157 ##
                0158 eta_masked = surfDiag.ETAN[-1,0,:,:].where(grid.HFacC[0,:,:]!=0)
                0159 plt.figure(figsize=(10,8)) 
                0160 surfDiag.TRELAX[-1,0,:,:].plot(cmap='RdBu_r', vmin=-250, vmax=250)
                0161 plt.xlim(0, 60)
                0162 plt.ylim(15, 75)
                0163 eta_masked.plot.contour(levels=np.arange(-.6,.7,.1), colors='k')
                0164 plt.title('Free surface height (contours, CI .1 m) and '
                0165           'TRELAX (shading, $\mathregular{W/m^2}$)')
                0166 plt.show()
                0167 # By default, this uses a plotting routine similar to
                0168 # pcolormesh to plot TRELAX.
                0169 # Note that xarray uses the coordinates of TRELAX automatically from
                0170 # the netcdf metadata! With axis labels! Even though the plotted TRELAX
                0171 # DataArray has coordinates Y,X (cell centers) it is plotted correctly 
                0172 # (effectively shading cells between Yp1,Xp1 locations, which are the
                0173 # lower left corners of grid cells, i.e. YG,XG). Also note we mask the
                0174 # land values when contouring the free surface height. Alternatively,
                0175 # one could make a smoother color plot using contourf: 
                0176 # surfDiag.TRELAX[-1,0,:,:].plot.contourf(
                0177 #          cmap='RdBu_r',levels=np.linspace(-250,250,1000))
                0178 
                0179 # figure 4.8 - barotropic streamfunction, plot at simulation end
                0180 # (w/overlaid labeled contours)
                0181 #
                0182 # here is an example where the xarray broadcasting is superior:
                0183 ubt = (dynDiag.UVEL * grid.drF).sum('Z') # depth-integrated u velocity
                0184 psi = (-ubt * grid.dyG).cumsum('Y').pad(Y=(1,0),constant_values=0.) / 1E6
                0185 # Note psi is computed and plotted at the grid cell corners which we
                0186 # compute as dimensioned (Ny,Nx+1); as noted, UVEL contains an extra
                0187 # data point in x, at the eastern edge. cumsum is done in y-direction.
                0188 # We have a wall at southern boundary (i.e. no reentrant flow from
                0189 # north). We need to add a row of zeros to specify psi(j=0).
                0190 # xarray .pad() allows one to add a row at the top and/or bottom,
                0191 # we just need the bottom row (j=0) padding the computed psi array.
                0192 # Thus after padding psi is shape (dynDiag.UVEL['T'].size,Ny+1,Nx+1) 
                0193 # Finally, since psi is computed at grid cell corners,
                0194 # we need to re-assign Yp1 coordinate to psi.
                0195 # As an aside comment, if data manipulation with xarray still seems a
                0196 # bit cumbersome, you are not mistaken... there are tools developed
                0197 # such as xgcm (https://xgcm.readthedocs.io/en/latest/) specifically
                0198 # for this purpose.
                0199 # Note xxx.values extracts the numpy-array data from xarray
                0200 # DataArray xxx; this is done in the plot statement below: 
                0201 
                0202 plt.figure(figsize=(10,8))
                0203 psi.isel(T=-1).assign_coords(Y=grid.Yp1.values).plot.contourf(
                0204              levels=np.linspace(-30, 30, 13), cmap='RdYlBu_r')
                0205 cs = psi.isel(T=-1).assign_coords(Y=grid.Yp1.values).plot.contour(
                0206              levels=np.arange(-35, 40, 5), colors='k')
                0207 plt.clabel(cs, fmt = '%.0f')
                0208 plt.xlim(0, 60)
                0209 plt.ylim(15, 75)
                0210 plt.title('Barotropic Streamfunction (Sv)');
                0211 plt.show()
                0212 
                0213 # figure 4.9 - potential temp at depth and xz slice, at simulation end
                0214 #
                0215 # plot THETA at MITgcm k=4 (220 m depth) and j=15 (28.5N)
                0216 klev =  3
                0217 jloc = 14
                0218 
                0219 theta_masked = dynDiag.THETA[-1,klev,:,:].where(grid.HFacC[klev,:,:]!=0)
                0220 plt.figure(figsize=(16,6))
                0221 plt.subplot(121)
                0222 # again we use pcolor for this plan view, grabs coordinates automatically
                0223 dynDiag.THETA[-1,klev,:,:].plot(cmap='coolwarm', vmin=0, vmax=30 )
                0224 # and overlay contours w/masked out boundary/land cells
                0225 theta_masked.plot.contour(levels=np.linspace(0, 30, 16), colors='k')
                0226 plt.xlim(0, 60)
                0227 plt.ylim(15, 75)
                0228 plt.title('a) THETA at %g m Depth ($\mathregular{^oC}$)' %grid.RC[klev])
                0229 
                0230 # For the xz slice, our limited vertical resolution makes for an ugly
                0231 # pcolor plot, we'll shade using contour instead, providing the centers
                0232 # of the vertical grid cells and cell centers in the x-dimension.
                0233 # Also mask out land cells at the boundary, which results in slight
                0234 # white space at domain edges.
                0235 theta_masked = dynDiag.THETA[-1,:,jloc,:].where(grid.HFacC[:,jloc,:]!=0)
                0236 plt.subplot(122)
                0237 theta_masked.plot.contourf(levels=np.arange(0,30,.2), cmap='coolwarm')
                0238 theta_masked.plot.contour(levels=np.arange(0,30,2), colors='k')
                0239 plt.title('b) THETA at %gN ($\mathregular{^oC}$)' %grid.YC[jloc,0])
                0240 plt.xlim(0, 60)
                0241 plt.ylim(-1800, 0)
                0242 plt.show()
                0243 # One approach to avoiding the white space at boundaries/top/bottom
                0244 # is to copy neighboring tracer values to land cells prior to contouring
                0245 # (and don't mask), and augment a row of z=0 data at the ocean surface
                0246 # and a row at the ocean bottom.
                0247 # (see tutorial Southern Ocean Reentrant Channel for an example)
                0248 # The white space occurs because contour uses data at cell centers, with
                0249 # values masked/undefined beyond the cell centers toward boundaries.
                0250 
                0251 # To instead plot using pcolor: 
                0252 # theta_masked.assign_coords(Z=grid.RC.values).plot(cmap='coolwarm')