Poleward Heat Transport#

%load_ext autoreload
%autoreload 2
%%capture 
# comment above line to see details about the run(s) displayed
from misc import *
import intake
import xarray as xr
import cftime
from datetime import timedelta
import numpy as np
%matplotlib inline
heat_transport = []
heat_transport_ts = []
for path, case in zip(ocn_path, casename):
  ds = xr.open_dataset(path+case+'_heat_transport.nc')
  heat_transport.append(ds)
  ds_ts = xr.open_dataset(path+case+'_heat_transport_ts.nc')
  heat_transport_ts.append(ds_ts)
# load pht-johns-2023 from oce-catalog
obs = 'pht-johns-2023'
catalog = intake.open_catalog(diag_config_yml['oce_cat'])
print('\n Reading data from: ', obs)
ds_obs = catalog[obs].to_dask()
 Reading data from:  pht-johns-2023
# Get the number of days in each month as a DataArray
weights = xr.DataArray(ds_obs.time.dt.days_in_month, coords=[ds_obs.time], dims=["time"])

# Broadcast and compute weighted monthly mean
def weighted_monthly_mean(da):
    w = weights.broadcast_like(da)
    return (da * w).resample(time="MS").sum() / w.resample(time="MS").sum()

pht_johns_2023 = weighted_monthly_mean(ds_obs['Q_sum'])
def shift_to_year1(ds_or_da):
    """
    Shift a dataset or DataArray time coordinate to year 1 using cftime.
    Works for numpy.datetime64 dates by converting them to cftime.
    """
    shifted = ds_or_da.copy()
    
    # Convert numpy.datetime64 to cftime.DatetimeNoLeap if needed
    old_time = shifted.time.values
    if np.issubdtype(old_time.dtype, np.datetime64):
        cftime_time = [cftime.DatetimeNoLeap(t.astype('datetime64[Y]').item().year,
                                             t.astype('datetime64[M]').item().month,
                                             t.astype('datetime64[D]').item().day)
                       for t in old_time]
    else:
        cftime_time = list(old_time)
    
    # Compute deltas in days relative to first timestep
    delta_days = [(t - cftime_time[0]).days for t in cftime_time]
    
    # New origin
    new_origin = cftime.DatetimeNoLeap(1, 1, 1)
    new_time = [new_origin + timedelta(days=d) for d in delta_days]
    
    # Assign new time coordinate
    shifted['time'] = new_time
    return shifted

Global Poleward Heat Transport#

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

for i in range(len(casename)):
    plot_heat_trans(heat_transport[i], label=label[i])
  
y = heat_transport[0].yq
plt.xlim(-80,90); plt.ylim(-2.5,3.0); plt.grid(True); 
plt.plot(y, y*0., 'k', linewidth=0.5)
#plt.plot(yobs,NCEP['Global'],'k--',linewidth=0.5,label='NCEP'); 
#plt.plot(yobs,ECMWF['Global'],'k.',linewidth=0.5,label='ECMWF')
plotGandW(GandW['Global']['lat'],GandW['Global']['trans'],GandW['Global']['err'])
jra = xr.open_dataset('/glade/work/gmarques/cesm/datasets/Heat_transport/jra55fcst_v1_3_annual_1x1/nht_jra55do_v1_3.nc')
jra_mean_global = jra.nht[:,0,:].mean('time').values
jra_std_global = jra.nht[:,0,:].std('time').values
plt.plot(jra.lat, jra_mean_global,'k', label='JRA-55 v1.3', color='#1B2ACC', lw=1)
plt.fill_between(jra.lat, jra_mean_global-jra_std_global, jra_mean_global+jra_std_global,
    alpha=0.25, edgecolor='#1B2ACC', facecolor='#089FFF')

plt.title('Global Poleward Heat Transport',fontsize=15)
plt.xlabel(r'Latitude [$\degree$N]',fontsize=15)
plt.ylabel('[PW]',fontsize=15)
plt.legend(loc=4,fontsize=15, ncol=2)
plt.ylim(-2.,2.5);
_images/4b369a78bf44651e0364f2b086e750fbc25852c40fd992da3c313f0370872188.png

Time series @ Equator#

fig, [ax1,ax2] = plt.subplots(nrows=2,figsize=(10,8),sharex=True)

