2.7 D-to-C – Interpolación con pulso Sinc(t)

[ interpola Sinc ] [ ejemplo ] [ algoritmo ] [ gráfica interactiva ]
..


1. Interpolación Sinc(t)

Referencia: McClellan 4-3.6 p147

La interpolación con pulso Sinc(t) es un pulso relacionado con la Transformada de Fourier Contínua en tiempo. El pulso Sinc(t) es de ancho infinito y su amplitud disminuye al alejarse de cero, pero no llega a cero o la señal se mantiene en cero.

pulso Sinc 01

El valor de p(0)=1 y p(nTs) = 0 para n = ±1, ±2, ±3, … El tipo de reconstrucción D-to-C es llamada interpolación de banda limitada, al ser equivalente a seleccionar los componentes de alias principal.

interpola Pulso Sinc animate

Si el muestreo cumple con las condiciones del teorema del muestreo, la reconstrucción de una onda cosenoidal es idéntica a la señal original que generó las muestras.

Una señal contínua x(t) de banda limitada sin componentes de frecuencia mayores a fmax se puede reconstruir exactamente a partir de las muestras x(n Ts), si las muestras se toman a una tasa de fs=1/Ts que es mayor que 2fmax.

[ interpola Sinc ] [ ejemplo ] [ algoritmo ] [ gráfica interactiva ]

2.1 Algoritmo en Python para un pulso Sinc(t)

El algoritmo es el mismo que para un pulso triangular, actualizando la sección de la función que define el pulso.

# D-to-C, Interpolacion con pulso Sinc(t)
# ejemplo 4.3.4 p144
import numpy as np
import matplotlib.pyplot as plt

# INGRESO
fs = 200  # Hz muestreo
fs_veces = 10 # suavizar x(t), sobremuestreo
titulo = 'pulso Sinc'
nT = 3.0  # graficar periodos de la señal

# pulso Sinc
pulso_causal = True # No causal, pulso centrado en cero
pulso_ancho = 6

# PROCEDIMIENTO
Ts = 1/fs # muestreo periodo
dtc = Ts/fs_veces # suavizar x(t), sobremuestreo

#https://blog.espol.edu.ec/telg1001/senales-escalon-%ce%bct-e-impulso-%ce%b4t/
#u = lambda t: np.piecewise(t,t>=0,[1,0])
u = lambda t: np.heaviside(t,1)
# pulso Sinc
u1 = lambda t: u(t+Ts*pulso_ancho/2)
u2 = lambda t: u(t-Ts*pulso_ancho/2)
sinc = lambda t: (np.sin(np.pi*t/Ts)/(np.pi*t/Ts))*(u1(t) - u2(t))             

# x[n] interpolacion en Ts, a muestreo fs
t_unpulso = np.arange(-Ts*nT,Ts*nT+dtc,dtc)
unpulso = sinc(t_unpulso)

# SALIDA
print('muestras:',len(t_unpulso))
print('t_unpulso:',t_unpulso)

# GRAFICA de un pulso
plt.axhline(0,color='black')
plt.axvline(-Ts,color='red',linestyle='dotted')
plt.axvline(Ts,color='red',linestyle='dotted')

# p(t) componentes
plt.plot(t_unpulso,unpulso, color='green',linestyle='dashed',
         label='p_sinc(t)')
plt.stem([0],[1],linefmt = 'C1:', label='xn[0]')

# grafica entorno
plt.xlabel('t')
plt.ylabel('amplitud')
texto = titulo + ' ; fs='+str(fs)
texto = texto +' ; Ts='+str(1/fs)
plt.title(texto)
plt.grid()
plt.legend()

plt.tight_layout()
plt.show()

[ interpola Sinc ] [ ejemplo ] [ algoritmo ] [ gráfica interactiva ]
..


2. Ejemplo

Desarrollar el ejercicio de pulsos triangulares y su algoritmo para realizar la gráfica de la señal sin(2π(83)t) con interpolación de pulsos Sinc(t)

interpola Pulso Sinc 01[ interpola Sinc ] [ ejemplo ] [ algoritmo ] [ gráfica interactiva ]
..


3. Algoritmo en Python Interpola con  Sinc

# D-to-C, Interpolacion con pulso Sinc(t)
# ejemplo 4.3.4 p144
import numpy as np
import matplotlib.pyplot as plt

# INGRESO
# señal x(t)
f0 = 83   # frecuencia de senal
fase0 = 0 # [0,2*np.pi]
xt = lambda t: np.cos(2*np.pi*f0*t + fase0)

fs = 200  # Hz muestreo
fs_veces = 20 # suavizar x(t), sobremuestreo
nT = 2.4  # graficar periodos de señal x(t)

titulo = 'pulso Sinc(t)'
# pulso Sinc
pulso_causal = True # No causal, centrado en cero
pulso_ancho = 4

casicero = 1e-10 # cero para valores menores

# PROCEDIMIENTO
Ts = 1/fs # tamaño de paso con fs
T  = 1/f0 # periodo de señal
dtc = Ts/fs_veces # suavizar x(t), sobremuestreo

# muestreo x(t)
ti = np.arange(0,nT*T+dtc,dtc)
xti = xt(ti)
ti_max = max(ti)

# muestreo x[n]
muestras_n = 2
if ti_max>=Ts: # varias muestras
    muestras_n = int(ti_max/Ts)+1
ki  = np.arange(0,muestras_n,1,dtype=int)
tki = ki*Ts # muestras x[n]
xki = xt(tki)
tkj = tki   # x[n] alias0
ti_0 = ti   # No Causal, pulso centrado en cero
if pulso_causal: # Causal
    tkj = tki + Ts*pulso_ancho/2
    ti_0 = ti + Ts*pulso_ancho/2
xti_0 = xti

def pulso_ti(muestras_n,fs,fs_veces,ancho=1,causal=True):
    ''' tiempos para las n muestras x(t) con pulsos a frecuencia fs.
    Para suavizar el pulso se usa fs_veces para el sobremuestreo.
    El ancho del pulso es veces el periodo de muestreo (1/Ts)
    Si es causal el tiempo inicia en t=0, centrado en cero.
    Si no es causal el tiempo inicia t en mitad de intervalo.
    '''
    Ts = 1/fs # muestreo periodo
    dtc = Ts/fs_veces # suavizar pulso(t), sobremuestreo
    t_Ts = np.arange(0,Ts,dtc) # tiempo en periodo Ts sobremuestreado
    
    mitad = Ts*ancho/2
    t_mitad = np.arange(0,mitad,dtc)
    t_pulsos = np.copy(t_mitad) # mitad de primer pulso
    for i in range(1,muestras_n,1):
        t_pulsos = np.concatenate((t_pulsos,t_Ts + t_pulsos[-1]+dtc))
    # mitad de último pulso para muestra_n
    t_pulsos = np.concatenate((t_pulsos,t_mitad + t_pulsos[-1]+dtc))

    # https://blog.espol.edu.ec/telg1001/sistemas-causales-y-no-causales/
    if causal == False: # centrado en cero
        t_pulsos = t_pulsos - mitad
    return(t_pulsos)

# x[n] interpolacion en Ts, a muestreo fs
t_pulsos = pulso_ti(muestras_n,fs,fs_veces,
                    pulso_ancho,pulso_causal)

#https://blog.espol.edu.ec/telg1001/senales-escalon-e-impulso/
#u = lambda t: np.piecewise(t,t>=0,[1,0])
u = lambda t: np.heaviside(t,1)
# pulso Sinc(t) # tambien definido como np.sinc()
u1 = lambda t: u(t+Ts*pulso_ancho/2)
u2 = lambda t: u(t-Ts*pulso_ancho/2)
sinc_tl = lambda t: ((np.sin(np.pi*t/Ts)/(np.pi*t/Ts))*(u1(t) - u2(t)))

def sinc_t(t):
    np.seterr(invalid='ignore') # t==0 division para cero
    resultado = sinc_tl(t) # en t=0, resultado es nan
    resultado = np.nan_to_num(resultado, nan=1) # cambia nan por 1
    return (resultado)

# unpulso muestreo
t_unpulso = pulso_ti(1,fs,fs_veces,
                     pulso_ancho,pulso_causal)
t_pulsoEval= t_unpulso
if pulso_causal:
    t_pulsoEval = t_unpulso-Ts*pulso_ancho/2
unpulso = sinc_t(t_pulsoEval)
muestras_pulso = len(t_unpulso)

muestras_N = len(xki)
mitad_pulso = int(muestras_pulso/2)
pulsos_vacio = np.zeros(len(t_pulsos),dtype=float)
xn_pulsos = np.copy(pulsos_vacio)
xk_pulsos = []
for j in range(0,muestras_N,1): # x[n] pulsos
    k0 = int(j*fs_veces)
    kn = k0 + muestras_pulso
    pulsoj = np.copy(pulsos_vacio)
    pulsoj[k0:kn]= pulsoj[k0:kn] + unpulso*xki[j]
    xk_pulsos.append(pulsoj)
    xn_pulsos = xn_pulsos+pulsoj
    
# SALIDA
print('muestras_pulso:',muestras_pulso)
print('t_unpulso:',t_unpulso)
print('muestras_tiempo:',len(t_pulsos))

# GRAFICAS ----------------------------
fig, [graf_t,graf_n] = plt.subplots(2,1)

# x(t) grafico entorno
t_causal = 0
if pulso_causal:
    t_causal = Ts*pulso_ancho/2
graf_t.axhline(0,color='black')
graf_t.axvline(0,color='black')
graf_t.set_xlabel('t')
graf_t.set_ylabel('amplitud')
graf_t.set_xlim([t_pulsos[0]-t_causal,
                 t_pulsos[-1]-t_causal])
graf_t.grid()
texto = titulo + ' ; f0='+str(f0)
texto = texto + ' ; fs='+str(fs)
texto = texto + ' ; causal:'+str(pulso_causal)
graf_t.set_title(texto)
# x(t) componentes
graf_t.plot(ti,xti,label='x(t)')
graf_t.stem(tki,xki,label='x[n]',linefmt = 'C1:')
for i in range(0,muestras_N,1):
    graf_t.plot(t_pulsos-t_causal,xk_pulsos[i],
                linestyle='dashed')
graf_t.legend()

# x[n] grafico entorno
graf_n.axhline(0,color='black')
graf_n.axvline(0,color='black')
graf_n.set_xlabel('t')
graf_n.set_ylabel('amplitud')
graf_n.set_xlim([t_pulsos[0],t_pulsos[-1]])
graf_n.grid()
# x[n] componentes
graf_n.plot(ti_0,xti_0,linestyle='dotted',
            label='x(t)')
graf_n.stem(tkj,xki,label='x[n]',linefmt = 'C1:')
graf_n.plot(t_pulsos,xn_pulsos,
            label='x(t) pulso',
            lw=2, color='orange')

graf_n.legend()
plt.tight_layout()
plt.show()

[ interpola Sinc ] [ ejemplo ] [ algoritmo ] [ gráfica interactiva ]
..


4. Algoritmo en Python para gráfico interactivo con fs

interpola Pulso Sinc animate

# D-to-C, Interpolacion con pulso Sinc(t)
# grafico interactivo
# ejemplo 4.3.4 p144
import numpy as np
import matplotlib.pyplot as plt

# INGRESO
# señal x(t)
f0 = 83   # frecuencia de senal
fase0 = 0 # [0,2*np.pi]
xt = lambda t: np.cos(2*np.pi*f0*t + fase0)

fs = 200  # Hz muestreo
fs_veces = 20 # suavizar x(t), sobremuestreo
nT = 2.4  # graficar periodos de señal x(t)

titulo = 'pulso Sinc(t)'
# pulso Sinc
pulso_causal = True # No causal, centrado en cero
pulso_ancho = 4

casicero = 1e-10 # cero para valores menores

# PROCEDIMIENTO
Ts = 1/fs # tamaño de paso con fs
T  = 1/f0 # periodo de señal
dtc = Ts/fs_veces # suavizar x(t), sobremuestreo

def xt_actualiza(xt,f0,fs,fs_veces,nT,
                 pulso_ancho=1,pulso_causal=True):
    ''' x(t) muestreada a fs, para nT periodos de x(t).
    gráfica suavizada con fs_veces. No causal, pulso centrado en cero.
    Causal el pulso inicia en t=0.
    '''
    Ts = 1/fs # tamaño de paso con fs
    T =  1/f0 # periodo de señal
    dtc = Ts/fs_veces # suavizar x(t), sobremuestreo
    
    # muestreo x(t)
    ti = np.arange(0,nT*T+dtc,dtc)
    xti = xt(ti)
    ti_max = max(ti)

    # muestreo x[n]
    muestras_n = 2
    if ti_max>=Ts: # varias muestras
        muestras_n = int(ti_max/Ts)+1
    ki  = np.arange(0,muestras_n,1,dtype=int)
    tki = ki*Ts # muestras x[n]
    xki = xt(tki)
    tkj = tki   # x[n] alias0
    ti_0 = ti   # No Causal, pulso centrado en cero
    if pulso_causal: # Causal
        tkj = tki + Ts*pulso_ancho/2
        ti_0 = ti + Ts*pulso_ancho/2
    xti_0 = xti
    return(ti,xti,tki,xki,tkj,ti_0,xti_0)

xt_list = xt_actualiza(xt,f0,fs,fs_veces,nT,
                       pulso_ancho,pulso_causal)
[ti,xti,tki,xki,tkj,ti_0,xti_0] = xt_list # x(t),x[n],x(t)_alias0
muestras_n = len(tki)

def pulso_ti(muestras_n,fs,fs_veces,ancho=1,causal=True):
    ''' tiempos para las n muestras x(t) con pulsos a frecuencia fs.
    Para suavizar el pulso se usa fs_veces para el sobremuestreo.
    El ancho del pulso es veces el periodo de muestreo (1/Ts)
    Si es causal el tiempo inicia en t=0, centrado en cero.
    Si no es causal el tiempo inicia t en mitad de intervalo.
    '''
    Ts = 1/fs # muestreo periodo
    dtc = Ts/fs_veces # suavizar pulso(t), sobremuestreo
    t_Ts = np.arange(0,Ts,dtc) # tiempo en periodo Ts sobremuestreado
    
    mitad = Ts*ancho/2
    t_mitad = np.arange(0,mitad,dtc)
    t_pulsos = np.copy(t_mitad) # mitad de primer pulso
    for i in range(1,muestras_n,1):
        t_pulsos = np.concatenate((t_pulsos,t_Ts + t_pulsos[-1]+dtc))
    # mitad de último pulso para muestra_n
    t_pulsos = np.concatenate((t_pulsos,t_mitad + t_pulsos[-1]+dtc))

    # https://blog.espol.edu.ec/telg1001/sistemas-causales-y-no-causales/
    if causal == False: # centrado en cero
        t_pulsos = t_pulsos - mitad
    return(t_pulsos)

