Forward checking
================

Dans cette série, vous allez programmer de nouveaux algorithmes de résolution de systèmes de contraintes, plus sophistiqués que ceux de la leçon précédente : 

*  Les heuristiques du *Variable Ordering* et du *Dynamic Variable Ordering* ;
*  Programmer l'heuristique du *Forward Checking*.

Vous les testerez ensuite sur le jeu du Sudoku.

Modules squelettes
------------------

Commencez par copier les modules suivants, qui implémentent partiellement le code de l'exercice. Le module ``moteur_psc_heuristique`` fournit le squelette du programme que nous allons développer. Les modules ``exemple_forward_checking.py`` et ``exemple_sudoku.py`` sont là pour vous permettre de tester votre programme. 

:download:`forward_checking.zip <../../squelettes/forward_checking.zip>`

.. Module ``.../moteur_psc_heuristique/__init__.py`` :

.. .. include::  ../../squelettes/forward_checking/moteur_psc_heuristique/__init__.py
    :code:

Module ``.../moteur_psc_heuristique/variable_avec_label.py`` :

.. include::  ../../squelettes/forward_checking/moteur_psc_heuristique/variable_avec_label.py
    :code:

Module ``.../moteur_psc_heuristique/contrainte_avec_propagation.py`` :

.. include::  ../../squelettes/forward_checking/moteur_psc_heuristique/contrainte_avec_propagation.py
    :code:

Module ``.../psc_heuristique/moteur_psc_heuristique.py`` :

.. include::  ../../squelettes/forward_checking/moteur_psc_heuristique/psc_heuristique.py
    :code:

Module ``.../sudoku.py`` :

.. include::  ../../squelettes/forward_checking/sudoku.py
    :code:
  
Module ``.../exemple_forward_checking.py`` :

.. include::  ../../squelettes/forward_checking/exemple_forward_checking.py
    :code:

Module ``.../exemple_sudoku.py`` :

.. include::  ../../squelettes/forward_checking/exemple_sudoku.py
    :code:

Comme les méthodes que vous allez programmer dans cet exercice sont une extension du code des chapitres précédents, il est plus commode de les implémenter dans des classes-filles, qui héritent des classes que nous avons déjà développées. Nous vous fournissons ainsi la classe ``VariableAvecLabel``, qui étend la classe ``Variable``, la classe ``ContrainteAvecPropagation``, qui étend la classe ``ContrainteBinaire``, et la classe ``PSCHeuristique``, qui hérite de la classe ``PSC``.

Exercice 1 : Variable ordering
------------------------------

Dans le cas du Backtracking, comme vous avez pu le constater, les variables sont instanciées les unes après les autres dans l'ordre où elles apparaissent dans ``self.variables``. L'heuristique du *Variable Ordering* consiste à trier ces variables de façon à instancier d'abord celles dont le domaine est le plus restreint. L'idée est de commencer par les variables les plus restrictives, car ce sont celles-ci qui ont le plus de chance d'aboutir à une instanciation inconsistante, et donc à un backtrack.

Le premier exercice consiste à implémenter cet algorithme dans la classe ``PSCHeuristique``. Pour trier les variables, vous pouvez appeler la méthode ``sort()`` de la liste ``self.variables`` en passant comme paramètre ``key`` une fonction lambda qui retourne la taille du domaine. Vous pouvez vous inspirer de la documentation disponible `ici <http://wiki.python.org/moin/HowTo/Sorting#head-d121eed08556ad7cb2a02a886788656dadb709bd>`__ .
    
Vous pouvez ensuite tester votre algorithme sur le fichier ``exemple_forward_checking.py`` en vérifiant s'il améliore la performance du Backtracking.

Exercice 2 : Algorithme du forward checking
-------------------------------------------

Des heuristiques peuvent aussi être employées afin d'améliorer la recherche par rapport au Backtracking. Vous allez ainsi programmer l'heuristique connue sous le nom de *Forward Checking*. Elle a pour but d'éviter à l'avance des instanciations inconsistantes en appliquant le critère de la consistance des arcs pendant la recherche. Pour cela, il faut ajouter à chaque variable un attribut ``self.label``, qui sera initialement égal au domaine de celle-ci. Le forward checking met alors à jour ces labels en appliquant la règle suivante : à chaque instanciation d'une variable :math:`x_{k}`, on retire toutes les valeurs inconsistantes avec :math:`x_{k}` des labels des variables qui ne sont pas encore instanciées.

