Tengo problemas para generar un conjunto de series temporales de colores estacionarios, dada su matriz de covarianza (sus densidades espectrales de potencia (PSD) y sus densidades espectrales de potencia cruzada (CSD)).
Sé que, dadas dos series temporales y , puedo estimar sus densidades espectrales de potencia (PSD) y densidades espectrales cruzadas (CSD) utilizando muchas rutinas ampliamente disponibles, como y funciones en Matlab, etc. Los PSD y CSD forman la matriz de covarianza:
psd()
csd()
¿Qué pasa si quiero hacer lo contrario? Dada la matriz de covarianza, ¿cómo genero una realización de y ?
Incluya cualquier teoría de fondo, o señale cualquier herramienta existente que haga esto (cualquier cosa en Python sería genial).
Mi intento
A continuación se muestra una descripción de lo que he intentado y los problemas que he notado. Es una lectura un poco larga, y lo siento si contiene términos que han sido mal utilizados. Si se puede señalar lo que es erróneo, sería muy útil. Pero mi pregunta es la que está en negrita arriba.
- Los PSD y CSD pueden escribirse como el valor esperado (o promedio de conjunto) de los productos de las transformadas de Fourier de la serie temporal. Entonces, la matriz de covarianza se puede escribir como:
donde
- Una matriz de covarianza es una matriz hermitiana, que tiene valores propios reales que son cero o positivos. Por lo tanto, se puede descomponer en
donde es una matriz diagonal cuyos elementos distintos de cero son las raíces cuadradas de los valores propios de ; es la matriz cuyas columnas son los vectores propios ortonormales de ;es la matriz de identidad.
- La matriz de identidad se escribe como
donde
y son series de frecuencia complejas y sin correlación con media cero y varianza unitaria.
- Al usar 3. en 2., y luego comparar con 1. Las transformadas de Fourier de la serie temporal son:
- La serie temporal se puede obtener utilizando rutinas como la transformada rápida inversa de Fourier.
He escrito una rutina en Python para hacer esto:
def get_noise_freq_domain_CovarMatrix( comatrix , df , inittime , parityN , seed='none' , N_previous_draws=0 ) :
"""
returns the noise time-series given their covariance matrix
INPUT:
comatrix --- covariance matrix, Nts x Nts x Nf numpy array
( Nts = number of time-series. Nf number of positive and non-Nyquist frequencies )
df --- frequency resolution
inittime --- initial time of the noise time-series
parityN --- is the length of the time-series 'Odd' or 'Even'
seed --- seed for the random number generator
N_previous_draws --- number of random number draws to discard first
OUPUT:
t --- time [s]
n --- noise time-series, Nts x N numpy array
"""
if len( comatrix.shape ) != 3 :
raise InputError , 'Input Covariance matrices must be a 3-D numpy array!'
if comatrix.shape[0] != comatrix.shape[1] :
raise InputError , 'Covariance matrix must be square at each frequency!'
Nts , Nf = comatrix.shape[0] , comatrix.shape[2]
if parityN == 'Odd' :
N = 2 * Nf + 1
elif parityN == 'Even' :
N = 2 * ( Nf + 1 )
else :
raise InputError , "parityN must be either 'Odd' or 'Even'!"
stime = 1 / ( N*df )
t = inittime + stime * np.arange( N )
if seed == 'none' :
print 'Not setting the seed for np.random.standard_normal()'
pass
elif seed == 'random' :
np.random.seed( None )
else :
np.random.seed( int( seed ) )
print N_previous_draws
np.random.standard_normal( N_previous_draws ) ;
zs = np.array( [ ( np.random.standard_normal((Nf,)) + 1j * np.random.standard_normal((Nf,)) ) / np.sqrt(2)
for i in range( Nts ) ] )
ntilde_p = np.zeros( ( Nts , Nf ) , dtype=complex )
for k in range( Nf ) :
C = comatrix[ :,:,k ]
if not np.allclose( C , np.conj( np.transpose( C ) ) ) :
print "Covariance matrix NOT Hermitian! Unphysical."
w , V = sp_linalg.eigh( C )
for m in range( w.shape[0] ) :
w[m] = np.real( w[m] )
if np.abs(w[m]) / np.max(w) < 1e-10 :
w[m] = 0
if w[m] < 0 :
print 'Negative eigenvalue! Simulating unpysical signal...'
ntilde_p[ :,k ] = np.conj( np.sqrt( N / (2*stime) ) * np.dot( V , np.dot( np.sqrt( np.diag( w ) ) , zs[ :,k ] ) ) )
zerofill = np.zeros( ( Nts , 1 ) )
if N % 2 == 0 :
ntilde = np.concatenate( ( zerofill , ntilde_p , zerofill , np.conj(np.fliplr(ntilde_p)) ) , axis = 1 )
else :
ntilde = np.concatenate( ( zerofill , ntilde_p , np.conj(np.fliplr(ntilde_p)) ) , axis = 1 )
n = np.real( sp.ifft( ntilde , axis = 1 ) )
return t , n
He aplicado esta rutina a PSD y CSD, cuyas expresiones analíticas se han obtenido del modelado de algún detector con el que estoy trabajando. Lo importante es que en todas las frecuencias, forman una matriz de covarianza (bueno, al menos pasan todas esas if
declaraciones en la rutina). La matriz de covarianza es 3x3. Las 3 series temporales se han generado aproximadamente 9000 veces, y los PSD y CSD estimados, promediados sobre todas estas realizaciones, se representan a continuación con los analíticos. Si bien las formas generales están de acuerdo, hay características ruidosas notables en ciertas frecuencias en los CSD (Fig.2). Después de un primer plano alrededor de los picos en los PSD (Fig.3), noté que los PSD en realidad están subestimados, y que las características ruidosas en los CSD ocurren casi a las mismas frecuencias que los picos en los PSD. No creo que esto sea una coincidencia, y que de alguna manera el poder se está filtrando de los PSD a los CSD. Hubiera esperado que las curvas estuvieran una encima de la otra, con tantas realizaciones de los datos.