def xn_actualiza(fs,fs_veces,xki,
                 pulso_ancho=1,pulso_causal=True):
    Ts = 1/fs # tamaño de paso con fs
    T =  1/f0 # periodo de señal
    dtc = Ts/fs_veces # suavizar x(t), sobremuestreo
    
    #https://blog.espol.edu.ec/telg1001/senales-escalon-e-impulso/
    #u = lambda t: np.piecewise(t,t>=0,[1,0])
    u = lambda t: np.heaviside(t,1)
    # pulso Sinc(t) # tambien definido como np.sinc()
    u1 = lambda t: u(t+Ts*pulso_ancho/2)
    u2 = lambda t: u(t-Ts*pulso_ancho/2)
    sinc_tl = lambda t: ((np.sin(np.pi*t/Ts)/(np.pi*t/Ts))*(u1(t) - u2(t)))

    def sinc_t(t):
        np.seterr(invalid='ignore') # t==0 division para cero
        resultado = sinc_tl(t) # en t=0, resultado es nan
        resultado = np.nan_to_num(resultado, nan=1) # cambia nan por 1
        return (resultado)

    # x[n] interpolacion en Ts, a muestreo fs
    muestras_n = len(xki)
    t_pulsos = pulso_ti(muestras_n,fs,fs_veces,
                        pulso_ancho,pulso_causal)

    # unpulso muestreo
    t_unpulso = pulso_ti(1,fs,fs_veces,
                         pulso_ancho,pulso_causal)
    t_pulsoEval= t_unpulso
    if pulso_causal:
        t_pulsoEval = t_unpulso-Ts*pulso_ancho/2
    unpulso = sinc_t(t_pulsoEval)
    muestras_pulso = len(t_unpulso)

    muestras_N = len(xki)
    mitad_pulso = int(muestras_pulso/2)
    pulsos_vacio = np.zeros(len(t_pulsos),dtype=float)
    xn_pulsos = np.copy(pulsos_vacio)
    xk_pulsos = []
    for j in range(0,muestras_N,1): # x(t) pulsos
        k0 = int(j*fs_veces)
        kn = k0 + muestras_pulso
        pulsoj = np.copy(pulsos_vacio)
        pulsoj[k0:kn]= pulsoj[k0:kn] + unpulso*xki[j]
        xk_pulsos.append(pulsoj)
        xn_pulsos = xn_pulsos+pulsoj
        
    return(t_pulsos,xn_pulsos,t_unpulso,unpulso,xk_pulsos)

xn_list = xn_actualiza(fs,fs_veces,xki,
                       pulso_ancho,pulso_causal)
[t_pulsos,x_pulsos,t_unpulso,unpulso,xk_pulsos] = xn_list # x[n]

# SALIDA
# SALIDA
print('fs:',fs,'Hz ; Ts:',Ts,' s')
print('pulso_causal:',pulso_causal)
print('muestras_pulso:',len(t_unpulso))
print('t_unpulso:',t_unpulso)
print('muestras_tiempo:',len(t_pulsos))

# GRAFICAS  interactivas----------------------------
from matplotlib.widgets import Slider, Button, TextBox
from matplotlib.collections import LineCollection
import telg1034 as dsp
colores = ["blue", "orange", "green", "red", "purple", "brown"]

fig, [graf_t,graf_n] = plt.subplots(2,1)

t_causal = 0
if pulso_causal:
    t_causal = Ts*pulso_ancho/2
# lineas de cada pulso
muestras_n = len(tki)
tabla = []
for j in range(0,muestras_n,1):
    pulsoj = np.concatenate(([t_pulsos-t_causal],[xk_pulsos[j]]),axis=0)
    pulsoj = np.transpose(pulsoj)
    tabla.append(pulsoj)
print('tamañotabla',np.shape(tabla))

linea_pulsosk = LineCollection(tabla,color=colores,
                               linestyles='dashed')
linea_pulsosk.set_array(t_pulsos)
graf_t.add_collection(linea_pulsosk)
    
# x(t) grafico entorno
graf_t.axhline(0,color='black')
graf_t.set_xlabel('t')
graf_t.grid()
texto = titulo + ' ; f0='+str(f0)
texto = texto +' ; fs='+str(fs)
graf_t.set_title(texto)
graf_t.set_xlim([t_pulsos[0]-t_causal,t_pulsos[-1]-t_causal])
# x(t) componentes
linea_xt, = graf_t.plot(ti,xti,label='x(t)')
puntos_xn = graf_t.stem(tki,xki,label='x[n]',linefmt = 'C1:')
#linea_pulso, = graf_t.plot(t_unpulso-t_causal,unpulso,
#                           label='pulso',linestyle='dashed',lw=2)
graf_t.legend()

# x[n] grafico entorno
graf_n.axhline(0,color='black')
graf_n.set_xlabel('t_n')
graf_n.grid()
# x[n] componentes
linea_xt0, = graf_n.plot(ti_0,xti_0,linestyle='dotted',
                         label='x(t)')
puntos_xki = graf_n.stem(tkj,xki,linefmt = 'C1:') #, label='x[n]')
linea_x_pulsos, = graf_n.plot(t_pulsos,x_pulsos,label='x(t) pulsos')
graf_n.set_xlim([t_pulsos[0],t_pulsos[-1]])
graf_n.legend()

plt.tight_layout()
# plt.show()

# grafica interactiva
plt.subplots_adjust(bottom=0.25) # espacio widgets