Dans la méthode ``forward_checking`` de la classe ``PSCHeuristique``, programmez donc l’algorithme ci-dessous : 

::

    solutions <- []
    variables <- [v1, v2, ..., vn]

    ForwardChecking(k, une_seule_solution)
    1.  IF k >= n THEN
    2.      IF NOT une_seule_solution THEN
    3.          ajouter la solution actuelle à solutions
    4.      ELSE
    5.          RETURN solutions = [solution actuelle]
    6.      END IF
    7.  ELSE
    8.      v <- variables[k]
    9.      sauvegarde_labels <- labels des variables vk, ..., vn
    10.     FOR EACH valeur de label d de la variable v DO
    11.         assigner la valeur d à la variable v
    12.         réduire le label de v à la seule valeur d
    13.         propager v=d aux labels des variables suivantes
    14.         IF v=d est consistant THEN
    15.             reste <- ForwardChecking(k+1, une_seule_solution)
    16.             IF reste != échec THEN
    17.                 RETURN reste
    18.             END IF
    19.         END IF
    20.         labels des variables <- sauvegarde_labels
    21.     END FOR
    22. END IF
    23. RETURN échec
    END ForwardChecking

La méthode ``forward_checking`` prend deux paramètres :

*  ``k`` : la profondeur courante (commence à 0) ;
*  ``une_seule_solution`` : si ``True``, retourne la première solution trouvée, sinon retourne toutes les solutions.

Les solutions seront stockées dans la variable de classe ``self.solutions``. Chaque solution sera représentée par un dictionnaire qui, à un nom de variable donné, associera la valeur de cette variable.

Les étapes 13 et 14 de l'algorithme ci-dessus sont à implémenter à l'aide de la fonction ``propagation_consistante`` de la classe ``PSCHeuristique``, que vous devez compléter. Pour chaque contrainte portant sur la variable courante et sur au moins une deuxième variable non encore instanciée, cette fonction doit appeler la méthode ``propage`` de la contrainte pour tenter de réduire le label de la deuxième variable. 

Vous devez aussi implémenter la méthode ``propage`` de la classe ``ContrainteAvecPropagation``. Pour chaque valeur possible de la deuxième variable, ``propage`` vérifiera si cette valeur est consistante avec la contrainte et la retirera du label de la variable si ce n'est pas le cas. Les deux méthodes ``propagation_consistante`` et ``propage`` devront retourner ``True`` si les contraintes peuvent être satisfaites, et ``False`` si au moins une des variables non encore instanciées n'a plus aucune valeur possible dans son label après propagation.

Lorsque vous avez terminé, vous pouvez tester votre implémentation du forward checking sur le fichier ``exemple_forward_checking.py`` en vérifiant s'il améliore la performance du Backtracking.

Exercice 3 : Dynamic variable ordering
--------------------------------------

L'heuristique du variable ordering ne trie la liste des variables qu'une seule fois, avant la recherche. Le *Dynamic Variable Ordering* est une heuristique encore plus efficace, qui trie la liste des variables par ordre croissant de la taille du label à chaque étape de la recherche. L'idée est donc de retrier la liste des variables à chaque étape :math:`k`, mais seulement à partir de la position :math:`k` (car les variables précédentes ont déjà été instanciées).
 
Implémentez cette heuristique dans la classe ``PSCHeuristique``, et appelez-la dans l'algorithme du forward checking entre les lignes 7 et 8 du pseudocode ci-dessus. Notez que vous n'avez pas besoin de trier toute la liste. En supposant que vous êtes à l'étape :math:`k`, il est plus efficace de chercher la variable possédant le plus petit label à partir de la position :math:`k` et de l'échanger avec la variable d'indice :math:`k`. Pour échanger les valeurs de deux variables ``a`` et ``b`` en Python, vous pouvez utiliser la syntaxe ``a, b = b, a``.

Testez finalement à nouveau votre algorithme sur le fichier ``exemple_forward_checking.py``, et comparez les résultats.

Exercice 4 : Sudoku
-------------------

Afin de comprendre ces algorithmes plus en détail, testez-les sur le fichier ``exemple_sudoku.py``, qui contient deux grilles de Sudoku. Essayez la grille ``A``, puis la grille ``B``. Que constatez-vous ?

::

    python3 exemple_sudoku.py A forward_checking
    python3 exemple_sudoku.py B forward_checking

Essayez maintenant d'utiliser le Backtracking. Que constatez-vous ?

::

    python3 exemple_sudoku.py A backtracking
    python3 exemple_sudoku.py B backtracking
