Optimisation 2 : projet Eternity II Dimitri Watel Semestre 5, 2016 Déroulement du projet Ce projet consiste en l’étude d’un problème via toutes les techniques de recherche opérationnelle que l’on vous a enseigné jusqu’à présent ou que vous connaissez. Il propose des étapes d’étude de l’aspect théorique ou appliqué. Il s’effectue en binôme. Vous devez rendre un code, un rapport et effectuer une soutenance. Le code et le rapport doivent être rendus en temps et la soutenance doit être effectuée sans quoi la note que le binôme se verra attribuée sera 0. En cas d’absence d’un des deux membres à la soutenance, la personne présente se verra le droit de soutenir seule. Si l’absence est justifiée, votre chargé de projet peut s’arranger pour faire soutenir l’autre personne un autre jour. Sinon elle se verra seule attribuée la note de 0. Le rapport Vous devez rendre le rapport dans une archive rapport.tar.gz. Le rapport en lui même doit être rédigé dans un fichier rapport.pdf. Si vous le souhaitez, vous pouvez ajouter des documents annexes dans un dossier Annexe, mais ces documents ne seront pas nécessairement relus par votre chargé de projet. Le fichier rapport.pdf doit avoir une vingtaine de pages maximum (images comprises). Il n’y a pas de minimum de page autre que celui que votre conscience vous impose. Bref, le rapport ne doit être ni insuffisant ni du remplissage inutile. Petit rappel : un rapport comporte une introduction rappelant rapidement le sujet et présentant rapidement la contribution, une conclusion, une page de garde, un sommaire, ... Les annexes servent à mettre les preuves trop longues, les tableaux trop grands, ... Dans tous les cas, le rapport doit se suffire à lui même. Par exemple, si vous décidez de placer une preuve en annexe, il peut être bon de laisser une idée de la preuve dans le rapport. Le rapport sera noté sur le fond et la forme. Le fond regroupe la justesse des preuves, la validité/pertinence des modèles de programmation linéaire et des algorithmes (pourquoi pensez vous que ce sont de bons modèles/algorithmes) et, enfin, la pertinence des évaluations numériques que vous effectuerez. La forme regroupe votre qualité à synthétiser, la qualité des images et des exemples, la clarté des propos, les fautes d’orthographe, ... Le code Vous devez rendre le code dans une archive nommée code.tar.gz. Ce code doit être commenté et il doit pouvoir se compiler sans erreur ni warning à l’aide d’un Makefile qui vous est donné. Il devra pouvoir fonctionner uniquement avec les fichiers présent dans l’archive à l’exception de possibles fichiers d’entrée et de sortie. Si une de ces conditions n’est pas respectée, la moyenne ne peut être attribuée à ce projet : il vaut mieux ne rien rendre que rendre un code qui ne compile pas ! Testez votre compilation régulièrement, et faites des sauvegardes au fur et à mesure. Vous utiliserez le langage C. Vous ne devez pas créer l’archive code.tar.gz vous-même. Une commande vous est fournie pour ce faire. Tout est expliqué dans la partie suivante. Organisation du code ; l’archive opti2.tar.gz Vous devez télécharger et décompresser l’archive opti2.tar.gz créant ainsi normalement un dossier opti2 qui contient tous les fichiers nécessaires au démarrage du projet. Cette archive contient de nombreux fichiers. Ceux qui vous intéressent sont : — Le fichier algorithm 0.c contient un modèle de code d’algorithmes. — Les fichiers algorithm exemple.c et algorithm lpexemple.c contiennent des exemples d’algorithmes. — Un fichier Makefile, déjà rédigé, pour compiler votre code. 1 — Un dossier Benchmark contenant des fichiers .input, chacun représentant une instance du problème (voir la section Fichier d’entrée et de sortie des codes, page 4 ). — Une bibliothèque proglin helper(.c,.h) pour créer et exécuter facilement un programme linéaire. — Un dossier customlibs où vous pourrez mettre des fichiers .c et .h indirectement liés au projet — Un dossier others où vous pourrez mettre tout autre fichier (par exemple, des fichiers pour générer des tests). Deux exemples vous sont donnés dans les fichiers algorithm exemple.c et algorithm lpexemple.c. Le second vous montre comment utiliser un programme linéaire. Vous pouvez tester l’archive avec les commandes suivantes $ make −j 4 $ . / a l g o r i t h m e x e m p l e exemple . i n p u t exemple . output $ . / a l g o r i t h m l p e x e m p l e exemple . i n p u t exemple . output Ce premier make est long. C’est normal, il ne devrait plus l’être par la suite. Tous les algorithmes que vous coderez seront placés dans le dossier racine opti2, sous le nom algorithm S.c et le fichier compilé se nommera algorithm S où S est le nom de votre algorithme. Votre fichier algorithm S.c doit respecter quelques contraintes afin de faciliter votre travail et la correction du chargé de projet. Pour créer un nouvel algorithme, vous êtes invités à suivre la procédure indiquée dans la partie Coder un nouvel algorithme, page 4. Votre code doit posséder au moins 2 algorithmes : algorithm exact1.c et algorithm pl1.c correspondant à la partie 2 de ce sujet. Pour compiler et exécuter votre algorithme, vous devez effectuer les commandes suivantes où S est le nom de votre algorithme et fichier.input fichier.output sont deux noms de fichiers. $ make $ . / a l g o r i t h m S f i c h i e r . i n p u t f i c h i e r . output Vous aurez peut-être besoin de fichiers .c et .h pour factoriser votre code, par exemple une bibliothèque décrivant les dictionnaires ou les listes chainées. Vous devez placer tous ces fichiers dans le dossier customlibs. En utilisant la commande make, ils seront automatiquement compris dans la chaı̂ne de compilation, quelque soit l’algorithme qui en a besoin. Il est donc, normalement inutile de toucher au fichier Makefile. Vous pouvez le constater avec la bibliothèque example déjà présente dans le dossier. Si toutefois vous êtes contraint de modifier le makefile, prévenez votre chargé de projet. Tout autre fichier que vous créez et que vous jugez utile de mettre dans l’archive code.tar.gz que vous rendrez peut être placé dans le dossier others. Par exemple, si vous générez des tests, si vous créez un fichier contenant le résultat des tests, ... Certains de vos algorithmes utiliseront une bibliothèque de programmation linéaire : deux bibliothèques de programmation linéaire vous sont proposées. Par défaut, en tapant la commande make pour compiler, vous utilisez la bibliothèque SCIP. Vous pouvez, si vous le souhaitez utiliser la bibliothèqe LPSOLVE si SCIP ne fonctionne pas sur votre machine avec la commande make LPS=TRUE. Une interface proglin helper.h est prévue pour que vous n’ayez pas à changer votre code si vous souhaitez tester la première ou la seconde bibliothèque. Vous ne devez pas créer l’archive code.tar.gz vous-même. Une commande vous est fournie pour ce faire. $ make a r c h i v e Cette commande ajoute l’archive code.tar.gz à la racine de opti2. Cette archive contient tous vos algorithmes et le contenu de vos dossiers customlibs et others. Vérifiez là avant d’envoyer votre code. Notation du code Votre code sera noté en deux parties. En premier lieu, chacun de vos fichiers algorithmes sera relu puis testé et évalué sur un ensemble d’instances. Ces instances ne sont pas exactement celles dont vous disposez dans l’archive opti2.tar.gz, mais seront générées de la même façon. Ces tests permettront uniquement de vérifier si les algorithmes répondent correctement aux entrées par rapport à ce qui est indiqué dans le rapport. Ensuite, vos deux algorithmes algorithm exact1.c et algorithm pl1.c seront comparés à ceux des autres binômes sur 10 instances de tailles au plus 15x15. Pour chaque instance, chaque algorithme sera exécuté 100 fois, puis le temps de calcul moyen est mesuré. Deux classements sont effectués pour chaque instance, un par algorithme. Un algorithme qui ne renverrait pas une solution en 2 minutes sur une des instances est retiré du classement de cette instance. Une première place rapporte 0.125 point, la seconde place 0.10, la troisième place 0.075, la quatrième 0.05, et la cinquième 0.025. Les autres places ne rapportent pas. Vous pouvez donc gagner jusqu’à 2.5 points en étant premier partout. La somme des points est arrondie au demi-point supérieur. Ces points sont bonus. 2 La soutenance Vous devez enfin effectuer une soutenance lors de la dernière séance. Sa durée est de 15 minutes + 5 minutes de questions. La soutenance n’a pas vocation à répéter oralement la totalité du rapport mais à évaluer vos qualités de présentation. Lors des 15 minutes de présentation, vous devez : présenter le sujet et vos résultats de la partie Développement. Vous pouvez également, à vos risques et périls, faire une démonstration de votre code pendant ces 15 minutes. Sachez toutefois que, présentation terminée ou non, à l’issue des 15 minutes, vous serez interrompus. Remarque : vous êtes deux sur le travail, cependant, un découpage artificiel de la présentation est inutile. Il vaut mieux qu’une seule personne présente le travail si vous avez travaillé ensemble du début à la fin. Les deux pourront répondre aux questions. L’archive à renvoyer Les archives rapport.tar.gz et code.tar.gz doivent être toutes les deux insérées dans une archive nommée opti2 binome N.tar.gz où N est votre numéro de binôme attribué à la première séance. Cette archive sera rendue seule sur exam.ensiie.fr. Dates importantes — Soutenance : vendredi 13 janvier, de 14h à 17h45. Toute absence à votre soutenance entraı̂nera la note de 0. — Rapport et code à rendre au plus tard le dimanche 15 janvier à 23h59. Tout rapport/code non rendu entraı̂nera la note de 0. 3 1 Problème étudié On étudie dans ce projet le problème de résoudre un puzzle. On dispose d’un ensemble de n × m pièces carrées de même taille. On souhaite les placer dans un rectangle ayant n pièces par ligne et m par colonne. Les pièces ont des bords droits, si bien qu’il est physiquement possible de placer chaque pièce à côté de n’importe quelle autre. Sur les bords de chaque pièces sont peintes des couleurs, distinctes ou non. On ne peut placer une pièce à côté d’une autre que si les couleurs des bords qui se touchent sont les mêmes. Dans cette version, il n’est pas possible de tourner les pièces. Un exemple de puzzle est donné en Figure 1. 1 3 3 2 2 3 2 3 1 1 3 2 2 1 1 1 3 1 1 2 2 1 2 3 1 3 1 2 3 2 3 1 Figure 1 1.1 Coder un nouvel algorithme Pour créer un nouvel algorithme dont le nom est S, vous devez copier le fichier algorithm 0.c en algorithm S.c. Vous pouvez ensuite remplir deux fonctions : — void run(int width, int height, int** pieces, int** puzzle) : le coeur de votre algorithme, qui remet les pièces du puzzle dans le bon sens. width et height indiquent la largeur et hauteur du puzzle. pieces contient l’ensemble des pièces mélangées, il s’agit d’un tableau de height · width lignes et 5 colonnes. Chaque ligne décrit l’identifiant et les 4 couleurs d’une pièce par un entier dans cet ordre : haut, gauche, bas et droite. La première pièce a pour identifiant 0, la suivante 1, ... la dernière (height · width - 1). Enfin, puzzle est un tableau de height lignes et width colonnes et est chargé de recueillir la sortie. Vous devez, avant la fin de la fonction, mettre dans puzzle[i][j] l’identifiant de la pièce que vous placeriez sur la ligne i et la colonne j du puzzle. Dans l’exemple de la Figure 1, width et height vaudraient 2. pieces serait un tableau contenant dans cet ordre la pièce en haut à gauche, celle en haut à droite, celle en bas à gauche puis la dernière en bas à droite. Il faudrait mettre dans puzzle[0][0] la valeur 1, dans puzzle[0][1] la valeur 2, dans puzzle[1][0] la valeur 3 et dans puzzle[1][1] la valeur 0. — int post process(double cpu time, int width, int height, int** pieces, int** puzzle) où vous pouvez afficher des informations dans une fichier ou dans la console. Cette fonction est appelée après run. Le paramètre cpu time est le temps de calcul de cette dernière fonction. La valeur des autres est celle des mêmes paramètres à la sortie de la fonction run. Attention ! Il ne faut pas libérer la mémoire des pointeurs pieces et puzzle dans ces fonctions. 1.2 Fichiers d’entrée et de sortie des codes Votre algorithme prendra en entrée le nom d’un fichier d’entrée .input (variable input file) et le nom d’un fichier de sortie .output (variable output file). Vous n’aurez pas à manipuler ces fichiers directement, le fichier main.c s’en charge pour vous. Toutefois savoir comment sont organisés ces fichiers peut vous intéresser, entre autres si vous voulez générer vos propres entrées. Ces deux types de fichiers commencent pas une ligne ### Commentaire, suivie par une ligne vide. Le commentaire permet d’expliquer par exemple comment le fichier est généré. Il n’est pas possible de le faire sur plus d’une ligne. Dans les fichiers d’entrée (fichiers .input) vous sont données 3 informations : les tailles n et m du puzzle et une liste de n × m pièces dans un ordre arbitraire, chacune décrite par ses 4 couleurs, séparées d’une virgule. Les 4 couleurs sont dans cet ordre : haut, gauche, bas, droite. Un identifiant est implicitement donné à chaque pièce : leur numéro dans l’ordre dans lequel elles vous sont données. La première pièce a pour identifiant 0, la suivante 1, ... la dernière le nombre de pièces moins 1. Dans le fichier de sortie (fichiers .output) sont écrites sur n × m lignes les pièces du puzzle. Elles sont écrites dans cet ordre : de gauche à droite les pièces de la ligne la plus haute du puzzle, puis la seconde ligne de gauche à droite, puis la troisième . . . jusqu’à la ligne du bas. Une pièce est décrite par son identifiant et ses 4 couleurs, séparés d’une virgule. Les couleurs sont dans cet ordre : haut, gauche, bas, droite. Dans l’exemple de la Figure 1, le fichier example.input et le fichier example.output correspondant seraient Listing 1 – .input Listing 2 – .output ### Exemple d ’ e n t r e e ### Exemple de s o r t i e 2 ,2 1 ,3 ,3 ,2 3 ,1 ,1 ,2 3 ,2 ,1 ,2 1 ,2 ,1 ,3 2 1 ,3 ,1 ,1 ,2 2 ,3 ,2 ,1 ,2 3 ,1 ,2 ,1 ,3 0 ,1 ,3 ,3 ,2 Étude commune 2.1 Modélisation Décrivez ce problème de manière formelle. On attend de vous que vous l’écriviez sous forme d’un problème de décision en détaillant l’instance et la sortie. On rappelle qu’il n’est pas possible de tourner les pièces. On nommera ce problème (PZL). Votre modèle doit être validé par votre chargé de TD avant de continuer. 2.2 Étude de la complexité On souhaite prouver que (PZL) est NP-Complet. 1. Expliquez pourquoi ce problème est dans la classe NP. 2. Pour prouver la NP-Complétude, on va effectuer une réduction de puis le problème de 3-partition. Vous pouvez décider de l’effectuer depuis un autre problème si vous le souhaitez, mais vous ne serez pas guidés. Le problème de 3 partitions consiste à savoir si un ensemble X de 3p entiers positifs peut être partitionné en p triplets de mêmes sommes. Soit p · B la somme totale des éléments de X. On suppose tous les entiers de X strictement compris entre B/2 et B/4. Ce problème est NP-Complet au sens fort. Voici des questions qui peuvent vous guider vers la réduction (a) Montrer que la somme de chaque triplet dans une solution de 3 partition est B. Montrer qu’il n’est pas possible de trouver 2 éléments ou moins de X dont la somme fait B. De même pour 4 éléments ou plus. (b) Comment forcer certaines pièces du puzzle à être posées sur un bord du puzzle ? À l’aide de cette technique, on va fabriquer un puzzle avec p + 2 lignes et B + 2 colonnes tel que, quelque soit la solution proposée, les 2p + 2B + 4 pièces de bords sont toujours les mêmes. (c) Soit i un entier. Comment forcer i pièces du puzzle à être côte à côte en ligne ? Pour chaque entier i de X, on va créer i pièces de la sorte. Soient pi ces pièces. On considère maintenant que pi est une super-pièce de hauteur 1 et de largeur i. (d) Faire en sorte qu’on puisse placer la super-pièce pi à côté d’une pièce de bord (gauche, droite, bas et haut), pour tout i ∈ X. Faire en sorte qu’on puisse placer pi à côté de pj , pour tout i, j ∈ X. (e) Montrer qu’on ne peut placer plus de 3 super-pièces côtes à côtes. Montrer ensuite que si, sur une ligne, on ne place que 2 pièces pi et pj côtes à côtes, il sera impossible de remplir le puzzle. (f) Montrer enfin que le puzzle ne peut être terminé si et seulement si on peut découper X en p triplets de même somme. Votre preuve n’est pas obligée de suivre ces indications. Dans tous les cas, votre preuve doit figurer dans le rapport sous une forme agréable à lire (dessins, découpage en lemme, théorème, ...). Vous avez le droit de demander de l’aide pour la comprendre et la terminer. 5 2.3 Algorithme exact Proposez un algorithme exact pour résoudre une instance de (PZL). Votre algorithme doit être constructif. Ce qui signifie que si le puzzle est réalisable, votre algorithme doit déterminer une manière d’agencer les pièces du puzzle. Cet algorithme ne doit pas utiliser la programmation linéaire. Vous décrirez l’algorithme dans le rapport, ainsi que sa complexité. Une preuve de l’exactitude de l’algorithme est un plus. Vous coderez ensuite cet algorithme dans un fichier algorithm exact1.c (voir la section Organisation du code : l’archive opti2.tar.gz page 1). Cet algorithme sera comparé aux algorithmes exacts des autres binômes (voir la section Notation du code page 2). 2.4 Programmation linéaire Proposez un programme linéaire pour modéliser une instance de (PZL). Votre programme doit être constructif. Ce qui signifie que si le puzzle est réalisable, votre programme doit déterminer une manière d’agencer les pièces du puzzle. Il peut être intéressant d’écrire le problème sous forme d’un problème d’optimisation pour guider le modèle vers une solution. Vous pouvez également voir le problème comme la résolution d’un problème de stable dans un graphe. Vous décrirez le programme dans le rapport, ainsi que sa taille. Une preuve de la validité du modèle est un plus. N’hésitez pas à penser à des inégalités valides si vous en connaissez. Vous coderez ensuite un algorithme construisant ce modèle, l’exécutant et déduisant une solution si elle existe dans un fichier algorithm pl1.c (voir la section Organisation du code : l’archive opti2.tar.gz page 1). Cet algorithme sera comparé aux programmes linéaires des autres binômes (voir la section Notation du code page 2). Pour manipuler un programme linéaire, vous êtes invités à utiliser la bibliothèque proglin helper(.c,.h) de l’archive opti2.tar.gz. Un exemple d’utilisation de programme linéaire vous est donné dans le fichier algorithm lpexample.c. 2.5 Évaluation des performances Proposez une évaluation de vos algorithmes algorithm exact1 et algorithm pl1. Vous êtes invités à utiliser les instances du dossier Benchmark de l’archive opti2.tar.gz. Ce dossier contient 4 sous-dossiers. easy contient des instances de petite taille ou très simple pour un humain pour vous aider à débugger. uniform contient des instances où les couleurs ont été générées aléatoirement avec une loi uniforme. Les tailles n et m varient de 5 à 50. Le nombre de couleurs k varie de 2 à 10. geometric contient des instances où les couleurs ont été générées aléatoirement avec une loi géométrique de paramètre p. Les tailles n et m varient de 5 à 50. Le nombre de couleurs k vaut 2, 4, 6, 8 ou 10 et p vaut 0.3, 0.6 ou 0.9. Enfin unique contient des instances où, pour chaque pièce, il n’y a au plus une pièce candidate pour chaque voisin (la couleur qui les uni est unique). Les tailles n et m varient de 5 à 100. Vous pouvez voir comment ces instances ont été générées avec le fichier generate.py, en python2. Vous pouvez si vous le souhaitez utiliser ce fichier pour générer d’autres instances. Le paramètre le plus intéressant à évaluer est le temps de calcul. Cependant, vous pouvez en imaginer d’autres qui peuvent expliquer le temps de calcul. Par exemple, si votre algorithme algorithm exact1 contient plusieurs parties distinguables, il peut être intéressant de savoir laquelle a pris plus de temps en fonction du type d’instance. Pour votre algorithme algorithm pl1, si vous l’avez décrit comme un problème d’optimisation, il est intéressant de savoir à quelle distance de l’optimal il se trouve, combien d’itérations il a effectuées, ... 6 3 Développement Dans cette section vous choisirez un des développements proposés et ne traiterez que celui ci. Dans cette partie, on s’intéresse au problème de maximiser le nombre de pièces posées tout en respectant la contrainte des couleurs, quitte à laisser des trous. On nomme ce problème (max-PZL). Si le puzzle est réalisable, alors une solution optimale consiste à placer toutes les pièces. Si le puzzle n’est pas réalisable, alors la solution optimale contiendra strictement moins de n × m pièces. 3.1 Approximabilité Soit r < 1, on dit qu’un algorithme A pour (max-PZL) est une approximation polynomiale de rapport r ou une r-approximation si et seulement si — La complexité en temps de A est polynomiale — Quelque soit l’instance I de solution optimale s∗ , l’algorithme A renvoie une solution s ≥ r · s∗ . Autrement dit, si le puzzle est faisable, l’algorithme arrive à placer au moins r · (n × m) pièces du puzzle. Décrivez dans votre rapport une 21 -approximation triviale (en plaçant la moitié des pièces) pour (max-PZL). On souhaite montrer qu’il est possible de construire une 95 -approximation. Voici quelques indications : 1. On suppose que le puzzle est de taille 3p × 3q et est réalisable. Montrer que, en regroupant la plupart des pièces en carrés de taille 2 pièces par 2 pièces, on peut placer au moins 59 des pièces. 2. Montrer que c’est toujours possible si le puzzle est de taille quelconque et réalisable. 3. Le problème de k-Set Packing est le suivant : soit X un ensemble quelconque et S un ensemble de sousensembles de X de taille au plus k, il faut trouver le plus grand sous-ensemble P de S constitué uniquement d’ensembles 2 à 2 disjoints. L’algorithme 1 est une k2 -approximation polynomiale pour ce problème. Utilisez ce problème et cette approximation pour construire une 59 -approximation pour (max-PZL) quand le puzzle est réalisable. Décrivez vos preuves ou vos idées dans le rapport. Algorithme 1 2 k approximation pour le problème du k-Set packing P ← ∅. Pour s ∈ S Faire Si s est disjoint avec tous les ensembles de P Alors Ajouter s à P Tant que Vrai Faire Pour p ∈ P , s, s0 ∈ S Faire Si s et s0 sont disjoints avec tous les ensembles de P \{p} Alors Retirer p de P Ajouter s et s0 à P Si P n’a pas changé Alors Renvoyer P Implémentez et évaluer la 12 et la 59 -approximations. Voici quelques perspectives en plus que vous pouvez ajouter au rapport si vous en avez le temps. — Généraliser ces résultats au cas où le puzzle n’est pas réalisable. — Que se passe-t-il si on essaie de regrouper les pièces autrement ? Peut-on améliorer le rapport d’approximation ? 3.2 Heuristique Monte Carlo On souhaite développer un algorithm de Backtracking probabiliste pour résoudre ce problème. L’idée est la suivante : savoir à tout instant si une pièce peut être placée sur des coordonnées i,j. On propose l’algorithme de backtracking suivant : poser une pièce sur un emplacement autorisé, compter, pour chaque case, combien de pièces on peut placer dessus, et, pour chaque pièce, sur combien de cases on peut la poser. Si aucune valeur ne fait 0, on continue avec une autre pièce sur une autre case. Si on tombe sur 0, le puzzle est impossible, on enlève la pièce qu’on a posée. On essaie alors de la poser ailleurs ou de changer de pièce. 7 Cet algorithme revient à faire un branch and bound très simpliste sur l’ensemble des solutions partielles. On appelle une solution partielle une configuration où certaines pièces sont déjà posées et d’autres non. Une solution partielle peut avoir des successeurs si on peut encore poser des pièces. Sinon c’est une feuille. Cet algorithme est beaucoup trop lent, car les solutions partielles sont très nombreuses. On va essayer de le guider avec une méthode de Monte Carlo. À partir d’une solution partielle P , on peut déterminer un score s(P ) et une solution réalisable f (P ) descendant de P de manière probabiliste avec l’algorithme 2. Algorithme 2 Score d’une solution partielle P et solution réalisable Entrées: Un entier h Pour i de 1 à h Faire En partant de la solution partielle P , placer au hasard des pièces jusqu’à ce que ce qu’on tombe sur une feuille fi Compter le nombre de pièces posées xi s(P ) ← la moyenne des xi , 1 ≤ i ≤ h f (P ) ← la meilleure solution fi , 1 ≤ i ≤ h Renvoyer s(P ), f (P ) On utilise ensuite l’algorithme 3 pour trouver une bonne solution partielle à notre problème. Algorithme 3 Algorithme de MonteCarlo pour (max-PZL) Entrées: Un entier w et un entier h f ← P0 ← une solution partielle où aucune pièce n’est posée. L ← [P0 ] Tant que L est non vide Faire P ← le premier élément de L, qu’on retire Pour successeur Q de P Faire Déterminer s(Q) et f (Q) avec l’algorithme 2 et l’entier h. Insérer Q dans L Si f contient moins de pièce que f (Q) Alors f ← f (Q) Trier L par score décroissant de score Ne conserver que les w premiers éléments de L Renvoyer f Implémentez et évaluez cet algorithme. Vous pouvez éventuellement le modifier. Dans quelle catégorie d’instances fonctionne-t-il le mieux ? Quel est l’impact des paramètres h et w ? ... Voici quelques perspectives en plus que vous pouvez ajouter au rapport si vous en avez le temps. — On considère qu’un générateur pseudo-aléatoire a une période après laquelle il recommence à renvoyer les mêmes chiffres. En c, la fonction int rand() de base renvoie un nombre pseudo aléatoire avec une période de 232 . En fonction de n et m, pourriez vous dire à partir de quand l’algorithme du calcul de score devient nécessairement biaisé ? — Pour revenir à l’idée du backtracking initial, on peut orienter le générateur aléatoire. Au lieu de choisir la case et la pièce à placer uniformément, on va choisir une autre loi de probabilité. Pour chaque case, on compte le nombre de pièces qu’on peut placer dessus. Trouvez une loi qui aura tendance à choisir la case avec le moins de pièces. Puis, pour toutes ces pièces, comptez le nombre de cases où on peut les placer. Trouvez une loi qui aura tendance à choisir la pièce avec le moins de cases. Ce faisant, on supprime assez peu de possibilités sur l’ensemble du puzzle, on aura donc plus de chances de placer plein de pièces. Vérifiez si les performances sont meilleures. 8