Exemplo n.º 1
0
def waqlocal(lon, lat, q, omega=None, sigma=None, flip=True, skip=10):
    """
    Return the local finite-amplitude wave activity. See
    :cite:`2016:huang` for details.

    Parameters
    ----------
    %(params_eqlat)s
    %(params_waq)s

    References
    ----------
    .. bibliography:: ../bibs/waqlocal.bib

    Todo
    ----
    Support using `omega`.
    """
    # Graticule considerations
    has_omega = omega is not None
    has_sigma = sigma is not None
    if flip:
        lat, q = -np.flipud(lat), -np.flip(q, axis=1)
        if has_omega:
            omega = -np.flip(omega, axis=1)
        if sigma is not None:
            sigma = np.flipd(sigma, axis=1)
    grid = _LongitudeLatitude(lon, lat)
    phib = grid.phib
    integral = const.a * phib[None, :]

    # Flatten (eqlat can do this, but not necessary here)
    arrays = [q]
    if has_omega:
        arrays.append(omega)
    if has_sigma:
        arrays.append(sigma)

    # Flatten (eqlat can do this, but not necessary here)
    with quack._ArrayContext(*arrays, nflat_right=(q.ndim - 2)) as context:
        # Get flattened data
        if has_omega and has_sigma:
            q, omega, sigma = context.data
        elif has_omega:
            q, omega = context.data
        elif has_sigma:
            q, sigma = context.data
        else:
            q = context.data

        # Get equivalent latiitudes
        bands, q_bands = eqlat(lon, lat, q, sigma=sigma, skip=skip)
        L = lon.size
        M = lat.size
        N = bands.shape[1]  # number of equivalent latitudes
        K = q.shape[2]  # number of extra dimensions

        # Get local wave activity measure, as simple line integrals
        waq = np.empty((L, M, K))
        percent = 0
        for k in range(K):
            if (k / K) > (0.01 * percent):
                print('%d%% finished' % (100 * k / K, ))
                percent = percent + 10

            # Loop through each contour
            waq_k = np.empty((L, N))
            for n in range(N):
                # Setup, large areas
                band = bands[0, n, k] * np.pi / 180
                if np.isnan(band):  # happens if contours intersect at edge
                    waq_k[:, n] = np.nan
                else:
                    # Get high anomalies at low latitude (below top graticule) and
                    # low anomalies at high latitude (above bottom graticule)
                    anom = q[:, :, k] - q_bands[0, n, k]
                    f_pos = (anom >= 0) & (phib[None, 1:] < band)
                    f_neg = (anom < 0) & (phib[None, :-1] >= band)

                    # See if band is sandwiched between latitudes
                    # want scalar id (might be zero)
                    mid, = np.where((phib[:-1] <= band) & (phib[1:] > band))
                    if mid.size > 0:
                        # Find longitudes where positive
                        # Perform partial integrals, positive and negative
                        f_pos_mid = anom[:, mid] >= 0
                        p_int = const.a * (band - phib[mid])
                        m_int = const.a * (phib[mid + 1] - band)

                    for l in range(L):
                        # Get individual integrals
                        integral_pos = (anom[l, f_pos[l, :]] *
                                        integral[:, f_pos[l, :]]).sum()
                        integral_neg = -(  # *minus* a *negative*
                            anom[l, f_neg[l, :]] *
                            integral[:, f_neg[l, :]]).sum()

                        # Get extra bits
                        if mid.size > 0:
                            if f_pos_mid[l]:
                                integral_extra = anom[l, mid] * p_int
                            else:
                                integral_extra = -anom[l, mid] * m_int
                        else:
                            integral_extra = 0

                        # Put it all together
                        waq_k[l,
                              n] = integral_pos + integral_neg + integral_extra

        # Interpolate to actual latitudes
        for l in range(L):
            waq[l, :, k] = np.interp(lat, bands[0, :, k], waq_k[l, :])

        # Replace context data
        context.replace_data(waq)

    # Return
    waq = context.data
    if flip:
        waq = np.flip(waq, axis=1)
    return waq
