L-Systèmes : Morphogénèse des plantes

publicité
L-Systèmes : Morphogénèse des plantes
November 13, 2010
Laurent Orseau <[email protected]>
Antoine Cornuéjols <[email protected]>
1
Introduction
La récursion, qui est le principe de base des langages de programmation fonctionnels
tels Scheme, Lisp ou Caml, est au coeur de nombreux systèmes vivants (notamment
la plupart des organismes pluri-cellulaires). En effet, la duplication est une forme de
récursion puisqu’il s’agit de "faire la même chose". Cette duplication peut être identique ou différer selon certaines caractéristiques, comme lorsqu’une reine fourmi donne
vie à différents types de fourmis. Il en va de même lors de la croissance d’organisme
multicellulaires : les cellules se répliquent localement, pour donner naissances à de
nouvelles cellules qui se répliqueront aussi et ainsi de suite.
La récursion est en fait une forme plus générale que l’itération telle qu’on la voit
dans les langages de programmation impératifs comme le C ou le Pascal, avec les
boucles for, while, reapeat until, ... En effet, il est possible de faire toute itération en
récursion, mais l’inverse n’est pas vrai. Les langages impératifs permettent d’ailleurs
de faire de la récursion, mais ils ne sont généralement pas créés pour cela.
Les itérations (seules) ne permettent pas de prendre en compte que les cellules qui
se répliquent peuvent donner naissances à plusieurs cellules qui vont toutes refaire la
même opération. Une itération ne fait qu’une seule chose à la fois. (Pour contourner
ce problème, il est nécessaire de passer par une mémoire sous forme de tableau ou de
liste.)
En revanche, la modélisation d’un tel processus est direct en récursion, car c’est ce
que fait exactement la récursion. Nous allons voir le lien très fort qui existe entre la
morphogénèse des plantes et le principe de récursion.
1
2
L-Systèmes
Le biologiste Aristid Lindenmayer (1926-1989) a modélisé la croissance de plantes par
un tel processus récursif. Avec un outil graphique adéquat, il est possible de représenter
des plantes qui semblent "naturelles".
Une feuille d’érable.
Une feuille générée par 10 lignes de
Scheme.
Une fougère générée par L-système (une quinzaine de lignes de Scheme).
2.1
Turtle Graphics
Commençons pour définir le langage de dessin : nous allons recréer le langage LOGO.
C’est un outil de dessin très simple à destination pédagogique, inventé par Seymourt
2
Papert en 1967. Il est très simple à utiliser, mais parce qu’il peut faire de la récursion,
il peut générer des dessins très complexes.
Le principe est de contrôler une "tortue" qui se déplace sur la fenêtre d’affichage
en avançant d’une certaine distance, soit en laissant une marque au sol (tirer un trait)
soit non, ou en tournant d’un certain angle.
Une tortue est donc définie par sa position en x et en y ainsi que par son angle (par
défaut le 0 est l’Est).
Question 1
Commencez par créer une fenêtre graphique et affichez-la :
(define monde (make-board 1 1 600 600))
(board-draw monde 0.1)
Question 2
Définissons maintenant une tortue initiale. Créez une fonction :
(make-tortue) → (list number? number? number?)
qui renvoie une tortue, c’est-à-dire une liste contenant la position en x et en y ainsi que
l’angle initial. Il est conseillé de placer la tortue vers (300, 400) mais vous pouvez et
pourrez changer ces valeurs à votre convenance.
Question 3
Comme dans les TP précédents, créez les accesseurs posx, posy et angle permettant d’extraire d’une tortue (une liste) la valeur correspondante.
Question 4
Créez une fonction :
(turn a t) → tortue?
a : number?
t : tortue?
qui renvoie la tortue t tournée d’un angle a.
Question 5
Créez une fonction :
(move dist t) → tortue?
dist : number?
t : tortue?
qui renvoie la tortue t déplacée d’une longueur dist dans la direction donnée par son
angle. La tortue est déplacée sans rien dessiner.
Attention, les fonctions prédéfinies cos et sin prennent des radians en entrée, mais
il est plus confortable pour la suite du TP d’utiliser des degrés. Il vous faudra donc les
convertir. Il est conseiller de créer une fonction intermédiaire :
3
(degres->radians deg) → number?
deg : number?
qui permet d’effectuer la conversion.
Question 6
La fonction :
(board-draw-line board x-src y-src x-dst y-dst) → void?
board : board?
x-src : number?
y-src : number?
x-dst : number?
y-dst : number?
permet de tracer à l’écran une ligne dans la fenêtre graphique, de x-src, y-src à
x-dst, y-dst.
Créez une fonction :
(draw dist t) → tortue?
dist : number
t : tortue?
similaire à move mais qui, en plus de renvoyer la nouvelle tortue, dessine un trait à
l’écran de son ancienne position à la nouvelle.
2.2
Appels imbriqués
Puisque chacune des fonctions définies prend une tortue et retourne la nouvelle tortue,
il faut imbriquer les appels à ces fonctions pour générer des séquences de dessin. Par
exemple, prenons l’appel :
(draw 50 (turn 20 (draw 100 (make-tortue))))
En Scheme, ce sont les appels de fonction les plus profondément imbriqués qui sont
exécutés en premier. Ici, on crée donc d’abord une tortue, puis on la fait avancer de 100
tout en traçant une ligne, puis on la fait tourner de 20 degrés, puis on la fait avancer de
50 en dessinant.
Vous pouvez dors et déjà vous amuser un peu avec ces primitives graphiques pour
bien les prendre en main.
3
Un premier exemple récursif
Tout l’intérêt de ce système graphique réside dans la récursion.
Définissez la fonction suivante :
(define (escargot n tortue)
(when (> n 1)
(escargot (* n 0.97) (draw n (turn 15 tortue)))))
4
Evidemment, lisez-là et comprenez-là.
Le when permet de n’exécuter une suite d’expressions que si la condition donnée
est vérifiée :
(when condition
expression1
expression2
...
expression-fin)
La valeur de retour du when est la valeur de expression-fin.
À la fin de votre programme, exécutez un appel à cette fonction :
(board-draw monde 0
(escargot 20 (make-turtle)))
Notez que cet appel est à l’intérieur d’un appel à board-draw, qui permet de préparer la fenêtre pour le dessin et de faire les pré-traitements et post-traitements nécessaires.
Modifiez la fonction escargot, essayez diverses choses, amusez-vous avec le langage graphique !
Un nautile
4
Quelques devinettes
Question 7
Créez une fonction :
(f1 n t) → void?
n : number?
t : tortue?
qui dessine un trait de longueur n (en avançant la tortue), appelle f1 avec la moitié
de n et la tortue tournée d’un angle de 90◦ vers la droite, puis appelle f1 avec la moitié
de n et la tortue tournée d’un angle de 90◦ vers la gauche. La récursion doit s’arrêter
lorsque n a une valeur inférieure à 1. Puis faites l’appel suivant :
(f1 100 (make-tortue))
5
Essayez ensuite de modifier les différentes valeurs (rapport et angle) pour voir le
résultat.
Question 8
Ecrivez une fonction :
(f4 profondeur prof-max tortue) → void?
profondeur : number?
prof-max : number?
tortue : tortue?
qui arrête le calcul à une profondeur maximale donnée et génère une image comme sur
les exemples suivants. Initialement, profondeur est toujours égal à 1.
(f4 1 2
(make-turtle))
(f4 1 5
(make-turtle))
(f4 1 10
(make-turtle))
Question 9
Trouvez la fonction qui génère la courbe suivante :
Le triangle de Sierpinski. Ou presque.
Indice : la tortue est partie du centre. Essayez de voir ce qu’elle a pu faire dans le
premier appel de la fonction...
Question 10
Trouvez la fonction qui génère la courbe suivante :
6
4.1
En Technicolor
Pour ajouter un peu d’épaisseur et de couleurs à vos traits, vous pouvez utiliser les
fonctions suivantes :
(board-set-pen-color board r g b) → void?
board : board?
r : integer?
g : integer?
b : integer?
Change la couleur courante du trait pour celle donnée en Red-Green-Blue. Les valeurs
sont des nombres entiers de 0 à 255 inclus. Attention à ne pas dépasser de ces bornes.
Vous pouvez paramétrer ces valeurs selon le paramètre de profondeur ou la longueur
du trait.
Par exemple :
(board-set-pen-color monde 0 255 0)
changera la couleur pour du vert.
La fonction :
(board-set-pen-width board width) → void?
board : board?
width : number?
Modifie la taille du trait.
4.2
Visualisation en direct live
Certaines figures peuvent prendre un certain temps avant d’apparaître à l’écran. Pour
voir le développement en direct sans que cela prenne trop de temps sur le temps de
calcul, on peut utiliser la technique suivante :
Par exemple, pour la fonction f1, les deux lignes du premier when ont été ajoutée
pour mettre le monde à jour uniquement lorsque n > 2 :
(define (f1 n tortue)
(when (> n 2)
7
(board-redraw monde))
....)
En effet, puisque f1 s’apelle récursivement 2 fois, il y a une explosion combinatoire
du nombre d’appels. Plus n est petit, plus le nombre d’exécutions de f1 est grand. En
forçant l’affichage uniquement pour des "grandes" valeurs de n, cet affichage ne sera
fait que peu de fois (et ne prendra donc pas beaucoup de temps de calcul).
5
À vous de jouer !
Concours de celui qui fera la plus belle création ! Envoyez-moi le code de vos trouvailles par email !
8
Téléchargement