# Source code for spectrum.modcovar

```"""Module related to the modcovar methods

"""

import numpy as np
from .psd import ParametricSpectrum

__all__ = ['modcovar', 'modcovar_marple', 'pmodcovar']

[docs]def modcovar_marple (X,IP):
"""Fast algorithm for the solution of the modified covariance least squares normal equations.

This implementation is based on [Marple]_. This code is far more
complicated and slower than :func:`modcovar` function, which is now the official version.
See :func:`modcovar` for a detailed description of Modified Covariance method.

:param X:    - Array of complex data samples X(1) through X(N)
:param int IP:   - Order of linear prediction model (integer)

:return:
* P    - Real linear prediction variance at order IP
* A    - Array of complex linear prediction coefficients
* ISTAT - Integer status indicator at time of exit
0. for normal exit (no numerical ill-conditioning)
1. if P is not a positive value
2. if DELTA' and GAMMA' do not lie in the range 0 to 1
3. if P' is not a positive value
4. if DELTA and GAMMA do not lie in the range 0 to 1

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

.. note:: validation. results similar to test example in Marple but
starts to differ for    ip~8. with ratio of 0.975 for ip=15 probably
due to precision.

:References: [Marple]_
"""
Pv = []
N = len(X)
A = np.zeros(N, dtype=complex)
D = np.zeros(N, dtype=complex)
C = np.zeros(N, dtype=complex)
R = np.zeros(N, dtype=complex)
#   Initialization
R1=0.
for K in range(1, N-1):
R1=R1 + 2.*(X[K].real**2 + X[K].imag**2)
R2 = X[0].real**2 + X[0].imag**2
R3 = X[N-1].real**2 + X[N-1].imag**2
R4 = 1. / (R1 + 2. * (R2 + R3))
P = R1 + R2 + R3
DELTA = 1. - R2 * R4
GAMMA = 1. - R3 * R4
LAMBDA = (X[0] * X[N-1]).conjugate()*R4
C[0] = X[N-1] * R4
D[0] = X[0].conjugate() * R4

M = 0
if (IP ==0):
P = (.5*R1+R2+R3)/float(N)
return [], P, []

#C   Main loop

for M in range(0, IP):
#print '---------------------', M
SAVE1 = 0+0j
for K in range(M+1, N):
SAVE1 = SAVE1 + X[K]*X[K-M-1].conjugate()
SAVE1 *= 2.
R[M] = SAVE1.conjugate()
THETA = X[N-1]*D[0]
PSI=X[N-1]*C[0]
XI = X[0].conjugate() * D[0]
if M==0:
pass
else:
for K in range(0, M):
THETA=THETA+X[N-K-2]*D[K+1]    # Eq. (8.D.45)
PSI = PSI + X[N-K-2]*C[K+1]    # Eq. (8.D.45)
XI = XI + X[K+1].conjugate() * D[K+1] # Eq. (8.D.45)
R[K] = R[K]-X[N-M-1] * X[N+1-M+K-1].conjugate() - X[M].conjugate() * X[M-K-1] # Eq. (8.D.37)
SAVE1=SAVE1+R[K].conjugate()*A[M-K-1]             # Eq. (8.D.24)
#print 'withini loop', K, THETA, PSI, XI, R[K], SAVE1
#print 'x   -------', C[K], C[K+1]
#print 'x   -------', D[K], D[K+1]
#print 'x   -------', N-K-2, K+1,N-M-1,  M, M-K-1
#print 'x   -------', X[N-K-2], X[K+1],X[N-M-1],  X[M], X[M-K-1]

#  Order update of A vector

C1 = -SAVE1/P
A[M]=C1                                   # Eq. (8.D.23)
P=P*(1.-C1.real**2-C1.imag**2)            # Eq. (8.D.25)
#print 'C1=' , C1, 'A[M]', A[M], 'P=',P
if M==0:
pass
else:
#for k in range(0, (M-1)/2+1):
#print 'BEFORE', A[0:4]
for K in range(0, (M+1)//2):
MK = M-K-1
SAVE1=A[K]
A[K]=SAVE1+C1*A[MK].conjugate() # Eq. (8.D.22)
#print 'A uopdate---->',M, K, MK, A[K], A[MK], SAVE1, C1
if (K != MK):
#print 'ENTERI IF', K, MK, A[MK], C1, SAVE1
A[MK]=A[MK]+C1*(SAVE1.conjugate()) # Eq. (8.D.22)
#print 'AFTER', A[0:4]
#print M+1, IP
if M+1 == IP:
#Pv.append(P)
P=.5*P/float(N-M-1)
Pv.append(P)
return A, P, Pv
else:
Pv.append(.5*P/float(N-M-1))
#Pv.append(P)

#   Time update of C,D vectors and GAMMA,DELTA,LAMBDA scalars

R1=1./(DELTA*GAMMA-(LAMBDA.real)**2-(LAMBDA.imag)**2)
C1=(THETA*(LAMBDA.conjugate())+PSI*DELTA)*R1
C2=(PSI*LAMBDA+THETA*GAMMA)*R1
C3=(XI*(LAMBDA.conjugate())+THETA*DELTA)*R1
C4=(THETA*LAMBDA+XI*GAMMA)*R1

#print 'C1C2C3C4', C1, C2, C3, C4
#print M, M/2+1, (M-1)/2+1
for K in range(0, (M)//2+1):
MK=M-K
SAVE1=C[K].conjugate()
SAVE2=D[K].conjugate()
SAVE3=C[MK].conjugate()
SAVE4=D[MK].conjugate()
C[K]=C[K]+C1*SAVE3+C2*SAVE4                # Eq. (8.D.43)
D[K]=D[K]+C3*SAVE3+C4*SAVE4                # Eq. (8.D.44)
#print '-------->', K,MK, SAVE1, SAVE2, SAVE3, SAVE4
if K != MK:
C[MK]=C[MK]+C1*SAVE1+C2*SAVE2              # Eq. (8.D.43)
D[MK]=D[MK]+C3*SAVE1+C4*SAVE2              # Eq. (8.D.44)
#print 'witninloop loop ',K,MK,C[MK], D[MK]
#print 'after loop'
#print 'C=',C[0:2]
#print 'D=',D[0:2]
R2=PSI.real**2+PSI.imag**2
R3=THETA.real**2+THETA.imag**2
R4=XI.real**2+XI.imag**2
R5=GAMMA-(R2*DELTA+R3*GAMMA+2.*np.real(PSI*LAMBDA*THETA.conjugate()))*R1
R2=DELTA-(R3*DELTA+R4*GAMMA+2.*np.real(THETA*LAMBDA*XI.conjugate()))*R1
GAMMA=R5                                     # Eq. (8.D.46)
DELTA=R2                                     # Eq. (8.D.47)
LAMBDA=LAMBDA+C3*PSI.conjugate()+C4*THETA.conjugate()  # Eq. (8.D.48)
#print 'before time updaqt',R2, R3, R4, R5, LAMBDA
if P <= 0.:
raise ValueError('Found an invalid negative value for P.')
if (DELTA > 0. and DELTA <= 1. and GAMMA > 0. and GAMMA <=1.):
pass
else:
raise ValueError('Found an invalid DELTA or GAMMA value.')
#   Time update of A vector; order updates of C,D vectors aand GAMMA,
#   DELTA,LAMBDA scalars
#print 'time update--------------'
R1=1./P
R2=1./(DELTA*GAMMA-LAMBDA.real**2-LAMBDA.imag**2) # Eq. (8.D.41)
EF=X[M+1]
EB=X[N-M-2]
#print 'efeb', EF, EB
#print 'A=',A[0:2]
for K in range(0, M+1):
#print 'efeb lloop', K,A[K], X[M-K], X[N-M+K-1]
EF=EF+A[K]*X[M-K]                        # Eq. (8.D.1)
EB=EB+A[K].conjugate()*X[N-M+K-1]            # Eq. (8.D.2)
C1=EB*R1                                     # Eq. (8.D.28)
C2=EF.conjugate()*R1                              # Eq. (8.D.29)
C3=(EB.conjugate()*DELTA+EF*LAMBDA)*R2
C4=(EF*GAMMA+(EB*LAMBDA).conjugate())*R2

#print 'c1c2c3c4', C1 ,C2, C3, C4
#print 'efeb',EF, EB

for K in range(M, -1, -1):
SAVE1=A[K]
A[K]=SAVE1+C3*C[K]+C4*D[K]                 # Eq. (8.D.38)
C[K+1]=C[K]+C1*SAVE1                       # Eq. (8.D.26)
D[K+1]=D[K]+C2*SAVE1                       # Eq. (8.D.27)
#print 'ACD',K, A[K], C[K+1], D[K+1]
C[0]=C1
D[0]=C2
R3=EB.real**2+EB.imag**2
R4=EF.real**2+EF.imag**2
P=P-(R3*DELTA+R4*GAMMA+2.*np.real(EF*EB*LAMBDA))*R2  # Eq. (8.D.42)
DELTA=DELTA-R4*R1                            # Eq. (8.D.32)
GAMMA=GAMMA-R3*R1                            # Eq. (8.D.33)
LAMBDA=LAMBDA+(EF*EB).conjugate()*R1                # Eq. (8.D.35)

#print 'final'
#print R3, R4, P, DELTA, GAMMA, LAMBDA
#print 'Afinal=',A[0:2]
#print 'P=',P
if (P > 0.):
pass
else:
raise ValueError("Found a invalid negative P value ")
if (DELTA > 0. and DELTA <= 1. and  GAMMA > 0. and GAMMA <= 1.):
pass
else:
raise ValueError("Found an invalid negative GAMMA or DELTA value ")

[docs]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.array(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

[docs]class pmodcovar(ParametricSpectrum):
"""Class to create PSD based on modified covariance algorithm

See :func:`~spectrum.modcovar.modcovar` for description.

.. rubric:: Examples

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

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

.. seealso:: :class:`modcovar`

"""
def __init__(self, data, order, NFFT=None, sampling=1.,
scale_by_freq=False):
"""**Constructor**

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

: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`.

"""
super(pmodcovar, self).__init__(data, ar_order=order,
NFFT=NFFT, sampling=sampling,
scale_by_freq=scale_by_freq)

def __call__(self):
from spectrum import arma2psd
ar, e = modcovar(self.data, self.ar_order)
self.ar = ar
psd = arma2psd(A=ar, T=self.sampling, NFFT=self.NFFT)

if self.datatype == 'real':
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 "Modified covariance PSD estimate\n"

def __str__(self):
return super(pmodcovar, self).__str__()
```