Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Calculate EKE for CARIB12

import xarray as xr
import xgcm as xgcm
import numpy as np
import gsw
import cmocean as cm
import matplotlib.pyplot as plt
import cartopy
import cartopy.crs as ccrs
import cartopy.feature as cfeature

import warnings
warnings.filterwarnings('ignore')

In this notebook...

You will examine output from the CARIB12 simulation or a CARIB12 simulation of your own. The notebook focuses n two simple analyses relevant to model development and validation:
1- Calculating and plotting Eddy Kinetic Energy to examine mesoscale variability of flow.
2- Plotting a Temperature-Salinity diagram to examine water mass properties.

You will either access the original CARIB12 simulation output or recreate your own CARIB12 simulation using CROCODASH.

There are several prompts along the notebook to complete the analysis with a comparisson to GLORYS reanlaysis data.

You can use the NPL 2025b environment for this notebook.

Seijo-Ellis, G., Giglio, D., Marques, G., & Bryan, F. (2024). CARIB12: a regional Community Earth System Model/Modular Ocean Model 6 configuration of the Caribbean Sea. Geoscientific Model Development, 17(24), 8989-9021

Inputs

if using the original CARIB12 output, you do not need to change any paths or file names in this notebook. If you have configured and completed your own CARIB12 simulation you will need to update the paths and filenmae accordingly.

#--- Path to where the model output is located:
parent_dir = '/glade/derecho/scratch/gseijo/FROM_CHEYENNE/CARIB_012.SMAG.001/run/'
#--- load daily data for 1 month of simulation to calculate EKE:
ds = xr.open_dataset(parent_dir + 'CARIB_12.SMAG.001.mom6.sfc.2017-09.nc',decode_timedelta=True)
ds
Loading...

Eddy Kinetic Energy

We will calculate EKE using Reynold’s decomposition of the velocity field:
EKE=(12)(u2+v2)EKE = (\frac{1}{2})(u^{\prime2} + v^{\prime2})
u=u+uu = \overline{u} + u^{\prime} and v=v+vv = \overline{v} + v^{\prime},
where u\overline{u} and v\overline{v} are the time averaged zonal and meridional velocities and, uu^{\prime} and vv^{\prime} the corresponding anomalies.

Remapping velocities

On a Arakawa C-grid, the velocities are calculated on cell faces
Here, we use xgcm to remap the velocities to the cell centers:

#--- use xGCM to remap u,v to cell center:
grid = xgcm.Grid(ds, coords={'X': {'center': 'xh', 'outer': 'xq'},
                         'Y': {'center': 'yh', 'outer': 'yq'},})

ds['u_c'] = grid.interp(ds['SSU'],'X',boundary='fill')
ds['v_c'] = grid.interp(ds['SSV'],'Y',boundary='fill')
ds
Loading...

Calculate EKE

def calculate_eke(ds):
    ''' this function calculates EKE. It assumes the input dataset has
    both zonal (u_c) and meridional (v_c) velocities remapped to cell centers in it. It will output a data array with EKE.
    Inputs: ds     --->  dataset with u_c and v_c variables.
    Outputs: da_eke ---> EKE in a data array
    '''
    #--- get mean velocities:
    u_ave = ds['u_c'].mean(dim='time')
    v_ave = ds['v_c'].mean(dim='time')
    #--- get anomalies:
    u_anom = ds['u_c'] - u_ave
    v_anom = ds['v_c'] - v_ave
    #--- get EKE:
    da_eke = 0.5 * (u_anom**2 + v_anom**2)

    return da_eke
    
    
EKE = calculate_eke(ds)
EKE
Loading...

Plot time mean EKE

#--- some plot params to play with:
vmini = 0.0
vmaxi = 0.15
colormap = 'turbo'

#--- plot time mean EKE:
fig, axs = plt.subplots(ncols = 1,figsize=(16, 5),subplot_kw=dict(projection=ccrs.PlateCarree()))

