Zusammenfassung Computationale Logik (c) 2004-07-23 Fabian M. Suchanek http://www.mpi-inf.mpg.de/~suchanek/personal/texts/summaries -> Summaries -> Computational Logic Dieses ist die Zusammenfassung des Kurses "Einfuehrung in die Computationale Logik/Introduction to Computational Logic" von Prof. Smolka an der Universitaet des Saarlandes im SS 2004. Da dieser Text ASCII-Graphiken enthaelt, sollte er ___ | nur mit einer Festschriftart (zB Courier New) benutzt / \ | / und ausgedruckt werden. Wenn die Schriftart in ( ) |< Ordnung ist, erscheint rechts ein OK. \___/ | \ Die Loesungen aller Uebungsblaetter sind integriert. Sie sind gekennzeichnet durch Strings der Form "Uebungsblatt #1.1". Durch das Weiterlesen akzeptiert der Leser, dass der Autor keinerlei Verantwortung fuer die Richtigkeit oder Vollstaendigkeit dieser Zusammenfassung uebernimmt. Wenn jemand einen Fehler gefunden hat, so waere ich fuer eine Mail dankbar. Nur so habe auch ich etwas von der Veroeffentlichung dieser Zusammenfassung. Meine E-Mail-Adresse ist f.m.suchanek@zweb.de, wobei das 'z' aus der Adresse geloescht werden muss. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Vorraussetzungen ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Obwohl das Lambda-Kalkuel die Mengenlehre als Grundlage der // Mathematik ersetzen koennte, wird hier aus Gruenden der // Verstaendlichkeit das neuartige Lambda-Kalkuel mit bekannten // Konzepten aus der Mengenlehre untermauert. Menge: Eine ungeordnete Ansammlung nicht identischer Elemente. Schreibweisen: M = {x1, x2, ..., xn} fuer die Menge der Elemente x1,...xn M = {x | bedingung } fuer die Menge aller x, fuer die die Bedingung gilt x in M fuer den Sachverhalt, dass das Element x in der Menge M ist. "in" ist dabei das kleine griechische Epsilon mit rundem Ruecken. Kardinalitaet einer Menge: Die Anzahl ihrer Elemente. Schreibweise: |M| Teilmenge einer Menge M: Eine Menge M', deren Elemente alle in M sind. Schreibweise: M' < M fuer den Sachverhalt, dass M' Teilmenge von M ist. "<" ist dabei ein um 90 Grad nach rechts gedrehtes "U". // Das Zeichen '<' wird hier ueberladen und bedeutet zwischen // Mengen "Teilmenge von" Obermenge einer Menge M: Eine Menge, von der M eine Teilmenge ist. Schreibweise: M' > M fuer den Sachverhalt, dass M' Obermenge von M ist. ">" ist dabei ein um 90 Grad nach links gedrehtes "U". Schnittmenge der Mengen M und M': Die Menge all derjenigen Elemente, die sowohl in M als auch in M' sind. Schreibweise: M /\ M' fuer die Schnittmenge von M und M'. "/\" ist dabei ein um 180 Grad gedrehtes "U". Disjunkte Mengen: Menge, deren Schnittmenge leer ist, d.h. die kein Element gemeinsam haben. Vereinigung der Mengen M und M': Die Menge aller Elemente von M und aller Elemente von M' ist. Schreibweise: M \/ M' fuer die Vereinigungsmenge von M und M'. "\/" ist dabei ein geweitetes "U". Differenz der Mengen M und M': Die Menge aller Elemente aus M, die nicht in M' sind. Schreibweise: M - M' Potenzmenge einer Menge M: Die Menge aller ihre Teilmengen. Schreibweise: P(M) Beispiel: P({1,2}) = { {}, {1}, {2}, {1,2} } N: Die Menge der natuerlichen Zahlen. Fuer eine Definition siehe "Constraints over the Reals" (cr.txt, auf Englisch). Z: Die Menge der ganzen Zahlen. Fuer eine Definition siehe "Constraints over the Reals" (cr.txt, auf Englisch). R: Die Menge der reellen Zahlen. Fuer eine Definition siehe "Constraints over the Reals" (cr.txt, auf Englisch). Primzahl: Eine natuerliche Zahl groesser 1, die nur durch 1 und sich selbst ohne Rest teilbar ist. Folge: Eine geordnete Ansammlung von Elementen. Tupel: Eine Folge fester Laenge. Paar: Ein Tupel mit 2 Elementen. Kartesisches Produkt der Mengen M, M', M'',...: Die Menge aller moeglichen Tupel mit einem Element aus M an erster Stelle, einem Element aus M' an zweiter Stelle usw. Schreibweise: M * M' * M'' fuer das Kartesische Produkt von M, M' und M''. "*" ist dabei ein stilisiertes "x". Beispiel: {a,b} * {1,2,3} = {(a,1), (a,2), (a,3), (b,1), (b,2), (b,3)} Man beachte, dass streng genommen die Schreibweise M*M'*M'' unschoen ist, da man bei einer Auswertung von links nach rechts erst alle Paare aus M und M' erhaelt und dann die Menge aller Paare, deren erste Komponente die M-M'-Paare sind und deren zweite Komponente die Elemente aus M'' sind. Das ist jedoch nicht gemeint. n-te Potenz einer Menge M: Das kartesische Produkt aus n M's. Es handelt sich also um die Menge aller Tupel mit n Elementen aus M. Schreibweise: M^n fuer M*M*...M (n-mal) Relation auf den Mengen M, M', M'', ...: Eine Teilmenge des kartesischen Produktes M*M'*M''*.... binaere Relation auf den Mengen M und M': Eine Teilmenge des kartesischen Produkts von M und M'. Schreibweise: R(a,b), a R b fuer "(a,b) in R" Beispiel: "<" ist die Menge aller Paare aus reellen Zahlen, deren erstes Element kleiner ist als das zweite: "<" = {(3,7), (-9,2), ...} Intuition: "Relation" heisst eigentlich nur "Beziehung". In der obigen Interpretation ist also eine "Beziehung" nichts anderes als die Menge derjenigen Paare, die in dieser Beziehung stehen. In dieser Zusammenfassung wird in R(a,b) a als "Eingabe" bezeichnet und b als "Ausgabe". Domain einer binaeren Relation R: Die Menge der Eingaben, fuer die R eine Ausgabe hat. Schreibweise: Dom(R) := {y | Es existiert ein x mit R(x,y) } Range einer binaeren Relation R: Die Menge der Ausgaben von R. Schreibweise: Ran(R) := {x | Es existiert ein y mit R(x,y) } Relation auf der Menge M: Eine binaere Relation auf den Mengen M und M. Intuition: Man kann sich eine Relation R auf einer Menge M sehr gut als einen Graphen vorstellen, in dem jeder Knoten ein Element aus M ist und zwei Knoten a und b immer dann verbunden sind, wenn R(a,b) gilt. (s.a. "Algorithms and Datastructures", ads.txt, auf Englisch). Transitive Relation: Eine Relation R auf einer Menge M, sodass gilt: Wenn R(a,b) und R(b,c), dann auch R(a,c). Intuition: Im zugehoerigen Graphen gibt es zu zwei Kanten a-->b-->c auch immer die Abkuerzungskante a-->c. Symmetrische Relation: Eine Relation R auf einer Menge M, sodass gilt: Wenn R(a,b), dann auch R(b,a). Intuition: Im zugehoerigen Graphen sind alle Kanten auch zurueck vorhanden, also ist der Graph bidirektional. Reflexive Relation: Eine Relation R auf einer Menge M, sodass fuer alle a aus M gilt: R(a,a). Intuition: Im zugehoerigen Graphen gibt sich jeder Knoten selbst die Kante. Irreflexive Relation: Eine Relation R auf einer Menge M, sodass fuer kein a aus M gilt: R(a,a). Aequivalenzrelation: Eine transitive, symmetrische und reflexive Relation. Beispiel: Das "=" auf den natuerlichen Zahlen ist eine Aequivalenz- Relation. Intuition: Der zugehoerige Graph zerfaellt in vollstaendig verkantete Komponenten, in denen je alle Knoten mit allen Knoten (inklusive sich selbst) verbunden sind. Antisymmetrische Relation: Eine Relation R auf einer Menge M, sodass R(a,b) und R(b,a) nur dann gleichzeitig gelten kann, wenn a=b. Intuition: Im zugehoerige Graphen gibt es keine Hin-und-Zurueck-Verbindungen (ausser Kanten zu sich selbst). Asymmetrische Relation: Eine antisymmetrische und irreflexive Relation. Intuition: Im zugehoerige Graphen gibt es keine Hin-und-Zurueck-Verbindungen und auch keine Kanten zu sich selbst. Vollstaendige Relation: Eine Relation auf einer Menge M, sodass fuer beliebige verschiedene a,b aus M gilt: R(a,b) oder R(b,a). Intuition: Im zugehoerige Graphen sind alle Knoten mit allen Knoten durch irgendeine Kante (Hin oder Zurueck) verbunden. Ordnung: Eine reflexive antisymmetrische und transitive Relation. Beispiel: Das Kleinergleich "=<" ist eine Ordnung auf den natuerlichen Zahlen. geordnete Menge: Eine Menge, auf der eine vollstaendige Ordnung definiert ist. Beispiel: Die natuerlichen Zahlen sind eine partiell geordnete Menge mit dem Kleinergleich "=<" Beispiel: Die Potenzmenge einer Menge M ist partiell geordnet mit der Teilmengenrelation "<" (die hier eigentlich einen Strich drunter haben muesste, da M < M gelten soll). lineare Ordnung: Eine asymmetrische, transitive und vollstaendige Relation. Beispiel: Kleinerrelation "<" auf den natuerlichen Zahlen. linear geordnete Menge: Eine Menge, auf der eine vollstaendige lineare Ordnung definiert ist. Komposition zweier Relationen R1 und R2 auf einer Menge M: Die Relation R1 o R2 := {(x,z) | Es existiert ein y, sodass R1(x,y) und R2(y,z)}. Intuition: Stellt man sich die Menge M als Knoten eines Graphen vor und die Kanten von R1 in blau eingezeichnet und die von R2 in rot, so ist die Komposition R1 o R2 die Menge all derer Kanten, die einen Weg aus einer blauen Kante und einer roten Kante abkuerzen wuerden. a --R1--> b ---R2--> c '----R1oR2--------> Identitaets-Relation: Die Relation Id auf einer beliebigen Menge M, die nur zwischen identischen Elementen gilt: Id := {(x,x) | x in M} n-te Potenz einer Relation R auf einer Menge M: Wenn n=0, dann die Identitaets-Relation, sonst die Komposition von R mit der (n-1)-ten Potenz von R. R^0:=Id R^n:=R o R^(n-1) Intuition: Im zugehoerigen Graphen ist R^n die Menge all derer Kanten, die einen Weg aus n Kanten abkuerzen wuerden. Transitive Huelle einer Relation R auf einer Menge M: Die Vereinigung aller Potenzen (ausser der 0-ten) von R. Intuition: Im zugehoerigen Graphen die Menge aller Kanten, die sagen, welche Knoten durch einen Weg irgendeiner Laenge in R verbunden sind. Schreibweise: R+ := {(x,y) | Es existiert ein n mit R^n(x,y) } Reflexiv-Transitive Huelle einer Relation R auf einer Menge M: Die Vereinigung der Identitaets-Relation mit der transitiven Huelle. Intuition: Im zugehoerigen Graphen die Menge aller Kanten, die sagen, welche Knoten durch einen Weg irgendeiner Laenge in R verbunden sind. Ausserdem kann man in 0 Schritten in jedem Knoten zu sich selbst kommen (interessantes Yoga-Konzept :-). Schreibweise: R* := R+ \/ Id Es gelten folgende Eigenschaften: R1 o (R2 o R3) = (R1 o R2) o R3 R1 o (R2 \/ R3) = (R1 o R3) \/ (R1 o R3) (R1 \/ R2) o R3 = (R1 o R3) \/ (R2 o R3) R o R* = R* o R = R+ R o Id = Id o R = R Totale Funktion, Funktion von der Menge M in die Menge M': Eine Relation auf den Mengen M und M', in der es zu jedem Element aus M exakt ein einziges Paar gibt. Schreibweise: f(a)=b fuer (a,b) in f M->M' fuer die Menge aller Funktionen von M nach M' Intuition: Eine Funktion kann man sich wie ein Werkzeug vorstellen, zB einen Hammer. Von alleine macht der Hammer (also f) ueberhaupt nichts. Aber sobald man ihn auf etwas anwendet (auf einen Nagel oder Daumen), verwandelt er das Objekt, auf das er angewendet wird (Nagel ist krumm, Daumen verformt). In eine Funktion geht also etwas rein (Nagel) und es kommt etwas raus (Nagel krumm). Beispiel: sqrt, die Wurzelfunktion, nimmt eine Zahl und gibt ihre Wurzel zurueck. Sie ist ein Werkzeug, welches man auf positive Zahlen anwenden kann. Definitionsmenge einer Funktion von M nach M': M. Intuition: Die Definitionsmenge einer Funktion ist die Menge derjenigen Objekte, auf die ich das Werkzeug sinnvoll anwenden kann. Beim Hammer: Hauptsaechlich Naegel. Schreibweise: Dom(f), ergibt sich daraus, dass Funktionen spezielle Relationen sind. Wertemenge einer Funktion von M nach M': M'. Intuition: Die Wertemenge einer Funktion ist die Menge all derer Objekte, die ich mithilfe des Werkzeugs erzeugen kann. Schreibweise: Ran(f), ergibt sich daraus, dass Funktionen spezielle Relationen sind. Partielle Funktion: Eine Teilmenge einer totalen Funktion. Es gibt also Eingabe-Elemente, fuer die die Funktion nicht definiert ist. Intuition: Sowas wie ein kaputtes Werkzeug, was nur mit bestimmten Objekten funktioniert. Schreibweise: M -' M' fuer die Menge aller partiellen Funktionen von M nach M'. Man benutzt einen Pfeil mit halber Pfeilspitze. Typ: Eine Menge. Beispiel: Die Menge der natuerlichen Zahlen ist ein Typ. Die Menge aller Funktionen von den reellen Zahlen in die natuerlichen Zahlen ist ebenfalls ein Typ. Boolean: Der Typ B={true, false}. Oft sieht man "true" und "false" als Benennungen fuer 1 und 0, sodass B={0,1}. Argument einer Funktion f: Ein Objekt x, auf das die Funktion angewendet wird, d.h. fuer das das y gefunden wird, sodass f(x)=y. Funktion mit mehreren Argumenten: Eine Funktion in eine Menge von Funktionen. Das heisst: Wenn man die Funktion auf etwas anwendet, bekommt man wieder eine Funktion. Diese Funktion kann ich wieder auf etwas anwenden, wobei ich dann entweder wieder eine Funktion oder aber schliesslich ein Objekt zurueck bekomme. Schreibweise: f(a,b) fuer f(a)(b) M->M'->M'' fuer M->(M'->M''), also den Typ aller Funktionen, die ein Element aus M nehmen und eine Funktion von M' nach M'' zurueck geben Intuition: Es geht hier um eine neue Sichtweise auf Funktionen mit mehreren Argumenten. Beispiel sei einer dieser neumodischen Bohrer, bei denen man den Bohrkopf auswechseln kann fuer grosse und kleine Loecher. Fuer dieses Werkzeug gibt es zwei Interpretationen: * herkoemmlich: Der Bohrer f ist ein Werkzeug. Ich gebe ihm zwei Objekte (naemlich einen Bohrkopf x und ein Brett y) und er erzeugt mir ein Brett z mit Loch drin: f(x,y)=z. * neu: Der Bohrer f ist ein Werkzeug. Ich kann es auf Bohrkoepfe anwenden. Wenn ich nun meinen Bohrer f auf einen Bohrkopf x anwende, bekomme ich einen fertigen Bohrer, genannt f(x). Den kann ich nun auf das Brett y anwenden: f(x)(y). Dann bekomme ich ein Brett z mit Loch: f(x)(y)=z. Wichtig ist, dass sowohl der Bohrer f als auch der Bohrer mit Bohrkopf, f(x), eigenstaendige Werkzeuge sind. Beispiel: "+" ist eine Funktion, die zwei Zahlen nimmt und ihre Summe zurueck gibt. Man schreibt +(a,b)=c oder auch a+b=c. Mit der neuen Interpretation ist "+" eine Funktion, die zunaechst nur 1 Argument nimmt, zB 3. Dann ist +(3) eine Funktion, die immer 3 addiert. Wende ich diese Funktion auf zB 4 an, +(3)(4), so bekomme ich 7 zurueck. Beispiel: X->Y ist der Typ aller Funktion, die zu einem X-Objekt ein Y-Objekt liefern R->R+ ist der Typ der Quadrat-Funktion: Diese nimmt eine reelle Zahl und gibt eine positive reelle Zahl zurueck. X->Y->Z bzw. X->(Y->Z) ist der Typ der Funktionen, die zu einem X-Objekt eine Funktion Y->Z liefern. Diese wiederum liefert zu einem Y-Objekt ein Z-Objekt. R->R->B ist der Typ von "<": "<" nimmt zunaechst eine reelle Zahl, z.B. 5. "<(5)" ist nun eine Funktion vom Typ R->B. Sie nimmt wiederum eine reelle Zahl, z.B. 9. Das Ergebnis, <(5)(9) oder in herkoemmlicher Schreibweise 5 < 9, ist nun vom Typ B, naemlich "true". Funktionen mit mehreren Argumenten lassen sich also umschreiben durch Funktionen mit 1 Argument. Dabei schreibt man das Funktionssymbol nach vorne. Identitaet: Die Funktion I mit I(x)=x. Die Identitaet ist die Identitaets-Relation Id, als Funktion aufgefasst. Arithmetisches IF: Eine Funktion f in {true, false} * R * R -> R mit f(true,x,y)=x und f(false,x,y)=y. Schreibweise: a?b:c fuer f(a,b,c) Beispiel: maxvalue = ( (a>b) ? a : b) // Diese Funktion dient hier lediglich zum Verfassen dieses Textes Operator in einer Menge M: Eine Funktion in M*M -> M. Schreibweise: a f b fuer f(a,b) Beispiel: "+" ist ein Operator in den reellen Zahlen. Assoziativitaet: Die Eigenschaft eines Operators f, dass gilt a f (b f c) = (a f b) f c Beispiel: "+" ist assoziativ, weil a+(b+c)=(a+b)+c. "-" hingegen ist nicht assoziativ. Kommutativitaet: Die Eigenschaft eines Operators f, dass gilt a f b = b f a Beispiel: "+" ist kommutativ, weil a+b=b+a. "-" hingegen ist nicht kommutativ. Schreibweise: a f b f c fuer (a f b) f c bei einem assoziativen und kommutativen Operator f. Konjunktion: Der Operator "&" in Boolean mit a & b := a?b:false Disjunktion: Der Operator "|" in Boolean mit a | b := a?true:b Komplement: Die Funktion ~ in Boolean->Boolean mit ~(a) := a?false:true Selbstinvertierende Funnktion: Eine Funktion f in M->M, sodass f(f(a))=a. Beispiel: Die Negation "-" auf ganzen Zahlen ist selbstinvertierend. Mengen-Funktion: Eine assoziative und kommutative Funktion, angewendet auf alle Elemente einer Menge. Schreibweise: F {x1,x2,...} := x1 f x2 f x3 ... meist vergroessert man das Symbol des Operators. Diese Zusammenfassung schreibt den Namen des Symbols oder des Ergebnisses in Grossbuchstaben Beispiel: SUMME {1,2,3} = 1+2+3 VEREINIGUNG {M1,M2,M3} = M1 \/ M2 \/ M3 unendliche Menge: Eine Menge, deren Kardinalitaet unendlich ist. injektive Funktion: Eine Funktion f in A->B, sodass es keine verschiedene x,y gibt, sodass f(x)=f(y). Beispiel: f(x):=x+1 ist injektiv f(x):=x^2 ist nicht injektiv surjektive Funktion: Eine Funktion f in A->B, sodass es fuer jedes b in B ein a in A gibt mit f(a)=f(b). bijektive Funktion, Bijektion: Eine injektive und surjektive Funktion. (s.a. "Algebra" (algebra.txt)) abzaehlbare Menge: Eine Menge M, fuer die es eine injektive Funktion f in M->N gibt, die jedem Element von M eine eigene natuerliche Zahl zuordnet. Beispiel: * {a,b,c} ist abzaehlbar mit f(a)=1, f(b)=2, f(c)=3 * die natuerlichen Zahlen sind abzaehlbar mit f(x)=x * die rationalen Zahlen sind abzaehlbar mit countRat(a/b) = a=0 ? 0: b<0 ? countRat(-a/-b) : a<0 ? countRat(-a/b)+1 : a=1 ? countRat((b-1)/1)+2: countRat((a-1)/(b+1))+2 * die reellen Zahlen sind nicht abzaehlbar s.a. "Analysis" (analysis.txt) und "Solving Constraints over the Reals" (cr.txt, auf Englisch) // Uebungsblatt #10.1c Ist M eine abzaehlbare Menge und X eine beliebige Menge, so ist M-X abzaehlbar. Die injektive Funktion, die M abzaehlt kann auch auf M-X beschraenkt werden, ohne dass ihre Injektivitaet litte. Generell: Ist M abzaehlbar, so ist auch jede Teilmenge von M abzaehlbar Ist M nicht abzaehlbar, so ist auch jede Obermenge von M nicht abzaehlbar. // Uebungsblatt #10.1g Damit gibt es dann zB keine nicht abzaehlbare Teilmenge der ganzen Zahlen. Endliche Mengen sind abzaehlbar. // Uebungsblatt #10.1d Ist M abzaehlbar und X endlich, so ist M\/X auch abzaehlbar. Ist f die injektive Funktion fuer M, so definiert man f'(x) := f(x)+|X| falls x in M k falls x das k-te Element von X ist f' ist injektiv. ueberabzaehlbar: nicht abzaehlbar. isomorphe, strukturgleiche Mengen: Zwei Mengen, zwischen denen es eine Bijektion gibt. Schreibweise: A ~=~ B ein "=" mit einer Schlaengellinie drueber strukturkleinere Menge als eine Menge B: Eine Menge A, sodass es eine injektive Funktion f:A->B gibt. Schreibweise: A ~< B ein "<" mit einer Schlaengellinie drunter Ist A ueberanzaehlbar so ist B ueberabzaehlbar. Ist B abzaehlbar, so ist A abzaehlbar. endliche Menge: Eine nicht unendliche Menge. Formal: M endlich := es gibt ein n in N mit M ~< {0,1,2,...n} Tupel einer Funktion f: Ein Tupel, welches fuer jedes moegliche Argument a der Funktion das jeweilige Ergebnis f(a) enthaelt. Vorraussetzung ist, dass die Definitionsmenge der Funktion endlich und linear geordnet ist. Beispiel: Sei f('a')=1, f('b')=2, ... f('z')=26, dann ist das Tupel (1,2,3,...,26) fuer die Werte a b c z Damit sind Funktionen auf endlichen linear geordneten Mengen und Tupel strukturgleich: A -> B ~=~ B^|A| // Die folgenden Begriffe werden gebraucht, um spaeter das // Lambda-Kalkuel als Formale Sprache hochziehen zu koennen. Alphabet: Eine Menge von Symbolen. Beispiel: Man stelle sich unter den Symbolen natuerlichsprachliche Woerter vor. Folge ueber einem Alphabet: Eine Folge von Symbolen des Alphabets. Schreibweise: w1w2...wn = (w1,w2,...wn) Man reiht die Symbole also einfach aneinander Beispiel: Man stelle sich unter einer solchen Folge eine willkuerliche Aneinanderreihung von natuerlichsprachlichen Woertern vor, zB "fressen Hund Student". Beispiel: Ein Wort ueber dem Alphabet {1,2,3} ist 22323 Kleene-Menge eines Alphabets S: Die Menge aller Folgen ueber S. Schreibweise: S* Beispiel: Wenn das Alphabet die Menge von natuerlichsprachlichen Wortern ist, dann ist ihre Kleene-Menge die Menge aller Folgen von natuerlichen Woertern, zB "Hund Katze der" oder "vielleicht Katze Katze". Beispiel: {1,2,3}*={1,2,3,11,22,33,12,13,21,23,...} Sprache ueber einem Alphabet S: Eine Teilmenge von S*. Beispiel: Man stelle sich unter einer Sprache die Menge derjenigen Woerterfolgen vor, die grammatikalisch richtige Saetze sind. Also ist "Der Hund frisst den Studenten" drin, nicht aber "fressen Hund Student". Produktionssystem ueber einem Alphabet S: Eine Relation auf S*. Schreibweise: --> fuer die Relation selber a --> b fuer den Sachverhalt, dass die Folgen a und b in der Relation --> stehen. Man sagt: a generiert b. Beispiel: siehe Grammatik Produktionsregel: Ein Element eines Produktionssystems. Man spricht von der linken und der rechten Seite einer solchen Regel und meint ihr erstes bzw zweites Element. Beispiel: siehe Grammatik Grammatik: Ein Tupel aus * einem Alphabet N aus sog. Nicht-Terminal-Symbolen * einem Alphabet T aus sog. Terminal-Symbolen * einem Produktionssystem "->" ueber N \/ T * einem sog. Startsymbol s0 aus N N und T muessen disjunkt sein. Die linken Seiten der Produktionsregeln muessen midestens ein Nicht-Terminal-Symbol enthalten. Beispiel: * N ist die Menge der grammatikalischen Kategorien, zB N = {Nomen, Verb, Satz, Artikel, ...} * T ist die Menge der tatsaechlichen Woerter, also zB T = {der, Hund, frisst, Student,...} * s0 ist die umfassendste grammatikalische Kategorie, naemlich s0 = Satz Das Produktionssystem sagt nun, wie richtige Saetze aufgebaut sind, zB dass ein Satz aus Artikel, Nomen und Verb besteht: Satz --> Artikel Nomen Verb ... und dass bestimmte Woerter bestimmten Kategorien angehoeren: Artikel --> der Nomen --> Hund Nomen --> Student Verb --> frisst Wenn ich mit dem Startsymbol "Satz" anfange und immer ein Element der Folge ersetze wie durch eine Produktionsregel angegeben, kann ich zB generieren: Satz ~~~> Artikel Nomen Verb ~~~> der Nomen Verb ~~~> der Nomen frisst ~~~> der Hund frisst Anmerkung: Das Phaenomen der Uebereinstimmung von Artikel und Nomen ("der Hund" und nicht "die Hund") laesst sich hiermit nur sehr unschoen darstellen. Deshalb untersuchen Linguisten meist das Englische. Schreibweise: Die Produktionsregeln muessen nicht eindeutig sein, es kann also zu derselbem linken Seite A mehrere moeglich rechte Seiten B, B', B'', ... geben. Statt A --> B A --> B' A --> B'' schreibt man A --> B | B' | B'' Sind alle anderen Mengen eindeutig, so gibt man oft nur die Produktionsregeln an. // s.a. "Computational Linguistics" (cl.txt, auf Englisch) // s.a. "Theoretische Informatik" (infod.txt) Kategorie eines Terminalsymbols t einer Grammatik: Das Nichtterminalsymbol n, fuer das n-->t eine Produktionsregel ist. Man sagt dann: t ist ein n. Beispiel: Fuer das Deutsche gibt es die Produktionsregel Artikel --> der Also ist die Kategorie von "der" "Artikel". Man sagt " 'der' ist ein Artikel". Formale Sprache, Sprache einer Grammatik (N,T,->,s0): Die Menge aller Folgen ueber T, welche aus s0 durch wiederholtes Ersetzen eines Elementes nach einer Produktionsregel generiert werden koennen. Beispiel: Fuer eine Grammatik der italienischen Sprache siehe "Zusammenfassung des Italienisch-Vertiefungskurses 1" (it.txt). Diese in Prolog geschriebene Grammatik generiert eine syntaktisch korrekte Teilmenge des Italienischen -- *mit* Uebereinstimmung von Artikel und Nomen versteht sich. Beispiel: Die gesamte mathematische Formelsprache laesst sich als Formale Sprache auffassen. Fuer eine Grammatik siehe "Human-oriented Theorem-Proving" (htp.txt, auf Englisch). Beispiel: Die Syntax der meisten Programmiersprachen ist ebenfalls eine Formale Sprache. Fuer eine Grammatik meiner Programmiersprache FAST siehe "FAST - A new Programming Language" (fast.htm, auf Englisch). Ausdruck: Ein Element einer Formalen Sprache, d.h. eine Folge von Symbolen, die sich mit ihrer Grammatik generieren laesst. Beispiel: "Der Hund frisst" ist eine Woerterfolge, die die Grammatik der deutschen Sprache generieren kann, also ein Ausdruck. Strukturelle Induktion: Der Beweis, dass eine Eigenschaft P fuer alle Ausdruecke einer formalen Sprache gilt, nach dem folgenden Schema: 1. Man zeigt, dass die Eigenschaft fuer alle Ausdruecke gilt, die sich direkt aus dem Startsymbol generieren lassen. 2. Man zeigt: Wenn die Eigenschaft fuer alle Ausdruecke mit einer einfacheren Struktur gilt, dann gilt sie auch fuer Ausdruecke, die sich aus solchen einfacheren Ausdruecken zusammensetzen. Die Annahme, dass die Eigenschaft fuer einfachere Ausdruecke gilt, heisst "Induktionsannahme". Damit gilt die Eigenschaft dann fuer alle Ausdruecke. Programmiersprache: Eine formale Sprache, in der man Anweisungen fuer einen Computer formulieren kann. Binaerer Baum, Baum: Ein Objekt oder das Symbol NIL oder ein Tupel aus einem Objekt und 2 Baeumen. Das Objekt heisst "Knoten", die beiden Baeume "Kinder". Schreibweise: Man stellt Baeume meist graphisch dar, zB ((d,b,e),a,(f,c,NIL)) als a / \ b c / \ | d e f Intuition: Fuer die Intuition genuegt die graphische Darstellung voellig. Liste: Das Symbol NIL oder ein Objekt und eine Liste. Schreibweise: x::L fuer die Liste aus dem Objekt x und der Liste L [] fuer NIL Intuition: Eine Liste ist nichts anderes als eine Reihe von Objekten. Man schachtelt diese Reihe allerdings rechtslastig: a b c ~~~> a und die Liste aus b c ~~~> a und die Liste aus b und die Liste aus c ~~~> a und die Liste aus b und die Liste aus c und die Liste NIL ~~~> a::(b::(c::[])) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ETT Syntax ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Hier wird die Elementare Typtheorie als formale Sprache eingefuehrt // Noch kann man damit nicht viel anfangen Konstante: Ein unveraenderliches Objekt. Konstantensymbol: Ein Symbol. Beispiel: c, sigma, k, ... Intuition: So ein Konstantensymbol wird spaeter auf eine wirkliche Konstante abgebildet werden (zB c auf die Zahl drei). Oft verwendet man als Konstantensymbole jedoch die ueblichen Symbole, zB das Zeichen "3" fuer die Zahl drei. Diese Unterscheidung zwischen dem Konstantensymbol "3" und der Zahl drei ist oft aeusserst verwirrend, zumal die Begriffe "Konstantensymbol" und "Konstante" oft durcheinander benutzt werden. Darueber hinaus gibt es selten Grund, ein Konstantensymbol auf eine andere Konstante abzubilden als die ueblicherweise gemeinte. Formalisten delektieren sich manchmal daran, die Symbole '+' und '*' auf etwas anderes als Addition und Multiplikation abzubilden, jedoch wagt es selten jemand, zB. "3" auf die Zahl vier abzubilden. Um der Korrektheit willen unterscheidet diese Zusammenfassung jedoch Konstanten und Konstantensymbole streng, strenger noch als die Formalisten. Atomares Typsymbol: Ein Symbol. Intuition: Klassisch gesehen wird so ein atomares Typsymbol spaeter auf einen Typ (dh eine Menge von Objekten) abgebildet werden. So wird zB das atomare Typsymbol "N" auf die Menge der natuerlichen Zahlen abgebildet, s.a. "Human-oriented Theorem Proving" (htp.txt, auf Englisch). Beispiel: N, das atomare Typsymbol fuer die natuerlichen Zahlen R, das atomare Typsymbol fuer die reellen Zahlen Typsymbol: Ein atomares Typsymbol oder ein Paar aus Typsymbolen. Schreibweise: T->T' fuer das Typsymbol (T,T') T->T'->T'' fuer das Typsymbol T->(T'->T'') Intuition: So ein zusammengesetztes Typsymbol steht fuer den Typ aller Funktionen von T nach T'. Auch hier muss rein technisch wieder unterschieden werden zwischen dem Typsymbol und dem wirklichen Typ. Funktionales Typsymbol: Ein Typsymbol der Form T->T'. Typstufe: Eine Funktion ord, die einem Typsymbol eine natuerliche Zahl zuordnet, sodass ord(b) :=0 falls b ein atomarer Typ ist ord(T->T') := max(1+ord(T),ord(T')) Beispiel: ord(B)=0 ord(N->B)=1 ord(N->N->B)=ord(N->(N->B))=1 ord((N->N)->B)=2 // Uebungsblatt #3.3d N->N->N->N->B (N->(N->(N->(N->B)))) "->" klammert rechtslastig 0 0 0 0 0 Basistypen haben Stufe 0 | | | 1__| links + 1 | | | 1 maximum | | 1_____| links + 1 | | 1 maximum | 1______| links + 1 | 1 maximum 1_______| links + 1 1 maximum (((N->N)->N)->N)->B 0 0 0 0 0 Basistypen haben Stufe 0 1__| | | | links + 1 1 | | | maximum 2_____| | | links + 1 2 | | maximum 3______| | links + 1 3 | maximum 4______| links + 1 4 maximum Variable: Ein Symbol. Meist waehlt man Kleinbuchstaben vom Ende des lateinischen Alphabets. Wichtig hier: Es gibt keine Analogie zu der Unterscheidung zwischen Konstante und Konstantensymbol. Variablen sind ausschliesslich Symbole. Beispiel: x,y,z ETT, Elementare Typtheorie: Die Formale Sprache, die gegeben ist durch die folgende Grammatik: * die Terminalsymbole T sind * Variablen * Konstantensymbole * atomare Typsymbole * das kleine griechische Epsilon mit rundem Ruecken, hier geschrieben "in" * das kleine griechische Lambda, hier geschrieben "lambda" * der Punkt "." * die runden Klammern "(" und ")" * die Nichtterminalsymbole sind VARIABLE, KONSTANTENSYMBOL, APPLIKATION, LAMBDAAUSDRUCK, TYPSYMBOL, ABSTRAKTION * das Startsymbol s0 ist LAMBDAAUSDRUCK * die Produktionsregeln sind LAMBDAAUSDRUCK --> KONSTANTENSYMBOL | VARIABLE | APPLIKATION | ABSTRAKTION KONSTANTENSYMBOL --> irgendwelche Konstantensymbole VARIABLE --> irgendwelche Variablen APPLIKATION --> ( LAMBDAAUSDRUCK LAMBDAAUSDRUCK ) ABSTRAKTION --> ( lambda VARIABLE in TYPSYMBOL . LAMBDAAUSDRUCK ) TYPSYMBOL --> irgendwelche atomaren Typsymbole | TYPSYMBOL -> TYPSYMBOL Beispiel: Seien die Variablen x,y,z. Seien die Konstantensymbole 1,2,3. Seien die atomaren Typsymbole R und B. Dann kann man in dieser Formalen Sprache zB generieren: // Anfang mit Startsymbol LAMBDAAUSDRUCK // 1. Produktionsregel anwenden APPLIKATION // 4. Produktionsregel anwenden ( LAMBDAAUSDRUCK LAMBDAAUSDRUCK ) // 1.Produktionsregel anwenden ( ABSTRAKTION LAMBDAAUSDRUCK ) // 5. Produktionsregel anwenden ( ( lambda VARIABLE in TYPSYMBOL . LAMBDAAUSDRUCK ) LAMBDAAUSDRUCK ) // 3. Produktionsregel anwenden ( ( lambda x in TYPSYMBOL . LAMBDAAUSDRUCK ) LAMBDAAUSDRUCK ) // 6. Produktionsregel anwenden ( ( lambda x in TYPSYMBOL->TYPSYMBOL . LAMBDAAUSDRUCK ) LAMBDAAUSDRUCK ) // 6. Produktionsregel anwenden ( ( lambda x in R->TYPSYMBOL . LAMBDAAUSDRUCK ) LAMBDAAUSDRUCK ) // 6. Produktionsregel anwenden ( ( lambda x in R->B . LAMBDAAUSDRUCK ) LAMBDAAUSDRUCK ) // 1. Produktionsregel anwenden ( ( lambda x in R->B . VARIABLE ) LAMBDAAUSDRUCK ) // 3. Produktionsregel anwenden ( ( lambda x in R->B . z ) LAMBDAAUSDRUCK ) // 1. Produktionsregel anwenden ( ( lambda x in R->B . z ) KONSTANTENSYMBOL ) // 2. Produktionsregel anwenden ( ( lambda x in R->B . z ) 3 ) Intuition: Diese Formale Sprache kann also diese Ausdruecke darstellen: * Variablen Jede Variable ansich ist ein Ausdruck * Konstantensymbole Jedes Konstantensymbol ansich ist ein Ausdruck. Man bemerke, dass dies nicht fuer atomare Typsymbole gilt! * Applikationen Eine Applikation ist ein Ausdruck, gefolgt von einem anderen Ausdruck. Der erste Ausdruck sollte eine Funktion sein. Dann bedeutet die Applikation f a das Anwenden der Funktion f auf das Objekt a. Interessanterweise kommt ETT ohne eine Unterscheidung von Konstanten und Funktionen aus: Funktionen sind Konstantensymbole -- die in Applikationen auftauchen duerfen. * Abstraktionen Eine Abstraktion beschreibt eine Funktion. Beispielsweise ist das Quadrieren eine Funktion, die eine reelle Zahl nimmt und als Ergebnis diese Zahl mit sich selbst multipliziert zurueck gibt, also lambda x in R . * x x | | '''''----- Das Ergebnis, x*x | '------------- Der Typ der Eingabevariable '------------------ Der Name der Eingabevariable Schreibweise: * Im Folgenden stehen a,b,c fuer Konstanten, x,y,z fuer Variablen, A, B, C fuer Lambda-Ausdruecke und T,T' fuer Typen * Wenn die Klammerung eindeutig ist, laesst man sie oft weg, lambda x in T . A fuer (lambda x in T . A) * Fuer mehrere Abstraktionen in Folge laesst man die lambdas weg lambda x in T y in T' . A fuer (lambda x in T.lambda y in T'.A) * Fuer mehrere linkslastig geklammerte Applikationen in Folge laesst man die Klammern weg A B C D fuer (((A B) C) D) * Funktionsdefinitionen schreibt man oft ohne Lambda f x := A fuer f := lambda x in T . A * Die Menge aller Variblen wird mit Var bezeichnet: Var := {x,y,z,...} * Applikationen schreibt man oft infix a f b fuer f a b Signatur: Ein Tupel aus * einer Menge TC aus atomaren Typsymbolen * einer Menge VC aus Konstantensymbolen, genannt "Wertkonstanten" * einer Funktion ty, die jeder Wertkonstanten ein Typsymbol zuordnet, wobei Typsymbole entsprechend der bekannten Definition atomare Typsymbole oder funktionale Typsymbole sein koennen. Eine Signatur spezifiziert also all diejenigen Terminalsymbole der ETT-Sprache, die nicht eingebaut sind (wie zB. lambda es ist) und die keine Variablen sind. Im Folgenden wird immer eine Signatur (TC,VC,ty) vorrausgesetzt. Menge der Typsymbole: Die Menge aller Typsymbole, die aus den atomaren Typsymbolen TC gebildet werden koennen, inklusiver der atomaren Typsymbole selbst. Schreibweise: Ty := TC \/ {T->T' | T in Ty, T' in Ty} Syntaktisches Objekt einer Formalen Sprache: Eines der Typsymbole, Konstantensymbole, oder Variablen. Notationelles Objekt einer Formalen Sprache: Die Form, in der ein syntaktisches Objekt hingeschrieben wird. Wir haben jetzt also 3 Abstraktionsstufen: Die wirklichen ("semantischen") Objekte, zB die Additionsfunktion Dies ist das mathematische Objekt ansich, also hier die Funktion selber. Die syntaktischen Objekte, zB das Funktionssymbol "+" Dies ist die Modellierung des mathematischen Objekts in der Formalen Sprache, hier also als Funktionssymbol vom Typsymbol R->R->R. Die notationellen Objekte, zB das Zeichen "+" Dies ist die Schreibweise, die wir fuer das syntaktische Objekt benutzen, hier also das griechische Kreuz aus zwei gleich langen sich rechtwinklig kreuzenden Balken. Intuition: Im Wesentlichen werden notationelle Objekte stark mit den syntaktischen Objekten identifiziert und auch zwischen den syntaktischen und semantischen Objekten besteht meist ein inniglicher Zusammenhang. Diese Zusammenfassung uebernimmt die gaengige Unterscheidung von Syntax und Semantik, indem die Objekte der Syntax hier durchgaengig als "Symbole" bezeichnet werden. Notationelle Objekte (und Konventionen) finden sich hier unter der Rubrik "Schreibweise". Syntaxbaum eines Lambda-Ausdrucks A: * Wenn A eine Variable ist, dann der Baum mit Knoten A und ohne Kinder. * Wenn A ein Konstantensymbol ist, dann der Baum mit Knoten A und ohne Kinder. * Wenn A eine Abstraktion der Form "LAMBDA x in T.B" ist, dann der Baum mit Knoten "LAMBDA x in T" und einem Kind, naemlich dem Syntaxbaum von B. * Wenn A eine Applikation der Form "B B'" ist, dann ein Baum mit leerem Knoten und 2 Kindern, naemlich den Syntaxbaeumen von A und B. Beispiel: Der Syntaxbaum von LAMBDA x in T . + 3 4 LAMBDA x in T | * / \ * 4 / \ + 3 Man beachte, dass "+ 3 4" fuer "(+ 3) 4" steht, was den linkslastigen Teilbaum erklaert. Kopf einer Lambda-Abstraktion: Die Folge ihrer Symbole vor dem Punkt. Rumpf einer Lambda-Abstraktion: Die Folge ihrer Symbole nach dem Punkt. Praeterm: Ein Lambda-Ausdruck. Die Menge aller Praeterme wird mit pT bezeichnet. // Das Skript verwendet "PT", was jedoch spaeter auch fuer die // Primbaeume steht. Auftreten einer Variablen in einem Praeterm: Eine Position in dem Praeterm, an der die Variable steht. Beispiel: In "(A x) (lambda x in T . B x)" tritt x dreimal auf. Vorkommende Variable eines Praeterms: Eine Variable, die in dem Praeterm auftritt. Schreibweise: VV(A) fuer die Menge der vorkommenden Variablen in A Gebundene Variable einer Abstraktion: Die im Kopf genannte Variable. Diese Variable ist nur im Rumpf der Lambda-Abstraktion gueltig (was das heisst, s.u.). Beispiel: In "(lambda x in T. A x) x" ist x innerhalb der Klammern gebunden, ausserhalb nicht Freies Auftreten einer Variablen in einem Praeterm: Ihr Auftreten in einem Unter-Praeterm, in dem sie nicht gebunden ist. Beispiel: In "(lambda x in T. A) x" ist das hintere Auftreten von x frei. Schreibweise: FV(A) fuer die Menge der freien Variablen des Praeterms A Definierendes Auftreten einer Variablen in einem Praeterm: Ihr Auftreten im Kopf einer Lambda-Abstraktion. Beispiel: In "lambda x in T . A x" tritt x links definierend auf. Benutzendes Auftreten einer Variablen in einem Praeterm: Ihr nicht definierendes Auftreten. Beispiel: In "lambda x in T . A x" tritt x rechts benutzend auf. Atomarer Praeterm: Ein Praeterm, der eine Variable oder eine Konstante ist. Zusammengesetzter Praeterm: Ein Praeterm, der nicht atomar ist. kombinatorischer Praeterm: Ein Praeterm, der keine Abstraktion enthaelt. In diesem Fall ist jede vorkommende Variable frei, FV(A)=VV(A). // Uebungsblatt #3.3f offener Praeterm: Ein Praeterm, der mindestens eine freie Variable hat. geschlossener Praeterm: Ein Praeterm, der nicht offen ist. // Uebungsblatt #3.3g ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Lambda-Elimination ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Kaum ist es da, wollen wir es schon wieder eliminieren: Auch // Terme mit lambda lassen sich kombinatorisch darstellen Die Funktion f geht homomorph durch: Die Anwendung von f auf eine Applikation (A B) liefert die Applikation (f(A) f(B)). Schoenfinkel-Funktionen: Die folgenden Funktionen: * Die "Identitaet" gibt einfach nur ihr Argument zurueck I x := x * Die "Konstante" ignoriert das zweite Argument K x y := x * Die "S-Funktion" wendet die ersten beiden Argumente auf das dritte an S f g x := (f x) (g x) Es gilt I = S K K. Lambda-Elimination: Die Funktion elim, die zu einem Praeterm einen kombinatorischen Praeterm liefert. Ignoriert man die Typsymbole, so geht das wie folgt: // Uebungsblatt #2.9 elim c := c elim x := x elim (A B) := (elim A) (elim B) geht homomorph durch elim (lambda x . x) := I elim (lambda x . y) := K y falls y!=x elim (lambda x . c) := K c elim (lambda x . lambda y . A) := elim (lambda x . elim(lambda y . A)) falls x frei in A K (elim (lambda y . A)) sonst elim (lambda x . A B) := S (elim (lambda x . A)) (elim (lambda x . B)) falls x frei in A B K (elim (A B)) sonst Wesentliche Idee: * Falls eine Abstraktion die Identitaet ist, wird sie zu I elim (lambda x . x) = I * Falls eine Abstraktion die gebundene Variable gar nicht braucht, kann sie durch K ersetzt werden elim (lambda x . y) = K y * Bei 2 ineinander geschachtelten Abstraktionen taucht das elim in die innere Abstraktion ab elim (lambda x . lambda y . x) = elim (lambda x . elim (lambda y . x)) * Bei einer Abstraktion um eine Applikation kopiert man das elim und lambda vor die beiden Terme und schreibt S davor: elim (lambda x . A B) = S (elim (lambda x . A)) (elim (lambda x . B)) Oft vereinfacht man den entstehenden Term noch mit der Eta-Regel. Diese wird spaeter eingefuehrt, hier reicht das Grundprinzip lambda x . A x ~~~> A Beispiele: // Uebungsblatt #2.8 elim(lambda x y . y) = elim(lambda x . elim(lambda y.y)) abgetaucht = elim(lambda x . I) Identitaet = K I Konstante elim(lambda x y z . y) = elim(lambda x . elim(lambda y . elim(lambda z . y))) abgetaucht = elim(lambda x . elim(lambda y . K y)) Konstante = elim(lambda x . elim(K)) eta = elim(lambda x . K) trivial = K K Konstante elim(lambda x y z . x) = elim(lambda x . elim(lambda y . elim(lambda z . x))) abgetaucht = elim(lambda x . elim(lambda y . K x)) Konstante = elim(lambda x . K (K x)) Konstante = S elim(lambda x.K) elim(lambda x. K x) davorkopiert = S (KK) elim(K) eta = S (KK) K trivial elim(lambda x. f x (f x x)) = elim(lambda x. (f x) (f x x)) geklammert = S elim(lambda x.f x) elim(lambda x.f x x) davorkopiert = S elim(f) elim(lambda x.(f x) x) eta = S f (S elim(lambda x.f x) elim(lambda x.x)) davorkopiert = S f (S elim(f) I) eta = S f (S f I) trivial ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ DeBruijnsche Darstellung von Praetermen ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Hier wird eine alternative Darstellung von Praetermen eingefuehrt, // die erst eine sinnvolle Beschreibung von Substitution, Typen und // damit Semantik ermoeglicht. DeBruijn-ETT: Die Formale Sprache ETT, wobei fuer die Variablen die Symbole <0>, <1>, <2>... benutzt werden. Ausserdem wird die Abstraktion zu ABSTRAKTION --> ( lambda TYPSYMBOL . LAMBDAAUSDRUCK ) d.h. der Variablenname faellt weg. Die Variablennummerierungen haben folgende Bedeutung: * die freien Variablen, die normalerweise mit x,y,z bezeichnet werden, werden einfach durchnummeriert und heissen <0>, <1>, <2>. * innerhalb einer Abstraktion steht die Variable <0> fuer die durch die Abstraktion gebundene Variable. Die freien Variablen werden um einen aufgeschoben: Die <0> wird zu <1>, die <1> zu <2> usw. Beispiel: Es gebe die 3 freien Variablen x,y,z mit den DeBruijnschen Namen <0>, <1>, <2> * A x ~~~~~> A <0> * A (x z) y ~~~~~> A ( <0> <2> ) <1> * lambda x in T . x ~~~~~> lambda T . <0> * z (lambda x in T . x z) ~~~~~> <2> (lambda T . <0> <3>) * (lambda m in T . lambda n in T . m n) ~~~~~> (lambda T . lambda T . <1> <0>) Manchmal laesst man die Abbildung der freien Variablen auf ihre DeBruijn-Nummerierung offen und schreibt zB . Heisst die freie Variable y bei DeBruijn zB <14>, so waere mit die Variable <16> gemeint. Von nun an werden Variablen als durchnummeriert betrachtet und eine Variable (dh. das Symbol, nicht ihr Wert!) soll gleich einer natuerlichen Zahl sein. Damit gibt es nun keine Doppeldeutigkeiten mehr: In einem Term der normalen ETT wie etwa x (lambda x in T . A) kann A nicht auf die freie Variable x zugreifen, weil jedes Auftreten von x als Auftreten der gebundenen Variable interpretiert wuerde. In der DeBruijn-Darstellung laesst sich unterscheiden: <0> (lambda T . <0>) meint die gebundene Variable <0> (lambda T . <1>) meint die freie Variable <0> Es gilt schlicht Var = {0,1,2,3,...} DeBruijn spricht sich wie "de Braun". Im Folgenden wird vorlesungskonform die Formale Sprache ETT als Notation (!) der deBruijn-ETT angesehen. Die Schreibweisen von ETT und deBruijn mischen sich also, sind aber beides nur Notationen fuer die Formale Sprache deBruijn-ETT. Im Syntax-Baum eines Praeterms nach DeBruijn sind demnach die Variablennamen ersetzt durch natuerliche Zahlen und die Lambdas tragen nur das Typsymbol und nicht mehr den Variablennamen. Beispiel: // Uebungsblatt #1.8a lambda x y. u (lambda z.v x z) (lambda z.w x y ((lambda z.z)z)) alle Variablen vom Typsymbol N lambda N x | lambda N y | *__________________ / \ * lambda N z / \ | lambda N z *_____________ | / \ * * *_______ / \ / \ / \ * <0> * <1> lambda N z <0> / \ / \ | <2> <2> <0> // Die Musterloesung addiert nicht die Werte zu den freien Variablen Shifting: Die Tatsache, dass die Variable innerhalb einer Abstraktion die Nummer hat. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Typisierung ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Typumgebung: Eine partielle Funktion, die zu einer Variablen ein Typsymbol liefert. Im Folgenden wird immer eine Typumgebung namens Gamma vorrausgesetzt. Typ: Gamma in Var -' Ty Schreibweise: Man benutzt das grosse griechische Gamma. // Uebungsblatt #3.3k Typ-Cons: Die folgende Funktion "::", die zu einem Typsymbol T und einer Typumgebung Gamma eine Typumgebung liefert: Typ: :: in Ty->(Var-'Ty)->(Var-'Ty) Definition: T :: Gamma := {(0,T)} \/ {(n+1,T') | Gamma(n)=T'} Intention: Diese Funktion shiftet also alle Variablen. Sei das Ergebnis der Typ-Cons die neue Typumgebung Gamma' := T :: Gamma Dann gilt: Gamma'(i) = Gamma(i-1) Ausserdem fuegt sie fuer die Variable 0 (deren Stelle ja sozusagen frei geworden ist) den Typ T hinzu: Gamma'(0)=T Die Typ-Cons ist also die geshiftete Version der Typumgebung Gamma, die innerhalb einer Abstraktion funktioniert, wo ja die Variable i nun i+1 heisst. // Uebungsblatt #2.5 Typrelation: Die folgende Relation "|- :" ueber Typumgebungen, Praetermen und Typsymbolen: Typ: "|-:" < (Var -> Ty) * pT * Ty Definition: Gamma |- c : ty(c) Konstanten haben das Typsymbol aus ty Gamma |- x : Gamma(x) Variablen haben das Typsymbol aus der Typumgebung; geht nur, wenn Gamma(x) definiert ist Gamma |- (A B) : T wenn gilt Gamma |- A : T'->T Gamma |- B : T' Gamma |- (lambda T.A):T->T' wenn gilt T::Gamma |- A : T' Intuition: Diese Relation gilt also im Wesentlichen zwischen einem Praeterm und seinem Typsymbol. Es ist allerdings eine Relation (und keine Funktion), da ja noch nicht gesagt ist, dass das Typsymbol erstens existiert und zweitens eindeutig ist. Sind zB a und b beide vom Typsymbol N, so gibt es kein T, sodass gilt Gamma |- (a b) : T Hat sich andererseits eine Variable in die Konstantensymbole verirrt, so liessen sich unter Umstaenden 2 Typsymbole finden. Term, Gamma-wohlgetypter Praeterm, Gamma-Term: Ein Praeterm, dem die Typrelation ein eindeutiges Typsymbol zuordnet. Da die Typumgebung Gamma vorrausgesetzt ist, spricht man einfach von einem Term. Schreibweise: Ter fuer die Menge aller Terme Typsymbol eines Terms A: Das Typsymbol T, sodass Gamma |- A : T. Beispiel: // Uebungsblatt #3.3p Sei T2 eine Abkuerzung fuer as Typsymbol T->T. Sei g vom Typsymbol T2. Wir bestimmen die Typsymbole schrittweise, Zeile fuer Zeile, in (lambda x. f x (lambda y. g y )) (f g g ) 1: | | | T2 | | T2 T2 2: | | T T T2->T2->? 3: T2->T2->? | | 4: | T2 | 5: | T2->T2->T2 6: T2->T2->T2 Funktionssymbol: Ein Konstantensymbol mit einem funktionalen Typsymbol. Stufe eines Terms: Die Typstufe seines Typsymbols. Schludrige ETT: Die formale Sprache ETT, wobei die Abstraktion die Form hat ABSTRAKTION --> ( lambda VARIABLE . LAMBDAAUSDRUCK ) und die Variablen wie bei DeBruijn Zahlen sind. Ueberdies ist eine Typumgebung Gamma vorhanden. Dieses entspricht der klassischen ETT, wobei man sich fuer die Typisierung auch der gebundenen Variablen auf Gamma verlaesst. Auch die schludrige ETT kann als Notation der deBruijn-ETT aufgefasst werden. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Rechnen mit Termen ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Free: Eine Funktion, die testet, ob eine Variable in einem deBruijn- Term frei vorkommt: Typ: free in Var->pT->B Definition: free x c := 0 free x x := 1 free x y := 0 fuer x!=y free x (A B) := (free x A) | (free x B) free x (lambda T . A) := free (x+1) A // Uebungsblatt #2.1 Up: Eine Funktion, die zu einer natuerlichen Zahl h und einem Praeterm einen neuen Praeterm liefert, in dem alle Variablen groessergleich h um 1 erhoeht wurden. Typ: up in N->pT->pT Definition: up h c := c up h x := x falls x=h up h (A B) := ((up h A) (up h B)) geht homomorph durch up h (lambda T . A) := up (h+1) A macht Shifting Beispiel: up 2 (<0> <1> <2> <3> (lambda T. <0> <1> <2> <3>)) = (<0> <1> <3> <4> (lambda T. <0> <1> <2> <4>)) Schreibweise: ^A := up 0 A wobei ^ ein Pfeil nach oben ist // Uebungsblatt #2.2 Down: Eine Funktion, die zu einer natuerlichen Zahl h und einem Praeterm einen neuen Praeterm liefert, in dem alle Variablen groessergleich h um 1 erniedrigt wurden. Die Definition ist aehnlich wie bei up. Schreibweise: vA := down 0 A wobei v ein Pfeil nach unten ist // Uebungsblatt #2.3 Substitution: Eine Funktion, die einen Praeterm, eine Variable und noch einen Praeterm bekommt und einen neuen Praeterm zurueck gibt, in dem jedes freie Auftreten der Variable durch den zweiten Praeterm ersetzt wurde. // Uebungsblatt #2.4 Typ: substitution in pT->Var->pT->pT Schreibweise: A[z->B] fuer die Anwendung der Substitution auf A, z und B Definition: x[z->C] := x falls x!=z Fremde Variablen nicht ersetzen z[z->C] := C z selber wird ersetzt c[z->C] := c Konstanten werden nicht ersetzt (A B)[z->C] := (A[z->C] B[z->C]) geht homomorph durch (lambda T . A)[z->C] := (lambda T . A[z+1 -> ^C]) Die Substitution verlagert sich in den Lambda-Ausdruck, jedoch mit Shifting Beispiel: (<0> <1> <2>)[1 -> c] = (<0> c <2>) (lambda T . <0> <1> <2>)[1 -> c ] = (lambda T . <0> <1> c) (lambda T . <0> <1> <2>)[1 -> <7>] = (lambda T . <0> <1> <8>) Im Falle von Standard-ETT muss man ein bisschen tricksen: // Uebungsblatt #1.7 (lambda x.y x) [y->x] = (lambda x'.x x') y (lambda x.x y) [y->x] = y (lambda x'.x' x) // Hier bindet die Substitution staerker als die Applikation! // Da ich eine solche Festlegung fuer willkuerlich und schwer // zu merken erachte, wird in dieser Zusammenfassung // stets geklammert. (lambda x.lambda y.f x y) [f -> x y] = (lambda x'.lambda y.x y x' y') Funktionssubstitution: Eine Funktion, die eine Funktion f, einen Wert x aus Dom(f) und einen Wert y aus Ran(f) bekommt und eine Funktion f' zurueck gibt, die sich wie f verhaelt, aber fuer x y zurueck gibt. Schreibweise: f[x->y] := { (a,b) | f(a)=b und a!=x} \/ {(x,y)} Intuition: Man ueberschreibt also den Rueckgabewert fuer x mit dem neuen Wert y. Smolka'sche Regel: Die folgende Funktion von schludrigen ETT-Termen in die DeBruin-Terme: // Uebungsblatt #2.6 Typ: smolka in pT->pT Definition: smolka c := c smolka x := x smolka (A B) := (smolka A) (smolka B) geht homomorph durch smolka (lambda x . A) := lambda (Gamma x) . (^smolka(A))[x+1 -> <0>] Intuition: Diese Funktion uebersetzt im Wesentlichen einen schludrigen ETT-Term, indem sie das Typsymbol aus Gamma holt. Beispiel: Sei Gamma(x)=N smolka (y (lambda x . x y)) = (y (lambda N . <0> )) Beispiel: // Uebungsblatt #3.3j Sei Gamma(x)=X, seien x und f Variablen smolka (lambda x . f x x) = lambda X . <0> <0> Beta-Vereinfachung: Die folgende Funktion auf Termen: Typ: beta in Ter -> Ter Definition: beta x := x beta c := c beta (lambda T . A) B := v(A[0 -> ^B]) beta (A B) := (beta A) (beta B) geht homomorph durch, falls A keine Abstraktion beta (lambda T . A) := lambda T. (beta A) Intuition: Beta entspricht dem Anwenden der Funktion. Hat man zB die Quadratfunktion f(x)=x*x, so errechnet sich f(5), indem man den Funktionsterm "x*x" hernimmt und alle x durch 5 ersetzt: Sei f = lambda N . * <0> <0> beta(f 5) = beta((lambda N . * <0> <0>) 5) = * 5 5 Beispiel: beta ((lambda T . a) b) = a beta ((lambda T . <7>) b) = <6> beta ((lambda T . <0>) b) = b Eta-Vereinfachung: Die folgende Funktion auf Termen Typ: eta in Ter -> Ter Definition: eta x := x eta c := c eta (A B) := (eta A) (eta B) geht homomorph durch eta (lambda T . A <0>) := A falls <0> nicht frei in A eta (lambda T . A) := lambda T. (eta vA) sonst Intuition: Wenn A eine Funktion ist, dann ist lambda T . A <0> sowas wie eine Wrapper-Funktion: Sie nimmt ein Argument und reicht dieses Argument dann an A weiter. Ein Grossteil aller Java-Funktionen sind solche Wrapper-Funktionen: public static int myGenuineFunction(int x) { return myAdversarysStupidFunction(x); } Da dieses Weiterleiten komplett nutzlos ist, kann die Wrapper-Funktion eliminiert werden. Vorraussetzung ist, dass die Variable wirklich nicht innerhalb des Funktionsterms A auftaucht. Beispiel: eta (lambda T . f <4> <5> <0>) = f <3> <4> Beispiel fuer infix geschriebene Funktionssymbole: eta (lambda x. lambda y. x + y) = + Lambda-Vereinfachung: Die Kombination von Eta-Vereinfachung und Beta-Vereinfachung. XXX-Reduktion: Das Umformen eines Terms nach der XXX-Vereinfachung. XXX ist Lambda, Eta oder Beta. Beispiel: // Uebungsblatt #1.8b lambda x y. u (lambda z.v x z) (lambda z.w x y ((lambda z.z)z)) lambda x y. u (lambda z.v x z) (lambda z.w x y z) beta lambda x y. u (v x) (w x y) eta XXX-Redex, XXX-Reducible-Expression: Ein Term, auf dem die XXX-Vereinfachung nicht die Identitaet ist. XXX ist Lambda, Eta oder Beta. // Uebungsblatt #3.3e XXX-normaler Term: Ein Term, der keine XXX-Redex ist. XXX ist Lambda, Eta oder Beta. // Uebungsblatt #3.3h ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Semantik ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Die Semantik ist im Wesentlichen die Abbildung von Termen auf // Objekte der (mathematischen) Welt. Prae-Denotation: Eine Funktion, die den Symbolen der Signatur wie folgt mathematische Objekte zuordnet. Typ: pD in (TC \/ VC)->(Mengen\/Objekte) Definition: pD(T) ist eine nicht-leere Menge fuer ein atomares Typsymbol T aus TC pD(c) ist ein Objekt fuer ein Konstantensymbol c Intuition: Diese Funktion bildet also die syntaktischen Symbole ab auf "echte" Objekte und Mengen. Jedes atomare Typsymbol entspricht einer Menge und jedes Konstantensymbol einer Konstante, wobei funktionale Typsymbole und Typkonsistenz noch nicht bedacht sind. Anmerkung: Diese Zusammenfassung verwendet diesen Begriff, um die Definition der Denotation etwas zu entzerren. Wrapper-Denotation zu einer Prae-Denotation pD: Die folgende Funktion wD, die sich wie die Prae-Denotation verhaelt, aber auch funktionale Typsymbole kennt: Typ: wD in (Ty \/ VC)->(Mengen\/Objekte) Definition: wD(c) := pD(c) fuer Konstantensymbole c wD(T) := pD(T) fuer atomare Typsymbole T wD(T->T') := wD(T)->wD(T') fuer Typsymbole T->T' Intuition: wD tut genau dasselbe wie die Prae-Denotation pD, kennt jedoch zusaetzlich funktionale Typsymbole. Das Typsymbol T->T' wird erwartungsgemaess abgebildet auf die Menge der Funktionen von wD(T) nach wD(T'). Denotation, Interpretation: Eine Prae-Denotation D, sodass mit der zugehoerigen Wrapper-Denotation wD gilt: D(c) in wD(ty(c)) fuer alle Konstantensymbole c Intuition: Diese Denotationsfunktion bildet also ein Konstantensymbol c ab in die Menge, die seinem Typsymbol entspricht. Man einigt sich meist darauf, dass die Konstantensymbole auf die ueblichen Konstanten abgebildet werden und die Typsymbole auf die ueblichen Typen. Schreibweise: Man verwendet das kalligraphische D -- und zwar fuer die Denotation und auch die zugehoerige Wrapper-Funktion! Man kann also von der "Denotation D" sprechen und dann trotzdem D (die Wrapper-Funktion naemlich) auf funktionale Typsymbole anwenden! Beispiel: D(N) = {0,1,2,3,...} D(3) = 3 D(N->N->N) = { *, +, -, ... } // Wrapper-Funktion Anmerkung: Diese "Interpretation" oder "Denotation" heisst anderswo "Struktur", s.a. "Human-oriented Theorem Proving" (htp.txt). Im Folgenden wird immer eine Denotation D vorrausgesetzt. Denjenigen Funktionen, die eigentlich D als Argument benutzen, wird ein "D" als Quasi-Namensbestandteil mitgegeben, um diesem impliziten Argument Rechnung zu tragen. Universum: Die Vereinigung aller Denotationen aller Typsymbole ValD := VEREINIGUNG D(t) fuer alle t in Ty Das Universum ist also die Menge aller Objekte und aller Funktionen, ueber die man mit der Denotation sprechen kann. "ValD" ist also der "Wald" aller benennbaren Dinge. Belegung, State, Zustand: Eine partielle Funktion von Variablen in das Universum. Die Menge all dieser Funktionen heisst "StaD": Definition: StaD := Var -' ValD Intuition: Ein Zustand aus StaD belegt eine Variable mit einem Wert. Die Variablen kommen quasi in die StaDt und von dort in den Wald. Beispiel: Sei s ein Zustand aus StaD. Dann koennte zB gelten s(x)=3 Anmerkung: Diese "Zustaende" heissen anderswo "Valuation", s.a. "Human-oriented Theorem Proving" (htp.txt) Gamma-Zustand: Ein Zustand, der alle von der Typumgebung Gamma erfassten Variablen typgerecht abbildet. Die Menge aller Gamma-Zustaende heisst "Sta(D,Gamma)". Definition: Sta(D,Gamma) := { s in StaD | Dom(Gamma) < Dom(s) und fuer alle Variablen x in Dom(Gamma) gilt: s(x) in D(Gamma(x)) } Intuition: Bisher konnte Gamma behaupten, eine Variable x sei vom Typsymbol N (und dieses wurde auf {0,1,2,3...} abgebildet), aber ein Zustand konnte x mit dem Wert "true" belegen. Das koennen Gamma-Zustaende nicht. Beispiel: Sei Gamma(x)=N. Sei D(N)={0,1,2,3...}. Dann ist die Funktion s mit s(x)=3 ein Gamma-Zustand, weil Dom(Gamma) = {x} < Dom(s) und s(x)=3 in D(Gamma(x))=D(N)={0,1,2,3,...} // Uebungsblatt #3.3L Variablen-Cons: Die folgende Funktion "::", die zu einer Variablen v und einem Zustand s den folgenden Zustand liefert: Typ: :: in Var->(Var-'ValD)->(Var-'ValD) Definition: v :: s := {(0,v)} \/ {(n+1,c) | s(n)=c} Intuition: Diese Funktion shiftet also alle Variablen aehnlich dem Typ-Cons. Innerhalb einer Abstraktion kann so garantiert werden, dass die Variable <1> (die ja in Wirklichkeit <0> ist) trotz ihrer Umbenennung auf denselben Wert abgebildet wird. Term-Denotation: Die folgende Funktion tD, die mithilfe einer Denotation D und eines Zustandes Terme auf mathematische Objekte abbildet: Typ: tD in Ter->StaD->ValD Definition: tD c s := D(c) tD x s := s(x) falls s fuer x definiert ist tD (A B) s := (tD A s) (tD B s) geht homomorph durch tD (lambda T . A) s := lambda x in wD(T) . (tD A x::s) Intuition: Die Term-Denotation ist also die Erweiterung der Denotation auf Terme. Damit kann man nun einem Lambda-Ausdruck ein mathematisches Objekt zuordnen. Beispiel: tD(+ 3 4) +,3,4 sind Konstantensymbole = tD(+) tD(3) tD(4) = + 3 4 Jetzt ist + die normale Additionsfunktion und 3 und 4 sind Zahlen = 7 Schreibweise: Fuer tD schreibt man (genau wie fuer die Denotation und die Wrapper-Denotation) ein kalligraphisches D. Wannimmer ein solches D auftaucht, kann es also alle Elemente der Formalen Sprache abbilden auf mathematische Objekte. Man sagt "Der Term A wertet zu K aus" fuer einen Zustand s und D A s = K. Anmerkung: Durch die Trennung von Symbolen und Objekten ist eine Abstraktion von der Notation gewonnen. Da allerdings die Symbole meist auf immer dieselben bekannten Objekte abgebildet werden und ueberdies diese Objekte wieder mit Symbolen bezeichnet werden, ist der Gewinn dieser Abstraktion nicht offensichtlich. Auch wenn naemlich + die uebliche Addition ist und 3 und 4 die Zahlen, so muss trotzdem die Zeichenfolge "+ 3 4" wiederum "interpretiert" werden. Besonders offensichtlich wird dieses Problem daran, dass wir lambda als Funktionsabstraktion fuer ETT definiert haben -- es aber nun auch auf semantischer Ebene verwenden (letzte Zeile der Definition), wo wir es ueberhaupt nicht eingefuehrt haben. Es soll natuerlich auch hier fuer die Funktions-Abstraktion stehen, aber eben dadurch, dass es "fuer etwas steht", ist es ansich ein syntaktisches Objekt. Mit der Funktions-Abstraktion geht die Existenz einer Variable auf sematischer Ebene einher: Der Ausdruck "lambda T.<0>" wird zu "lambda x in D(T).x" auswerten -- und wir haben es mit einer Variable (einem hoechst syntaktischen Objekt) in der Semantik zu tun. Bildchen: Hier ein zusammenfassendes Bildchen fuer den Zusammenhang zwischen Syntax und Semantik _____________ Konstantensymbole ----ty---> / funktionale \ <---Gamma-- Variablen VC | Typsymbole | | | | | | | | atomare | | | | Typsymbole |Typsymbole Ty | |Denotation D | TC | | | \__|__________/ Zustand s in StaD| | | | | | Denotation D| |Wrapper-Denotation wD | | | | | | V V | | _______________ | | / (echte) Typen \ | V \_______________/ V ______________________________/\_____________________________ / (echte) Konstanten \ \_____________________________________________________________/ Universum ValD Koinzidenz: Die Tatsache, dass es keinen Unterschied macht, welchen Zustand man fuer die Term-Denotation verwendet, solange beide Zustaende in ihren Werten fuer die freien Variablen uebereinstimmen. Formal: Seien s und s' Zustaende in StaD und sei s(x)=s(x') fuer alle freien Variablen x eines Terms M Dann gilt fuer eine Term-Denotation tD: tD M s = tD M s' Beweis: // Uebungsblatt #3.2 tD c s = D c = tD c s' tD x s = tD x s' nach Vorraussetzung tD (A B) s = (tD A s) (tD B s) = (tD A s') (tD B s') nach Induktionsannahme = tD (A B) s' tD (lambda T . A) s = lambda x in D(T) . (tD A x::s) s und s' stimmen in allen freien Variablen ueberein. Also stimmen auch x::s und x::s' in allen freien Variablen ueberein. Da A einfacher ist als (lambda T . A) folgt per Induktionsannahme: = lambda x in D(T) . (tD A x::s') = tD (lambda T . A) s' Substitutionslemma: Die Tatsache, dass es fuer die Term-Denotation keinen Unterschied macht, ob man eine Substitution am Term durchfuehrt oder die Substitution in den Zustand verschiebt. Formal: Sei s ein Zustand aus StaD. Dann gilt fuer eine Term-Denotation tD: tD (A[x->B]) s = tD A (s[x->(tD B s)]) Signifikante Variable eines Terms: Eine Variable, deren Zustand die Denotation des Terms beeinflusst. Schreibweise: SV(A) := {x in Dom(Gamma) | es existiert ein Zustand s und ein Wert w, sodass D A s != D A s[x->w] } Intuition: Wenn man einer signifikanten Variable einen anderen Wert zuweist, kann sich der Wert des Terms aendern. Beispiel: In x+4 ist x hochgradig signifikant. In 4+x-x ist der Wert von x voellig irrelevant. // Uebungsblatt #6.5 In x&y&(1|u|v) sind x und y signifikant, u und v hingegen nicht Wegen der Koinzidenz muss jede signifikante Variable eine freie Variable sein: SV(A) < FV(A). ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Lambda-Kalkuel fuer Mengen und Aussagen ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Die Aussagenlogik und die Mengenlehre lassen sich auf ETT // zurueckfuehren. s.a. "Human-oriented Theorem-Proving" (htp.txt, auf Englisch) unter Typtheorie "Q0" Gleichheit: Ein Funktionssymbol "=" vom Typsymbol T->T->B fuer alle Typsymbole T. Die Gleichheit soll durch die Denotation auf eine Aequivalenzrelation abgebildet werden. Intuition: Dieses Symbol soll die uebliche Gleichheit darstellen. Wichtig ist, dass zwei Funktionen genau dann gleich sind, wenn sie fuer alle Argumente dasselbe Ergebnis liefern. Aussage: Ein Term vom Typsymbol B. Beispiel: Ist das Konstantensymbol "<" mit Typsymbol N->N->B gegeben, und umfassen die Konstantensymbole die natuerlichen Zahlen so ist eine Aussage zB < 3 6 Wahr: Das Konstantensymbol 1 := = = = vom Typsymbol B Fuer "wahr" schreibt man also 1. Man definiert "wahr" als die Gleichheit von "=" und "=". Mit egal welcher Denotation wird also "1" auf jeden Fall auf das Objekt abgebildet, was in dem zugehoerigen Universum "wahr" bedeutet. Falsch: Das Konstantensymbol 0 := (lambda x in B. x) = (lambda x in B . 1) vom Typsymbol B Fuer "falsch" schreibt man also 0. Da (lambda x in B. x) 0 != (lambda x in B . 1) 0 sind die beiden Funktionen also fuer ein Argument verschieden und damit insgesamt verschieden. Die Denotation wuerde sich also in Widersprueche verstricken, wenn sie Gleichheit auf den beiden zu "wahr" auswertete. Also wertet sie die Gleichheit auf den beiden zu irgendetwas anderem aus -- und dieses andere nennen wir "falsch". Negation: Das Funktionssymbol ~ x := (x=0) vom Typsymbol B->B Die Negation dreht also ihr Argument um: Wenn etwas gleich falsch ist, dann ist seine Negation wahr. logisches Und: Das Funktionssymbol x & y := (lambda g in B->B->B . g x y) = (lambda g in B->B->B . g 1 1) "x & y" soll genau dann auf "wahr" abgebildet werden, wenn x und y auf "wahr" abgebildet werden. Es gibt 2 Faelle: 1. D(x)=D(1) und D(y)=D(1), also beide sind wahr Dann sind die Funktionen offenbar gleich 2. Eins der beiden, zB x, ist nicht wahr, also D(x)=D(0) Dann gibt es ein Argument fuer die Funktion (lambda g in B->B->B . g x y) sodass die Gleichung nicht stimmt, naemlich die normale Konjunktionsfunktion "und". Von der wissen wir zwar nicht, wie sie aussieht, aber wir wissen, dass es sie gibt: Es gibt ja auf 2 boolschen Werten nur 16 moegliche Funktionen und eine davon gibt nur dann "wahr", wenn beide Argumente "wahr" sind. Nennen wir diese Funktion also "und" und sei "und" auch das zugehoerige Funktionssymbol. Dann ist D( (lambda g in B->B->B . g x y) und) = D(und x y) = D(und) D(x) D(y) = und wahr falsch = falsch Das ist aber offensichtlich ungleich D(und 1 1) = wahr Es sieht so aus, als wuerde sich hier der Reiter am eigenen Schopf aus dem Sumpf ziehen. Dem ist nicht so: Die und-Funktion existiert ja bereits. Wir muessen bloss ein Symbol dafuer erfinden und sicherstellen, dass das Symbol genau auf diese Funktion verweist. Letztendlich liefert unser neu definiertes "&" genau das Funktionssymbol zu der ueblichen Konjunktion. logisches Oder: Das Funktionssymbol x | y := ~(~x & ~y) Man ueberzeugt sich leicht durch Ausprobieren aller moeglichen Belegungen von x und y, dass obiger Ausdruck nur dann zu "wahr" auswertet, wenn mindestens eines der Arumente "wahr" ist. logische Implikation: Das Funktionssymbol x => y := ~x | y Dieses Funktionssymbol entspricht der Aussage "Wenn x, dann y". Der einzige Fall, in dem diese Aussage falsch ist, ist wenn x wahr ist, aber y falsch. Wenn x falsch ist, liefert die Implikation immer wahr (Prinzip des Ex Falso Quod Libet). Mit der Implikation geht keinerlei kausaler Zusammenhang einher! logische Aequivalenz: Das Funktionssymbol x <=> y := (~x & ~y) | (x & y) Die Aequivalenz wertet genau dann zu "wahr" aus, wenn x und y in ihrer Auswertung uebereinstimmen. logisches Exklusives Oder, XOR: Das Funktionssymbol x xor y := (~x & y) | (x & ~y) Die Aequivalenz wertet genau dann zu "wahr" aus, wenn x und y in ihrer Auswertung verschieden sind. logische Differenz: Das Funktionssymbol x - y := x & ~y Die logische Differenz wertet genau dann zu "wahr" aus, wenn x zu "wahr" auswertet, aber y nicht. logisches Nand: Das folgende Funktionssymbol x nand y := ~(x & y) vom Typsymol B->B->B Diese Funktion wertet also genau dann zu "wahr" aus, wenn mindestens eines der beiden Argumente zu "falsch" auswertet. logisches Nor: Das folgende Funktionssymbol x nor y := ~(x | y) vom Typsymol B->B->B Diese Funktion wertet also genau dann zu "wahr" aus, wenn beide Argumente zu "falsch" auswerten. parametrisierte Aussage: Eine Abstraktion mit Typsymbol T->B fuer irgendeinen Typ T. Eine parametrisierte Aussage ist also ein Term, der erst zur Aussage wird, wenn man ihm ein Argument gibt. Beispiel: P := lambda x in N . x > 3 ist eine parametrisierte Aussage ueber eine natuerliche Zahl x. Nun kann man pruefen, ob P fuer eine Zahl (zB 4) gilt, indem man P auf 4 anwendet und prueft, ob P(4)=1. Intuition: Eine parametrisierte Aussage ist eine Eigenschaft. Die Eigenschaft (zB Rot-sein) ansich ist weder wahr noch falsch. Erst wenn man sie auf etwas bezieht ("Die Ampel ist rot") kann man von Wahrheit oder Falschheit sprechen. Man bemerke die notationell besonders schoene Aehnlichkeit zur Praedikatenlogik: In ETT und der Praedikatenlogik wuerde man schreiben: rot(Ampel) -- in der Praedikatenlogik als Anwendung des Praedikates "rot" auf das Objekt "Ampel" und in ETT als Anwendung der Funktion "rot" auf das Objekt "Ampel". Im Sinne von ETT ist eine Eigenschaft also das Wissen, diese Eigenschaft zu ueberpruefen (Ich sollte Philosoph werden). Parametrisierte Aussagen gibt es auch mit mehreren Argumenten: Dann sind sie Beziehungen zwischen Objekten. Beispielsweise die Beziehung "groesserals", die zwischen Zahlen gilt: groesserals := lambda x in N . lambda y in N: x>y Fuer "3 ist groesser als 2" schreibt man dann "groesserals(3)(2)". In herkoemmlicher Schreibweise entspricht das genau dem praedikatenlogischen "groesserals(x,y)". Anders als in der Praedikatenlogik kann man in ETT aber auch die Eigenschaft ausdruecken, kleiner als eine bestimmte Zahl zu sein: groesserals(17) ist die Eigenschaft, kleiner als 17 zu sein. Diese Faehigkeit, Beziehungen schrittweise zu Eigenschaften und dann zu Aussagen zu reduzieren groesserals ~~> groesserals(17) ~~> groesserals(17)(13) hat ETT der Praedikatenlogik voraus. Ebenso die Faehigkeit, "on the fly" mit lambda neue Praedikate zu erzeugen. Schreibweise: Um ein Symbol fuer alle Typen zu definieren, wird in dieser Zusammenfassung von nun an der allgemeingueltige Typ "T" verwendet. Allquantor: Das Funktionssymbol All f := ((lambda x in T . f x) = (lambda x in T . 1)) vom Typsymbol (T->B)->B Dieser Allquantor bekommt also als Argument eine parametrisierte Aussage f ueber Objekte vom Typsymbol T. "All f" wertet genau dann zu "wahr" aus, wenn f fuer alle Objekte immer "wahr" liefert. Nur dann naemlich sind die beiden Abstraktionen gleich. Es wird also ausgenutzt, dass das "Fuer alle" schon in der Definition der Gleichheit drin steckt: Zwei Funktionen sind ja genau dann "gleich", wenn sie "fuer alle" Argumente uebereinstimmen. Beispiel: P := lambda x in N . x > 3 All P = 0 Schreibweise: All x in T . A fuer "All (lambda x in T . A)" Existenzquantor: Das Funktionssymbol Ex f := ~ All x in T . ~ (f x) vom Typsymbol (T->B)->B Der Existenzquantor bekommt also eine parametrisierte Aussage und wertet dann zu "wahr" aus, wenn es nicht der Fall ist, dass die Aussage fuer alle Objekte "falsch" ist -- also dann, wenn es mindestens ein Objekt gibt, sodass die Aussage gilt. Beispiel: P := lambda x in N . x > 3 Ex P = ~ All x in N . ~ (P x) = 1 Schreibweise: Ex x in T . A fuer "Ex (lambda x in T . A)" Leibnizsche Gleichheit: Die folgende Definition des Gleichheitssymbols: (x = y) := All f in (T->B) . (f x) => (f y) vom Typsymbol T->T->B Zwei Dinge x und y sind also genau dann gleich, wenn alle boolschen Funktionen, die auf x zu "wahr" auswerten, auch auf y zu "wahr" auswerten. Sind naemlich x und y gleich, so ist die Implikation (f x) => (f y) bzw (f x) => (f x) immer erfuellt. Sind x und y ungleich, so gibt es eine Funktion, fuer die die Implikation nicht gilt: Naemlich die Funktion lambda z in T . z=x, die nur fuer x selber zu "wahr" auswertet. Fasst man die Funktionen als parametrisierte Aussagen bzw. Eigenschaften auf, so zeigt sich Leibniz' urspruenglicher Gedanke: Zwei Dinge sind genau dann gleich, wenn es keine Eigenschaft gibt, die sie unterscheidet. Anstatt den Allquantor ueber die Gleichheit zu definieren (wie es in dieser Zusammenfassung geschehen ist), koennte man also auch die Gleichheit durch Allquantor und Implikation definieren. charakteristische Funktion einer Menge M: Eine Funktion f, die fuer Elemente von M 1 liefert und sonst 0. Beispiel: Sei M={1,2,3}. Sei f die charakteristische Funktion von M. Dann gilt: f(1)=1, f(2)=1, f(3)=1, f(4)=0, f(5)=0,... Nun muss nur noch der Typ von f festgelegt sein: Auf welche Objekte darf f ueberhaupt angewandt werden? Man einigt sich darauf, Mengen generell als Teilmengen eines Typs zu betrachten. (Die Obermenge der obigen Menge M waere die Menge der natuerlichen Zahlen). Dann kann man den Typ der Funktion angeben, im obigen Beispiel f in N->B Fuer Mengen wird im folgenden immer eine Obermenge mit Symbol T angenommen. Es zeigt sich, dass charakteristische Funktionen und ihre zugehoerigen (Teil-)Mengen strukturell gleich sind. Fuer endliche Typen ist auch das zur charakteristischen Funktion gehoerige Tupel strukturgleich: M->B ~=~ P(M) ~=~ B^|M|. Menge in ETT: Eine charakteristische Funktion. Mengen werden also in ETT ausgedrueckt durch Funktionen nach B. Fuer ETT ist eine Menge also eine parametrisierte Aussage, die genau fuer die Elemente der Menge gelten. Mengen sind also Eigenschaften -- und Eigenschaften sind Mengen. Fuer ETT ist also zB. die Eigenschaft, rot zu sein, nichts anderes als die Menge aller roten Gegenstaende. Vereinigung in ETT: Das Funktionssymbol M \/ N := lambda x in T . (M x) | (N x) vom Typsymbol (T->B)->(T->B)->(T->B) Dieses Funktionssymbol bekommt also zwei Mengen (als parametrisierte Aussagen) und liefert eine neue Menge (als parametrisierte Aussage) zurueck. Diese neue parametrisierte Aussage gilt fuer ein x genau dann wenn das x in der ersten Menge ist (also (M x) zu "wahr" auswertet) oder das x in der zweiten Menge ist (N x). // Uebungsblatt #1.2 Schnitt in ETT: Das Funktionssymbol M /\ N := lambda x in T . (M x) & (N x) vom Typsymbol (T->B)->(T->B)->(T->B) Es ahmt genau den Schnitt von Mengen nach. Komplement in ETT: Das Funktionssymbol -M := lambda x in T . ~(M x) vom Typsymbol (T->B)->(T->B) Das Komplement einer Menge M ist genau die Menge der Elemente, die nicht in M sind. Da ETT Mengen immer als Teilmengen einer Typ-Obermenge betrachtet, sind dies also die restlichen Elemente der Typ-Obermenge. Differenz in ETT: Das Funktionssymbol M - N := lambda x in T . (M x) & ~(N x) vom Typsymbol (T->B)->(T->B)->(T->B) Es ahmt genau die Differenz von Mengen nach. Teilmengentest in ETT: Das Funktionssymbol M < N := All x in T . (M x) => (N x) vom Typsymbol (T->B)->(T->B)->B Diese Funktion bekommt zwei Mengen und liefert genau dann "wahr", wenn jedes Element der ersten Menge in der zweiten Menge ist. Elementtest in ETT: Das Funktionssymbol x in M := (M x) vom Typsymbol T->(T->B)->B Diese Funktion bekommt ein Element x und eine Menge M und prueft, ob x in M ist. Das ist genau dann der Fall, wenn M(x) "wahr" liefert. Schoenfinkels U-Kombinator, Disjunktheitstest: Das Funktionssymbol M || N := All x in T . ~((M x) & (N x)) vom Typsymbol (T->B)->(T->B)->B Diese Funktion bekommt zwei Mengen und liefert genau dann "wahr", wenn jedes moegliche Element nicht zugleich in beiden Mengen ist. // Die Musterloesung des Uebungszettels sagt ~(M x <=> N x). Dann // jedoch waeren {a} und {b} nicht disjunkt, wenn T noch das // Element c umfasst! Schreibweise: Schoenfinkel schrieb "U" fuer "||". Auswahloperator: Das folgende Funktionssymbol: eps vom Typsymbol (T->B)->T wobei gilt: All M in (T->B) . (Ex x in T. M(x)) => M(eps(M)) Das heisst: Man gibt eps eine Menge und bekommt ein Element der Menge zurueck. Das funktioniert natuerlich nur, wenn die Menge nicht leer ist. Ist die Menge leer, liefert eps *irgendein* anderes Element. Schreibweise: ein kleines griechisches Epsilon mit eingebeultem Ruecken // Dieses eps ist hoechst interessant. Man kann zB zeigen, dass // Ex x in T. M(x) <=> M(eps(M)) // s.a. "Human-oriented Theorem Proving" (htp.txt, auf Englisch) // Darueber hinaus ist es sehr nuetzlich: In der Mathematik kann // man problemlos Mengen durch Bedingungen definieren. Aber es ist // unmoeglich, ein Individuum durch Bedingungen zu definieren // ohne natuerliche Sprache zu benutzen ("dasjenige x, welches..."). // eps schafft hier Abhilfe. Man kann zB das Minimum zweier Zahlen // definieren als: // min(a,b) := eps({c | c =< a & c =< b & (a=c | b=c)}) // In ETT, wo Aussagen Mengen und Mengen Funktionen sind, kann man // sogar noch schoener definieren: // "dasjenige x sodass A" := eps(lambda x in T.A) // min(a,b) := dasjenige c sodass c =< a & c =< b & (a=c | b=c) Alternative Definition von Funktionssymbolen: Eine Definition einer Teilmenge von Funktionssymbolen durch eine Teilmenge vorausgesetzter Funktionssymbole. Beispiele: Seien folgende Funktionssymbole von folgenden Typsymbolen: + : N->N->N * : N->N->N =< : N->N->B Ex : (N->B)->B All : (N->B)->B = : N->N->B 0 : N 1 : N 2 : N ~, &, |, => wie ueblich Seien folgende Variablen von folgenden Typsymbolen: a, b, c : B m, n, o : N M : N->B f : N->T * Wie bereits gesehen lassen sich alle logischen Operatoren durch "=" definieren. * Hat man "All" und "=>", so kann man per Leibnizscher Gleichheit das "=" bauen und danach alle anderen logischen Operatoren wie ueblich. * Mit "&" und "~" lassen sich die Funktionssymbole =>, <=>, | bauen wie oben angegeben. // Uebungsblatt #1.1 * Mit 0 und "=>" lassen sich bauen: ~ a := a => 0 1 := ~0 a | b := ~a => b a & b := ~(~a | ~b) a <=> b := (a => b) & (b => a) a = b := (a <=> b) fuer Gleichheit auf Typsymbol B // Uebungsblatt #1.3 * Mit den Funktionssymbolen ~, &, +, *, =<, Ex lassen sich bauen: m = n := (m =< n) & (n =< m) m < n := (m =< n) & ~(m=n) a | b := ~(~a & ~b) a => b := ~a | b All x in N. M(x) := ~(Ex x in N.~M(x)) min(m,n,o) := (o =< m) & (o =< n) & ((o=m) | (o=n)) eins(m) := ~((m+m)=m) & (m*m=m) teilt(m,n) := (Ex x in N. x*m=n) & (1 =< n) falls man n=0 ausschliessen moechte // was die Musterloesung tut mod(m,n,o) := (Ex x in N. (x*n+o=m)) & (o < n) ggt(m,n,o) := teilt(o,m) & teilt(o,n) & ~Ex x in N. (x>o & teilt(x,m) & teilt(x,n)) beschraenkt(M):= Ex x in N . All y in N. M(y) => y < x max(M,m) := M(m) & (All x in N . M(x) => x =< m) // Uebungsblatt #1.4 // Erklaerungen der Einfachheit halber ohne Unterschied // Semantik/Syntax * Mit Schoenfinkels U-Kombinator (dem Disjunktheitstest) laesst sich bauen: ~ a := (lambda x in T . a) || (lambda x in T . a) Wenn a=1, dann sind beide Lambda-Ausdruecke aequivalent zur vollen Menge T. Der Disjunktheitstest liefert 0. Wenn a=1, dann sind beide Lambda-Ausdruecke aequivalent zur leeren Menge. Der Disjunktheitstest liefert 1. a & b := ~ ((lambda x in T . a) || (lambda x in T . b)) Ist eines der beiden Argumente 0, so ist einer der beiden lambda-Ausdruecke die leere Menge. Diese ist disjunkt mit jeder anderen Menge. Da "&" in diesem Fall aber 0 liefern soll, muss das Ergebnis noch umgedreht werden All x in T . M(x) := (lambda x in T . ~M(x)) || (lambda x in T . ~M(x)) Dass M fuer alle x gilt, heisst, dass M die volle Menge T ist. Also ist ~M die leere Menge. Die leere Menge ist die einzige, die mit sich selbst disjunkt ist. Ex x in T . M(x) := ~ ( M || M ) Dass M fuer ein x gilt, heisst, dass M nicht leer sein darf. Fuer die leere Menge liefert M||M "wahr". // Uebungsblatt #1.5 * Mit dem Auswahloperator und den Funktionssymbolen ~, &, |, *, +, =<, = kann man bauen: 0 := eps(lambda x in N . x+x=x) Dasjenige x, sodass x+x=x Ex x in T . M(x) := M(eps(M)) Wenn M nicht leer ist, wird eps ein Element aus M waehlen und es gilt M(eps(M)). Wenn M leer ist, wird eps irgendein Element liefern -- was dann natuerlich nicht in M ist. 1 := eps(lambda x in N . x*x=x & ~(x=0)) 1 ist die einzige Zahl ungleich 0, die mit sich selbst multipliziert sich selbst ergibt. m - n := eps(lambda x in N . n+x=m) Das Ergebnis der Subtraktion ist diejenige Zahl, die die Summe aufgehen laesst. if(a,m,n) := eps(lambda x in N. ((x=m) & a) | ((x=n) & ~a)) Wenn a wahr ist, enthaelt diese Menge genau m, sonst n max(m,n) := if(m, All, Ex kann man bauen: injektiv(f) := All x in N. All y in N. f(x)=f(y) => x=y unendlich(M) := Ex f in N->T. injektiv(f) & All x in N.M(f(x)) Die Menge M ist unendlich, wenn es eine injektive Funktion von den natuerlichen Zahlen nach T gibt, sodass alle Ergebnisse dieser Funktion in M liegen. // Die Musterloesung benutzt Ex, obwohl es nicht // gegeben ist. Tz, tz, das gibt Punktabzug... // Uebungsblatt #3.4 * Mit Ex, All, |, &, ~, =, =<, 0, 2, * kann man bauen: teilt(a,b) := (Ex x in N . x*a=b) & ~(0=b) prim(a) := ~(Ex x in N. teilt(x,a) & ~(x=1) & ~(x=a)) & a!=1 & a!=0 unendlich(M) := All x in N. Ex y in N. M(y) & x P(Rule X) -> P(X) -> P(X) C# 0 R A := A C# n R A := (C (n-1) R A) \/ {x in X| Ex B<(C (n-1) R A): (B,x) in R} Dieser Operator bekommt also eine natuerliche Zahl n (die "Ableitungstiefe"), ein Regelsystem R ueber X und eine Anfangsmenge A. Er liefert die Menge derjenigen Elemente, die durch Anwendung von weniger als n Regeln auf A erhalten werden koennen. Beispiel: Sei die Regelmenge die obige Inferenzregel Mul. Dann ist C# 0 Mul {2,3} = {2,3} C# 1 Mul {2,3} = {2,3} \/ {6} = {2,3,6} C# 2 Mul {2,3} = {2,3,6} \/ {6,12,18} = {2,3,6,12,18} usw. Ableitungsoperator fuer eine Menge X: Die folgende Funktion C in P(Rule X) -> P(X) -> P(X) C R A := VEREINIGUNG {C# n R A | n in N} Der Ableitungsoperator nimmt also die Menge A und leitet mit den Regeln aus R alles ab, was er ableiten kann. Beispiel: Sei die Regelmenge die obige Inferenzregel Mul. Dann ist C Mul {2,3} = {2^n*3^m | n, m in N} - {1} Die Rolle von A kann durch Start-Inferenzregeln uebernommen werden. Operationale Definition einer Menge KP(X). Das heisst also * Monotonie: Ist A Teilmenge von B, dann ist auch f(A) Teilmenge von f(B). * Expansivitaet: f(A) ist Obermenge von A, die Funktion fuegt also zu ihrer Argumentsmenge etwas hinzu. * Idempotenz: Zweimal anwenden ist dasselbe wie einmal anwenden. Beispiel: Fuer die Menge der n-dimensionalen Vektoren ist diejenige Funktion ein Abschlussoperator, die zu einer gegebenen Menge von Vektoren die Menge all ihrer Linearkombinationen (also die lineare Huelle) liefert. Diese Funktion ist * expansiv, denn die Basisvektoren sind ebensfalls in der linearen Huelle * monoton, denn je mehr Basisvektoren man nimmt, desto groesser ist die lineare Huelle (mindestens gleichgross) * idempotent, denn die lineare Huelle einer linearen Huelle ist ebendiese lineare Huelle. Das ist wirklich extrem einfach: Es ist kompliziert, darauf zu kommen, aber wenn man es nachher sieht, erscheint es einfach. Eigenschaften eines Abschlussoperators fuer eine Menge X: * A < f(B) genau dann wenn f(A\/B)=f(B) Beweis: '=>': Sei A < f(B). Es gilt: B < A\/B, daraus folgt per Monotonie f(B) < f(A\/B). Es gilt: B < f(B) wegen Expansivitaet und A < f(B) nach Vorraussetzung A\/B < f(B), als Konsequenz f(A\/B)< f(f(B)), wegen Monotonie f(A\/B)< f(B) wegen Idempotenz Aus diesen beiden Ergebnissen folgt f(A\/B)=f(B) '<=': Sei f(A\/B)=f(B). Es gilt: A < A \/ B A\/B < f(A\/B) wegen Expansivitaet A < f(A\/B) wegen Transitivitaet von '<' A < f(B) da f(A\/B)=f(B) nach Vorraussetzung * Wenn f(A)=f(B), dann f(A\/C)=f(B\/C) Beweis: Es gilt: A < f(A) wegen Expansivitaet A < f(B) wegen f(A)=f(B) f(B) < f(B\/C) wegen Monotonie A < f(B\/C) aus den vorhergehenden 2 Ungleichungen f(A\/B\/C)=f(B\/C) mithilfe der vorhergehenden Eigenschaft f(A\/B\/C)=f(A\/C) analog Also folgt: f(A\/C)=f(A\/B\/C)=f(B\/C) * f(A\/B)=f(A\/C) genau dann wenn C < f(A\/B) und B < f(A\/C) Beweis: '=>': Sei f(A\/B)=f(A\/C). Es gilt: C < A\/C A\/C < f(A\/C) per Monotonie C < f(A\/C) aus den vorhergehenden 2 Zeilen C < f(A\/B) nach Vorraussetzung B < f(A\/C) analog '<=': Sei C < f(A\/B) und B < f(A\/C). Es gilt: f(A\/B\/C)=f(A\/B) nach der ersten Eigenschaft f(A\/B\/C)=f(A\/C) nach der ersten Eigenschaft f(A\/C)=f(A\/B) per Transitivitaet von "=" // Uebungsblatt #5.9 Kompaktheit eines Abschlussoperators fuer eine Menge X: Seine Eigenschaft, dass man jedes Element jeder Ergebnismenge des Operators mit einem endlichen Argument erhalten kann. Ein Abschlussoperator f ist nicht kompakt, wenn es eine unendliche Teilmenge A < X gibt, sodass es ein x in f(A) gibt, sodass es keine endliche Teilmenge B1}) := N f(A) := A sonst Waehle A={n|n>1}, x=1. Dann gibt es kein endliches BK zwei beliebige Uebersetzungsfunktionen. Dann gilt fuer jede Formel A: A == f(A) da f Uebersetzungsfunktion A == g(A) da g Uebersetzungsfunktion f(A)== g(A) da == transitiv ist f(A)= g(A) da f(A) und g(A) in kanonischer Termmenge K Damit sind die Uebersetzungsfunktionen gleich. // Das Skript definiert Kanonizitaet nur fuer boolsche Formeln (s.u.) Theorie einer Denotation D: Die Menge aller in D universell gueltigen Gleichungen. Schreibweise: Th(D) := {A=B | D |= A=B} Th ist eine Aequivalenzrelation. Semantisch aus Gleichungsmenge M folgende Gleichung: Eine Gleichung, die in allen Denotationen, die M erfuellen, universell gueltig ist. Schreibweise: M |= A=B Intuition: Dieses entspricht der Folgerung in der "wirklichen Welt", zB. folgt aus "Wenn es regnet wird die Strasse nass" und "es regnet", dass die Strasse nass ist. Semantische Huelle, Semantic closure, Semantischer Abschluss einer Gleichungsmenge M: Die Menge aller Gleichungen, die in all den Denotationen universell gueltig sind, in denen die Gleichungen aus M universell gueltig sind. Schreibweise: SC(M) := { A=B | M |= A=B } Intuition: Hat man eine Menge von Gleichungen (zB Wissen ueber die Welt), so ist der semantische Abschluss all das, was man daraus folgern kann. Der Semantische Abschluss ist eine Abschlussoperation fuer die Menge der Gleichungen, d.h. * Monotonie: Man kann in jedem Fall die gegebenen Gleichungen folgern * Expansivitaet: Hat man eine Gleichung mehr, so kann man mehr daraus folgern * Idempotenz: Hat man einmal alle Gleichungen gefolgert, so kann man nicht noch mehr folgern. SC ist eine Aequivalenzrelation. Bei Vollstaendigkeit ist der Semantische Abschluss kompakt: Jede Gleichung, die sich aus einer Gleichungsmenge M semantisch folgern laesst, laesst sich auch aus einer endlichen Teilmenge von M semantisch folgern. Semantische Abgeschlossenheit der Theorie: Die folgende Tatsache: Wenn M < Th(D) dann SC(M) < Th(D) Alles, was sich aus einem Bestandtteil der Theorie semantisch folgern laesst, ist bereits in der Theorie enthalten. Fuer den Spezialfall M=Th(D) gilt: SC(Th(D)) = Th(D) Von x erfuellte Eigenschaft: Eine Eigenschaft, die fuer x zu "wahr" auswertet. Bedingung: Eine Eigenschaft, die es zu erfuellen gilt. leere Bedingung: Eine Bedingung, die immer erfuellt ist. Beispiel: lambda x in T. 1 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Universell gueltige Gleichungen - Syntax ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Hier wird gezeigt, wie man gueltige Gleichungen auf rein // syntaktischer Ebene systematisch herleiten kann Substitutionsaehnlichkeit: Die folgende Relation auf Paaren von Termen: A/B ~= A'/B' Sie gilt genau dann, wenn ich A hernehmen kann, darin ein B durch ein B' ersetzen kann und A' erhalte. Beispiel: (f x x) / x ~= (f y x) / y Schreibweise: A/B ~= A'/B' mit einem Ungefaehrgleichzeichen anstelle des "~=" Intuition: Eine Erklaerung fuer die Bruch-aehnliche Schreibweise mag sein: A verhaelt sich zu seinem Bestandteil B wie A' sich zu seinem Bestandteil B' verhaelt. Als Eselsbruecke kann man B' auch "auf die andere Seite bringen": A/B ~= A'/B' A/B*B' ~= A' und es so lesen: Nimm A. Nimm ein B an guenstiger Position raus und tu stattdessen B' rein. Dann bekommst du A'. Beispiel: (f x x) / x ~= (f y x) / y umgeschrieben zu (f x x) / x * y ~= (f y x) x raus, y rein (f y x) ~= (f y x) Es gilt: // Uebungsblatt #3.3q Zu zeigen: M/M ~= N/N "Auf andere Seite": M/M*N ~= N "Eins raus,eins rein": N = N // Uebungsblatt #3.3r Wenn A/B ~= A'/B' dann A'/B' ~= A/B Gleichheitsregel: Eine Inferenzregel ueber der Menge der Gleichungen. Schreibweise: Gleichung1 Gleichung2 ... Seitenbedingung ----------------------------------------- GleichungK Diese Regel bedeutet: Wenn die oberen Gleichungen (genannt "Praemissen") universell gueltig sind und die Seitenbedingung erfuellt ist, dann ist auch die GleichungK (genannt "Konklusion") universell gueltig. Die Menge der Praemissen und die Seitenbedingung koennen leer sein. Das bedeutet dann, dass die Konklusion immer gueltig ist. Man sagt: Man leitet die Konklusion aus den Praemissen ab (oder her). Intuition: Wir koennen also mechanisch syntaktisch neue Gleichungen produzieren und diese sind dann auch in der "wirklichen" Welt der Semantik gueltig. Korrektheit einer Gleichheitsregel: Ihre Eigenschaft, dass die Konklusion universell gueltig ist, wenn auch die Praemissen universell gueltig sind. Reflexivitaets-Regel: Die folgende Gleichheitsregel: ------------ A = A Ohne irgendwelche Praemissen oder Seitenbedingungen kann ich also immer die Gleichheit zweier gleichen Dinge folgern. Korrektheit: Durch die Reflexivitaet der normalen Gleichheit ist sicher gestellt, dass D A s = D A s fuer alle Zustaende s. Symmetrie-Regel: Die folgende Gleichheitsregel: A = B ---------- B = A Korrektheit: Durch die Symmetrie der normalen Gleichheit gegeben. Transitivitaets-Regel: Die folgende Gleichheitsregel: A = B B = C ------------ A = C Korrektheit: Durch die Transitivitaet der normalen Gleichheit gegeben. Kongruenz-Gesetz: Die folgende Tatsache: Wenn A == B, dann * C A == C B * A C == B C * lambda x in T . A == lambda x in T . B Ersetzungs-Regel: Die folgende Gleichheitsregel: A/B ~= A'/B' B=B' ---------------------- A = A' Sind also zwei Terme A und A' substitutionsaehnlich bezueglich eines Bestandteiles B (bzw B') und sind darueber hinaus die Bestandteile B und B' gleich, so sind auch die Terme A und A' gleich. Intuition: Ich darf einen Term A hernehmen, einen Bestandteil durch etwas Aequivalentes ersetzen und der Ergebnisterm A' ist dann gleich A. Beispiel: (f x) / f ~= (g x) / g f=g ------------------------------------ f x = g x Wichtig ist, dass man 1. einen oder (per Transitivitaet) beliebig viele Bestandteile ersetzen kann 2. diese Bestandteile beliebig komplex sein duerfen Korrektheit: Beweis per struktureller Induktion. Es gibt 3 Faelle: * Sei A=B. Wegen B=B' ist dann B'=A. Wegen ~= ist dann A'=A. * Sei A=(A1 A2) und A'= (A1' A2'). Die Substitutionsaehnlichkeit kann nicht ueber Applikationen "hinwegspringen". Sie muss sich also auf die Paare (A1,A1') und (A2,A2') fortsetzen. Per Induktionsannahme sind dann A1=A1' und A2=A2'. Mit dem Kongruenzgesetz folgt dann A=A'. * Sei A=(lambda x in T. A1) und A'=(lambda x in T. A1'). Dann muss sich die Substitutionsaehnlichkeit auf A1 und A1' beziehen. Per Induktionsannahme folgt dann A1=A1' und per Kongruenz-Gesetz A=A'. Einsetzungs-Regel: Die folgende Gleichheitsregel: A = B x und C haben dasselbe Typsymbol ------------------------------------------ A[x->C] = B[x->C] Intuition: Man darf also eine freie Variable mit einem Term instantiieren. Dies entspricht der gaengigen Interpretation von freien Variablen als allquantifizierte Variablen. Im Unterschied zur Ersetzungsregel 1. kann die Regel nur auf (freie) Variablen angewandt werden 2. muss die Ersetzung fuer alle Auftreten erfolgen 3. muss die Gleichheit x=C nicht gegeben sein. Man beachte, dass die Substitution nur freie Variablen ersetzt, da sie ueber deBruijn-Terme definiert wurde. Beispiel: ((lambda x in T. x) x) = (a x) ------------------------------ ((lambda x in T. x) C) = (a C) Korrektheit: Es moegen x und C dasselbe Typsymbol haben, es moege A=B fuer alle Zustaende s gelten, d.h. D A s = D B s fuer alle s aus StaD Nun gilt mit dem Substitutionslemma: D (A[x->C]) s = D A (s[x->(D C s)]) D (B[x->C]) s = D B (s[x->(D C s)]) Da D A s = D B s fuer alle s aus StaD, gilt dies insbesondere auch fuer den Zustand s[x->(D C s)]. Also A[x->C] = B[x->C]. Beta-Regel: Die folgende Gleichheitsregel: ------------------------------- (lambda x in T . A) B = A[x->B] Ohne Praemissen kann also gefolgert werden, dass eine Beta-Redex gleich ihrer reduzierten Form ist. Korrektheit: Sei s ein beliebiger Zustand. D ((lambda x in T . A) B) s = (lambda x in D(T) . (D A s)) (D B s) = D A[x->B] s per einer unbewiesenen Proposition // Uebungsblatt #3.3a Eta-Regel: Die folgende Gleichheitsregel: x nicht in FV(A) ------------------------ (lambda x in T. A x) = A Wenn x nicht frei in A auftaucht, kann also gefolgert werden, dass eine Eta-Redex gleich ihrer reduzierten Form ist. Korrektheit: Sei s ein beliebier Zustand. D (lambda x in T. A x) s = lambda v in D(T). (D (A v) v::s) = lambda v in D(T). (D A v::s) (D v v::s) = lambda v in D(T). (D A v::s) v = lambda v in D(T). (D A s) v da v nicht frei in A, Koinzidenz = D A s (und dieses hier(?)) // Uebungsblatt #3.3b Die Eta-Regel kann durch folgende Gleichheitsregel ersetzt werden: // Uebungsblatt #3.3c -------------------------- lambda x . y x = y Grund: Durch die Substitution [y->A] erhaelt man die normale Eta-Regel. Die Bedingung, dass x nicht in den freien Variablen von A vorkommen darf, ist durch die Definition der Substitution gesichert. Da Eta-regel und Beta-Regel die Aequivalenz erhalten, kann man jeden Term in einen aequivalenten lambda-normalen Term umformen. // Uebungsblatt #3.3i Beweis: Das Ableiten einer Gleichung aus einer Menge gegebener Gleichungen mithilfe der Gleichheitsregeln, welche auf alle gegebenen und bereits abgeleiteten Gleichungen angewandt werden duerfen. Ein solcher Beweis bedeutet: Wenn die gegebenen Gleichungen universell gelten, gilt auch die abgeleitete "bewiesene" Gleichung universell. Schreibweise: M |- A=B wobei M die Menge der gegebenen Gleichungen ist und A=B die hergeleitete Gleichung. Gleichungs-Reihe: Ein Beweis durch eine Folge von Termen, in der jeder Term mit seinem Nachfolgerterm universell gleich ist. Per Transitivitaet ist dann der erste Term mit dem letzten Term universell gleich. Neben jeden Term schreibt man die Gleichheitsregel mit ihren Praemissen, die es erlaubt hat, den Term aus seinem Vorgaenger zu folgern. Beispiel: Sei die gegebenen Gleichung f x x = x. Dann kann man (lambda y . f y (lambda z . g z))(f g g)=g wie folgt herleiten: (lambda y . f y (lambda z . g z))(f g g) = (lambda y . f y (lambda z . g z)) g f x x = x, Eins, Ers heisst: Aus f x x = x wird per Einsetzung f g g = g gemacht und dann per Ersetzung f g g durch g ersetzt = f g (lambda z . g z) Beta, Eins heisst: Aus der Beta-Regel wird per Einsetzung (lambda y.f y (lambda z.g z)) g=f g (lambda z.g z)) gemacht. = f g g Eta, Eins, Ers heisst: Aus der Eta-Regel wird per Einsetzung (lambda z . g z) = g gemacht. Dann wird (lambda z . g z) durch g ersetzt. = g f x x = x, Ein Beispiel: // Uebungsblatt #3.1a Sei gegeben: Ein Typsymbol T 0 : T f,g : T->T +,* : T->T->T (1) x*x=x (2) x*y=y*x (3) x*f(x)=0 (4) x+0=x (5) x*(y*z)=(x*y)*z (6) x+(y*z)=(x+y)*(x+z) Man kann herleiten: x*0 = x*(x*f(x)) (3) ers Das heisst: Man ersetzt den Bestandteil 0 durch etwas nach Gl. (3) aequivalentes = (x*x)*f(x) (5) eins Das heisst: Man nimmt Gl. (5) und instantiiert die Variablen passend = x*f(x) (1) ers = 0 (3) x*(x+y) = (x+0)*(x+y) (4) ers = x+(0*y) (5) eins = x+(y*0) (2) eins ers Das heisst: Man instantiiert die Variablen in (2) passend und ersetzt dann (0*y) durch (y*0) = x+0 x*0=0 eins ers Die Musterloesung vergisst hier ein Eins(?) = x (4) lambda x . g(x*(x+y)) = lambda x . g(x) x*(x+y)=x ers = lambda y . g(y) Verwendung anderer Notation (lambda x y . x*((x+y)*y)) (u+0) ((lambda g . (g u)*(g u)) f) = (lambda x y . (x*(x+y))*y) u ((lambda g . (g u)) f) = (lambda x y . x*y) u ((lambda g . (g u)) f) = * u f(u) = 0 In Zukunft werden solche Beweise ohne Angabe der Gleichungsregeln gefuehrt werden. Beispiele fuer eine Denotation, in der die gegebenen Gleichungen universell gueltig sind, waeren: // Uebungsblatt #3.1b * D(T) = {0,1} D(0) = 0 D(*) = & D(+) = | D(f) = ~ Dieses entspricht einer klassischen boolschen Algebra, s.u. * D(T) = P({a,b}) D(0) = {} D(*) = /\ D(+) = \/ D(f) = lambda M . P({a,b}) - M Dieses entspricht einer Potenzmengenalgebra, s.u. * D(T) = N D(0) = 0 D(*) = min D(+) = max D(f) = lambda x . 0 Deduktive Huelle, Deduktiver Abschluss, Deductive Closure, Deduktive Maechtigkeit einer Gleichungsmenge M: Die Menge aller Gleichungen, die sich aus M herleiten lassen. Dh die Menge an Gleichungen, die operational durch die Gleichheitsregeln defininiert ist. Schreibweise: DC(M) := { A=B | M |- A=B } Der Deduktive Abschluss ist ein Abschlussoperator fuer die Menge der Gleichungen: * Monotonie: Hat man eine Gleichung mehr zum Ableiten zur Verfuegung, so kann man eventuell mehr Gleichungen herleiten. * Expanisivitaet: Die Menge der herleitbaren Gleichungen schliesst auf jeden Fall die gegebenen Gleichungen ein. * Idempotenz: Hat man alle Gleichungen abgeleitet, so lassen sich aus dieser neuen Menge keine nochmals neuen Gleichungen herleiten. Es gelten insbesondere die bereits bewiesenen Eigenschaften: * Wenn A < DC(B) dann DC(A\/B)=DC(B) Wenn A aus B hergeleitet wurde, so kann man aus A\/B dasselbe herleiten wie aus B allein. * Wenn DC(A)=DC(B), dann DC(A\/C)=DC(B\/C) Kann man aus A und B dasselbe herleiten, so kann man beide Gleichungsmengen um C erweitern und A\/C und B\/C haben immernoch dieselbe deduktive Maechtigkeit. * DC(A\/B)=DC(A\/C) genau dann wenn C < DC(A\/B) und B < DC(A\/C) Sind die deduktiven Maechtigekeiten von A\/B und A\/C gleich, d.h. erfuellen B und C dieselbe Rolle, dann ist C aus A\/B herleitbar und B aus A\/C herleitbar. Wegen der Korrektheit (s.u.) ist DC eine Aequivalenzrelation. Der deduktive Abschluss ist kompakt: Jede Gleichung, die sich aus einer Gleichungsmenge M herleiten laesst, laesst sich auch aus einer endlichen Teilmenge von M herleiten: Wenn M |- A=B dann existiert ein endliches M'B Funktionssymbole & und | vom Typsymbol B->B->B mit den folgenden Axiomen (genannt "BAx"): Kommutativitaet: A & B = B & A A | B = B & A Distributivitaet: A & (B | C) = (A & B) | (A & C) A | (B & C) = (A | B) & (A | C) Komplemenz: A & ~A = 0 // Klingt immmer wie De-menz. A | ~A = 1 // Gibt es ein schoeneres Wort? Identitaet: 1 & A = A 0 | A = A Wir nehmen noch die logische Implikation und die logische Aequivalenz hinzu wie oben definiert. Im Unterschied zu den Definitionen aus dem vorigen Abschnitt werden die boolschen Symbole hier als Ausgangssymbole postuliert und nicht aus der Gleichheit heraus definiert. Die Axiome zurren dann die Relationen der Symbole untereinander fest. Es zeigt sich, dass die Axiome auch mit den durch Gleichheit definierten Symbolen gelten. Schreibweise: + fuer | * fuer &, bzw AB fuer A*B -x (x mit Ueberstrich) fuer ~x Aus Gruenden der Symboleindeutigkeit unterbleibt diese Schreibweise in dieser Zusammenfassung. Boolsche Algebra: Eine Algebra fuer die Boolsche algebraische Struktur. Beispiel: Jede Denotation, die "=" auf eine Aequivalenzrelation abbildet und "&", "|" und "~" konform mit den Definitionen aus dem vorigen Abschnitt abbildet, ist eine Boolsche Algebra. //...behaupte ich jetzt mal so... Klassische boolsche Algebra: Die folgende boolsche Algebra: D(B)={true,false} D(0)=false D(1)=true D(&)=& D(|)=| D(~)=~ Diese Algebra bildet also die Funktionssymbole auf genau die ihnen ueblicherweise entsprechenden Funktionen ab. Schreibweise: Ein kalligraphisches "T" fuer die klassische Boolsche Algebra. Wird der Buchstabe "T" wie eine Denotation verwendet, so soll er hier fuer die klassische boolsche Algebra stehen, auch wenn T hier eigentlich als Typvariable dient. Potenzmengen-Algebra: Die folgende boolsche Algebra: D(B)=P(X) fuer irgendeine Menge X D(0)={} D(1)=X D(&)=/\ Schnitt D(|)=\/ Vereinigung D(~)=P(X)- Mengendifferenz Beispiel: X={a,b}, D(B)=P(X)={{},{a},{b},{a,b}} D(0 & 1) s = D(0)(s) D(&)(s) D(1)(s) = {} /\ {a,b} = {} = D(0) // Uebungsblatt #4.7 Sei s ein Zustand und s(x)={a}, s(y)={} D(x <=> y) s = D( x & y | ~ x & ~ y) s = (s(x)/\s(y)) \/ ((P(X)-s(x)) /\ P(X)-s(y)) = ({a} /\ {} ) \/ ( {b} /\ {a,b} ) = {} \/ {b} = {b} Beachtenswert ist, dass {b} weder D(0) noch D(1) ist. // Uebungsblatt #5.1c Sei s ein Zustand und s(x)={a}, s(y)={b} D(x & y) s = D(x)(s) D(&)(s) D(y)(s) = s(x) /\ s(y) = {a} /\ {b} = {} = D(0) Beachtenswert ist, dass D(x&y)s=0, obwohl weder D(x)s=0 noch D(y)s=0. // Uebungsblatt #5.1d Seien x und f Variablen vom Typsymbol B bzw. B->B. Die folgende Gleichung ist nicht universell gueltig, da sie fuer den folgenden Zustand s nicht gilt: s(x) = {a} s(f)({}) = {} s(f)({a}) = {a} s(f)({b}) = {} s(f)({a,b}) = {} Gleichung: (f x) = x & (f 1) | ~x & (f 0) Linke Seite: D(f x)s = s(f)({a}) = {a} Rechte Seite: D(x & (f 1) | ~x & (f 0)) s = {a} /\ {} \/ {b} /\ {} = {} // Uebungsblatt #5.1a Eine Potenzmengenalgebra mit 1024 Elementen laesst sich bauen mit X={1,2,3,4,5,6,7,8,9,10}, D(B)=P(X). Stone's Satz: Jede boolsche Algebra ist strukturgleich zu einer Potenzmengen-Algebra. Daraus folgt unter anderem: * Alle boolschen Algebren belegen B mit einer Menge mit 2^n Elementen // Uebungsblatt #5.1b * Es gibt keine boolsche Algebra, die B mit einer unendlichen, aber abzaehlbaren Menge belegt (die Potenzmenge einer unendlichen Menge ist sofort ueberabzaehlbar). Beispiel: Die klassische boolsche Algebra T ist aequivalent zu einer zwei-elementigen Potenzmengen-Algebra, zB P({{}} = { {}, {{}} } Boolscher Algebren-Satz: Die Tatsache, dass eine Gleichung genau dann universell in der klassischen boolschen Algebra gueltig ist, wenn sie in den Potenzmengen-Algebren universell gueltig ist. Boolscher Term: Ein Term in der Boolschen algebraischen Struktur. Im Folgenden wird immer eine boolsche Algebra als Denotation angenommen. Boolsche Formel: Ein boolscher Term nach der folgenden Grammatik: For --> 0 For --> 1 For --> x falls Gamma(x)=B For --> ~ For For --> For "&" For For --> For "|" For Es handelt sich also um die kombinatorischen boolschen Terme. Alle vorkommenden Variablen sind frei. Intuition: Das sind die ganz normalen boolschen Formeln, ohne Lambden. Schreibweise: For fuer die Menge der boolschen Formeln Boolsche Gleichung: Eine Gleichung aus boolschen Formeln. Tautologie: Eine in der klassischen boolschen Algebra universell gueltige boolsche Gleichung. Per Boolschen Algebren-Satz ist sie damit in allen boolschen Algebren universell gueltig. Boolscher Korrektheitssatz: Die Tatsache, dass eine boolsche Gleichung, die sich aus den Boolschen Axiomen herleiten laesst, in den boolschen Algebren universell gueltig ist. Beweis: Siehe Korrektheitsbeweise bei den Gleichheitsregeln. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Boolsches Rechnen ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Dualer Term eines boolschen Terms A: Der boolsche Term, der aus A entsteht, wenn man * die Symbole "&" und "|" vertauscht * die Symbole "0" und "1" vertauscht Schreibweise: dual in Ter->Ter dual(0) := 1 dual(1) := 0 dual(x) := x dual(A & B) := dual(A) | dual(B) dual(A | B) := dual(A) & dual(B) dual(~A) := ~dual(A) ^A fuer das Duale von A, wobei das "^" ein Dach ueber A ist. Beispiel: dual(a & (1 | ~b)) = a | (0 & ~b) Es gilt: dual(A => B) = dual(~A | B) = dual(B) - dual(A) dual(A - B) = dual(A & ~B) = dual(B) => dual(A) dual(dual(A)) = A D dual(A) s = D ~A {(x,D ~x s) | x in Dom(s) } Die letzte Zeile heisst: Das Duale einer Formel A hat dieselbe Denotation wie ~A, wenn man alle Variablen-Werte umdreht. Es faellt auf, dass jedes Boolsche Axiom ein Duales hat. Beispiel: Komplemenz: A & ~A = 0 A | ~A = 1 Duale Denotation zu einer boolschen Algebra D: Die folgende Denotation ^D: * ^D(1):=D(0) * ^D(0):=D(1) * ^D(&):=D(|) * ^D(|):=D(&) Diese Denotation vertauscht also genau die Denotation D. ^D ist ebenfalls eine boolsche Algebra. Beweis: Fuer einen boolschen Term A und sein Duales dual(A) gilt: ^D(A) = D(dual(A)) Zu zeigen ist, dass ^D die boolschen Axiome erfuellt. Sei A=B ein boolsches Axiom und dual(A)=dual(B) sein Duales. Dann ist ^D(A)=^D(B) <=> D(dual(A))=D(dual(B)) Da aber dual(A)=dual(B) ebenfalls ein Axiom ist, gilt D(dual(A))=D(dual(B)). Also gilt auch ^D(A)=^D(B) und als Resultat ist A=B in ^D gueltig. Dualitaetssatz: Die Tatsache, dass eine boolsche Gleichung A=B genau dann universell gueltig ist, wenn ihr Duales dual(A)=dual(B) universell gueltig ist. Beweis 1: Da A=B universell gueltig ist, gibt es per Vollstaendigkeit (noch zu zeigen) einen Beweis fuer A=B. In diesem Beweis ersetze man alle boolschen Axiome durch ihre Dualen. Die Gleichheitsregeln funktionieren auch weiterhin und man erhaelt einen Beweis fuer dual(A)=dual(B). Per Korrektheitssatz folgt die universelle Gueltigkeit von dual(A)=dual(B). Beweis 2: Da A=B universell gueltig ist, ist A=B per boolschem Algebren-Satz in allen boolschen Algebren universell gueltig. Sei D eine solche Algebra. Dann ist A=B auch in der dualen Denotation ^D universell gueltig, ^D(A)=^D(B). Also D(dual(A))=D(dual(B)) und dual(A)=dual(B) ist in einer boolschen Algebra und damit in allen universell gueltig. Boolscher Monotonie-Satz: Die folgende Tatsache: Wenn eine neue boolsche Gleichung mithilfe einer bereits bewiesenen Gleichung hergeleitet werden kann, dann kann die neue Gleichung auch ohne die bereits bewiesene Gleichung hergeleitet werden. Wenn M |- A=B dann gilt fuer jede Gleichung A'=B': M \/ {A=B} |- A'=B' impliziert M |- A'=B' Beweis: Man kann dem Beweis von M \/ {A=B} |- A'=B' einfach den Beweis von M |- A=B vorstellen und erhaelt einen normalen Beweis. Alternativer Beweis: Die Behauptung folgt aus den Eigenschaften des Abschlussoperators DC. Das heisst: Man kann bereits beweisene Gleichungen als Hilfmittel fuer neue Beweise benutzen. Boolsche Gesetze: Die folgenden universell gueltigen boolschen Gleichungen: (mit Eselsbruecken) Boolsche Axiome: Kommutativitaet: A & B = B & A A | B = B & A // commutare heisst vertauschen Distributivitaet: A & (B | C) = (A & B) | (A & C) A | (B & C) = (A | B) & (A | C) // to distribute/distribuere heisst verteilen Komplemenz: A & ~A = 0 A | ~A = 1 // Verwurstung von A mit seinem *Komplement* Identitaet: 1 & A = A 0 | A = A // "1 &" ist eine Identitaetsfunktion, sie // liefert ihr Argument Zusaetzliche Gleichungen: Assoziativitaet: A & (B & C) = (A & B) & C A | (B | C) = (A | B) | C // Mal ist A assozial, mal C Idempotenz: A & A = A A | A = A // "Selbstmaechtigkeit", ich und ich bin ich. Dominanz: 0 & A = 0 1 | A = 1 // Die Null dominiert hier (soll's ja geben) Absorption: A | (A & B) = A A & (A | B) = A // Das B wird hier absorbiert, da sein // Wahrheitswert irrelevant ist DeMorgan: ~(A & B) = ~A | ~B ~(A | B) = ~A & ~B // Das "~" zieht sich hier in die Klammern und // negiert jeden einzelnen Bestandteil: // A wird zu ~A, "|" wird zu "&", B wird zu ~B Negation: ~1=0 ~0=1 Doppelnegation: ~~A=A Resolution: (A & B) | (~A & C) = (A & B) | (~A & C) | (B&C) (A | B) & (~A | C) = (A | B) & (~A | C) & (B|C) Intuition: Es gilt also alles, was man so gemeinhin intuitiv fuer gueltig erachtet. Neu ist vielleicht die Resolution: Sie fuegt zwei Termen einen dritten (redundanten) hinzu. Wenn B&C wahr ist, dann auch (A&B)|(~A&C). Generell kann ein Schema der Form A = A|B verstanden werden als B=>A A = A&B verstanden werden als A=>B Schreibweise: Die Menge der Boolschen Gesetze wird mit "BG" bezeichnet. Aufgrund der Assoziativitaet und Kommutativitaet von "&" und "|" laesst man hier oft die Klammerung weg: A & B & C fuer (A & B) & C A | B | C fuer (A | B) | C Man legt die Prioritaeten der Operatoren fest und schreibt: A | B & C fuer A | (B & C) A & B | C fuer (A & B) | C // Das Skript fordert ferner Rechtsassoziativitaet. Sie wirkt // sich hauptsaechlich auf "=>" aus. Diese Zusammenfassung // klammert jedoch in allen doppeldeutigen Faellen. // Es ist hoechst interessant, dass Mathematiker manchmal // linksassoziativ denken (a-b-c = (a-b)-c) und manchmal // rechtsassoziativ (a^b^c = a^(b^c)) und manchmal gar nicht: // a/b/c halten sie fuer undefiniert. Es gilt: * DC(Bax) = DC(BG) Beweis: '<': BAx < BG DC(BAx)': DC(BAx) > BG DC(DC(BAx))> DC(BG) Monotonie DC(BAx) > DC(BG) Idempotenz Es folgt DC(BAx)=DC(BG) * SC(BAx)=SC(BG) Beweis: Folgt aus Deduktiv-Semantischer Monotonie, siehe dort. // Uebungsblatt #5.4 Weitere Boolsche Gesetze: Die folgenden aus BG herleitbaren universell gueltigen Gleichungen: // Uebungsblatt #4.1 x <=> y = x&y | ~x&~y Definition = ~x&~y | x&y Kommutativitaet = y <=> x Definition x <=> x = ~x&~x | x&x Definition = ~x | x Idempotenz = 1 Komplemenz x <=> ~x = x&~x | ~x&x Definition = 0 | 0 Komplemenz = 0 Dominanz ~(x <=> y) = ~(x&y | ~x&~y) Definition = ~(x&y) & ~(~x&~y) DeMorgan = (~x|~y)& (~~x|~~y) DeMorgan = (~x|~y)& (x|y) Doppelnegation = (~x|~y)&x | (~x|~y)&y Distributivitaet = (~x&x |~y&x) | (~x&y | ~y&y) Distributivitaet = ( 0 |~y&x) | (~x&y | 0) Komplemenz = (~y&x) | (~x&y) Identitaet = x <=> ~y Definition x <=> (y <=> z) = x&(y&z|~y&~z) | ~x&((~y&z)|(~z&y)) Def & vorige = x&y&z | x&~y&~z | ~x&~y&z | ~x&~z&y Distributiv. (x <=> y) <=> z = (x&y|~x&~y)&z | ((~y&x)|(~x&y))&~z Def & vorige = x&y&z | ~x&~y&z | ~y&x&~z | ~x&y&~z Distributiv. = x&y&z | x&~y&~z | ~x&~y&z | ~x&~z&y Kommutativ. = x <=> (y <=> z) vorige ~x <=> ~y = (~x&~y) | (~~x&~~y) Definition = (~x&~y) | (x&y) Doppelnegation = x <=> y Definition x <=> 1 = x&1 | ~x&~1 Definition = x | ~x&~1 Identitaet = x | ~x&0 Negation = x | 0 Dominanz = x Identitaet x <=> 0 = x&0 | ~x&~0 Definition = 0 | ~x&~0 Dominanz = 0 | ~x&1 Negation = 0 | ~x Identitaet = ~x Identitaet x <=> y = x&y | ~x&~y Definition = (x&y | ~x) & (x&y | ~y) Distributivitaet = (x|~x) & (y|~x) & (x|~y) & (y|~y) Distributivitaet = 1 & (y|~x) & (x|~y) & 1 Komplemenz = (y|~x) & (x|~y) Identitaet, Ass. = (~x|y) & (~y|x) Kommutativitaet = (x=>y) & (y=>x) Definition (x&y) => z = ~(x&y) | z Definition = ~x | ~y | z DeMorgan = ~x | (y => z) Definition = x => (y => z) Definition ~x&y | x&z = (~x&y|x) & (~x&y|z) Distributivitaet = (~x|x) & (y|x) & (~x|z) & (y|z) Distributivitaet = 1 & (y|x) & (~x|z) & (y|z) Komplemenz = (y|x) & (~x|z) & (y|z) Identitaet = (x|y) & (~x|z) & (y|z) Kommutativitaet = (x|y) & (~x|z) Resolution = (~x=>y) & (x=>z) Definition (x|y)&(~x|~y)=>(y<=>~x) = ~((x|y)&(~x|~y)) | (y<=>~x) Definition = ~(x|y) | ~(~x|~y) | (y<=>~x) DeMorgan = ~x&~y | ~~x&~~y | (y<=>~x) DeMorgan = ~x&~y | x& y | (y<=>~x) Doppelnegation = (x<=>y) | (y<=>~x) Definition = (x<=>y) | ~(y<=>x) bereits gezeigt = 1 Komplemenz // Es sind noch eine ganze Anzahl (genau genommen unendlich viele) // weitere universell gueltige Gleichungen ableitbar, die jedoch hier // nicht gebraucht werden und daher nicht aufgefuehrt sind. Man kann auch mit Funktionssymbolen rechnen: // Uebungsblatt #4.2 Sei K f g x := f (g x) Komposition Sei V f x y := f y x Vertauschung Sei Z f := K f f Zweifachausfuehrung Dann kann man zeigen: V (=>) 0 = (lambda f x y. f y x) (=>) 0 Definition = lambda y. => y 0 Beta = lambda y. ~y | 0 Definition = lambda y. ~y Identitaet = ~ Eta K (xor) ~ = (lambda f g x. f (g x)) (xor) ~ Definition = lambda x. xor (~ x) Beta = lambda x. lambda y . xor (~ x) y Eta (rueckwaerts) = lambda x . lambda y. x&y | ~x&~y Definition & Vereinf. = lambda x . lambda y. x <=> y Definition = <=> Eta // Tippfehler auf dem Aufgabenblatt) V (&) = (lambda f x y. f y x) (&) Definition = lambda x y. y & x Beta = lambda x y. x & y Kommutativitaet = & Eta Z (~) = (lambda h. K h h) (~) Definition = (lambda h. (lambda f g x. f (g x)) h h) (~) Def = (lambda h. lambda x. h (h x)) (~) Beta = lambda x. ~ (~ x) Beta = lambda x. x Doppelnegation K (|) (~) = (lambda f g x . f (g x)) (|) (~) Definition = lambda x . | (~ x) Beta = lambda x . lambda y . | (~x) y Eta (rueckwaerts) = lambda x . lambda y . x => y Definition = => Eta Logiker-Verlies: Das folgende Problem // Aufgrund seines Erheitungswertes hier der Originaltext aus dem // Uebungsblatt #7.9 Sie wurden von einem Logiker gekapert und sitzen in einem Verlies mit zwei Tueren, von denen eine in die Freiheit und die andere in die Vorlesung "Logik ohne Gnade" fuehrt. Der Logiker gibt Ihnen eine faire Chance, seiner Vorlesung zu entkommen und gewaehrt Ihnen eine Frage, die er mit 0 oder 1 beantworten wird. Die Frage muss als Boolesche Formel formuliert sein, in der die Variablen x und y vorkommen koennen, denen der Logiker die folgenden Werte gibt: x = 1 genau dann, wenn die linke Tuer in die Freiheit fuehrt. y = 1 genau dann, wenn der Logiker die Frage korrekt beantwortet. Uns interessiert eigentlich der Wert von x. Wenn aber der Logiker luegt (y=0), so wollen wir den werrt von x invertieren. Das geschieht mit x <=> y // Das ist gar nicht so trivial, denn wenn der Logiker luegt, so wird // er ueber die Aussage "x<=>y" luegen. Nachpruefungs-Problem: Das folgende Problem // Auch hier der Originaltext aus dem Uebungsblatt #7.10 Sie machen die muendliche Nachpruefung zur Vorlesung "Logik ohne Gnade" bei Professor x und seinem Assistenten y: x sagt: Ich sage die Wahrheit und y sagt die Wahrheit. y sagt: x luegt. Sie bestehen, wenn Sie ohne weitere Information herausfinden, ob x und y die Wahrheit sagen oder lügen und auch begründen können, warum dies so ist. Wir formalisieren beide Aussagen, wobei wir die boolschen Variablen xWahrheit und yWahrheit benutzen: xWahrheit <=> xWahrheit & yWahrheit yWahrheit <=> ~xWahrheit Uns interssieren die Werte von xWahrheit und yWahrheit, sodass beide Gleichungen erfuellt sind. Es zeigt sich xWahrheit = 0 yWahrheit = 1 // Interessanterweise ist die Aussage des Professors "Ich sage die // Wahrheit" tatsaechlich zu etwas nuetze -- obwohl eigentlich die // Aussage "Ich sage die Wahrheit" ansich weder falsch noch wahr ist. // Denn wenn jemand sagt "Ich sage die Wahrheit", so ist es moeglich, // dass er die Wahrheit spricht (dann stimmt auch seine Aussage) oder // aber dass er luegt (dann ist seine Aussage falsch, was ja auch // mit seinem Luegen kohaerent ist). Aehnlich kann man einen Zeugen // Jehovas widerlegen, der mir einmal sagte: "Natuerlich ist die // Bibel wahr! Es doch in der Bibel drin, dass sie wahr ist!". Dieses // sagt nichts ueber den Wahrheitsgehalt der Bibel. // In unserem Fall aber ist die Aussage nuetzlich: Waeren die Aussagen // Professor: Der Assistent sagt die Wahrheit // Assistent: Der Professor luegt // so gaebe es schlichtweg keinen konsistenten Weltzustand. Aequivalente Gleichungen: Zwei Gleichungen A=B und A'=B', die dieselbe Loesungsmenge haben. Durch Korrektheit und Vollstaendigkeit (noch zu zeigen) wird dies im Falle boolscher Algebra zu: BAx \/ {A = B} |- A' = B' und BAx \/ {A'= B'} |- A = B Normalisierte Gleichung einer boolschen Gleichung A=B: Die Gleichung A<=>B = 1. Die normalisierte Gleichung und die Ausgangsgleichung sind aequivalent: // Uebungsblatt #4.6a * BAx \/ {A=B} |- A<=>B=1 Mit A=B laesst sich also die normalisierte Gleichung herleiten. Beweis: A<=>B = A<=>A da A=B = 1 mit weiterem boolschen Gesetz Also gilt nebenbei: {A<=>B=1} < DC(BAx \/ {A=B}) * BAx \/ {A<=>B=1} |- A = B Mit der normalisierten Gleichung laesst sich also A=B herleiten. Beweis: A = A & 1 Identitaet = A & (A<=>B) da A<=>B=1 = A & ((A & B) | (~A & ~B)) nach Definition von <=> = (A & A & B) | (~A & ~B & A)Distributivitaet = A & B Idempotenz, Komplemenz B = A & B analog B = A Transitivitaet von "=" Also gilt nebenbei: {A=B} < DC(BAx \/ {A<=>B=1}) Mit den Eigenschaften des Abschlussoperators folgt nebenbei: DC(BAx \/ {A=B}) = DC(BAx \/ {A<=>B=1}) // Uebungsblatt #5.5b Nur am Rande: Aehnlich laesst sich A != B normalisieren zu ~(A<=>B)=1. Normalisierte Gleichung der boolschen Gleichungen A=1,B=1: Die Gleichung A & B = 1 Die normalisierte Gleichung und die Ausgangsgleichungen sind aequivalent: * BAx \/ {A=1, B=1} |- A & B=1 Beweis: A & B = 1 & 1 = 1 mit A=1,B=1 und Identitaet * BAx \/ A & B=1 |- A=1, B=1 Beweis: A = ~~A Doppelnegation = ~(~A & 1) Identitaet = ~(~A & A & B) da A & B=1 = ~0 Komplemenz = 1 Negation B = 1 analog Mit den Eigenschaften des Abschlussoperators folgt: DC(BAx \/ {A=1, B=1}) = DC(BAx \/ {A & B=1}) // Uebungsblatt #5.5a Beispiel: // Uebungsblatt #4.3 {A1=B1, A2=B2, A3=B3} ~~~> (A1<=>B1) & (A2<=>B2) & (A3<=>A3) = 1 // Uebungsblatt #4.4 {A=~B, A=(A<=>B)} ~~~> {(A<=>~B)=1, (A<=>(A<=>B))=1} ~~~> {(A<=>~B)=1, ((A<=>A)<=>B)=1} ~~~> {(A<=>~B)=1, 1<=>B=1} ~~~> {(A<=>~B)=1, B=1} ~~~> {(A<=>~1)=1, B=1} ~~~> {(A<=>0)=1, B=1} ~~~> {~A=1, B=1} ~~~> ~A&B=1 Nur am Rande: Aehnlich laesst sich A=0,B=0 normalisieren: // Uebungsblatt #4.5 * BAx \/ {A=0, B=0} |- (A | B)=0 Beweis: A & B = 0 & 0 = 0 mit A=0,B=0 und Absorption * BAx \/ {(A | B)=0} |- A=0, B=0 Beweis: A = A & 1 Identitaet = A & ~0 Negation = A & ~(A|B) da (A|B)=0 = A & ~A & ~B DeMorgan = 0 Komplemenz Nur am Rande: Man kann auch den "Modus Ponens" zeigen: // Uebungsblatt #4.6 BAx \/ {A=>B=1, A=1} |- B=1 B = B | 0 Identitaet = B | ~1 Negation = B | ~(A=>B) da (A=>B)=1 = B | ~(~A|B) Definition = B | ~~A&~B DeMorgan = B | A&~B Doppelnegation = B | 1&~B da A=1 = B | ~B Identitaet = 1 Komplemenz Trick bei solchen Beweisen: * Um zu zeigen, dass A=0 Man beginne mit A=A & 1 =A & denn bei einer Konjunktion hat man gute Chancen, sie zu 0 umzuformen * Um zu zeigen, dass A=1: Man beginne mit A=A | 0 =A | ~1 =A | ~ denn bei einer Disjunktion hat man gute Chancen, sie zu 1 umzuformen boolsches Gleichungssystem: Eine Menge von Gleichungen. Loesungsmenge eines Gleichungssystems: Die Schnittmenge der Loesungsmengen der Gleichungen. Man definiert fuer die Loesungsmenge des leeren Gleichungssystems die Menge aller moeglichen Zustaende. Schreibweise: Sol({}) := StaT Sol(M) := SCHNITT { Sol(A=B) | A=B in M } = { s in StaT | T A s = T B s fuer alle A=B in M } Normalisierte Gleichung eines boolschen Gleichungssystems S: Die boolsche Gleichung, die man wie folgt erhaelt: * Normalisiere jede einzelne Gleichung aus S nach dem Schema A=B ~~~> A <=> B = 1 * Fasse immer zwei normalisierte Gleichungen zu einer normalisierten Gleichung zusammen nach dem Schema A=1, B=1 ~~~> A & B = 1 Jede einzelne Normalisierung erhaelt die Aequivalenz, also sind Gleichungssystem und normalisierte Gleichung aequivalent. Auf diese Weise laesst sich also jedes boolsche Gleichungssystem umschreiben zu einer einzigen aequiavlenten boolschen Gleichung: Gleichungssystem ~~~Normierung~~~> boolsche Gleichung Gleichungssystem und Gleichung haben dann dieselbe Loesungsmenge. Vorraussetzung ist, dass das Gleichungssystem aus endlich vielen Gleichungen besteht! Terme sind stets endlich, Mengen hingegen nicht notwendigerweise. Da sich also boolsche Gleichungen und boolsche Gleichungssystem ineinander umwandeln lassen, uebertragen sich die folgenden Begriffe von boolschen Gleichungen auf boolsche Gleichungssysteme: * Universell gueltiges boolsches Gleichungssystem * Unerfuellbares boolsches Gleichungssystem * Erfuellbares boolsches Gleichungssystem Beispiel: // Uebungsblatt #5.2 { x<=>y=0, y<=>z=0, x<=>u=0} ist in der klassischen boolschen Algebra nicht erfuellbar, obwohl jede Teilmenge erfuellbar ist (liegt daran, dass es nur 2 moegliche Belegungen fuer eine Variable gibt und nicht 3). ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Boolsche Vollstaendigkeit ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Im Folgenden wird immer die klassische boolschen Algebra T // vorrausgesetzt Implikation einer boolschen Formel A: Eine boolsche Formel B, sodass A&B=A universell gueltig ist. Das heisst: Es gibt keinen Zustand, in dem A gueltig waere und B nicht. Also impliziert A B. boolsche Uebersetzungsfunktion: Eine Uebersetzungsfunktion von der Menge der boolschen Terme in eine Teilmenge. Beispiel: // Uebungsblatt #5.6 Sei For' die Menge der boolschen Formeln nach der folgenden Grammatik: For' --> x falls Gamma(x)=B For' --> For' nand For' Man kann eine Uebersetzungsfunktion f in For->For' wie folgt definieren: f(x) :=x f(~A) := f(A) nand f(A) f(1) := x0 nand f(~x0) fuer irgendeine Variable x0 f(0) := f(~1) f(A&B) := (f(A) nand f(B)) nand (f(A) nand f(B)) f(A|B) := f(~(~A & ~B)) // Uebungsblatt #5.7 Sei Ter' die Menge der boolschen Termen ach der folgenden Grammatik: Ter' --> x Ter' --> nand Ter' --> lambda x.Ter' Ter' --> Ter' Ter' Man kann eine Uebersetzungsfunktion f in Ter->Ter' wie folgt definieren: f(x) := x f(lambda x.A) := lambda x.f(A) f(A B) := f(A) f(B) f(~) := lambda x. x nand x f(&) := lambda x.lambda y. f(~) (f(x) nand f(y)) f(|) := lambda x.lambda y. f(~(~x & ~y)) f(0) := x0 nand f(~x0) f(1) := f(~0) Konditional: Das folgende Funktionssymbol (A,B,C) := (~A & B) | (A & C) vom Typsymbol B->B->B->B Wenn A zu "falsch" auswertet, dann liefert das Konditional B, ansonsten liefert es C. Intuition: Das ist ein IfThenElse oder das Fragezeichen in C, Java und FAST, allerdings nur fuer boolsche Werte und mit vertauschten Argumenten! (A,B,C) := A?C:B Semantische Eigenschaft: // Uebungsblatt #4.6b T (x,A,B) s = T A s falls s(x)=0 Beweis: T (x,A,B) s = T ((~x & A) | (x & B)) s = 1 & (T A s) | 0 & (T B s) = T A s Analog folgt: T (x,A,B) s = T B s falls s(x)=1 Syntaktische Eigenschaften: // Uebungsblatt #5.3 (A,B,B) = (~A & B) | (A & B) Definition = B | (~A & A) Distributivitaet = B | 0 Komplemenz = B Identitaet ~(A,B,C) = ~ ((~A & B) | (A & C)) Definition = ~(~A & B) & ~(A & C) DeMorgan = (A | ~B) & (~A | ~C) DeMorgan = (A | ~B) & ~A | (A | ~B) & ~C Distributivitaet = (A & ~A | ~B & ~A) | (A & ~C | ~B & ~C) Distributivitaet = ~A & ~B | A & ~C | ~B & ~C Komplemenz = ~A & ~B | A & ~C Resolution = (A,~B,~C) Definition (~A,B,C) = (~~A & B) | (~A & C) Definition = (A & B) | (~A & C) Doppelnegation = (~A & C) | (A & B) Kommuativitaet = (A,C,B) Definition (A,B,C)&D = (~A & B | A & C) & D Definition = (~A & B & D | A & C & D) Distributivitaet = (A, B & D, C & D) Definition (A,B,C)oD = (A, B o D, C o D) fuer jedes boolsche Funktionssymbol o (warum (?)) dual(x,y,z)=dual((~x & y) | (x & z)) Definition = (~x | y) & (x | z) Definition = (~x | y) & x | (~x | y) & z Distriyutivitaet = (~x & x | y & x) | (~x & z | y & z) Distriyutivitaet = x & y | ~x & z | y & z Komplemenz, Id = x & y | ~x & z Resolution = (x,z,y) fuer komplexe Terme muss jeder einzelne noch dualisiert werden (A,B1,C1)&(A, B2, C2) = (~A&B1 | A&C1) & (~A&B2 | A&C2) Definition = (~A&B1 & (~A&B2 | A&C2)) | ( A&C1 & (~A&B2 | A&C2)) Distributivitaet = (~A&B1&~A&B2 | ~A&B1&A&C2) | ( A&C1&~A&B2 | A&C1&A&C2) Distributivitaet = (~A&B1&B2 | 0) | ( 0 | A&C1&C2) Komplemenz = (~A&B1&B2) | (A&C1&C2) Identitaet = (A,B1&B2,C1&C2) Definition (A,B1,C1) o (A, B2, C2) = (A, B1 o B2, C1 o C2) fuer jedes boolsche Funktionssymbol o (warum (?)) (A,B,C) = ~A&B | A&C Definition = (~A&B | A) & (~A&B | C) Distributivitaet = (~A|A) & (A&B) & (~A|C) & (B|C) Distributivitaet = 1 & (A&B) & (~A|C) & (B|C) Komplemenz = (A&B) & (~A|C) & (B|C) Identitaet = (~A|C) & (A|B) Resolution Entwicklungssatz, Shannon-Expansion: Die folgende Tatsache: BAx |- A = (x,A[x->0],A[x->1]) fuer irgendeine Variable x Einen Term A kann man also wie folgt umschreiben: Entweder ist x falsch, dann kann man x auch gleich durch 0 ersetzen. Oder aber x ist wahr, dann kann man es durch 1 ersetzen. Beispiel: (x & y) | z = (x, (0 & y) | z, (1 & y) | z) = (x, z, y | z) = (~x & z) | (x & (y | z)) Beweis: Wir zeigen BAx |- x & A = x & A[x->1] durch strukturelle Induktion ueber A: * Falls x keine freie Variable in A ist, gilt A = A[x->1] Es folgt x & A = x & A[x->1] * Falls A = x, gilt: x & A = x & x da A=x = x per Idempotenz = x & 1 per Identitaet = x & A[x->1] da A=x * Falls A = ~A', gilt: x & A = x & ~A' da A=~A' = 0 | x & A' per Identitaet = x & ~x | x & ~A' per Komplemenz = x & (~x | ~A') per Distribuitivitaet = x & ~(x & A') per DeMorgan = x & ~(x & A'[x->1]) per Induktionsprothese = x & ~A'[x->1] obige Schritte rueckwaerts = x & A[x->1] * Falls A = A1 & A2, gilt: x & A = x & A1 & A2 da A = A1 & A2 = x & x & x & A1 & A2 per Idempotenz = x & (x & A1) & (x & A2) per Assoziativitaet = x & (x & A1[x->1]) & (x & A2[x->1]) per Induktionshypothese = x & A1[x->1] & A2[x->1] per Idempotenz = x & (A1 & A2)[x->1] per Definition der Substitution = x & A[x->1] * Falls A = A1 | A2, gilt: x & A = x & (A1 | A2) da A = A1 | A2 = x & x & (A1 | A2) per Idempotenz = x & (x & A1 | x & A2) per Distributivitaet = x & (x & A1[x->1] | x & A2[x->1]) per Induktionshypothese = x & (A1[x->1] | A2[x->1]) per Distributivitaet und DeMorgan = x & (A1 | A2)[x->1] per Definition der Substitution = x & A[x->1] Analog laesst sich zeigen: BAx |- ~x & A = x & A[x->0] Also gilt: A = (x,A,A) per Eigenschaften des Konditionals = ~x & A | x & A nach Definition des Konditionals = ~x & A[x->0] | x & A[x->1] nach obigem Beweis = (x,A[x->0],A[x->1]) Resultat: Aus einer beliebigen Formel laesst sich eine beliebige Variable "herausziehen": Sie taucht dann nur noch im Kopf des Konditionals auf, nicht mehr jedoch in den beiden Teiltermen. Decision Tree, Entscheidungsbaum: Eine Boolsche Formel nach der folgenden Grammatik: DT --> 0 DT --> 1 DT --> (x,DT,DT) fuer eine Variable x Ein Entscheidungsbaum darf also nur aus 0, 1, und Konditionalen bestehen. Jeder Entscheidungsbaum ist ein Baum: 0 und 1 sind Blaetter und (x,A,B) ist ein Baum, in dem die Variable x der Knoten ist und A und B die Kinder. Jede boolsche Formel laesst sich als aequivalenter Entscheidungsbaum schreiben. Beweis: siehe unten bei "Primbaum". Schreibweise: DT fuer die Menge der Entscheidungsbaeume Intuition: So ein Entscheidungsbaum laesst einen den Wert einer Formel anhand der Werte der Variablen ablesen. Der linke Ast steht dabei fuer die Belegung der Variable mit "falsch", der rechte Ast steht fuer eine Belegung mit "wahr". Beispiel: x & (y | z) x / \ 0 y / \ z 1 / \ 0 1 Reduzierter Entscheidungsbaum: Ein Entscheidungsbaum, bei dem kein Knoten (syntaktisch) gleiche Kinder hat: Fuer alle (x,A,B) gilt: A != B Geordneter Entscheidungsbaum: Ein Entscheidungsbaum, bei dem in allen Knoten (x,A,B) die Variable x kleiner ist als alle in A und B vorkommenden Variablen. Dieser Begriff setzt eine lineare Ordnung auf den Variablen voraus. Da Variablen natuerliche Zahlen sind, ist eine moegliche Ordnung trivial gegeben, es ist jedoch auch jede andere Ordnung moeglich. Schreibweise: ODT fuer die Menge der geordneten Entscheidungsbaeume Prime Tree, Primbaum: Ein reduzierter und geordneter Entscheidungsbaum. Ein Primbaum stellt also einen genormten Entscheidungsbaum dar. Zu jeder boolschen Formel existiert genau ein aequivalenter Primbaum. Beweis der Existenz: Wir bauen eine Uebersetzungsfunktion primBaum, die einer boolschen Formel einen aequivalenten Primbaum zuordnet: primbaum(A) := 0 falls FV(A)={} und BAx |- A=0 // Eigentlich ist die Bedingung FV(A)={} // ueberfluessig. Diese Version ist jedoch // leichter zu implementieren primbaum(A) := 1 falls FV(A)={} und BAx |- A=1 primbaum(A) := (x,primbaum(A[x->0]),primbaum(A[x->1])) falls x die kleinste in A vorkommende Variable ist und primbaum(A[x->0]) != primbaum(A[x->1]) primbaum(A) := primbaum(A[x->0]) falls x die kleinste in A vorkommende Variable ist und primbaum(A[x->0]) = primbaum(A[x->1]) Beweis der Aequivalenz: BAx |- primbaum(A)=A per struktureller Induktion ueber primbaum(A): * Falls primbaum(A)=1, so ist per Vorraussetzung BAx |- A=1 und damit BAx |- primbaum(A)=A. * Falls primbaum(A)=0, gilt BAx |- A=0 und BAx |- primbaum(A)=A. * Falls primbaum(A)=(x,A0,A1) gilt nach Induktionduktionshypothese BAx |- primbaum(A[x->0])=A0 BAx |- primbaum(A[x->1])=A1 Also BAx |- primbaum(A)=(x,A[x->0],A[x->1]) Und mit dem Entwicklungssatz BAx |- primbaum(A)=A Beweis der Eindeutigkeit: ("Kanonizitaet") Behauptung: Seien A und B syntaktisch verschiedene Primbaeume. Dann gibt es einen Zustand s, sodass in der klassischen boolschen Algebra T gilt: T A s != T B s Beweis durch Induktion ueber das Maximum der Knotenzahl von A und B: * Falls das Maximum 1 ist, so haben beide Baeume 1 Knoten, d.h. sie sind entweder 1 oder 0. Da sie verschieden sind, ist einer 1 und der andere 0 und die Algebra taete gut daran, sie fuer egal welchen Zustand s auf zwei verschiedene Werte abzubilden. * Falls das Maximum groesser als 1 ist, gibt es 3 Faelle: * Falls A=(x,A0,A1) ist und x nicht frei in B vorkommt, gilt: A0 und A1 sind beides Primbaeume mit geringerer Knotenzahl als A. Sie sind syntaktisch verschieden. Also laesst sich die Induktionshypothese anwenden und es existiert ein s mit T A0 s != T A1 s. Nun gibt es 2 Faelle * Falls T A s != T B s, so ist der unterscheidende Zustand gefunden * Falls T A s = T B s, so aendern wir s um: Sei zB s(x)=0. Wir betrachten statt s den Zustand s[x->1]. Dann gilt: T A s[x->1] = T A1 s[x->1] da das Konditional dann A1 waehlt = T A1 s da x nicht frei in A1 vorkommt != T A0 s mit A1!=A0 und Induktionsannahme = T A s da das Konditional dann A0 waehlt = T B s nach Annahme = T B s[x->1] da x nicht frei in B vorkommt Per Transitivitaet folgt also fuer s[x->1]: T A s[x->1] != T B s[x->1] * Falls B=(x,B0,B1) ist und x nicht frei in A vorkommt, kann man analog zum vorherigen Fall argumentieren. * Falls A=(x,A0,A1) und B=(x,B0,B1), so gibt es 2 Faelle * Falls A0 != B0 A0 und B0 sind beides Primbaeume mit geringerer Knotenzahl als A. Sie sind syntaktisch verschieden. Also laesst sich die Induktionshypothese anwenden und es existiert ein s mit T A0 s != T B0 s. Da x nicht frei in A0 oder B0 vorkommt, gilt diese Ungleichung auch mit s[x->0]: T A0 s[x->0] != T B0 s[x->0] Fuer diesen Zustand s[x->0] gilt nun: T A s[x->0] = T A0 s[x->0] != T B0 s[x->0] = T B s[x->0] * Falls A1 != B1 kann man analog argumentieren # Falls weder A0 != B0 noch A1 != B1, so ist A=B. Dieser Fall tritt nach Vorraussetzung nicht ein. # Falls A=(x,A0,A1) und B=(y,B0,B1), so gibt es 2 Faelle: # Falls x < y, gilt: Nach Konstruktion des Primbaums B ist y ist die kleinste Variable in B. x ist kleiner als y, also kann x nicht in B vorkommen. Damit tritt ein vorhergehender Fall ein. # Falls x > y, kann man analog argumentieren. Dieser Fall tritt also nicht ein. Die Menge der Primbaeume ist also kanonisch: Zwei Primbaeume sind genau dann aequivalent, wenn sie (syntaktisch) gleich sind. Da jede Formel zu ihrem Primbaum aequivalent ist, folgt: Zei Formeln sind genau dann aequivalent, wenn ihre Primbaeume (syntaktisch) gleich sind. Zu jeder Formel laesst sich also eine eindeutige Darstellung in Form eines Primbaumes berechnen: For ~~~primbaum~~~> PT Schreibweise: PT fuer die Menge der Primbaeume com, pi fuer die primbaum-Funktion Trick zum Zeichnen von Primbaeumen: Man beginne mit der ersten Variable x. Man ersetze in der Formel alle x durch 0 und vereinfache. Diese Formel kann man neben den linken Ast schreiben. Man ersetze in der Ausgangsformel x durch 1 und vereinfache. Diese Formel kann man neben den rechten Ast schreiben. Sind die Formeln gleich, so hat x im Primbaum nichts zu suchen, zeiche den Primbaum der vereinfachten Formel. Sind die Formeln nicht gleich, zeichne nach demselben Verfahren die Primbaeume der vereinfachten Formeln fuer die naechste Variable. Beispiel: (x|z) & (~x|~y|~z) & (x&~y | ~z) mit x (y <=> z) x__________ 0<=>(y<=>z) / \ y___ y__ 0<=>(0<=>z) / \ / \ z z z z 0<=>(0<=>0) / \ / \ / \ / \ 0 1 1 0 1 0 0 1 // Uebungsblatt #6.2d Der Primbaum einer beliebigen Formel A ist gleich dem von A & A, da A&A==A und da Primbaeume kanonisch sind. // Uebungsblatt #6.2e Der Primbaum einer Formel A mit A==1 ist 1: Wegen der Kanonizitaet und A==1 muss A denselben Primbaum haben wie 1. Und 1 hat den Primbaum 1. // Uebungsblatt #6.6 Die Variablenordnung hat einen grossen Einfluss auf den Primbaum. Sei die Formel ~x&y&u | x&z&v Fuer die Ordnung x < y < u < v < z x_____ y&u / \ z&v y v / \ / \ 0 u 0 z / \ / \ 0 1 0 1 Fuer die Ordnung u < v < x < y < z u___________ x&z&v / \ ~x&y | x&z&v v v______ / \ ~x&y / \ ~x&y | x&z 0 x x x___ / \ / \ y / \ z 0 z y 0 y z / \ / \ / \ / \ 0 1 0 1 0 1 0 1 Primbaum einer Variable x: Der Primbaum (x,0,1). Die Variable wird also in ein aequivalentes Konditional eingepackt. Dies ergibt sich unmittelbar aus der obigen Definition der primbaum-Funktion. Funktion: var2primbaum(x):=(x,0,1) // Uebungsblatt #6.7a Primbaum-Negation: Die folgende Funktion: neg in PT -> PT neg(0):=1 neg(1):=0 neg((x,A,B)) := (x,neg(A), neg(B)) Die Primbaumnegation liefert also das Komplement der zugrundeliegenden Formel. Der negierte Primbaum hat einfach alle Blaetter von 0 nach 1 vertauscht und umgekehrt. Diese Funktion funktioniert wegen der Eigenschaft des Konditionals, dass ~(A,B,C) = (A,~B,~C). // Uebungsblatt #6.7b Beispiel: // Uebungsblatt #6.2b ~(x <=> (y <=> z)) x__________ / \ y___ y__ / \ / \ z z z z / \ / \ / \ / \ 1 0 0 1 0 1 1 0 Primbaum-Dualisierung: Die folgende Funktion: dual in PT -> PT dual(0):=1 dual(1):=0 dual((x,A,B)) := (x,dual(B), dual(C)) Diese Funktion liefert also das Duale der zugrundeliegenden Formel. Der duale Primbaum * hat alle Blaetter von 0 nach 1 vertauscht und umgekehrt * ist um die vertikale Achse gespiegelt Die Funktion funktioniert, weil dual(x,y,z)=(x,z,y), zuzueglich rekursivem Abstieg. // Uebungsblatt #6.7c Beispiel: // Uebungsblatt #6.2c Bei der Dualisierung von (x <=> (y <=> z)) aendert sich nichts, der Primbaum ist derselbe. Zwar werden die Teilbaeume vertauscht, aber durch die Anschliessende Negation aller Blaetter ist das Ergebnis der Ausgangsbaum. // Uebungsblatt #6.2f Aus der Gleichheit der Primbaueme folgt wegen der Kanonizitaet die Aequivalenz: (x <=> (y <=> z)) == dual(x <=> (y <=> z)). Daher ist auch die normierte Gleichung (x <=> (y <=> z)) <=> dual(x <=> (y <=> z)) = 1 universell gueltig. Damit ist der Primbaum dieser Aequivalenz 1. Primbaum-Konjunktion: Die folgende Funktion: and in PT*PT->PT and(A,0):=0 and(A,1):=A and(0,A):=0 and(1,A):=A and(A,A):=A and((x,A0,A1),(x,B0,B1)):=red(x,and(A0,B0),and(A1,B1))) and((x,A0,A1),(y,B0,B1)):=red(x,and(A0,(y,B0,B1)), and(A1,(y,B0,B1))) falls x and falsch) benutzt sie dafuer die Eigenschaft des Konditionals, dass (x,A0,A1)&(x,B0,B1)=(x,A0&B0,A1&B1) Falls die Variable des einen Konditionals kleiner ist als die des anderen, also (x,A0,A1)&(y,B0,B1) mit xPT or(A,B) := neg(and(neg(A),neg(B))) Diese Funktion liefert also die Disjunktion der zugrundeliegenden Formeln und verwendet dazu DeMorgan mit x|y=~(~x&~y). // Uebungsblatt #6.7e Primbaum-Nor: Die folgende Funktion: nor in PT*PT->PT nor(T,A):=F nor(A,T):=F nor(F,F):=T nor(F,(x,A0,A1)):=red(x,nor(F,A0),nor(F,A1)) nor((x,A0,A1),F):=red(x,nor(A0,F),nor(A1,F)) nor((x,A0,A1),(x,B0,B1)):=red(x,nor(A0,B0),nor(A1,B1)) nor((x,A0,A1),(y,B0,B1)):=red(x,nor(A0,(y,B0,B1)), nor(A1,(y,B0,B1))) falls xy wobei red(x,A,A):=A red(x,A,B):=(x,A,B) falls A!=B Diese Funktion liefert also das logische Nor der zugrundeliegenden Formeln. Dazu betrachtet sie alle moeglichen Faelle von Formeln und faengt die Trivialfaelle ab (eine Formel wahr~~>Nor falsch). Dann benutzt sie die Eigenschaft des Konditionals, dass (x,A0,A1)o(x,B0,B1) = (x,A0 o B0,A1 o B1) fuer jeden logischen Operator o. Alternativ koennte man or und neg benutzen: nor(A,B):=neg(or(A,B)) // Uebungsblatt #6.7k Entscheidungsbaum-Primierung: Die folgende Funktion: simplify in For->PT simplify(0):=0 simplify(1):=1 simplify((x,A,B)):=or(and(neg(var2primbaum(x)),simplify(A)), and( var2primbaum(x) ,simplify(B))) Diese Funktion uebersetzt also einen Entscheidungsbaum in einen Primbaum. Dazu uebersetzt sie das Konditional (x,A,B) in sein Aequivalent ~x&A | x&B und baut dieses Aequivalent mithilfe der Primbaumfunktionen. // Uebungsblatt #6.7f Alternative Primbaum-Funktion: Die folgende Funktion: com in For -> PT com(0) :=0 com(1) :=1 com(~A) :=neg(com(A)) com(A & B):=and(com(A),com(B)) com(A | B):=or(com(A),com(B)) Diese Funktion uebersetzt eine Formel in einen Primbaum. Dazu uebertraegt sie alle boolschen Operatoren auf die entsprechenden Primbaumfunktionen. // Uebungsblatt #6.7g Signifikante Variablen eines Primbaums: Seine freien Variablen. Beweis: Zu zeigen: Wenn x in FV(A) dann x in SV(A) bzw Wenn x nicht in SV(A) dann x nicht in FV(A) Sei also x eine nicht signifikante Variable in A. Dann gilt: A == A[x->0] Mit Kanonizitaet der Primbaeume gilt: primbaum(A) = primbaum(A[x->0]) Da A bereits ein Primbaum ist, gilt: A = primbaum(A[x->0]) Da die Primbaum-Funktion keine Variablen hinzufuegt und x nicht frei in A[x->0] vorkommt, gilt: x nicht in FV(primbaum(A[x->0])) und damit x nicht in FV(A) Da alle signifikanten Variablen sowieso freie Variablen sind, folgt Gleichheit: FV(A) = SV(A) fuer Primbaeume A Die signifikanten Variablen lassen sich wie folgt in aufsteigender Reihenfolge berechnen: sv(0):=[] sv(1):=[] sv((x,A,B)):= x::merge(sv(A),sv(B)) mit merge([],L):=L merge(L,[]):=L merge(x::L1,x::L2):= x::merge(L1,L2) merge(x::L1,y::L2):= x::merge(L1,y::L2) falls xy Die signifikanten Variablen einer Formel lassen sich aufzaehlen, indem man zunaechst die Formel in einen Primbaum umwandelt und dann die sv-Funktion benutzt. // Uebungsblatt #6.7h Formel eines Entscheidungsbaums: Die mit der folgenden Funktion gewonnene Formel: formel(0):=0 formel(1):=1 formel((x,A,B)):=~x&formel(A) | x&formel(B) Diese Funktion uebersetzt einfach das Konditional zurueck in seine eigentliche Form. Die resultierende Formel ist nicht besonders schoen, da jede einfache Variable x ausgedrueckt ist durch ~x&0 | x&1, aber nach Schoenheit war hier nicht gefragt. // Uebungsblatt #6.7i Boolscher Vollstaendigkeitssatz: Die Tatsache, dass sich jede universell gueltige boolsche Gleichung aus den boolschen Axiomen herleiten laesst. Beweis: Sei A=B eine UGG, d.h. A=B in Th(T) /\ For^2 Zu zeigen: BAx |- A=B Es gilt: BAx |- A=primBaum(A) A=primBaum(A) in Th(T) wegen Korrektheit Analog gilt: BAx |- B=primBaum(B) B=primBaum(B) in Th(T) wegen Korrektheit Mit A=B in Th(T) und der Transitivitaet von Th folgt: primBaum(A)=primBaum(B) in Th(T) Mit der Kanonizitaet von Primbaeumen folgt: primBaum(A)=primBaum(B) Durch Einsetzen oben folgt: BAx |- A=primBaum(B) Per Transitivitaet folgt: BAx |- A=B Formal ausgedrueckt: DC(BAx) /\ For^2 > Th(T) /\ For^2 Tautologie-Satz: Die folgende Tatsache: DC(BAx) /\ For^2 = SC(BAx) /\ For^2 = Th(T) /\ For^2 Das heisst: Innerhalb der boolschen Formeln ist das aus BAx herleitbare gleich dem aus BAx semantisch folgerbaren gleich dem, was in der klassischen Boolschen Algebra ueberhaupt gilt. Beweis: Th(T) /\ For^2 < DC(BAx) /\ For^2 wegen boolscher Vollstaendigkeit < SC(BAx) /\ For^2 wegen boolscher Korrektheit < Th(T) /\ For^2 wegen BAx < Th(T) mit semantischer Abgeschlossenheit Damit gilt Gleichheit. Eine Formel ist genau dann in der klassischen boolschen Algebra gueltig, wenn sie herleitbar ist. Wegen des Boolschen-Algebren-Satzes ist das genau dann der Fall, wenn sie in allen boolschen Algebren gueltig ist. Wahrheitstafel einer boolschen Formel F: Die Denotationen der Formel F fuer alle moeglichen Zustaende. Beispiel: F = x & ~y x y F 0 0 0 0 1 0 1 0 1 1 1 1 Mithilfe der Wahrheitstafel von A<=>B kann also gezeigt werden, ob eine Gleichung A=B universell gueltig ist (ueberall 1), erfuellbar (mindestens eine 1) oder unerfuellbar (keine 1) ist. Dieses Verfahren ist jedoch recht aufwaendig, da es fuer n Variablen 2^n verschiedene Zustaende gibt. Verifikation einer boolschen Gleichung: Die Bestimmung der universellen Gueltigkeit der Gleichung. Dazu kennen wir 3 Verfahren: * Wir koennen versuchen, die Gleichung mithilfe der Gleichheitsregeln zu beweisen. Falls die Gleichung nicht universell gueltig ist, kann man dieses eventuell dann nicht herausfinden. * Wir koennen die Wahrheitstafel der normierten Gleichung aufstellen und schauen, ob sie ueberall 1 ergibt. Bei n Variablen hat man dann jedoch 2^n Paare zu vergleichen. * Wir koennen zu jedem Term den aequivalenten Primbaum bestimmen und schauen, ob die Primbaeume (syntaktisch) gleich sind. Genau dann ist die Gleichung universell gueltig. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Boolsche Funktionen ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ klassischer boolscher Zustand: Ein Zustand in der klassischen boolschen Algebra, der Variablen abbildet auf die boolschen Werte 0 und 1: s in (Var -> B) Intuition: Also die ganz normale Variablenbelegung wie zB x=1, y=0, z=1. Wenn man so will, steht jede Variable fuer eine Aussage ueber die Welt (zB "es regnet", "ich schreibe im CL-Test ab"). Ein klassischer boolscher Zustand entspricht dann einem "Zustand" der Welt, in dem bestimmte Aussagen gelten und andere nicht. Die verschiedenen klassischen boolschen Zustaende spiegeln also moegliche Weltzustaende wider. Wir nehmen eine endliche Menge "Var" von Variablen an und darueber hinaus eine lineare Ordnung auf den Variablen. Dann ist ein klassischer boolscher Zustand strukturgleich zu einem Tupel aus boolschen Werten: Var -> B ~=~ B^|Var| Fuer n Variablen gibt es also 2^n klassische boolsche Zustaende. Klassische boolsche Zustaende koennen wie folgt Variablen beliebigen (endlichen) Typs simulieren: Man nummeriert alle Elemente des Typs durch. Ist 2^n eine Obergrenze fuer die Anzahl der Elemente des Typs, so nimmt man statt einer Variable dieses Typs n Variablen mit Typsymbol B. Ist s ein Zustand und sind die n Variablen x1,...xn, so ist der durch diesen Zustand denotierte Wert der eigentlichen Variablen x dasjenige Element des Typs von x, welches die Nummer s(x1) + s(x2)*2 + s(x3)*4 + ... + s(xn)*2^n hat. Damit laesst sich eine grosse Anzahl von Problemen in boolsche Formeln und letztlich in boolsche Gleichungen umformen. Variablenmenge: Eine Teilmenge der (boolschen) Variablen. Intuition: Man kann sich eine Variablenmenge vorstellen als die Menge derjenigen Variablen, die auf "wahr" abgebildet werden. Anders ausgedrueckt: Eine Variablenmenge ist eine Ansammlung von den Aussagen, die in einer bestimmten Welt gelten. Eine Variablenmenge beschreibt also einen Weltzustand. Und das ist sie auch: Eine Variablenmenge ist ein klassischer boolscher Zustand: Jede Variablenmenge M ist strukturgleich zu demjenigen Zustand, der die Variablen aus M abbildet auf 1 und die anderen auf 0. Dies folgt unmittelbar aus der Strukturgleichheit von Teilmengen und ihren charakteristischen Funktionen: P(Var) ~=~ Var -> B Bei einer endlichen Variablenmenge ist dies noch ~=~ B^|Var| Boolsche Menge: Eine Menge von klassischen boolschen Zustaenden. Erfuellender Zustand einer boolschen Formel A: Ein Zustand s mit T A s = 1. Es handelt sich also um eine geschickte Wahl der Variablenwerte, um die Formel A zu "wahr" auszuwerten. Boolsche Menge einer boolschen Formel A: Die Menge aller erfuellenden Zustaende von A. Schreibweise: M(A) := { s in StaT | T A s = 1 } Es zeigt sich, dass die Funktion M, die jeder Formel ihre Menge der erfuellenden Zustaende zuordnet, eine Potenzmengenalgebra ueber der der Menge der Zustaende ist: M(0)={} M(1)=(Var->B) M(A & B) = M(A) /\ M(B) M(A | B) = M(A) \/ M(B) M(~A) = (Var->B) - M(A) // siehe "boolsche Funktion" fuer Strukturgleichheiten Boolsche Funktion: Eine Funktion von klassischen boolschen Zustaenden nach Boolean: f in (Var -> B) -> B BF fuer die Menge der boolschen Funktionen -- eine bestimmte Variablenmenge Var vorrausgesetzt Intuition: So eine Funktion bekommt also eine Variablenbelegung (zB x=1, y=0, z=1, also einen Weltzustand) und sagt, ob ihr diese Belegung passt (1) oder nicht (0). Eine boolsche Funktion ist strukturgleich zu * einer boolschen Menge bzw. einer Menge von klassischen boolschen Zustaenden Die boolsche Funktion laesst sich auffassen als die Menge aller "akzeptierten" Weltzustaende. Dies folgt aus der Strukturgleichheit von Teilmengen und charakteristischen Funktionen, also (Var->B)->B ~=~ P(Var->B) * einer Menge von Variablenmengen Jede Variablenmenge ansich ist strukturgleich zu einem klassischen boolschen Zustand. (Var->B)->B ~=~ P(P(Var)) Fuer n Variablen gibt es also 2^(2^n) boolsche Funktionen. * einer Menge von 0-1-Tupeln mit |Var| Elementen (bei endlicher Funktion) Jedes 0-1-Tupel ansich ist strukturgleich zu einem klassischen boolschen Zustand: (Var->B)->B ~=~ P(|B|^|Var|) * einer endlichen Menge von beliebigen Objekten (bei endlicher Funktion) Sobald man diese beliebigen Objekte naemlich binaer codiert, d.h. durch 0 und 1 ausdrueckt, handelt es sich um eine Menge von 0-1-Tupeln. Jedes 0-1-Tupel ansich ist strukturgleich zu einem klassischen boolschen Zustand: (Var->B)->B ~=~ P(M) fuer eine binaer codierbare Menge M * einem Primbaum (bei endlicher Funktion) Jeder Primbaum hat eine eindeutige boolsche Menge erfuellender Zustaende. Haetten zwei Primbaume dieselbe boolsche Menge erfuellender Zustaende, so waeren diese zwei Primbaeume aequivalent -- und damit wegen der Kanonizitaet syntaktisch gleich. Boolsche Mengen sind strukturgleich zu boolschen Funktionen, also (Var->B)->B ~=~ PT Eine aequivalente boolsche Funktion laesst sich berechnen fuer * eine boolschen Formel Von einer boolschen Formel kann man den Primbaum berechnen und dieser ist strukturgleich zu einer boolschen Funktion. Alternativ: Die klassische boolsche Algebra T berechnet fuer eine Formel exakt diejenige boolsche Funktion, die strukturgleich zur Menge der erfuellenden Zustaende ist: T A ~=~ { s in StaT | T A s = 1 } Umgekehrt kann man zu einer boolschen Menge eine nicht eindeutig bestimmte aequivalente Formel berechnen, z.B. so: formel({}) := 0 formel({s1,s2,...}) := (KONJUNKTION {x | s1(x)=1}) | formel({s2,s3,...}) Um allgemein einen Term auszuwerten, werden Denotation und Zustand benoetigt. In diesem Teil der Zusammenfassung und Vorlesung haben wir die Denotation bereits festgelegt, naemlich die klassische boolsche Algebra T. Damit ist der Wert einer Formel nur noch abhaengig vom Zustand, dh. der Wert der Formel ist eine Funktion von einem Zustand auf einen Wert -- also eine boolsche Funktion. * eine boolschen Gleichung Jede boolsche Gleichung laesst sich normalisieren zu einer Gleichung der Form A=1. Diese Gleichung ist genau gueltig in Zustaenden, die A erfuellen, dh. die Loesungsmenge von A=1 ist genau die Menge der erfuellenden Zustaende von A. Diese Menge ist strukturgleich zu einer boolschen Funktion. Umgekehrt laesst sich zu einer boolschen Funktion eine nicht eindeutig bestimmte aequivalente Gleichung finden: Man berechnet die aequivalente Formel A zu der boolschen Funktion und setzt dann A=1. * ein boolsches Gleichungssystem Ein boolsches Gleichungssystem laesst sich normieren zu einer einzelnen aequivalenten boolschen Gleichung der Form A=1. Zu dieser Gleichung laesst sich eine boolsche Funktion finden. Umgekehrt kann man zu einer boolschen Funktion ein boolsches Gleichungssystem finden: Man berechnet die Gleichung zu der boolschen Funktion und diese ist ja dann ein (kleines) Gleichungssystem. Um auf boolschen Funktionen, boolschen Mengen, Mengen von boolschen Zustaenden, Mengen von 0-1-Tupeln oder Mengen von binaer codierbaren Objekten, kann man also ebenso auf Primbaeumen rechnen. Boolsche Formeln und boolsche Gleichungssysteme lassen sich ebenso in Primbaeume uebersetzen. Bildchen: Gleichungssystem ~~normalisieren~~> Gleichung | ~~normalisieren~> | Formel | | ~~~primbaum~~~~> | | | Primbaum | | | ~=~ | | '~~erfZustaende~~> Boolsche '~~~~~~~~~~~~~~~~~'~~Loesungsmenge~~~~~~~~~~~~~~~~~> Menge ~=~ Boolsche Funktion ~=~ Menge aus: __________________________________________________________________ / \ | binaer codiertes ~=~ 0-1-Tupel ~=~ Variablen- ~=~ boolscher | | Objekt Menge Zustand | \__________________________________________________________________/ Da sich also boolsche Formeln und boolsche Gleichungen ineinander umwandeln lassen, uebertragen sich die folgenden Begriffe von boolschen Gleichungen auf boolsche Formeln: * Universell gueltige boolsche Formel * Unerfuellbare boolsche Formel * Erfuellbare boolsche Formel Beispiel: // Uebungsblatt #6.1 Bei 5 Variablen gibt es 2^5=32 moegliche Zustaende Var->B. Da boolsche Mengen Teilmengen aller boolschen Zustaende sind, gibt es |P(Var-B)| = 2^(2^5) = 4294967296 verschiedene Boolsche Mengen. Wegen der Strukturgleichheit gibt es ebensoviele boolsche Funktionen und Primbaeume (eine bestimmte Variablenordnung vorausgesetzt). // Uebungsblatt #6.3e Wegen der Aequivalenz von Gleichungssystemen und Formeln kann man Primbaeume auch fuer Gleichungssysteme angeben: { x= x<=>y, x=~y } x 0=0<=>y / \ 1=1<=>y 1=y / \ 0=y --> unerfuellbar y 0 / \ 0 1 // Uebungsblatt #6.8 Primbaeume koennen zur Bestimmung der Loesungsmenge von Gleichungssystemen verwendet werden. Sei gegeben: r => k & m ~r => ~k & c ~k => ~c Vereinfachung zu (~r|k) & (~r|m) & (r|~k) & (r|c) & (k|~c) Die Vereinfachung der Formel kommt der Primbaumentwicklung entgegen, kostet aber natuerlich Aufwand. Man kann die Formeln soweit vereinfachen, wie es leicht von der Hand geht und danach den Primbaum anfangen. Primbaum fuer cB). All x in Var.s(x)=1 Diese Funktion akzeptiert nur diejenigen Zustaende, die fuer alle Variablen 1 liefern. Da Formeln aber endlich sind, kann keine Formel sicherstellen, dass alle unendlich vielen Variablen 1 sind. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Klauselformen ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Klausel: Eine endliche Menge von boolschen Formeln. Schreibweise: Cla fuer die Menge aller Klauseln (alles Cla?) Klauseln werden meist mit C bezeichnet. Beispiel: { ~x|y, z&z&x } Bisher ist noch nicht gesagt, wie solch eine Klausel zu verstehen ist, wir haben noch keine Interpretation fuer Klauseln. // Anderswo bezeichnet eine Klausel eine Menge von *Literalen*. // Diese Unterscheidung wird spaeter eingefuehrt. Klauselform: Eine endliche Menge von Klauseln. Schreibweise: CF fuer die Menge aller Klauselformen Klauselformen werden meist mit S bezeichnet. Beispiel: { { ~x|y, z&z&x }, {x,z|(y&z)} , {~y,x|~y&z} } Disjunktive Klauselform: Eine Disjunktion von Konjunktionen. Diese Zusammenfassung benutzt die Funktion Dis in CF -> For Dis({}) := 0 Dis({{}}) := 1 Dis(S) := DISJUNKTION { KONJUNKTION C | C in S } Beispiel: Dis {{x,y},{z}} = x&y | z Schreibweise: Dis S Im Skript: Ein fett gedrucktes "D" Anmerkung: Diese Zusammenfassung weicht hier vom Skript ab! Fuer das Skript ist "Dis" eine Art Etikett fuer die Klauselform, welches angibt, dass die Klauselform disjunktiv zu verstehen ist. Dis S ist im Skript eine Menge von Klauseln, mit dem Stempel "disjunktiv" Dis S ist in dieser Zusammenfassung bereits eine Formel Grund fuer die Abweichung ist, dass das Konzept einer "Funktion" leichter verstaendlich ist das das eines "Etiketts". Darueber hinaus ergeben sich alle Definitionen des Skripts wie von selbst, wenn man "Dis" als Funktion sieht. Beispiel: Die Menge der Formeln wird im Skript um "Dis S" erweitert. Das ist hier nicht noetig, da "Dis S" bereits eine Formel ist. Die Denotation von "Dis S" muss auch nicht definiert werden. Als kleiner Nachteil ist eine disjunktive Klauselform nicht mehr eine Klauselform im strengen Sinne (der Modifikator "disjunktiv" ist exozentrisch. S.a. "Introduction to Linguistics", (lingu.txt, auf Englisch)). Jetzt ist auch die Mengenschreibweise nuetzlich: In einer Menge gibt es keine zwei gleichen Elemente (die wuerden auch nichts bringen, da sowohl & als auch | idempotent sind) und die Anordnung spielt keine Rolle (sowohl & als auch | sind kommutativ und assoziativ). Die Mengenschreibweise abstrahiert daher genau ueber diejenigen Informationen, die irrelevant sind. Interessanterweise wird das Prinzip der Klauseln hier allgemeiner eingefuehrt als es gemeinhin ueblich ist: Klauseln koennen disjunktiv oder konjunktiv verstanden werden. Anmerkung: Die in der Vorlesung benutzte Schreibweise D S s = (Ex C in S. All A in C. T A s) vermischt meines Erachtens nach Syntax und Semantik: Auf syntaktischer Ebene (also in ETT) ist der Existenzquantor eine Funktion Ex in (T->B)->B. Die obige Gleichung hingegen ist auf semantischer (normaler, mathematischer) Ebene. Auf dieser Ebene ist der Existenzquantor ein "Quantor" und kann nicht einfach mit irgendetwas gleich gesetzt werden. Wie bisher schon einige Male werden also hier die Errungenschaften der ETT auf semantische Ebene transferiert -- was die Unterscheidung Syntax/Semantik wieder etwas in Frage stellt. Konjunktive Klauselform: Eine Konjunktion von Disjunktionen. Diese Zusammenfassung benutzt die Funktion Kon in CF -> For Kon({}) := 1 Kon({{}}) := 0 Kon(S) := KONJUNKTION { DISJUNKTION C | C in S } Beispiel: Kon {{x,y},{z}} = (x|y) & z Schreibweise: Kon S Im Skript: Ein fett gedrucktes "K" Anmerkung: Siehe Anmerkung bei "Disjunktive Klauselform". // Uebungsblat #7.7 Man kann zwei Klauselformen angeben, die in ihrer konjunktiven Denotation uebereinstimmen, nicht aber in ihrer disjunktiven: S1={{0}} S2={{}} Es ist Kon(S1) == Kon(S2) == 0 aber Dis(S1) == 0 und Dis(S2)==1 Duales einer disjunktiven Klauselform Dis S: Die konjunktive Klauselform dual(Dis S) := Kon {{dual(A) | A in C} | C in S} Beispiel: dual(Dis {{x&y,z},{x}}) = Kon {{x|y,z},{x}} Duales einer konjunktiven Klauselform Kon S: Die disjunktive Klauselform dual(Kon S) := Dis {{dual(A) | A in C} | C in S} Aequivalente Klauselformen: Zwei Klauselformen, deren disjunktive Klauselformen aequivalent sind und deren konjunktive Klauselformen auch aequivalent sind. S == S' := Kon S == Kon S' und Dis S == Dis S' Klauselerweiterungssatz: Die Tatsache, dass wenn zwei Klauselformen S und S' aequivalent sind und wenn C in S, dann gilt: S' == (S' \/ {C}) Beweis: Zu zeigen: Kon S' == Kon (S'\/{C}) und Dis S' == Dis (S'\/{C}) Fuer die konjunktive Klauselform: Sei Kon S = C1 & C2 & ... & C Sei Kon S' = C1' & C2' & ... Aus S == S' folgt: Kon S == Kon S' Wir betrachten (Kon S') & C == (Kon S) & C wegen Aequivalenz == Kon S wegen Idempotenz == Kon S' nach Vorraussetzung Fuer die disjunktive Klauselform: analog Es folgt: S' == (S' \/ {C}) Redundante Klausel: Eine Klausel, die man loeschen kann, ohne die Denotation der Klauselform zu veraendern. Das Loeschen redundanter Klauseln ist also eine Aequivalenztransformation. Triviale Klausel: Eine Klausel C, in der es eine Formel A gibt, sodass auch ~A in C ist Beispiel: { x&z, z&~y|z, ~(x&z), ~z } Eine triviale Klausel ist redundant. Beweis: Folgt aus Komplemenz-Axiom. In der Disjunktiven Klauselform: Die triviale Klausel entspricht x & ... & ~x und ist falsch. Damit kann sie in der Disjunktion der Klauseln weggelassen werden. In der Konjunktiven Klauselform: Die triviale Klausel entspricht x | ... | ~x und ist wahr. Damit ist sie in der Konjunktion der Klauseln unnuetz und kann weggelassen werden. Von einer Klausel C subsumierte Klausel: Eine Klausel, die eine Obermenge von C ist. Diese Klausel ist dann redundant. Beweis: Folgt aus dem Absorptions-Axiom. Beispiel: {x} subsumiert {x,y} Intuition: Die kleinere Klauselform ist "maechtiger" als die grosse. Ihre Aussage ist staerker und impliziert die Aussage der groesseren Klauselform. Bereinigte Klauselform. Eine Klauselform, die weder triviale noch subsumierte Klauseln enthaelt. Literal: Eine boolsche Formel nach der folgenden Grammatik: LITERAL --> VARIABLE | ~ VARIABLE Es handelt sich also um eine Variable, eventuell negiert. Beispiele: x, ~z, y Komplementaere Literale: Zwei Literale, bei denen das eine die Negation des anderen ist. Beispiel: x und ~x Schreibweise: -L fuer das komplementaere Literal des Literals L Literale Klausel: Eine Klausel, die nur aus Literalen besteht. Literale Klauselform: Eine Klauselform, die nur aus literalen Klauseln besteht. Das Duale einer konjunktiven literalen Klauselform ist die entsprechende disjunktive Klauselform und umgekehrt: dual(Dis S) = Kon S dual(Kon S) = Dis S Beweis: Folgt aus der Definition der Dualitaet fuer Klauselformen, da fuer Literale L gilt: dual(L)=L. Fuer literale Klauselformen S und S' ist Kon S == Kon S' genau dann wenn Dis S == Dis S' Beweis: Wenn Kon S == Kon S', dann dual(Kon S) == dual(Kon s') und damit Dis S == Dis S'. Triviale Klauselform: Eine Klauselform S, die nur aus trivialen Klauseln besteht. Dann gilt: Kon S == 1 Dis S == 0 Beweis: Die trivialen Klauseln sind in Kon S alle aequivalent zu 1 und in Dis S alle aequivalent zu 0. Normale Klauselform, Normalenform: Eine literale Klauselform ohne triviale Klauseln. Beispiel: { {x, ~z, y}, {~x}, {z, y} } Intuition: Also die "normalen" Klauseln, mit denen man anderswo Resolution betreibt. Schreibweise: NF fuer die Menge aller Normalenformen Normalenform einer Formel A: Eine Normalenform, deren Denotation gleich der Denotation von A ist: S ist disjunktive Normalenform von A := Dis S == A S ist konjunktive Normalenform von A := Kon S == A Es handelt sich also um eine in diesem Sinne aequivalente Normalenform zu einer Formel. Es gibt zu derselben Formel mehrere aequivalente Normalenformen: Formel A = x Normalenform S1 = {{x}} Normalenform S2 = {{x},{x,y}} Umgekehrt kann man zu jeder Normalenform mehrere aequivalente Formeln angeben: Normalenform S = {{x}} Formel A1 = x Formel A2 = x & x S ist eine disjunktive Normalenform fuer eine Formel A genau dann wenn S eine konjunktive Normalenform ist fuer dual(A). Beweis: Folgt aus der Dualitaetsbeziehung zwischen Klauselformen. Disjunktive Normalenform eines geordneten Entscheidungsbaumes: Die mit der folgenden Funktion erhaltene Normalenform: dnf in ODT -> NF dnf 0 := {} dnf 1 := {{}} dnf (x, A, B) := { C \/ {~x} | C in dnf(A) } \/ { C \/ {x} | C in dnf(B) } Diese Funktion bildet also einen geordneten Entscheidungsbaum ab auf eine aequivalente disjunktive Normalenform. Die trivialen Entscheidungsbaeume werden auf ihre trivialen aequivalenten Klauselformen gemappt. Auch im komplexen Fall bleibt die Aequivalenz erhalten: Dis ({ C \/ {~x} | C in dnf(A) } \/ { C \/ {x} | C in dnf(B) }) Sei dnf(A)={{A11,...},{A21,...},...} und dnf(B)={{B11,...},{B21,...},...} == Dis { { x,A11,...}, { x, A21,...}, ..., {~x,B11,...}, {~x, B21,...} } == ( x&A11&... | x&A21&... ... | ~x&B11&... | ~x&B21&... ... ) == (x & (A11|A21|...) | ~x & (B11|B21|...) ) == (x & A | ~x & B ) == (x,A,B) Das Resultat ist in jedem Fall eine bereinigte disjunktive Normalenform, in der nur Variablen aus dem Entscheidungsbaum vorkommen, denn * es werden niemals Variablen hinzugefuegt * das Resultat ist eine Normalenform * alle Klauseln sind literal. Die Blaetter werden zu: dnf(x,0,1) = {} \/ da dnf(0) leer ist {{} \/ {x}} = {{x}} * keine Klausel ist trivial. Da es sich um einen geordneten Entscheidungsbaum handelt, taucht jede Variable waehrend des Rekursionsabstiegs nur ein Mal auf. Es koennen also nicht x und ~x in derselben Klausel landen. * das Resultat ist bereinigt, d.h. enthaelt keine subsumierten Klauseln. Beweis: Durch Descente Infinie (Widerspruch ueber Induktion) (s.a. "Human-oriented Theorem Proving" (htp.txt,auf Englisch)) Seien D1 und D2 in dnf(A) und D1 < D2 Wir betrachten die oberste Variable x: A = (x,A0,A1). dnf (x, A0, A1) := { C \/ {~x} | C in dnf(A0) } \/ { C \/ { x} | C in dnf(A1) } Da D1 eine Teilmenge von D2 ist, kann nicht D1 in der ersten Menge sein und D2 in der zweiten: Dann haette D1 ja ein ~x, was D2 fehlt. Auch kann analog nicht D1 in der zweiten Menge sein und D2 in der ersten, sie muessen beide in derselben Menge sein. Seien also zB D1 und D2 in der ersten Menge, dann enthalten sie beide ein ~x. Aus der Teilmengen-Beziehung D1 < D2 folgt dann: D1 - {~x} < D2 - {~x} Nun ist aber D1-{~x} in dnf(A0) und D2-{~x} in dnf(A0). Es gibt also zwei sich subsumierende Klauseln in dnf(A0). Also kann man zu jedem Entscheidungsbaum, der zu einer nicht bereinigten Klauselform fuehrt, einen kleineren Entscheidungsbaum angeben (A0), der zu einer nicht bereinigten Klauselform fuehrt. Das ist aber unmoeglich, da es eine Untergrenze fuer die Groesse von Entscheidungsbaeumen gibt. Also gibt es keinen Entscheidungsbaum, der zu einer nicht bereinigten Klauselform fuehrt. Ein Entscheidungsbaum laesst sich auch graphisch recht gut in eine disjunktive Normalenform ueberfuehren: Man betrachtet alle Pfade von der Wurzel zu einer "1" und sammelt die anliegenden Variablen zu je einer Klausel pro Pfad auf. Wenn man einen linken Ast waehlt, negiert man die Variable. Beispiel: x / \ 1 y { {~x}, {x,~y} } / \ 1 0 Intuition: Es gibt 2 Moeglichkeiten, diesen Baum "wahr" zu machen: entweder x ist falsch oder x ist wahr und y ist falsch. Genau diesen Satz uebersetzt man in die disjunktive Normalenform. Algorithmus: Eine Folge von Anweisungen. Divergierender, Nicht-terminierender Algorithmus: Ein Algorithmus, bei dem man bei ordnungsgemaesser Ausfuehrung nie das Ende erreicht. Beispiel: * Heb die Hand <-- hier ist der Algorithmus zu Ende terminiert * Waehrend Liechtenstein nicht Fussball-Weltmeister ist heb die Hand terminiert vermutlich nicht (es sei denn Rehhagel wechselt) Konjunktive Normalenform eines geordneten Entscheidungsbaumes: (?) Ein Entscheidungsbaum laesst sich auch graphisch recht gut in eine konjunktive Normalenform ueberfuehren: Man betrachtet alle Pfade von der Wurzel zu einer "0" und sammelt die anliegenden Variablen zu je einer Klausel pro Pfad auf. Wenn man einen rechten Ast waehlt, negiert man die Variable. Beispiel: x / \ 1 y { {~x,~y} } / \ 1 0 Intuition: Es gibt 1 Moeglichkeit, diesen Baum "falsch" zu machen: x und y muessen beide "wahr" sein: x & y. Nun wollen wir aber nicht, dass der Baum falsch wird, also bilden wir ~(x&y)=~x|~y: Das ist genau der Ausdruck, der mit obigem Algorithmus entsteht. Invertiertes eines klassischen boolschen Zustandes s: Ein klassischer boolscher Zustand, der ueberall da 1 liefert, wo s 0 liefert und andersrum: s' x := 1 - (s x) In der Interpretation des Zustandes als Variablenmenge handelt es sich genau um das Komplement. Beispiel: Seien die Variablen x und y. Sei der Zustand s mit s(x)=0, s(y)=1 bzw. als Menge: {y} Dann ist das Invertierte: s'(x)=1, s'(y)=0 bzw. als Menge: {x} Duales einer boolschen Funktion: Die durch die folgende Funktion erhaltene boolsche Funktion: dual in ((Var -> B)->B) -> ((Var -> B)->B) dual(f s) := 1-(f s') wobei s' der invertierte Zustand des Zustandes s ist. Beispiel: Variablen: x und y Zustaende: s1: s1(x)=0, s1(y)=0 {} s1': {x,y} s2: s2(x)=0, s2(y)=1 {y} s2': {x} s3: s3(x)=1, s3(y)=0 {x} s3': {y} s4: s4(x)=1, s4(y)=1 {x,y} s4': {} Boolsche Funktion: Als Funktion: f(s1)=0, f(s2)=0, f(s3)=0, f(s4)=1 Als Menge: { {x,y} } Als Formel: x & y Duales: Als Funktion: f(s1)=0, f(s2)=1, f(s3)=1, f(s4)=1 Als Menge: { {x}, {y} , {x,y} } Als Formel: x | y Intuition: Damit verhaelt sich die Dualisierung von boolschen Funktionen voellig konform mit der Dualisierung von Formeln und Primbaeumen. Eigenschaften: Es gilt fuer eine Formel A und eine Denotation D: D dual(A) = dual(D A) --- '----> D angewandt auf A ist die boolsche Funktion: Sie bewertet Zustaende Ist S eine literale Klauselform, so gilt S ist Disjunktive Normalenform fuer dual(A) genau dann wenn S ist Konjunktive Normalenform fuer A Resolvierbare Klauseln: Zwei literale Klauseln, die zwei komplementaeren Literale enthalten -- also dieselbe Variable, in der einen Klausel negiert und in der anderen nicht negiert. Beispiel: {x,~z} und {z,~y,x} Resolvente zweier resolvierbarer Klauseln: Ihre Vereinigung, unter Ausschluss der komplementaeren Literale. Beispiel: {x,~z} und {z,~y,x} ergeben {x,~y} Das Hinzufuegen einer Resolvente ist eine Aequivalenztransformation. Beweis: Folgt aus Resolutionsaxiom Echte Resolvente in einer Klauselform: Eine Resolvente zweier resolvierbarer Klauseln der Klauselform, die weder trivial ist noch subsumiert. Saturierte Klauselform: Eine Klauselform, die keine echte Resolvente hat. Primform: Eine saturierte bereinigte Normalenform. Das heisst: * keine trivialen Klauseln * keine subsumierten Klauseln * alle ableitbaren echte Resolventen sind drin Jede Klauselform kann in eine aequivalente Primform ueberfuehrt werden, indem wiederholt subsumierte und triviale Klauseln geloescht werden und echte Resolventen hinzugefuegt werden. Schreibweise: Diesen Prozess veranschaulicht man meist graphisch. Beispiel: {{~x,~y}, {x,~y,z}, {x,y} } \ \_________/ \ {x,z} Resolution \ ////// / Subsumierte Klausel loeschen \______________/ {~y,z} Resolution {{~x,~y}, {~y,z}, {x,z}, {x,y} } Ergebnis Beispiel: // Uebungsblatt #7.1 Sei die Formel A=(z=>(x|y)) & (x | y | z) & (y=>x) & (x=>0) Sie ist aequivalent zu 0, A=0 * Ihr Primbaum ist primbaum(A)=0 * Ihre konjunktive Primform ist {{}} * Ihre disjunktive Primform ist {} * Die konjunktive Primform von dual(A) ist die disjunktive Primform von A, also {} * Die disjunktive Primform von dual(A) ist die konjunktive Primform von A, also {{}} * Die signifikanten Variablen von A sind gar keine Jede Klausel in einer Klauselform S wird von einer Klausel in der Primform von S subsumiert. Beweis: Es gibt 2 Faelle: * Die Klausel ueberlebt den Prozess und landet in der Primform. Dann wird sie trivialerweise von einer Klausel in der Primform subsumiert (naemlich von sich selbst) * Die Klausel ueberlebt den Prozess nicht. Dann wurde sie nur deshalb rausgeschmissen, weil eine andere Klausel sie subsumiert. Beispiel: // Uebungsblatt #7.3 Sei x=1 Die Formel A ist also aequivalent zur Formel x|y|z. * Disjunktive Primform: {{x},{y},{z}} * Konjunktive Primform: {{x,y,z}} * Primbaum: x / \ y 1 / \ z 1 / \ 0 1 * Konjunktive Primform fuer dual(A): Die disjunktive Primform von A, also {{x},{y},{z}} // Uebungsblatt #7.4 Man kann Formeln angeben, deren disjunktive und konjunktive Primform uebereinstimmen, z.B. die Formel "x". Sowohl disjunktive als auch konjunktive Primform sind {{x}}. Beispiel: // Uebungsblatt #7.6 Sei A die Formel ~( (x|z) => x&~y&z) => x&y&z Es ist am kluegsten, zunaechst den Primbaum zu berechnen: x / \ z z / \ / \ 1 0 0 1 * Die disjunktive Primform von A kann an den Pfaden von der Wurzel zu den 1-Blaettern abgelesen werden: {{~x,~z},{x,z}} * Die konjunktive Primform von A kann an den Pfaden von der Wurzel zu den 0-Blaettern abgelesen werden, dabei invertieren: {{x,~z},{~x,z}} * Die konjunktive Primform von dual(A) ist die disjunktive Primform von A: {{~x,~z},{x,z}} * Die konjunktive Primform von ~A ergibt sich durch Umdrehen der Blaetter und Ablesen der Pfade zu den 0-Blaettern: {{~x,~z},{x,z}} * Die disjunktive Klauselform zu x|A ergibt sich durch Hinzufuegen von "{x}" zur disjunktiven Primform von A: {{~x,~z},{x,z},{x}} Diese muss jetzt noch geprimt werden und es ergibt sich: {{x},{~z}} * Die signifikanten Variablen von z|A sind die signifikanten Variablen von A (naemlich x und z) und z, also SV(z|A)={x,z}\/{z}={x,z} * Die signifikanten Variablen von y|A sind die signifikanten Variablen von A (naemlich x und z) und y, also SV(y|A)={x,z}\/{y}={x,y,z} Saturationslemma: Die Tatsache das die Berechnung der Primform terminiert. Beweis: Es gibt bei endlicher Variablenmenge nur endlich viele moegliche Klauseln. Fuer Nicht-Terminierung muesste also eine Klausel geloescht werden und spaeter wieder erzeugt werden. Eine Klausel C wird geloescht, wenn sie von einer Klausel D subsumiert wird. C < D ~~~> C fliegt raus Dann wird C aber nie wieder erzeugt, denn es werden ja nur echte (nicht subsumierte) Resolventen hinzugefuegt. D wuerde jeglichen Versuch, C erneut hinzuzufuegen, verhindern. Sollte D einmal geloescht werden, so nur aufgrund einer Klausel E, die D subsumiert. D < E ~~~~> D fliegt raus Wegen der Transitivitaet der Teilmengenbeziehung subsumiert E dann aber auch C -- und uebernimmt so den Job von D, die Einfuehrung von C zu verhindern C < D < E ~~~~~> E verhindert Einfuehrung von C und D // Diese Zusammenfassung folgt der Vorlesung und nicht dem Skript // fuer den Beweis der Eindeutigkeit von Primformen x-lose Klauselform einer Klauselform S: Diejenige Klauselform, die aus S entsteht, wenn man * Alle Klauseln loescht, in denen ~x vorkommt * Aus den verbleibenden Klauseln x loescht Schreibweise: S/x := {C - {~x} | C in S, x nicht in S } In der Vorlesung S mit Index x Beispiel: {{x,y},{~x,z},{c}} / x = {{y}, {c}} Intuition: Die x-lose Klauselform ist all das, was in der disjunktiven Denotation uebrigbleibt, wenn x wahr ist. Die x-lose disjunktive Klauselform ist aequivalent zur Ausgangsklauselform, wenn x zu 1 auswertet: T (Dis S/x) s = T (Dis S) s falls s(x)=1. Beweis: Sei s ein Zustand mit s(x)=1 Sei die Klauselform S={{A11,A12,...}{A21,...},...} Dann gilt: Dis S = (A11&A12&... | A21&A22&... | ... ) Sei ein beliebiges Aij=~x. Dann gilt: Dis S == (A11&A12&... | A21& ~x &... | ... ) == (A11&A12&... | A21& 0 &... | ... ) == (A11&A12&... | | ... ) Die Klausel fliegt also komplett raus. Sei ein beliebiges Aij=x Dis S == (A11&A12&... | A21& x &... | ... ) == (A11&A12&... | A21& 1 &... | ... ) == (A11&A12&... | A21 &... | ... ) In diesen Klauseln kann man x also weglassen. Also Dis S == Dis S/x Ist S eine Normalenform, so ist auch S/x eine Normalenform, da durch Wegnehmen einer Variable kein komplementaeres Paar entstehen kann. Ist S saturiert, so ist auch S/x saturiert, denn gaebe es eine echte Resolvente, so waere dies (um x erweitert) auch eine echte Resolvente in S. ~x-lose Klauselform einer Klauselform S: Diejenige Klauselform, die aus S entsteht, wenn man * Alle Klauseln loescht, in denen x vorkommt * Aus den verbleibenden Klauseln ~x loescht Schreibweise: S/x := {C - {x} | C in S, ~x nicht in S } In der Vorlesung S mit Index ~x Beispiel: {{x,y},{~x,z},{c}} / ~x = {{z}, {c}} Intuition: Die x-lose Klauselform ist all das, was in der disjunktiven Denotation uebrigbleibt, wenn x falsch ist. Die ~x-lose disjunktive Klauselform ist aequivalent zur Ausgangsklauselform, wenn x zu 0 auswertet. Beweis: Analog zu x-lose Klauselform. Ist S eine Normalenform, so ist auch S/~x eine Normalenform. Ist S saturiert, so ist auch S/~x saturiert. Ist S eine Primform und kommt eine Variable x in S vor, so ist S/x != S/~x. Beweis: durch Widerspruch Sei S/x = {C1,C2,...} Sei S/~x = {D1,D2,...} Sei S/x = S/~x, d.h. C1=D1, C2=D2, ... Dann ist S = { C1\/{x},C2\/{x},..., D1\/{~x}, ..., E1, E2, ...} wobei E1, E2,... die Klauseln ohne x und ohne ~x sein sollen Wir berechnen probeweise eine Resolvente (mit C1=D1): S = { C1\/{x},C2\/{x},..., D1\/{~x}, ..., E1, E2, ...} \__________________/ C1 Da S ja prim ist, muss C1 als echte Resolvente in S sein. Da aber C1 < C1\/{x}, waere die Klausel C1\/{x} von C1 subsumiert -- und das gibt es nicht in einer Primform. Also darf es keine zwei gleichen Klauseln in S/x und S/~x geben. Subsumptionslose Klauselform einer Klauselform S: Die Klauselform S, aus der alle subsumierten Klauseln entfernt wurden. Schreibweise: sub-(S) := { C in S | Es existiert kein D in S mit D < C } Wegen der Redundanz von subsumierten Klauseln ist dies eine Aequivalenztransformation. Ist S eine Primform, so ist sub-(S/x) != sub-(S/~x) Beweis: S/x und S/~x enthalten keine gleichen Klauseln. Also kann auch durch Wegstreichen von Klauseln keine Gleichheit erreicht werden. Primbaum einer disjunktiven Primform: Der mit der folgenden Funktion konstruierte Primbaum: pf2pt({}) := 0 pf2pt({{}}) := 1 pf2pt(S) := (x, pf2pt(sub-(S/~x)), pf2pt(sub-(S/x))) falls x die kleinste Variable in S ist Beispiel: pf2pt({{x,y},{~x,~y}}) = (x,pf2pt(sub-({{x,y},{~x,~y}}/~x)), pf2pt(sub-({{x,y},{~x,~y}}/x))) = (x,pf2pt(sub-({{~y}})), pf2pt(sub-({{ y}}))) = (x,pf2pt( {{~y}} ), pf2pt( {{ y}} )) = (x,(y,pf2pt(sub-({{~y}}/~y)), pf2pt(sub-({{~y}}/ y))), (y,pf2pt(sub-({{ y}}/~y)), pf2pt(sub-({{ y}}/ y)))) = (x,(y,pf2pt(sub-({{}})), pf2pt(sub-( {} ))), (y,pf2pt(sub-( {} )), pf2pt(sub-({{}})))) = (x,(y,1,0),(y,0,1)) x / \ y y / \ / \ 1 0 0 1 Damit laesst sich jede Primform umwandeln in einen Primbaum. Das Resultat ist aequivalent. Beweis: Zu zeigen: Dis S == pf2pt(S) fuer alle S Beweis durch strukturelle Induktion ueber pf2pt(S): * pf2pt(S)=0 Dann muss gelten S={}. Da Dis {} == 0 sind die Denotationen gleich. * pf2pt(S)=1 Analog * pf2pt(S)=(x,A,B) Dann ist A=pf2pt(sub-(S/~x)) B=pf2pt(sub-(S/ x)) Da sub- eine Aequivalenztransformation ist, gilt per Induktionsannahme: A == Dis S/~x B == Dis S/x Es gibt 2 Faelle: * s(x)=1 Wegen der semantischen Eigenschaften des Konditionals gilt: T (x,A,B) s = T A s Per obiger Beobachtung gilt: T A s = T (Dis S/~x) s Durch die Eigenschaft von S/~x gilt: = T (DisDen S) s Damit gilt: T (x,A,B) s = T (Dis S) s * s(x)=0 Analog. Das Resultat ist ein Primbaum. Beweis: * Das Resultat ist offensichtlich ein Enscheidungsbaum. * Der Entscheidungsbaum ist geordnet, denn pf2pt zieht immer die kleinste Variable zuerst aus der Menge. Da niemals Variablen hinzu gefuegt werden, folgt die Geordnetheit des Entscheidungsbaumes. * Der Entscheidungsbaum ist reduziert. Beweis: Zu zeigen: pf2pt(sub-(S/~x)) != pf2pt(sub-(S/~x)). Durch Kanonizitaet von Primbaeumen reicht es, zu zeigen: T pf2pt(sub-(S/~x)) != T pf2pt(sub-(S/~x)) Wegen Aequivalenz reicht es, zu zeigen: T (Dis S/~x) != T (Dis S/x) Dies ist gegeben, da S Primform ist und S/~x und S/x somit keine gleiche Klausel enthalten. Zwei verschiedene Primformen werden auf zwei verschiedene Primbaeume abgebildet. Beweis: (?) Es folgt, dass Primbaeume und Primformen strukturgleich sind. Damit sind Primformen strukturgleich zu Boolschen Funktionen und die Menge der Primformen ist kanonisch. Beispiel: // Uebungsblatt #7.2 * Wieviele Funktion f in PT->PF gibt es, sodass fuer alle Primbaeume A gilt A == Kon(f A) ? Anmerkung: Das Uebungsblatt produziert hier einen Typfehler: Im Skript ist das kalligraphische K eine Funktion, die zu einer Klauselform die konjunktive Denotation liefert. Das "==" aber darf nur zwischen Formeln stehen und nicht zwischen Formeln und Denotationen. Richtig waere das fette K an dieser Stelle. // Marco meinte, die Notation sei in der Tat etwas "schlampig" Da sowohl Primbaeume als auch konjunktive Primformen eindeutig sind (deshalb heissen sie ja so), kann es nur eine einzige solche Funktion geben. Sie ist bijektiv, d.h. sowohl injektiv als auch surjektiv. Genau dasselbe gilt fuer eine Funktion g, wenn fuer alle Primbaeume A gelten soll A == Dis(g A). * Wieviele Funktionen f in PF->PT gibt es, sodass fuer alle Primformen S gilt dual(f S) == Dis(S) ? Auch hier kann es nur eine solche Funktion geben. Beispiel: //Uebungsblatt #7.8 * Wieviele konjunktive Primformen gibt es, in denen hoechstens die Variablen x, y und z vorkommen? Da Primformen strukturgleich zu boolschen Funktionen ist, gibt es davon genau soviele wie boolsche Funktionen, also 2^(2^3)=256. Allgemein gilt: Es gibt 2^(2^n) Primformen fuer n Variablen. * Fuer n Variablen gibt es genau 2^(2^(n-1)) Primformen S mit Kon(S)==Dis(S). Grund: Jede Primform entspricht genau einem Primbaum. Die Bedingung Kon(S)==Dis(S) uebersetzt sich fuer Primbaeume dahingehend, dass der Primbaum mit seinem eigenen Dualen uebereinstimmen muss. Wieviele solcher Primbaeume gibt es mit n Variablen? Wir spannen einen kompletten Entscheidungsbaum fuer die n Variablen auf. Sei z die letzte Variable, d.h. die Kinder von z sind Einsen und Nullen und damit die Blaetter des Baumes: _______x_______ / \ y y / \ / \ z z z z / \ / \ / \ / \ # # # # $ $ $ $ Sobald wir uns fuer Werte von z in der linken Haelfte des Baumes entschieden haben (die #), sind die rechten Werte (die $) automatisch bestimmt, wenn wir wollen, dass der Baum zu sich selbst dual ist. Bei n Variablen gibt es genau 2^(2^(n-1)) Moeglichkeiten, die # zu bestimmen. Zwar sind nicht alle diese Entscheidungsbaeume Primbaeume, aber wenn man reduziert, so geschieht dies auf beiden Seiten und die Dualitaet bleibt erhalten. Waehlt man zB alle #=0, so klappt der gesamte Baum zusammen zu (x,0,1) -- und ist immer noch zu sich selbst dual. // Diese Loesung verdanken wir Conny Resolutionssatz: Die Tatsache dass wenn S und S' aequivalente Normalformen sind und S darueber hinaus eine Primform ist, dann wird jede Klausel aus S' von einer Klausel aus S subsumiert. Beweis: Wir berechnen die Primform S'' von S'. Es gilt: S == S' == S'' und aufgrund der Kanonizitaet von Primformen: S = S''. Also ist S die Primform von S'. Es wurde bereits gezeigt, dass jede Klausel von einer Klausel der Primform subsumiert wird. Minimalform: Eine Normalenform, aus der man keine Klausel und kein Literal entfernen kann, ohne die Denotation zu aendern. Es kann mehrere aequivalente Minimalformen zu einer Normalenform geben. Eine moegliche aequivalente Minimalform zu einer Normalform kann berechnet werden, indem man die Primform berechnet und alle Resolventen rausschmeisst. Wenn eine Normalenform minimal ist, dann ist sie eine Teilmenge ihrer Primform. Beweis: Sei S eine Minimalform und sei S' die zugehoerige Primform. Sei C eine Klausel in S. Dann gibt es nach dem Resolutionssatz eine Klausel D in S', die C subsumiert. C in S D in S' C > D Wir nehmen an D!=C. Dann gilt nach dem Klauselerweiterungssatz: S == S \/ {D} Man darf also die subsumierende Klausel hinzufuegen. Nun ist C redundant, es gilt: S == (S-{C}) \/ {D} Da D echt kleiner ist als C, kann S keine Minimalform sein, Widerspruch. boolsche Funktion einer konjunktiven Klauselform Kon S: Die klassische boolsche Algebra angewandt auf die konjunktive Klauselform. Schreibweise: Skript: K S mit kalligraphischem K hier: T (Kon S) Dies ist eine boolsche Funktion: Man gibt ihr noch einen Zustand mit und erhaelt dann "wahr" oder "falsch". boolsche Funktion einer disjunktiven Klauselform Dis S: Die klassische boolsche Algebra angewandt auf die disjunktive Klauselform. Schreibweise: Skript: D S mit kalligraphischem D hier: T (Dis S) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Programme ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Signatur einer formalen Sprache: Die wie folgt erhaltene Signatur: * In jeder Regel der Form Nichtterminalsymbol --> Terminalsymbol Nichtterminalsymbol ... wird * das links stehende Nichtterminalsymbol zu einem Typ * das Terminalsymbol zu einem Funktionssymbol, welches als Eingabetypsymbole die rechts stehenden Nichtterminalsymbole hat und als Ausgabetypsymbol das links stehende Nichtterminalsymbol. Stehen rechts keine Nichtterminalsymbole, so haben wir es mit einem 0-stelligen Funktionssymbol zu tun, also einem Konstantensymbol. * Durch eine Regel der folgenden Form Nichtterminalsymbol --> Nichtterminalsymbol wird das Typsymbol des rechten Nichtterminalsymbols aus der gesamten Signatur gestrichen und durch das Typsymbol des linken Nichtterminalsymbols ersetzt. // Dadurch geht Information verloren! Daraus eventuell entstehende // Inkonsistenzen muessen nachher gepatcht werden. Schoen waere es, // wenn das rechte Nichterminalsymbol ein Untertyp des linken sein // koennte (wie die natuerlichen Zahlen von den ganzen Zahlen oder // Birnen von Fruechten). Dies geht aber in ETT nicht. * Alle anderen Regel muessen auf eine der obigen Formen gebracht werden (unter Veraenderung der formalen Sprache) Nach dieser Transformation ist die Menge der wohltypisierten, kombinatorischen und variablenlosen Ausdruecke in ETT mit dieser Signatur im Wesentlichen gleich der Menge der mit der Grammatik generierbaren Woerter. Beispiel: siehe "IMP" Lokation: Ein Symbol. Mit "Lokation" werden die Variablen von Programmiersprachen bezeichnet. Im Unterschied zu logischen Variablen haben sie nicht pro Zustand einen globalen festen Wert, sondern koennen neue Werte zugewiesen bekommen. Intuition: Man kann sich eine Lokation als einen Zettel vorstellen, auf dem eine Zahl (=der Wert der Lokation) steht. Wird ein neuer Wert zugewiesen, so wird der alte Wert gestrichen und der neue Wert stattdessen auf dem Zettel vermerkt. Beispiel: in while(i<4) printf("Never or forever"); ist "i" eine Lokation. Schreibweise: Loc fuer die Menge aller Lokationen IMP: Die von folgender Grammatik generierte formale Sprache: // Kommandos Com --> Loc := AExp | Com ; Com | if BExp then ( Com ) else Com | while BExp do ( Com ) // Arithmetische Ausdruecke AExp --> Con | Loc | AExp + AExp // Konstanten Con --> die ganzen Zahlen // Boolsche Ausdruecke BExp --> AExp =< AExp // Lokationen Loc --> irgendwelche Symbole Es handelt sich also um eine vereinfachte Programmiersprache, die Lokationen, Zuweisungen, Bedingungen und Schleifen kennt. Ein Ausdruck in dieser Sprache drueckt einen Algorithmus aus. Das Startsymbol ist "Com". Beispiele: X := 7+3 weist der Lokation X den Wert 10 zu if 4=<2 then Y:=1 else Y:=2 weist der Lokation Y den Wert 2 zu I:=7;while I=<7 do (I:=I + 1; X:=X+X) multipliziert X mit 2 "IMP" spricht sich wie der Wortanfang von "implizieren". Diese formale Sprache laesst sich wie folgt in eine Signatur umwandeln: // Typsymbole Com, AExp, BExp // Con und Loc fliegen raus // wegen AExp --> Con|Loc // Konstantensymbole z : AExp fuer alle z in Z // Con/AExp --> ganze Z. X : AExp fuer alle Lokationen X // Loc/AExp --> X1|... + : AExp -> AExp -> AExp // AExp --> AExp + AExp =<: AExp-> AExp -> BExp // BExp --> AExp =< AExp ; : Com -> Com -> Com // Com --> Com ; Com if : BExp -> Com -> Com // Com --> if BExp then // Com else Com X:= : AExp -> Com fuer alle Lok. X // Ausnahme while : BExp -> Com -> Com // wegen Com --> while BExp // Com Die wohltypisierten, kombinatorischen und variablenlosen Ausdruecke in ETT unter dieser Signatur entsprechen (im Wesentlichen) den in IMP generierbaren Woertern. Es wurden lediglich einige Konstrukte in funktionale Form gebracht: Das "do", "then" und "else" sind verschwunden (warum waren die ueberhaupt da? :-) und das Semikolon wird ein infix geschriebenes Funktionssymbol. Die Zuweisung "X:=" wurde separat behandelt. Das liegt daran, dass durch die Elimination des Typs "Loc" Information verloren gegangen ist: Zwar ist jede Loc eine AExp, aber noch lange nicht jede AExp ein Loc. Um Zuweisungen der Bauart "3:=" zu verhindern, musste gepatcht werden. Loc-Zustand: 1) Zustand einer Katze vor einem Mauseloch 2) Eine Funktion von Lokationen in die natuerlichen Zahlen. Ein Loc-Zustand uebernimmt also die Aufgabe eines (Variablen-)Zustandes fuer Lokationen. Schreibweise: Sigma := Loc -> Z wobei S das grosse griechische Sigma ist (das Summenzeichen) So ein Loc-Zustand beschreibt also den Zustand eines Programmes: Naemlich welche Variablen welche Werte haben. Wir werden ein Programm im Wesentlichen als eine Funktion auf den Loc-Zustaenden betrachten, d.h. untersuchen, welche Lokationen wie belegt werden, wenn die Lokationen mit den und den Werten initialisiert wurden. Totalisierte Funktion einer partiellen Funktion f in A-'B: Die totale Funktion f' in A->(B\/{ _|_ }), die voellig mit f uebereinstimmt und fuer die undefinierten Eingaben _|_ liefert. Beispiel: Man koennte die Division "/" totalisieren zu "/'". Dann haette man 6 /' 2 = 3 6 /' 0 = _|_ Schreibweise: _|_ ist ein umgedrehtes "T" und spricht sich "bottom" Man kann auch jedes andere Symbol, das nicht in B ist, benutzen // Damit lassen sich interessante Exception-Handlings simulieren... IMP-Programm: Ein kombinatorischer, variablenloser Lambda-Ausdruck in der Signatur der formalen Sprache IMP. IMP-Programm-Denotation: Die folgende Denotation fuer die IMP-Signatur: // Typsymbole D Com := Sigma -> Sigma \/ {_|_} Ein Programm bekommt einen Loc-Zustand und ergibt einen Loc-Zustand. Wenn es nicht terminiert, soll seine Denotation _|_ sein. D AExp := Sigma -> Z Ein arithmetischer Ausdruck bekommt einen Loc-Zustand und berechnet daraus eine ganze Zahl D BExp := Sigma -> B Ein boolscher Ausdruck bekommt einen Loc-Zustand und berechnet daraus einen boolschen Wert // Uebungsblatt #8.1 // Ausdruecke // Fuer arithmetische Ausdruecke wird die Funktion A benutzt, // weil arithmetische Ausdruecke selber keine Programme sind A X s := s(X) fuer eine Lokation X und einen Loc-Zustand (!) s Die Denotation einer Lokation ist ihr Wert A n s := n Eine Zahl wertet zu sich selbst aus A(a+b)s := (A a s) + (A b s) // Boolsche Ausdruecke // Fuer boolsche Ausdruecke wird die Funktion B benutzt B(a= A a s] Eine Zuweisung X:=a liefert einen neuen Loc-Zustand, in dem X den Wert (A a s) hat. D(p1;p2) s := (D p1 s)=_|_ ? _|_ : (D p2 (D p1 s)) Das ";" bekommt 2 Programme, d.h. in der Semantik 2 Funktionen Loc-Zustand nach Loc-Zustand. "s" ist gewissermassen der Anfangs-Loc-Zustand. Die Denotatation guckt nun, ob das erste Programm fuer s etwas sinnvolles liefert. Falls nein, kommt _|_ zurueck. Falls ja, fuettert es das zweite Programm mit dem Resultat des ersten Programms. D(if b p1 p2) s := (B b s) ? (D p1 s) : (D p2 s) Ein "if" bekommt einen boolschen Ausdruck, d.h. in der Semantik eine Funktion Loc-Zustand nach Bool, und zwei Programme, d.h. in der Semantik, d.h. in der Semantik 2 Funktionen Loc-Zustand nach Loc-Zustand. Falls der boolsche Ausdruck angewandt auf den Loc-Zustand s "wahr" ergibt, wird das erste Programm auf s losgelassen, sonst das zweite. D(while b p) s:= W(B b)(D p)(s) Wir lagern die Denotation der while-Schleife auf eine Funktion W aus. Diese basiert auf einer Funktionsschar W[n]: W in N -> (Sigma->B) -> (Sigma -> Sigma \/ {_|_}) -> Sigma -> Sigma \/ {_|_} W bekommt also einen Index, die while-Bedingung und den while-Schleifenkoerper und einen Loc-Zustand und berechnet daraus einen neuen Loc-Zustand. Mit anderen Worten: W[n](b,p) berechnet die Denotation der While-schleife: W[n](b,p) in Sigma -> Sigma \/ {_|_} W[n](b,p) wird genau dann etwas ungleich _|_ liefern, wenn n die richtige Anzahl von Schleifendurchlaeufen ist. Zunaechst 0 Durchlaeufe: W[0](b,p) s := b(s) ? _|_ else s W[0] fragt: Ist die Bedingung b unter dem gegebenen Loc-Zustand s wahr? Wenn ja, ist 0 die falsche Anzahl von Durchlaeufen (denn dann wird die Schleife ja mindestens einmal durchlaufen). Wenn b falsch ergibt, wird die Schleife 0-mal durchlaufen und das Ergebnis ist der unberuehrte Loc-Zustand s. Jetzt der Allgemeinfall: W[n+1](b,p) s := b(s) & p(s)!=_|_ ? W[n](b,p) p(s) : _|_ Die Schleife wird n+1-mal durchlaufen, genau dann wenn die Bedingung im momentanen Loc-Zustand wahr ist, der Schleifenkoerper im momentanen Loc-Zustand nicht divergiert und wenn die Schleife nach einmaligem Abarbeiten des Schleifenkoerpers (p(s)) noch n-mal durchlaufen werden kann. Der resultierende Loc-Zustand ergibt sich aus dem n-maligen Laufenlassen der Schleife auf dem Ergebnis eines Abarbeitens des Schleifenkoerpers. Die Denotation W der Schleife ist dann W(b,p) s := s' falls ein n in N existiert mit W[n](b,p) s=s' W(b,p) s := _|_ sonst Da dieser letzte Teil von der Uebungsaufgabe divergiert, hier die While-Schleife nochmal funktional: D(while b p) s:= W(B b)(D p)(s) W bedingung proggy s = if (bedingung s) then W bedingung proggy (proggy s) else s Wenn also die Bedingung in s wahr ist, rufen wir uns selbst auf, schicken aber den Zustand einmal durch den Schleifenrumpf. Wenn die Bedingung falsch ist, geben wir s unveraendert zurueck. Diese Funktion divergiert, wenn die Schleife divergiert. Die Denotation eines Programmes entspricht dem "Laufenlassen". Das, was das Programm macht, reduzieren wir dabei darauf, welche Variablen wie veraendert werden. Die Denotation eines Programmes gibt genau diese Veraenderung in Form einer Funktion ZustandVorher->ZustandNachher an. Schreibweisen: p1 ; p2 ; p3 := (p1;p2);p3 a - n := a + (-n) false := 1 =< 0 true := 0 =< 1 skip := X := X fuer irgendeine feste Lokation X a < b := a+1 =< b a >= b := b =< a a > b := b < a s |- p => s' := (D p s)=s' do p until b := p; while ~b do p Operationale Definition einer Relation R: Die operationale Definition der Menge R, d.h. ein Regelsystem S, sodass C S {}=R Da Funktionen Relationen sind und Relationen Mengen, kann man auch Funktionen und Relationen operational definieren. Operationale Definition der IMP-Programm-Denotation: Die folgenden Inferenzregeln ueber der Menge {(s,p,s') | s und s' in Sigma\/{_|_}, p in Com}: // ...in alternativer Schreibweise, dh. "s|-p=>s'" steht fuer // das Tripel (s,p,s') und die Tatsache (D p s)=s'. // vgl. "IMP-Programm-Denotation" Die durch diese Inferenzregeln definierte Relation "s|-p=>s'" stimmt mit der unter "IMP-Programm-Denotation" definierten Relation ueberein. ----------------------------- s |- X:=a => s[X->(A a s)] // Zuweisung aendert Wert s1 |- p1 => s2 s2 |- p2 => s3 ----------------------------------- s1 |- p1;p2 => s3 // Sequenz fuettert s2 an p2 (B b s) = 0 s |- p2 => s' ------------------------------------ s |- if b then p1 else p2 => s' // b falsch=>p2 (B b s) = 1 s |- p1 => s' ------------------------------------ s |- if b then p1 else p2 => s' // b wahr=>p1 (B b s) = 0 ----------------------- s |- while b do p => s // b falsch => s bleibt (B b s) = 1 s |- p => s' s' |- while b do p => s'' ------------------------------------------------------ s |- while b do p => s'' // rek. Aufdroeseln Regulaere Programmiersprache: Die von folgender Grammatik generierte formale Sprache: // Programme ('*',';','+' und '?' sind Terminalsymbole) Pro --> Loc := AExp // Zuweisung | Pro ; Pro // Sequenz | Pro + Pro // "Vereinigung" | Pro* // "Iteration" | BExp ? // "Test" // Arithmetische Ausdruecke AExp --> Con | Loc | AExp + AExp // Boolsche Ausdruecke ('~' ist Terminalsymbol) BExp --> AExp =< AExp | ~ BExp // Konstanten Con --> ganze Zahlen // Lokationen Loc --> irgendwelche Symbole Die regulaere Programmiersprache laesst sich in eine Signatur fuer ETT umwandeln. In dieser Programmiersprache werden Programme allgemein als binaere Relationen zwischen Loc-Zustaenden aufgefasst. Diese Sicht umfasst die Sicht der IMP-Sprache. Die regulaere Programmiersprache ist maechtiger als die IMP-Sprache. Welche regulaeren Programme welche Relation bedeuten, wird durch die regulaere Programm-Denotation festgelegt. Die Programmiersprache heisst "regulaer", weil sich die ausgedrueckten Relationen durch eine regulaere Sprache beschreiben liessen (s. "Computational Logic", cl.txt, auf Englisch oder "Theoretische Informatik", infod.txt). Da es sich um eine Relation statt um eine Funktion auf Loc-Zustaenden handelt, kann ein Programm auch fuer einen Eingabe-Loc-Zustand gar keinen Ausgabe-Loc-Zustand haben (es terminiert nicht) oder aber mehrere (es ist indeterministisch). Sprechweise: Das Programm weist die Lokation X zu fuer Das Programm weist der Lokation X einen Wert zu. Indeterministisches Programm: Ein Programm, welches zu einem Eingabezustand mehrere Ausgabezustaende hat. Indeterministische Programme koennen nicht in IMP geschrieben werden, wohl aber in der regulaeren Programmiersprache. Regulaeres Programm: Ein Element der Regulaeren Programmiersprache. Regulaere-Programm-Denotation: Die folgende Denotation fuer die Signatur der regulaeren Programmiersprache: // Typsymbole R Pro := Sigma * Sigma Programme sind nun binaere Relationen auf Loc-Zustaenden Das _|_ entfaellt, da nicht terminierende Programme die leere Relation sind. Relationen sind maechtiger als Funktionen, da sie fuer einen Eingabezustand mehrere Ausgabezustaende enthalten koennen (Indeterminismus). R AExp := Sigma -> Z R BExp := Sigma -> B // Ausdruecke A X s := s(X) fuer eine Lokation X und einen Loc-Zustand s A n s := n fuer eine Konstante n A(a+b)s := (A a s) + (A b s) B(a= (A a s)] R(p1;p2) := R(p1)(s) o R(p2)(s) Das Hintereinanderhaengen zweier Programme entspricht der Komposition der beiden Relationen. R(p1+p2) := R(p1)(s) \/ R(p2)(s) In der Regulaeren Programmiersprache kann man Programme vereinigen. Ist zB p1=(X:=1) und p2=(X:=2) und werden diese Programme vereinigt, so ergeben sich fuer irgendeinen Eingabezustand 2 Ausgabezustaende: Einer, in dem X den Wert 1 hat und einer, in dem X den Wert 2 hat. R(p*) := (R p)* Der Stern entspricht dem einmaligen Ausfuehren von p, vereinigt mit dem zweimaligen Ausfuehren von p usw. Der Ergebniszustand enthaelt also (indeterministisch) all das, was bei irgendeiner Anzahl von Ausfuehrungen von p herauskommt: R(p*) := Id \/ R(p) \/ R(p)oR(p) \/ R(p)oR(p)oR(p) ... Syntaktisch: p* = skip; p; p+p; p+p+p; ... R(b?) := {(s,s) | s in Sigma, (B b s)=1} Der sogenannte Test ist die Identitaet auf der Menge all derer Zustaende, fuer die b zu "wahr" auswertet. Der Test ist insbesondere dann interessant, wenn er mit ";" hinter einem anderen Programm steht. Dann naemlich geht die Menge der Ausgabezustaende des vorhergehenden Programms als Eingabe in den Test. Diesen Test ueberleben nur diejenigen Ausgabezustaende, die b erfuellen. Alle anderen verschwinden. Die regulaere Programm-Denotation berechnet also zu einem regulaeren Programm die zugehoerige Zustands-Relation. Schreibweisen: p1 + p2 + p3 := (p1+p2)+p3 // egal wg Assoziativitaet von \/ p1 o p2 o p3 := (p1op2)op3 // egal wg Assoziativitaet von o p + p1;p2 := p + (p1;p2) if b then p1 else p2 := (b?;p1) + (~b?;p2) // interessant while b do p := (b?;p)*;~b? // sehr interessant fail := false? // leere Relation, keiner ueberlebt skip := true? // vollstaendige Relation, alle drin Beispiel: R( I:=1; while I=<2 do (X:=X+10;I:=I+1) ) Dieses Programm addiert also 20 zu X. Die Schleife wird 2-mal durchlaufen: Einmal mit I=1 und einmal mit I=2. while aufloesen = R( I:=1; (I=<2?;(X:=X+10;I:=I+1))*;~(I=<2)? ) ^-- erstes Semikolon aufloesen = R( I:=1 ) o R( (I=<2?;(X:=X+10;I:=I+1))*;~(I=<2)? ) zweites Semikolon -^ aufloesen = {(s,s[I->1])|s in Sigma} o R( (I=<2?;(X:=X+10;I:=I+1))* ) o R(~(I=<2)? ) Stern aufloesen (0-mal, 1-mal, 2-mal usw.) Dies entspricht dem Hintereinanderkopieren des Schleifenkoerpers 0-mal, 1-mal, 2-mal usw. = {(s,s[I->1])} o ( R(I=<2) o R(Id) \/ R(I=<2?;(X:=X+10;I:=I+1))) \/ R(I=<2?;(X:=X+10;I:=I+1)) o I=<2?;(X:=X+10;I:=I+1))) \/ R(I=<2?;(X:=X+10;I:=I+1)) o I=<2?;(X:=X+10;I:=I+1)) o I=<2?;(X:=X+10;I:=I+1))) \/ R(I=<2?;(X:=X+10;I:=I+1)) o I=<2?;(X:=X+10;I:=I+1)) o I=<2?;(X:=X+10;I:=I+1)) o I=<2?;(X:=X+10;I:=I+1))) \/ ... ) o R(~(I=<2)? ) Dieses entspricht dem 0-maligen, 1-maligen, 2-maligen usw. Durchlaufen der while-Schleife. Natuerlich wird die Schleife in der Realitaet nur 2-mal durchlaufen. Dies wird sich darin zeigen, dass all diejenigen Mengen, die nicht dem 2-maligen Durchlaufen entsprechen, zur leeren Menge werden werden. Was uebrig bleibt ist der Code, den man durch 2-maliges Hintereinanderkopieren des Schleifenkoepers (mit I=1 und I=2) erhalten haette. Id und innere Semikola aufloesen = {(s,s[I->1])} o ( {(s,s)|s(I)=<2 } \/ {(s,s)| s(I)=<2 } o {(s,s[X->s(X)+10,I->s(I)+1])} \/ {(s,s)| s(I)=<2 } o {(s,s[X->s(X)+10,I->s(I)+1])} o {(s,s)| s(I)=<2 } o {(s,s[X->s(X)+10,I->s(I)+1])} \/ {(s,s)| s(I)=<2 } o {(s,s[X->s(X)+10,I->s(I)+1])} o {(s,s)| s(I)=<2 } o {(s,s[X->s(X)+10,I->s(I)+1])} ... ) o {(s,s)| s(I)>2 } Vordere und hintere Relation in die Klammer holen Dies entspricht dem kompletten Aufdroeseln des Programms in den den gesamten Programmcode bei 0-maligem Durchlaufen, bei 1-maligem Durchlaufen usw. = {(s,s[I->1])} o {(s,s)|s(I)=<2 } o {(s,s)| s(I)>2 } \/ {(s,s[I->1])} o {(s,s)| s(I)=<2 } o {(s,s[X->s(X)+10,I->s(I)+1])} o {(s,s)| s(I)>2 } \/ {(s,s[I->1])} o {(s,s)| s(I)=<2 } o {(s,s[X->s(X)+10,I->s(I)+1])} o {(s,s[X->s(X)+10,I->s(I)+1])} o {(s,s)| s(I)>2 } Die Menge der ersten Zeile ist leer! Es gibt keinen Zustand s, der Ausgabe der zweiten Relation ist (d.h. s(I)=<2) und der Eingabe der dritten Relation ist (d.h. s(I)>2). Die erste Zeile faellt somit weg. Dieses entspricht der Tatsache, dass die while-Schleife nicht 0-mal ausgefuehrt werden kann. Knoepfen wir uns den zweiten Block vor: Alle Ausgaben der ersten Relation ueberstehen die zweite unbeschadet, die zweite fungiert wie Id und ist ueberfluessig: = {(s,s[I->1]) } o {(s,s[X->s(X)+10,I->s(I)+1])} o {(s,s)| s(I)>2 } \/ ... Alle Eingaben zu der zweiten Relation haben s(I)=1. Also haben alle Ausgaben der zweiten Relation I=2: = {(s,s[I->1]) } o {(s,s[X->s(X)+10,I->2)])} o {(s,s)| s(I)>2 } \/ ... Alle Ausgaben der zweiten Relation haben s(I)=2. Also findet sich keine Eingabe fuer die dritte Relation, denn deren Eingaben (ebenso wie Ausgaben) haben alle S(I)>2. Damit ist die Komposition leer, die erste Menge komponiert mit der leeren Menge ergibt die leere Menge. Der Abschluss-Test ~b? sagt, dass nach Ablauf der Schleife ~b gelten muss (weil sonst die Schleife erneut durchlaufen wuerde). Dieser Test hat jetzt die Menge, die dem (illegalen) einmaligen Durchlaufen entspricht, getoetet. Es bleibt n-maliges Durchlaufen mit n>=2. n>2 fliegt nach aehnlichen Berechnungen raus, sodass bleibt: = {(s,s[I->1])} o {(s,s)| s(I)=<2 } o {(s,s[X->s(X)+10,I->s(I)+1])} o {(s,s[X->s(X)+10,I->s(I)+1])} o {(s,s)| s(I)>2 } = {(s,s[X->s(X)+10,I->2])} o {(s,s[X->s(X)+10,I->3])} = {(s,s[X->s(X)+20,I->3])} Das Programm erhoeht also X um 20 und setzt I auf 3. Laesst man es auf dem Initialzustand {(X,100),(I,42)} laufen so ergibt sich (bei Interpretation der Relation als Funktion): {(s,s[X->s(X)+20,I->3])} {(X,100),(I=42)} = {(X,120),(I=3)} Beispiel: // Uebungsblatt #8.2 * Programm mit Denotation {}: fail Fail filtert laesst nur diejenigen Eingabezustaende durch, die die Bedingung "false" erfuellen. Das ist keiner. Dieses Programm kann man auch in IMP schreiben: while true do skip Es laesst keinen Zustand durch. * Programm mit Denotation {(s,s)| s in Sigma}: skip Skip laesst alle Zustaende durch, die die Bedingung "true" erfuellen. Das sind alle. Dieses Programm kann man auch in IMP schreiben, es ist ebenfalls "skip". * Programm mit Denotation {(s,s[X->n])| s in Sigma & n>=7}: X:=7;(X:=X+1)* Es handelt sich um die Menge aller Zustandspaare, in denen X nachher einen Wert groessergleich 7 hat. Diese Zustandspaare werden genau durch dasjenige Programm beschrieben, welches X auf 7 setzt und danach eine beliebige Erhoehung von X erlaubt. Da dieses Programm indeterministisch ist, kann es nicht in IMP geschrieben werden. * Programm mit Denotation {(s,s[X->3*s(X)])| s in Sigma}: X:=X+X+X Es handelt sich um die Menge aller Zustandspaare, in denen X nachher seinen Wert verdreifacht hat. Dies laesst sich durch dreifache Addition bewerkstelligen. Dieses Programm laesst sich genau so auch in IMP schreiben. * Programm mit Denotation {(s,s[X->2*n])| s in Sigma & n in N}: X:=0;(X:=X+2)* Es handelt sich um die Menge aller Zustandspaare, in denen X nachher eine gerade positive Zahl ist. Dazu setzt man X auf 0 und erlaubt dann beliebig viele Additionen von 2. Da dieses Programm indeterministisch ist, kann es nicht in IMP geschrieben werden. * Programm mit Denotation {(s,s[X->s(X)-n])| s in Sigma & n in N}: (X:=X+(-1))* Es handelt sich um die Menge aller Zustandspaare, in denen X nachher einen kleineren Wert hat. Dazu erlaubt man beliebig viele Subtraktionen mit 1. Da dieses Programm indeterministisch ist, kann es nicht in IMP geschrieben werden. * Programm mit Denotation {(s,s[X->z])| s in Sigma & z in Z}: (X:=X+1)*;(X:=X-1)* oder (X:=X+1)*+(X:=X-1)* Es handelt sich um die Menge aller Zustandspaare, in denen X alle ganzen Zahlen als Wert annimmt. Dazu erhoeht man X zunaechst beliebig und erniedrigt es dann beliebig. Auf diese Weise werden alle ganzen Zahlen abgedeckt. Da dieses Programm indeterministisch ist, kann es nicht in IMP geschrieben werden. Operationale Definition der Denotation regulaerer Programme: Die folgenden Inferenzregeln: // Uebungsblatt #8.6 s |- p => s' steht fuer (s,s') in R(p) --------------------------------------- s |- (X:=a) => s[x -> (A a s)] Ohne jede Vorraussetzung ersetzt die Zuweisung immer den Wert der Lokation. s |- p1 => s' s' |- p2 => s'' --------------------------------------- s |- (p1;p2) => s'' Wenn p1 s' generiert und p2 aus s' s'' macht, dann macht p1;p2 aus s s''. s |- p1 => s' --------------------------------------- s |- (p1+p2) => s' Wenn (s,s') in R(p1) ist, dann ist es auch in R(p1+p2). Und andersrum: s |- p2 => s' --------------------------------------- s |- (p1+p2) => s' --------------------------------------- s |- p* => s Der Zustand s kann p* unbeschadet ueberleben. s |- p => s' s' |- p* => s'' --------------------------------------- s |- p* => s'' Wenn p s' generiert und p* aus s' s'' macht, dann ist auch (s,s'') in R(p*), weil das p* auch das einmalige Mehranwenden von p mit enthaelt. B b s = 1 --------------------------------------- s |- b? => s Wenn b in s zu 1 auswertet, dann ist s in R(b?). Nicht-regulaere Relation: Eine Relation auf Zustaenden, die nicht durch ein regulaeres Programm beschrieben werden kann. Beispiel: // Uebungsblatt #8.7a Bei unendlich vielen Lokationen die Funktion f s := lambda X in Loc.1 Diese Funktion setzt alle Variablen auf 1. Das kann kein Programm schaffen, da jede Zuweisung einer Lokation ein Kommando ist und da ein Programm nur aus endlichen vielen Kommandos bestehen kann. Beispiel: // Uebungsblatt #8.7b Bei nur einer Lokation X: f s := 1 falls X einen Wert enthaelt, der einem Programm entspricht, welches terminiert 0 sonst Man kann jedes Programm als Zahl codieren (s.u. Goedelisierung) und in die Lokation X stecken. Die Funktion f aber ist nicht berechenbar (s.u.), das heisst sie kann nicht mir algorithmischen Mitteln ausgerechnet werden -- auch nicht mit regulaeren Programmen. Aequivalente Programme: Zwei Programme, die dieselbe Denotation haben. Diese Programme koennen zwar (syntaktisch) verschieden sein, machen aber das gleiche. D.h.: Laesst man sie auf der selben Initialbelegung fuer die Lokationen laufen, so ergeben sie nachher dieselben Endbelegungen. In der Tat koennen mehere verschiedene Programme dasselbe tun (zB mit unterschiedlicher Effizienz). Anmerkung: Programmaequivalenz ist unentscheidbar, man kann kein Programm schreiben, welches Programme auf Aequivalenz testet. Insbesondere koennen aequivalente Programme nicht ohne weiteres ineinander ueberfuehrt werden wie aequivalente Formeln. Vorlaeufiger Beweis (spaeter ausfuehrlicher): Reduktion auf das Halte-Problem Das Halte-Problem ist aequivalent zu der Frage, ob das fragliche Programm aequivalent zum Programm while(1=<42) X:=X ist. Waere Programm-Aequivalenz entscheidbar, so waere auch das Halte-Problem entscheidbar -- und das ist es nicht (s. "Theoretische Informatik" (infod.txt) oder unten). Beispiele: (fuer regulaere Programme und fuer IMP, falls definiert) Folgende Aequivalenzen ergeben sich sofort aus den entsprechenden Denotationen: // Skip als neutrales Element skip ; p == p p ; skip == p // Fail als "Null" fail ; p == fail p ; fail == fail p + fail == p // Distributivitaet p;(p1+p2) == (p;p1)+(p;p2) (p1+p2);p == (p1;p)+(p2;p) // Wir sehen, dass sich das Semikolon, was Distributivitaet, // Prioritaet und fast Bedeutung angeht, so verhaelt wie das // "&" in der boolschen Algebra (wenn "+" fuer "|" steht). // Sternchen p*;p* == p* // p* enthaelt p*;p* (b?)* == skip // 0-mal Test auch OK p;p* == p*;p p* == skip + p* // * enthaelt skip p* == skip + (p;p*) // 0-mal Ausfuehren // separat // Kommutativitaet, Assoziativitaet, Idempotenz p1;(p2;p3) == (p1;p2);p3 p1+(p2+p3) == (p1+p2)+p3 p1+p2 == p2+p1 p + p == p Beispiele: // Uebungsblatt #8.3 while false do p == skip fail* == (false?)* == skip skip* == (true?)* == skip while b do skip == (b?;skip)*;(~b)? == (b?)*;(~b)? == skip;(~b)? == (~b)? while true do p == (true?;skip)*;(~true)? == (true?)*;false? == skip;false? == false? == fail // Interessant hier, dass "fail" fuer Nicht-Terminierung steht while b do fail == (b?;fail)*;(~b)? == (fail)*;(~b)? == (false?)*;(~b)? == skip;(~b)? == (~b)? p* == skip + (p;p*) == skip + (p;(skip + p*)) == skip + (p;skip) + (p;p*) == skip + p + (p;p*) == p + skip + (p;p*) == p + p* p* == skip + (p;p*) == skip + (p;(p+p*)) == skip + (p;p) + (p;p*) == (p;p) + skip + (p;p*) == (p;p) + p* b?+skip == b? + (b?)* == (b?)* == skip if b then skip else fail == (b?;skip) + ((~b)?;fail) == b? + fail == b? Beispiel: // Uebungsblatt #8.4a Finde den Wert der Variablen(!) x, sodass x=if b then (p;x) else skip Behauptung: x=while b do p Beweis: if b then (p;x) else skip = if b then (p;while b do p) else skip // x=while b do p = b?;p;while b do p + ~b?;skip // Definition if = b?;p;(b?;p)*;~b? + ~b? // Definition while,skip = b?;p;(b?;p)*;~b? + skip;~b? // Einfuehrung skip = ((b?;p;(b?;p)*) + skip) ; ~b? // Distributivitaet = ( (b?;p)* + skip) ; ~b? // p* enthaelt p*;p* = ( (b?;p)* ) ; ~b? // * enthaelt skip = while b do p // Definition while Beispiel: // Uebungsblatt #8.4b Finde den Wert der Variablen q, sodass q = p;(b? + ((~b)?;q)) Behauptung: q = do p until b = p; while ~b do p Beweis: p;(b? + ((~b)?;q)) = p;(b? + ((~b)?;p; while ~b do p)) // q=p; while ~b do p = p;(b? + ((~b)?;p; ((~b)?;p)*;b?)) // Definition while = p;(b? + ( ((~b)?;p)*;b?)) // p;p* = p* = p;(skip;b? + ((~b)?;p)*;b? ) // skip;p = p = p;(skip + ((~b)?;p)*);b? // Distributivitaet = p; ((~b)?;p)* ;b? // skip+p*=p* = p; while ~b do p // Definition while For-Schleife: Das folgende Programm: for X to a do p := while (X=a)?;skip = (X=a)? Beweis: (X=a)? = (X=a)? = (X=a)? + (X>a)? = ((X=a)? + (X>a)? = ((X=a)? + skip;(X>a)? = (((X=a)? = ((X=a)? = while (X= AUSDRUCK RELATION AUSDRUCK RELATION --> = | =< | >= | ... AUSDRUCK --> VARIABLE AUSDRUCK --> ganze Zahlen AUSDRUCK --> AUSDRUCK OPERATOR AUSDRUCK OPERATOR --> + | - | / | * | ... Es handelt sich also um die normale Formelsprache ohne Quantoren aus der Schule. Beispiel fuer First-Order-Formeln: 3 >= x + 4 Anmerkung: Fuer eine solche Formel haben wir keine Denotation definiert. Loc-Bedingung: Eine Menge von Loc-Zustaenden. Intuition: So eine Menge beschreibt eine Menge von Zustaenden, in denen das Programm sich befinden soll. Soll zB die Lokation X einen Wert groesser 0 haben und die Lokation Y 5 sein, so waere eine Loc-Bedingung (angenommen es gibt nur X und Y): { {(X,1),(Y,5)}, {(X,2),(Y,5)}, {(X,3),(Y,5)}, ... } Schreibweise: Man schreibt eine First-Order-Formel, die genau fuer die Loc-Zustaende der Menge erfuellt ist (mit den Lokationen als Variablen): { F } := { s in Sigma | F[X:=s(X)][Y:=s(Y)]... gilt } Beispiel: { X>0 & Y=5 } = { {(X,1),(Y,5)}, {(X,2),(Y,5)}, {(X,3),(Y,5)},...} Anmerkung: Diese Formel ist auf rein semantischem Niveau! Wir schreiben sie zwar hin wie eine erweiterte boolsche Formel, aber es handelt sich nicht um ein syntaktisches Objekt! Als Konsequenz haben wir gar keine Rechenmoeglichkeiten zur Hand: Alles, was wir fuer die Boolsche Algebra entwickelt haben (Boolsche Axiome etc.) ist hier nicht anwendbar, ebenso die Gleichheitsregeln. Wir gehen hier also mit Objekten um, ueber die wir nichts aussagen koennen, da jede Gleichung auf semantischem Niveau atomar ist. Mit anderen Worten: Wir muessen mit den Regeln aus der Schule rechnen. Natuerlich werden wir unsere boolschen Rechentechniken trotzdem auf diese Formeln anwenden -- was wiedereinmal die Unterscheidung Syntax/Semantik in Frage stellt... Eigenschaften: {A} /\ {B} = { A & B } fuer durch Formeln A,B beschriebene Mengen {A} \/ {B} = { A | B } Programm-Spezifikation: Ein Paar aus einem Paar von Loc-Bedingungen (A,B) und einer Menge von Lokationen L. Die beiden Loc-Bedingungen heissen "Vorbedingung" und "Nachbedingung". Dieses Paar (A,B) soll heissen: Wenn ein Loc-Zustand in der ersten Menge ist, dann ist der Ergebnis-Loc-Zustand des Programms in der zweiten Menge. Eine solche Programm-Spezifikation drueckt also ganz abstrakt eine zu berechnende Funktion aus (mach aus einem Loc-Zustand einen neuen) und sagt nicht, wie das Berechnen geschehen soll. Die angegebene Menge von Lokationen L sagt, welche Lokationen das Programm nicht zuweisen darf. Diese Menge ist notwendig, weil es sonst meist trivial ist, die Nachbedingung zu erfuellen. Beispiel: Eine Spezifikation zur Berechnung der Fakultaet von X ist ({ X >=1 }, { Y = X! }, {X} ) Waere X zuweisbar, so waere "X:=1;Y:=1" ein die Spezifikation erfuellendes Programm. Anmerkung: Die Menge nicht zuweisbarer Lokationen wird in der Vorlesung sehr stiefmuetterlich/-vaeterlich behandelt und gehoert offiziell gar nicht zur Spezifikation dazu. In dieser Zusammenfassung wird die Menge daher auch weggelassen. Staerkere Loc-Bedingung als eine Loc-Bedingung B: Eine Teilmenge von B. Diese Loc-Bedingung ist "staerker" in dem Sinne, dass sie weniger Loc-Zustaende enthaelt. Sie laesst also weniger Loc-Zustaende zu und ist weniger "permissiv", weniger grosszuegig. Wenn die staerkere Bedingung erfuellt ist, ist automatisch die schwaechere Bedingung erfuellt. Schreibt man die Bedingungen als Formeln, so impliziert genau die staerkere Bedingung die schwaechere. Beispiel: Die erste (starke) Loc-Bedingung haelt X und Y fest: { {(X,42), (Y,1)} } oder { X=42 & Y=1 } Die zweite (schwaechere) Loc-Bedingung haelt nur X fest: { {(X,42),(Y,0)}, {(X,42),(Y,1)}, {(X,42),(Y,2)}, ... } oder { X=42 } Diese schwache Bedingung enthaelt die starke im mengentheoretischen Sinne. Und die starke Bedingung impliziert die schwache. Intuition: Das ist wie bei Konzepten: Die (allgemeine, schwache, grosse) Menge der Fahrzeuge enthaelt die (spezielle, starke, kleine) Menge der Autos. Als Formel: Auto sein impliziert Fahrzeug sein, das starke impliziert das schwache. Eine Spezifikation (A,B) partiell erfuellendes Programm: Ein Programm, fuer das gilt: Wenn ein Loc-Zustand in A ist und das Programm fuer diesen Loc-Zustand eine Ausgabe hat, dann ist das diese Ausgabe nachher in B. Ausserdem weist das Programm die nicht zuweisbaren Lokationen nicht zu. Das heisst: Wenn das Programm ueberhaupt etwas berechnet, dann berechnet es das richtige. Formal: Fuer alle (s,s') in R(p): s in A => s' in B Fuer eine Loc-Bedingung A lieferndes Programm: Ein Programm p, fuer das es einen Loc-Zustand s in A und einen anderen Loc-Zustand s' gibt, sodass (s,s') in R(p). Das heisst: Es gibt einen Anfangs-Loc-Zustand, fuer den das Programm einen Ergebnis-Loc-Zustand berechnet -- und nicht etwa in einer endlosen Schleife absackt. Beispiele fuer nicht liefernde Programme: fail while true do X:=X+1 Fuer eine Loc-Bedingung A terminierendes Program: Ein einfaches Programm, welches fuer A liefert. Da einfache Programme eine Funktion (und nicht eine Relation) auf Loc-Zustaenden beschreiben, gibt es zu jedem Eingabe-Zustande entweder genau einen Ausgabezustand (es terminiert) oder keinen. Anmerkung: Nehmen wir eine Loc-Bedingung A, die alle Loc-Zustaende erlaubt. Sei p ein Programm, was nur fuer X=Y=Z=42 terminiert. Dann folgt schon, dass "p fuer A terminiert", obwohl p nur fuer einen einzigen Loc-Zustand aus A terminiert. Fuer eine Spezifikation (A,B) korrektes Programm: Ein Program, welches (A,B) partiell erfuellt und fuer A liefert. Das heisst: Erstens, wenn es ueberhaupt etwas liefert, dann liefert es was richtiges und zweitens, es liefert etwas. Man trennt diese beiden Eigenschaften, da die Methoden fuer ihre Beweise sehr verschieden sind. Notwendigkeitsoperator auf einer Menge X: Die Funktion N in P(X^2) -> P(X) -> P(X) N R B := { x in X | Fuer alle y in X: (x,y) in R => y in B } Dieser Operator bekommt also eine Relation R auf X und eine Zielmenge B. Er liefert all diejenigen Elemente x aus X, die mit allen Ausgabewerten in B sind. Insbesondere liefert er auch diejenigen x, die ueberhaupt nicht Eingabewert von R sind. Intuition: Man kann sich dies als einen Graphen vorstellen, in dem die Knoten alle moeglichen Elemente aus X sind. Dann ist R eine Relation auf diesen Knoten, sprich eine Menge von Pfeilen zwischen diesen Knoten. B ist eine speziell ausgezeichnete Teilmenge der Knoten. Der Notwenigkeitsoperator fragt jetzt nach allen Knoten, die entweder gar keinen abgehenden Pfeil haben oder von denen alle Pfeile auf einen B-Knoten gehen. Beispiele: N R {} = Sigma - Dom(R) Die Suche ist nach denjenigen Knoten, die * nach {} gehen (keiner) oder * keinen ausgehenden Pfeil haben das sind die Knoten in Sigma - Dom(R) N Id B = B Die Suche ist nach denjenigen Knoten, die * nach B gehen das sind in der Identitaetsrelation genau die Knoten aus B * keinen ausgehenden Pfeil haben Die gibt es nicht N {(a,a)|a in A} B = (Sigma - A) \/ B Die Suche ist nach denjenigen Knoten, die * nach B gehen das sind die, die in A und B sind * keinen ausgehenden Pfeil haben das sind die, die in Sigma - A sind Dies ist aber eine Obermenge von B: Entweder ist ein Knoten aus B in A (dann ist er in A/\B) oder nicht (dann ist er in (Sigma - A). Man kann also vereinfachen zu (Sigma-A) \/ B. N R Sigma = Sigma Eigenschaften: N (R\/R') B = (N R B) /\ (N R' B) Wir vereinigen die Relationen R und R' (nehmen also beide Pfeilmengen). N fragt nach denjenigen Knoten x, fuer die *alle* ausgehenden Pfeile nach B gehen. Wenn alle ausgehenden Pfeile von x nach B gehen sollen, so muessen R-Pfeile von x nach B gehen *und* die R'-Pfeile von x nach B gehen. N (VEREINIGUNG {R[i]|i=1...}) B = SCHNITT {N R[i] B | i=1... } Ergibt sich aus vorhergehender Eigenschaft N (R o R') B = N R (N R' B) Wir nehmen den Graphen, in dem die Pfeile immer einen R-Pfeil und einen R'-Pfeil abkuerzen. Wir suchen all die Knoten, die mit einem solchen Abkuerzpfeil in B landen. Dazu nehmen wir zunaechst alle Knoten B', die mit einem R'-Pfeil in B landen. Die gesuchten Knoten sind dann diejenigen, die mit einem R-Pfeil in B' landen. A < B => N R A < N R B ("Monotonie im 2. Argument") Eine eingeschraenktere Zielmenge heisst eine eingeschraenktere Ausgangsmenge. R < R' => N R B > N R' B ("Antimonotonie im 1. Argument") Hat man weniger Pfeile, so wird die notwendige Menge groesser, denn jeder Knoten ohne Pfeil gehoert ja mit dazu. N R* B = N (VEREINIGUNG {R^n | n=1...}) B = SCHNITT {N R^n B | n=1... } < N R^0 B = N Id B = B Nimmt man die transitive Huelle von R, so ist die notwendige Menge auf jeden Fall eine Teilmenge von B. I < N R I <=> I = N R* I Ist I eine Teilmenge seiner eigenen notwenidgen Menge, so ist I gleich dieser notwendigen Menge fuer die transitive Huelle. Eine Knotenmenge I mit I < N R I heisst "Invariante". Die Invariante "ueberlebt" sozusagen den N-Prozess. N R* B = VEREINIGUNG { I | I < B und I < N R I } Die notwendige Menge unter einer transitiven Huelle ist die Vereinigung aller Invarianten. Schwaechste Vorbedingung eines Programms p fuer eine Loc-Bedingung B, SVB von p fuer B: Die groesstmoegliche Menge an Loc-Zustaenden, fuer die p eine Ausgabe in B erzeugt. Die "groesstmoegliche Menge an Loc-Zustaenden" ist genau im Wortsinn der vorhergehenden Definitionen eine "schwaechste Loc-Bedingung". Dies entspricht genau dem Notwendigkeitsoperator auf der Menge der Programme. Schreibweise: // Uebungsblatt #9.3a N in P(Sigma^2) -> P(Sigma) -> P(Sigma) N R(p) B := { s in Sigma | Fuer alle s' in Sigma: (s,s') in R(p) => s' in B } Man schreibt auch "N p B" fuer "N R(p) B" Ein Programm p erfuellt die Spezifikation (A,B) partiell genau dann, wenn A eine Teilmenge von N p B ist. Beweis: Wenn p (A,B) erfuellt, dann landet das Programm fuer jedes s in A in einem Ausgabe-Loc-Zustand in B. Da die SVB die groesstmoegliche Menge an ebensolchen in B landenden Loc-Zustaenden ist, muss die SVB schonmal A enthalten. Da umgekehrt jeder Loc-Zustand der SVB in B landet, erfuellt jede Teilmenge A der SVB eine Spezifikation (A,B). Es gilt: N (X:=a) B = { s | s[X->(A a s)] in B} = {F[X->a]} in Formelschreibweise, falls {F}=B Wenn nach der Zuweisung X:=a die Bedingung B fuer X gelten soll, so muss die Bedingung B vorher fuer a gegolten haben. N (b?) B = { s | B b s = 0 oder s in B } = { s | B b s => s in B } = {b=>F} in Formelschreibweise, falls {F}=B N (p1;p2) B = N p1 (N p2 B) = ((N p2) o (N p1)) B Macht das Programm zunaechst p1 und dann p2, so fragt man, was die SVB ist, damit p2 in B landet. Dann versucht man, mit p1 genau in dieser SVB zu landen. N (p1+p2) B = (N p1 B) /\ (N p2 B) = {F1 & F2} in Formelschreibweise, falls {Fi}=(N pi B) Da "+" genau die Vereinigung ist und diese zum Schnitt wird. N (p*) B = VEREINIGUNG { I | I < B und I < N p I } Wird p iteriert, so finde alle Invarianten, die Teilmengen von B sind und vereinige sie. Diese Menge kann algorithmisch nur sehr schwer berechnet werden. Man findet daher manuell eine Invariante I. N (if b then p1 else p2) = {(b=>p1) & (~b=>p2)} Ergibt sich aus der Definition von if. Beispiele: // Uebungsblatt #9.1 N (X:=X) {Z=X*Y} = {Z=X*Y} Damit nachher Z=X*Y gilt, muss das auch vorher gegolten haben, denn das Programm tut ja nichts. N (Z:=X) {Z=X*Y} = {X=X*Y} = {Y=1 | X=0} Wenn vorher X=X*Y gilt, dann gilt nachher Z=X*Y, denn Z nimmt ja den Wert von X an. N (if X X+Y=X*Y) & (X>=Y => Z=X*Y) } Wenn X X+Y=X*Y)==true, dieser Teil faellt weg. N (while X==5} = {X>=5} Damit X nachher in jedem Fall >=5 ist, muss es das schon vorher gewesen sein, denn der Stern beinhaltet ja auch die 0-malige Ausfuehrung. N (X:=X+1)* {X=<5} = {} Es kann keinen solchen Zustand geben. Denn gaebe es einen (der zB X mit -1024 belegt), so wuerde das Programm unendlich viele unerwuenschte Zustaende produzieren. N (while X=<7 do X:=X+1) {X>=8} = Sigma Das Programm erzeugt immer einen erwuenschten Zustand. Hoare-Tripel: Ein Tupel aus einer Loc-Bedingung, einem Programm und einer anderen Loc-Bedingung. So ein Tupel (A,p,B) bedeutet: p soll die Spezifikation (A,B) partiell erfuellen. Gueltiges Hoare-Tripel: Ein Hoare-Tripel (A,p,B), sodass A < N p B. Also erfuellt p die Spezifikation (A,p,B) partiell. Schreibweise: IPC fuer die Menge aller gueltigen Hoare-Tripel Beispiel: // Uebungsblatt #9.4 Die Menge M={ (A,p,B) in P(Sigma) x Pro x P(Sigma) | es existiert A' < Sigma mit A < A' und (A',p,B) in IPC } ist die Menge aller Hoare-Tripel, erweitert um noch ein paar Tripel mit staerkeren (kleineren) Vorbedingungen. Es gilt: M = { (A,p,B) | A < N p B } M = { (A,p,B) | Fuer alle (s,S') in R(p): s in A => R(p) in B } Verifikationsregeln: Die folgende operationale Definition der Menge IPC: // Entsprechen im Wesentlichen den Eigenschaften der SVB ------------------- (B[X:=a],(X:=a),B) Das Programm X:=a erfuellt die Spezifikation (B[X:=a],p,B), ohne Vorbedingung // Uebungsblatt #9.5 Die Ersetzung muss in die Vorbedingung und nicht in die Nachbedingung: (B, (X:=a), B[X->a]) ist ungueltig zB fuer ({X=1}, (X:=42), {X=1}[X->42]) = ({X=1}, (X:=42), {42=1}) = ({X=1}, (X:=42), {} ) (A,p1,A') (A',p2,A'') ---------------------- (A,(p1;p2),A'') Fuehrt p1 von A nach A' und fuehrt p2 von A' nach A'', so fuehrt p1;p2 von A nach A''. (A,p1,B) (A',p2,B) ---------------------- ((A /\ A'),(p1+p2),B) Entspricht der Vereinigung von Relationen --------------------- ({b=>F},(b?),{F}) "b?" landet in {F} fuer alle Knoten, die b nicht erfuellen oder aber schon in F sind: ~b | F, d.h. b => F (A,p ,I) ----------- falls IC (~b)? C ----------------- 7. Rechtestes Kommando ## (~b)? C p I ------------------------------- 6. Rechtestes Kommando ## (~b)? ; p I mit I=>(b=>B), I=>## ------------------------------- 5. *-Regel anwenden A p I ((~b)? ; p)* b=>B ----------------------------------- 4. neue BedVariable # p I ((~b)? ; p)* b=>B ----------------------------------- 3. * mit I ueberspringen # p ; ((~b)? ; p)* b=>B b? B ------------------------------------------ 2. Rechtestes Kommando # p ; ((~b)? ; p)* ; b? B ------------------------------------------ 1. Def aufloesen # do p until b B Diese Ableitung liest sich von unten nach oben. Es wurde je eine Regel angewendet. Algorithmus: * Man tastet sich von rechts nach links Semikolon fuer Semikolon vor * Wenn das Kommando einfach ist, berechne die SVB direkt und fuege sie anstelle des Semikolons ein (zB in Schritt 2) * Wenn ein Kommando ein Stern ist, fuege ein "I" anstelle des vorhergehenden Semikolons ein (Schritt 3). Wende dann die Stern-Regel an: ## p I --------- I=>## und I=>B I p* B wobei ## eine Meta-Variable ist. * Wenn man auf diese Weise eine Bedingung fuer eine Stelle ausrechnet, an der eine Meta-Variable stand, so bekommt diese Meta-Variable diesen Wert (man kann die Stelle auch einfach freilassen und bei Instantiierung spaeter dann im gesamten Baum ersetzen) * Wenn das Kommando eine Programmvariable ist (zB "p"), fuehre eine neue Bedingungsvariable ein und fuege sie anstelle des Semikolons ein (passiert hier in Schritt 4: Das A wird neu eingefuehrt). * Sammle alle im Baum stehenden Seitenbedingungen und alle Tripel mit einer Programmvariable (zB "A p I") auf. Diese geben die Praemissen der neuen Ableitungsregel. Der letzte Schritt im Beispiel: I=>(b=>B) I=>(~b=>C) C p I A p I Man fasst zusammen: A p I I=>(~b=>C) C p I I=>(b=>B) ------------------------------------------- A do p until b B Beispiel: // Uebungsblatt #9.7 X=A (X=X+1] ---------------------------------- ## (X=X+1] X:=X+1 I ------------------------------------------------------ ## (X=##,I=>(X>a=>B) ------------------------------------------------------ I ((X=a=>B ------------------------------------------------------ # ((X=a=>B (X>a)? B ------------------------------------------------------------------ # ((X=a)? B ------------------------------------------------------------------ # for X to A do p B Seitenbedingungen: A p I[X->X+1] I=>(X=A) I=>(X>a=>B) I=>(X=A) I=>(X>a=>B) A p I[X->X+1] --------------------------------------------------- I for X to A do p B Aehnlich kann man zeigen: (A,p,I) ------------------- falls I /\ {b} < A und I/\{~b}=0 Nachbedingung: Y=X^2 Schreibweisen: X++ := (X:=X+1) 2X := (X+X) X+=A := (X:=X+A) AZ+1][Y->Y+2Z+1] Y+=2Z+1 I[Z->Z+1] ----------------------------------------- ## Y+=2Z+1 I[Z->Z+1] Z++ I ---------------------------------------- I&ZY=X^2,I=>## ## Y+=2Z+1 ; Z++ I ---------------------------------------- I[Z,Y->0] Y:=0 I[Z->0] Z:=0 I while Z0] Z:=0 I while Z Y=X^2 I => I[Z->Z+1][Y->Y+2Z+1] X>=0 => I[Z->0][Y->0] // damit I am Anfang gilt Finden der Invarianten I: Dazu ueberlegt man sich, was die Schleife macht: Sie addiert in Y die ersten Z ungeraden Zahlen. Die Summe der ersten Z ungeraden Zahlen ist Z^2. Also Y=Z^2. Dazu nimmt man noch die um 1 abgeschwaechte Schleifenbedingung: Y = Z^2 & Z= Y=X^2 I => I[Z->Z+1][Y->Y+2Z+1] X>=0 => I[Z->0][Y->0] // damit I am Anfang gilt Beispiel: // Uebungsblatt #10.5 Programm zum Negieren positiver Zahlen: Y:=0; while X>0 do (Y:=Y-1;X:=X-1) Vorbedingung: X=X0 & X>=0 Nachbedingung: Y=-X0 Baeumchen: Y-1=-X0 Y:=Y-1 Y=-X0 X:=X-1 Y=-X0 --------------------------------------------- # Y:=Y-1 Y=-X0 X:=X-1 Y=-X0 ----------------------------------------- # Y:=Y-1 ; X:=X-1 Y=-X0 ----------------------------------------- I[Y->0] Y:=0 I while X>0 do (Y:=Y-1;X:=X-1) Y=-X0 ------------------------------------------------------- # Y:=0 I while X>0 do (Y:=Y-1;X:=X-1) Y=-X0 ------------------------------------------------------- # Y:=0 ; while X>0 do (Y:=Y-1;X:=X-1) Y=-X0 Bedingungen: I & (X>0) => Y-1=-X0 I & (X=<0) => Y=-X0 X=X0 & X>=0 => I[Y->0] Invariante: Die Schleife zaehlt X und Y zusammen runter, wobei Y von 0 startet und X von X0: Y-0 = X-X0 oder X0 = X-Y Wir nehmen noch die um 1 gelockerte Schleifenbedingung hinzu: X0 = X-Y & X>=0 Beispiel: // Uebungsblatt #10.6 Programm zum Negieren beliebiger ganzer Zahlen: Y:=0; while X>0 do (Y:=Y-1;X:=X-1); while X<0 do (Y:=Y+1;X:=X+1); // etwas kuerzer als die Musterloesung Vorbedingung: X=X0 Nachbedingung: Y=-X0 Baeumchen mit Abkuerzungen: Zweite Schleife: I2[X->X+1][Y->Y+1] Y:=Y+1 I2[X->X+1] X:=X+1 I2 -------------------------------------------------------- ## Y:=Y+1 ; X:=X+1 I2 ------------------------------------------ I2 while X<0 do (Y:=Y+1;X:=X+1); Y=-X0 Bedingungen: I2 & X<0 => I2[X->X+1][Y->Y+1] I2 & X>=0 => Y=-X0 Schreibweise: I2' := I2[X->X+1][Y->Y+1] Erste Schleife: I1[X->X-1][Y->Y-1] Y:=Y-1 I1[X->X-1] X:=X-1 I1 -------------------------------------------------------- ## Y:=Y-1 ; X:=X-1 I1 ------------------------------------------ I1 while X>0 do (Y:=Y-1;X:=X-1); I2' Bedingungen: I1 & X>0 => I1[X->X-1][Y->Y-1] I1 & X=<0 => I2[X->X+1][Y->Y+1] Programmanfang: I1[Y->0] --------------------- # Y:=0 I1 Bedingungen: X=X0 => I1[Y->0] Finden der Invarianten: I1: X0 = X-Y & X>=0 wie gehabt | X<0 & I2 neu I2: X0 = X-Y & X=<0 analog | X>0 & Y=-X0 falls nicht durchlaufen Pathologisches Beispiel: C:=X; Z:=0; while C >=1 do (Z:=Z+Z;C:=C-1) Dieses etwas kranke Programm zaehlt C runter und laesst Z auf 0. Falls X positiv ist, hat C nachher den Wert 0. Falls X negativ ist, hat C nachher den Wert X. Vorbedingung A = { X =< 0 } Nachbedingung B = { C=X } Erfuellt das Programm diese Spezifikation? Wir fangen mit dem letzten Kommando an: Wir wollen die SVB finden, sodass die Schleife nach B geht. Dazu verwenden wir die entsprechende Verifikationsregel und erhalten folgende Anforderungen and diese SVB I und eine Vorbedingung A': 1. Der Schleifenkoerper erfuellt (A',I) Also (Z:=Z+Z;C:=C-1) erfuellt (A',I). Wie sieht A' aus? A'=I[C->C-1][Z->Z+Z] 2. I /\ {C>=1} < A' = I[C->C-1][Z->Z+Z] 3. I /\ {C<1} < B = { C=X } Dieses I wollen wir jetzt mit dem vorhergehenden Kommando Z:=0 erreichen. Also (I[Z->0],Z,I). Und I[Z->0] wollen wir mit dem Kommando C:=X erreichen, also ist die SVB des gesamten Programms I[Z->0][C->X]. Wenn unsere gegebene Vorbedingung A diese SVB impliziert, wird die Spezifikation erfuellt. Also fordern wir: 4. A < I[Z->0][C->X] Nun laesst sich ein I finden, welches all diese 4 Bedingungen erfuellt. Es ist I = {C=<0 & C=X} Hier wurde in dem obigen Schema Invariante = WasTutSchleife & GelockerteSchleifenbedingung [ | ~Schleifenbedingung & Endbedingung ] nur die zweite Zeile beruecksichtigt, da die Schleife ueberhaupt nicht durchlaufen wird. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Berechenbarkeit ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Endlosschleife: Das Programm WHILE true DO skip Es terminiert nicht. Schreibweise: loop fuer WHILE true DO skip Eingabe eines Programms: Der initiale Loc-Zustand. Barbierparadoxon: Die Behauptung, es gaebe einen Barbier, der genau alle die Maenner rasiert, die sich nicht selbst rasieren. Einen solchen Barbier kann es nicht geben, denn: * Wenn der Barbier sich selbst nicht rasiert, so muss er sich rasieren, widerspruch * Rasiert der Barbier sich selbst, so darf er sich nicht rasieren, denn er soll ja nur die Maenner rasieren, die sich *nicht* selbst rasieren. Formal: Ex b. All x. p(x,b) <=> ~p(x,x) // b ist "Barbier", p "rasieren" = 0 // Behauptung ist einfach falsch // Beweis s.u. Dieses Paradoxon ist von elementarer Bedeutung und hat viele Interpretationen. Beispiel: Sei M die Menge aller Mengen, die sich nicht selbst enthalten: M = { Menge A | A nicht in A } Enthaelt M sich selbst oder nicht? Dies ist das Barbierparadoxon mit b=M, p=enthaelt. Diese Menge kann es nicht geben. Anmerkung: Das hat die Mathematiker lange beschaeftigt, da sie davon ausgingen, dass jede definierbare Menge existiert. Eine Definition kann jedoch in sich selbst inkonsistent sein, sodass das definierte Objekt nicht existieren kann. Beispiel: Sei K der momentane Koenig von Frankreich. Null-Zustand: Der Loc-Zustand s0, der immer nur 0 liefert. Programm-Funktion: Die Funktion F in Com->Z->Z F p x := (D p (s0[X0->x]))=_|_ ? _|_ : (D p (s0[X0->x])) X0 Diese Funktion fuellt also die Eingabe x in irgendeine Lokation X0, laesst das Programm p drauf laufen und liefert dann den Wert von X0. Sprechweise: Das Programm p laeuft/terminiert/divergiert auf x. // Uebungsblatt #11.1a Berechenbare Funktion: Eine Funktion f, fuer die ein ein Programm P gibt, sodass f = F P. Schreibweise: BF := {F c | c in Com} Dies schliesst auch den Fall der Nicht-Terminierung, _|_, mit ein. Beispiele: lambda x in Z.x ist berechenbar mit skip lambda x in Z._|_ ist berechenbar mit loop lambda x in Z.x+3 ist berechenbar mit X0:=X0+3 // Uebungsblatt #11.1b Reinliches Programm: Ein Programm, welches am Ende alle Lokationen bis auf die Ausgabelokation auf 0 setzt. Jedes Programm laesst sich reinlich machen indem man am Ende die entsprechenden Zuweisungen einfuegt. Komposition zweier Programme p1 und p2: Das Programm p1;p2. Es gilt F (p1;p2) x = (F p1 x) == _|_ ? _|_ : F p2 (F p1 x) Sind also f1 und f2 berechenbare Funktionen (mit Programmen p1 und p2), so ist auch ihre Komposition (f1 o f2)(x)=f2(f1(x)) berechenbar, naemlich vom Programm p1;p2. Zusammenhang zwischen Surjektivitaet und Injektivitaet: Die Tatsache, dass fuer eine nichtleere Menge A folgende Aussagen aequivalent sind: * Es existiert ein injektives f in A->B * Es existiert ein surjektives f in B->A // Uebungsblatt #10.1a Also ist eine nichtleere Menge M genau dann abzaehlbar, wenn es eine surjektive Funktion von N nach M gibt. Intuition: Das ist recht einleuchtend: Wenn ich jede natuerliche Zahl auf ein Element aus M abbilde und damit alle M's erwische, dann habe ich M abgezaehlt. NN-Funktionen: Die Funktionen von den natuerlichen Zahlen in die natuerlichen Zahlen. Die Menge dieser Funktionen ist ueberabzaehlbar. Beweis: (ein sogenannter "Diagonalschluss") Angenommen, die Funktionen seien abzaehlbar. Dann koennen wir sie (mit der Bijektion von N) durchnummerieren: f[0], f[1], ... Jetzt bauen wir eine Funktion, die nicht in dieser Liste sein kann: Sei f(x) := f[x](x) + 1 Wir nehmen also die x-te Funktion, wenden sie auf x an und addieren 1. Dieses f ist nicht in der Liste. Denn waere es in der Liste, so haette es eine Nummer k und es gaelte fuer das Argument k: f(k)=f[k](k) da f=f[k] f(k)=f[k](k)+1 da f(x)=f[x](x)+1 nach Definition von f Also f(k)=f(k)+1 Aus diesem Widerspruch folgt, dass f nicht in der Liste sein kann. Damit ist die Menge ueberabzaehlbar. // Uebungsblatt #10.1e Es folgt, dass P(N) ueberabzaehlbar ist, denn N->N ist eine echte Teilmenge von P(N). // Uebungsblatt #10.1f Es folgt, dass A = { S < N | S abzaehlbar } ueberabzaehlbar ist: N ist abzaehlbar. Damit ist jede Teilmenge von N abzaehlbar. Damit ist A die Menge aller Teilmengen von N. Damit ist A=P(N) und A ist ueberabzaehlbar. Goedelfunktion: Die Funktion goedel in N^n->N goedel(i[1],...i[n]) := p[1]^i[1] + p[2]^i[2] + ... + p[n]^i[n] wobei die p[j] verschiedene Primzahlen sind Diese Funktion bildet also ein Tupel aus natuerlichen Zahlen ab auf eine einzige natuerliche Zahl. // Uebungsblatt #10.2e Aufgrund der Eindeutigkeit der Primfaktorzerlegung ist die Goedelfunktion eine Injektion: Jedes Tupel wird auf eine andere Zahl abgebildet. Allerdings ist sie keine Surjektion: Bestimmte Zahlen (zB andere Primzaehlen) werden nie erreicht. Viele Informationen lassen sich als Tupel aus natuerlichen Zahlen darstellen und damit goedelisieren: * Eine ganze Zahl als Tupel aus Vorzeichen (0:positiv, 1:negativ) und Betrag * Eine rationale Zahl als Tupel zweier ganzer Zahlen * Ein Tupel von Tupeln durch Goedelisierung der inneren Tupel und dann des umschliessenden Tupels. * Ein durch eine Grammatik generierter Ausdruck wie folgt: Man nummeriert alle Nichtterminalsymbole und alle Terminalsymbole durch (muessen abzaehlbar viele sein) und codiert jedes Terminalsymbol des Ausdrucks durch das Paar aus sich selbst und seinem Nichtterminalsymbol. * Ein Programm, denn jedes Programm wurde von der jeweiligen Grammatik erzeugt und ist damit goedelisierbar. Ueberabzaehlbare Mengen lassen sich nicht goedelisieren (denn sonst haette man ja eine Bijektion nach N und die Menge waere abzaehlbar). Umgekehrt ist jede goedelisierbare Menge abzaehlbar, denn die Goedelfunktion ist eine injektive Funktion aus der Menge in die natuerlichen Zahlen. Beispiel: // Uebungsblatt #10.1b Damit ist N^3 abzaehlbar, denn man kann N^3 goedelsieren als goedel(a,b,c) = 2^a + 3^b + 5^c // Uebungsblatt #10.2f Die Menge aller berechenbaren Funktionen ist abzaehlbar, denn zu jeder solchen Funktion gibt es irgendein Programm und dieses kann man goedelisieren. Damit hat man eine injektive Abbildung in die natuerlichen Zahlen. Beispiel fuer eine Goedelisierung: // Uebungsblatt #10.3 Sei die Sprache gegeben T --> c() T --> v() T --> s(T,T) Sie laesst sich wie folgt goedelisieren: goedelpair(a,b) := 2^a * 3^b goedel(c(x)) := goedelpair(0,x) goedel(v(x)) := goedelpair(1,x) goedel(s(a,b)) := goedelpair(2,goedel(a),goedel(b)) ungoedelpair(x) := 2^(floor(log2 x))*3^(floor(log3 x))!=x ? error : (log2 x,log3 x) ungoedel(x) := (a,b) := ungoedelpair(x); a=0 ? c(b) : a=1 ? v(b) : a=2 ? (c,d):=ungoedelpair(b); s(ungoedel(c),ungoedel(d)) : error Darstellung fuer eine Menge MZ. Diese Funktion ordnet also jedem Element aus M eine eindeutige ganze Zahl zu. Wir nehmen als Darstellung die Goedelfunktion. Wir gehen davon aus, dass die Darstellung berechenbar ist. Schreibweise: X := #c fuer die Ausfuehrung des Programms zur Bestimmung der Darstellung von c und die anschliessende Zuweisung dieses Werts an die Lokation X. Darstellung einer Menge M: Die Menge der Goedelnummern ihrer Elemente. Schreibweise: #M := {#x | x in M} Speicher: Die Menge der in einem Programm verwendeten Lokationen. Durch Goedelisierung laesst sich mit nur einer Lokation ein beliebig grosser Speicher simulieren: Will man n simulierte Lokationen haben, so codiert man sie durch den Wert #(#(1,#WertVon1), #(2,#WertVon2), ... ) dh mit der Goedelisierung eines Tupels, welches fuer jede simulierte Lokation dessen Nummer und dessen Wert enthaelt. Damit dies funktioniert, darf nur an endliche vielen Stellen ein Wert ungleich 0 stehen (sonst hat man ein "unendliches Tupel" und bekommt die Goedelnummer nicht ausgerechnet, die NN-Funktionen bleiben also ueberabzaehlbar). Diese Zahl schreibt man nun in die zur Verfuegung stehende Lokation. Universelles Kommando: Das Programm cu mit * Eingabe: ein Paar aus einem anderen Programm p und einer Eingabe x, goedelisiert * Ausgabe: Die Ausgabe von p fuer x Dieses Programm simuliert also das Ausfuehren von p auf x, formal: // Uebungsblatt #11.1h F cu #(#p,#x) = F p x Wenn p auf x nicht terminiert, dann terminiert auch cu nicht. Ein solches universelles Kommando kann die gesamten Lokationen (den gesamten Speicher) des interpretierten Programms in einer einzigen Lokation ablegen. Als Konsequenz existiert eine fixe Teilmenge L der Lokationen, sodass jede berechenbare Funktion mit nur diesen Lokationen berechnet werden kann: Es ist die Menge der vom universellen Kommando benutzten Lokationen. Soll ein Funktionswert berechnet werden, so uebergibt man das zugehoerige Programm samt Argument an das universelle Kommando und dieses berechnet den Funktionswert -- mit nur seinen eigenen Lokationen. Problem: Ein Tupel aus * einer Menge M * einer Darstellung von M * einer Obermenge X von M Das "Problem" besteht darin, zu einem x in X zu entscheiden, ob x in M. Das ist nicht so trivial wie es scheinen mag, insbesondere, wenn X und M unendlich oder sogar ueberabzaehlbar sind. Schreibweise: (X,#,M) M fuer (X,#,M). Eine Obermenge X und eine Darstellung # werden dabei implizit vorausgesetzt. Pruefer fuer ein Problem (X,#,M): Ein Programm, welches fuer eine Eingabe x genau dann terminiert, wenn x in M ist. Steuerbares Programm: Ein Programm, welches als Eingabe zusaetzlich eine Anzahl von maximalen Schleifendurchlaeufen bekommt. Das Programm ist so geschrieben, dass es mit der Ausgabe 0 abbricht, wenn seine Schleifen insgesamt oefter als die gewuenschte Anzahl durchlaufen werden. Jedes Programm laesst sich zu einem steuerbaren Programm umbauen. Rekursiv aufzaehlbare, Semi-entscheidbare, pruefbares Problem: Eine Problem, zu dem es einen Pruefer gibt. Dh es gibt eine berechenbare Funktion, die fuer ein x nur dann _|_ liefert, wenn x nicht in M ist. Sind (X,#,M1) und (Y,$,M2) pruefbar, so gilt: * (X,#,M1) \/ (Y,$,M2) pruefbar * (X,#,M1) /\ (Y,$,M2) pruefbar Beweis: Man baut zunaechst die Pruefer p1 und p2 fuer M1 und M2 zu steuerbaren Pruefern um. Dann baut man ein Programm, welches beide Pruefer zunaechst mit 0 Schleifendurchlaeufen, dann mit 1 Schleifendurchlauf usw. ausfuehrt. Erst wenn beide Pruefer '1' liefern (bei M1/\M2) oder ein Pruefer '1' liefert (bei M1\/M2), hoert man auf und gibt '1' zurueck. Offensichtlich terminiert dieser Pruefer nicht fuer Elemente, die nicht zu der zusammengesetzten Menge gehoeren. Anmerkung: Es klingt so, als muesste man die Pruefer p1 und p2 fuer diesen Beweis kennen. Das ist nicht so: Sind M1 und M2 pruefbar, so gibt es irgendwelche Pruefer p1 und p2 (selbst wenn kein Mensch weiss, wie die aussehen). Wir zeigen, dass es dann auch in der grossen Menge der Programme ein Programm gibt, welches M1\/M2 prueft -- auch wenn wir nicht wissen, wie dieses Programm aussieht. Es reicht, dass das Programm existieren kann. // Uebungsblatt #11.1b // Uebungsblatt #11.1g Beispiel: Die Menge { c in Com | F c 0 = 7 } ist pruefbar. Der Pruefer ist X0:=0; c; if X0=7 then skip else loop Entscheider fuer ein Problem (X,#,M): Ein Programm, welches fuer eine Eingabe x genau dann 1 liefert, wenn x in M und genau dann 0 liefert, wenn x nicht in M. Ein Entscheider ist maechtiger als ein Pruefer: Aus jedem Entscheider kann man einen Pruefer bauen, indem man eine Endlosschleife anfuegt, andersrum geht das nicht. Beispiele: Entscheider fuer {}: // Uebungsblatt #10.2a X0:=0 Entscheider fuer N: // Uebungsblatt #10.2b if X0>=0 then X0:=1 else X0:=0 Entscheider fuer {x in Z | -2==-2 then if X0=<13 then X0:=1 else X0:=0 else X0:=0 Entscheidbares Problem: Ein Problem, zu dem es einen Entscheider gibt. Mit anderen Worten: Eine Menge, deren charakteristische Funktion berechenbar ist. Fuer 2 entscheidbare Probleme (X,#,M1) und (Y,$,M2) gilt: * (X,#,M1) /\ (Y,$,M2) ist entscheidbar * (X,#,M1) \/ (Y,$,M2) ist entscheidbar * (X,#,M1) - (Y,$,M2) ist entscheidbar Beweis: Sind M1 und M2 entscheidbar, so gibt es die zugehoerigen Entscheider p1 und p2. Man kann nun ein Programm schreiben, welches die Ausgaben von p1 und p2 &-verknuepft, |-verknuepft oder voneinander subtrahiert. // Uebungsblatt #10.2d // Uebungsblatt #11.1d Alle endlichen Mengen sind entscheidbar. Man braucht bloss ein Programm zu bauen, was die Eingabe auf Gleichheit mit dem ersten Element, dem zweiten Element usw. testet. Ein solches Programm ist unter Umstaenden lang, aber endlich. // Uebungsblatt #11.1c Beispiel: // Uebungsblatt #11.1i Ist die Menge M entscheidbar, so gibt es ein Programm c mit M = {x in Z | F c x = _|_} d.h. es gibt ein Programm c, welches genau fuer die Elemente von M divergiert. Sei e der Entscheider fuer M, dann ist c: e; if X0=1 then loop else skip // Uebungsblatt #11.1j Ist die Menge M lediglich pruefbar, so muss nicht unbedingt ein solches Programm geben: Das Programm c ist quasi ein Pruefer fuer X-M. X-M muss aber nicht unbedingt pruefbar sein. Zusammenhang zwischen Entscheidbarkeit und Pruefbarkeit: Die Tatsache, dass ein Problem (X,#,M) genau dann entscheidbar ist, wenn (X,#,M) und (X,#,X-M) pruefbar sind. Beweis: '=>': Ist (X,#,M) entscheidbar, so kann man die beiden Pruefer bauen, indem man an den Entscheider kurz vor Ende Endlosschleifen einbaut. '<=': Man baut zunaechst beide Pruefer zu steuerbaren Pruefern um. Der Entscheider ist dann ein Programm, welches beide Pruefer zunaechst mit 0 Schleifendurchlaeufen, dann mit 1 Schleifendurchlauf usw. ausfuehrt. Der Pruefer, der zuerst '1' liefert, sagt, ob das Element in M ist oder nicht. Ausserdem gilt: Ist M < E < X und ist E entscheidbar, dann ist X-M genau dann pruefbar, wenn (X-M)/\E pruefbar ist. Beweis: E ist entscheidbar, d.h. E ist pruefbar und X-E ist pruefbar. '=>': Angenommen X-M ist pruefbar. Da E pruefbar ist, ist auch (X-M)/\E pruefbar. '<=': Angenommen (X-M)/\E ist pruefbar. Es gilt: X-M = (X-E) \/ (E-M) = (X-E) \/ ((X-M) /\ E) Hier ist das Bildchen dazu: ________________________________ / X ___________________ \ | / E ______ E-M \ X-E | | | / M \ X-M | | | \______/ E/\X-M | | | \___________________/ | \________________________________/ Alle beteiligten Mengen sind pruefbar, damit ist auch X-M pruefbar. Kontraposition: Die Tatsache, dass a => b == ~b => ~a Reduktionsfunktion von einem Problem (X,#,M1) auf ein Problem (Y,$,M2): Eine Funktion f in X->Y, die jedes Element aus M1 auf ein Element aus M2 abbildet. Wenn ein Element x nicht in M1 ist, so soll auch f(x) nicht in M2 sein. Ausserdem muss es ein berechenbares Pendant g geben, welches genau dasselbe tut wie f, allerdings auf den Darstellungen: $(f(x)) = g(#x) Diese Bedingung soll lediglich sicherstellen, dass f berechenbar ist. Wir werden in Zukunft nur f angeben und darauf vertrauen, dass ein berechenbares Aequivalent existiert. Da x in M1 genau dann, wenn f(x) in M2, ergibt sich: f in X->Y ist genau dann eine Reduktionsfunktion von (X,#,M1) auf (Y,$,M2), wenn f eine Reduktion von (X-M1,#,X) auf (Y,#,Y-M2) ist. Sei f eine Reduktionsfunktion in (X,#,M1) -> (Y,$,M2). Dann gilt * Wenn (Y,$,M2) pruefbar dann (X,#,M1) pruefbar Beweis: Wir nehmen das fragliche Element x aus X und bilden es auf sein zugehoeriges Element f(x) in Y ab. Dann pruefen wir, ob f(x) in M2. Terminiert diese Pruefung, so ist x auch in M1. * Wenn (Y,$,M2) entscheidbar dann (X,#,M1) entscheidbar Beweis: f ist nicht nur eine Reduktionsfunktion von (X,#,M1) nach (Y,$,M2), sondern auch von (X,#,X-M1) nach (Y,$,Y-M2). Also kann man nach der vorhergehenden Eigenschaft pruefen, ob ein x in M1 ist und ob es in X-M1 ist. Damit ist M1 dann entscheidbar. * Wenn (X,#,M1) nicht pruefbar dann (Y,$,M2) nicht pruefbar Beweis: Folgt durch Kontraposition aus der ersten Eigenschaft. * Wenn (X,#,M1) unentscheidbar dann (Y,$,M2) unentscheidbar Beweis: Folgt durch Kontraposition aus der zweiten Eigenschaft. Reduktion eines Problems P1 auf ein Problem P2: Das Anwenden einer Reduktionsfunktion von P2 nach P1. Anmerkung: Da man recht leicht damit durcheinander kommt, was nun auf was reduziert wird, verwendet diese Zusammenfassung ausschliesslich den Begriff der Reduktionsfunktion. Eine Reduktionsfunktion von A nach B bekommt ein Element aus A und bildet es ab auf ein Element aus B. Halteproblem: Das Problem (Com,#,{c in Com | F c (#c) = _|_}) Dh: Die Menge all derer Programme, die auf ihrer eigenen Goedelnummer divergieren. Das Halte-Problem ist nicht pruefbar. Beweis: // Uebungsblatt #11.1k Ein solches Programm P kann es nicht geben, denn es waere das Barbierparadoxon mit b=P, x=Programme, p="terminiert auf": Ex P . All c in Com . terminiert(P,c) <=> ~terminiert(c,c) Die Frage ist: Terminiert ein solches Programm P auf seiner eigenen Goedelnummer? Wenn ja, dann ist P nicht im Halteproblem und P sollte fuer sich selbst divergieren. Wenn aber P auf seiner eigenen Goedelnummer divergiert, dann ist P im Halteproblem -- und P sollte terminieren. Das Halteproblem laesst sich auf eine Reihe von anderen Problemen reduzieren, die damit allesamt nicht pruefbar werden. Schreibweise dafuer: Um zu beweisen, dass eine Menge M nicht pruefbar ist, gibt man eine Reduktionsfunktion f an mit c in {c in Com | F c (#c) = _|_} <=> f(c) in M beziehungsweise aequivalent: F c (#c) = _|_ <=> f(c) in M Beispiele: Siehe Alle-Eingaben-Halteproblem Trugschluesse: Wenn M pruefbar ist, so ist *nicht* jede Teilmenge von M pruefbar! Formal: M1 < M2, (X,#,M2) pruefbar impliziert *nicht* (X,#,M1) pruefbar Gegenbeispiel: (Z,#,{z in Z | Ex c in Com. z=#c}) Dies ist die Menge aller Zahlen, die Goedelnummern von Programmen sind. Sie ist pruefbar (Goedelnummer "entpacken" und auf syntaktische Korrektheit pruefen). Von dieser Menge ist das Halteproblem eine Teilmenge. Und das Halteproblem ist nicht pruefbar. // Uebungsblatt #11.1e Wenn M pruefbar ist, so ist *nicht* immer ihr Komplement pruefbar! Formal: (X,#,M) pruefbar impliziert *nicht* (X,#,X-M) pruefbar Gegenbeispiel: (Com,#,{c in Com | F c (#c)!=_|_}) Die Menge aller Programme, die auf ihrer Goedelnummer konvergieren, ist pruefbar (einfach durchlaufen lassen). Die Menge aller Programme, die auf ihrer Goedelnummer divergieren, ist nicht pruefbar (Halteproblem). Alle-Eingaben-Halteproblem: Das Problem (Com,#,{c in Com | All x in Z. F c x != _|_}) Dh die Menge T aller Programme, die auf allen Eingaben terminieren. Weder (Com,#,T) noch (Com,#,Com-T) sind pruefbar. Beweis: // Uebungsblatt #11.5 Reduktionsfunktion vom Halteproblems nach Com-T: Com-T = {c in Com | Ex x in Z. F c x = _|_} Dies ist die Menge aller Programme, die auf irgendeiner Eingabe divergieren. Jedes Programm c aus dem Halteproblem laesst sich wie folgt zu einem Programm aus Com-T machen: F c (#c) = _|_ <=> (X0:=#c;c) in Com-T Wenn das Programm im Halteproblem ist, so divergiert dieses Programm. Insbesondere gibt es eine Eingabe, auf der das Programm divergiert (egal welche Eingabe, sie wird eh ueberschrieben). Also ist das Programm dann in Com-T. Wenn das Programm nicht im Halteproblem ist, so terminiert X0:=#c;c Dann gibt es aber auch keine Eingabe, auf der dieses Programm divergieren wuerde. Also ist das Programm nicht in Com-T. Also ist lambda c in Com. #c;c eine Reduktion des Halteproblems auf Com-T und Com-T ist damit nicht pruefbar. Reduktionsfunktion vom Halteproblem nach T: Sei cv ein gesteuertes universelles Kommando (dh ein Interpreter, der zusaetzlich zu Programm und Eingabe noch eine maximale Schleifenanzahl bekommt). Jedes Programm c aus dem Halteproblem laesst sich wie folgt zu einem Programm in T machen: red(c):= X0 := #(#c,#c,X0); cv; if X0=0 then skip else loop Programm--^ ^ ^--Schleifenanzahl '-----Argument (Goedelnummer von c) F c (#c) = _|_ <=> red(c) in T Wenn c im Halteproblem ist, so wird das universelle Kommando divergieren. Da es aber durch die maximale Schleifenanzahl X0 dann abgebrochen wird, landen wir also in dem "if" mit X0=0. Das Programm terminiert (egal fuer welche Eingabe) und gehoert also in T. Wenn c nicht im Halteproblem ist, so wird das universelle Kommando eine Anzahl von Schleifendurchlaeufen machen (zB 1024) und dann terminieren. Fuer X0>1024 wird das universelle Kommando dabei nicht abgewuergt und hinterlaesst das Ergebnis von c (!=0) in X0. Dann gehen wir in die Endlosschleife. Also gibt es Eingaben, fuer die das Programm nicht terminiert (naemlich alles >1024) und damit ist das Programm nicht in T. Damit existiert eine Reduktionsfunktion vom Halteproblem nach T. // Dieser Beweis ist (c) 2004-07-10 David Steurer Es folgt, dass T und Com-T nicht pruefbar sind. Sichere Lokation: Eine Lokation, die von den nachfolgenden Kommandos nicht ueberschrieben wird. Da es unendlich viele Lokationen gibt, aber ein Programm nur endlich viele Lokationen veraendern kann (da es selbst endlich ist), gibt es immer sichere Lokationen. Schreibweise: Xs X mit Index s Semantische Eigenschaft eines Programms: Eine Eigenschaft der von dem Programm berechneten Funktion. Menge der Programme fuer eine Menge an Funktionen M: Die Menge der Programme, die eine Funktion aus M berechnen. Schreibweise: prg(M):={c in Com | F c in M} Satz von Rice: Die folgende Tatsache: Sei M eine Menge von berechenbaren Funktionen Z->Z\/{_|_}. Sei die Funktion (lambda x in Z._|_) in M. Enthalte M mindestens eine berechenbare Funktion nicht. Dann ist (Com,#,{c in Com | F c in M}) nicht pruefbar. Das heisst: Man betrachtet irgendeine Klasse von Funktionen. Diese darf nicht trivial sein in zweierlei Sinn: * Die ewig divergierende Funktion (lambda x in Z._|_) ist mit dabei * Es ist mindestens eine berechenbare Funktion *nicht* dabei (sonst wuerde ja jede von irgendeinem Programm berechnete Funktion sofort zur Menge gehoeren) Dann ist nicht pruefbar, ob ein bestimmtes Programm eine Funktion dieser Menge berechnet, prg(M) ist nicht pruefbar. Beweis: Sei prg(M)={c in Com | F c in M} die fragliche Menge aller Programme, die eine Funktion aus M berechnen. Reduktionsfunktion vom Halteproblem auf (Com,#,prg(M)): Sei f eine Funktion, die nicht in M ist. Sei cf ein Programm, welches f berechnet. Jedes Programm c im Halteproblem laesst sich wie folgt zu einem Programm in prg(M) machen: Xs:=X0; X0:=#(#c,#c); cu; X0:=Xs; cf Wenn c im Halteproblem ist, so divergiert das Universelle Kommando cu auf #(#c,#c). Also berechnet das Programm die Funktion (lambda x in Z._|_) und diese ist nach Vorraussetzung in M. Wenn c nicht im Halteproblem ist, so terminiert cu. In diesem Fall berechnet das Programm die Funktion f und diese ist nicht in M. Wir kennen also jetzt 2 Methoden, um zu zeigen, dass eine Menge von Programmen nicht pruefbar ist: * Eine Reduktionsfunktion vom Halteproblem auf die fragliche Menge finden * Zeigen, dass die fragliche Menge nach Rice nicht pruefbar ist, d.h. das die Menge der berechneten Funktionen (lambda x in Z._|_) enthaelt und dass es mindestens eine berechnenbare Funktion gibt, die nicht in dieser Menge ist. Fuer Mengen, die etwas anderes als Programme enthalten, kommt nur die Reduktionsfunktion vom Halteproblem in Frage. Beispiele: siehe Satz der unpruefbaren Divergenz Heuristik der unpruefbaren Divergenz: Die Ueberlegung, dass die Menge der Programme, die eine Funktion berechnen, die fuer irgendein Argument divergiert, wahrscheinlich eher nicht pruefbar ist. Dies schliesst die folgenden Faelle ein: Die zu folgenden Mengen gehoerenden Probleme sind nicht pruefbar: // Uebungsblatt #11.2b X1={c in Com | Ex z in Z. F c z = _|_} Beweis per Reduktion: F c (#c) = _|_ <=> (X0:=#c;c) in X1 Beweis per Rice: Die Funktionenmenge ist {f | Ex z in Z. f z = _|_} (lambda x._|_) ist in X1 (lambda x.1) ist in BF-X1 // Uebungsblatt #11.2a X2={c in Com | All z in Z. F c z = _|_} Beweis per Reduktion: Beweis per Rice: Die Funktionenmenge ist {f | All z in Z. f z = _|_} (lambda x._|_) ist in X1 (lambda x.1) ist in BF-X1 X3={c in Com | F c a = _|_} fuer ein a in Z Beweis per Reduktion: F c (#c) = _|_ <=> (X0:=#c;c) in X3 Beweis per Rice: Die Funktionenmenge ist {f | f a = _|_} (lambda x._|_) ist in X1 (lambda x.1) ist in BF-X1 // Die obigen Beweise sind in der Tat jedesmal voellig // gleich (kein Druckfehler) X4={(c,x) in Com*Z | F c x = _|_} Beweis per Reduktion: F c (#c) = _|_ <=> (c,#c) in X4 X5={(c,x) in Com*Z | F c x = _|_ & x<0} Beweis per Reduktion: F c (#c) = _|_ <=> (neg(X0);c,-#c) in X5 wobei neg(X0) ein Programm ist, welches X0 negiert. X6={c in Com | {x in Z | F c x != _|_} endlich} Dies ist die Menge aller Programme, die nur fuer endlich viele Eingaben terminieren und ansonsten divergieren. Beweis per Reduktion: F c (#c) = _|_ <=> (c,#c) in X6 // Uebungsblatt #11.2c X7 = {c in Com | F c 0 != 7 } Dies ist die Menge aller Programme, die auf 0 etwas ungleich 7 liefern (also unter anderem auch divergieren koennen). Beweis per Reduktion: F c (#c) = _|_ <=> (X0:=#c;c) in X7 Beweis per Rice: Die Funktionenmenge ist {f | f 0 != 7} (lambda x._|_) ist in X1 (lambda x.7) ist in BF-X1 // Uebungsblatt #11.2d X8 = {c in Com | All x in Z. F c x in {1, _|_} } Dies ist die Menge aller Programme, die immer divergieren oder 1 liefern. Beweis per Reduktion: F c (#c) = _|_ <=> (X0:=#c;c;X0:=42) in X8 Beweis per Rice: Die Funktionenmenge ist {f | All x in Z. f x in {1,_|_} } (lambda x._|_) ist in X1 (lambda x.7) ist in BF-X1 // Uebungsblatt #11.3 X9 = { x in Z | F (X0:=#(X0,X0);cu) x = _|_ } Dies ist die Menge aller Zahlen, welche Goedelnummern von Programmen im Halteproblem sind. Beweis per Reduktion: F c (#c) = _|_ <=> #c in X9 // Uebungsaufgabe #8.1d Von einer Masterarbeit, deren Ziel es ist, ein Programm zu schreiben, welches Divergenz entscheidet, wird abgeraten. Die Arbeit koennte nicht terminieren. Satz der semantischen Unentscheidbarkeit: Die Tatsache, dass das Problem (Com,#,{c in Com | F c in M}) M beliebige Menge von Funktionen, M != BF unentscheidbar ist. Dh es kann nicht entschieden werden, ob ein Programm eine Funktion einer gegebenen Menge M von Funktionen berechnet. Fuer den Spezialfall der einelementigen Menge {f} heisst das: Es kann nicht entschieden werden, ob ein Programm f berechnet. Intuition: Das ist schon eine maechtige Behauptung. Natuerlich kann in den meisten Faellen *geprueft* werden, ob ein Programm eine Funktion berechnet (einfach laufen lassen und gucken, ob das richtige rauskommt). Aber es kann nicht *entschieden* werden. Denn wenn das Programm endlos laeuft, kann man nicht abbrechen und behaupten, es wuerde die Funktion nicht berechnen -- es kann ja sein, dass es in der naechsten Sekunde mit dem richtigen Ergebnis terminiert haette. Beweis: Da M!=BF ist die ewig divergierende Funktion (lambda x in Z._|_) entweder in M oder in BF-M. Damit ist nach Rice entweder prg(M) nicht pruefbar oder prg(BF-M) nicht pruefbar. Also ist prg(M) unentscheidbar. Beispiel: Hat man eine Menge berechenbarer Funktionen und will zeigen, dass die Menge der zugehoerigen Programme unentscheidbar ist, so muss man lediglich zeigen, dass es eine berechenbare Funktion ausserhalb der Menge gibt. X1={c in Com | F c a = b} fuer zwei Zahlen a und b Beweis: Die Funktion (lambda x in Z.b-1) ist nicht in X1. Satz der Unpruefbarkeit der Programmaequivalenz: Die Tatsache, dass das Problem (Com,#,{(c1,c2) in Com^2 | F c1 = Fc2}) nicht pruefbar ist. Dh die Menge der Paare aus aequivalenten Programmen, also die Relation der Programmaequivalenz, ist nicht pruefbar. Beweis: Reduktionsfunktion vom Halteproblem auf die fragliche Menge: Zu jedem Programm c im Halteproblem kann wie folgt ein Element der obigen Menge erzeugt werden: (X0:=#(#c,#c);cu , loop) Ist c im Halteproblem, so terminiert das universelle Kommando cu auf #(#c,#c) nicht und ist somit aequivalent zu loop -- und andersrum. Mit anderen Worten: Liesse sich Programmaequivalenz pruefen, so liesse sich pruefen, ob ein bestimmtes Programm auf seiner Goedelnummer terminiert, indem man fragt, ob das Programm aequivalent zu loop ist. Church-Turing-These: Die These, dass der intuitive Begriff der Berechenbarkeit mit dem der Informatik uebereinstimmt. Also: Alles, von dem man sagen wuerde, dass man es ausrechnen kann, kann man auch ausrechnen. Diese These stuetzt sich auf die Tatsache, dass alle Berechnungsmodelle, die bisher entwickelt wurden (Turingmaschinen, Kalkuele, Programme usw.) dieselbe Menge an Funktionen fuer entscheidbar und pruefbar halten. arithmetischer Ausdruck: Ein Element der folgenden formale Sprache: AE --> Variable | Konstante | AE + AE | AE * AE Variable --> irgendwelche Symbole Konstante --> die ganzen Zahlen Diese Sprache ist mit der naheliegenden Signatur und Denotation versehen. Arithmetische Formel: Ein Element der folgenden formalen Sprache: AF --> AE = AE | ~AF | AF & AF | Ex Variable . AF zuzueglich der Regeln fuer arithmetische Ausdruecke. Diese Sprache ist mit der naheliegenden Signatur und Denotation versehen. ganzzahliges Polynom: Eine Funktion in Z^n->Z, die von einem arithmetischen Ausdruck berechnet werden kann. Beispiel: f(x,y,z)=x^4*y^5 + 3*z*x^2 Loesung eines ganzzahligen Polynoms: Eine Eingabe fuer das Polynom, sodass die Ausgabe 0 ist. Hilberts 10. Problem: Das Problem (AE, #, {e in AE | Ex f in Var->Z . D e f = 0}) Sprich: Die Menge aller ganzzahligen Polynome, die eine Loesung haben. Satz von Matiyasevich: Die Tatsache, dass das Problem (AE, #, {e in AE | All f in Var->Z . D e f != 0}) nicht pruefbar ist. Das heisst: Es kann nicht geprueft werden, ob ein ganzzahliges Polynom *keine* Loesung hat. Intuition: Man kann natuerlich pruefen, ob ein Polynom eine Loesung hat. Dazu baut man ein Programm, welches alle Zahlen durchprobiert. Wenn es eine Loesung gibt, terminiert das Programm. Matiyasevich hat nun gezeigt, dass es aber kein Programm geben kann, welches prueft, ob ein Polynom *keine* Loesung hat. Wenn man einfach alle Zahlen durchprobiert, kann man ja niemals aufhoeren und sagen, dass es definitiv keine Loesung gibt. Daraus folgt, dass Hilberts 10. Problem unentscheidbar ist. // Zu dem Zeitpunkt, als Matiyasevich das bewiesen hat, hatte // Hilbert das Problem aber schon nicht mehr. Gueltige arithmetische Formel: Eine arithmetische Formel, die fuer alle Variablenbelegungen die Denotation 1 hat. Goedelscher Unvollstaendigkeitssatz: Die Tatsache, dass das Problem (AF, #, {a in AF | All f in Var->Z . D a f = 1}) nicht pruefbar ist. Dh man kann nicht pruefen, ob eine bestimmte arithmetische Formel gueltig ist. Das bedeutet * dass es keinen Algorithmus gibt, der allgemein bestimmt, ob eine arithmetische Formel gueltig ist * dass es also kein vollstaendiges Deduktionssystem fuer arithmetische Formeln geben kann * dass diese Tatsache auch fuer alle Obermengen von AF gilt, d.h. fuer alle komplexeren Formeln und Ausdruecke, wie zB Formeln ueber den reellen Zahlen und Ausdruecke mit Zahlen und den boolschen Operatoren. Beweis: Reduktionsfunktion von der Matiyasevich-Menge auf die Menge der gueltigen Formeln: Sei e ein arithmetischer Ausdruck, fuer dessen Polynom es keine ganzzahlige Loesung gibt. Dann ist ~(e=0) eine gueltige arithmetische Formel. Hat e aber eine ganzzahlige Loesung, so ist ~(e=0) eine ungueltige Formel. Da die Menge der arithmetischen Ausdruecke mit ganzzahliger Loesung nach Matiyasevich nicht pruefbar ist, ist auch die Menge der gueltigen arithmetischen Formeln nicht pruefbar. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Praedikatenlogik ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // In diesem Abschnitt werden die Quantoren durch eine algebraische // Struktur eingefuehrt. Quantifizierte Boolsche algebraische Struktur: Die boolsche algebraische Struktur, erweitert um das Typsymbol X und die Funktionssymbole All : (X -> B)->B Ex : (X -> B)->B und um folgende Axiome (die sogenannten Quantoraxiome, QAx): Instantiierung (AllI): All f = (All f) & (f x) Dh. wenn eine parametrisierte Aussage f fuer alle Argumente gilt, dann gilt sie auch fuer ein beliebiges Argument x. Die Schreibweise des Axioms A = A & B heisst nichts anderes als A => B Elimination (AllE): All x.u = u Dh. wenn eine Variable nicht in einem Term vorkommt, dann kann man einen Allquantor ueber diese Variable weglassen. Distribution (All|): All x. ((f x) | u) = (All x. f x) | u = (All f) | u Wenn u gilt, dann wird u auf der linken Seite den Ausdruck ((f x) | u) fuer jedes einzelne x wahr machen. Wenn u nicht gilt, so muss (All x. f x) gelten, damit die linke Seite wahr ist. Genau das sagt die rechte Seite. Das Axiom heisst Oder-Distribution, weil sich der All-Quantor ueber das Oder hineindistribuiert: All x. ((f x) | u) = (All x. f x) | (All x.u) = (All f) | u Quantor-DeMorgan (All~): ~All f = Ex x.~(f x) Dieses ist die Standardbeziehung zwischen den Quantoren. Anmerkung: Man haette den Existenzquantor auch aus der Signatur herauslassen koennen und spaeter als Schreibweise wie im letzten Axiom defininieren koennen. Alle bisher fuer die boolsche algebraische Struktur definierten Begriffe und Schreibweisen erweitern ihren Anwendungsbereich entsprechend auf die quantifiziert boolsche algebraische Struktur. Beispiel: // Uebungsblatt #11.4b Ex x.(x=k) = ~~Ex x.(x=k) Doppelnegation = ~All x.(x!=k) vorhergehender Beweis = ~((All x.(x!=k)) & k!=k) AllI = ~((All x.(x!=k)) & 0) Axiomatik von "=" = ~0 Dominanz = 1 Logische Axiome: Die Axiome der quantifizierten Boolschen algebraischen Struktur. Schreibweise: LAx := QAx \/ BAx Durch die Korrektheit der Gleichheitsregeln ist jede mit LAx herleitbare Gleichung universell gueltig. Standardinterpretation fuer die quantifizierte Boolsche algebraische Struktur: Eine Denotation, die die Konstantensymbole der Struktur auf die ueblichen Konstanten abbildet, also * D & = & * D ~ = ~ * D All = (lambda f in (D X)->B. f = lambda x in (D X).1) usw., s.o. Quantifizierte Formel: Ein Lambda-Ausdruck in der quantifizierten boolschen algebraischen Struktur vom Typsymbol B. Schreibweise: Man schreibt einen Punkt nach der Variable eines Quantors um auszudruecken, dass man eine oeffnende Klammer nach der Variable und eine schliessende Klammer am Ende des Terms weggelassen hat. zB All x . A & B fuer All x. (A & B) Anmerkung: Diese Schreibweise unterbleibt hier, da * nicht immer auf den ersten Blick klar ist, wo "das Ende" eines Terms ist. ZB erstreckt sich der Punkt in der folgenden Zeichenkette ueber das erste "=", nicht jedoch ueber das zweite "=": All x. x=x = 1 * Variable, ihr Typ und die quantifizierte Formel durch selektive Verwendung des Punktes nicht immer auf den ersten Blick getrennt sind. Quantorgesetze: Die folgende Menge universell gueltiger quantifizierter Formeln, die eine Obermenge der Quantoraxiome ist: Instantiierung: (AllI): All f = (All f) & (f x) (ExI): Ex f = (Ex f) | (f x) Elimination: (AllE): All x.u = u (ExE): Ex x.u = u Distribution: (All|): All x. ((f x) | u) = (All f) | u (Ex&): Ex x. ((f x) & u) = (Ex f) & u (All&): All x. ((f x) & u) = (All f) & u (Ex|): Ex x. ((f x) | u) = (Ex f) | u (All&'): All x. ((f x) & (g x)) = (All f) & (All g) (Ex|'): Ex x. ((f x) | (g x)) = (Ex f) | (Ex g) Quantor-DeMorgan: (All~): ~All f = Ex x.~(f x) (Ex~): ~Ex f = All x.~(f x) Vertauschung: (AllAll): All x. All y. f x y = All y. All x. f x y (ExEx): Ex x. Ex y. f x y = Ex y. Ex x. f x y Wegen der Eta-Regel gilt: Q f = Q x.(f x) fuer einen Quantor Q. All diese Quantorgesetze lassen sich aus LAx herleiten. Beweis: Gegeben: BAx und * (AllI): All f = (All f) & (f x) * (AllE): All x.u = u * (All|): All x. ((f x) | u) = (All x. f x) | u = (All f) | u * (All~): ~All f = Ex x.~(f x) Beweis von ExI: // Uebungsblatt #11.4c, Uebungsblatt #12.1b Ex f = Ex x. f x Eta = Ex x. ~(~(f x)) Doppelnegation = ~All x. ~(f x) All~ = ~((All x. ~(f x)) & ~(f x)) AllI = ~(All x. ~(f x)) | (f x) DeMorgan = (Ex x. (f x)) | (f x) All~ = (Ex f) | (f x) Eta Beweis von ExE: // Uebungsblatt #12.1c Ex x.u = Ex x. ~~u Doppelnegation = ~All x. ~u All~ = ~~u AllE = u Doppelnegation Beweis von Ex&: // Uebungsblatt #12.1d Ex x.((f x) & u) = Ex x.~~((f x) & u) Doppelnegation = ~All x.~((f x) & u) All~ = ~All x.(~(f x) | ~u) DeMorgan = ~((All x.~(f x)) | ~u) All| = ~(All x.~(f x)) & ~~u DeMorgan = Ex x.(f x) & u All~ = (Ex f) & u Eta Beweis von All&': All x. ((f x) & (g x)) = (f x) & (g x) (?) Beweis von Ex|': Ex x.((f x) | (g x)) = Ex x.~~((f x) | (g x)) Doppelneg = ~All x.~((f x) | (g x)) All~ = ~All x.(~(f x) & ~(g x)) DeMorgan = ~((All x.~(f x)) & (All x.~(gx))) All&' = ~(All x.~(f x)) | ~(All x.~(g x)) DeMorgan = (Ex f) | (Ex g) All~ Beweis von All&: All x. ((f x) & u)) = All x.((f x) & ((lambda x.u) x)) beta = (All f) & (All x.u) All&' = (All f) & u AllE Beweis von Ex|: Ex x. ((f x) | u)) = Ex x.((f x) | ((lambda x.u) x)) beta = (Ex f) | (Ex x.u) Ex|' = (Ex f) | u ExE Beweis von Ex~: // Uebungsblatt #11.4a, Uebungsblatt #12.1a All x.~(f x) = ~~(All x. ~(f x)) Doppelneg = ~(Ex x. f x) All~ = ~(Ex f) Eta Beweis von AllAll: (?) Beweis von ExEx: (?) Generell gilt fuer einen Quantor Q aus {Ex, All}: * Wenn A fuer alle x gilt, dann gilt es auch fuer ein spezielles (AllI) * Wenn A fuer irgendein x gilt, dann genuegt es, wenn es fuer ein spezielles gilt (ExI) * Ein Quantor ueber einem Term ohne die Variable kann weggelassen werden (QE) * Ueber "&" und "|" und variablenfremde Terme kann man Q frei raus und rein schieben (Q&, Q|) * All ueber eine Konjunktion mit derselben Variable kann man aufsplitten (All&') * Ex ueber eine Disjunktion mit derselben Variable kann man aufsplitten (Ex&') * DeMorgan vertauscht Ex und All (Regel Q~) * Dieselben Quantoren koennen untereinander vertauscht werden (Regel QQ) Beispiele: // Uebungsblatt #10.4 Ex b. All x. p(x,b) <=> ~p(x,x) = Ex b. ( (All x. p(x,b) <=> ~p(x,x)) & p(b,b) <=> ~p(b,b)) AllI = Ex b. ( (All x. p(x,b) <=> ~p(x,x)) & 0 ) BG = Ex b. ( 0 ) Dominanz = 0 ExE // Uebungsblatt #12.1e u => All f = ~u | All f Definition "=>" = All x. ((f x) | ~u) All| = All x. (u=>(f x)) Definition "=>" // Uebungsblatt #12.1f u & (Ex f) => v = ~(u & (Ex f)) | v Definition "=>" = ~u | ~(Ex f) | v DeMorgan = ~u | (All x. ~(f x)) | v Ex~ = All x. (~u | ~(f x) | v) All = All x. (u & (f x) => v) Definition "=>" // Uebungsblatt #12.1g (u => (f x)) => (u => Ex f) = (u & ~(f x)) | ~u | Ex f Bool = (u & ~(f x)) | ~u | Ex f | (f x) ExI = (u & ~(f x)) | ~(u & ~(f x)) | Ex f Bool = 1 Bool // Uebungsblatt #12.1h (u & (f x) => v) => (u & All f => v) = (u & (f x) & ~v) | ~u | ~(All f) | v Bool = (u & (f x) & ~v) | ~u | (Ex z. ~(f z)) | v All~ = (u & (f x) & ~v) | ~u | (Ex z. ~(f z)) | v | ~(f x) ExI = (u & (f x) & ~v) | ~(u & (f x) & ~v) | (Ex z. ~(f z)) Bool = 1 Bool // Uebungsblatt #12.2 Es gibt zwar All&', aber nicht All|': (All x. ((f x) | (g x))) != (All f) | (All g) zB fuer f x := (x =0)?1:0 g x := (x!=0)?1:0 Analog gibt es zwar ein Ex|', aber kein Ex&': (Ex x. ((f x) & (g x))) != (Ex f) & (Ex g) fuer dieselben Funktionen wie oben. Duale Formel einer quantifizierten Formel: Das Duale der zugrundeliegenden boolschen Formel, wobei zusaetzlich noch die Quantoren All und Ex vertauscht werden. Die Schreibweisen uebertragen sich dementsprechend. Der Dualitaetssatz behaelt seine Gueltigkeit. Praenex-Formel: Eine quantifizierte Formel, bei der alle Quantoren aussen stehen. "Aussen" ist hier im Sinne der Grammatik gemeint, d.h. es gibt in der Formel keinen Quantor, der ein Argument zu einem normalen boolschen Operator ist. Beispiel: All x. (f x & g z) ist eine Praenex-Formel (All x. f x) & (g z) nicht Zu jeder quantifizierten Formel laesst sich eine aequivalente Praenex-Formel finden. Dazu benutzt man im Wesentlichen die Quantor-DeMorgan-Gesetze und die Distributivgesetze, die einen untergeordneten Quantor nach aussen heben koennen. Die Praenex-Formel ist nicht eindeutig, da man gleiche Quantoren stets untereinander vertauschen kann. Anmerkung: Die Praenex-Formel einer quantifizierten Formel wird auch als "Praenex-Normalform" bezeichnet, was aufgrund der Uneindeutigkeit der Praenex-Form nicht ganz intuitiv ist und daher hier unterbleibt. Natuerliches Schliessen: Die folgende operationale Definition der Menge der gueltigen Formel. Sie bedeuten: Wenn die Formel oben universell gueltig ist, dann ist auch die Formel unten universell gueltig. Der Beweis der Korrektheit erfolgt durch Nachweis der Gueltigkeit der Implikation ObereFormel => UntereFormel fuer jede Regel. (AllL): A & B[x->M] => C --------------------- A & All x.B => C Beweis der Korrektheit: zu zeigen: Lax \/ {A & B[x->M] => C} |- A & All x.B => C = 1 A & All x.B => C = ~(A & All x.B) | C Definition "=>" = ~A | ~(All x.B) | C DeMorgan = ~A | (Ex x.~B) | C All~ = ~A | (Ex x.~B) | C | ~B[x->M] ExI = A & B[x->M] => C | (Ex x.~B) Definition "=>" = 1 | (Ex x.~B) Vorraussetzung = 1 Dominanz (ExR): A => B[x->M] --------------------- A => Ex x.B Beweis der Korrektheit: zu zeigen: Lax \/ {A => B[x->M]} |- A => Ex x.B = 1 A => Ex x.B = ~A | (Ex x.B) Definition "=>" = ~A | (Ex x.B) | B[x->M] ExI = A => B[x->M] | (Ex x.B) Definition "=>" = 1 | (Ex x.B) Vorraussetzung = 1 Dominanz (AllR): A=>B -------------- x nicht in FV(A) A=>All x.B Beweis der Korrektheit: zu zeigen: Lax \/ {A => B} |- A => All x.B = 1 A => All x.B = ~A | All x.B Definition "=>" = All x.(~A | B) All| = All x.(A => B) Definition "=>" = All x.1 Vorraussetzung = 1 AllE (ExL): A & B => C ------------------ x nicht in FV(A&C) A & Ex x.B => C Beweis der Korrektheit: zu zeigen: Lax \/ {A & B => C} |- A & Ex x.B => C = 1 A & Ex x.B => C = ~(A & Ex x.B) | C Definition "=>" = ~A | ~(Ex x.B) | C DeMorgan = ~A | (All x.~B) | C Ex~ = ~A | (All x.~B | C) All| = All x.~B | C | ~A All| = All x.A & B => C Definition "=>" = All x.1 Vorraussetzung = 1 AllE (Lam): A ---------- |- A=B B Beweis der Korrektheit: Folgt direkt aus Gleichungsregeln (Bool): A ---------- |- A=>B = 1 B Beweis der Korrektheit: Folgt direkt aus BAx (Done): ---------- 1 Beweis der Korrektheit: zu zeigen: Lax |- 1=1 1=1 // Leider sind nicht alle Beweise so erfreulich griffig (Cut): A=>B A & B => C ----------------------------- A => C Beweis der Korrektheit: zu zeigen: Lax \/ {A=>B} \/ {A & B => C} |- A=>C = 1 A => C = ~A | C Definition "=>" = ~A | C | 0 | 0 Identitaet = ~A | C | ~1 | ~1 Negation = ~A | C | ~(A=>B) | ~(A&B => C) Vorraussetzung = ~A | C | A&~B | A&B&~C Definition "=>" = (C,(A,1,(B,1,~C)),1) Entscheidungsbaum = ~C => ~C einzig spannende Teil, Rest ist eh 1 = ~~C | ~C Definition "=>" = 1 Komplemenz (&R): A=>B A=>C ----------------- A => B & C Beweis der Korrektheit: zu zeigen: Lax \/ {A=>B} \/ {A => C} |- A=>B & C = 1 A => B & C = ~A | B & C Definition "=>" = (~A | B) & (~A | C) Distribution = (A => B) & (A => C) Definition "=>" = 1 & 1 Vorraussetzung (und Werbung) = 1 Idempotenz (|L): A & B1 => C A & B2 => C ------------------------------ A & (B1 | B2) => C Beweis der Korrektheit: zu zeigen: Lax \/ {A&B1=>C} \/ {A&B2=>C} |- A&(B1|B2)=>C = 1 A & (B1 | B2) => C = ~A | ~(B1 | B2) | C Definition "=>" = ~A | ~B1 & ~B2 | C DeMorgan = (~A | ~B1 | C) & (~A | ~B1 | C) Distribution = (A&B1 => C) & (A&B2 => C) Definition "=>" = 1 & 1 Vorraussetzung = 1 Idempotenz Anmerkung: Es handelt sich um eine etwas abgewandelte Form des Sequentenkalkuels von Gerhard Genzen. Die verwandten Kalkuele finden sich detailliert beschrieben und in allen Facetten illustriert in "Human-oriented Theoremproving" (htp.txt, auf englisch). Fuer die Original-Regeln des Sequentenkalkuels, siehe dort den Abschnitt "Sequent calculus" und fuer das eigentliche natuerliche Schliessen den Abschnitt "Natural deduction calculus". Intuition: Hinter diesen Regeln steckt ein ganz faszinierendes Verfahren, der gegenseitigen Abhaenigkeit von quantifizierten Variablen Rechnung zu tragen. Ein ueblicher Beweis gehorcht folgendem Schema: A // zu beweisende Formel -| 1 => A (Bool) // Umwandlung in Implikation -| B => C () // Anwendung von Regeln ... /\ -| Y => Z // Herleitung einer Tautologie || -| 1 (Bool) // Reduktion auf "1" mit Bool -| (Done) // Beweis-Ende Dabei ist der Beweis von unten nach oben zu lesen: Aus dem Nichts folgt die 1. Aus 1 folgt Y=>Z usw bis schliesslich die zu beweisende Formel A folgt. Deshalb steht der Nagel andersrum. Da die Regeln des Natuerlichen Schliessens aber oben die Praemisse stehen haben und unten die Konklusion, werden die Regeln "rueckwaerts" angewandt: Wenn aus einer schon fertigen Zeile im Beweis die darunter folgende Zeile gemacht werden soll, so matcht man die bereits fertige Zeile mit dem unteren Teil einer Regel und fuegt den oberen Teil der Regel als neue Zeile hinzu. Am einfachsten sieht man das in dem obigen Beispiel an der Done-Regel: Aus der Zeile "1" wird die leere Zeile -- und "1" steht unten in der Done-Regel und die leere Zeile steht oben in der Done-Regel. Die Zeilen selber haben immer die Form einer Implikation: Man will aus den links stehenden Praemissen die rechts stehende Konklusion folgern. Meist ist die Praemisse eine Konjunktion und die Konklusion eine Disjunktion. Praemisse und Konklusion verhalten sich dual. Es gilt der Zusammenhang: A => B = ~A | B Die Praemisse ist also immer irgendwie das "Negierte", waehren die Konklusion das "positive" ist. In der Konklusion werden sich die Quantoren also wie ueblich verhalten, wohingegen in der Praemisse alles genau andersrum sein wird. Man kan zwischen den beiden per Negation hin- und herschaufeln: A & A' => B | B' ----------------------- (Bool) A' => B | B' | ~A A & A' => B | B' ----------------------- (Bool) A & A' & ~B' => B Wird die linke Seite leer, so ist sie "1", wird die rechte Seite leer, so ist sie "0". Dieses Geschaufel sorgt dafuer, dass man die Regeln immer anwenden kann, auch wenn zB bei ExR die rechte Seite nicht genau die Form "Ex x. B" hat. Inkonsequenterweise wurde bei AllL und ExL das "A &" noch dazu genommen. Im Folgenden wird nur der "Kern" der Regeln erklaert. Wenn man diesen Kern anwenden will, schreibt man einfach jedesmal noch "(Bool)" dazu, damit traegt man dann eventuellem Herumgeschaufel Rechung, welches noetig sein koennte, um die Formel auf die im Skript gegebene Form zu bringen. Die Regeln lassen sich in 4 Kategorien einteilen: * Abschwaechende Regeln: AllL: A[x->M] => B --------------------- All x.A => B Die Aussage "All x.A" ist staerker als die Aussage A fuer irgendein M. Wenn also B schon aus der schwachen Praemisse folgt, dann folgt B erst recht aus der starken Praemisse. ExR: A => B[x->M] --------------------- A => Ex x.B Die Aussage "Ex x.B" ist schwaecher als die Aussage, dass B fuer ein ganz bestimmtes x (naemlich M) gilt. Wenn man also schon die starke Konklusion folgern kann, dann kan man erst recht die schwache Konklusion folgern. Angewandt auf einen Beweis heisst das: * einen Existenzquantor rechts kann man in der naechsten Zeile "instantiieren". Der Existenzquantor verhaelt sich also "normal": Wenn die Aussage in der unteren Zeile fuer ein bestimmtes x gilt, dann impliziert dies fuer die vorhergehenden Zeile die Existenz eines solchen x. * einen Allquantor links kann man in der naechsten Zeile instantiieren * Quantifizierende Regeln AllR: A=>B -------------- x nicht in FV(A) A=>All x.B Diese Regel sorgt dafuer, dass freie Variablen implizit allquantifiziert sind: Wenn man B folgern kann und darin ein x vorkommt, fuer das sonst nichts vorausgesetzt ist (in A), dann gilt B fuer alle x. ExL: A => B --------------- x nicht in FV(B) Ex x.A => B Dual zur vorhergehenden Regel kann man links einen Existenzquantor einfuehren. Angewandt auf einen Beweis heisst das: * einen Allquantor rechts kann man einfach weglassen (wenn die Variable nicht links vorkommt) * einen Existenzquantor links kann man einfach weglassen (wenn die Variable nicht links vorkommt) * Boolsche Regeln Lam: A ---------- |- A=B B Diese Lambda-Regel erlaubt beta- und eta-Ersetzungen. Bool: A ---------- |- A=>B = 1 B Diese Regel erlaubt alle boolschen Transformationen, solange A B impliziert. Done: ---------- 1 Diese Regel dient lediglich zum Abschluss des Beweises. Im Wesentlichen kann man also alle bisher bekannten Transformationen anwenden. * Zusammenfuehrende Regeln Cut: A=>B A & B => C ----------------------------- A => C Hier wird ein ueberfluessiges B rausgeschnitten. In der Beweisfuehrung bislang fuer uns noch nicht von Bedeutung (und man kann zeigen, dass die Regel ueberfluessig ist). &R: A=>B A=>C --------------- A => B & C Anwendung im Beweis: Hat man rechts ein "&" stehen, kann man den Beweis aufsplitten in 2 Aeste, den man jeden fuer sich auf "1" zurueck fuehren muss. |L: A => C B => C ------------------------------ A | B => C Anwendung im Beweis bei Faellen, in denen links ein "|" steht. Der Beweisalgorithmus ist also wie folgt: * Obiges Schema fuer die zu beweisende Formel hinschreiben * Wiederhole * Falls sich irgendwas mit Bool und Lambda vereinfachen laesst, tue dieses (Bool, Lam) * Falls ein "|" links der Hauptoperator ist, splitte links (|L) * Falls ein "&" rechts der Hauptoperator ist, splitte rechts (&R) * Falls ein Quantor der Hauptoperator ist, ueberlege, ob man ihn * instantiieren muss oder * weglassen kann abhaengig davon, ob die Implikation von unten nach oben dann stimmt. Der Name der Regel ergibt sich aus Quantor und Seite. Dieses Beweisverfahren ist auf der rechten Seite folgendes Spiel: Wenn eine Existenz bewiesen werden soll, so finde ein x, fuer das die Aussage gilt. Wenn eine Allquantifizierung bewiesen werden soll, so beweise die Aussage fuer irgendein beliebiges, aber festes x. Auf der linken Seite geht das Spiel genau invers: Der Allquantor muss instantiiert werden und der Existenzquantor hinterlaesst eine freie Variable. Es gibt eine Menge dualer Zusammenhaenge in dieser Geschichte und auch Tricks, wie man eine Instantiierung (den einzig kreativen Teil eines solchen Beweises) solange hinauszoegern kann, bis der richtige Instantiierungsterm offensichtlich ist (siehe dazu "Human-oriented Theorem Proving" (htp.txt, auf Englisch) unter "Gamma-variable"). Beispiele: // Uebungsblatt #12.4 ~Ex x. All y. ( (h x y) <=> ~(h y y) ) -| (Ex x. All y. ( (h x y) <=> ~(h y y) )) => 0 Bool -| All y. ( (h x y) <=> ~(h y y) ) => 0 Bool,ExL y mit x instantiieren -| ( (h x x) <=> ~(h x x) ) => 0 Bool,AllL -| 0 => 0 Bool -| 1 Bool -| Done (Ex x. All y. g x y) => (All x. Ex y. g x y) -| (All y. g x y) => (All x. Ex y. g x y) Bool,ExL -| (All y. g x y) => (Ex y. g x y) Bool,AllR y mit y instantiieren -| g x y => (Ex y. g x y) Bool,AllL y mit y instantiieren -| g x y => g x y Bool,ExR -| 1 Bool -| Done (All x. (f x) & (g x)) => (All x. f x) -| (All x. (f x) & (g x)) => (f x) Bool,AllR x mit x instantiieren -| (f x) & (g x)) => (f x) Bool,AllL -| 1 Bool -| Done (All f) & (All g) => (All x. (f x) & (g x)) -| (All f) & (All g) => ((f x) & (g x)) Bool,AllR Variable mit x instantiieren -| (f x) & (All g) => ((f x) & (g x)) Bool,AllL -| (f x) & (g x) => ((f x) & (g x)) Bool,AllL -| 1 Bool -| Done (Ex x. (f x) | (g x)) => (Ex f) | (Ex g) -| ((f x) | (g x)) => (Ex f) | (Ex g) Bool,ExL Variable mit x instantiieren -| ((f x) | (g x)) => (f x) | (Ex g) Bool,ExR Variable mit x instantiieren -| ((f x) | (g x)) => (f x) | (g x) Bool,ExR -| 1 Bool -| Done (All x. All y. h x y) => (All y. All x. h x y) -| (All x. All y. h x y) => (All x. h x y) Bool,AllR -| (All x. All y. h x y) => (h x y) Bool,AllR -| (All y. h x y) => (h x y) Bool,AllL -| (h x y) => (h x y) Bool,AllL -| 1 Bool -| Done // Aeusserst faszinierend, dass man durch wechselseitiges Weglassen // von Quantoren Aussagen beweisen kann -- und noch dazu nur die // gueltigen. Hoeherstufiger Quantor: Ein Quantor vom Typ (*->B)->B, wobei '*' fuer einen Funktionstyp steht, der mehrere Argumente aus X bekommt und ein Ergebnis aus X liefert. Dieser Quantor quantifiziert also nicht ueber ein X, sondern ueber eine Funktion von Xen nach X. Beispiel: Siehe Skolemisierung Skolemisierung: Das folgende zusaetzliche Quantoraxiom: (AllEx): All x. Ex y . f x y = Ex g . All x . f x (g x) Es bedeutet: Wenn fuer alle x ein y existiert, dann gibt es eine Funktion g, die zu jedem x ebendieses y liefert. Der erste Quantor rechts ist hoeherstufig. Beispiel: All mann . Ex frau . ist_traum_frau_fuer(frau,mann) = Ex traumfrau() . All mann . ist_traum_frau_fuer(traumfrau(mann),mann) Hier liefert die Funktion "traumfrau" zu jedem Mann seine Traumfrau. // Diese Funktion ist leider nicht berechenbar, um nicht zu sagen // unberechenbar. Skolem-Formel: Eine quantifizierte Formel, bei der ganz aussen alle Existenzquantoren stehen und dann alle Allquantoren. Jede quantifizierte Formel laesst sich in eine aequivalente Skolem-Formel umwandeln, indem man zunaechst die Praenex-Formel bildet und dann so oft wie moeglich Skolemisierung anwendet. Anmerkung: Die Skolem-Formel einer quantifizierten Formel wird auch als "Skolem-Normalform" bezeichnet, was aufgrund der Uneindeutigkeit der Skolem-Form nicht ganz intuitiv ist und daher hier unterbleibt. Beispiele: // Uebungsblatt #12.3 (All f) & (Ex g) = All x. ((f x) & (Ex g)) All& = Ex y. All x. ((f x) & (g y)) Ex& // Hier haengt es stark davon ab, wierum man die Regeln anwendet // Sorum ist eindeutig leichter (Ex f) => (Ex f) = 1 // Ist schon in Skolem-Form All x. ((f x) => (Ex f) & (g x)) = All x. (~(f x) | (Ex f) & (g x)) = Definition "=>" All x. ( (~(f x) | (Ex f)) & (~(f x) | (g x))) = DeMorgan All x. ( (~(f x) | (Ex f) | (f x)) & (~(f x) | (g x))) = ExI All x. ( 1 & (~(f x) | (g x))) = Bool All x. ( (~(f x) | (g x))) u <=> (All x. (f x) => (Ex f) & (g x)) = u <=> (All x. ~(f x) | (Ex f) & (g x)) = Bool u <=> (All x. ~(f x) | ((Ex f) | (f x)) & (g x)) = ExI u <=> (All x. (f x) => (g x)) = Bool ~u & ~(All x. (f x) => (g x)) | u & (All x. (f x) => (g x)) = ~u & (Ex x. (f x) & ~(g x)) | u & (All x. (f x) => (g x)) = ~u & (Ex y. (f y) & ~(g y)) | u & (All x. (f x) => (g x)) = All x. Ex y. ~u & (f y) & ~(g y) | u & ((f x) => (g x)) All x. All y. Ex (f x y) = All x. All y. Ex z. (f x y z) = Eta Ex s. All x. All y. f x y s(x)(y) 2 AllEx All x. ( (All (f x)) => (All (g x)) ) = All x. ( (All y. f x y) => (All z. g x z) ) = Eta All x. ( ~(All y. f x y) | (All z. g x z) ) = Def => All x. ( (Ex y. ~(f x y)) | (All z. g x z) ) = All~ All x. Ex y. (~(f x y) | (All z. g x z) ) = Ex | All x. Ex y. All z. (~(f x y) | (g x z) ) = All | Ex s. All x. All z. (~(f x s(x)) | (g x z) ) = AllEx // Diese Zusammenfassung ist die laengste Datei, die ich jemals // geschrieben habe. Moege sie jemandem etwas nutzen.