# slider: barras para valores 
# amplitud slider [x,y,ancho,alto]
fs_donde = plt.axes([0.2, 0.10, 0.65, 0.03])
df_pasos = 5
fs_slider = Slider(fs_donde, 'fs',
                   (f0//df_pasos)*df_pasos,
                   (max([fs,10*f0])+2*df_pasos),
                   valinit = fs, valstep = df_pasos,
                   orientation='horizontal')

def grafico_actualiza(val):
    # actualiza valores x,y
    fs = fs_slider.val
    
    Ts = 1/fs # tamaño de paso con fs
    t_causal = 0
    if pulso_causal:
        t_causal = Ts*pulso_ancho/2
    
    xt_list = xt_actualiza(xt,f0,fs,fs_veces,nT,
                           pulso_ancho,pulso_causal)
    [ti,xti,tki,xki,tkj,ti_0,xti_0] = xt_list # x(t),x[n],x(t)_alias0
    dsp.stem_update(puntos_xn,tki,xki,graf_t) # x[n]
    dsp.stem_update(puntos_xki,tkj,xki,graf_n)
    linea_xt0.set_xdata(ti_0) #x(t)_alias0
    linea_xt0.set_ydata(xti_0)
    
    xn_list = xn_actualiza(fs,fs_veces,xki,pulso_ancho,pulso_causal)
    [t_pulsos,x_pulsos,t_unpulso,unpulso,xk_pulsos] = xn_list # x[n] pulsos, pulso
    linea_x_pulsos.set_xdata(t_pulsos) # x[n] pulsos
    linea_x_pulsos.set_ydata(x_pulsos)
    #linea_pulso.set_xdata(t_unpulso-t_causal) # pulso lti
    #linea_pulso.set_ydata(unpulso)

    # lineas de cada pulso
    muestras_n = len(tki)
    tabla = []
    for j in range(0,muestras_n,1):
        pulsoj = np.concatenate(([t_pulsos-t_causal],[xk_pulsos[j]]),axis=0)
        pulsoj = np.transpose(pulsoj)
        tabla.append(pulsoj)
    linea_pulsosk.set_segments(tabla)

    texto = titulo+' ; f0='+str(f0)
    texto = texto +' ; fs='+str(fs)
    graf_t.set_title(texto)

    graf_t.set_xlim([t_pulsos[0]-t_causal,t_pulsos[-1]-t_causal])
    graf_n.set_xlim([t_pulsos[0],t_pulsos[-1]])

    fig.canvas.draw_idle() # actualiza figura

# boton reinicio de gráfica
btn_rstdonde = plt.axes([0.85, 0.025, 0.1, 0.04])
btn_rst = Button(btn_rstdonde, 'Reset',
                 hovercolor='0.975')
def grafico_reinicia(event):
    fs_slider.reset()
    return()

# objetos interactivos
fs_slider.on_changed(grafico_actualiza)
btn_rst.on_clicked(grafico_reinicia)

plt.show()

[ interpola Sinc ] [ ejemplo ] [ algoritmo ] [ gráfica interactiva ]

2.6 D-to-C – Interpolación con pulso triangular

[ interpola triángulo ] [ ejemplo ] [ algoritmo ] [ gráfica interactiva ]
..


1. Interpolación Lineal

Referencia: McClellan 4-3.3 p143

La interpolación lineal, con pulso triangular, o polinomio de primer orden consiste en los segmentos de línea:

p(t) = \begin{cases} 1-\frac{|t|}{Ts} & -T_s \leq t \leq -\frac{1}{2} T_s \\ 0 & otro caso \end{cases}

pulso Triangular 01El ancho del pulso es de dos veces el periodo de muestreo Ts, lo que causa que los pulsos se superpongan entre muestras. El resultado de la interpolación de varios impulsos requiere que se sumen las partes superpuestas, situación a considerar en la variable tiempo t del resultado y que se resume en la función pulso_ti().

1.1 Algoritmo en Python para un pulso triangular

# D-to-C, Interpolacion con pulso triangular
# ejemplo 4.3.3 p143
import numpy as np
import matplotlib.pyplot as plt

# INGRESO
fs = 200  # Hz muestreo
fs_veces = 10 # suavizar x(t), sobremuestreo
titulo = 'pulso triangular'
nT = 2.0  # graficar periodos de la señal

# pulso triangular
pulso_causal = True # No causal, pulso centrado en cero

# PROCEDIMIENTO
Ts = 1/fs # muestreo periodo
#https://blog.espol.edu.ec/telg1001/senales-escalon-%ce%bct-e-impulso-%ce%b4t/
#u = lambda t: np.piecewise(t,t>=0,[1,0])
u = lambda t: np.heaviside(t,1)
# pulso triangular
u1 = lambda t: u(t+Ts)
u2 = lambda t: u(t-Ts)
triangular = lambda t: (1-abs(t)/Ts)*(u1(t) - u2(t))             
pulso_ancho = 2

# x[n] interpolacion en Ts, a muestreo fs
Ts = 1/fs ; dtc = Ts/fs_veces
t_unpulso = np.arange(-Ts*nT,Ts*nT+dtc,dtc)
unpulso = triangular(t_unpulso)

# SALIDA
print('muestras:',len(t_unpulso))
print('t_unpulso:',t_unpulso)

# GRAFICA de un pulso
# p(t) componentes
plt.plot(t_unpulso,unpulso, color='green',linestyle='dashed',
         label='p_tri(t)')
plt.axvline(-Ts,color='red',linestyle='dotted')
plt.axvline(Ts,color='red',linestyle='dotted')
plt.stem([0],[1],linefmt = 'C1:', label='xn[0]')
plt.axhline(0,color='Black')

# grafica entorno
plt.xlabel('t')
plt.ylabel('amplitud')
texto = titulo + ' ; fs='+str(fs)
texto = texto +' ; Ts='+str(1/fs)
plt.title(texto)
plt.grid()
plt.legend()

plt.tight_layout()
plt.show()

[ interpola triángulo ] [ ejemplo ] [ algoritmo ] [ gráfica interactiva ]
..


2. Ejemplo con señal Senoidal y pulsos triangular

La señal x(t) tipo cosenoidal con f0=83 Hz y de amplitud unitaria,  se convierte a muestras discretas con frecuencia fs=200 Hz.

x(t) = \cos(2\pi (83)t)

interpola Pulso Triangular animate

Cada pulso triangular aplicado en una muestra se superpone al siguiente aplicado en la muestra nTs.

interpola Pulso Triangular 01En la reconstrucción para el vector tiempo t_pulsos, se debe considerar que el primer pulso empieza antes que el valor de la muestra, por lo que la señal presenta un retraso de mitad de pulso.

interpola Pulso Triangular 02En este caso, como el ancho del pulso es 2Ts, la señal resultante se atrasa Ts com se muestra en la gráfica.

La función pulso_ti() construye el vector de tiempo considerando ancho del pulso y el número de muestras x[n]. El inicio del vector tiempo también considera si el sistema es causal al aplicar el atraso a la unidad de tiempo, pues la reconstrucción empieza en el tiempo cero para las señales en tiempo real.

El vector tiempo también debe considerar la frecuencia de muestreo fs y para la gráfica el sobre-muestreo fs_veces. Al final se observa que la señal reconstruida tiene una duración adicional al tiempo de la última muestra, por lo que se añade una duración de mitad de pulso al extremo final. El efecto se observa en la señal x(t)_pulsos.

[ interpola triángulo ] [ ejemplo ] [ algoritmo ] [ gráfica interactiva ]
..


3.Algoritmo en Python Interpola con pulso triangular

En la reconstrucción de la señal x(t) usando pulsos, se realiza un proceso semejante al aplicado en pulso_ti() , en las partes que se superponen los impulsos se suman para dar un resultado de interpolación lineal de rectas que unen los valores de las muestras.

# D-to-C, Interpolacion con pulso triangular
# ejemplo 4.3.3 p143
import numpy as np
import matplotlib.pyplot as plt

# INGRESO
# señal x(t)
f0 = 83   # frecuencia de senal
fase0 = 0 # [0,2*np.pi]
xt = lambda t: np.cos(2*np.pi*f0*t + fase0)

fs = 200  # Hz muestreo
fs_veces = 20 # suavizar x(t), sobremuestreo
nT = 2.4  # graficar periodos de la señal

titulo = 'pulso triangular'
# pulso triangular
pulso_causal = True # No causal, pulso centrado en cero
pulso_ancho = 2

casicero = 1e-10 # cero para valores menores

# PROCEDIMIENTO
Ts = 1/fs # tamaño de paso con fs
T =  1/f0 # periodo de señal
dtc = Ts/fs_veces # suavizar x(t), sobremuestreo

# muestreo x(t)
ti = np.arange(0,nT*T+dtc,dtc)
xti = xt(ti)
ti_max = max(ti)

# muestreo x[n]
muestras_n = 2
if ti_max>=Ts: # varias muestras
    muestras_n = int(ti_max/Ts)+1
ki  = np.arange(0,muestras_n,1,dtype=int)
tki = ki*Ts # muestras x[n]
xki = xt(tki)
tkj = tki   # x[n] alias0
ti_0 = ti   # No Causal, pulso centrado en cero
if pulso_causal: # Causal
    tkj = tki + Ts*pulso_ancho/2 
    ti_0 = ti + Ts*pulso_ancho/2
xti_0 = xti

def pulso_ti(muestras_n,fs,fs_veces,ancho=1,causal=True):
    ''' tiempos para las n muestras x(t) con pulsos a frecuencia fs.
    Para suavizar el pulso se usa fs_veces para el sobremuestreo.
    El ancho del pulso es veces el periodo de muestreo (1/Ts)
    Si es causal el tiempo inicia en t=0, centrado en cero.
    Si no es causal el tiempo inicia t en mitad de intervalo.
    '''
    Ts = 1/fs # muestreo periodo
    dtc = Ts/fs_veces # suavizar pulso(t), sobremuestreo
    t_Ts = np.arange(0,Ts,dtc) # tiempo en periodo Ts sobremuestreado
    
    mitad = Ts*ancho/2
    t_mitad = np.arange(0,mitad,dtc)
    t_pulsos = np.copy(t_mitad) # mitad de primer pulso
    for i in range(1,muestras_n,1):
        t_pulsos = np.concatenate((t_pulsos,t_Ts + t_pulsos[-1]+dtc))
    # mitad de último pulso para muestra_n
    t_pulsos = np.concatenate((t_pulsos,t_mitad + t_pulsos[-1]+dtc))

    # https://blog.espol.edu.ec/telg1001/sistemas-causales-y-no-causales/
    if causal == False: # centrado en cero
        t_pulsos = t_pulsos - mitad
    return(t_pulsos)

# x[n] interpolacion en Ts, a muestreo fs
t_pulsos = pulso_ti(muestras_n,fs,fs_veces,
                    pulso_ancho,pulso_causal)

#https://blog.espol.edu.ec/telg1001/senales-escalon-%ce%bct-e-impulso-%ce%b4t/
#u = lambda t: np.piecewise(t,t>=0,[1,0])
u = lambda t: np.heaviside(t,1)
# pulso triangular
u1 = lambda t: u(t+Ts*pulso_ancho/2)
u2 = lambda t: u(t-Ts*pulso_ancho/2)
triangular = lambda t: (1-abs(t)*fs)*(u1(t) - u2(t))             

# unpulso muestreo
t_unpulso = pulso_ti(1,fs,fs_veces,
                     pulso_ancho,pulso_causal)
t_pulsoEval= t_unpulso
if pulso_causal:
    t_pulsoEval = t_unpulso-Ts*pulso_ancho/2
unpulso = triangular(t_pulsoEval)
muestras_pulso = len(t_unpulso)

muestras_N = len(xki)
mitad_pulso = int(muestras_pulso/2)
pulsos_vacio = np.zeros(len(t_pulsos),dtype=float)
xn_pulsos = np.copy(pulsos_vacio)
xk_pulsos = []
for j in range(0,muestras_N,1): # x(t) pulsos
    k0 = int(j*fs_veces)
    kn = k0 + muestras_pulso
    pulsoj = np.copy(pulsos_vacio)
    pulsoj[k0:kn]= pulsoj[k0:kn] + unpulso*xki[j]
    xk_pulsos.append(pulsoj)
    xn_pulsos = xn_pulsos+pulsoj

    
# SALIDA
print('muestras_pulso:',muestras_pulso)
print('t_unpulso:',t_unpulso)
print('muestras_tiempo:',len(t_pulsos))

# GRAFICAS ----------------------------
fig, [graf_t,graf_n] = plt.subplots(2,1)

# x(t) grafico entorno
t_causal = 0
if pulso_causal:
    t_causal = Ts*pulso_ancho/2
graf_t.axhline(0,color='black')
graf_t.axvline(0,color='black')
graf_t.set_xlabel('t')
graf_t.set_ylabel('amplitud')
graf_t.set_xlim([t_pulsos[0]-t_causal,
                 t_pulsos[-1]-t_causal])
graf_t.grid()
texto = titulo + ' ; f0='+str(f0)
texto = texto + ' ; fs='+str(fs)
texto = texto + ' ; causal:'+str(pulso_causal)
graf_t.set_title(texto)
# x(t) componentes
graf_t.plot(ti,xti,label='x(t)')
graf_t.stem(tki,xki,label='x[n]',linefmt = 'C1:')
for i in range(0,muestras_N,1):
    graf_t.plot(t_pulsos-t_causal,xk_pulsos[i],
                linestyle='dashed')
graf_t.legend()

# x[n] grafico entorno
graf_n.axhline(0,color='black')
graf_n.axvline(0,color='black')
graf_n.set_xlabel('t')
graf_n.set_ylabel('amplitud')
graf_n.set_xlim([t_pulsos[0],t_pulsos[-1]])
graf_n.grid()
# x[n] componentes
graf_n.plot(ti_0,xti_0,linestyle='dotted',
            label='x(t)')
graf_n.stem(tkj,xki,label='x[n]',linefmt = 'C1:')
graf_n.plot(t_pulsos,xn_pulsos,
            label='x(t) pulso',
            lw=2, color='orange')

graf_n.legend()
plt.tight_layout()
plt.show()

[ interpola triángulo ] [ ejemplo ] [ algoritmo ] [ gráfica interactiva ]
..


4. Algoritmo en Python para gráfico interactivo con fs

interpola Pulso Triangular animateLas instrucciones en Python son semejantes al caso con pulso rectangular.
Para dibujar cada pulso, se usa un objeto de líneas agrupadas o LineCollection() que permite actualizar las lineas al actualizar la tabla.

# D-to-C, Interpolacion con pulso triangular
# grafico interactivo
# ejemplo 4.3.3 p143
import numpy as np
import matplotlib.pyplot as plt

# INGRESO
# señal x(t)
f0 = 83   # frecuencia de senal
fase0 = 0 # [0,2*np.pi]
xt = lambda t: np.cos(2*np.pi*f0*t + fase0)

fs = 200  # Hz muestreo
fs_veces = 20 # suavizar x(t), sobremuestreo
nT = 2.4  # graficar periodos de la señal

titulo = 'pulso triangular'
# pulso triangular
pulso_causal = True # No causal, pulso centrado en cero
pulso_ancho = 2

casicero = 1e-10 # cero para valores menores

# PROCEDIMIENTO
Ts = 1/fs # tamaño de paso con fs
T =  1/f0 # periodo de señal
dtc = Ts/fs_veces # suavizar x(t), sobremuestreo

def xt_actualiza(xt,f0,fs,fs_veces,nT,
                 pulso_ancho=1,pulso_causal=True):
    ''' x(t) muestreada a fs, para nT periodos de x(t).
    gráfica suavizada con fs_veces. No causal, pulso centrado en cero.
    Causal el pulso inicia en t=0.
    '''
    Ts = 1/fs # tamaño de paso con fs
    T =  1/f0 # periodo de señal
    dtc = Ts/fs_veces # suavizar x(t), sobremuestreo
    
    # muestreo x(t)
    ti = np.arange(0,nT*T+dtc,dtc)
    xti = xt(ti)
    ti_max = max(ti)

    # muestreo x[n]
    muestras_n = 2
    if ti_max>=Ts: # varias muestras
        muestras_n = int(ti_max/Ts)+1
    ki  = np.arange(0,muestras_n,1,dtype=int)
    tki = ki*Ts # muestras x[n]
    xki = xt(tki)
    tkj = tki   # x[n] alias0
    ti_0 = ti   # No Causal, pulso centrado en cero
    if pulso_causal: # Causal
        tkj = tki + Ts*pulso_ancho/2 
        ti_0 = ti + Ts*pulso_ancho/2
    xti_0 = xti
    return(ti,xti,tki,xki,tkj,ti_0,xti_0)

xt_list = xt_actualiza(xt,f0,fs,fs_veces,nT,
                       pulso_ancho,pulso_causal)
[ti,xti,tki,xki,tkj,ti_0,xti_0] = xt_list # x(t),x[n],x(t)_alias0
muestras_n = len(tki)

def pulso_ti(muestras_n,fs,fs_veces,ancho=1,causal=True):
    ''' tiempos para las n muestras x(t) con pulsos a frecuencia fs.
    Para suavizar el pulso se usa fs_veces para el sobremuestreo.
    El ancho del pulso es veces el periodo de muestreo (1/Ts)
    Si es causal el tiempo inicia en t=0, centrado en cero.
    Si no es causal el tiempo inicia t en mitad de intervalo.
    '''
    Ts = 1/fs # muestreo periodo
    dtc = Ts/fs_veces # suavizar pulso(t), sobremuestreo
    t_Ts = np.arange(0,Ts,dtc) # tiempo en periodo Ts sobremuestreado
    
    mitad = Ts*ancho/2
    t_mitad = np.arange(0,mitad,dtc)
    t_pulsos = np.copy(t_mitad) # mitad de primer pulso
    for i in range(1,muestras_n,1):
        t_pulsos = np.concatenate((t_pulsos,t_Ts + t_pulsos[-1]+dtc))
    # mitad de último pulso para muestra_n
    t_pulsos = np.concatenate((t_pulsos,t_mitad + t_pulsos[-1]+dtc))

    # https://blog.espol.edu.ec/telg1001/sistemas-causales-y-no-causales/
    if causal == False: # centrado en cero
        t_pulsos = t_pulsos - mitad
    return(t_pulsos)

def xn_actualiza(fs,fs_veces,xki,
                 pulso_ancho=1,pulso_causal=True):
    Ts = 1/fs # tamaño de paso con fs
    T =  1/f0 # periodo de señal
    dtc = Ts/fs_veces # suavizar x(t), sobremuestreo
    
    #https://blog.espol.edu.ec/telg1001/senales-escalon-%ce%bct-e-impulso-%ce%b4t/
    #u = lambda t: np.piecewise(t,t>=0,[1,0])
    u = lambda t: np.heaviside(t,1)
    # pulso triangular
    u1 = lambda t: u(t+Ts*pulso_ancho/2)
    u2 = lambda t: u(t-Ts*pulso_ancho/2)
    triangular = lambda t: (1-abs(t)*fs)*(u1(t) - u2(t))             

    # x[n] interpolacion en Ts, a muestreo fs
    muestras_n = len(xki)
    t_pulsos = pulso_ti(muestras_n,fs,fs_veces,
                    pulso_ancho,pulso_causal)
    
    # unpulso muestreo
    t_unpulso = pulso_ti(1,fs,fs_veces,
                         pulso_ancho,pulso_causal)
    t_pulsoEval= t_unpulso
    if pulso_causal:
        t_pulsoEval = t_unpulso-Ts*pulso_ancho/2
    unpulso = triangular(t_pulsoEval)
    muestras_pulso = len(t_unpulso)

    muestras_N = len(xki)
    mitad_pulso = int(muestras_pulso/2)
    pulsos_vacio = np.zeros(len(t_pulsos),dtype=float)
    xn_pulsos = np.copy(pulsos_vacio)
    xk_pulsos = []
    for j in range(0,muestras_N,1): # x(t) pulsos
        k0 = int(j*fs_veces)
        kn = k0 + muestras_pulso
        pulsoj = np.copy(pulsos_vacio)
        pulsoj[k0:kn]= pulsoj[k0:kn] + unpulso*xki[j]
        xk_pulsos.append(pulsoj)
        xn_pulsos = xn_pulsos+pulsoj
        
    return(t_pulsos,xn_pulsos,t_unpulso,unpulso,xk_pulsos)

xn_list = xn_actualiza(fs,fs_veces,xki,
                       pulso_ancho,pulso_causal)
[t_pulsos,x_pulsos,t_unpulso,unpulso,xk_pulsos] = xn_list # x[n]
    
# SALIDA
print('fs:',fs,'Hz ; Ts:',Ts,' s')
print('pulso_causal:',pulso_causal)
print('muestras_pulso:',len(t_unpulso))
print('t_unpulso:',t_unpulso)
print('muestras_tiempo:',len(t_pulsos))

# GRAFICAS  interactivas----------------------------
from matplotlib.widgets import Slider, Button, TextBox
from matplotlib.collections import LineCollection
import telg1034 as dsp
colores = ["blue", "orange", "green", "red", "purple", "brown"]

fig, [graf_t,graf_n] = plt.subplots(2,1)

t_causal = 0
if pulso_causal:
    t_causal = Ts*pulso_ancho/2
# lineas de cada pulso
muestras_n = len(tki)
tabla = []
for j in range(0,muestras_n,1):
    pulsoj = np.concatenate(([t_pulsos-t_causal],[xk_pulsos[j]]),axis=0)
    pulsoj = np.transpose(pulsoj)
    tabla.append(pulsoj)
print('tamañotabla',np.shape(tabla))

linea_pulsosk = LineCollection(tabla,color=colores,
                               linestyles='dashed')
linea_pulsosk.set_array(t_pulsos)
graf_t.add_collection(linea_pulsosk)
    
# x(t) grafico entorno
graf_t.axhline(0,color='black')
graf_t.set_xlabel('t')
graf_t.grid()
texto = titulo + ' ; f0='+str(f0)
texto = texto +' ; fs='+str(fs)
graf_t.set_title(texto)
graf_t.set_xlim([t_pulsos[0]-t_causal,t_pulsos[-1]-t_causal])
# x(t) componentes
linea_xt, = graf_t.plot(ti,xti,label='x(t)')
puntos_xn = graf_t.stem(tki,xki,label='x[n]',linefmt = 'C1:')
#linea_pulso, = graf_t.plot(t_unpulso-t_causal,unpulso,
#                           label='pulso',linestyle='dashed',lw=2)
graf_t.legend()

# x[n] grafico entorno
graf_n.axhline(0,color='black')
graf_n.set_xlabel('t_n')
graf_n.grid()
# x[n] componentes
linea_xt0, = graf_n.plot(ti_0,xti_0,linestyle='dotted',
                         label='x(t)')
puntos_xki = graf_n.stem(tkj,xki,linefmt = 'C1:') #, label='x[n]')
linea_x_pulsos, = graf_n.plot(t_pulsos,x_pulsos,label='x(t) pulsos')
graf_n.set_xlim([t_pulsos[0],t_pulsos[-1]])
graf_n.legend()

plt.tight_layout()
# plt.show()

# grafica interactiva
plt.subplots_adjust(bottom=0.25) # espacio widgets

# slider: barras para valores 
# amplitud slider [x,y,ancho,alto]
fs_donde = plt.axes([0.2, 0.10, 0.65, 0.03])
df_pasos = 5
fs_slider = Slider(fs_donde, 'fs',
                   (f0//df_pasos)*df_pasos,
                   (max([fs,10*f0])+2*df_pasos),
                   valinit = fs, valstep = df_pasos,
                   orientation='horizontal')

def grafico_actualiza(val):
    # actualiza valores x,y
    fs = fs_slider.val
    
    Ts = 1/fs # tamaño de paso con fs
    t_causal = 0
    if pulso_causal:
        t_causal = Ts*pulso_ancho/2
    
    xt_list = xt_actualiza(xt,f0,fs,fs_veces,nT,
                           pulso_ancho,pulso_causal)
    [ti,xti,tki,xki,tkj,ti_0,xti_0] = xt_list # x(t),x[n],x(t)_alias0
    dsp.stem_update(puntos_xn,tki,xki,graf_t) # x[n]
    dsp.stem_update(puntos_xki,tkj,xki,graf_n)
    linea_xt0.set_xdata(ti_0) #x(t)_alias0
    linea_xt0.set_ydata(xti_0)
    
    xn_list = xn_actualiza(fs,fs_veces,xki,pulso_ancho,pulso_causal)
    [t_pulsos,x_pulsos,t_unpulso,unpulso,xk_pulsos] = xn_list # x[n] pulsos, pulso
    linea_x_pulsos.set_xdata(t_pulsos) # x[n] pulsos
    linea_x_pulsos.set_ydata(x_pulsos)
    #linea_pulso.set_xdata(t_unpulso-t_causal) # pulso lti
    #linea_pulso.set_ydata(unpulso)

    # lineas de cada pulso
    muestras_n = len(tki)
    tabla = []
    for j in range(0,muestras_n,1):
        pulsoj = np.concatenate(([t_pulsos-t_causal],[xk_pulsos[j]]),axis=0)
        pulsoj = np.transpose(pulsoj)
        tabla.append(pulsoj)
    linea_pulsosk.set_segments(tabla)

    texto = titulo+' ; f0='+str(f0)
    texto = texto +' ; fs='+str(fs)
    graf_t.set_title(texto)

    graf_t.set_xlim([t_pulsos[0]-t_causal,t_pulsos[-1]-t_causal])
    graf_n.set_xlim([t_pulsos[0],t_pulsos[-1]])

    fig.canvas.draw_idle() # actualiza figura

# boton reinicio de gráfica
btn_rstdonde = plt.axes([0.85, 0.025, 0.1, 0.04])
btn_rst = Button(btn_rstdonde, 'Reset',
                 hovercolor='0.975')
def grafico_reinicia(event):
    fs_slider.reset()
    return()

# objetos interactivos
fs_slider.on_changed(grafico_actualiza)
btn_rst.on_clicked(grafico_reinicia)

plt.show()

[ interpola triángulo ] [ ejemplo ] [ algoritmo ] [ gráfica interactiva ]

2.5 D-to-C – Interpolación Zero-Order Hold o con pulso rectangular

[ interpola pulso ] [ pulso rectangular ] [ algoritmo ] [ gráfica interactiva ]
..


1. Interpolación con Pulsos

Referencia: McClellan 4-3.1 p141

Un  sistema que convierte una señal y[n] en forma Discreta a la forma Contínua y(t), conocido como D-to-C,  usa interpolación para rellenar los espacios entre muestras. En el proceso se deben considerar efectos como el «aliasing» y «folding».

La implementación del concepto D-to-C en un sistema físico o «hardware» se los conoce como Convertidor Digital-Analógico (DAC) que completa los espacios entre muestras con un pulso p(t)

y(t) = \sum_{n=-\infty}^{\infty} y[n] p(t-nTs)

[ interpola pulso ] [ pulso rectangular ] [ algoritmo ] [ gráfica interactiva ]

..


2. Interpolación con Pulso rectangular, Zero-Order Hold

Un pulso simple, simétrico es un pulso rectangular de la forma:

p(t) = \begin{cases} 1 & -\frac{1}{2} T_s \lt t \leq \frac{1}{2} T_s \\ 0 & otro caso \end{cases}

Pulso Rectangular 01

Para una señal senoidad de frecuencia 83Hz y amplitud unitaria se obtienen las muestras x[n]. La frecuencia de muestreo fs es de 200Hz.  El proceso de re-construcción de x(t) llena los espacios entre muestras usando el pulso descrito en p(t). En éste caso p(t) es un pulso rectangular.

Los valores de las muestras x[n[ se presentan marcados con puntos para como una referencia en la gráfica.

Interpola Pulso Rectangular animate

Si consideramos un muestreo de señal a frecuencia fs=200Hz, se tiene que el pulso tiene una duración Ts = 5 ms y amplitud 1. Cada término de x[n]p(t-nTs) crea una sección plana de amplitud x[n] centrada en nTs, como se muestra en la gráfica.

2.1 Algoritmo en Python para un pulso

# D-to-C, Interpolacion con pulso rectangular
# ejemplo 4.3.2 p142
import numpy as np
import matplotlib.pyplot as plt

# INGRESO
fs = 200  # Hz muestreo
fs_veces = 16 # suavizar x(t), sobremuestreo
titulo = 'pulso rectangular'
nT = 2  # graficar periodos de la señal

# pulso rectangular
pulso_causal = True # No causal, pulso centrado en cero
pulso_ancho = 1
dutyc = 1 # entre [0,1] duty_cycle

# PROCEDIMIENTO
Ts = 1/fs
dtc = Ts/fs_veces # suavizar x(t), sobremuestreo

#https://blog.espol.edu.ec/telg1001/senales-escalon-%ce%bct-e-impulso-%ce%b4t/
#u = lambda t: np.piecewise(t,t>=0,[1,0])
u = lambda t: np.heaviside(t,1)
# pulso rectangular
#dutyc = 1 # entre [0,1] duty_cycle
u1 = lambda t: u(t+Ts*dutyc/2)
u2 = lambda t: u(t-Ts*dutyc/2)
rectangular = lambda t: u1(t) - u2(t)             

# x[n] interpolacion en Ts, a muestreo fs
t_unpulso = np.arange(-Ts/2*nT,Ts/2*nT+dtc,dtc)
unpulso = rectangular(t_unpulso)

# SALIDA
print('muestras:',len(t_unpulso))
print('t_unpulso:',t_unpulso)

# GRAFICA de un pulso
plt.axhline(0,color='Black')
plt.axvline(-Ts,color='red',linestyle='dotted')
plt.axvline(Ts,color='red',linestyle='dotted')

# p(t) componentes
plt.plot(t_unpulso,unpulso, color='green',linestyle='dashed',
         label='p_rect(t)')
plt.stem([0],[1],linefmt = 'C1:', label='xn[0]')

# grafica entorno
plt.xlabel('t')
plt.ylabel('amplitud')
texto = titulo + ' ; fs='+str(fs)
texto = texto +' ; Ts='+str(Ts)
plt.title(texto)
plt.grid()
plt.legend()
plt.tight_layout()
plt.show()

[ interpola pulso ] [ pulso rectangular ] [ algoritmo ] [ gráfica interactiva ]
..


3. Algoritmo en Python Interpola con pulso rectangular

El algoritmo se divide en:
– cálculo de x(t)
– cálculo de x[n]
– reconstrucción x(t) a partir de x[n] usando pulsos
– gráfica x(t)
– gráfica de x[n]

interpola Pulso Rectangular 01

La señal x(t) se convierte a discreta usando una tasa de muestras fs. Para observar la señal x(t), se indica el número de periodos nT de la frecuencia f0. Como ejemplo en la gráfica se realizan nT=2.3 periodos de la señal cosenoidal.

La señal de x(t) se suaviza visualmente al usar el factor fs_veces como sobre-muestreo respecto a fs. En este caso se usa fs=16. lo que genera muestras de señal cada Ts/fs_veces denominado en el algoritmo como tamaño de paso dtc.

Para el relleno de los espacios entre muestras x[n] se usa un modelo de pulso rectangular de tamaño Ts=1/fs que se copia cada vez que se avanza un espacio Ts,

Los valores de tiempo en la gráfica deben considerar la cantidad de muestras x[n], el ancho del pulso, el tiempo de inicio si es o no causal y la frecuencia de muestreo. El proceso se lo agrupa en la función pulso_ti(). El ancho del pulso cambia acorde a la forma del pulso a usar, triangular o Sinc(t). El pulso también se suaviza para la gráfica con fs_veces.

El proceso de reconstrucción de x(t) con pulsos consiste en repetir los valores del vector de un pulso en los tiempos que se dispone de una nueva muestra xki. En el caso que el ancho del pulso sea mayor que 1, los valores de los pulsos se traslapan y se requiere sumar los valores en un solo vector xn_pulsos . El efecto de ésta operación se detalla mejor con las señales triangular y Sinc(t) por tener un ancho de pulso mayor a 1.

Por el momento cada pulso se replica en cada muestra x[n] de la primera figura en la gráfica usando un color diferente. El resultado final de xn_pulsos se presenta en la segunda gráfica como x(t) pulsos.

# D-to-C, Interpolacion con pulso rectangular
# ejemplo 4.3.2 p142
import numpy as np
import matplotlib.pyplot as plt

# INGRESO
# señal x(t)
f0 = 83   # frecuencia de senal
fase0 = 0 # [0,2*np.pi]
xt = lambda t: np.cos(2*np.pi*f0*t + fase0)

fs = 200  # Hz muestreo
fs_veces = 16 # suavizar x(t), sobremuestreo
nT = 2.3  # graficar periodos de la señal

titulo = 'Interpola x(t) pulso rectangular '
# pulso rectangular
pulso_causal = True # No causal, pulso centrado en cero
pulso_ancho = 1
dutyc = 1 # entre [0,1] duty_cycle

casicero = 1e-10 # cero para valores menores

# PROCEDIMIENTO
Ts = 1/fs # tamaño de paso con fs
T =  1/f0 # periodo de señal
dtc = Ts/fs_veces # suavizar x(t), sobremuestreo

# muestreo x(t)
ti = np.arange(0,nT*T+dtc,dtc)
xti = xt(ti)
ti_max = max(ti)

# muestreo x[n]
muestras_n = 2
if ti_max>=Ts: # varias muestras
    muestras_n = int(ti_max/Ts)+1
ki  = np.arange(0,muestras_n,1,dtype=int)
tki = ki*Ts # muestras x[n]
xki = xt(tki)
tkj = tki   # x[n] alias0
ti_0 = ti   # No Causal, pulso centrado en cero
if pulso_causal: # Causal
    tkj = tki + Ts*pulso_ancho/2 
    ti_0 = ti + Ts*pulso_ancho/2
xti_0 = xti

def pulso_ti(muestras_n,fs,fs_veces,ancho=1,causal=True):
    ''' tiempos para las n muestras x(t) con pulsos a frecuencia fs.
    Para suavizar el pulso se usa fs_veces para el sobremuestreo.
    El ancho del pulso es veces el periodo de muestreo (1/Ts)
    Si es causal el tiempo inicia en t=0, centrado en cero.
    Si no es causal el tiempo inicia t en mitad de intervalo.
    '''
    Ts = 1/fs # muestreo periodo
    dtc = Ts/fs_veces # suavizar pulso(t), sobremuestreo
    t_Ts = np.arange(0,Ts,dtc) # tiempo en periodo Ts sobremuestreado
    
    mitad = Ts*ancho/2
    t_mitad = np.arange(0,mitad,dtc)
    t_pulsos = np.copy(t_mitad) # mitad de primer pulso
    for i in range(1,muestras_n,1):
        t_pulsos = np.concatenate((t_pulsos,t_Ts + t_pulsos[-1]+dtc))
    # mitad de último pulso para muestra_n
    t_pulsos = np.concatenate((t_pulsos,t_mitad + t_pulsos[-1]+dtc))

    # https://blog.espol.edu.ec/telg1001/sistemas-causales-y-no-causales/
    if causal == False: # centrado en cero
        t_pulsos = t_pulsos - mitad
    return(t_pulsos)

# x[n] interpolacion en Ts, a muestreo fs
t_pulsos = pulso_ti(muestras_n,fs,fs_veces,
                    pulso_ancho,pulso_causal)

# https://blog.espol.edu.ec/telg1001/senales-escalon-%ce%bct-e-impulso-%ce%b4t/
#u = lambda t: np.piecewise(t,t>=0,[1,0])
u = lambda t: np.heaviside(t,1)
# pulso rectangular entre [-(1/fs)/2,(1/fs)/2], no causal
u1 = lambda t: u(t+Ts*dutyc/2)
u2 = lambda t: u(t-Ts*dutyc/2)
rectangular = lambda t: u1(t) - u2(t)             

# unpulso muestreo
t_unpulso = pulso_ti(1,fs,fs_veces,
                     pulso_ancho,pulso_causal)
t_pulsoEval= t_unpulso
if pulso_causal:
    t_pulsoEval = t_unpulso-Ts*pulso_ancho/2
unpulso = rectangular(t_pulsoEval)
muestras_pulso = len(t_unpulso)

# reconstruye x(t) con pulsos
muestras_N = len(xki)
mitad_pulso = int(muestras_pulso/2)
pulsos_vacio = np.zeros(len(t_pulsos),dtype=float)
xn_pulsos = np.copy(pulsos_vacio)
xk_pulsos = []
for j in range(0,muestras_N,1): # x(t) pulsos
    k0 = int(j*fs_veces)
    kn = k0 + muestras_pulso
    pulsoj = np.copy(pulsos_vacio)
    pulsoj[k0:kn]= pulsoj[k0:kn] + unpulso*xki[j]
    xk_pulsos.append(pulsoj)
    xn_pulsos = xn_pulsos+pulsoj
    
# SALIDA
print('fs:',fs,'Hz ; Ts:',Ts,' s')
print('pulso_causal:',pulso_causal,' : dutycycle:',dutyc)
print('muestras_pulso:',muestras_pulso)
print('t_unpulso:',t_unpulso)
print('muestras_tiempo:',len(t_pulsos))

# GRAFICAS ----------------------------
fig, [graf_t,graf_n] = plt.subplots(2,1)

# x(t) grafico entorno
t_causal = 0
if pulso_causal:
    t_causal = Ts*pulso_ancho/2
graf_t.axhline(0,color='black')
graf_t.axvline(0,color='black')
graf_t.set_xlabel('t')
graf_t.set_ylabel('amplitud')
graf_t.set_xlim([t_pulsos[0]-t_causal,
                 t_pulsos[-1]-t_causal])
graf_t.grid()
texto = titulo + ' ; f0='+str(f0)
texto = texto + ' ; fs='+str(fs)
texto = texto + ' ; causal:'+str(pulso_causal)
graf_t.set_title(texto)
# x(t) componentes
graf_t.plot(ti,xti,label='x(t)')
graf_t.stem(tki,xki,label='x[n]',linefmt = 'C1:')
for i in range(0,muestras_N,1):
    graf_t.plot(t_pulsos-t_causal,xk_pulsos[i],
                linestyle='dashed')
graf_t.legend()

# x[n] grafico entorno
graf_n.axhline(0,color='black')
graf_n.axvline(0,color='black')
graf_n.set_xlabel('t')
graf_n.set_ylabel('amplitud')
graf_n.set_xlim([t_pulsos[0],t_pulsos[-1]])
graf_n.grid()
# x[n] componentes
graf_n.plot(ti_0,xti_0,linestyle='dotted',
            label='x(t)')
graf_n.stem(tkj,xki,label='x[n]',linefmt = 'C1:')
graf_n.plot(t_pulsos,xn_pulsos,
            label='x(t) pulso',
            lw=2, color='orange')

graf_n.legend()
plt.tight_layout()
plt.show()

[ interpola pulso ] [ pulso rectangular ] [ algoritmo ] [ gráfica interactiva ]
..


4. Algoritmo en Python para gráfico interactivo con fs

Para fines didácticos se añade un control en la gráfica para la frecuencia de muestreo fs, con lo que observa mejor el efecto que sub-muestreo, sobre-muestreo y el uso de 2 veces la frecuencia usada en la señal o Nyquist.

Interpola Pulso Rectangular animateCada cambio de fs tiene efectos sobre las muestras, el ancho de los pulsos y la reconstrucción de x(t). Por tal motivo se agrupan los cálculos de x(t) y x[n] en una función xt_actualiza(), y el proceso de reconstrucción y operaciones con x[n] como la función xn_actualiza().

# D-to-C, Interpolacion con pulso rectangular
# grafico interactivo
# ejemplo 4.3.2 p142
import numpy as np
import matplotlib.pyplot as plt

# INGRESO
# señal x(t)
f0 = 83   # frecuencia de senal
fase0 = 0 # [0,2*np.pi]
xt = lambda t: np.cos(2*np.pi*f0*t + fase0)

fs = 200  # Hz muestreo
fs_veces = 16 # suavizar x(t), sobremuestreo
nT = 2.3  # graficar periodos de la señal

titulo = 'Interpola x(t) pulso rectangular '
# pulso rectangular
pulso_causal = True # No causal, pulso centrado en cero
pulso_ancho = 1
dutyc = 1 # entre [0,1] duty_cycle

casicero = 1e-10 # cero para valores menores

# PROCEDIMIENTO
Ts = 1/fs # tamaño de paso con fs
T =  1/f0 # periodo de señal
dtc = Ts/fs_veces # suavizar x(t), sobremuestreo

def xt_actualiza(xt,f0,fs,fs_veces,nT,
                 pulso_ancho=1,pulso_causal=True):
    ''' x(t) muestreada a fs, para nT periodos de x(t).
    gráfica suavizada con fs_veces. No causal, pulso centrado en cero.
    Causal el pulso inicia en t=0.
    '''
    Ts = 1/fs # tamaño de paso con fs
    T =  1/f0 # periodo de señal
    dtc = Ts/fs_veces # suavizar x(t), sobremuestreo

    # muestreo x(t)
    ti = np.arange(0,nT*T+dtc,dtc)
    xti = xt(ti)
    ti_max = max(ti)

    # muestreo x[n]
    muestras_n = 2
    if ti_max>=Ts: # varias muestras
        muestras_n = int(ti_max/Ts)+1
    ki  = np.arange(0,muestras_n,1,dtype=int)
    tki = ki*Ts # muestras x[n]
    xki = xt(tki)
    tkj = tki   # x[n] alias0
    ti_0 = ti   # No Causal, pulso centrado en cero
    if pulso_causal: # Causal
        tkj = tki + Ts*pulso_ancho/2 
        ti_0 = ti + Ts*pulso_ancho/2
    xti_0 = xti
    return(ti,xti,tki,xki,tkj,ti_0,xti_0)

xt_list = xt_actualiza(xt,f0,fs,fs_veces,nT,
                       pulso_ancho,pulso_causal)
[ti,xti,tki,xki,tkj,ti_0,xti_0] = xt_list # x(t),x[n],x(t)_alias0
muestras_n = len(tki)

def pulso_ti(muestras_n,fs,fs_veces,ancho=1,causal=True):
    ''' tiempos para las n muestras x(t) con pulsos a frecuencia fs.
    Para suavizar el pulso se usa fs_veces para el sobremuestreo.
    El ancho del pulso es veces el periodo de muestreo (1/Ts)
    Si es causal el tiempo inicia en t=0, centrado en cero.
    Si no es causal el tiempo inicia t en mitad de intervalo.
    '''
    Ts = 1/fs # muestreo periodo
    dtc = Ts/fs_veces # suavizar pulso(t), sobremuestreo
    t_Ts = np.arange(0,Ts,dtc) # tiempo en periodo Ts sobremuestreado
    
    mitad = Ts*ancho/2
    t_mitad = np.arange(0,mitad,dtc)
    t_pulsos = np.copy(t_mitad) # mitad de primer pulso
    for i in range(1,muestras_n,1):
        t_pulsos = np.concatenate((t_pulsos,t_Ts + t_pulsos[-1]+dtc))
    # mitad de último pulso para muestra_n
    t_pulsos = np.concatenate((t_pulsos,t_mitad + t_pulsos[-1]+dtc))

    # https://blog.espol.edu.ec/telg1001/sistemas-causales-y-no-causales/
    if causal == False: # centrado en cero
        t_pulsos = t_pulsos - mitad
    return(t_pulsos)

def xn_actualiza(fs,fs_veces,xki,dutyc,
                 pulso_ancho=1,pulso_causal=True):
    ''' x(t) reconstruida con pulsos
    '''
    Ts = 1/fs # tamaño de paso con fs
    T =  1/f0 # periodo de señal
    dtc = Ts/fs_veces # suavizar x(t), sobremuestreo

    #https://blog.espol.edu.ec/telg1001/senales-escalon-e-impulso/
    #u = lambda t: np.piecewise(t,t>=0,[1,0])
    u = lambda t: np.heaviside(t,1)
    # pulso rectangular
    #dutyc = 1 # entre [0,1] duty_cycle
    u1 = lambda t: u(t+Ts*dutyc/2)
    u2 = lambda t: u(t-Ts*dutyc/2)
    rectangular = lambda t: u1(t) - u2(t)

    # x[n] interpolacion en Ts, a muestreo fs
    muestras_n = len(xki)
    t_pulsos = pulso_ti(muestras_n,fs,fs_veces,
                    pulso_ancho,pulso_causal)

    # unpulso muestreo
    t_unpulso = pulso_ti(1,fs,fs_veces,
                         pulso_ancho,pulso_causal)
    t_pulsoEval= t_unpulso
    if pulso_causal:
        t_pulsoEval = t_unpulso-Ts*pulso_ancho/2
    unpulso = rectangular(t_pulsoEval)
    muestras_pulso = len(t_unpulso)

    muestras_N = len(xki)
    pulsos_vacio = np.zeros(len(t_pulsos),dtype=float)
    xn_pulsos = np.copy(pulsos_vacio)
    xk_pulsos = []
    for j in range(0,muestras_N,1): # x(t) pulsos
        k0 = int(j*fs_veces)
        kn = k0 + muestras_pulso
        pulsoj = np.copy(pulsos_vacio)
        pulsoj[k0:kn]= pulsoj[k0:kn] + unpulso*xki[j]
        xk_pulsos.append(pulsoj)
        xn_pulsos = xn_pulsos+pulsoj

    return(t_pulsos,xn_pulsos,t_unpulso,unpulso)

xn_list = xn_actualiza(fs,fs_veces,xki,dutyc,
                       pulso_ancho,pulso_causal)
[t_pulsos,x_pulsos,t_unpulso,unpulso] = xn_list # x[n] pulsos, pulso

# SALIDA
print('fs:',fs,'Hz ; Ts:',Ts,' s')
print('pulso_causal:',pulso_causal,' : dutycycle:',dutyc)
print('muestras_pulso:',len(t_unpulso))
print('t_unpulso:',t_unpulso)
print('muestras_tiempo:',len(t_pulsos))

# GRAFICAS  interactivas----------------------------
from matplotlib.widgets import Slider, Button, TextBox
import telg1034 as dsp
graf_dx = 0.12 # margen en eje x

fig, [graf_t,graf_n] = plt.subplots(2,1)

# x(t) grafico entorno
graf_t.axhline(0,color='black')
graf_t.set_xlabel('t')
graf_t.grid()
texto = titulo + ' ; f0='+str(f0)
texto = texto +' ; fs='+str(fs)
graf_t.set_title(texto)
# x(t) componentes
linea_xt, = graf_t.plot(ti,xti,label='x(t)')
puntos_xn = graf_t.stem(tki,xki,label='x[n]',linefmt = 'C1:')
graf_t.set_xlim([t_pulsos[0],ti[-1]])
graf_t.legend()

# x[n] grafico entorno
graf_n.axhline(0,color='black')
graf_n.set_xlabel('t_n')
graf_n.grid()
# x[n] componentes
linea_xt0, = graf_n.plot(ti_0,xti_0,linestyle='dotted',
                         label='x(t)')
puntos_xki = graf_n.stem(tkj,xki,linefmt = 'C1:') #, label='x[n]')
linea_x_pulsos, = graf_n.plot(t_pulsos,x_pulsos,label='x(t) pulsos')
linea_pulso, = graf_n.plot(t_unpulso,unpulso,
                           label='pulso',linestyle='dashed',lw=2)
graf_n.set_xlim([t_pulsos[0],ti[-1]])
graf_n.legend()

plt.tight_layout()
# plt.show()

# grafica interactiva
plt.subplots_adjust(bottom=0.25) # espacio widgets

# slider: barras para valores 
# amplitud slider [x,y,ancho,alto]
fs_donde = plt.axes([0.2, 0.10, 0.65, 0.03])
df_pasos = 5
fs_slider = Slider(fs_donde, 'fs',
                   (f0//df_pasos)*df_pasos,
                   (max([fs,10*f0])+2*df_pasos),
                   valinit = fs, valstep = df_pasos,
                   orientation='horizontal')

def grafico_actualiza(val):
    # actualiza valores x,y
    fs = fs_slider.val
    
    xt_list = xt_actualiza(xt,f0,fs,fs_veces,nT,
                           pulso_ancho,pulso_causal)
    [ti,xti,tki,xki,tkj,ti_0,xti_0] = xt_list # x(t),x[n],x(t)_alias0
    dsp.stem_update(puntos_xn,tki,xki,graf_t) # x[n]
    dsp.stem_update(puntos_xki,tkj,xki,graf_n)
    linea_xt0.set_xdata(ti_0) #x(t)_alias0
    linea_xt0.set_ydata(xti_0)
    
    xn_list = xn_actualiza(fs,fs_veces,xki,dutyc,pulso_ancho,pulso_causal)
    [t_pulsos,x_pulsos,t_unpulso,unpulso] = xn_list # x[n] pulsos, pulso
    linea_x_pulsos.set_xdata(t_pulsos) # x[n] pulsos
    linea_x_pulsos.set_ydata(x_pulsos)
    linea_pulso.set_xdata(t_unpulso) # pulso lti
    linea_pulso.set_ydata(unpulso)

    texto = titulo+' ; f0='+str(f0)
    texto = texto +' ; fs='+str(fs)
    graf_t.set_title(texto)
    
    graf_t.set_xlim([t_pulsos[0],ti[-1]])
    graf_n.set_xlim([t_pulsos[0],ti[-1]])

    fig.canvas.draw_idle() # actualiza figura

# boton reinicio de gráfica
btn_rstdonde = plt.axes([0.85, 0.025, 0.1, 0.04])
btn_rst = Button(btn_rstdonde, 'Reset',
                 hovercolor='0.975')
def grafico_reinicia(event):
    fs_slider.reset()
    return()

# objetos interactivos
fs_slider.on_changed(grafico_actualiza)
btn_rst.on_clicked(grafico_reinicia)

plt.show()

[ interpola pulso ] [ pulso rectangular ] [ algoritmo ] [ gráfica interactiva ]

2.4 Muestreo – Reconstrucción en tiempo, sub y sobre muestreo

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

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.

espectro n coseno animate

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 ] [ gráfica interactiva ]
..


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 discreto coseno sobremuestreo 01

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.

eñal(t):   cos(((2*pi)*100)*t + pi/3)
 espectro_t:
  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

muestreo fs: 500  ; dt: 0.002  ; fs_veces: 12
muestreo_tipo:  sobre-muestreo
muestreo: 11  tamaño ki: 11 ; tamaño ti: 121
x(t): cos(((2*pi)*100)*t + 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})$']
  x_expand : cos(2*pi*n/5 + 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
with sym.evaluate(False): # no simplificar freq angular w >2*pi por Sympy
    x = sym.cos(DosPi*100*t + pi/3)
xt_etiqueta = 'x(t)' ; xn_etiqueta = 'x[n]'
titulo = x # copia para titulo de grafico (antes de procesar)

# señal muestreada
fs = 500   # muestreo discreto, freq_sampling >0
fs_veces = 12  # suavizar x(t), sobremuestreo
muestras_n = 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 ] [ gráfica interactiva ]
..


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 discreto coseno submuestreo 01

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 >0

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(((2*pi)*100)*t + pi/3)
 espectro_t:
  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

muestreo fs: 80  ; dt: 0.0125  ; fs_veces: 12
muestreo_tipo:  sub-muestreo
muestreo: 11  tamaño ki: 11 ; tamaño ti: 121
x(t): cos(((2*pi)*100)*t + 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 : [-2.5 -1.5 -0.5  0.5  1.5  2.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})$']
  x_expand : cos(5*pi*n/2 + pi/3)
  freq_factor : pi
  freq_max : 2.5
  freq_min : 0.5
  BW : 2.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 ] [ gráfica interactiva ]
..


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 discreto coseno folding 01
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(((2*pi)*100)*t + pi/3)
 espectro_t:
  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

muestreo fs: 125  ; dt: 0.008  ; fs_veces: 12
muestreo_tipo:  sub-muestreo y folding
muestreo: 11  tamaño ki: 11 ; tamaño ti: 121
x(t): cos(((2*pi)*100)*t + 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 : [-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})$']
  x_expand : cos(8*pi*n/5 + pi/3)
  freq_factor : pi
  freq_max : 2.4
  freq_min : 0.3999999999999999
  BW : 2.0
  freq_alias : [ True  True False False  True  True]

señal reconstruida con alias principal:
cos(25*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 discreto coseno folding 02

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


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
with sym.evaluate(False): # no simplificar freq angular w >2*pi por Sympy
    x = sym.cos(DosPi*100*t + pi/3)
xt_etiqueta = 'x(t)' ; xn_etiqueta = 'x[n]'
titulo = x # copia para titulo de grafico (antes de procesar)

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

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

# PROCEDIMIENTO ----------------
# espectro x(t) - operaciones
# Ref: Blog|Espectro-Operaciones en dominio de tiempo y frecuencia
# unaseñal = x.subs(pi,np.pi) # mantener valores >2pi como float
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_t = dsp.cos_spectrum_list(x_term)

# espectro de cada señal
freq = un_espectro_t['freq']
ampl = un_espectro_t['ampl']
fase = un_espectro_t['fase']
etiqueta = un_espectro_t['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_t['freq_max'] = freq_max
un_espectro_t['freq_min'] = freq_min

# revisa muestreo Nyquist
if abs(2*freq_max-fs)<tolera:
    muestreo_tipo = 'Nyquist'
if fs>(freq_max*2):
    muestreo_tipo = 'sobre-muestreo'
if fs<(freq_max*2):
    muestreo_tipo = 'sub-muestreo'
if fs<(freq_max*2) and fs>freq_max:
    muestreo_tipo = 'sub-muestreo y folding'

# Muestreo de señales sinusoidales
# Ref: blog|Muestreo con Python
xt = sym.lambdify(t,x, modules=equivalentes)
xn = x.subs(t,n/fs)

# muestreo x[n]
dt = 1/fs   # tamaño de paso con fs
ki = np.arange(0,muestras_n,1,dtype=int)
tki = ki*dt
xki = xt(tki)

nT = int(np.max(tki)/dt) # periodos a graficar

# muestreo x(t)
dtc = dt/fs_veces # suavizar x(t), sobremuestreo
ti = np.arange(0,(muestras_n-1)*dt+dtc, dtc)
xti = xt(ti)

# operaciones para espectro x[n]
# Ref: blog|Espectro x[n] – Señal discreta en tiempo
#xn = xn.doit()#.subs(pi,np.pi).doit() # mantener valores >2pi
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_n['x_expand'] = x_term_expand

freqn_conteo = len(freq_n)

# normaliza w frecuencia angular
for i in range(0,freqn_conteo,1):
    while abs(freq_n[i].subs(pi,np.pi))>np.pi:
        if freq_n[i]>0:
            freq_norm = freq_n[i]-2*pi
        else:
            freq_norm = freq_n[i]+2*pi
        freq_n[i] = freq_norm

# Añadir alias a señal
freqn_alias = np.zeros(freqn_conteo,dtype=bool)
freq_m = np.zeros(0,dtype=float)
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):
        freq_k = (freq_i+2*pi*k)
        if  not((freq_k in freq_n) or (freq_k in freq_m)):
            # añade freq_n[i] + 2*pi*k
            freq_m = np.concatenate([freq_m,[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]])
        freq_k = (freq_i-2*pi*k)
        if not((freq_k in freq_n) or (freq_k in freq_m)):
            # añade freq_n[i] - 2*pi*k
            freq_m = np.concatenate([freq_m,[freq_i-2*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]])
freq_n = np.concatenate([freq_n,freq_m])

# 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

# alias principal, 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]
    if abs(unafreq) > 1:
        freq_alias[i] = True
freq_alias_conteo = np.sum(freq_alias)
un_espectro_n['freq_alias'] = freq_alias

# reconstruye señal con alias principal
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 = float(freq_x0[i]*unfactor*(fs/(2*pi)))
        x0t = x0t + 2*ampl_x0[i]*sym.cos(DosPi*una_freq*t+sym.UnevaluatedExpr(fase_x0[i])) 
    if freq_x0[i]>=0 and freq_x0[i]<tolera: # es cero
        una_freq = float(freq_x0[i]*unfactor*(fs/(2*pi)))
        x0t = x0t + ampl_x0[i]*sym.cos(DosPi*una_freq*t+sym.UnevaluatedExpr(fase_x0[i]))
x0t = dsp._float_is_int(x0t) # si es entero
x0tr = x0t.subs(pi,np.pi)
if x0tr.has(t): # dos componentes en la constante.
    x0tr = sym.lambdify(t,x0tr,modules=equivalentes)
else: # componentes constantes
    constante = float(x0tr.doit())
    x0tr = lambda t: constante +0*t
    x0t = x0t.simplify()
x0_ti = x0tr(ti)

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

print('\nmuestreo fs:',fs,' ; dt:',dt, ' ; fs_veces:',fs_veces)
print('muestreo_tipo: ',muestreo_tipo)
print('muestreo:',muestras_n,
      ' tamaño ki:',len(ki),'; tamaño ti:',len(ti))
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 ] [ gráfica interactiva ]
..


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|Espectro x[n] – Señal discreta en tiempo
graf_dx = 0.12 # margen en eje x
fig_espectro = plt.figure()

# grafica espectro x(t) continuo en tiempo ---------
graf_fasor = fig_espectro.add_subplot(311)
ampl_max = float(max(ampl))
freq_max = float(max(max(freq),fs))*(1+graf_dx/2)
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*4)])
graf_fasor.grid()
graf_fasor.axhline(0,color='black')
graf_fasor.axvline(0,linestyle='dotted',color='grey')
graf_fasor.set_ylabel('Magnitud '+xt_etiqueta)
graf_fasor.set_xlabel('freq Hz', labelpad=0,)

