import gsw
import numpy as np
from scipy import interpolate, signal
[docs]
def nsqfcn(s, t, p, p0, dp, lon, lat):
"""Calculate square of buoyancy frequency [rad/s]^2 for profile of
temperature, salinity and pressure.
The Function: (1) low-pass filters t,s,p over dp,
(2) linearly interpolates t and s onto pressures, p0,
p0+dp, p0+2dp, ....,
(3) computes upper and lower potential densities at
p0+dp/2, p0+3dp/2,...,
(4) converts differences in potential density into nsq
(5) returns NaNs if the filtered pressure is not
monotonic.
If you want to have the buoyancy frequency in [cyc/s] then calculate
sqrt(n2)./(2*pi). For the period in [s] do sqrt(n2).*2.*pi
Adapted from Gregg and Alford.
Gunnar Voet
gvoet@ucsd.edu
Parameters
----------
s : float
Salinity
t : float
In-situ temperature
p : float
Pressures
p0 : float
Lower bound (start) pressure for output values (not important...)
dp : float
Pressure interval of output data
lon : float
Longitude of observation
lat : float
Latitude of observation
Returns
-------
n2 : Buoyancy frequency squared in (rad/s)^2
pout : Pressure vector for n2
"""
G = 9.80655
# Make sure data has dtype np.ndarray
if type(s) is not np.ndarray:
s = np.array(s)
if type(p) is not np.ndarray:
p = np.array(p)
if type(t) is not np.ndarray:
t = np.array(t)
# Delete negative pressures
xi = np.where(p >= 0)
p = p[xi]
s = s[xi]
t = t[xi]
# Exclude nan in t and s
xi = np.where((~np.isnan(s)) & (~np.isnan(t)))
p = p[xi]
s = s[xi]
t = t[xi]
# Put out all nan if no good data left
if ~p.any():
n2 = np.nan
pout = np.nan
# Reverse order of upward profiles
if p[-1] < p[0]:
p = p[::-1]
t = t[::-1]
s = s[::-1]
# Low pass filter temp and salinity to match specified dp
dp_data = np.diff(p)
dp_med = np.median(dp_data)
# [b,a]=butter(4,2*dp_med/dp); %causing problems...
a = 1
b = np.hanning(2 * np.floor(dp / dp_med))
b = b / np.sum(b)
tlp = signal.filtfilt(b, a, t)
slp = signal.filtfilt(b, a, s)
plp = signal.filtfilt(b, a, p)
# Check that p is monotonic
if np.all(np.diff(plp) >= 0):
pmin = plp[0]
pmax = plp[-1]
# # Sort density if opted for
# if sort_dens
# rho = sw_pden(slp,tlp,plp,plp);
# [rhos, si] = sort(rho,'ascend');
# tlp = tlp(si);
# slp = slp(si);
# end
while p0 <= pmin:
p0 = p0 + dp
# End points of nsq window
pwin = np.arange(p0, pmax, dp)
ft = interpolate.interp1d(plp, tlp)
t_ep = ft(pwin)
fs = interpolate.interp1d(plp, slp)
s_ep = fs(pwin)
# Determine the number of output points
(npts,) = t_ep.shape
# Compute pressures at center points
pout = np.arange(p0 + dp / 2, np.max(pwin), dp)
# Compute potential density of upper window pts at output pressures
sa_u = gsw.SA_from_SP(s_ep[0:-1], t_ep[0:-1], lon, lat)
pd_u = gsw.pot_rho_t_exact(sa_u, t_ep[0:-1], pwin[0:-1], pout)
# Compute potential density of lower window pts at output pressures
sa_l = gsw.SA_from_SP(s_ep[1:], t_ep[1:], lon, lat)
pd_l = gsw.pot_rho_t_exact(sa_l, t_ep[1:], pwin[1:], pout)
# Compute buoyancy frequency squared
n2 = G * (pd_l - pd_u) / (dp * pd_u)
else:
print(" filtered pressure not monotonic")
n2 = np.nan
pout = np.nan
return n2, pout
[docs]
def adiabatic_leveling(
P,
S,
T,
lon,
lat,
bin_width=100.0,
order=1,
return_diagnostics=False,
cap=None,
):
"""Generate smooth buoyancy frequency profile by adiabatic leveling.
Parameters
----------
P : 1-D ndarray
Pressure [dbar]
S : 1-D ndarray
Practical salinity [-]
T : 1-D ndarray
Temperature [degrees C]
lon : float
Longitude [-180...+360]
lat : float
Latitude [-90...+90]
bin_width : float, optional
Pressure bin width [dbar]
order : int, optional
Degree of polynomial fit.
return_diagnostics : bool, optional
Flag to return additional argument pcoefs. False by default.
cap : {None, 'left', 'right', 'both'}, optional
Flag to change proceedure at ends of array where bins may be partially
filled. None by default, meaning they are included. Can also specify
'left', 'right' or 'both' to cap method before partial bins.
Returns
-------
N2_ref : 1-D ndarray
Reference buoyancy frequency [s-2]
pcoefs : 2-D ndarray
Fitting coefficients, returned only when the flag return_diagnostics is
set True.
Notes
-----
Based on method of Bray and Fofonoff (1981) :cite:`Bray1981`.
"""
valid = np.isfinite(P) & np.isfinite(S) & np.isfinite(T)
valid = np.squeeze(np.argwhere(valid))
P_, S_, T_ = P[valid], S[valid], T[valid]
flip = False
if (np.diff(P_) < 0).all():
flip = True
P_ = np.flipud(P_)
S_ = np.flipud(S_)
T_ = np.flipud(T_)
elif (np.diff(P_) < 0).any():
raise ValueError("P must be monotonically increasing/decreasing.")
i1 = np.searchsorted(P_, P_ - bin_width / 2.0)
i2 = np.searchsorted(P_, P_ + bin_width / 2.0)
if cap is None:
Nd = P_.size
elif cap == "both":
icapl = i2[0]
icapr = i1[-1]
elif cap == "left":
icapl = i2[0]
icapr = i2[-1]
elif cap == "right":
icapl = i1[0]
icapr = i1[-1]
else:
raise ValueError(
"The argument cap must be either None, 'both', 'left' or 'right'"
)
if cap is not None:
i1 = i1[icapl:icapr]
i2 = i2[icapl:icapr]
valid = valid[icapl:icapr]
Nd = icapr - icapl
dimax = np.max(i2 - i1)
Pb = np.full((dimax, Nd), np.nan)
Sb = np.full((dimax, Nd), np.nan)
Tb = np.full((dimax, Nd), np.nan)
for i in range(Nd):
imax = i2[i] - i1[i]
Pb[:imax, i] = P_[i1[i] : i2[i]]
Sb[:imax, i] = S_[i1[i] : i2[i]]
Tb[:imax, i] = T_[i1[i] : i2[i]]
Pbar = np.nanmean(Pb, axis=0)
SAb = gsw.SA_from_SP(Sb, Pb, lon, lat)
rho = gsw.pot_rho_t_exact(SAb, Tb, Pb, Pbar)
sv = 1.0 / rho
rhobar = np.nanmean(rho, axis=0)
p = np.full((order + 1, Nd), np.nan)
for i in range(Nd):
imax = i2[i] - i1[i]
p[:, i] = np.polyfit(Pb[:imax, i], sv[:imax, i], order)
g = gsw.grav(lat, Pbar)
# The factor 1e-4 is needed for conversion from dbar to Pa.
if order == 1:
N2 = -1e-4 * rhobar**2 * g**2 * p[0, :]
elif order == 2:
N2 = -1e-4 * rhobar**2 * g**2 * (p[1, :] + 2 * Pbar * p[0, :])
elif order == 3:
N2 = (
-1e-4
* rhobar**2
* g**2
* (p[2, :] + 2 * Pbar * p[1, :] + 3 * Pbar**2 * p[0, :])
)
else:
raise ValueError("Fits are only included up to 3rd order.")
N2_ref = np.full_like(P, np.nan)
pcoef = np.full((order + 1, P.size), np.nan)
if flip:
N2_ref[valid] = np.flipud(N2)
pcoef[:, valid] = np.fliplr(p)
else:
N2_ref[valid] = N2
pcoef[:, valid] = p
if return_diagnostics:
return N2_ref, pcoef
else:
return N2_ref