import control as ctrl
import numpy as np
import matplotlib.pyplot as plt

Problema de levitador magnético#

Definimos el sistema en estudio

G=ctrl.tf(-40,[1,20,-100,-2000])
G
\[\frac{-40}{s^3 + 20 s^2 - 100 s - 2000}\]
G.pole()
array([ 10.+0.j, -20.+0.j, -10.+0.j])
s=ctrl.tf('s')

Otra forma de definirlo:

Gs=-40/((s+20)*(s**2-100))
Gs
\[\frac{-40}{s^3 + 20 s^2 - 100 s - 2000}\]

Lo analizamos con el bode

ctrl.bode(Gs, dB=True)
plt.gcf().set_size_inches(12,8)
../../../_images/be0d3a7a3fce292fb56bba6cff1b648a62c5c2d490aaf477f13acb5885aa6f2b.png
zeta=0.46
sv=np.exp(-np.pi*zeta/(np.sqrt(1-zeta**2)))
sv
0.19641014802711199

Obtenermos el \(\omega_n\)

wn=4/0.4/0.46
wn
21.73913043478261

Del resutlado anterior defino \(\omega_c=22\), calculo la ganancia necesaria para obtener esa frecuencia de corte

wc=22
k1=1/np.abs(G(wc*1j))
k1
434.08920742170034
ctrl.stability_margins(G*k1)
(inf, 132.27368900609372, inf, nan, 22.0, nan)
T=ctrl.feedback(k1*G)
T.pole()
array([-21.27108628+20.16268541j, -21.27108628-20.16268541j,
        22.54217255 +0.j        ])

Vemos que hicimos un controlador inestable. Vamos a estudiar la estabildad con Nyquist (en la pizarra). Luego verificamos con el Nyquist.

ctrl.rlocus(-G);
plt.gcf().set_size_inches(8,6)
../../../_images/5cb7a9d31c22945a370d10b8f77781de3c22f1f1cf660bd487046d28647a0be9.png
ctrl.rlocus(G);
plt.gcf().set_size_inches(8,6)
../../../_images/2c9c330b4af725ccf324c3a22f57525ea959833045ea4941adf11a47d27ac762.png

Vemos que pasa en la frecuencia de cruce que necesitamos

ctrl.bode(-k1*G);
plt.gcf().set_size_inches(12,8)
../../../_images/a677334b84f2b0e93ef0ec92d0e9e240ce5892e3132470b472d19f327150d7cf.png
ctrl.stability_margins(-k1*G)
(0.11518369760210831,
 -47.72631099390625,
 0.7892414128612042,
 0.0,
 22.0,
 24.28778602304735)

Podemos ver que el sistema neceista más de 90 grados lo cual es imposible de conseguir con un solo cero. Vamos a tener que agregar un cero y un lead como mínimo.

Vamos a tratar de agregar un cero, en algún lugar que no molesta al transitorio pero que agrega fase. Este lugar podria ser -10, tapando el polo real negativo de la planta.

d1=(s+10)
ctrl.bode(-k1*G*d1);
plt.gcf().set_size_inches(12,8)
../../../_images/7ce80de1609c9a26739c0d2b81a853fafef46a62f7f33618f4fcdc32185f5371.png
phi_max=50*np.pi/180
alpha = (1-np.sin(phi_max))/(1+np.sin(phi_max))
alpha
0.13247433143179424
wmax=22
TD=1/(wmax*np.sqrt(alpha))
z=-1/TD
p=-1/(alpha*TD)
z, p
(-8.007345153856452, -60.444503228001686)
d2=(-s/z+1)/(-s/p+1)
d2
\[\frac{60.44 s + 484}{8.007 s + 484}\]
kd2=1/np.abs(d2(wc*1j))
ctrl.bode(d2*kd2, dB=True);
plt.gcf().set_size_inches(12,8)
../../../_images/79ae0a01b681b34c7bc3ecaa7594eb8da88b1a4e45da1ff73162ceecfc14db7b.png
C1=d1*d2*kd2
ctrl.bode(-G*d1*d2*kd2, dB=True);
fig=plt.gcf()
fig.set_size_inches(12,8)
../../../_images/8b822363fbcee300ab89c8a9f306246621431b06d7d6808d23368c61b3f4cfc0.png
k3=1/np.abs((-G*d1*d2*kd2)(22j))
k3
17.96273921204669
T=ctrl.feedback(-G*d1*d2*kd2*k3)
T.pole()
array([-34.40165524+32.89590354j, -34.40165524-32.89590354j,
       -10.         +0.j        ,  -1.64119275 +0.j        ])

