Ce projet implémente un moteur de traitement et de normalisation d'expressions logiques propositionnelles écrit en C++ et interfacé avec Python. Le système transforme des formules logiques complexes en Forme Normale Conjonctive (CNF) textuelle puis structurelle, permettant un export au format DIMACS pour une résolution par le solveur MiniSAT.
Ce projet tel qu'il est actuellement à pour simple but de résoudre l'inférence suivante :
Pensez à lancer la commande suivante pour compiler la bibliothèque logicEngine. Sinon le fichier inference.py ne fonctionnera pas.
make logicEngineA la fin du fichier inference.py, on va retrouver cette ligne print(verify_X_implication("file_K.txt", "file_f.txt", "file_X.txt")).
Ce print va afficher dans la console le résultat de la fonction verify_X_implication, True ou False en fonction des informations qu'il recevera en paramètre.
True: l'inférence est validéFalse: l'inférence n'est pas validé
Prenons l'exemple suivant :
file_K.txt
cafe, (gasoil > -miam)
file_f.txt
miam
file_X.txt
-miam
On inscrit ces trois formules dans trois fichier différent, qui vont être donner à la fonction verify_X_implication(file_K, file_f, file_X) qui est responsable de la résolution de l'inférence.
Dans notre exemple la fonction va renvoyé True car il n'y a pas de gasoil et donc on peut déduire que le
Mais en rajoutant du gasoil (False, car il y a du
Structure de base d'une Pile prenant des chaîne de caratère.
void stack(std::string element): Ajoute une chaîne de caractères au sommet de la pile.std::string unstack(): Retire et renvoie l'élément situé au sommet de la pile.std::string top(): Renvoie l'élément au sommet sans le supprimer de la pile.bool isEmpty(): Indique si la pile est vide.
Classe principale menant à la convertion en forme normal conjonction sous forme d'une liste de clauses.
Voici les méthodes que cette classe contient.
| méthode | description |
|---|---|
void setFormule(std::string f) |
Assigne une nouvelle chaîne de caractères à l'attribut contenant la formule. |
string getFormule() |
Renvoie la formule brute. |
string negation_formula() |
Renvoie la formule sous forme cnf mais négative. |
string nnf() |
Renvoie la formule avec les négations simplifié. |
string implFree() |
Renvoie la formule après simplification des implications, sans simplication des négations. |
string morgan() |
Renvoie la formule après application de la loi de De Morgan, sans simplification des négations. |
string implFreeNnf() |
Renvoie le résultat de implFree, avec simplification des négations. |
string morganNnf() |
Renvoie le résultat de morgan, avec simplification des négations. |
string cnf() |
Méthode principale exécutant le pipeline complet de normalisation : simplification des négation, élimination des implications, application des lois de De Morgan, puis distribution itérative des opérateurs. |
vector<vector<string>> cnfList() |
Cette méthode va simplement reprendre l'expression sous forme CNF et va en faire une liste de clauses. C'est cette liste qui sera utilisé pour facilité la génération du fichier que souhaite MiniSAT. |
vector<vector<string>> cnfListNegation() |
Renvoie la négation de la forme cnfList. |
Ces fonctions effectuent les transformations mathématiques directement sur les structures de chaînes de caractères.
| fonctions | description |
|---|---|
string implFree(string formule) |
Supprime les connecteurs d'implication (>) en appliquant l'équivalence logique : |
string nnf(string formule) |
Parcourt l'expression pour éliminé les doubles négations : |
morganStape(string formule) |
Applique une étape de la loi de De Morgan : |
string morgan(string formule) |
Applique de manière itérative la lois de De Morgan afin de distribuer et descendre les symboles de négation (-) directement devant les variables atomiques : |
string blocDistrib(string left, string right) |
Gère la distributivité de l'opérateur |
string cnfStape(string formule) |
Analyse l'expression à l'aide d'une pile. Dès qu'une sous-formule fermée est détectée, elle en extrait les composants et applique la distribution si l'opérateur principal extrait est une disjonction. |
string cnf(string formule) |
Renvoie la formule sous forme CNF. |
vector<vector<string>> cnfList(string formule) |
Analyse la chaîne CNF finale pour en extraire la structure algébrique. Les expressions séparées par des connecteurs [[A, B], [A, C]]. |
string negation_formula |
Renvoie la formule sous forme cnf mais négative : |
vector<vector<string>> cnfListNegation(string formule) |
[[-A], [-B, -C]]
|
string cleanParenthese(string formule) |
Fait un nettoyage des parenthèses parasites |
Le module de liaison binding.cxx permet de lier les méthodes ou classe C++ à Python sans perdre en éfficacité. C'est une sorte de minibibliothèque personnalisé qui s'appelle ici logicEngine. Et on a la possibilité de choisir quelles fonctions ou classes pourrons être utilisé dans le code Python.
- Liaison de la classe
Formuleavec exposition des méthodesset_formule,getFormule,cnf,cnfList,negation_formulaetcnfListNegation.
Note : la méthode
cnfetnegation_formulapeuvent techniquement être retirer de la bibliothèque. Mais dans le doûte elles ont était gardés. (On ne sait jamais pour plus tard, même si on peut changer ça très facilement)
Le fichier inference.py gère la gestion des données (fichiers txt) en entrèes, la création des fichier DIMAC CNF pour MiniSat, l'execution de MiniSat et l'algorithme principale permettant d'appliquer l'inférence :
| fonction | Description |
|---|---|
build_expressions_list(expression_file: str) -> list |
Renvoi une liste contenant les expressions du fichier expressions_file |
build_clauses_list(expression_files: list[str]) -> list |
Renvoi un cnfList liant chaque formule entre eux. |
write_dimac_file_from_clauses(clauses: list, input_file="temp_eval.cnf") -> None |
Créer un ficher DIMAC CNF d'une fomule sous forme cnfList |
build_negation_cnfList(expression_file: str) -> list |
Construit la négation d'une formule logique donnée sous forme de cnfList. |
execute_minisat(input_file="input.cnf", result_file="output.txt") -> str |
Exécute MiniSAT sur un fichier DIMACS créé et lit le résultat. |
verify_X_implication(file_K, file_f, file_X) -> str |
Renvoie True si l'inférence finale est satisfiable, sinonFalse |
Voici un exemple d'exécution de la fonction.
file_K.txt
cafe, (gasoil > -miam), gasoil
file_f.txt
miam
file_X.txt
-miam
print(verify_X_implication("file_K.txt", "file_f.txt", "file_X.txt"))Le résultat sera
FalseCar le café contient du gasoil, le gasoil va activé l'implication (gasoil > -miam) et MiniSat va détecter un "bug logique" qui va donc provoqué un résultat insatisfiable(UNSAT). Cela veut dire qu'à un certain moment MiniSat n'a pas trouver d'affectation qui pourrait satisfaire la formule. Donc on ne peut pas dire que miam est envisageable dans ces conditions.
Il faut noter que ces algorithmes n'ont pas été pensés pour être optimaux dès le départ. Donc, un très grand nombre de formules testés peut possiblement augmenter le temps d'exécution du processeur. Par exemple la fonction
blocDistribn'est pas du tout optimiser. Elle est à revoir.
L'inférence fonctionne parfaitement du moment où la syntaxe des formules est bien respecté. Les algorithmes n'étant pas pousser pour gérer les cas particulier, on va retrouver beaucoup de résultat bizzard selon la manière dont on écrit une formule.
Dans un premier voici la notation de chaque symbole mathématique vers sa version "informatique".
| mathématique | informatique | langage naturel |
|---|---|---|
> |
IMPLIES | |
& |
AND | |
| |
OR | |
- |
NOT |
Voici donc les règles à respecter pour une bonne notation des formules.
-
Une formule possedant uniquement que des littéraux simple (avec ou sans négation) et uniquement les opérateur binaire
$\land$ ou$\lor$ ne doit pas avoir de parenthèse global. Exemple :correcte incorrect concéquence cnf concéquence cnfList a & b & c(a & b & c)a & bc[[a], [b,c]]a | b | c(a | b | c)a | bc[[a,bc]]a & b | c(a & b | c)a | bc[[a,bc]]a | b & c(a | b & c)a & bc[[a], [bc]] -
Une formule respectant la même règle au dessus mais cette fois-ci comprenant des implication ne doit toujours pas prendre de paranthèse global mais l'implication dois en avoir. Exemple :
correcte incorrect concéquence cnf concéquence cnfList (a > b) & c & d((a > b) & c & d)(-a | b) & cd[[-a,b], [cd]](a > b) & c & da > b & c & da > b & c & d[[a>b], [c], [d]](a > b) & c & d(a > b & c & d)-a & bcd[[-a], [bcd]] -
Une formule qui se trouve déjà dans une situation de CNF (donc avec les parenthèse précisé), peut avoir autant de parenthèse global qu'elle le souhaite. Exemple :
correcte concéquence cnf concéquence cnfList a & (b | c)a & (b | c)[[a], [b,c]](a & (b | c))a & (b | c)[[a], [b,c]]((a & (b | c)))a & (b | c)[[a], [b,c]] -
Une formule qui se trouve dans une situation de distributivité simplie, double, ou n-distribution doit prendre des parenthèse global. En bref sans la parenthèse global la distributivité n'est pas appliqué. Exemple :
correcte incorrect concéquence cnf concéquence cnfList (a | (b & c))a | (b & c)a | (b & c)[[a,b], [c]]((a & b) | (c & d))(a & b) | (c & d)(a & b) | (c & d)[[a], [b,c], [d]]((a & b) | (c & d) | (e & f))(a & b) | (c & d) | (e & f)(a & b) | (c & d) | (e & f)[[a], [b,c], [d,e], [f]] -
Une formule qui ne compose que des implications doit prendre une parenthèse global. Exemple :
correcte incorrect concéquence cnf concéquence cnfList ((a > b) > c)(a > b) > c(-a | b) > c[[-a,b>c]]((a > b) > (c > d))(a > b) > (c > d)(-a | b) > (-c | d)[[-a,b>-c,d]]
Si vous souhaité tester une formule, rendez-vous dans le fichier Workdir/main.cxx, un test sera déjà disponible. Vous n'avez qu'à la remplacer et observer dans la console votre résultat.