Turning ripples into waves

Water-mass mixing percentage in the South Atlantic

This IPython notebook demonstrates how to calculate and plot the mixing percentage of the three major water masses from the South Atlantic.

The data used is freely available, and consist of temperature and salinity downloaded from the "World Ocean Atlas 2009".

The mixing percentage of each water mass is obtained by solving a linear system of equations where we have 3 unknowns and three equations according to Mamayev (1975):

$$ m_1T_1 + m_2T_2 + m_3T_3 = T $$ $$ m_1S_1 + m_2S_2 + m_3S_3 = S $$ $$ m_1 + m_2 + m_3 = 1 $$


  • $T, S$ are the temperature and salinity data where we want to know the mixing rations;
  • $T_n, S_n$ the temperature and salinity pairs for the water masses core 1, 2, and 3;
  • $m_1, m_2, m_3$ the water mass mixing percentages we are looking for.

The function below solves the linear system and returns the percentages for the mixing of three water masses.

In [2]:
import numpy as np
import as ma

def mixing(T, S, inds):
    Compute the water mass mixing percentage using Mamayev's (1975) mixing

    T : Conservative Temperature
    S : Absolute Salinity
    inds :  2x3 array with thermohaline indices
            [T1 T2 T3
            S1 S2 S3]

    m1, m2, m3 : Water mass percentage for masses 1, 2 e 3.


    a = np.r_[inds, np.ones((1, 3))]
    b = np.c_[T.ravel(), S.ravel(), np.ones(T.shape).ravel()].T
    m = np.linalg.solve(a, b)
    m1 = m[0].reshape(T.shape)
    m2 = m[1].reshape(T.shape)
    m3 = m[2].reshape(T.shape)

    # Mask values outside mixing triangle.
    m1 = ma.masked_outside(ma.masked_invalid(m1), 0, 1)
    m2 = ma.masked_outside(ma.masked_invalid(m2), 0, 1)
    m3 = ma.masked_outside(ma.masked_invalid(m3), 0, 1)

    m1 = 100 * m1
    m2 = 100 * m2
    m3 = 100 * m3
    return m1, m2, m3

The water masses we will investigate are the: South Atlantic Central Water (SACW) formed in the center of the South Atlantic Subtropical Gyre; the Antarctic Intermediary Water (AAIW) formed in the Antarctic Convergence zone originated by upwelling the North Atlantic Deep Water (NADW); and last but not least the NADW, which is formed across the Greenland-Iceland-Scotland Ridge. We could include more water masses in the analysis, for example: the Antarctic Bottom Water (AABW) and/or the Circumpolar Deep Water (CDW). However, that would require two more equations, and therefore two more conservative properties. Temperature and Salinity are the easiest conservative variables to compute. Other variables like oxygen, silicate and etc could be used, but would require a special treatment to became quasi-conservative to be included in the analysis.

We use the woa_subset() fucntion from the oceans module to download the data. To prevent downloading the data every time we save it in a file when it is first downloaded and then check if the file exist every time before running the notebook.

In [3]:
import os
from oceans.datasets import woa_subset

lon = -25.5
kw = dict(clim_type='annual', resolution='1deg', levels=slice(0, 40),
          llcrnrlat=-70, urcrnrlat=-10, llcrnrlon=lon, urcrnrlon=lon)

if os.path.isfile('./data/data.npz'):
    npz = np.load('./data/data.npz')
    depth = npz['depth']
    S, T = npz['S'], npz['T']
    longitude, latitude = npz['longitude'], npz['latitude']
    salinity = woa_subset(var='salinity', **kw)
    salinity = salinity['OA Climatology']['annual'].squeeze()

    temperature = woa_subset(var='temperature', **kw)
    temperature = temperature['OA Climatology']['annual'].squeeze()

    S = salinity.values.filled(fill_value=np.nan)
    T = temperature.values.filled(fill_value=np.nan)
    depth = temperature.columns.values.astype(float)
    latitude = temperature.index.values.astype(float)
    longitude = np.array([lon]*len(latitude))
    np.savez('./data/data.npz', T=T, S=S, longitude=longitude, latitude=latitude, depth=depth)

The cell below specifies the core T, S pairs for Tropical Water (TW) + South Atlantic Central Water (SACW), Antarctic Intermediary Water (AAIW), and North Atlantic Deep Water (NADW) respectively.

Caveat: The water masses cores used here were modified and may not correspond directly with the cores found in the literature. The reason for that was to show both the Tropical Water and the South Atlantic Central Water as one water mass.

In [4]:
cores = np.array([[26.42, 5, 4.05], [37.14, 33.90, 35.07]])
sacw, aaiw, nadw = mixing(T, S, cores)

These few lines below are just to get contour lines for $\sigma_{\theta}$.

In [5]:
import gsw
s = ma.masked_invalid(S).mean(axis=0)
t = ma.masked_invalid(T).mean(axis=0)
Te = np.linspace(t.min(), t.max(), 25)
Se = np.linspace(s.min(), s.max(), 25)