# espectro x(t) continuo en tiempo
graf_fasor.stem(freq,ampl) # grafica espectro 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')
texto = '$' + sym.latex(titulo)+'$' +'  ; 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.set_xlabel('t')
graf_xt.set_ylabel('amplitud')
graf_xt.grid()

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]_alias0')
graf_xt.legend()

# grafica espectro x[n] de frecuencias discretas---------
# Ref: blog|Espectro x[n] – Señal discreta en tiempo
graf_fasorn = fig_espectro.add_subplot(313)
# grafica x[n] - entorno
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*4)
freq_max = float(max(un_espectro_n['freq_max'],1))*(1+graf_dx/2)
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')
graf_fasorn.set_ylabel('Magnitud '+xn_etiqueta)
graf_fasorn.set_xlabel('freq rad')

# grafica x[n] - magnitud
freq_alias0 = np.invert(freq_alias) 
graf_fasorn.stem(freq_n[freq_alias0],ampl_n[freq_alias0],
                 linefmt = 'C0--',label = 'alias0')
if len(freq_n[freq_alias])>0:
    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_txt = 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_txt)
    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.legend()

plt.tight_layout()
plt.show()

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


7. Gráfica Interactiva con Python

Se reescribe un poco el algoritmo, agrupando algunos bloques que deben actualizarse al cambiar la variable fs