for i in range(len(casename)):
    total = (heat_transport_ts[i].T_ady_2d_global_eq + heat_transport_ts[i].T_diffy_2d_global_eq + \
            heat_transport_ts[i].T_hbd_diffy_2d_global_eq)*1.0e-15 # in PW
    total.plot(ax=ax1, label=str(label[i])+ f" HT={total.mean().values:.2f} ± {total.std().values:.2f} PW") # in PW
    
    diff = (heat_transport_ts[i].T_diffy_2d_global_eq + heat_transport_ts[i].T_hbd_diffy_2d_global_eq)*1.0e-15 # in PW
    diff.plot(ax=ax2, label=str(label[i])+ f" HT={diff.mean().values:.3f} ± {diff.std().values:.3f} PW") # in PW

# Add shaded band to highlight target range
ax1.axhspan(0.2, 0.3, color="orange", alpha=0.2, label="Target (0.2–0.3 PW)")

# Collect handles and labels from one axis (assuming they are the same)
handles1, labels1 = ax1.get_legend_handles_labels()
handles2, labels2 = ax2.get_legend_handles_labels()

# Put one legend on the right
fig.legend(handles1, labels1, loc="center left", bbox_to_anchor=(1, 0.95))
fig.legend(handles2, labels2, loc="center left", bbox_to_anchor=(1, 0.45))

#  Top title
fig.suptitle("Global Heat Transport Time Series at the Equator", fontsize=14, y=1.02) 
# Titles for each subplot
ax1.set_title("Total Heat Transport")
ax2.set_title("Diffusive (neutral + horizontal)")

# Axis labels
ax1.set_xlabel("")
ax2.set_xlabel("Time")               
ax1.set_ylabel("[PW]")
ax2.set_ylabel("[PW]")

# grid
ax1.grid(True, which="both", linestyle="--", alpha=0.7)
ax2.grid(True, which="both", linestyle="--", alpha=0.7)

plt.tight_layout()
plt.show()
_images/db8a66907e0f012baa300f9080112d401d666d7ff64a3060ece7323ecf046225.png

Atlantic Poleward Heat Transport#

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

for i in range(len(casename)):
  basin_code = genBasinMasks(grd[i].geolon, grd[i].geolat, depth[i])
  m = 0*basin_code; m[(basin_code==2) | (basin_code==4) | (basin_code==6) | (basin_code==7) | (basin_code==8)] = 1

  HTplot = heatTrans(heat_transport[i].T_ady_2d, 
           heat_transport[i].T_diffy_2d,
           heat_transport[i].T_hbd_diffy_2d,
           vmask=m*np.roll(m,-1,axis=-2))

  yy = grd[i].geolat_c[:,:].max(axis=-1)
  HTplot[yy<-34] = np.nan
  plt.plot(yy,HTplot, linewidth=3,label=label[i])
  
jra_mean_atl = jra.nht[:,1,:].mean('time').values
jra_std_atl = jra.nht[:,1,:].std('time').values
plt.plot(jra.lat, jra_mean_atl,'k', label='JRA-55 v1.3', color='#1B2ACC', lw=1)
plt.fill_between(jra.lat, jra_mean_atl-jra_std_atl, jra_mean_atl+jra_std_atl,
alpha=0.25, edgecolor='#1B2ACC', facecolor='#089FFF')
#plt.plot(yobs,NCEP['Atlantic'],'k--',linewidth=0.5,label='NCEP')
#plt.plot(yobs,ECMWF['Atlantic'],'k.',linewidth=0.5,label='ECMWF')
plotGandW(GandW['Atlantic']['lat'],GandW['Atlantic']['trans'],GandW['Atlantic']['err'])
plt.xlabel(r'Latitude [$\degree$N]',fontsize=15)
plt.title('Atlantic Poleward Heat Transport',fontsize=15)
plt.legend(ncol=2,fontsize=15)
plt.ylabel('[PW]',fontsize=15)
plt.xlim(-80,90); plt.ylim(-0.5,2.3); plt.grid(True)
plt.plot(y, y*0., 'k', linewidth=0.5);
_images/843f9355f25933ee423a97c163967ae1f9e3557ca318c6eb296eff097fcd4c62.png

Time series @ Equator#

fig, [ax1,ax2] = plt.subplots(nrows=2,figsize=(10,8),sharex=True)

