import numpy as np
import scipy.signal as signal
import matplotlib.pyplot as plt
'ggplot')
plt.style.use(
import control as ct
Das Ziel der MRAC Methode ist es, den Fehler \(e\) zwischen dem Referenzausgang \(y_r\) und dem Prozessausgang \(y_p\) gegen null zu bringen, \(e = y_r - y_p = 0\). Dies bedeutet jedoch nicht, dass die Reglerparameter zu den wahren Werten konvergieren. Die Konvergenz hängt besonders vom Referenz-Signal \(u_r\) ab. Diesen Aspekt wollen wir anhand von zwei einfachen Beispielen zeigen.
Beispiel 1: Direct MRAC, MIT Rule
Wir betrachten ein ganz einfaches Beispiel mit einem Steuerungsparameter \(\theta\), wie wir es in dem Blogeintrag
Adaptive Regelung - Direct MRAC, MIT Rule, Verstärkung
besprochen haben. Wir nehmen für \(G(s) = 1\) an, womit der Prozess mit \(y=bu\) beschrieben ist. Der Regler wird mit \(u_p = \theta u_r\) angegeben, der Referenz-Ausgang ist durch \(y_r = b_r u_r\) definiert. Der Fehler kann nun mit
\[ e = (b\theta - b_r)u_r = k(\theta - \theta^{\ast})u_c \]
angeben werden, wobei \(\theta^{\ast} = \frac{b_r}{b}\) gilt. Das MIT Gesetz liefert die folgende Differenzialgleichung für die Adaption:
\[ \dfrac{d\theta}{dt} = - \gamma k^2 u_c^2 \left( \theta - \theta^{\ast} \right) \]
Die Lösung dieser Gleichung lautet
\[ \theta(t) = \theta^{\ast} + \left( \theta_0 - \theta^{\ast} \right)e^{-\gamma k^{2} I_t} \]
wobei \(I_t\) durch das Integral
\[ I_t = \int_{0}^{t} u_c^2 (\tau) d\tau \]
definiert wird. Die Lösung für \(\theta(t)\) in den Fehler eingesetzt, ergibt die Gleichung
\[ e(t) = k u_c(t) \left( \theta_0 - \theta^{\ast} \right)e^{-\gamma k^2 I_t}. \]
Der Fehler geht also immer zu null, weil entweder das Integral \(I_t\) divergiert oder das Referenz-Signal verschwindet \(u_r(t) \rightarrow 0\). Die Entwicklung des Adaptionsparameters \(\theta(t)\) hängt also vom Eingangssignal ab.
Code
# linear system
= 1
b = ct.tf('s')
s = b*s/s
G_plant_tf
# io model
= ct.LinearIOSystem(
io_plant
G_plant_tf,=('up'),
inputs=('yp'),
outputs=0,
states='plant'
name )
# linear system
= 2
br = br*s/s
G_model_tf
# io model
= ct.LinearIOSystem(
io_ref_model
G_model_tf,=('ur'),
inputs=('yr'),
outputs=0,
states='ref_model'
name )
def adaptive_controller_state(t, x, u, params):
"""Internal state of adpative controller, f(t,x,u;p)"""
# parameters
= params["gam"] # adaptation gain, aka. learning rate
gam = params["signb"]
signb
# controller inputs
= u[0]
ur = u[1]
yp = u[2]
yr
# controller states
# theta = x[0] # is not needed here
# algebraic relationships
= yr - yp
e
# dynamics xd = f(x,u)
= gam*yr*e*signb
d_theta
return [d_theta]
def adaptive_controller_output(t, x, u, params):
"""Algebraic output from adaptive controller, g(t,x,u;p)"""
# controller inputs
= u[0]
ur = u[1]
yp = u[2]
yr
# controller state
= x[0]
theta
# control law
= theta*ur
up
return [up, theta]
={"gam":1,"signb":np.sign(b)}
params
= ct.NonlinearIOSystem(
io_controller
adaptive_controller_state,
adaptive_controller_output,=3,
inputs=('up', 'theta'),
outputs=1,
states=params,
params='control',
name=0
dt )
= ct.InterconnectedSystem(
io_closed
(io_plant, io_ref_model, io_controller),=(
connections'plant.up', 'control.up'),
('control.u[1]', 'plant.yp'),
('control.u[2]', 'ref_model.yr')
(
),=('control.u[0]', 'ref_model.ur'),
inplist=('plant.yp', 'ref_model.yr', 'control.up', 'control.theta'),
outlist=0
dt )
# set initial conditions
= np.zeros((1, 1))
X0 0] = 0 # state of system X0[
# set simulation duration and time steps
= 6
Tend = 0.1
dt
# define simulation time span
= np.arange(0, Tend, dt)
t_vec # define control input
= np.zeros((2, len(t_vec)))
ur_vec 0, :20] = 1
ur_vec[1, :] = ur_vec[0, :] ur_vec[
# simulate the system, with different gammas
= ct.input_output_response(io_closed, t_vec, ur_vec, X0, params={"gam":0.5})
tout1, yout1 = ct.input_output_response(io_closed, t_vec, ur_vec, X0, params={"gam":1})
tout2, yout2 = ct.input_output_response(io_closed, t_vec, ur_vec, X0, params={"gam":2}) tout3, yout3
=(16,8))
plt.figure(figsize3,1,1)
plt.subplot(0,:], label=r'$y_{\gamma = 0.5}$')
plt.plot(tout2, yout1[0,:], label=r'$y_{\gamma = 1.0}$')
plt.plot(tout2, yout2[0,:], label=r'$y_{\gamma = 2.0}$')
plt.plot(tout3, yout3[1,:] ,label=r'$y_{soll}$', linestyle="--")
plt.plot(tout1, yout1[=14)
plt.legend(fontsize'Systemantworten')
plt.title(3,1,2)
plt.subplot(2,:], label=r'$\gamma = 0.5$')
plt.plot(tout1, yout1[2,:], label=r'$\gamma = 1.0$')
plt.plot(tout2, yout2[2,:], label=r'$\gamma = 2.0$')
plt.plot(tout3, yout3[=4, fontsize=14)
plt.legend(locr'Regler $u_p$')
plt.title(3,1,3)
plt.subplot(1,:]-yout1[0,:], label=r'$\gamma = 0.5$')
plt.plot(tout1, yout1[1,:]-yout2[0,:], label=r'$\gamma = 1.0$')
plt.plot(tout2, yout2[1,:]-yout3[0,:], label=r'$\gamma = 2.0$')
plt.plot(tout3, yout3[=4, fontsize=14)
plt.legend(locr'Fehler $e$')
plt.title( plt.show()
=(16,8))
plt.figure(figsize3,:], label=r'$\gamma = 0.5$')
plt.plot(tout1, yout1[3,:], label=r'$\gamma = 1.0$')
plt.plot(tout2, yout2[3,:], label=r'$\gamma = 2.0$')
plt.plot(tout3, yout3[=4, fontsize=14)
plt.legend(locr'Adaptionsparameter $\theta$')
plt.title( plt.show()
Wenn \(u_r = 0\) gilt, dann folgt \(e = 0\) und die Parameter werden nicht weiter angepasst. Weiters sehen wir den Einfluss von \(\gamma\).
Das ein Sprung als Referenz-Signal nicht immer eine ausreichende Anregung ist, wollen wir anhand eines weiteren Beispiels besprechen.
Beispiel 2: Direct MRAC, Lyapunov Rule
Aufgrund des Beispiels 1 könnte man meinen, dass es genügt \(u_r \neq 0\) zu wählen, damit die Adaptionsparameter zu den wahren Werten konvergieren. Dem ist aber nicht so. Selbst wenn also \(u_r \neq 0\) gilt, so werden womöglich die wahren Werte nicht erreicht. Das wollen anhand eines weiteren Beispiels besprechen.
Es sei ein System der Form
\[ \dot{x}_p(t) = a x_p(t) + b u_p(t) \]
gegeben, wobei \(u_p(t)\) der Eingang und \(x_p(t)\) ein messbarer Zustand ist. Die Modellparameter \(a\) und \(b\) sind unbekannt. Das geschlossene System soll nun wie das Modell
\[ \dot{x}_r(t) = a_r x_r(t) + b_r u_r(t) \]
verhalten. Das wollen wir mit dem Regler
\[ u_p(t) = k_x(t)x_p(t) + k_r(t)u_r(t) \]
erreichen, wobei \(k_x(t)\) ein Regelparameter (Feedback Gain) und \(k_r(t)\) ein Steuerungsparameter (Feedforward Gain) entspricht. Beide Parameter werden ständig angepasst und verändern sich dann natürlich über die Zeit.
Zur Anpassung der Parameter verwenden wir das Lyapunov-Gesetz:
\[ \begin{split} \dot{k}_x &= \gamma x_p e\\ \dot{k}_r &= \gamma u_r e \end{split} \]
Dabei ist der Fehler wieder duch \(e=x_r - x_p\) gebeben und \(\gamma\) ist eine Adaptionsverstärkung (Adaption Gain).
Die Herleitung des Lyapunov-Gesetzes (Lyapunov Rule) wird später nachgereicht. Hier wollen wir nur diese Methode implementieren und die Auswirkung der des Referenz-Signals \(u_r\) studieren.
Dazu wählen wir zwei verschiedene Referenz-Signale: - Sprung - Sinus
Code
# linear system
= 1.
Ap = 3
Bp = 1
Cp = 0
Dp
= ct.StateSpace(Ap,Bp,Cp,Dp)
G_plant_ss
# io_plant_model
= ct.LinearIOSystem(
io_plant
G_plant_ss,=('up'),
inputs=('xp'),
outputs=('xp'),
states='plant'
name )
# linear system
= -4
Ar = 4
Br = 1
Cr = 0
Dr
= ct.StateSpace(Ar,Br,Cr,Dr)
G_model_ss
# io_ref_model
= ct.LinearIOSystem(
io_ref_model
G_model_ss,=('ur'),
inputs=('xr'),
outputs=('xr'),
states='ref_model'
name )
= (Ar-Ap)/Bp
kx_star print(f"Der optimale Wert für {kx_star = }")
= (Br)/Bp
kr_star print(f"Der optimale Wert für {kr_star = }")
Der optimale Wert für kx_star = -1.6666666666666667
Der optimale Wert für kr_star = 1.3333333333333333
def adaptive_controller_state(t, x, u, params):
"""Internal state of adpative controller, f(t,x,u;p)"""
# parameters
= params["gam"]
gam
# controller inputs
= u[0]
ur = u[1]
xp = u[2]
xr
# controller states
= x[0] # kx
x1 = x[1] # kr
x2
# algebraic relationships
= xr - xp
e
# dynamics xd = f(x,u)
= gam*xp*e
d_x1 = gam*ur*e
d_x2
return [d_x1, d_x2]
def adaptive_controller_output(t, x, u, params):
"""Algebraic output from adaptive controller, g(t,x,u;p)"""
# controller inputs
= u[0]
ur = u[1]
xp = u[2]
xr
# controller state
= x[0]
kx = x[1]
kr
# control law
= kx*xp + kr*ur
up
return [up, kx, kr]
={"gam":1, "Ar":Ar, "Br":Br}
params
= ct.NonlinearIOSystem(
io_controller
adaptive_controller_state,
adaptive_controller_output,=3,
inputs=('up', 'kx', 'kr'),
outputs=2,
states=params,
params='control',
name=0
dt )
= ct.InterconnectedSystem(
io_closed
(io_plant, io_ref_model, io_controller),=(
connections'plant.up', 'control.up'),
('control.u[1]', 'plant.xp'),
('control.u[2]', 'ref_model.xr')
(
),=('control.u[0]', 'ref_model.ur'),
inplist=('plant.xp', 'ref_model.xr', 'control.up', 'control.kx', 'control.kr'),
outlist=0
dt )
# set initial conditions
= np.zeros((4, 1))
X0 0] = 0 # state of plant
X0[1] = 0 # state of ref_model
X0[2] = 0 # state of controller
X0[3] = 0 # state of controller X0[
Sprung als Anregeung
Als Referenz-Signal \(u_r = \sigma(t)\) wird ein Sprung gewählt.
# set simulation duration and time steps
= 1000
n_steps = 6
Tend
# define simulation time span
= np.linspace(0, Tend, n_steps)
t_vec # define control input
= np.zeros((2, n_steps))
ur_vec
= np.sin(1. * np.pi * t_vec)
sin 0, :] = 4
ur_vec[1, :] = ur_vec[0, :] ur_vec[
# simulate the system, with different gammas
= ct.input_output_response(io_closed, t_vec, ur_vec, X0, params={"gam":2.0}) tout1, yout1
=(16,8))
plt.figure(figsize3,1,1)
plt.subplot(0,:], label=r'$y_{\gamma = 2.0}$')
plt.plot(tout1, yout1[1,:] ,label=r'$y_{r}$')
plt.plot(tout1, yout1[=4)
plt.legend(loc'Systemantworten $y_p, (y_r)$')
plt.title(3,1,2)
plt.subplot(2,:], label=r'$u$')
plt.plot(tout1, yout1[=4)
plt.legend(locr'Regler $u_p$')
plt.title(3,1,3)
plt.subplot(0,:] - yout1[1,:], label=r'$e_{\gamma = 2.0}$')
plt.plot(tout1, yout1[=4)
plt.legend(loc'Fehler $e$')
plt.title( plt.show()
Der Fehler \(e = x_r - x_p \approx 0\) verschwindet.
=(16,8))
plt.figure(figsize2,1,1)
plt.subplot(3,:], label=r'$\gamma = 2.0$')
plt.plot(tout1, yout1[0,Tend,linestyle='--',label=r'$k_x^{\ast}$')
plt.hlines(kx_star,=4)
plt.legend(locr'Reglerparameter $k_x$')
plt.title(2,1,2)
plt.subplot(4,:], label=r'$\gamma = 2.0$')
plt.plot(tout1, yout1[0,Tend,linestyle='--', label=r'$k_r^{\ast}$')
plt.hlines(kr_star,=4)
plt.legend(locr'Reglerparameter $k_r$')
plt.title( plt.show()
Die Adaptionsparameter des Regler konvergieren hier zwar, aber nicht zum wahren Wert, da ab eines gewissen Zeitpunkts, kein nennenswerter Fehler zur Verfügung steht. Das Referenz-Signal ist also nicht geeignet, um das System genügen anzuregen. Deshalb wollen wir ein anderes Signal als Referenz-Signal wählen.
Sinus als Anregeung
Als Referenz-Signal \(u_r = \sin(t)\) wird ein Sinus gewählt.
= ct.input_output_response(io_closed, t_vec, ur_vec*sin, X0, params={"gam":2.0}) tout2, yout2
=(16,8))
plt.figure(figsize3,1,1)
plt.subplot(0,:], label=r'$y_{\gamma = 2.0}$')
plt.plot(tout2, yout2[1,:] ,label=r'$y_{r}$')
plt.plot(tout2, yout2[=4)
plt.legend(loc'Systemantworten $y_p, (y_r)$')
plt.title(3,1,2)
plt.subplot(2,:], label=r'$u$')
plt.plot(tout2, yout2[=4)
plt.legend(locr'Regler $u_p$')
plt.title(3,1,3)
plt.subplot(0,:] - yout2[1,:], label=r'$e_{\gamma = 2.0}$')
plt.plot(tout2, yout2[=4)
plt.legend(loc'Fehler $e$')
plt.title( plt.show()
Auch hier veschwindet der Fehler \(e = y_r - y_p \approx 0\).
=(16,8))
plt.figure(figsize2,1,1)
plt.subplot(3,:], label=r'$\gamma = 2.0$')
plt.plot(tout2, yout2[0,Tend,linestyle='--',label=r'$k_x^{\ast}$')
plt.hlines(kx_star,=4)
plt.legend(locr'Reglerparameter $k_x$')
plt.title(2,1,2)
plt.subplot(4,:], label=r'$\gamma = 2.0$')
plt.plot(tout2, yout2[0,Tend,linestyle='--',label=r'$k_r^{\ast}$')
plt.hlines(kr_star,=4)
plt.legend(locr'Reglerparameter $k_r$')
plt.title( plt.show()
Wie wir sehen, konvergieren die Parameter zum wahren Wert. Der Sinus als Referenz-Signal ist also deutlich besser geeignet als der Sprung. Wir sehen weiters, dass das Verhalten vom Problem abhängig ist. Während der Sprung für das Problem im Beispiel 1 ausreichend ist, gilt das für das Beispiel 2 nicht.
Fazit
Die Konvergenz der Adaptionsparameter hängt von der Wahl des Referenz-Signals \(u_r\) ab. Dieser Sachverhalt wird in der Literatur auch unter dem Begriff Persistent Excitation Condition verhandelt. Das Referenz-Signal muss das System genügend anregen um Daten mit Informationsgehalt zu erzeugen, aus denen sich dann die Parameter bestimmen lassen.
Die Persistent Excitation Condition ist für die adaptive Regelung, die Systemidentifikation als auch das bestärkende Lernen von Bedeutung. Deswegen studieren wir diese in einem späteren Blogeintrag genauer. Zuvor wollen wir aber besser mit der adaptiven Regelung vertraut werden.
Das obige Verhalten hängt nicht nur von der Art des Signals \(u_r\) ab, sondern auch von der Amplitude \(|\gamma|\cdot|u_r|\). Normalisierte Algorithmen adressieren diesen Aspekt der adaptiven Regelung. Im nächsten Blogeintrag werden wir deshalb normalisierte Algorithmen besprechen.
Referenzen
- Adaptive Control, Second Editon, 2008 (Karl J. Aström, Björn Wittenmark)
- Model-Reference Adaptive Control: A primer, 2018 (Nhan T. Nguyen)
- Robust Adaptive control, 2012 (Petros A. Ioannou, Jing Sun)
- Deep Model Reference Adaptive Control 2019 (Girish Joshi, Girish Chowdhary)