def common_thermodynamics(depth, lon, lat, SP, t): """Wrapper for various thermodynamic calculations. Assumes input data is 1D with size N or M, or 2D with size N*M, where M denotes profiles and N depths. Parameters ---------- depth : numpy array Depth (m), size N. lon : numpy array Longitude, size M. lat : numpy array Latitude, size M. SP : numpy array Practical salinity, size N*M. t : numpy array Temperature, size N*M. """ p = gsw.p_from_z(-depth, np.mean(lat)) SA = gsw.SA_from_SP(SP, p[:, np.newaxis], lon[np.newaxis, :], lat[np.newaxis, :]) CT = gsw.CT_from_t(SA, t, p[:, np.newaxis]) sig0 = gsw.pot_rho_t_exact(SA, t, p[:, np.newaxis], 0) N2, p_mid = gsw.Nsquared(SA, CT, p[:, np.newaxis], lat[np.newaxis, :]) return p, SA, CT, sig0, p_mid, N2
def tsappen_ct(p, **kwargs): ''' add potential density contours to the current axis ''' ax = kwargs.get('axis', plt.gca()) pref = kwargs.get('pref', 0) levels = kwargs.get('levels', np.arange(20, 31)) colors = kwargs.get('colors', 'k') keys = ['axis', 'pref', 'levels', 'colors'] for key in keys: if key in kwargs: kwargs.pop(key) xlim = ax.get_xlim() ylim = ax.get_ylim() xax = np.arange(np.min(xlim) - 0.01, np.max(xlim) + 0.01, 0.01) yax = np.arange(np.min(ylim) - 0.01, np.max(ylim) + 0.01, 0.01) sa, ct = np.meshgrid(xax, yax) # pden = sw.pden(x, y, pref, 0)-1000 t = gsw.t_from_CT(sa, ct, p) pden = gsw.pot_rho_t_exact(sa, t, pref, 0) - 1000 c = plt.contour(sa, ct, pden, levels, colors=colors, **kwargs) plt.clabel(c, fmt='%2.1f')
def compute_potdens(ds, saltname='SALT', tempname='THETA'): import gsw """ compute the potential density """ # compute the Conservative Temperature from the model's potential temperature temp = ds[tempname].transpose(*('time', 'k', 'face', 'j', 'i')) salt = ds[tempname].transpose(*('time', 'k', 'face', 'j', 'i')) CT = gsw.CT_from_pt(salt, temp) z, lat = xr.broadcast(ds['Z'], ds['YC']) z = z.transpose(*('k', 'face', 'j', 'i')) lat = lat.transpose(*('k', 'face', 'j', 'i')) # compute pressure from depth p = gsw.p_from_z(z, lat) # compute in-situ temperature T = gsw.t_from_CT(salt, CT, p) # compute potential density rho = gsw.pot_rho_t_exact(salt, T, p, 0.) # create new dataarray darho = xr.full_like(temp, 0.) darho = darho.load().chunk({'time': 1, 'face': 1}) darho.name = 'RHO' darho.attrs['long_name'] = 'Potential Density ref at 0m' darho.attrs['standard_name'] = 'RHO' darho.attrs['units'] = 'kg/m3' darho.values = rho # filter special value darho = darho.where(darho > 1000) darho = darho.assign_coords(XC=ds['XC'], YC=ds['YC'], Z=ds['Z']) return darho
def potential_density(salt_PSU, temp_C, pres_db, lat, lon, pres_ref=0): """ Calculate density from glider measurements of salinity and temperature. The Basestation calculates density from absolute salinity and potential temperature. This function is a wrapper for this functionality, where potential temperature and absolute salinity are calculated first. Note that a reference pressure of 0 is used by default. Parameters ---------- salt_PSU : array, dtype=float, shape=[n, ] practical salinty temp_C : array, dtype=float, shape=[n, ] temperature in deg C pres_db : array, dtype=float, shape=[n, ] pressure in decibar lat : array, dtype=float, shape=[n, ] latitude in degrees north lon : array, dtype=float, shape=[n, ] longitude in degrees east Returns ------- potential_density : array, dtype=float, shape=[n, ] Note ---- Using seawater.dens does not yield the same results as this function. We get very close results to what the SeaGlider Basestation returns with this function. The difference of this function with the basestation is on average ~ 0.003 kg/m3 """ try: import gsw salt_abs = gsw.SA_from_SP(salt_PSU, pres_db, lon, lat) temp_pot = gsw.t_from_CT(salt_abs, temp_C, pres_db) pot_dens = gsw.pot_rho_t_exact(salt_abs, temp_pot, pres_db, pres_ref) except ImportError: import seawater as sw pot_dens = sw.pden(salt_PSU, temp_C, pres_db, pres_ref) pot_dens = transfer_nc_attrs( getframe(), temp_C, pot_dens, 'potential_density', units='kg/m3', comment='', standard_name='potential_density', ) return pot_dens
def densitystep(S, T, P): """ """ assert S.shape == T.shape assert S.shape == P.shape try: import gsw rho0 = gsw.pot_rho_t_exact(S, T, P, 0) assert S.ndim == 1, "Not able to densitystep an array ndim > 1" ds = ma.concatenate([ma.masked_all(1), np.sign(np.diff(P))*np.diff(rho0)]) return ma.fix_invalid(ds) except ImportError: print("Package gsw is required and is not available.")
def densitystep(S, T, P): """ """ assert S.shape == T.shape assert S.shape == P.shape try: import gsw rho0 = gsw.pot_rho_t_exact(S, T, P, 0) assert S.ndim == 1, "Not able to densitystep an array ndim > 1" ds = ma.concatenate( [ma.masked_all(1), np.sign(np.diff(P)) * np.diff(rho0)]) return ma.fix_invalid(ds) except ImportError: print("Package gsw is required and is not available.")
def _compute_data(self,data, units, names, p_ref = 0, baltic = False, lon=0, lat=0, isen = '0'): """ Computes convservative temperature, absolute salinity and potential density from input data, expects a recarray with the following entries data['C']: conductivity in mS/cm, data['T']: in Situ temperature in degree Celsius (ITS-90), data['p']: in situ sea pressure in dbar Arguments: p_ref: Reference pressure for potential density baltic: if True use the Baltic Sea density equation instead of open ocean lon: Longitude of ctd cast default=0 lat: Latitude of ctd cast default=0 Returns: list [cdata,cunits,cnames] with cdata: recarray with entries 'SP', 'SA', 'pot_rho', etc., cunits: dictionary with units, cnames: dictionary with names """ sen = isen + isen # Check for units and convert them if neccessary if(units['C' + isen] == 'S/m'): logger.info('Converting conductivity units from S/m to mS/cm') Cfac = 10 if(('68' in units['T' + isen]) or ('68' in names['T' + isen]) ): logger.info('Converting IPTS-68 to T90') T = gsw.t90_from_t68(data['T' + isen]) else: T = data['T' + isen] SP = gsw.SP_from_C(data['C' + isen], T, data['p']) SA = gsw.SA_from_SP(SP,data['p'],lon = lon, lat = lat) if(baltic == True): SA = gsw.SA_from_SP_Baltic(SA,lon = lon, lat = lat) PT = gsw.pt0_from_t(SA, T, data['p']) CT = gsw.CT_from_t(SA, T, data['p']) pot_rho = gsw.pot_rho_t_exact(SA, T, data['p'], p_ref) names = ['SP' + sen,'SA' + sen,'pot_rho' + sen,'pt0' + sen,'CT' + sen] formats = ['float','float','float','float','float'] cdata = {} cdata['SP' + sen] = SP cdata['SA' + sen] = SA cdata['pot_rho' + sen] = pot_rho cdata['pt' + sen] = PT cdata['CT' + sen] = CT cnames = {'SA' + sen:'Absolute salinity','SP' + sen: 'Practical Salinity on the PSS-78 scale', 'pot_rho' + sen: 'Potential density', 'pt' + sen:'potential temperature with reference sea pressure (p_ref) = 0 dbar', 'CT' + sen:'Conservative Temperature (ITS-90)'} cunits = {'SA' + sen:'g/kg','SP' + sen:'PSU','pot_rho' + sen:'kg/m^3' ,'CT' + sen:'deg C','pt' + sen:'deg C'} return [cdata,cunits,cnames]
def densitystep(S, T, P): """Estimates the potential density step of successive mesurements Expects the data to be recorded along the time, i.e. first measurement was recorded first. This makes difference since the first measurement has no reference to define the delta change. This is relevant for the type of instrument. For instance: XBTs are always measured surface to bottom, CTDs are expected the same, but Spray underwater gliders measure bottom to surface. """ assert T.shape == P.shape assert T.shape == S.shape assert T.ndim == 1, "Not ready to densitystep an array ndim > 1" try: import gsw except ImportError: print("Package gsw is required and is not available.") rho0 = gsw.pot_rho_t_exact(S, T, P, 0) ds = ma.concatenate([ma.masked_all(1), np.sign(np.diff(P)) * np.diff(rho0)]) return ma.fix_invalid(ds)
def densitystep(SA, t, p, auto_rotate=False): """Estimates the potential density step of successive mesurements Expects the data to be recorded along the time, i.e. first measurement was recorded first. This makes difference since the first measurement has no reference to define the delta change. This is relevant for the type of instrument. For instance: XBTs are always measured surface to bottom, CTDs are expected the same, but Spray underwater gliders measure bottom to surface. """ assert np.shape(t) == np.shape(p) assert np.shape(t) == np.shape(SA) assert np.ndim(t) == 1, "Not ready to densitystep an array ndim > 1" rho0 = gsw.pot_rho_t_exact(SA, t, p, 0) y = np.nan * np.atleast_1d(t) y[1:] = np.sign(np.diff(p)) * np.diff(rho0) if isinstance(y, ma.MaskedArray): y[y.mask] = np.nan y = y.data return y
def __init__(self, rho, z, salt=None, density_class=InterpDensity, **kwargs): self.__dict__.update(**kwargs) if salt is None: self.rho = rho else: # Compute potential density from the nonlinear EOS self.rho = gsw.pot_rho_t_exact(salt, rho, -z, 0.) # Check monotonicity of z assert np.all(np.diff(z)>0),\ 'input z must be increasing, z=0 at surface and positive up' self.z = z self.Fi = density_class(self.rho, self.z,\ density_func=self.density_func, order=self.order,\ initguess=self.initguess, bounds=self.bounds)
for a in csec: a = np.asarray(a) if a.size < 1: sec_tstart.append(np.nan) sec_tend.append(np.nan) elif a.size == 1: sec_tstart.append(ctd.time[a - 1] - 60 / 86400) sec_tend.append(ctd.time[a - 1] + 60 / 86400) else: sec_tstart.append(ctd.time[a[0] - 1] - 60 / 86400) sec_tend.append(ctd.time[a[-1] - 1] + 60 / 86400) ctd.SA = gsw.SA_from_SP(ctd.S, ctd.P, ctd.lon[np.newaxis, :], ctd.lat[np.newaxis, :]) ctd.CT = gsw.CT_from_t(ctd.SA, ctd.T, ctd.P) ctd.sig0 = gsw.pot_rho_t_exact(ctd.SA, ctd.T, ctd.P, 0) ctd_datavars = { "times": ( ["depth", "profile"], utils.datenum_to_datetime(ctd.time_full), { "Variable": "Measurement time" }, ), "p": (["depth", "profile"], ctd.P, { "Variable": "Pressure" }), "SP": (["depth", "profile"], ctd.S, { "Variable": "Practical salinity" }),
def adiabatic_level(P, SA, T, lat, P_bin_width=200., deg=1): """Generate smooth buoyancy frequency profile by applying the adiabatic levelling method of Bray and Fofonoff (1981). THIS FUNCTION HAS BEEN SUPERSEEDED BY: adiabatic_level_sw adiabatic_level_gsw Both of which are significantly faster by over a factor of 10. Parameters ---------- P : 1-D ndarray Pressure [dbar] SA : 1-D ndarray Absolute salinity [g/kg] T : 1-D ndarray Temperature [degrees C] lat : float Latitude [-90...+90] p_bin_width : float, optional Pressure bin width [dbar] deg : int, optional Degree of polynomial fit. (DEGREES HIGHER THAN 1 NOT YET TESTED) Returns ------- N2_ref : 1-D ndarray Reference buoyancy frequency [s-2] Notes ----- Calls to the gibbs seawater toolbox are slow and therefore this function is quite slow. """ N2_ref = np.NaN * P.copy() nans = np.isnan(P) | np.isnan(SA) | np.isnan(T) # If there are nothing but NaN values don't waste time. if np.sum(nans) == nans.size: return N2_ref P = P[~nans] SA = SA[~nans] T = T[~nans] P_min, P_max = np.min(P), np.max(P) shape = (P.size, P.size) Pm = np.NaN * np.empty(shape) SAm = np.NaN * np.empty(shape) Tm = np.NaN * np.empty(shape) # Populate bins. for i in range(len(P)): P_bin_min = np.maximum(P[i] - P_bin_width / 2., P_min) P_bin_max = np.minimum(P[i] + P_bin_width / 2., P_max) in_bin = np.where((P >= P_bin_min) & (P <= P_bin_max))[0] Pm[in_bin, i] = P[in_bin] SAm[in_bin, i] = SA[in_bin] Tm[in_bin, i] = T[in_bin] P_bar = np.nanmean(Pm, axis=0) T_bar = np.nanmean(Tm, axis=0) SA_bar = np.nanmean(SAm, axis=0) # Perform thermodynamics once only... rho_bar = gsw.pot_rho_t_exact(SA_bar, T_bar, P_bar, P_bar) sv = 1. / gsw.pot_rho_t_exact(SAm, Tm, Pm, P_bar) p = [] for P_bin, sv_bin in zip(Pm.T, sv.T): bnans = np.isnan(P_bin) p.append( np.polyfit(P_bin[~bnans], sv_bin[~bnans] - np.nanmean(sv_bin), deg)) p = np.asarray(p) g = gsw.grav(lat, P_bar) # The factor 1e-4 is needed for conversion from dbar to Pa. N2_ref[~nans] = -1e-4 * rho_bar**2 * g**2 * p[:, 0] return N2_ref
def adiabatic_level_gsw(P, S, T, lon, lat, bin_width=100., order=1, ret_coefs=False, cap=None): """Generate smooth buoyancy frequency profile by applying the adiabatic levelling method of Bray and Fofonoff (1981). This function uses the newest theormodynamic toolbox, 'gsw'. 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. (DEGREES HIGHER THAN 1 NOT PROPERLY TESTED) ret_coefs : bool, optional Flag to return additional argument pcoefs. False by default. cap : 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 ret_coefs is set True. """ 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.) i2 = np.searchsorted(P_, P_ + bin_width / 2.) 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. / 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 ret_coefs: return N2_ref, pcoef else: return N2_ref
fill=fill) # Interpolate salinity and pressure to level of thermistors print("Interpolating CTD") for i in tqdm(range(ctd.time.size)): nans = np.isnan(ctd.p[:, i]) ctd.p[nans, i] = np.interp(ctd.depth_nominal[nans], ctd.depth_nominal[~nans], ctd.p[~nans, i]) ctd.SP[nans, i] = np.interp(ctd.p[nans, i], ctd.p[~nans, i], ctd.SP[~nans, i]) # Thermodynamics ctd.depth = -gsw.z_from_p(ctd.p, ctd.lat) ctd.SA = gsw.SA_from_SP(ctd.SP, ctd.p, ctd.lon, ctd.lat) ctd.CT = gsw.CT_from_t(ctd.SA, ctd.t, ctd.p) ctd.sig0 = gsw.pot_rho_t_exact(ctd.SA, ctd.t, ctd.p, 0) ctd.N2, ctd.p_mid = gsw.Nsquared(ctd.SA, ctd.CT, ctd.p, ctd.lat) ctd.b = -g * (ctd.sig0 - rho0) / rho0 ctd = utils.apply_utm(ctd) # ADCP print("Despiking ADCP") adcp.depth = adcp.pop("z") n1 = 2 n2 = 5 size = 201 xmin = -1.0 xmax = 1.0 fill = True for i in tqdm(range(adcp.depth.size)): adcp.u[i, :] = utils.despike(adcp.u[i, :],
uni['den']['cmap']=pden_cmap uni['den']['vmin']=24.5 uni['den']['vmax']=28 uni['o2']={} uni['o2']['vmin']=270 uni['o2']['vmax']=325 uni['o2']['cmap']=cm.rainbow uni['odiff']={} uni['odiff']['vmin']=-20 uni['odiff']['vmax']=20 uni['odiff']['cmap']=cm.RdBu_r salvec=linspace(31,36,103) tmpvec=linspace(-3,16,103) salmat,tmpmat=meshgrid(salvec,tmpvec) SA_vec=gsw.SA_from_SP(salvec,zeros(len(salvec)),-44,59.5) SA_vec_1000=gsw.SA_from_SP(salvec,1e3*ones(len(salvec)),-44,59.5) CT_vec=gsw.CT_from_pt(SA_vec,tmpvec) pdenmat=zeros((shape(salmat))) pdenmat2=zeros((shape(salmat))) sigma1mat=zeros((shape(salmat))) for ii in range(len(salvec)): for jj in range(len(tmpvec)): pdenmat[jj,ii]=gsw.sigma0(SA_vec[ii],CT_vec[jj]) pdenmat2[jj,ii]=gsw.pot_rho_t_exact(SA_vec[ii],tmpvec[jj],750,0)-1e3 sigma1mat[jj,ii]=gsw.sigma1(SA_vec[ii],CT_vec[jj])
def adiabatic_level(P, SA, T, lat, P_bin_width=200., deg=1): """Generate smooth buoyancy frequency profile by applying the adiabatic levelling method of Bray and Fofonoff (1981). Parameters ---------- P : 1-D ndarray Pressure [dbar] SA : 1-D ndarray Absolute salinity [g/kg] T : 1-D ndarray Temperature [degrees C] lat : float Latitude [-90...+90] p_bin_width : float, optional Pressure bin width [dbar] deg : int, optional Degree of polynomial fit. (DEGREES HIGHER THAN 1 NOT YET TESTED) Returns ------- N2_ref : 1-D ndarray Reference buoyancy frequency [s-2] Notes ----- Calls to the gibbs seawater toolbox are slow and therefore this function is quite slow. """ N2_ref = np.NaN*P.copy() nans = np.isnan(P) | np.isnan(SA) | np.isnan(T) # If there are nothing but NaN values don't waste time. if np.sum(nans) == nans.size: return N2_ref P = P[~nans] SA = SA[~nans] T = T[~nans] P_min, P_max = np.min(P), np.max(P) shape = (P.size, P.size) Pm = np.NaN*np.empty(shape) SAm = np.NaN*np.empty(shape) Tm = np.NaN*np.empty(shape) # Populate bins. for i in xrange(len(P)): P_bin_min = np.maximum(P[i] - P_bin_width/2., P_min) P_bin_max = np.minimum(P[i] + P_bin_width/2., P_max) in_bin = np.where((P >= P_bin_min) & (P <= P_bin_max))[0] Pm[in_bin, i] = P[in_bin] SAm[in_bin, i] = SA[in_bin] Tm[in_bin, i] = T[in_bin] P_bar = np.nanmean(Pm, axis=0) T_bar = np.nanmean(Tm, axis=0) SA_bar = np.nanmean(SAm, axis=0) # Perform thermodynamics once only... rho_bar = gsw.pot_rho_t_exact(SA_bar, T_bar, P_bar, P_bar) sv = 1./gsw.pot_rho_t_exact(SAm, Tm, Pm, P_bar) p = [] for P_bin, sv_bin in zip(Pm.T, sv.T): bnans = np.isnan(P_bin) p.append(np.polyfit(P_bin[~bnans], sv_bin[~bnans] - np.nanmean(sv_bin), deg)) p = np.asarray(p) g = gsw.grav(lat, P_bar) # The factor 1e-4 is needed for conversion from dbar to Pa. N2_ref[~nans] = -1e-4*rho_bar**2*g**2*p[:, 0] return N2_ref
elif ndim == 2 and item.shape[1] == nnans.size: MP[key] = item[:, nnans] print('- Renaming variables and calculating new ones') Np = len(MP['id']) if np.ndim(MP['lon']) != 0: MP['lon'] = MP.pop('lon')[0] MP['lat'] = MP.pop('lat')[0] MP['P'] = np.tile(MP['p'][:, np.newaxis].astype(float), (1, Np)) MP['T'] = MP.pop('t') MP['S'] = MP.pop('s') MP['z'] = gsw.z_from_p(MP['P'], MP['lat']) MP['HAB'] = MP['z'] - np.nanmin(MP['z']) + 20. MP['SA'] = gsw.SA_from_SP(MP['S'], MP['P'], MP['lon'], MP['lat']) MP['CT'] = gsw.CT_from_t(MP['SA'], MP['T'], MP['P']) MP['sig4'] = gsw.pot_rho_t_exact(MP['SA'], MP['T'], MP['P'], 4000) MP['g'] = gsw.grav(MP['lat'], MP['P']) MP['sig4m'] = np.nanmean(MP['sig4']) MP['sig4_sorted'] = utils.nansort(MP['sig4'], axis=0) MP['N2'], MP['P_mid'] = gsw.Nsquared(MP['SA'], MP['CT'], MP['P'], MP['lat']) MP['z_mid'] = gsw.z_from_p(MP['P_mid'], MP['lat']) MP['ual'], MP['uac'] = rotate_overflow(MP['u'], MP['v'], MP['sig4'], sig4max) print('- Calculating the overflow integrated velocity') ual_ = MP['ual'].copy() uac_ = MP['uac'].copy() mask = MP['sig4'] < sig4max ual_[mask] = np.nan uac_[mask] = np.nan
def match_sigmas(btl_prs, btl_oxy, btl_tmp, btl_SA, ctd_os, ctd_prs, ctd_tmp, ctd_SA, ctd_oxyvolts, ctd_time): # Construct Dataframe from bottle and ctd values for merging btl_data = pd.DataFrame(data={ "CTDPRS": btl_prs, "REFOXY": btl_oxy, "CTDTMP": btl_tmp, "SA": btl_SA, }) time_data = pd.DataFrame( data={ "CTDPRS": ctd_prs, "OS": ctd_os, "CTDTMP": ctd_tmp, "SA": ctd_SA, "CTDOXYVOLTS": ctd_oxyvolts, "CTDTIME": ctd_time, }) time_data["dv_dt"] = calculate_dVdT(time_data["CTDOXYVOLTS"], time_data["CTDTIME"]) # Merge DF merged_df = pd.DataFrame( columns=["CTDPRS", "CTDOXYVOLTS", "CTDTMP", "dv_dt", "OS"], dtype=float) merged_df["REFOXY"] = btl_data["REFOXY"].copy() # calculate sigma referenced to multiple depths for idx, p_ref in enumerate([0, 1000, 2000, 3000, 4000, 5000, 6000]): btl_data[f"sigma{idx}"] = ( gsw.pot_rho_t_exact( btl_data["SA"], btl_data["CTDTMP"], btl_data["CTDPRS"], p_ref, ) - 1000 # subtract 1000 to get potential density *anomaly* ) + 1e-8 * np.random.standard_normal(btl_data["SA"].size) time_data[f"sigma{idx}"] = ( gsw.pot_rho_t_exact( time_data["SA"], time_data["CTDTMP"], time_data["CTDPRS"], p_ref, ) - 1000 # subtract 1000 to get potential density *anomaly* ) + 1e-8 * np.random.standard_normal(time_data["SA"].size) rows = (btl_data["CTDPRS"] > (p_ref - 500)) & (btl_data["CTDPRS"] < (p_ref + 500)) time_sigma_sorted = time_data[f"sigma{idx}"].sort_values().to_numpy() sigma_min = np.min([ np.min(btl_data.loc[rows, f"sigma{idx}"]), np.min(time_sigma_sorted) ]) sigma_max = np.max([ np.max(btl_data.loc[rows, f"sigma{idx}"]), np.max(time_sigma_sorted) ]) time_sigma_sorted = np.insert(time_sigma_sorted, 0, sigma_min - 1e-4) time_sigma_sorted = np.append(time_sigma_sorted, sigma_max + 1e-4) # TODO: can this be vectorized? cols = ["CTDPRS", "CTDOXYVOLTS", "CTDTMP", "dv_dt", "OS"] inds = np.concatenate( ([0], np.arange(0, len(time_data)), [len(time_data) - 1])) for col in cols: merged_df.loc[rows, col] = np.interp( btl_data.loc[rows, f"sigma{idx}"], time_sigma_sorted, time_data[col].iloc[inds], ) # Apply coef and calculate CTDOXY sbe_coef0 = _get_sbe_coef() # initial coefficient guess merged_df['CTDOXY'] = _PMEL_oxy_eq( sbe_coef0, (merged_df['CTDOXYVOLTS'], merged_df['CTDPRS'], merged_df['CTDTMP'], merged_df['dv_dt'], merged_df['OS'])) return merged_df
def post_process(self, verbose=True): print("\nPost processing") print("---------------\n") # Very basic self.ascent = self.hpid % 2 == 0 self.ascent_ctd = self.ascent * np.ones_like(self.UTC, dtype=int) self.ascent_ef = self.ascent * np.ones_like(self.UTCef, dtype=int) # Estimate number of observations. self.nobs_ctd = np.sum(~np.isnan(self.UTC), axis=0) self.nobs_ef = np.sum(~np.isnan(self.UTCef), axis=0) # Figure out some useful times. self.UTC_start = self.UTC[0, :] self.UTC_end = np.nanmax(self.UTC, axis=0) if verbose: print("Creating time variable dUTC with units of seconds.") self.dUTC = (self.UTC - self.UTC_start) * 86400 self.dUTCef = (self.UTCef - self.UTC_start) * 86400 if verbose: print("Interpolated GPS positions to starts and ends of profiles.") # GPS interpolation to the start and end time of each half profile. idxs = ~np.isnan(self.lon_gps) & ~np.isnan(self.lat_gps) self.lon_start = np.interp(self.UTC_start, self.utc_gps[idxs], self.lon_gps[idxs]) self.lat_start = np.interp(self.UTC_start, self.utc_gps[idxs], self.lat_gps[idxs]) self.lon_end = np.interp(self.UTC_end, self.utc_gps[idxs], self.lon_gps[idxs]) self.lat_end = np.interp(self.UTC_end, self.utc_gps[idxs], self.lat_gps[idxs]) if verbose: print("Calculating heights.") # Depth. self.z = gsw.z_from_p(self.P, self.lat_start) # self.z_ca = gsw.z_from_p(self.P_ca, self.lat_start) self.zef = gsw.z_from_p(self.Pef, self.lat_start) if verbose: print("Calculating distance along trajectory.") # Distance along track from first half profile. self.__ddist = utils.lldist(self.lon_start, self.lat_start) self.dist = np.hstack((0., np.cumsum(self.__ddist))) if verbose: print("Interpolating distance to measurements.") # Distances, velocities and speeds of each half profile. self.profile_ddist = np.zeros_like(self.lon_start) self.profile_dt = np.zeros_like(self.lon_start) self.profile_bearing = np.zeros_like(self.lon_start) lons = np.zeros((len(self.lon_start), 2)) lats = lons.copy() times = lons.copy() lons[:, 0], lons[:, 1] = self.lon_start, self.lon_end lats[:, 0], lats[:, 1] = self.lat_start, self.lat_end times[:, 0], times[:, 1] = self.UTC_start, self.UTC_end self.dist_ctd = self.UTC.copy() nans = np.isnan(self.dist_ctd) for i, (lon, lat, time) in enumerate(zip(lons, lats, times)): self.profile_ddist[i] = utils.lldist(lon, lat) # Convert time from days to seconds. self.profile_dt[i] = np.diff(time) * 86400. d = np.array([self.dist[i], self.dist[i] + self.profile_ddist[i]]) idxs = ~nans[:, i] self.dist_ctd[idxs, i] = np.interp(self.UTC[idxs, i], time, d) self.dist_ef = self.__regrid('ctd', 'ef', self.dist_ctd) if verbose: print("Estimating bearings.") # Pythagorian approximation (?) of bearing. self.profile_bearing = np.arctan2(self.lon_end - self.lon_start, self.lat_end - self.lat_start) if verbose: print("Calculating sub-surface velocity.") # Convert to m s-1 calculate meridional and zonal velocities. self.sub_surf_speed = self.profile_ddist * 1000. / self.profile_dt self.sub_surf_u = self.sub_surf_speed * np.sin(self.profile_bearing) self.sub_surf_v = self.sub_surf_speed * np.cos(self.profile_bearing) if verbose: print("Interpolating missing velocity values.") # Fill missing U, V values using linear interpolation otherwise we # run into difficulties using cumtrapz next. self.U = self.__fill_missing(self.U) self.V = self.__fill_missing(self.V) # Absolute velocity self.calculate_absolute_velocity(verbose=verbose) if verbose: print("Calculating thermodynamic variables.") # Derive some important thermodynamics variables. # Absolute salinity. self.SA = gsw.SA_from_SP(self.S, self.P, self.lon_start, self.lat_start) # Conservative temperature. self.CT = gsw.CT_from_t(self.SA, self.T, self.P) # Potential temperature with respect to 0 dbar. self.PT = gsw.pt_from_CT(self.SA, self.CT) # In-situ density. self.rho = gsw.rho(self.SA, self.CT, self.P) # Potential density with respect to 1000 dbar. self.rho_1 = gsw.pot_rho_t_exact(self.SA, self.T, self.P, p_ref=1000.) # Buoyancy frequency regridded onto ctd grid. N2_ca, __ = gsw.Nsquared(self.SA, self.CT, self.P, self.lat_start) self.N2 = self.__regrid('ctd_ca', 'ctd', N2_ca) if verbose: print("Calculating float vertical velocity.") # Vertical velocity regridded onto ctd grid. dt = 86400. * np.diff(self.UTC, axis=0) # [s] Wz_ca = np.diff(self.z, axis=0) / dt self.Wz = self.__regrid('ctd_ca', 'ctd', Wz_ca) if verbose: print("Renaming Wp to Wpef.") # Vertical water velocity. self.Wpef = self.Wp.copy() del self.Wp if verbose: print("Calculating shear.") # Shear calculations. dUdz_ca = np.diff(self.U, axis=0) / np.diff(self.zef, axis=0) dVdz_ca = np.diff(self.V, axis=0) / np.diff(self.zef, axis=0) self.dUdz = self.__regrid('ef_ca', 'ef', dUdz_ca) self.dVdz = self.__regrid('ef_ca', 'ef', dVdz_ca) if verbose: print("Calculating Richardson number.") N2ef = self.__regrid('ctd', 'ef', self.N2) self.Ri = N2ef / (self.dUdz**2 + self.dVdz**2) if verbose: print("Regridding piston position to ctd.\n") # Regrid piston position. self.ppos = self.__regrid('ctd_ca', 'ctd', self.ppos_ca) self.update_profiles()
continue good = ~allbad[:, i] TY["s1"][err, i] = np.interp(TY["z"][err], TY["z"][good], TY["s1"][good, i]) print("- Renaming variables and calculating new ones") TY["T"] = TY.pop("t1") TY["P"] = TY.pop("p") TY["S"] = TY.pop("s1") TY["spd"] = np.sqrt(TY["u"]**2 + TY["v"]**2) TY["z"] = -np.tile(TY["z"][:, np.newaxis].astype(float), (1, Np)) TY["depth"] = -TY["z"] TY["SA"] = gsw.SA_from_SP(TY["S"], TY["P"], TY["lon"], TY["lat"]) TY["CT"] = gsw.CT_from_t(TY["SA"], TY["T"], TY["P"]) TY["sig4"] = gsw.pot_rho_t_exact(TY["SA"], TY["T"], TY["P"], 4000) TY["g"] = gsw.grav(TY["lat"], TY["P"]) TY["b"] = -TY["g"] * (TY["sig4"] - sig4b) / sig4b TY["sig4m"] = np.nanmean(TY["sig4"]) TY["sig4_sorted"] = utils.nansort(TY["sig4"], axis=0) TY["b_sorted"] = -TY["g"] * (TY["sig4_sorted"] - sig4b) / sig4b TY["N2"], TY["P_mid"] = gsw.Nsquared(TY["SA"], TY["CT"], TY["P"], np.nanmean(TY["lat"], axis=0)) TY["z_mid"] = gsw.z_from_p(TY["P_mid"], TY["mlat"]) ddist = utils.lldist(TY["mlon"], TY["mlat"]) TY["dist"] = np.hstack((0, np.cumsum(ddist))) TY["dist_r"] = -(TY["dist"] - TY["dist"].max()) print("- Estimating distances") lon = TY["lon"].flatten() lat = TY["lat"].flatten()
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 [email protected] 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
def eps_overturn(P, Z, T, S, lon, lat, dnoise=0.001, pdref=4000): ''' Calculate profile of turbulent dissipation epsilon from structure of a ctd profile. Currently this takes only one profile and not a matrix from a whole twoyo. ''' import numpy as np import gsw # avoid error due to nan's in conditional statements np.seterr(invalid='ignore') z0 = Z.copy() z0 = z0.astype('float') # Find non-NaNs x = np.where(np.isfinite(T)) x = x[0] # Extract variables without the NaNs p = P[x].copy() z = Z[x].copy() z = z.astype('float') t = T[x].copy() s = S[x].copy() # cn2 = ctdn['n2'][x].copy() SA = gsw.SA_from_SP(s, t, lon, lat) CT = gsw.CT_from_t(SA, t, p) PT = gsw.pt0_from_t(SA, t, p) sg4 = gsw.pot_rho_t_exact(SA, t, p, pdref)-1000 # Create intermediate density profile D0 = sg4[0] sgt = D0-sg4[0] n = sgt/dnoise n = np.round(n) sgi = [D0+n*dnoise] # first element for i in np.arange(1, np.alen(sg4), 1): sgt = sg4[i]-sgi[i-1] n = sgt/dnoise n = np.round(n) sgi.append(sgi[i-1]+n*dnoise) sgi = np.array(sgi) # Sort Ds = np.sort(sgi, kind='mergesort') Is = np.argsort(sgi, kind='mergesort') # Calculate Thorpe length scale TH = z[Is]-z cumTH = np.cumsum(TH) aa = np.where(cumTH > 1) aa = aa[0] # last index in overturns aatmp = aa.copy() aatmp = np.append(aatmp, np.nanmax(aa)+10) aad = np.diff(aatmp) aadi = np.where(aad > 1) aadi = aadi[0] LastItems = aa[aadi].copy() # first index in overturns aatmp = aa.copy() aatmp = np.insert(aatmp, 0, -1) aad = np.diff(aatmp) aadi = np.where(aad > 1) aadi = aadi[0] FirstItems = aa[aadi].copy() # % Sort temperature and salinity for calculating the buoyancy frequency PTs = PT[Is] SAs = SA[Is] CTs = CT[Is] # % Loop through detected overturns # % and calculate Thorpe Scales, N2 and dT/dz over the overturn # THsc = nan(size(Z)); THsc = np.zeros_like(z)*np.nan N2 = np.zeros_like(z)*np.nan # CN2 = np.ones_like(z)*np.nan DTDZ = np.zeros_like(z)*np.nan out = {} out['idx'] = np.zeros_like(z0) for iostart, ioend in zip(FirstItems, LastItems): idx = np.arange(iostart, ioend+1, 1) out['idx'][x[idx]] = 1 sc = np.sqrt(np.mean(np.square(TH[idx]))) # ctdn2 = np.nanmean(cn2[idx]) # Buoyancy frequency calculated over the overturn from sorted profiles # Go beyond overturn (I am sure this will cause trouble with the # indices at some point) n2, Np = gsw.Nsquared(SAs[[iostart-1, ioend+1]], CTs[[iostart-1, ioend+1]], p[[iostart-1, ioend+1]], lat) # Fill depth range of the overturn with the Thorpe scale THsc[idx] = sc # Fill depth range of the overturn with N^2 N2[idx] = n2 # Fill depth range of the overturn with average 10m N^2 # CN2[idx] = ctdn2 # % Calculate epsilon THepsilon = 0.9*THsc**2.0*np.sqrt(N2)**3 THepsilon[N2 <= 0] = np.nan THk = 0.2*THepsilon/N2 out['eps'] = np.zeros_like(z0)*np.nan out['eps'][x] = THepsilon out['k'] = np.zeros_like(z0)*np.nan out['k'][x] = THk out['n2'] = np.zeros_like(z0)*np.nan out['n2'][x] = N2 out['Lt'] = np.zeros_like(z0)*np.nan out['Lt'][x] = THsc return out
def adiabatic_level(S, T, p, lat, pref=0, pressure_range=400, order=1, axis=0, eta_calc=True): """ Adiabatic Leveling from Bray and Fofonoff (1981) - or at least my best attempt at this procedure. ------------- Data should be loaded in bins - might try to add a way to check that later ------------- CTD and ladcp data are on two different grids so this function uses the finer CTD data to generate the regressions which are then used on the pressure grid PARAMETERS ---------- S: Salinity T: Temperature p: Pressure grid lat: latitude grid pref: reference pressure pressure_range: window of pressure for adiabtic Leveling order: polynomial order for fitting axis: axis to perform calculations on eta_calc: If true, calculates eta via (rho - rho_ref)/(drho_ref/dz) in same windoes as adiabatic leveling calculations Returns ------- N2: Bouyancy Frequency Squared N2_ref: Buoyancy Frequency Squared reference field from adiabtic Leveling p_mid: midpoint pressure grid from N2 calculations strain: strain calculated using N2 (vertical gradient of vertical isopycnal displacement) """ # Pressure window - See Bray and Fofonoff 1981 for details plev = pressure_range # Calculate Buoyancy Frequency using GSW toolbox N2, p_mid = gsw.stability.Nsquared(S, T, p, lat, axis=axis) # Keeps as rank 2 array making it compatible with casts and arrays if len(N2.shape) < 2: N2 = np.expand_dims(N2, 1) p_mid = np.expand_dims(p_mid, 1) S = np.expand_dims(S, 1) T = np.expand_dims(T, 1) p = np.expand_dims(p, 1) # Calculate reference N2 Field alpha = np.full((p_mid.shape[0], p_mid.shape[1]), np.nan) g = np.full((p_mid.shape[0], p_mid.shape[1]), np.nan) rho_bar = np.full((p_mid.shape[0], p_mid.shape[1]), np.nan) drho_dz = np.full((p_mid.shape[0], p_mid.shape[1]), np.nan) # N2_ref = np.full_like(N2, np.nan) for i in range(p_mid.shape[axis]): # Bottom and top for window - the max and min of the 2-element array # allows windows to run to ends of profile (I think) for k in range(p_mid.shape[1]): p_min = np.max([p_mid[i, k] - 0.5 * plev, p[0, k]]) p_max = np.min([p_mid[i, k] + 0.5 * plev, p[-1, k]]) data_in = np.logical_and(p >= p_min, p <= p_max) if np.sum(data_in) > 0: p_bar = np.nanmean(p[data_in[:, k], k]) t_bar = np.nanmean(T[data_in[:, k], k]) s_bar = np.nanmean(S[data_in[:, k], k]) rho_bar[i, k] = gsw.density.rho(s_bar, t_bar, p_bar) # Potential Temperature referenced to pbar theta = gsw.conversions.pt_from_t(S[data_in[:, k], k], T[data_in[:, k], k], p[data_in[:, k], k], p_bar) sv = 1. / gsw.pot_rho_t_exact(S[data_in[:, k], k], theta, p[data_in[:, k], k], p_bar) # Regress pressure onto de-meaned specific volume and store coefficients Poly = np.polyfit(p[data_in[:, k], k], sv - np.nanmean(sv), order) # Do something with coefficients that I dont understand alpha[i, k] = Poly[order - 1] # calculate reference N2 reference field g[i, k] = gsw.grav(lat.T[k], p_bar) # Calculate N2 grid (10^-4 to convert from db to pascals) N2_ref = -1e-4 * rho_bar**2 * g**2 * alpha # strain calcuations strain = (N2 - N2_ref) / N2_ref # Append row of nans onto the top or bottom because computing N2 chops a row p_mid = np.vstack((np.zeros((1, p_mid.shape[1])), p_mid)) rho_bar = np.vstack((np.full((1, p_mid.shape[1]), np.nan), rho_bar)) return N2_ref, N2, strain, p_mid, rho_bar
def post_process(self, verbose=True): print("\nPost processing") print("---------------\n") # Very basic self.ascent = self.hpid % 2 == 0 self.ascent_ctd = self.ascent*np.ones_like(self.UTC, dtype=int) self.ascent_ef = self.ascent*np.ones_like(self.UTCef, dtype=int) # Estimate number of observations. self.nobs_ctd = np.sum(~np.isnan(self.UTC), axis=0) self.nobs_ef = np.sum(~np.isnan(self.UTCef), axis=0) # Figure out some useful times. self.UTC_start = self.UTC[0, :] self.UTC_end = np.nanmax(self.UTC, axis=0) if verbose: print("Creating time variable dUTC with units of seconds.") self.dUTC = (self.UTC - self.UTC_start)*86400 self.dUTCef = (self.UTCef - self.UTC_start)*86400 if verbose: print("Interpolated GPS positions to starts and ends of profiles.") # GPS interpolation to the start and end time of each half profile. idxs = ~np.isnan(self.lon_gps) & ~np.isnan(self.lat_gps) self.lon_start = np.interp(self.UTC_start, self.utc_gps[idxs], self.lon_gps[idxs]) self.lat_start = np.interp(self.UTC_start, self.utc_gps[idxs], self.lat_gps[idxs]) self.lon_end = np.interp(self.UTC_end, self.utc_gps[idxs], self.lon_gps[idxs]) self.lat_end = np.interp(self.UTC_end, self.utc_gps[idxs], self.lat_gps[idxs]) if verbose: print("Calculating heights.") # Depth. self.z = gsw.z_from_p(self.P, self.lat_start) # self.z_ca = gsw.z_from_p(self.P_ca, self.lat_start) self.zef = gsw.z_from_p(self.Pef, self.lat_start) if verbose: print("Calculating distance along trajectory.") # Distance along track from first half profile. self.__ddist = utils.lldist(self.lon_start, self.lat_start) self.dist = np.hstack((0., np.cumsum(self.__ddist))) if verbose: print("Interpolating distance to measurements.") # Distances, velocities and speeds of each half profile. self.profile_ddist = np.zeros_like(self.lon_start) self.profile_dt = np.zeros_like(self.lon_start) self.profile_bearing = np.zeros_like(self.lon_start) lons = np.zeros((len(self.lon_start), 2)) lats = lons.copy() times = lons.copy() lons[:, 0], lons[:, 1] = self.lon_start, self.lon_end lats[:, 0], lats[:, 1] = self.lat_start, self.lat_end times[:, 0], times[:, 1] = self.UTC_start, self.UTC_end self.dist_ctd = self.UTC.copy() nans = np.isnan(self.dist_ctd) for i, (lon, lat, time) in enumerate(zip(lons, lats, times)): self.profile_ddist[i] = utils.lldist(lon, lat) # Convert time from days to seconds. self.profile_dt[i] = np.diff(time)*86400. d = np.array([self.dist[i], self.dist[i] + self.profile_ddist[i]]) idxs = ~nans[:, i] self.dist_ctd[idxs, i] = np.interp(self.UTC[idxs, i], time, d) self.dist_ef = self.__regrid('ctd', 'ef', self.dist_ctd) if verbose: print("Estimating bearings.") # Pythagorian approximation (?) of bearing. self.profile_bearing = np.arctan2(self.lon_end - self.lon_start, self.lat_end - self.lat_start) if verbose: print("Calculating sub-surface velocity.") # Convert to m s-1 calculate meridional and zonal velocities. self.sub_surf_speed = self.profile_ddist*1000./self.profile_dt self.sub_surf_u = self.sub_surf_speed*np.sin(self.profile_bearing) self.sub_surf_v = self.sub_surf_speed*np.cos(self.profile_bearing) if verbose: print("Interpolating missing velocity values.") # Fill missing U, V values using linear interpolation otherwise we # run into difficulties using cumtrapz next. self.U = self.__fill_missing(self.U) self.V = self.__fill_missing(self.V) # Absolute velocity self.calculate_absolute_velocity(verbose=verbose) if verbose: print("Calculating thermodynamic variables.") # Derive some important thermodynamics variables. # Absolute salinity. self.SA = gsw.SA_from_SP(self.S, self.P, self.lon_start, self.lat_start) # Conservative temperature. self.CT = gsw.CT_from_t(self.SA, self.T, self.P) # Potential temperature with respect to 0 dbar. self.PT = gsw.pt_from_CT(self.SA, self.CT) # In-situ density. self.rho = gsw.rho(self.SA, self.CT, self.P) # Potential density with respect to 1000 dbar. self.rho_1 = gsw.pot_rho_t_exact(self.SA, self.T, self.P, p_ref=1000.) # Buoyancy frequency regridded onto ctd grid. N2_ca, __ = gsw.Nsquared(self.SA, self.CT, self.P, self.lat_start) self.N2 = self.__regrid('ctd_ca', 'ctd', N2_ca) if verbose: print("Calculating float vertical velocity.") # Vertical velocity regridded onto ctd grid. dt = 86400.*np.diff(self.UTC, axis=0) # [s] Wz_ca = np.diff(self.z, axis=0)/dt self.Wz = self.__regrid('ctd_ca', 'ctd', Wz_ca) if verbose: print("Renaming Wp to Wpef.") # Vertical water velocity. self.Wpef = self.Wp.copy() del self.Wp if verbose: print("Calculating shear.") # Shear calculations. dUdz_ca = np.diff(self.U, axis=0)/np.diff(self.zef, axis=0) dVdz_ca = np.diff(self.V, axis=0)/np.diff(self.zef, axis=0) self.dUdz = self.__regrid('ef_ca', 'ef', dUdz_ca) self.dVdz = self.__regrid('ef_ca', 'ef', dVdz_ca) if verbose: print("Calculating Richardson number.") N2ef = self.__regrid('ctd', 'ef', self.N2) self.Ri = N2ef/(self.dUdz**2 + self.dVdz**2) if verbose: print("Regridding piston position to ctd.\n") # Regrid piston position. self.ppos = self.__regrid('ctd_ca', 'ctd', self.ppos_ca) self.update_profiles()