An谩lisis de los sistemas en su forma de espacio de estados#

Supongamos que tenemos la siguiente funci贸n transferencia:

G=ctrl.tf([1,1],[1,2,3])
G
\[\frac{s + 1}{s^2 + 2 s + 3}\]

De los apuntes de teor铆a podemos escribir una representaci贸n de espacios de estados en la forma can贸nica de controlabilidad que tendr谩 el mismo comportamiento externo de \(G\) de la siguiente manera :

Ac=np.matrix([[-2, -3],[1,0]])
Ac
matrix([[-2, -3],
        [ 1,  0]])

Antes de seguir, verificamos que los polos de \(G\) sean los autovalores de \(\mathbf {Ac}\)

print(G.pole())
print(np.linalg.eigvals(Ac))
[-1.+1.41421356j -1.-1.41421356j]
[-1.+1.41421356j -1.-1.41421356j]

Ahora definimos el resto de las matrices

Bc=np.matrix("1;0")
Bc
matrix([[1],
        [0]])
Cc=np.matrix("1 1")
Cc
matrix([[1, 1]])
Dc=0

Vamos ahora a definir un sistema \(sys\) que se comporte igual que \(G\)

sys_c=ctrl.ss(Ac,Bc,Cc,Dc)

Si \(sys\) tiene los mismos polos, los mismos ceros y la misma ganancia en estado estacionario, entonces se comportar谩 igual que \(G\).

Veamos los polos:

sys_c.pole()
array([-1.+1.41421356j, -1.-1.41421356j])

Ahora los ceros:

sys_c.zero()
array([-1.+0.j])

Y la ganancia en estado estacionario en particular (o en cualquier frecuencia en general):

sys_c.dcgain()
0.3333333333333333
G.dcgain()
0.3333333333333333

Por lo tanto podemos ver que la funci贸n transferencia de \(sys\) ser谩 la misma que la de \(G\). Es decir, \(sys\) se comporta externamente igual que \(G\). Verificamos

ctrl.tf(sys_c)
\[\frac{s + 1}{s^2 + 2 s + 3}\]

Vamos ahora a definir el sistema en espacio de estados, que se comporte como \(G\) en la forma can贸nica de observabilidad. De la teor铆a sabemos que:

Ao=Ac.T
Bo=Cc.T
Co=Bc.T
Do=Dc
sys_o=ctrl.ss(Ao,Bo,Co,Do)
sys_o.pole()
array([-1.+1.41421356j, -1.-1.41421356j])
sys_o.zero()
array([-1.+0.j])
sys_o.dcgain()
0.3333333333333333
ctrl.tf(sys_o)
\[\frac{s + 1}{s^2 + 2 s + 3}\]
ctrl.tf(sys_o)
\[\frac{s + 1}{s^2 + 2 s + 3}\]

El m贸dulo de control de Python puede transformar una funci贸n transferencia a una forma de estados (no necesariamente en alguna forma can贸nica). Esto se hace:

sys=ctrl.ss(G)
sys
\[\begin{split} \left(\begin{array}{rllrll|rll} -2\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&3\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}\\ -1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&3.&\hspace{-1em}33&\hspace{-1em}\cdot10^{-16}&0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}\\ \hline 1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&-1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}\\ \end{array}\right) \end{split}\]

Adem谩s Python puede transformar un sistema en espacio de estados a alguna de las formas can贸nicas.

Por ejemplo para escribir \(sys\) (que est谩 ahora en espacio de estados) en su forma can贸nica de controlabilidad podemos hacer

sys_c, Tc=ctrl.canonical_form(sys, 'reachable') # controlable
sys_c, Tc
(StateSpace(array([[-2., -3.],
        [ 1.,  0.]]), array([[1.],
        [0.]]), array([[1., 1.]]), array([[0.]])),
 array([[ 1.00000000e+00, -2.22044605e-16],
        [-0.00000000e+00, -1.00000000e+00]]))

Notar que esta funci贸n necesita como primer argumento un sistema de espacios de estados.

Si queremos la forma can贸nica de observabilidad podemos hacer:

sys_o,To = ctrl.canonical_form(sys, 'observable')
sys_o, To
(StateSpace(array([[-2.,  1.],
        [-3.,  0.]]), array([[1.],
        [1.]]), array([[1., 0.]]), array([[0.]])),
 array([[ 1., -1.],
        [ 1.,  1.]]))

Existe otra forma can贸nica de representaci贸n en espacio de estados que se la conoce como forma can贸nica modal. Esta la podemos obtener haciendo:

