Un problème complet, du français au graphique
Ce que ce chapitre apporte
- Traduire un énoncé d'exploitation en variables booléennes, en écrivant ce que chacune signifie.
- Poser chaque contrainte séparément et compter les clauses qu'elle produit.
- Résoudre avec un solveur écrit dans la page, et relire le modèle rendu.
- Vérifier une solution contre l'énoncé, et non contre la formule.
- Dessiner le résultat pour qu'il soit lisible par quelqu'un qui ne connaît pas SAT.
- Diagnostiquer un UNSAT par un noyau insatisfiable, et distinguer minimal de minimum.
- Trouver un optimum par résolutions successives.
Le problème
Sept services doivent être placés sur des hôtes de virtualisation.
auth,auth-2,api,api-2,cache,logs,batch- Chaque hôte dispose de 3 places.
- Redondance :
authetauth-2sont deux instances du même service et ne doivent pas partager un hôte. Même règle pourapietapi-2. - Licence :
batchest lié à une licence attachée àh1, il doit y être.
Question 1 : trois hôtes suffisent-ils ? Question 2 : deux hôtes suffiraient-ils ? Question 3 : quel est le nombre minimal d'hôtes ?
Étape 1 : les variables
Sept services et trois hôtes donnent 21 variables. Et rien dans ces variables n'interdit qu'un service soit sur deux hôtes à la fois, ni qu'un hôte en héberge dix : ce sont des booléens indépendants, et tout ce qui les lie devra être écrit.
Voici la version miniature du problème, avec deux services et deux hôtes, pour manipuler les clauses avant de les produire par milliers. Les variables sont pour « le service est sur l'hôte 1 », et ainsi de suite.
- c1(a1∨a2)
- c2(¬a1∨¬a2)⇒ a2 = 0
- c3(b1∨b2)
- c4(¬b1∨¬b2)
- c5(¬a1∨¬b1)⇒ b1 = 0
- c6(¬a2∨¬b2)
Propagation unitaire : a2 = 0, b1 = 0. Ces valeurs ne sont pas des choix, elles sont imposées.
C'est exactement ce qui se produira sur les 21 variables : la plupart des affectations seront déduites, et le solveur ne décidera qu'un petit nombre de fois. Le chapitre 4 appelait cela une porte dérobée, et les problèmes de placement en ont de très petites.
Étape 2 : les contraintes, une famille à la fois
Chaque phrase de l'énoncé devient une famille de clauses, et il vaut la peine de les garder séparées : c'est ce qui permettra, plus loin, de savoir laquelle est responsable d'un échec.
Un hôte au moins. Une clause par service, de trois littéraux : . Sept clauses.
Un hôte au plus. Une clause par service et par paire d'hôtes, l'encodage par paires du chapitre 3. Trois paires pour trois hôtes, donc 21 clauses.
Places par hôte. « Au plus 3 parmi 7 » s'écrit en interdisant tous les groupes de quatre services sur un même hôte. Il y a tels groupes, et trois hôtes : 105 clauses.
Anti-affinité. Pour chaque paire redondante et chaque hôte, une clause de deux littéraux. Six clauses.
Épinglage. Une clause unitaire. Une clause.
C'est le comportement typique des contraintes de cardinalité, et il faut le voir venir. Avec 30 services au lieu de 7, cette famille passerait à clauses par hôte, ce qui n'est plus raisonnable. Le chapitre 3 donnait la parade : un encodage à variables auxiliaires, linéaire au lieu de combinatoire.
Le compte des clauses n'est donc pas une curiosité : c'est le premier signal d'alerte d'une modélisation qui ne passera pas à l'échelle.
Étape 3 : le solveur
Celui du chapitre 7, sans rien de plus. Vingt-cinq lignes, propagation unitaire comprise.
Sur vingt et une variables, cela ne change rien : la recherche tient en quelques dizaines d'étapes. Sur un million, cela fait toute la différence, et c'est le contenu des chapitres 8 et 9.
Employer le solveur du cours ici n'est donc pas une simplification abusive : c'est le bon outil pour la taille du problème, et il rend exactement la même réponse.
Étape 4 : résoudre, relire, vérifier, dessiner
Le solveur rend une liste de numéros. Il reste trois gestes, et aucun n'est facultatif.
Ce qu'il faut contrôler, c'est que la formule décrit l'énoncé. Le seul moyen est de relire la solution et de la confronter aux phrases de départ, une par une, sans passer par les clauses. Une contrainte oubliée n'apparaîtra que là.
Ce bloc de vingt lignes est donc la partie la plus importante du chapitre. Il s'écrit une fois et sert à chaque exécution.
On y voit d'un coup les places libres, la répartition inégale, et le fait que
h3 n'accueille qu'un seul service. Aucune de ces remarques ne se lit sur une liste de numéros, et toutes appellent une question à poser à celui qui a écrit l'énoncé.
Étape 5 : quand il n'y a pas de solution
Deux hôtes offrent places pour 7 services. Aucun placement n'est possible, et le solveur répondra insatisfiable.
C'est le principe des tiroirs des chapitres 4 et 6, sous son déguisement le plus ordinaire. Ici il est minuscule et se réfute instantanément ; sur trente services et vingt-neuf places, le même problème mettrait un solveur en échec, parce que toute preuve par résolution en est exponentiellement longue.
Un modélisateur averti ajoute donc ce contrôle avant de lancer quoi que ce soit : si le nombre de services dépasse le nombre de places, la réponse est connue et il est inutile de la demander.
Reste le cas intéressant : l'insatisfiabilité dont on ne voit pas la cause. Le code ci-dessous extrait un noyau insatisfiable par la méthode la plus simple, en retirant les clauses une à une.
Remarquer surtout la famille absente. La contrainte « un hôte au plus » n'apparaît pas dans le noyau : la contradiction n'a pas besoin d'elle. C'est logique, puisque autoriser un service à être sur deux hôtes ne libérerait aucune place. Le noyau vient de le démontrer sans qu'on ait eu à y penser.
C'est ce qu'on attend d'un diagnostic : il désigne les coupables et il innocente les autres.
La méthode par suppression dépend de l'ordre dans lequel les clauses sont testées. Une clause conservée tôt peut empêcher d'en retirer plusieurs autres ensuite, et un autre ordre donnerait un noyau différent, parfois plus petit.
Trouver le noyau minimum est un problème d'optimisation, nettement plus coûteux, et il n'apporte presque rien au diagnostic. Un noyau minimal suffit largement à savoir quelle phrase de l'énoncé relâcher.
Étape 6 : chercher le minimum
SAT répond par oui ou par non ; il ne sait pas optimiser. On obtient donc un minimum en posant la question plusieurs fois.
Deux améliorations classiques quand elle ne l'est pas. La recherche dichotomique sur le nombre d'hôtes, qui divise le nombre d'appels par un logarithme. Et la résolution incrémentale du chapitre 9, qui garde entre deux appels tout ce que le solveur a appris, de sorte que le deuxième appel coûte beaucoup moins que le premier.
Au-delà, il existe des outils faits pour cela : MaxSAT, qui minimise directement le nombre de contraintes souples violées, et les solveurs de contraintes pseudo-booléennes, qui savent manipuler des sommes au lieu de clauses.
C'est vrai ici, et faux en général. Les instances insatisfiables sont souvent les plus coûteuses, parce qu'il faut démontrer qu'aucune solution n'existe au lieu d'en exhiber une. Sur un problème de placement serré, chercher à prouver que hôtes ne suffisent pas peut demander bien plus de temps que de trouver une solution à .
Le nombre de clauses n'est donc pas une mesure de difficulté : c'est une mesure de taille, et le chapitre 4 a montré que les deux n'ont pas grand-chose à voir.
Passer à l'échelle
2. Les symétries. Des hôtes identiques sont interchangeables : chaque solution existe en autant de copies qu'il y a de façons de permuter les hôtes. Sur les instances insatisfiables, le solveur réfute chaque copie séparément. Le remède du chapitre 3 s'applique : imposer un ordre arbitraire entre hôtes équivalents.
3. Le solveur lui-même. Bien après les deux premiers. Passer du solveur de ce cours à un solveur CDCL réel ne devient le facteur limitant qu'une fois la modélisation propre.
L'ordre compte : optimiser le solveur avant l'encodage revient à accélérer le calcul de quelque chose qu'il ne fallait pas calculer.
Elle s'encode encore en clauses, par un compteur binaire, au prix d'un nombre important de variables auxiliaires. Mais c'est le moment où il faut se demander si SAT reste le bon outil : un solveur de contraintes pseudo-booléennes, ou un solveur de programmation linéaire en nombres entiers, traitera ce problème plus naturellement.
Savoir quand ne pas employer SAT fait partie de savoir l'employer.
Exercices type
Modifier le code pour interdire à `cache` et `logs` de partager un hôte. Que change-t-on ?
Une seule ligne : ajouter le couple à la liste des paires anti-affines.
La famille anti-affinite passe de 6 à 9 clauses, une par hôte pour la nouvelle paire. Le modèle passe de 140 à 143 clauses.
C'est tout l'intérêt d'avoir gardé les familles séparées et de les avoir produites par compréhension : une contrainte nouvelle de l'énoncé se traduit par une modification locale, et le reste du modèle ne bouge pas.
Combien de clauses produirait la contrainte de places avec 30 services et 5 places par hôte ?
Il faut interdire tous les groupes de 6 services sur un même hôte, soit groupes.
clauses par hôte. Avec dix hôtes, près de six millions de clauses pour une seule phrase de l'énoncé.
C'est inutilisable, et c'est prévisible en une multiplication faite avant d'écrire le code. L'encodage séquentiel du chapitre 3, en clauses avec des variables auxiliaires, donnerait ici quelques milliers de clauses au total.
La leçon : compter avant de générer. C'est ce que fait l'étape 2 de ce chapitre, et c'est la raison pour laquelle elle existe.
Le noyau insatisfiable ne contient pas la famille « un hôte au plus ». Pourquoi ?
Parce que cette contrainte n'aide en rien à créer la contradiction.
Le raisonnement qui rend le problème impossible est un comptage : sept services réclament chacun au moins une place, il n'y en a que six. Autoriser un service à occuper deux hôtes ne libérerait aucune place ; cela en consommerait davantage.
Retirer la contrainte « un hôte au plus » laisse donc le problème tout aussi insatisfiable, et le noyau, qui ne garde que ce qui est nécessaire, l'écarte.
C'est le genre d'information qu'un noyau donne gratuitement, et qu'une relecture manuelle du modèle ne donnerait qu'au prix d'un raisonnement.
Pourquoi vérifier la solution contre l'énoncé et non contre les clauses ?
Parce que le solveur a déjà vérifié la solution contre les clauses, et qu'il ne se trompe pas. Refaire ce contrôle ne teste que le solveur, qui n'est pas en cause.
Ce qui reste à tester est le seul endroit où une erreur peut se cacher : la traduction de l'énoncé en clauses. Une contrainte oubliée est absente de la formule, donc absente du raisonnement du solveur, donc invisible dans un contrôle qui repasse par la formule.
Le contrôle doit donc repartir des phrases de l'énoncé, une par une, et les appliquer au placement relu. C'est le seul moyen d'attraper un oubli.
On veut le placement qui laisse le plus d'hôtes complètement libres. Comment s'y prendre ?
SAT ne sait pas optimiser, il faut donc poser plusieurs questions.
Ajouter, pour chaque hôte, une variable signifiant « l'hôte est vide », liée aux placements par les clauses pour chaque service .
Puis demander successivement : existe-t-il une solution avec au moins 1 hôte vide, avec au moins 2, avec au moins 3 ? Chaque question est une contrainte de cardinalité sur les , et la dernière réponse positive donne l'optimum.
Deux remarques. La recherche dichotomique réduit le nombre d'appels. Et si la sortie doit être un compromis entre plusieurs objectifs, il vaut mieux passer à MaxSAT, conçu pour cela.
Le solveur du cours suffit-il pour ce problème ? Et pour un problème dix fois plus grand ?
Ici, oui. Vingt et une variables et cent quarante clauses se résolvent en quelques dizaines d'étapes. Aucune des techniques des chapitres 8 et 9 ne changerait le résultat, ni le temps perceptible.
Dix fois plus grand, non. Avec 70 services et 30 hôtes, deux choses se produisent dans cet ordre : l'encodage de cardinalité explose, puis, une fois celui-ci corrigé, le solveur naïf s'effondre parce qu'il repropage la formule entière à chaque affectation.
Le bon réflexe est donc d'abord de revoir l'encodage, ensuite seulement de prendre un vrai solveur. Ils se branchent au même endroit du code : la fonction resoudre est remplacée par un appel à une bibliothèque, et tout le reste du chapitre, modélisation, relecture, contrôle, dessin, reste inchangé.
1.Dans ce modèle, quelle famille de contraintes produit le plus de clauses ?
2.Pourquoi deux hôtes ne suffisent-ils pas ?
3.Le contrôle de la solution doit se faire…
4.Un noyau insatisfiable minimal est-il le plus petit possible ?
5.Pour trouver le nombre minimal d'hôtes, on…
6.En passant à 70 services, qu'est-ce qui casse en premier ?
La méthode
- Écrire l'énoncé en français et numéroter ses phrases avant de toucher au code.
- Définir les variables par une phrase « vaut 1 si et seulement si… », et une fonction de numérotation qui servira dans les deux sens.
- Une famille de clauses par phrase, gardées séparées.
- Compter les clauses de chaque famille avant de lancer quoi que ce soit.
- Relire le modèle rendu et le retraduire dans les termes du problème.
- Contrôler contre l'énoncé, phrase par phrase, jamais contre la formule.
- Dessiner, pour rendre la solution discutable par quelqu'un d'autre.
- Devant un UNSAT, extraire un noyau et regarder autant les familles présentes que les absentes.
- Optimiser par appels répétés, et changer d'outil quand les objectifs deviennent nombreux.
Synthèse
- Une variable par couple objet, valeur, et rien dans les variables ne contraint quoi que ce soit.
- Une famille de clauses par phrase de l'énoncé : c'est ce qui rend le diagnostic possible.
- Les contraintes de cardinalité dominent le modèle : ici 105 clauses sur 140.
- Compter avant de générer : vaut près de 600 000, et cela se voit avant d'écrire le code.
- Le décodeur partage sa numérotation avec l'encodeur, donc ils ne peuvent pas diverger.
- Le contrôle se fait contre l'énoncé, pas contre la formule : c'est le seul endroit où un oubli apparaît.
- Un noyau insatisfiable nomme les familles coupables et innocente les autres.
- Minimal n'est pas minimum : la méthode par suppression dépend de l'ordre.
- SAT décide, il n'optimise pas : un optimum s'obtient par appels répétés, dichotomie ou MaxSAT.
- À l'échelle, l'encodage casse avant le solveur, et les symétries avant les deux.
- Savoir quand ne pas employer SAT fait partie de savoir l'employer.