this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Format code

+873 -629
+1
log/august.typ
··· 1 +
+1 -1
log/july.typ
··· 5 5 == 7-11 Juillet 6 6 7 7 - Capteur IMU rajouté 8 - - Ajout du tick (temps) de simulation 8 + - Ajout du tick (temps) de simulation 9 9 - Essais d'utilisation de gz-unitree avec les politiques RL#footnote[Reinforcement Learning] de Gepetto 10 10 11 11 == 14-18 Juillet
+3 -3
log/may.typ
··· 1 1 == 19-23 Mai 2 2 3 - - Mise en place de l'environnement de développement 3 + - Mise en place de l'environnement de développement 4 4 - Documentation sur Nix le langage @nix-language 5 - - Découverte de la description d'une dérivation @nix-derivation et d'un flake 5 + - Découverte de la description d'une dérivation @nix-derivation et d'un flake 6 6 - Découverte de l'infrastructure autour de nixpkgs (github, la CI, Hydra @hydra...) 7 7 - Packaging en flake et CI basique (`nix build`) de `open-dynamic-robot-initiative/{interface_controls,master-board}` @odri-controls, @odri-masterboard 8 8 - Début du travail de packaging de `open-dynamic-robot-initiative/robot_properties_solo` 9 9 - Migration de `robot_properties_solo` vers uv @uv 10 10 - Début du packaging de `xacro` sur NixOS/nixpkgs @nixpkgs-xacro 11 11 - Création d'un JSON Schema @json-schema pour des fichiers de configuration de `robot_properties_solo` et mise en place d'une CI pour les valider @odri-properties-solo 12 - - Recherche autour d'une potentielle validation au runtime en C++ des fichiers de config par le JSON Schema 12 + - Recherche autour d'une potentielle validation au runtime en C++ des fichiers de config par le JSON Schema 13 13 - Découverte des overlays Nix 14 14 15 15 == 26-28 Mai
+1
log/november.typ
··· 1 +
+1
log/september.typ
··· 1 +
+35 -31
logs.typ
··· 1 - #import "template.typ": arkheion, arkheion-appendices 2 - #show: arkheion.with( 3 - title: "Stage au LAAS", 4 - authors: ( 5 - (name: "Gwenn Le Bihan", email: "gwenn.lebihan@etu.inp-n7.fr", affiliation: "ENSEEIHT"), 6 - ), 7 - date: datetime.today(), 8 - logo: "enseeiht.jpeg", 9 - abstract: [ 10 - Ce stage porte sur l'intégration de Nix et NixOS dans les processus de développement et de déploiement logiciel dans le domaine robotique au sein du LAAS. Nix, le _package manager_, et NixOS, l'OS, sont des technologies permettant une reproductibilité, une qualité importante dans le monde de la recherche. 11 - 12 - J'ai été aussi amenée à travailler sur la création d'un _plugin_ pour Gazebo, un logiciel de simulation robotique, pour l'utiliser avec le _SDK_ d'un robot de Unitree. 13 - ], 14 - ) 15 - 16 - #outline( 17 - title: [Table des matières], 18 - ) 19 - 20 - #pagebreak() 21 - 22 - 23 - #include "biblio.typ" 24 - 25 - = Journal de bord 26 - 27 - #for month in ("may", "june", "july", "august", "september", "november") { 28 - include("log/" + month + ".typ") 29 - } 30 - 31 - #bibliography("bib.yaml") 1 + #import "template.typ": arkheion, arkheion-appendices 2 + #show: arkheion.with( 3 + title: "Stage au LAAS", 4 + authors: ( 5 + ( 6 + name: "Gwenn Le Bihan", 7 + email: "gwenn.lebihan@etu.inp-n7.fr", 8 + affiliation: "ENSEEIHT", 9 + ), 10 + ), 11 + date: datetime.today(), 12 + logo: "enseeiht.jpeg", 13 + abstract: [ 14 + Ce stage porte sur l'intégration de Nix et NixOS dans les processus de développement et de déploiement logiciel dans le domaine robotique au sein du LAAS. Nix, le _package manager_, et NixOS, l'OS, sont des technologies permettant une reproductibilité, une qualité importante dans le monde de la recherche. 15 + 16 + J'ai été aussi amenée à travailler sur la création d'un _plugin_ pour Gazebo, un logiciel de simulation robotique, pour l'utiliser avec le _SDK_ d'un robot de Unitree. 17 + ], 18 + ) 19 + 20 + #outline( 21 + title: [Table des matières], 22 + ) 23 + 24 + #pagebreak() 25 + 26 + 27 + #include "biblio.typ" 28 + 29 + = Journal de bord 30 + 31 + #for month in ("may", "june", "july", "august", "september", "november") { 32 + include ("log/" + month + ".typ") 33 + } 34 + 35 + #bibliography("bib.yaml")
+197 -167
rapport/context.typ
··· 1 - #import "utils.typ": todo, comment, refneeded 2 - #import "@preview/fletcher:0.5.8": node, edge 1 + #import "utils.typ": comment, refneeded, todo 2 + #import "@preview/fletcher:0.5.8": edge, node 3 3 #import "@preview/fletcher:0.5.8" 4 4 #import "@preview/diagraph:0.3.6" 5 5 6 6 #show figure: set block(spacing: 4em) 7 - #let diagram = (caption: none, ..args) => figure(caption: caption, fletcher.diagram(..args)) 7 + #let diagram = (caption: none, ..args) => figure( 8 + caption: caption, 9 + fletcher.diagram(..args), 10 + ) 8 11 #let dontbreak = content => block(breakable: false, content) 9 12 10 13 #show math.equation.where(block: true): set block(spacing: 2em) ··· 12 15 //#let prod = $op(Pi, limits: #true)$ 13 16 #let card = $op("card")$ 14 17 #let indicatrix = contents => $thin op(bb(1), limits: #true)_(#contents) thin$ 15 - #let argmax = $op("arg" #h(1em/12) "max", limits: #true)$ 18 + #let argmax = $op("arg" #h(1em / 12) "max", limits: #true)$ 16 19 #let exp = $op(bb(E), limits: #true)$ 17 - #let function = (name, input_domain, output_domain, args, body) => $#name : thick thick cases(delim: #none, #input_domain &-> #output_domain, #args &|-> #body)$ 20 + #let function = (name, input_domain, output_domain, args, body) => { 21 + $#name : thick thick cases(delim: #none, #input_domain &-> #output_domain, #args &|-> #body)$ 22 + } 18 23 19 24 20 25 == Bases théoriques du _Reinforcement Learning_ ··· 56 61 57 62 #let exhaustive_memory_table = (caption, filled: false) => { 58 63 let maybe = content => if filled { content } else { [] } 59 - let costs = (plus_one, minus_one) => [ $L(x+1,) = #plus_one quad L(x-1,) = #minus_one$ ] 64 + let costs = ( 65 + plus_one, 66 + minus_one, 67 + ) => [ $L(x+1,) = #plus_one quad L(x-1,) = #minus_one$ ] 60 68 pad(x: 7%, y: 10%, figure( 61 69 table( 62 70 columns: (2fr, 1.9fr, 3fr), 63 71 align: (left, center, left), 64 72 inset: 8pt, 65 - [*État actuel* \ $(x, "retour")$], [*Meilleure action* \ +1 ou -1], [*Coûts associés* \ #maybe[avec $L = (x, "retour") |-> |x-2|$]], 73 + [*État actuel* \ $(x, "retour")$], 74 + [*Meilleure action* \ +1 ou -1], 75 + [*Coûts associés* \ #maybe[avec $L = (x, "retour") |-> |x-2|$]], 76 + 66 77 [ $(0, "C'est plus")$ ], maybe[ +1 ], maybe(costs(2, 2)), 67 78 [ $(1, "C'est plus")$ ], maybe[ +1 ], maybe(costs(1, 2)), 68 79 [ $(3, "C'est moins")$ ], maybe[ -1 ], maybe(costs(2, 3)), 69 80 [ $(4, "C'est moins")$ ], maybe[ -1 ], maybe(costs(3, 4)), 70 - [ $(5, "C'est moins")$ ], maybe[ -1 ], maybe(costs(4, 5)) 71 - ), 72 - caption: caption 81 + [ $(5, "C'est moins")$ ], maybe[ -1 ], maybe(costs(4, 5)), 82 + ), 83 + caption: caption, 73 84 )) 74 85 } 75 86 76 - #exhaustive_memory_table(filled: false)[ Exemple d'agent à mémoire exhaustive pour un "C'est plus ou c'est moins" dans ${ 0, 1, 2 }$, avec pour solution 2 ] 87 + #exhaustive_memory_table( 88 + filled: false, 89 + )[ Exemple d'agent à mémoire exhaustive pour un "C'est plus ou c'est moins" dans ${ 0, 1, 2 }$, avec pour solution 2 ] 77 90 78 - L'entraînement consiste donc ici en l'exploration de l'entièreté des états possibles de l'environnement, et, pour chaque état, le calcul du coût associé à chaque action possible. 91 + L'entraînement consiste donc ici en l'exploration de l'entièreté des états possibles de l'environnement, et, pour chaque état, le calcul du coût associé à chaque action possible. 79 92 80 93 Il faut définir la fonction de coût, souvent appelée $L$ pour _loss_: 81 94 82 95 $ 83 - L: E -> S 96 + L: E -> S 84 97 $ 85 98 86 99 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 87 100 88 - On remplit la colonne "Action à effectuer" avec l'action au coût le plus bas: 101 + On remplit la colonne "Action à effectuer" avec l'action au coût le plus bas: 89 102 90 - #exhaustive_memory_table(filled: true)[ Entraînement terminé, avec pour fonction coût $L$ la distance à la solution ] 103 + #exhaustive_memory_table( 104 + filled: true, 105 + )[ Entraînement terminé, avec pour fonction coût $L$ la distance à la solution ] 91 106 92 107 Ici, cette approche exhaustive suffit parce que l'ensemble des états possibles de l'environnement, $E$, posssède 6 éléments 93 108 ··· 150 165 Le score associé à un état $s_t$ et une action $a_t$, appelée $Q(s_t, a_t)$ ici pour "quality" @qlearning-etymology, est mis à jour avec cette valeur @maxq: 151 166 152 167 $ 153 - (1 - alpha) underbrace(Q(s_t, a_t), "valeur actuelle") + alpha ( underbrace(R_(t+1), "récompense\npour cette action") + gamma underbrace(max_a Q(S_(t+1), a), "récompense de la meilleure\naction pour l'état suivant") ) 168 + (1 - alpha) underbrace(Q(s_t, a_t), "valeur actuelle") + alpha ( underbrace(R_(t+1), "récompense\npour cette action") + gamma underbrace(max_a Q(S_(t+1), a), "récompense de la meilleure\naction pour l'état suivant") ) 154 169 $ 155 170 156 171 L'expression comporte deux hyperparamètres: ··· 185 200 Pour alléger les notations, on surchargera les fonctions récompenses pour qu'elle puissent prendre en entrée des éléments de $S times A$, en ignorant simplement l'action choisie: 186 201 187 202 $ 188 - forall (s, a) in S times A, forall r in "récompenses", r(s, a) := r(s) 203 + forall (s, a) in S times A, forall r in "récompenses", r(s, a) := r(s) 189 204 $ 190 205 191 206 ··· 197 212 198 213 #diagram( 199 214 node((0, 0), $s_t$), 200 - edge(corner: right, label-pos: 2/8, label-side: left)[choix de l'action], 201 - edge("->", corner: right, label-pos: 3/8, label-side: left)[$cal(P)$], 215 + edge(corner: right, label-pos: 2 / 8, label-side: left)[choix de l'action], 216 + edge("->", corner: right, label-pos: 3 / 8, label-side: left)[$cal(P)$], 202 217 node((1, -1))[$a_t$], 203 - edge("->", corner: right, label-pos: 5/8, label-side: left)[$M$], 204 - edge(corner: right, label-pos: 6/8, label-side: left)[simulation], 218 + edge("->", corner: right, label-pos: 5 / 8, label-side: left)[$M$], 219 + edge(corner: right, label-pos: 6 / 8, label-side: left)[simulation], 205 220 node((2, 0))[$s_(t+1)$], 206 - edge((2, 0), (2, .75), (0, .75), (0, 0), "-->", label-side: left)[itération] 221 + edge((2, 0), (2, .75), (0, .75), (0, 0), "-->", label-side: left)[itération], 207 222 ) 208 223 209 - Quand on "déroule" $cal(P)$ en en partant d'un certain état initial $s_0$, on obtient une suite d'états et d'actions: 224 + Quand on "déroule" $cal(P)$ en en partant d'un certain état initial $s_0$, on obtient une suite d'états et d'actions: 210 225 211 - #diagram($ 212 - s_0 edge(a_0, ->) & s_1 edge(a_1, ->) & s_2 edge(a_2, ->) & dots.c 213 - $) 226 + #diagram( 227 + $ 228 + s_0 edge(a_0, ->) & s_1 edge(a_1, ->) & s_2 edge(a_2, ->) & dots.c 229 + $, 230 + ) 214 231 215 232 216 233 Pour tout pas de temps $t in NN$, on a: 217 234 218 235 $ 219 - cases( 220 - a_t &= cal(P)(s_t), 221 - s_(t+1) &= M(s_t, a_t), 222 - ) 236 + cases( 237 + a_t & = cal(P)(s_t), 238 + s_(t+1) & = M(s_t, a_t), 239 + ) 223 240 $ 224 241 225 242 Un chemin se modélise aisément par une suite d'éléments de $S times A$. Ainsi, on note ··· 229 246 230 247 231 248 $ 232 - cal(C)_p := setbuilder( 233 - (s_t, a_t)_(t in NN) " avec " 234 - cases( 235 - & a_0 &= p(s_0), 236 - forall t in NN quad & a_(t+1) &= p(s_t), 237 - forall t in NN quad & s_(t+1) &= M(s_t, a_t) 238 - ), 239 - s_0 in S 240 - ) 249 + cal(C)_p := setbuilder( 250 + (s_t, a_t)_(t in NN) " avec " 251 + cases( 252 + & a_0 & = p(s_0), 253 + forall t in NN quad & a_(t+1) & = p(s_t), 254 + forall t in NN quad & s_(t+1) & = M(s_t, a_t) 255 + ), 256 + s_0 in S 257 + ) 241 258 $ 242 259 243 260 l'ensemble des chemins possibles avec la politique $p$. C'est tout simplement l'ensemble de tout les "déroulements" de la politique $p$ en partant des états possibles de l'environnement. ··· 246 263 On définit également l'ensemble de _tout_ les chemins d'états possibles, peut importe la politique, $cal(C)$ : 247 264 248 265 $ 249 - cal(C) := 250 - setbuilder( 251 - cases( 252 - & c_0 &= (s_0, a_0), 253 - forall t in NN quad & c_(t+1) &= M(c_t) 254 - ), 255 - (s_0, a) in S times A^NN 256 - ) 266 + cal(C) := 267 + setbuilder( 268 + cases( 269 + & c_0 & = (s_0, a_0), 270 + forall t in NN quad & c_(t+1) & = M(c_t) 271 + ), 272 + (s_0, a) in S times A^NN 273 + ) 257 274 $ 258 275 259 276 On notera que, selon $M$, on peut avoir $cal(C) subset.neq (S times A)^NN$: par exemple, certains états de l'environnement peuvent représenter des "impasses", où il est impossible d'évoluer vers un autre état, peut importe l'action choisie. 260 277 261 278 On note aussi que $cal(C)$ (et donc $cal(C)_p$ aussi) est dénombrable, étant construit à partir de $(S times A)^NN$ et $S$, $A$ et $NN$ étant aussi dénombrables#footnote[ 262 - On a $card cal(C) <= card((S times A)^NN) = card(S times A) ^ (card NN) = (card S card A)^(card NN) <= (aleph_0)^(card NN) = attach(aleph_0, tl: 2) = aleph_0$ 279 + On a $card cal(C) <= card((S times A)^NN) = card(S times A)^(card NN) = (card S card A)^(card NN) <= (aleph_0)^(card NN) = attach(aleph_0, tl: 2) = aleph_0$ 263 280 ] 264 281 265 282 #align(center)[ 266 - _Cette formalisation est utile par la suite, \ pour proprement définir certaines grandeurs._ 283 + _Cette formalisation est utile par la suite, \ pour proprement définir certaines grandeurs._ 267 284 ] 268 285 #comment[pas sûre de cette phrase] 269 286 270 287 ==== Récompense attendue $eta$ 271 288 272 - $eta$ représente la récompense moyenne à laquelle l'on peut s'attendre pour une politique $p$ avec fonction de récompense $r$. 289 + $eta$ représente la récompense moyenne à laquelle l'on peut s'attendre pour une politique $p$ avec fonction de récompense $r$. 273 290 274 291 Elle prend en compte le _discount factor_ $gamma$ : les récompenses des actions deviennent de moins en moins#footnote[En supposant $gamma < 1$, ce qui est souvent le cas #refneeded #todo[Mettre dans la def de $gamma$]] importantes avec le temps. $eta$ est définie ainsi @trpo 275 292 276 293 #let policyexp = policy => $exp_((c_t)_(t in NN) op(~) #policy op(in) cal(S))$ 277 294 278 295 $ 279 - eta(p, r) 280 - underbracket( 281 - sum_((c_t)_(t in NN) in cal(S)) 296 + eta(p, r) 282 297 underbracket( 283 - rho_0(s_0) 284 - product_(t=0)^oo Q_p (c_t), "probabilité du chemin" 298 + sum_((c_t)_(t in NN) in cal(S)) 299 + underbracket( 300 + rho_0(s_0) 301 + product_(t=0)^oo Q_p (c_t), "probabilité du chemin" 302 + ) 303 + quad 304 + underbracket(sum_(t=0)^oo gamma^t r(c_t), "récompense associée"), 305 + "pour tout chemin possible" 285 306 ) 286 - quad 287 - underbracket( 288 - sum_(t=0)^oo gamma^t r(c_t), "récompense associée" 289 - ), 290 - "pour tout chemin possible" 291 - ) 292 307 $ 293 308 294 309 295 310 On peut également exprimer $eta(p, r)$ comme une espérance. Soit $C$ une variable aléatoire de $cal(S)$. On a (cf @proof-eta-esperance) 296 311 297 312 $ 298 - eta(p, r) = exp( sum_(t=0)^oo gamma^t r(C_t) ) 313 + eta(p, r) = exp(sum_(t=0)^oo gamma^t r(C_t)) 299 314 $ 300 315 301 316 ··· 356 371 Pour calculer $A_(p, r)(s, a)$, on regarde l'espérance des récompenses cumulées pour tout chemin commençant par $s$, et on la compare à celle pour tout chemin commençant par $M(s, a)$ 357 372 358 373 $ 359 - A_(p, r)(s, a) := 360 - underbracket( 361 - exp_((s_t, a_t)_(t in NN) op(~) p op(in) cal(S) \ s_0 = s \ s_1 = M(s_0, a)) sum_(t=0)^oo gamma^t r(s_t), 362 - Q(s, a) 363 - ) - underbracket( 364 - exp_((s_t, a_t)_(t in NN) op(~) p op(in) cal(S) \ s_0 = s) sum_(t=0)^oo gamma^t r(s_t), 365 - V(s) 366 - ) 374 + A_(p, r)(s, a) := 375 + underbracket( 376 + exp_((s_t, a_t)_(t in NN) op(~) p op(in) cal(S) \ s_0 = s \ s_1 = M(s_0, a)) sum_(t=0)^oo gamma^t r(s_t), 377 + Q(s, a) 378 + ) - underbracket( 379 + exp_((s_t, a_t)_(t in NN) op(~) p op(in) cal(S) \ s_0 = s) sum_(t=0)^oo gamma^t r(s_t), 380 + V(s) 381 + ) 367 382 $ 368 383 369 384 ··· 384 399 385 400 386 401 $ 387 - eta(p', r) 388 - &= eta(p, r) + policyexp(p') sum_(t=0)^oo gamma^t A_(p, r)(c_t) \ 389 - &#[Qui se simplifie en @trpo] \ 390 - &= eta(p, r) + sum 402 + eta(p', r) & = eta(p, r) + policyexp(p') sum_(t=0)^oo gamma^t A_(p, r)(c_t) \ 403 + & #[Qui se simplifie en @trpo] \ 404 + & = eta(p, r) + sum 391 405 $ 392 406 393 407 ··· 395 409 396 410 Il est théoriquement possible d'utiliser $A$ pour optimiser une politique, en maximisant sa valeur à un état donné: 397 411 398 - #diagram(caption: [Boucle d'entraînement], 412 + #diagram( 413 + caption: [Boucle d'entraînement], 399 414 node((0, 0))[$s_t$], 400 415 edge("-"), 401 416 node(name: <policy>, (0, -1))[$cal(P)$], ··· 406 421 edge(<final>, (0, 0), "-->", label-side: left)[itération], 407 422 // edge("d,d,l,l,l,u,u,u", <policy>, "->", label-pos: 33%, label-side: left, align(center, [$Q_cal(P)(s_(t+1), argmax_(a in A) A_(cal(P), R)(s_(t+1), a)) <- A_(cal(P), R) (dots)$ \ Mise à jour])) 408 423 // edge("d,d,l,l,l,u,u,u", <policy>, "->", label-pos: 37%, label-side: left, align(center)[$argmax_(a in A) A_(cal(P), R)(s_(t+1), a)$ \ mise à jour de $cal(P)$]) 409 - edge("d,l,l,l,u,u", <policy>, "->", label-pos: 33%, label-side: left, align(center)[ 410 - // mise à jour de $cal(P)$ \ 424 + edge("d,l,l,l,u,u", <policy>, "->", label-pos: 33%, label-side: left, align( 425 + center, 426 + )[ 427 + // mise à jour de $cal(P)$ \ 411 428 $Q_cal(P)(s_(t+1), a_(t+1)^*) <- A_(cal(P), R)(s_(t+1), a_(t+1)^*)$ 412 - ]) 429 + ]), 413 430 ) <policy-update-loop> 414 431 415 - Avec 432 + Avec 416 433 417 434 $ 418 - a_(t+1)^* &:= argmax_(a in A) A_(cal(P), R)(s_(t+1), a) \ 419 - 435 + a_(t+1)^* & := argmax_(a in A) A_(cal(P), R)(s_(t+1), a) \ 420 436 $ 421 437 422 438 Mais, en pratique, des erreurs d'approximations peuvent rendre $A_(cal(P), R)(s_(t+1), a_(t+1)^*)$ négatif, ce qui empêche de s'en servir pour définir une valeur de $Q_(cal(P))$ @trpo ··· 425 441 Le _surrogate advantage_ détermine la performance d'une politique par rapport à une autre 426 442 427 443 $ 428 - cL_r (p', p) := exp_((s_t, a_t)_(t in NN) in cal(C)) sum_(t=0)^oo (Q_p (s_t, a_t)) / (Q_p' (s_t, a_t)) A_(p, r)(s_t, a_t) 444 + cL_r (p', p) := exp_((s_t, a_t)_(t in NN) in cal(C)) sum_(t=0)^oo (Q_p (s_t, a_t)) / (Q_p' (s_t, a_t)) A_(p, r)(s_t, a_t) 429 445 $ 430 446 431 447 ··· 441 457 L'idée de la _TRPO_ est de maximiser le _surrogate advantage_ du nouveau $Q$ tout en limitant l'ampleur des modifications apportées à $Q$, ce qui procure une stabilité à l'algorithme, et évite qu'un seul "faux pas" dégrade violemment la performance de la politique. 442 458 443 459 $ 444 - Q' = & cases( 445 - argmax_(q) cL_r (q, Q), 446 - "s.c. distance"(Q', Q) < delta 447 - ) 460 + Q' = & cases( 461 + argmax_(q) cL_r (q, Q), 462 + "s.c. distance"(Q', Q) < delta 463 + ) 448 464 $ 449 465 450 466 Avec $delta$ une limite supérieure de distance entre $Q'$, la nouvelle politique, et $Q$, l'ancienne. ··· 454 470 Il existe plusieurs manières de mesurer l'écart entre deux distributions de probabilité, dont notamment la _divergence de Kullback-Leibler_, aussi appelée entropie relative @kullback-leibler @kullback-leibler2: 455 471 456 472 $ 457 - D_"KL" (P || P') := sum_(x in cal(X)) P(x) log P(x) / (P'(x)) 473 + D_"KL" (P || P') := sum_(x in cal(X)) P(x) log P(x) / (P'(x)) 458 474 $ 459 475 460 476 Avec $cal(X)$ l'espace des échantillons et $P, P'$ deux distributions de probabilité sur celui-ci. Dans notre cas, $cal(X) = S times A$, ··· 464 480 Pour évaluer cette distance, on regarde la plus grande des distances entre des paires de distributions de probabilité de politiques $Q_cal(P)$ et $Q_cal(P)'$ pour $s in S$ fixé @trpo 465 481 466 482 $ 467 - max_(s in S) D_"KL" (Q_cal(P)' (s, dot) || Q_cal(P) (s, dot)) < delta 483 + max_(s in S) D_"KL" (Q_cal(P)' (s, dot) || Q_cal(P) (s, dot)) < delta 468 484 $ 469 485 470 486 ··· 483 499 484 500 485 501 $ 486 - forall s in S, Q(s, 1) = Q(s, 2) 502 + forall s in S, Q(s, 1) = Q(s, 2) 487 503 $ 488 504 489 505 490 506 et 491 507 492 508 $ 493 - Q' := (s, a) |-> cases( 494 - Q(s, a) dot 2 si a = 1 \ 495 - Q(s, a) dot 1/2 si a = 2 \ 496 - Q(s, a) sinon 497 - ) \ 498 - 509 + Q' := (s, a) |-> cases( 510 + Q(s, a) dot 2 si a = 1 \ 511 + Q(s, a) dot 1/2 si a = 2 \ 512 + Q(s, a) sinon 513 + ) \ 499 514 $ 500 515 501 - 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$. 516 + 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$. 502 517 503 518 ==== Région de confiance 504 519 505 520 Cette contrainte définit un ensemble réduit de $cal(P)'$ acceptables comme nouvelle politique, aussi appelé une _trust region_ (région de confiance), d'où la méthode d'optimisation tire son nom @trpo. 506 521 507 - #let ddot = [ #sym.dot #h(-1em/16) #sym.dot ] 522 + #let ddot = [ #sym.dot #h(-1em / 16) #sym.dot ] 508 523 509 - En pratique, l'optimisation sous cette contrainte est trop demandeuse en puissance de calcul, on utilise plutôt l'espérance @trpo 524 + En pratique, l'optimisation sous cette contrainte est trop demandeuse en puissance de calcul, on utilise plutôt l'espérance @trpo 510 525 511 526 $ 512 - overline(D_"KL") := bb(E)_(s in S) D_"KL" (Q(s, dot) || Q'(s, dot)) 527 + overline(D_"KL") := bb(E)_(s in S) D_"KL" (Q(s, dot) || Q'(s, dot)) 513 528 $ 514 529 515 530 ··· 517 532 518 533 === _Proximal Policy Optimization_ 519 534 520 - 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. 535 + 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. 521 536 522 537 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 523 538 524 539 525 540 $ 526 - argmax_(cal(P)') & exp_((s, a) in cal(S)) L(s, a, cal(P), cal(P'), R) \ 527 - "s.c." & top 541 + argmax_(cal(P)') & exp_((s, a) in cal(S)) L(s, a, cal(P), cal(P'), R) \ 542 + "s.c." & top 528 543 $ 529 544 530 545 ==== Avec pénalité _(PPO-Penalty)_ ··· 532 547 _PPO-Penalty_ soustrait une divergence K-L pondérée à l'avantage: 533 548 534 549 $ 535 - L(s, a, cal(P), cal(P'), R) = (Q_cal(P) (s, a)) / (Q_cal(P') (s, a)) A_(cal(P), R) (s, a) - beta D_"KL" (cal(P) || cal(P')) 550 + L(s, a, cal(P), cal(P'), R) = (Q_cal(P) (s, a)) / (Q_cal(P') (s, a)) A_(cal(P), R) (s, a) - beta D_"KL" (cal(P) || cal(P')) 536 551 $ 537 552 538 553 Avec $beta$ ajusté automatiquement pour être dans la même échelle que l'autre terme de la soustraction. ··· 543 558 544 559 545 560 $ 546 - L(s, a, cal(P), cal(P'), R) = min( 547 - &(Q_cal(P)' (s, a)) / (Q_cal(P) (s, a)) A_(cal(P)', R)(s, a), quad \ 548 - &op("clip")( 549 - (Q_cal(P)' (s, a)) / (Q_cal(P) (s, a)), 550 - 1 - epsilon, 551 - 1 + epsilon 552 - ) A_(cal(P)', R)(s, a) 553 - ) 561 + L(s, a, cal(P), cal(P'), R) = min( 562 + & (Q_cal(P)' (s, a)) / (Q_cal(P) (s, a)) A_(cal(P)', R)(s, a), quad \ 563 + &op("clip")( 564 + (Q_cal(P)' (s, a)) / (Q_cal(P) (s, a)), 565 + 1 - epsilon, 566 + 1 + epsilon 567 + ) A_(cal(P)', R)(s, a) 568 + ) 554 569 $ 555 570 556 571 Avec $epsilon in RR_+^*$ un paramètre indiquant à quel point l'on peut s'écarter de la politique précédente, et 557 572 558 573 $ 559 - op("clip") := (x, m, M) |-> cases( 560 - m si x < m, 561 - M si x > M, 562 - x sinon 563 - ) 574 + op("clip") := (x, m, M) |-> cases( 575 + m si x < m, 576 + M si x > M, 577 + x sinon 578 + ) 564 579 $ 565 580 566 581 La complexité de l'expression, et la présence d'un $min$ au lieu de simplement un $op("clip")$ est dûe au fait que l'avantage $A_(cal(P)', R) (s, a)$ peut être négatif. L'expression se simplifie en séparant les cas (cf @proof-ppo-clip-simplify) 567 582 568 - #let named_point = (x, y, shape: "@", color: black, side: right, content) => edge((x, y), shape + "-", (x+0.01, y), label-side: side, stroke: color, text(fill: color, content)) 583 + #let named_point = ( 584 + x, 585 + y, 586 + shape: "@", 587 + color: black, 588 + side: right, 589 + content, 590 + ) => edge( 591 + (x, y), 592 + shape + "-", 593 + (x + 0.01, y), 594 + label-side: side, 595 + stroke: color, 596 + text(fill: color, content), 597 + ) 569 598 570 - #let equation_and_diagram = (eqn, diagrm) => stack(dir: ltr, 599 + #let equation_and_diagram = (eqn, diagrm) => stack( 600 + dir: ltr, 571 601 block(width: 70%, math.equation(numbering: none, block: true, eqn)), 572 - diagrm 602 + diagrm, 573 603 ) 574 604 575 605 #dontbreak[ 576 606 577 - / Si l'avantage est positif: $a$ est un meilleur choix que $cal(P)(s)$. 607 + / Si l'avantage est positif: $a$ est un meilleur choix que $cal(P)(s)$. 578 608 579 - #equation_and_diagram( 580 - $ 581 - L(s, a, cal(P), cal(P)', R) = min( 582 - (Q_cal(P)' (s, a)) / (Q_cal(P) (s, a)), 583 - quad 1 + epsilon 584 - ) A_(cal(P)', R)(s, a) 585 - $, 586 - diagram( 587 - spacing: (2.7em, 2em), 588 - node((-1, 0))[$cal(P)'$], 589 - edge((-1, 0), "->", (3, 0), stroke: luma(150)), 590 - edge((-1, 0), "-|", (1, 0), extrude: (1, -1, 0) ), 591 - named_point(1, 0, shape: "|")[$1+epsilon$], 592 - named_point(0, 0)[$cal(P)$], 593 - named_point(1.5, 0, color: red, side: left)[$times$], 594 - named_point(0.5, 0, color: olive, side: left)[$checkmark$], 609 + #equation_and_diagram( 610 + $ 611 + L(s, a, cal(P), cal(P)', R) = min( 612 + (Q_cal(P)' (s, a)) / (Q_cal(P) (s, a)), 613 + quad 1 + epsilon 614 + ) A_(cal(P)', R)(s, a) 615 + $, 616 + diagram( 617 + spacing: (2.7em, 2em), 618 + node((-1, 0))[$cal(P)'$], 619 + edge((-1, 0), "->", (3, 0), stroke: luma(150)), 620 + edge((-1, 0), "-|", (1, 0), extrude: (1, -1, 0)), 621 + named_point(1, 0, shape: "|")[$1+epsilon$], 622 + named_point(0, 0)[$cal(P)$], 623 + named_point(1.5, 0, color: red, side: left)[$times$], 624 + named_point(0.5, 0, color: olive, side: left)[$checkmark$], 625 + ), 595 626 ) 596 - ) 597 627 598 - / Si l'avantage est négatif: choisir $a$ est pire que garder $cal(P)(s)$. 628 + / Si l'avantage est négatif: choisir $a$ est pire que garder $cal(P)(s)$. 599 629 600 - #equation_and_diagram( 601 - $ 602 - L(s, a, cal(P), cal(P)', R) = max( 603 - 1 - epsilon, quad 604 - (Q_cal(P)' (s, a)) / (Q_cal(P) (s, a)) 605 - ) A_(cal(P)', R)(s, a) 606 - $, 607 - diagram( 608 - spacing: (2.7em, 2em), 609 - node((3, 0))[$cal(P)'$], 610 - edge((-1, 0), "<-", (3, 0), stroke: luma(150)), 611 - edge((1, 0), "|-", (3, 0), extrude: (1, -1, 0) ), 612 - named_point(1, 0, shape: "|")[$1-epsilon$], 613 - named_point(2, 0)[$cal(P)$], 614 - named_point(0, 0, color: red, side: left)[$times$], 615 - named_point(1.5, 0, color: olive, side: left)[$checkmark$], 616 - ), 617 - ) 630 + #equation_and_diagram( 631 + $ 632 + L(s, a, cal(P), cal(P)', R) = max( 633 + 1 - epsilon, quad 634 + (Q_cal(P)' (s, a)) / (Q_cal(P) (s, a)) 635 + ) A_(cal(P)', R)(s, a) 636 + $, 637 + diagram( 638 + spacing: (2.7em, 2em), 639 + node((3, 0))[$cal(P)'$], 640 + edge((-1, 0), "<-", (3, 0), stroke: luma(150)), 641 + edge((1, 0), "|-", (3, 0), extrude: (1, -1, 0)), 642 + named_point(1, 0, shape: "|")[$1-epsilon$], 643 + named_point(2, 0)[$cal(P)$], 644 + named_point(0, 0, color: red, side: left)[$times$], 645 + named_point(1.5, 0, color: olive, side: left)[$checkmark$], 646 + ), 647 + ) 618 648 619 649 ] 620 650 621 651 // L'algorithme de mise à jour est le suivant @ppo-openai: 622 - // 652 + // 623 653 // 1. Mise à jour de la politique: 624 - // 654 + // 625 655 // $ 626 656 // cal(P') = argmax_p 1/T sum_(t=1)^T L(s, a, cal(P), p, R) 627 657 // $ ··· 634 664 635 665 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. 636 666 637 - On est donc dans un cas où il est très utile de 667 + On est donc dans un cas où il est très utile de 638 668 639 669 Un environnement de RL#footnote[Reinforcement Learning] ne se résume pas à son moteur de physique: il faut également charger des modèles 3D, le modèle du robot (qui doit être contrôlable par les actions), et également, pendant les phases de développement, avoir un moteur de rendu graphique, une interface et des outils de développement. 640 670 ··· 657 687 658 688 #todo[Déterminer si je parle de ça, en fonction de cmb de pages il reste après avoir fait le reste, ça fera ptet trop...] 659 689 660 - Il est possible d'éviter la définition manuelle de la fonction coût, ce qui requiert d'instrumentaliser l'environnement avec des capteurs supplémentaires, en fournissant à la place 690 + Il est possible d'éviter la définition manuelle de la fonction coût, ce qui requiert d'instrumentaliser l'environnement avec des capteurs supplémentaires, en fournissant à la place 661 691 662 - === Inventaire des simulateurs en robotique 692 + === Inventaire des simulateurs en robotique 663 693 664 694 ==== Isaac 665 695 ··· 671 701 672 702 Bien que MuJoCo est décrit comme un moteur de simulation physique et non un simulateur, il embarque une commande `simulate` qui le rend fonctionnellement équivalent à un simulateur @mujoco-simulate. 673 703 674 - ==== Gazebo 704 + ==== Gazebo 675 705 676 706 Les intérêts de Gazebo @gazebo sont multiples: 677 707 ··· 686 716 687 717 688 718 689 - === Inventaire des moteurs de simulation physique 719 + === Inventaire des moteurs de simulation physique 690 720 691 721 ==== DART 692 722 693 - DART, pour Dynamic Animation and Robotics Toolkit @dart, 723 + DART, pour Dynamic Animation and Robotics Toolkit @dart, 694 724 695 725 ==== Bullet 696 726 ··· 702 732 703 733 == Le _H1v2_ d'Unitree 704 734 705 - Le _H1v2_ est un modèle de robot humanoïde créé par la société Unitree. 735 + Le _H1v2_ est un modèle de robot humanoïde créé par la société Unitree. 706 736 707 737 Il possède plus de 26 degrés de liberté, dont 708 738 709 - - 6 dans chaque jambe (3 à la hanche, 2 au talon et un au genou), 739 + - 6 dans chaque jambe (3 à la hanche, 2 au talon et un au genou), 710 740 - 7 dans chaque bras (3 à l'épaule, 3 au poignet et un au coude) @h1v2 711 741 712 742 ··· 718 748 719 749 #figure( 720 750 caption: [Arbre des dépendances pour _Gepetto/h1v2-Isaac_], 721 - scale(10%, reflow: true, diagraph.render(read("./isaac-deptree.dot"))) 751 + scale(10%, reflow: true, diagraph.render(read("./isaac-deptree.dot"))), 722 752 ) 723 753 724 754 Bien que toutes ces dépendances puissent être spécifiées à des versions 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 pour des raisons de performance @cpp-python. Des problèmes de reproductibilité peuvent donc subsister à l'installation des dépendances, étant donné la dépendance du processus de compilation à la machine compilant le code.
+329 -194
rapport/gz-unitree.typ
··· 1 1 #import "@preview/zebraw:0.5.5" 2 - #import "@preview/fletcher:0.5.8": diagram, node, edge 2 + #import "@preview/fletcher:0.5.8": diagram, edge, node 3 3 #import "@preview/cetz:0.4.2" 4 - #import "./utils.typ": dontbreak, todo, refneeded 4 + #import "./utils.typ": dontbreak, refneeded, todo 5 5 #show figure: set block(spacing: 2em) 6 - #let zebraw = (..args) => zebraw.zebraw(lang: false, background-color: luma(255).opacify(0%), ..args) 6 + #let zebraw = (..args) => zebraw.zebraw( 7 + lang: false, 8 + background-color: luma(255).opacify(0%), 9 + ..args, 10 + ) 7 11 8 12 // Utile: message marquant le début du dev de gz-unitree, 23 juin 2025 9 13 // https://matrix.to/#/!MmlaUevGqfiZYSHREv:laas.fr/$omjzydhckQuIVkcNBw0LTYVT7Td1C9UeLqbIisJAnFg?via=laas.fr ··· 30 34 gutter: 2em, 31 35 [ 32 36 33 - Pour arriver à ces solutions, du débuggage du traffic RTPS (le protocole sur lequel est construit DDS @dds), _Wireshark_ @wireshark s'est avéré utile. 37 + Pour arriver à ces solutions, du débuggage du traffic RTPS (le protocole sur lequel est construit DDS @dds), _Wireshark_ @wireshark s'est avéré utile. 34 38 35 39 36 40 C'est notamment grâce à ce traçage des paquets que le problème d'ID de domaine a été découvert: notre _subscriber_ DDS était réglé sur le domaine anonyme (ID 0) alors que le SDK d'Unitree communique sur le domaine d'ID 1. 37 41 38 42 C'est aussi Wireshark qui nous a permis de voir quels étaient les types IDL utilisés pour les messages. 39 43 ], 40 - figure(caption: [_Wireshark_ permet de visualiser des méta-données sur les paquets RTPS], 41 - stack( 42 - spacing: 1em, 43 - image("./wireshark-wrong-domain.png"), 44 - image("./wireshark-message-type.png"), 45 - )) 44 + figure( 45 + caption: [_Wireshark_ permet de visualiser des méta-données sur les paquets RTPS], 46 + stack( 47 + spacing: 1em, 48 + image("./wireshark-wrong-domain.png"), 49 + image("./wireshark-message-type.png"), 50 + ), 51 + ), 46 52 )) 47 53 48 54 Voici une trace wireshark d'un échange usuel entre commandes (`rt/lowcmd`) et états (`rt/lowstate`) ··· 52 58 #let overlayed-img = contents => layout(bounds => { 53 59 let size = measure(img, ..bounds) 54 60 img 55 - place(top+left, block(..size, contents)) 61 + place(top + left, block(..size, contents)) 56 62 }) 57 63 58 64 #figure( ··· 61 67 #diagram(spacing: (4.54pt, 2.58pt), { 62 68 node((0, 0))[] 63 69 let annotations-x = 80 64 - let annotate = (y-start, y-end, label) => edge((annotations-x, y-start), "|-|", (annotations-x, y-end), label-fill: white, label-side: left, label) 70 + let annotate = (y-start, y-end, label) => edge( 71 + (annotations-x, y-start), 72 + "|-|", 73 + (annotations-x, y-end), 74 + label-fill: white, 75 + label-side: left, 76 + label, 77 + ) 65 78 66 79 annotate(3, 20)[Attente] 67 80 annotate(20, 60)[Initialisation] 68 81 annotate(60, 100)[Échange `rt/` \ `lowstate` $arrows.lr$ `lowcmd`] 69 82 }) 70 - ] 83 + ], 71 84 ) 72 85 73 86 ··· 77 90 Un _system plugin_ Gazebo consiste en la définition d'une classe héritant de `gz::sim::System`, ainsi que d'autres interfaces permettant notamment d'exécuter notre code avant ou après une mise à jour de l'état du simulateur (avec `gz::sim::ISystem`{`Pre`,`Post`}`Update`) 78 91 79 92 #dontbreak( 80 - 81 - ```cpp 82 - #include <gz/sim/System.hh> 83 - namespace gz_unitree 84 - { 85 - class UnitreePlugin : 86 - public gz::sim::System, 87 - public gz::sim::ISystemPreUpdate 88 - { 89 - public: 90 - UnitreePlugin(); 91 - public: 92 - ~UnitreePlugin() override; 93 - public: 94 - void PreUpdate(const gz::sim::UpdateInfo &_info, 95 - gz::sim::EntityComponentManager &ecm) override; 96 - }; 97 - } 98 - ``` 99 - 93 + ```cpp 94 + #include <gz/sim/System.hh> 95 + namespace gz_unitree 96 + { 97 + class UnitreePlugin : 98 + public gz::sim::System, 99 + public gz::sim::ISystemPreUpdate 100 + { 101 + public: 102 + UnitreePlugin(); 103 + public: 104 + ~UnitreePlugin() override; 105 + public: 106 + void PreUpdate(const gz::sim::UpdateInfo &_info, 107 + gz::sim::EntityComponentManager &ecm) override; 108 + }; 109 + } 110 + ```, 100 111 ) 101 112 102 113 Il faut ensuite implémenter la classe puis appeler une macro ajoutant le plugin à Gazebo ··· 116 127 117 128 #zebraw( 118 129 numbering: false, 119 - highlight-lines: (..range(3, 5)), 130 + highlight-lines: (..range(3, 5),), 120 131 ```xml 121 132 <sdf version='1.11'> 122 133 <world name="default"> ··· 127 138 <link name='pelvis'> 128 139 <inertial> 129 140 ... 130 - ``` 141 + ```, 131 142 ) 132 143 133 144 Avec `filename` le chemin vers le plugin compilé, qui sera cherché dans les répertoires spécifiés par `GZ_SIM_SYSTEM_PLUGIN_PATH` @gz-system-plugin-path @sdf-plugin-filename. ··· 144 155 En plus de cela, il y a bien évidemment la politique de contrôle $cal(P)$, qui interagit via les canaux DDS avec le robot (qu'il soit réel, ou simulé) 145 156 146 157 #let legend = ( 147 - ..descriptions 158 + ..descriptions, 148 159 ) => grid( 149 - columns: (1fr, 3fr), 160 + columns: (1fr, 3fr), 150 161 align: left, 151 162 row-gutter: 0.5em, 152 - ..descriptions.pos().map(((arrow, desc)) => ( 153 - diagram(edge((0, 0), arrow, (0.75, 0))), 154 - desc 155 - )).flatten() 163 + ..descriptions 164 + .pos() 165 + .map(((arrow, desc)) => ( 166 + diagram(edge((0, 0), arrow, (0.75, 0))), 167 + desc, 168 + )) 169 + .flatten() 156 170 ) 157 171 158 172 #let architecture = ( 159 - caption, 160 - group-inset: 12pt, 161 - group-color: luma(80), 173 + caption, 174 + group-inset: 12pt, 175 + group-color: luma(80), 162 176 show-legend: true, 163 - ..edges 164 - ) => figure(caption: caption, 165 - pad( 166 - y: 10pt + group-inset, 177 + ..edges, 178 + ) => figure(caption: caption, pad( 179 + y: 10pt + group-inset, 167 180 diagram( 168 - debug: false, 169 - node-stroke: 0.5pt, 181 + debug: false, 182 + node-stroke: 0.5pt, 170 183 edge-corner-radius: 6pt, 171 - { 172 - 173 - if show-legend { 174 - node((2, 4.5), stroke: none, width: 15em, legend(("--", "Message DDS"), ("@->", "Désynchronisation"))) 175 - } 184 + { 185 + if show-legend { 186 + node((2, 4.5), stroke: none, width: 15em, legend( 187 + ("--", "Message DDS"), 188 + ("@->", "Désynchronisation"), 189 + )) 190 + } 176 191 177 - let group = (nodes, label, alignment: bottom + center, name: none) => node( 178 - name: name, 179 - enclose: nodes, 180 - snap: false, 181 - inset: group-inset, 182 - stroke: group-color.lighten(75%) + 2pt, 183 - align(alignment, move(dy: 2 * group-inset * if alignment.y == bottom { 1 } else { -1 }, text(fill: group-color, label))) 184 - ) 192 + let group = ( 193 + nodes, 194 + label, 195 + alignment: bottom + center, 196 + name: none, 197 + ) => node( 198 + name: name, 199 + enclose: nodes, 200 + snap: false, 201 + inset: group-inset, 202 + stroke: group-color.lighten(75%) + 2pt, 203 + align(alignment, move( 204 + dy: 2 * group-inset * if alignment.y == bottom { 1 } else { -1 }, 205 + text(fill: group-color, label), 206 + )), 207 + ) 185 208 186 - let subtitled = (title, subtitle) => [#title \ #text(size: 0.8em, subtitle)] 209 + let subtitled = (title, subtitle) => [#title \ #text( 210 + size: 0.8em, 211 + subtitle, 212 + )] 187 213 188 - node(name: <configure>, (0, 1), `::Configure`) 189 - node(name: <preupdate>, (0, 2), `::PreUpdate`) 190 - group((<configure>, <preupdate>), `gz::sim::System`, alignment: top + center) 214 + node(name: <configure>, (0, 1), `::Configure`) 215 + node(name: <preupdate>, (0, 2), `::PreUpdate`) 216 + group( 217 + (<configure>, <preupdate>), 218 + `gz::sim::System`, 219 + alignment: top + center, 220 + ) 191 221 192 - node(name: <channelfactory>, enclose: ((1, 0), (2, 0)), inset: 8pt, subtitled(`ChannelFactory`, [domaine 1, interface `lo`])) 193 - node(name: <publisher>, (1, 1), inset: 8pt, subtitled(`ChannelPublisher` , [canal `rt/lowstate`])) 194 - node(name: <subscriber>, (2, 1), inset: 8pt, subtitled(`ChannelSubscriber` , [canal `rt/lowcmd`])) 195 - group(name: <dds>, (<channelfactory>, <publisher>, <subscriber>), alignment: top+center)[SDK d'Unitree] 222 + node( 223 + name: <channelfactory>, 224 + enclose: ((1, 0), (2, 0)), 225 + inset: 8pt, 226 + subtitled(`ChannelFactory`, [domaine 1, interface `lo`]), 227 + ) 228 + node(name: <publisher>, (1, 1), inset: 8pt, subtitled( 229 + `ChannelPublisher`, 230 + [canal `rt/lowstate`], 231 + )) 232 + node(name: <subscriber>, (2, 1), inset: 8pt, subtitled( 233 + `ChannelSubscriber`, 234 + [canal `rt/lowcmd`], 235 + )) 236 + group( 237 + name: <dds>, 238 + (<channelfactory>, <publisher>, <subscriber>), 239 + alignment: top + center, 240 + )[SDK d'Unitree] 196 241 197 242 198 - node(name: <lowstate>, (1, 2), `::LowStateWriter`) 199 - node(name: <lowcmd>, (2, 2), `::CmdHandler`) 200 - node(name: <statebuf>, (1, 3), subtitled("State buffer", `statebuf`)) 201 - node(name: <cmdbuf>, (2, 3), subtitled("Commands buffer", `cmdbuf`)) 202 - group((<lowstate>, <lowcmd>, <statebuf>, <cmdbuf>))[Plugin internals] 243 + node(name: <lowstate>, (1, 2), `::LowStateWriter`) 244 + node(name: <lowcmd>, (2, 2), `::CmdHandler`) 245 + node(name: <statebuf>, (1, 3), subtitled("State buffer", `statebuf`)) 246 + node(name: <cmdbuf>, (2, 3), subtitled("Commands buffer", `cmdbuf`)) 247 + group((<lowstate>, <lowcmd>, <statebuf>, <cmdbuf>))[Plugin internals] 203 248 204 - node(name: <policy>, (0, -1), $cal(P)$) 249 + node(name: <policy>, (0, -1), $cal(P)$) 205 250 206 - for e in edges.pos() { 207 - e 208 - } 209 - } 210 - ))) 251 + for e in edges.pos() { 252 + e 253 + } 254 + }, 255 + ), 256 + )) 211 257 212 258 #architecture([Phase d'initialisation du plugin], show-legend: false, { 213 - edge(<configure>, "u", <channelfactory>, "->", label-side: left, label-pos: 50%)[appelle] 259 + edge( 260 + <configure>, 261 + "u", 262 + <channelfactory>, 263 + "->", 264 + label-side: left, 265 + label-pos: 50%, 266 + )[appelle] 214 267 edge(<channelfactory>, "->", <publisher>)[initialise] 215 268 edge(<channelfactory>, "->", <subscriber>)[initialise] 216 269 edge(<publisher>, "<->", <lowstate>)[`std::bind`] ··· 226 279 227 280 Cette initialisation est faite à l'initialisation du plugin par Gazebo, en la faisant dans la méhode `::Configure` du plugin. 228 281 229 - En pratique, on utilise `std::bind` @cpp-bind pour fixer l'instance d'`UnitreePlugin` et ainsi passer des méthodes de la classe comme des simples fonctions 282 + En pratique, on utilise `std::bind` @cpp-bind pour fixer l'instance d'`UnitreePlugin` et ainsi passer des méthodes de la classe comme des simples fonctions 230 283 231 284 #grid( 232 285 columns: 2, 233 286 gutter: 1em, 234 - figure( 235 - caption: [Création d'un _subscriber_ à `rt/lowcmd` dans `UnitreePlugin::Configure`], 236 - text(size: 0.8em, ```cpp 237 - auto subscriber = ChannelSubscriberPtr<LowCmd_>( 238 - new ChannelSubscriber<LowCmd_>("rt/lowcmd") 239 - ); 287 + figure( 288 + caption: [Création d'un _subscriber_ à `rt/lowcmd` dans `UnitreePlugin::Configure`], 289 + text(size: 0.8em, ```cpp 290 + auto subscriber = ChannelSubscriberPtr<LowCmd_>( 291 + new ChannelSubscriber<LowCmd_>("rt/lowcmd") 292 + ); 240 293 241 - auto handler = std::bind( 242 - &UnitreePlugin::CmdHandler, 243 - this, 244 - std::placeholders::_1 245 - ) 294 + auto handler = std::bind( 295 + &UnitreePlugin::CmdHandler, 296 + this, 297 + std::placeholders::_1 298 + ) 246 299 247 - subscriber->InitChannel(handler, 1); 300 + subscriber->InitChannel(handler, 1); 248 301 249 - . 250 - ```) 251 - ), 302 + . 303 + ```), 304 + ), 252 305 253 - figure( 254 - caption: [Création du _publisher_ pour `rt/lowstate` dans `UnitreePlugin::Configure`], 255 - text(size: 0.8em, ```cpp 256 - auto publisher = ChannelPublisherPtr<LowState_>( 257 - new ChannelPublisher<LowState_>("rt/lowstate") 258 - ); 306 + figure( 307 + caption: [Création du _publisher_ pour `rt/lowstate` dans `UnitreePlugin::Configure`], 308 + text(size: 0.8em, ```cpp 309 + auto publisher = ChannelPublisherPtr<LowState_>( 310 + new ChannelPublisher<LowState_>("rt/lowstate") 311 + ); 259 312 260 - publisher->InitChannel(); 313 + publisher->InitChannel(); 261 314 262 - this->publisher_thread = CreateRecurrentThreadEx( 263 - "low_state_writer", 264 - UT_CPU_ID_NONE, 265 - 500, 266 - &UnitreePlugin::LowStateWriter, 267 - this 268 - ); 269 - ```) 270 - ) 315 + this->publisher_thread = CreateRecurrentThreadEx( 316 + "low_state_writer", 317 + UT_CPU_ID_NONE, 318 + 500, 319 + &UnitreePlugin::LowStateWriter, 320 + this 321 + ); 322 + ```), 323 + ), 271 324 ) 272 325 273 326 == `rt/lowcmd` ··· 277 330 Pour appliquer une commande à un moteur, on calcule la force effective que le moteur doit appliquer: 278 331 279 332 $ 280 - tau = 281 - underbracket(K_p Delta q, "proportional") + 282 - underbracket(tau_"ff", "integrative") + 333 + tau = 334 + underbracket(K_p Delta q, "proportional") + 335 + underbracket(tau_"ff", "integrative") + 283 336 underbracket(K_d Delta dot(q), "derivative") 284 337 $ 285 338 286 - Avec 339 + Avec 287 340 288 341 / $tau$: pour _torque_, la force à donner au moteur 289 342 / $tau_"ff"$: le $tau$ "feed-forward", #todo[I de PID ou pas?] 290 343 / $Delta q$: écart d'angle de rotation du moteur entre la consigne et l'état actuel 291 344 / $Delta dot(q)$: vitesse de changement de la consigne#footnote[ 292 345 293 - #let ddt = derivee => $ ( op("d") #derivee ) / ( op("d") t ) $ 346 + #let ddt = derivee => $ ( op("d") #derivee ) / ( op("d") t ) $ 294 347 295 - On a bien $ddt(Delta q) = Delta dot(q)$ par linéarité de la dérivation temporelle: 348 + On a bien $ddt(Delta q) = Delta dot(q)$ par linéarité de la dérivation temporelle: 296 349 297 - $ 298 - ddt(Delta q) = ddt(q_"new" - q_"old") = ddt(q_"new") - ddt(q_"old") = Delta ddt(q) = Delta dot(q) 299 - $ 350 + $ ddt(Delta q) = ddt(q_"new" - q_"old") = ddt(q_"new") - ddt(q_"old") = Delta ddt(q) = Delta dot(q) $ 300 351 301 - ] 352 + ] 302 353 / $K_p$: prépondérance de la partie proportionelle 303 354 / $K_p$: prépondérance de la partie dérivée 304 355 ··· 314 365 En pratique, les valeurs actuelles pour le calcul de $Delta q$ et $Delta dot(q)$ proviennent de l'état du moteur, accessible dans `rt/lowstate` avec les champs `q` et `dq` du moteur en question @h1-rt-lowstate 315 366 316 367 ```cpp 317 - // Avec i l'indice du moteur 368 + // Avec i l'indice du moteur 318 369 auto force = cmdbuf->tau_ff.at(i) + // tau_ff 319 370 cmdbuf->kp.at(i) * ( // K_p 320 371 cmdbuf->q_target.at(i) - lowstate.motor_state().at(i).q() // Delta q 321 - ) + 372 + ) + 322 373 cmdbuf->kd.at(i) * ( // K_d 323 374 cmdbuf->dq_target.at(i) - lowstate.motor_state().at(i).dq() // Delta q. 324 375 ); ··· 337 388 338 389 #architecture([Phase de réception des commandes], { 339 390 edge(<policy>, (2, -1), (2, 0), "-->", label-pos: 10%)[(1A) publish] 340 - edge(<policy>, (2, -1), (2, 0), stroke: none, label-pos: 60%, label-side: left)[(1A) subscription] 391 + edge( 392 + <policy>, 393 + (2, -1), 394 + (2, 0), 395 + stroke: none, 396 + label-pos: 60%, 397 + label-side: left, 398 + )[(1A) subscription] 341 399 edge((2, 0), <subscriber>, "->")[(2)] 342 400 edge(<subscriber>, "->", <lowcmd>)[(3)] 343 401 edge(<lowcmd>, "->", <cmdbuf>)[(4)] ··· 353 411 / Si `::PreUpdate` est moins fréquente: Certaines commandes seront simplement ignorées par Gazebo, qui ne vera pas la valeur du buffer avant qu'il change de nouveau. 354 412 355 413 // L'initialisation du subscriber se fait pendant l'initialisation du plugin, c'est à dire dans `UnitreePlugin::Configure`. On relie la réception d'un message à une fonction, qui est ici une méthode, `UnitreePlugin::CmdHandler`. 356 - // 414 + // 357 415 // #dontbreak[ 358 - // 416 + // 359 417 // ```cpp 360 - // ... 361 - // 362 - // void UnitreePlugin::Configure() 363 - // { 418 + // ... 419 + // 420 + // void UnitreePlugin::Configure() 421 + // { 364 422 // ``` 365 - // 423 + // 366 424 // Instanciation d'un canal 367 - // 425 + // 368 426 // ```cpp 369 - // ChannelFactory::Instance()->Init(1, "lo" /* loopback interface */); 427 + // ChannelFactory::Instance()->Init(1, "lo" /* loopback interface */); 370 428 // ``` 371 - // 429 + // 372 430 // Création de $x |-> mono("CmdHandler")(mono("this"), x)$. L'utilitaire `std::bind` permet de passer à `InitChannel` une fonction simple 373 - // 431 + // 374 432 // ```cpp 375 433 // auto handler = std::bind( 376 434 // &UnitreePlugin::CmdHandler, ··· 378 436 // std::placeholders::_1 379 437 // ) 380 438 // ``` 381 - // 439 + // 382 440 // Création du subscriber 383 - // 441 + // 384 442 // ```cpp 385 443 // auto subscriber = ChannelSubscriberPtr<LowCmd_>( 386 444 // new ChannelSubscriber<LowCmd_>("rt/lowcmd") 387 445 // ); 388 - // 389 - // 446 + // 447 + // 390 448 // subscriber->InitChannel(handler, 1); 391 449 // } 392 450 // ``` 393 - // 451 + // 394 452 // Définition du handler 395 - // 453 + // 396 454 // ```cpp 397 - // void UnitreePlugin::CmdHandler(const void *msg) 455 + // void UnitreePlugin::CmdHandler(const void *msg) 398 456 // { 399 457 // LowCmd_ _cmd = *(const LowCmd_ *)msg; 400 - // 458 + // 401 459 // // Remplissage du buffer interne à la classe 402 460 // MotorCommand motor_command_tmp; 403 - // for (size_t i = 0; i < H1_NUM_MOTOR; ++i) 461 + // for (size_t i = 0; i < H1_NUM_MOTOR; ++i) 404 462 // { 405 463 // motor_command_tau_ff[i] = _cmd.motor_cmd()[i].tau(); 406 464 // ... 407 465 // } 408 - // 466 + // 409 467 // this->motor_command_buffer.SetData(motor_command_tmp); 410 468 // } 411 469 // ``` 412 - // 470 + // 413 471 // ] 414 472 415 473 ··· 423 481 424 482 #table( 425 483 // columns: (1.5fr, 0.5fr, 3fr, 2fr), 426 - columns: 4, 484 + columns: 4, 427 485 stroke: none, 428 486 inset: 8pt, 429 487 430 488 "Champ", "Type", "Description", "Où récupérer la valeur", 431 489 table.hline(), 432 490 433 - `version`, $NN^2$, [Tuple représentant la version d'Unitree], [Expérimentalement], 434 - `mode_pr`, ${0, 1}$, [Défini sur 0 par défaut], [0], 491 + `version`, 492 + $NN^2$, 493 + [Tuple représentant la version d'Unitree], 494 + [Expérimentalement], 495 + `mode_pr`, ${0, 1}$, [Défini sur 0 par défaut], [0], 435 496 `mode_machine`, ${4, 6}$, [Défini sur 6 par défaut], [6], 436 - `tick`, $NN quad ("ms")$, [Non documenté, proablement le temps écoulé depuis le début de la simulation], [Messages `gz::msgs::Clock` sur le topic Gazebo `/clock` ], 497 + `tick`, 498 + $NN quad ("ms")$, 499 + [Non documenté, proablement le temps écoulé depuis le début de la simulation], 500 + [Messages `gz::msgs::Clock` sur le topic Gazebo `/clock` ], 437 501 `wireless_remote`, ${0, 1}^(40)$, [Non documenté], [_Laissé vide_], 438 502 `reserve`, $NN^4$, [Non documenté], [_Laissé vide_], 439 - `crc`, $NN$, [Somme de contrôle du message, utilisant _CRC32_. ], [Implémentation de CRC32 par Unitree #footnote[ 440 - Une implémentation ad-hoc existe dans le code source de `unitree_sdk2` et de `unitree_mujoco` #todo[Mettre en annexe ?] #refneeded 441 - ]], 442 - `imu_state…`, "struct.", [Valeurs des capteurs intertiels du robot], [Messages `gz::msgs::IMU` sur le topic Gazebo `/imu`], 443 - ` .quaternion`, $RR^4$, [Posture dans l'espace du robot, dans l'ordre $(w, x, y, z)$], [$w$, $x$, $y$ et $z$ sur `.orientation()`], 444 - ` .rpy`, $RR^3$, [Angle d'Euler du robot, dans l'ordre $(r, p, y)$], `.linear_acceleration()`, 445 - ` .gyroscope`, $RR^3$, todo[], $"atan"_2(2(w x + y z), 1 - 2 (x^2 + y^2) )) \ "asin"(2 (w y - z x)) \ "atan"_2(2(w z + x y), 1 - 2(y^2 + z^2))$, 503 + `crc`, 504 + $NN$, 505 + [Somme de contrôle du message, utilisant _CRC32_. ], 506 + [Implémentation de CRC32 par Unitree #footnote[ 507 + Une implémentation ad-hoc existe dans le code source de `unitree_sdk2` et de `unitree_mujoco` #todo[Mettre en annexe ?] #refneeded 508 + ]], 509 + `imu_state…`, 510 + "struct.", 511 + [Valeurs des capteurs intertiels du robot], 512 + [Messages `gz::msgs::IMU` sur le topic Gazebo `/imu`], 513 + ` .quaternion`, 514 + $RR^4$, 515 + [Posture dans l'espace du robot, dans l'ordre $(w, x, y, z)$], 516 + [$w$, $x$, $y$ et $z$ sur `.orientation()`], 517 + ` .rpy`, 518 + $RR^3$, 519 + [Angle d'Euler du robot, dans l'ordre $(r, p, y)$], 520 + `.linear_acceleration()`, 521 + ` .gyroscope`, 522 + $RR^3$, 523 + todo[], 524 + $"atan"_2(2(w x + y z), 1 - 2 (x^2 + y^2) )) \ "asin"(2 (w y - z x)) \ "atan"_2(2(w z + x y), 1 - 2(y^2 + z^2))$, 446 525 ` .accelerometer`, $RR^3$, todo[], `.angular_velocity()`, 447 - `motor_state…`, [$"struct."^(35)$], [Etat de chaque moteur], `gz::sim::Model(…)→joints`, 526 + `motor_state…`, 527 + [$"struct."^(35)$], 528 + [Etat de chaque moteur], 529 + `gz::sim::Model(…)→joints`, 448 530 ` .mode`, ${0, 1}$, [$0$ pour "Brake" et $1$ pour "FOC" #todo[]], [0], 449 - ` .q`, $RR quad ("rad")$, [Angle en radians de rotation du moteur], `.Position()`, 450 - ` .dq`, $RR quad ("rad" dot "s"^(-1))$, [Angle de rotation du moteur], `.Velocity()`, 451 - ` .ddq`, $RR quad ("rad" dot "s"^(-2))$, [Angle de rotation du moteur], [_Laissé vide_], 452 - ` .tau_est`, $RR quad ("N" dot "m")$, [Estimation de la torque #todo[]], [_Laissé vide_], 531 + ` .q`, 532 + $RR quad ("rad")$, 533 + [Angle en radians de rotation du moteur], 534 + `.Position()`, 535 + ` .dq`, 536 + $RR quad ("rad" dot "s"^(-1))$, 537 + [Angle de rotation du moteur], 538 + `.Velocity()`, 539 + ` .ddq`, 540 + $RR quad ("rad" dot "s"^(-2))$, 541 + [Angle de rotation du moteur], 542 + [_Laissé vide_], 543 + ` .tau_est`, 544 + $RR quad ("N" dot "m")$, 545 + [Estimation de la torque #todo[]], 546 + [_Laissé vide_], 453 547 ) 454 548 455 549 ··· 468 562 edge(<lowstate>, "->", <publisher>)[(2)] 469 563 edge(<publisher>, "->", (1, 0))[(3)] 470 564 edge(<policy>, (1, -1), (1, 0), "<--", label-pos: 20%)[(4) subscription] 471 - edge(<policy>, (1, -1), (1, 0), stroke: none, label-pos: 60%, label-side: left)[(4) publish] 565 + edge( 566 + <policy>, 567 + (1, -1), 568 + (1, 0), 569 + stroke: none, 570 + label-pos: 60%, 571 + label-side: left, 572 + )[(4) publish] 472 573 }) 473 574 474 575 475 - Ici également, `LowStateWriter` s'exécute _en parallèle_ du code de `::PreUpdate`: En effet, la création du `ChannelPublisher` démarre une boucle qui vient éxécuter `LowStateWriter` périodiquement, dans un autre _thread_: on a donc aucune garantie de synchronisation entre les deux. 576 + Ici également, `LowStateWriter` s'exécute _en parallèle_ du code de `::PreUpdate`: En effet, la création du `ChannelPublisher` démarre une boucle qui vient éxécuter `LowStateWriter` périodiquement, dans un autre _thread_: on a donc aucune garantie de synchronisation entre les deux. 476 577 477 578 Ici, il y a en plus non pas deux, mais _trois_ boucles indépendantes qui sont en jeux: 478 579 ··· 484 585 Similairement à la réception de commandes: 485 586 486 587 / Si `::PreUpdate` est plus fréquente: On perdra des états intermédiaires, la résolution temporelle de l'évolution de l'état du robot disponible pour (ou acceptable par#footnote[ 487 - En fonction de si `::LowStateWriter` est plus fréquente que $cal(P)$ (dans ce cas là, c'est ce qui est acceptable par $cal(P)$ qui est limitant) ou inversement (dans ce cas, c'est ce que la boucle du publisher met à disposition de $cal(P)$ qui est limitant) 488 - ]) $cal(P)$ sera moins grande 588 + En fonction de si `::LowStateWriter` est plus fréquente que $cal(P)$ (dans ce cas là, c'est ce qui est acceptable par $cal(P)$ qui est limitant) ou inversement (dans ce cas, c'est ce que la boucle du publisher met à disposition de $cal(P)$ qui est limitant) 589 + ]) $cal(P)$ sera moins grande 489 590 / Si `::PreUpdate` est moins fréquente: $cal(P)$ reçevra plusieurs fois le même état, ce qui sera représentatif du fait que la simulation n'a pas encore avancé. 490 591 491 592 492 593 == Désynchronisations 493 594 494 - Dans un même appel de `::PreUpdate`, on effectue d'abord la mise à jour du _State buffer_, puis on lit dans le _Commands buffer_. 595 + Dans un même appel de `::PreUpdate`, on effectue d'abord la mise à jour du _State buffer_, puis on lit dans le _Commands buffer_. 495 596 496 - Un cycle correspond donc à trois boucles indépendantes, représentées ci-après: 597 + Un cycle correspond donc à trois boucles indépendantes, représentées ci-après: 497 598 498 599 - Celle de la simulation (en bleu), qui doit englober l'entièreté d'un cycle 499 600 - Celle du `ChannelPublisher` (en rouge) 500 601 - Celle de $cal(P)$ (en vert) 501 602 502 - #architecture([Cycle complet. Un cycle commence avec la flèche "update" partant de `::PreUpdate`], { 503 - let colored-edge = (color, label, ..args) => edge(stroke: color, label: text(fill: color, label), ..args) 504 - let sim-edge = (label, ..args) => colored-edge(blue, label, ..args) 505 - let publisher-edge = (label, ..args) => colored-edge(red, label, ..args) 506 - let policy-edge = (label, ..args) => colored-edge(olive.darken(30%), label, ..args) 603 + #architecture( 604 + [Cycle complet. Un cycle commence avec la flèche "update" partant de `::PreUpdate`], 605 + { 606 + let colored-edge = (color, label, ..args) => edge( 607 + stroke: color, 608 + label: text(fill: color, label), 609 + ..args, 610 + ) 611 + let sim-edge = (label, ..args) => colored-edge(blue, label, ..args) 612 + let publisher-edge = (label, ..args) => colored-edge(red, label, ..args) 613 + let policy-edge = (label, ..args) => colored-edge( 614 + olive.darken(30%), 615 + label, 616 + ..args, 617 + ) 507 618 508 - // Simulation loop 509 - sim-edge("read", <preupdate>, "d,d,r,r", <cmdbuf>, "<-@") 510 - sim-edge("update", <preupdate.east>, "d", <statebuf>, "->", label-pos: 70%, label-side: right) 619 + // Simulation loop 620 + sim-edge("read", <preupdate>, "d,d,r,r", <cmdbuf>, "<-@") 621 + sim-edge( 622 + "update", 623 + <preupdate.east>, 624 + "d", 625 + <statebuf>, 626 + "->", 627 + label-pos: 70%, 628 + label-side: right, 629 + ) 511 630 512 - // lowstate publisher loop 513 - publisher-edge("read", <statebuf>, "@->", <lowstate>) 514 - publisher-edge("", <lowstate>, "-", <publisher>) 515 - publisher-edge("", <publisher>, (1, 0), <channelfactory.west>, "->") 631 + // lowstate publisher loop 632 + publisher-edge("read", <statebuf>, "@->", <lowstate>) 633 + publisher-edge("", <lowstate>, "-", <publisher>) 634 + publisher-edge("", <publisher>, (1, 0), <channelfactory.west>, "->") 516 635 517 - // policy loop 518 - // dds part 519 - policy-edge("commands", <policy>, (2.25, -1), (2.25, 0), <channelfactory.east>, "-->", label-pos: 10%) 520 - policy-edge("state", <policy>, (0, 0), <channelfactory.west>, "<--@", label-pos: 80%) 521 - // non-dds part 522 - policy-edge("", <channelfactory.east>, (2, 0), <subscriber>, "->") 523 - policy-edge("", <subscriber>, "-", <lowcmd>) 524 - policy-edge("update", <lowcmd>, "->", <cmdbuf>) 525 - }) 636 + // policy loop 637 + // dds part 638 + policy-edge( 639 + "commands", 640 + <policy>, 641 + (2.25, -1), 642 + (2.25, 0), 643 + <channelfactory.east>, 644 + "-->", 645 + label-pos: 10%, 646 + ) 647 + policy-edge( 648 + "state", 649 + <policy>, 650 + (0, 0), 651 + <channelfactory.west>, 652 + "<--@", 653 + label-pos: 80%, 654 + ) 655 + // non-dds part 656 + policy-edge("", <channelfactory.east>, (2, 0), <subscriber>, "->") 657 + policy-edge("", <subscriber>, "-", <lowcmd>) 658 + policy-edge("update", <lowcmd>, "->", <cmdbuf>) 659 + }, 660 + ) 526 661 527 662 Ces désynchronisations pourraient expliquer les problèmes de performance recontrés (cf @perf) 528 663 ··· 534 669 535 670 == Amélioration des performances <perf> 536 671 537 - Les premiers essais affichent un 672 + Les premiers essais affichent un 538 673 539 674 == Enregistrement de vidéos <video> 540 675
+17 -11
rapport/main.typ
··· 8 8 ) 9 9 10 10 #show terms: it => grid( 11 - columns: 2, row-gutter: 1em, column-gutter: (15pt, 0pt), align: (left, left), 12 - ..it.children.map(item => 13 - (strong(item.term), item.description) 14 - ).flatten() 15 - ) 11 + columns: 2, row-gutter: 1em, column-gutter: (15pt, 0pt), align: (left, left), 12 + ..it.children.map(item => (strong(item.term), item.description)).flatten() 13 + ) 16 14 17 15 18 16 #let imagefigure(path, caption, size: 100%) = figure( ··· 67 65 #show ref: it => { 68 66 let eq = math.equation 69 67 let el = it.element 70 - let appendix_root = if el == none { 0 } else { counter("appendices").at(el.location()).at(0) } 68 + let appendix_root = if el == none { 0 } else { 69 + counter("appendices").at(el.location()).at(0) 70 + } 71 71 if el != none and el.func() == eq { 72 72 // Override equation references. 73 73 numbering( 74 74 el.numbering, 75 - ..counter(eq).at(el.location()) 75 + ..counter(eq).at(el.location()), 76 76 ) 77 77 } else if el != none and appendix_root != 0 { 78 - let letter = numbering(el.numbering, counter("appendices").at(el.location()).at(0)) 79 - let heading_path = numbering("1.1", ..counter(heading).at(el.location()).slice(1)) 78 + let letter = numbering( 79 + el.numbering, 80 + counter("appendices").at(el.location()).at(0), 81 + ) 82 + let heading_path = numbering( 83 + "1.1", 84 + ..counter(heading).at(el.location()).slice(1), 85 + ) 80 86 let path = letter + "." + heading_path 81 87 if appendix_root == 1 { 82 88 [preuve en #path] ··· 117 123 118 124 #pagebreak() 119 125 120 - = Remerciements 126 + = Remerciements 121 127 122 128 #outline() 123 129 ··· 125 131 126 132 #include "context.typ" 127 133 128 - = Packaging reproductible avec Nix 134 + = Packaging reproductible avec Nix 129 135 130 136 131 137 #include "nix.typ"
+4 -3
rapport/nix.typ
··· 1 - #import "utils.typ": todo, comment, refneeded 1 + #import "utils.typ": comment, refneeded, todo 2 2 3 3 == Reproductibilité 4 4 ··· 84 84 ) 85 85 ```, 86 86 87 - [ *Python* (`if` et `else` sont des instructions) ], [ *OCaml* (`if` et `else` forment une expression) ], 87 + [ *Python* (`if` et `else` sont des instructions) ], 88 + [ *OCaml* (`if` et `else` forment une expression) ], 88 89 ) 89 90 90 91 Afin de décrire les dépendances d'un programme, l'environnement de compilation, et les étapes pour le compiler (en somme, afin de définir le $f in "bin"^"src"$), Nix comprend un langage d'expressions @nix-language. Un fichier `.nix` définit une fonction, que Nix sait exécuter pour compiler le code source. ··· 181 182 182 183 Ici encore, cela apporte un gain en terme de reproductibilité: l'état de configuration de l'OS sur lequel est déployé le programme du robot est, lui aussi, rendu reproductible. 183 184 184 - == Packaging Nix pour _gz-unitree_ 185 + == Packaging Nix pour _gz-unitree_ 185 186 186 187 #todo[Faire cette partie]
+129 -109
rapport/proofs.typ
··· 1 - #import "utils.typ": todo, comment, refneeded 1 + #import "utils.typ": comment, refneeded, todo 2 2 #show math.equation.where(block: true): set block(spacing: 2em) 3 3 4 4 == Cas dégénéré de $D_"KL" (Q, Q') = 0$ sans utilisation de $max$ <dkl-zero> ··· 13 13 $ forall s in S, Q'(s, 2) := 1/2 Q(s, 2) $ <dkl-zero-a2> 14 14 $ forall s in S, forall a in A - {1, 2}, Q'(s, a) := Q(s, a) $ <dkl-zero-else> 15 15 16 - #let why = (content, reference) => $underbracket(#content, "d'après " #ref(reference))$ 16 + #let why = (content, reference) => { 17 + $underbracket(#content, "d'après " #ref(reference))$ 18 + } 17 19 #let crossout = (content, reference) => $why(cancel(#content), reference)$ 18 20 19 21 On a 20 22 21 23 $ 22 - 23 - D_"KL" ( Q || Q' ) 24 - &= sum_((s, a) in S times A) Q(s, a) log Q(s, a) / (Q'(s, a)) \ 25 - &"On découpe la somme selon les valeurs de " A ":" \ 26 - &= sum_(s in S) 27 - sum_(a in A - {1, 2}) [ Q(s, a) log Q(s, a) / (Q'(s, a)) ] 28 - + Q(s, 1) log Q(s, 1) / (Q'(s, 1)) 29 - + Q(s, 2) log Q(s, 2) / (Q'(s, 2)) \ 30 - &= sum_(s in S) 31 - crossout(sum_(a in A - {1, 2}) Q(s, a) log Q(s, a) / (Q(s, a)), #<dkl-zero-else>) 32 - + Q(s, 1) log Q(s, 1) / why( 2 Q(s, 1), #<dkl-zero-a1>) 33 - + Q(s, 2) log Q(s, 2) / why( 1/2 Q(s, 2), #<dkl-zero-a2>) \ 34 - &= sum_(s in S) 35 - Q(s, 1) lr([ log Q(s, 1) - log Q(s, 1) - log 2 ], size: #200%) + \ 36 - & quad quad thick thick Q(s, 2) [ log Q(s, 2) - log Q(s, 2) - log 1/2 ] \ 37 - &= sum_(s in S) - Q(s, 1) log 2 + Q(s, 2) log 2 \ 38 - &= sum_(s in S) log 2 thin crossout((Q(s, 2) - Q(s, 1)), #<dkl-zero-qeq>) \ 39 - &= sum_(s in S) 0 = 0 40 - 24 + D_"KL" ( Q || Q' ) 25 + &= sum_((s, a) in S times A) Q(s, a) log Q(s, a) / (Q'(s, a)) \ 26 + &"On découpe la somme selon les valeurs de " A ":" \ 27 + &= sum_(s in S) 28 + sum_(a in A - {1, 2}) [ Q(s, a) log Q(s, a) / (Q'(s, a)) ] 29 + + Q(s, 1) log Q(s, 1) / (Q'(s, 1)) 30 + + Q(s, 2) log Q(s, 2) / (Q'(s, 2)) \ 31 + &= sum_(s in S) 32 + crossout(sum_(a in A - {1, 2}) Q(s, a) log Q(s, a) / (Q(s, a)), #<dkl-zero-else>) 33 + + Q(s, 1) log Q(s, 1) / why(2 Q(s, 1), #<dkl-zero-a1>) 34 + + Q(s, 2) log Q(s, 2) / why(1/2 Q(s, 2), #<dkl-zero-a2>) \ 35 + &= sum_(s in S) 36 + Q(s, 1) lr([ log Q(s, 1) - log Q(s, 1) - log 2 ], size: #200%) + \ 37 + & quad quad thick thick Q(s, 2) [ log Q(s, 2) - log Q(s, 2) - log 1/2 ] \ 38 + &= sum_(s in S) - Q(s, 1) log 2 + Q(s, 2) log 2 \ 39 + &= sum_(s in S) log 2 thin crossout((Q(s, 2) - Q(s, 1)), #<dkl-zero-qeq>) \ 40 + &= sum_(s in S) 0 = 0 41 41 $ 42 42 43 43 == $eta(p, r)$ comme une espérance <proof-eta-esperance> ··· 48 48 On a 49 49 50 50 $ 51 - exp(sum_(t=0)^oo gamma^t r(C_t)) 52 - &= sum_((c_t)_(t in NN) in cal(S)) (sum_(t=0)^oo gamma^t r(c_t)) bb(P)(sum_(t=0)^oo gamma^t r(C_t) = sum_(t=0)^oo gamma^t r(c_t)) \ 53 - &= sum_((c_t)_(t in NN) in cal(S)) (sum_(t=0)^oo gamma^t r(c_t)) bb(P)(C = (c_t)_(t in NN)) \ 54 - // &= sum_((c_t)_(t in NN) in cal(S)) (sum_(t=0)^oo gamma^t r(c_t)) bb(P)(inter.big_(t=0)^oo C_t = c_t) \ 55 - // &= sum_((c_t)_(t in NN) in cal(S)) (sum_(t=0)^oo gamma^t r(c_t)) product_(t=0)^oo bb(P)(C_t = c_t) \ 51 + exp(sum_(t=0)^oo gamma^t r(C_t)) 52 + &= sum_((c_t)_(t in NN) in cal(S)) (sum_(t=0)^oo gamma^t r(c_t)) bb(P)(sum_(t=0)^oo gamma^t r(C_t) = sum_(t=0)^oo gamma^t r(c_t)) \ 53 + &= sum_((c_t)_(t in NN) in cal(S)) (sum_(t=0)^oo gamma^t r(c_t)) bb(P)(C = (c_t)_(t in NN)) \ 54 + // &= sum_((c_t)_(t in NN) in cal(S)) (sum_(t=0)^oo gamma^t r(c_t)) bb(P)(inter.big_(t=0)^oo C_t = c_t) \ 55 + // &= sum_((c_t)_(t in NN) in cal(S)) (sum_(t=0)^oo gamma^t r(c_t)) product_(t=0)^oo bb(P)(C_t = c_t) \ 56 56 $ 57 57 58 58 ··· 60 60 Soit $S$ (resp. $A$) la suite des premiers (resp. deuxièmes) éléments de $C$, c'est-à-dire $forall t in NN, (S_t, A_t) := C_t$. 61 61 62 62 63 - Étant donné la définition de $cal(S)$: 63 + Étant donné la définition de $cal(S)$: 64 64 65 - - $S_t$ dépend de $A_(t-1)$ et $S_(t-1)$ 65 + - $S_t$ dépend de $A_(t-1)$ et $S_(t-1)$ 66 66 - $A_t$ dépend de $S_t$ 67 67 68 68 On a alors, pour toute suite $(c_t)_(t in NN) in cal(S)$ : 69 69 70 70 $ 71 - P(C = (c_t)_(t in NN)) 72 - = \ 73 - bb(P)(S_0 = s_0)bb(P)(A_0 = a_0 | S_0 = s_0) 74 - product_(t=1)^oo 75 - // bb(P)(S_t = s_t mid(|) cases(S_(t-1) = s_(t-1), A_(t-1) = a_(t-1))) 76 - bb(P)(S_t = s_t mid(|) C_(t-1) = c_(t-1)) 77 - bb(P)(A_t = a_t mid(|) S_t = s_t) \ 71 + P(C = (c_t)_(t in NN)) 72 + = \ 73 + bb(P)(S_0 = s_0)bb(P)(A_0 = a_0 | S_0 = s_0) 74 + product_(t=1)^oo 75 + // bb(P)(S_t = s_t mid(|) cases(S_(t-1) = s_(t-1), A_(t-1) = a_(t-1))) 76 + bb(P)(S_t = s_t mid(|) C_(t-1) = c_(t-1)) 77 + bb(P)(A_t = a_t mid(|) S_t = s_t) \ 78 78 $ 79 79 80 - On a 80 + On a 81 81 82 82 $ 83 - bb(P)(S_0 = s_0) &= rho_0(s_0) \ 84 - forall t in NN, quad bb(P)(A_t = a_t mid(|) S_t = s_t) &= Q_p (s_t, a_t) \ 85 - forall t in NN^*, quad 86 - bb(P)(S_t = s_t | C_(t-1) = c_(t-1)) &= bb(P)(M(C_(t-1)) = M(c_(t-1)) | C_(t-1) = c_(t-1)) \ 87 - &= bb(P)(C_(t-1) = c_(t-1) | C_(t-1) = c_(t-1)) = 1 83 + bb(P)(S_0 = s_0) &= rho_0(s_0) \ 84 + forall t in NN, quad bb(P)(A_t = a_t mid(|) S_t = s_t) &= Q_p (s_t, a_t) \ 85 + forall t in NN^*, quad 86 + bb(P)(S_t = s_t | C_(t-1) = c_(t-1)) &= bb(P)(M(C_(t-1)) = M(c_(t-1)) | C_(t-1) = c_(t-1)) \ 87 + &= bb(P)(C_(t-1) = c_(t-1) | C_(t-1) = c_(t-1)) = 1 88 88 $ 89 89 90 90 91 91 Donc on a 92 92 93 93 $ 94 - P(C = (c_t)_(t in NN)) 95 - &= rho_0(s_0) Q_p (s_0, a_0) product_(t=1)^oo Q_p (s_t, a_t) \ 96 - &= rho_0(s_0) product_(t=0)^oo Q_p (s_t, a_t) 94 + P(C = (c_t)_(t in NN)) 95 + &= rho_0(s_0) Q_p (s_0, a_0) product_(t=1)^oo Q_p (s_t, a_t) \ 96 + &= rho_0(s_0) product_(t=0)^oo Q_p (s_t, a_t) 97 97 $ 98 98 99 99 Et ainsi 100 100 101 101 $ 102 - exp(sum_(t=0)^oo gamma^t r(C_t)) 103 - &= sum_((c_t)_(t in NN) in cal(S)) (sum_(t=0)^oo gamma^t r(c_t)) bb(P)(C = (c_t)_(t in NN)) \ 104 - &= sum_((c_t)_(t in NN) in cal(S)) (sum_(t=0)^oo gamma^t r(c_t)) rho_0(s_0) product_(t=0)^oo Q_p (s_t, a_t) \ 105 - &= eta(p, r) quad qed 102 + exp(sum_(t=0)^oo gamma^t r(C_t)) 103 + &= sum_((c_t)_(t in NN) in cal(S)) (sum_(t=0)^oo gamma^t r(c_t)) bb(P)(C = (c_t)_(t in NN)) \ 104 + &= sum_((c_t)_(t in NN) in cal(S)) (sum_(t=0)^oo gamma^t r(c_t)) rho_0(s_0) product_(t=0)^oo Q_p (s_t, a_t) \ 105 + &= eta(p, r) quad qed 106 106 $ 107 107 108 108 == Simplification de l'expression de $L(s, a, cal(P), cal(P)', R)$ dans PPO-Clip <proof-ppo-clip-simplify> ··· 117 117 v(0.5em) 118 118 set math.equation(numbering: none) 119 119 show math.equation.where(block: true): set align(left) 120 - block(breakable: false, //stroke: 0.5pt+black, 121 - grid(columns: (1fr, 1fr), row-gutter: 1em, 120 + block( 121 + breakable: false, //stroke: 0.5pt+black, 122 + grid( 123 + columns: (1fr, 1fr), 124 + row-gutter: 1em, 122 125 123 - grid.cell(align: center)[ *Cas $alpha > 0$* ], 124 - grid.cell(align: center)[ *Cas $alpha < 0$* ], 125 - [ 126 + grid.cell(align: center)[ *Cas $alpha > 0$* ], 127 + grid.cell(align: center)[ *Cas $alpha < 0$* ], 128 + [ 126 129 127 - $ 128 - &L(s, a, cal(P), cal(P'), R) \ 129 - &= min(q/q' alpha, quad clip(q/q', thick 1-epsilon, thick 1+epsilon) alpha) \ 130 - &= min(q/q', quad clip(q/q', thick 1-epsilon, thick 1+epsilon) ) alpha why(alpha > 0) \ 131 - $ 132 - ], 133 - [ 130 + $ 131 + &L(s, a, cal(P), cal(P'), R) \ 132 + &= min(q/q' alpha, quad clip(q/q', thick 1-epsilon, thick 1+epsilon) alpha) \ 133 + &= min(q/q', quad clip(q/q', thick 1-epsilon, thick 1+epsilon)) alpha why(alpha > 0) \ 134 + $ 135 + ], 136 + [ 134 137 135 - $ 136 - &L(s, a, cal(P), cal(P'), R) \ 137 - &= min(q/q' alpha, quad clip(q/q', thick 1-epsilon, thick 1+epsilon) alpha) \ 138 - &= max(q/q', quad clip(q/q', thick 1-epsilon, thick 1+epsilon) ) alpha why(alpha < 0) \ 139 - $ 140 - ], 138 + $ 139 + &L(s, a, cal(P), cal(P'), R) \ 140 + &= min(q/q' alpha, quad clip(q/q', thick 1-epsilon, thick 1+epsilon) alpha) \ 141 + &= max(q/q', quad clip(q/q', thick 1-epsilon, thick 1+epsilon)) alpha why(alpha < 0) \ 142 + $ 143 + ], 141 144 142 - grid.hline(stroke: 0.5pt), 143 - grid.cell(colspan: 2, align: center, inset: 1em)[*...et $q slash q' in [1-epsilon, 1+epsilon]$*], 144 - [ 145 + grid.hline(stroke: 0.5pt), 146 + grid.cell( 147 + colspan: 2, 148 + align: center, 149 + inset: 1em, 150 + )[*...et $q slash q' in [1-epsilon, 1+epsilon]$*], 151 + [ 145 152 146 - $ 147 - &= min(q/q', quad clip(q/q', thick 1-epsilon, thick 1+epsilon) ) alpha \ 148 - &= min(q/q', quad q/q') alpha \ 149 - &= min(q/q' , 1+epsilon) alpha why(1+epsilon > q/q') \ 150 - $ 153 + $ 154 + & = min(q/q', quad clip(q/q', thick 1-epsilon, thick 1+epsilon)) alpha \ 155 + & = min(q/q', quad q/q') alpha \ 156 + & = min(q/q', 1+epsilon) alpha why(1+epsilon > q/q') \ 157 + $ 151 158 152 159 153 - ], [ 160 + ], 161 + [ 154 162 155 - $ 156 - &= max(q/q', quad clip(q/q', thick 1-epsilon, thick 1+epsilon) ) alpha \ 157 - &= max(q/q', quad q/q') alpha \ 158 - &= max(q/q' , 1-epsilon) alpha why(1-epsilon < q/q') \ 159 - $ 163 + $ 164 + & = max(q/q', quad clip(q/q', thick 1-epsilon, thick 1+epsilon)) alpha \ 165 + & = max(q/q', quad q/q') alpha \ 166 + & = max(q/q', 1-epsilon) alpha why(1-epsilon < q/q') \ 167 + $ 160 168 161 - ], 169 + ], 162 170 163 - grid.hline(stroke: 0.5pt), 164 - grid.cell(colspan: 2, align: center, inset: 1em)[*...et $q slash q' > 1+epsilon$*], 165 - [ 171 + grid.hline(stroke: 0.5pt), 172 + grid.cell( 173 + colspan: 2, 174 + align: center, 175 + inset: 1em, 176 + )[*...et $q slash q' > 1+epsilon$*], 177 + [ 166 178 167 - $ 168 - &= min(q/q', quad clip(q/q', thick 1-epsilon, thick 1+epsilon) ) alpha \ 169 - &= min(q/q', quad 1+epsilon) alpha \ 170 - $ 179 + $ 180 + & = min(q/q', quad clip(q/q', thick 1-epsilon, thick 1+epsilon)) alpha \ 181 + & = min(q/q', quad 1+epsilon) alpha \ 182 + $ 171 183 172 - ], [ 184 + ], 185 + [ 173 186 174 - $ 175 - &= max(q/q', quad clip(q/q', thick 1-epsilon, thick 1+epsilon) ) alpha \ 176 - &= max(q/q', quad 1+epsilon) alpha \ 177 - &= max(q/q', quad 1-epsilon) alpha why(1-epsilon < 1+epsilon < q / q') \ 178 - $ 187 + $ 188 + & = max(q/q', quad clip(q/q', thick 1-epsilon, thick 1+epsilon)) alpha \ 189 + & = max(q/q', quad 1+epsilon) alpha \ 190 + & = max(q/q', quad 1-epsilon) alpha why(1-epsilon < 1+epsilon < q / q') \ 191 + $ 179 192 180 - ], 193 + ], 181 194 182 - grid.hline(stroke: 0.5pt), 183 - grid.cell(colspan: 2, align: center, inset: 1em)[*...et $q slash q' < 1-epsilon$*], 184 - [ 195 + grid.hline(stroke: 0.5pt), 196 + grid.cell( 197 + colspan: 2, 198 + align: center, 199 + inset: 1em, 200 + )[*...et $q slash q' < 1-epsilon$*], 201 + [ 185 202 186 - $ 187 - &= min(q/q', quad clip(q/q', thick 1-epsilon, thick 1+epsilon) ) alpha \ 188 - &= min(q/q', quad 1-epsilon) alpha \ 189 - &= min(q/q', quad 1+epsilon) alpha why(1+epsilon > 1-epsilon > q / q') \ 190 - $ 203 + $ 204 + & = min(q/q', quad clip(q/q', thick 1-epsilon, thick 1+epsilon)) alpha \ 205 + & = min(q/q', quad 1-epsilon) alpha \ 206 + & = min(q/q', quad 1+epsilon) alpha why(1+epsilon > 1-epsilon > q / q') \ 207 + $ 191 208 192 - ], [ 209 + ], 210 + [ 193 211 194 212 195 - $ 196 - &= max(q/q', quad clip(q/q', thick 1-epsilon, thick 1+epsilon) ) alpha \ 197 - &= max(q/q', quad 1-epsilon) alpha 198 - $ 213 + $ 214 + & = max(q/q', quad clip(q/q', thick 1-epsilon, thick 1+epsilon)) alpha \ 215 + & = max(q/q', quad 1-epsilon) alpha 216 + $ 199 217 200 - ])) 218 + ], 219 + ), 220 + ) 201 221 }
+155 -110
rapport/sdk2-study.typ
··· 1 1 #import "./utils.typ": todo 2 - #import "@preview/fletcher:0.5.8": diagram, node, edge 2 + #import "@preview/fletcher:0.5.8": diagram, edge, node 3 3 4 4 #show figure: set block(spacing: 3em) 5 5 6 - Unitree met à disposition du public un _SDK_#footnote[Kit de développement logiciel (Software Development Kit)] permettant de contrôler ses robots (dont le H1v2). 6 + Unitree met à disposition du public un _SDK_#footnote[Kit de développement logiciel (Software Development Kit)] permettant de contrôler ses robots (dont le H1v2). 7 7 8 8 == Canaux DDS 9 9 ··· 17 17 18 18 #figure( 19 19 caption: [`LowCmd.idl`, traduit depuis sa conversion en C++ @lowcmd_hpp], 20 - ```c 21 - struct MotorCmd 22 - { 23 - uint8 mode; 24 - float q; 25 - float dq; 26 - float tau; 27 - float kp; 28 - float kd; 29 - unsigned long reserve; 30 - }; 20 + ```c 21 + struct MotorCmd 22 + { 23 + uint8 mode; 24 + float q; 25 + float dq; 26 + float tau; 27 + float kp; 28 + float kd; 29 + unsigned long reserve; 30 + }; 31 31 32 - struct Cmd 33 - { 34 - uint8 mode_pr; 35 - uint8 mode_machine; 36 - MotorCmd motor_cmd[35]; 37 - unsigned long reserve[4]; 38 - unsigned long crc; 39 - }; 40 - ``` 32 + struct Cmd 33 + { 34 + uint8 mode_pr; 35 + uint8 mode_machine; 36 + MotorCmd motor_cmd[35]; 37 + unsigned long reserve[4]; 38 + unsigned long crc; 39 + }; 40 + ```, 41 41 ) 42 42 43 43 DDS groupe les mesages dans des _topics_. Les messages sont échangés sur un topic de la manière suivante ··· 58 58 59 59 #figure( 60 60 caption: [Résultat de `tree lib thirdparty` dans le dépot git], 61 - ``` 62 - lib 63 - ├── aarch64 64 - │   └── libunitree_sdk2.a 65 - └── x86_64 66 - └── libunitree_sdk2.a 67 - thirdparty 68 - ├── CMakeLists.txt 69 - ├── include 70 - │   └── ... 71 - └── lib 72 - ├── aarch64 73 - │   ├── libddsc.so 74 - │   ├── libddsc.so.0 -> libddsc.so 75 - │   ├── libddscxx.so 76 - │   └── libddscxx.so.0 -> libddscxx.so 77 - └── x86_64 78 - ├── libddsc.so 79 - ├── libddsc.so.0 -> libddsc.so 80 - ├── libddscxx.so 81 - └── libddscxx.so.0 -> libddscxx.so 82 - ``` 61 + ``` 62 + lib 63 + ├── aarch64 64 + │   └── libunitree_sdk2.a 65 + └── x86_64 66 + └── libunitree_sdk2.a 67 + thirdparty 68 + ├── CMakeLists.txt 69 + ├── include 70 + │   └── ... 71 + └── lib 72 + ├── aarch64 73 + │   ├── libddsc.so 74 + │   ├── libddsc.so.0 -> libddsc.so 75 + │   ├── libddscxx.so 76 + │   └── libddscxx.so.0 -> libddscxx.so 77 + └── x86_64 78 + ├── libddsc.so 79 + ├── libddsc.so.0 -> libddsc.so 80 + ├── libddscxx.so 81 + └── libddscxx.so.0 -> libddscxx.so 82 + ```, 83 83 ) 84 84 85 85 Compiler le SDK nécéssite l'existance de ces fichiers binaires: 86 86 87 87 #import "@preview/zebraw:0.5.5" 88 - #let zebraw = (..args) => zebraw.zebraw(lang: false, background-color: luma(255), ..args) 88 + #let zebraw = (..args) => zebraw.zebraw( 89 + lang: false, 90 + background-color: luma(255), 91 + ..args, 92 + ) 89 93 90 94 #figure( 91 95 caption: [Extrait de `cmake/unitree_sdk2Targets.cmake`], 92 96 zebraw( 93 97 numbering-offset: 63 - 1, 94 98 highlight-lines: (4,), 95 - ```cmake 96 - # Create imported target unitree_sdk2 97 - add_library(unitree_sdk2 STATIC IMPORTED GLOBAL) 98 - set_target_properties(unitree_sdk2 PROPERTIES 99 - IMPORTED_LOCATION ${_IMPORT_PREFIX}/lib/libunitree_sdk2.a 100 - INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include;${_IMPORT_PREFIX}/include" 101 - INTERFACE_LINK_LIBRARIES "ddsc;ddscxx;Threads::Threads" 102 - LINKER_LANGUAGE CXX 103 - ``` 104 - )) 99 + ```cmake 100 + # Create imported target unitree_sdk2 101 + add_library(unitree_sdk2 STATIC IMPORTED GLOBAL) 102 + set_target_properties(unitree_sdk2 PROPERTIES 103 + IMPORTED_LOCATION ${_IMPORT_PREFIX}/lib/libunitree_sdk2.a 104 + INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include;${_IMPORT_PREFIX}/include" 105 + INTERFACE_LINK_LIBRARIES "ddsc;ddscxx;Threads::Threads" 106 + LINKER_LANGUAGE CXX 107 + ```, 108 + ), 109 + ) 105 110 106 111 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. 107 112 ··· 114 119 zebraw( 115 120 highlight-lines: (18, 19), 116 121 numbering: false, 117 - ``` 118 - -- The C compiler identification is GNU 13.3.0 119 - -- The CXX compiler identification is GNU 13.3.0 120 - -- Detecting C compiler ABI info 121 - -- Detecting C compiler ABI info - done 122 - -- Check for working C compiler: /usr/bin/cc - skipped 123 - -- Detecting C compile features 124 - -- Detecting C compile features - done 125 - -- Detecting CXX compiler ABI info 126 - -- Detecting CXX compiler ABI info - done 127 - -- Check for working CXX compiler: /usr/bin/c++ - skipped 128 - -- Detecting CXX compile features 129 - -- Detecting CXX compile features - done 130 - -- Setting build type to 'Release' as none was specified. 131 - -- Current system architecture: x86_64 132 - -- Performing Test CMAKE_HAVE_LIBC_PTHREAD 133 - -- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Success 134 - -- Found Threads: TRUE 135 - -- Importing: /home/glebihan/playground/unitree_sdk2/thirdparty/lib/x86_64/libddsc.so 136 - -- Importing: /home/glebihan/playground/unitree_sdk2/thirdparty/lib/x86_64/libddscxx.so 122 + ``` 123 + -- The C compiler identification is GNU 13.3.0 124 + -- The CXX compiler identification is GNU 13.3.0 125 + -- Detecting C compiler ABI info 126 + -- Detecting C compiler ABI info - done 127 + -- Check for working C compiler: /usr/bin/cc - skipped 128 + -- Detecting C compile features 129 + -- Detecting C compile features - done 130 + -- Detecting CXX compiler ABI info 131 + -- Detecting CXX compiler ABI info - done 132 + -- Check for working CXX compiler: /usr/bin/c++ - skipped 133 + -- Detecting CXX compile features 134 + -- Detecting CXX compile features - done 135 + -- Setting build type to 'Release' as none was specified. 136 + -- Current system architecture: x86_64 137 + -- Performing Test CMAKE_HAVE_LIBC_PTHREAD 138 + -- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Success 139 + -- Found Threads: TRUE 140 + -- Importing: /home/glebihan/playground/unitree_sdk2/thirdparty/lib/x86_64/libddsc.so 141 + -- Importing: /home/glebihan/playground/unitree_sdk2/thirdparty/lib/x86_64/libddscxx.so 137 142 138 - CMake Error at CMakeLists.txt:42 (message): 139 - Unitree SDK library for the architecture is not found 143 + CMake Error at CMakeLists.txt:42 (message): 144 + Unitree SDK library for the architecture is not found 140 145 141 146 142 - -- Configuring incomplete, errors occurred! 143 - ``` 147 + -- Configuring incomplete, errors occurred! 148 + ```, 144 149 ) 145 150 } 146 151 ··· 159 164 ... 160 165 ``` 161 166 162 - 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? 167 + 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? 163 168 164 169 Ces constats ont motivé une première tentative de décompilation de ces `libunitree_sdk2.a` pour comprendre le fonctionnement du SDK2, via _Ghidra_ @ghidra. 165 - 170 + 166 171 Cependant, la découverte de l'existance d'un bridge officiel SDK $arrows.lr$ Mujoco @unitree_mujoco a rendu cette piste non nécéssaire. 167 172 168 173 == Un autre bridge existant: `unitree_mujoco` ··· 176 181 node((1, 0), "SDK") 177 182 node((2, 0), "Robot") 178 183 179 - edge((0,0), (0, 0), "<-", bend: 130deg, loop-angle: 180deg)[] 180 - edge((2.25,0), (2.25, 0), "->", bend: -130deg, loop-angle: -180deg)[] 184 + edge((0, 0), (0, 0), "<-", bend: 130deg, loop-angle: 180deg)[] 185 + edge((2.25, 0), (2.25, 0), "->", bend: -130deg, loop-angle: -180deg)[] 181 186 182 187 183 188 for i in range(0, 2) { 184 - edge((i, 0), (i+1, 0), "->", shift: 3pt)[ordres] 185 - edge((i, 0), (i+1, 0), "<-", shift: -3pt, label-side: right)[état] 189 + edge((i, 0), (i + 1, 0), "->", shift: 3pt)[ordres] 190 + edge((i, 0), (i + 1, 0), "<-", shift: -3pt, label-side: right)[état] 186 191 } 187 192 })) 188 193 ··· 195 200 node((2, 0))[`unitree_mujoco`] 196 201 node((3, 0), "Mujoco") 197 202 198 - edge((0,0), (0, 0), "<-", bend: 130deg, loop-angle: 180deg)[] 199 - edge((3.25,0), (3.25, 0), "->", bend: -130deg, loop-angle: -180deg)[] 203 + edge((0, 0), (0, 0), "<-", bend: 130deg, loop-angle: 180deg)[] 204 + edge((3.25, 0), (3.25, 0), "->", bend: -130deg, loop-angle: -180deg)[] 200 205 201 206 for i in range(0, 3) { 202 - edge((i, 0), (i+1, 0), "->", shift: 3pt)[ordres] 203 - edge((i, 0), (i+1, 0), "<-", shift: -3pt, label-side: right)[état] 207 + edge((i, 0), (i + 1, 0), "->", shift: 3pt)[ordres] 208 + edge((i, 0), (i + 1, 0), "<-", shift: -3pt, label-side: right)[état] 204 209 } 205 210 206 211 edge((0, 1), (2, 1), "|-|", label-side: right)[API du SDK] ··· 218 223 node((2, 0))[`gz-unitree`] 219 224 node((3, 0), text(fill: blue)[Gazebo]) 220 225 221 - edge((0,0), (0, 0), "<-", bend: 130deg, loop-angle: 180deg)[] 222 - edge((3.25,0), (3.25, 0), "->", bend: -130deg, loop-angle: -180deg, stroke: blue)[] 226 + edge((0, 0), (0, 0), "<-", bend: 130deg, loop-angle: 180deg)[] 227 + edge( 228 + (3.25, 0), 229 + (3.25, 0), 230 + "->", 231 + bend: -130deg, 232 + loop-angle: -180deg, 233 + stroke: blue, 234 + )[] 223 235 224 236 for i in range(0, 3) { 225 237 let col = if i == 2 { blue } else { black } 226 - edge((i, 0), (i+1, 0), "->", shift: 3pt, stroke: col, text(fill: col)[ordres]) 227 - edge((i, 0), (i+1, 0), "<-", shift: -3pt, stroke: col, label-side: right, text(fill: col)[état]) 238 + edge((i, 0), (i + 1, 0), "->", shift: 3pt, stroke: col, text( 239 + fill: col, 240 + )[ordres]) 241 + edge( 242 + (i, 0), 243 + (i + 1, 0), 244 + "<-", 245 + shift: -3pt, 246 + stroke: col, 247 + label-side: right, 248 + text(fill: col)[état], 249 + ) 228 250 } 229 251 230 252 edge((0, 1), (2, 1), "|-|", label-side: right)[API du SDK] 231 - edge((2, 1), (3, 1), "|-|", stroke: blue + 1.25pt, label-side: right, text(fill: blue)[*API de Gazebo*]) 253 + edge((2, 1), (3, 1), "|-|", stroke: blue + 1.25pt, label-side: right, text( 254 + fill: blue, 255 + )[*API de Gazebo*]) 232 256 })) 233 257 234 - 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) renvoyé par le robot. 258 + 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) renvoyé par le robot. 235 259 236 260 Le `low` indique que ce sont des messages bas-niveau: par exemple, `rt/lowcmd` correspond directement à des ordres de tension pour les moteurs, et non pas à des messages plus avancés tel que "avancer de $x$ mètres" #todo[ces messages plus haut-niveau = sport mode non? dire quand ils servent] 237 261 ··· 239 263 240 264 Étant donné le modèle _pub/sub_ de DDS, on parle de _pub(lication)_ de message, et de _sub(scription)_#footnote[abonnement] aux messages d'un canal (pour les recevoir) 241 265 242 - #figure(caption: [Cycle de vie de la simulation avec le bridge pour Mujoco], diagram({ 243 - node(name: <sdk>, (0, 0))[SDK] 244 - node(enclose: ((1, 1), (-1, 1)), stroke: blue, inset: 10pt, snap: false, text(fill: blue)[Canaux \ DDS]) 245 - node(name: <lowcmd>, (1, 1))[`rt/lowcmd`] 246 - node(name: <lowstate>, (-1, 1) )[`rt/lowstate`] 247 - node(name: <bridge>, enclose: ((1, 2), (-1, 2)), stroke: black, inset: 10pt)[Bridge] 248 - node(name: <mujoco>, (0, 3))[Mujoco] 266 + #figure( 267 + caption: [Cycle de vie de la simulation avec le bridge pour Mujoco], 268 + diagram({ 269 + node(name: <sdk>, (0, 0))[SDK] 270 + node( 271 + enclose: ((1, 1), (-1, 1)), 272 + stroke: blue, 273 + inset: 10pt, 274 + snap: false, 275 + text(fill: blue)[Canaux \ DDS], 276 + ) 277 + node(name: <lowcmd>, (1, 1))[`rt/lowcmd`] 278 + node(name: <lowstate>, (-1, 1))[`rt/lowstate`] 279 + node( 280 + name: <bridge>, 281 + enclose: ((1, 2), (-1, 2)), 282 + stroke: black, 283 + inset: 10pt, 284 + )[Bridge] 285 + node(name: <mujoco>, (0, 3))[Mujoco] 249 286 250 287 251 - edge(<sdk>, <lowcmd>, "->", bend: 30deg)[pub] 252 - edge(<lowcmd>, (1, 2), "..>", bend: 20deg)[via sub] 253 - edge((1, 2), <mujoco>, "->", bend: 20deg, `data->ctrl[i] = ...`) 288 + edge(<sdk>, <lowcmd>, "->", bend: 30deg)[pub] 289 + edge(<lowcmd>, (1, 2), "..>", bend: 20deg)[via sub] 290 + edge((1, 2), <mujoco>, "->", bend: 20deg, `data->ctrl[i] = ...`) 254 291 255 - edge(<sdk>, <lowstate>, "<..", bend: -30deg)[via sub] 256 - edge(<lowstate>, (-1, 2), "<-", bend: -20deg)[pub] 257 - edge((-1, 2), <mujoco>, "<-", bend: -20deg, `... = data->sensordata[i]`) 292 + edge(<sdk>, <lowstate>, "<..", bend: -30deg)[via sub] 293 + edge(<lowstate>, (-1, 2), "<-", bend: -20deg)[pub] 294 + edge((-1, 2), <mujoco>, "<-", bend: -20deg, `... = data->sensordata[i]`) 258 295 259 - edge(<mujoco>, <mujoco>, "->", bend: 130deg, loop-angle: -90deg, `mj_step(model, data)`) 260 - })) 296 + edge( 297 + <mujoco>, 298 + <mujoco>, 299 + "->", 300 + bend: 130deg, 301 + loop-angle: -90deg, 302 + `mj_step(model, data)`, 303 + ) 304 + }), 305 + ) 261 306 262 307 Le but est donc de reproduire un cycle de vie équivalent, mais en remplaçant la partie spécifique à Mujoco par une partie adaptée à Gazebo.