sys_m, Tm = ctrl.canonical_form(sys, 'modal')
sys_m, Tm
(StateSpace(array([[-1.        ,  3.41421356],
        [-0.58578644, -1.        ]]), array([[ 0.92387953],
        [-0.38268343]]), array([[ 0.5411961 , -1.30656296]]), array([[0.]])),
 array([[ 0.92387953, -0.38268343],
        [ 0.38268343,  0.92387953]]))

Esta forma can贸nica lo que hace es aislar los modos unos de otros. Es decir cada variables de estado aparece en la diagonal. Sin embargo, como vemos en este caso, no tenemos una forma diagonal de la matriz \(\mathbf A\). Esto sucede por que hay casos que no se puede o no se desea aislarlos:

  • en caso de que se tengan autovalores complejos conjugados, no se a铆slan para lograr tener una matriz \(\mathbf A\), \(\mathbf B\) y \(\mathbf C\) a coeficiente reales.

  • en caso de que sean multiples no se pueden aislar por que tienen el mismo autovector y no se pueden usar estos para la transformaci贸n. Para estos casos se utiliza la forma de Jordan.

Vamos a ver otro ejemplo:

s=ctrl.tf('s')
G2 = 1/((s+1)*(s+2)*(s+3))
G2
\[\frac{1}{s^3 + 6 s^2 + 11 s + 6}\]

Vemos que esta funci贸n transferencia tiene los polos en -1, -2 y -3. Transformemos a espacio de estados usando la funci贸n ss.

sys2 = ctrl.ss(G2)
sys2
\[\begin{split} \left(\begin{array}{rllrllrll|rll} -6\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&1.&\hspace{-1em}1&\hspace{-1em}\phantom{\cdot}&-0.&\hspace{-1em}6&\hspace{-1em}\phantom{\cdot}&-1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}\\ -10\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}\\ 0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&-1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}\\ \hline 0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&-0.&\hspace{-1em}1&\hspace{-1em}\phantom{\cdot}&0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}\\ \end{array}\right) \end{split}\]

Veamos al sistema en su forma can贸nica de controlabilidad.

ctrl.canonical_form(sys2, 'reachable')
(StateSpace(array([[ -6., -11.,  -6.],
        [  1.,   0.,   0.],
        [  0.,   1.,   0.]]), array([[1.],
        [0.],
        [0.]]), array([[-6.25762068e-18, -6.30808537e-17,  1.00000000e+00]]), array([[0.]])),
 array([[-1.00000000e+00,  6.86319688e-17,  2.44249065e-16],
        [ 3.55271368e-17,  1.00000000e-01,  9.43689571e-18],
        [-6.25762068e-18,  6.30808537e-18, -1.00000000e-01]]))

Ahora lo podemos ver en la forma can贸nica de observabilidad

ctrl.canonical_form(sys2, 'observable')
(StateSpace(array([[ -6.,   1.,   0.],
        [-11.,   0.,   1.],
        [ -6.,   0.,   0.]]), array([[0.],
        [0.],
        [1.]]), array([[1., 0., 0.]]), array([[0.]])),
 array([[ 0.0000000e+00, -4.4408921e-18, -1.0000000e-01],
        [-0.0000000e+00,  1.0000000e-01, -6.0000000e-01],
        [-1.0000000e+00,  6.0000000e-01, -1.1000000e+00]]))

Finalmente obtengamos la forma can贸nica modal:

sys2_m, Tm =ctrl.canonical_form(sys2, 'modal')
sys2_m.A
array([[-3.00000000e+00, -1.34094587e-14,  1.70959581e-15],
       [ 2.58286564e-14, -2.00000000e+00, -6.07766669e-15],
       [ 8.45930682e-15,  6.29737212e-15, -1.00000000e+00]])

Nota

  • La funci贸n canonical_form devuelve un sistema en la forma can贸nica solicitada y la matriz de transformaci贸n necesaria para obtenerlo

  • en la diagonal de la matriz \(\mathbf A\) de la forma can贸nica modal tenemos los modos del sistema (polos de \(G\) o autovalores de \(A\))

  • fuera de la diagonal \(\mathbf A\) tenemos ceros o valores muy cercanos a ceros (debido a problemas num茅ricos en el c谩lculo).

  • En caso de tener autovalores repetidos, tendremos una matriz \(\mathbf{A}\) diagonal por bloques. Los valores fuera de la diagonal ser谩n 1.

  • En general, los autovalores complejos conjugados tampoco se ponen en la diagonal, para evitar tener tener matrices con valores complejos.

Verificamos el resto de las matrices:

sys2_m.B
array([[ 16.43928222],
       [-22.71563338],
       [ -7.08872344]])