Tg, Sg = np.meshgrid(Te, Se)
sigma_theta = gsw.sigma0(Sg, Tg)
cnt = np.linspace(sigma_theta.min(), sigma_theta.max(), 10)

We need to define the color-maps for the contouring.

In [6]:
import brewer2mpl

Reds = brewer2mpl.get_map('Reds', 'Sequential', 9).mpl_colormap
Blues = brewer2mpl.get_map('Blues', 'Sequential', 9).mpl_colormap
Greens = brewer2mpl.get_map('Greens', 'Sequential', 9).mpl_colormap

And finally the final figure:

In [7]:
import matplotlib.pyplot as plt

from matplotlib.ticker import MaxNLocator
from mpl_toolkits.basemap import Basemap
from mpl_toolkits.axes_grid1 import make_axes_locatable
from mpl_toolkits.axes_grid1.inset_locator import inset_axes

# Grid for contouring.
zg, xg = np.meshgrid(depth, latitude)

fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(14, 6), facecolor='w')
divider = make_axes_locatable(ax)

# Main figure.
percent = [50, 60, 70, 80, 90, 100]
m1 = ax.contourf(xg, zg, sacw, percent, cmap=Reds, zorder=3)
m2 = ax.contourf(xg, zg, aaiw, percent, cmap=Greens, zorder=2)
m3 = ax.contourf(xg, zg, nadw, percent, cmap=Blues, zorder=1)

m1.set_clim(percent[0], percent[-1])
m2.set_clim(percent[0], percent[-1])
m3.set_clim(percent[0], percent[-1])

# Colorbars.
dy = 0.04
left, bottom, height, width = 0.14, 0.3, 0.13, 0.02

rect1 = [left, bottom, height, width]  # Top.
rect2 = [left, bottom - dy, height, width]  # Center.
rect3 = [left, bottom - 2*dy, height, width]  # Bottom.

cax1 = plt.axes(rect1, axisbg='w')
cax2 = plt.axes(rect2, axisbg='w')
cax3 = plt.axes(rect3, axisbg='w')

kw = dict(orientation='horizontal', extend='min')
cb1 = fig.colorbar(m1, cax=cax1, **kw)
cb2 = fig.colorbar(m2, cax=cax2, **kw)
cb3 = fig.colorbar(m3, cax=cax3, **kw)


new_labels = ['%s%%' % l.get_text() for l in cax3.xaxis.get_ticklabels()]

kw = dict(rotation=0, labelpad=20, y=1,
            verticalalignment='center', horizontalalignment='left')'SACW', **kw)"right")'AAIW', **kw)"right")'NADW', **kw)"right")

# Some tweaking.


new_labels = [r'%s $^\circ$S' % l.get_text()[1:] for l in

new_labels = [r'%s m' % l.get_text() for l in
ax.set_xlabel(u'WOA09 water masses at longitude %3.1f\u00B0' % lon)

axin = inset_axes(ax, width="35%", height="35%", loc=4)
inmap = Basemap(projection='ortho', lon_0=lon, lat_0=0,
                ax=axin, anchor='NE')
inmap.plot(longitude, latitude, 'r', latlon=True)

# T-S Diagram.
axTS = divider.append_axes("right", 2, pad=0.1)
cs = axTS.contour(Sg, Tg, sigma_theta, colors='grey', levels=cnt, zorder=1)
kw = dict(color='r', fontsize=14, fontweight='black')
axTS.text(35.00, 11.9, 'SACW', **kw)
axTS.text(34.50, 3.12, 'AAIW', **kw)
axTS.text(34.82, 1.72, 'NADW', **kw)

axTS.plot(s, t, 'k')
axTS.set_xlabel("Salinity [g kg$^{-1}$]")
axTS.set_ylabel("Temperature [$^\circ$C]", rotation=-90, labelpad=20)
axTS.set_title("T-S Diagram")

The contouring of the mixing percentages is a simple, yet powerful tool to understand mixing the ocean.

Matplotlib allows us to contour the results with three separated colormaps, facilitating the interpretation. A world map inset shows the transect (in red) of the data. In additional, we plot the Temperature-Salinity Diagram (T-S), to help visualize each the water mass center. The T-S diagram displays the seawater density (as gray contours) from lighter to heavier water (at the top and at the bottom of the figure respectively) and the meridional mean of the temperature and salinity.

We can see that we have both AAIW and SACW cores (100%) in our domain. We can also note that, even though the SACW occupies the majority of the T-S diagram range, it is not the major water mass in our domain. Another important remark is that the NADW reaches from the North Atlantic all the way to the Antarctica, where it mixes with colder and fresher water to creates the AAIW. That is an important part to the global Conveyor Belt (or Thermohaline Circulation).

The "white" gap at bottom the contour represents the AABW and the CDW that are not resolved in our model. The overlap of the contour is an evidence of a poor choice for the water mass cores (probably bad choices for the water masses cores).

In [8]:

This post was written as an IPython notebook. It is available for download or as a static html.

Creative Commons License
python4oceanographers by Filipe Fernandes is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
Based on a work at