La frecuencia de muestreo fs se ajusta por medio de una barra de desplazamiento entre los valores de fmax/2 y 2*fmax*+50.

espectro discreto coseno interactivo 01

# ejercicio 4.5 p133 Muestreo y Reconstrucción de señales sinusoidales
# grafico interactivo
# 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
with sym.evaluate(False): # no simplificar freq angular w >2*pi por Sympy
    x = sym.cos(DosPi*100*t + pi/3)
xt_etiqueta = 'x(t)' ; xn_etiqueta = 'x[n]'
titulo = x # copia para titulo de grafico (antes de procesar)

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

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

# PROCEDIMIENTO ----------------
# espectro x(t) - operaciones
# Ref: Blog|Espectro-Operaciones en dominio de tiempo y frecuencia
# unaseñal = x.subs(pi,np.pi) # mantener valores >2pi como float
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_t = dsp.cos_spectrum_list(x_term)

# espectro de cada señal
freq = un_espectro_t['freq']
ampl = un_espectro_t['ampl']
fase = un_espectro_t['fase']
etiqueta = un_espectro_t['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_t['freq_max'] = freq_max
un_espectro_t['freq_min'] = freq_min

def sampling_type(fs,freq_max,tolera=1e-9):
    '''revisa muestreo Nyquist
    '''
    if abs(2*freq_max-fs)<tolera:
        muestreo_tipo = 'Nyquist'
    if fs>(freq_max*2):
        muestreo_tipo = 'sobre-muestreo'
    if fs<(freq_max*2):
        muestreo_tipo = 'sub-muestreo'
    if fs<(freq_max*2) and fs>freq_max:
        muestreo_tipo = 'sub-muestreo y folding'
    return(muestreo_tipo)

muestreo_tipo = sampling_type(fs,freq_max)

# Muestreo de señales sinusoidales
# Ref: blog|Muestreo con Python
xt = sym.lambdify(t,x, modules=equivalentes)
xn = x.subs(t,n/fs)

# muestreo x[n]
dt = 1/fs   # tamaño de paso con fs
ki = np.arange(0,muestras_n,1,dtype=int)
tki = ki*dt
xki = xt(tki)

nT = int(np.max(tki)/dt) # periodos a graficar

# muestreo x(t)
dtc = dt/fs_veces # suavizar x(t), sobremuestreo
ti = np.arange(0,(muestras_n-1)*dt+dtc, dtc)
xti = xt(ti)

def espectro_xn_analiza(xn,fs,aliasn):
    '''operaciones para espectro x[n]
    # Ref: blog|Espectro x[n] – Señal discreta en tiempo
    '''
    #xn = xn.doit()#.subs(pi,np.pi).doit() # mantener valores >2pi
    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_n['x_expand'] = x_term_expand

    freqn_conteo = len(freq_n)

    # normaliza w frecuencia angular
    for i in range(0,freqn_conteo,1):
        while abs(freq_n[i].subs(pi,np.pi))>np.pi:
            if freq_n[i]>0:
                freq_norm = freq_n[i]-2*pi
            else:
                freq_norm = freq_n[i]+2*pi
            freq_n[i] = freq_norm

    # Añadir alias a señal
    freqn_alias = np.zeros(freqn_conteo,dtype=bool)
    freq_m = np.zeros(0,dtype=float)
    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):
            freq_k = (freq_i+2*pi*k)
            if  not((freq_k in freq_n) or (freq_k in freq_m)):
                # añade freq_n[i] + 2*pi*k
                freq_m = np.concatenate([freq_m,[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]])
            freq_k = (freq_i-2*pi*k)
            if not((freq_k in freq_n) or (freq_k in freq_m)):
                # añade freq_n[i] - 2*pi*k
                freq_m = np.concatenate([freq_m,[freq_i-2*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]])
    freq_n = np.concatenate([freq_n,freq_m])

    # 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

    # alias principal, 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]
        if abs(unafreq) > 1:
            freq_alias[i] = True
    freq_alias_conteo = np.sum(freq_alias)
    un_espectro_n['freq_alias'] = freq_alias

    # reconstruye señal con alias principal
    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
    print('freq_alias0',freq_alias0)
    print('x0_cuenta',x0_cuenta,fs,pi)
    for i in range(0,x0_cuenta,1):
        print('i',i,'freq_x0[i]',freq_x0[i],fase_x0[i],ampl_x0[i], unfactor)
        if (float(freq_x0[i]))>tolera:
            una_freq = float(freq_x0[i]*unfactor*(fs/(2*pi)))
            print('una_freq>0',una_freq, unfactor, fs)
            x0t = x0t + 2*ampl_x0[i]*sym.cos(DosPi*una_freq*t+sym.UnevaluatedExpr(fase_x0[i])) 
        if freq_x0[i]>=0 and freq_x0[i]<tolera: # es cero
            una_freq = float(freq_x0[i]*unfactor*(fs/(2*pi)))
            print('una_freq<tolera',una_freq, unfactor, fs)
            x0t = x0t + ampl_x0[i]*sym.cos(DosPi*una_freq*t+sym.UnevaluatedExpr(fase_x0[i]))
        print('i',i,'x0t',x0t)
    x0t = dsp._float_is_int(x0t) # si es entero
    un_espectro_n['x0t'] = x0t
    return (un_espectro_n)

