from sympy import init_printing
init_printing()
from sympy import *
Wir wollen hier eine Exakte Eingangs-Ausgangslinearisierung im SISO-Fall mithilfe differentialgeometrischen Methoden durchführen. Jürgen Adamy gibt mit seinem Buch Nichtlineare Systeme und Regelungen eine sehr gute Übersicht über die nichtlineare Regelungstechnik. Wer eine tiefe Einführung in die differentialgeometrischen Methoden der Physik will, kann diese in dem herausragenden Buch The Geometry of Physics finden.
Auf eine theoretische Einführung wollen wir hier erstmal verzichten. Diese soll in einem späteren Blogeintrag nachgereicht werden.
Es soll nur gezeigt werden, dass Python / Sympy auch für nichtlineare Methoden gut geeignet ist.
Eingang/Aungangs - Linearisierung
Ein nichtlineares SISO-System ist in der Form
\[ \begin{split} \dot{x} &= f (x) + g(x)u \\ y &= h(x) \end{split} \]
gegeben.
Durch das Anwenden der Lie Ableitung können wir den relativen Grad ermitteln:
\[ \begin{split} y &= L^0_f h \\ \dot{y} &= L^1_f h \\ \ddot{y} &= L^2_f h \\ \vdots \\ y^{(r)} &= L^r_f h + L_g (L^{r-1}_f h)u. \end{split} \]
In der letzten Gleichung tritt zum ersten Mal der Eingang \(u\) in Erscheinung. Die Anzahl der notwendigen Ableitungen wird als relativer Grad \(r\) bezeichnet. Wir wollen hier einfachheitshalber annehmen, dass der relative Grad \(r\), gleich der Anzahl der Zustände \(x_i\) ist (\(r=n\)).
Oft werden die Abkürzungen \(\alpha(x) = L^r_f h\) und \(\beta(x) = L_g (L^{r-1}_f g)\) für \(y^{(r)} = \alpha(x)+\beta(x) u\) eingeführt.
Wir suchen nun einen Eingang, der die Nichtlinearitäten im System kompensiert, d. h. \(y^{(r)} = \alpha(x) + \beta(x) u \equiv v\). Daraus folgt:
\[ \begin{split} v = \alpha(x) + \beta(x) u \\ u = \frac{1}{\beta(x)}[-\alpha(x) + v] \end{split} \]
Wir können neue Zustände \(z\) einführen:
\[ \begin{align} z_1 &= y = L^0_f h \\ z_2 &= \dot{y} = L^1_f h \\ \vdots \\ z_r &= y^{(r-1)} = L^{r-1}_f h \\ z_{r+1} &= y^{(r)} = \alpha + \beta u = v \end{align} \]
In diesen neuen Zuständen ist das System nun linear.
Das System kann also in Form eines Zustandsraums wie folgt dargestellt werden
\[ \begin{split} \begin{bmatrix} \dot{z}_1 \\ \dot{z}_2 \\ \vdots \\ \dot{z}_r \\ \end{bmatrix} &= \begin{bmatrix} 0 & 1 & \cdots & 0 \\ 0 & 0 & \ddots & 0 \\ 0 & 0 & \cdots & 1 \\ 0 & 0 & \cdots & 0 \end{bmatrix} \begin{bmatrix} z_1 \\ z_2 \\ \vdots \\ z_r \\ \end{bmatrix} + \begin{bmatrix} 0 \\ 0 \\ \vdots \\ 1 \\ \end{bmatrix} \\ y &= \begin{bmatrix} 1 & 0 & \cdots 0 \end{bmatrix} \begin{bmatrix} z_1 \\ z_2 \\ \vdots \\ z_r \\ \end{bmatrix} \end{split} \]
und entspricht eine Kaskade von r Integratoren. Jetzt können wir jede Art von Regler für das lineare System mit \(r\) Integratoren entwerfen. Am besten geeignet wäre ein Zustandsregler. Die Transformation zwischen den beiden Zustandsräumen, \(x\) und \(z\) wollen wir mit \(\Phi(x)\) bezeichnen:
\[ z = \Phi(x) = \begin{bmatrix} \varphi_1(x) \\ \varphi_2(x) \\ \vdots \\ \varphi_r(x) \end{bmatrix} = \begin{bmatrix} L^0_f h \\ L^1_f h \\ \vdots \\ L^{r-1}_f h \end{bmatrix} \]
Algorithmus für die Eingangs-/Ausgangslinearisierung:
- Ermitteln des relativen Grads \(r\), des nichtlinearen Systems
\[ \begin{split} L_g [L^k_f h(x)] &= 0 \\ L_g [L^{r-1}_f h(x)] &\neq 0 \end{split} \]
- Ermitteln der Zustandstransformation
\[ z = \Phi(x) = \begin{bmatrix} \varphi_1(x) \\ \varphi_2(x) \\ \vdots \\ \varphi_r(x) \end{bmatrix} = \begin{bmatrix} L^0_f h \\ L^1_f h \\ \vdots \\ L^{r-1}_f h \end{bmatrix} \]
- Definieren des neuen Eingangs, sodass die Zustände linearisiert werden
\[ \begin{split} \alpha(x) &= L^{r}_{f} h \\ \beta(x) &= L_g (L^{(r-1)}_{f} h) \\ u &= \frac{1}{\beta(x)}\left[-\alpha(x) + v \right] \end{split} \]
Beispiel
Gegeben sei das nichtlineare Eingrößensystem:
\[ \begin{split} \dot{x} &= \begin{bmatrix} 0 \\ x_1 + x_2^2 \\ x_1 - x_2 \end{bmatrix} + \begin{bmatrix} e^{x_2} \\ e^{x_2} \\ 0 \end{bmatrix} u \\ y &= h(x) = x_3 \end{split} \]
CODE: Beispiel
Umsetzung der Eingangs-Ausgangslinearisierung in Python / Sympy.
Jacobi Matrix
def jac(f,x):
return f.jacobian(x)
Lie Ableitung
def lie_deriv(f, h, x, n=1):
if n==0:
= h
Lfh else:
= h.jacobian(x)@f
Lfh for i in range(1,n):
= Lfh.jacobian(x)@f
Lfh return Lfh
Zustände und Eingänge
= symbols('x1 x2 x3', real=True)
x1, x2, x3 = Matrix([[x1], [x2], [x3]])
x_vec
= symbols('u', real=True)
u = Matrix([[u]])
u_vec
= symbols('z1 z2 z3', real=True)
z1, z2, z3 = Matrix([[z1], [z2], [z3]])
z_vec
= symbols('v', real=True)
v = Matrix([[v]]) v_vec
Anlegen des nichtlinearen Systems
= Matrix([
f 0],
[+x2**2],
[x1-x2],
[x1
])
f
\(\displaystyle \left[\begin{matrix}0\\x_{1} + x_{2}^{2}\\x_{1} - x_{2}\end{matrix}\right]\)
= Matrix([
g
[exp(x2)],
[exp(x2)],0],
[
])
g
\(\displaystyle \left[\begin{matrix}e^{x_{2}}\\e^{x_{2}}\\0\end{matrix}\right]\)
= Matrix([
h
[x3],
])
h
\(\displaystyle \left[\begin{matrix}x_{3}\end{matrix}\right]\)
Ermitteln des relativen Grades
= lie_deriv(f, h, x_vec, 0)
Lf0h
display(Lf0h)= lie_deriv(g, Lf0h, x_vec, 1)
LgLf0h display(LgLf0h)
\(\displaystyle \left[\begin{matrix}x_{3}\end{matrix}\right]\)
\(\displaystyle \left[\begin{matrix}0\end{matrix}\right]\)
= lie_deriv(f, h, x_vec, 1)
Lf1h
display(Lf1h)= lie_deriv(g, Lf1h, x_vec, 1)
LgLf1h display(LgLf1h)
\(\displaystyle \left[\begin{matrix}x_{1} - x_{2}\end{matrix}\right]\)
\(\displaystyle \left[\begin{matrix}0\end{matrix}\right]\)
= lie_deriv(f, h, x_vec, 2)
Lf2h
display(Lf2h)= lie_deriv(g, Lf2h, x_vec, 1)
LgLf2h display(LgLf2h)
\(\displaystyle \left[\begin{matrix}- x_{1} - x_{2}^{2}\end{matrix}\right]\)
\(\displaystyle \left[\begin{matrix}- 2 x_{2} e^{x_{2}} - e^{x_{2}}\end{matrix}\right]\)
Zustandstransformationen
Lf0h
\(\displaystyle \left[\begin{matrix}x_{3}\end{matrix}\right]\)
Lf1h
\(\displaystyle \left[\begin{matrix}x_{1} - x_{2}\end{matrix}\right]\)
Lf2h
\(\displaystyle \left[\begin{matrix}- x_{1} - x_{2}^{2}\end{matrix}\right]\)
= Matrix([[Lf0h],[Lf1h],[Lf2h]])
phi_x phi_x
\(\displaystyle \left[\begin{matrix}x_{3}\\x_{1} - x_{2}\\- x_{1} - x_{2}^{2}\end{matrix}\right]\)
= dict(zip(z_vec, phi_x))
z2x z2x
\(\displaystyle \left\{ z_{1} : x_{3}, \ z_{2} : x_{1} - x_{2}, \ z_{3} : - x_{1} - x_{2}^{2}\right\}\)
= dict(zip(phi_x, z_vec))
_x2z _x2z
\(\displaystyle \left\{ x_{3} : z_{1}, \ - x_{1} - x_{2}^{2} : z_{3}, \ x_{1} - x_{2} : z_{2}\right\}\)
Neuer Eingang, der die Zustände linearisiert
= Lf3h = lie_deriv(f, h, x_vec, 3)
alpha alpha
\(\displaystyle \left[\begin{matrix}- 2 x_{2} \left(x_{1} + x_{2}^{2}\right)\end{matrix}\right]\)
= LgLf2h
beta beta
\(\displaystyle \left[\begin{matrix}- 2 x_{2} e^{x_{2}} - e^{x_{2}}\end{matrix}\right]\)
= 1/beta[0]*(-alpha[0]+v)
u u
\(\displaystyle \frac{v + 2 x_{2} \left(x_{1} + x_{2}^{2}\right)}{- 2 x_{2} e^{x_{2}} - e^{x_{2}}}\)
= simplify(f+g*u)
fc fc
\(\displaystyle \left[\begin{matrix}- \frac{v + 2 x_{2} \left(x_{1} + x_{2}^{2}\right)}{2 x_{2} + 1}\\\frac{- v + x_{1} + x_{2}^{2}}{2 x_{2} + 1}\\x_{1} - x_{2}\end{matrix}\right]\)
Global linearisiertes System
= simplify(jac(phi_vec,x_vec)@fc).subs(_x2z)
fg_z fg_z
\(\displaystyle \left[\begin{matrix}z_{2}\\z_{3}\\v\end{matrix}\right]\)
= h.subs(_x2z)
h_z h_z
\(\displaystyle \left[\begin{matrix}z_{1}\end{matrix}\right]\)
= jac(fg_z, z_vec)
Az Az
\(\displaystyle \left[\begin{matrix}0 & 1 & 0\\0 & 0 & 1\\0 & 0 & 0\end{matrix}\right]\)
= jac(fg_z, v_vec)
Bz Bz
\(\displaystyle \left[\begin{matrix}0\\0\\1\end{matrix}\right]\)
= jac(h_z, z_vec)
Cz Cz
\(\displaystyle \left[\begin{matrix}1 & 0 & 0\end{matrix}\right]\)
Wir haben also das System
\[ \begin{split} \dot{x} &= \begin{bmatrix} 0 \\ x_1 + x_2^2 \\ x_1 - x_2 \end{bmatrix} + \begin{bmatrix} e^{x_2} \\ e^{x_2} \\ 0 \end{bmatrix} u \\ y &= h(x) = x_3 \end{split} \]
mittels eines Reglers gobal linearisiert:
\[ \begin{split} \begin{bmatrix} \dot{z}_1 \\ \dot{z}_2 \\ \dot{z}_3 \\ \end{bmatrix} &= \begin{bmatrix} 0 & 1 & 0 \\ 0 & 0 & 1 \\ 0 & 0 & 0 \end{bmatrix} \begin{bmatrix} z_1 \\ z_2 \\ z_3 \\ \end{bmatrix} + \begin{bmatrix} 0 \\ 0 \\ 1 \end{bmatrix} v \\ y &= \begin{bmatrix} 1 & 0 & 0 \end{bmatrix} \begin{bmatrix} z_1 \\ z_2 \\ z_3 \end{bmatrix} \end{split} \]
Fazit
Python / Sympy bietet viele Möglichkeiten für die nichtlineare Regelungtechnik. Dieser Blogeintrag bezieht sich auf differentialgeometrischen Methoden, genauer auf die Exakte Eingangs- Ausgangslinearisierung im Eingrößenfall. Gerade das sympy.diffgeom
Paket scheint sich sehr gut für diese Methoden zu eignen. Jedoch sollte der theoretische Aufwand, um sich in solche Module einzuarbeiten, nicht unterschätzt werden. Deswegen haben wir hier nur Basis-Befehle benutzt, was für erste Schritte ausreichend ist.
Gut an diesen Methoden ist, dass diese mühelos in Steuerungen implementiert werden können. Selbst Arduino und Raspberry Pico Controller sind dafür geeignet.
Aber auch andere nichtlinearen Methoden können mit Python angegangen werden. Als Beispiel soll der nichtlineare MPC Regler genannt werden. Will man aber die Optimierung auf den Steuerungen in Echtzeit durchführen, braucht man auch dort geeignete Optimierer. Besonders interessant, könnte hier der Einsatz von Echtzeit-Linux-Systemen sein, da die Entwicklungsplattform dann große Ähnlichkeit mit der Produktivsystem hat.
Referenzen
- https://docs.sympy.org/latest/modules/diffgeom.html
- Nichtlineare Systeme und Regelungen, 3. Auflage (Jürgen Adamy, 2018)
- The Geometry of Physics, Third Edition (Theodore Frankel, 2012)