def cohere_pairs( X, ij, NFFT=<NUM_LIT>, Fs=<NUM_LIT:2>, detrend=detrend_none,<EOL>window=window_hanning, noverlap=<NUM_LIT:0>,<EOL>preferSpeedOverMemory=True,<EOL>progressCallback=donothing_callback,<EOL>returnPxx=False):
numRows, numCols = X.shape<EOL>if numRows < NFFT:<EOL><INDENT>tmp = X<EOL>X = np.zeros( (NFFT, numCols), X.dtype)<EOL>X[:numRows,:] = tmp<EOL>del tmp<EOL><DEDENT>numRows, numCols = X.shape<EOL>seen = {}<EOL>for i,j in ij:<EOL><INDENT>seen[i]=<NUM_LIT:1>; seen[j] = <NUM_LIT:1><EOL><DEDENT>allColumns = list(seen.keys())<EOL>Ncols = len(allColumns)<EOL>del seen<EOL>if np.iscomplexobj(X): numFreqs = NFFT<EOL>else: numFreqs = NFFT//<NUM_LIT:2>+<NUM_LIT:1><EOL>if cbook.iterable(window):<EOL><INDENT>assert(len(window) == NFFT)<EOL>windowVals = window<EOL><DEDENT>else:<EOL><INDENT>windowVals = window(np.ones((NFFT,), typecode(X)))<EOL><DEDENT>ind = list(range(<NUM_LIT:0>, numRows-NFFT+<NUM_LIT:1>, NFFT-noverlap))<EOL>numSlices = len(ind)<EOL>FFTSlices = {}<EOL>FFTConjSlices = {}<EOL>Pxx = {}<EOL>slices = list(range(numSlices))<EOL>normVal = norm(windowVals)**<NUM_LIT:2><EOL>for iCol in allColumns:<EOL><INDENT>progressCallback(i/Ncols, '<STR_LIT>')<EOL>Slices = np.zeros( (numSlices,numFreqs), dtype=np.complex_)<EOL>for iSlice in slices:<EOL><INDENT>thisSlice = X[ind[iSlice]:ind[iSlice]+NFFT, iCol]<EOL>thisSlice = windowVals*detrend(thisSlice)<EOL>Slices[iSlice,:] = fft(thisSlice)[:numFreqs]<EOL><DEDENT>FFTSlices[iCol] = Slices<EOL>if preferSpeedOverMemory:<EOL><INDENT>FFTConjSlices[iCol] = conjugate(Slices)<EOL><DEDENT>Pxx[iCol] = np.divide(np.mean(absolute(Slices)**<NUM_LIT:2>), normVal)<EOL><DEDENT>del Slices, ind, windowVals<EOL>Cxy = {}<EOL>Phase = {}<EOL>count = <NUM_LIT:0><EOL>N = len(ij)<EOL>for i,j in ij:<EOL><INDENT>count +=<NUM_LIT:1><EOL>if count%<NUM_LIT:10>==<NUM_LIT:0>:<EOL><INDENT>progressCallback(count/N, '<STR_LIT>')<EOL><DEDENT>if preferSpeedOverMemory:<EOL><INDENT>Pxy = FFTSlices[i] * FFTConjSlices[j]<EOL><DEDENT>else:<EOL><INDENT>Pxy = FFTSlices[i] * np.conjugate(FFTSlices[j])<EOL><DEDENT>if numSlices><NUM_LIT:1>: Pxy = np.mean(Pxy)<EOL>Pxy = np.divide(Pxy, normVal)<EOL>Cxy[(i,j)] = np.divide(np.absolute(Pxy)**<NUM_LIT:2>, Pxx[i]*Pxx[j])<EOL>Phase[(i,j)] =  np.arctan2(Pxy.imag, Pxy.real)<EOL><DEDENT>freqs = Fs/NFFT*np.arange(numFreqs)<EOL>if returnPxx:<EOL><INDENT>return Cxy, Phase, freqs, Pxx<EOL><DEDENT>else:<EOL><INDENT>return Cxy, Phase, freqs<EOL><DEDENT>
Cxy, Phase, freqs = cohere_pairs(X, ij, ...)

Compute the coherence for all pairs in *ij*.  *X* is a
(*numSamples*, *numCols*) numpy array.  *ij* is a list of tuples
(*i*, *j*).  Each tuple is a pair of indexes into the columns of *X*
for which you want to compute coherence.  For example, if *X* has 64
columns, and you want to compute all nonredundant pairs, define *ij*
as::

  ij = []
  for i in range(64):
      for j in range(i+1,64):
          ij.append( (i, j) )

The other function arguments, except for *preferSpeedOverMemory*
(see below), are explained in the help string of :func:`psd`.

Return value is a tuple (*Cxy*, *Phase*, *freqs*).

  - *Cxy*: a dictionary of (*i*, *j*) tuples -> coherence vector for that
    pair.  I.e., ``Cxy[(i,j)] = cohere(X[:,i], X[:,j])``.  Number of
    dictionary keys is ``len(ij)``.

  - *Phase*: a dictionary of phases of the cross spectral density at
    each frequency for each pair.  The keys are ``(i,j)``.

  - *freqs*: a vector of frequencies, equal in length to either
    the coherence or phase vectors for any (*i*, *j*) key..  Eg,
    to make a coherence Bode plot::

      subplot(211)
      plot( freqs, Cxy[(12,19)])
      subplot(212)
      plot( freqs, Phase[(12,19)])

For a large number of pairs, :func:`cohere_pairs` can be much more
efficient than just calling :func:`cohere` for each pair, because
it caches most of the intensive computations.  If *N* is the
number of pairs, this function is O(N) for most of the heavy
lifting, whereas calling cohere for each pair is
O(N\N{SUPERSCRIPT TWO}).  However, because of the caching, it is
also more memory intensive, making 2 additional complex arrays
with approximately the same number of elements as *X*.

The parameter *preferSpeedOverMemory*, if *False*, limits the
caching by only making one, rather than two, complex cache arrays.
This is useful if memory becomes critical.  Even when
*preferSpeedOverMemory* is *False*, :func:`cohere_pairs` will
still give significant performace gains over calling
:func:`cohere` for each pair, and will use subtantially less
memory than if *preferSpeedOverMemory* is *True*.  In my tests
with a (43000, 64) array over all non-redundant pairs,
*preferSpeedOverMemory* = *True* delivered a 33% performace boost
on a 1.7GHZ Athlon with 512MB RAM compared with
*preferSpeedOverMemory* = *False*.  But both solutions were more
than 10x faster than naievly crunching all possible pairs through
cohere.

.. seealso::
    :file:`test/cohere_pairs_test.py` in the src tree:
        For an example script that shows that this
        :func:`cohere_pairs` and :func:`cohere` give the same
        results for a given pair.