Exemple #1
0
def t_tide(xin, **kwargs):
    """T_TIDE Harmonic analysis of a time series
     [NAME,FREQ,TIDECON,XOUT]=T_TIDE(XIN) computes the tidal analysis
     of the (possibly complex) time series XIN.

     [TIDESTRUC,XOUT]=T_TIDE(XIN) returns the analysis information in
     a structure formed of NAME, FREQ, and TIDECON.

     XIN can be scalar (e.g. for elevations), or complex ( =U+sqrt(-1)*V
     for eastward velocity U and northward velocity V.

     Further inputs are optional, and are specified as property/value pairs
     [...]=T_TIDE(XIN,property,value,property,value,...,etc.)

     These properties are:

           'interval'       Sampling interval (hours), default = 1.

       The next two are required if nodal corrections are to be computed,
       otherwise not necessary. If they are not included then the reported
       phases are raw constituent phases at the central time.

       If your time series is longer than 18.6 years then nodal corrections
       are not made -instead we fit directly to all satellites (start time
       is then just used to generate Greenwich phases).

           'start time'     [year,month,day,hour,min,sec]
                            - min,sec are optional OR
                            decimal day (matlab DATENUM scalar)
           'latitude'       decimal degrees (+north) (default: none).

       Where to send the output.
           'output'         where to send printed output:
                            'none'    (no printed output)
                            'screen'  (to screen) - default
                            FILENAME   (to a file)

       Correction factor for prefiltering.
           'prefilt'        FS,CORR
                            If the time series has been passed through
                            a pre-filter of some kind (say, to reduce the
                            low-frequency variability), then the analyzed
                            constituents will have to be corrected for
                            this. The correction transfer function
                            (1/filter transfer function) has (possibly
                            complex) magnitude CORR at frequency FS (cph).
                            Corrections of more than a factor of 100 are
                            not applied; it is assumed these refer to tidal
                            constituents that were intentionally filtered
                            out, e.g., the fortnightly components.

       Adjustment for long-term behavior ("secular" behavior).
           'secular'        'mean'   - assume constant offset (default).
                            'linear' - get linear trend.

       Inference of constituents.
           'inference'      NAME,REFERENCE,AMPRAT,PHASE_OFFSET
                            where NAME is an array of the names of
                            constituents to be inferred, REFERENCE is an
                            array of the names of references, and AMPRAT
                            and PHASE_OFFSET are the amplitude factor and
                            phase offset (in degrees)from the references.
                            NAME and REFERENCE are Nx4 (max 4 characters
                            in name), and AMPRAT and PHASE_OFFSET are Nx1
                            (for scalar time series) and Nx2 for vector
                            time series (column 1 is for + frequencies and
                            column 2 for - frequencies).
                            NB - you can only infer ONE unknown constituent
                            per known constituent (i.e. REFERENCE must not
                            contain multiple instances of the same name).

       Shallow water constituents
           'shallow'        NAME
                            A matrix whose rows contain the names of
                            shallow-water constituents to analyze.

       Resolution criterions for least-squares fit.
           'rayleigh'       scalar - Rayleigh criteria, default = 1.
                            Matrix of strings - names of constituents to
                                       use (useful for testing purposes).

       Calculation of confidence limits.
           'error'          'wboot'  - Boostrapped confidence intervals
                                       based on a correlated bivariate
                                       white-noise model.
                            'cboot'  - Boostrapped confidence intervals
                                       based on an uncorrelated bivariate
                                       coloured-noise model (default).
                            'linear' - Linearized error analysis that
                                       assumes an uncorrelated bivariate
                                       coloured noise model.

       Computation of "predicted" tide (passed to t_predic, but note that
                                        the default value is different).
           'synthesis'      0 - use all selected constituents
                            scalar>0 - use only those constituents with a
                                       SNR greater than that given (1 or 2
                                       are good choices, 2 is the default).
                                  <0 - return result of least-squares fit
                                       (should be the same as using '0',
                                       except that NaN-holes in original
                                       time series will remain and mean/trend
                                       are included).

       Least squares soln computational efficiency parameter
        'lsq'        'direct'  - use A\ x fit
                'normal'  - use (A'A)\(A'x) (may be necessary
                        for very large input vectors since
                                       A'A is much smaller than A)
                'best'      - automatically choose based on
                        length of series (default).

           It is possible to call t_tide without using property names,
           in which case the assumed calling sequence is

              T_TIDE(XIN,INTERVAL,START_TIME,LATITUDE,RAYLEIGH)


      OUTPUT:

        nameu=list of constituents used
        fu=frequency of tidal constituents (cycles/hr)
        tidecon=[fmaj,emaj,fmin,emin,finc,einc,pha,epha] for vector xin
               =[fmaj,emaj,pha,epha] for scalar (real) xin
           fmaj,fmin - constituent major and minor axes (same units as xin)
           emaj,emin - 95
     confidence intervals for fmaj,fmin
           finc - ellipse orientations (degrees)
           einc - 95
     confidence intervals for finc
           pha - constituent phases (degrees relative to Greenwich)
           epha - 95
     confidence intervals for pha
        xout=tidal prediction

     Note: Although missing data can be handled with NaN, it is wise not
           to have too many of them. If your time series has a lot of
           missing data at the beginning and/or end, then truncate the
           input time series.  The Rayleigh criterion is applied to
           frequency intervals calculated as the inverse of the input
           series length.

     A description of the theoretical basis of the analysis and some
     implementation details can be found in:

     Pawlowicz, R., B. Beardsley, and S. Lentz, "Classical Tidal
       "Harmonic Analysis Including Error Estimates in MATLAB
        using T_TIDE", Computers and Geosciences, 28, 929-937 (2002).

     (citation of this article would be appreciated if you find the
      toolbox useful).
    R. Pawlowicz
    11/8/99 -  Completely rewritten from the transliterated-
                to-matlab IOS/Foreman fortran code by S. Lentz
                and B. Beardsley.
    3/3/00  -   Redid errors to take into account covariances
                between u and v errors.
    7/21/00 -   Found that annoying bug in error calc!
    11/1/00 -   Added linear error analysis.
    8/29/01 -   Made synth=1 default, also changed behavior
                when no lat/time given so that phases are raw
                at central time.
    9/1/01  -   Moved some SNR code to t_predic.
    9/28/01 -   made sure you can't choose Z0 as constituent.
    6/12/01 -   better explanation for variance calcs, fixed
                bug in typed output (thanks Mike Cook).
    8/2/03 -    Added block processing for long time series (thanks
                to Derek Goring).
    9/2/03 -    Beta version of 18.6 year series handling
    12/2/03 -   Bug (x should be xin) fixed thanks to Mike Cook (again!)
    4/3/11 -    Changed (old) psd to (new) pwelch calls, also
                isfinite for finite.
    23/3/11 -   Corrected my conversion from psd to pwelch, thanks
                to Dan Codiga and (especially) Evan Haug!

     Version 1.3
     ----------------------Parse inputs-----------------------------------
    """

    dt = 1
    stime = np.array([])
    lat = np.array([])
    output = True
    ray = 1
    fid = 1
    corr_fs = np.array([0, 1000000.0])
    corr_fac = np.array([1, 1])
    secular = 'mean'
    infiname = np.array([])
    infirefname = np.array([])
    shallownames = np.array([])
    constitnames = np.array([])
    errcalc = 'cboot'
    synth = 2
    lsq = 'best'
    k = 1
    pi = np.pi
    out_style = 'classic'
    isComplex = False

    # Use kargs to set values other then the defaults
    if kwargs is not None:
        for key, value in kwargs.items():
            if (key == 'dt'):
                dt = value
            if (key == 'stime'):
                stime = np.array(value)
            if (key == 'lat'):
                lat = np.array(value)
            if (key == 'constitnames'):
                constitnames = tu.fourpad(value)
            if (key == 'errcalc'):
                errcalc = value
            if (key == 'output'):
                output = value
            if (key == 'synth'):
                synth = value
            if (key == 'out_style'):
                out_style = tu.style_check(value)
            if (key == 'secular'):
                secular = value

    # Check to make sure that incoming data is a vector.
    inn = xin.shape
    if len(inn) != 1:
        error('Input time series is not a vector')
    if 'complex' in xin.dtype.name:
        isComplex = True

    # Check size of incoming data.
    nobs = max(xin.shape)

    # Set matrix method if auto-choice.
    # This could be increased for nobs>10000.
    # 100,000 uses a reasonable amount of ram for current systems.
    # Kept as is for the time being to stay true to Matlab version.
    if lsq[0:3] == 'bes':
        if nobs > 10000:
            lsq = 'normal'
        else:
            lsq = 'direct'

    # Check to see if timeseries spans 18.6 years.
    if nobs*dt > 18.6*365.25*24:
        longseries = 1
        ltype = 'full'
    else:
        longseries = 0
        ltype = 'nodal'
    nobsu = nobs - np.remainder(nobs-1, 2)

    # Make series odd to give a center point
    # Time vector for entire time series centered at series midpoint.
    t = (dt*(np.arange(nobs)+1-np.ceil(nobsu/2)))

    if stime.size != 0:
        centraltime = stime + np.dot(np.floor(nobsu/2)/24.0, dt)
    else:
        centraltime = np.array([])

    # -------Get the frequencies to use in the harmonic analysis-----------
    tmptuple = tu.constituents(ray/(np.dot(dt, nobsu)),
                               constitnames, shallownames,
                               infiname, infirefname,
                               centraltime)
    nameu, fu, ju, namei, fi, jinf, jref = tmptuple

    mu = len(fu)
    mi = len(fi)

    # # inferred
    # Find the good data points
    # (here I assume that in a complex time series, if u is bad, so is v).
    gd = np.flatnonzero(np.isfinite(xin[0:nobsu]))
    ngood = max(gd.shape)

    # Now solve for the secular trend plus the analysis. Instead of solving
    # for + and - frequencies using exp(i*f*t), I use sines and cosines to
    # keep tc real.  If the input series is real, than this will
    # Automatically use real-only computation (faster).
    # However, for the analysis, it's handy to get the + and - frequencies
    # ('ap' and 'am'), and so that's what we do afterwards.
    # The basic code solves the matrix problem Ac=x+errors where the functions
    # to use in the fit fill up the A matrix, which is of size
    # (number points)x(number constituents). This can get very, very large
    # for long time series, and for this the more complex block processing
    # algorithm was added. It should give
    # identical results (up to roundoff error)
    if lsq[0:3] == 'dir':
        if secular[0:3] == 'lin':
            tc = np.hstack([np.ones((len(t), 1)),
                            np.cos(2*pi*np.outer(t, fu)),
                            np.sin(2*pi*np.outer(t, fu)),
                            t.reshape(-1, 1)*(2/dt/nobsu)])
        else:
            tc = np.hstack([np.ones((len(t), 1)),
                            np.cos(2*pi*np.outer(t, fu)),
                            np.sin(2*pi*np.outer(t, fu))])

        coef = np.linalg.lstsq(tc[gd, :], xin[gd])[0].T

        # z0 a+ and a- amplitudes
        z0 = coef[0]
        ap = (coef[1:mu+1]-1j*coef[(1+mu):(mu*2)+1])/2
        am = (coef[1:mu+1]+1j*coef[(1+mu):(mu*2)+1])/2

        if secular[0:3] == 'lin':
            dz0 = coef[-1]
        else:
            dz0 = 0

        # Save least squares fitted prediction incase synth<=0
        xout = np.dot(tc, coef)

    else:
        # More complicated code required for long
        # timeseries when memory maybe a problem.
        # Modified from code submitted by Derek Goring (NIWA Chrischurch)
        # Basically the normal equations are formed
        # (rather than using Matlab's algorithm for least squares);
        # this can be done by adding up subblocks of data.
        # Notice how the code is messier,
        # and we have to recalculate everything to get the original fit.
        nsub = 5000
        # Block length - doesn't matter really but should be small enough to
        # get allocated quickly
        if secular[0:3] == 'lin':
            lhs = np.zeros(shape=(2*mu+2, 2*mu+2), dtype='float64')
            rhs = np.zeros(shape=(2*mu+2, ), dtype='float64')
            for j1 in range(1, (ngood+1), nsub):
                j2 = np.min([j1+nsub-1, ngood])
                tslice = t[gd[(j1-1):j2]-1]
                E = np.hstack([np.ones((j2-j1+1, 1)),
                               np.cos(2*pi*np.outer(tslice, fu)),
                               np.sin(2*pi*np.outer(tslice, fu)),
                               tslice.reshape(-1, 1)*(2/dt/nobsu)])
                rhs = rhs + np.dot(E.T, xin[(gd[(j1-1):j2]-1)])
                lhs = lhs + np.dot(E.T, E)
        else:
            lhs = np.zeros(shape=(2*mu+1, 2*mu+1), dtype='float64')
            rhs = np.zeros(shape=(2*mu+1, ), dtype='float64')
            for j1 in range(1, (ngood+1), nsub):
                j2 = np.min([j1+nsub-1, ngood])
                tslice = t[gd[(j1-1):j2]-1]
                E = np.hstack([np.ones((j2-j1+1, 1)),
                               np.cos(2*pi*np.outer(tslice, fu)),
                               np.sin(2*pi*np.outer(tslice, fu))])
                rhs = rhs + np.dot(E.T, xin[(gd[(j1-1):j2]-1)])
                lhs = lhs + np.dot(E.T, E)
        coef = np.linalg.lstsq(lhs, rhs)[0].T

        # z0 a+ and a- amplitudes
        z0 = coef[0]
        ap = (coef[1:mu+1]-1j*coef[(1+mu):(mu*2)+1])/2
        am = (coef[1:mu+1]+1j*coef[(1+mu):(mu*2)+1])/2

        if secular[0:3] == 'lin':
            dz0 = coef[-1]
        else:
            dz0 = 0

        xout = xin.copy()
        # Copies over NaN
        if secular[0:3] == 'lin':
            for j1 in range(1, (nobs+1), nsub):
                j2 = np.min([j1+nsub-1, nobs])
                tslice = t[(j1-1):j2]
                E = np.hstack([np.ones((j2-j1+1, 1)),
                               np.cos(2*pi*np.outer(tslice, fu)),
                               np.sin(2*pi*np.outer(tslice, fu)),
                               np.dot(tslice, (2/dt/nobsu)).reshape(-1, 1)])
                xout[(j1-1):j2] = np.dot(E, coef)
        else:
            for j1 in range(1, (nobs+1), nsub):
                j2 = np.min([j1+nsub-1, nobs])
                tslice = t[(j1-1):j2]
                E = np.hstack([np.ones((j2-j1+1, 1)),
                               np.cos(2*pi*np.outer(tslice, fu)),
                               np.sin(2*pi*np.outer(tslice, fu))])
                xout[(j1-1):j2] = np.dot(E, coef)

    # Check variance explained
    # (but do this with the original fit, and the residuals!)
    xres = xin-xout

    # Real time series
    varx = np.cov(np.real(xin[(gd)]))
    varxp = np.cov(np.real(xout[(gd)]))
    varxr = np.cov(np.real(xres[(gd)]))

    if isComplex:
        # Complex time series
        vary = np.cov(np.imag(xin[(gd)]))
        varyp = np.cov(np.imag(xout[(gd)]))
        varyr = np.cov(np.imag(xres[(gd)]))

    ####################################################################
    # ---------- Correct for prefiltering--------------------------------
    ####################################################################
    corrfac = spi.interpolate.interp1d(corr_fs, corr_fac)(fu)
    # To stop things blowing up!
    corrfac[corrfac > 100] = 1
    corrfac[corrfac < 0.01] = 1
    corrfac[np.isnan(corrfac)] = 1
    ap = ap*np.squeeze(corrfac)
    am = am * np.squeeze(np.conj(corrfac))

    ####################################################################
    # ---------------Nodal Corrections-----------------------------------
    # Generate nodal corrections and calculate phase relative to
    # Greenwich. Note that this is a slightly weird way to do the nodal
    # corrections, but is 'traditional'. The "right" way would be to
    # change the basis functions used in the least-squares fit above.
    ####################################################################
    if ((lat.size != 0) & (stime.size != 0)):
        # Time and latitude
        # Get nodal corrections at midpoint time.
        v, u, f = t_vuf(ltype, centraltime,
                        np.hstack([ju, jinf]).astype(int), lat)
        vu = np.dot((v + u), 360)
        # total phase correction (degrees)
        nodcor = 'Greenwich phase computed with nodal\n \
                  corrections applied to amplitude\n \
                  and phase relative to center time\n'
    elif (stime.size != 0):
        # Time only
        # Get nodal corrections at midpoint time
        v, u, f = t_vuf(ltype, centraltime,
                        np.hstack([ju, jinf]).astype(int))
        vu = np.dot((v + u), 360)
        # total phase correction (degrees)
        nodcor = 'Greenwich phase computed, no nodal corrections'
    else:
        # No time, no latitude
        nshape = (len(ju)+len(jinf), 1)
        vu = np.zeros(nshape, dtype='float64')
        f = np.ones(nshape, dtype='float64')
        nodcor = 'Phases at central time'

    ####################################################################
    # ---------------Inference Corrections------------------------------
    # Once again, the "right" way to do this
    # would be to change the basis functions.
    ####################################################################
    ii = np.flatnonzero(np.isfinite(jref))
    if ii:
        print('   Do inference corrections\\n')
        snarg = np.dot(np.dot(nobsu*pi, (fi[(ii-1)]-fu[(jref[(ii-1)]-1)])), dt)
        scarg = sin(snarg) / snarg
        if infamprat.shape[1] == 1:
            # For real time series
            pearg = np.dot(2*pi,
                           (vu[(mu+ii-1)] -
                            vu[(jref[(ii-1)]-1)] +
                            infph[(ii-1)]))/360
            pcfac = infamprat[(ii-1)]*f[(mu+ii-1)] / \
                f[(jref[(ii-1)]-1)]*exp(np.dot(i, pearg))
            pcorr = 1 + pcfac * scarg
            mcfac = conj(pcfac)
            mcorr = conj(pcorr)
        else:
            # For complex time series
            pearg = np.dot(2*pi,
                           (vu[(mu+ii-1)] -
                            vu[(jref[(ii-1)]-1)] +
                            infph[(ii-1), 0]))/360
            pcfac = infamprat[(ii-1), 0]*f[(mu+ii-1)] / \
                f[(jref[(ii-1)]-1)]*exp(np.dot(i, pearg))
            pcorr = 1 + pcfac * scarg
            mearg = np.dot(-2*pi,
                           (vu[(mu+ii-1)] -
                            vu[(jref[(ii-1)]-1)] +
                            infph[(ii-1), 1]))/360
            mcfac = infamprat[(ii-1), 1]*f[(mu+ii-1)] / \
                f[(jref[(ii-1)]-1)]*exp(np.dot(i, mearg))
            mcorr = 1+mcfac*scarg
        ap[(jref[(ii-1)]-1)] = ap[(jref[(ii-1)]-1)] / pcorr
        # Changes to existing constituents
        ap = np.array([ap, ap[(jref[(ii-1)]-1)]*pcfac]).reshape(1, -1)
        # Inferred constituents
        am[(jref[(ii-1)]-1)] = am[(jref[(ii-1)]-1)] / mcorr
        am = np.array([am, am[(jref[(ii-1)]-1)]*mcfac]).reshape(1, -1)
        fu = np.array([fu, fi[(ii-1)]]).reshape(1, -1)
        nameu = np.array([nameu, namei[(ii-1), :]]).reshape(1, -1)

    ####################################################################
    # --------------Error Bar Calculations------------------------------
    #
    # Error bar calcs involve two steps:
    # 1) Estimate the uncertainties in the analyzed amplitude
    #   for both + and - frequencies (i.e., in 'ap' and 'am').
    #   A simple way of doing this is to take the variance of the
    #   original time series and divide it into the amount appearing
    #   in the bandwidth of the analysis (approximately 1/length).
    #   A more sophisticated way is to assume "locally white"
    #   noise in the vicinity of, e.g., the diurnal consistuents.
    #   This takes into account slopes in the continuum spectrum.
    #
    # 2) Transform those uncertainties into ones suitable for ellipse
    #   parameters (axis lengths, angles). This can be done
    #   analytically for large signal-to-noise ratios. However, the
    #   transformation is non-linear at lows SNR, say, less than 10
    #   or so.
    #
    ####################################################################

    # Fill in "internal" NaNs with linearly interpolated
    # values so we can fft things.
    xr = tu.fixgaps(xres)

    nreal = 1

    if errcalc.endswith('boot'):
        # print('Using nonlinear bootstrapped error estimates.');
        ################################################################
        # "noise" matrices are created with the right covariance
        # structure to add to the analyzed components to
        # create 'nreal' REPLICATES.
        ################################################################

        nreal = 300
        # Create noise matrices
        NP, NM = tu.noise_realizations(xr[(np.isfinite(xr))],
                                       fu, dt, nreal, errcalc)
        # All replicates are then transformed (nonlinearly) into
        # ellipse parameters. The computed error bars are then
        # based on the std dev of the replicates.

        AP = np.repeat(ap, nreal).reshape(len(ap), nreal) + NP
        AM = np.repeat(am, nreal).reshape(len(am), nreal) + NM
        # Add to analysis (first column of NM,NP=0
        # so first column of AP/M holds ap/m).

        # Angle/magnitude form:
        epsp = np.angle(AP)*180/pi
        epsm = np.angle(AM)*180/pi

        ap = np.absolute(AP)
        am = np.absolute(AM)
    else:
        if errcalc == 'linear':
            print('Using linearized error estimates.')
            ############################################################
            # Uncertainties in analyzed amplitudes are computed in
            # different spectral bands. Real and imaginary parts of
            # the residual time series are treated separately
            # (no cross-covariance is assumed).
            #
            # Noise estimates are then determined from a linear analysis
            # of errors, assuming that everything is uncorrelated.
            # This is OK for scalar timeseries but can fail for vector
            # time series if the noise is not isotropic.
            ############################################################

            ercx, eicx = tu.noise_stats(xr[np.isfinite(xr)], fu, dt)

            # Note - here we assume that the error in the cos and sin
            # terms is equal, and equal to total power in the
            # encompassing frequency bin. It seems like there should be
            # a factor of 2 here somewhere but it only works this way!
            # <shrug>

            emaj, emin, einc, epha = errell(ap+am, np.dot(1j, (ap-am)),
                                            ercx, ercx, eicx, eicx)
            epsp = np.dot(np.angle(ap), 180) / pi
            epsm = np.dot(np.angle(am), 180) / pi
            ap = np.absolute(ap)
            am = np.absolute(am)
        else:
            print("Unrecognized type of error analysis: " +
                  errcalc + " specified!")
    # -----Convert complex amplitudes to standard ellipse parameters----
    aap = ap / np.repeat(f, nreal).reshape(f.shape[0], nreal)
    # Apply nodal corrections and
    aam = am / np.repeat(f, nreal).reshape(f.shape[0], nreal)
    # compute ellipse parameters.
    fmaj = aap + aam
    # major axis
    fmin = aap - aam
    # minor axis

    gp = np.mod(np.repeat(vu, nreal).reshape(vu.shape[0], nreal)-epsp, 360)
    # pos. Greenwich phase in deg.
    gm = np.mod(np.repeat(vu, nreal).reshape(vu.shape[0], nreal)+epsm, 360)
    # neg. Greenwich phase in deg.
    finc = ((epsp + epsm) / 2)
    finc[:, 0] = np.mod(finc[:, 0], 180)

    # Ellipse inclination in degrees
    # (mod 180 to prevent ambiguity, i.e.,
    # we always ref. against northern
    # semi-major axis.
    finc = tu.cluster(finc, 180)
    # Cluster angles around the 'true' angle to avoid 360 degree wraps.
    pha = np.mod(gp + finc, 360)
    # Greenwich phase in degrees.
    pha = tu.cluster(pha, 360)
    # Cluster angles around the 'true' angle to avoid 360 degree wraps.

    # ----------------Generate 95% CI-----------------------------------
    # For bootstrapped errors, we now compute limits of the distribution.
    if errcalc.endswith('boot'):
        def booterrcalc(para, nreal):
            errval = np.multiply(
                      np.median(
                       np.absolute(
                        fmaj-(np.median(fmaj, 1).reshape(-1, 1) *
                              np.ones([1, nreal]))), 1)/0.6375, 1.96)
            return errval

        emaj = booterrcalc(fmaj, nreal)
        emin = booterrcalc(fmin, nreal)
        einc = booterrcalc(finc, nreal)
        epha = booterrcalc(pha, nreal)

    else:
        # In the linear analysis, the 95 CI are computed from the sigmas
        # by this fudge factor (infinite degrees of freedom).
        emaj = np.dot(1.96, emaj)
        emin = np.dot(1.96, emin)
        einc = np.dot(1.96, einc)
        epha = np.dot(1.96, epha)

    if isComplex:
        tidecon = np.array([fmaj[:, 0], emaj, fmin[:, 0], emin,
                            finc[:, 0], einc, pha[:, 0], epha]).T
    else:
        tidecon = np.array([fmaj[:, 0], emaj, pha[:, 0], epha]).T
    tideconout = tidecon.copy()
    # Sort results by frequency (needed if anything has been inferred
    # since these are stuck at the end of the list by code above).
    if any(np.isfinite(jref)):
        fu, I = sort(fu)
        nameu = nameu[(I-1), :]
        tidecon = tidecon[(I-1), :]
    snr = (tidecon[:, 0] / tidecon[:, 1]) ** 2
    # signal to noise ratio
    # --------Generate a 'prediction' using significant constituents----
    xoutOLD = xout
    if synth >= 0:
        if lat.size != 0 & stime.size != 0:
            # This does not account for latitude,
            # functionality not added to t_predic yet.
            xout = t_predic(stime + np.array([range(nobs)])*dt/24.0,
                            nameu, fu, tidecon, synth=synth)
        elif (stime.size != 0):
            xout = t_predic(stime + np.array([range(nobs)])*dt/24.0,
                            nameu, fu, tidecon, synth=synth)
        else:
            xout = t_predic(t / 24.0, nameu, fu, tidecon, synth=synth)
    elif output:
        print('   Returning fitted prediction\n')

    # Check variance explained (but now do this
    # with the synthesized fit) and the residuals!
    xres = xin[:] - xout[:]

    # -----------------Output results-----------------------------------
    if output is not False:
        out = {}
        out['nobs'] = nobs
        out['ngood'] = ngood
        out['dt'] = dt
        out['xin'] = xin
        out['ray'] = ray
        out['nodcor'] = nodcor
        out['z0'] = z0
        out['dz0'] = dz0
        out['xingd'] = xin[gd]
        out['xoutgd'] = xout[gd]
        out['xresgd'] = xres[gd]
        out['isComplex'] = isComplex

        out['fu'] = fu
        out['nameu'] = nameu
        out['tidecon'] = tidecon
        out['snr'] = snr
        out['synth'] = synth

        if stime.size != 0:
            out['stime'] = stime

        if 'classic' in out_style:
            tu.classic_style(out)
        if 'pandas' in out_style:
            tu.pandas_style(out)

    xout = xout.reshape(inn[0], 1)

    return nameu, fu, tideconout, xout
