Projet informatique L3A : Abel Douzal et Charles Schwing

Introduction

On travaille sur les quatre recollements du carré suivants : T^2 (tore), K^2 (bouteille de Klein) et P^2 (surface de Boy)

Ci après les quatre différents recollements du carré :

Flatsurfaces.svg.png

Ci-après, des représentation (plongements) des surfaces susmentionnées :

Surfaces-info-carre-recollement.png

S^2 est la sphère de dimension 2, T^2 est le tore de dimension 2, K^2 est la bouteille de Klein, P^2 est la surface de Boy (ou le plan projectif) (que l'on appellera Charlie dans les algorithmes par souci de concision)

Ce travail se décompose en deux parties -relativement- indépendantes : La première traite de différents jeux de la vie sur des grilles recollées selon les trois surfaces susmentionnées. La seconde consiste en la création de labyrinthes sur ces surfaces, et de leur résolution. Quelques références sont présentées en fin de document.

On importe quelques modules nécessaires au fonctionnement des fonctions qui suivent :

1 JEU DE LA VIE

Un jeu de la vie est une règle d'évolution sur une grille pouvant prendre seulement deux états : vie ou mort. La vie d'une case ne dépendant que des états de ses voisins.

On cherche à créer des jeux de la vie selon les règles : conway, day & night, highlife et life 3 4. Ces règles servent à déterminer l'évolution des cellules de la grille, recollée selon les surfaces : tore, klein et charlie.

L'objectif est de réaliser une fonction d'animation des évolutions possibles. On cherche en plus à avoir une idée de l'évolution de la population totale avec le temps.

Ci dessous la fonction init_grille initialisant une grille noire (toutes les cases sont mortes), et la fonction aleat_grille initialisant une grille aléatoire peuplée selon une loi uniforme. Toutes les deux sont de taille n * m.

On définit trois fonctions de voisinage pour une case de coordonnées (i,j) de la grille selon le type de surface (tore, klein ou charlie).

Une version analogue de voisin_charlie, utilisant voisin_klein.

Ci-dessous, quatre fonctions définissant les quatre règles de vie mentionnées au dessus :

On a seulement à changer la fonction de voisinage pour changer de type de surface.

La fonction suivante donne la grille de génération suivante selon la règle choisie, et le type de surface :

La fonction suivante donne l'évolution de la population de la grille selon le temps (selon le nombre de générations) :

On définit une structure de départ (ici un objet et un planeur pour la règle conway).

L'exemple suivant donne l'évolution de la grille après 60 itérations (ou plus il suffit de modifier) selon la règle conway et le type de surface klein :

On crée à présent une fonction qui affiche une animation de l'évolution de la grille selon la règle de vie et le type de surface.

La fonction animer anime dans un fichier .gif un jeu sur une grille de taille n * m en nbrimages séquences, avec la règle de vie regle et sur la surface de type surface. Attention, cette fonction est très lente pour des grilles, ou un nombre de séquences importants.

2 LABYRINTHE

Préliminaire :

On cherche à créer des labyrinthes sans boucles (i.e. des labyrinthes ayant une structure d'arbres), aléatoirement sur des grilles recollées selon les différents types de surface : tore, bouteille de Klein et la surface de Boy (toujours appelée charlie).

Pour construire un labyrinthe sur une grille recollée au niveau de ses bords, on commence par créer un graphe (grille, c'est-à-dire que chaque sommet possède quatre voisins), et l'on pondère les arêtes selon une loi uniforme. On détermine ensuite l'arbre couvrant de poids minimum : ce sera le labyrinthe recherché.

On passera de la structure de graphe sous forme de tableau des voisins à celle sous forme de matrice d'adjacence par la conversion suivante : le point de coordonnées (i,j) de la matrice d'adjacence est représentée par l'entier m*i+j dans le tableau des voisins.

On initialise un labyrinthe de type grille où on peut se déplacer au plus dans quatre directions, cela sur une surface fermée de type tore, klein ou charlie le poids donné sur les arêtes sert uniquement à déterminer de façon aléatoire (supposée uniforme) un labyrinthe qui sera un arbre couvrant de poids minimal sur le graphe connexe pondéré ainsi formé.

Ci-après les trois fonctions qui permettent de générer sous forme de couple : tableau des voisins, liste des poids des arêtes, un graphe grille recollée selon le type de surface indiqué dans le nom de la fonction. Les poids donnés sont pris selon une loi supposée uniforme.

On privilégiera donc la fonction de init_lab_tore.

Voici une fonction qui nous sera utile dans la suite, elle retourne l'indice du premier élément égal à x dans la liste l. La fonction weight donne le poids dans le graphe pondéré (G,W) entre les sommets u et v.

Algorithme à proprement parler

On met en place la structure union-find pour gérer les partitions d'ensembles, avec améliorations, d'où un coût en O((k+n)α(n)) où α(n) est l'inverse de la fonction (n -> A(n,n)) où A est la fonction d'Ackermann (On ne prouve pas ce résultat). On le fait sous la forme d'un module/classe pour une réutilisation facile.

Algorithme de Kruskal : pour déterminer l'arbre couvrant de poids minimal dans un graphe non orienté connexe pondéré par des réels : G=(V,E,W). Le coût temporel est celui du tri des arêtes soit en O(|E|log|E|) Le graphe est représenté par le tableau de la liste des voisins (en python la liste de la liste des voisins) les poids (weight) est une matrice de flottants de taille |G|^2 ~ |E|.

Voici une fonction pour changer le format de données en sortie de kruskal en vue de l'afficher (ça peut être amélioré ou intégré à affichage, c'est comme on veut). De plus elle permet de sélectionner le type de surface du graphe utilisé :

Les fonctions suivantes sont les fonctions d'affichage des labyrinthes associés au type de surface : m est la taille du côté de la grille, e est une variable qui permet de contrôler l'empilement au niveau du graphique. La stratégie d'affichage est par "cassage de murs" : on construit une grille quadrillée noire, et on place un segment blanc sur les cloisons à éliminer (là où passe le labyrinthe) les spécificités apparaissant au niveau des bordures.

La ligne qui suit est un exemple d'effacement de cloisons sur les coins de la grille de type surface de Boy (charlie) :

On crée donc la fonction qui génère un labyrinthe aléatoirement, et qui renvoie le labyrinthe en question sous forme du graphe en format tableau de voisins (sans répétitions) de l'arbre obtenu, et de la liste des murs "à détruire".

Résolution du labyrinthe en partant de début et en arrivant à fin : on réalise un parcours en largeur, avec l'algorithme BFS bien connu (ici la fonction est générale parce-que c'est toujours utile de l'avoir), puis on affiche le chemin reliant les sommets.

Les deux fonctions suivantes affichent le chemin reliant deux points tirés aléatoirement (uniformément) dans le labyrinthe. plot_chemin l'affiche en rouge, lorsque un chemin passe par une "bordure" de la grille, elle réapparaît de l'autre côté (suivant le type de surface) et le chemin est matérialisé par une grande ligne traversante.

ci dessous une ligne exemple :

Cette fonction est la fonction recherchée : elle affiche un voyage -qui est le plus court entre 2 points- (un chemin au hasard sur un labyrinthe au hasard sur une surface choisie de taille choisie m).

3 Références

— [1] Christophe Dürr et Jill-Jênn Vie, Programmation efficace, Ellipses

— [2] Ismael Belghiti, Roger Mansuy, Jill-Jênn Vie, Les clefs pour l'info, Calvage & Mounet

— [3] Olivier Carton, Langages formels Calculabilité et complexité, Vuibert

— [4] https://fr.wikipedia.org/wiki/Homologie_(mathématiques)

usages notables : [1] a servi pour les algorithmes union-find, et kruskal [2] pour la notion de fonction d'Ackermann dans le coût de union-find [3] pour la notion de complexité

Ces ouvrages ont aussi contribué à la réalisation des autres fonctions de ce devoir, par l'enseignement qu'ils ont fourni.