예제 #1
0
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
예제 #2
0
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')
예제 #3
0
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
예제 #4
0
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
예제 #5
0
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.")
예제 #6
0
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.")
예제 #7
0
    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]
예제 #8
0
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)
예제 #9
0
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
예제 #10
0
파일: imodes.py 프로젝트: mrayson/iwaves
    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"
        }),
예제 #12
0
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
예제 #13
0
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
예제 #14
0
                                 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, :],
예제 #15
0
파일: AR45_funcs.py 프로젝트: ilebras/OSNAP
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
예제 #18
0
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
예제 #19
0
    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()
예제 #20
0
            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()
예제 #21
0
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
예제 #22
0
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
예제 #23
0
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
예제 #24
0
    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()