un_espectro_n = espectro_xn_analiza(xn,fs,aliasn)
x0t = un_espectro_n['x0t']
x0tr = x0t.subs(pi,np.pi)
if x0tr.has(t): # dos componentes en la constante.
    x0tr = sym.lambdify(t,x0tr,modules=equivalentes)
else: # componentes constantes
    constante = float(x0tr.doit())
    x0tr = lambda t: constante +0*t
    x0t = x0t.simplify()
x0_ti = x0tr(ti)
    

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

print('\nmuestreo fs:',fs,' ; dt:',dt, ' ; fs_veces:',fs_veces)
print('muestreo_tipo: ',muestreo_tipo)
print('muestreo:',muestras_n,
      ' tamaño ki:',len(ki),'; tamaño ti:',len(ti))
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)

# GRAFICAS  interactivas----------------------------
from matplotlib.widgets import Slider, Button, TextBox
# Grafica de espectro de frecuencias
# Ref: blog|Espectro x[n] – Señal discreta en tiempo
graf_dx = 0.12 # margen en eje x
fig_espectro = plt.figure()

# grafica espectro x(t) continuo en tiempo ---------
# espectro x(t) grafico entorno
graf_fasor = fig_espectro.add_subplot(311)
a_max = float(max(ampl))
f_max = float(max(max(freq),fs))*(1+graf_dx/2)
if f_max!=0:
    graf_fasor.set_xlim([-f_max*(1+graf_dx),
                         f_max*(1+graf_dx)])
else:
    graf_fasor.set_xlim([-1,1])
graf_fasor.set_ylim([0,a_max*(1+graf_dx*4)])
graf_fasor.grid()
graf_fasor.axhline(0,color='black')
graf_fasor.axvline(0,linestyle='dotted',color='grey')
graf_fasor.set_ylabel('Magnitud '+xt_etiqueta)
graf_fasor.set_xlabel('freq Hz', labelpad=0,)

