EPITA

Introdution à Prolog, cours n° 2 : le second ordre en Prolog

 Jean-François Perrot

  1. La négation par l'échec
    1. La primitive call
    2. La négation
    3. Interprétation

  2. Analyse et synthèse de termes
    1. Analyse/synthèse des termes par les prédicats spécialisés
    2. Le prédicat très spécial "=.."

  3. Manipulation de la base de connaissances
    1. Ajouter un fait ou une clause: assert
    2. Supprimer un fait ou une clause: retract
    3. Exemple : Un petit lecteur-évaluateur interactif d'exp. arithmétiques
    4. Autre exemple de l'emploi d'assert/retract : AZERTYOP.

  4. Les prédicats setof et bagof
    1. setof(FormeInstance, LittéralPrédicat, EnsembleTrouvé)
    2. bagof(FormeInstance, LittéralPrédicat, ListeTrouvée)
    3. Questions de Logique

  5. Moteurs d'inférences, à l'ordre 0 strict
    1. Ordre 0
    2. Moteur en marche arrière
    3. Moteur en marche avant
    4.  Module d'explication, visualisant l'arbre de preuve
    5. Exemple : une base de connaissances relative aux quadrilatères
    6. Exécution

  6. Cryptarithmétique élémentaire
    1. L'exemple standard SEND+MORE = MONEY
    2. Généralisation

La négation par l'échec

  1. La primitive call

    call(Terme) Terme est un littéral.
    lance la résolution de Terme dans le contexte courant
    (analogue aux funcall et apply de Lisp)

  2. La négation

    prédicat défini par
    not(P) :- call(P) , !, fail.
    not(P).


  3. Interprétation

    la négation par échec et l'hypothèse du monde clos : ce qui n'est pas démontrable est réputé faux...

    Exemple : not(pere(jules, X))  ne cherche pas tous les X dont jules n'est pas pere,
    mais constate que jules a un fils, et répond false
    (il faut donc lire not(∃X pere(jules,X)), contrairement à la pratique ordinaire ).

    Le fond du problème est qu'une clause Prolog avec une variable libre réussit dès qu'on a trouvé une valeur idoine,
    donc s'interprète comme précédée d'un quantificateur existentiel, pas universel !

Analyse de termes

  1. Analyse des termes par les prédicats spécialisés

    functor(Terme, Fonc, Arité)
    arg(N°, Terme, Argt)


    ?- functor(la_mere(la_mere(julie)), F, A).
    F = la_mere,
    A = 1.

    ?- arg(1, la_mere(la_mere(julie)), A).
    A = la_mere(julie).


    Exemple d'emploi :

    /* subst, repris de Sterling & Shapiro p. 140
      
       subst(Old, New, Ancien, Moderne) ==
       Moderne est Ancien dans lequel on a substitué New à toutes les occurrences de Old
    */

    subst(Old, New, Old, New). % le terme lui-même
    subst(Old, New, Term, Term) :- atomic(Term), Term \= Old.
          %the backslash (\) is normally used to indicate negation in Prolog.
    subst(Old, New, Term, Autre) :-
        compound(Term),
        functor(Term, F, N),
        functor(Autre, F, N),
        subst(N, Old, New, Term, Autre). % arité 4 !
       
    subst(N, Old, New, Term, Autre) :-
        N > 0,
        arg(N, Term, ArgOld),
        subst(Old, New, ArgOld, ArgNew),
        arg(N, Autre, ArgNew),
        M is N-1,
        subst(M, Old, New, Term, Autre).
    subst(0, Old, New, Terme, Autre).


    Exécution :

    ?- subst(julie, marie, la_mere(la_mere(julie)), X).
    X = la_mere(la_mere(marie)) ;
    false.

    ?- subst(julie, marie, X, la_mere(la_mere(marie))).
    false.

    ?- subst(julie, X, la_mere(la_mere(julie)), la_mere(la_mere(marie))).
    X = marie ;
    false.

    ?- subst(X, Y, la_mere(la_mere(julie)), la_mere(la_mere(marie))).
    X = la_mere(la_mere(julie)),
    Y = la_mere(la_mere(marie)) ;
    X = la_mere(julie),
    Y = la_mere(marie) ;
    X = julie,
    Y = marie ;
    false.

  2. Le prédicat très spécial "=.."

    Terme =.. [Fonc, Arg1, Arg2,...]

    ?- X =..[pere, max, toto].
    X = pere(max, toto).


    Exemple de mise en œuvre dans la section suivante.