c1 = EKE[:,:,:].mean(dim='time').plot.pcolormesh(vmin=vmini,vmax=vmaxi,cmap=colormap,transform=ccrs.PlateCarree(),add_colorbar = True,extend='max')

axs.coastlines(color='k')
axs.add_feature(cartopy.feature.LAND,color='gray')
axs.set_facecolor('gray')
axs.set_ylabel('')
axs.set_xlabel('')
axs.gridlines(color='k',linestyle=':',draw_labels=['left','bottom'], dms=True, x_inline=False, y_inline=False)
_ = axs.set_title('Time mean EKE [$m^{2}/s^{2}$]',fontweight ='bold')
<Figure size 1600x500 with 2 Axes>

Prompt

Import GLORYS data for the same time period and calculate the mean EKE.
The GLORYS data can be found here: /glade/campaign/collections/rda/data/d010049//glade/campaign/collections/rda/data/d010049/
Alternatively, you can incorporate other datasets by leveraging other packages you have learned about this week.

Use xESMF to regrid both datasets to a common grid and compare the time mean EKE between CARIB12 and GLORYS.

What can you learn from comparing CARIB12 to the GLORYS reanalysis?
How would it inform the development f the configuration?

Temperature - Salinity Diagram

if using the original CARIB12 output, you do not need to change any paths or file names in this notebook. If you have configured and completed your own CARIB12 simulation you will need to update the paths and filenmae accordingly.

#--- load monthly mean 3D data of simulation to plot a T-S diagram:
ds = xr.open_dataset(parent_dir + 'CARIB_12.SMAG.001.mom6.hm.2017-09.nc',decode_timedelta=True)
ds
Loading...

Potential Density

#--- Get mean temperature and salinity profiles to simplify calculations for the demo:
ptemp = ds['thetao'].mean(dim='time').mean(dim=['xh','yh'])
salt  = ds['so'].mean(dim='time').mean(dim=['xh','yh'])

Prompt

The previous step does an area average without considering variation in the area of each cell. Repeat the previous step by implementing an area-weighted average computation.
The following example could be applied to this notebook:

weights = np.cos(np.deg2rad(data.lat))
weights.name = "weights"
data_weighted = data.weighted(weights)
weighted_mean = data_weighted.mean(("lon", "lat"))

where “data” is a data array with dimensions “lon” and “lat” corresponding to longitudes and latitudes, respectively.

#--- Compute sigma-theta (potential density anomaly, referenced to 0 dbar)
#--- Need Absolute Salinity and Conservative Temperature for GSW
Tgrid = np.linspace(ptemp.min()-0.5, ptemp.max()+0.5, 50)
Sgrid = np.linspace(salt.min()-0.1, salt.max()+0.1, 50)
Sg, Tg = np.meshgrid(Sgrid, Tgrid)

SA = gsw.SA_from_SP(Sg, p=0, lon=0, lat=0)
CT = gsw.CT_from_pt(SA, Tg)

sigma0 = gsw.sigma0(SA, CT) 

Plot TS diagram with sigma contours

plt.figure(figsize=(6,6))

# Density contours
cs = plt.contour(Sg, Tg, sigma0, colors='gray', linewidths=0.7)
plt.clabel(cs, inline=True, fontsize=8, fmt="%.1f")

# Time- and space-mean profile colored by depth
sc = plt.scatter(salt, ptemp, c=ptemp['zl'], vmin=0,vmax=2000,cmap='turbo_r')
plt.colorbar(sc, label='Depth [m]')

plt.xlabel("Salinity [PSU]")
plt.ylabel("Potential Temperature [°C]")
plt.title("Mean " + r'$\theta$' +"-S Diagram")
plt.grid(True)
plt.show()
<Figure size 600x600 with 2 Axes>

Prompt

Repeat for GLORYS data and compare the vertical structure and water mass properties between the two datasets.

Final Prompt

Leveraging what you’ve learned throughout the workshop and this notebook, can you incorporate a similar analysis into CUPiD?
Good luck!