TP2 Prolog : premiers pas
Transcription
TP2 Prolog : premiers pas
Intelligence Artificielle TP 1 : Prolog Partie 2 1 Exercice 1 Dans cet exercice, nous allons écrire les règles qui décrivent les relations familiales. On commence avec la base de faits suivante : homme(jean). homme(pierre). homme(jacques). homme(paul). homme(leon). homme(alain). homme(luc). homme(gaston). femme(lucie). femme(andree). femme(claire). femme(martine). femme(julie). femme(armelle). femme(anne). femme(odette). parent(jean,paul). parent(jean,alain). parent(paul,luc). parent(pierre,jacques). parent(pierre,jean). parent(leon,lucie). parent(claire,lucie). parent(andree,jean). parent(andree,jacques). parent(lucie,paul). parent(lucie,alain). parent(martine,luc). parent(jacques, anne). parent(julie, armelle). parent(jacques, armelle). parent(julie, anne). parent(gaston, andree). parent(gaston, leon). parent(odette, andree). parent(odette, leon). Ici les prédicats homme(X), femme(X) et parent(X,Y) décrivent respectivement que “X est un homme”, “X est une femme” et “X est un parent de Y” (père ou mère). Questions : — Dessiner sur une feuille l’arbre généalogique. — Entrer les faits dans un fichier. — Écrire les règles suivantes dans votre programme : — pere(X,Y) qui traduit ”X est père de Y” 1 — — — — — grand pere(X,Y) qui traduit ”X est grand père de Y” mere(X,Y) et grand mere(X,Y) frere(X,Y) et soeur(X,Y) cousin(X,Y) et cousine(X,Y) oncle(X,Y) et tante(X,Y) — Charger le programme dans prolog et trouver les choses suivantes — Qui est le père d’alain ? — Qui est le grand père de luc ? — La liste de tous les pères. — La liste de tous les grand pères. — La liste des cousins-cousines. — Proposer d’autres questions pour vérifier la validité de votre arbre. 2 Exercice 2 Il existe en Prolog des prédicats qui vérifient si un terme appartient à un ensemble donné. Par exemple integer(X) est vrai si X est un entier. Voici une liste des prédicats qui existe : var, nonvar,integer, float, number, atom, atomic, compound. Question : Écrire des prédicats pour vérifier les cas suivants : — Les nombres entiers. — Les nombres entiers négatifs. — Les nombres naturels. — Les nombres sauf 0. — Les entiers pairs. — Les entiers pairs et négatifs. — Les entiers impairs et positifs. — 0, 1, 2, 3, 4, 5, 6, 7, 8 — 0, 3, 6, 9, . . . — 1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14, . . . 3 Exercice 3 Un ancêtre est une personne dont quelqu’un descend. Nous allons écrire un prédicat pour la relation “ancêtre” où ancetre(A,B) veut dire que A est 2 un ancêtre de B. Si la base de connaissances contient des relations “parent”, on peut facilement définir la relation “ancêtre”. Un parent est un ancêtre pour son enfant et tous les ancêtres d’un parent sont aussi des ancêtres pour l’enfant. Par exemple, supposons le graphe suivant qui représente des relations “parent” où les parents sont juste au dessus de leur enfants. On peut représenter le graphe en Prolog par les faits suivants, où p(X,Y) traduit que X est le parent de Y : p(a,b). p(a,c). p(c,d). p(d,e). p(d,f). p(n,w). p(w,y). p(z,y). p(y,x). p(y,m). p(x,f). p(x,t). Quel sont les ancêtres de e ? A partir du graphe, on constate qu’ils sont d, c et a. On obtient cela en commençant par trouver les parents de e, puis leurs ancêtres. Ceci nous fournit le cas base (un ancêtre est un parent), et le cas récursif (un ancêtre d’un parent est un ancêtre) pour la définition du prédicat ancetre. 3 Questions : 1. Programmez le prédicat ancetre avec la base de connaissance donnée ci-dessus. ancetre(A,B) :- p(A,B). % un parent est un ancetre ancetre(A,B) :- p(X,B), ancetre(A,X). % un ancetre du parent est un ancetre Effectuez les différents tests suivants : (a) Essayez différents buts et utilisez le backtracking pour trouver les ancêtres. Trouvez la plus longue liste d’ancêtres. (b) Essayez des buts avec des variables dans les paramètres et utilisez le backtracking. 2. Faites les différents tests suivants, en utilisant le debugeur. Dans les parties 2b et 2c vous pouvez observer les appels récursifs au prédicat ancetre. (a) | ?- ancetre(a,e). (b) | ?- trace, ancetre(a,e). (c) | ?- spy(ancetre), ancetre(a,e). (tapez l pour sauter à l’utilisation de ancetre) (d) | ?- nospy(ancetre), spy(p), ancetre(a,e). (tapez l pour sauter à l’utilisation de p) 3. Modifiez la définition du prédicat ancetre comme indiqué dans les cas suivants, et remarquez les différences dans l’exécution. (a) Changez l’ordre des deux clauses de sort que l’appel récursif arrive en premier avant le cas de base. (b) Changer l’ordre des prédicats dans la clause récursif. 4. Modifiez le prédicat ancetre de sorte à ce qu’il affiche le nombre de générations entre les personnes. Par exemple le but | ?- ancetre(a, e, N). retournera N=3. Testez avec différents buts en incluant des buts avec des variables dans les arguments. Observez le résultat avec le débugeur. 4 Exercice 4 Supposez que pour chaque entier naturel n on désire écrire la séquence de tous les entiers successifs entre 0 et n. Pour trouver le programme qui 4 permet de faire cela on peut faire quelques observations. Premièrement, on observe que pour n = 0 la réponse est 0. D’un autre coté, si n > 0, la tâche est accomplie si nous écrivons la séquence de 0 à n − 1 et si ensuite nous ajoutons n à la séquence. Ces observations nous fournissent le cas de base et le cas récursif pour un prédicat seq qui réalise cette tâche. Par exemple : |?-seq(3). devra imprimer la séquence 0123. Une définition du prédicat seq peut être écrite comme suit, où nous avons ajouté une vérification que le nombre est bien un entier naturel. La séquence sera affichée sous forme d’une colonne. seq(0) :- write(0), nl. seq(N) :- nat(N), M is N-1, seq(M), write(N), nl. % % % % nl passe à la ligne vérifie si N est naturel affiche la séquence de 0 à N-1 affiche N nat(X) :- integer(X), X >= 0. % prédicat de vérification des nombres naturels Pour un autre exemple de ce type de programmation, supposez que l’on désire calculer la somme 0 + 1 + 2 + · · · + n pour tout entier naturel n. On peut faire quelques observations sur la somme pour construire le programme. Premièrement, si n = 0 le résultat doit être 0. Ensuite, pour n > 0, la tâche serait accomplie si nous calculons la somme 0 + 1 + · · · + n − 1 à laquelle nous ajoutons n. De la même manière que dans le cas précédent, cette observation nous donne le cas de base et le cas récursif pour le prédicat somme . Par exemple, le but |?- somme(3, Res). devrait donner Res=6. La définition du prédicats somme peut être la suivante : somme(0,0). somme(N,S) :- nat(N), K is N-1, somme(K,T), S is T+N. Questions : 1. Implémentez le programme du prédicat seq et essayez avec différents buts. Ensuite, faites les tests suivants, dans les parties 1b et 1c vous observerez les appels récursifs au prédicat seq. (a) |?- seq(3). (b) |?- trace, seq(3). (c) |?- spy(seq), seq(3). (tapez l pour sauter aux appels de seq) (d) |?- nospy(seq), spy(nat), seq(3). (tapez l pour sauter aux appels de nat) 5 2. Faites les modifications suivantes à la définition du prédicat seq. (a) Changez l’ordre des deux clauses de sorte que le cas de base arrive après la cas récursif. Testez la nouvelle définition et observez les différences avec l’ancienne. (b) A partir de cette dernière modification du prédicat seq, supprimez l’appel au prédicat nat. Observer le comportement, en utilisant le debugeur. 3. Modifiez la définition original du prédicat seq de sorte que les numéros s’affichent dans l’ordre inverse. Par exemple le but |?- seq(3). devrait afficher 3210 4. Implémentez le programme du prédicat somme et essayez avec différents buts pour vérifier qu’il marche bien. Ensuite, faites les tests suivants, dans les parties 4b et 4c vous observerez les appels récursifs au prédicat somme. (a) |?- somme(3,X). (b) |?- trace, somme(3,X). (c) |?- spy(somme), somme(3,X). (tapez l pour sauter aux appels de somme) (d) |?- nospy(somme), spy(is), somme(3,X). (tapez l pour sauter aux appels de is) (e) |?- trace, somme(N,2)., qu’observez vous ? 5. Faites les modifications suivantes à la définition du prédicat somme. (a) Changez l’ordre des deux clauses de sorte que le cas de base arrive après la cas récursif. Testez la nouvelle définition et observez les différences avec l’ancienne. (b) A partir de cette dernière modification du prédicat somme, supprimez l’appel au prédicat nat. Observer le comportement, en utilisant le debugeur. (c) L’ordre des prédicats dans les formules est important, car elle sont exécutées de gauche à droite. Pour observer cela, modifier l’ordre des termes de la clause récursive de somme comme ci-dessous, et observez les différences dans l’exécution du but somme(3,X). somme(0,0). somme(N,S) :- nat(N), somme(K,T), K is N-1, S is T+N. 6 5 Exercice 5 Un ensemble E est définit par induction s’il peut être décrit en nommant quelques éléments spécifiques de E (le cas de base) et en donnant une ou plusieurs règles pour construire de nouveaux éléments de E à partir d’éléments existants dans E (le cas d’induction). On fait l’hypothèse que E ne contient que des éléments construit à partir de ces règles. Par exemple, soit E l’ensemble défini comme suit : — Base : 2 ∈ E — Induction : si x ∈ E alors x + 3 ∈ E Soit le prédicat dansE(x) pour tester si x ∈ E. On peut définir dansE comme suit : dansE(2). dansE(X) :- Y is X-3, Y>=2, dansE(Y). Questions : 1. Donner une définition de l’ensemble E. 2. Implémentez le prédicat dansE, et vérifiez qu’il teste bien l’appartenance à l’ensemble que vous avez définit. Essayez avec des arguments qui ne sont pas dans E et des arguments non entiers. 3. Faites les tests suivants : — |?- dansE(5). — |?- trace, dansE(5). — |?- spy(dansE), dansE(5). (tapez l pour sauter aux appels de dansE) — Observez ce qu’il se passe si vous mettez une variable comme argument. 4. Pour chacune des définitions inductives d’ensemble suivantes, donnez une définition de l’ensemble A, ensuite donnez une définition du prédicat dansA(x) qui teste l’appartenance de x dans A. Testez votre prédicat par rapport à votre définition de l’ensemble. Refaites les tests précédents avec dansA. (a) — Base : 3 ∈ A — Induction : si x ∈ A alors 2x + 1 ∈ A (b) — Base : 0 ∈ A — Induction : si x ∈ A alors x + 4 ∈ A et x + 9 ∈ A 7 6 Exercice 6 Vous devez écrire deux programmes, l’un pour calculer la factorielle d’un entier et l’autre pour calculer le nombre Fibonacci d’un entier. On vous donne les définitions récursives suivantes : 1. Factorielle — Base : 0 ! = 1, — Récursion : n! = (n − 1)! × n 2. Fibonacci — Base : fib(0) = 0, fib(1) = 1 — Récursion : fib(n) = fib(n-1) + fib(n-2). Par exemple |?- fac(5,N). donnerait le résultat suivant : N=120, et |?fibo(8,N). donnerait N=21. Testez vos prédicat avec différentes valeurs. 8