def apply_isopycnal_displacement(self, srho_file, rho_1_0=1031., method=1): """Input the path to picked array of smoothed potential density.""" print("\nAdding isopycnal displacements.") print("-------------------------------\n") setattr(self, 'mu', np.nan*self.rho_1.copy()) with open(srho_file) as f: srho_1 = _pickle.load(f) setattr(self, 'srho_1', srho_1) print(" Added: srho_1.") # Use smoothed profiles as they are. if method == 0: pass if method == 1: print(" Further smoothing by averaging adjecent profiles.") # Add initial smooth profiles to Profile objects. self.update_profiles() # Do more smoothing by averaging adjecent profiles. for i in xrange(len(self.hpid)): hpids = np.arange(self.hpid[i] - 5, self.hpid[i] + 6) self.srho_1[:, i] = self.get_mean_profile(hpids, 'srho_1', self.z[:, i]) else: raise ValueError('Invalid method.') for i in xrange(len(self.hpid)): srho_1dz = utils.finite_diff(self.z[:, i], srho_1[:, i], self.UTC[:, i]) self.mu[:, i] = (self.rho_1[:, i] - srho_1[:, i])/srho_1dz print(" Added: mu.") # I added a minus because I *think* gravity should be negative. self.b = -gsw.grav(self.lat_start, self.P) \ * (self.rho_1 - srho_1)/rho_1_0 print(" Added: b.") self.update_profiles()
def pgf(z, y, x, eta, rho, pa=0., rho0=1025., geographic=True): """ USAGE ----- Py, Px = pgf(z, y, x, eta, rho, pa=0., rho0=1025., geographic=True) Calculates total horizontal pressure gradient force per unit mass [m/s2] (barotropic + baroclinic + barometric components), i.e., P(z,y,x) = -(1/rho0)*grad_H(pa) -g*grad_H(eta) + (g/rho0)*Integral{grad_H(rho)}. 'rho0' is the reference potential density used in the Boussinesq Approximation (defaults to 1025. kg/m3), 'g' is the gravitational acceleration, 'pa(y,x)' is the atmospheric pressure in [N/m2] (defults to 0.), 'eta(y,x)' is the free surface elevation in [m], 'rho(z,y,x)' is the potential density and 'grad_H' is the horizontal gradient operator. The Integral(rho) is calculated from z' = eta(x,y) down through z' = z. The coordinate arrays (z,y,x) are distances in the (vertical,meridional,zonal) directions. The vertical axis originates at the surface (z = 0), i.e., rho[0,y,x] = rho_surface and rho[-1,y,x] = rho_bottom. If geographic==True (default), (y,x) are assumed to be (latitude,longitude) and are converted to meters before computing (dy,dx). If geographic==False, (y,x) are assumed to be in meters. """ z, y, x, eta, rho = map(np.asanyarray, (z, y, x, eta, rho)) ny, nx = eta.shape # Shape of the (x,y,u,v) arrays. if z.ndim==1: z = np.expand_dims(z, 1) z = np.expand_dims(z, 1) z = np.tile(z, (1, ny, nx)) ## Calculating grid spacings. if geographic: dlat, _ = np.gradient(y) _, dlon = np.gradient(x) deg2m = 111120.0 # [m/deg] dx = dlon*deg2m*np.cos(y*np.pi/180.) # [m] dy = dlat*deg2m # [m] else: dy, _ = np.gradient(y) _, dx = np.gradient(x) dz, _, _ = np.gradient(z) dz = np.abs(dz) ## Get gravitational acceleration. if geographic: g = grav(y) else: g = 9.81 ## pa (x,y) derivatives. if pa: dpay, dpax = np.gradient(pa) dpady = dpay/dy dpadx = dpax/dx ## eta (x,y) derivatives. detay, detax = np.gradient(eta) detady = detay/dy detadx = detax/dx ## rho (x,y) derivatives. _, drhoy, drhox = np.gradient(rho) drhody = drhoy/dy drhodx = drhox/dx ## Barometric pressure gradient force per unit mass. if pa==0.: PGF_bm_y, PGF_bm_x = np.zeros((ny, nx)), np.zeros((ny, nx)) else: PGF_bm_y = -dpady/rho0 PGF_bm_x = -dpadx/rho0 ## Barotropic pressure gradient force per unit mass. PGF_bt_y = -g*detady PGF_bt_x = -g*detadx ## Vertical integration from z' = eta(x,y) through z' = z. Iy = np.cumsum(drhody*dz, axis=0) Ix = np.cumsum(drhodx*dz, axis=0) ## Baroclinic pressure gradient force per unit mass. PGF_bc_y = +g*Iy/rho0 PGF_bc_x = +g*Ix/rho0 ## Total pressure gradient force per unit mass. PGF_y = PGF_bm_y + PGF_bt_y + PGF_bc_y PGF_x = PGF_bm_x + PGF_bt_x + PGF_bc_x return PGF_y, PGF_x
def __init__(self, h, T=None, L=None, thetao=None, Ho=None, lat=None): self.T = np.asarray(T, dtype=np.float) self.L = np.asarray(L, dtype=np.float) self.Ho = np.asarray(Ho, dtype=np.float) self.lat = np.asarray(lat, dtype=np.float) self.thetao = np.asarray(thetao, dtype=np.float) if isinstance(h, str): if L is not None: if h == 'deep': self.h = self.L / 2. elif h == 'shallow': self.h = self.L * 0.05 else: self.h = np.asarray(h, dtype=np.float) if lat is None: g = 9.81 # Default gravity. else: g = gsw.grav(lat, p=0) if L is None: self.omega = 2 * np.pi / self.T self.Lo = (g * self.T ** 2) / 2 / np.pi # Returns wavenumber of the gravity wave dispersion relation using # newtons method. The initial guess is shallow water wavenumber. self.k = self.omega / np.sqrt(g) # TODO: May change to, # self.k = self.w ** 2 / (g * np.sqrt(self.w ** 2 * self.h / g)) f = g * self.k * np.tanh(self.k * self.h) - self.omega ** 2 while np.abs(f.max()) > 1e-10: dfdk = (g * self.k * self.h * (1 / (np.cosh(self.k * self.h))) ** 2 + g * np.tanh(self.k * self.h)) self.k = self.k - f / dfdk # FIXME: f = g * self.k * np.tanh(self.k * self.h) - self.omega ** 2 self.L = 2 * np.pi / self.k if isinstance(h, str): if h == 'deep': self.h = self.L / 2. elif h == 'shallow': self.h = self.L * 0.05 else: self.Lo = self.L / np.tanh(2 * np.pi * self.h / self.L) self.k = 2 * np.pi / self.L self.T = np.sqrt(2 * np.pi * self.Lo / g) self.omega = 2 * np.pi / self.T self.hoL = self.h / self.L self.hoLo = self.h / self.Lo self.C = self.omega / self.k # or L / T self.Co = self.Lo / self.T self.G = 2 * self.k * self.h / np.sinh(2 * self.k * self.h) self.n = (1 + self.G) / 2 self.Cg = self.n * self.C self.Ks = np.sqrt(1 / (1 + self.G) / np.tanh(self.k * self.h)) if thetao is None: self.theta = np.NaN self.Kr = np.NaN if thetao is not None: self.theta = np.rad2deg(np.asin(self.C / self.Co * np.sin(np.deg2rad(self.thetao)))) self.Kr = np.sqrt(np.cos(np.deg2rad(self.thetao)) / np.cos(np.deg2rad(self.theta))) if Ho is None: self.H = np.NaN if Ho is not None: self.H = self.Ho * self.Ks * self.Kr
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
print("Estimating w and b.") om = np.fft.fftfreq(nperseg, 15 * 60) c4w["w_hi"] = np.fft.ifft( 1j * pi2 * om[:, np.newaxis, np.newaxis] * np.fft.fft(-c4w["gamman_hi"] / c4["dgdz"], axis=0), axis=0, ).real c4w["w_hib"] = np.fft.ifft( 1j * pi2 * om[:, np.newaxis, np.newaxis] * np.fft.fft(-c4w["gamman_hib"] / c4["dgdz"], axis=0), axis=0, ).real # Estimate buoyancy variables c4w["b_hi"] = -gsw.grav(-c4["z"], cc["lat"]) * c4w["gamman_hi"] / c4["gamman_lo"] c4w["b_hib"] = -gsw.grav(-c4["z"], cc["lat"]) * c4w["gamman_hib"] / c4["gamman_lo"] c4["N"] = np.sqrt(c4["N2"]) print("Estimating covariance spectra.") freq, c4w["Puu"] = sig.welch(c4w["u_hi"], **spec_kwargs) _, c4w["Pvv"] = sig.welch(c4w["v_hi"], **spec_kwargs) _, c4w["Pww"] = sig.welch(c4w["w_hi"], **spec_kwargs) _, c4w["Pwwg"] = sig.welch(c4w["gamman_hi"] / c4["dgdz"], **spec_kwargs) c4w["Pwwg"] *= (pi2 * freq[:, np.newaxis, np.newaxis])**2 _, c4w["Pbb"] = sig.welch(c4w["b_hi"], **spec_kwargs) _, c4w["Cuv"] = sig.csd(c4w["u_hi"], c4w["v_hi"], **spec_kwargs) _, c4w["Cuwg"] = sig.csd(c4w["u_hi"], c4w["gamman_hi"] / c4["dgdz"], **spec_kwargs) c4w["Cuwg"] *= -1j * pi2 * freq[:, np.newaxis, np.newaxis]
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
def __init__(self, h, T=None, L=None, thetao=None, Ho=None, lat=None): self.T = np.asarray(T, dtype=np.float) self.L = np.asarray(L, dtype=np.float) self.Ho = np.asarray(Ho, dtype=np.float) self.lat = np.asarray(lat, dtype=np.float) self.thetao = np.asarray(thetao, dtype=np.float) if isinstance(h, str): if L is not None: if h == "deep": self.h = self.L / 2.0 elif h == "shallow": self.h = self.L * 0.05 else: self.h = np.asarray(h, dtype=np.float) if lat is None: g = 9.81 # Default gravity. else: g = gsw.grav(lat, p=0) if L is None: self.omega = 2 * np.pi / self.T self.Lo = (g * self.T**2) / 2 / np.pi # Returns wavenumber of the gravity wave dispersion relation using # newtons method. The initial guess is shallow water wavenumber. self.k = self.omega / np.sqrt(g) # TODO: May change to, # self.k = self.w ** 2 / (g * np.sqrt(self.w ** 2 * self.h / g)) f = g * self.k * np.tanh(self.k * self.h) - self.omega**2 while np.abs(f.max()) > 1e-10: dfdk = g * self.k * self.h * (1 / (np.cosh( self.k * self.h)))**2 + g * np.tanh(self.k * self.h) self.k = self.k - f / dfdk # FIXME: f = g * self.k * np.tanh(self.k * self.h) - self.omega**2 self.L = 2 * np.pi / self.k if isinstance(h, str): if h == "deep": self.h = self.L / 2.0 elif h == "shallow": self.h = self.L * 0.05 else: self.Lo = self.L / np.tanh(2 * np.pi * self.h / self.L) self.k = 2 * np.pi / self.L self.T = np.sqrt(2 * np.pi * self.Lo / g) self.omega = 2 * np.pi / self.T self.hoL = self.h / self.L self.hoLo = self.h / self.Lo self.C = self.omega / self.k # or L / T self.Co = self.Lo / self.T self.G = 2 * self.k * self.h / np.sinh(2 * self.k * self.h) self.n = (1 + self.G) / 2 self.Cg = self.n * self.C self.Ks = np.sqrt(1 / (1 + self.G) / np.tanh(self.k * self.h)) if thetao is None: self.theta = np.NaN self.Kr = np.NaN if thetao is not None: self.theta = np.rad2deg( np.asin(self.C / self.Co * np.sin(np.deg2rad(self.thetao)))) self.Kr = np.sqrt( np.cos(np.deg2rad(self.thetao)) / np.cos(np.deg2rad(self.theta))) if Ho is None: self.H = np.NaN if Ho is not None: self.H = self.Ho * self.Ks * self.Kr
dict_vars['CT']) # save dictionary to file write_dict(dict_vars, path, filename) print('data stored in file') elif filename[4:] == 'stations': for station in dict_ctd['station'][0, :]: dict_stations[station]['SA'] = SA_from_SP( dict_stations[station]['S'], dict_stations['P'], dict_stations[station]['lon'], dict_stations[station]['lat']) dict_stations[station]['CT'] = CT_from_t( dict_stations[station]['SA'], dict_stations[station]['T'], dict_stations['P']) dict_stations[station]['g'] = grav( dict_stations[station]['lat'], dict_stations['P']) dict_stations[station]['depth'] = abs( z_from_p(dict_stations['P'], dict_stations[station]['lat'])) dict_stations[station]['pt'] = pt_from_t( dict_stations[station]['SA'], dict_stations[station]['T'], dict_stations['P'], p_ref) dict_stations[station]['sigma0'] = sigma0( dict_stations[station]['SA'], dict_stations[station]['CT']) dict_stations[station]['spiciness0'] = spiciness0( dict_stations[station]['SA'], dict_stations[station]['CT']) dict_stations[station]['deltaD'] = geo_strf_dyn_height( dict_stations[station]['SA'], dict_stations[station]['CT'], dict_stations['P'], p_ref=p_ref)