def bispectrumdx(x, y, z, nfft=None, wind=None, nsamp=None, overlap=None): """ Parameters: x - data vector or time-series y - data vector or time-series (same dimensions as x) z - data vector or time-series (same dimensions as x) nfft - fft length [default = power of two > segsamp] wind - window specification for frequency-domain smoothing if 'wind' is a scalar, it specifies the length of the side of the square for the Rao-Gabr optimal window [default=5] if 'wind' is a vector, a 2D window will be calculated via w2(i,j) = wind(i) * wind(j) * wind(i+j) if 'wind' is a matrix, it specifies the 2-D filter directly segsamp - samples per segment [default: such that we have 8 segments] - if x is a matrix, segsamp is set to the number of rows overlap - percentage overlap, allowed range [0,99]. [default = 50]; - if x is a matrix, overlap is set to 0. Output: Bspec - estimated bispectrum: an nfft x nfft array, with origin at the center, and axes pointing down and to the right. waxis - vector of frequencies associated with the rows and columns of Bspec; sampling frequency is assumed to be 1. """ (lx, lrecs) = x.shape (ly, nrecs) = y.shape (lz, krecs) = z.shape if lx != ly or lrecs != nrecs or ly != lz or nrecs != krecs: raise Exception('x, y and z should have identical dimensions') if ly == 1: x = x.reshape(1,-1) y = y.reshape(1,-1) z = z.reshape(1,-1) ly = nrecs nrecs = 1 if not overlap: overlap = 50 overlap = max(0,min(overlap,99)) if nrecs > 1: overlap = 0 if not nsamp: nsamp = 0 if nrecs > 1: nsamp = ly if nrecs == 1 and nsamp <= 0: nsamp = np.fix(ly/ (8 - 7 * overlap/100)) if nfft < nsamp: nfft = 2**nextpow2(nsamp) overlap = np.fix(overlap/100 * nsamp) nadvance = nsamp - overlap nrecs = np.fix((ly*nrecs - overlap) / nadvance) # create the 2-D window if not wind: wind = 5 m = n = 0 try: (m, n) = wind.shape except ValueError: (m,) = wind.shape n = 1 except AttributeError: m = n = 1 window = wind # scalar: wind is size of Rao-Gabr window if max(m, n) == 1: winsize = wind if winsize < 0: winsize = 5 # the window size L winsize = winsize - (winsize%2) + 1 # make it odd if winsize > 1: mwind = np.fix(nfft/winsize) # the scale parameter M lby2 = (winsize - 1)/2 theta = np.array([np.arange(-1*lby2, lby2+1)]) # force a 2D array opwind = np.ones([winsize, 1]) * (theta**2) # w(m,n) = m**2 opwind = opwind + opwind.transpose() + (np.transpose(theta) * theta) # m**2 + n**2 + mn opwind = 1 - ((2*mwind/nfft)**2) * opwind Hex = np.ones([winsize,1]) * theta Hex = abs(Hex) + abs(np.transpose(Hex)) + abs(Hex + np.transpose(Hex)) Hex = (Hex < winsize) opwind = opwind * Hex opwind = opwind * (4 * mwind**2) / (7 * np.pi**2) else: opwind = 1 # 1-D window passed: convert to 2-D elif min(m, n) == 1: window = window.reshape(1,-1) if np.any(np.imag(window)) != 0: print ("1-D window has imaginary components: window ignored") window = 1 if np.any(window) < 0: print ("1-D window has negative components: window ignored") window = 1 lwind = np.size(window) w = window.ravel(order='F') # the full symmetric 1-D windf = np.array(w[range(lwind-1, 0, -1) + [window]]) window = np.array([window], np.zeros([lwind-1,1])) # w(m)w(n)w(m+n) opwind = (windf * np.transpose(windf)) * hankel(np.flipud(window), window) winsize = np.size(window) # 2-D window passed: use directly else: winsize = m if m != n: print ("2-D window is not square: window ignored") window = 1 winsize = m if m%2 == 0: print ("2-D window does not have odd length: window ignored") window = 1 winsize = m opwind = window # accumulate triple products Bspec = np.zeros([nfft, nfft]) # the hankel mask (faster) mask = hankel(np.arange(nfft),np.array([nfft-1]+range(nfft-1))) locseg = np.arange(nsamp).transpose() x = x.ravel(order='F') y = y.ravel(order='F') z = z.ravel(order='F') for krec in xrange(nrecs): xseg = x[locseg].reshape(1,-1) yseg = y[locseg].reshape(1,-1) zseg = z[locseg].reshape(1,-1) Xf = np.fft.fft(xseg - np.mean(xseg), nfft) / nsamp Yf = np.fft.fft(yseg - np.mean(yseg), nfft) / nsamp CZf = np.fft.fft(zseg - np.mean(zseg), nfft) / nsamp CZf = np.conjugate(CZf).ravel(order='F') Bspec = Bspec + \ flat_eq(Bspec, (Xf * np.transpose(Yf)) * CZf[mask].reshape(nfft, nfft)) locseg = locseg + int(nadvance) Bspec = np.fft.fftshift(Bspec) / nrecs # frequency-domain smoothing if winsize > 1: lby2 = int((winsize-1)/2) Bspec = convolve2d(Bspec,opwind) Bspec = Bspec[range(lby2+1,lby2+nfft+1), :][:, np.arange(lby2+1,lby2+nfft+1)] if nfft%2 == 0: waxis = np.transpose(np.arange(-1*nfft/2, nfft/2)) / nfft else: waxis = np.transpose(np.arange(-1*(nfft-1)/2, (nfft-1)/2+1)) / nfft # cont1 = plt.contour(abs(Bspec), 4, waxis, waxis) cont = plt.contourf(waxis, waxis, abs(Bspec), 100, cmap=plt.cm.Spectral_r) plt.colorbar(cont) plt.title('Bispectrum estimated via the direct (FFT) method') plt.xlabel('f1') plt.ylabel('f2') plt.show() return (Bspec, waxis)
def bicoherencex(w, x, y, nfft=None, wind=None, nsamp=None, overlap=None): """ Direct (FD) method for estimating cross-bicoherence Parameters: w,x,y - data vector or time-series - should have identical dimensions nfft - fft length [default = power of two > nsamp] actual size used is power of two greater than 'nsamp' wind - specifies the time-domain window to be applied to each data segment; should be of length 'segsamp' (see below); otherwise, the default Hanning window is used. segsamp - samples per segment [default: such that we have 8 segments] - if x is a matrix, segsamp is set to the number of rows overlap - percentage overlap, 0 to 99 [default = 50] - if y is a matrix, overlap is set to 0. Output: bic - estimated cross-bicoherence: an nfft x nfft array, with origin at center, and axes pointing down and to the right. waxis - vector of frequencies associated with the rows and columns of bic; sampling frequency is assumed to be 1. """ if w.shape != x.shape or x.shape != y.shape: raise ValueError('w, x and y should have identical dimentions') (ly, nrecs) = y.shape if ly == 1: ly = nrecs nrecs = 1 w = w.reshape(1, -1) x = x.reshape(1, -1) y = y.reshape(1, -1) if not nfft: nfft = 128 if not overlap: overlap = 50 overlap = max(0, min(overlap, 99)) if nrecs > 1: overlap = 0 if not nsamp: nsamp = 0 if nrecs > 1: nsamp = ly if nrecs == 1 and nsamp <= 0: nsamp = np.fix(ly / (8 - 7 * overlap / 100)) if nfft < nsamp: nfft = 2**nextpow2(nsamp) overlap = np.fix(overlap / 100 * nsamp) nadvance = nsamp - overlap nrecs = np.fix((ly * nrecs - overlap) / nadvance) if not wind: wind = np.hanning(nsamp) try: (rw, cw) = wind.shape except ValueError: (rw, ) = wind.shape cw = 1 if min(rw, cw) != 1 or max(rw, cw) != nsamp: print("Segment size is " + str(nsamp)) print("Wind array is " + str(rw) + " by " + str(cw)) print("Using default Hanning window") wind = np.hanning(nsamp) wind = wind.reshape(1, -1) # Accumulate triple products bic = np.zeros([nfft, nfft]) Pyy = np.zeros([nfft, 1]) Pww = np.zeros([nfft, 1]) Pxx = np.zeros([nfft, 1]) mask = hankel(np.arange(nfft), np.array([nfft - 1] + range(nfft - 1))) Yf12 = np.zeros([nfft, nfft]) ind = np.transpose(np.arange(nsamp)) w = w.ravel(order='F') x = x.ravel(order='F') y = y.ravel(order='F') for k in xrange(nrecs): ws = w[ind] ws = (ws - np.mean(ws)) * wind Wf = np.fft.fft(ws, nfft) / nsamp CWf = np.conjugate(Wf) Pww = Pww + flat_eq(Pww, (Wf * CWf)) xs = x[ind] xs = (xs - np.mean(xs)) * wind Xf = np.fft.fft(xs, nfft) / nsamp CXf = np.conjugate(Xf) Pxx = Pxx + flat_eq(Pxx, (Xf * CXf)) ys = y[ind] ys = (ys - np.mean(ys)) * wind Yf = np.fft.fft(ys, nfft) / nsamp CYf = np.conjugate(Yf) Pyy = Pyy + flat_eq(Pyy, (Yf * CYf)) Yf12 = flat_eq(Yf12, CYf.ravel(order='F')[mask]) bic = bic + (Wf * np.transpose(Xf)) * Yf12 ind = ind + int(nadvance) bic = bic / nrecs Pww = Pww / nrecs Pxx = Pxx / nrecs Pyy = Pyy / nrecs mask = flat_eq(mask, Pyy.ravel(order='F')[mask]) bic = abs(bic)**2 / ((Pww * np.transpose(Pxx)) * mask) bic = np.fft.fftshift(bic) # Contour plot of magnitude bispectrum if nfft % 2 == 0: waxis = np.transpose(np.arange(-1 * nfft / 2, nfft / 2)) / nfft else: waxis = np.transpose(np.arange(-1 * (nfft - 1) / 2, (nfft - 1) / 2 + 1)) / nfft cont = plt.contourf(waxis, waxis, bic, 100, cmap=plt.cm.Spectral_r) plt.colorbar(cont) plt.title('Bicoherence estimated via the direct (FFT) method') plt.xlabel('f1') plt.ylabel('f2') colmax, row = bic.max(0), bic.argmax(0) maxval, col = colmax.max(0), colmax.argmax(0) print('Max: bic(' + str(waxis[col]) + ',' + str(waxis[col]) + ') = ' + str(maxval)) plt.show() return (bic, waxis)
def bicoherence(y, nfft=None, wind=None, nsamp=None, overlap=None): """ Direct (FD) method for estimating bicoherence Parameters: y - data vector or time-series nfft - fft length [default = power of two > segsamp] actual size used is power of two greater than 'nsamp' wind - specifies the time-domain window to be applied to each data segment; should be of length 'segsamp' (see below); otherwise, the default Hanning window is used. segsamp - samples per segment [default: such that we have 8 segments] - if x is a matrix, segsamp is set to the number of rows overlap - percentage overlap, allowed range [0,99]. [default = 50]; - if x is a matrix, overlap is set to 0. Output: bic - estimated bicoherence: an nfft x nfft array, with origin at the center, and axes pointing down and to the right. waxis - vector of frequencies associated with the rows and columns of bic; sampling frequency is assumed to be 1. """ # Parameter checks (ly, nrecs) = y.shape if ly == 1: y = y.reshape(1, -1) ly = nrecs nrecs = 1 if not nfft: nfft = 128 if not overlap: overlap = 50 if nrecs > 1: overlap = 0 if not nsamp: nsamp = 0 if nrecs > 1: nsamp = ly if nrecs > 1 and nsamp <= 0: nsamp = np.fix(ly / (8 - 7 * overlap/100)) if nfft < nsamp: nfft = 2**nextpow2(nsamp) overlap = np.fix(nsamp * overlap/100) nadvance = nsamp - overlap nrecs = np.fix ((ly*nrecs - overlap) / nadvance) if not wind: wind = np.hanning(nsamp) try: (rw, cw) = wind.shape except ValueError: (rw,) = wind.shape cw = 1 if min(rw, cw) == 1 or max(rw, cw) == nsamp: print ("Segment size is " + str(nsamp)) print ("Wind array is " + str(rw) + " by " + str(cw)) print ("Using default Hanning window") wind = np.hanning(nsamp) wind = wind.reshape(1,-1) # Accumulate triple products bic = np.zeros([nfft, nfft]) Pyy = np.zeros([nfft,1]) mask = hankel(np.arange(nfft),np.array([nfft-1]+range(nfft-1))) Yf12 = np.zeros([nfft,nfft]) ind = np.arange(nsamp) y = y.ravel(order='F') for k in xrange(nrecs): ys = y[ind] ys = (ys.reshape(1,-1) - np.mean(ys)) * wind Yf = np.fft.fft(ys, nfft)/nsamp CYf = np.conjugate(Yf) Pyy = Pyy + flat_eq(Pyy, (Yf*CYf)) Yf12 = flat_eq(Yf12, CYf.ravel(order='F')[mask]) bic = bic + ((Yf * np.transpose(Yf)) * Yf12) ind = ind + int(nadvance) bic = bic / nrecs Pyy = Pyy / nrecs mask = flat_eq(mask, Pyy.ravel(order='F')[mask]) bic = abs(bic)**2 / ((Pyy * np.transpose(Pyy)) * mask) bic = np.fft.fftshift(bic) if nfft%2 == 0: waxis = np.transpose(np.arange(-1*nfft/2, nfft/2)) / nfft else: waxis = np.transpose(np.arange(-1*(nfft-1)/2, (nfft-1)/2+1)) / nfft cont = plt.contourf(waxis,waxis,bic,100, cmap=plt.cm.Spectral_r) plt.colorbar(cont) plt.title('Bicoherence estimated via the direct (FFT) method') plt.xlabel('f1') plt.ylabel('f2') colmax, row = bic.max(0), bic.argmax(0) maxval, col = colmax.max(0), colmax.argmax(0) print ( 'Max: bic('+str(waxis[col])+','+str(waxis[col])+') = '+str(maxval)) plt.show() return (bic, waxis)
def bispectrumi(y, nlag=None, nsamp=None, overlap=None, flag='biased', nfft=None, wind=None): """ Parameters: y - data vector or time-series nlag - number of lags to compute [must be specified] segsamp - samples per segment [default: row dimension of y] overlap - percentage overlap [default = 0] flag - 'biased' or 'unbiased' [default is 'unbiased'] nfft - FFT length to use [default = 128] wind - window function to apply: if wind=0, the Parzen window is applied (default) otherwise the hexagonal window with unity values is applied. Output: Bspec - estimated bispectrum it is an nfft x nfft array with origin at the center, and axes pointing down and to the right waxis - frequency-domain axis associated with the bispectrum. - the i-th row (or column) of Bspec corresponds to f1 (or f2) value of waxis(i). """ (ly, nrecs) = y.shape if ly == 1: y = y.reshape(1,-1) ly = nrecs nrecs = 1 if not overlap: overlap = 0 overlap = min(99, max(overlap,0)) if nrecs > 1: overlap = 0 if not nsamp: nsamp = ly if nsamp > ly or nsamp <= 0: nsamp = ly if not 'flag': flag = 'biased' if not nfft: nfft = 128 if not wind: wind = 0 nlag = min(nlag, nsamp-1) if nfft < 2*nlag+1: nfft = 2^nextpow2(nsamp) # create the lag window Bspec = np.zeros([nfft, nfft]) if wind == 0: indx = np.array([range(1,nlag+1)]).T window = make_arr((1, np.sin(np.pi*indx/nlag) / (np.pi*indx/nlag)), axis=0) else: window = np.ones([nlag+1,1]) window = make_arr((window, np.zeros([nlag,1])), axis=0) # cumulants in non-redundant region overlap = np.fix(nsamp * overlap / 100) nadvance = nsamp - overlap nrecord = np.fix((ly*nrecs - overlap) / nadvance) c3 = np.zeros([nlag+1,nlag+1]) ind = np.arange(nsamp) y = y.ravel(order='F') s = 0 for k in xrange(nrecord): x = y[ind].ravel(order='F') x = x - np.mean(x) ind = ind + int(nadvance) for j in xrange(nlag+1): z = x[range(nsamp-j)] * x[range(j, nsamp)] for i in xrange(j,nlag+1): Sum = np.dot(z[range(nsamp-i)].T, x[range(i,nsamp)]) if flag == 'biased': Sum = Sum/nsamp else: Sum = Sum / (nsamp-i) c3[i,j] = c3[i,j] + Sum c3 = c3 / nrecord # cumulants elsewhere by symmetry c3 = c3 + np.tril(c3,-1).T # complete I quadrant c31 = c3[1:nlag+1, 1:nlag+1] c32 = np.zeros([nlag, nlag]) c33 = np.zeros([nlag, nlag]) c34 = np.zeros([nlag, nlag]) for i in xrange(nlag): x = c31[i:nlag, i] c32[nlag-1-i,0:nlag-i] = x.T c34[0:nlag-i, nlag-1-i] = x if i+1 < nlag: x = np.flipud(x[1:len(x)]) c33 = c33 + np.diag(x,i+1) + np.diag(x,-(i+1)) c33 = c33 + np.diag(c3[0, nlag:0:-1]) cmat = make_arr( (make_arr((c33, c32, np.zeros([nlag,1])), axis=1), make_arr((make_arr((c34, np.zeros([1,nlag])), axis=0), c3), axis=1)), axis=0 ) # apply lag-domain window wcmat = cmat if wind != -1: indx = np.arange(-1*nlag, nlag+1).T window = window.reshape(-1,1) for k in xrange(-nlag, nlag+1): wcmat[:, k+nlag] = (cmat[:, k+nlag].reshape(-1,1) * \ window[abs(indx-k)] * \ window[abs(indx)] * \ window[abs(k)]).reshape(-1,) # compute 2d-fft, and shift and rotate for proper orientation Bspec = np.fft.fft2(wcmat, (nfft, nfft)) Bspec = np.fft.fftshift(Bspec) # axes d and r; orig at ctr if nfft%2 == 0: waxis = np.transpose(np.arange(-1*nfft/2, nfft/2)) / nfft else: waxis = np.transpose(np.arange(-1*(nfft-1)/2, (nfft-1)/2+1)) / nfft cont = plt.contourf(waxis, waxis, abs(Bspec), 100, cmap=plt.cm.Spectral_r) plt.colorbar(cont) plt.title('Bispectrum estimated via the indirect method') plt.xlabel('f1') plt.ylabel('f2') plt.show() return (Bspec, waxis)