Exemplo n.º 2
0
def waq(lon, lat, q, sigma=None, omega=None, flip=True, skip=10):
    """
    Return the finite-amplitude wave activity. See
    :cite:`2010:nakamura` for details.

    Parameters
    ----------
    %(params_eqlat)s
    %(params_waq)s

    References
    ----------
    .. bibliography:: ../bibs/waq.bib
    """
    # Graticule considerations
    has_omega = omega is not None
    has_sigma = sigma is not None
    if flip:
        lat, q = -np.flipud(lat), -np.flip(q, axis=1)
        if has_omega:
            omega = -np.flip(omega, axis=1)
        if has_sigma:
            sigma = np.flipd(sigma, axis=1)
    grid = _LongitudeLatitude(lon, lat)
    areas, dphi, phib = grid.areas, grid.dphi, grid.phib

    # Flatten (eqlat can do this, but not necessary here)
    arrays = [q]
    if has_omega:
        arrays.append(omega)
    if has_sigma:
        arrays.append(sigma)
    with quack._ArrayContext(*arrays, nflat_right=(q.ndim - 2)) as context:
        # Get flattened data
        if has_omega and has_sigma:
            q, omega, sigma = context.data
        elif has_omega:
            q, omega = context.data
        elif has_sigma:
            q, sigma = context.data
        else:
            q = context.data

        # Get equivalent latiitudes
        bands, q_bands = eqlat(lon, lat, q, sigma=sigma, skip=skip)
        M = lat.size  # number of latitudes onto which we interpolate
        N = bands.shape[1]  # number of equivalent latitudes
        K = q.shape[2]  # number of extra dimensions

        # Get activity
        waq = np.empty((1, M, K))
        percent = 0
        for k in range(K):
            # Status message
            if (k / K) > (0.01 * percent):
                print('%d%% finished' % (percent, ))
                percent = percent + 10

            # Loop through each contour
            # i, bandki in enumerate(bandk[0,:,k]):
            # enumerate(zip(bandk, q_bandk)):
            waq_k = np.empty(N)
            for n in range(N):
                # First, main blocks
                band = bands[0, n, k] * np.pi / 180
                if np.isnan(band):
                    waq_k[n] = np.nan
                    continue

                # Work with anomalies? Should be identical, since
                # positive/negative regions have same area by construction.
                # anom = q[:,:,k] - q_bands[0,n,k]
                # f_pos = (anom >= 0) & (phib[None,1:] < band)
                # f_neg = (anom < 0) & (phib[None,:-1] >= band)
                # integral = (anom[f_pos]*areas[f_pos]).sum() -
                # (anom[f_neg]*areas[f_neg]).sum() # minus a negative

                # Get the integrand
                qk, Qk = q[:, :, k], q_bands[0, n, k]
                if has_omega:
                    qint = q[:, :, k]  # the thing being integrated
                else:
                    qint = omega[:, :, k]

                # Get high anomalies at low latitude (below top graticule) and
                # low anomalies at high latitude (above bottom graticule)
                f_pos = (qk >= Qk) & (phib[None, 1:] < band)
                f_neg = (qk < Qk) & (phib[None, :-1] >= band)
                integral = (
                    (qint[f_pos] * areas[f_pos]).sum() -
                    (qint[f_neg] * areas[f_neg]).sum()  # minus a negative
                )

                # Account for tiny pieces along equivalent latitude cells
                mid, = np.where((phib[:-1] <= band) & (phib[1:] > band))
                integral_extra = 0
                if mid:
                    # Find longitudes where positive
                    f_pos_mid = qk[:, mid] >= Qk
                    # Positive high latitude and negative, low latitude
                    p_dphi = np.cos(
                        (band + phib[mid]) / 2) * (band - phib[mid])
                    m_dphi = np.cos(
                        (band + phib[mid + 1]) / 2) * (phib[mid + 1] - band)
                    # Extra pieces due to discrete grid
                    integral_extra = (
                        qint[f_pos_mid, mid].sum() *
                        (areas[mid] * m_dphi / dphi[mid]) -
                        qint[~f_pos_mid, mid].sum() *
                        (areas[mid] * p_dphi / dphi[mid])  # noqa: E501
                    )

                # Put it all together
                waq_k[n] = ((integral + integral_extra) /
                            (2 * np.pi * const.a * np.cos(band)))

            # Interpolate
            nanfilt = np.isnan(waq_k)
            if sum(~nanfilt) == 0:
                warnings._warn_climopy(
                    f'Warning: No valid waqs calculated for k {k}.')
                waq[0, :, k] = np.nan
            else:
                waq[0, :, k] = np.interp(lat, bands[0, ~nanfilt, k],
                                         waq_k[~nanfilt])

        # Reapply data
        context.replace_data(waq)

    # Return
    waq = context.waq
    if flip:
        waq = np.flip(waq, axis=1)
    return waq