def _arburg2(X, order):
    """This version is 10 times faster than arburg, but the output rho is not correct.

    returns [1 a0,a1, an-1]

    """
    x = np.array(X)
    N = len(x)

    if order <= 0.:
        raise ValueError("order must be > 0")

    # Initialisation
    # ------ rho, den
    rho = sum(abs(x)**2.) / N  # Eq 8.21 [Marple]_
    den = rho * 2. * N

    # ------ backward and forward errors
    ef = np.zeros(N, dtype=complex)
    eb = np.zeros(N, dtype=complex)
    for j in range(0, N):  #eq 8.11
        ef[j] = x[j]
        eb[j] = x[j]

    # AR order to be stored
    a = np.zeros(1, dtype=complex)
    a[0] = 1
    # ---- rflection coeff to be stored
    ref = np.zeros(order, dtype=complex)

    temp = 1.
    E = np.zeros(order+1)
    E[0] = rho

    for m in range(0, order):
        #print m
        # Calculate the next order reflection (parcor) coefficient
        efp = ef[1:]
        ebp = eb[0:-1]
        #print efp, ebp
        num = -2.* np.dot(ebp.conj().transpose(),  efp)
        den = np.dot(efp.conj().transpose(),  efp)
        den += np.dot(ebp,  ebp.conj().transpose())
        ref[m] = num / den

        # Update the forward and backward prediction errors
        ef = efp + ref[m] * ebp
        eb = ebp + ref[m].conj().transpose() * efp

        # Update the AR coeff.
        a.resize(len(a)+1)
        a = a + ref[m] * np.flipud(a).conjugate()

        # Update the prediction error
        E[m+1] = (1 - ref[m].conj().transpose()*ref[m]) * E[m]
        #print 'REF', ref, num, den
    return a, E[-1], ref

def
_arburg2
(
X
,
order
)
:
x
=
np
.
array
(
X
)
N
=
len
(
x
)
if
order
<=
0.
:
raise
ValueError
(
"order must be > 0"
)
rho
=
sum
(
abs
(
x
)
**
2.
)
/
N
den
=
rho
*
2.
*
N
ef
=
np
.
zeros
(
N
,
dtype
=
complex
)
eb
=
np
.
zeros
(
N
,
dtype
=
complex
)
for
j
in
range
(
0
,
N
)
:
ef
[
j
]
=
x
[
j
]
eb
[
j
]
=
x
[
j
]
a
=
np
.
zeros
(
1
,
dtype
=
complex
)
a
[
0
]
=
1
ref
=
np
.
zeros
(
order
,
dtype
=
complex
)
temp
=
1.
E
=
np
.
zeros
(
order
+
1
)
E
[
0
]
=
rho
for
m
in
range
(
0
,
order
)
:
efp
=
ef
[
1
:
]
ebp
=
eb
[
0
:
1
]
num
=
2.
*
np
.
dot
(
ebp
.
conj
(
)
.
transpose
(
)
,
efp
)
den
=
np
.
dot
(
efp
.
conj
(
)
.
transpose
(
)
,
efp
)
den
+=
np
.
dot
(
ebp
,
ebp
.
conj
(
)
.
transpose
(
)
)
ref
[
m
]
=
num
/
den
ef
=
efp
+
ref
[
m
]
*
ebp
eb
=
ebp
+
ref
[
m
]
.
conj
(
)
.
transpose
(
)
*
efp
a
.
resize
(
len
(
a
)
+
1
)
a
=
a
+
ref
[
m
]
*
np
.
flipud
(
a
)
.
conjugate
(
)
E
[
m
+
1
]
=
(
1
ref
[
m
]
.
conj
(
)
.
transpose
(
)
*
ref
[
m
]
)
*
E
[
m
]
return
a
,
E
[
1
]
,
ref

This version is 10 times faster than arburg but the output rho is not correct .


def _numpy_cholesky(A, B):
    """Solve Ax=B using numpy cholesky solver

    A = LU

    in the case where A is square and Hermitian, A = L.L* where L* is
    transpoed and conjugate matrix

    Ly = b

    where

    Ux=y

    so x = U^{-1} y
    where U = L*
    and y = L^{-1} B
    """
    L = numpy.linalg.cholesky(A)
    # A=L*numpy.transpose(L).conjugate()
    # Ly = b
    y = numpy.linalg.solve(L,B)
    # Ux = y
    x = numpy.linalg.solve(L.transpose().conjugate(),y)
    return x, L

def _numpy_cholesky ( A , B ) : L = numpy . linalg . cholesky ( A ) y = numpy . linalg . solve ( L , B ) x = numpy . linalg . solve ( L . transpose ( ) . conjugate ( ) , y ) return x , L

Solve Ax = B using numpy cholesky solver


def _numpy_solver(A, B):
    """This function solve Ax=B directly without taking care of the input
    matrix properties.
    """
    x = numpy.linalg.solve(A, B)
    return x

def _numpy_solver ( A , B ) : x = numpy . linalg . solve ( A , B ) return x

This function solve Ax = B directly without taking care of the input matrix properties .


def CHOLESKY(A, B, method='scipy'):
    """Solve linear system `AX=B` using CHOLESKY method.

    :param A: an input Hermitian matrix
    :param B: an array
    :param str method: a choice of method in [numpy, scipy, numpy_solver]

        * `numpy_solver` relies entirely on numpy.solver (no cholesky decomposition)
        * `numpy` relies on the numpy.linalg.cholesky for the decomposition and
          numpy.linalg.solve for the inversion.
        * `scipy` uses scipy.linalg.cholesky for the decomposition and
          scipy.linalg.cho_solve for the inversion.

    .. rubric:: Description

    When a matrix is square and Hermitian (symmetric with lower part being
    the complex conjugate of the upper one), then the usual triangular
    factorization takes on the special form:

    .. math:: A = R R^H

    where :math:`R` is a lower triangular matrix with nonzero real principal
    diagonal element. The input matrix can be made of complex data. Then, the
    inversion to find :math:`x` is made as follows:

    .. math::  Ry = B

    and

    .. math::   Rx = y

    .. doctest::

        >>> import numpy
        >>> from spectrum import CHOLESKY
        >>> A = numpy.array([[ 2.0+0.j ,  0.5-0.5j, -0.2+0.1j],
        ...    [ 0.5+0.5j,  1.0+0.j ,  0.3-0.2j],
        ...    [-0.2-0.1j,  0.3+0.2j,  0.5+0.j ]])
        >>> B = numpy.array([ 1.0+3.j ,  2.0-1.j ,  0.5+0.8j])
        >>> CHOLESKY(A, B)
        array([ 0.95945946+5.25675676j,  4.41891892-7.04054054j,
               -5.13513514+6.35135135j])

    """
    if method == 'numpy_solver':
        X = _numpy_solver(A,B)
        return X
    elif method == 'numpy':
        X, _L = _numpy_cholesky(A, B)
        return X
    elif method == 'scipy':
        import scipy.linalg
        L = scipy.linalg.cholesky(A)
        X = scipy.linalg.cho_solve((L, False), B)
    else:
        raise ValueError('method must be numpy_solver, numpy_cholesky or cholesky_inplace')
    return X

Solve linear system AX = B using CHOLESKY method .


def speriodogram(x, NFFT=None, detrend=True, sampling=1.,
                   scale_by_freq=True, window='hamming', axis=0):
    """Simple periodogram, but matrices accepted.

    :param x: an array or matrix of data samples.
    :param NFFT: length of the data before FFT is computed (zero padding)
    :param bool detrend: detrend the data before co,puteing the FFT
    :param float sampling: sampling frequency of the input :attr:`data`.

    :param scale_by_freq:
    :param str window:

    :return: 2-sided PSD if complex data, 1-sided if real.

    if a matrix is provided (using numpy.matrix), then a periodogram
    is computed for each row. The returned matrix has the same shape as the input
    matrix.

    The mean of the input data is also removed from the data before computing
    the psd.

    .. plot::
        :width: 80%
        :include-source:

        from pylab import grid, semilogy
        from spectrum import data_cosine, speriodogram
        data = data_cosine(N=1024, A=0.1, sampling=1024, freq=200)
        semilogy(speriodogram(data, detrend=False, sampling=1024), marker='o')
        grid(True)

    .. plot::
        :width: 80%
        :include-source:

        import numpy
        from spectrum import speriodogram, data_cosine
        from pylab import figure, semilogy, figure ,imshow
        # create N data sets and make the frequency dependent on the time
        N = 100
        m = numpy.concatenate([data_cosine(N=1024, A=0.1, sampling=1024, freq=x)
            for x in range(1, N)]);
        m.resize(N, 1024)
        res = speriodogram(m)
        figure(1)
        semilogy(res)
        figure(2)
        imshow(res.transpose(), aspect='auto')

    .. todo:: a proper spectrogram class/function that takes care of normalisation
    """
    x = np.array(x)
    # array with 1 dimension case
    if x.ndim == 1:
        axis = 0
        r = x.shape[0]
        w = Window(r, window)   #same size as input data
        w = w.data
    # matrix case
    elif x.ndim == 2:
        logging.debug('2D array. each row is a 1D array')
        [r, c] = x.shape
        w = np.array([Window(r, window).data for this in range(c)]).reshape(r,c)

    if NFFT is None:
        NFFT = len(x)

    isreal = np.isrealobj(x)

    if detrend == True:
        m = np.mean(x, axis=axis)
    else:
        m = 0

    if isreal == True:
        if x.ndim == 2:
            res =  (abs (rfft (x*w - m, NFFT, axis=0))) ** 2. / r
        else:
            res =  (abs (rfft (x*w - m, NFFT, axis=-1))) ** 2. / r
    else:
        if x.ndim == 2:
            res =  (abs (fft (x*w - m, NFFT, axis=0))) ** 2. / r
        else:
            res =  (abs (fft (x*w - m, NFFT, axis=-1))) ** 2. / r

    if scale_by_freq is True:
        df = sampling / float(NFFT)
        res*= 2 * np.pi / df

    if x.ndim == 1:
        return res.transpose()
    else:
        return res

def
speriodogram
(
x
,
NFFT
=
,
detrend
=
True
,
sampling
=
1.
,
scale_by_freq
=
True
,
window
=
'hamming'
,
axis
=
0
)
:
x
=
np
.
array
(
x
)
if
x
.
ndim
==
1
:
axis
=
0
r
=
x
.
shape
[
0
]
w
=
Window
(
r
,
window
)
w
=
w
.
data
elif
x
.
ndim
==
2
:
logging
.
debug
(
'2D array. each row is a 1D array'
)
[
r
,
c
]
=
x
.
shape
w
=
np
.
array
(
[
Window
(
r
,
window
)
.
data
for
this
in
range
(
c
)
]
)
.
reshape
(
r
,
c
)
if
NFFT
is
:
NFFT
=
len
(
x
)
isreal
=
np
.
isrealobj
(
x
)
if
detrend
==
True
:
m
=
np
.
mean
(
x
,
axis
=
axis
)
else
:
m
=
0
if
isreal
==
True
:
if
x
.
ndim
==
2
:
res
=
(
abs
(
rfft
(
x
*
w
m
,
NFFT
,
axis
=
0
)
)
)
**
2.
/
r
else
:
res
=
(
abs
(
rfft
(
x
*
w
m
,
NFFT
,
axis
=
1
)
)
)
**
2.
/
r
else
:
if
x
.
ndim
==
2
:
res
=
(
abs
(
fft
(
x
*
w
m
,
NFFT
,
axis
=
0
)
)
)
**
2.
/
r
else
:
res
=
(
abs
(
fft
(
x
*
w
m
,
NFFT
,
axis
=
1
)
)
)
**
2.
/
r
if
scale_by_freq
is
True
:
df
=
sampling
/
float
(
NFFT
)
res
*=
2
*
np
.
pi
/
df
if
x
.
ndim
==
1
:
return
res
.
transpose
(
)
else
:
return
res

Simple periodogram but matrices accepted .


def WelchPeriodogram(data, NFFT=None,  sampling=1., **kargs):
    r"""Simple periodogram wrapper of numpy.psd function.

    :param A: the input data
    :param int NFFT: total length of the final data sets (padded
        with zero if needed; default is 4096)
    :param str window:

    :Technical documentation:

    When we calculate the periodogram of a set of data we get an estimation
    of the spectral density. In fact as we use a Fourier transform and a
    truncated segments the spectrum is the convolution of the data with a
    rectangular window which Fourier transform is

    .. math::

        W(s)= \frac{1}{N^2} \left[ \frac{\sin(\pi s)}{\sin(\pi s/N)} \right]^2

    Thus oscillations and sidelobes appears around the main frequency. One aim of t he tapering is to reduced this effects. We multiply data by a window whose  sidelobes are much smaller than the main lobe. Classical window is hanning window.  But other windows are available. However we must take into account this energy and divide the spectrum by energy of taper used. Thus periodogram becomes :

    .. math::

        D_k \equiv \sum_{j=0}^{N-1}c_jw_j \; e^{2\pi ijk/N}  \qquad k=0,...,N-1

    .. math::

        P(0)=P(f_0)=\frac{1}{2\pi W_{ss}}\arrowvert{D_0}\arrowvert^2

    .. math::

        P(f_k)=\frac{1}{2\pi W_{ss}} \left[\arrowvert{D_k}\arrowvert^2+\arrowvert{D_{N-k}}\arrowvert^2\right]        \qquad k=0,1,...,     \left( \frac{1}{2}-1 \right)

    .. math::

        P(f_c)=P(f_{N/2})= \frac{1}{2\pi W_{ss}} \arrowvert{D_{N/2}}\arrowvert^2

    with

    .. math::

        {W_{ss}} \equiv N\sum_{j=0}^{N-1}w_j^2

    .. plot::
        :width: 80%
        :include-source:

        from spectrum import WelchPeriodogram, marple_data
        psd = WelchPeriodogram(marple_data, 256)

    """
    from pylab import psd
    spectrum = Spectrum(data, sampling=1.)

    P = psd(data, NFFT, Fs=sampling, **kargs)
    spectrum.psd = P[0]
    #spectrum.__Spectrum_sides = 'twosided'

    return P, spectrum

