La coupure et le problème de la négation

On présente en ce chapitre le prédicat prédéfini très important qui aura pour effet de stopper la recherche de Prolog dès qu’il aura trouvé une solution dont on veut qu’elle soit unique. Ce prédicat permet de contrôler l’espace de recherche, d’économiser le travail de Prolog et de définir une négation.

Contrôle de l’exploration de l’arbre par la primitive
1. « coupure »
Le prédicat prédéfini coupure permet de contrôler l’exploration de l’arbre de recherche. En effet, lorsque cette coupure (notée cut, slash « / » ou plus souvent point d’exclamation « ! ») s’efface, celui-ci est évalué à vrai et tous les choix en attente sont supprimés entre ce nœud et celui où est apparue la coupure. « ! » signifie donc que la première solution trouvée sur sa gauche suffira, mais que Prolog cherchera toutes les solutions aux prédicats sur la droite de « ! ».

Lorsque l’on est certain de l’unicité d’une solution, la coupure empêche les retours en arrière inutiles, voire néfaste, par exemple dans :


min(X, Y, X) :- X <= Y, !.
min(X, Y, Y) :- X > Y.


On pourrait supprimer la condition X > Y de la seconde clause au cas où le troisième argument (le résultat du min) est inconnu, ce qui est l’utilisation la plus courante, mais alors min(3, 5, 5). réussirait, ce qu’il vaut mieux éviter.

Cependant, cela peut convenir dans la mesure où min sera utilisé de façon fonctionnelle pour connaître le troisième argument. Aussi, la plupart du temps les clauses suivantes suffiront :


min(X, Y, X) :- X <= Y, !.
min(X, Y, Y).


Prenons maintenant l’exemple d’un programme Prolog où P, Q, R sont des propositions. Dans l’arbre de recherche, deux sous-arbres vont être coupés (trait gras).

Là où apparaît la coupure, c’est-à-dire pour trouver les solutions de P, la recherche des solutions par W est supprimée au cas où une première solution a été trouvée par Q et les autres solutions de Q ne sont par recherchées.

Par contre, la coupure, telle qu’elle est placée, ne limite pas les solutions de R, ce qui fait que même si la clause R :- S. était placée plus bas, toutes les solutions de R sont recherchées. On demande si R est prouvable, il le sera si P ou S le
sont.


R :- P.
R :- S.
P :- U.
P :- Q, !, V.
P :- W.



La description de l’effet de la coupure dans l’arbre de recherche n’est pas facile à donner, aussi, pour un second exemple, on considère quatre programmes ne variant que par la dernière clause pi mise à la place de p. Les axiomes sont :


q(a).
q(b).
q(c).
r(b, b1).
r(c, c1).
r(a, a1).
r(a, a2).
r(a, a3).

Les règles sont successivement :

p0(X, Y) :- q(X), r(X, Y).
p1(X, Y) :- q(X), r(X, Y), /.
p2(X, Y) :- q(X), //, r(X, Y).
p3(X, Y) :- ///, q(X), r(X, Y).
pi(d, d1).

Avec i successivement 0, 1, 2, 3.

2. Le prédicat « faux » ou « impasse »
Lorsque c’est le tour du prédicat prédéfini fail (l’impasse) d’être effacé, alors la recherche continue en remontant au nœud supérieur. Tout simplement parce que fail est toujours évalué à faux. Ce prédicat ne sert quasiment que pour la négation.

Cette négation n’est pas toujours présente dans les prédicats prédéfinis des différentes versions de Prolog ; en ce cas, elle est facile à redéfinir (paragraphe suivant).
Que se passe-t-il, dans l’exemple précédent, si l’on remplace les clauses p, successivement par les clauses p4 p5 p6 correspondant à p1 p2 p3 dans lesquelles « impasse » prend la place de « coupure » ? La réponse sera no dans tous les cas puisque fail est évaluée à faux, la différence sera qu’elle sera immédiate pour p6, surviendra après recherche des solutions de q pour p5 et après recherche des solutions de q et de r pour p4.

3. Négation
Le problème qui se pose pour la négation est que le « faux » est remplacé par le concept de « non prouvable ». Cette négation n’est donc qu’une « négation par l’absence », elle peut être simulée par les deux clauses dont l’ordre est indispensable :

not(P) :- P, !, fail.
not(P).

En effet, la première clause signifie que si P est attesté, alors la coupure cessera toute recherche ultérieure et l’impasse fail forcera la tête not(P) à être fausse. La seconde clause indique que si P n’est pas présent dans la base de connaissance, alors not(P) est vrai. Par exemple « différent » (prédéfini par \==) peut être redéfini par :


dif(X, X) :- !, fail.
dif(X, Y).

Il est également possible de construire un opérateur conditionnel :

if(P, Q, R) :- P, !, Q.
if(P, Q, R) :- R.

Supposons en effet que P soit vérifié, alors, le if, sans aller voir la seconde clause, va chercher à résoudre Q. Par contre, si P n’est pas satisfait, alors Prolog passe à la seconde clause qui l’oblige à résoudre R. Par ailleurs, not(P) n’instancie rien, P peut être résolu en donnant des solutions s’il contient des variables, alors que not(P) sera un succès sans donner d’instanciation. Un exemple extrêmement réduit aidera à comprendre ce mécanisme.


etudiant(max).
etudiant(luc).
mineur(jean).
etudiantmajeur(X) :- not(mineur(X)), etudiant(X).

La question etudiantmajeur(luc). donnera vrai dans la mesure où le fait mineur(luc) n’existe pas dans la base, mais la question etudiantmajeur(X). ne donnera rien, il n’y a pas d’instanciation pour X, sauf si on inverse l’ordre des hypothèses de etudiantmajeur, car alors X = max puis X = luc prouveront d’abord etudiant(X).

Voyons maintenant l’effet de la double négation, si on reprend le petit programme :


femme(eve).
petit(eve).
femme(irma).
taille(irma, 155).
femme(julie).
taille(julie, 165).
petit(X) :- taille(X, T), T < 160.
femme(X). → X = eve ; X = irma ; X = julie ; X = carmela ; no
not(femme(eve)). → no
homme(eve). → no
not(not(femme(eve))). → yes
petit(X). → X = eve ; X = irma ; X = carmela ; no
not(petit(X)). → no
not(not(femme(X))). → X = _1039
not(not(petit(X))). → X = _1685

On voit qu’il n’y a pas d’instanciation à ces deux dernières questions.