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