| 1 | #!/usr/bin/env python
|
|---|
| 2 | """ Fast algorithm for spectral analysis of unevenly sampled data
|
|---|
| 3 |
|
|---|
| 4 | The Lomb-Scargle method performs spectral analysis on unevenly sampled
|
|---|
| 5 | data and is known to be a powerful way to find, and test the
|
|---|
| 6 | significance of, weak periodic signals. The method has previously been
|
|---|
| 7 | thought to be 'slow', requiring of order 10(2)N(2) operations to analyze
|
|---|
| 8 | N data points. We show that Fast Fourier Transforms (FFTs) can be used
|
|---|
| 9 | in a novel way to make the computation of order 10(2)N log N. Despite
|
|---|
| 10 | its use of the FFT, the algorithm is in no way equivalent to
|
|---|
| 11 | conventional FFT periodogram analysis.
|
|---|
| 12 |
|
|---|
| 13 | Keywords:
|
|---|
| 14 | DATA SAMPLING, FAST FOURIER TRANSFORMATIONS,
|
|---|
| 15 | SPECTRUM ANALYSIS, SIGNAL PROCESSING
|
|---|
| 16 |
|
|---|
| 17 | Example:
|
|---|
| 18 | > import numpy
|
|---|
| 19 | > import lomb
|
|---|
| 20 | > x = numpy.arange(10)
|
|---|
| 21 | > y = numpy.sin(x)
|
|---|
| 22 | > fx,fy, nout, jmax, prob = lomb.fasper(x,y, 6., 6.)
|
|---|
| 23 |
|
|---|
| 24 | Reference:
|
|---|
| 25 | Press, W. H. & Rybicki, G. B. 1989
|
|---|
| 26 | ApJ vol. 338, p. 277-280.
|
|---|
| 27 | Fast algorithm for spectral analysis of unevenly sampled data
|
|---|
| 28 | bib code: 1989ApJ...338..277P
|
|---|
| 29 |
|
|---|
| 30 | """
|
|---|
| 31 | from numpy import *
|
|---|
| 32 | from numpy.fft import *
|
|---|
| 33 |
|
|---|
| 34 | def __spread__(y, yy, n, x, m):
|
|---|
| 35 | """
|
|---|
| 36 | Given an array yy(0:n-1), extirpolate (spread) a value y into
|
|---|
| 37 | m actual array elements that best approximate the "fictional"
|
|---|
| 38 | (i.e., possible noninteger) array element number x. The weights
|
|---|
| 39 | used are coefficients of the Lagrange interpolating polynomial
|
|---|
| 40 | Arguments:
|
|---|
| 41 | y :
|
|---|
| 42 | yy :
|
|---|
| 43 | n :
|
|---|
| 44 | x :
|
|---|
| 45 | m :
|
|---|
| 46 | Returns:
|
|---|
| 47 |
|
|---|
| 48 | """
|
|---|
| 49 | nfac=[0,1,1,2,6,24,120,720,5040,40320,362880]
|
|---|
| 50 | if m > 10. :
|
|---|
| 51 | print 'factorial table too small in spread'
|
|---|
| 52 | return
|
|---|
| 53 |
|
|---|
| 54 | ix=long(x)
|
|---|
| 55 | if x == float(ix):
|
|---|
| 56 | yy[ix]=yy[ix]+y
|
|---|
| 57 | else:
|
|---|
| 58 | ilo = long(x-0.5*float(m)+1.0)
|
|---|
| 59 | ilo = min( max( ilo , 1 ), n-m+1 )
|
|---|
| 60 | ihi = ilo+m-1
|
|---|
| 61 | nden = nfac[m]
|
|---|
| 62 | fac=x-ilo
|
|---|
| 63 | for j in range(ilo+1,ihi+1): fac = fac*(x-j)
|
|---|
| 64 | yy[ihi] = yy[ihi] + y*fac/(nden*(x-ihi))
|
|---|
| 65 | for j in range(ihi-1,ilo-1,-1):
|
|---|
| 66 | nden=(nden/(j+1-ilo))*(j-ihi)
|
|---|
| 67 | yy[j] = yy[j] + y*fac/(nden*(x-j))
|
|---|
| 68 |
|
|---|
| 69 | def fasper(x,y,ofac,hifac, MACC=4):
|
|---|
| 70 | """ function fasper
|
|---|
| 71 | Given abscissas x (which need not be equally spaced) and ordinates
|
|---|
| 72 | y, and given a desired oversampling factor ofac (a typical value
|
|---|
| 73 | being 4 or larger). this routine creates an array wk1 with a
|
|---|
| 74 | sequence of nout increasing frequencies (not angular frequencies)
|
|---|
| 75 | up to hifac times the "average" Nyquist frequency, and creates
|
|---|
| 76 | an array wk2 with the values of the Lomb normalized periodogram at
|
|---|
| 77 | those frequencies. The arrays x and y are not altered. This
|
|---|
| 78 | routine also returns jmax such that wk2(jmax) is the maximum
|
|---|
| 79 | element in wk2, and prob, an estimate of the significance of that
|
|---|
| 80 | maximum against the hypothesis of random noise. A small value of prob
|
|---|
| 81 | indicates that a significant periodic signal is present.
|
|---|
| 82 |
|
|---|
| 83 | Reference:
|
|---|
| 84 | Press, W. H. & Rybicki, G. B. 1989
|
|---|
| 85 | ApJ vol. 338, p. 277-280.
|
|---|
| 86 | Fast algorithm for spectral analysis of unevenly sampled data
|
|---|
| 87 | (1989ApJ...338..277P)
|
|---|
| 88 |
|
|---|
| 89 | Arguments:
|
|---|
| 90 | X : Abscissas array, (e.g. an array of times).
|
|---|
| 91 | Y : Ordinates array, (e.g. corresponding counts).
|
|---|
| 92 | Ofac : Oversampling factor.
|
|---|
| 93 | Hifac : Hifac * "average" Nyquist frequency = highest frequency
|
|---|
| 94 | for which values of the Lomb normalized periodogram will
|
|---|
| 95 | be calculated.
|
|---|
| 96 |
|
|---|
| 97 | Returns:
|
|---|
| 98 | Wk1 : An array of Lomb periodogram frequencies.
|
|---|
| 99 | Wk2 : An array of corresponding values of the Lomb periodogram.
|
|---|
| 100 | Nout : Wk1 & Wk2 dimensions (number of calculated frequencies)
|
|---|
| 101 | Jmax : The array index corresponding to the MAX( Wk2 ).
|
|---|
| 102 | Prob : False Alarm Probability of the largest Periodogram value
|
|---|
| 103 | MACC : Number of interpolation points per 1/4 cycle
|
|---|
| 104 | of highest frequency
|
|---|
| 105 |
|
|---|
| 106 | History:
|
|---|
| 107 | 02/23/2009, v1.0, MF
|
|---|
| 108 | Translation of IDL code (orig. Numerical recipies)
|
|---|
| 109 | """
|
|---|
| 110 | #Check dimensions of input arrays
|
|---|
| 111 | n = long(len(x))
|
|---|
| 112 | if n != len(y):
|
|---|
| 113 | print 'Incompatible arrays.'
|
|---|
| 114 | return
|
|---|
| 115 |
|
|---|
| 116 | nout = 0.5*ofac*hifac*n
|
|---|
| 117 | nfreqt = long(ofac*hifac*n*MACC) #Size the FFT as next power
|
|---|
| 118 | nfreq = 64L # of 2 above nfreqt.
|
|---|
| 119 |
|
|---|
| 120 | while nfreq < nfreqt:
|
|---|
| 121 | nfreq = 2*nfreq
|
|---|
| 122 |
|
|---|
| 123 | ndim = long(2*nfreq)
|
|---|
| 124 |
|
|---|
| 125 | #Compute the mean, variance
|
|---|
| 126 | ave = y.mean()
|
|---|
| 127 | ##sample variance because the divisor is N-1
|
|---|
| 128 | var = ((y-y.mean())**2).sum()/(len(y)-1)
|
|---|
| 129 | # and range of the data.
|
|---|
| 130 | xmin = x.min()
|
|---|
| 131 | xmax = x.max()
|
|---|
| 132 | xdif = xmax-xmin
|
|---|
| 133 |
|
|---|
| 134 | #extirpolate the data into the workspaces
|
|---|
| 135 | wk1 = zeros(ndim, dtype='complex')
|
|---|
| 136 | wk2 = zeros(ndim, dtype='complex')
|
|---|
| 137 |
|
|---|
| 138 | fac = ndim/(xdif*ofac)
|
|---|
| 139 | fndim = ndim
|
|---|
| 140 | ck = ((x-xmin)*fac) % fndim
|
|---|
| 141 | ckk = (2.0*ck) % fndim
|
|---|
| 142 |
|
|---|
| 143 | for j in range(0L, n):
|
|---|
| 144 | __spread__(y[j]-ave,wk1,ndim,ck[j],MACC)
|
|---|
| 145 | __spread__(1.0,wk2,ndim,ckk[j],MACC)
|
|---|
| 146 |
|
|---|
| 147 | #Take the Fast Fourier Transforms
|
|---|
| 148 | wk1 = ifft( wk1 )*len(wk1)
|
|---|
| 149 | wk2 = ifft( wk2 )*len(wk1)
|
|---|
| 150 |
|
|---|
| 151 | wk1 = wk1[1:nout+1]
|
|---|
| 152 | wk2 = wk2[1:nout+1]
|
|---|
| 153 | rwk1 = wk1.real
|
|---|
| 154 | iwk1 = wk1.imag
|
|---|
| 155 | rwk2 = wk2.real
|
|---|
| 156 | iwk2 = wk2.imag
|
|---|
| 157 |
|
|---|
| 158 | df = 1.0/(xdif*ofac)
|
|---|
| 159 |
|
|---|
| 160 | #Compute the Lomb value for each frequency
|
|---|
| 161 | hypo2 = 2.0 * abs( wk2 )
|
|---|
| 162 | hc2wt = rwk2/hypo2
|
|---|
| 163 | hs2wt = iwk2/hypo2
|
|---|
| 164 |
|
|---|
| 165 | cwt = sqrt(0.5+hc2wt)
|
|---|
| 166 | swt = sign(hs2wt)*(sqrt(0.5-hc2wt))
|
|---|
| 167 | den = 0.5*n+hc2wt*rwk2+hs2wt*iwk2
|
|---|
| 168 | cterm = (cwt*rwk1+swt*iwk1)**2./den
|
|---|
| 169 | sterm = (cwt*iwk1-swt*rwk1)**2./(n-den)
|
|---|
| 170 |
|
|---|
| 171 | wk1 = df*(arange(nout, dtype='float')+1.)
|
|---|
| 172 | wk2 = (cterm+sterm)/(2.0*var)
|
|---|
| 173 | pmax = wk2.max()
|
|---|
| 174 | jmax = wk2.argmax()
|
|---|
| 175 |
|
|---|
| 176 |
|
|---|
| 177 | #Significance estimation
|
|---|
| 178 | #expy = exp(-wk2)
|
|---|
| 179 | #effm = 2.0*(nout)/ofac
|
|---|
| 180 | #sig = effm*expy
|
|---|
| 181 | #ind = (sig > 0.01).nonzero()
|
|---|
| 182 | #sig[ind] = 1.0-(1.0-expy[ind])**effm
|
|---|
| 183 |
|
|---|
| 184 | #Estimate significance of largest peak value
|
|---|
| 185 | expy = exp(-pmax)
|
|---|
| 186 | effm = 2.0*(nout)/ofac
|
|---|
| 187 | prob = effm*expy
|
|---|
| 188 |
|
|---|
| 189 | if prob > 0.01:
|
|---|
| 190 | prob = 1.0-(1.0-expy)**effm
|
|---|
| 191 |
|
|---|
| 192 | return wk1,wk2,nout,jmax,prob
|
|---|
| 193 |
|
|---|
| 194 | def getSignificance(wk1, wk2, nout, ofac):
|
|---|
| 195 | """ returns the peak false alarm probabilities
|
|---|
| 196 | Hence the lower is the probability and the more significant is the peak
|
|---|
| 197 | """
|
|---|
| 198 | expy = exp(-wk2)
|
|---|
| 199 | effm = 2.0*(nout)/ofac
|
|---|
| 200 | sig = effm*expy
|
|---|
| 201 | ind = (sig > 0.01).nonzero()
|
|---|
| 202 | sig[ind] = 1.0-(1.0-expy[ind])**effm
|
|---|
| 203 | return sig
|
|---|