+1
-1
biblio.typ
+1
-1
biblio.typ
···
14
14
15
15
=== Une base de code partiellement open-source
16
16
17
-
Une partie du code source de ce SDK n'est pas disponible, et n'est que distribué sous forme de binaires @sdk2-in-source-binaries. J'ai donc chercher à comprendre cette partie du code par ingénierie inverse, ce qui ne s'est pas avéré nécéssaire.
17
+
Une partie du code source de ce SDK n'est pas disponible, et n'est que distribué sous forme de binaires @sdk2-in-source-binaries. J'ai donc chercher à comprendre cette partie du code par ingénierie inverse, ce qui ne s'est pas avéré nécessaire.
18
18
19
19
Au final, en explorant le code source du plugin pour un autre logiciel de simulation, Mujoco @mujoco @unitree-mujoco, j'ai pu comprendre comment interfacer le SDK avec Gazebo.
20
20
+21
-21
rapport/context.typ
+21
-21
rapport/context.typ
···
30
30
- Un _environnement_, que les actions viennent modifier
31
31
- Un _score_ (_coût_ s'il doit être minimisé, _récompense_ inversement) qui dépend de l'état pré- et post-action de l'environnement ainsi que de l'action qui a été effectuée
32
32
33
-
La phase d'apprentissage consiste à trouver, par des cycles d'essai/erreur, quelles sont les meilleures actions à prendre en fonction de l'environnement actuel, avec "meilleur" définit comme "qui minimise le coût" (ou maximise la récompense):
33
+
La phase d'apprentissage consiste à trouver, par des cycles d'essai/erreur, quelles sont les meilleures actions à prendre en fonction de l'environnement actuel, avec "meilleur" défini comme "qui minimise le coût" (ou maximise la récompense):
34
34
35
35
#diagram({
36
36
node((0, 0))[Agent]
···
41
41
edge((2, 0), (0, 0), "->", bend: 45deg)[Mise à jour]
42
42
})
43
43
44
-
Cette technique est particulièrement adaptée au problèmes qui se prêtent à une modélisation type "jeu vidéo", dans le sens où l'agent représente le personnage-joueur, et le coût un certain score, qui est condition de victoire ou défaite.
44
+
Cette technique est particulièrement adaptée aux problèmes qui se prêtent à une modélisation type "jeu vidéo", dans le sens où l'agent représente le personnage-joueur, et le coût un certain score, qui est condition de victoire ou défaite.
45
45
46
46
En robotique, une approche similaire explore l'espace d'action (en général un courant à envoyer aux moteurs) de façon à optimiser le coût.
47
47
48
48
En robotique, on a des correspondances claires pour ces quatres notions:
49
49
50
50
/ Agent: Robot pour lequel on développe le programme de contrôle (appelé _politique_)
51
-
/ Actions: Envoi d'ordres aux moteurs, souvent le courant électrique à appliquer // #footnote[il y a techniquement deux principales manières de contrôler un robot: l'envoi de commandes de courant, ou contrôle par puissance, et l'envoi de vitesses cibles, qui laisse la détermination du courant nécéssaire au microcontrolleurs sur le robot même]
51
+
/ Actions: Envoi d'ordres aux moteurs, souvent le courant électrique à appliquer // #footnote[il y a techniquement deux principales manières de contrôler un robot: l'envoi de commandes de courant, ou contrôle par puissance, et l'envoi de vitesses cibles, qui laisse la détermination du courant nécessaire au microcontrolleurs sur le robot même]
52
52
/ Environnement: Le monde réel. C'est de loin la partie la plus difficile à simuler informatiquement. On utilise des moteurs de simulation physique, dont la pluralité des implémentations est importante, voir @why_multiple_simulators
53
53
/ Coût: Ensemble de contraintes ("ne pas endommager le robot") et d'évaluations spécifiques à la tâche à effectuer ("s'est déplacé de 5m en avant selon l'axe $x$").
54
54
···
98
98
L: E -> S
99
99
$
100
100
101
-
avec $E$ l'ensemble des états possibles de l'environnement, et $S$ un ensemble muni d'un ordre total (on utilise souvent $[0, 1]$). Ces fonctions coût, qui ne dépendent que de l'état actuel de l'environnement, représente un domaine du RL#footnote[Reinforcement Learning] appelé _Q-Learning_ @qlearning
101
+
avec $E$ l'ensemble des états possibles de l'environnement, et $S$ un ensemble muni d'un ordre total (on utilise souvent $[0, 1]$). Ces fonctions coût, qui ne dépendent que de l'état actuel de l'environnement, représentent un domaine du RL#footnote[Reinforcement Learning] appelé _Q-Learning_ @qlearning
102
102
103
103
On remplit la colonne "Action à effectuer" avec l'action au coût le plus bas:
104
104
···
144
144
145
145
==== Tendances à la "tricherie" des agents
146
146
147
-
Expérimentalement, on sait que des tendances "tricheuses" émergent facilement pendant l'entraînement: l'agent découvre des séries d'actions qui causent un bug avantageux vis à vis du coût associé, soit parce qu'il y a un bug dans le calcul de l'état de l'environnement post-action, soit parce que la fonction coût ne prend pas suffisemment bien en compte toutes les possibilités de l'environnement (autrement dit, il manque de contraintes).
147
+
Expérimentalement, on sait que des tendances "tricheuses" émergent facilement pendant l'entraînement: l'agent découvre des séries d'actions qui causent un bug avantageux vis à vis du coût associé, soit parce qu'il y a un bug dans le calcul de l'état de l'environnement post-action, soit parce que la fonction coût ne prend pas suffisamment bien en compte toutes les possibilités de l'environnement (autrement dit, il manque de contraintes).
148
148
149
149
Dans le cas de la robotique, cela arrive particulièrement souvent, et il faut donc un simulateur qui soit suffisamment réaliste.
150
150
151
151
==== Sous-spécification de la fonction coût
152
152
153
-
Un exemple populaire est l'expérience de pensée du Maximiseur de trombones @trombones: On imagine un agent avec pour environnement le monde réel, pour actions "prendre des décisions"; "envoyer des emails"; etc. et pour fonction récompense "le nombre de trombones existant sur Terre". Il finirait possiblement par réduire en escalavage tout être vivant capable de produire des trombones: la fonction coût est sous-spécifiée
153
+
Un exemple populaire est l'expérience de pensée du Maximiseur de trombones @trombones: on imagine un agent avec pour environnement le monde réel, pour actions "prendre des décisions"; "envoyer des emails"; etc. et pour fonction récompense "le nombre de trombones existant sur Terre". Il finirait possiblement par réduire en escalavage tout être vivant capable de produire des trombones: la fonction coût est sous-spécifiée.
154
154
155
155
==== La validation comme méthode de mitigation <why_multiple_simulators>
156
156
157
-
Comme ces bugs sont des comportements non voulus, il est très probables qu'ils ne soient pas exactement les mêmes en changeant d'implémentation.
157
+
Comme ces bugs sont des comportements non voulus, il est très probable qu'ils ne soient pas exactement les mêmes en changeant d'implémentation.
158
158
159
159
Il convient donc de se servir de _plusieurs_ implémentations: une sert à la phase d'entraînement, pendant laquelle l'agent développe des "tendances à la tricherie", puis une autre sert à la phase de _validation_.
160
160
161
-
Cette phase consiste en le lancement de l'agent dans une autre implémentation, avec les mêmes actions mais qui, crucialement, ne comporte pas les mêmes bugs que l'environnement ayant servi à la phase d'apprentissage.
161
+
Cette phase consiste à lancer l'agent dans une autre implémentation, avec les mêmes actions mais qui, crucialement, ne comporte pas les mêmes bugs que l'environnement ayant servi à la phase d'apprentissage.
162
162
163
-
Les "techniques de triche" ainsi apprises deviennent inefficace, et si le score devient bien pire que celui de l'apprentissage, on peut détecter les cas de triche.
163
+
Les "techniques de triche" ainsi apprises deviennent inefficaces, et si le score devient bien inférieur à celui de l'apprentissage, on peut détecter les cas de triche.
164
164
165
165
On peut même aller plus loin, et multiplier les phases de validation avec des implémentations supplémentaires, ce qui réduit encore la probabilité qu'une technique de triche se glisse dans l'agent final.
166
166
···
209
209
edge((2, 0), (2, .75), (0, .75), (0, 0), "-->", label-side: left)[itération],
210
210
)
211
211
212
-
Quand on "déroule" $Pi$ en en partant d'un certain état initial $s_0$, on obtient une suite d'états et d'actions:
212
+
Quand on "déroule" $Pi$ en partant d'un certain état initial $s_0$, on obtient une suite d'états et d'actions:
213
213
214
214
#diagram(
215
215
$
···
243
243
)
244
244
$
245
245
246
-
l'ensemble des chemins possibles avec la politique $pi$. C'est tout simplement l'ensemble de tout les "déroulements" de la politique $pi$ en partant des états possibles de l'environnement.
246
+
l'ensemble des chemins possibles avec la politique $pi$. C'est tout simplement l'ensemble de tous les "déroulements" de la politique $pi$ en partant des états possibles de l'environnement.
247
247
248
248
249
-
On définit également l'ensemble de _tout_ les chemins d'états possibles, peut importe la politique, $cal(C)$ :
249
+
On définit également l'ensemble de _tous_ les chemins d'états possibles, peut importe la politique, $cal(C)$ :
250
250
251
251
#let definitions_paths_set = $
252
252
cal(C) & :=
···
364
364
node(name: <bottom>, (4.5, +1.5))[$sum_(i=t+1)^oo gamma^t r(s'_i)$ ]
365
365
node((5.75, +1.5), align(
366
366
left,
367
-
)[si $Pi$ avait choisit $a'_t$ \ au lieu de $a_t$])
367
+
)[si $Pi$ avait choisi $a'_t$ \ au lieu de $a_t$])
368
368
edge(<break>, <bottom>, "->", bend: -25deg)[$a'_t$]
369
369
370
370
// top-branch path
371
371
node(name: <top>, (4.5, -1.5))[$sum_(i=t+1)^oo gamma^t r(s''_i)$]
372
372
node((5.75, -1.5), align(
373
373
left,
374
-
)[si $Pi$ avait choisit $a''_t$ \ au lieu de $a_t$])
374
+
)[si $Pi$ avait choisi $a''_t$ \ au lieu de $a_t$])
375
375
edge(<break>, <top>, "->", bend: 25deg)[$a''_t$]
376
376
377
377
// Expectation bar V(s)
···
397
397
398
398
On considère tout les chemins à partir de l'état $s_t$, et l'on regarde l'espérance...
399
399
400
-
/ pour $V(s_t)$: de tout les chemins
400
+
/ pour $V(s_t)$: de tous les chemins
401
401
/ pour $Q(s_t, a_t)$: du chemin où l'on a choisi $a_t$
402
402
403
403
En suite, il suffit de faire la différence, pour savoir l'_avantage_ que l'on a à choisir $a_t$ par rapport au reste.
···
487
487
488
488
489
489
490
-
Pour évaluer cette distance, on regarde la plus grande des distances entre des paires de distributions de probabilité de politiques $Q_Pi$ et $Q_Pi'$, pour tout $s in S$ @trpo
490
+
Pour évaluer cette distance, on regarde la plus grande des distances entre des paires de distribution de probabilité de politique $Q_Pi$ et $Q_Pi'$, pour tout $s in S$ @trpo
491
491
492
492
$
493
493
max_(s in S) D_"KL" (Q_Pi' (s, dot) || Q_Pi (s, dot)) < delta
···
521
521
)
522
522
$
523
523
524
-
On a $D_"KL" (Q, Q') = 0$ (cf @dkl-zero), alors qu'il y a eu une modification très importante des probabilités de choix de l'action 1 et 2 dans tout les états possibles : si on imagine $Q(s, 1) = Q(s, 2) = 1 slash 4$, on a après modification $Q'(s, 1) = 1 slash 2$ et $Q'(s, 2) = 1 slash 8$.
524
+
On a $D_"KL" (Q, Q') = 0$ (cf @dkl-zero), alors qu'il y a eu une modification très importante des probabilités de choix de l'action 1 et 2 dans tous les états possibles : si on imagine $Q(s, 1) = Q(s, 2) = 1 slash 4$, on a après modification $Q'(s, 1) = 1 slash 2$ et $Q'(s, 2) = 1 slash 8$.
525
525
526
526
==== Région de confiance
527
527
528
-
Cette contrainte définit un ensemble réduit de $Pi'$ acceptables comme nouvelle politique, aussi appelé une _trust region_ (région de confiance), d'où la méthode d'optimisation tire son nom @trpo.
528
+
Cette contrainte définit un ensemble réduit de $Pi'$ acceptables comme nouvelle politique, aussi appelée une _trust region_ (région de confiance), d'où la méthode d'optimisation tire son nom @trpo.
529
529
530
530
#let ddot = [ #sym.dot #h(-1em / 16) #sym.dot ]
531
531
···
545
545
546
546
La _PPO_ repose sur le même principe de stabilisation de l'entraînement par limitation de l'ampleur des changements de politique à chaque pas.
547
547
548
-
Cependant, les méthodes _PPO_ préfèrent changer la quantité à optimiser, pour limiter intrinsèquement l'ampleur des modifications, en résolvant un problème d'optimisation sans contraintes @ppo
548
+
Cependant, les méthodes _PPO_ préfèrent changer la quantité à optimiser, pour limiter intrinsèquement l'ampleur des modifications, en résolvant un problème d'optimisation sans contrainte @ppo
549
549
550
550
551
551
$
···
681
681
682
682
Dans le contexte de la robotique, le calcul de l'état post-action de l'environnement est le travail du _moteur de physique_.
683
683
684
-
Bien évidemment, ce sont des programmes complexes avec des résolutions souvent numériques d'équation physiques; il est presque inévitable que des bugs se glissent dans ces programmes.
684
+
Bien évidemment, ce sont des programmes complexes avec des résolutions souvent numériques d'équations physiques; il est presque inévitable que des bugs se glissent dans ces programmes.
685
685
686
686
687
687
···
726
726
scale(7%, reflow: true, diagraph.render(read("./isaac-deptree.dot"))),
727
727
)
728
728
729
-
Bien que toutes ces dépendances puissent être spécifiées avec des contraintes de version strictes @lockfiles pour éviter des changements imprévus de comportement du code venant des bibliothèques, beaucoup celles-ci ont besoin de compiler du code C++ _à l'installation_#footnote[Pour des raisons de performance @cpp-python, certaines bibliothèques implémentent leurs fonctions critiques en C++. C'est par exemple le cas de NumPy @numpy]: fixer la version de la bibliothèque ne suffit pas donc à guarantir la reproductibilité de la compilation de l'arbre des dépendances.
729
+
Bien que toutes ces dépendances puissent être spécifiées avec des contraintes de version strictes @lockfiles pour éviter des changements imprévus de comportement du code venant des bibliothèques, beaucoup ont besoin de compiler du code C++ _à l'installation_#footnote[Pour des raisons de performance @cpp-python, certaines bibliothèques implémentent leurs fonctions critiques en C++. C'est par exemple le cas de NumPy @numpy]: fixer la version de la bibliothèque ne suffit pas donc à garantir la reproductibilité de la compilation de l'arbre des dépendances.
+14
-14
rapport/gz-unitree.typ
+14
-14
rapport/gz-unitree.typ
···
22
22
23
23
On change d'approche, en préférant plutôt utiliser les abstractions fournies par le SDK de Unitree (cf @receive-lowcmd et @send-lowstate)
24
24
25
-
Enfin, si un pare-feu est actif, il faut autoriser le traffic UDP l'intervalle d'addresses IP `224.0.0.0/4`. Par exemple, avec _ufw_ @ufw
25
+
Enfin, si un pare-feu est actif, il faut autoriser le trafic UDP l'intervalle d'adresses IP `224.0.0.0/4`. Par exemple, avec _ufw_ @ufw
26
26
27
27
```bash
28
28
sudo ufw allow in proto udp from 224.0.0.0/4
···
34
34
gutter: 2em,
35
35
[
36
36
37
-
Pour arriver à ces solutions, _Wireshark_ @wireshark s'est avéré utile, étant capable d'inspecter du traffic RTPS#footnote[Le protocole sur lequel est construit DDS @dds],
37
+
Pour arriver à ces solutions, _Wireshark_ @wireshark s'est avéré utile, étant capable d'inspecter du trafic RTPS#footnote[Le protocole sur lequel est construit DDS @dds],
38
38
39
39
40
40
C'est notamment grâce à ce traçage des paquets que le problème de domaine DDS a été découvert: notre _subscriber_ DDS était réglé sur le domaine anonyme (ID aléatoire représenté par un 0 lors de la configuration) alors que le SDK d'Unitree communique sur le domaine d'ID 1.
···
376
376
$ ddt(Delta q) = ddt(q_"new" - q_"old") = ddt(q_"new") - ddt(q_"old") = Delta ddt(q) = Delta dot(q) $
377
377
378
378
]
379
-
/ $K_p$: prépondérance de la partie proportionelle
379
+
/ $K_p$: prépondérance de la partie proportionnelle
380
380
/ $K_p$: prépondérance de la partie dérivée
381
381
382
382
Cette équation met à jour $tau$ pour rapprocher l'état actuel du moteur de la nouvelle consigne, en prenant en compte
383
383
384
-
- L'erreur sur l'angle $Delta q$ (partie proportionelle).
384
+
- L'erreur sur l'angle $Delta q$ (partie proportionnelle).
385
385
- L'erreur sur la vitesse de changement de $Delta q$ (partie dérivative). Cette prise en compte de la vitesse permet de lisser les changements appliqués aux moteurs.
386
-
- Un couple dit de _feed-forward_, $tau_"ff"$, qui permet le maintient du robot à un état stable. On pourrait le déterminer en lançant une première simulation, avec pour objectif le maintient debout. Une fois la stabilité atteinte, on relève les couples des moteurs. Intuitivement, on peut voir $tau_"ff"$ comme un manière de s'affranchir de la partie "maintient debout" dans l'expression de la commande, similairement à la mise à zéro ("tarer") d'une balance.
386
+
- Un couple dit de _feed-forward_, $tau_"ff"$, qui permet le maintien du robot à un état stable. On pourrait le déterminer en lançant une première simulation, avec pour objectif le maintien debout. Une fois la stabilité atteinte, on relève les couples des moteurs. Intuitivement, on peut voir $tau_"ff"$ comme un manière de s'affranchir de la partie "maintien debout" dans l'expression de la commande, similairement à la mise à zéro ("tarer") d'une balance.
387
387
388
388
On contrôle la prépondérance des deux erreurs dans le calcul de la nouvelle consigne grâce à deux coefficients, $K_p$ et $K_d$.
389
389
···
391
391
== `rt/lowcmd` <receive-lowcmd>
392
392
393
393
394
-
On trouve dans les messages `rt/lowcmd` les champs nécéssaires à au calcul de $tau$ @h1-rt-lowcmd comme décrit précédemment:
394
+
On trouve dans les messages `rt/lowcmd` les champs nécessaires à au calcul de $tau$ @h1-rt-lowcmd comme décrit précédemment:
395
395
396
396
#let greyedout = content => text(fill: luma(120), emph(content))
397
397
#let undocumented = greyedout[Non documenté]
···
860
860
861
861
== Vérification sur des politiques réelles
862
862
863
-
Après avoir testé le bridge sur les politiques d'examples fournies par Unitree, il a été testé sur une politique en cours de développement au sein de l'équipe de robotique du LAAS, Gepetto.
863
+
Après avoir testé le bridge sur les politiques d'exemples fournies par Unitree, il a été testé sur une politique en cours de développement au sein de l'équipe de robotique du LAAS, Gepetto.
864
864
865
865
L'analyse de la vidéo (cf @video) montre que le bridge fonctionne: le comportement du robot est similaire à celui sur Isaac.
866
866
···
920
920
921
921
#image("./profiler-two-ticks.png")
922
922
923
-
Quelques mesures ont été tentées pour réduire le temps nécéssaire à l'envoi d'un message DDS:
923
+
Quelques mesures ont été tentées pour réduire le temps nécessaire à l'envoi d'un message DDS:
924
924
925
-
/ Restreindre DDS à `localhost`: Il est possible que DDS envoie les messages en mode "broadcast", c'est-à-dire à tout addresse IP accessible dans un certain intervalle. En restreignant à `localhost`, on s'assure que le message n'a pas à être copié plusieurs fois.
925
+
/ Restreindre DDS à `localhost`: Il est possible que DDS envoie les messages en mode "broadcast", c'est-à-dire à tout adresse IP accessible dans un certain intervalle. En restreignant à `localhost`, on s'assure que le message n'a pas à être copié plusieurs fois.
926
926
/ Déplacer dans un autre thread: C'est ce qui a motivé la désynchronisation du thread "LowStateWriter" (cf @send-lowstate)
927
-
/ Ajuster la fréquence d'envoi: Une fois `LowStateWriter` déplacé dans un thread indépendant, on peut ajuster la fréquence d'envoi, le thread étant récurrant#footnote[Créé avec `CreateRecurrentThreadEx`]
927
+
/ Ajuster la fréquence d'envoi: Une fois `LowStateWriter` déplacé dans un thread indépendant, on peut ajuster la fréquence d'envoi, le thread étant récurrent#footnote[Créé avec `CreateRecurrentThreadEx`]
928
928
929
929
Ainsi que d'autres optimisations, qui ne sont pas en rapport avec cette phase d'un cycle:
930
930
···
932
932
/ Utilisation d'une implémentation de CRC32 plus rapide: tentative avec _CRC++_ @crcpp non achevée, à cause d'un _stack smashing_ pendant l'exécution
933
933
934
934
935
-
Après optimisations, on arrive à atteindre un RTF aux alentours des 30%. Des recherches supplémentaires sont nécéssaires pour atteindre un RTF raisonnable.
935
+
Après optimisations, on arrive à atteindre un RTF aux alentours des 30%. Des recherches supplémentaires sont nécessaires pour atteindre un RTF raisonnable.
936
936
937
937
== Enregistrement automatique de vidéos <video>
938
938
···
984
984
985
985
=== Une image de base avec Docker
986
986
987
-
L'environnement d'exécution des workflows ne comporte pas d'installation de Gazebo. Étant donné le temps de compilation élevé, on peut "factoriser" cette étape dans une _image de base_, de laquelle on démarre pour chaque exécution du workflow, dans laquelle tout les programmes nécéssaires sont déjà installés.
987
+
L'environnement d'exécution des workflows ne comporte pas d'installation de Gazebo. Étant donné le temps de compilation élevé, on peut "factoriser" cette étape dans une _image de base_, de laquelle on démarre pour chaque exécution du workflow, dans laquelle tous les programmes nécessaires sont déjà installés.
988
988
989
-
Pour cela, on part d'une image Ubuntu, dans lequelle on installe le nécéssaire: Just (pour lancer des commandes, un sorte de Makefile mais plus moderne @just), FFMpeg (pour l'encodage H.264 servant à la création du fichier vidéo), XVFB (pour émuler un serveur X, cf @simulate-x), Python (pour lancer la politique RL), Gazebo et gz-unitree.
989
+
Pour cela, on part d'une image Ubuntu, dans lequelle on installe le nécessaire: Just (pour lancer des commandes, un sorte de Makefile mais plus moderne @just), FFMpeg (pour l'encodage H.264 servant à la création du fichier vidéo), XVFB (pour émuler un serveur X, cf @simulate-x), Python (pour lancer la politique RL), Gazebo et gz-unitree.
990
990
991
991
```dockerfile
992
992
FROM ubuntu:24.04
···
1052
1052
[
1053
1053
1054
1054
==== Un environnement de développement contraignant
1055
-
Développer et débugger une définition de workflow peut s'avérer complexe et particulièrement chronophage: n'ayant pas d'accès interactif au serveur exécutant celui-ci, il faut envoyer ses changements au dépôt git, attendre que le workflow s'exécute entièrement, et regarde si quelque chose s'est mal passé.
1055
+
Développer et débugger une définition de workflow peut s'avérer complexe et particulièrement chronophage: n'ayant pas d'accès interactif au serveur exécutant celui-ci, il faut envoyer ses changements au dépôt git, attendre que le workflow s'exécute entièrement, et regarder si quelque chose s'est mal passé.
1056
1056
1057
1057
Par exemple, si jamais des fichiers sont manquants, ou ne sont pas au chemin attendu, il faut modifier le workflow pour y rajouter des instruction listant le contenu d'un répertoire (en utilisant `ls` ou `tree`, par exemple), lancer le workflow à nouveau et regarder les logs.
1058
1058
+4
-1
rapport/main.typ
+4
-1
rapport/main.typ
+7
-8
rapport/nix.typ
+7
-8
rapport/nix.typ
···
2
2
3
3
=== État dans le domaine de la programmation
4
4
5
-
La différence entre une fonction au sens mathématique et une fonction au sens programmatique consiste en le fait que, par des raisons de practicité, on permet aux `function`s des langages de programmation d'avoir des _effets de bords_. Ces effets affectent, modifient ou font dépendre la fonction d'un environnement global qui n'est pas explicitement déclaré comme une entrée (argument) de la fonction en question @purefunctions.
5
+
La différence entre une fonction au sens mathématique et une fonction au sens programmatique consiste dans le fait que, pour des raisons de practicité, on permet aux `function`s des langages de programmation d'avoir des _effets de bords_. Ces effets affectent, modifient ou font dépendre la fonction d'un environnement global qui n'est pas explicitement déclaré comme une entrée (argument) de la fonction en question @purefunctions.
6
6
7
-
Cette liberté permet, par exemple, d'avoir accès à la date et à l'heure courante, interagir avec un système de fichier d'un ordinateur, générer une surface pseudo aléatoire par bruit de Perlin, etc.
7
+
Cette liberté permet, par exemple, d'avoir accès à la date et à l'heure courante, d'interagir avec un système de fichier d'un ordinateur, de générer une surface pseudo aléatoire par bruit de Perlin, etc.
8
8
9
-
Mais, en contrepartie, on perd une équation qui est fondamentale en mathématiques:
9
+
Mais, en contrepartie, on perd une équation qui est fondamentale en mathématique:
10
10
11
11
$
12
12
forall E, F, forall f: E->F, forall (e_1, e_2) in E^2, e_1 = e_2 => f(e_1) = f(e_2)
···
29
29
30
30
En dehors du besoin de vérifiabilité du monde de la recherche, la reproductibilité est une qualité recherchée en programmation @reproducibility.
31
31
32
-
Il existe donc des langages de programmation dits _fonctionnels_, qui, de manière plus ou moins stricte, limitent les effets de bords. Certains langages font également la distinction entre une fonction _pure_ (sans effets de bord) et une fonction classique @fortran-pure. Certaines fonctions, plutôt appelées _procédures_, sont uniquement composées d'effet de bord et ne renvoie pas de valeur @ibm-function-procedure-routine.
32
+
Il existe donc des langages de programmation dits _fonctionnels_, qui, de manière plus ou moins stricte, limitent les effets de bords. Certains langages font également la distinction entre une fonction _pure_ (sans effets de bord) et une fonction classique @fortran-pure. Certaines fonctions, plutôt appelées _procédures_, sont uniquement composées d'effet de bord et ne renvoient pas de valeur @ibm-function-procedure-routine.
33
33
34
34
35
35
=== État dans le domaine de la robotique
36
36
37
-
En robotique, pour donner des ordres au matériel, on intéragit beaucoup avec le monde extérieur (ordres et lecture d'état de servo-moteurs, flux vidéo d'une caméra, etc), souvent dans un langage plutôt bas-niveau, pour des questions de performance et de proximité abstractionnelle au matériel.
37
+
En robotique, pour donner des ordres au matériel, on interagit beaucoup avec le monde extérieur (ordres et lecture d'état de servo-moteurs, flux vidéo d'une caméra, etc.), souvent dans un langage plutôt bas-niveau, pour des questions de performance et de proximité abstractionnelle au matériel.
38
38
39
39
De fait, les langages employés sont communément C, C++ ou Python#footnote[Il arrive assez communément d'utiliser Python, un langage haut-niveau, mais c'est dans ce cas à but de prototypage. Le code contrôlant les moteurs est écrit dans un langage bas niveau, mais appelé par Python via FFI.] @programming-languages-robotics, des langages bien plus impératifs que fonctionnels @imperative-languages.
40
40
···
148
148
149
149
=== Un ecosystème de dépendances
150
150
151
-
Afin de conserver la reproductibilité même lorsque l'on dépend de libraries tierces, ces dépendances doivent également avoir une compilation reproductible: on déclare donc des dépendances à des paquets Nix, disponibles sur un registre centralisé, _Nixpkgs_ @nixpkgs.
151
+
Afin de conserver la reproductibilité même lorsque l'on dépend de bibliothèques tierces, ces dépendances doivent également avoir une compilation reproductible: on déclare donc des dépendances à des paquets Nix, disponibles sur un registre centralisé, _Nixpkgs_ @nixpkgs.
152
152
153
153
Ainsi, écrire un paquet Nix pour son logiciel demande parfois d'écrire des paquets Nix pour les dépendances de notre projet, si celles-ci n'existent pas encore, et cela récursivement. On peut ensuite soumettre ces autres paquets à Nixpkgs @nixpkgs-contributing afin que d'autres puissent en dépendre sans les réécrire.
154
154
···
234
234
- Un outil de génération de paquets Nix à partir de paquets ROS ou Gazebo, développé par Guilhem Saurel au sein de l'équipe Gepetto, _gazebros2nix_ @gazebros2nix
235
235
236
236
Au début du développement de _gz-unitree_, des essais d'utilisation des paquets Nix pour le développement et la compilation ont été réalisés, mais des erreurs subsistaient, en particulier avec Gazebo.
237
-
Des efforts supplémentaires sont nécéssaires pour empaqueter _gz-unitree_.
238
-
237
+
Des efforts supplémentaires sont nécessaires pour empaqueter _gz-unitree_.
+7
-7
rapport/sdk2-study.typ
+7
-7
rapport/sdk2-study.typ
···
57
57
58
58
CycloneDDS est capable d'un débit d'environ #qty("1", "GB/s"), pour des messages d'environ #qty("1", "kB") chacun @dds-benchmark. On remarque, en pratique, des tailles de message entre #qty("0.9", "kB") et #qty("1.3", "kB") dans le cas des échanges commandes/état avec le robot.
59
59
60
-
Et enfin, les topics peuvent être isolés d'autres topics via des _domain_#[s], identifiés par un numéro. Deux topics portant le même nom reste isolés si ils sont sur deux domaines différents.
60
+
Et enfin, les topics peuvent être isolés d'autres topics via des _domain_#[s], identifiés par un numéro. Deux topics portant le même nom reste isolés s'ils sont sur deux domaines différents.
61
61
62
62
63
63
== Une base de code partiellement open-source
···
90
90
```,
91
91
)
92
92
93
-
Compiler le SDK nécéssite l'existance de ces fichiers binaires:
93
+
Compiler le SDK nécessite l'existance de ces fichiers binaires:
94
94
95
95
#import "@preview/zebraw:0.6.0"
96
96
#let zebraw = (..args) => zebraw.zebraw(
···
119
119
120
120
Ici est défini, via `set_target_properties(... IMPORTED_LOCATION)`, le chemin d'une bibliothèque à lier avec la bibliothèque finale @cmake-imported-location. Ici, c'est un des fichiers pré-compilés que l'on lie.
121
121
122
-
On confirme cette nécéssite en lançant `mkdir build && cd build && cmake ..` après avoir supprimé le répertoire `lib/` :
122
+
On confirme cette nécessite en lançant `mkdir build && cd build && cmake ..` après avoir supprimé le répertoire `lib/` :
123
123
124
124
#{
125
125
show regex(".*CMake Error.*"): set text(fill: red)
···
173
173
...
174
174
```
175
175
176
-
Ces particularités laissent planner quelques doutes sur la nature open-source du code: ces binaires requis sont-ils seulement présent pour améliorer l'expérience développeur en accélererant la compilation, ou "cachent"-ils du code non public?
176
+
Ces particularités laissent planer quelques doutes sur la nature open-source du code: ces binaires requis sont-ils seulement présent pour améliorer l'expérience développeur en accélererant la compilation, ou "cachent"-ils du code non public?
177
177
178
178
Ces constats ont motivé une première tentative de décompilation de ces `libunitree_sdk2.a` pour comprendre le fonctionnement du SDK, via _Ghidra_ @ghidra.
179
179
180
-
Cependant, la découverte de l'existance d'un bridge officiel SDK $arrows.lr$ Mujoco @unitree_mujoco a rendu l'exploration de cette piste non nécéssaire.
180
+
Cependant, la découverte de l'existance d'un bridge officiel SDK $arrows.lr$ Mujoco @unitree_mujoco a rendu l'exploration de cette piste non nécessaire.
181
181
182
182
== Un autre bridge existant: `unitree_mujoco`
183
183
···
200
200
}
201
201
}))
202
202
203
-
Un bridge se substitue au robot physique, interceptant les ordres du SDK et les traduisants en des appels de fonctions provenant de l'API du simulateur, et symmétriquement pour les envois d'états au SDK. On peut apparenter le fonctionnement d'un bridge à celui d'une attaque informatique de type "Man in the Middle" (MitM).
203
+
Un bridge se substitue au robot physique, interceptant les ordres du SDK et les traduisant en des appels de fonctions provenant de l'API du simulateur, et symétriquement pour les envois d'états au SDK. On peut apparenter le fonctionnement d'un bridge à celui d'une attaque informatique de type "Man in the Middle" (MitM).
204
204
205
205
206
206
#figure(caption: [Fonctionnement via _unitree\_mujoco_ du SDK], diagram({
···
264
264
)[*API de Gazebo*])
265
265
}))
266
266
267
-
Le bridge de Mujoco fonctionne en interceptant les messages sur le canal `rt/lowcmd` et en en envoyant dans le canal `rt/lowstate`, qui correspondent respectivement aux commandes envoyées au robot et à l'état (angles des joints, moteurs, valeurs des capteurs, etc) reçu depuis le robot.
267
+
Le bridge de Mujoco fonctionne en interceptant les messages sur le canal `rt/lowcmd` et en envoyant dans le canal `rt/lowstate`, qui correspondent respectivement aux commandes envoyées au robot et à l'état (angles des joints, moteurs, valeurs des capteurs, etc) reçu depuis le robot.
268
268
269
269
Le `low` indique que ce sont des messages bas-niveau. Par exemple, `rt/lowcmd` correspond directement à des ordres en valeurs de couple pour les moteurs, au lieu d'envoyer des ordres plus évolués, tels que "se déplacer de $x$ mètres en avant" @h1-motion-services
270
270
+2
-2
rapport/thanks.typ
+2
-2
rapport/thanks.typ
···
1
1
Je tiens à remercier Olivier Stasse et Guilhem Saurel, qui m'ont suivie pendant toute la durée du stage et répondu à mes questionnements, sans qui je n'aurais pu mener ces recherches. Merci aussi à Côme Perrot, grâce à qui j'ai pu éclaircir certaines zones d'ombre sur ma compréhension de la théorie du reinforcement learning appliquée à la robotique.
2
2
3
-
Merci à Sylvie Chambon, Géraldine Morin et Ronan Guivarch, professeurs à l'ENSEEIHT, qui m'ont soutenue à travers des périodes parfois difficiles.
3
+
Merci à Sylvie Chambon, Géraldine Morin et Ronan Guivarch, professeurs à l'ENSEEIHT, qui m'ont soutenue pendant des périodes parfois difficiles.
4
4
5
-
Merci aussi à Laurenz Mädje et Marin Haug pour avoir créé Typst, une alternative moderne à LaTeX qui a rendu l'écriture de ce rapport bien plus agréable. Merci à Joseph Wilson (paquet Typst "Fletcher") et Johannes Wolf (paquet Typst "CeTZ"), qui ont rendu la création de diagrammes très ergonomique.
5
+
Merci aussi à Laurenz Mädje et Marin Haug pour avoir créé Typst, une alternative moderne à LaTeX qui a rendu l'écriture de ce rapport bien plus agréable. Merci à Joseph Wilson (paquet Typst "Fletcher") et Johannes Wolf (paquet Typst "CeTZ"), qui ont permis la création de diagrammes très ergonomique.
+2
-4
rapport/utils.typ
+2
-4
rapport/utils.typ
···
1
1
#let comment = content => text(fill: gray)[(Note: #content)]
2
2
#let todo = content => text(fill: red)[(TODO: #content)]
3
-
#let refneeded = text(fill: luma(100), [[Réf. nécéssaire]])
3
+
#let refneeded = text(fill: luma(100), [[Réf. nécessaire]])
4
4
#let dontbreak = content => block(breakable: false, content)
5
5
6
6
// https://github.com/typst/typst/issues/3147#issuecomment-2457554155
···
98
98
lines_from_start
99
99
.slice(
100
100
0,
101
-
lines_from_start.position(predicate(ends))
102
-
+ if keep_delimiting { 1 } else { 0 },
101
+
lines_from_start.position(predicate(ends)) + if keep_delimiting { 1 } else { 0 },
103
102
)
104
103
.join("\n")
105
104
}
···
179
178
raw(lang: lang, dedent(transform(contents)))
180
179
}
181
180
}
182
-