# Source code for spectrum.yulewalker

"""Yule Walker method to estimate AR values.

.. topic:: Estimation of AR values using Yule-Walker method

.. autosummary::

aryule
pyule

.. codeauthor:: Thomas Cokelaer 2011

"""

from .correlation import CORRELATION
from .levinson import LEVINSON
from .psd import ParametricSpectrum
from spectrum import tools
import numpy as np

__all__ = ['aryule', 'pyule']

[docs]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(, A, noise);
>>> #filter a white noise input to create AR(4) process
>>> [ar, var, reflec] = aryule(y, 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

[docs]class pyule(ParametricSpectrum):
"""Class to create PSD based on the Yule Walker method

See :func:aryule for description.

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

from spectrum import *
p = pyule(marple_data, 15, NFFT=4096)
p.plot(sides='centerdc')

"""
def __init__(self, data, order, norm='biased', NFFT=None, sampling=1.,
scale_by_freq=True):
"""**Constructor**

For a detailled description of the parameters, see :func:aryule.

:param array data: input data (list or numpy.array)
:param int order:
:param int NFFT: total length of the final data sets (padded with
zero if needed; default is 4096)
:param float sampling: sampling frequency of the input :attr:data
:param str norm: don't change if you do not know

"""
super(pyule, self).__init__(data, ar_order=order,  NFFT=NFFT,
scale_by_freq=scale_by_freq,
sampling=sampling)
self.sampling = sampling
self._norm_aryule = norm

def __call__(self):
from . import arma
ar, rho, k = aryule(self.data, self.ar_order, norm=self._norm_aryule)
psd = arma.arma2psd(ar, NFFT=self.NFFT, rho=rho, T=self.sampling)
# save the AR and reflection coefficients.
self.ar = ar
self.reflection = k

# save the PSD
if self.datatype == 'real':
# see doc/concepts.rst for details
if self.NFFT % 2 == 0:
newpsd  = psd[0:int(self.NFFT/2 + 1)] * 2
else:
newpsd  = psd[0:int((self.NFFT+1) / 2)] * 2
self.psd = newpsd
else:
self.psd = psd
if self.scale_by_freq is True:
self.scale()
return self

def _str_title(self):
return "PYule PSD estimate\n"

def __str__(self):
return super(pyule, self).__str__()