sys2_m.C
array([[ 0.03041495,  0.04402255, -0.07053456]])
sys2_m.D
array([[0.]])

En esta forma can贸nica, la matriz \(B\) como la conexi贸n de la entrada con el modo del sistema . Y de forma an谩loga a la matriz \(C\) como la conexi贸n del modo con la salida del sistema.

Ejercicio an谩lisis de Controlabilidad y Observabilidad#

Sunpongamos que se tienen las sieguentes funciones transferencias \(G_1\) y \(G_2\):

Ejemplo de conexi贸n de sistemas#

G1=ctrl.tf(1,[1,1])
G2=ctrl.tf([1,1],[1,2])
G1
\[\frac{1}{s + 1}\]
G2
\[\frac{s + 1}{s + 2}\]

Podemos ver que G1 tiene un polo en -1 y G2 tiene un polo en -2 y un cero en -1.

Ahora supongamos que queremos un nuevo sistema que:

  • conecta la salida de \(G1\) con la entrada de \(G2\)

  • la salida es la salida de \(G2\)

  • la entrada es la entrada de \(G1\)

Para eso vamos a usar una funci贸n que se llama connect. Esta utiliza sistemas en espacio de estados, entonces hacemos:

sys1 = ctrl.ss(G1)
sys2= ctrl.ss(G2)

Y luego realizamos las conexiones:

sys_c = ctrl.connect(ctrl.append(sys1, sys2),[[2,1]],[1],[2])
sys_c
\[\begin{split} \left(\begin{array}{rllrll|rll} -1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}\\ 1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&-2\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}\\ \hline 1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&-1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}\\ \end{array}\right) \end{split}\]

Analizamos los polos y ceros del sistema ya interconectado

sys_c.pole()
array([-2.+0.j, -1.+0.j])
sys_c.zero()
array([-1.+0.j])

Vemos que el sistema conserva los mismos polos y los mismos ceros que el original, lo cual era de esperarse, ya que su funci贸n transferencia ser铆a \(G1.G2\)

Obtengamos la forma can贸nica de controlabilidad del sistema

sys_c, Tc=ctrl.canonical_form(sys_c, 'reachable')
sys_c
\[\begin{split} \left(\begin{array}{rllrll|rll} -3\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&-2\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}\\ 1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}\\ \hline 1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}\\ \end{array}\right) \end{split}\]

Podemos ver que las matrices \( \mathbf A\), \(\mathbf B\), \(\mathbf C\) y \(\mathbf D\) coinciden con lo visto en teor铆a.

Ahora obtengamos la forma can贸nica de observabilidad:

ctrl.canonical_form(sys_c, 'observable')
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[40], line 1
----> 1 ctrl.canonical_form(sys_c, 'observable')

File /usr/share/miniconda/lib/python3.11/site-packages/control/canonical.py:64, in canonical_form(xsys, form)
     62     return reachable_form(xsys)
     63 elif form == 'observable':
---> 64     return observable_form(xsys)
     65 elif form == 'modal':
     66     return modal_form(xsys)

File /usr/share/miniconda/lib/python3.11/site-packages/control/canonical.py:185, in observable_form(xsys)
    182 Tzx = solve(Wrz, Wrx)  # matrix left division, Tzx = inv(Wrz) * Wrx
    184 if matrix_rank(Tzx) != xsys.nstates:
--> 185     raise ValueError("Transformation matrix singular to working precision.")
    187 # Finally, compute the output matrix
    188 zsys.B = Tzx @ xsys.B

ValueError: Transformation matrix singular to working precision.

Podemos ver que este sistema no puede ser escrito en su forma can贸nica de observabilidad. Esto sucede por que el sistema no es observable

Interpretaci贸n

Un sistema observable necesita tener evidencia de lo que sucede con cada modo en al menos una salida del sistema. Como el modo en -1 se ve completamente tapado por el cero en esa misma frecuencia este sistema no es observable por que no tiene ninguna evidencia del modo en -1 a la salida del mismo.

Ahora hagamos la conexi贸n al rev茅s (primero \(G1\) y luego \(G2\)):

sys_o = ctrl.connect(ctrl.append(sys1, sys2),[[1,2]],[2],[1])
sys_o
\[\begin{split} \left(\begin{array}{rllrll|rll} -1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&-1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}\\ 0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&-2\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}\\ \hline 1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}\\ \end{array}\right) \end{split}\]

Obtengamos la forma can贸nica observable:

sys_o, To=ctrl.canonical_form(sys_o, 'observable')
sys_o
\[\begin{split} \left(\begin{array}{rllrll|rll} -3\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}\\ -2\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}\\ \hline 1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}\\ \end{array}\right) \end{split}\]