Vemos que este diseno no resultó del todo correcto. Este se debe a varias razones:

  • poca ganancia en estado estacionario

  • un polo negativo chico que resulta dominante frente a los que diseñamos

t,y=ctrl.step_response(T, T=np.linspace(0,5,2000))
fig, ax = plt.subplots(figsize=(12, 4))
ax.plot(t,y)
ax.set_title('Respuesta al escalón')
ax.set_xlabel('Tiempo')
ax.grid()
../../../_images/48850787146b4de9cd2c866ea11b88c7bea05f887a642eb81e5e29e6ceea1cea.png
L=-G*d1*d2*kd2*k3
ctrl.bode(L, dB=True);
../../../_images/66a60542e32f21cfd08f9252928f1180a933e7950d55929006cef723bd6d8015.png
fig=plt.gcf()
fig.set_size_inches(12,8)
<Figure size 1200x800 with 0 Axes>
G
\[\frac{-40}{s^3 + 20 s^2 - 100 s - 2000}\]
G.pole()
array([ 10.+0.j, -20.+0.j, -10.+0.j])

Para lograr un controlador que en principio funcione y epnsar de otra manera los lead-lag podemos hacer loop-shaping, y después ajusta a nuestro a lo que necesitemos.

Cls=((s+10)*(s+10)*(s+20))/s
ctrl.bode(-G*Cls);
fig=plt.gcf()
fig.set_size_inches(12,8)
../../../_images/bb18d875166a1379b5beac4711e50b716b07670171e024cf8d5465016bb13d57.png

Analizamos que margen de fase tenemos a 22 rad y luego vemos la respuesta. Podemos después redisenar y mejorar aumentando la frecuencia de corte. Recordar que los isstemas inestables son más faciles de controlar más rápido que lento

kls=1/np.abs((G*Cls)(wc*1j))
ctrl.bode(-G*Cls*kls);
fig=plt.gcf()
fig.set_size_inches(12,8)
../../../_images/58e20b6688d38e5515f3e151d160cc17c8eb17e9b3c5d176081be63b53851980.png
ctrl.stability_margins(-G*Cls*kls)
(0.4545454545454543,
 41.112090439166934,
 0.6422688979612565,
 9.999999999999996,
 22.000000000000004,
 16.941860377377967)
T=ctrl.feedback(-G*Cls*kls)
t,y=ctrl.step_response(T)
fig, ax = plt.subplots(figsize=(12, 4))
ax.plot(t,y)
ax.set_title('Respuesta al escalón')
ax.set_xlabel('Tiempo')
ax.grid()
../../../_images/3723c66c8e2347810ac71bc38009bd6cceef7dc251fbb81f461173ddc97be01f.png
Cls
\[\frac{s^3 + 40 s^2 + 500 s + 2000}{s}\]
Cls2=Cls*1/(s/400+1)**2
Cls2
\[\frac{1.6 \times 10^{5} s^3 + 6.4 \times 10^{6} s^2 + 8 \times 10^{7} s + 3.2 \times 10^{8}}{s^3 + 800 s^2 + 1.6 \times 10^{5} s}\]
Tu=ctrl.feedback(-Cls2,G)
t,y=ctrl.step_response(Tu, T=np.linspace(0,1,2000))
fig, ax = plt.subplots(figsize=(12, 4))
ax.plot(t,y)
ax.set_title('Respuesta al escalón')
ax.set_xlabel('Tiempo')
ax.grid()
../../../_images/12ee77d190af810520968178e9c93699a2a9eb41275ca178e6af89cf9b8c5128.png