11/12: weitere Prädikate

Manipulation der Wissensbasis

Prolog stellt Prädikate zur Verfügung, die eine Manipulation der Wissensbasis während der Laufzeit eines Programms ermöglichen: assert/1, (asserta/1, assertz/1), retract/1, retractall/1

% assert(a/z)/1:
% assert(Clause) fügt die Klausel Clause zur Wissensbasis dynamisch hinzu.
% asserta(Clause) fügt die Klausel als neue erste Klausel hinzu.
% assertz(Clause) fügt die Klausel als neue letzte Klausel hinzu.
% retract/1:
% retract(Clause) löscht eine Klausel, die mit Clause matcht, aus der Wissensbasis.
% retractall/1:
% retractall(Clause) löscht alle Klauseln, die mit Clause matchen.

Beispiel:

Im Prinzip ist es mit den Manipulationsprädikaten möglich, direkt von der Konsole zu programmieren:

?- assert(sterblich(X):- mensch(X)). true.
?- assert(mensch(mia)).
true.
?- assert(sterblich(garfield)). true.
?- sterblich(X). X = mia;
X = garfield.

Dies ist jedoch nur selten eine gute Idee, da sie genau aufpassen müssen, in welcher Reihenfolge sie die Klauseln einfügen.

Hinzufügen & Löschen von Klauseln

?- assert(mensch(anna)).
true.
?- asserta(mensch(tom)).
true.
?- assert(mensch(mia)).
true.
?- assertz(mensch(otto)).
true.
?- listing(mensch/1).
:- dynamic mensch/1. mensch(tom).
mensch(anna). mensch(mia).
mensch(otto). true.
?- retract(mensch(mia)).
true.
?- retract(mensch(X)).
X = tom. true.
?- listing(mensch/1). :- dynamic mensch/1.
mensch(anna). mensch(otto).
true.
?- retractall(mensch(X)).
true.
?- listing(mensch/1).
:- dynamic mensch/1. true.

copy_term/2: Anwendung von assert/1 und retract/

copy_term(Term1,Term2) erstellt in Term2 eine Kopie von Term1 mit neuen Variablen.

% Praedikate, die waehrend der Laufzeit veraendert werden sollen,
% muessen als dynamisch deklariert werden.
:- dynamic temporaer/1.
copy_term(T1,T2):-
    assert( temporaer(T1)),
    retract(temporaer(T2)).
?- copy_term(vorfahr(A,B),Z).
Z = vorfahr(_G1, _G2).
?- copy_term(vorfahr(A,B),Z1),copy_term(vorfahr(A,B),Z2).
Z1 = vorfahr(_G1, _G2),
Z2 = vorfahr(_G3, _G4).

Programmierung durch Memoisierung

Die Programmiertechnik Ergebnisse in der Wissensbasis zu speichern, um bei späteren Berechnungen darauf zurück greifen zu können, heißt Memoisierung.

Beispiel: Fibonacci-Zahlen ohne Memoisierung
fib(0) = 0, fib(1) = 1, fib(n) = fib(n − 2) + fin(n − 1)

fib(0,0). % fib(0)=0
fib(1,1). % fib(1)=1
% fib(n)=fib(n-1)+fib(n-2)
fib(N,Res) :-
    N>1,
    N1 is N-1,
    N2 is N-2,
    fib(N1,R1),
    fib(N2,R2),
    Res is R1+R2.
% Testen sie die folgenden Anfragen:
?- fib(4,R).
?- fib(25,R).
?- fib(40,R).
% Warum ist das Programm so langsam?

Vorsicht

Die Manipulation der Wissensbasis ist eine sehr mächtige Programmiertechnik.
Aber, sie

  • kann die Deklarativität eines Programms zerstören,
  • kann zu schwer lesbarem Code führen,
  • sollte mit Bedacht eingesetzt werden.

Insbesondere sollte immer überprüft werden, ob zu einem dynamischen Prädikat bereits Klauseln in der Wissensbasis (durch zum Beispiel abgebrochene vorangegangene Aufrufe) stehen.

Fibonacci-Zahlen mit Memoisierung
fib(0) = 0, fib(1) = 1, fib(n) = fib(n − 2) + fin(n − 1)

% fib/2 muss als dynamisches
% Praedikat deklariert werden:
:- dynamic(fib/2).
fib(0,0):-!. % fib(0)=0
fib(1,1):-!. % fib(1)=1
fib(N,Res) :-
    N>1,
    N1 is N-1,
    N2 is N-2,
    fib(N1,R1),
    fib(N2,R2),
    Res is R1+R2,
    asserta(fib(N,Res):-!).
% Teste die folgenden Anfragen:
?- fib(40,R).
?- fib(100,R).
?- fib(1000,R).
% Durch Memoisierung können rekursive Programme effektiver werden.

Alle Lösungen einer Prologanfrage

  • Auf Anfragen mit nicht instantiierten Variablen antwortet Prolog mit einer Variablenbelegung, die die Aussage wahr macht.
  • Durch die Eingabe des Semikolons wird Prolog aufgefordert alternative Belegungen zu erzeugen.
  • So können alle möglichen Lösungen nacheinander generiert werden.