for i in range(len(casename)):
    total = (heat_transport_ts[i].T_ady_2d_atl_eq + heat_transport_ts[i].T_diffy_2d_atl_eq + \
            heat_transport_ts[i].T_hbd_diffy_2d_atl_eq)*1.0e-15 # in PW
    total.plot(ax=ax1, label=str(label[i])+ f" HT={total.mean().values:.2f} ± {total.std().values:.2f} PW") # in PW
    
    diff = (heat_transport_ts[i].T_diffy_2d_atl_eq + heat_transport_ts[i].T_hbd_diffy_2d_atl_eq)*1.0e-15 # in PW
    diff.plot(ax=ax2, label=str(label[i])+ f" HT={diff.mean().values:.3f} ± {diff.std().values:.3f} PW") # in PW

# Add shaded band to highlight target range
ax1.axhspan(0.4, 0.6, color="orange", alpha=0.2, label="Target (0.4–0.6 PW)")

# Collect handles and labels from one axis (assuming they are the same)
handles1, labels1 = ax1.get_legend_handles_labels()
handles2, labels2 = ax2.get_legend_handles_labels()

# Put one legend on the right
fig.legend(handles1, labels1, loc="center left", bbox_to_anchor=(1, 0.95))
fig.legend(handles2, labels2, loc="center left", bbox_to_anchor=(1, 0.45))

#  Top title
fig.suptitle("Atlantic Heat Transport Time Series at the Equator", fontsize=14, y=1.02) 
# Titles for each subplot
ax1.set_title("Total Heat Transport")
ax2.set_title("Diffusive (neutral + horizontal)")

# Axis labels
ax1.set_xlabel("")
ax2.set_xlabel("Time")               
ax1.set_ylabel("[PW]")
ax2.set_ylabel("[PW]")

# grid
ax1.grid(True, which="both", linestyle="--", alpha=0.7)
ax2.grid(True, which="both", linestyle="--", alpha=0.7)

plt.tight_layout()
plt.show()
_images/5745dbe2cf51bd6a3010233b11743e9e2b03f0f01cb78f869b7fea35c0facc3f.png

Time series @ ~ 26.5 N#

fig, [ax1,ax2] = plt.subplots(nrows=2,figsize=(10,8),sharex=True)

for i in range(len(casename)):
    total = (heat_transport_ts[i].T_ady_2d_rapid + heat_transport_ts[i].T_diffy_2d_rapid + \
            heat_transport_ts[i].T_hbd_diffy_2d_rapid)*1.0e-15 # in PW
    total.plot(ax=ax1, label=str(label[i])+ f" HT={total.mean().values:.2f} ± {total.std().values:.2f} PW") # in PW
    
    diff = (heat_transport_ts[i].T_diffy_2d_rapid + heat_transport_ts[i].T_hbd_diffy_2d_rapid)*1.0e-15 # in PW
    diff.plot(ax=ax2, label=str(label[i])+ f" HT={diff.mean().values:.5f} ± {diff.std().values:.5f} PW") # in PW

# Add shaded band to highlight target range
ax1.axhspan(1.08, 1.32, color="orange", alpha=0.2, label="Target (1.08–1.32 PW)")

# rapid data
obs_shifted = shift_to_year1(pht_johns_2023)*1.0e-15 # PW

ax1.plot(obs_shifted.time ,obs_shifted.values, 
         label='Johns23' f" HT={obs_shifted.mean().values:.2f} ± {obs_shifted.std().values:.2f} PW", lw=2)

# Collect handles and labels from one axis (assuming they are the same)
handles1, labels1 = ax1.get_legend_handles_labels()
handles2, labels2 = ax2.get_legend_handles_labels()

# Put one legend on the right
fig.legend(handles1, labels1, loc="center left", bbox_to_anchor=(1, 0.95))
fig.legend(handles2, labels2, loc="center left", bbox_to_anchor=(1, 0.45))

#  Top title
fig.suptitle("Atlantic Heat Transport Time Series at ~ 26.5 N", fontsize=14, y=1.02) 
# Titles for each subplot
ax1.set_title("Total Heat Transport")
ax2.set_title("Diffusive (neutral + horizontal)")

# Axis labels
ax1.set_xlabel("")
ax2.set_xlabel("Time")               
ax1.set_ylabel("[PW]")
ax2.set_ylabel("[PW]")

# grid
ax1.grid(True, which="both", linestyle="--", alpha=0.7)
ax2.grid(True, which="both", linestyle="--", alpha=0.7)