Y ahora la forma can贸nica controlable:

ctrl.canonical_form(sys_o, 'reachable')
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[43], line 1
----> 1 ctrl.canonical_form(sys_o, 'reachable')

File /usr/share/miniconda/lib/python3.11/site-packages/control/canonical.py:62, in canonical_form(xsys, form)
     60 # Call the appropriate tranformation function
     61 if form == 'reachable':
---> 62     return reachable_form(xsys)
     63 elif form == 'observable':
     64     return observable_form(xsys)

File /usr/share/miniconda/lib/python3.11/site-packages/control/canonical.py:120, in reachable_form(xsys)
    117 Wrz = ctrb(zsys.A, zsys.B)
    119 if matrix_rank(Wrx) != xsys.nstates:
--> 120     raise ValueError("System not controllable to working precision.")
    122 # Transformation from one form to another
    123 Tzx = solve(Wrx.T, Wrz.T).T  # matrix right division, Tzx = Wrz * inv(Wrx)

ValueError: System not controllable to working precision.

Podemos ver que ahora no podemos obtener la forma can贸nica de observabilidad.

Interpretaci贸n

La raz贸n de esto es que no se puede excitar el modo -1, que ahora qued贸 tapado desde el lado de la entrada. Toda excitaci贸n que tenga 芦frecuencia generalizada -1禄 ser谩 tapada por el cero en -1. Por lo tanto este sistema no es controlable, ya que no puede excitar desde esa entrada el modo -1.

Es importante destacar que ambos sistemas presentan la misma funci贸n transferencia \(G1.G2\), pero uno es solo controlable y el otro es solo observable. Esto se debe a que la controlabilidad y la observabilidad son propiedades internas de un sistema.

Atenci贸n

La controlabilidad y la observabilidad no pueden ser decididas a partir de modelos externos de un sistema como son las funciones transferencias.

Estas dos mismas propiedades pueden ser analizadas con la forma modal. Tomemos el sistema \(sys_C\) donde la entrada esta conectada a \(G1\) y la salida a \(G2\):

sys_mc, Tmc = ctrl.canonical_form(sys_c, 'modal')
sys_mc
\[\begin{split} \left(\begin{array}{rllrll|rll} -2\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&-1.&\hspace{-1em}79&\hspace{-1em}\cdot10^{-16}&-2.&\hspace{-1em}24&\hspace{-1em}\phantom{\cdot}\\ 4.&\hspace{-1em}53&\hspace{-1em}\cdot10^{-17}&-1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&-1.&\hspace{-1em}41&\hspace{-1em}\phantom{\cdot}\\ \hline -0.&\hspace{-1em}447&\hspace{-1em}\phantom{\cdot}&0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}\\ \end{array}\right) \end{split}\]

Podemos ver que la matriz \(\mathbf B\) tiene todos los valores distintos de 0. Esto significa que todos los modos est谩n conectados a la entrada, por lo que deducimos que el sistema es controlable.

Pero podemos ver que \(\mathbf C\) tiene un 0 en el primer elemento. Y que en la diagonal de \( \mathbf A\) el primer elemento es -1. Esto quiere decir que no hay ninguna evidencia del modo -1 en la salida por que el sistema no es observable.

Probemos ahora con la segunda conexi贸n:

sys_mo, Tmo = ctrl.canonical_form(sys_o, 'modal')
sys_mo
\[\begin{split} \left(\begin{array}{rllrll|rll} -2\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&9.&\hspace{-1em}05&\hspace{-1em}\cdot10^{-17}&-1.&\hspace{-1em}41&\hspace{-1em}\phantom{\cdot}\\ -8.&\hspace{-1em}95&\hspace{-1em}\cdot10^{-17}&-1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&-0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}\\ \hline -0.&\hspace{-1em}707&\hspace{-1em}\phantom{\cdot}&-0.&\hspace{-1em}447&\hspace{-1em}\phantom{\cdot}&0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}\\ \end{array}\right) \end{split}\]

Podemos ver ahora que la matriz \(\mathbf C\) no tiene ning煤n valor igual a 0, por lo que ahora el sistema ser铆a observable (todos los modos est谩n presentes de alguna manera en la salida). Pero tenemos un 0 en la primer fila de \(\mathbf B\), que se corresponde con el modo en -1. Esto quiere decir que no podemos excitar este modo. Por lo tanto no es controlable.

Importante

La controlabilidad y la observabilidad pueden ser evaluadas con el sistema en su forma can贸nica modal analizando las relaciones de la matrices \(\mathbf{B}\) y \(\mathbf{C}\) con la matriz \(\mathbf{A}\).