<< Page principale

2. Le problème SAT

2.1. Définitions

Le problème de SATisfaisabilité (ou de satisfiabilité, hérité de l'anglais) d'un ensemble de clauses propositionnelles consiste à trouver une affectation des variables logiques propositionnelles qui fasse en sorte que toutes les clauses soient interprétées à vrai.

Ce problème est l'archétype des problèmes NP-Complets.

Pour $n$ variables, il existe $2^n$ configurations dont une partie (ou aucune) est solution du problème et on ne connaît pas d'algorithme polynomial qui permettrait d'atteindre une solution.

$$ \table \text"Configuration", x_1, x_2, ..., x_{n-1}, x_n ; 0, 0, 0, ..., 0, 0; 1, 0, 0, ..., 0, 1; 2, 0, 0, ..., 1, 0; ... ; 2^{n}-1, 1, 1, ..., 1, 1; $$

Si le problème ne possède pas de solution, il faudra alors tester toutes les configurations possibles et montrer qu'elles sont toutes insatisfiables (insatisfaisables) pour démontrer que le problème est inconsistant (insatisfiable).

Par exemple, si $n = 64$, alors $2^n = 1,8 × 10^{19}$. Si on pouvait générer et tester une configuration en 1 ns (ce qui serait très efficace), il faudrait $1,8 × 10^{10}$ secondes pour résoudre le problème soit environ 584 ans sur une seule machine.

2.2. Rappels de logique

On rappelle qu'une clause est une disjonction de littéraux de la forme :

$$ (c_1) \text" " x_1 ∨ ¬ x_2 ∨ … ∨ ¬ x_i $$

Un problème exprimé sous forme normale conjonctive (CNF) est une conjonction de disjonctions :

$$ ⋀↙{i=1}↖{k} ( ⋁↙{j=1}↖{j_k} l_j ) $$

où $l_j$ est un littéral de la forme $x_i$ ou $¬ x_i $.

Exemple

Considérons le problème suivant :

  • s'il fait beau ou il n'y a pas de vent alors je fais du vélo
  • il fait beau

Peut on en déduire si je fais ou non du vélo ?

Pour cela, on modélise le problème en remplaçant les expressions par des variables propositionnelles :

Le problème s'exprime alors sous forme logique par :

Or, une expression de la forme $X → Y$ est équivalente à $¬X ∨ Y$. La première expression donne alors :

$$ ¬(x_1 ∨ ¬ x_2) ∨ x_3 \;\;\;\; ⇒ \;\;\;\; (¬x_1 ∧ x_2) ∨ x_3 \;\;\;\; ⇒ \;\;\;\; (¬x_1 ∨ x_3) ∧ (x_2 ∨ x_3) $$

Le problème est au final représenté sous forme CNF par 3 clauses :

$$ (¬x_1 ∨ x_3) ∧ (x_2 ∨ x_3) ∧ (x_1) $$ $$ \{\table (c_1) , ¬x_1 ∨ x_3; (c_2), x_2 ∨ x_3; (c_3), x_1; $$

Dès lors, on peut résoudre le problème de deux manières différentes :

  • soit en ajoutant la négation de la conclusion (que l'on tente de démontrer) et en prouvant l'inconsistance ($H ∧ ¬C → ⊥$)
  • soit en trouvant une instanciation du problème qui montre la consistance

Si on choisit la seconde solution, on doit rendre vraies les clauses $(c_1)$, $(c_2)$, $(c_3)$. On obtient l'une des résolutions suivantes :

img   img

Dans les deux cas, on obtient les mêmes solutions :

  • $x_1 = V$, $x_2= F$, $x_3 = V$
  • $x_1 = V$, $x_2= V$, $x_3 = V$
  • Comme $x_3 = V$ dans les deux cas, on en déduit que l'on fait du vélo.

    Exercice 2.1

    Reprendre l'exercice et démontrer l'inconsistance si on ajoute la négation de la conclusion, soit $¬ x_3$, à l'ensemble de clauses précédentes. On a donc :

    $$ \{\table (c_1) , ¬x_1 ∨ x_3; (c_2), x_2 ∨ x_3; (c_3), x_1; (c4), ¬x_3 ; $$

    Ces deux résolutions font apparaître deux choses importantes :

    • il n'est pas nécessaire de générer certaines configurations puisque l'instanciation de certaines variables falsifie certaines clauses (cas de $x_1 = V$ et $x_3 = F$ pour (c_1)$
    • l'ordre dans lequel on instancie les variables possède une influence sur le temps de résolution puisqu'il permettra d'éliminer plus ou moins rapidement certaines branches de l'arbre de résolution : notamment, ici il faut commencer avec les clauses les plus petites comme $(c_3)$ et poursuivre avec les clauses qui contiennent des variables déjà instanciées comme $(c_1)$, après avoir instancié $x_1$ de $(c_3)$

    Comme nous le verrons plus tard, une méthode de force brute (brute force) va générer et tester toutes les configurations. Une méthode plus intelligente va tenter de construire une configuration qui satisfasse les clauses à mesure que l'on instancie des variables (recherche arborescente avec retour arrière).

    2.3. Algèbre de Boole

    Nous allons montrer qu'on peut utiliser une autre approche que celle de la logique pour créer un problème de satisfaction de contraintes (CSP), dont nous reparlerons plus en détail dans le chapitre suivant.

    L'algèbre de Boole, du nom du mathématicien, logicien et philosophe britannique George Boole (1815-1864), est une partie des mathématiques qui s'intéresse à une approche algébrique de la logique, alors que la logique se fonde sur des systèmes de réécriture qui consistent à manipuler des symboles. La logique possède bien évidemment un volet sémantique et l'algèbre de Boole vient renforcer la sémantique logique en remplaçant le vrai et le faux par le 1 et le 0, le et et le ou par les opérateurs $+$ et $.$ (addition et multiplication).

    Cette vision arithmétique de la logique a permis de mettre au point un système de calcul qui possède des applications dans la mise au point de circuits électroniques et autorise à aborder les problèmes de la logique sous un angle différent, ce qui peut, dans certains cas, donner la possibilité de résoudre un problème beaucoup plus simplement ou rapidement. Nous allons l'expérimenter sur le problème des pigeons.

    2.4. Définitions

    Soit un ensemble $\sc A = \{ 0, 1 \}$ pour lequel on a $0 ≤ 1$. On définit alors les opérations suivantes sur $\sc A$

    Une variable complémentée $\ov x$ est également dite signée ou négative.

    Le résultat des opérations $+$ et $.$ apparaît ci-dessous d'après la sémantique que nous avons donnée.

     a   b   a + b   a.b 
     0   0   0   0 
     0   1   1   0 
     1   0   1   0 
     1   1   1   1 
    Table de vérité

    Le quadruplet $({\sc A,+,.,\ov x)$ est appelé algèbre de Boole s'il respecte les axiomes suivants :

    Si l'on rapporte ces opérations à la logique, alors :

    Ainsi, l'expression $a + \ov a = 1$ peut s'interpréter : dire qu'une chose est vraie ou n'est pas vraie est toujours vrai. Je peux par exemple remplacer $a$ par l'énoncé il pleut, et donc, dans ce cas, il est vrai que : il pleut ou il ne pleut pas.

    De la même manière $a . \ov a = 0$ signifie qu'on ne peut pas dire une chose et son contraire. Je ne peux pas à la fois être grand et ne pas être grand.

    C'est grâce à cette modélisation de la logique sous forme d'opérations arithmétiques que l'on peut simplifier certains traitements modélisés sous forme de fonctions booléennes.

    2.4.1. Le problème des pigeons

    Le problème des pigeons est un problème logique très simple à concevoir de mise en correspondance. Nous allons le modéliser sous forme logique avant de basculer vers l'algèbre de Boole et l'algèbre classique.

    Problème des pigeons

    Etant donné $n$ pigeons et $q$ pigeonniers, chaque pigeon devant trouver un pigeonnier et un pigeonnier ne pouvant accueillir qu'au maximum un seul pigeon, restera t-il des pigeons sans pigeonnier ? Où, en d'autres termes, chaque pigeon trouvera t-il un pigeonnier ?

    Il est évident pour un cerveau humain que si $n ≤ q$, le problème possède des solutions et il n'en possède pas dès lors que $p > q$.

    Pour modéliser le problème en logique propositionnelle, il faut utiliser un ensemble de variables propositionnelles et exprimer des clauses entre les variables.

    On utilisera une matrice de $n × q$ variables propositionnelles pour représenter le problème des pigeons : $$ X(n,q) = [\table x_1^1 , x_1^2 , ⋯ , x_1^q ; x_1^2 , x_2^2 , ⋯ , x_2^q ; ⋮ , , , ⋮ ; x_n^1 , x_n^2 , ⋯ , x_n^q ; ] $$

    Si la variable $x_i^j$ est à vrai cela signifie que le pigeon $i$ est dans le pigeonnier $j$. Cela implique donc que les variables $x_i^1, ..., x_i^{j-1}, x_i^{j+1}, ..., x_i^{q}$ sont à faux. En d'autres termes, le pigeon étant dans le pigeonnier $j$, il ne peut pas être dans les autres pigeonniers.

    On doit donc exprimer deux types de contraintes :

    Dans le cas ou $n=3$ et $q=3$, le problème est modélisé comme suit, on renomme les variables afin de faciliter l'écriture des clauses :

    $$ X(3,3) = [ \table a , b , c ; d , e , f ; g , h , i ; ] = [ \table x_1^1 , x_1^2 , x_1^3; x_2^1 , x_2^2 , x_2^3; x_3^1 , x_3^2 , x_3^3; ] $$

    Soit un total de 21 clauses pour $n=3$ et $q=3$.

    Le nombre de clauses générées est défini par la formule :

    $$ n × ( 1 + ∑↙{i=1}↖{q-1} i ) + q × ( ∑↙{i=1}↖{n-1} i ) $$

    Si $n = q = 10$, on génère $910$ clauses.

    2.4.2. Résolution du problème en logique

    Pour résoudre ce problème en logique il faut utiliser un solveur ou un démonstrateur automatique de théorème comme Otter (Organized Techniques for Theorem-proving and Effective Research, [2]).

    La méthode qui permet de résoudre le problème sous forme de clauses consiste à appliquer la règle dite du Principe de Résolution définie par John Alan Robinson [3].

    A partir d'une clause qui possède une variable propositionnelle $p$ et une autre clause qui possède la variable sous forme négative ($¬ p$), on génère une nouvelle clause :

    $$ {\cl"green"{p} ∨ L \;\;\;\;\;\; \cl"green"{¬p} ∨ M}/{ L ∨ M } $$

    $L$ et $M$ étant des variables propositionnelles séparées par le symbole $∨$.

    On applique cette règle entre toutes les clauses quand cela est possible. On va donc générer un ensemble de plus en plus important de clauses à mesure que le nombre de pigeons et de pigeonniers augmentent. Les nouvelles clauses ajoutées à l'ensemble initial seront utilisées pour générer encore plus de clauses. Il se peut que l'on génère plusieurs fois la même clause, dans ce cas, si elle existe déjà, on ne l'ajoutera pas. On n'ajoutera pas également les tautologies qui sont de la forme $¬p ∨ p$, car dans ce cas $¬p ∨ p = 1$, il en résulte alors que $1 ∨ L = 1$.

    On terminera dans les deux cas suivants :

    Si le problème est insatisfiable c'est qu'on a pu générer à partir d'un sous-ensemble de clauses la clause qui se résume à un seul littéral $p$, et qu'à partir d'un autre sous-ensemble on a généré son contraire. Le problème est donc insatisfiable car on ne peut affirmer une chose et son contraire.

    Par exemple dans le cas ou $p=3$ et $q=2$, le problème n'a pas de solution et la résolution se fait en 18 étapes. Voici la preuve trouvée par Otter :

    
    ---------------- PROOF ----------------
    4 [] -p1_1| -p2_1.
    5 [] -p1_1| -p3_1.
    6 [] -p2_1| -p3_1.
    7 [] -p1_2| -p2_2.
    8 [] -p1_2| -p3_2.
    9 [] -p2_2| -p3_2.
    10 [] p1_1|p1_2.
    11 [] p2_1|p2_2.
    12 [] p3_1|p3_2.
    13 [hyper,11,7,10] p1_1|p2_1.
    14 [hyper,12,9,11] p2_1|p3_1.
    15 [hyper,12,8,10] p1_1|p3_1.
    16 [hyper,15,6,13] p1_1.
    17 [hyper,16,5,14] p2_1.
    18 [hyper,17,4,16] $F.
    

    Les variables $x_i^j$ sont renommées en pi_j car Otter considère que le symbole x est une variable du calcul des prédicats et non une variable du calcul propositionnel.

    Otter utilise ici une règle appelée hyper-résolution (HR) qui est dérivée du Principe de Résolution et on génère la clause vide, matérialisée par \$F. Par exemple, la clause 13 est obtenue par :

    A mesure que $p$ et $q$ augmentent, le nombre de clauses augmente et la résolution prend plus de temps. Si on prend $p=5$ et $q=4$, Otter prouvera qu'il n'y a pas de solution en 142 étapes.

    2.4.3. Modélisation sous forme de contraintes de cardinalité

    Pour résoudre plus simplement ce problème, on peut le modéliser sous un autre formalisme qui utilise des contraintes de cardinalité, opérateur introduit dans le cadre de la Programmation Logique avec Contraintes par Pascal Van Hentenryck et Yves Deville en 1991 [4].

    Nous utilisons ici une variante de l'opérateur pour la logique :

    $$ \#(α, β, \{ L \} )$$

    où $α$ et $β$ sont des entiers positifs ou nuls tels que $0 ≤ α ≤ β$ et $L$ est une liste de variables booléennes.

    La contrainte signifie qu'au minimum $α$ et au plus $β$ variables de $L$ sont vraies.

    On modélise alors le problème par une matrice de variables booléennes $X(n,q)$, telles que $x_i^j = 1$ signifie que le pigeon $i$ est dans le pigeonnier $j$.

    Le problème des pigeons s'exprime alors par deux types de contraintes :

    2.4.4. Résolution avec des contraintes de cardinalité

    On peut vérifier la consistance (ou l'inconsistance) du problème en utilisant un système de déduction comme celui de [1] (cf. pages 80 et 81). On utilise la règle d'extension $(Ext)$ définie ainsi :

    $$ {\#(α_1, β_1, \{ L_1 \}) \;\;\;\; \#(α_2, β_2, \{ L_2 \}) } / { \#(α_1 + α_2, β_1 + β_2, \{ L_1 ∪ L_2\})} $$

    On remplace deux contraintes de cardinalité par une seule en faisant en quelque sorte la somme des deux contraintes initiales. Pour cela, il est généralement préférable de faire en sorte que $L_1 ∩ L_2 = ∅$.

    Si on applique la règle $(Ext)$ sur l'ensemble des contraintes liées à $Ctr_L$, puis sur l'ensemble des contraintes liées à $Ctr_C$, on obtient respectivement :

    Le problème se résume donc à ces deux contraintes.

    La règle d'inconsistance $(Inc_1)$ permet de déduire rapidement si le problème possède ou pas une solution. Cette règle stipule que si on dispose de deux contraintes de cardinalité sur le même ensemble de variables alors, on a une inconsistance (donc pas de solution), si l'intersection des intervalles $[α_1,β_1]$ et $[α_2,β_2]$ est vide.

    $$ (Inc_1) \;\;\;\;\; [α_1,β_1] ∩ [α_2,β_2] = ∅ \;\;\;\; {\#(α_1, β_1, \{ L \}) \;\;\;\; \#(α_2, β_2, \{ L \}) } / { ⊥ } $$

    Et c'est bien le cas pour ce problème :

    Voici la traduction en minizinc de ce problème :

    pigeon_hole.mzn

    1. %% ========================================================
    2. %% Auteur: Jean-Michel Richer
    3. %% email: jean-michel.richer@univ-angers.fr
    4. %% ========================================================
    5. %%
    6. %% Problème des pigeons (PigeonHole Problem) :
    7. %%
    8. %%    Etant donné N pigeons et P pigeonniers, chaque
    9. %%    pigeon pourra t-il trouver un pigeonnier, sachant
    10. %%    qu'un pigeonnier ne peut accueillir qu'un seul pigeon.
    11. %%
    12. %%    Ce problème admet des solutions si N <= P.
    13. %%    Si N > P alors, il n'y a pas de solution.
    14. %%
    15. %% ========================================================
    16.  
    17. include "globals.mzn";
    18.  
    19. %% --------------------------
    20. %%         Variables
    21. %% --------------------------
    22.  
    23. int: N = 3;
    24. int: P = 3;
    25.  
    26. array[1..N,1..P] of var 0..1 : holes;
    27.  
    28. %% --------------------------
    29. %%        Constraints
    30. %% --------------------------
    31.  
    32. constraint forall(i in 1..N) (
    33.   sum(holes[i,1..P]) = 1
    34. );
    35.  
    36. constraint forall(j in 1..P) (
    37.   sum(holes[1..N,j]) <= 1
    38. );
    39.  
    40. %% --------------------------
    41. %%          Search
    42. %% --------------------------
    43.  
    44. solve satisfy;
    45.  
    46. %% --------------------------
    47. %%           Result
    48. %% --------------------------
    49.  
    50. output [
    51.   if j = 1 then "\n" else " " endif ++
    52.   show(holes[i,j]) ++ " "
    53.   | i in 1..N, j in 1..P
    54. ] ++ ["\n"];
    55.  
    include "globals.mzn";
    int: N = 3;
    int: P = 3;
    
    array[1..N,1..P] of var 0..1 : holes;
    
    constraint forall(i in 1..N) (
      sum(holes[i,1..P]) = 1
    );
    
    constraint forall(j in 1..P) (
      sum(holes[1..N,j]) <= 1
    );
    
    solve satisfy;
    
    output [
      if j = 1 then "\n" else " " endif ++
      show(holes[i,j]) ++ " "
      | i in 1..N, j in 1..P
    ] ++ ["\n"];

    On peut également le représenter sous forme entière

    2.4.5. Pour conclure

    Comme nous le verrons dans le chapitre suivant, la résolution du problème SAT peut se faire via des méthodes exactes qui peuvent prendre beaucoup de temps, mais l'utilisation de techniques comme la construction d'une solution et la vérification des inconsistances locales permet d'éliminer certaines branches de l'arbre de recherche.

    L'utilisation de méthodes approchées ne garantit pas de trouver la consistance ou l'inconsistance d'un problème. On peut alors avoir recours à des méthodes hybrides qui se basent sur la construction d'une bonne solution (méthode gloutonne) puis répération de la solution.

    Dans le cas du problème des pigeons, on peut utiliser les symétries sur la matrice des variables afin de résoudre efficacement le problème.

    2.5. Bibliographie

    1. J.-M. Richer, Une approche de résolution de problèmes en logique des prédicats fondée sur des techniques de satisfaction de contraintes. PhD thesis, Université de Bourgogne, Dijon, 1999
    2. W. W. McCune, "Otter (organized techniques for theorem-proving and effective research) 2. 0 users guide" , 3, 1990
    3. J. A. Robinson, "A machine-oriented logic based on the resolution principle", Journal of the ACM, vol. 12, p. 23–41, Jan. 1965
    4. P. V. Hentenryck and Y. Deville, “The cardinality operator : A new logical connective for constraint logic programming,” in Constraint Logic Program- ming, Selected Research. WCLP 1991, Marseilles, France (F. Benhamou and A. Colmerauer, eds.), pp. 283–403, MIT Press, 1991

    2.6. Exercice

    Mettre au point un solveur pour le problème des pigeons. On donnera au programme les paramètres que sont le nombre de pigeons ($N$) et le nombre de pigeonniers ($P$).

    On créera une classe pour créer une contrainte de cardinalité avec les paramètres $α$ et $β$ ainsi que les variables qui font partie de la contraintes.

    Les variables booléennes seront stockées sous forme d'un tableau d'entiers. Une variable contiendra une des valeurs suivantes :

    L'interprétation d'une contrainte aura comme résultat l'une des valeurs suivantes :

    On comptera le nombre de variables à Vrai :

    resolution_sat.algo

    1. /**
    2.  * sous-programme de résolution du problème avec backtrack
    3.  */
    4. procedure resoudre(variables : tableau entiers;
    5.     contraintes : tableau de Contraintes;
    6.     int indice_variable)
    7.  
    8.     // si on a affecté toutes les variables, on vérifie si toutes les contraintes
    9.     // sont satisfaites et dans ce cas il s'agit d'une solution
    10.    
    11.     si indice_variable >= taille(variables) alors
    12.         si contraintes_satisfaites(contraintes, variables) = SATISFAITES alors
    13.             affiche "Solution !"
    14.         fin si
    15.        
    16.     sinon
    17.    
    18.         // sinon, on tente d'affecté la variable courante à Vrai, puis
    19.         // si au moins une contrainte n'est pas satisfaite alors on
    20.         // ne va pas plus loin
    21.         variables[ indice_variable ] = 1
    22.         si contraintes_satisfaites(contraintes, variables) != INSATISFAITES alors
    23.             resoudre(variables, contraintes, indice_variable + 1)
    24.         fin si
    25.  
    26.         // on essaye ensuite avec l'affectation de la variable à Faux et
    27.         // si au moins une contrainte n'est pas satisfaite alors on
    28.         // ne va pas plus loin
    29.         variables[ indice_variable ] = 0
    30.         si contraintes_satisfaites(contraintes, variables) != INSATISFAITES alors
    31.             resoudre(variables, contraintes, indice_variable + 1)
    32.         fin si
    33.  
    34.         // on remet la variable dans un état non affecté
    35.         variables[ indice_variable ] = 2
    36.        
    37.     fin si
    38.    
    39. fin procedure
    40.  
    41. /**
    42.  * fonction qui vérifie si les contraintes sont satisfaites
    43.  */
    44. fonction contraintes_satisfaites(contraintes : tableau de Contraintes,
    45.                                  variables : tableau entiers) : entier
    46.    
    47.     var valeur_retour : entier =
    48.    
    49.     pour chaque contrainte de contraintes faire
    50.         selon que la contrainte est
    51.             SATISFAITE: ne rien faire;
    52.             INSATISFAITE: retourner INSATISFAITES
    53.             NON_INTERPRETABLE: retourner NON_INTERPRETABLES
    54.         fin si
    55.     fin pour
    56.    
    57.     retourner SATISFAITES
    58. fin fonction
    59.  

    On définira donc trois fichiers :

    cardinality_constraint.py

    cardinality_constraint.py

    1. SATISFIED = 1
    2. UNSATISFIED = 0
    3. NOT_EVALUABLE = 2
    4.  
    5. """
    6. PURPOSE
    7.  
    8.     Define a cardinality constraint of the form
    9.         #(alpha, beta, L)
    10.      which is satisfied if at least alpha and
    11.      at most beta literal of L are set to true
    12.      
    13. PARAMETERS
    14.    
    15.     - alpha : minimum of literals that should be true
    16.     - beta  : maximum of literals that should be true
    17.     - variables_index : list of the indices of variables
    18.    
    19. """
    20. class CardinalityConstraint(object):
    21.  
    22.     """
    23.         Constructor
    24.     """
    25.     def __init__(self, alpha, beta, variables_index):
    26.         self.alpha = alpha
    27.         self.beta = beta
    28.         self.variables_index = variables_index
    29.        
    30.     """
    31.         return
    32.         - NOT_EVALUABLE if at least one variable
    33.             is not assigned true (1) or false (0)
    34.         - SATISIFIED if all variables are assigned
    35.             a value and that the number of variables
    36.             set to true is in [alpha,beta]
    37.         - UNSATISIFIED if all variables are assigned
    38.             a value and that the number of variables
    39.             set to true is not in [alpha, beta]
    40.     """
    41.     def is_satisfied(self, p_variables):
    42.         #
    43.         # you need to implement this       
    44.         #
    45.  
    46.  

    pigeon_hole_problem.py

    pigeon_hole_problem.py

    1. import cardinality_constraint as cc
    2.  
    3. """
    4.     Class used to represent the Pigeon Hole Problem
    5.     for N pigeons and P pigeonholes
    6.    
    7.     We then have
    8.     - N*P variables
    9.     - N cardinality constraints of type #(1,1)
    10.     - P cardinality constraints of type #(0,1)
    11.    
    12.     Variables are set to
    13.     - 0 for false
    14.     - 1 for true
    15.     - 2 if not assigned
    16.    
    17. """
    18. class PigeonHoleProblem(object):
    19.  
    20.     """
    21.         Constructor given the number of pigeons (N)
    22.         and the number of pigeonholes (P)
    23.     """
    24.     def __init__(self, N, P):
    25.         self.N = N
    26.         self.P = P
    27.         self.l_variables = []
    28.         for i in range(N*P):
    29.             self.l_variables.append(2)
    30.  
    31.         self.l_constraints = []
    32.         # constraint on lines (pigeons)
    33.         for i in range(N):
    34.             l_index = [ i*P+j for j in range(P) ]
    35.             self.l_constraints.append( cc.CardinalityConstraint(1, 1, l_index) )
    36.         # constraints on columns (holes)
    37.         for j in range(P):
    38.             l_index = [ i*P+j for i in range(N) ]
    39.             self.l_constraints.append( cc.CardinalityConstraint(0, 1, l_index) )
    40.            
    41.     """
    42.         Check if all constraints are satisfied and return
    43.         - cardinality_constraint.SATISFIED
    44.             if all constraints are satisfied
    45.         - cardinality_constraint.UNSATISFIED
    46.             if at least one constraint is not satisfied
    47.         - cardinality_constraint.NOT_EVALUABLE
    48.             if at least one constraint is not evaluable
    49.             because if contains variables which are
    50.             not assigned the value true (1) or false (0)
    51.     """    
    52.     def constraints_are_satisfied(self, l_v, l_c):
    53.         #
    54.         # you need to implement this       
    55.         #
    56.        
    57.  
    58.     """
    59.         Print a solution as a matrix of N rows
    60.         and P columns given a list of variables
    61.     """
    62.     def print_solution(self, l_v):
    63.         #
    64.         # you need to implement this       
    65.         #
    66.  
    67.     """
    68.         Method that recursively solves the problem
    69.         by trying to assign the value true (1) or false (0)
    70.         to the current variable and then passes to the next
    71.         variable is there is no unsatisfied constraint.
    72.         When all variables are assigned a value then we
    73.         check if it is a solution.
    74.     """
    75.     def _solve_(self, l_v, l_c, index):
    76.         #
    77.         # you need to implement this       
    78.         #
    79.  
    80.     """
    81.         Main method that must be called to solve the Pigeon Hole
    82.         Problem. This method will print the number of solutions
    83.         found at the end.
    84.         This method calls the _solve_ method that recursively
    85.         looks for a solution
    86.     """
    87.     def solve(self):
    88.         self.nbr_solutions = 0
    89.         self._solve_(self.l_variables, self.l_constraints, 0)
    90.         print("nbr_solutions=", self.nbr_solutions)
    91.  

    pigeons.py

    pigeons.py

    1. import cardinality_constraint as cc
    2. import pigeon_hole_problem as php
    3. import sys
    4.  
    5. php = php.PigeonHoleProblem(3, 3)
    6. php.solve()
    7.