# espectro x(t) - componentes 
puntos_xt = graf_fasor.stem(freq,ampl) # espectro 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')
    
# espectro x(t) - fs frecuencia de muestreo
puntos_fs = graf_fasor.stem(fs,a_max/2,linefmt = 'C3:', ) # fs
puntos_fs_etiq = graf_fasor.annotate('fs',xy=(fs,(a_max/2)),
        xytext=(0,5),textcoords='offset points',ha='center')
texto = '$' + sym.latex(titulo)+'$' +'  ; fs='+str(fs)
texto = texto + '  ; alias='+str(aliasn)
graf_fasor.set_title(texto)

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

# grafica espectro x[n] de frecuencias discretas---------
# Ref: blog|Espectro x[n] – Señal discreta en tiempo
graf_fasorn = fig_espectro.add_subplot(313)
# grafica x[n] - entorno
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*4)
w_max = float(max(un_espectro_n['freq_max'],1))*(1+graf_dx/2)
graf_fasorn.set_xlim([-w_max,w_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')
graf_fasorn.set_ylabel('Magnitud '+xn_etiqueta)
graf_fasorn.set_xlabel('freq rad')

# grafica x[n] - magnitud
freq_alias0 = np.invert(freq_alias) 
puntos_alias0 = graf_fasorn.stem(freq_n[freq_alias0],
                                 ampl_n[freq_alias0],
                 linefmt = 'C0--',label = 'alias0')
if len(freq_n[freq_alias])>0:
    puntos_alias = graf_fasorn.stem(freq_n[freq_alias],
                                    ampl_n[freq_alias],
                     linefmt = 'C1--',label = 'alias')
# actualiza eje x etiquetas
if un_espectro_n['freq_factor'] != sym.S.One:
    unfactor_txt = 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_txt)
    graf_fasorn.set_xticks(ticks=freq_n,labels=freq_etiq)
# etiquetas de fasor
a0_etiq = []
for k in range(0,len(freq_n),1):
    puntos_a0_etiq = graf_fasorn.annotate(etiq_n[k],
                    xy=(freq_n[k],ampl_n[k]),
                    xytext=(0,5),textcoords='offset points',
                    ha='center')
    a0_etiq.append(puntos_a0_etiq)
graf_fasorn.legend()

plt.tight_layout()

# grafica interactiva
plt.subplots_adjust(bottom=0.25) # espacio widgets

# slider: barras para valores 
# amplitud slider [x,y,ancho,alto]
fs_donde = plt.axes([0.2, 0.10, 0.65, 0.03])
df_pasos=5
fs_slider = Slider(fs_donde, 'fs',
                   freq_max/2, (max([fs,2*freq_max])+2*df_pasos),
                   valinit = fs, valstep = df_pasos,
                   orientation='horizontal')
txt_xndonde = plt.axes([0.2, 0.02, 0.55, 0.045])
txt_xn = TextBox(txt_xndonde, "alias0 x(t): ",
                   initial=x0t)

def grafico_actualiza(val):
    # actualiza valores x,y
    fs = fs_slider.val
    # espectro x(t)
    dsp.stem_update(puntos_fs,[fs],[ampl_max/2],graf_fasor)
    puntos_fs_etiq.xy = (fs, ampl_max/2)
    puntos_fs_etiq.set_visible(True)
    texto = '$' + sym.latex(titulo)+'$' +'  ; fs='+str(fs)
    texto = texto + '  ; alias='+str(aliasn)
    graf_fasor.set_title(texto)

    muestreo_tipo = sampling_type(fs,freq_max)
    print(' \n***** fs',fs,muestreo_tipo)

    # grafica x(t), x[n] --------------
    ti_max = max(ti)
    dt = 1/fs
    muestras_n=2
    if ti_max>=dt:
        muestras_n = int(ti_max/dt)+1
    ki = np.arange(0,muestras_n,1,dtype=int)
    tki = ki*dt
    xki = xt(tki)
    dsp.stem_update(puntos_xn,tki,xki,graf_xt)

    # Nuevo espectro x[n]
    xn = x.subs(t,n/fs)
    un_espectro_n = espectro_xn_analiza(xn,fs,aliasn)
    x0t = un_espectro_n['x0t']
    print('x0t:',x0t)
    
    x0tr = x0t.subs(pi,np.pi)
    if x0tr.has(t): # dos componentes en la constante.
        x0tr = sym.lambdify(t,x0tr,modules=equivalentes)
    else: # componentes constantes
        constante = float(x0tr.doit())
        x0tr = lambda t: constante +0*t
        x0t = x0t.simplify()
    x0_ti = x0tr(ti)

    linea_xt_a0.set_xdata(ti)
    linea_xt_a0.set_ydata(x0_ti)

    # grafica x[n] - magnitud
    freq_alias = un_espectro_n['freq_alias']
    freq_alias0 = np.invert(freq_alias)
    freq_n = un_espectro_n['freq']
    ampl_n = un_espectro_n['ampl']
    dsp.stem_update(puntos_alias0,
                    freq_n[freq_alias0],
                    ampl_n[freq_alias0],graf_fasorn)
    
    if len(freq_n[freq_alias])>0:
        dsp.stem_update(puntos_alias,
                        freq_n[freq_alias],
                        ampl_n[freq_alias],graf_fasorn)
    if un_espectro_n['freq_factor'] != sym.S.One:
        unfactor_txt = 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_txt)
        graf_fasorn.set_xticks(ticks=freq_n,labels=freq_etiq)
    # etiquetas de fasor
    etiq_n = un_espectro_n['etiq']
    for ann in a0_etiq:
        ann.remove()
    a0_etiq.clear()
    for k in range(0,len(freq_n),1):
        puntos_a0_etiq = graf_fasorn.annotate(etiq_n[k],
                        xy=(freq_n[k],ampl_n[k]),
                        xytext=(0,5),textcoords='offset points',
                        ha='center')
        a0_etiq.append(puntos_a0_etiq)
    
    txt_xn.set_val(x0t)
    
    fig_espectro.canvas.draw_idle() # actualiza figura

# boton reinicio de gráfica
btn_rstdonde = plt.axes([0.8, 0.025, 0.1, 0.04])
btn_rst = Button(btn_rstdonde, 'Reset',
                 hovercolor='0.975')
def grafico_reinicia(event):
    fs_slider.reset()
    return()

# objetos interactivos
fs_slider.on_changed(grafico_actualiza)
btn_rst.on_clicked(grafico_reinicia)

plt.show()

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

2.3 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 otros alias, k=1
alias principal
ω1[n] = 0.4π
ω3[n] = 0.4π + 2π = 2.4
ω4[n] = 0.4π – 2π = -1.6
alias folded
(pliegue negativo)
ω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 discreto n coseno 01

[ 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 Algoritmo 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
with sym.evaluate(False): # no simplificar freq angular w >2*pi
    x1 = sym.cos(0.4*pi*n)
    x2 = sym.cos(1.6*pi*n)
    x3 = sym.cos(2.4*pi*n)
    xn = x1+x2+x3
x_etiqueta_n = 'x[n]'

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

tolera = 1e-9 # tolerancia a error, números iguales

# PROCEDIMIENTO
titulo = xn # copia para titulo de grafico (antes de procesar)
# espectro x[n]
# Ref: Blog|Espectro-Operaciones en dominio de tiempo y frecuencia
xn = xn.subs(pi,np.pi) # mantener valores >2pi
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]:  ',titulo)
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 entorno
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')
texto = '$' + sym.latex(titulo)+'$' +'  ; fs='+str(fs)
graf_fasorn.set_title(texto)
graf_fasorn.set_ylabel(x_etiqueta_n)
graf_fasorn.set_xlabel('freq rad')

# 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, graficar
    graf_fasorn.stem(freq_n[freq_alias],ampl_n[freq_alias],
                     linefmt = 'C1--', label='alias')
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')

# etiquetas eje x usando unfactor
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)
    
graf_fasorn.legend()
plt.tight_layout()
plt.show()

[ espectro_n ] [ algoritmo ] [ gráfica ]

2.2 Aliasing con Python

[ alias ] [ ejemplo ] [ algoritmo ] [ ejercicio ] [ gráfico interactivo ]
..


1. Concepto de alias

Referencia: Downey 2.1 p21

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

muestreo fs cos animate

[ alias ] [ ejemplo ] [ algoritmo ] [ ejercicio ] [ gráfico interactivo ]
..


2. Ejemplo

Referencia: McClellan 4.1.2 p127

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

