Segway

TD1

Cette séance comporte une partie travaux dirigés suivie d’une partie travaux pratiques.

La partie travaux pratique consiste en la synthèse et l’analyse d’une loi de commande par retour d’état pour un pendule inversé mobile (à la segway).

1ere partie, sur papier

2eme partie, sur machine

Plomberie

Pour utiliser la bibliotheque d’automatique (python control toolbox) disponible sur mon compte ENAC, vous pouvez taper la commande suivante dans votre terminal:

source /home/personnel/SINA/Drouinan/venv/bin/activate

À titre de vérification, la commande suivante ne devrait pas renvoyer d’erreur:

python3 -c "import control"

Le reste

Nous considérons ici le pendule inversé décrit sur la figure 1

Fig1. - Pendule inversé mobile.


La mise en équations détaillée est disponible sur wikipedia. En utilisant les équations de Lagrange, on peut obtenir:

qui peut être réarrangé en:


1. Donnez une représentation d’état du système

Nous sommes ici en présence d’un système dynamique non linéaire de dimension 4, possédant une unique entrée. En utilisant comme vecteur d’état et comme entrée , il vient

2. Cherchez un point d’équilibre.

Le pendule est à l’équilibre pour tout

3. Simulez numériquement la réponse du pendule à une perturbation.

Je vous propose un squelette de code réalisant cette opération afin de vous permettre de vous focaliser sur la loi de commande.

class Param:
    def __init__(self, sat=None):
        self.m     = 0.5     # mass in kg
        self.M     = 0.25    # mass in kg
        self.l     = 1.      # length in m
        self.sat   = sat     # max thrust of motor
#
# Components of the state vector
#
sv_x    = 0  # pos of the cg of the cart in m
sv_th   = 1  # orientation of the arm in rad, 0 up
sv_xd   = 2  # velocity of the cart in m/s
sv_thd  = 3  # rotational velocity of the arm in rad/s
sv_size = 4  # dimension of the state vector

#
# Components of the input vector
#
iv_f    = 0  # force on the cart in N
iv_size = 1  # dimension of the input vector
#
# Dynamic model as state space representation
#
# X : state
# U : input
# P : param
#
# returns Xd, time derivative of state
#
def dyn(X, t, U, P):

    Xd = np.zeros(sv_size)
    th, thd = X[sv_th], X[sv_thd] 
    sth, cth = np.sin(th), np.cos(th)

    # saturate input
    f = np.clip(U[iv_f], -P.sat, P.sat) if P.sat != None else U[iv_f]
    # upper rows are found directly in state vector
    Xd[sv_x]  = X[sv_xd]
    Xd[sv_th] = thd
    # compute the lower rows
    a = P.M + P.m*(sth**2)
    Xd[sv_xd]  = (f - P.m*P.l*(thd**2)*sth + P.m*9.81*sth*cth) / a
    Xd[sv_thd] = (f*cth - P.m*P.l*(thd**2)*sth*cth +(P.M+P.m)*9.81*sth) / a / P.l
    
    return Xd

La fonction sim_open_loop (lignes 130 à 136) réalise une simulation du pendule sur une durée de 10 secondes, avec un pas de temps de 0.01 secondes avec une entrée nulle et à partir de l’état initial

def sim_open_loop(X0):
    P, U = Param(), [0]
    time = np.arange(0., 10, 0.01)
    X = scipy.integrate.odeint(dyn, X0, time, args=(U, P ))
    U = np.zeros((len(time), 1))
    return time, X, U

Les lignes 160 à 164 réalisent la simulation et l’affichent

    time, X, U = sim_open_loop([0, 0, 0, 0.001])
    print('The pendulum is tipping, your job is now to keep it upright')
    plot(time, X, U)
    plt.show()

La figure 2 présente les résultats.

Fig2. - Simulation du pendule inversé mobile en boucle ouverte, à gauche à l'équilibre, à droite perturbé


La figure 3, en plus des chronogrammes, présente une animation. Ce type de représentation, intuitive pour l’humain est assez simple à réaliser avec matplotlib.

Fig3. - Simulation du pendule inversé mobile en boucle ouverte, annimation


4 Réglez empiriquement un correcteur par retour d’état stabilisant le pendule. Simulez

C’est possible, mais long et douloureux. Je propose de le garder pour plus tard.

5 Calculez la représentation d’état linéarisée autour du point d’équilibre.

Comme nous disposons d’un modèle du système, nous allons l’utiliser. Comme ce modéle est non-linéaire, nous allons le linéariser autour du point d’équilibre précédenent calculé.

La représentation d’état linéarisée est

avec

Dans notre cas les jacobiens peuvent etre calculés analytiquement. Le résultat du calcul est présenté dans le squelette de code.

Les lignes 64 à 91 contiennent la définition des jacobiens de la fonction de dynamique (oui, je les ai calculés…).

