Selon Wikipédia, un “L-système ou système de Lindenmayer est un système de réécriture ou grammaire formelle, inventé en 1968 par le biologiste hongrois Aristid Lindenmayer. Un L-système modélise le processus de développement et de prolifération de plantes ou de bactéries.”
Les L-systèmes permettent d’obtenir des dessins de plantes relativement réalistes (c’est subjectif …).
Je vous propose de comprendre et programmer un type de L-sytème : la réécriture d’arête.
Vous pouvez visionner la vidéo suivante :
Résumé de l’exemple :
On commence par un segment (voyons-le comme une branche ou un tronc)
Il va être remplacé par un ensemble de segments, un motif : on peut le voir comme le motif de base
Chaque branche va ensuite être remplacée, de manière recursive, par ce motif en version réduite.
Voici les étapes successives que l’on obtient (de 0 à 4):
Lindenmayer a inventé une façon de décrire le motif de réécriture, en utilisant une longueur et un angle :
la lettre F va symboliser le dessin d’une arête, un segment (une branche si l’on parle de végétaux)
le signe - indique que l’on va tourner à droite
le signe + indique que l’on va tourner à gauche
le fait de placer des crochets autour d’une séquence indique que l’on revient en arrière, après cette séquence, avant de continuer,
Ainsi, notre motif de base donné en exemple est symbolisé par la règle suivante, appliquée avec un angle de 20 degrés et une division par 3 des longueurs à chaque itération :
F -> F[+F]F[-F]F
L’utilisation du module Turtle de Python est particulièrement adapté à cette situation, puisque :
F va correspondre à l’instruction forward(taille)
- à l’instruction rt(angle)
+ à l’instruction lt(angle)
Je vous propose de commencer par programmer directement l’exemple proposé, avant de créer un interpréteur de règle simple, qui permettra à l’utilisateur d’écrire une règle et de laisser le programme dessiner le résultat au niveau voulu.
import turtle
def arbre_branche(t, taille, angle):
""" F -> F[+F]F[-F]F"""
# F : on construit une branche tout droit
t.fd(taille)
# [+F : on construit une branche sur la gauche
t.lt(angle)
t.fd(taille)
# ] : on revient en arrière
t.bk(taille)
t.rt(angle)
# F on construit une branche tout droit
t.fd(taille)
# [-F : on construit une branche sur la droite
t.rt(angle)
t.fd(taille)
# ] : On revient en arrière
t.bk(taille)
t.lt(angle)
# F : on construit une branche tout droit
t.fd(taille)
def main():
# initialisation de Turtle
t = turtle.Turtle()
myWin = turtle.Screen()
myWin.setup(800, 600)
# On se place pour démarrer
t.lt(90)
t.up()
t.bk(250)
t.down()
t.color("green")
arbre_branche(t, 100, 20)
# La fenêtre se fermera lors d'un clic de souris
myWin.exitonclick()
# appel du programme principal
main()
turtle.mainloop() # pour empecher la fenêtre de se fermer
Dans l’exemple ci-dessus, nous avons programmé directement le dessin d’une branche.
Le procédé étant récursif, nous allons rendre la fonction arbre_branche
récursive.
Comme tout procédé récursif, nous devons avoir :
Lorsque l’on dessine une branche, on doit :
Cela donne :
import turtle
def arbre_branche(t, niveau, taille, angle):
""" F -> F[+F]F[-F]F"""
if niveau == 0:
t.fd(taille)
else:
# F : on construit une branche tout droit
arbre_branche(t, niveau - 1, taille/3, angle)
# [+F : on construit une branche sur la gauche
t.lt(angle)
arbre_branche(t, niveau - 1, taille/3, angle)
# ] : on revient en arrière
t.bk(taille/3)
t.rt(angle)
# F on construit une branche tout droit
arbre_branche(t, niveau - 1, taille/3, angle)
# [-F : on construit une branche sur la droite
t.rt(angle)
arbre_branche(t, niveau - 1, taille/3, angle)
# ] : On revient en arrière
t.bk(taille/3)
t.lt(angle)
# F : on construit une branche tout droit
arbre_branche(t, niveau - 1, taille/3, angle)
def main():
# initialisation de Turtle
t = turtle.Turtle()
myWin = turtle.Screen()
myWin.setup(800, 600)
# On se place pour démarrer
t.lt(90)
t.up()
t.bk(250)
t.down()
t.color("green")
arbre_branche(t, 2, 500, 20)
# La fenêtre se fermera lors d'un clic de souris
myWin.exitonclick()
# appel du programme principal
main()
turtle.mainloop() # pour empecher la fenêtre de se fermer
Remarquez le changement de la taille dans l’appel de la fonction, qui correspond à la taille d’une branche du niveau de départ (ici 3). À chaque itération, la taille est divisée par 3 : il faut donc adapter la taille donnée dans l’appel de la fonction pour obtenir quelque chose de réaliste. Pour le calcul de la taille finale de notre plante, il faut faire appel à des notions de mathématiques.
A chaque appel récursif, la taille va être divisée par 3, donc au bout de n appels, la taille sera de $ /frac taille {3^n} $ Mais à ce moment-là, combien y aurat-il de segments tracés sur la tige principale ? Cela dépend du nombre de F hors des crochets qu’il y a dans la règle.
On commence à obtenir quelque chose de sympathique non ?
Mais comment faire si l’on veut changer le motif (la règle) ?
Il faut tout reprogrammer …
Je vous propose plutôt de créer un interpréteur qui va lire une chaine de caractères contenant la séquence à dessiner.
Nous allons passer en argument à la fonction arbre_branche
une chaîne de caractère regle
correspondant à la règle. Pour réaliser notre dessin, il faudra parcourir cette chaine et interpréter chaque caractère.
Vous remarquerez l’utilisation d’une variable de type MaPile
qui, comme son nom l’indique, est une classe qui implémente une pile.
Si pile est du type MaPile
, vous avez accès aux méthode pile.empile(element)
et pile.depile()
.
Vous devez compléter le code ci-dessous :
import turtle
class MaPile():
""" implémentation d'une pile simple """
def __init__(self):
self.pile = []
def estVide(self):
return (False if len(self.pile) > 0 else True)
def empile(self, element):
self.pile.insert(0,element)
def depile(self):
if not self.estVide():
return self.pile.pop(0)
def arbre_branche(t, regle, niveau, taille, angle):
""" interpréteur de règle """
if niveau == 0:
t.fd(taille)
else:
pile = MaPile() # on crée une pile vide
for r in regle:
if r == "F":
# F : on construit une branche tout droit
arbre_branche(t, regle, niveau - 1, taille/3, angle)
elif r == "+":
# on construit une branche sur la gauche
t.lt(angle)
elif r == "-":
# on construit une branche sur la droite
t.rt(angle)
elif r == "[":
# on empile la position et la direction de la tortue
position = t.pos()
direction = t.heading()
pile.empile((position,direction))
elif r == "]":
# on depile la position et la direction de la tortue
...
# on replace la tortue
t.penup()
t.setpos(...)
t.seth(...)
t.pendown()
def main():
# initialisation de Turtle
t = turtle.Turtle()
myWin = turtle.Screen()
myWin.setup(800, 600)
# On se place pour démarrer
t.lt(90)
t.up()
t.bk(250)
t.down()
t.color("green")
angle = 20
regle = "F[+F]F[-F]F"
arbre_branche(t, regle, 3, 500, 20)
# La fenêtre se fermera lors d'un clic de souris
myWin.exitonclick()
# appel du programme principal
main()
turtle.mainloop() # pour empecher la fenêtre de se fermer
Il est intéressant de s’intéresser à une grammaire un peu plus évoluée. Au lieu de procéder à de la réécriture d’arête, on peut procéder à de la réécriture de noeud (node rewriting en anglais). Cela consiste à envisager des bourgeons desquels les nouvelles branches vont partir. Ces bourgeons seront symbolisés par la lettre X.
On considérera que le point de départ est un bourgeon.
A chaque itération, on remplace les X par une nouvelle branche construite selon la même règle, à une échelle inférieure.
Exemple : considérons la règle X->FF[+FX][-FX] correspondant à la chaîne de caractères ""FF[+FX][-FX]"
Voici ce que l’on obtient lors des 3 premières itérations (on a fait aparaître les bourgeons) :