food(sushi).
food(apple).
food(pudding).
mag(popeye,X):- food(X).
?- mag(popeye,X). X=sushi; X=apple; X=pudding;
false.

Mithilfe der eingebauten Prädikate findall/3, bagof/3 und setof/3 können alle Lösungen auf einmal generiert und in einer Liste gesammelt werden.

Sammlung aller Lösungen: bagof/3

Das Prädikat bagof(O,Z,L) generiert eine Liste L aller Objekte O, die das Ziel Z wahr machen.

  • Gibt es kein solches Objekt so scheitert das Prädikat.
  • Zusätzlich werden freie Variablen gebunden.
?- bagof(Y,figur(X,Y),L).
X = garfield,
L = [katze, 7];
X = popeye,
L = [mensch, 30];
X = snoopy,
L = [hund, 7];
false.
?- bagof(Y,figur(snoopy,Y),L).
L = [hund, 7].
?- bagof(Y,figur(hans,Y),L).
false.
?- bagof((X,Y),figur(X,Y),L).
L = [(garfield, katze), (snoopy, hund), (popeye, mensch),
     (garfield, 7), (snoopy, 7), (popeye, 30)].
?- bagof(Y,X^figur(X,Y),L).
L = [katze, hund, mensch, 7, 7, 30].
?- findall(L, bagof(Y,figur(X,Y),L),List).
List = [[katze, 7], [mensch, 30], [hund, 7]].
?- findall(f(X,L), bagof(Y,figur(X,Y),L),List).
List = [f(garfield, [katze, 7]), f(popeye, [mensch, 30]),
        f(snoopy, [hund, 7])].
 

Sammlung aller Lösungen: findall/3

Das Prädikat findall(O,Z,L) generiert eine Liste L aller Objekte O, die das Ziel Z wahr machen.

  • Gibt es kein solches Objekt so wird L mit der leeren Liste unifiziert.
figur(garfield, katze).
figur(snoopy, hund).
figur(popeye, mensch).
figur(garfield,7).
figur(snoopy, 7).
figur(popeye,30).
?- findall(Y,figur(X,Y),L).
L = [katze,hund,mensch,7,7,30].
?- findall(name(X),figur(X,Y),L).
L = [name(garfield), name(snoopy), name(popeye),
     name(garfield), name(snoopy), name(popeye)].
?- findall(Y,(figur(X,Y),number(Y)),L).
L = [7,7,30].
?- findall((X,Y),figur(X,Y),L).
L = [(garfield, katze),(snoopy, hund),(popeye, mensch),
     (garfield, 7), (snoopy, 7), (popeye, 30)].
?- findall(Y,figur(snoopy,Y),L).
L = [hund, 7].
?- findall(Y,figur(hans,Y),L).
L = [].

Sammlung aller Lösungen: setof/3

Das Prädikat setof(O,Z,L) generiert eine Liste L aller Objekte O, die das Ziel Z wahr machen.

  • Gibt es kein solches Objekt so scheitert das Prädikat.
  • Zusätzlich werden freie Variablen gebunden.
  • Doppelte Elemente in L werden entfernt.
  • Die Elemente in L werden sortiert.
?- setof(X, Y^figur(X,Y),L).
[garfield,snoopy,popeye].
?- setof(Y,figur(X,Y),L).
X = garfield,
L = [katze, 7] ;
X = popeye,
L = [mensch, 30] ;
X = snoopy,
L = [hund, 7]; false.
?- setof(alter(Y), (figur(X,Y),number(Y)),L).
[alter(7),alter(30)].
?- setof(Y, figure(hans,Y), L).
false.

Dateien

Dateiausgabe mit open/3

  • In Prolog können Ausgaben (Streams) mit dem eingebauten Prädikat open/3 in Dateien umgeleitet werden.
  • Das Prädikat nimmt als erstes Argument einen Dateinamen. Das zweite Argument bestimmt den Modus (read, write, execute, default, all). Zuletzt wird eine Variable als Stream instanziiert.
write_file :-
    open(’test.txt’,write,FStream),
    write(FStream,’hello’),
    nl(FStream),
    close(FStream).
?- write_file.

Prolog-Programme ausführen (aus Python)

  • Prolog-Programme können aus anderen Programmiersprachen aufgerufen werden.
  • Das nachfolgende Beispiel beschreibt einen möglichen Prolog-Aufruf aus Python heraus.
import subprocess
pl_file = "path/prolog_file.pl"
p = subprocess.Popen(["swipl","-q","-s",pl_file,"-g","main,halt", \ "-t","nl,halt"],stdout=subprocess.PIPE)
p.wait()
stdout,stderr = p.communicate()

Dateien einlesen mit consult/1

  • Wichtig: Die einzulesende Datei muss sich in Prolog-Notation befinden!
  • Daten können bspw. in Form von Prädikaten eingelesen werden.
