Partie 1 : SAT solveur François Thiré January 16, 2017 1 Introduction Le projet logique de cette année va se découper en trois parties plus ou moins indépendantes. L’objectif est à chaque fois de vous faire découvrir comment la logique peut-être utilisée pour résoudre des problèmes. Il existe de nombreuses façon différentes d’utiliser la logique et il n’est pas possible en un semestre de présenter toutes ces façons. Cependant, voici 5 grandes catégories1 : • Les SAT solveurs • Les SMT solveurs • Les démonstrateurs automatiques • Les assistants de preuve • La programmation logique Les SAT solveurs : ce sont des outils qui permettent de répondre au problème de la satisfiabilité d’une formule exprimée en logique propositionnelle. La première partie du projet s’intéresse à cette catégorie et elle sera donc détaillée ci-dessous. Les SMT solveurs : ces outils sont également des solveurs mais cette fois du premier ordre auquel on a rajouté des théories. Des théories, il en existe des dizaines, mais on peut notamment citer : • l’arithmétique • l’arithmétique linéaire (sans la multiplication) • les nombre flottants 1 les points soulignés en gras sont ceux qui vont nous concerner 1 Les SMT solveurs sont en général basés sur un SAT solveur auquel on a rajouté une procédure de décision pour chaque théorie. Dans le cas de l’arithmétique linéaire par exemple, cette procédure est généralement l’algorithme du simplex que vous verrez si vous faites le cours Algorithmique 2. En se plaçant dans cette théorie, par exemple vous pouvez donner la formule suivante à un SMT solveur : ∀x, y, z, 2x ≥ y + 4z ∨ 2x + 3z ≥ 3(y + 1) ∨ 7y + 3 ≥ 6x + 4z et il vous répondra que c’est effectivement un théorème (était-ce évident ?). Le problème aussi bien avec les SAT solveurs que les SMT solveurs, c’est qu’ils intègrent seulement une procédure de décision. Autrement dit leur réponse peut-être : • Oui • Non • Je ne sais pas Mais imaginez que le SMT solveur ait un bug dans son code et qu’une fois de temps en temps il se trompe, comment peut-on le savoir ? Afin d’augmenter notre degré de confiance il peut-être plus intéressant de fournir une preuve (ou un trace de preuve) dudit théorème. C’est l’objet des démonstrateurs automatiques. Les démonstrateurs automatiques : ce sont des outils qui à l’instar des SMT solveurs prennent en entrée un énoncé mathématique mais cette fois essayent aussi de trouver une preuve (voir un contre-exemple). La contre-partie c’est que trouver la preuve d’un énoncé peut prendre beaucoup plus de temps que les procédures de décision que l’on trouve dans un SMT solveur. Les assistants de preuve : ce sont des outils qui comme les démonstrateurs automatiques construisent des preuves mais non plus de façon automatique mais en interagissant avec un utilisateur. On ne peut pas attendre (en tout cas aujourd’hui) des démonstrateurs automatiques de trouver des preuves de gros théorèmes (petit théorème de Fermat, théorème de Feit-Thompson, théorème de Fermat-Wiles). Heureusement, il est cependant possible d’écrire cette preuve formellement et d’avoir un outil qui vérifie si la preuve est correct. C’est ce qu’on étudiera dans la seconde partie de ce projet. La programmation logique : c’est un autre paradigme de programmation qui est très intéressant car il contient intrinsèquement du non-déterminisme. Autrement dit, on a gratuitement un algorithme qui permet de faire du backtracking (ce dont on avait besoin pour le solveur du projet Fling par exemple). Ce paradigme permet ainsi d’écrire en très peu de code des solveurs basiques pour de nombreux problèmes (notamment tous les problèmes logiques comme le sudoku, les dérivés de carrés latins, ...). Mais il est aussi utilisé en bioinformatique ou bien en traitement automatique de la langue. C’est ce qu’on verra dans la dernière partie de ce cours. 2 2 Description du projet Cette partie s’intéresse aux SAT solveurs. Un SAT solveur est un outil qui prend en entrée une formule exprimée en logique propositionnelle et renvoie une réponse : • SAT si la formule est satisfiable (accompagné généralement d’un environnement qui satisfasse la formule) • UNSAT si la formule est insatisfiable (parfois avec une trace de preuve) Ce projet va se découper en trois phases: • Implémenter une procédure de décision correcte et complète (DPLL) pour le problème • Tester cette implémentation sur un ou plusieurs problèmes • Comparer les performances avec les outils actuellement sur le marché 3 Consignes Pour ce projet, le langage n’est pas imposé. Par contre, si vous souhaitez utiliser un autre langage qu’Ocaml, prévenez-moi avant afin que je donne mon aval. Cependant, je vous recommande d’utiliser Ocaml qui est tout à fait adapté pour ce projet. Le sujet est volontairement succint et peu détaillé afin que vous soyez un maximum libre dans vos choix. 4 Un solveur naïf Avant d’implémenter l’algorithme DPLL, je vous conseille d’abord de faire un solveur très naïf afin de se mettre en jambe. Ce dernier pour résoudre une formule propositionnelle portant sur un ensemble de variables X va énumérer toutes les fonctions possibles de X 7→ B tant qu’il n’a pas trouvé un modèle. Ce solveur s’avèrera très vite inefficace mais il pourra être utile pour déboguer DPLL ainsi que pour faire des comparaisons dans la dernière partie. Cependant, je vous conseille de ne pas passer plus d’une séance sur ce solveur, c’est juste histoire de s’échauffer un peu. Bonus: Si vous cherchez un peu de challenge, je vous invite à implémenter ce solveur avec seulement des fonctions récursives terminales (et évidemment de façon purement fonctionnelle). En testant rapidement le solveur naïf, on se rend compte qu’il n’est pas possible d’aller très loin. On va donc maintenant voir comment implémenter un algorithme un peu plus malin que le solveur précédent. 3 5 Vers un solveur un peu plus malin : DPLL L’algorithme DPLL2 a été publié pour la première fois en 1962 et reste un élément essentiel dans les implémentations des SAT-solveurs aujourd’hui. L’efficacité de cet algorithme repose sur trois critères simples appliqués aux formules en forme normale conjonctive3 . Ces critères sont : • Si la formule cnf contient une clause vide, alors la formule est insatisfiable • Si la formule cnf contient une clause contenant un unique littéral, alors la valeur de ce littéral est déterminée pour la suite de la recherche • Si la formule cnf contient une variable qui apparait uniquement positivement ou uniquement négativement, alors on peut supprimer toutes les clauses où cette variable apparaît. Si aucun de ces critères n’est applicable, il faut revenir à une méthode naïve. Pour cela l’algorithme est paramétré par une méthode choose_variable qui choisit une variable sur laquelle on branche: • Premier cas on met la valeur de cette variable à true et on simplifie selon les critères ci-dessus • De même, en mettant la valeur de la variable à false A vous de jouer maintenant. Si vous souhaitez tester votre programme, je vous conseille d’implémenter le format DIMACS4 . Vous trouverez pléthore d’exemples dans ce format. 6 Transformer un problème en une formule propositionnelle Dans le monde académique, afin de tester la rapidité des solveurs SAT, il existe des concours pour comparer les meilleurs solveurs existants. Cependant, avec notre algorithme très simple on n’ira pas très loin dans ce genre de concours. Je vous propose donc dans cette partie de créer d’autres exemples afin de comparer vos solveurs. Pour cela on va prendre des problèmes logiques que l’on va transformer en une formule propositionnelle de telle sorte que le problème a une solution si et seulement si la formule propositionnelle est satisfiable. Je vous propose dans cette partie plusieurs idées de problèmes qui peuvent être intéressants à regarder. Je vous demande d’en implémenter un. L’idéal, serait que vous vous répartissiez les problèmes afin que l’on puisse comparer les résultats à la fin. Les étoiles sont une indication du temps à y passer. • Eternity II (***,*,-) 2 https://en.wikipedia.org/wiki/DPLL_algorithm 3 https://en.wikipedia.org/wiki/Conjunctive_normal_form 4 http://people.sc.fsu.edu/~jburkardt/data/cnf/cnf.html 4 • Sudoku (*,*,**) • Démineur (*,*,-) • Picross (***,*,**) • Alcazar (***,**,**) • Towers (*,*,-) • Loopy (**,**,**) • Tents (**,*,**) • Tracks (**,**,**) Pour chacun de ces problèmes, il y a deux tâches à faire plus une optionnelle (bonus) : • Encoder le problème vers une formule SAT • Générer aléatoirement des instances du problème (cela peut nécessiter la partie une) • Vérifier l’unicité de la solution Les étoiles indiquent la difficulté (ou plutôt) le temps nécessaire que j’estime pour chacune de ces trois tâches. Ce n’est qu’une idée car je n’ai pas implémenté tous ces problèmes. Evidemment, si vous vous attaquez à des problèmes plus compliqués, cela influera positivement sur votre note (si vous arrivez à la fin). 7 Benchmark Lorsqu’on a implémenté DPLL, on a vu qu’on pouvait paramétriser l’algorithme par le choix d’une variable. Il serait intéressant que vous implémentiez plusieurs choix5 afin de voir les différences : • littéral le plus présent • littéral le plus présent dans une clause minimale • UP/GUP/SUP • JW rule Pour ces différents algorithmes + l’algorithme naïf, je vous demande de faire tourner vos programmes sur les exemples générés à la partie 2 et de faire une comparaison (idéalement dans un tableau). N’hésitez-pas à laisser des commentaires pour expliquer ce que vous observez si vous le pouvez. N’hésitez-pas non plus à comparer vos solveurs. Cependant, ceci est pertinent seulement si vous utilisez le même langage et le même compilateur. 5 https://www.cs.ubc.ca/~hutter/EARG.shtml/earg/papers07/lagoudakis01learning. pdf 5