2.3 Reconstrucción, sobre-muestreo, sub-muestreo y Nyquist

[ reconstrucción ] [ sobre-muestreo ] [ sub-muestreo ] [ algoritmo ] [ gráfica ]
..

1. Muestreo y Reconstrucción

La salida de un convertidor contínuo a discreto C-D es una señal discreta en tiempo con un número infinito de alias. Al observar un gráfico de espectro de frecuencias ω es necesario recordar que existen más señales fuera del intervalo mostrado.

Considere una señal x(t) simple, de una sola frecuencia, cuyo espectro contiene dos líneas a ±ω0 con amplitudes de (1/2 A e±ω0)

x(t) = A cos\Big(\omega_0 t + \varphi \Big) x[n] = x(n/f_s) A cos((\omega_0 /f_s)*n + \varphi = \frac{1}{2}A e^{j\varphi}e^{j(\omega_0/f_s)n} + \frac{1}{2}A e^{-j\varphi}e^{j(-\omega_0/f_s)n}

también tiene dos frecuencias ω en ±ω0/fs, con todos los alias en
±ω0/fs + 2πk, siendo k= ±1, ±2, ±3, …

[ reconstrucción ] [ sobre-muestreo ] [ sub-muestreo ] [ algoritmo ] [ gráfica ]
..


2. Sobre-muestreo

Referencia: McClellan ejercicio 4.5 p133

Realice el espectro contínuo de la señal x(t), la señal muestreada a fs=500 y el espectro discreto resultante. Observe que la frecuencia de muestreo es superior a la frecuencia de la señal x(t) para evitar el problema de aliasing. El proceso se conoce como sobre-muestreo. Para el ejercicio se usa una frecuencia 2.5 veces la recomendada por Nyquist.

x(t) = cos\Big(2\pi (100) t + \pi /3 \Big)

espectro n coseno sobremuestreo

Los convertidores D-C trasforman el espectro discreto en tiempo a una salida de espectro contínuo en el tiempo, seleccionando solo un par de las líneas de todas las posibilidades mostradas.

Para ser consistentes con la operación Digital hacia Analógico (D-A) se tomará siempre la frecuencia mas baja de cada grupo de alias o alias principal con |ω|<π.
El resultado siempre se encontrará entre [-fs/2, +fs/2].

En resumen, en sobre-muestreo la frecuencia original f0 es menor que fs/2 permite la reconstrucción mas exacta.

Para el análisis con el algoritmo se obtienen los siguientes parámetros para realizar la gráfica.

señal(t):   cos(100*t*(2*pi) + pi/3)
 espectro:
  freq : [-100.  100.]
  ampl : [1/2 1/2]
  fase : [-pi/3 pi/3]
  etiq : ['$\\frac{1}{2}$$ e^j(- \\frac{\\pi}{3})$'
 '$\\frac{1}{2}$$ e^j(\\frac{\\pi}{3})$']
  freq_max : 100.0
  freq_min : 100.0
  x_expand : cos(2*pi*n/5 + pi/3)

muestreo_tipo:  sobre-muestreo
muestreo: 11
 tamaño ki: 11 ; tamaño ti: 121
fs: 500  ; dt: 0.002  ; fs_veces: 12
x(t): cos(100*t*(2*pi) + pi/3)
x[n]: cos(n*(2*pi)/5 + pi/3)

señal[n]:   cos(n*(2*pi)/5 + pi/3)
 espectro_n:
  freq : [-2.4 -1.6 -0.4  0.4  1.6  2.4]
  ampl : [1/2 1/2 1/2 1/2 1/2 1/2]
  fase : [-pi/3 pi/3 -pi/3 pi/3 -pi/3 pi/3]
  etiq : ['$\\frac{1}{2}$$ e^j(\\frac{\\pi}{3})$'
 '$\\frac{1}{2}$$ e^j(- \\frac{\\pi}{3})$'
 '$\\frac{1}{2}$$ e^j(- \\frac{\\pi}{3})$'
 '$\\frac{1}{2}$$ e^j(\\frac{\\pi}{3})$'
 '$\\frac{1}{2}$$ e^j(- \\frac{\\pi}{3})$'
 '$\\frac{1}{2}$$ e^j(\\frac{\\pi}{3})$']
  freq_factor : pi
  freq_max : 2.4
  freq_min : 0.4
  BW : 2.0
  freq_alias : [ True  True False False  True  True]

señal reconstruida con alias principal:
cos(100*t*(2*pi) + pi/3)

Las instrucciones del bloque de ingreso para el algoritmo son:

# INGRESO
# usar np.pi para evitar frecuencia simplificada por sympy.
x = sym.cos(DosPi*100*t + pi/3)
x_etiqueta = 'x(t)' ; x_etiqueta_n = 'x[n]'

# señal muestreada
fs = 500  # muestreo discreto, freq_sampling
fs_veces = 12 # sobremuestreo para x(t)
muestrasN = 10 + 1  # para x[n]

aliasn = 1 # alias añadidos a x[n]
tolera = 1e-9 # tolerancia a error, números iguales

[ reconstrucción ] [ sobre-muestreo ] [ sub-muestreo ] [ algoritmo ] [ gráfica ]
..


3. Sub-muestreo y aliasing

Cuando fs < 2f0 la señal se encuentra sub-muestreada y se produce el aliasing. Para el ejercicio anterior, si el ejercicio lo realiza con fs = 80Hz, siendo f0=100 Hz, la tasa de muestreo es menor que la frecuencia de Nyquist y se presenta la señal de alias.

espectro n coseno submuestreo

las instrucciones para el bloque de ingreso del algoritmo son las mismas que en el ejercicio anterior, excepto que fs = 80 Hz

# señal muestreada
fs = 80  # muestreo discreto, freq_sampling

el resultado con el algoritmo es una señal reconstruida de tan solo 20Hz

x_0(t) = cos\Big(2\pi (20) t + \pi /3 \Big)
señal(t):   cos(100*t*(2*pi) + pi/3)
 espectro:
  freq : [-100.  100.]
  ampl : [1/2 1/2]
  fase : [-pi/3 pi/3]
  etiq : ['$\\frac{1}{2}$$ e^j(- \\frac{\\pi}{3})$'
 '$\\frac{1}{2}$$ e^j(\\frac{\\pi}{3})$']
  freq_max : 100.0
  freq_min : 100.0
  x_expand : cos(5*pi*n/2 + pi/3)

muestreo_tipo:  sub-muestreo
muestreo: 11
 tamaño ki: 11 ; tamaño ti: 121
fs: 80  ; dt: 0.0125  ; fs_veces: 12
x(t): cos(100*t*(2*pi) + pi/3)
x[n]: cos(5*n*(2*pi)/4 + pi/3)

señal[n]:   cos(5*n*(2*pi)/4 + pi/3)
 espectro_n:
  freq : [-4.5 -2.5 -0.5  0.5  2.5  4.5]
  ampl : [1/2 1/2 1/2 1/2 1/2 1/2]
  fase : [-pi/3 -pi/3 -pi/3 pi/3 pi/3 pi/3]
  etiq : ['$\\frac{1}{2}$$ e^j(\\frac{\\pi}{3})$'
 '$\\frac{1}{2}$$ e^j(- \\frac{\\pi}{3})$'
 '$\\frac{1}{2}$$ e^j(- \\frac{\\pi}{3})$'
 '$\\frac{1}{2}$$ e^j(- \\frac{\\pi}{3})$'
 '$\\frac{1}{2}$$ e^j(\\frac{\\pi}{3})$'
 '$\\frac{1}{2}$$ e^j(\\frac{\\pi}{3})$']
  freq_factor : pi
  freq_max : 4.5
  freq_min : 0.5
  BW : 4.0
  freq_alias : [ True  True False False  True  True]

señal reconstruida con alias principal:
cos(20*t*(2*pi) + pi/3)

[ reconstrucción ] [ sobre-muestreo ] [ sub-muestreo ] [ algoritmo ] [ gráfica ]
..


4. Folding o plegado de señal por sub-muestreo

Para una señal x(t) de 100 Hz se aplica muestreo insuficiente en el intervalo [f0,2f0] se presenta aliasing denominado «folding» o plegado de señal. Para el ejercicio de prueba y con fs = 125, se tiene:

x(t) = cos\Big(2\pi (100) t + \pi /3 \Big)

espectro n coseno folding
Los componentes de frecuencia entre ±π también son ±0.4π, sin embargo  el componente en +0.4π es un alias del componente de la frecuencia negativa -1.6π, que genera el «plegado» o «folding». La reconstrucción de la señal se realiza a f = 0.4(fs/2π) = fs/5 = 25 Hz. También se observa una inversión de fase. El resultado es semejante a una señal muestreada de 25 Hz con inversión de fase.

señal(t):   cos(100*t*(2*pi) + pi/3)
 espectro:
  freq : [-100.  100.]
  ampl : [1/2 1/2]
  fase : [-pi/3 pi/3]
  etiq : ['$\\frac{1}{2}$$ e^j(- \\frac{\\pi}{3})$'
 '$\\frac{1}{2}$$ e^j(\\frac{\\pi}{3})$']
  freq_max : 100.0
  freq_min : 100.0
  x_expand : cos(8*pi*n/5 + pi/3)

muestreo_tipo:  sub-muestreo y folding
muestreo: 11
 tamaño ki: 11 ; tamaño ti: 121
fs: 125  ; dt: 0.008  ; fs_veces: 12
x(t): cos(100*t*(2*pi) + pi/3)
x[n]: cos(4*n*(2*pi)/5 + pi/3)

señal[n]:   cos(4*n*(2*pi)/5 + pi/3)
 espectro_n:
  freq : [-3.6 -1.6 -0.4  0.4  1.6  3.6]
  ampl : [1/2 1/2 1/2 1/2 1/2 1/2]
  fase : [-pi/3 -pi/3 pi/3 -pi/3 pi/3 pi/3]
  etiq : ['$\\frac{1}{2}$$ e^j(\\frac{\\pi}{3})$'
 '$\\frac{1}{2}$$ e^j(- \\frac{\\pi}{3})$'
 '$\\frac{1}{2}$$ e^j(- \\frac{\\pi}{3})$'
 '$\\frac{1}{2}$$ e^j(- \\frac{\\pi}{3})$'
 '$\\frac{1}{2}$$ e^j(\\frac{\\pi}{3})$'
 '$\\frac{1}{2}$$ e^j(\\frac{\\pi}{3})$']
  freq_factor : pi
  freq_max : 3.6
  freq_min : 0.3999999999999999
  BW : 3.2
  freq_alias : [ True  True False False  True  True]

señal reconstruida con alias principal:
cos(25.0*t*(2*pi) - (pi/3))

Otro caso a observar es cuando fs=100Hz, el resultado es una constante, pues siempre se obtiene la muestra de la señal con la misma magnitud.

espectro n coseno folding02

5. Algoritmo en Python

El algoritmo contiene componentes desarrollados en secciones anteriores. Para el espectro de frecuencias en dominio del tiempo se basa en lo descrito en Espectro – Operaciones en dominio de tiempo y frecuencia.

El desarrollo del muestreo usando la señal x(t) y fs toma como referencia lo descrito en Muestreo y aliasing.

La construcción del espectro discreto a partir de x[n] usa lo desarrollado en Espectro x[n] – Señal discreta en tiempo

# ejercicio 4.5 p133 Muestreo y Reconstrucción de señales sinusoidales
# telg1034 DSP fiec-espol edelros@espol.edu.ec
import numpy as np
import matplotlib.pyplot as plt
import sympy as sym
import telg1034 as dsp

# variables continuas
from telg1034 import t,A,w,f,p,pi,DosPi,I,equivalentes
# variables discretas
from telg1034 import n

# INGRESO
# usar np.pi para evitar frecuencia simplificada por sympy.
x = sym.cos(DosPi*100*t + pi/3)
x_etiqueta = 'x(t)' ; x_etiqueta_n = 'x[n]'

# señal muestreada
fs = 500  # muestreo discreto, freq_sampling
fs_veces = 12 # sobremuestreo para x(t)
muestrasN = 10 + 1  # para x[n]

aliasn = 1 # alias añadidos a x[n]
tolera = 1e-9 # tolerancia a error, números iguales

# PROCEDIMIENTO ----------------
# operaciones para espectro x(t)
# Ref: Blog|Espectro-Operaciones en dominio de tiempo y frecuencia
x_term = dsp.x_list_term_Add(x)
Xe_term = dsp.cos_to_euler_one_term(x_term)
x_term_expand = dsp.euler_to_cos_list(Xe_term)
un_espectro = dsp.cos_spectrum_list(x_term)
# espectro de cada señal
freq = un_espectro['freq']
ampl = un_espectro['ampl']
fase = un_espectro['fase']
etiqueta = un_espectro['etiq']

# freq_max y freq_min
freq_max = float(max(freq))
if len(freq[freq>0])>0:
    freq_min = float(min(freq[freq>0]))
else:
    freq_min = 0
un_espectro['freq_max'] = freq_max
un_espectro['freq_min'] = freq_min

# revisa muestreo Nyquist
if freq_max<=(fs/2):
    muestreo_tipo = 'sobre-muestreo'
if freq_max>(fs):
    muestreo_tipo = 'sub-muestreo'
if freq_max>(fs/2) and freq_max<=fs:
    muestreo_tipo = 'sub-muestreo y folding'
    
# Muestreo de señales sinusoidales
# Ref: blog|2.1 Muestreo y aliasing
xn = x.subs(t,n/fs)
# muestreo x[n]
dt = 1/fs 
ki = np.arange(0,muestrasN,1,dtype=int)
# muestreo x(t)
dtc = dt/fs_veces # para x(t)
ti = np.arange(0,ki[muestrasN-1]*dt + dtc, dtc)
tki = ki*dt

xt = sym.lambdify(t,x, modules=equivalentes)
xki = xt(tki) 
xti = xt(ti)

# operaciones para espectro x[n]
# Ref: blog|2.2 Espectro x[n] – Señal discreta en tiempo
x_term = dsp.x_list_term_Add(xn)
Xe_term = dsp.cos_to_euler_one_term(x_term)
x_term_expand = dsp.euler_to_cos_list(Xe_term)
un_espectro_n = dsp.cos_spectrum_list(x_term)
# espectro de cada señal
freq_n = np.array(un_espectro_n['freq'])
ampl_n = np.array(un_espectro_n['ampl'])
fase_n = np.array(un_espectro_n['fase'])
etiq_n = np.array(un_espectro_n['etiq'])
un_espectro['x_expand'] = x_term_expand

# Añadir alias a señal
freqn_conteo = len(freq_n)
freqn_alias = np.zeros(freqn_conteo,dtype=bool)
for i in range(0,freqn_conteo,1):
    freq_i = freq_n[i]
    ampl_i = ampl_n[i]
    fase_i = fase_n[i]
    etiq_i = etiq_n[i]
    for k in range(1,aliasn+1,1):
        # añade freqm[i] + 2*pi*k
        freq_n = np.concatenate([freq_n,[freq_i+2*pi*k]])
        ampl_n = np.concatenate([ampl_n,[ampl_i]])
        fase_n = np.concatenate([fase_n,[fase_i]])
        etiq_n = np.concatenate([etiq_n,[etiq_i]])
        freqn_alias = np.concatenate([freqn_alias,[True]])
        # añade freqm[i] - 2*pi*k
        freq_n = np.concatenate([freq_n,[-(freq_i.evalf()+2*np.pi*k)]])
        ampl_n = np.concatenate([ampl_n,[ampl_i]])
        fase_n = np.concatenate([fase_n,[-fase_i]])
        texto = '$' + sym.latex(ampl_i)+'$'
        if fase_i!=sym.S.Zero:
            texto = texto+f'$ e^j('+sym.latex(fase_i)+')$'
        etiq_n = np.concatenate([etiq_n,[texto]])
        freqn_alias = np.concatenate([freqn_alias,[True]])
# ordena frecuencias
orden = np.argsort(freq_n)
freq_n = freq_n[orden]
ampl_n = ampl_n[orden]
fase_n = fase_n[orden]
etiq_n = etiq_n[orden]
freqn_alias = freqn_alias[orden]

# revisa factor pi en freq_n
unfactor = sym.S.One ; factor_pi = False
freqn_conteo = len(freq_n)
for i in range(0,freqn_conteo,1):
    if freq_n[i].has(pi):
        factor_pi = True
if factor_pi:
    freq_n = np.array(freq_n/pi,dtype=float)
    unfactor = pi
# actualiza espectro de señal con factor pi,alias,orden
un_espectro_n['freq_factor'] = unfactor
un_espectro_n['freq'] = freq_n
un_espectro_n['ampl'] = ampl_n
un_espectro_n['fase'] = fase_n
un_espectro_n['etiq'] = etiq_n

# ancho de banda, freq_max y freq_min
freq_posi = freq_n[freq_n>0]
freq_max_n = float(max(freq_posi))
freq_min_n = 0
if len(freq_posi)>0:
    freq_min_n = float(min(freq_posi))
freq_centro_n = (freq_max_n + freq_min_n)/2
un_espectro_n['freq_max'] = freq_max_n
un_espectro_n['freq_min'] = freq_min_n
un_espectro_n['BW'] = freq_max_n - freq_min_n
    
# aliasing, Revisa espectro de frecuencias  
freq_conteo = len(freq_n)
freq_alias = np.zeros(freq_conteo,dtype=bool)
for i in range (0,freq_conteo,1):
    unafreq = freq_n[i]
    k = 1
    unalias = unafreq + k*2
    while unalias<=freq_max_n:
        for j in range(i+1,freq_conteo,1): 
            if abs(unalias - freq_n[j])<tolera:
                if abs(freq_n[i])>abs(freq_n[j]):
                    freq_alias[i] = True
                else:
                    freq_alias[j] = True
        k = k+1
        unalias = unafreq + k*2
freq_alias_conteo = np.sum(freq_alias)
un_espectro_n['freq_alias'] = freq_alias

# reconstruye señal sin alias
freq_alias0 = np.invert(freq_alias)
freq_x0 = freq_n[freq_alias0]
ampl_x0 = ampl_n[freq_alias0]
fase_x0 = fase_n[freq_alias0]
x0_cuenta = len(freq_x0)
x0t = sym.S.Zero
for i in range(0,x0_cuenta,1):
    if freq_x0[i]>=0:
        una_freq = freq_x0[i]*unfactor*(fs/(2*pi))
        una_freq = dsp._float_is_int(una_freq) # si es entero
        x0t = x0t + 2*ampl_x0[i]*sym.cos(DosPi*una_freq*t+sym.UnevaluatedExpr(fase_x0[i]))        
x0tr = x0t.subs(pi,np.pi)
x0tr = sym.lambdify(t,x0tr,modules=equivalentes)
x0_ti = x0tr(ti)

# SALIDA
print('señal(t):  ',x)
print(' espectro:')
for parametro in un_espectro:
    print(' ',parametro,':',un_espectro[parametro])

print('\nmuestreo_tipo: ',muestreo_tipo)
print('muestreo:',muestrasN)
print(' tamaño ki:',len(ki),'; tamaño ti:',len(ti))
print('fs:',fs,' ; dt:',dt, ' ; fs_veces:',fs_veces)
print('x(t):',x)
print('x[n]:',xn)

print('\nseñal[n]:  ',xn)
print(' espectro_n:')
for parametro in un_espectro_n:
    print(' ',parametro,':',un_espectro_n[parametro])

print('\nseñal reconstruida con alias principal:')
print(x0t)

[ reconstrucción ] [ sobre-muestreo ] [ sub-muestreo ] [ algoritmo ] [ gráfica ]
..


6. Gráfica

Con los componentes descritos en la parte de algoritmos, se ajustan las gráficas de cada sección para cada sub-gráfica en la figura.

# GRAFICAS  ----------------------------------------
# Grafica de espectro de frecuencias
# Ref: blog|2.2 Espectro x[n] – Señal discreta en tiempo
graf_dx = 0.12
fig_espectro = plt.figure()
ampl_max = float(max(ampl))
freq_max = float(max(max(freq),fs))*(1+graf_dx/2)
graf_fasor = fig_espectro.add_subplot(311)
if freq_max!=0:
    graf_fasor.set_xlim([-freq_max*(1+graf_dx),
                         freq_max*(1+graf_dx)])
else:
    graf_fasor.set_xlim([-1,1])
graf_fasor.set_ylim([0,ampl_max*(1+graf_dx*2)])
graf_fasor.grid()
graf_fasor.axhline(0,color='black')
graf_fasor.axvline(0,linestyle='dotted',color='grey')
graf_fasor.stem(freq,ampl) # grafica magnitud
for k in range(0,len(freq),1): # etiquetas de fasor
    graf_fasor.annotate(etiqueta[k],xy=(freq[k],ampl[k]),
        xytext=(0,5),textcoords='offset points',ha='center')
# fs frecuencia de muestreo
graf_fasor.stem(fs,ampl_max/2,linefmt = 'C3:', ) # fs
graf_fasor.annotate('fs',xy=(fs,ampl_max/2),
        xytext=(0,5),textcoords='offset points',ha='center')
graf_fasor.set_ylabel('Magnitud '+x_etiqueta)
graf_fasor.set_xlabel('freq Hz', labelpad=0,)
texto = '$' + sym.latex(x)+'$' +'  ; fs='+str(fs)
texto = texto + '  ; alias='+str(aliasn)
graf_fasor.set_title(texto)

# GRAFICA x(t), x[n], x(t)_alias0
graf_xt = fig_espectro.add_subplot(312)
graf_xt.plot(ti,xti, color='gray',label='x(t)')
graf_xt.plot(ti,x0_ti, color='blue',label='x(t)_alias0',
             linestyle='dotted')
graf_xt.stem(tki,xki,linefmt = 'C0:',label='x[n]')
graf_xt.set_xlabel('t')
graf_xt.set_ylabel('amplitud')
graf_xt.legend()
graf_xt.grid()

# GRAFICAS de espectro de frecuencias discretas---------
# Ref: blog|2.2 Espectro x[n] – Señal discreta en tiempo
freq_n = un_espectro_n['freq']
ampl_n = un_espectro_n['ampl']
etiq_n = un_espectro_n['etiq']
freq_alias = un_espectro_n['freq_alias']
ampl_max = float(max(ampl_n))*(1+graf_dx*2)
freq_max = float(max(un_espectro_n['freq_max'],1))*(1+graf_dx/2)
# grafica
graf_fasorn = fig_espectro.add_subplot(313)
graf_fasorn.set_xlim([-freq_max,freq_max])
graf_fasorn.set_ylim([min([min(ampl_n),0]),ampl_max])
graf_fasorn.grid()
graf_fasorn.axhline(0,color='black')
graf_fasorn.axvline(0,linestyle='dotted',color='grey')
# grafica magnitud
freq_noalias = np.invert(freq_alias) 
graf_fasorn.stem(freq_n[freq_noalias],ampl_n[freq_noalias],
                 linefmt = 'C0--')
graf_fasorn.stem(freq_n[freq_alias],ampl_n[freq_alias],
                 linefmt = 'C1--',label = 'alias') 
if un_espectro_n['freq_factor'] != sym.S.One:
    unfactor = r'$'+sym.latex(un_espectro_n['freq_factor'])+'$'
    freq_etiq = []
    for un_num in freq_n:
        freq_etiq.append(f''+str(round(un_num,4))+unfactor)
    graf_fasorn.set_xticks(ticks=freq_n,labels=freq_etiq)
for k in range(0,len(freq_n),1): # etiquetas de fasor
    graf_fasorn.annotate(etiq_n[k],xy=(freq_n[k],ampl_n[k]),
                 xytext=(0,5),textcoords='offset points',
                 ha='center')
graf_fasorn.set_ylabel('Magnitud '+x_etiqueta_n)
graf_fasorn.set_xlabel('freq rad')
graf_fasorn.legend()

plt.tight_layout()
plt.show()

[ reconstrucción ] [ sobre-muestreo ] [ sub-muestreo ] [ algoritmo ] [ gráfica ]

2.2 Espectro x[n] – Señal discreta en tiempo

[ espectro_n ] [ algoritmo ] [ gráfica ]
..


1. Espectro de frecuencias de x[n]

Referencia: McClellan 4.1.4 p129

De forma semejante al Espectro para Operaciones en dominio de tiempo y frecuencia, se aplica el concepto de frecuencias en radiantes. El espectro permitirá analizar las señales alias: alias principal junto a otros alias generados con ±2π en cada frecuencia. Por ejemplo, para:

x1[n] = cos(0.4π n)

Es señal básica con frecuencia en radianes en 0.4π, sería alias principal, con componentes en ±0.4π. En el dominio discreto ‘n‘, las señales alias se forman con cada componente desplazados en 2πk a la derecha y -2πk a la izquierda. Siendo k un número entero. Para k=1.

Señal / alias principal otros alias, k=1
ω1[n] = 0.4π ω3[n] = 0.4π + 2π = 2.4
ω4[n] = 0.4π – 2π = -1.6
ω2[n] = – 0.4π ω5[n] = -0.4π + 2π = 1.6
ω6[n] = -0.4π – 2π = -2.4

el espectro con señales alias para k=1 es:

espectro n coseno

[ espectro_n ] [ algoritmo ] [ gráfica ]

..


2. Algoritmo en Python

El punto de partida es el algoritmo usado en Espectro para Operaciones en dominio de tiempo y frecuencia, donde las operaciones son semejantes y aplicadas al dominio ‘t‘, que deben cambiarse al domino ‘n‘. Se aplica un cambio de variable y una bandera ‘esdiscreta‘ para ajustar el eje de frecuencias a radianes en el análisis de la función cos_spectrum_list(x_senales) del archivo telg1034.py. con lo que se logra obtener básicamente los resultados del espectro ajustados al nuevo dominio

    #...
    for i in range(0,x_conteo,1):
        unasenal = x_senales[i]
        
        # revisa si unasenal es discreta con n
        esdiscreta = False
        varindepend = unasenal.free_symbols
        if (n in varindepend) and not(t in varindepend):
            unasenal = unasenal.subs(n,t)
            esdiscreta = True
        # analiza unasenal con t
        #...

cuando la señal es discreta, las frecuencias consideran el factor 2π, que se separaba cuando se calculaban en Hz.

            if esdiscreta:
                freq_k = freq_k*2*pi
            x_freq_spectr.append(freq_k)

con lo que el algoritmo se ajusta a la variable y es funcional.

Para la sección gráfica, se puede considerar el factor π como un elemento a simplificar en el vector de frecuencias. Se revisa si existe el factor en el arreglo de frecuencias y se procede a separarlo dentro del algoritmo de espectro para t. Se actualizan los valores en los resultados.

...
# revisa factor pi en freq_n
unfactor = sym.S.One ; factor_pi = False
freqn_conteo = len(freq_n)
for i in range(0,freqn_conteo,1):
    if freq_n[i].has(pi):
        factor_pi = True
if factor_pi:
    freq_n = np.array(freq_n/pi,dtype=float)
    unfactor = pi
# actualiza espectro de señal con factor pi
un_espectro_n['freq_factor'] = unfactor
un_espectro_n['freq'] = freq_n
...

2.1 Revisión de señales «alias» en x_senales en algoritmo para espectro

Para tener un marcador de frecuencias alias en las señales analizadas, se compara cada frecuencia del espectro para diferentes valores de k en la operación ±2π, mientras no sobrepase la frecuencia máxima del intervalo de freq.

...
# aliasing, Revisa espectro de frecuencias 
freq_conteo = len(freq_n)
freq_alias  = np.zeros(freq_conteo,dtype=bool)
for i in range (0,freq_conteo,1):
    unafreq = freq_n[i]
    k = 1
    unalias = unafreq + k*2
    while unalias<=freq_max:
        for j in range(i+1,freq_conteo,1):
            if abs(unalias - freq_n[j])<tolera:
                if abs(freq_n[i])>abs(freq_n[j]):
                    freq_alias[i] = True
                else:
                    freq_alias[j] = True
        k = k+1
        unalias = unafreq + k*2
un_espectro_n['freq_alias'] = freq_alias
...

Se marcan las frecuencias alias en la lista freq_alias para diferenciarlas con otro color en la gráfica de espectro, obteniendo el resultado presentado en el ejercicio.

[ espectro_n ] [ algoritmo ] [ gráfica ]

2.2 Instrucciones en Python

La señal para el espectro se construye con los componentes alias presentados en la parte teórica. Se analiza solo la señal x4[n] para el espectro.

El parámetro tolera en el bloque de ingreso es la tolerancia para redondear los valores de frecuencia en la gráfica cuando tienen varios dígitos decimales.

# ejercicio 4.4 p130 Espectro de señales senoidales discretas
# telg1034 DSP fiec-espol edelros@espol.edu.ec
import numpy as np
import matplotlib.pyplot as plt
import sympy as sym
import telg1034 as dsp

# variables continuas
from telg1034 import t,A,w,f,p,pi,DosPi,I,equivalentes
# variables discretas
from telg1034 import n

# INGRESO
# usar np.pi para evitar frecuencia simplificada por sympy
x1 = sym.cos(0.4*pi*n)
x2 = sym.cos(1.6*np.pi*n)
x3 = sym.cos(2.4*np.pi*n)
xn = x1+x2+x3
x_etiqueta_n = 'x[n]'

fs = 60  # muestreo discreto, freq_sampling
fs_veces = 12 # sobremuestreo para x(t)
muestrasN = 6 + 1  # para x[n]

tolera = 1e-9 # tolerancia a error, números iguales
# PROCEDIMIENTO
# espectro x[n]
# Ref: Blog|Espectro-Operaciones en dominio de tiempo y frecuencia
x_term = dsp.x_list_term_Add(xn)
Xe_term = dsp.cos_to_euler_one_term(x_term)
x_term_expand = dsp.euler_to_cos_list(Xe_term)
un_espectro_n = dsp.cos_spectrum_list(x_term)
un_espectro_n['x_expand'] = x_term_expand
# espectro de cada señal
freq_n = un_espectro_n['freq']
ampl_n = un_espectro_n['ampl']
fase_n = un_espectro_n['fase']
etiq_n = np.array(un_espectro_n['etiq'])

# revisa factor pi en freq_n
unfactor = sym.S.One ; factor_pi = False
freqn_conteo = len(freq_n)
for i in range(0,freqn_conteo,1):
    if freq_n[i].has(pi):
        factor_pi = True
if factor_pi:
    freq_n = np.array(freq_n/pi,dtype=float)
    unfactor = pi
# actualiza espectro de señal con factor pi
un_espectro_n['freq_factor'] = unfactor
un_espectro_n['freq'] = freq_n

# ancho de banda, freq_max y freq_min
freq_posi = freq_n[freq_n>0]
freq_max = float(max(freq_posi))
freq_min = 0
if len(freq_posi)>0:
    freq_min = float(min(freq_posi))
freq_centro = (freq_max+freq_min)/2
un_espectro_n['freq_max'] = freq_max
un_espectro_n['freq_min'] = freq_min
un_espectro_n['BW'] = freq_max-freq_min

# aliasing, Revisa espectro de frecuencias 
freq_conteo = len(freq_n)
freq_alias  = np.zeros(freq_conteo,dtype=bool)
for i in range (0,freq_conteo,1):
    unafreq = freq_n[i]
    k = 1
    unalias = unafreq + k*2
    while unalias<=freq_max:
        for j in range(i+1,freq_conteo,1):
            if abs(unalias - freq_n[j])<tolera:
                if abs(freq_n[i])>abs(freq_n[j]):
                    freq_alias[i] = True
                else:
                    freq_alias[j] = True
        k = k+1
        unalias = unafreq + k*2
un_espectro_n['freq_alias'] = freq_alias

# SALIDA
print('senal[n]:  ',xn)
print(' espectro:')
for parametro in un_espectro_n:
    print(' ',parametro,':',un_espectro_n[parametro])

[ espectro_n ] [ algoritmo ] [ gráfica ]
..


3. Gráfica espectro dominio n

Se añaden la gráfica para el espectro de frecuencias ajustada al dominio n

# GRAFICAS de espectro de frecuencias discretas---------
graf_dx = 0.12
fig_espectro = plt.figure()
freq_alias_conteo = np.sum(freq_alias)
ampl_max = float(max(ampl_n))*(1+graf_dx*2)
freq_max = float(max(un_espectro_n['freq_max'],1))*(1+graf_dx/2)
# grafica
graf_fasorn = fig_espectro.add_subplot(111)
graf_fasorn.set_xlim([-freq_max,freq_max])
graf_fasorn.set_ylim([min([min(ampl_n),0]),ampl_max])
graf_fasorn.grid()
graf_fasorn.axhline(0,color='black')
graf_fasorn.axvline(0,linestyle='dotted',color='grey')
# grafica magnitud
freq_noalias = np.invert(freq_alias) 
graf_fasorn.stem(freq_n[freq_noalias],ampl_n[freq_noalias],
                 linefmt = 'C0--')
if freq_alias_conteo>0: # hay alias
    graf_fasorn.stem(freq_n[freq_alias],ampl_n[freq_alias],
                     linefmt = 'C1--', label='alias') 
if un_espectro_n['freq_factor'] != sym.S.One:
    unfactor = r'$'+sym.latex(un_espectro_n['freq_factor'])+'$'
    freq_etiq = []
    for un_num in freq_n:
        freq_etiq.append(f''+str(round(un_num,4))+unfactor)
    graf_fasorn.set_xticks(ticks=freq_n,labels=freq_etiq)
for k in range(0,len(freq_n),1): # etiquetas de fasor
    graf_fasorn.annotate(etiq_n[k],xy=(freq_n[k],ampl_n[k]),
                         xytext=(0,5),textcoords='offset points',
                         ha='center')
graf_fasorn.set_ylabel(x_etiqueta_n)
graf_fasorn.set_xlabel('freq rad')
graf_fasorn.legend()
texto = '$' + sym.latex(xn)+'$' +'  ; fs='+str(fs)
graf_fasorn.set_title(texto)

plt.show()

[ espectro_n ] [ algoritmo ] [ gráfica ]

2.1 Muestreo y aliasing

[ muestras ] [ alias ] [ ejemplo ] [ algoritmo ] [ ejercicio alias ]
..


1. Muestras de una señal

Referencia: McClellan 4.1 p123

Una señal discreta en tiempo x[n], representa como una secuencia ordenada de números que puede ser almacenada para su uso y procesamiento posterior. Las muestras de una señal contínua o analógica x(t) se encuentran espaciadas en el tiempo separadas por un tiempo Ts.

x[n] = x(nTs) =x(n/fs)   ; -∞ <n < ∞

Cada valor de x[n] se denomina una muestra de la señal contínua x(t). Ts  también se expresa como frecuencia de muestreo fs = 1/Ts  en muestras/s.

1.1 Muestreo de una señal sinusoidal

Para obtener muestras de una señal x(t) = A cos(ωt+φ) se tiene que:

x[n] = x(nTs) = A cos(ωnTs +φ)
= A cos( (ωTs )n+φ)
= A cos(ωrad n+φ)

donde se tiene la frecuencia en radianes normalizada.
La frecuencia normalizada es un parámetro adimensional al quitar la unidad de tiempo de x(t), siendo ‘n‘ el índice de posición en la secuencia de muestras.

1.2. Ejemplo

Referencia: McClellan Figura 4.3 p126

Una señal tipo coseno de 100 Hz se toman muestras con una tasa fs = 500 muestras/s. Realice y observe las gráficas de t y n correspondientes

 x(t) = cos(2π(100)t)

muestreo: 11
 tamaño ki: 11
 tamaño ti: 101
fs: 500  ; dt: 0.002  ; fs_veces: 10
x(t): cos(100*t*(2*pi))
x[n]: cos(n*(2*pi)/5)

De la gráfica se puede observar que sin sobreponer la forma de onda x(t) sobre x[n] no es sencillo discernir la forma exacta de la forma de onda original. Para una mejor observación se sobre-muestrea 10 veces la señal para que sea más semejante a la forma contínua de x(t).

muestreo coseno 100Hz 011.3 Algoritmo

# ejercicio figura 4.1 p126 Muestreo de señales sinusoidales
# telg1034 DSP fiec-espol edelros@espol.edu.ec
import numpy as np
import matplotlib.pyplot as plt
import sympy as sym
import telg1034 as dsp
# variables continuas
from telg1034 import t,A,w,f,p,pi,DosPi,I,equivalentes
# variables discretas
n = sym.Symbol('n', integer=True, nonnegative=True)

# INGRESO
# señal continua
x = sym.cos(100*DosPi*t + 0)

fs = 500  # muestreo discreto
fs_veces = 10 # sobremuestreo para x(t)
muestrasN = 10 + 1  # para x[n]

# PROCEDIMIENTO
xn = x.subs(t,n/fs)
# muestreo x[n]
dt = 1/fs 
ki = np.arange(0,muestrasN,1,dtype=int)
# muestreo x(t)
dtc = dt/fs_veces # para x(t)
ti = np.arange(0,ki[muestrasN-1]*dt + dtc, dtc)
tki = ki*dt

xt = sym.lambdify(t,x, modules=equivalentes)
xki = xt(tki) 
xti = xt(ti)

# SALIDA
print('muestreo:',muestrasN)
print(' tamaño ki:',len(ki))
print(' tamaño ti:',len(ti))
print('fs:',fs,' ; dt:',dt, ' ; fs_veces:',fs_veces)
print('x(t):',x)
print('x[n]:',xn)

# GRAFICA x(t) y x[n]
plt.subplot(211) # x(t) contínua
plt.plot(ti,xti, color='gray',label='x(t)')
plt.stem(tki,xki,linefmt = 'C0:',
         label='x[n]')
plt.xlabel('t')
plt.ylabel('amplitud')
plt.title('x(t) vs x[n]')
plt.legend()

plt.subplot(212) # x[n] discreta
plt.stem(xki,linefmt = 'C0:',label='x[n]')
#plt.xticks(ki)
plt.xlabel('n')
plt.ylabel('amplitud')
plt.legend()
plt.show()

[ muestras ] [ alias ] [ ejemplo ] [ algoritmo ] [ ejercicio alias ]
..


2. Concepto de alias

Una definición simple de «alias» es de dos nombres para dos personas: «Pancho» y «Francisco». Dos señales diferentes en su forma discreta pueden tener la misma secuencia de valores.
..


2.1 Ejemplo

Referencia: McClellan 4.1.2 p127

Considere las siguientes señales y compare sus gráficas.

x1[n] = cos(0.4π n)

x2[n] = cos(2.4π n) = cos(0.4π n + 2π n )  = cos(0.4π n)

dado que 2πn es un número entero de periodos de la señal coseno.

Con Python se realiza las gráficas obteniendo las muestras usando fs para observar los puntos sobre x1(t) de ω=0.4π. Para suavizar la curva x(t) se usa fs_veces como sobremuestreo de la señal y crear la linea azul.
Se compara con x2(t) realizando el mismo proceso, que al unificar las gráficas se observa que los puntos  de muestra son válidos para ambas señales x1(t), x2(t).

muestreo: 13
 tamaño ki: 13
 tamaño ti: 145
fs: 60  ; dt: 0.016666666666666666  ; fs_veces: 12
cos(0.4*pi*n)
cos(7.5398223686155*n)

La gráfica permite mostrar que las muestras son iguales en cada dt=1/fs para ambas señales.

muestreo Alias coseno dos señales
[ muestras ] [ alias ] [ ejemplo ] [ algoritmo ] [ ejercicio alias ]
..


2.2 Instrucciones en Python

 

# ejercicio 4.2 p128 Muestreo de señales sinusoidales
# telg1034 DSP fiec-espol edelros@espol.edu.ec
import numpy as np
import matplotlib.pyplot as plt
import sympy as sym
import telg1034 as dsp

# variables continuas
from telg1034 import t,A,w,f,p,pi,DosPi,I,equivalentes
# variables discretas
from telg1034 import n

# INGRESO
# usar np.pi para evitar frecuencia fundamental automatica de sympy
x1 = sym.cos(0.4*pi*n) 
x2 = sym.cos(2.4*np.pi*n)

x_senales = [x1,x2]
x_etiqueta = ['x1[n]','x2[n]']

fs = 60  # muestreo discreto
fs_veces = 12 # sobremuestreo para x(t)

muestrasN = 12 + 1  # para x[n]

# PROCEDIMIENTO
x_conteo = len(x_senales)
# muestreo x[n]
dt = 1/fs 
ki = np.arange(0,muestrasN,1,dtype=int)
# muestreo x(t)
dtc = dt/fs_veces # para x(t)
ti = np.arange(0,ki[muestrasN-1]*dt+dtc,dtc)
x_muestras = {}
for k in range(0,x_conteo,1):
    unasenal = x_senales[k]
    xtk = unasenal.subs(n,t*fs)
    xt = sym.lambdify(t,xtk, modules=equivalentes)
    xki = xt(ki*dt) 
    xti = xt(ti)
    x_muestras[k] = {'ki':ki,'xki':xki,
                     'ti':ti,'xti':xti,}

# SALIDA
print('muestreo:',muestrasN)
print(' tamaño ki:',len(ki))
print(' tamaño ti:',len(ti))
print('fs:',fs,' ; dt:',dt, ' ; fs_veces:',fs_veces)
for unasenal in x_senales:
    print(unasenal)

# GRAFICA
for i in range(0,len(x_muestras),1):
    color_i = dsp._graf_lineacolor_i(i)
    estilo_i = ':' # estilo de la línea
    if i%2 == 0:  #es impar
        estilo_i = '--'
    # muestreo x[n]
    ki = x_muestras[i]['ki']
    xki = x_muestras[i]['xki']
    puntofmt = color_i + estilo_i
    plt.stem(ki*dt, xki, linefmt = puntofmt,
             label='x'+str(i)+'[n]')
    # muestreo x(t)
    ti = x_muestras[i]['ti']
    xti = x_muestras[i]['xti']
    plt.plot(ti,xti, '-', color = color_i,
             label='x'+str(i)+'(t)')
plt.axhline(0,color='black')
plt.xlabel('t')
plt.ylabel('amplitud')
plt.title('muestreo y alias')
plt.legend()
plt.show()

[ muestras ] [ alias ] [ ejemplo ] [ algoritmo ] [ ejercicio alias ]
..


3. Ejercicios con alias

Referencia: McClellan ejercicio 42 p128

Muestre que x2[n] es un alias de x1[n] y encuentre dos frecuencias que sean alias de 0.4π rad.

x1[n] = 7 cos(0.4π n – 0.2π)

x2[n] = 7 cos(8.4π n – 0.2π) = cos(0.4π n + 4(2π n) – 0.2π )  = cos(0.4π n- 0.2π)

Usando el algoritmo del numeral anterior se realiza la gráfica.

muestreo Alias coseno

Para los alias se usa la frecuencia normalizada en radianes: ωrad , considerando que si se suma varias veces 2π se obtiene un alias.

ωalias= 0.4π + 2π k   ; donde k = 0,1,2,3…

El alias principal se define como la frecuencia única en e intervalo entre
-π <ωalias < π , que para el ejercicio es 0.4π por lo que se propone usar como alias:

x3[n] = 7 cos((0.4π + 2π )n – 0.2π)

x4[n] = 7 cos((0.4π + 2(2π) )n – 0.2π)

muestreoAlias04_cosenoEl bloque de ingreso para el algoritmo del ejercicio es:

# INGRESO
# usar np.pi para evitar frecuencia fundamental automatica
x1 = 7*sym.cos(0.4*np.pi*n - 0.2*pi)
x2 = 7*sym.cos(8.4*np.pi*n - 0.2*pi)
x3 = 7*sym.cos((0.4*np.pi+2*pi*(1))*n - 0.2*pi )
x4 = 7*sym.cos((0.4*np.pi+2*pi*(2))*n - 0.2*pi)

##x_senales = [x1,x2]
##x_etiqueta = ['x1[n]','x2[n]']
x_senales = [x1,x3,x4]
x_etiqueta = ['x1[n]','x3[n]','x4[n]']

fs = 60  # muestreo discreto, freq_sampling
fs_veces = 40 # sobremuestreo para x(t)

muestrasN = 5 + 1  # para x[n]

[ muestras ] [ alias ] [ ejemplo ] [ algoritmo ] [ ejercicio alias ]