/* file.pl */
data(1,popeye).
data(1,pluto).
data(2,goofy).
data(2,mia).
data(1,anna).
load_file :- consult(’file.pl’).
?- load_file, findall(T,data(1,T),L).
L = [popeye, pluto, anna].

Module

  • ermöglichen eine strukturierte Programmierung,
  • verstecken Hilfsprädikate,
  • erleichtern das Programmieren in Teams.

Libraries

  • Libraries sind Module, die wichtige Prädikate zusammenfassen.
  • Im SWI-Manual finden sie eine Liste der wichtigsten Libraries.
:- use_module(library(lists)). 

Arbeit mit Modulen

Deklarieren von Modulen:

% Datei tools.pl
% Modulname: tools
% oeffentliche Praedikate: my_reverse/2, my_member/2
:- module(tools, [my_reverse/2, my_member/2]).
my_reverse(Liste,R) :-
    reverse_acc(Liste,[],R).
reverse_acc([],Acc,Acc).
reverse_acc([H|T],Acc,R) :-
    reverse_acc(T,[H|Acc],R).
my_member(X,[X|T]).
my_member(X,[_|T]):- my_member(X,T).

Aufruf von Modulen :

% Aus dem Modul reverse werden alle oeffentlichen Praedikate importiert
:- use_module(tools).
% Aus dem Modul reverse wird das Praedikat reverse/2 importiert
:- use_module(tools, [my_reverse/2]).

Graphviz: dot-Format

Graphviz ist eine Software zur Visualisierung von Graphen (http://www.graphviz.org/).

Zur Beschreibung von gerichteten Graphen wird das dot-Format verwendet:

digraph G {
main -> parse;
main -> init;
main -> cleanup;
parse -> execute; execute -> make_string;
execute -> printf
init -> make_string;
main -> printf;
execute -> compare;
}

Grafische Ausgabe eines Ableitungsbaums

Ziel: Erzeuge zu einem Baum in der Prolog-Term-Notation eine Beschreibung des Baums im dot-Format und generiere daraus ein Bild des Baums.

tree(1,s(np(det(die), n(katze)), vp(v(klaut), np(det(die), n(maus))))).

Generiere für jeden Knoten eine dot-Knotendefinition:

 q1[label=np];

Generiere für jede Mutter-Tochter-Beziehung eine dot-Kante:

q2->q3;

So generiert mgraph(1) die dot-Datei graph.dot und die jpg-Datei graph.jpg

digraph G { q0[label=s]; q1[label=np]; q2[label=det]; q3[label=die];
    q2->q3;
    q1->q2; q4[label=n]; q5[label=katze];
    q4->q5;
    q1->q4;
    q0->q1; q6[label=vp]; q7[label=v]; [...]
}

Aufruf von der Shell:

$ dot graph.dot -Tjpg -o graph.jpg

Graphviz/DOT-Darstellung eines Ableitungsbaums

:- dynamic(node/1).
% Graphviz/DOT (graphviz liegt im Unterordner gvdot)
dotjpeg :- shell(’gvdot/dot.exe output.dot -Tjpg -o graph.jpg’).
% Hauptaufruf
mgraph(N) :-
    open(’output.dot’,write,FS),
    tree(N,T),
    pdot(T,FS),
    close(FS),
    dotjpeg.
% Initialisierung des Tab - Zaehlers.
pdot(Term,FS):-
    retractall(node(_)),
    write(FS,’digraph G {’),
    nl(FS),
    assert(node(0)),
    pdot(0,Term,FS),
    write(FS,’}’).
% Baum drucken.
pdot(N,Term,FS):-
    Term =.. [F| Args ], % Struktur zu Liste.
    tab(FS,4),
    write(FS,q),
    write(FS,N), write(FS,’[label=’),
    write(FS,F),write(FS,’];’),
    nl(FS), % Ausgabe des Mutterknotens N1 is N+1,
    retract(node(_)),
    assert(node(N1)),
    pdot1(N,Args,FS). % Unterbaeume drucken.
% Unterbaeume drucken .
pdot1(Nmother,[H|T],FS):-
    node(Nchild),
    pdot(Nchild,H,FS), % Drucke eine Schwester.
    tab(FS,4),
    write(FS,q),
    write(FS,Nmother),
    write(FS,’->’),
    write(FS,q),
    write(FS,Nchild),
    write(FS,’;’),
    nl(FS),
    pdot1(Nmother,T,FS). % Drucke die anderen Schwestern .
    pdot1(_,[],_FS). % Termination .
% Beispielbaeume
tree(1,s(np(det(die), n(katze)), vp(v(klaut), np(det(die),
     n(maus))) tree(2,np(det(die), n(katze))).

Zusammenfassung

  • Wir haben gelernt, wie wir dynamisch die Wissensbasis verändern können.
  • Wir haben wichtige Prädikate zur Aufsammlung aller Lösungen einer Anfrage kennengelernt.
  • Wir können Ergebnisse in Dateien schreiben und Dateien mit Prologklauseln einlesen.
  • Wir haben gesehen, wie wir ein Prologprogramm in Module zerlegen können.