Análisis de la sensibilidad en la topología Sallen-Key
Por Mariano Llamedo Soria
Resumen
En este notebook se analizará la respuesta en frecuencia de un filtro pasabajo activo, mediante el uso de las siguientes funciones:
Análisis de la respuesta en frecuencia: analyze_sys, bodePlot, pzmap, pretty_print_bicuad_omegayq, GroupDelay
De presentación algebraica: print_latex, a_equal_b_latex_s, print_subtitle
Dibujo de redes: dibujar_elemento_derivacion, dibujar_puerto_entrada, dibujar_espacio_derivacion, dibujar_elemento_serie, dibujar_puerto_salida
Finalmente se presentará un análisis estadístico cualitativo de la sensibilidad de dicha respuesta a los elementos circuitales.
Introducción
En el siguiente documento analizaremos la dispersión de parámetros \(Q\), \(\omega_0\) y la ganancia \(K\) correspondiente a una topología Sallen-Key en su configuración pasabajo
Como se analizó en clase, la transferencia de tensión está definida por la siguiente función
siendo los parámetros de la transferencia en función de los componentes de la red:
Los primeros dos parámetros, \(\omega_0^2\) y \(K\), no llaman la atención, pero sí la selectividad \(Q\) que depende de la ganancia \(K\) de una forma muy singular. Como se observa en el denominador, si la ganancia iguala o supera el valor de 2 (o 3 en el caso que \(G_1=G_2=G\)), el valor de \(Q\) podría incrementarse demasiado, incluso en teoría comprometiendo la estabilidad de la red. Esta sería una forma de analizarlo, sin embargo también hay otra forma, si despejamos \(K\) de la expresión simplificada para \(G_1=G_2=G\)
Si necesitamos un filtro con un \(Q\) elevado (no mucho más grande que 3), la ganancia requerida estará apenas por debajo de 3. En consecuencia, implementar \(K\) mediante dos resistores (\(R_A\) y \(R_B\)) con tolerancias comerciales resulta difícil o impracticable.
Análisis de la Sensibilidad
Para este tipo de circuitos con dependencias restrictivas (o marginalmente problemáticas), resulta necesario estudiar en detalle cómo afectan el valor de los componentes a la transferencia total \(T(s)\), o más precisamente a algún parámetro en particular: \(K\), \(Q\) u \(\omega_0\). Este estudio se denomina análisis de la sensibilidad, que según se estudió en clase, analiza la dependencia lineal entre el valor del componente y el parámetro en cuestión. Para este caso, la sensibilidad de \(Q\) respecto a \(K\) se denomina \(S_K^Q\)
y se aproxima a la relación lineal entre el error relativo entre \(Q\) y \(K\)
es decir que un error relativo \(\Delta K/K=5\%\), para un valor de \(Q = 5\), se convierte en \(\Delta Q/Q= 3 \times 15-1 = 44\%\).
Restaría analizar \(S_{R_A}^K\) y \(S_{R_B}^K\) para poder calcular
Esto se deja para quienes tengan interés especial.
Análisis de Monte Carlo
Sin embargo, no es el único método para estudiar la sensibilidad de una red respecto de sus componentes. Otro enfoque muy utilizado es el denominado análisis de Monte Carlo. El mismo puede resumirse mediante los siguientes pasos:
Definir las variables independientes: sus posibles valores y distribuciones estadísticas. En caso de incertidumbre, se puede asumir una distribución uniforme entre dos valores límite, lo que equivale a no favorecer ningún valor en dicho intervalo, o desconocer cómo se distribuyen los valores de un proceso de fabricación.
Samplear o tomar al azar un valor para cada componenente (o variable elegida en 1.), y calcular determinísticamente la transferencia \(T_i(s)\).
Repetir \(N\) veces para acumular una cantidad significante de realizaciones. Una regla adecuada para asumir un valor de \(N\) adecuado es de 10 veces la cantidad de variables independientes definida en 1..
Con los \(N\) resultados acumulados de \(T_i(s)\), se procede a calcular los valores más representativos de centralidad, dispersión, y eventualmente, los casos más y menos favorables.
Estos serían los resultados del análisis de Monte Carlo, comportamientos de la red desde un enfoque más generalista, y una persepctiva más amplia, ya que podemos hablar al menos de 3 comportamientos:
El más probable, dado por el comportamiento central que denominamos \(T_0(s)\). Podría ser el comportamiento medio o mediano.
El menos favorable para la red, o el peor caso analizado. Habría que definir una función de costo, en este caso podría ser el valor del parámetro \(Q\). Tener un \(Q\) elevado implica un costo elevado para esta red, dado que el \(Q\) objetivo por lo general será más bajo.
El más favorable para la red, o el mejor caso analizado, sería simplemente el más cercano al \(Q\) objetivo.
Todo esto, se contrasta lógicamente con el análisis de sensibilidad donde solo podemos aspirar a tener un intervalo de incertidumbre, o error porcentual \( \frac{\Delta Q}{Q} \)
Experimentación
Realizaremos un estudio de la dispersión, estilo Monte Carlo, de los parámetros \(Q\), \(\omega_0\) y \(K\) muestreando valores de \(C\), \(R\) y \(R_B\) con una distribución uniforme, definida por los valores centrales y la tolerancia de cada componente, de la siguiente manera:
\(C = 1\)
\(R = 1\)
\(R_B = R \times (2-\frac{1}{Q}) = 2-\frac{1}{Q}\)
Siendo \(\omega_0 = 1\) y \(Q = 10\). Elegimos un \(Q\) elevado e impracticable para acentuar las limitaciones de esta red.
Para ello se calculará la función transferencia \(T_i(s)\) a partir de los 3 valores sampleados \(C_i, R_i\) y \(R_{Bi}\) de cada componente. Finalmente se calculan las \(N\) respuestas de módulo y fase, y los valores de los parámetros de cada transferencia \(Q_i\) y \(\omega_{0i}\).
Se procede a inicializar la simulación de la forma usual.
# Inicialización e importación de módulos
# Módulos externos
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
from scipy.signal import TransferFunction
# Esta parte de código la agregamos SOLO en los notebooks para fijar el estilo de los gráficos.
fig_sz_x = 13
fig_sz_y = 7
fig_dpi = 80 # dpi
fig_font_size = 13
mpl.rcParams['figure.figsize'] = (fig_sz_x, fig_sz_y)
mpl.rcParams['figure.dpi'] = fig_dpi
plt.rcParams.update({'font.size':fig_font_size})
Separamos la inicialización del módulo pyTC2 para enfatizar las funciones concretas que se necesitan.
# Ahora importamos las funciones de PyTC2
from pytc2.sistemas_lineales import pzmap, GroupDelay, bodePlot, pretty_print_bicuad_omegayq
from pytc2.general import print_subtitle, print_latex, a_equal_b_latex_s
Ahora se definen las cuestiones más generales de la simulación, como:
la cantidad de iteraciones \(N\)
el comportamiento esperado de la red, es decir los parámetros \(Q_0\) y \(\omega_{00}\) de \(T_0(s)\).
los componentes de la red sujetos al análisis Monte Carlo y sus valores centrales \(C_0, R_0\) y \(R_{B0}\)
la tolerancia de cada componente (5%)
#################################
## parámetros de la simulación ##
#################################
# Cantidad de iteraciones o experimentos
NN = 1000
# Tolerancia de los componentes
tol = 5
# Q y \omega_0 proyectados
QQ = 10
W0 = 1
# Valores de los componentes
CC = 1
RR = 1
RB = (2-1/QQ)*RR
La simulación continúa con el sampleo de \(C_i, R_i\) y \(R_{Bi}\); y finalmente, cálculo de las transferencias \(T_i(s)\)
# Valores de los componentes para cada iteración:
# Cada valor es muestreado independientemente de una distribución uniforme,
# limitada por la tolerancia impuesta.
all_C = np.random.uniform(CC * (100-tol/2)/100 , CC * (100+tol/2)/100, size=NN )
all_R = np.random.uniform(RR * (100-tol/2)/100 , RR * (100+tol/2)/100, size=NN )
all_RB = np.random.uniform(RB * (100-tol/2)/100 , RB * (100+tol/2)/100, size=NN )
plt.close('all')
fig_hdl = plt.figure(figsize=(fig_sz_x, fig_sz_y), dpi= fig_dpi, facecolor='w', edgecolor='k')
axes_hdl = fig_hdl.subplots(2, 1, sharex='col')
fig_id = fig_hdl.number
# analizaremos cada iteración resultante
for (this_C, this_R, this_RB) in zip( all_C, all_R, all_RB):
this_KK = 1 + this_RB/this_R
this_QQ = 1/(3-this_KK)
this_w0 = 1/this_R/this_C
num = [this_KK * (this_w0**2)]
den = [1, this_w0/this_QQ, this_w0**2]
my_tf = TransferFunction( num, den )
_, axes_hdl = bodePlot(my_tf, fig_id)
plt.sca(axes_hdl[0])
plt.ylim(np.array([-10., 50]) )
plt.xlim(np.array([3. * 10.**-1, 3. * 10.**-0]) )
# visualizamos la última realización a modo de ejemplo
print_subtitle('Transferencia sampleada al azar')
print_latex(a_equal_b_latex_s('T_N(s)', pretty_print_bicuad_omegayq(num, den, displaystr=False)))
# finalmente ploteamos también la transferencia con los valores esperados
# sin incertidumbre alguna sobre sus valores.
KK = 1 + RB/RR
QQ = 1/(3-KK)
WW0 = 1/RR/CC
num = [KK * (WW0**2)]
den = [1, WW0/QQ, WW0**2]
# visualizamos la transferencia esperada o media
print_subtitle('Transferencia deseada')
print_latex(a_equal_b_latex_s('T_0(s)', pretty_print_bicuad_omegayq(num, den, displaystr=False)))
my_tf = TransferFunction( num, den )
w, mag, phase = my_tf.bode(n=300)
(mag_ax_hdl, phase_ax_hdl) = axes_hdl
plt.sca(mag_ax_hdl)
plt.semilogx(w, mag, '--k', linewidth=3, label = '$T_0(s)$' ) # Bode magnitude plot
plt.legend()
plt.sca(phase_ax_hdl)
plt.semilogx(w, phase*np.pi/180, '--k', linewidth=3, label = '$T_0(s)$') # Bode phase plot
plt.legend()
plt.show()
Transferencia sampleada al azar
Transferencia deseada
En la respuesta de módulo y fase podemos observar la notoria dispersión de las N iteraciones de \(T(s)\). En trazo negro intercalado observamos la transferencia esperada \(T_0(s)\). Notar la gran dispersión en torno al valor de \(\omega = 1\), donde la transferencia de módulo debería valer \(20 \times \log{Q_0}\) = 20 dB. Más abajo observaremos que se alcanzan valores de \(Q_i\) bien por encima de 50.
En la respuesta de fase se evidencia claramente la dispersión de \(\omega_0i\), ya que este tipo de transferencia atraviesa los \(-\pi/2\) radianes exactamente en \(\omega_0i\).
Se continúa ahora con el análisis estadístico de cada parámetro \(\omega_0\) y \(Q\).
# Ahora vamos a hacer un estudio estadístico de los parámetros Q y \omega_0
# calculo los valores de los parámetros para TODAS las iteraciones
all_KK = 1 + all_RB/all_R
all_QQ = 1/(3-all_KK)
all_w0 = 1/all_R/all_C
plt.figure()
plt.hist( all_QQ, 20 )
ylims = plt.ylim()
plt.plot(np.array([QQ, QQ]), ylims, '--k', linewidth=3, label = '$Q_0$' ) # Bode magnitude plot
maxQQ = np.max(all_QQ)
maxQQ_idx = np.where(all_QQ == maxQQ)
plt.plot(np.array([maxQQ, maxQQ]), np.array(ylims)/10, '--r', linewidth=3, label = '$Q_M$: peor caso' ) # Bode magnitude plot
plt.title('Q para cada experimento')
plt.legend()
#plt.figure(figsize=(fig_sz_x*6/10, fig_sz_y*4/10), dpi= fig_dpi, facecolor='w', edgecolor='k')
plt.figure()
plt.hist( 20*np.log10(all_KK), 20 )
ylims = plt.ylim()
plt.plot(20*np.log10(np.array([KK, KK])), ylims, '--k', linewidth=3, label = '$KK_0$' ) # Bode magnitude plot
plt.plot(20*np.log10(np.array([all_KK[maxQQ_idx], all_KK[maxQQ_idx]])), np.array(ylims)/5, '--r', linewidth=3, label = '$KK_M$: peor caso' ) # Bode magnitude plot
plt.title('Ganancia K para cada experimento')
plt.legend()
#plt.figure(figsize=(fig_sz_x*6/10, fig_sz_y*4/10), dpi= fig_dpi, facecolor='w', edgecolor='k')
plt.figure()
plt.hist( all_w0, 20 )
ylims = plt.ylim()
plt.plot(np.array([WW0, WW0]), ylims, '--k', linewidth=3, label = '$\omega_0$' ) # Bode magnitude plot
plt.plot([all_w0[maxQQ_idx], all_w0[maxQQ_idx]], np.array(ylims)/5, '--r', linewidth=3, label = '$\omega_{0M}$: peor caso' ) # Bode magnitude plot
plt.title('$\omega_0$ para cada experimento')
plt.legend()
<matplotlib.legend.Legend at 0x767cb6cd7c40>
Como se puede ver en los histogramas, el parámetro \(Q\) es el más afectado o sensible en esta topología, razón por la cual habrá que ser extremadamente cuidadoso en el diseño de este tipo de redes activas. El \(Q\) depende de la ganancia \(k\) que a su vez depende de la relación de dos resistores. Esta alta sensibilidad puede mitigarse, o bien utilizando resistores con tolerancias bajas (\(\le\)1%), o asumiendo que el Q a implementar sea bajo de forma tal de relajar el valor de la ganancia a implementar.
Notar también que el peor caso de \(Q\), no necesariamente coincide con el peor caso de \(\omega_0\), como se observa en el histograma de frecuencia.