x_1[n] = \cos(0.4\pi n) x_2[n] = \cos(2.4\pi n) = cos(0.4\pi n + 2\pi n ) = cos(0.4\pi 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 sobre-muestreo 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: 12
 tamaño ki: 12
 tamaño ti: 133
fs: 10  ; dt: 0.1  ; fs_veces: 12
cos((0.4*pi)*n)
cos((2.4*pi)*n)

La gráfica permite mostrar que las muestras son iguales cuando se repite n veces el valor de tiempo de muestreo dt=1/fs para ambas señales.

muestreo Alias coseno 02

Si la frecuencia de x2[n] fuese solamente π, la gráfica muestra la diferencia de valores para x1[n] y x2[n]

x_1[n] = \cos(0.4\pi n) x_2[n] = \cos(\pi n) = cos(0.4\pi n + 0.6\pi n ) = cos(\pi n)
muestreo: 12
 tamaño ki: 12
 tamaño ti: 133
fs: 10  ; dt: 0.1  ; fs_veces: 12
cos((0.4*pi)*n)
cos((1.0*pi)*n)

En la gráfica se destaca la diferencia de valores para cada señal luego del primer dt.
muestreo Alias coseno 03

[ alias ] [ ejemplo ] [ algoritmo ] [ ejercicio ] [ gráfico interactivo ]
..


3. Algoritmo 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

# variables continuas
t = sym.Symbol('t', real=True)
# variables discretas
n = sym.Symbol('n', integer=True, positive=True)

pi = sym.pi
DosPi = sym.UnevaluatedExpr(2*sym.pi)
equivalentes = [{'DiracDelta': lambda x: 1*(x==0)},
                {'Heaviside': lambda x,y: np.heaviside(x, 1)},
                'numpy',]
# INGRESO
# usar sym.UnevaluatedExpr() para no simplificar
#   automaticamente freq angular w>(2*pi) en Sympy
with sym.evaluate(False): # no simplificar freq angular w >2*pi
    w1 = 0.4
    w2 = 0.4+2
    x1 = sym.cos(w1*pi*n) 
    x2 = sym.cos(w2*pi*n)

xn = [x1,x2]
xn_etiqueta  = ['x1[n]','x2[n]']
xt_etiqueta = ['x1(t)]','x2(t)']
titulo = 'x1[n] = '+str(x1)+'  ||  x2[n] = '+str(x2)

nT = 2.25 # periodos a graficar
fs = 10   # muestreo discreto
fs_veces = 12   # suavizar x(t), sobremuestreo

# PROCEDIMIENTO
muestras_n = int(nT*(2/w1))+1
x_conteo = len(xn)
# muestreo x[n]
dt = 1/fs # tamaño de paso con fs
ki = np.arange(0,muestras_n,1,dtype=int)

# muestreo x(t)
dtc = dt/fs_veces # suavizar x(t), sobremuestreo
ti = np.arange(0,(muestras_n-1)*dt+dtc, dtc)
xn_muestras = {}

for i in range(0,x_conteo,1):
    unasenal = xn[i]
    xtk = unasenal.subs(n,t*fs)
    xt = sym.lambdify(t,xtk, modules=equivalentes)
    
    xki = xt(ki*dt) 
    xti = xt(ti)
    xn_muestras[i] = {'ki':ki,'xki':xki,
                      'ti':ti,'xti':xti,}

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

# GRAFICA
def _graf_lineacolor_i(i):
    ''' función auxiliar, asigna el color en la paleta 
    de matplotlib acorde al número i entre 0 y 9
    '''
    if (i<=9): # color de la línea
        lineacolor = 'C'+str(i)
    else:
        numcolor = i%10
        lineacolor = 'C'+str(numcolor)
    return(lineacolor)

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

plt.tight_layout()
plt.legend()
plt.show()

[ alias ] [ ejemplo ] [ algoritmo ] [ ejercicio ] [ gráfico interactivo ]
..


4. 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: 7
 tamaño ki: 7
 tamaño ti: 272
fs: 10  ; dt: 0.1  ; fs_veces: 45
cos((0.4*pi)*n - 0.2*pi)
cos((8.4*pi)*n - 0.2*pi)

muestreo Alias coseno 04
Usando los parámetros de entrada:

# INGRESO
# usar sym.UnevaluatedExpr() para no simplificar
#   automaticamente freq angular w>(2*pi) en Sympy
with sym.evaluate(False): # no simplificar freq angular w >2*pi
    w1 = 0.4
    w2 = 0.4+8
    x1 = sym.cos(w1*pi*n-0.2*pi) 
    x2 = sym.cos(w2*pi*n-0.2*pi)

xn = [x1,x2]
xn_etiqueta  = ['x1[n]','x2[n]']
xt_etiqueta = ['x1(t)]','x2(t)']
titulo = 'x1[n] = '+str(x1)+'  ||  x2[n] = '+str(x2)

nT = 1.25 # periodos a graficar
fs = 10   # muestreo discreto
fs_veces = 45   # suavizar x(t), sobremuestreo

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…

5. Alias Principal

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

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

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

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

muestreo Alias coseno 05

muestreo: 7
 tamaño ki: 7
 tamaño ti: 272
fs: 10  ; dt: 0.1  ; fs_veces: 45
cos((0.4*pi)*n - 0.2*pi)
cos((2.4*pi)*n - 0.2*pi)
cos((4.4*pi)*n - 0.2*pi)

El bloque de ingreso para el algoritmo y el número de muestras para el periodo de la señal de menor frecuencia angular es referenciado a ω0.

# INGRESO
# usar sym.UnevaluatedExpr() para no simplificar
#   automaticamente freq angular w>(2*pi) en Sympy
with sym.evaluate(False): # no simplificar freq angular w >2*pi
    w0 = 0.4
    w1 = 0.4+2
    w2 = 0.4+4
    x0 = sym.cos(w0*pi*n - 0.2*pi) 
    x1 = sym.cos(w1*pi*n - 0.2*pi)
    x2 = sym.cos(w2*pi*n - 0.2*pi)

xn = [x0,x1,x2]
xn_etiqueta  = ['x0[n]','x1[n]','x2[n]']
xt_etiqueta = ['x0(t)','x1(t)','x2(t)']
titulo = 'xk[n]= sym.cos(0.4*pi*n + k*2*pi*n- 0.2*pi)'

nT = 1.25 # periodos a graficar
fs = 10   # muestreo discreto
fs_veces = 45   # suavizar x(t), sobremuestreo

# PROCEDIMIENTO
muestras_n = int(nT*(2/w0))+1

[ alias ] [ ejemplo ] [ algoritmo ] [ ejercicio ] [ gráfico interactivo ]
..


5. Gráfico interactivo

Se añade en la fórmula de la señal x2[n] el valor de k como control para el gráfico:

x_1[n] = \cos(0.4\pi n) x_2[n] = \cos(2.4\pi n) = cos(0.4\pi n +k( 2\pi n )) = cos(0.4\pi n)

El gráfico interactivo resultante se actualiza x2[n] y el título, también se muestra en los resultados si hay el «alias».

muestras: 12
 tamaño ki: 12
 tamaño ti: 606
fs: 10  ; dt: 0.1  ; fs_veces: 55
cos((0.4*pi)*n)
cos((2.4*pi)*n)
k: 1 x2[n] NO es alias de x1[n]
k: 2 x2[n] es alias de x1[n]
k: 3 x2[n] NO es alias de x1[n]
k: 4 x2[n] es alias de x1[n]
k: 5 x2[n] NO es alias de x1[n]
k: 6 x2[n] es alias de x1[n]
k: 7 x2[n] NO es alias de x1[n]

5.1 Algoritmo interactivo en Python

Se actualiza la parte gráfica a la forma de objetos fig, graf para crear los objetos interactivos de barra de desplazamiento (slider) k , botón Reset y cuadro de texto para la observación si es o no alias.

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

# variables continuas
t = sym.Symbol('t', real=True)
# variables discretas
n = sym.Symbol('n', integer=True, positive=True)

pi = sym.pi
DosPi = sym.UnevaluatedExpr(2*sym.pi)
equivalentes = [{'DiracDelta': lambda x: 1*(x==0)},
                {'Heaviside': lambda x,y: np.heaviside(x, 1)},
                'numpy',]
# INGRESO
# usar sym.UnevaluatedExpr() para no simplificar
#   automaticamente freq angular w>(2*pi) en Sympy
with sym.evaluate(False): # no simplificar freq angular w >2*pi
    w1 = 0.4
    k = 2 
    w2 = 0.4+k
    x1 = sym.cos(w1*pi*n) 
    x2 = sym.cos(w2*pi*n)

xn = [x1,x2]
xn_etiqueta = ['x1[n]','x2[n]']
xt_etiqueta = ['x1(t)]','x2(t)']
titulo = 'x1[n] = '+str(x1)+'  ||  x2[n] = '+str(x2)

nT = 2.25 # periodos a graficar
fs = 10   # muestreo discreto
fs_veces = 55   # suavizar x(t), sobremuestreo

# PROCEDIMIENTO
muestras_n = int(nT*(2/w1))+1
x_conteo = len(xn)
# muestreo x[n]
dt = 1/fs # tamaño de paso con fs
ki = np.arange(0,muestras_n,1,dtype=int)

# muestreo x(t)
dtc = dt/fs_veces # para x(t)
ti = np.arange(0,(muestras_n-1)*dt+dtc, dtc)
xn_muestras = {}

for i in range(0,x_conteo,1):
    unasenal = xn[i]
    xtk = unasenal.subs(n,t*fs)
    xt = sym.lambdify(t,xtk, modules=equivalentes)
    
    xki = xt(ki*dt) 
    xti = xt(ti)
    xn_muestras[i] = {'ki':ki,'xki':xki,
                     'ti':ti,'xti':xti,}
if k%2==0:
    observa = 'x2[n] es alias de x1[n]'
else:
    observa = 'x2[n] NO es alias de x1[n]'

# SALIDA
print('muestras:',muestras_n)
print(' tamaño ki:',len(ki))
print(' tamaño ti:',len(ti))
print('fs:',fs,' ; dt:',dt, ' ; fs_veces:',fs_veces)
for unasenal in xn:
    print(unasenal)

# GRAFICA interactiva
from matplotlib.widgets import Slider, Button, TextBox
def _graf_lineacolor_i(i):
    ''' función auxiliar, asigna el color en la paleta
    de matplotlib acorde al número i entre 0 y 9
    '''
    if (i<=9): # color de la línea
        lineacolor = 'C'+str(i)
    else:
        numcolor = i%10
        lineacolor = 'C'+str(numcolor)
    return(lineacolor)

fig, grf = plt.subplots()
puntos = [] ; lineas = []
for i in range(0,len(xn_muestras),1):
    color_i = _graf_lineacolor_i(i)
    estilo_i = ':' # estilo de la línea
    if i%2 == 0:  #es impar
        estilo_i = '--'
    # muestreo x[n]
    ki = xn_muestras[i]['ki']
    xki = xn_muestras[i]['xki']
    puntofmt = color_i + estilo_i
    puntoi = grf.stem(ki*dt, xki, linefmt = puntofmt,
                      label=xn_etiqueta[i])
    # muestreo x(t)
    ti = xn_muestras[i]['ti']
    xti = xn_muestras[i]['xti']
    lineai, = grf.plot(ti,xti, '-', color = color_i,
             label=xt_etiqueta[i])
    puntos.append(puntoi)
    lineas.append(lineai)
#plt.axhline(0,color='black')
grf.set_xlabel('t')
grf.set_ylabel('amplitud')
grf.set_title(titulo)

plt.tight_layout()
plt.legend()
#plt.show()

# grafica elementos interactivos
plt.subplots_adjust(bottom=0.25) # espacio widgets

# slider: barras para valores 
# amplitud slider [x,y,ancho,alto]
k_donde = plt.axes([0.2, 0.10, 0.65, 0.03])
k_slider = Slider(k_donde, 'k',
                   0, 8,
                   valinit=k,valstep= 1,
                   orientation='horizontal')
txt_donde = plt.axes([0.2, 0.02, 0.4, 0.045])
txt_observa = TextBox(txt_donde, "Observación: ",
                   initial=observa)
#text_box.on_submit(submit)

def stem_update(puntos,xdata,ydata,grafico):
    '''actualiza puntos/tallos de plt.stem
    con los datos de cada eje en un gráfico
    '''
    puntos.markerline.set_xdata(xdata)
    puntos.markerline.set_ydata(ydata)
    segmentos=[] # tallos o troncos
    for j in range(0,len(xdata),1):
        segmentos.append([[xdata[j],0],
                          [xdata[j],ydata[j]]])
    puntos.stemlines.set_segments(segmentos)
    puntos.baseline.set_xdata([xdata[0],xdata[-1]])
    grafico.relim()
    grafico.autoscale_view()
    return(puntos,grafico)

def grafico_actualiza(val):
    
    # actualiza valores x,y
    k = k_slider.val
    with sym.evaluate(False):
        w2 = 0.4+k 
        x2 = sym.cos(w2*pi*n)
    xn[1] = x2
    titulo = 'x1[n] = '+str(x1)+'  ||  x2[n] = '+str(x2)
    
    i = 1
    unasenal = xn[i]
    xtk = unasenal.subs(n,t*fs)
    xt = sym.lambdify(t,xtk, modules=equivalentes)
    
    xki = xt(ki*dt) 
    xti = xt(ti)
    xn_muestras[i] = {'ki':ki,'xki':xki,
                     'ti':ti,'xti':xti,}

    # actualiza grafico
    stem_update(puntos[1],ki*dt,xki,grf)
    lineas[i].set_ydata(xti)
    grf.set_title(titulo)
    fig.canvas.draw_idle() # actualiza figura

    # observación
    if k%2==0:
        observa = 'x2[n] es alias de x1[n]'
    else:
        observa = 'x2[n] NO es alias de x1[n]'
    txt_observa.set_val(observa)
    print('k:',k,observa)
    
# boton reinicio de gráfica
btn_rstdonde = plt.axes([0.8, 0.025, 0.1, 0.04])
btn_rst = Button(btn_rstdonde, 'Reset',
                 hovercolor='0.975')
def grafico_reinicia(event):
    k_slider.reset()
    return()

# objetos interactivos
k_slider.on_changed(grafico_actualiza)
btn_rst.on_clicked(grafico_reinicia)

plt.show()

[ alias ] [ ejemplo ] [ algoritmo ] [ ejercicio ] [ gráfico interactivo ]

2.1 Muestreo con Python

[ muestras ] [ ejemplo ] [ algoritmo ] [ gráfico interactivo ]
..


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.

muestreo fs cos animate

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.

[ muestras ] [ ejemplo ] [ algoritmo ] [ gráfico interactivo ]
..


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: 100
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 de x(t), se sobre-muestrea 10 veces la señal para que sea visiblemente semejante a la forma contínua.
muestreo Alias 01 oseno[ muestras ] [ ejemplo ] [ algoritmo ] [ gráfico interactivo ]
..


3. Algoritmo en Python

# 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
# variables continuas
t = sym.Symbol('t', real=True)
# variables discretas
n = sym.Symbol('n', integer=True, positive=True)

pi = sym.pi
DosPi = sym.UnevaluatedExpr(2*sym.pi)
equivalentes = [{'DiracDelta': lambda x: 1*(x==0)},
                {'Heaviside': lambda x,y: np.heaviside(x, 1)},
                'numpy',]
# INGRESO
# señal continua
f0 =100
x = sym.cos(f0*DosPi*t + 0)
# señal discreta
fs = 500  # muestreo discreto

Tn = 2  # periodos a graficar
fs_veces = 10 # suavizar x(t), sobremuestreo

# PROCEDIMIENTO
xt = sym.lambdify(t,x, modules=equivalentes)
xn = x.subs(t,n/fs)

# muestreo x[n]
muestras = int(Tn*(fs/f0))+1
if muestras<2:
    muestras=2
ki = np.arange(0,muestras,1,dtype=int)
dt = 1/fs
tk = ki*dt
xk = xt(tk)
 
# muestreo x(t)
dtc = dt/fs_veces # sobremuestreo para x(t)
ti = np.arange(0,(muestras-1)*dt + dtc, dtc)
xti = xt(ti)

# SALIDA
print('muestreo:',muestras)
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(tk,xk,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(xk,linefmt = 'C0:',label='x[n]')
#plt.xticks(ki)
plt.xlabel('n')
plt.ylabel('amplitud')
plt.legend()

plt.tight_layout()
plt.show()

[ muestras ] [ ejemplo ] [ algoritmo ] [ gráfico interactivo ]
..


4. Muestreo – gráfico interactivo

Para el ejercicio de muestreo, se añade un control deslizante para la frecuencia de muestreo fs.

muestreo Alias coseno 07

Se incorpora un botón de reinicio (Reset) para el valor predeterminado de fs.

El gráfico se actualiza o reinicia al interactuar con el control deslizante fs o el botón de reinicio.

# objetos interactivos
fs_slider.on_changed(grafico_actualiza)
btn_rst.on_clicked(grafico_reinicia)

La actualización de gráfico se realiza solo para los puntos muestra en el eje tiempo y en el eje n.

Referencia: Gráficas 2D interactivas – matplotlib.widgets

4.1 Algoritmo en Python – muestreo interactivo para fs

Para el algoritmo se debe reemplazar la parte desde la gráfica # GRAFICA x(t) y x[n] , con las siguientes instrucciones que cambian a un objeto las lineas y los puntos para actualizar con cada interacción.

# GRAFICA x(t) y x[n] interactiva
from matplotlib.widgets import Slider, Button
fig, [grf1,grf2] = plt.subplots(2, 1)
# x(t) contínua
linea1, = grf1.plot(ti,xti, color='gray',label='x(t)')
puntos1 = grf1.stem(tk,xk,linefmt = 'C0:',
         label='x[n]')
grf1.set_xlabel('t')
grf1.set_ylabel('amplitud')
grf1.set_title(x)
grf1.legend()

# x[n] discreta
puntos2 = grf2.stem(xk,linefmt = 'C0:',label='x[n]')
grf2.set_xlabel('n')
grf2.set_ylabel('amplitud')
grf2.legend()

plt.tight_layout()
# plt.show()

# grafica interactiva
plt.subplots_adjust(bottom=0.25) # espacio widgets

# slider: barras para valores 
# amplitud slider [x,y,ancho,alto]
fs_donde = plt.axes([0.2, 0.10, 0.65, 0.03])
fs_slider = Slider(fs_donde, 'fs',
                   int(f0/2), f0*20,
                   valinit=fs,valstep= 10,
                   orientation='horizontal')

def stem_update(puntos,xdata,ydata,grafico):
    '''actualiza puntos/tallos de plt.stem
    con los datos de cada eje en un gráfico
    '''
    puntos.markerline.set_xdata(xdata)
    puntos.markerline.set_ydata(ydata)
    segmentos=[] # tallos o troncos
    for j in range(0,len(xdata),1):
        segmentos.append([[xdata[j],0],
                          [xdata[j],ydata[j]]])
    puntos.stemlines.set_segments(segmentos)
    puntos.baseline.set_xdata([xdata[0],xdata[-1]])
    grafico.relim()
    grafico.autoscale_view()
    return(puntos,grafico)

def grafico_actualiza(val):
    # actualiza valores x,y
    fs = fs_slider.val
    muestras = int(Tn*(fs/f0))+1
    if muestras<2:
        muestras=2
    ki = np.arange(0,muestras,1,dtype=int)
    dt = 1/fs
    tk = ki*dt
    xk = xt(tk)
    print('f0:',f0,' ;fs:',fs,' ',tipomuestreo)
    # actualiza grafico
    stem_update(puntos1,tk,xk,grf1)
    stem_update(puntos2,ki,xk,grf2)
    fig.canvas.draw_idle() # actualiza figura

# boton reinicio de gráfica
btn_rstdonde = plt.axes([0.8, 0.025, 0.1, 0.04])
btn_rst = Button(btn_rstdonde, 'Reset',
                 hovercolor='0.975')
def grafico_reinicia(event):
    fs_slider.reset()
    return()

# objetos interactivos
fs_slider.on_changed(grafico_actualiza)
btn_rst.on_clicked(grafico_reinicia)

plt.show()

[ muestras ] [ ejemplo ] [ algoritmo ] [ gráfico interactivo ]