def WelchPeriodogram ( data , NFFT = None , sampling = 1. , ** kargs ) : r from pylab import psd spectrum = Spectrum ( data , sampling = 1. ) P = psd ( data , NFFT , Fs = sampling , ** kargs ) spectrum . psd = P [ 0 ] return P , spectrum

r Simple periodogram wrapper of numpy . psd function .


def DaniellPeriodogram(data, P, NFFT=None, detrend='mean', sampling=1.,
                       scale_by_freq=True, window='hamming'):
    r"""Return Daniell's periodogram.

    To reduce fast fluctuations of the spectrum one idea proposed by daniell
    is to average each value with points in its neighboorhood. It's like
    a low filter.

    .. math:: \hat{P}_D[f_i]= \frac{1}{2P+1} \sum_{n=i-P}^{i+P} \tilde{P}_{xx}[f_n]

    where P is the number of points to average.

    Daniell's periodogram is the convolution of the spectrum with a low filter:

    .. math:: \hat{P}_D(f)=   \hat{P}_{xx}(f)*H(f)

    Example::

        >>> DaniellPeriodogram(data, 8)

    if N/P is not integer, the final values of the original PSD are not used.

    using DaniellPeriodogram(data, 0) should give the original PSD.

    """
    psd = speriodogram(data, NFFT=NFFT, detrend=detrend, sampling=sampling,
                   scale_by_freq=scale_by_freq, window=window)

    if len(psd) % 2 == 1:
        datatype = 'real'
    else:
        datatype = 'complex'

    N = len(psd)
    _slice = 2 * P + 1
    if datatype == 'real': #must get odd value
        newN = np.ceil(psd.size/float(_slice))
        if newN % 2 == 0:
            newN = psd.size/_slice
    else:
        newN = np.ceil(psd.size/float(_slice))
        if newN % 2 == 1:
            newN = psd.size/_slice

    newpsd = np.zeros(int(newN)) # keep integer division
    for i in range(0, newpsd.size):
        count = 0 #needed to know the number of valid averaged values
        for n in range(i*_slice-P, i*_slice+P+1): #+1 to have P values on each sides
            if n > 0 and n<N: #needed to start the average
                count += 1
                newpsd[i] += psd[n]
        newpsd[i] /= float(count)

    #todo: check this
    if datatype == 'complex':
        freq = np.linspace(0, sampling, len(newpsd))
    else:
        df = 1. / sampling
        freq = np.linspace(0,sampling/2., len(newpsd))
    #psd.refreq(2*psd.size()/A.freq());
    #psd.retime(-1./psd.freq()+1./A.size());

    return newpsd, freq

def DaniellPeriodogram ( data , P , NFFT = None , detrend = 'mean' , sampling = 1. , scale_by_freq = True , window = 'hamming' ) : r psd = speriodogram ( data , NFFT = NFFT , detrend = detrend , sampling = sampling , scale_by_freq = scale_by_freq , window = window ) if len ( psd ) % 2 == 1 : datatype = 'real' else : datatype = 'complex' N = len ( psd ) _slice = 2 * P + 1 if datatype == 'real' : newN = np . ceil ( psd . size / float ( _slice ) ) if newN % 2 == 0 : newN = psd . size / _slice else : newN = np . ceil ( psd . size / float ( _slice ) ) if newN % 2 == 1 : newN = psd . size / _slice newpsd = np . zeros ( int ( newN ) ) for i in range ( 0 , newpsd . size ) : count = 0 for n in range ( i * _slice - P , i * _slice + P + 1 ) : if n > 0 and n < N : count += 1 newpsd [ i ] += psd [ n ] newpsd [ i ] /= float ( count ) if datatype == 'complex' : freq = np . linspace ( 0 , sampling , len ( newpsd ) ) else : df = 1. / sampling freq = np . linspace ( 0 , sampling / 2. , len ( newpsd ) ) return newpsd , freq


def centerdc_gen(self):
        """Return the centered frequency range as a generator.

        ::

            >>> print(list(Range(8).centerdc_gen()))
            [-0.5, -0.375, -0.25, -0.125, 0.0, 0.125, 0.25, 0.375]

        """
        for a in range(0, self.N):
            yield (a-self.N/2) * self.df

def centerdc_gen ( self ) : for a in range ( 0 , self . N ) : yield ( a - self . N / 2 ) * self . df

Return the centered frequency range as a generator .