#
# (analytic) Jacobians of the Dynamic Model
#
def jacobian(X, U, P):

    th, thd = X[sv_th], X[sv_thd] 
    sth, cth = np.sin(th), np.cos(th)
    v = P.M + P.m*(sth**2)

    A = np.zeros((4,4))
    A[sv_x][sv_xd] = 1.
    A[sv_th][sv_thd] = 1.

    f3num = U[iv_f] - P.m*P.l*(thd**2)*sth + P.m*9.81*sth*cth 
    f3den = P.M + P.m*(sth**2)
    xdd = f3num/f3den
    df3n_dth = -P.m*P.l*(thd**2)*cth + P.m*9.81*(2.*(cth**2)-1.)
    df3d_dth = 2.*P.m*sth*cth
    A[sv_xd][sv_th] = (df3n_dth*f3den-f3num*df3d_dth)/(f3den**2)
    A[sv_xd][sv_thd] = -2*P.m*P.l*thd*sth/f3den
    A[sv_thd][sv_th] = 1./P.l*(9.81*cth + A[sv_xd][sv_th]*cth - xdd*sth)
    A[sv_thd][sv_thd] = cth/P.l*A[sv_xd][sv_thd]

    B = np.zeros((4,1))
    B[sv_xd]  = 1./f3den 
    B[sv_thd] = cth/v/P.l 
    
    return A,B

Lorsqu’un résultat analytique n’est pas disponible, on peut facilement utiliser une differenciation numérique pour approcher les jacobiens (comme cela est aussi présenté dans le squelette de code).

Les lignes 96 à 110 contiennent une version numerique du jacobien (utilisée avant d’obtenir la version analytique). D’autre part, ce calcul numérique peut aussi servir à valider le calcul et l’implémentation de l’expression analytique.

def num_jacobian(X, U, P):
    dX = np.diag([0.01, 0.01, 0.01, 0.01])
    A = np.zeros((sv_size, sv_size))
    for i in range(0, sv_size):
        dx = dX[i,:]
        delta_f = dyn(X+dx/2, 0, U, P) - dyn(X-dx/2, 0, U, P)
        delta_f = delta_f / dx[i]
        A[:,i] = delta_f

    dU = np.diag([0.01])
    delta_f = dyn(X, 0, U+dU/2, P) - dyn(X, 0, U-dU/2, P)
    delta_f = delta_f / dU
    B = np.zeros((sv_size,iv_size))
    B[:,0] = delta_f
    return A,B

Le calcul des jacobiens pour les paramètres par défaut conduit à

P = Param()
A, B = jacobian([0, 0, 0, 0], [0], P)
print('A\n{}\nB\n{}'.format(A, B))

A
[[ 0.    0.    1.    0.  ]
[ 0.    0.    0.    1.  ]
[ 0.   19.62  0.   -0.  ]
[ 0.   29.43  0.   -0.  ]]
B
[[0.]
[0.]
[4.]
[4.]]

6 Discutez la stabilité et la commandabilité du modèle linéarisé.

Les strutures propres (valeurs propres, et vecteurs propres) peuvent être obtenus avec:

eig_va, eig_ve =  np.linalg.eig(A)
print(f'pole bo {eig_va}')

pole bo [ 0.         0.         5.4249424 -5.4249424]

Le linéarisé possède un mode à partie réelle positive, il est donc instable. Cela confirne notre intuition (le pendule tombe si on ne fait rien contre) ainsi que la précédente simulation.

La matrice de commandabilité et son rang peuvent être calculés avec

Qc = control.matlab.ctrb(A, B)
print(f'rang Qc {np.linalg.matrix_rank(Qc)}')

Le système est en conséquence complètement commandable. Ceci confirme aussi notre intuition: le monocycle n’est pas une utopie.

7 Synthèse d’un correcteur par retour d’état.

Synthétisez un correcteur par retour d’état satisfaisant à l’objectif de commande suivant:

Simulez la réponse du pendule stabilisé

cl_poles = [-2, -2.1, -2.2, -2.3]
#K = control.matlab.place(A, B, cl_poles)
K = np.array(control.matlab.acker(A, B, poles)) Vous pouvez vérifier que votre calcul à fonctionné en calculant

print(f'poles bf {np.linalg.eig(A-np.dot(B, K))[0]}')

Le squelette de code contient une fonction permettant la simulation du pendule et de son correcteur, sim_state_feedback.

def sim_state_feedback(X0, K):
    P = Param()
    def cl_dyn(X, t): return dyn(X, t, [-np.dot(K, X)], P)
    time = np.arange(0., 10, 0.01)
    X = scipy.integrate.odeint(cl_dyn, X0, time)
    U =  np.array([[-np.dot(K, Xi)] for Xi in X])
    return time, X, U
Fig4. - Pendule inversé mobile stabilisé.


Vous pouvez essayer d’expérimenter avec différentes positions des pôles, réels ou complexes.

Fig5. - différentes localisations des pôles.


Vous pouvez essayer différentes amplitudes de perturbation, c’est-à-dire éloigner le pendule de plus en plus de sa position d’équilibre.

Vous constaterez sans doute que le pendule tombe à partir d’une certaine valeur de la perturbation.

Fig5. - perturbation d'ampleur croissantes.


8 Saturations:

Simulez une saturation d’actionneur à 50 pour cent de la valeur maximum atteinte par le correcteur précédemment synthétisé. Simulez.

Téléchargement:

Remarques:

Un probleme similaire est résolu ici

dynamique commande