Exemple #2
0
def t_predic(tim, names, freq, tidecon, **kwargs):
    """T_PREDIC Tidal prediction
     YOUT=T_PREDIC(TIM,NAMES,FREQ,TIDECON) makes a tidal prediction
     using the output of T_TIDE at the specified times TIM in decimal
     days (from DATENUM). Optional arguments can be specified using
     property/value pairs:

           YOUT=T_PREDIC(...,TIDECON,property,value,...)

     Available properties are:

        In the simplest case, the tidal analysis was done without nodal
        corrections, and thus neither will the prediction. If nodal
        corrections were used in the analysis, then it is likely we will
        want to use them in the prediction too and these are computed
        using the latitude, if given.

         'latitude'        decimal degrees (+north) (default: none)

        If the original analysis was >18.6 years satellites are
        not included and we force that here:

         'anallength'      'nodal' (default)
                           'full'  For >18.6 years.

        The tidal prediction may be restricted to only some of the
        available constituents:

         'synthesis'    0 - Use all selected constituents.  (default)
                        scalar>0 - Use only those constituents with a SNR
                                   greater than that given (1 or 2 are
                                   good choices).


      It is possible to call t_predic without using property names, in
      which case the assumed calling sequence is

        YOUT=T_PREDIC(TIM,NAMES,FREQ,TIDECON,LATITUDE,SYNTHESIS);

      T_PREDIC can be called using the tidal structure available as an
      optional output from T_TIDE

        YOUT=T_PREDIC(TIM,TIDESTRUC,...)

      This is in fact the recommended calling procedure (and required
      when the analysis results are from series>18.6 years in length)
     R. Pawlowicz 11/8/99
     Version 1.0
     8/2/03 - Added block processing to generate prediction (to
              avoid memory overflows for long time series).
     29/9/04 - small bug with undefined ltype fixed
    """

    longseries = 0
    ltype = 'nodal'
    lat = np.array([])
    synth = 0
    k = 1
    tim = tim.reshape(-1, 1)

    # Use kwargs to set values other then the defaults
    if kwargs is not None:
        for key, value in kwargs.items():
            if (key == 'ltype'):
                ltype = value
            if (key == 'synth'):
                synth = value

    # Do the synthesis.
    snr = (tidecon[:, 0] / tidecon[:, 1]) ** 2
    # signal to noise ratio
    if synth > 0:
        I = snr > synth
        if not any(I):
            print('No predictions with this SNR')
            yout = np.nan + np.zeros(shape=(tim.shape, tim.shape),
                                     dtype='float64')
            return yout
        tidecon = tidecon[I, :]
        names = names[I]
        freq = freq[I]
    if tidecon.shape[1] == 4:
        # Real time series
        ap = np.multiply(tidecon[:, 0]/2.0,
                         np.exp(-1j*tidecon[:, 2]*np.pi/180))
        am = np.conj(ap)
    else:
        ap = np.multiply((tidecon[:, 0] + tidecon[:, 2]) / 2.0,
                         np.exp(np.dot(np.dot(1j, np.pi) / 180,
                                       (tidecon[:, 4] - tidecon[:, 6]))))

        am = np.multiply((tidecon[:, 0] - tidecon[:, 2]) / 2.0,
                         np.exp(np.dot(np.dot(1j, np.pi) / 180,
                                       (tidecon[:, 4] + tidecon[:, 6]))))

    # Mean at central point (get rid of one point at end to
    # take mean of odd number of points if necessary).
    jdmid = np.mean(tim[0:np.dot(2, np.fix((max(tim.shape) - 1) / 2)) + 1])
    if longseries:
        const = t_get18consts
        ju = np.zeros(shape=(freq.shape, freq.shape), dtype='float64')
        for k in range(1, (names.shape[0]+1)):
            inam = strmatch(names[(k-1), :], const.name)
            if max(inam.shape) == 1:
                ju[(k-1)] = inam
            else:
                if max(inam.shape) > 1:
                    minf, iminf = np.min(abs(freq[(k-1)] - const.freq(inam)))
                    ju[(k-1)] = inam[(iminf-1)]
    else:
        const, sat, cshallow = t_getconsts(np.array([]))
        ju = np.zeros((len(freq),), dtype='int32')
        # Check to make sure names and frequencies match expected values.
        for k in range(0, (names.shape[0])):
            ju[k] = np.argwhere(const['name'] == names[(k)])
        # if any(freq~=const.freq(ju)),
        # error('Frequencies do not match names in input');
        # end;
    # Get the astronical argument with or without nodal corrections.
    if ((lat.size != 0) & (np.absolute(jdmid) > 1)):
        v, u, f = t_vuf(ltype, jdmid, ju, lat)
    else:
        if np.fabs(jdmid) > 1:
            # a real date
            v, u, f = t_vuf(ltype, jdmid, ju)
        else:
            v = np.zeros((len(ju),), dtype='float64')
            u = v
            f = np.ones((len(ju),), dtype='float64')

    ap = ap * f * np.exp(+1j*2*np.pi*(u + v))
    am = am * f * np.exp(-1j*2*np.pi*(u + v))
    tim = tim - jdmid

    n, m = tim.shape
    ntim = max(tim.shape)
    nsub = 10000
    yout = np.zeros([n*m, ], dtype='complex128')

    # longer than one year hourly.
    for j1 in np.arange(0, ntim, nsub):
        j1 = j1.astype(int)
        j2 = np.min([j1 + nsub, ntim]).astype(int)
        tap = np.repeat(ap, j2-j1).reshape(len(ap), j2-j1)
        tam = np.repeat(am, j2-j1).reshape(len(am), j2-j1)

        touter = np.outer(24*1j*2*np.pi*freq, tim[j1:j2])
        yout[j1:j2] = np.sum(np.multiply(np.exp(touter), tap), axis=0) +\
            np.sum(np.multiply(np.exp(-touter), tam), axis=0)

    if (tidecon.shape[1] == 4):
        return np.real(yout)
    else:
        return yout