plt.tight_layout()
plt.show()
_images/aae4f9b360d4d72f9d7045ad661a2f629b7897122fa2f3b0c0dae51caf378d60.png

Indo-Pacific Poleward Heat Transport#

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

for i in range(len(casename)):
  basin_code = genBasinMasks(grd[i].geolon, grd[i].geolat, depth[i])
  m = 0*basin_code; m[(basin_code==3) | (basin_code==5)] = 1
  HTplot = heatTrans(heat_transport[i].T_ady_2d, 
           heat_transport[i].T_diffy_2d,
           heat_transport[i].T_hbd_diffy_2d,
           vmask=m*np.roll(m,-1,axis=-2))

  yy = grd[i].geolat_c[:,:].max(axis=-1)
  HTplot[yy<-34] = np.nan
  plt.plot(yy,HTplot, linewidth=3,label=label[i])

jra_mean_indo = jra.nht[:,2,:].mean('time').values
jra_std_indo = jra.nht[:,2,:].std('time').values
plt.plot(jra.lat, jra_mean_indo,'k', label='JRA-55 v1.3', 
                 color='#1B2ACC', lw=1)
plt.fill_between(jra.lat, jra_mean_indo-jra_std_indo, 
                 jra_mean_indo+jra_std_indo,
                 alpha=0.25, edgecolor='#1B2ACC', 
                 facecolor='#089FFF')

#plt.plot(yobs,NCEP['IndoPac'],'k--',linewidth=0.5,label='NCEP')
#plt.plot(yobs,ECMWF['IndoPac'],'k.',linewidth=0.5,label='ECMWF')
plotGandW(GandW['IndoPac']['lat'],GandW['IndoPac']['trans'],GandW['IndoPac']['err'])
plt.xlabel(r'Latitude [$\degree$N]',fontsize=10)
plt.legend(ncol=2,fontsize=15)
plt.title('Indo-Pacific Poleward Heat Transport',fontsize=15)
plt.ylabel('[PW]',fontsize=15)
plt.xlim(-80,90); plt.ylim(-2.5,2.5); plt.grid(True)
plt.plot(y, y*0., 'k', linewidth=0.5);
_images/62436625e82f82308548275d87c89a1d3e38f681997768decb54c4ca00335b78.png

By components#

colors = ['tab:blue','tab:orange','tab:green','tab:purple','tab:gray','tab:cyan']
matplotlib.rcParams.update({'font.size': 14})
fig, ax = plt.subplots(nrows=4, ncols=1, figsize=(12,12), sharex=True)
plt.suptitle('Northward global ocean heat transport')
for i in range(len(casename)):
  #print(i)
  ds = heat_transport[i]
  #print(ds)
  total = (ds.T_ady_2d + ds.T_diffy_2d +
          ds.T_hbd_diffy_2d) 
  (total.sum(dim='xh')* 1.e-15).plot(ax=ax[0], lw=3,label=label[i], color=colors[i]); 
  (ds.T_ady_2d.sum(dim='xh')* 1.e-15).plot(ax=ax[1], lw=3,label=label[i], color=colors[i]); 
  (ds.T_diffy_2d.sum(dim='xh') * 1.e-15).plot(ax=ax[2], lw=3, color=colors[i]); 
  (ds.T_hbd_diffy_2d.sum(dim='xh') * 1.e-15).plot(ax=ax[3], lw=3, color=colors[i]); 

ax[0].legend(ncol=2)
ax[0].grid(); ax[1].grid();ax[2].grid(); ax[3].grid()
#ax[0].set_ylim(-1.5, 1.5); ax[1].set_ylim(-1.5, 1.5)
#ax[2].set_ylim(-0.075, 0.05); ax[3].set_ylim(-0.075, 0.05)
ax[0].set_ylabel('PW');ax[1].set_ylabel('PW');ax[2].set_ylabel('PW'); ax[3].set_ylabel('PW')
ax[0].set_xlabel('');ax[1].set_xlabel(''); ax[2].set_xlabel(''); ax[3].set_xlabel('Latitude [$\degree$N]')

ax[0].set_title('a) total = advection + neutral + horizontal')
ax[1].set_title('b) advection')
ax[2].set_title('c) neutral diffusion');
ax[3].set_title('d) horizontal diffusion');
_images/1086824657eccb53737e5567a82e3c151fcede032a16474611b3885482672e6f.png