import pandas
import numpy as np
import numpy.ma as ma
import matplotlib.pyplot as plt
from datetime import datetime
from oceans.colormaps import cm
from pandas import DataFrame, read_csv
from IPython.display import Image, SVG
from mpl_toolkits.basemap import Basemap
%config InlineBackend.figure_format='retina'
04 de Novembro de 2013
Prof. Filipe Fernandes
#Mistura simples de duas águas tipo.
Image('./figures/massa_dagua_mistura_01.png', retina=True)
# Mistura de três águas tipo.
Image('./figures/massa_dagua_mistura_02.png', retina=True)
# Detalhe da formação de um diagrama TS com uma mistura de três águas tipo.
Image('./figures/massa_dagua_mistura_03.png', retina=True)
%%latex
Modelo da Análise Clássica:
\begin{align*}
x_1 T_1 + x_2 T_2 + x_3 T_3 = T \\
x_1 S_1 + x_2 S_2 + x_3 S_3 = S \\
x_1 + x_2 + x_3 = 1 \\
\text{ou} \\
\mathbf{Ax} = \mathbf{b}
\end{align*}
%%latex
\begin{align*}
NO &= 9\text{NO}_3 + \text{O}_2 \\
PO &= 135\text{PO}_4 + \text{O}_2
\end{align*}
Infelizmente não podemos aplicar a equação acima diretamente. Isso porque:
Isso nos leva a um sistema que raramente fecha em 1 como esperado.
Por isso temos que utilizar uma solução por ajustes de mínimos quadrados do tipo:
\[D^2 = (\mathbf{Ax-b})'\mathbf{W}'\mathbf{W}(\mathbf{Ax-b})\]
# Load bottle data: http://www.nodc.noaa.gov/woce/woce_v3/wocedata_1/whp/
# These files are mMissing C14 data.
path = './WOCE/BOTTLE'
kw = dict(skiprows=5, skipfooter=1, skipinitialspace=True, header=0)
# http://www.nodc.noaa.gov/woce/woce_v3/wocedata_1/whp/data/onetime/atlantic/a16/a16s/index.htm
A16S = read_csv('%s/a16s_hy1.csv' % path, **kw)
A16S_units = A16S.iloc[0]
A16S = A16S.iloc[1:]
# http://www.nodc.noaa.gov/woce/woce_v3/wocedata_1/whp/data/onetime/atlantic/a16/a16c/index.htm
A16C = read_csv('%s/a16c_hy1.csv' % path, **kw)
A16C_units = A16C.iloc[0]
A16C = A16C.iloc[1:]
# http://www.nodc.noaa.gov/woce/woce_v3/wocedata_1/whp/data/onetime/atlantic/a16/a16n/index.htm
A16N = read_csv('%s/a16n_hy1.csv' % path, **kw)
A16N_units = A16N.iloc[0]
A16N = A16N.iloc[1:]
# Merge all 3 section into one.
A16 = A16S.merge(A16C, how='outer').merge(A16N, how='outer')
# Select a continous track.
maskN = np.logical_and(A16['STNNBR'] >= 1, A16['STNNBR'] <= 124)
maskS = np.logical_and(A16['STNNBR'] >= 237, A16['STNNBR'] <= 278)
maskC = np.logical_and(A16['STNNBR'] >= 314, A16['STNNBR'] <= 373)
mask = maskN | maskS | maskC
A16 = A16[mask]
# Use time as index.
dtime = [datetime.strptime('%s %03d' % (int(date), int(time)), '%Y%m%d %H%M') for
date, time in zip(A16['DATE'], A16['TIME'])]
# Drop unused and non-numeric columns.
A16.drop(['DATE','TIME', 'EXPOCODE', 'SECT_ID'], axis=1, inplace=True)
A16.index = dtime
# Force all values to be numeric.
A16 = A16.convert_objects(convert_numeric=True)
# Select good data using flags.
# Multiple NA is used. -999.00, -999.9 and etc,
# so I'm getting all data that is marked as less than -999.
A16[A16 <= -999] = np.NaN
def apply_flag(flag):
prop = flag.split('_')[0]
mask = A16[flag] == 2.
A16[prop][mask] = np.NaN
flags = ['BTLNBR_FLAG_W', 'CTDSAL_FLAG_W', 'SALNTY_FLAG_W', 'SILCAT_FLAG_W',
'OXYGEN_FLAG_W', 'NITRAT_FLAG_W', 'NITRIT_FLAG_W', 'PHSPHT_FLAG_W',
'CFC-11_FLAG_W', 'CFC-12_FLAG_W', 'TCARBN_FLAG_W', 'PCO2_FLAG_W']
for flag in flags:
# Not sure if "exchange" format already applied bad flag.
# So I'm just dropping that column.
#apply_flag(flag)
A16.drop([flag], axis=1, inplace=True)
# Compute AOU.
import seawater as sw
from oceans.sw_extras import o2sat
Osat = o2sat(A16['CTDSAL'].values, A16['THETA'].values)
A16['AOU'] = Osat - A16['OXYGEN']
# Normalize Total DIC.
A16['TCARBN_NORM'] = A16['TCARBN'] * (35 / A16['CTDSAL'])
pt = sw.ptmp(A16['CTDSAL'], A16['CTDTMP'], A16['CTDPRS'], pr=0)
A16['PDEN'] = sw.pden(A16['CTDSAL'], A16['CTDTMP'], A16['CTDPRS'], pr=0) - 1000
pandas.set_option('display.max_columns', 40)
A16.head(5)
STNNBR | CASTNO | SAMPNO | BTLNBR | LATITUDE | LONGITUDE | DEPTH | CTDPRS | CTDTMP | CTDSAL | SALNTY | OXYGEN | SILCAT | NITRAT | NITRIT | PHSPHT | CFC-11 | CFC-12 | THETA | TCARBN | PCO2 | PCO2TMP | CTDOXY | AOU | TCARBN_NORM | PDEN | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1989-02-01 13:45:00 | 237 | 1 | 1 | 1 | -32.4933 | -24.985 | 4148 | 3.4 | 22.4242 | 35.6509 | NaN | 226.1 | 1.47 | 0.29 | 0.00 | 0.09 | 1.930 | 1.011 | 22.4235 | NaN | NaN | NaN | NaN | -11.414709 | NaN | 24.592728 |
1989-02-01 13:45:00 | 237 | 1 | 2 | 2 | -32.4933 | -24.985 | 4148 | 22.0 | 21.8333 | 35.6469 | NaN | 229.4 | 1.47 | 0.29 | 0.00 | 0.08 | NaN | NaN | 21.8290 | NaN | NaN | NaN | NaN | -12.456727 | NaN | 24.757427 |
1989-02-01 13:45:00 | 237 | 1 | 3 | 3 | -32.4933 | -24.985 | 4148 | 51.1 | 18.6389 | 35.5794 | NaN | 251.7 | 1.47 | 0.29 | 0.00 | 0.11 | 2.242 | 1.150 | 18.6299 | NaN | NaN | NaN | NaN | -21.700186 | NaN | 25.558316 |
1989-02-01 13:45:00 | 237 | 1 | 4 | 4 | -32.4933 | -24.985 | 4148 | 63.1 | 17.3043 | 35.5762 | NaN | 256.9 | 1.37 | 0.29 | 0.00 | 0.14 | NaN | NaN | 17.2938 | NaN | NaN | NaN | NaN | -21.029268 | NaN | 25.886125 |
1989-02-01 13:45:00 | 237 | 1 | 5 | 5 | -32.4933 | -24.985 | 4148 | 83.5 | 16.2117 | 35.6145 | NaN | 244.2 | 1.37 | 0.39 | 0.02 | 0.18 | 2.274 | 1.159 | 16.1983 | NaN | NaN | NaN | NaN | -3.363767 | NaN | 26.174684 |
import os
from ctd import CTD
from glob import glob
from pandas import Panel
from collections import OrderedDict
def get_pattern(fname, pattern):
with open(fname) as f:
for line in f.readlines():
if line.startswith(pattern):
value = float(line.split('=')[1])
continue
return value
names = ['CTDPRS', 'CTDTMP', 'CTDSAL', 'CTDOXY']
def load_ctd_section(fname):
kw = dict(skiprows=20, skipfooter=1, names=names, usecols=(0, 2, 4, 6), index_col='CTDPRS')
df = read_csv(fname, **kw)
return CTD(df)
# Using only contiguous track.
maskN = range(1, 124 + 1)
maskS = range(237, 278 + 1)
maskC = range(314, 373 + 1)
mask = maskN + maskS + maskC
path = './WOCE/CTD'
A16S = sorted(glob('%s/a16s*.csv' % path), reverse=True)
A16C = sorted(glob('%s/a16c*.csv' % path), reverse=False)
A16N = sorted(glob('%s/a16n*.csv' % path), reverse=True)
fnames = A16S + A16C + A16N
lon, lat, CTD_A16 = [], [], OrderedDict()
for fname in fnames:
# Select a continous track.
STNNBR = int(fname.split('_')[1])
if STNNBR in mask:
name = os.path.basename(fname).split('.')[0]
lon.append(get_pattern(fname, 'LONGITUDE'))
lat.append(get_pattern(fname, 'LATITUDE'))
CTD_A16.update({STNNBR: load_ctd_section(fname)})
CTD_A16 = Panel.fromDict(CTD_A16)
CTDTMP = CTD(CTD_A16.minor_xs('CTDTMP'))
CTDTMP.lon, CTDTMP.lat = lon, lat
CTDSAL = CTD(CTD_A16.minor_xs('CTDSAL'))
CTDSAL.lon, CTDSAL.lat = lon, lat
from oceans.sw_extras import gamma_GP_from_SP_pt
p = CTDSAL.index.values.astype(float)
s, t, p, lon, lat = np.broadcast_arrays(CTDSAL.values, CTDTMP.values, p[..., None], lon, lat)
pt = sw.ptmp(s, t, p, pr=0)
#gamma = gamma_GP_from_SP_pt(s, pt, p, lon, lat)
pden = sw.pden(s, t, p, pr=0) - 1000
# Get bottles x, y.
from pandas import pivot_table
LON = pivot_table(A16, values='LONGITUDE', rows=['CTDPRS'], cols=['STNNBR'])
LAT = pivot_table(A16, values='LATITUDE', rows=['CTDPRS'], cols=['STNNBR'])
# Get just common stations.
common = []
for stn in CTDSAL.columns:
if stn in LON.columns:
common.append(stn)
common = np.array(common, np.float_)
LON = LON[common]
LAT = LAT[common]
DIST = np.r_[0, sw.dist(LAT.mean(), LON.mean())[0].cumsum()]
bottles = DataFrame(LON.notnull().values, index=LON.index, columns=DIST)
X, Y = np.meshgrid(bottles.columns.values, bottles.index.values)
X, Y = X[bottles.values], Y[bottles.values]
# Make salinity section.
lon_0 = 360 + A16['LONGITUDE'].mean()
lat_0 = A16['LATITUDE'].mean()
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
def make_basemap(projection='ortho', resolution='c', ax=None):
m = Basemap(projection=projection, resolution=resolution,
lon_0=lon_0, lat_0=lat_0, ax=ax)
m.drawcoastlines()
m.fillcontinents(color='0.85')
return fig, m
from ctd.plotting import plot_section
levels = np.arange(33.8, 37.4, 0.1)
fig, ax, cbar = plot_section(CTDSAL, cmap=cm.odv, marker=None,
levels=levels, figsize=(8, 4))
ax.set_ylabel('Depth [m]')
ax.set_xlabel('Distance in [km]')
axin = inset_axes(ax, width="40%", height="40%", loc=4)
fig, m = make_basemap(ax=axin)
kw = dict(marker='.', color='#FF9900', linestyle='none')
m.plot(*m(A16['LONGITUDE'].values, A16['LATITUDE'].values), **kw)
ax.plot(X, Y, 'k.', alpha=0.5, markersize=2)
levels = [22.5, 23, 24.5, 25.5, 26.5, 27, 27.5, 27.845, 27.9]
levels = [25, 26.5] + [27, 27.5] + [27.7, 27.85]
cs = ax.contour(DIST, CTDSAL.index.values.astype(float), ma.masked_invalid(pden), levels=levels, colors='k')
_ = ax.clabel(cs, fmt='%2.2f')
# Seção de salinidade no Atlântico.
Image('./figures/Atlantic_salinity.png', width=2206/4., height=1122/4., retina=False)
from oceans.ff_tools import lsqfitgm
def plot_gmfit(x, y, ax):
"""Plots the geometric mean functional regression (neutral regression.)"""
#y = interp_series(y)
m, b, r, sm, sb = lsqfitgm(x, y)
x = np.array([x.min(), x.max()])
ax.plot(x, m * x + b, 'k-', linewidth=2)
#r = trunc(r, 2)
ax.text(0.75, 0.10, r'$r^2 = %2.2f$' % r, transform=ax.transAxes)
# Plot equation.
if False:
ax.text(0.05, 0.93, r'$y = %2.2fx\pm%2.2f %+2.2f\pm%2.2f$' %
(m, sm, b, sb), transform=ax.transAxes)
cbarkw = dict(extend='both', orientation='vertical')
df = A16[['PHSPHT', 'NITRAT', 'OXYGEN']].dropna()
sckw = dict(cmap=cm.odv, alpha=0.7, vmax=350, vmin=0)
fig, ax = plt.subplots()
cs = ax.scatter(df['PHSPHT'], df['NITRAT'], c=df['OXYGEN'], **sckw)
fig.colorbar(cs, **cbarkw)
ax.set_title(r'Oxygen [$\mu$mol kg$^{-1}$]')
ax.set_xlabel(r'Phosphate [$\mu$mol kg$^{-1}$]')
ax.set_ylabel(r'Nitrate [$\mu$mol kg$^{-1}$]')
ax.axis('tight')
plot_gmfit(df['PHSPHT'], df['NITRAT'], ax)
Image('./figures/nvp_01.png', retina=True)
df = A16[['PHSPHT', 'TCARBN_NORM', 'AOU']].dropna()
sckw = dict(cmap=cm.odv, alpha=0.7, vmax=300, vmin=-25)
fig, ax = plt.subplots()
cs = ax.scatter(df['PHSPHT'], df['TCARBN_NORM'], c=df['AOU'], **sckw)
fig.colorbar(cs, **cbarkw)
ax.grid(True)
ax.set_title(r'AOU [$\mu$mol kg$^{-1}$]')
ax.set_xlabel(r'Phosphate [$\mu$mol kg$^{-1}$]')
ax.set_ylabel(r'nTDIC (Sal normalized) [$\mu$mol kq$^{-1}$]')
ax.axis('tight')
plot_gmfit(df['PHSPHT'], df['TCARBN_NORM'], ax)
# Fosfato vs Carbono Orgânico Dissolvido (oxigênio em cores).
Image('./figures/ntdicvp_01.png', retina=True)
df = A16[['PHSPHT', 'AOU']].dropna()
fig, ax = plt.subplots()
ax.scatter(df['PHSPHT'], df['AOU'], c='k', alpha=0.7)
ax.grid(True)
ax.set_xlabel(r'Phosphate [$\mu$mol kg$^{-1}$]')
ax.set_ylabel(r'AOU [$\mu$mol kg$^{-1}$]')
ax.axis('tight')
plot_gmfit(df['PHSPHT'], df['AOU'], ax)
Image('./figures/aouvsp.png', retina=True)
df = A16[['NITRAT', 'AOU']].dropna()
fig, ax = plt.subplots()
ax.scatter(df['NITRAT'], df['AOU'], c='k', alpha=0.7)
ax.grid(True)
ax.set_xlabel(r'Nitrate [$\mu$mol kg$^{-1}$]')
ax.set_ylabel(r'AOU [$\mu$mol kg$^{-1}$]')
ax.axis('tight')
plot_gmfit(df['NITRAT'], df['AOU'], ax)
Image('./figures/aouvn.png', retina=True)
df = A16[['TCARBN_NORM', 'AOU']].dropna()
fig, ax = plt.subplots()
ax.scatter(df['TCARBN_NORM'], df['AOU'], c='k', alpha=0.7)
ax.grid(True)
ax.set_xlabel(r'nTDIC (Sal normalized) [$\mu$mol kq$^{-1}$]')
ax.set_ylabel(r'AOU [$\mu$mol kg$^{-1}$]')
ax.axis('tight')
plot_gmfit(df['TCARBN_NORM'], df['AOU'], ax)
Image('./figures/auovdic.png', retina=True)
A relação estequiométrica C:AOU:N:P é 110.47:66.48:15.3:1. Mas apenas Nitrato vs Fosfato e DIC vs Fosfato mostraram uma relação linear.
AOU:P não mostra a mesma tendência linear devido a alta concentração de fosfato pré-formado na superfície.
Nutrientes pré-formados = Total de nutrientes - Nutrientes Regenerados
Nutrientes pré-formados são característicos da região de onde se original, podemos ser usados como traçadores de massas d'água!
Como se espera que a concentração de nutrientesaumente com o aumento do AOU todos os nutrientes que estão presentes em uma concentração maior que zero em AOU zero podem ser chamados de pré-formados.
É a diferença entre a concentração medida de Oxigênio dissolvido e a concentração do seu Estado de Equilíbrio de Saturação
Tais diferenção ocorrem por atividade biológica que muda o oxigênio disponível na água.
Consequentemte AOU de uma amostra de água representa a some da atividade biológica que essa amostra sofreu desde seu equilíbrio com a atmosfera.
df = A16[['PHSPHT', 'AOU', 'PDEN']].dropna()
mask = np.logical_and(df['PDEN'] >=25, df['PDEN'] <= 26.7)
df = df[mask]
fig, ax = plt.subplots()
ax.scatter(df['PHSPHT'], df['AOU'], c='k', alpha=0.7)
ax.grid(True)
ax.set_xlabel(r'Phosphate [$\mu$mol kg$^{-1}$]')
ax.set_ylabel(r'AOU [$\mu$mol kg$^{-1}$]')
ax.axis('tight')
plot_gmfit(df['PHSPHT'], df['AOU'], ax)
# AOU vs Fosfato apenas das camadas superficiais.
Image('./figures/aouvp.png', retina=True)
df = A16[['NITRAT', 'AOU', 'PDEN']].dropna()
mask = np.logical_and(df['PDEN'] >=25, df['PDEN'] <= 26.7)
df = df[mask]
fig, ax = plt.subplots()
ax.scatter(df['NITRAT'], df['AOU'], c='k', alpha=0.7)
ax.grid(True)
ax.set_xlabel(r'Nitrate [$\mu$mol kg$^{-1}$]')
ax.set_ylabel(r'AOU [$\mu$mol kg$^{-1}$]')
ax.axis('tight')
plot_gmfit(df['NITRAT'], df['AOU'], ax)
# AOU vs Carbônico orgânico Dissolvido apenas das camadas superficiais.
Image('./figures/aouvdic.png', retina=True)
A Taxa de PO\(_4^{-3}\) é estimado usando um ajuste linear entre Fosfato e idade.
A idade é derivada de dados de radio carbono. Isso porque o método do CFC é limitado a idades entre 5-35 anos, logo idades por 14C são recomendadas para massas d'água mais velhas (profundas).
A inclinação da reta é de 0.00139, o que significa que 1,39 \(\times\) 10\(^{-3} \mu\)mol de P é formado por ano.
Image('./figures/pvage.png', retina=True)
columns = [r'Temperature ($^\circ$C)', 'Salinity (PSU)', 'Silicate', r'NO ($\mu$mol kg$^{-1}$)']
index = ['AAIW', 'UCDW', 'UNADW', 'TDDW', 'AABW', 'Uncertainties']
data = np.c_[
[ 3.65, 2.53, 4, 2.3, 0.14, 0.16],
[34.18, 34.65, 34.97, 34.91, 34.68, 0.016],
[ 15, 67, 18, 36, 113, 1.9],
[ 506, 468, 422, 442, 526, 8.8]
]
df = DataFrame(data, index=index, columns=columns)
df.index.name = 'Water type'
df
Temperature ($^\circ$C) | Salinity (PSU) | Silicate | NO ($\mu$mol kg$^{-1}$) | |
---|---|---|---|---|
Water type | ||||
AAIW | 3.65 | 34.180 | 15.0 | 506.0 |
UCDW | 2.53 | 34.650 | 67.0 | 468.0 |
UNADW | 4.00 | 34.970 | 18.0 | 422.0 |
TDDW | 2.30 | 34.910 | 36.0 | 442.0 |
AABW | 0.14 | 34.680 | 113.0 | 526.0 |
Uncertainties | 0.16 | 0.016 | 1.9 | 8.8 |
%%latex
\begin{align*}
x_1 T_1 + x_2 T_2 + x_3 T_3 + x_4 T_4 &= T \\
x_1 S_1 + x_2 S_2 + x_3 S_3 + x_4 S_4 &= S \\
x_1 O_1 + x_2 O_2 + x_3 O_3 + x_4 O_4 + -\alpha J_o &= O \\
x_1 N_1 + x_2 N_2 + x_3 N_3 + x_4 N_4 + R_N\alpha J_o &= N \\
x_1 P_1 + x_2 P_2 + x_3 P_3 + x_4 P_4 + R_P\alpha J_o &= P \\
x_1 + x_2 + x_3 + x_4 &= 1
\end{align*}
Most of our knowledge of the circulation is somewhat indirect, using the geostrophic method to determine velocity referenced to a known velocity pattern at some depth. If the reference velocity pattern is not known well, then we must deduce it. Deduction of the absolute velocity field is based on all of the information that we can bring to bear. This includes identifying sources of waters, by their contrasting properties, and determining which direction they appear to spread on average.
Water properties are used to trace parcels over great distances. Over these distances, parcels mix with waters of other properties. It is assumed that mixing is easier along isentropic (isopycnal) surfaces than across them and certainly changes in T/S characteristics do often compensate (so that density remains unchanged). However, it is clear from distributions of some properties that of course there is mixing both along and across isopycnals (isopycnal and diapycnal mixing). This tracing of waters is useful (in conjunction with the relative geostrophic flow calculations that can be made from the observed density field), in order to describe the average general ocean circulation.
We use the concept of water masses as a convenient way to tag the basic source waters. The definition of a "water mass" is somewhat vague, but is in the sense of "cores" of high or low properties, such as salinity or oxygen, in the vertical and along isopycnal surfaces. A range of densities (depths) is usually considered for a given water mass. Water mass definitions may change as a layer is followed from one basin or ocean to another, particularly if the trans-basin exchange involves mixing.
Traditionally, water mass analysis was based on plotting various properties against each other, and attempting to explain the observed distributions of properties as a result of mixing between the identified "sources". However, point sources of waters occur only in relatively few regions, and in general "source" waters have a range of properties. The sources are almost always surface waters, or near-surface waters that are created by, say, air-sea interaction, brine rejection, or flow over and through a narrow passage/sill.
The most commonly used property-property displays are (a) potential temperature vs. salinity, and (b) properties along isopycnal surfaces. Figure. Potential temperature versus salinity along 20 and 25W in the Atlantic Ocean, from Iceland across the equator to South Georgia Island. Blue - equator to Iceland. Red - equator to about 30S. Green - 30S to South Georgia Island. The Atlantic 25W meridional potential temperature and salinity sections were already shown.
Tracers Seawater properties are valuable tools for tracing water parcels, because water mass formation processes imprint distinct properties on the water parcels. They are of most use when the sources and sinks of one property compared with another differ. Some tracers are biogenic and hence non-conservative. These include oxygen and the various nutrients, all discussed very briefly here. Some useful tracers are inert but with time-dependent inputs, such as chlorofluorocarbons (CFCs). Some useful tracers have decay times and decay products, which can serve as a useful measure of age, such as bomb tritium and helium. The latter are referred to as transient tracers, and are not discussed here.