Inférence à chaînage avant sans variables
=========================================

L'inférence à chaînage avant est à la base de la plupart des systèmes experts utilisés à l'heure actuelle. Cette série d'exercices a pour but d'introduire cette technique. La réalisation d'un tel système se fera en plusieurs étapes, la première se limitant à des règles sans variables. Dans les étapes suivantes, vous implémenterez un moteur d'inférence qui utilisera des règles avec variables.

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

Les modules de cette section fournissent le squelette du programme que nous allons développer. Le module ``exemple_impots_sans_variables.py`` représente le code d'un fichier test.

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

Module ``.../moteur_sans_variables/regle_sans_variables.py`` :

.. include::  ../../squelettes/inference_sans_variables/moteur_sans_variables/regle_sans_variables.py
    :code:

Module ``.../moteur_sans_variables/connaissance.py`` :

.. include::  ../../squelettes/inference_sans_variables/moteur_sans_variables/connaissance.py
    :code:

Module ``.../moteur_sans_variables/chainage.py`` :

.. include::  ../../squelettes/inference_sans_variables/moteur_sans_variables/chainage.py
    :code:

Module ``.../moteur_sans_variables/chainage_avant_sans_variables.py`` :

.. include::  ../../squelettes/inference_sans_variables/moteur_sans_variables/chainage_avant_sans_variables.py
    :code:

Module ``.../exemple_impots_sans_variables.py`` :

.. include::  ../../squelettes/inference_sans_variables/exemple_impots_sans_variables.py
    :code:

Idée de base
------------

L'idée de base d'un moteur d'inférence à chaînage avant est de déduire toutes les faits possibles à partir d'un ensemble de règles et de faits initiaux, c'est-à-dire de propositions qui sont tenues pour vraies dès le départ. Chaque fois qu'un nouveau fait est déduit, l'ensemble des règles doit être appliqué à nouveau à la base des faits : il est en effet possible que le fait nouvellement déduit permette le déclenchement d'une règle qui a déjà été essayée auparavant sans succès. Le processus d'inférence se termine lorsque plus aucun fait nouveau ne peut être déduit.

Exercice 1 : Les faits et les règles
------------------------------------

Dans cette série, les propositions ne contiendront pas de variables et seront représentées par des chaînes de caractères (``string``) contenant leur description en langage naturel. Les faits seront donc des propositions. En outre, les règles seront des clauses de Horn, et donc composées de deux parties :

* Un ensemble de conditions (des propositions qui doivent être toutes satisfaites pour que la règle se déclenche) ;
* Une seule conclusion (une proposition qui pourra le cas échéant être inséré dans la base des faits).

Les règles seront ainsi représentées par la classe ``RegleSansVariables`` du module ``regle_sans_variables.py``. Cette classe possède deux attributs : une liste de propositions, qui représentent les conditions, et une proposition, qui représente la conclusion. Pour utiliser ``RegleSansVariables``, vous devez donc compléter les méthodes :

* ``depend_de(self, fait)``, qui doit retourner ``True`` si le fait passé en argument fait partie des conditions ;
* ``satisfaite_par(self, faits)``, qui doit retourner ``True`` si toutes les conditions de déclenchement de la règle sont présentes dans la liste de faits passée en argument ;
* ``__repr__``, qui retourne une représentation d'une règle sous forme de ``string``. Cela nous permettra d'afficher les règles de manière plus pratique en utilisant la syntaxe ``print(règle)``, au lieu de ``print(règle.conditions)`` et ``print(règle.conclusion)``.

Pensez à utilisez l'opérateur ``in`` pour écrire ``depend_de`` et la méthode ``issubset`` de la classe ``set`` pour implémenter ``satisfaite_par``.

Les faits et les règles pertinents pour un problème seront collectés dans la classe ``BaseConnaissances``, qui est décrite dans le module ``connaissance.py``.

Exercice 2 : Le moteur d'inférence à chaînage avant sans variables
------------------------------------------------------------------

Vous disposez maintenant du code nécessaire pour implémenter le moteur à chaînage avant sans variables. Ce code est à implémenter dans ``chainage_avant_sans_variables.py``, en complétant la classe ``ChainageAvantSansVariables``. Notez que cette dernière est une sous-classe de la classe ``Chainage`` du module ``chainage.py`` et qu'elle hérite par conséquent des deux méthodes ``affiche_solutions`` et ``affiche_trace``, qui servent à afficher les résultats et le parcours de l'algorithme. La classe ``ChainageAvantSansVariables`` doit recevoir une instance de ``BaseConnaissances`` en tant que paramètre de son constructeur. C'est à partir du contenu de cette base de connaissance qu'elle recherchera des faits nouveaux.

Votre tâche consiste à implémenter la méthode ``chaine`` dans la classe ``ChainageAvantSansVariables``. N'oubliez pas de placer les faits déduits dans la variable ``self.solutions`` au moment où ils sont découverts. Vous pouvez également ajouter les règles et les faits à ``self.trace`` à mesure qu'ils interviennent dans l'inférence.

Pour rappel, l'algorithme à implémenter est le suivant :

::

    ChainageAvantSansVariables(faits_depart, regles)
    1.  solutions <- liste vide
    2.  Q <- faits_depart
    3.  WHILE Q n'est pas vide DO
    4.      q <- premier(Q)
    5.      Q <- reste(Q)
    6.      IF q n'est pas dans solutions THEN 
    7.          ajouter q à solutions
    8.          FOR EACH règle r de regles DO
    9.              IF r.depend_de(q) et r.satisfaite_par(solutions) THEN
    10.                 ajouter la conclusion de r en queue de Q
    11.             END IF
    12.         END FOR 
    13.     END IF
    14. END WHILE
    15. RETURN solutions	
    END ChainageAvantSansVariables

Rappelez-vous qu'en Python, les listes peuvent s'employer comme des queues grâces aux méthodes ``append`` et ``pop``.

Test du programme
-----------------

``exemple_impots_sans_variables.py`` contient les règles et les faits nécessaires pour le calcul du montant d'une réduction d'impôts. Après avoir écrit votre programme, testez-le sur un premier exemple en ajoutant l'option ``A`` et vérifiez que le fait ``'réduc-300'`` est correctement déduit. Vous pouvez afficher la trace en utilisant l'option ``trace``.

::

    python3 exemple_impots_sans_variables.py A
    python3 exemple_impots_sans_variables.py A trace

Le module contient un deuxième exemple. Quelle devrait être alors la réduction d'impôts ? 

::

    python3 exemple_impots_sans_variables.py B
    python3 exemple_impots_sans_variables.py B trace