Manipulation de la base de connaissances

  1. Ajouter un fait ou une clause: assert

    assert(Fait)   :
            Ex: assert(pere(max, toto)).
    ?- assert(pere(max, toto)).
    ERROR: assert/1: No permission to modify static_procedure `pere/2'

    N'est possible (en SWI-Prolog) que si le prédicat binaire pere a été déclaré dynamic
    à la compilation : ":- dynamic pere/2." doit figurer dans le fichier source.

    assert(Clause) : assert((Tete :- Corps))
    Attention au double parenthésage !
    ?- assert(
    |    (oncle_pat(X,Y) :-
    |    pere(U, Y), frere(U, X)
    |      )).
    true.

    avec deux variantes asserta (ajoute en tête du paquet de clauses) et assertz (id. en queue).
  2. Supprimer un fait ou une clause: retract

    Même syntaxe.
    son allié retractall pour prédicats dynamiques : si pere avait été déclaré dynamic : retractall(pere(X, Y)).
    à rapprocher de abolish pour prédicats statiques : dans le cas contraire abolish(pere/2).

  3. Exemple : Un petit lecteur-évaluateur interactif d'exp. arithmétiques


    :- dynamic base/2.

    go :- retractall(base(X, Y)), % DPFTR
          encore.

    encore :- nl, write('Lecture : '), read(Lu), traite(Lu).

    traite($) :- !, write('Au-revoir'), nl.

    traite(Exp) :- eval(Exp, Val), nl, write('Valeur de '), write(Exp),
            write(' = '), write(Val), nl, encore.

    eval(Exp, Exp) :- number(Exp), !.
    eval(Exp, Val) :- atomic(Exp), !, valbase(Exp, Val).
    eval(Exp, Val) :- functor(Exp, F, 2),
                      arg(1, Exp, Ag), arg(2,Exp,Ad),
                      eval(Ag, Valg), eval(Ad, Vald),
                      T =..[F, Valg, Vald], Val is T.

    valbase(Var, Val) :- base(Var, Val), !.
    valbase(Var, Val) :- nl, write('Valeur de '), write(Var), write(' ? '),
                read(Val), assert(base(Var, Val)). % une seule valeur par "variable"



    Exécution :
    ?- go.

    Lecture : x+y-2*x.

    Valeur de x ? 3.

    Valeur de y ? 4.

    Valeur de x+y-2*x = 1

    Lecture : x+y+z.

    Valeur de z ? 3.

    Valeur de x+y+z = 10

    Lecture : '$'.
    Au-revoir
    true.

  4. Autre exemple de l'emploi d'assert/retract : AZERTYOP.

    Exemple de conversation :

    ?- azer.

    À vos ordres M'sieu !

    |: montre.
    1 est par terre
    2 est par terre
    3 est par terre
    4 est sur 1
     Et voila tout !


    À vos ordres M'sieu !

    |: pose(4,4).
    Ça s'peut pas M'sieu !


    À vos ordres M'sieu !

    |: pose(4,1).
    Il y est déjà M'sieu !


    À vos ordres M'sieu !

    |: poser(1,4).
    J'compran pas M'sieu !
                   poser(1, 4)

    À vos ordres M'sieu !

    |: pose(1,4).
    J'ai posé 4 par terre
    J'ai posé 1 sur 4

    À vos ordres M'sieu !

    |: pose(9,10).
    9 n'est pas un cube !
    10 n'est pas un cube !
    Y m'faut des cubes M'sieu !

    À vos ordres M'sieu !

    |: pose(3,1).
    J'ai posé 3 sur 1

    À vos ordres M'sieu !

    |: montre.
    2 est par terre
    4 est par terre
    1 est sur 4
    3 est sur 1
     Et voila tout !


    À vos ordres M'sieu !

    |: pose(4,2).
    J'ai posé 3 par terre
    J'ai posé 1 par terre
    J'ai posé 4 sur 2

    À vos ordres M'sieu !

    |: fin.
    Au revoir
    false.



    Le programme

    /****************************************************************************/

    /*               A Z E R T Y O P de P. Greussay,
                     adapté à Prolog par J-F. Perrot
                                                                                  */
    /******************************************************************************/

    /*   Utilitaires

    printlist([]):- nl.
    printlist([T|C]):- write(T),printlist(C).
    */
    printlist(L) :- maplist(write, L), nl.
     
    /*   L'univers d'AZERTYOP   */

    cube(0).
    cube(1).
    cube(2).
    cube(3).
    cube(4).

    :- dynamic sur/2.
    init :-
        retractall(sur(X, Y)),
        assert(sur(1,0)),
        assert(sur(4,1)),
        assert(sur(2,0)),
        assert(sur(3,0)).

    /*  D'abord une boucle infinie d'interaction "toplevel"         */

    azer :- init, encore.
    encore :-
            nl, printlist(['À vos ordres M\'sieu ! ']), nl,
               read(C), exec(C),
            encore.

    /*           Les tâches d'AZERTYOP                            */

    exec(fin):- !,printlist(['Au ', revoir]), fail.
    exec(montre):- !, montre.
    exec(pose(X,Y)):- verifie(X,Y), !, pose(X,Y).
    exec(pose(X,Y)):- !, printlist(['Y m\'faut des cubes M\'sieu ! ']).
    exec(X):- printlist(['J\'compran pas M\'sieu ! ']),
                tab(15), write(X), nl.

    montre:- sur(X,0), write(X), write(' est par terre'), nl, fail.
    montre:- sur(X,Y), Y \= 0, write(X), write(' est sur '), write(Y),
             nl, fail.
    montre:- printlist([' Et voila tout !']), nl.

    pose(0,X):- !, printlist(['J\'peux pas poser la terre M\'sieu !']), nl.
    pose(X,X):- !, printlist(['Ça s\'peut pas M\'sieu !']), nl.
    pose(X,Y):- sur(X,Y), !, printlist(['Il y est déjà M\'sieu !']), nl.
    pose(X,Y):- degager(X), degager(Y),
                  retract(sur(X,_)), !,    /* pas de back-track sur "retract" */
                  assert(sur(X,Y)), chante(X,Y).

    chante(X,0) :- !, write('J\'ai posé '), write(X), write(' par terre'), nl.
    chante(X,Y) :- write('J\'ai posé '), write(X), write(' sur '), write(Y), nl.
     
    degager(0):- !.
    degager(X):- sur(Y,X), !, pose(Y,0).
    degager(X).

    verifie(X,Y):- cube(X), !, verif2(Y).
    verifie(X,Y):- printlist([X, ' n\'est pas un cube !']), verif2(Y), fail.
    verif2(Y) :- cube(Y), !.
    verif2(Y) :- printlist([Y, ' n\'est pas un cube !']), fail.



Les prédicats setof et bagof

  1. setof(FormeInstance, LittéralPrédicat, EnsembleTrouvé)

    l'ensemble étant représenté par une liste ordonnée (ordre standard des termes) sans  répétition.

    Les variables du terme FormeInstance ne doivent pas réapparaître ailleurs que dans le LittéralPrédicat.
    Elles sont liées dans le setof.

    Exemple (avec la seconde famille) :
    ?- setof(X, pere(cesar, X), S).
    S = [adrienne, marc].


    ?- setof(mere(X), pere(cesar, X), S).
    S = [mere(adrienne), mere(marc)].


    Backtrack sur variable libre  dans LittéralPrédicat :
    ?- setof(Y, pere(Y, X), S).
    X = adrienne,
    S = [cesar] ;
    X = antoine,
    S = [marc] 
    ;
    X = cesar,
    etc.

  2. bagof(FormeInstance, LittéralPrédicat, ListeTrouvée)

    La normalisation de la liste des résultats opérée par setof coûte cher en temps de calcul.
    Le prédicat bagof fait la même chose sans normaliser,
    donc avec les éventuelles répétitions et suivant l'ordre de la démonstration.
    Il répond donc exactement au besoin de manipuler par programme la liste complète des résultats.

    Exemple :

    /* Enumeration des chemins d'un graphe sans circuit */

    Graphe

    /* Le graphe est donne' par le predicat "arc", ses sommets sont des
    atomes */

    chemin(0, Org, [Org]). /* "Org" comme "origine" */
    chemin(R, Org, [Org|S]):- arc(Org, Next), RN is R-1,
                         chemin(RN, Next, S).
                         
    lesChemins(R, Org, Liste) :- bagof(Ch, chemin(R, Org, Ch), Liste).

    showChemins(R, Org) :- lesChemins(R, Org, Liste), lwrite(Liste).

    lwrite([]) :- write('Et voila tout !').
    lwrite([Ch|S]) :- write(Ch), nl, lwrite(S).
    % mieux que  maplist(write, Liste)

    /* Premiere generation */

    /* Le couple (a1, b1) a trois enfants a2, b2, c2 */
    arc(a1, a2). arc(b1, a2). arc(a1, b2). arc(b1, b2).
    arc(a1, c2). arc(b1, c2).
    /* Le couple (x1, y1) en a deux x2, y2 */
    arc(x1, x2). arc(y1, x2). arc(x1, y2). arc(y1, y2).
    /* Le couple (u1, v1) en a deux u2, v2 */
    arc(u1, u2). arc(v1, u2). arc(u1, v2). arc(v1, v2).
    /* Le couple (z1, t1) en a un  t2 */
    arc(z1, t2). arc(t1, t2).

    /* Deuxieme generation */

    /* a2 avec y2 engendrent x3 et y3 */
    arc(a2, x3). arc(y2, x3). arc(a2, y3).  arc(y2, y3).
    /* b2 avec v2 engendrent u3 et v3 */
    arc(b2, u3). arc(v2, u3). arc(b2, v3).  arc(v2, v3).
    /* x2 avec c2 engendrent c3 et t3 */
    arc(x2, c3). arc(c2, c3). arc(x2, t3). arc(c2, t3).
    /* u2 avec t2 engendrent a3 et b3 */
    arc(u2, a3). arc(t2, a3). arc(u2, b3).  arc(u2, b3).

    /* Troisieme generation */

    /* a3 avec y3 engendrent x4 et y4 */
    arc(a3, x4). arc(y3, x4). arc(a3, y4).  arc(y3, y4).
    /* b3 avec v3 engendrent u4 et v4 */
    arc(b3, u4). arc(v3, u4). arc(b3, v4).  arc(v3, v4).
    /* x3 avec c3 engendrent c4 et t4 */
    arc(x3, c4). arc(c3, c4). arc(x3, t4). arc(c3, t4).
    /* u3 avec t3 engendrent a4 et b4 */
    arc(u3, a4). arc(t3, a4). arc(u3, b4).  arc(u3, b4).

    /* Quatrieme generation */

    /* a4 avec y4 engendrent x5 et y5 */
    arc(a4, x5). arc(y4, x5). arc(a4, y5).  arc(y4, y5).
    /* b4 avec v4 engendrent u5 et v5 */
    arc(b4, u5). arc(v4, u5). arc(b4, v5).  arc(v4, v5).
    /* x4 avec c4 engendrent c5 et t5 */
    arc(x4, c5). arc(c4, c5). arc(x4, t5). arc(c4, t5).
    /* u4 avec t4 engendrent a5 et b5 */
    arc(u4, a5). arc(t4, a5). arc(u4, b5).  arc(u4, b5).



    Exécution :
    ?- showChemins(3, x1).
    [x1, x2, c3, c4]
    [x1, x2, c3, t4]
    [x1, x2, t3, a4]
    [x1, y2, x3, c4]
    [x1, y2, x3, t4]
    [x1, y2, y3, x4]
    [x1, y2, y3, y4]
    Et voila tout !
    true.

  3. Questions de Logique

    Le LittéralPrédicat dans le setof peut être composé par
    Exemples :
    ?- setof(X, Y^pere(Y, X), S).
    S = [adrienne, antoine, cesar, enza, lea, mara, marc, paul].

    ?- setof(X, Y^(pere(Y,X), masc(X)), S).
    S = [cesar, marc].
    mais pas setof(X, (Y^pere(Y,X), masc(X)), S) qui est logiquement équivalent.

    ?- setof(X, Y^Z^(pere(Y,X), masc(X) ; mere(Z, X), fem(X)), S).
    S = [cesar, enza, lea, marc].

    ?- bagof(X, Y^(pere(Y, X) ; mere(X, Y)), S).
    S = [cesar,lea,adrienne,enza,marc,mara,antoine,paul,silvia,julie,lea,pippa,lea,enza,enza].

    Quid de la négation ??? Limitation de la négation par échec...
    Le "minimum" setof(X, not(pere(jules, X)), S) renvoie false
    (dans des versions précédentes de Prolog, il provoquait une erreur !)

Moteurs d'inférences, à l'ordre 0 strict

  1. Ordre 0

  2. Moteur en marche arrière

    Prédicat pprouver(But, EnAttente), le second argument pour éviter de boucler,
    et son itéré lprouver pour listes de buts

    :- dynamic(fait/1).
    :- dynamic(deduit/2).

    prouver(But) :- pprouver(But, []).

    pprouver(But, BEA) :- fait(But),!.
    pprouver(But, BEA) :- membre(But, BEA), !, fail.
    pprouver(But, BEA) :- regle([But | Premisses]),
                    lprouver(Premisses, [But|BEA]),
                    assert(fait(But)),
                    assert(deduit(But,  Premisses)). /* pour les explications */
     
    lprouver([], BEA).
    lprouver([P|S], BEA) :- pprouver(P, BEA), lprouver(S, BEA).

    membre(X, [X|S]) :- !.
    membre(X, [_|S]) :- membre(X, S).




  3. Moteur en marche avant


    saturer :- nouveau(_),!,  saturer.
    saturer :- write('Saturation terminee'), nl.

    nouveau(Z) :- regle([Z|Prem]), not(fait(Z)), lfait(Prem),
                assert(fait(Z)), /* essentiel ! */
                assert(deduit(Z, Prem)). /* pour les explications */
    lfait([]).
    lfait([X | R]) :- fait(X), lfait(R).



  4.  Module d'explication, visualisant l'arbre de preuve


    :- dynamic(deja_vu/1).

    pourquoi(Fait) :- explique(Fait, 0).

    explique(Fait, Prof) :- deja_vu(Fait), !, tab(Prof),
            write('Le fait "'), write(Fait),
            write('" a ete obtenu precedemment.'), nl.
    explique(Fait, Prof) :- deduit(Fait, LPrem), !, tab(Prof),
            write('J''ai deduit "'), write(Fait), write('" de '),
            write(LPrem), nl, Profp is Prof+1,
            lexplique(LPrem, Profp),
            tab(Prof),
            write('Ceci acheve la preuve de "'), write(Fait),
            write('"'), nl, assert(deja_vu(Fait)).

    explique(Fait, Prof) :- fait(Fait), !, tab(Prof),
            write('Le fait "'), write(Fait),
            write('" fait partie des hypotheses initiales.'), nl.

    explique(Fait, Prof) :-
            write('Mais j''ai jamais pretendu que "'), write(Fait),
            write('" !'), nl.

    lexplique([], _).
    lexplique([Fait|SFaits], Prof) :- explique(Fait, Prof), lexplique(SFaits, Prof).



  5. Exemple : une base de connaissances relative aux quadrilatères


    /* (1)  Formulation explicite des proprietes evoquees */

    /* Le vocabulaire et sa signification : on rappelle que le discours se refere a un quadrilatere hypothetique dont il s'agit de determiner la nature et les proprietes */

    trad(carre, 'Le quadrilatere est un carre').
    trad(losange,  'Le quadrilatere est un losange').
    trad(rectangle,  'Le quadrilatere est un rectangle').
    trad(parallelogramme,  'Le quadrilatere est un parallelogramme').
    trad(trapeze,  'Le quadrilatere est un trapeze').
    trad(trapeze_isocele,  'Le quadrilatere est un trapeze isocele').
    trad(trapeze_rectangle,  'Le quadrilatere est un trapeze rectangle').

    trad(inscriptible,  'Le quadrilatere est inscriptible (dans un cercle)').

    trad(diag_egales,  'Les diagonales sont egales').
    trad(diag_ortho,  'Les diagonales sont orthogonales').
    trad(diag_milieu,  'Les diagonales se coupent en leur milieu').

    trad(ang_op_suppl, 'Les angles opposes sont supplementaires').
    trad(ang_op_egaux, 'Les angles opposes sont egaux').
    trad(deux_ang_op_droits, 'Il y a deux angles droits opposes ').
    trad(quatre_ang_droits, 'Les quatre angles sont droits').
    trad(un_ang_droit, 'Il y a un angle droit').

    trad(quatre_cotes_egaux, 'Les quatre cotes sont egaux').
    trad(cotes_op_egaux, 'Les cotes opposes sont egaux').
    trad(cotes_op_paralleli, 'Les cotes opposes sont paralleles').

    trad(deux_cotes_op_egaux, 'Il y a deux cotes opposes egaux').
    trad(deux_cotes_op_par, 'Il y a deux cotes opposes paralleles').
    trad(deux_cotes_adj_egaux, 'Il y a deux cotes adjacents egaux').

    /* Application a l'explicitation des regles, dont le format est celui de clauses de Horn propositionnelles [Concusion | Premisses].
    Exemple : pour la regle "[carre, rectangle, losange]" on developpera :
    SI Le quadrilatere est un rectangle
    ET SI Le quadrilatere est un losange
    ALORS Le quadrilatere est un carre     */

    tradregle([Concl, Prem1|SPrem]) :- write('SI '),
            trad(Prem1, T), write(T), nl, tradprem(SPrem),
            write('ALORS '), tradconcl(Concl).
    tradprem([]).
    tradprem([P|S]) :- write('ET SI '), trad(P, T), write(T), nl,
                    tradprem(S).
    tradconcl(C) :- trad(C, T), write(T), nl.



    /* (2) LISTE DES REGLES, par centres d'interet */

    /* Carres et losanges */

    regle([carre, rectangle, losange]).
    regle([rectangle, carre]).
    regle([losange, carre]).

    regle([carre, inscriptible, diag_ortho]).

    regle([parallelogramme, losange]).
    regle([losange, parallelogramme, diag_ortho]).
    regle([losange, parallelogramme, deux_cotes_adj_egaux]).

    regle([losange, quatre_cotes_egaux]).
    regle([quatre_cotes_egaux, losange]).


    /* Rectangles */

    regle([rectangle, quatre_ang_droits]).
    regle([quatre_ang_droits, rectangle]).

    regle([parallelogramme, rectangle]).
    regle([rectangle, parallelogramme, un_ang_droit]).
    regle([un_ang_droit, rectangle]).
    regle([rectangle, parallelogramme, inscriptible]).
    regle([inscriptible, rectangle]).
    regle([rectangle, parallelogramme, diag_egales]).
    regle([diag_egales, rectangle]).


    /* Parallelogrammes */

    regle([parallelogramme, cotes_op_egaux]).
    regle([cotes_op_egaux, parallelogramme]).

    regle([parallelogramme, cotes_op_par]).
    regle([cotes_op_par, parallelogramme]).

    regle([parallelogramme, diag_milieu]).
    regle([diag_milieu, parallelogramme]).

    regle([parallelogramme, ang_op_egaux]).
    regle([ang_op_egaux, parallelogramme]).


    /* Trapezes */

    regle([trapeze, deux_cotes_par]).
    regle([deux_cotes_par, trapeze]).

    regle([trapeze_rectangle, trapeze, un_ang_droit]).
    regle([trapeze, trapeze_rectangle]).
    regle([un_ang_droit, trapeze_rectangle]).

    regle([trapeze, trapeze_isocele]). /* deux cotes op egaux mais pas un parallelogramme */
    regle([trapeze_isocele, trapeze, diag_egales]).
    regle([diag_egales, trapeze_isocele]).
    regle([trapeze_isocele, trapeze, inscriptible]).
    regle([inscriptible, trapeze_isocele]).
    regle([deux_cotes_op_egaux, trapeze_isocele]).


    /* Regles "generales" */

    regle([un_ang_droit, quatre_ang_droits]).
    regle([deux_ang_op_droits, quatre_ang_droits]).
    regle([deux_ang_op_droits, un_ang_droit, ang_op_suppl]).
    regle([deux_ang_op_droits, un_ang_droit, ang_op_egaux]).
    regle([quatre_ang_droits, ang_op_suppl, ang_op_egaux]).

    regle([deux_ang_op_egaux, ang_op_egaux]).
    regle([deux_ang_op_egaux, deux_ang_op_droits]).

    regle([inscriptible, ang_op_suppl]).
    regle([ang_op_suppl, inscriptible]).

    regle([deux_cotes_par, cotes_op_par]).
    regle([cotes_op_par, deux_cotes_par, deux_ang_op_egaux]).

    regle([cotes_op_egaux, quatre_cotes_egaux]).
    regle([deux_cotes_adj_egaux, quatre_cotes_egaux]).
    regle([deux_cotes_op_egaux, quatre_cotes_egaux]).
    regle([quatre_cotes_egaux, cotes_op_egaux, deux_cotes_adj_egaux]).



  6. Exécution



    ?- assert(fait(trapeze_rectangle)).
    true.

    ?- assert(fait(trapeze_isocele)).
    true.

    ?- prouver(rectangle). % en marche arrière
    true .

    ?- pourquoi(rectangle).
    J'ai deduit "rectangle" de [quatre_ang_droits]
     J'ai deduit "quatre_ang_droits" de [ang_op_suppl, ang_op_egaux]
      J'ai deduit "ang_op_suppl" de [inscriptible]
       J'ai deduit "inscriptible" de [trapeze_isocele]
        Le fait "trapeze_isocele" fait partie des hypotheses initiales.
       Ceci acheve la preuve de "inscriptible"
      Ceci acheve la preuve de "ang_op_suppl"
      J'ai deduit "ang_op_egaux" de [parallelogramme]
       J'ai deduit "parallelogramme" de [cotes_op_par]
        J'ai deduit "cotes_op_par" de [deux_cotes_par, deux_ang_op_egaux]
         J'ai deduit "deux_cotes_par" de [trapeze]
          J'ai deduit "trapeze" de [trapeze_rectangle]
           Le fait "trapeze_rectangle" fait partie des hypotheses initiales.
          Ceci acheve la preuve de "trapeze"
         Ceci acheve la preuve de "deux_cotes_par"
         J'ai deduit "deux_ang_op_egaux" de [deux_ang_op_droits]
          J'ai deduit "deux_ang_op_droits" de [un_ang_droit, ang_op_suppl]
           J'ai deduit "un_ang_droit" de [trapeze_rectangle]
            Le fait "trapeze_rectangle" fait partie des hypotheses initiales.
           Ceci acheve la preuve de "un_ang_droit"
           Le fait "ang_op_suppl" a ete obtenu precedemment.
          Ceci acheve la preuve de "deux_ang_op_droits"
         Ceci acheve la preuve de "deux_ang_op_egaux"
        Ceci acheve la preuve de "cotes_op_par"
       Ceci acheve la preuve de "parallelogramme"
      Ceci acheve la preuve de "ang_op_egaux"
     Ceci acheve la preuve de "quatre_ang_droits"
    Ceci acheve la preuve de "rectangle"
    true.

    ?- fait(X), writeln(X), fail. % la collection des faits établis au cours de la preuve
    trapeze_rectangle             % dans l'ordre chronologique de leur démonstration
    trapeze_isocele
    inscriptible
    ang_op_suppl
    inscriptible
    trapeze
    deux_cotes_par
    un_ang_droit
    deux_ang_op_droits
    deux_ang_op_egaux
    cotes_op_par
    parallelogramme
    ang_op_egaux
    quatre_ang_droits
    rectangle             % comme il est normal, c'est le dernier à avoir été établi
    false.

    ?- saturer. % en marche avant
    Saturation terminee
    true.

    ?- fait(X), writeln(X), fail.
    trapeze_rectangle
    trapeze_isocele
    inscriptible
    ang_op_suppl
    inscriptible
    trapeze
    deux_cotes_par
    un_ang_droit
    deux_ang_op_droits
    deux_ang_op_egaux
    cotes_op_par
    parallelogramme
    ang_op_egaux
    quatre_ang_droits
    rectangle
    diag_egales           % conséquences qui n'avaient pas encore été prises en compte...
    cotes_op_egaux
    diag_milieu
    deux_cotes_op_egaux
    false.

    ?- pourquoi(deux_cotes_op_egaux).
    J'ai deduit "deux_cotes_op_egaux" de [trapeze_isocele]
     Le fait "trapeze_isocele" fait partie des hypotheses initiales.
    Ceci acheve la preuve de "deux_cotes_op_egaux"
    true.



Cryptarithmétique élémentaire

  1. L'exemple standard SEND+MORE = MONEY


    /* tous_diff(Liste) <=> tous les elements de la Liste sont differents
    deux a deux */

    tous_diff([]).
    tous_diff([A|S]) :- not(member(A, S)), tous_diff(S).

    /* Arithmetique */

    chiffre(0). chiffre(1). chiffre(2). chiffre(3). chiffre(4).
    chiffre(5). chiffre(6). chiffre(7). chiffre(8). chiffre(9).

    col(Reste_p, Ch1, Ch2, Chr, Reste_n) :- chiffre(Ch1), chiffre(Ch2),
            Somme is Reste_p+Ch1+Ch2,
            Reste_n is Somme//10, Chr is Somme mod 10.

    /* L'enonce : Generate & Test  */

    sol([S,E,N,D,M,O,R,Y]) :-       
        col( 0, D, E, Y, R1),
        col(R1, N, R, E, R2),       
        col(R2, E, O, N, R3),
        col(R3, S, M, O, R4),
        col(R4, 0, 0, M, 0),
        not(M=0),
        tous_diff([S,E,N,D,M,O,R,Y]).
           
    /* La jolie impression */

    joli([S,E,N,D,M,O,R,Y]) :- nl,
            tab(10), write(S), write(E), write(N), write(D), nl,
       tab(7), write(' + '), write(M), write(O), write(R), write(E), nl,
       tab(8), write('------'), nl,
              tab(9), write(M), write(O), write(N), write(E), write(Y),
            nl.

    /* Le predicat d'appel */

    ca :- sol([S,E,N,D,M,O,R,Y]), joli([S,E,N,D,M,O,R,Y]), fail.



    Exécution
    ?- ca.

              9567
            + 1085
            ------
             10652

    false.

  2. Généralisation

    Pour essayer les nombreux exemples qu'on trouve sur le réseau...

    /* Le compilateur de problèmes cryptarithmétiques */

    :- dynamic(sol/1).

    /* Auxiliaires */
    rev(L, R) :- revl(L, [], R).

    revl([], R, R).
    revl([X|S], T, R) :- revl(S, [X|T], R).

    concat([], X, X).
    concat([A | B], C, [A | D]) :- concat(B, C, D).

    /* member(X, [X|S]) :- !.
    member(X, [Y|S]) :- member(X, S). */

    /* pour l'exe'cution ... */
    tous_diff([]).
    tous_diff([A|S]) :- not(member(A, S)), tous_diff(S).

    chiffre(0). chiffre(1). chiffre(2). chiffre(3). chiffre(4).
    chiffre(5). chiffre(6). chiffre(7). chiffre(8). chiffre(9).

    col(Reste_p, Ch1, Ch2, Chr, Reste_n) :- chiffre(Ch1), chiffre(Ch2),
            Somme is Reste_p+Ch1+Ch2,
            Reste_n is Somme//10, Chr is Somme mod 10.

    /* Queue de la clause */
    csq(U, V, W, C) :-  rev(U, RU), rev(V, RV), rev(W, RW), rcsq(RU, RV, RW, C).

    rcsq([DU|SU], [DV|SV], [DW|SW], ((col(0, DU, DV, DW, R), CS))) :-
        rcss(SU, SV, SW, R, CS).

    rcss([], [], [PW], R, ((col(R, 0, 0, PW, 0), not(PW = 0)))). /* arret 1 */
    rcss([PU], [PV], [PW], R, ((col(R, PU, PV, PW, 0), not(PW = 0)))). /*arret2*/
    rcss([NU|SU], [NV|SV], [NW|SW], R, ((col(R, NU, NV, NW, RN), CS))) :-
        rcss(SU, SV, SW, RN, CS). /* avec creation d'une nouvelle var ds RN */

    /* Le predicat-maitre */
    csol(U, V, W, ((sol(LV) :- (Q, tous_diff(LV))))) :- 
        concat(U, V, UV), concat(UV, W, UVW), sort(UVW, LV), csq(U, V, W, Q).

    /* Auxiliaires pour la jolie impression */

    long([], 0).
    long([X|S], K) :- long(S, H), K is H+1.

    ligne([]) :- nl.
    ligne([X|S]) :- V is X, write(V), ligne(S).

    trait(0):- !, nl.
    trait(N) :- write('-'), M is N-1, trait(M).

    /* La jolie impression */

    ijoli(D, U, V, W) :- long(U, H), long(V, K), long(W, L),
                iijoli(D, U, V, W, H, K, L).

    iijoli(D, U, V, W, H, H, H) :-  /* les trois de meme longueur */
                  nl, tab(D), tab(2), ligne(U),
                  tab(D), write('+ '), ligne(V),
                  tab(D), K is H+2, trait(K),
                  tab(D), tab(2), ligne(W).

    iijoli(D, U, V, W, H, H, L) :- L is H+1, !, /* un de plus pour la somme */
                  nl, tab(D), tab(2), ligne(U),
                  tab(D), write('+ '), ligne(V),
                  tab(D), K is H+2, trait(K),
                  tab(D), tab(1), ligne(W).

    iijoli(D, U, V, W, H, K, K) :- H < K, !, DH is K-H,
                  nl, tab(D), tab(2), tab(DH), ligne(U),
                  tab(D), write('+ '), ligne(V),
                  tab(D), T is K+2, trait(T),
                  tab(D), tab(2), ligne(W).

    iijoli(D, U, V, W, H, K, H) :- K < H, !, DK is H-K,
                  nl, tab(D), tab(2), ligne(U),
                  tab(D), write('+ '), tab(DK), ligne(V),
                  tab(D), T is H+1, trait(T),
                  tab(D), tab(1), ligne(W).

    /* Exe'cution avec jolie impression */

    auvol(U, V, W) :- csol(U,V,W,(T :- Q)), assert((T :- Q)),
              call(T), ijoli(15, U,V,W).

    /* Essais */

    av1 :- auvol([S,E,N,D], [M,O,R,E], [M,O,N,E,Y]).
    av2 :- auvol([B,I,G], [B,O,Y], [M,A,N]).
    av3 :- auvol([D,O,N,A,L,D], [G,E,R,A,L,D], [R,O,B,E,R,T]).



    Exécution
    ?- av3.

                     526485
                   + 197485
                   --------
                     723970
    true ;
    false.