def onesided_gen(self):
        """Return the one-sided frequency range as a generator.

        If :attr:`N` is even, the length is N/2 + 1.
        If :attr:`N` is odd, the length is (N+1)/2.

        ::

            >>> print(list(Range(8).onesided()))
            [0.0, 0.125, 0.25, 0.375, 0.5]
            >>> print(list(Range(9).onesided()))
            [0.0, 0.1111, 0.2222, 0.3333, 0.4444]

        """
        if self.N % 2 == 0:
            for n in range(0, self.N//2 + 1):
                yield n * self.df
        else:
            for n in range(0, (self.N+1)//2):
                yield n * self.df

def onesided_gen ( self ) : if self . N % 2 == 0 : for n in range ( 0 , self . N // 2 + 1 ) : yield n * self . df else : for n in range ( 0 , ( self . N + 1 ) // 2 ) : yield n * self . df

Return the one - sided frequency range as a generator .


def plot(self, filename=None, norm=False, ylim=None,
              sides=None,  **kargs):
        """a simple plotting routine to plot the PSD versus frequency.

        :param str filename: save the figure into a file
        :param norm: False by default. If True, the PSD is normalised.
        :param ylim: readjust the y range .
        :param sides: if not provided, :attr:`sides` is used. See :attr:`sides`
            for details.
        :param kargs: any optional argument accepted by :func:`pylab.plot`.

        .. plot::
            :width: 80%
            :include-source:

            from spectrum import *
            p = Periodogram(marple_data)
            p.plot(norm=True, marker='o')

        """
        import pylab
        from pylab import ylim as plt_ylim
        #First, check that psd attribute is up-to-date
        # just to get the PSD to be recomputed if needed
        _ = self.psd

        # check that the input sides parameter is correct if provided
        if sides is not None:
            if sides not in self._sides_choices:
                raise errors.SpectrumChoiceError(sides, self._sides_choices)

        # if sides is provided but identical to the current psd, nothing to do.
        # if sides not provided, let us use self.sides
        if sides is None or sides == self.sides:
            frequencies = self.frequencies()
            psd = self.psd
            sides = self.sides
        elif sides is not None:
            # if sides argument is different from the attribute, we need to
            # create a new PSD/Freq ; indeed we do not want to change the
            # attribute itself

            # if data is complex, one-sided is wrong in any case.
            if self.datatype == 'complex':
                if sides == 'onesided':
                    raise ValueError("sides cannot be one-sided with complex data")

            logging.debug("sides is different from the one provided. Converting PSD")
            frequencies = self.frequencies(sides=sides)
            psd = self.get_converted_psd(sides)

        if len(psd) != len(frequencies):
            raise ValueError("PSD length is %s and freq length is %s" % (len(psd), len(frequencies)))

        if 'ax' in list(kargs.keys()):
            save_ax = pylab.gca()
            pylab.sca(kargs['ax'])
            rollback = True
            del kargs['ax']
        else:
            rollback = False

        if norm:
            pylab.plot(frequencies, 10 * stools.log10(psd/max(psd)),  **kargs)
        else:
            pylab.plot(frequencies, 10 * stools.log10(psd),**kargs)

        pylab.xlabel('Frequency')
        pylab.ylabel('Power (dB)')
        pylab.grid(True)

        if ylim:
            plt_ylim(ylim)

        if sides == 'onesided':
            pylab.xlim(0, self.sampling/2.)
        elif sides == 'twosided':
            pylab.xlim(0, self.sampling)
        elif sides == 'centerdc':
            pylab.xlim(-self.sampling/2., self.sampling/2.)

        if filename:
            pylab.savefig(filename)

        if rollback:
            pylab.sca(save_ax)

        del psd, frequencies

a simple plotting routine to plot the PSD versus frequency .


def power(self):
        r"""Return the power contained in the PSD

        if scale_by_freq is False, the power is:

        .. math:: P = N \sum_{k=1}^{N} P_{xx}(k)

        else, it is

        .. math:: P =  \sum_{k=1}^{N} P_{xx}(k) \frac{df}{2\pi}

        .. todo:: check these equations

        """
        if self.scale_by_freq == False:
            return sum(self.psd) * len(self.psd)
        else:
            return sum(self.psd) * self.df/(2.*numpy.pi)

def power ( self ) : r if self . scale_by_freq == False : return sum ( self . psd ) * len ( self . psd ) else : return sum ( self . psd ) * self . df / ( 2. * numpy . pi )


def ipy_notebook_skeleton():
    """Returns a dictionary with the elements of a Jupyter notebook"""
    py_version = sys.version_info
    notebook_skeleton = {
        "cells": [],
        "metadata": {
            "kernelspec": {
                "display_name": "Python " + str(py_version[0]),
                "language": "python",
                "name": "python" + str(py_version[0])
            },
            "language_info": {
                "codemirror_mode": {
                    "name": "ipython",
                    "version": py_version[0]
                },
                "file_extension": ".py",
                "mimetype": "text/x-python",
                "name": "python",
                "nbconvert_exporter": "python",
                "pygments_lexer": "ipython" + str(py_version[0]),
                "version": '{0}.{1}.{2}'.format(*sys.version_info[:3])
            }
        },
        "nbformat": 4,
        "nbformat_minor": 0
    }
    return notebook_skeleton

def
ipy_notebook_skeleton
(
)
:
py_version
=
sys
.
version_info
notebook_skeleton
=
{
"cells"
:
[
]
,
"metadata"
:
{
"kernelspec"
:
{
"display_name"
:
"Python "
+
str
(
py_version
[
0
]
)
,
"language"
:
"python"
,
"name"
:
"python"
+
str
(
py_version
[
0
]
)
}
,
"language_info"
:
{
"codemirror_mode"
:
{
"name"
:
"ipython"
,
"version"
:
py_version
[
0
]
}
,
"file_extension"
:
".py"
,
"mimetype"
:
"text/x-python"
,
"name"
:
"python"
,
"nbconvert_exporter"
:
"python"
,
"pygments_lexer"
:
"ipython"
+
str
(
py_version
[
0
]
)
,
"version"
:
'{0}.{1}.{2}'
.
format
(
*
sys
.
version_info
[
:
3
]
)
}
}
,
"nbformat"
:
4
,
"nbformat_minor"
:
0
}
return
notebook_skeleton


def rst2md(text):
    """Converts the RST text from the examples docstrigs and comments
    into markdown text for the IPython notebooks"""

    top_heading = re.compile(r'^=+$\s^([\w\s-]+)^=+$', flags=re.M)
    text = re.sub(top_heading, r'# \1', text)

    math_eq = re.compile(r'^\.\. math::((?:.+)?(?:\n+^  .+)*)', flags=re.M)
    text = re.sub(math_eq,
                  lambda match: r'$${0}$$'.format(match.group(1).strip()),
                  text)
    inline_math = re.compile(r':math:`(.+)`')
    text = re.sub(inline_math, r'$\1$', text)

    return text

def
rst2md
(
text
)
:
top_heading
=
re
.
compile
(
r'^=+$\s^([\w\s-]+)^=+$'
,
flags
=
re
.
M
)
text
=
re
.
sub
(
top_heading
,
r'# \1'
,
text
)
math_eq
=
re
.
compile
(
r'^\.\. math::((?:.+)?(?:\n+^  .+)*)'
,
flags
=
re
.
M
)
text
=
re
.
sub
(
math_eq
,
lambda
match
:
r'$${0}$$'
.
format
(
match
.
group
(
1
)
.
strip
(
)
)
,
text
)
inline_math
=
re
.
compile
(
r':math:`(.+)`'
)
text
=
re
.
sub
(
inline_math
,
r'$\1$'
,
text
)
return
text


def add_markdown_cell(self, text):
        """Add a markdown cell to the notebook

        Parameters
        ----------
        code : str
            Cell content
        """
        markdown_cell = {
            "cell_type": "markdown",
            "metadata": {},
            "source": [rst2md(text)]
        }
        self.work_notebook["cells"].append(markdown_cell)

def add_markdown_cell ( self , text ) : markdown_cell = { "cell_type" : "markdown" , "metadata" : { } , "source" : [ rst2md ( text ) ] } self . work_notebook [ "cells" ] . append ( markdown_cell )


def save_file(self):
        """Saves the notebook to a file"""
        with open(self.write_file, 'w') as out_nb:
            json.dump(self.work_notebook, out_nb, indent=2)

def save_file ( self ) : with open ( self . write_file , 'w' ) as out_nb : json . dump ( self . work_notebook , out_nb , indent = 2 )


def arma2psd(A=None, B=None, rho=1., T=1., NFFT=4096, sides='default',
        norm=False):
    r"""Computes power spectral density given ARMA values.

    This function computes the power spectral density values
    given the ARMA parameters of an ARMA model. It assumes that
    the driving sequence is a white noise process of zero mean and
    variance :math:`\rho_w`. The sampling frequency and noise variance are
    used to scale the PSD output, which length is set by the user with the
    `NFFT` parameter.

    :param array A:   Array of AR parameters (complex or real)
    :param array B:   Array of MA parameters (complex or real)
    :param float rho: White noise variance to scale the returned PSD
    :param float T:   Sample interval in seconds to scale the returned PSD
    :param int NFFT:  Final size of the PSD
    :param str sides: Default PSD is two-sided, but sides can be set to centerdc.

    .. warning:: By convention, the AR or MA arrays does not contain the
        A0=1 value.

    If :attr:`B` is None, the model is a pure AR model. If :attr:`A` is None,
    the model is a pure MA model.

    :return: two-sided PSD

    .. rubric:: Details:

    AR case: the power spectral density is:

    .. math:: P_{ARMA}(f) = T \rho_w \left|\frac{B(f)}{A(f)}\right|^2

    where:

    .. math:: A(f) = 1 + \sum_{k=1}^q b(k) e^{-j2\pi fkT}
    .. math:: B(f) = 1 + \sum_{k=1}^p a(k) e^{-j2\pi fkT}

    .. rubric:: **Example:**

    .. plot::
        :width: 80%
        :include-source:

        import spectrum.arma
        from pylab import plot, log10, legend
        plot(10*log10(spectrum.arma.arma2psd([1,0.5],[0.5,0.5])), label='ARMA(2,2)')
        plot(10*log10(spectrum.arma.arma2psd([1,0.5],None)), label='AR(2)')
        plot(10*log10(spectrum.arma.arma2psd(None,[0.5,0.5])), label='MA(2)')
        legend()

    :References: [Marple]_
    """
    if NFFT is None:
        NFFT = 4096

    if A is None and B is None:
        raise ValueError("Either AR or MA model must be provided")

    psd = np.zeros(NFFT, dtype=complex)

    if A is not None:
        ip = len(A)
        den = np.zeros(NFFT, dtype=complex)
        den[0] = 1.+0j
        for k in range(0, ip):
            den[k+1] = A[k]
        denf = fft(den, NFFT)

    if B is not None:
        iq = len(B)
        num = np.zeros(NFFT, dtype=complex)
        num[0] = 1.+0j
        for k in range(0, iq):
            num[k+1] = B[k]
        numf = fft(num, NFFT)

    # Changed in version 0.6.9 (divided by T instead of multiply)
    if A is not None and B is not None:
        psd = rho / T * abs(numf)**2. / abs(denf)**2.
    elif A is not None:
        psd = rho / T / abs(denf)**2.
    elif B is not None:
        psd = rho / T * abs(numf)**2.

    psd = np.real(psd)
    # The PSD is a twosided PSD.
    # to obtain the centerdc
    if sides != 'default':
        from . import tools
        assert sides in ['centerdc']
        if sides == 'centerdc':
            psd = tools.twosided_2_centerdc(psd)

    if norm == True:
        psd /= max(psd)

    return psd

r Computes power spectral density given ARMA values .


def arma_estimate(X, P, Q, lag):
    """Autoregressive and moving average estimators.

    This function provides an estimate of the autoregressive
    parameters, the moving average parameters, and the driving
    white noise variance of  an ARMA(P,Q) for a complex or real data sequence.

    The parameters are estimated using three steps:

        * Estimate the AR parameters from the original data based on a least
          squares modified Yule-Walker technique,
        * Produce a residual time sequence by filtering the original data
          with a filter based on the AR parameters,
        * Estimate the MA parameters from the residual time sequence.

    :param array X: Array of data samples (length N)
    :param int P: Desired number of AR parameters
    :param int Q: Desired number of MA parameters
    :param int lag: Maximum lag to use for autocorrelation estimates

    :return:
        * A     - Array of complex P AR parameter estimates
        * B     - Array of complex Q MA parameter estimates
        * RHO   - White noise variance estimate

    .. note::
      *  lag must be >= Q (MA order)

    **dependencies**:
        * :meth:`spectrum.correlation.CORRELATION`
        * :meth:`spectrum.covar.arcovar`
        * :meth:`spectrum.arma.ma`

    .. plot::
        :width: 80%
        :include-source:

        from spectrum import arma_estimate, arma2psd, marple_data
        import pylab

        a,b, rho = arma_estimate(marple_data, 15, 15, 30)
        psd = arma2psd(A=a, B=b, rho=rho, sides='centerdc', norm=True)
        pylab.plot(10 * pylab.log10(psd))
        pylab.ylim([-50,0])

    :reference: [Marple]_
    """
    R = CORRELATION(X, maxlags=lag, norm='unbiased')
    R0 = R[0]
    #C   Estimate the AR parameters (no error weighting is used).
    #C   Number of equation errors is M-Q .
    MPQ = lag - Q + P

    N = len(X)
    Y = np.zeros(N-P, dtype=complex)

    for K in range(0, MPQ):
        KPQ = K + Q - P+1
        if KPQ < 0:
            Y[K] = R[-KPQ].conjugate()
        if KPQ == 0:
            Y[K] = R0
        if KPQ > 0:
            Y[K] = R[KPQ]

    # The resize is very important for the normalissation.
    Y.resize(lag)
    if P <= 4:
        res = arcovar_marple(Y.copy(), P)    #! Eq. (10.12)
        ar_params = res[0]
    else:
        res = arcovar(Y.copy(), P)    #! Eq. (10.12)
        ar_params = res[0]

    # the .copy is used to prevent a reference somewhere. this is a bug
    # to be tracked down.
    Y.resize(N-P)

    #C   Filter the original time series
    for k in range(P, N):
        SUM = X[k]
        #SUM += sum([ar_params[j]*X[k-j-1] for j in range(0,P)])
        for j in range(0, P):
            SUM = SUM + ar_params[j] * X[k-j-1]   #! Eq. (10.17)
        Y[k-P] = SUM

    #  Estimate the MA parameters (a "long" AR of order at least 2*IQ
    #C   is suggested)
    #Y.resize(N-P)
    ma_params, rho = ma(Y, Q, 2*Q)     #! Eq. (10.3)

    return ar_params, ma_params, rho

def arma_estimate ( X , P , Q , lag ) : R = CORRELATION ( X , maxlags = lag , norm = 'unbiased' ) R0 = R [ 0 ] MPQ = lag - Q + P N = len ( X ) Y = np . zeros ( N - P , dtype = complex ) for K in range ( 0 , MPQ ) : KPQ = K + Q - P + 1 if KPQ < 0 : Y [ K ] = R [ - KPQ ] . conjugate ( ) if KPQ == 0 : Y [ K ] = R0 if KPQ > 0 : Y [ K ] = R [ KPQ ] Y . resize ( lag ) if P <= 4 : res = arcovar_marple ( Y . copy ( ) , P ) ar_params = res [ 0 ] else : res = arcovar ( Y . copy ( ) , P ) ar_params = res [ 0 ] Y . resize ( N - P ) for k in range ( P , N ) : SUM = X [ k ] for j in range ( 0 , P ) : SUM = SUM + ar_params [ j ] * X [ k - j - 1 ] Y [ k - P ] = SUM ma_params , rho = ma ( Y , Q , 2 * Q ) return ar_params , ma_params , rho

Autoregressive and moving average estimators .


def ma(X, Q, M):
    """Moving average estimator.

    This program provides an estimate of the moving average parameters
    and driving noise variance for a data sequence based on a
    long AR model and a least squares fit.

    :param array X: The input data array
    :param int Q: Desired MA model order (must be >0 and <M)
    :param int M: Order of "long" AR model (suggest at least 2*Q )

    :return:
        * MA    - Array of Q complex MA parameter estimates
        * RHO   - Real scalar of white noise variance estimate

    .. plot::
        :width: 80%
        :include-source:

        from spectrum import arma2psd, ma, marple_data
        import pylab

        # Estimate 15 Ma parameters
        b, rho = ma(marple_data, 15, 30)
        # Create the PSD from those MA parameters
        psd = arma2psd(B=b, rho=rho, sides='centerdc')
        # and finally plot the PSD
        pylab.plot(pylab.linspace(-0.5, 0.5, 4096), 10 * pylab.log10(psd/max(psd)))
        pylab.axis([-0.5, 0.5, -30, 0])

    :reference: [Marple]_
    """
    if Q <= 0 or Q >= M:
        raise ValueError('Q(MA) must be in ]0,lag[')

    #C   Fit a high-order AR to the data
    a, rho, _c = yulewalker.aryule(X, M, 'biased')   #! Eq. (10.5)

    #add an element unity to the AR parameter array
    a = np.insert(a, 0, 1)

    #C   Find MA parameters from autocorrelations by Yule-Walker method
    ma_params, _p, _c = yulewalker.aryule(a, Q, 'biased')    #! Eq. (10.7)

    return ma_params, rho

def
ma
(
X
,
Q
,
M
)
:
if
Q
<=
0
or
Q
>=
M
:
raise
ValueError
(
'Q(MA) must be in ]0,lag['
)
a
,
rho
,
_c
=
yulewalker
.
aryule
(
X
,
M
,
'biased'
)
a
=
np
.
insert
(
a
,
0
,
1
)
ma_params
,
_p
,
_c
=
yulewalker
.
aryule
(
a
,
Q
,
'biased'
)
return
ma_params
,
rho


def CORRELOGRAMPSD(X, Y=None, lag=-1, window='hamming',
                    norm='unbiased', NFFT=4096, window_params={},
                    correlation_method='xcorr'):
    """PSD estimate using correlogram method.

    :param array X: complex or real data samples X(1) to X(N)
    :param array Y: complex data samples Y(1) to Y(N). If provided, computes
        the cross PSD, otherwise the PSD is returned
    :param int lag: highest lag index to compute. Must be less than N
    :param str window_name: see :mod:`window` for list of valid names
    :param str norm: one of the valid normalisation of :func:`xcorr` (biased,
        unbiased, coeff, None)
    :param int NFFT: total length of the final data sets (padded with zero
        if needed; default is 4096)
    :param str correlation_method: either `xcorr` or `CORRELATION`.
        CORRELATION should be removed in the future.

    :return:
        * Array of real (cross) power spectral density estimate values. This is
          a two sided array with negative values following the positive ones
          whatever is the input data (real or complex).

    .. rubric:: Description:

    The exact power spectral density is the Fourier transform of the
    autocorrelation sequence:

    .. math:: P_{xx}(f) = T \sum_{m=-\infty}^{\infty} r_{xx}[m] exp^{-j2\pi fmT}

    The correlogram method of PSD estimation substitutes a finite sequence of
    autocorrelation estimates :math:`\hat{r}_{xx}` in place of :math:`r_{xx}`.
    This estimation can be computed with :func:`xcorr` or :func:`CORRELATION` by
    chosing a proprer lag `L`. The estimated PSD is then

    .. math:: \hat{P}_{xx}(f) = T \sum_{m=-L}^{L} \hat{r}_{xx}[m] exp^{-j2\pi fmT}

    The lag index must be less than the number of data samples `N`. Ideally, it
    should be around `L/10` [Marple]_ so as to avoid greater statistical
    variance associated with higher lags.

    To reduce the leakage of the implicit rectangular window and therefore to
    reduce the bias in the estimate, a tapering window is normally used and lead
    to the so-called Blackman and Tukey correlogram:

    .. math:: \hat{P}_{BT}(f) = T \sum_{m=-L}^{L} w[m] \hat{r}_{xx}[m] exp^{-j2\pi fmT}

    The correlogram for the cross power spectral estimate is

    .. math:: \hat{P}_{xx}(f) = T \sum_{m=-L}^{L} \hat{r}_{xx}[m] exp^{-j2\pi fmT}

    which is computed if :attr:`Y` is not provide. In such case,
    :math:`r_{yx} = r_{xy}` so we compute the correlation only once.

    .. plot::
        :width: 80%
        :include-source:

        from spectrum import CORRELOGRAMPSD, marple_data
        from spectrum.tools import cshift
        from pylab import log10, axis, grid, plot,linspace

        psd = CORRELOGRAMPSD(marple_data, marple_data, lag=15)
        f = linspace(-0.5, 0.5, len(psd))
        psd = cshift(psd, len(psd)/2)
        plot(f, 10*log10(psd/max(psd)))
        axis([-0.5,0.5,-50,0])
        grid(True)

    .. seealso:: :func:`create_window`, :func:`CORRELATION`, :func:`xcorr`,
        :class:`pcorrelogram`.
    """
    N = len(X)
    assert lag<N, 'lag must be < size of input data'
    assert correlation_method in ['CORRELATION', 'xcorr']
    if Y is None:
        Y = numpy.array(X)
        crosscorrelation = False
    else:
        crosscorrelation = True

    if NFFT is None:
        NFFT = N
    psd = numpy.zeros(NFFT, dtype=complex)

    # Window should be centered around zero. Moreover, we want only the
    # positive values. So, we need to use 2*lag + 1 window and keep values on
    # the right side.
    w = Window(2.*lag+1, window, **window_params)
    w = w.data[lag+1:]

    # compute the cross correlation
    if correlation_method == 'CORRELATION':
        rxy = CORRELATION (X, Y, maxlags=lag, norm=norm)
    elif correlation_method == 'xcorr':
        rxy, _l = xcorr (X, Y, maxlags=lag, norm=norm)
        rxy = rxy[lag:]

    # keep track of the first elt.
    psd[0] = rxy[0]

    # create the first part of the PSD
    psd[1:lag+1] = rxy[1:] * w

    # create the second part.
    # First, we need to compute the auto or cross correlation ryx
    if crosscorrelation is True:
        # compute the cross correlation
        if correlation_method == 'CORRELATION':
            ryx = CORRELATION(Y, X, maxlags=lag, norm=norm)
        elif correlation_method == 'xcorr':
            ryx, _l = xcorr(Y, X, maxlags=lag, norm=norm)
            ryx = ryx[lag:]
        #print len(ryx), len(psd[-1:NPSD-lag-1:-1])

        psd[-1:NFFT-lag-1:-1] = ryx[1:].conjugate() * w
    else: #autocorrelation no additional correlation call required
        psd[-1:NFFT-lag-1:-1] = rxy[1:].conjugate() * w

    psd = numpy.real(fft(psd))

    return psd

def
CORRELOGRAMPSD
(
X
,
Y
=
,
lag
=
1
,
window
=
'hamming'
,
norm
=
'unbiased'
,
NFFT
=
4096
,
window_params
=
{
}
,
correlation_method
=
'xcorr'
)
:
N
=
len
(
X
)
assert
lag
<
N
,
'lag must be < size of input data'
assert
correlation_method
in
[
'CORRELATION'
,
'xcorr'
]
if
Y
is
:
Y
=
numpy
.
array
(
X
)
crosscorrelation
=
False
else
:
crosscorrelation
=
True
if
NFFT
is
:
NFFT
=
N
psd
=
numpy
.
zeros
(
NFFT
,
dtype
=
complex
)
w
=
Window
(
2.
*
lag
+
1
,
window
,
**
window_params
)
w
=
w
.
data
[
lag
+
1
:
]
if
correlation_method
==
'CORRELATION'
:
rxy
=
CORRELATION
(
X
,
Y
,
maxlags
=
lag
,
norm
=
norm
)
elif
correlation_method
==
'xcorr'
:
rxy
,
_l
=
xcorr
(
X
,
Y
,
maxlags
=
lag
,
norm
=
norm
)
rxy
=
rxy
[
lag
:
]
psd
[
0
]
=
rxy
[
0
]
psd
[
1
:
lag
+
1
]
=
rxy
[
1
:
]
*
w
if
crosscorrelation
is
True
:
if
correlation_method
==
'CORRELATION'
:
ryx
=
CORRELATION
(
Y
,
X
,
maxlags
=
lag
,
norm
=
norm
)
elif
correlation_method
==
'xcorr'
:
ryx
,
_l
=
xcorr
(
Y
,
X
,
maxlags
=
lag
,
norm
=
norm
)
ryx
=
ryx
[
lag
:
]
psd
[
1
:
NFFT
lag
1
:
1
]
=
ryx
[
1
:
]
.
conjugate
(
)
*
w
else
:
psd
[
1
:
NFFT
lag
1
:
1
]
=
rxy
[
1
:
]
.
conjugate
(
)
*
w
psd
=
numpy
.
real
(
fft
(
psd
)
)
return
psd


def _get_data(url):
    """Helper function to get data over http or from a local file"""
    if url.startswith('http://'):
        # Try Python 2, use Python 3 on exception
        try:
            resp = urllib.urlopen(url)
            encoding = resp.headers.dict.get('content-encoding', 'plain')
        except AttributeError:
            resp = urllib.request.urlopen(url)
            encoding = resp.headers.get('content-encoding', 'plain')
        data = resp.read()
        if encoding == 'plain':
            pass
        elif encoding == 'gzip':
            data = StringIO(data)
            data = gzip.GzipFile(fileobj=data).read()
        else:
            raise RuntimeError('unknown encoding')
    else:
        with open(url, 'r') as fid:
            data = fid.read()

    return data

def
_get_data
(
url
)
:
if
url
.
startswith
(
'http://'
)
:
try
:
resp
=
urllib
.
urlopen
(
url
)
encoding
=
resp
.
headers
.
dict
.
get
(
'content-encoding'
,
'plain'
)
except
AttributeError
:
resp
=
urllib
.
request
.
urlopen
(
url
)
encoding
=
resp
.
headers
.
get
(
'content-encoding'
,
'plain'
)
data
=
resp
.
read
(
)
if
encoding
==
'plain'
:
pass
elif
encoding
==
'gzip'
:
data
=
StringIO
(
data
)
data
=
gzip
.
GzipFile
(
fileobj
=
data
)
.
read
(
)
else
:
raise
RuntimeError
(
'unknown encoding'
)
else
:
with
open
(
url
,
'r'
)
as
fid
:
data
=
fid
.
read
(
)
return
data


def _select_block(str_in, start_tag, end_tag):
    """Select first block delimited by start_tag and end_tag"""
    start_pos = str_in.find(start_tag)
    if start_pos < 0:
        raise ValueError('start_tag not found')
    depth = 0
    for pos in range(start_pos, len(str_in)):
        if str_in[pos] == start_tag:
            depth += 1
        elif str_in[pos] == end_tag:
            depth -= 1

        if depth == 0:
            break
    sel = str_in[start_pos + 1:pos]
    return sel

def
_select_block
(
str_in
,
start_tag
,
end_tag
)
:
start_pos
=
str_in
.
find
(
start_tag
)
if
start_pos
<
0
:
raise
ValueError
(
'start_tag not found'
)
depth
=
0
for
pos
in
range
(
start_pos
,
len
(
str_in
)
)
:
if
str_in
[
pos
]
==
start_tag
:
depth
+=
1
elif
str_in
[
pos
]
==
end_tag
:
depth
-=
1
if
depth
==
0
:
break
sel
=
str_in
[
start_pos
+
1
:
pos
]
return
sel


def _parse_dict_recursive(dict_str):
    """Parse a dictionary from the search index"""
    dict_out = dict()
    pos_last = 0
    pos = dict_str.find(':')
    while pos >= 0:
        key = dict_str[pos_last:pos]
        if dict_str[pos + 1] == '[':
            # value is a list
            pos_tmp = dict_str.find(']', pos + 1)
            if pos_tmp < 0:
                raise RuntimeError('error when parsing dict')
            value = dict_str[pos + 2: pos_tmp].split(',')
            # try to convert elements to int
            for i in range(len(value)):
                try:
                    value[i] = int(value[i])
                except ValueError:
                    pass
        elif dict_str[pos + 1] == '{':
            # value is another dictionary
            subdict_str = _select_block(dict_str[pos:], '{', '}')
            value = _parse_dict_recursive(subdict_str)
            pos_tmp = pos + len(subdict_str)
        else:
            raise ValueError('error when parsing dict: unknown elem')

        key = key.strip('"')
        if len(key) > 0:
            dict_out[key] = value

        pos_last = dict_str.find(',', pos_tmp)
        if pos_last < 0:
            break
        pos_last += 1
        pos = dict_str.find(':', pos_last)

    return dict_out

def
_parse_dict_recursive
(
dict_str
)
:
dict_out
=
dict
(
)
pos_last
=
0
pos
=
dict_str
.
find
(
':'
)
while
pos
>=
0
:
key
=
dict_str
[
pos_last
:
pos
]
if
dict_str
[
pos
+
1
]
==
'['
:
pos_tmp
=
dict_str
.
find
(
']'
,
pos
+
1
)
if
pos_tmp
<
0
:
raise
RuntimeError
(
'error when parsing dict'
)
value
=
dict_str
[
pos
+
2
:
pos_tmp
]
.
split
(
','
)
for
i
in
range
(
len
(
value
)
)
:
try
:
value
[
i
]
=
int
(
value
[
i
]
)
except
ValueError
:
pass
elif
dict_str
[
pos
+
1
]
==
'{'
:
subdict_str
=
_select_block
(
dict_str
[
pos
:
]
,
'{'
,
'}'
)
value
=
_parse_dict_recursive
(
subdict_str
)
pos_tmp
=
pos
+
len
(
subdict_str
)
else
:
raise
ValueError
(
'error when parsing dict: unknown elem'
)
key
=
key
.
strip
(
'"'
)
if
len
(
key
)
>
0
:
dict_out
[
key
]
=
value
pos_last
=
dict_str
.
find
(
','
,
pos_tmp
)
if
pos_last
<
0
:
break
pos_last
+=
1
pos
=
dict_str
.
find
(
':'
,
pos_last
)
return
dict_out


def parse_sphinx_searchindex(searchindex):
    """Parse a Sphinx search index

    Parameters
    ----------
    searchindex : str
        The Sphinx search index (contents of searchindex.js)

    Returns
    -------
    filenames : list of str
        The file names parsed from the search index.
    objects : dict
        The objects parsed from the search index.
    """
    # Make sure searchindex uses UTF-8 encoding
    if hasattr(searchindex, 'decode'):
        searchindex = searchindex.decode('UTF-8')

    # parse objects
    query = 'objects:'
    pos = searchindex.find(query)
    if pos < 0:
        raise ValueError('"objects:" not found in search index')

    sel = _select_block(searchindex[pos:], '{', '}')
    objects = _parse_dict_recursive(sel)

    # parse filenames
    query = 'filenames:'
    pos = searchindex.find(query)
    if pos < 0:
        raise ValueError('"filenames:" not found in search index')
    filenames = searchindex[pos + len(query) + 1:]
    filenames = filenames[:filenames.find(']')]
    filenames = [f.strip('"') for f in filenames.split(',')]

    return filenames, objects


def embed_code_links(app, exception):
    """Embed hyperlinks to documentation into example code"""
    if exception is not None:
        return

    # No need to waste time embedding hyperlinks when not running the examples
    # XXX: also at the time of writing this fixes make html-noplot
    # for some reason I don't fully understand
    if not app.builder.config.plot_gallery:
        return

    # XXX: Whitelist of builders for which it makes sense to embed
    # hyperlinks inside the example html. Note that the link embedding
    # require searchindex.js to exist for the links to the local doc
    # and there does not seem to be a good way of knowing which
    # builders creates a searchindex.js.
    if app.builder.name not in ['html', 'readthedocs']:
        return

    print('Embedding documentation hyperlinks in examples..')

    gallery_conf = app.config.sphinx_gallery_conf

    gallery_dirs = gallery_conf['gallery_dirs']
    if not isinstance(gallery_dirs, list):
        gallery_dirs = [gallery_dirs]

    for gallery_dir in gallery_dirs:
        _embed_code_links(app, gallery_conf, gallery_dir)


def _get_link(self, cobj):
        """Get a valid link, False if not found"""

        fname_idx = None
        full_name = cobj['module_short'] + '.' + cobj['name']
        if full_name in self._searchindex['objects']:
            value = self._searchindex['objects'][full_name]
            if isinstance(value, dict):
                value = value[next(iter(value.keys()))]
            fname_idx = value[0]
        elif cobj['module_short'] in self._searchindex['objects']:
            value = self._searchindex['objects'][cobj['module_short']]
            if cobj['name'] in value.keys():
                fname_idx = value[cobj['name']][0]

        if fname_idx is not None:
            fname = self._searchindex['filenames'][fname_idx] + '.html'

            if self._is_windows:
                fname = fname.replace('/', '\\')
                link = os.path.join(self.doc_url, fname)
            else:
                link = posixpath.join(self.doc_url, fname)

            if hasattr(link, 'decode'):
                link = link.decode('utf-8', 'replace')

            if link in self._page_cache:
                html = self._page_cache[link]
            else:
                html = get_data(link, self.gallery_dir)
                self._page_cache[link] = html

            # test if cobj appears in page
            comb_names = [cobj['module_short'] + '.' + cobj['name']]
            if self.extra_modules_test is not None:
                for mod in self.extra_modules_test:
                    comb_names.append(mod + '.' + cobj['name'])
            url = False
            if hasattr(html, 'decode'):
                # Decode bytes under Python 3
                html = html.decode('utf-8', 'replace')

            for comb_name in comb_names:
                if hasattr(comb_name, 'decode'):
                    # Decode bytes under Python 3
                    comb_name = comb_name.decode('utf-8', 'replace')
                if comb_name in html:
                    url = link + u'#' + comb_name
            link = url
        else:
            link = False

        return link

def _get_link ( self , cobj ) : fname_idx = None full_name = cobj [ 'module_short' ] + '.' + cobj [ 'name' ] if full_name in self . _searchindex [ 'objects' ] : value = self . _searchindex [ 'objects' ] [ full_name ] if isinstance ( value , dict ) : value = value [ next ( iter ( value . keys ( ) ) ) ] fname_idx = value [ 0 ] elif cobj [ 'module_short' ] in self . _searchindex [ 'objects' ] : value = self . _searchindex [ 'objects' ] [ cobj [ 'module_short' ] ] if cobj [ 'name' ] in value . keys ( ) : fname_idx = value [ cobj [ 'name' ] ] [ 0 ] if fname_idx is not None : fname = self . _searchindex [ 'filenames' ] [ fname_idx ] + '.html' if self . _is_windows : fname = fname . replace ( '/' , '\\' ) link = os . path . join ( self . doc_url , fname ) else : link = posixpath . join ( self . doc_url , fname ) if hasattr ( link , 'decode' ) : link = link . decode ( 'utf-8' , 'replace' ) if link in self . _page_cache : html = self . _page_cache [ link ] else : html = get_data ( link , self . gallery_dir ) self . _page_cache [ link ] = html comb_names = [ cobj [ 'module_short' ] + '.' + cobj [ 'name' ] ] if self . extra_modules_test is not None : for mod in self . extra_modules_test : comb_names . append ( mod + '.' + cobj [ 'name' ] ) url = False if hasattr ( html , 'decode' ) : html = html . decode ( 'utf-8' , 'replace' ) for comb_name in comb_names : if hasattr ( comb_name , 'decode' ) : comb_name = comb_name . decode ( 'utf-8' , 'replace' ) if comb_name in html : url = link + u'#' + comb_name link = url else : link = False return link


def tf2zp(b,a):
    """Convert transfer function filter parameters to zero-pole-gain form

    Find the zeros, poles, and gains of this continuous-time system:

    .. warning:: b and a must have the same length.

    ::

        from spectrum import tf2zp
        b = [2,3,0]
        a = [1, 0.4, 1]
        [z,p,k] = tf2zp(b,a)          % Obtain zero-pole-gain form
        z =
            1.5
            0
        p =
           -0.2000 + 0.9798i
            -0.2000 - 0.9798i
        k =
           2

    :param b: numerator
    :param a: denominator
    :param fill: If True, check that the length of a and b are the same. If not, create a copy of the shortest element and append zeros to it.
    :return: z (zeros), p (poles), g (gain)

    Convert transfer function f(x)=sum(b*x^n)/sum(a*x^n) to
    zero-pole-gain form f(x)=g*prod(1-z*x)/prod(1-p*x)

    .. todo:: See if tf2ss followed by ss2zp gives better results.  These
        are available from the control system toolbox.  Note that
        the control systems toolbox doesn't bother, but instead uses

    .. seealso:: scipy.signal.tf2zpk, which gives the same results but uses a different
        algorithm (z^-1 instead of z).
    """
    from numpy import roots
    assert len(b) == len(a), "length of the vectors a and b must be identical. fill with zeros if needed."

    g = b[0] / a[0]
    z = roots(b)
    p = roots(a)

    return z, p, g

Convert transfer function filter parameters to zero - pole - gain form


def eqtflength(b,a):
    """Given two list or arrays, pad with zeros the shortest array

    :param b: list or array
    :param a: list or array

    .. doctest::

        >>> from spectrum.transfer import eqtflength
        >>> a = [1,2]
        >>> b = [1,2,3,4]
        >>> a, b, = eqtflength(a,b)

    """
    d = abs(len(b)-len(a))
    if d != 0:
        if len(a) > len(b):
            try:
                b.extend([0.]*d)
            except:
                b = np.append(b, [0]*d)
        elif len(b)>len(a):
            try:
                a.extend([0.]*d)
            except:
                a = np.append(a, [0]*d)
        return b,a
    else:
        return b,a

def eqtflength ( b , a ) : d = abs ( len ( b ) - len ( a ) ) if d != 0 : if len ( a ) > len ( b ) : try : b . extend ( [ 0. ] * d ) except : b = np . append ( b , [ 0 ] * d ) elif len ( b ) > len ( a ) : try : a . extend ( [ 0. ] * d ) except : a = np . append ( a , [ 0 ] * d ) return b , a else : return b , a

Given two list or arrays pad with zeros the shortest array


def ss2zpk(a,b,c,d, input=0):
    """State-space representation to zero-pole-gain representation.

    :param A: ndarray State-space representation of linear system.
    :param B: ndarray State-space representation of linear system.
    :param C: ndarray State-space representation of linear system.
    :param D: ndarray State-space representation of linear system.
    :param int input: optional For multiple-input systems, the input to use.

    :return:
        * z, p : sequence  Zeros and poles.
        * k : float System gain.

    .. note:: wrapper of scipy function ss2zpk
    """
    import scipy.signal
    z, p, k = scipy.signal.ss2zpk(a, b, c, d, input=input)
    return z, p, k

def ss2zpk ( a , b , c , d , input = 0 ) : import scipy . signal z , p , k = scipy . signal . ss2zpk ( a , b , c , d , input = input ) return z , p , k

State - space representation to zero - pole - gain representation .


def zpk2tf(z, p, k):
    r"""Return polynomial transfer function representation from zeros and poles

    :param ndarray z: Zeros of the transfer function.
    :param ndarray p: Poles of the transfer function.
    :param float k: System gain.

    :return:
        b : ndarray Numerator polynomial.
        a : ndarray Numerator and denominator polynomials.

    :func:`zpk2tf` forms transfer function polynomials from the zeros, poles, and gains
    of a system in factored form.

    zpk2tf(z,p,k) finds a rational transfer function

    .. math:: \frac{B(s)}{A(s)} = \frac{b_1 s^{n-1}+\dots b_{n-1}s+b_n}{a_1 s^{m-1}+\dots a_{m-1}s+a_m}

    given a system in factored transfer function form

    .. math:: H(s) = \frac{Z(s)}{P(s)} = k \frac{(s-z_1)(s-z_2)\dots(s-z_m)}{(s-p_1)(s-p_2)\dots(s-p_n)}

    with p being the pole locations, and z the zero locations, with as many.
    The gains for each numerator transfer function are in vector k.
    The zeros and poles must be real or come in complex conjugate pairs.
    The polynomial denominator coefficients are returned in row vector a and
    the polynomial numerator coefficients are returned in matrix b, which has
    as many rows as there are columns of z.

    Inf values can be used as place holders in z if some columns have fewer zeros than others.

    .. note:: wrapper of scipy function zpk2tf
    """
    import scipy.signal
    b, a = scipy.signal.zpk2tf(z, p, k)
    return b, a

def zpk2tf ( z , p , k ) : r import scipy . signal b , a = scipy . signal . zpk2tf ( z , p , k ) return b , a

r Return polynomial transfer function representation from zeros and poles


def zpk2ss(z, p, k):
    """Zero-pole-gain representation to state-space representation

    :param sequence z,p: Zeros and poles.
    :param float k: System gain.

    :return:
        * A, B, C, D : ndarray State-space matrices.

    .. note:: wrapper of scipy function zpk2ss
    """
    import scipy.signal
    return scipy.signal.zpk2ss(z,p,k)

def zpk2ss ( z , p , k ) : import scipy . signal return scipy . signal . zpk2ss ( z , p , k )

Zero - pole - gain representation to state - space representation


def enbw(data):
    r"""Computes the equivalent noise bandwidth

    .. math:: ENBW = N \frac{\sum_{n=1}^{N} w_n^2}{\left(\sum_{n=1}^{N} w_n \right)^2}

    .. doctest::

        >>> from spectrum import create_window, enbw
        >>> w = create_window(64, 'rectangular')
        >>> enbw(w)
        1.0

    The following table contains the ENBW values for some of the
    implemented windows in this module (with N=16384). They have been
    double checked against litterature (Source: [Harris]_, [Marple]_).

    If not present, it means that it has not been checked.

    =================== ============ =============
    name                 ENBW        litterature
    =================== ============ =============
    rectangular         1.           1.
    triangle            1.3334       1.33
    Hann                1.5001       1.5
    Hamming             1.3629       1.36
    blackman            1.7268       1.73
    kaiser              1.7
    blackmanharris,4    2.004        2.
    riesz               1.2000       1.2
    riemann             1.32         1.3
    parzen              1.917        1.92
    tukey 0.25          1.102        1.1
    bohman              1.7858       1.79
    poisson 2           1.3130       1.3
    hanningpoisson 0.5  1.609        1.61
    cauchy              1.489        1.48
    lanczos             1.3
    =================== ============ =============

    """
    N = len(data)
    return N * np.sum(data**2) / np.sum(data)**2

def enbw ( data ) : r N = len ( data ) return N * np . sum ( data ** 2 ) / np . sum ( data ) ** 2

r Computes the equivalent noise bandwidth


def _kaiser(n, beta):
    """Independant Kaiser window

    For the definition of the Kaiser window, see A. V. Oppenheim & R. W. Schafer, "Discrete-Time Signal Processing".

    The continuous version of width n centered about x=0 is:

    .. note:: 2 times slower than scipy.kaiser
    """
    from scipy.special import iv as besselI
    m = n - 1
    k = arange(0, m)
    k = 2. * beta / m * sqrt (k * (m - k))
    w = besselI (0, k) / besselI (0, beta)
    return w

def _kaiser ( n , beta ) : from scipy . special import iv as besselI m = n - 1 k = arange ( 0 , m ) k = 2. * beta / m * sqrt ( k * ( m - k ) ) w = besselI ( 0 , k ) / besselI ( 0 , beta ) return w


def window_visu(N=51, name='hamming', **kargs):
    """A Window visualisation tool

    :param N: length of the window
    :param name: name of the window
    :param NFFT: padding used by the FFT
    :param mindB: the minimum frequency power in dB
    :param maxdB: the maximum frequency power in dB
    :param kargs: optional arguments passed to :func:`create_window`

    This function plot the window shape and its equivalent in the Fourier domain.

    .. plot::
        :width: 80%
        :include-source:

        from spectrum import window_visu
        window_visu(64, 'kaiser', beta=8.)

    """
    # get the default parameters
    mindB = kargs.pop('mindB', -100)
    maxdB = kargs.pop('maxdB', None)
    norm = kargs.pop('norm', True)

    # create a window object
    w = Window(N, name, **kargs)

    # plot the time and frequency windows
    w.plot_time_freq(mindB=mindB, maxdB=maxdB, norm=norm)

def window_visu ( N = 51 , name = 'hamming' , ** kargs ) : mindB = kargs . pop ( 'mindB' , - 100 ) maxdB = kargs . pop ( 'maxdB' , None ) norm = kargs . pop ( 'norm' , True ) w = Window ( N , name , ** kargs ) w . plot_time_freq ( mindB = mindB , maxdB = maxdB , norm = norm )


def window_kaiser(N, beta=8.6, method='numpy'):
    r"""Kaiser window

    :param N: window length
    :param beta: kaiser parameter (default is 8.6)

    To obtain a Kaiser window that designs an FIR filter with
    sidelobe attenuation of :math:`\alpha` dB, use the following :math:`\beta` where
    :math:`\beta = \pi \alpha`.

    .. math::

        w_n = \frac{I_0\left(\pi\alpha\sqrt{1-\left(\frac{2n}{M}-1\right)^2}\right)} {I_0(\pi \alpha)}

    where

      * :math:`I_0` is the zeroth order Modified Bessel function of the first kind.
      * :math:`\alpha` is a real number that determines the shape of the
        window. It determines the trade-off between main-lobe width and side
        lobe level.
      * the length of the sequence is N=M+1.

    The Kaiser window can approximate many other windows by varying
    the :math:`\beta` parameter:

    ===== ========================
    beta  Window shape
    ===== ========================
    0     Rectangular
    5     Similar to a Hamming
    6     Similar to a Hanning
    8.6   Similar to a Blackman
    ===== ========================

    .. plot::
        :width: 80%
        :include-source:

        from pylab import plot, legend, xlim
        from spectrum import window_kaiser
        N = 64
        for beta in [1,2,4,8,16]:
            plot(window_kaiser(N, beta), label='beta='+str(beta))
        xlim(0,N)
        legend()

    .. plot::
        :width: 80%
        :include-source:

        from spectrum import window_visu
        window_visu(64, 'kaiser', beta=8.)

    .. seealso:: numpy.kaiser, :func:`spectrum.window.create_window`
    """
    if N == 1:
        return ones(1)
    if method == 'numpy':
        from numpy import kaiser
        return kaiser(N, beta)
    else:
        return _kaiser(N, beta)

def window_kaiser ( N , beta = 8.6 , method = 'numpy' ) : r if N == 1 : return ones ( 1 ) if method == 'numpy' : from numpy import kaiser return kaiser ( N , beta ) else : return _kaiser ( N , beta )


def window_blackman(N, alpha=0.16):
    r"""Blackman window

    :param N: window length

    .. math:: a_0 - a_1 \cos(\frac{2\pi n}{N-1}) +a_2 \cos(\frac{4\pi n }{N-1})

    with

    .. math::

        a_0 = (1-\alpha)/2, a_1=0.5, a_2=\alpha/2 \rm{\;and\; \alpha}=0.16

    When :math:`\alpha=0.16`, this is the unqualified Blackman window with
    :math:`a_0=0.48`  and :math:`a_2=0.08`.

    .. plot::
        :width: 80%
        :include-source:

        from spectrum import window_visu
        window_visu(64, 'blackman')

    .. note:: Although Numpy implements a blackman window for :math:`\alpha=0.16`,
        this implementation is valid for any :math:`\alpha`.

    .. seealso:: numpy.blackman, :func:`create_window`, :class:`Window`

    """
    a0 = (1. - alpha)/2.
    a1 = 0.5
    a2 = alpha/2.

    if (N == 1):
        win = array([1.])
    else:
        k = arange(0, N)/float(N-1.)
        win =  a0 - a1 * cos (2 * pi * k) + a2 * cos (4 * pi * k)
    return win

def window_blackman ( N , alpha = 0.16 ) : r a0 = ( 1. - alpha ) / 2. a1 = 0.5 a2 = alpha / 2. if ( N == 1 ) : win = array ( [ 1. ] ) else : k = arange ( 0 , N ) / float ( N - 1. ) win = a0 - a1 * cos ( 2 * pi * k ) + a2 * cos ( 4 * pi * k ) return win


def window_gaussian(N, alpha=2.5):
    r"""Gaussian window

    :param N: window length

    .. math:: \exp^{-0.5 \left( \sigma\frac{n}{N/2} \right)^2}

    with :math:`\frac{N-1}{2}\leq n \leq \frac{N-1}{2}`.

    .. note:: N-1 is used to be in agreement with octave convention. The ENBW of
         1.4 is also in agreement with [Harris]_

    .. plot::
        :width: 80%
        :include-source:

        from spectrum import window_visu
        window_visu(64, 'gaussian', alpha=2.5)

    .. seealso:: scipy.signal.gaussian, :func:`create_window`
    """
    t = linspace(-(N-1)/2., (N-1)/2., N)
    #t = linspace(-(N)/2., (N)/2., N)
    w = exp(-0.5*(alpha * t/(N/2.))**2.)
    return w

def window_gaussian ( N , alpha = 2.5 ) : r t = linspace ( - ( N - 1 ) / 2. , ( N - 1 ) / 2. , N ) w = exp ( - 0.5 * ( alpha * t / ( N / 2. ) ) ** 2. ) return w


def window_cosine(N):
    r"""Cosine tapering window also known as sine window.

    :param N: window length

    .. math:: w(n) = \cos\left(\frac{\pi n}{N-1} - \frac{\pi}{2}\right) = \sin \left(\frac{\pi n}{N-1}\right)

    .. plot::
        :width: 80%
        :include-source:

        from spectrum import window_visu
        window_visu(64, 'cosine')

    .. seealso:: :func:`create_window`, :class:`Window`
    """
    if N ==1:
        return ones(1)
    n = arange(0, N)
    win = sin(pi*n/(N-1.))
    return win

def window_cosine ( N ) : r if N == 1 : return ones ( 1 ) n = arange ( 0 , N ) win = sin ( pi * n / ( N - 1. ) ) return win

r Cosine tapering window also known as sine window .


def window_lanczos(N):
    r"""Lanczos window also known as sinc window.

    :param N: window length

    .. math:: w(n) = sinc \left(  \frac{2n}{N-1} - 1 \right)

    .. plot::
        :width: 80%
        :include-source:

        from spectrum import window_visu
        window_visu(64, 'lanczos')

    .. seealso:: :func:`create_window`, :class:`Window`
    """
    if N ==1:
        return ones(1)

    n = linspace(-N/2., N/2., N)
    win = sinc(2*n/(N-1.))
    return win

def window_lanczos ( N ) : r if N == 1 : return ones ( 1 ) n = linspace ( - N / 2. , N / 2. , N ) win = sinc ( 2 * n / ( N - 1. ) ) return win

r Lanczos window also known as sinc window .


def window_bartlett_hann(N):
    r"""Bartlett-Hann window

    :param N: window length

    .. math:: w(n) = a_0 + a_1 \left| \frac{n}{N-1} -\frac{1}{2}\right| - a_2 \cos \left( \frac{2\pi n}{N-1} \right)

    with :math:`a_0 = 0.62`, :math:`a_1 = 0.48` and :math:`a_2=0.38`

    .. plot::
        :width: 80%
        :include-source:

        from spectrum import window_visu
        window_visu(64, 'bartlett_hann')

    .. seealso:: :func:`create_window`, :class:`Window`
    """
    if N == 1:
        return ones(1)
    n = arange(0, N)

    a0 = 0.62
    a1 = 0.48
    a2 = 0.38

    win = a0 -  a1 *abs(n/(N-1.)-0.5) -a2 * cos(2*pi*n/(N-1.))
    return win

def window_bartlett_hann ( N ) : r if N == 1 : return ones ( 1 ) n = arange ( 0 , N ) a0 = 0.62 a1 = 0.48 a2 = 0.38 win = a0 - a1 * abs ( n / ( N - 1. ) - 0.5 ) - a2 * cos ( 2 * pi * n / ( N - 1. ) ) return win


def _coeff4(N, a0, a1, a2, a3):
    """a common internal function to some window functions with 4 coeffs

    For the blackmna harris for instance, the results are identical to octave if N is odd
    but not for even values...if n =0 whatever N is, the w(0) must be equal to a0-a1+a2-a3, which
    is the case here, but not in octave..."""
    if N == 1:
        return ones(1)

    n = arange(0, N)
    N1 = N - 1.

    w = a0 -a1*cos(2.*pi*n / N1) + a2*cos(4.*pi*n / N1) - a3*cos(6.*pi*n / N1)

    return w

def _coeff4 ( N , a0 , a1 , a2 , a3 ) : if N == 1 : return ones ( 1 ) n = arange ( 0 , N ) N1 = N - 1. w = a0 - a1 * cos ( 2. * pi * n / N1 ) + a2 * cos ( 4. * pi * n / N1 ) - a3 * cos ( 6. * pi * n / N1 ) return w


def window_nuttall(N):
    r"""Nuttall tapering window

    :param N: window length

    .. math:: w(n) = a_0 - a_1 \cos\left(\frac{2\pi n}{N-1}\right)+ a_2 \cos\left(\frac{4\pi n}{N-1}\right)- a_3 \cos\left(\frac{6\pi n}{N-1}\right)

    with :math:`a_0 = 0.355768`, :math:`a_1 = 0.487396`, :math:`a_2=0.144232` and :math:`a_3=0.012604`

    .. plot::
        :width: 80%
        :include-source:

        from spectrum import window_visu
        window_visu(64, 'nuttall', mindB=-80)

    .. seealso:: :func:`create_window`, :class:`Window`
    """
    a0 = 0.355768
    a1 = 0.487396
    a2 = 0.144232
    a3 = 0.012604

    return _coeff4(N, a0, a1, a2, a3)


def window_blackman_nuttall(N):
    r"""Blackman Nuttall window

    returns a minimum, 4-term Blackman-Harris window. The window is minimum in the sense that its maximum sidelobes are minimized.
    The coefficients for this window differ from the Blackman-Harris window coefficients and produce slightly lower sidelobes.

    :param N: window length

    .. math:: w(n) = a_0 - a_1 \cos\left(\frac{2\pi n}{N-1}\right)+ a_2 \cos\left(\frac{4\pi n}{N-1}\right)- a_3 \cos\left(\frac{6\pi n}{N-1}\right)

    with :math:`a_0 = 0.3635819`, :math:`a_1 = 0.4891775`, :math:`a_2=0.1365995` and :math:`0_3=.0106411`

    .. plot::
        :width: 80%
        :include-source:

        from spectrum import window_visu
        window_visu(64, 'blackman_nuttall', mindB=-80)

    .. seealso:: :func:`spectrum.window.create_window`
    .. seealso:: :func:`create_window`, :class:`Window`

    """
    a0 = 0.3635819
    a1 = 0.4891775
    a2 = 0.1365995
    a3 = 0.0106411
    return _coeff4(N, a0, a1, a2, a3)


def window_blackman_harris(N):
    r"""Blackman Harris window

    :param N: window length

    .. math:: w(n) = a_0 - a_1 \cos\left(\frac{2\pi n}{N-1}\right)+ a_2 \cos\left(\frac{4\pi n}{N-1}\right)- a_3 \cos\left(\frac{6\pi n}{N-1}\right)

    =============== =========
    coeff            value
    =============== =========
    :math:`a_0`     0.35875
    :math:`a_1`     0.48829
    :math:`a_2`     0.14128
    :math:`a_3`     0.01168
    =============== =========

    .. plot::
        :width: 80%
        :include-source:

        from spectrum import window_visu
        window_visu(64, 'blackman_harris', mindB=-80)

    .. seealso:: :func:`spectrum.window.create_window`
    .. seealso:: :func:`create_window`, :class:`Window`
    """
    a0 = 0.35875
    a1 = 0.48829
    a2 = 0.14128
    a3 = 0.01168
    return _coeff4(N, a0, a1, a2, a3)


def window_bohman(N):
    r"""Bohman tapering window

    :param N: window length

    .. math:: w(n) = (1-|x|) \cos (\pi |x|) + \frac{1}{\pi} \sin(\pi |x|)

    where x is a length N vector of linearly spaced values between
    -1 and 1.

    .. plot::
        :width: 80%
        :include-source:

        from spectrum import window_visu
        window_visu(64, 'bohman')

    .. seealso:: :func:`create_window`, :class:`Window`
    """
    x = linspace(-1, 1, N)
    w = (1.-abs(x)) * cos(pi*abs(x)) + 1./pi * sin(pi*abs(x))
    return w

def window_bohman ( N ) : r x = linspace ( - 1 , 1 , N ) w = ( 1. - abs ( x ) ) * cos ( pi * abs ( x ) ) + 1. / pi * sin ( pi * abs ( x ) ) return w


def window_flattop(N, mode='symmetric',precision=None):
    r"""Flat-top tapering window

    Returns symmetric or periodic flat top window.

    :param N: window length
    :param mode: way the data are normalised. If mode is *symmetric*, then
        divide n by N-1. IF mode is *periodic*, divide by N,
        to be consistent with octave code.

    When using windows for filter design, the *symmetric* mode
    should be used (default). When using windows for spectral analysis, the *periodic*
    mode should be used. The mathematical form of the flat-top window in the symmetric
    case is:

    .. math:: w(n) = a_0
        - a_1 \cos\left(\frac{2\pi n}{N-1}\right)
        + a_2 \cos\left(\frac{4\pi n}{N-1}\right)
        - a_3 \cos\left(\frac{6\pi n}{N-1}\right)
        + a_4 \cos\left(\frac{8\pi n}{N-1}\right)

    =====  =============
    coeff  value
    =====  =============
    a0     0.21557895
    a1     0.41663158
    a2     0.277263158
    a3     0.083578947
    a4     0.006947368
    =====  =============

    .. plot::
        :width: 80%
        :include-source:

        from spectrum import window_visu
        window_visu(64, 'bohman')

    .. seealso:: :func:`create_window`, :class:`Window`
    """
    assert mode in ['periodic', 'symmetric']
    t = arange(0, N)

    # FIXME: N=1 for mode = periodic ?
    if mode == 'periodic':
        x = 2*pi*t/float(N)
    else:
        if N ==1:
            return ones(1)
        x = 2*pi*t/float(N-1)
    a0 = 0.21557895
    a1 = 0.41663158
    a2 = 0.277263158
    a3 = 0.083578947
    a4 = 0.006947368

    if precision == 'octave':
        #to compare with octave, same as above but less precise
        d  = 4.6402
        a0 = 1./d
        a1 = 1.93/d
        a2 = 1.29/d
        a3 = 0.388/d
        a4 = 0.0322/d
    w = a0-a1*cos(x)+a2*cos(2*x)-a3*cos(3*x)+a4*cos(4*x)
    return w

def window_flattop ( N , mode = 'symmetric' , precision = None ) : r assert mode in [ 'periodic' , 'symmetric' ] t = arange ( 0 , N ) if mode == 'periodic' : x = 2 * pi * t / float ( N ) else : if N == 1 : return ones ( 1 ) x = 2 * pi * t / float ( N - 1 ) a0 = 0.21557895 a1 = 0.41663158 a2 = 0.277263158 a3 = 0.083578947 a4 = 0.006947368 if precision == 'octave' : d = 4.6402 a0 = 1. / d a1 = 1.93 / d a2 = 1.29 / d a3 = 0.388 / d a4 = 0.0322 / d w = a0 - a1 * cos ( x ) + a2 * cos ( 2 * x ) - a3 * cos ( 3 * x ) + a4 * cos ( 4 * x ) return w


def window_taylor(N, nbar=4, sll=-30):
    """Taylor tapering window

    Taylor windows allows you to make tradeoffs between the
    mainlobe width and sidelobe level (sll).

    Implemented as described by Carrara, Goodman, and Majewski
    in 'Spotlight Synthetic Aperture Radar: Signal Processing Algorithms'
    Pages 512-513

    :param N: window length
    :param float nbar:
    :param float sll:

    The default values gives equal height
    sidelobes (nbar) and maximum sidelobe level (sll).

    .. warning:: not implemented

    .. seealso:: :func:`create_window`, :class:`Window`
    """
    B = 10**(-sll/20)
    A = log(B + sqrt(B**2 - 1))/pi
    s2 = nbar**2 / (A**2 + (nbar - 0.5)**2)
    ma = arange(1,nbar)
    def calc_Fm(m):
        numer = (-1)**(m+1) * prod(1-m**2/s2/(A**2 + (ma - 0.5)**2))
        denom = 2* prod([ 1-m**2/j**2 for j in ma if j != m])
        return numer/denom
    Fm = array([calc_Fm(m) for m in ma])
    def W(n):
        return 2 * np.sum(Fm * cos(2*pi*ma*(n-N/2 + 1/2)/N)) + 1
    w = array([W(n) for n in range(N)])
    # normalize (Note that this is not described in the original text)
    scale = W((N-1)/2)
    w /= scale
    return w

def window_taylor ( N , nbar = 4 , sll = - 30 ) : B = 10 ** ( - sll / 20 ) A = log ( B + sqrt ( B ** 2 - 1 ) ) / pi s2 = nbar ** 2 / ( A ** 2 + ( nbar - 0.5 ) ** 2 ) ma = arange ( 1 , nbar ) def calc_Fm ( m ) : numer = ( - 1 ) ** ( m + 1 ) * prod ( 1 - m ** 2 / s2 / ( A ** 2 + ( ma - 0.5 ) ** 2 ) ) denom = 2 * prod ( [ 1 - m ** 2 / j ** 2 for j in ma if j != m ] ) return numer / denom Fm = array ( [ calc_Fm ( m ) for m in ma ] ) def W ( n ) : return 2 * np . sum ( Fm * cos ( 2 * pi * ma * ( n - N / 2 + 1 / 2 ) / N ) ) + 1 w = array ( [ W ( n ) for n in range ( N ) ] ) scale = W ( ( N - 1 ) / 2 ) w /= scale return w


def window_riesz(N):
    r"""Riesz tapering window

    :param N: window length

    .. math:: w(n) = 1 - \left| \frac{n}{N/2}  \right|^2

    with :math:`-N/2 \leq n \leq N/2`.

    .. plot::
        :width: 80%
        :include-source:

        from spectrum import window_visu
        window_visu(64, 'riesz')

    .. seealso:: :func:`create_window`, :class:`Window`
    """
    n = linspace(-N/2., (N)/2., N)

    w = 1 - abs(n/(N/2.))**2.
    return w

def window_riesz ( N ) : r n = linspace ( - N / 2. , ( N ) / 2. , N ) w = 1 - abs ( n / ( N / 2. ) ) ** 2. return w


def window_riemann(N):
    r"""Riemann tapering window

    :param int N: window length

    .. math:: w(n) = 1 - \left| \frac{n}{N/2}  \right|^2

    with :math:`-N/2 \leq n \leq N/2`.

    .. plot::
        :width: 80%
        :include-source:

        from spectrum import window_visu
        window_visu(64, 'riesz')

    .. seealso:: :func:`create_window`, :class:`Window`
    """
    n = linspace(-N/2., (N)/2., N)
    w = sin(n/float(N)*2.*pi) / (n / float(N)*2.*pi)
    return w

def window_riemann ( N ) : r n = linspace ( - N / 2. , ( N ) / 2. , N ) w = sin ( n / float ( N ) * 2. * pi ) / ( n / float ( N ) * 2. * pi ) return w


def window_poisson(N, alpha=2):
    r"""Poisson tapering window

    :param int N: window length

    .. math:: w(n) = \exp^{-\alpha \frac{|n|}{N/2} }

    with :math:`-N/2 \leq n \leq N/2`.

    .. plot::
        :width: 80%
        :include-source:

        from spectrum import window_visu
        window_visu(64, 'poisson')
        window_visu(64, 'poisson', alpha=3)
        window_visu(64, 'poisson', alpha=4)

    .. seealso:: :func:`create_window`, :class:`Window`
    """
    n = linspace(-N/2., (N)/2., N)
    w = exp(-alpha * abs(n)/(N/2.))
    return w

def window_poisson ( N , alpha = 2 ) : r n = linspace ( - N / 2. , ( N ) / 2. , N ) w = exp ( - alpha * abs ( n ) / ( N / 2. ) ) return w


def window_poisson_hanning(N, alpha=2):
    r"""Hann-Poisson tapering window

    This window is constructed as the product of the Hanning and Poisson
    windows. The parameter **alpha** is the Poisson parameter.

    :param int N: window length
    :param float alpha: parameter of the poisson window

    .. plot::
        :width: 80%
        :include-source:

        from spectrum import window_visu
        window_visu(64, 'poisson_hanning', alpha=0.5)
        window_visu(64, 'poisson_hanning', alpha=1)
        window_visu(64, 'poisson_hanning')

    .. seealso:: :func:`window_poisson`, :func:`window_hann`
    """
    w1 = window_hann(N)
    w2 = window_poisson(N, alpha=alpha)
    return w1*w2

def window_poisson_hanning ( N , alpha = 2 ) : r w1 = window_hann ( N ) w2 = window_poisson ( N , alpha = alpha ) return w1 * w2


def window_cauchy(N, alpha=3):
    r"""Cauchy tapering window

    :param int N: window length
    :param float alpha: parameter of the poisson window

    .. math:: w(n) = \frac{1}{1+\left(\frac{\alpha*n}{N/2}\right)**2}

    .. plot::
        :width: 80%
        :include-source:

        from spectrum import window_visu
        window_visu(64, 'cauchy', alpha=3)
        window_visu(64, 'cauchy', alpha=4)
        window_visu(64, 'cauchy', alpha=5)

    .. seealso:: :func:`window_poisson`, :func:`window_hann`
    """
    n = linspace(-N/2., (N)/2., N)
    w = 1./(1.+ (alpha*n/(N/2.))**2)
    return w

def window_cauchy ( N , alpha = 3 ) : r n = linspace ( - N / 2. , ( N ) / 2. , N ) w = 1. / ( 1. + ( alpha * n / ( N / 2. ) ) ** 2 ) return w


def compute_response(self, **kargs):
        """Compute the window data frequency response

        :param norm: True by default. normalised the frequency data.
        :param int NFFT: total length of the final data sets( 2048 by default.
            if less than data length, then NFFT is set to the data length*2).

        The response is stored in :attr:`response`.

        .. note:: Units are dB (20 log10) since we plot the frequency response)

        """
        from numpy.fft import fft, fftshift

        norm = kargs.get('norm', self.norm)

        # do some padding. Default is max(2048, data.len*2)
        NFFT = kargs.get('NFFT', 2048)
        if NFFT < len(self.data):
            NFFT = self.data.size * 2

        # compute the fft modulus
        A = fft(self.data, NFFT)
        mag = abs(fftshift(A))

        # do we want to normalise the data
        if norm is True:
            mag = mag / max(mag)

        response = 20. * stools.log10(mag) # factor 20 we are looking at the response
                                    # not the powe
        #response = clip(response,mindB,100)
        self.__response = response

def compute_response ( self , ** kargs ) : from numpy . fft import fft , fftshift norm = kargs . get ( 'norm' , self . norm ) NFFT = kargs . get ( 'NFFT' , 2048 ) if NFFT < len ( self . data ) : NFFT = self . data . size * 2 A = fft ( self . data , NFFT ) mag = abs ( fftshift ( A ) ) if norm is True : mag = mag / max ( mag ) response = 20. * stools . log10 ( mag ) self . __response = response


def plot_frequencies(self, mindB=None, maxdB=None, norm=True):
        """Plot the window in the frequency domain

        :param mindB: change the default lower y bound
        :param maxdB: change the default upper lower bound
        :param bool norm: if True, normalise the frequency response.

        .. plot::
            :width: 80%
            :include-source:

            from spectrum.window import Window
            w = Window(64, name='hamming')
            w.plot_frequencies()

        """
        from pylab import plot, title, xlim, grid, ylim, xlabel, ylabel
        # recompute the response
        self.compute_response(norm=norm)

        plot(self.frequencies, self.response)
        title("ENBW=%2.1f" % (self.enbw))
        ylabel('Frequency response (dB)')
        xlabel('Fraction of sampling frequency')
        # define the plot limits
        xlim(-0.5, 0.5)
        y0, y1 = ylim()
        if mindB:
            y0 = mindB
        if maxdB is not None:
            y1 = maxdB
        else:
            y1 = max(self.response)

        ylim(y0, y1)

        grid(True)

def
plot_frequencies
(
self
,
mindB
=
,
maxdB
=
,
norm
=
True
)
:
from
pylab
import
plot
,
title
,
xlim
,
grid
,
ylim
,
xlabel
,
ylabel
self
.
compute_response
(
norm
=
norm
)
plot
(
self
.
frequencies
,
self
.
response
)
title
(
"ENBW=%2.1f"
%
(
self
.
enbw
)
)
ylabel
(
'Frequency response (dB)'
)
xlabel
(
'Fraction of sampling frequency'
)
xlim
(
0.5
,
0.5
)
y0
,
y1
=
ylim
(
)
if
mindB
:
y0
=
mindB
if
maxdB
is
not
:
y1
=
maxdB
else
:
y1
=
max
(
self
.
response
)
ylim
(
y0
,
y1
)
grid
(
True
)


def plot_window(self):
        """Plot the window in the time domain

        .. plot::
            :width: 80%
            :include-source:

            from spectrum.window import Window
            w = Window(64, name='hamming')
            w.plot_window()

        """
        from pylab import plot, xlim, grid, title, ylabel, axis
        x = linspace(0, 1, self.N)
        xlim(0, 1)
        plot(x, self.data)
        grid(True)
        title('%s Window (%s points)' % (self.name.capitalize(), self.N))
        ylabel('Amplitude')
        axis([0, 1, 0, 1.1])

def
plot_window
(
self
)
:
from
pylab
import
plot
,
xlim
,
grid
,
title
,
ylabel
,
axis
x
=
linspace
(
0
,
1
,
self
.
N
)
xlim
(
0
,
1
)
plot
(
x
,
self
.
data
)
grid
(
True
)
title
(
'%s Window (%s points)'
%
(
self
.
name
.
capitalize
(
)
,
self
.
N
)
)
ylabel
(
'Amplitude'
)
axis
(
[
0
,
1
,
0
,
1.1
]
)


def plot_time_freq(self, mindB=-100, maxdB=None, norm=True,
            yaxis_label_position="right"):
        """Plotting method to plot both time and frequency domain results.

        See :meth:`plot_frequencies` for the optional arguments.

        .. plot::
            :width: 80%
            :include-source:

            from spectrum.window import Window
            w = Window(64, name='hamming')
            w.plot_time_freq()

        """
        from pylab import subplot, gca

        subplot(1, 2, 1)
        self.plot_window()

        subplot(1, 2, 2)
        self.plot_frequencies(mindB=mindB, maxdB=maxdB, norm=norm)

        if yaxis_label_position=="left":
            try: tight_layout()
            except: pass
        else:
            ax = gca()
            ax.yaxis.set_label_position("right")

def plot_time_freq ( self , mindB = - 100 , maxdB = None , norm = True , yaxis_label_position = "right" ) : from pylab import subplot , gca subplot ( 1 , 2 , 1 ) self . plot_window ( ) subplot ( 1 , 2 , 2 ) self . plot_frequencies ( mindB = mindB , maxdB = maxdB , norm = norm ) if yaxis_label_position == "left" : try : tight_layout ( ) except : pass else : ax = gca ( ) ax . yaxis . set_label_position ( "right" )

Plotting method to plot both time and frequency domain results .


def TOEPLITZ(T0, TC, TR, Z):
    """solve the general toeplitz linear equations

    Solve TX=Z

    :param T0: zero lag value
    :param TC: r1 to rN
    :param TR: r1 to rN

    returns X

    requires 3M^2+M operations instead of M^3 with gaussian elimination

    .. warning:: not used right now
    """
    assert len(TC)>0
    assert len(TC)==len(TR)
    M = len(TC)
    X = numpy.zeros(M+1,dtype=complex)
    A = numpy.zeros(M,dtype=complex)
    B = numpy.zeros(M,dtype=complex)
    P = T0
    if P == 0: raise ValueError("P must be different from zero")
    if P == 0: raise ValueError("P must be different from zero")
    X[0] = Z[0]/T0
    for k in range(0, M):
        save1 = TC[k]
        save2 = TR[k]
        beta = X[0]*TC[k]
        if k == 0:
            temp1 = -save1 / P
            temp2 = -save2 / P
        else:
            for j in range(0, k):
                save1 = save1 + A[j] * TC[k-j-1]
                save2 = save2 + B[j] * TR[k-j-1]
                beta = beta + X[j+1] * TC[k-j-1]
            temp1 = -save1 / P
            temp2 = -save2/P
        P = P * (1. - (temp1*temp2))
        if P <= 0:
            raise ValueError("singular matrix")
        A[k] = temp1
        B[k] = temp2
        alpha = (Z[k+1]-beta)/P
        if k == 0:
            X[k+1] = alpha
            for j in range(0,k+1):
                X[j] = X[j] + alpha * B[k-j]
            continue

        for j in range(0, k):
            kj = k-j-1
            save1 = A[j]
            A[j] = save1 + temp1 * B[kj]
            B[kj] = B[kj] + temp2*save1

        X[k+1] = alpha
        for j in range(0,k+1):
            X[j] = X[j] + alpha*B[k-j]
    return X

def
TOEPLITZ
(
T0
,
TC
,
TR
,
Z
)
:
assert
len
(
TC
)
>
0
assert
len
(
TC
)
==
len
(
TR
)
M
=
len
(
TC
)
X
=
numpy
.
zeros
(
M
+
1
,
dtype
=
complex
)
A
=
numpy
.
zeros
(
M
,
dtype
=
complex
)
B
=
numpy
.
zeros
(
M
,
dtype
=
complex
)
P
=
T0
if
P
==
0
:
raise
ValueError
(
"P must be different from zero"
)
if
P
==
0
:
raise
ValueError
(
"P must be different from zero"
)
X
[
0
]
=
Z
[
0
]
/
T0
for
k
in
range
(
0
,
M
)
:
save1
=
TC
[
k
]
save2
=
TR
[
k
]
beta
=
X
[
0
]
*
TC
[
k
]
if
k
==
0
:
temp1
=
save1
/
P
temp2
=
save2
/
P
else
:
for
j
in
range
(
0
,
k
)
:
save1
=
save1
+
A
[
j
]
*
TC
[
k
j
1
]
save2
=
save2
+
B
[
j
]
*
TR
[
k
j
1
]
beta
=
beta
+
X
[
j
+
1
]
*
TC
[
k
j
1
]
temp1
=
save1
/
P
temp2
=
save2
/
P
P
=
P
*
(
1.
(
temp1
*
temp2
)
)
if
P
<=
0
:
raise
ValueError
(
"singular matrix"
)
A
[
k
]
=
temp1
B
[
k
]
=
temp2
alpha
=
(
Z
[
k
+
1
]
beta
)
/
P
if
k
==
0
:
X
[
k
+
1
]
=
alpha
for
j
in
range
(
0
,
k
+
1
)
:
X
[
j
]
=
X
[
j
]
+
alpha
*
B
[
k
j
]
continue
for
j
in
range
(
0
,
k
)
:
kj
=
k
j
1
save1
=
A
[
j
]
A
[
j
]
=
save1
+
temp1
*
B
[
kj
]
B
[
kj
]
=
B
[
kj
]
+
temp2
*
save1
X
[
k
+
1
]
=
alpha
for
j
in
range
(
0
,
k
+
1
)
:
X
[
j
]
=
X
[
j
]
+
alpha
*
B
[
k
j
]
return
X


def HERMTOEP(T0, T, Z):
    """solve Tx=Z by a variation of Levinson algorithm where T
    is a complex hermitian toeplitz matrix

    :param T0: zero lag value
    :param T: r1 to rN

    :return: X

    used by eigen PSD method
    """
    assert len(T)>0
    M = len(T)
    X = numpy.zeros(M+1,dtype=complex)
    A = numpy.zeros(M,dtype=complex)
    P = T0
    if P == 0: raise ValueError("P must be different from zero")
    X[0] = Z[0]/T0
    for k in range(0, M):
        save = T[k]
        beta = X[0]*T[k]
        if k == 0:
            temp = -save / P
        else:
            for j in range(0, k):
                save = save + A[j] * T[k-j-1]
                beta = beta + X[j+1] * T[k-j-1]
            temp = -save / P
        P = P * (1. - (temp.real**2+temp.imag**2))
        if P <= 0:
            raise ValueError("singular matrix")
        A[k] = temp
        alpha = (Z[k+1]-beta)/P

        if k == 0:
            #print 'skipping code for k=0'
            X[k+1] = alpha
            for j in range(0,k+1):
                X[j] = X[j] + alpha * A[k-j].conjugate()
            continue
        khalf = (k+1)//2
        for j in range(0, khalf):
            kj = k-j-1
            save=A[j]
            A[j] = save+temp*A[kj].conjugate()
            if j != kj:
                A[kj] = A[kj] + temp*save.conjugate()
        X[k+1] = alpha
        for j in range(0,k+1):
            X[j] = X[j] + alpha * A[k-j].conjugate()
    return X

def
HERMTOEP
(
T0
,
T
,
Z
)
:
assert
len
(
T
)
>
0
M
=
len
(
T
)
X
=
numpy
.
zeros
(
M
+
1
,
dtype
=
complex
)
A
=
numpy
.
zeros
(
M
,
dtype
=
complex
)
P
=
T0
if
P
==
0
:
raise
ValueError
(
"P must be different from zero"
)
X
[
0
]
=
Z
[
0
]
/
T0
for
k
in
range
(
0
,
M
)
:
save
=
T
[
k
]
beta
=
X
[
0
]
*
T
[
k
]
if
k
==
0
:
temp
=
save
/
P
else
:
for
j
in
range
(
0
,
k
)
:
save
=
save
+
A
[
j
]
*
T
[
k
j
1
]
beta
=
beta
+
X
[
j
+
1
]
*
T
[
k
j
1
]
temp
=
save
/
P
P
=
P
*
(
1.
(
temp
.
real
**
2
+
temp
.
imag
**
2
)
)
if
P
<=
0
:
raise
ValueError
(
"singular matrix"
)
A
[
k
]
=
temp
alpha
=
(
Z
[
k
+
1
]
beta
)
/
P
if
k
==
0
:
X
[
k
+
1
]
=
alpha
for
j
in
range
(
0
,
k
+
1
)
:
X
[
j
]
=
X
[
j
]
+
alpha
*
A
[
k
j
]
.
conjugate
(
)
continue
khalf
=
(
k
+
1
)
//
2
for
j
in
range
(
0
,
khalf
)
:
kj
=
k
j
1
save
=
A
[
j
]
A
[
j
]
=
save
+
temp
*
A
[
kj
]
.
conjugate
(
)
if
j
!=
kj
:
A
[
kj
]
=
A
[
kj
]
+
temp
*
save
.
conjugate
(
)
X
[
k
+
1
]
=
alpha
for
j
in
range
(
0
,
k
+
1
)
:
X
[
j
]
=
X
[
j
]
+
alpha
*
A
[
k
j
]
.
conjugate
(
)
return
X

solve Tx = Z by a variation of Levinson algorithm where T is a complex hermitian toeplitz matrix


def get_short_module_name(module_name, obj_name):
    """ Get the shortest possible module name """
    parts = module_name.split('.')
    short_name = module_name
    for i in range(len(parts) - 1, 0, -1):
        short_name = '.'.join(parts[:i])
        try:
            exec('from %s import %s' % (short_name, obj_name))
        except ImportError:
            # get the last working module name
            short_name = '.'.join(parts[:(i + 1)])
            break
    return short_name

def
get_short_module_name
(
module_name
,
obj_name
)
:
parts
=
module_name
.
split
(
'.'
)
short_name
=
module_name
for
i
in
range
(
len
(
parts
)
1
,
0
,
1
)
:
short_name
=
'.'
.
join
(
parts
[
:
i
]
)
try
:
exec
(
'from %s import %s'
%
(
short_name
,
obj_name
)
)
except
ImportError
:
short_name
=
'.'
.
join
(
parts
[
:
(
i
+
1
)
]
)
break
return
short_name


def identify_names(code):
    """Builds a codeobj summary by identifying and resolving used names

    >>> code = '''
    ... from a.b import c
    ... import d as e
    ... print(c)
    ... e.HelloWorld().f.g
    ... '''
    >>> for name, o in sorted(identify_names(code).items()):
    ...     print(name, o['name'], o['module'], o['module_short'])
    c c a.b a.b
    e.HelloWorld HelloWorld d d
    """
    finder = NameFinder()
    finder.visit(ast.parse(code))

    example_code_obj = {}
    for name, full_name in finder.get_mapping():
        # name is as written in file (e.g. np.asarray)
        # full_name includes resolved import path (e.g. numpy.asarray)
        module, attribute = full_name.rsplit('.', 1)
        # get shortened module name
        module_short = get_short_module_name(module, attribute)
        cobj = {'name': attribute, 'module': module,
                'module_short': module_short}
        example_code_obj[name] = cobj
    return example_code_obj

def identify_names ( code ) : finder = NameFinder ( ) finder . visit ( ast . parse ( code ) ) example_code_obj = { } for name , full_name in finder . get_mapping ( ) : module , attribute = full_name . rsplit ( '.' , 1 ) module_short = get_short_module_name ( module , attribute ) cobj = { 'name' : attribute , 'module' : module , 'module_short' : module_short } example_code_obj [ name ] = cobj return example_code_obj


def _thumbnail_div(full_dir, fname, snippet, is_backref=False):
    """Generates RST to place a thumbnail in a gallery"""
    thumb = os.path.join(full_dir, 'images', 'thumb',
                         'sphx_glr_%s_thumb.png' % fname[:-3])
    ref_name = os.path.join(full_dir, fname).replace(os.path.sep, '_')

    template = BACKREF_THUMBNAIL_TEMPLATE if is_backref else THUMBNAIL_TEMPLATE
    return template.format(snippet=snippet, thumbnail=thumb, ref_name=ref_name)

def _thumbnail_div ( full_dir , fname , snippet , is_backref = False ) : thumb = os . path . join ( full_dir , 'images' , 'thumb' , 'sphx_glr_%s_thumb.png' % fname [ : - 3 ] ) ref_name = os . path . join ( full_dir , fname ) . replace ( os . path . sep , '_' ) template = BACKREF_THUMBNAIL_TEMPLATE if is_backref else THUMBNAIL_TEMPLATE return template . format ( snippet = snippet , thumbnail = thumb , ref_name = ref_name )


def modcovar(x, order):
    """Simple and fast implementation of the covariance AR estimate

    This code is 10 times faster than :func:`modcovar_marple` and more importantly
    only 10 lines of code, compared to a 200 loc for :func:`modcovar_marple`

    :param X:        Array of complex data samples
    :param int order:   Order of linear prediction model

    :return:
        * P    - Real linear prediction variance at order IP
        * A    - Array of complex linear prediction coefficients

    .. plot::
        :include-source:
        :width: 80%

        from spectrum import modcovar, marple_data, arma2psd, cshift
        from pylab import log10, linspace, axis, plot

        a, p = modcovar(marple_data, 15)
        PSD = arma2psd(a)
        PSD = cshift(PSD, len(PSD)/2) # switch positive and negative freq
        plot(linspace(-0.5, 0.5, 4096), 10*log10(PSD/max(PSD)))
        axis([-0.5,0.5,-60,0])

    .. seealso:: :class:`~spectrum.modcovar.pmodcovar`

    :validation: the AR parameters are the same as those returned by
        a completely different function :func:`modcovar_marple`.

    :References: Mathworks
    """
    from spectrum import corrmtx
    import scipy.linalg
    X = corrmtx(x, order, 'modified')
    Xc = np.matrix(X[:,1:])
    X1 = np.array(X[:,0])

    # Coefficients estimated via the covariance method
    # Here we use lstsq rathre than solve function because Xc is not square matrix
    a, residues, rank, singular_values = scipy.linalg.lstsq(-Xc, X1)

    # Estimate the input white noise variance

    Cz = np.dot(X1.conj().transpose(), Xc)
    e = np.dot(X1.conj().transpose(), X1) + np.dot(Cz, a)
    assert e.imag < 1e-4, 'wierd behaviour'
    e = float(e.real) # ignore imag part that should be small

    return a, e

def
modcovar
(
x
,
order
)
:
from
spectrum
import
corrmtx
import
scipy
.
linalg
X
=
corrmtx
(
x
,
order
,
'modified'
)
Xc
=
np
.
matrix
(
X
[
:
,
1
:
]
)
X1
=
np
.
array
(
X
[
:
,
0
]
)
a
,
residues
,
rank
,
singular_values
=
scipy
.
linalg
.
lstsq
(
Xc
,
X1
)
Cz
=
np
.
dot
(
X1
.
conj
(
)
.
transpose
(
)
,
Xc
)
e
=
np
.
dot
(
X1
.
conj
(
)
.
transpose
(
)
,
X1
)
+
np
.
dot
(
Cz
,
a
)
assert
e
.
imag
<
1e-4
,
'wierd behaviour'
e
=
float
(
e
.
real
)
return
a
,
e


def aryule(X, order, norm='biased', allow_singularity=True):
    r"""Compute AR coefficients using Yule-Walker method

    :param X: Array of complex data values, X(1) to X(N)
    :param int order: Order of autoregressive process to be fitted (integer)
    :param str norm: Use a biased or unbiased correlation.
    :param bool allow_singularity:

    :return:
        * AR coefficients (complex)
        * variance of white noise (Real)
        * reflection coefficients for use in lattice filter

    .. rubric:: Description:

    The Yule-Walker method returns the polynomial A corresponding to the
    AR parametric signal model estimate of vector X using the Yule-Walker
    (autocorrelation) method. The autocorrelation may be computed using a
    **biased** or **unbiased** estimation. In practice, the biased estimate of
    the autocorrelation is used for the unknown true autocorrelation. Indeed,
    an unbiased estimate may result in nonpositive-definite autocorrelation
    matrix.
    So, a biased estimate leads to a stable AR filter.
    The following matrix form represents the Yule-Walker equations. The are
    solved by means of the Levinson-Durbin recursion:

     .. math::

        \left( \begin{array}{cccc}
        r(1) & r(2)^* & \dots & r(n)^*\\
        r(2) & r(1)^* & \dots & r(n-1)^*\\
        \dots & \dots & \dots & \dots\\
        r(n) & \dots & r(2) & r(1) \end{array} \right)
        \left( \begin{array}{cccc}
        a(2)\\
        a(3) \\
        \dots \\
        a(n+1)  \end{array} \right)
        =
        \left( \begin{array}{cccc}
        -r(2)\\
        -r(3) \\
        \dots \\
        -r(n+1)  \end{array} \right)

    The outputs consists of the AR coefficients, the estimated variance of the
    white noise process, and the reflection coefficients. These outputs can be
    used to estimate the optimal order by using :mod:`~spectrum.criteria`.

    .. rubric:: Examples:

    From a known AR process or order 4, we estimate those AR parameters using
    the aryule function.

    .. doctest::

        >>> from scipy.signal import lfilter
        >>> from spectrum import *
        >>> from numpy.random import randn
        >>> A  =[1, -2.7607, 3.8106, -2.6535, 0.9238]
        >>> noise = randn(1, 1024)
        >>> y = lfilter([1], A, noise);
        >>> #filter a white noise input to create AR(4) process
        >>> [ar, var, reflec] = aryule(y[0], 4)
        >>> # ar should contains values similar to A

    The PSD estimate of a data samples is computed and plotted as follows:

    .. plot::
        :width: 80%
        :include-source:

        from spectrum import *
        from pylab import *

        ar, P, k = aryule(marple_data, 15, norm='biased')
        psd = arma2psd(ar)
        plot(linspace(-0.5, 0.5, 4096), 10 * log10(psd/max(psd)))
        axis([-0.5, 0.5, -60, 0])

    .. note:: The outputs have been double checked against (1) octave outputs
        (octave has norm='biased' by default) and (2) Marple test code.

    .. seealso:: This function uses :func:`~spectrum.levinson.LEVINSON` and
        :func:`~spectrum.correlation.CORRELATION`. See the :mod:`~spectrum.criteria`
        module for criteria to automatically select the AR order.

    :References: [Marple]_

    """
    assert norm in ['biased', 'unbiased']
    r = CORRELATION(X, maxlags=order, norm=norm)
    A, P, k = LEVINSON(r, allow_singularity=allow_singularity)
    return A, P, k

def aryule ( X , order , norm = 'biased' , allow_singularity = True ) : r assert norm in [ 'biased' , 'unbiased' ] r = CORRELATION ( X , maxlags = order , norm = norm ) A , P , k = LEVINSON ( r , allow_singularity = allow_singularity ) return A , P , k

r Compute AR coefficients using Yule - Walker method


def LEVINSON(r, order=None, allow_singularity=False):
    r"""Levinson-Durbin recursion.

    Find the coefficients of a length(r)-1 order autoregressive linear process

    :param r: autocorrelation sequence of length N + 1 (first element being the zero-lag autocorrelation)
    :param order: requested order of the autoregressive coefficients. default is N.
    :param allow_singularity: false by default. Other implementations may be True (e.g., octave)

    :return:
        * the `N+1` autoregressive coefficients :math:`A=(1, a_1...a_N)`
        * the prediction errors
        * the `N` reflections coefficients values

    This algorithm solves the set of complex linear simultaneous equations
    using Levinson algorithm.

    .. math::

        \bold{T}_M \left( \begin{array}{c} 1 \\ \bold{a}_M \end{array} \right) =
        \left( \begin{array}{c} \rho_M \\ \bold{0}_M  \end{array} \right)

    where :math:`\bold{T}_M` is a Hermitian Toeplitz matrix with elements
    :math:`T_0, T_1, \dots ,T_M`.

    .. note:: Solving this equations by Gaussian elimination would
        require :math:`M^3` operations whereas the levinson algorithm
        requires :math:`M^2+M` additions and :math:`M^2+M` multiplications.

    This is equivalent to solve the following symmetric Toeplitz system of
    linear equations

    .. math::

        \left( \begin{array}{cccc}
        r_1 & r_2^* & \dots & r_{n}^*\\
        r_2 & r_1^* & \dots & r_{n-1}^*\\
        \dots & \dots & \dots & \dots\\
        r_n & \dots & r_2 & r_1 \end{array} \right)
        \left( \begin{array}{cccc}
        a_2\\
        a_3 \\
        \dots \\
        a_{N+1}  \end{array} \right)
        =
        \left( \begin{array}{cccc}
        -r_2\\
        -r_3 \\
        \dots \\
        -r_{N+1}  \end{array} \right)

    where :math:`r = (r_1  ... r_{N+1})` is the input autocorrelation vector, and
    :math:`r_i^*` denotes the complex conjugate of :math:`r_i`. The input r is typically
    a vector of autocorrelation coefficients where lag 0 is the first
    element :math:`r_1`.

    .. doctest::

        >>> import numpy; from spectrum import LEVINSON
        >>> T = numpy.array([3., -2+0.5j, .7-1j])
        >>> a, e, k = LEVINSON(T)

    """
    #from numpy import isrealobj
    T0  = numpy.real(r[0])
    T = r[1:]
    M = len(T)

    if order is None:
        M = len(T)
    else:
        assert order <= M, 'order must be less than size of the input data'
        M = order

    realdata = numpy.isrealobj(r)
    if realdata is True:
        A = numpy.zeros(M, dtype=float)
        ref = numpy.zeros(M, dtype=float)
    else:
        A = numpy.zeros(M, dtype=complex)
        ref = numpy.zeros(M, dtype=complex)

    P = T0

    for k in range(0, M):
        save = T[k]
        if k == 0:
            temp = -save / P
        else:
            #save += sum([A[j]*T[k-j-1] for j in range(0,k)])
            for j in range(0, k):
                save = save + A[j] * T[k-j-1]
            temp = -save / P
        if realdata:
            P = P * (1. - temp**2.)
        else:
            P = P * (1. - (temp.real**2+temp.imag**2))
        if P <= 0 and allow_singularity==False:
            raise ValueError("singular matrix")
        A[k] = temp
        ref[k] = temp # save reflection coeff at each step
        if k == 0:
            continue

        khalf = (k+1)//2
        if realdata is True:
            for j in range(0, khalf):
                kj = k-j-1
                save = A[j]
                A[j] = save + temp * A[kj]
                if j != kj:
                    A[kj] += temp*save
        else:
            for j in range(0, khalf):
                kj = k-j-1
                save = A[j]
                A[j] = save + temp * A[kj].conjugate()
                if j != kj:
                    A[kj] = A[kj] + temp * save.conjugate()

    return A, P, ref