Python L1, TD/TP 08.04


Ceci est notre dernière droite avant le devoir surveillé. Dernière occasion pour votre auto-évaluation, pour reconnaître vos lacunes et les combler en posant des questions. Ce cours de programmation est une des matières les plus importantes en L1 Informatique ; Python est utilisé ailleurs, et en L2 il est enseigné et utilisé dans au moins trois autres enseignements. Et plusieurs projets en L3 s'appuient sur Python également.

Vous n'avez pas de vraiment nouveaux exercices pour aujourd'hui – sauf si quelqu'un le veut ! ; j'en ai plusieurs à vous offrir ... – seulement quelques suggestions, qui peuvent vous aider dans votre devoir maison. J'espère que vous avez commencé à travailler sur les automates cellulaires, et que vous comprenez que le devoir - exercice surveillé après les vacances, porte sur l'extension du système des automates, peut-être sur un nombre d'états (et de couleurs) supérieur à deux, ou/et un environnement plus grand que le voisinage immédiat.

Je confirme ce que je vous ai dit lors des TD : Votre devoir maison est un travail de deux - trois demi-journées, pas plus, même pour les débutants ; la seule inconnue est le temps passé sur la documentation et la lecture supplémentaire, et aussi sur la préparation de votre mini-rapport [dont la réalisation influencera la note !].

Exercice 1.

Construire une interface Matplotlib qui dessine sur la surface de la figure N × M, par ex. 3 × 4, boutons cliquables. Ils doivent occuper un rectangle de la surface occupant presque tout, sauf les interstices entre les boutons.

Les zones vides entre les boutons doivent être paramétrables.

La seule action de chaque bouton est de changer sa couleur de rouge à vert, et vice-versa, après le clic. Ceci pourra vous servir pour le codage de la règle d'automate de votre devoir

Vous devez lire la documentation des widgets Matplotlib. L'organisation du contrôle de l'interface ici n'est pas évident, car Matplotlib n'est pas conçu pour faire de très nombreux boutons, chacun avec son callback. Je vous rappelle l'essentiel.

On peut avoir un callback commun pour tous les boutons, ou des callbacks séparés pour chacun ; aucune solution n'est idéale, mais tout est relativement facile. Le problème est, qu'il faut savoir quel bouton a été cliqué, quel est le "propriétaire" du callback, afin de pouvoir exécuter b.color="..." et modifier la couleur du bouton. Donc, dans la première solution, il faut dans le callback fun(event) récupérer l'objet "événement", et trouver les coordonnées de la souris.
Dans la seconde solution, il faut faire une fonction pour chaque bouton. Bien sûr, on ne le codera pas à la main. La solution la plus élégante serait d'écrire un générateur de fonctions :
def cbgener(params):
   ...
   def fun(event):
      ...
      code dépend. des params
      ...
   ...
   return fun
et, en créant le bouton b=Button(...) on peut immédiatement spécifier cf=cbgener(b), passer le bouton au callback ; ensuite b.on_clicked(cf).

Quand votre callback, ou un autre fragment de code modifie des parties de votre figure, alors f.canvas.draw() doit normalement rafraîchir l'affichage. N'utilisez pas show() !

Attention ! Le système événementiel du Matplotlib est très imparfait et demande de sérieux corrections. Par exemple, si on fait ginput() sur une figure qui contient des boutons, ginput peut intercepter le clic sur un bouton, et désorganiser votre programme. Il faut être vigilant, et si besoin, m'avertir, et demander de l'aide. Si vous faites une interface pourrie, il n'y aura pas d'excuses genre "ça ne marche pas, je ne sais pas pourquoi...".


"Game of Life"

Exercice 2.

Le "Jeu de la vie" de John Conway est un automate cellulaire en 2 dimensions. Sa présentation dynamique doit donc être une vraie animation, dans le temps.

Voici l'applet de Edwin Martin. (Si vous ne le voyez pas, vérifiez l'état de Java sur votre plate-forme. C'est une de très nombreux applets sur le Web).


Vous allez coder le jeu de la vie de manière assez simple, pas vraiment interactive, mais très efficace, à l'aide des tableaux numpy, et les dispositifs d'affichage Matplotlib. Il est inutile (en fait : interdit) de faire cet exercice de manière différente (et plus banale), comme ici, ou ici. Vous devez apprendre des techniques de programmation matricielle sans boucles.

L'idée générale est la suivante. Vous savez que les tableaux numpy (par ex. contenant 0 et 1 pour les cellules mortes et vivantes) admettent l'addition élément par élément. Mais si à un tableau A on ajoute le même tableau décalé horizontalement à droite, on obtient sur un site [i,j] la somme de valeurs : de la cellule elle même et de son voisin de gauche.

Alors, si on somme les tableaux décalés à gauche, a droite, vers le bas et le haut, et dans les deux directions (les quatre sens) ensemble (mais sans l'original non-décalé), on trouvera sur [i,j] le nombre de ses voisins vivants ! D'un seul coup, pour tout le tableau, sans boucles.

Si on adopte les conditions cycliques horizontalement et verticalement, ce décalage est implémenté par l'instruction roll(tableau,décal,axe=None). Pour un tableau 2-dim aa=array([[1,2,3,4],[5,6,7,8],[9,10,11,12]]), voici le résultat de l'impression :

print(aa)            [[ 1  2  3  4]
                      [ 5  6  7  8]
                      [ 9 10 11 12]]
 
print(roll(aa,1,0))  [[ 9 10 11 12]
                      [ 1  2  3  4]
                      [ 5  6  7  8]]
 
print(roll(aa,-1,0)) [[ 5  6  7  8]
                      [ 9 10 11 12]
                      [ 1  2  3  4]]
 
print(roll(aa,1,1))  [[ 4  1  2  3]
                      [ 8  5  6  7]
                      [12  9 10 11]]
 
print(roll(aa,-1,1)) [[ 2  3  4  1]
                      [ 6  7  8  5]
                      [10 11 12  9]]
On voit que l'axe 0 fait le décalage vertical, et 1 : horizontal. L'axe 0, c'est le premier, extérieur, donc en le spécifiant, la procédure roll fait "bouger" les lignes. L'axe 1, le second – les colonnes. Si on ne spécifie pas l'axe, le tableau entier, monolithique, subira la rotation : roll(aa,1) array([[12,1,2,3],[4,5,6,7],[8,9,10,11]]).

La technique matricielle du codage évite pas seulement les boucles, mais aussi les conditionnelles, puisque la condition if a>0 ... si a est un tableau, est ambiguë ! On utilise des combinaisons algébriques, avec 1 : vrai, et 0 : faux. La multiplication c'est le "et" logique (x & y marche aussi dans ce cas), et x+y-x*y est le "ou" ; ceci dit, on peut aussi utiliser x | y. L'opération 1-x c'est la négation. Si une opération retourne une valeur reconnue comme Booléenne, et le programme proteste, que l'on ne peut ajouter 2 à un False, il suffit de multiplier le tableau booléen par 1.

Donc, avant de commencer à travailler sur l'interface, l'animation, etc., il faut travailler sur la logique du problème.


Exercice 3.

Lire et coder quelques exemples sur l'animation fonctionnelle en Matplotlib.


Semaine dernière
Retour à l'index