<jupyter_start><jupyter_text>Tutoriel CRRID GénériqueObjectif : Illustrer les propriétés de la CRRID.Concrètement, nous considérons un système qui a un controle par une loi de commande retardée (expression mathématique) et l'on prend compte et tirons avantage de ce retard dans le calcul des gains de la loi de commande retardée pour stabiliser le système. Informations importantes  :- Stabilité : Lorsque l'on a l'équation décrivant un système dans l'espace de Laplace (transformée de Laplace), la stabilité est traduite par l'ensemble des racines (complexes) de l'équation ayant leurs parties réelles négatives. On dit que les racines sont à gauche (de l'axe des ordonnées).- Transformée de Laplace (juste pour info) : Dans notre cas, la partie la plus importante dans la transformée de Laplace est la dérivation, c'est ce qui nous permet de passer d'une équation différentielle à une équation polynomiale. L'idée très grossière est qu'une dérivée d'ordre $n$ dans l'espace "classique"/"temporel" correspond à une multiplication par un terme $s^n$ dans l'espace de Laplace (fréquentiel). Donc, par exemple, $4\times k \times y''(t)\rightarrow 4ks^2y(s)$On traite une équation différentielle du type :$$\frac{d^n y(t)}{dt^n} + \sum\limits_{i=0}^{n-1}a_i\frac{d^i y(t)}{dt^i} + \sum\limits_{j=0}^m \alpha_j\frac{d^j y(t-\tau)}{dt^j}=0$$- $n, m\in\mathbb{N}, n>m$- $a_i, \alpha_j \in \mathbb{R}$- $s\in\mathbb{C}$,- $\tau \in \mathbb{R}$Par une transformée de Laplace :$$Q(s) = s^n+a_{n-1}s^{n-1}+...+a_0 + \left[\alpha_ms^m+...+\alpha_0\right]e^{-s\tau}$$Cette équation est polynomiale et non différentielle ce qui la rend beaucoup plus simple à traiter.On souhaite réaliser ce que l'on appelle un placement de pôle. Cela signifie dans ce cas que l'on veut trouver les coefficients ($a_i$ et $\alpha_j$ parfois notés $b_j$) soient de telle sorte que pour un ensemble de racines $s_i,\ i\in [0, n+m+1], \ Q(s_i)=0$ (degré $n+m+1\to n+m+1$ racines de multiplicité $1$ à assigner). ***La différence avec la MID correspond simplement au fait que l'on cherche à placer $n+m+1$ racines de multiplicité $1$ au lieu de $1$ racine de multiplicité $n+m+1$.***On connaît donc les valeurs de racines à "forcer" pour le quasipolynôme en jouant sur les coefficients.  Packages nécessairesOn utilise numpy pour les opérations mathématiques numériques telles que les opérations matricielles (produit matriciel par exemple).Sympy permet de réaliser des calculs en symbolique sur Python et donc de travailler avec des expressions mathématiques et en numérique.<jupyter_code># pip install sympy si nécessaire (module not found), pareil avec cxroots
%matplotlib notebook
import numpy as np
import sympy as sp
import cxroots as cx
import matplotlib.pyplot as plt<jupyter_output><empty_output><jupyter_text>Paramètres$n$ est le degré du polynôme non retardé, $m$ est celui du polynôme retardé (celui multiplié par l'exponentielle).$s_0$ est le taux de décroissance exponentiel. Il s'agit de la racine dominante que l'on souhaite assigner (celle la plus à droite lorsqu'on plot les racines). Celui-ci doit être négatif pour que le système soit stable. Un $s_0$ positif signifie donc que le système est instable.Dans le cadre de la CRRID, il existe deux possibilité de sélection de racines, nous pouvons soit les donner librement soit les définir comme équidistantes. Dans ce dernier cas, il faut donc donner la racine la plus à droite et la distance entre chaque racine.$\tau$ est le retard considéré.<jupyter_code>def equidistant(s0, d):
    return [s0-i*d for i in range(n+m+1)]
n = 2
m = 1
sList = [-1, -3, -5, -7] # Given roots
sList = equidistant(-1, 2) # Racines équidistantes, partant de -1 et de distance 2
value_tau = 1<jupyter_output><empty_output><jupyter_text>Création de symbolesLes variables s, tau, a et alpha vont désormais correspondre à des symboles et non des variables numériques.On considère dans nos cas concrets que les $a_i$ correspondent aux paramètres du système (de son équation) et les $\alpha_j$ correspondent aux gains de la loi de commande retardée.<jupyter_code>s = sp.symbols('s')  # define variable s for our problem to be solved
tau = sp.symbols('tau')  # define variable tau : delay

a = sp.symbols(["a{:d}".format(i) for i in range(n)], real=True)
alpha = sp.symbols(["alpha{:d}".format(i) for i in range(m + 1)], real=True)<jupyter_output><empty_output><jupyter_text>Création du quasipolynome<jupyter_code>Polynomial = s**n + np.array(a).dot([s**i for i in range(n)]) # Revient à faire s^n + a_{n-1}^{n-1}...
Delayed = np.array(alpha).dot([s**i for i in range(m+1)])*sp.exp(-s*tau) # Revient à faire 
#b^m*s^m + b_{m-1}^{m-1}...
Q = Polynomial + Delayed 
Q<jupyter_output><empty_output><jupyter_text>Créer le système à résoudreDans ce cadre de MID générique, on a $n+m+1$ inconnues qui sont les $a_i$ ($n$ variables de $0$ à $n-1$ inclus) et $\alpha_j$ ($m+1$ variables de $0$ à $m$ inclus).On parle donc de multiplicité de $n+m+1$ et on souhaite donc avoir $n+m+1$ équations. Pour l'instant nous avons une première équation qui correspond à notre quasipolynôme (=0). Étant en multiplicité $n+m+1$ en ***Multiplicity Induced Dominancy*** (MID), on va chercher à ce que la racine (en $s$) que l'on souhaite placer en $s_0$ soit de multiplicité $n+m+1$. Cela revient à ce que les dérivées successives de $Q$ par rapport à $s$ soient nulles lorsque $s=s_0$.Il faut donc calculer les $n+m$ dérivées suivantes ce qui se fait très simplement avec Sympy en utilisant la méthode diff en spécifiant en argument la variable de différenciation.<jupyter_code>Sys = [Q] * (n + m + 1)
print(Q)
for i in range(n + m + 1):
    Sys[i] = Sys[i].subs({s: sList[i]})
    Sys[i] = Sys[i].subs({tau: value_tau})
Sys<jupyter_output>a0 + a1*s + s**2 + (alpha0 + alpha1*s)*exp(-s*tau)<jupyter_text>Résolution du systèmeAprès avoir créé le système dérivatif, il suffit de le résoudre avec Sympy en spécifiant bien que l'on souhaite obtenir les expressions des $a_i$ et des $\alpha_j$.Ici, on a bien $n+m+1 = 4$ ce qui nous retourne $a_0, a_1, \alpha_0$ et $\alpha_1$ en gardant bien à l'esprit que $a_2=1$.Le ".args[0]" sert à extraire les solutions à partir du solveur qui retourne un set.<jupyter_code>sol = sp.linsolve(Sys, alpha + a).args[0] # Solveur selon les alpha et les a
sol<jupyter_output><empty_output><jupyter_text>Vérification de l'ordre des inconnuesOn vérifie que les variables retournées par le solveur sont dans ce sens : $\alpha_0,\alpha_1,a_0,a_1$.<jupyter_code>alpha + a
solNum = sol.subs({tau : value_tau})
solNum
a_num = list(solNum[m + 1:])
alpha_num = list(solNum[:m + 1])
print(a_num, alpha_num)<jupyter_output>[(-14*exp(2) + 35 + 3*exp(4))/(-2*exp(2) + 1 + exp(4)), (-12 + 4*exp(2))/(-1 + exp(2))] [(4 - 28*exp(2))/(-2*exp(5) + exp(3) + exp(7)), -4/(-exp(3) + exp(5))]<jupyter_text>RootfindingAprès avoir calculé ces coefficients, on souhaite vérifier de manière visuelle que les résultats trouvés fonctionnent. Pour cela, on reconstitue le quasipolynôme mais en remplaçant cette fois ci les coefficients par les valeurs trouvées. On remplace également $\tau$ par sa valeur et l'on dérive ensuite ce quasipolynôme par rapport à s. La dérivée est utile pour le rootfinding qui est l'algorithme qui va nous trouver les racines du quasipolynôme dans un contour donné.La définition d'un contour est indispensable pour du rootfinding complexe. On a ici choisi un rectangle entre $-100$ et $10$ en abscisse et $-100$ et $-100$ en ordonnée.<jupyter_code>QNumerique = s**n + np.array(a_num).dot([s**i for i in range(n)])+\
            np.array(alpha_num).dot([s**i for i in range(m+1)])*sp.exp(-s*tau)

# Possibilité de faire ça de manière plus propre avec un "zip"

QNumerique = QNumerique.subs(tau, value_tau)
QNumerique
sysRootFinding = [QNumerique, QNumerique.diff(s)]
sysFunc = [sp.lambdify(s, i) for i in sysRootFinding]
rect = cx.Rectangle([-100, 10], [-100, 100])
roots = rect.roots(sysFunc[0], sysFunc[1], rootErrTol=1e-5, absTol=1e-5, M = n + m + 1)
print(roots)
xroot = np.real(roots[0])
yroot = np.imag(roots[0])
plt.figure(figsize=(8,6))
plt.axhline(linewidth=2, color='black')
plt.axvline(linewidth=2, color='black')
plt.axvspan(0, 1, alpha=0.5, color='red')
plt.scatter(xroot, yroot)
plt.grid()
plt.title("Root spectrum")
plt.xlabel(r"$Re(s)$")
plt.ylabel(r"$Im(s)$")<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12043269230769231, 'fraction_numerical': 0.017548076923076923, 'mean_word_length': 5.140959409594096, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 32, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '8', 'dir': 'jupyter-structured-clean-dedup', 'id': '415480', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 3156, 'n_tokens_neox': 3055, 'n_words': 1062, 'path': 'jupyter notebook original/CRRID Tutorial.ipynb', 'repo_name': 'Jayvir29/P3Delta-Online', 'size': '8320'}