Arithmetik im Automatischen Theorembeweisen

Werbung
Arithmetik im
Automatischen Theorembeweisen
Diplomarbeit
von:
Holger Trölenberg
Universität Potsdam
Institut für Informatik
Aufgabenstellung und Betreuung:
Prof. Dr. Christoph Kreitz
Inhaltsverzeichnis
1 Einleitung......................................................................................................................1
2 Stand der Technik.........................................................................................................2
2.1 Bedeutung der Arithmetik im Automatischen Beweisen.............................................2
2.2 Übersicht über Arithmetische Theorien.......................................................................2
2.2.1 Peano-Arithmetik......................................................................................................2
2.2.2 Presburger Arithmetik...............................................................................................2
2.2.3 Induktionsfreie Arithmetik........................................................................................3
2.3 Arithmetische Entscheidungsprozeduren....................................................................5
2.3.1 Elimination of Quantifiers........................................................................................5
2.3.2 Fourier-Motzkin Variable Elimination.....................................................................5
2.3.3 Omega-Test...............................................................................................................6
2.3.4 Cooper......................................................................................................................6
2.3.5 Hodes........................................................................................................................6
2.3.6 Loop Residue............................................................................................................6
2.3.7 Automatenbasierte Entscheidungsprozeduren..........................................................6
2.3.8 Sup-Inf......................................................................................................................6
2.3.9 Lineare Optimierung.................................................................................................7
2.3.10 Arith........................................................................................................................7
3 Implementierung..........................................................................................................8
3.1 Erster Schritt: Umformen in polynomielle Form......................................................10
3.2 Zweiter Schritt: Umformen in kanonische polynomielle Form.................................11
3.3 Dritter Schritt: Ersetzen der Konstanten-freien Teile durch Variablen......................13
3.4 Vierter Schritt: Konvertieren aller Relationen in Größergleich-Relationen..............15
3.5 Fünfter Schritt: Konstruieren des Graphen für den
Max-Path-Weights-Algorithmus................................................................................16
3.6 Sechster Schritt: Suchen nach einem positiven Zyklus.............................................17
4 Test der Implementierung..........................................................................................18
4.1 Test von polyRelations...........................................................................................18
4.2 Test von canRelations.............................................................................................19
4.3 Test von varRelations.............................................................................................20
4.4 Test von geRelations...............................................................................................20
4.5 Test von mpwGraph.....................................................................................................21
4.6 Test von maxPathWeights.........................................................................................21
4.7 Test von arith...........................................................................................................22
4.8 Test der Laufzeit von Arith......................................................................................23
5 Ausblick.......................................................................................................................24
Quellen............................................................................................................................25
Anhang: Quelltext der Implementierung....................................................................26
ii
Kapitel 1: Einleitung
Diese Arbeit beschäftigt sich mit dem Versuch, dem automatischen Beweiser LeanCop
Unterstützung für Arithmetik hinzuzufügen. Dazu wurde eine Entscheidungsprozedur für
eine eingeschränkte Arithmetik implementiert, um sie in LeanCop zu integrieren.
LeanCop ist ein automatischer Theorembeweiser für klassische Logik 1. Stufe, der auf dem
Konnektionskalkül von W. Bibel basiert. Er ist von Jens Otten in Prolog implementiert,
sehr kompakt und effizient. In der aktuellen Version LeanCop 2.1 unterstützt er SWIProlog, ECLiPSe Prolog und SICStus Prolog, Eingabe von Formeln im TPTP-Format
neben dem eigenen LeanCop-Format und die Generierung von lesbaren Beweisen in
verschiedenen Detailgraden. Bei der CASC-22 belegte er den 5. Platz von insgesamt 11
Theorembeweisern nach Anzahl gelöster Probleme.
Es gibt bereits viele interaktive Beweiser, die Arithmetik beherrschen, z.B. Coq, MetaPRL
und NuPRL. Seit kurzem beginnen auch automatische Beweiser, Arithmetik zu
unterstützen.
Als ersten Schritt, Arithmetik in LeanCop zu integrieren, wurde die Entscheidungsprozedur
Arith implementiert, da sie eine Arithmetik verwendet, die mächtiger ist als die Presburger
Arithmetik, welche die meisten anderen Entscheidungsprozeduren verwenden: die
Induktionsfreie Arithmetik. Nach Arith sollen weitere Entscheidungsprozeduren für andere
Arten von Arithmetik integriert werden, damit LeanCop für eine logische Formel, die
Arithmetik enthält, die passendste Prozedur auswählen und aufrufen kann. Außerdem soll
die Behandlung von Arithmetik so verbessert werden, dass auch dabei ein Beweis generiert
wird.
Kapitel 2 versucht einen Überblick über arithmetische Entscheidungsprozeduren und deren
zu Grunde liegenden arithmetischen Theorien zu geben. Kapitel 3 beleuchtet die
Implementierung der Entscheidungsprozedur Arith und Kapitel 4 deren Test. Kapitel 5
zeigt mögliche Anknüpfungspunkte für weiterführende Arbeiten auf.
[1, 2]
1
Kapitel 2: Stand der Technik
2.1 Bedeutung der Arithmetik im Automatischen Beweisen
Arithmetik hat im automatischen Theorembeweisen viele Anwendungen: z.B. in
Programmsynthese und -verifikation und Mathematik. In der Programmverifikation,
beispielsweise, lässt sich die Gültigkeit von Arrayindizes und For-Schleifenvariablen mit
Hilfe von Ungleichungssystemen prüfen.
2.2 Übersicht über Arithmetische Theorien
Gödel's Unvollständigkeitssätzen zu Folge ist die Arithmetik der natürlichen Zahlen weder
entscheidbar noch vollständig axiomatisierbar. Daher muss man sich im Automatischen
Beweisen auf entscheidbare Fragmente der Arithmetik beschränken.
Im Folgenden werden die wichtigsten Arithmetischen Theorien jeweils kurz genauer
beleuchtet.
2.2.1 Peano-Arithmetik
Die Peano-Axiome zur Definition der Menge der natürlichen Zahlen wurden 1889 von
Giuseppe Peano formal formuliert. Sie lauten (informal):
1. 0 ist eine Zahl.
2. Wenn n eine Zahl ist, so gibt es genau einen Nachfolger von n, welcher ebenso eine Zahl
ist.
3. 0 ist nicht Nachfolger einer Zahl.
4. Zwei Zahlen mit gleichem Nachfolger sind selbst gleich.
5. Die kleinste Menge, die 0 und jeden Nachfolger eines ihrer Elemente enthält, ist die
Menge der natürlichen Zahlen.
Diese Axiome beschreiben die natürlichen Zahlen, die Nachfolgeroperation, Gleichheit
und Induktion. Darauf aufbauend können Addition, Multiplikation und die KleinergleichRelation definiert werden, sodass (ℕ, +, 0, ⋅, 1, ≤) einen geordneten Halbring bildet.
Die Peano-Arithmetik enthält alle Peano-Axiome, nur das Induktionsaxiom 5 ist in
Prädikatenlogik zweiter Stufe definiert und wird durch abzählbar unendlich viele
Induktionsaxiome der Prädikatenlogik erster Stufe ersetzt, eines für jede mögliche logische
Formel:
5. Für jede logische Formel 1. Stufe P(x): P(0) ∧ ∀(x.P(x) ⇒ P(x')) ⇒ ∀x.P(x)
Peano-Arithmetik ist unentscheidbar und unvollständig und ihre Konsistenz ist nicht
nachweisbar.
[3]
2.2.2 Presburger Arithmetik
1929 erfand Mojżesz Presburger eine Arithmetik, die nach ihm benannt wurde. Sie basiert
auf der Peano-Arithmetik, beinhaltet aber nicht die Multiplikation sondern nur die
Addition. Die Axiome lauten:
1. 0 ≠ x'
2
2. x' = y' ⇒ x = y
3. x + 0 = x
4. x + y' = (x + y)'
5. Für jede logische Formel 1. Stufe P(x): P(0) ∧ ∀(x.P(x) ⇒ P(x')) ⇒ ∀x.P(x)
Wieder lassen sich die Operationen Addition, Multiplikation mit Konstanten und die
Relationen definieren.
Die Presburger Arithmetik ist schwächer als die Peano-Arithmetik, da Konzepte wie
Multiplikation, Teilbarkeit und Primzahlen mit ihr nicht formalisiert werden können. Das
macht sie entscheidbar, vollständig und konsistent.
[4]
2.2.3 Induktionsfreie Arithmetik
Die Induktionsfreie Arithmetik wurde 1978 von Constable und O'Donnell formuliert. Sie
kennt Addition, Subtraktion, Multiplikation auf ganzen Zahlen und die
Vergleichsrelationen, aber keine Induktion. Außerdem ist die Substitution auf ganze
Vergleichsoperanden einer Relation beschränkt. Das macht Induktionsfreie Arithmetik
entscheidbar.
Axiome der Induktionsfreien Arithmetik: (gelten für alle ganzen Zahlen w, x, y, z)
Gleichheitsaxiome:
Reflexivität: x = x
Symmetrie: x = y ⇒ y = x
Transitivität: x = y ∧ y = z ⇒ x = z
eingeschränkte Substitutivität: x = y ∧ x ρ z ⇒ y ρ z
für ρ ∈ {=, ≠, <, >, ≤, ≥}
x=y∧zρx⇒zρy
Axiome der Konstantenarithmetik: 1 + 1 = 2, 2 + 1 = 3, 3 + 1 = 4, …
Ringaxiome der ganzen Zahlen:
Kommutativgesetze: x + y = y + x, x ∗ y = y ∗ x
Assoziativgesetze: (x + y) + z = x + (y + z), (x ∗ y) ∗ z = x ∗ (y ∗ z)
Distributivgesetz: x ∗ (y + z) = (x ∗ y) + (x ∗ z)
Neutrale Elemente: x + 0 = x, x ∗ 1 = x
Entgegengesetztes: x + (−x) = 0
Definition der Subtraktion: x − y = x + (−y)
Axiome der diskreten linearen Ordnung:
Irreflexivität: ¬(x < x)
Trichotomie: x < y ∨ x = y ∨ y < x
Transitivität: x < y ∧ y < z ⇒ x < z
Diskretheit: ¬(x < y ∧ y < x + 1)
Definition von Ordnungsrelationen und Ungleichheiten:
3
x ≠ y ⇔ ¬(x = y)
x>y⇔y<x
x≤y⇔x<y∨x=y
x≥y⇔y<x∨x=y
Monotonieaxiome:
Addition: x ≥ y ∧ z ≥ w ⇒ x + z ≥ y + w
Subtraktion: x ≥ y ∧ z ≤ w ⇒ x − z ≥ y − w
Multiplikation: x ≥ 0 ∧ y ≥ z ⇒ x ∗ y ≥ x ∗ z
Faktorisierung: x > 0 ∧ x ∗ y ≥ x ∗ z ⇒ y ≥ z
Die Monotonoieaxiome der Addition und Subtraktion werden auch als triviale Monotonien
bezeichnet, wenn z und w Konstanten sind.
[5, 6]
4
2.3 Arithmetische Entscheidungsprozeduren
Im formalen Schließen gibt es oft Teilprobleme, die zwar einfach zu beweisen sind, für die
sich ein formaler Beweis jedoch nicht lohnt und lediglich die Aussage, ob die aufgestellte
Behauptung wahr ist oder nicht, von Interesse ist. Hier lohnt sich der Einsatz von
Entscheidungsprozeduren, die das Problem oft auf ein anderes reduzieren, um so eine
schnelle Lösung zu finden, die einem normalen Benutzer nicht unbedingt verständlich ist.
Eine Arithmetische Entscheidungsprozedur prüft die Allgemeingültigkeit einer Formel
gemäß einer bestimmten Arithmetischen Theorie. Dabei ist eine Formel eine logische
Verknüpfung von Gleichungen und Ungleichungen der Arithmetischen Theorie.
Die Prozedur muss korrekt und vollständig sein und für jede syntaktisch gültige Formel
terminieren. Die Korrektheit der Entscheidungsprozedur muss sich in der jeweiligen
Arithmetischen Theorie beweisen lassen. Da Arithmetik im Allgemeinen unentscheidbar
ist, muss sich eine Entscheidungsprozedur auf eine eingeschränkte, entscheidbare
arithmetische Theorie beschränken.
Im Folgenden werden die wichtigsten Arithmetischen Entscheidungsprozeduren
vorgestellt. Alle außer Arith (Kapitel 2.3.10) sind für die Presburger Arithmetik geeignet,
Arith für die Induktionsfreie Arithmetik.
[7]
2.3.1 Elimination of Quantifiers
Bei diesem Verfahren werden nacheinander alle Quantoren und ihre quantifizierten
Variablen entfernt, indem jeweils die logische Formel durch eine äquivalente Formel ohne
den Quantor ersetzt wird. Das ursprünglich von G. Kreisel und J. L. Krevine entwickelte
Verfahren ist sehr ineffizient, da sich die Formel dabei stark vergrößert.
Diese Idee wird u.a. verwendet in den Entscheidungsprozeduren Fourier-Motzkin Variable
Elimination, Omega-Test, Cooper und Hodes.
[8, 9, 10]
2.3.2 Fourier-Motzkin Variable Elimination
Dieses Verfahren zum Lösen von Ungleichungssystemen mit rationalen oder reellen
Variablen wurde 1826 von Joseph Fourier entwickelt und 1936 von Theodore Motzkin
wieder entdeckt. Eine Variable nach der anderen kann eliminiert werden, bis das
Ungleichungssystem nur noch Konstanten enthält, deren Vergleich trivial ist.
Um eine Variable x zu eliminieren, werden die Ungleichungen so umgeformt, dass die
Variable mit einem positiven Koeffizienten allein auf einer Seite steht, und dann in drei
Gruppen eingeteilt: m Ungleichungen der Form aix ≥ Ai, n der Form bix ≤ Bi (ai, bi > 0) und
k Ungleichungen, die x nicht enthalten. Es gilt max
{ ∣1≤i≤m }≤x≤min { ∣1≤i ≤n }.
Ai
ai
Bi
bi
Das Ungleichungssystem ist äquivalent zu dem mit den m⋅n Ungleichungen bj⋅Ai ≤ ai⋅Bj für
alle 1 ≤ i ≤ m, 1 ≤ j ≤ n und den k Ungleichungen ohne x. In diesem Ungleichungssystem
kommt x nicht mehr vor. Mit Hilfe obiger Formel kann eine erfüllende Variablenbelegung
rekonstruiert werden.
[11]
5
2.3.3 Omega-Test
Der Omega-Test in eine Erweiterung der Fourier-Motzkin Variable Elimination auf die
Arithmetik der ganzen Zahlen. Er wurde 1991 von William Pugh entwickelt.
[12]
2.3.4 Cooper
Diese Entscheidungsprozedur basiert auf Elimination of Quantifiers, ist aber effizienter, da
die Formel nicht in disjunktive Normalform transformiert werden muss und im weiteren
Verfahren deutlich weniger wächst. Sie wurde 1972 von D. C. Cooper entwickelt. Laut D.
2
Oppen (1975) hat der Algorithmus im Worst-Case mit O 22  die bestmögliche,
n
erreichbare Zeitkomplexität.
[8, 10]
2.3.5 Hodes
Auch diese Entscheidungsprozedur basiert auf Elimination of Quantifiers, verwendet
jedoch Presburger Arithmetik mit reellen Zahlen. Sie wurde 1971 von Louis Hodes
entwickelt.
[9]
2.3.6 Loop Residue
Dieses Verfahren wurde 1981 von Robert Shostak entwickelt und entscheidet Presburger
Arithmetik mit reellen Variablen. Es ist eine Erweiterung eines Verfahrens von V. R. Pratt,
1971, das ein System von Ungleichungen mit jeweils höchstens zwei Variablen in einen
Graphen umformt und bestimmte Kreise (Loops) sucht, die das Ungleichungssystem
widerlegen. Das Loop Residue-Verfahren kann darüber hinaus Ungleichungen mit beliebig
vielen Variablen und beliebigen Koeffizienten verarbeiten.
[13]
2.3.7 Automatenbasierte Entscheidungsprozeduren
Diese Klasse von Entscheidungsprozeduren basiert auf endlichen Automaten. Für
Arithmetik mit reellen Variablen werden Büchli-Automaten benötigt, die ω-Sprachen, also
Sprachen mit unendlichen Wörtern, akzeptieren.
[14]
2.3.8 Sup-Inf
Diese Entscheidungsprozedur für existenzquantorenfreie Presburger Arithmetik wurde
1975 von W. W. Bledsoe entwickelt und 1977 von Robert E. Shostak verbessert. Sie
verwendet zwei Algorithmen SUP und INF, die das Supremum (kleinste untere Schranke)
und das Infimum (größte obere Schranke) der möglichen Belegungen der Variablen in der
negierten Formel ermitteln. Die Formel ist gültig genau dann, wenn für eine der Variablen
das Supremum kleiner als das Infimum ist, genau dann, wenn es keine erfüllende Belegung
für die negierte Formel gibt.
[10]
6
2.3.9 Lineare Optimierung
Ungleichungssysteme in Presburger Arithmetik lassen sich auch als Probleme der linearen
Optimierung darstellen. Dadurch lassen sich Lösungsverfahren für lineare Optimierung
auch als Entscheidungsprozeduren verwenden.
2.3.10 Arith
Diese Entscheidungsprozedur für Induktionsfreie Arithmetik wurde 1982 von T. Chan
entwickelt, um die Korrektheit eines logischen Schlusses über Arithmetik zu prüfen.
Arith prüft, ob eine gegebene Konklusion, die eine Disjunktion arithmetischer Relationen
ist, aus einer gegebenen Hypothese folgt, die eine Konjunktion arithmetischer Relationen
ist. Alle sechs Vergleichsrelationen (=, ≠, <, >, ≤, ≥) werden akzeptiert; alle Variablen sind
implizit allquantifiziert. Arith führt im Groben die folgenden Schritte durch, um zu prüfen
ob Hypothese ⇒ Konklusion gültig ist:
Zuerst werden alle Relationen der Konklusion negiert und der Hypothese hinzugefügt, so
dass nun die Widersprüchlichkeit der Hypothese zu zeigen ist. Dann werden alle
arithmetischen Terme in eine polynomielle Normalform umgeformt. Jeder Term hat dann
die Form c oder c + T oder T, wobei c eine Konstante ist und T ein beliebiges Polynom (in
mehreren Variablen) ohne ein konstantes Glied. Dann werden alle konstantenfreien Terme
T durch neu eingeführte Variablen ersetzt, gleiche Terme durch gleiche Variablen. Nun
werden alle Gleichungen und Ungleichungen zuerst in Größergleich-Ungleichungen und
dann in gewichtete Kanten eines gerichteten Graphen umgeformt. Die Konjunktion der
Größergleich-Ungleichungen ist genau dann widersprüchlich, wenn der Graph einen Kreis
besitzt, dessen Kantengewichte eine positive Summe ergeben.
Der Schwachpunkt der Prozedur ist, dass für viele Probleme die nichttrivialen
Monotonieaxiome benötigt werden (siehe 2.2.3), welche von dem geschilderten Verfahren
nicht berücksichtigt werden. Beispielsweise lässt sich der Schluss x = 3 ∧ y = 2 ⇒
x + y = 5 nicht prüfen, da der Term x + y durch eine neue Variable ersetzt wird und eine
Anwendung des Monotonieaxioms für Addition nötig wäre. Aus diesem Grund wird Arith
in erster Linie in interaktiven Beweisentwicklungssystemen eingesetzt, beispielsweise in
MetaPRL und NuPRL.
[6, 15]
7
Kapitel 3: Implementierung
Dieses Kapitel beschäftigt sich mit der Implementierung der Entscheidungsprozedur Arith
für Induktionsfreie Arithmetik entsprechend [6]. Auf Optimierungen, wie z.B. die dort
beschriebene Prozedur nodesat, wurde dabei zugunsten der leichten Verständlichkeit
verzichtet.
Als Implementierungssprache wurde Prolog gewählt, da sich die Prozedur so
unkompliziert in LeanCop, welches hauptsächlich in Prolog geschrieben wurde, integrieren
lässt.
Die Hauptprozedur arith nimmt als Eingabe eine Hypothese und eine Konklusion, die
jeweils Listen von Relationen sind. Die Hypothese repräsentiert die Konjunktion ihrer
Relationen und die Konklusion die Disjunktion der ihren. Die möglichen
Relationsoperatoren sind die Prolog-Operatoren =, \=, <, >, =< und >=. Deren Operanden
werden als Komparanden bezeichnet und dürfen so aufgebaut sein, wie in Kapitel 3.1
beschrieben.
liefert true, falls die Disjunktion der eingegebenen Konklusionsrelationen in der
Induktionsfreien Arithmetik aus der Konjunktion der Hypothesenrelationen folgt.
Sämtliche Variablen werden dabei als implizit all-quantifiziert behandelt.
arith
% arith(+Hypothesis, +Conclusion)
arith(Hypothesis, [Pos|Conclusion]) :!, negRelation(Pos, Neg), arith([Neg|Hypothesis], Conclusion).
arith(Relations, []) :\+((
polyRelations(Relations, PolyRelations),
canRelations(PolyRelations, CanRelations),
varRelations(CanRelations, VarRelations, Vars, Const),
geRelations(VarRelations, GERelations),
N is Vars+Const, mpwGraph(GERelations, N, Graph),
maxPathWeights(Graph, N)
)).
% negRelation(+Positive, -Negative)
negRelation(A=B, A\=B).
negRelation(A\=B, A=B).
negRelation(A<B, A>=B).
negRelation(A>B, A=<B).
negRelation(A=<B, A>B).
negRelation(A>=B, A<B).
Zuerst werden nacheinander alle Relationen der Konklusion entfernt, mit Hilfe der
Prozedur negRelation negiert und der Hypothese hinzugefügt, wodurch die repräsentierte
Aussage logisch äquivalent bleibt. Dann wird geprüft, ob die entstehende Konklusion
widersprüchlich, also nicht gültig ist. Die Prüfung auf Gültigkeit erfolgt in sechs Schritten,
die durch separate Prozeduren behandelt und von der Hauptprozedur nacheinander
aufgerufen werden.
Die ersten drei Schritte dienen dazu, die Relationen Schritt für Schritt umzuformen und
sind nach demselben Grundschema konzipiert:
Ein- und Ausgabe sind jeweils eine Liste von Relationen. Rekursiv wird eine Relation nach
der anderen weitestgehend unabhängig von den anderen Relationen umgeformt.
Dazu wird zuerst die Relation in linken Komparand, Operator und rechten Komparand
8
zerlegt. Dann werden beide Komparanden nacheinander umgeformt und dann wieder mit
demselben Operator zur Ergebnis-Relation zusammengesetzt.
Dieses Vorgehen lässt sich im Quellcode des ersten Schritts gut erkennen:
% polyRelations(+Relations, -PolynomialRelations)
polyRelations([], []).
polyRelations([Rel|Relations], [Poly|PolyRelations]) :Rel=..[Op, RelA, RelB],
polyComparand(RelA, PolyA), polyComparand(RelB, PolyB),
Poly=..[Op, PolyA, PolyB],
polyRelations(Relations, PolyRelations).
9
3.1 Erster Schritt: Umformen in polynomielle Form
Im ersten Schritt werden alle Komparanden durch Anwendung des Distributivgesetzes von
Addition und Multiplikation in eine polynomielle Form umgeformt. Die Prozedur
polyRelations (Quelltext siehe oben) implementiert diesen Schritt.
Der Eingabekomparand darf sich auf beliebige Weise aus Variablen, ganzen Zahlen,
Atomen und den Operationen Addition, Subtraktion, Multiplikation und den beiden
einstelligen Vorzeichenoperationen zusammensetzen. Dabei werden die Prolog-Operatoren
+, - und * verwendet. Andere Rechenoperationen werden von dieser Implementierung
nicht unterstützt. Potenzen mit natürlichen Exponenten sollten daher als Produkt dargestellt
werden. Beispiel: X3 sollte als X*X*X dargestellt werden. Atome werden wie Variablen
behandelt.
Der Ausgabekomparand ist eine Summe von Produkten. Die Produkte werden erst im
nächsten Schritt vereinfacht und bleiben zunächst so, wie sie durchs „Ausmultiplizieren“
entstehen. Beispiel: 3*X*Y+3*(-1*1)*Y+4*Y+Y*2
Die Prozedur polyComparand leistet die eigentliche Umformung eines Komparanden:
Bei Multiplikation und Addition werden zunächst die beiden Operanden rekursiv
umgeformt, bei der Multiplikation werden dann die entstehenden Polynome miteinander
multipliziert. Dazu multipliziert die Prozedur polyLeftMult zuerst das rechte Polynom
mit den Gliedern des linken und danach die Prozedur polyRightMult jeweils ein linkes
Glied mit den rechten.
Die Subtraktion wird auf Addition des Entgegengesetzten abgebildet, das negative
Vorzeichen auf Multiplikation mit −1 und das positive Vorzeichen wird ignoriert.
Ein Term, der nur aus einer Variable besteht, muss in der ersten Klausel behandelt werden,
weil er sonst mit allem unifizieren würde, und bleibt unverändert. Alle anderen Terme, also
Zahlen und Atome, – aber auch alle unerwarteten Terme – werden in der letzten Klausel
behandelt. Damit nicht jeder Term nach Backtracking auch mit der letzten Klausel
unifizieren kann, haben alle vorigen Klauseln Cuts.
% polyComparand(+Term, -Polynomial)
polyComparand(Var, Var) :- var(Var), !.
polyComparand(A*B, P) :- !, polyComparand(A, P1),
polyComparand(B, P2), polyLeftMult(P1, P2, P).
polyComparand(A+B, P1+P2) :- !, polyComparand(A, P1),
polyComparand(B, P2).
polyComparand(A-B, P) :- !, polyComparand(A+(-B), P).
polyComparand(-A, P) :- !, polyComparand((-1)*A, P).
polyComparand(+A, P) :- !, polyComparand(A, P).
polyComparand(Atom, Atom).
% polyLeftMult(+P1, +P2, -P)
polyLeftMult(Var, R, P) :- var(Var), !, polyRightMult(Var, R, P).
polyLeftMult(A+B, R, P1+P2) :- !, polyLeftMult(A, R, P1),
polyLeftMult(B, R, P2).
polyLeftMult(Monomial, R, P) :- polyRightMult(Monomial, R, P).
% polyRightMult(+Factor, +P2, -P)
polyRightMult(L, Var, L*Var) :- var(Var), !.
polyRightMult(L, A+B, P1+P2) :- !, polyRightMult(L, A, P1),
polyRightMult(L, B, P2).
polyRightMult(L, Monomial, L*Monomial).
10
Der im Quelltext verwendete Begriff „Monomial“, zu deutsch Monom, bezeichnet ein
Glied eines Polynoms, also ein Produkt aus Potenzen von Variablen und einem optionalen
Koeffizienten. Beispiel: 3ab2. In dieser Implementierung kommen jedoch ggf. mehrere
Zahlen aber keine Potenzen in den „Monomials“ vor, wie bereits in einem Beispiel
demonstriert.
3.2 Zweiter Schritt: Umformen in kanonische polynomielle Form
Im zweiten Schritt werden die polynomiellen Komparanden zu einer kanonischen Form
vereinfacht, indem die in den Gliedern enthaltenen Zahlen zu Koeffizienten und Glieder
mit gleichen Variablen zusammengefasst werden und alles sortiert wird. Der Quelltext der
Prozedur canRelations folgt wieder dem Grundschema und ist im Anhang zu finden.
Der Eingabekomparand muss eine Summe von Produkten sein, wie im ersten Schritt
beschrieben.
Der Ausgabekomparand ist ebenfalls eine Summe von Gliedern, die Produkte sind, wobei
jedoch kein Glied 0 ist, höchstens der erste Faktor eines Glieds eine Zahl sein kann – als
Koeffizient – und keine zwei Glieder abgesehen vom Koeffizienten gleich sind. Außerdem
sind in jedem Glied alle Faktoren (hinter dem Koeffizienten) sortiert, so dass gleiche
Faktoren aufeinander folgen, und die Glieder sind ebenfalls sortiert, so dass ein Glied, das
nur aus einer Zahl besteht, ganz links steht. Letzteres ist für den dritten Schritt von
Bedeutung. Beispiel: 2+3*X*Y+(-3)*Y+6*Y*Y
Die Prozedur canComparand formt einen Komparanden um:
% canComparand(+Polynomial, -CanonicalComparand)
canComparand(Poly, Can) :canPoly2List(Poly, PL), msort(PL, SPL), canList2Can(SPL, Can2),
(Can2==[] -> Can=0 ; Can=Can2).
Dazu wird der Komparand zunächst in eine Liste von Monomen umgewandelt, diese dann
sortiert und wieder zurück in ein kanonisches Polynom umgewandelt. Die letzte Zeile
behandelt den Spezialfall, dass sich der gesamte Komparand zu 0 vereinfacht.
Die Umwandlung von Polynom in Liste von Monomen übernehmen die Prozeduren
canPoly2List und canMonEnc:
% canPoly2List(+Polynomial, -Monomials)
canPoly2List(Var, [monomial([Var], 1)]) :- var(Var), !.
canPoly2List(A+B, P) :!, canPoly2List(A, PA), canPoly2List(B, PB), append(PA, PB, P).
canPoly2List(Monomial, [monomial(CM, C)]) :canMonEnc(Monomial, M, C), msort(M, CM).
% canMonEnc(+Monomial, -Factors, -Coefficient)
canMonEnc(Var, [Var], 1) :- var(Var), !.
canMonEnc(A*B, M, C) :!, canMonEnc(A, MA, CA), canMonEnc(B, MB, CB), append(MA, MB, M),
C is CA*CB.
canMonEnc(N, [], N) :- number(N), !.
canMonEnc(Factor, [Factor], 1).
canPoly2List durchläuft das Polynom rekursiv und kodiert jedes Glied mit Hilfe von
canMonEnc zu einem Monom in der Form monomial(Factors, Coefficient), wobei
Coefficient der Koeffizient und Factors eine sortierte Liste aller anderen Faktoren ist.
Für die Addition werden zunächst beide Operanden rekursiv in Monomlisten umgewandelt
11
und diese dann konkateniert. Wie schon bei polyComparand behandelt die erste Klausel
Variablen und die letzte Glieder und alles unerwartete; wieder werden Cuts verwendet. Die
letzte Klausel kodiert ein Glied mit Hilfe von canMonEnc und sortiert seine Faktoren.
durchläuft ein Glied rekursiv analog zu canPoly2List und unterscheidet
zwischen Zahlen und anderen Faktoren. Die Zahlen werden von den anderen Faktoren
getrennt, zu einem Koeffizienten multipliziert und rekursiv zurück gereicht. Die anderen
Faktoren werden in Listen konkateniert.
canMonEnc
Zum Sortieren der Faktoren innerhalb eines Glieds und der kodierten Monome wird eine
vordefinierte Prolog-Prozedur verwendet. Damit die Monome nicht nach Koeffizienten
sondern nach den anderen Faktoren sortiert werden, steht in der Monomkodierung die
Liste der Faktoren vor dem Koeffizienten.
Die Rückumwandlung von der Liste von Monomen in ein kanonisches Polynom wird von
den Prozeduren canList2Can und canMonDec geleistet:
% canList2Can(+Monomials, -CanonicalTerm)
canList2Can([], []).
canList2Can([monomial(M1, C1), monomial(M2, C2)|SPL], Can) :M1==M2, !, C is C1+C2, canList2Can([monomial(M1, C)|SPL], Can).
canList2Can([monomial(_, 0)|SPL], Can) :- !, canList2Can(SPL, Can).
canList2Can([monomial(M, C)|SPL], Can) :- canMonDec(M, C, CM),
canList2Can(SPL, Can2), (Can2==[] -> Can=CM ; Can=CM+Can2).
% canMonDec(+Factors, +Coefficient, -Monomial)
canMonDec(_, 0, 0) :- !.
canMonDec([], C, C).
canMonDec([F, F2|M], 1, F*CM) :- !, canMonDec([F2|M], 1, CM).
canMonDec([F], 1, F) :- !.
canMonDec([F|M], C, C*CM) :- canMonDec([F|M], 1, CM).
durchläuft die Liste der Monome von links nach rechts und wandelt sie in
eine rechtsassoziative Summe um. Ein konstantes Monom, das also außer einem
Koeffizienten keine anderen Faktoren hat, wurde zuvor beim Sortieren links eingeordnet
und landet so in der kanonischen Form links außen, was für den dritten Schritt von
Bedeutung ist.
canList2Can
Monome, die abgesehen von ihren Koeffizienten gleich sind, folgen auf Grund der
Sortierung aufeinander und werden entsprechend dem Distributivgesetz zusammengefasst.
Ein Monom mit Koeffizient 0 wird ignoriert und eines mit einem anderen Koeffizienten
wird dekodiert und rekursiv mit allen folgenden in eine Summe umgewandelt.
Das Dekodieren der Monome leistet canMonDec, indem die Liste der Faktoren und der
Koeffizient rekursiv in ein Produkt umgewandelt werden. Zuerst wird in der letzten
Klausel der Koeffizient verwendet (sofern er nicht 1 ist), dann werden die anderen
Faktoren der Reihe nach rekursiv als rechtsassoziatives Produkt nachgestellt, wofür der
Koeffizient nun auf 1 gesetzt ist und ignoriert wird.
Die erste Klausel für Monome mit Koeffizient 0 wird nicht verwendet und ist nur der
Vollständigkeit halber implementiert.
12
3.3 Dritter Schritt: Ersetzen der Konstanten-freien Teile durch Variablen
Nach dem zweiten Schritt haben die Komparanden die Form c+T, c oder T, wobei c eine
Konstante – also eine ganze Zahl – ist und T ein beliebiges Polynom, das keine Konstanten
enthält. T ist ein Konstanten-freier Teil. Im dritten Schritt wird jeder Konstanten-freie Teil
aller Komparanden durch neue Variablen ersetzt, wobei gleiche Teile auch durch gleiche
Variablen ersetzt werden.
Der Eingabekomparand muss in kanonischer Form sein, wie im zweiten Schritt
beschrieben.
Der Ausgabekomparand hat die Form N+v(V), N oder v(V), wobei N eine ganze Zahl und V
eine positive ganze Zahl ist.
Auch die Prozedur varRelations folgt dem Grundschema, sie liefert jedoch zusätzlich
noch die Anzahl der eingeführten Variablen zurück und ob es Komparanden der Form c
bzw. N, also ohne variablen Teil, gibt:
% varRelations(+CanonicalRelations, -VarReplacedRelations,
%
-NVariables, -Constants)
varRelations(CanRelations, VarRelations, Vars, Const) :varRelations(CanRelations, VarRelations, [], Vars, Const).
varRelations([], [], [], 0, 0) :- !.
varRelations([], [], [subst(_, Vars)|_], Vars, 0).
varRelations([Can|CanRelations], [Var|VarRelations], Substitutions,
Vars, Const) :Can=..[Op, CanA, CanB],
varSubst(CanA, VarA, Substitutions, SubsA, CA),
varSubst(CanB, VarB, SubsA, SubsB, CB),
Var=..[Op, VarA, VarB],
varRelations(CanRelations, VarRelations, SubsB, Vars, C),
Const is C\/CA\/CB.
In einem zusätzlichen Parameter wird die anfangs leere Liste der Variablensubstitutionen
von der Behandlung des linken Operanden zu der des rechten und rekursiv weitergereicht
und dabei nach Bedarf erweitert. Ein konstanten-freier Term wird durch einen Term der
Form v(N) ersetzt, wobei N eine natürliche Zahl ist. Die neuen Variablen werden in der
Reihenfolge ihrer Einführung bei 1 beginnend durchnummeriert. Für jeden neuen ersetzten
Term wird eine entsprechende Variablensubstitution der Form subst(Term, N) der Liste
vorangestellt.
Die Anzahl der Variablen wird am Ende der Rekursion anhand der Nummer der ersten
Substitution in der Liste ermittelt, die auch der Anzahl der Substitutionen entspricht, und
rekursiv bis zum ursprünglichen Aufruf zurück gereicht. Ob konstante Komparanden
vorkommen, wird bei der Behandlung der einzelnen Komparanden durch die Prozedur
varSubst ermittelt und mit Hilfe bitweiser Disjunktion rekursiv zurück gereicht. Dabei
steht 1 für wahr und 0 für falsch.
% varSubst(+CanonicalComparand, -VarReplacedTerm, +SubstitutionsIn,
%
-SubstitutionsOut, -Constant)
varSubst(N, N, S, S, 1) :- number(N), !.
varSubst(N+C, N+V, S, S2, 0) :- number(N), !,
varSubst(C, V, S, S2, _).
varSubst(C, v(V), S, S, 0) :- member(subst(D, V), S), C==D, !.
varSubst(C, v(V), S, [subst(C, V)|S], 0) :- length([_|S], V).
varSubst
belässt konstante Komparanden unverändert und liefert dabei 1 in Constant
13
zurück. Bei Komparanden mit Konstante und variablem Teil, bleibt die Konstante
unverändert und der Variable Teil wird genau so behandelt, wie ohne Konstante; in
Constant wird 0 zurückgeliefert.
Bei einem variablen Teil wird zunächst die Liste der Substitutionen nach diesem Term
durchsucht und bei Erfolg die entsprechende Variablennummer verwendet. Dabei muss die
nicht unifizierende Gleichheit verwendet werden, da z.B. Terme, die nur aus einer PrologVariable bestehen, mit allen Termen unifizieren können. Existiert der Term noch nicht, so
wird er hinzugefügt, wobei seine Nummer um eins höher ist als die bisherige Anzahl der
Substitutionen, die der bisherigen höchsten Nummer entspricht.
14
3.4 Vierter Schritt: Konvertieren aller Relationen in GrößergleichRelationen
Im vierten Schritt wird jede Relation durch ein oder mehrere äquivalente GrößergleichRelationen ersetzt.
Die Komparanden der Relationen in der Eingabeliste müssen in der Form N+v(V), N oder
v(V) vorliegen, wie im dritten Schritt beschrieben. Zusätzlich wird auch die Form v(V)+N
unterstützt.
Die Ausgabe ist eine Liste von Größergleich-Relationen, die äquivalent zur eingegebenen
Liste von Relationen ist. Falls Ungleich-Relationen in der Eingabe vorkommen, werden
mehrere alternative Listen durch Backtracking zurückgeliefert, deren Disjunktion logisch
äquivalent zur Eingabeliste ist.
Bei n Ungleich-Relationen entstehen 2n Alternativen. Die eingegebene Relationenliste ist
in der Induktionsfreien Arithmetik genau dann gültig, wenn die Disjunktion aller
Alternativen gültig ist, also wenn wenigstens eine Alternative gültig ist.
Die Prozedur geRelations wandelt rekursiv jede Relation mit Hilfe der Prozedur geRel in
eine Liste äquivalenter Größergleich-Relationen um und konkateniert diese Listen rekursiv.
% geRelations(+VarReplacedRelations, -GreaterEqualRelations)
geRelations([], []).
geRelations([Var|VarRelations], GERelations) :geRel(Var, GERel),
geRelations(VarRelations, GERels),
append(GERel, GERels, GERelations).
% geRel(+Relation, -GreaterEqualRelations)
geRel(X\=Y, R) :- geRel(X>Y, R).
geRel(X\=Y, R) :- geRel(Y>X, R).
geRel(X=Y, [X>=Y, Y>=X]).
geRel(X>=Y, [X>=Y]).
geRel(X=<Y, [Y>=X]).
geRel(X<Y, R) :- geRel(Y>X, R).
geRel(X>Y, [X>=Z]) :(number(Y) -> Z is Y+1;
Y=v(_) -> Z=Y+1;
Y=v(V)+A -> B is A+1, Z=v(V)+B;
Y=A+v(V), B is A+1, Z=B+v(V)).
wandelt eine Relation in eine Liste äquivalenter Größergleich-Relationen um. Die
meisten Fälle sind selbsterklärend. Bei der Größer-Relation muss rechts 1 addiert werden,
was bei jeder möglichen Form des rechten Komparanden anders geschieht. Für UngleichRelationen gibt es zwei verschiedene Varianten, die mittels Backtracking durchprobiert
werden können.
geRel
15
3.5 Fünfter Schritt: Konstruieren des Graphen für den Max-PathWeights-Algorithmus
Im fünften Schritt wird eine Liste von Größergleich-Relationen in einen Graph umgeformt,
der genau dann einen positiven Zyklus enthält, wenn die Konjunktion der Relationen
widersprüchlich ist.
Die Eingabe sind eine Liste von Größergleich-Relationen, in deren Komparanden die
Konstanten-freien Teile durch Variablen ersetzt sind, und die Anzahl der Knoten des
Graphen. Diese ergibt sich aus der Anzahl der eingeführten Variablen, erhöht um 1, falls es
konstante Komparanden gibt, was in der Hauptprozedur implementiert ist.
Die Ausgabe ist ein gerichteter Graph mit Kantengewichten, dargestellt als eine Liste von
gewichteten Kanten in der Form mpw(Origin, Target, Weight). Origin ist der
Ausgangsknoten der Kante, Target der Endknoten und Weight das Kantengewicht. Es
besteht folgender Zusammenhang zur Eingabe:
Es sei N die Anzahl der Knoten. Ein Knoten Node korrespondiert mit jedem Komparanden,
der die eingeführte Variable v(Node) enthält; falls ein konstanter Komparand vorhanden
ist, korrespondiert der letzte Knoten N mit jedem konstanten Komparanden. Eine Kante
mpw(Origin, Target, Weight) korrespondiert mit jeder Größergleich-Relation, deren
linker Komparand mit Origin und rechter Komparand mit Target korrespondiert. Seien
LWeight und RWeight die dabei jeweils in dem linken und rechten Komparanden
enthaltenen Konstanten (bzw. 0, falls nicht vorhanden), dann ist Weight das Maximum
aller RWeight − LWeight. Der Grund dafür ist, dass für die Suche nach einem positiven
Zyklus nur der jeweils größte Weight-Wert zwischen zwei bestimmten Knoten relevant ist.
Die Prozedur mpwGraph betrachtet rekursiv eine Größergleich-Relation nach der anderen.
Zunächst werden die Komparanden untersucht, Origin, Target, LWeight und RWeight
ermittelt und Weight errechnet. Nach dem rekursiven Aufruf wird die neue Kante in den
zurück gereichten Graphen eingefügt, falls sie noch nicht existiert. Falls diese Kante
bereits mit niedrigerem Weight-Wert existiert, wird sie ersetzt.
% mpwGraph(+GreaterEqualRelations, +NNodes, -Graph)
mpwGraph([], _, []).
mpwGraph([X>=Y|GERelations], N, Graph) :(number(X) -> Origin=N, LWeight=X ;
(X=v(Origin) -> LWeight=0 ;
(X=v(Origin)+LWeight ; X=LWeight+v(Origin)), !)),
(number(Y) -> Target=N, RWeight=Y ;
(Y=v(Target) -> RWeight=0 ;
(Y=v(Target)+RWeight ; Y=RWeight+v(Target)), !)),
Weight is RWeight-LWeight,
mpwGraph(GERelations, N, G),
(append(Head, [mpw(Origin, Target, W)|Tail], G) ->
(W<Weight ->
append(Head, [mpw(Origin, Target, Weight)|Tail], Graph) ;
Graph=G) ;
Graph=[mpw(Origin, Target, Weight)|G]).
16
3.6 Sechster Schritt: Suchen nach einem positiven Zyklus
Im sechsten Schritt wird der im fünften Schritt konstruierte Graph nach einem positiven
Zyklus durchsucht. Ein positiver Zyklus ist ein Kreis im Graphen, bei dem die Summe der
Kantengewichte positiv ist.
Die Prozedur maxPathWeights implementiert den Algorithmus aus [6], wobei die dortigen
For-Schleifen in eine Rekursion mit Hilfe der Parameter K, I und J , die den For-SchleifenVariablen entsprechen, umgewandelt werden.
Zur Eingabe gehört neben der Kantenliste aus dem fünften Schritt wieder die Anzahl der
Knoten des Graphen.
Die Prozedur liefert genau dann true, wenn kein positiver Zyklus gefunden wird und
ansonsten false.
Der Algorithmus folgt dem Bottom-Up-Prinzip: Für von 1 bis N steigende K wird jeweils
zwischen allen möglichen Knoten I und J der Pfad mit dem jeweils höchsten Gewicht
ermittelt, der nur über Knoten mit Nummern nicht größer als K führt. Das Gewicht eines
Pfads ist dabei die Summe seiner Kantengewichte und N die Anzahl der Knoten. Sobald ein
Pfad von I nach I mit positivem Gewicht entsteht, also ein positiver Zyklus, wird der
Algorithmus abgebrochen und die Prozedur liefert false.
% maxPathWeights(+Graph, +NNodes)
maxPathWeights(Graph, N) :- maxPathWeights(Graph, N, 1, 1, 1).
maxPathWeights(Graph, N, K, I, J) :(K>N, ! ;
(I>N -> K1 is K+1, maxPathWeights(Graph, N, K1, 1, 1) ;
(J>N -> I1 is I+1, maxPathWeights(Graph, N, K, I1, 1) ;
NegInf = [], % negative infinity
(append(Head, [mpw(I,J,Aij)|Tail], Graph), ! ;
Aij=NegInf, Head=[], Tail=Graph),
(append(_, [mpw(I,K,Aik)|_], Graph), ! ; Aik=NegInf),
(append(_, [mpw(K,J,Akj)|_], Graph), ! ; Akj=NegInf),
((Aik=NegInf;Akj=NegInf) -> NAij=Aij ;
(Aij=NegInf -> NAij is Aik+Akj ;
NAij is max(Aij, Aik+Akj))),
(NAij=NegInf -> NGraph=Graph ;
append(Head, [mpw(I,J,NAij)|Tail], NGraph)),
(J\=N, ! ;
(append(_, [mpw(I,I,Aii)|_], NGraph) -> Aii=<0 ; true)),
J1 is J+1, maxPathWeights(NGraph, N, K, I, J1)
) ) ).
17
Kapitel 4: Test der Implementierung
Zum Testen der Implementierung wurde SWI-Prolog 5.6.64 unter Windows XP verwendet.
Bei der Wahl der Testfälle wurde versucht, sowohl viele verschiedene Fälle als auch einige
interessante Fälle abzudecken.
In der folgenden Dokumentation der Testfälle folgt hinter ?- stets die gestellte Anfrage und
in den darauf folgenden Zeilen die Antwort von Prolog.
4.1 Test von polyRelations
leere Liste:
?- polyRelations([], P).
P = [].
einfachste Komparanden:
?- polyRelations([0\=1, 5=X, D>=Y], P).
P = [0=1, 5=X, D=Y].
Summen:
?- polyRelations([X+Y+Z=A+B, 3+5>7+A], P).
P = [X+Y+Z=A+B, 3+5>7+A].
Vorzeichen:
?- polyRelations([-(-X)=< -0, -(+3*4)< +X+(-4)], P).
P = [-1* (-1*X)=<0, -1* (3*4)<X+ -4].
Differenzen:
?- polyRelations([0-5=3-X, A-(-B)\=X-4], P).
P = [0+ -1*5=3+ -1*X, A+ -1* (-1*B)\=X+ -1*4].
Produkte:
?- polyRelations([3*0*5<X*X*X, Y*4>X*(-X)], P).
P = [3*0*5<X*X*X, Y*4>X* (-1*X)].
verschachtelte Operationen:
?- polyRelations([(X+1)*(X-1)\=(4+(N-M)*3)*(X+2*Y)], P).
P = [X*X+X* (-1*1)+ (1*X+1* (-1*1))\=4*X+4* (2*Y)+ (N*3*X+N*3* (2*Y)+ (1*M*3*X+ -1*M*3* (2*Y)))].
nicht unterstützte Terme:
?- polyRelations(['Hallo Welt'], P).
false.
?- polyRelations([(1+X//Y)*5\=Y^'irgendwas'*X, c=5], P).
P = [1*5+X//Y*5\=Y^irgendwas*X, c=5].
Ergebnis: Jede Antwort entspricht der Erwartung.
18
4.2 Test von canRelations
leere Liste:
?- polyRelations([], P), canRelations(P, C).
P = [],
C = [].
einfachste Komparanden:
?- polyRelations([0\=1, 5=X, D>=Y], P), canRelations(P, C).
P = [0\=1, 5=X, D>=Y],
C = [0\=1, 5=X, D>=Y].
Summen:
?- polyRelations([5+0-5=3-X, A-(-B)\=X-4], P), canRelations(P, C).
P = [5+0+ -1*5=3+ -1*X, A+ -1* (-1*B)\=X+ -1*4],
C = [0=3+ -1*X, B+A\= -4+X].
Vorzeichen:
?- polyRelations([-(-X)=< -0, -(+3*4)< +X+(-4)], P), canRelations(P, C).
P = [-1* (-1*X)=<0, -1* (3*4)<X+ -4],
C = [X=<0, -12< -4+X].
Differenzen:
?- polyRelations([0-5=3-X, A-(-B)\=X-4], P), canRelations(P, C).
P = [0+ -1*5=3+ -1*X, A+ -1* (-1*B)\=X+ -1*4],
C = [-5=3+ -1*X, B+A\= -4+X].
Produkte:
?- polyRelations([3*0*5<X*X*X, Y*4>X*(-X)], P), canRelations(P, C).
P = [3*0*5<X*X*X, Y*4>X* (-1*X)],
C = [0<X* (X*X), 4*Y> -1* (X*X)].
verschachtelte Operationen:
?- polyRelations([(X+1)*(X-1)\=(4+(N-M)*3)*(X+2*Y)], P), canRelations(P,
C).
P = [X*X+X* (-1*1)+ (1*X+1* (-1*1))\=4*X+4* (2*Y)+ (N*3*X+N*3* (2*Y)+ (1*M*3*X+ -1*M*3* (2*Y)))],
C = [-1+X*X\=4*X+ (3* (X*N)+ (-3* (X*M)+ (6* (N*Y)+ (-6* (M*Y)+8*Y))))].
nicht unterstützte Terme:
?- canRelations(['Hallo Welt'], C).
false.
?- polyRelations([(1+X//Y)*5\=Y^'irgendwas'*3*X, c*D+D=5], P),
canRelations(P, C).
P = [1*5+X//Y*5\=Y^irgendwas*3*X, c*D+D=5],
C = [5+5* (X//Y)\=3* (X*Y^irgendwas), D+D*c=5].
Ergebnis: Jede Antwort entspricht der Erwartung.
19
4.3 Test von varRelations
leere Liste:
?- varRelations([], V, Vars, Const).
V = [],
Vars = 0,
Const = 0.
Konstanten:
?- varRelations([3>0, -5=<5], V, Vars, Const).
V = [3>0, -5=<5],
Vars = 0,
Const = 1.
mit Konstanten-freien Teilen:
?- varRelations([X=A+B, -5+(A+B)>3+(A+(B+X)), -5*A+3*B\=2+X], V, Va, Co).
V = [v(1)=v(2), -5+v(2)>3+v(3), v(4)\=2+v(1)],
Va = 4,
Co = 0.
nicht unterstützte Terme:
?- varRelations([3+X*'Heinz'>=5+X//Y, X*'Heinz'=4], V, Vars, Const).
V = [3+v(1)>=5+v(2), v(1)=4],
Vars = 2,
Const = 1.
Ergebnis: Jede Antwort entspricht der Erwartung.
4.4 Test von geRelations
Gleichheitsrelationen:
?- geRelations([v(1)=3, 5+v(2)=v(1)], G).
G = [v(1)>=3, 3>=v(1), 5+v(2)>=v(1), v(1)>=5+v(2)].
Größergleich- und Kleinergleich-Relationen:
?- geRelations([v(1)>=3, 5+v(2)=<v(1), 5=<3], G).
G = [v(1)>=3, v(1)>=5+v(2), 3>=5].
Größer- und Kleiner-Relationen:
?- geRelations([v(1)>3, 5+v(2)<v(1), -1+v(2)<3], G).
G = [v(1)>=4, v(1)>=6+v(2), 3>=0+v(2)].
Ungleichheitsrelationen:
?- geRelations([v(1)\=3, 5+v(2)=<v(1), -1\=3], G).
G = [v(1)>=4, v(1)>=5+v(2), -1>=4] ;
G = [v(1)>=4, v(1)>=5+v(2), 3>=0] ;
G = [3>=v(1)+1, v(1)>=5+v(2), -1>=4] ;
G = [3>=v(1)+1, v(1)>=5+v(2), 3>=0].
Ergebnis: Jede Antwort entspricht der Erwartung. Bei den Ungleichheitsrelationen werden
die zusätzlichen 3 Antworten nach Backtracking ausgegeben.
20
4.5 Test von mpwGraph
ohne konstante Komparanden:
?- mpwGraph([v(1)>=v(2), 2+v(2)>=5+v(3), v(3)>=1+v(2)], 3, G).
G = [mpw(1, 2, 0), mpw(2, 3, 3), mpw(3, 2, 1)].
mit konstanten Komparanden:
?- mpwGraph([10>=v(1), 2+v(2)>=5, v(3)>=1], 4, G).
G = [mpw(4, 1, -10), mpw(2, 4, 3), mpw(3, 4, 1)].
mehrere Ungleichungen zwischen denselben Variablen:
?- mpwGraph([10>=v(1), 2>=5+v(1), v(2)>=v(1), 4+v(2)>=2+v(1)], 3, G).
G = [mpw(3, 1, 3), mpw(2, 1, 0)].
Ergebnis: Jede Antwort entspricht der Erwartung. Im letzten Test werden nur die Kanten
mit dem höheren Weight-Wert verwendet.
4.6 Test von maxPathWeights
alle Kantengewichte 0; kein positiver Zyklus:
?- maxPathWeights([mpw(1,1,0), mpw(1,2,0), mpw(2,2,0), mpw(2,1,0)], 2).
true.
kein positiver Zyklus:
?- maxPathWeights([mpw(1,1,-10), mpw(1,2,2), mpw(2,3,5), mpw(3,1,-8)],3).
true.
positiver Zyklus (1 - 1):
?- maxPathWeights([mpw(1,1,10), mpw(1,2,2), mpw(2,3,5), mpw(3,1,-8)], 3).
false.
positiver Zyklus (1 - 2 - 3 - 1):
?- maxPathWeights([mpw(1,2,2), mpw(2,1,-2), mpw(2,3,5), mpw(3,3,-2),
mpw(3,1,-6)], 3).
false.
Ergebnis: Jede Antwort entspricht der Erwartung.
21
4.7 Test von arith
leere Listen (entspricht wahr ⇒ falsch):
?- arith([], []).
false.
leere Konklusion (true, wenn Hypothese ⇒ falsch, bzw. Hypothese ist widersprüchlich):
?- arith([3=3], []).
false.
?- arith([3\=3], []).
true.
?- arith([X>2, Y=X, 3>Y], []).
true.
leere Hypothese (true, wenn wahr ⇒ Konklusion, bzw. Konklusion
?- arith([], [1<5]).
true.
?- arith([], [1>=5]).
false.
mit Hypothese und Konklusion, true:
?- arith([X=3], [X>0]).
true.
?- arith([X\=3], [X>3, 3>X]).
true.
?- arith([1<A, A<B+C, B+C<D, D<6, D\=5], [3=B+C]).
true.
?- arith([1<A, A<B+C, B+C<D, D<4], [-1>1]).
true.
?- arith([(N+1)*(N-1)=X, Y=(N+1)*(1+N)-N*2], [X<Y]).
true.
mit Hypothese und Konklusion, false:
?- arith([X>0], [X=3]).
false.
?- arith([(N+1)*(N-1)=X, Y+2=(N+1)*(1+N)-N*2], [X<Y]).
false.
false, obwohl herleitbar in Standardarithmetik:
?- arith([X=2, Y=3], [X+Y=5]).
false.
?- arith([X=2, Y=3], [X=5-Y]).
false.
ist gültig):
Ergebnis: Jede Antwort entspricht der Erwartung.
Die letzten beiden Testfälle demonstrieren die Schwäche der Induktionsfreien Arithmetik:
Nur ein vollständiger konstantenfreier Teil eines Komparanden kann substituiert werden.
Beim letzten Testfall werden Y und -Y bzw. (-1)*Y durch unterschiedliche Variablen
substituiert.
22
4.8 Test der Laufzeit von Arith
Die Antwort für jeden der obigen Testfälle kam sofort, was bei der geringen Komplexität
der Testfälle nicht verwunderlich ist. Um eine merkliche Verzögerung zu erzielen, wird
folgende Anfrage verwendet, die die Auffächerung des Suchraums bei vielen
Ungleichheitsrelationen ausnutzt:
?- arith([X\=1, X\=2, X\=3, X\=4, X\=5, X\=6, X\=7, X\=8, X\=9, X\=10,
X\=11, X\=12, X\=13, X= -1], [X<0]).
true.
Bei dieser Anfrage gibt es auf dem verwendeten Testsystem (3 GHz Doppelkernprozessor)
bis zur Antwort eine ungefähre Verzögerung von 0,5 Sekunden. Untersucht man die
Antwortzeit bei verlängerten Versionen dieser Anfrage, so stellt man eine exponentielle
Abhängigkeit der Laufzeit von der Anzahl der Ungleichheitsrelationen fest.
ca. 1 Sekunde
ca. 2 Sekunden
ca. 4 Sekunden
ca. 8 Sekunden
ca. 16 Sekunden
Diese exponentielle Abhängigkeit zur Basis 2 ist aufgrund der Implementierung
offensichtlich, da im schlimmsten Fall die Kombination von je 2 Alternativen für jede
Ungleichheitsrelation untersucht wird.
arith([X\=1,
arith([X\=1,
arith([X\=1,
arith([X\=1,
arith([X\=1,
…
…
…
…
…
,
,
,
,
,
X\=14,
X\=15,
X\=16,
X\=17,
X\=18,
X=
X=
X=
X=
X=
-1],
-1],
-1],
-1],
-1],
[X<0]).:
[X<0]).:
[X<0]).:
[X<0]).:
[X<0]).:
23
Kapitel 5: Ausblick
Es ist viel Potenzial zum Optimieren von Arith vorhanden. Die Schritte 1 bis 3 verfolgen
alle dasselbe Prinzip und nehmen jede Relation auseinander, nur um sie am Ende wieder
zusammenzusetzen. Sie können so zusammengezogen werden, so dass die Relationen nur
einmal zerteilt werden und zum Schluss gleich zu den Größergleich-Relationen von Schritt
4 zusammengefügt werden. Schritt 6 könnte kantenbasiert vorgehen und nur die Kanten
betrachten, die auch tatsächlich existieren.
Nach der Implementierung von Arith ist der nächste logische Schritt die Integration in
LeanCop. Dazu muss das Zusammenspiel von Arithmetik mit dem Konnektionskalkül
untersucht werden, um Situationen zu finden, in denen Arith hilfreich ist, ebenso wie die
entsprechenden Punkte in der Implementierung, an denen Arith aufgerufen werden kann.
Zum Einen können arithmetische Terme in Argumenten von Prädikaten vorkommen, die
unifiziert werden müssen (Beispiel: P(3) ∧ (∀X(P(2X + 1) ⇒ Q(3X + 3))) ⇒ Q(6)), zum
Anderen können die arithmetischen Relationen selbst als Prädikate vorkommen (Beispiel:
∀X∀Y(X = 2 ∧ Y = 3 ⇒ X + Y = 5).
Um Arithmetik in TPTP-Formeln zu unterstützen, muss die Übersetzung in das eigene
LeanCop-Format über die Prädikatenebene hinaus fortgeführt werden, da arithmetische
Terme in Argumenten von Prädikaten oder, im Falle der Relationen, als Prädikate
vorkommen können. In letzterem Fall besteht ein Konflikt zur in leanCop bereits
implementierten Behandlung von Gleichheit, die Ungleichheiten A!=B im TPTP-Format
nicht zu A\=B übersetzt, sondern zu ~(A=B). Dieser Konflikt könnte aufgelöst werden,
indem die Übersetzung berücksichtigt, ob A und B arithmetische Terme sind.
Arith muss vervollständigt werden, indem eine Taktik für die Anwendung der
nichttrivialen
Monotonieaxiome
vor
dem
Aufruf
der
implementierten
Entscheidungsprozedur Arith entwickelt wird, damit auch Formeln wie die letzten Testfälle
von arith korrekt geprüft werden können. Weitere Axiome können zusätzliche Arten von
arithmetischen Termen umformen, wie Division und Potenzen mit Konstanten.
Weitere Entscheidungsprozeduren und andere Verfahren zur Behandlung anderer
Teilmengen der Arithmetik können implementiert werden und dabei miteinander
kooperieren. Die Verfahren können gegebenenfalls modifiziert werden, um einen Beweis
zu generieren.
24
Quellen:
1. www.leancop.de, 10.12.2009
2. Jens Otten: leanCoP 2.0 and ileanCoP 1.2: High Performance Lean Theorem Proving in
Classical and Intuitionistic Logic. Potsdam, 2008
3. http://planetmath.org/encyclopedia/PeanoArithmetic.html, 10.12.2009
4. http://planetmath.org/encyclopedia/PressburgerArithmetic.html, 10.12.2009
5. R. L. Constable, M. J. O'Donnell: A Programming Logic. Winthrop, Cambridge,
Massachusetts, 1978
6. T. Chan: An algorithm for checking PL/CV arithmetic inferences. Technical Report
TR77-326, Cornell University, Ithaca, NY, USA 1982.
7. C. Kreitz: Skriptum zur Vorlesung Automatisierte Logik und Programmierung, Kapitel
5. http://www.cs.uni-potsdam.de/ti/lehre/03-ALuP-I/skript.htm, 10.12.2009
8. D. C. Cooper: Theorem proving in arithmetic without multiplication. Machine
Intelligence 7, pp. 91-99, 1972
9. Louis Hodes: Solving problems by formula manipulation in logic and linear inequalities.
Proceedings of the 2nd international joint conference on Artificial intelligence, pp. 553–
559, London, England, 1971
10. Robert E. Shostak: On the SUP-INF Method for Proving Presburger Formulas. Journal
of the ACM (JACM), Volume 24, Issue 4, pp. 529–543, ACM, New York, NY, USA,
1977
11. Alexander Schrijver: Theory of linear and integer programming. John Wiley & Sons,
Inc., New York, NY, USA, 1986
12. William Pugh: The Omega test: a fast and practical integer programming algorithm for
dependence analysis. Proceedings of the 1991 ACM/IEEE conference on
Supercomputing, pp. 4–13, ACM, New York, NY, USA, 1991
13. Robert Shostak: Deciding Linear Inequalities by Computing Loop Residues. Journal of
the ACM (JACM), Volume 28, Issue 4, ACM, New York, NY, USA, 1981
14. Bernard Boigelot, Sébastien Jodogne, Pierre Wolper: An effective decision procedure
for linear arithmetic over the integers and reals. ACM Transactions on Computational
Logic (TOCL), Volume 6, Issue 3, pp. 614–633, ACM, New York, NY, USA, 2005
15. Yegor Bryukhov, Alexei Kopylov, Vladimir Krupski, Aleksey Nogin: Implementing
and Automating Basic Number Theory In MetaPRL Proof Assistant. 2003
25
Anhang: Quelltext der Implementierung
%%
%%
%%
%%
%%
%%
%%
%%
%%
%%
%%
%%
%%
%%
%%
%%
%%
%%
File: arith.pl
-
Date: 06 December 2009
Purpose: Check whether a conclusion can be deduced from a
hypothesis in induction-free arithmetic
The hypothesis is a conjunction and the conclusion a
disjunction of arithmetic relations.
Author:
Holger Trölenberg
Usage:
arith(+H, +C). %
%
%
%
where H and C are lists of relations (=,
\=, <, >, =<, >=) whose comparands can
contain integers, variables, and the
operations +, -, *, unary +, and unary -
Example: arith([(N+1)*(N-1)=X], [X<N*N]).
true.
arith([A=2, B=3], [A+B=5]).
false.
arith(Hypothesis, [Pos|Conclusion]) :!, negRelation(Pos, Neg), arith([Neg|Hypothesis], Conclusion).
arith(Relations, []) :\+((
polyRelations(Relations, PolyRelations),
canRelations(PolyRelations, CanRelations),
varRelations(CanRelations, VarRelations, Vars, Const),
geRelations(VarRelations, GERelations),
N is Vars+Const, mpwGraph(GERelations, N, Graph),
maxPathWeights(Graph, N)
)).
% negRelation(+Positive, -Negative)
% negates a relation by substituting the operand
negRelation(A=B, A\=B).
negRelation(A\=B, A=B).
negRelation(A<B, A>=B).
negRelation(A>B, A=<B).
negRelation(A=<B, A>B).
negRelation(A>=B, A<B).
%
%
%
%
------------------------------------------------------------------polyRelations(+Relations, -PolynomialRelations)
converts both comparands of each relation in the list into
polynomials
polyRelations([], []).
polyRelations([Rel|Relations], [Poly|PolyRelations]) :Rel=..[Op, RelA, RelB],
polyComparand(RelA, PolyA), polyComparand(RelB, PolyB),
Poly=..[Op, PolyA, PolyB],
polyRelations(Relations, PolyRelations).
% polyComparand(+Term, -Polynomial)
% converts one comparand into a polynomial
polyComparand(Var, Var) :- var(Var), !.
polyComparand(A*B, P) :- !, polyComparand(A, P1),
26
polyComparand(B, P2), polyLeftMult(P1, P2, P).
polyComparand(A+B, P1+P2) :- !, polyComparand(A, P1),
polyComparand(B, P2).
polyComparand(A-B, P) :- !, polyComparand(A+(-B), P).
polyComparand(-A, P) :- !, polyComparand((-1)*A, P).
polyComparand(+A, P) :- !, polyComparand(A, P).
polyComparand(Atom, Atom).
% polyLeftMult(+P1, +P2, -P)
% multiplies 2 polynomials
polyLeftMult(Var, R, P) :- var(Var), !, polyRightMult(Var, R, P).
polyLeftMult(A+B, R, P1+P2) :- !, polyLeftMult(A, R, P1),
polyLeftMult(B, R, P2).
polyLeftMult(Monomial, R, P) :- polyRightMult(Monomial, R, P).
% polyRightMult(+Factor, +P2, -P)
% multiplies a polynomial with a factor
polyRightMult(L, Var, L*Var) :- var(Var), !.
polyRightMult(L, A+B, P1+P2) :- !, polyRightMult(L, A, P1),
polyRightMult(L, B, P2).
polyRightMult(L, Monomial, L*Monomial).
%
%
%
%
------------------------------------------------------------------canRelations(+PolynomialRelations, -CanonicalRelations)
converts both comparands of each relation in the list from
polynomial into canonical form
canRelations([], []).
canRelations([Poly|PolyRelations], [Can|CanRelations]) :Poly=..[Op, PolyA, PolyB],
canComparand(PolyA, CanA), canComparand(PolyB, CanB),
Can=..[Op, CanA, CanB],
canRelations(PolyRelations, CanRelations).
% canComparand(+Polynomial, -CanonicalComparand)
% converts one polynomial comparand into canonical form
canComparand(Poly, Can) :canPoly2List(Poly, PL), msort(PL, SPL), canList2Can(SPL, Can2),
(Can2==[] -> Can=0 ; Can=Can2).
% canPoly2List(+Polynomial, -Monomials)
% converts a polynomial term into a list of encoded monomials
canPoly2List(Var, [monomial([Var], 1)]) :- var(Var), !.
canPoly2List(A+B, P) :!, canPoly2List(A, PA), canPoly2List(B, PB), append(PA, PB, P).
canPoly2List(Monomial, [monomial(CM, C)]) :canMonEnc(Monomial, M, C), msort(M, CM).
% canMonEnc(+Monomial, -Factors, -Coefficient)
% splits a monomial term into the coefficient and the other factors
canMonEnc(Var, [Var], 1) :- var(Var), !.
canMonEnc(A*B, M, C) :!, canMonEnc(A, MA, CA), canMonEnc(B, MB, CB), append(MA, MB, M),
C is CA*CB.
canMonEnc(N, [], N) :- number(N), !.
canMonEnc(Factor, [Factor], 1).
27
% canList2Can(+Monomials, -CanonicalTerm)
% converts a sorted list of encoded monomials into a canonical term
canList2Can([], []).
canList2Can([monomial(M1, C1), monomial(M2, C2)|SPL], Can) :M1==M2, !, C is C1+C2, canList2Can([monomial(M1, C)|SPL], Can).
canList2Can([monomial(_, 0)|SPL], Can) :- !, canList2Can(SPL, Can).
canList2Can([monomial(M, C)|SPL], Can) :- canMonDec(M, C, CM),
canList2Can(SPL, Can2), (Can2==[] -> Can=CM ; Can=CM+Can2).
% canMonDec(+Factors, +Coefficient, -Monomial)
% combines a coefficient and a sorted list of factors into a
% canonical Monomial
canMonDec(_, 0, 0) :- !.
canMonDec([], C, C).
canMonDec([F, F2|M], 1, F*CM) :- !, canMonDec([F2|M], 1, CM).
canMonDec([F], 1, F) :- !.
canMonDec([F|M], C, C*CM) :- canMonDec([F|M], 1, CM).
%
%
%
%
%
%
%
------------------------------------------------------------------varRelations(+CanonicalRelations, -VarReplacedRelations,
-NVariables, -Constants)
replaces the constant-free parts of the canonical comparands of the
relations in the list with variables, returns the number of
introduced variables, and sets Constants to 1, if there are any
constant comparands, and to 0 otherwise
varRelations(CanRelations, VarRelations, Vars, Const) :varRelations(CanRelations, VarRelations, [], Vars, Const).
varRelations([], [], [], 0, 0) :- !.
varRelations([], [], [subst(_, Vars)|_], Vars, 0).
varRelations([Can|CanRelations], [Var|VarRelations], Substitutions,
Vars, Const) :Can=..[Op, CanA, CanB],
varSubst(CanA, VarA, Substitutions, SubsA, CA),
varSubst(CanB, VarB, SubsA, SubsB, CB),
Var=..[Op, VarA, VarB],
varRelations(CanRelations, VarRelations, SubsB, Vars, C),
Const is C\/CA\/CB.
%
%
%
%
%
%
%
varSubst(+CanonicalComparand, -VarReplacedTerm, +SubstitutionsIn,
-SubstitutionsOut, -Constant)
replaces the constant-free part of one comparand with a new or
previously used variable, depending on the supplied list of
substitutions, updates the substitution list accordingly, and sets
Constant to 1, if the comparand is only a constant, and to 0
otherwise
varSubst(N, N, S, S, 1) :- number(N), !.
varSubst(N+C, N+V, S, S2, 0) :- number(N), !,
varSubst(C, V, S, S2, _).
varSubst(C, v(V), S, S, 0) :- member(subst(D, V), S), C==D, !.
varSubst(C, v(V), S, [subst(C, V)|S], 0) :- length([_|S], V).
%
%
%
%
%
------------------------------------------------------------------geRelations(+VarReplacedRelations, -GreaterEqualRelations)
converts each relation with replaced constant-free parts in the
list into one or two equivalent greater-than-or-equal-relations;
each inequality (\=) is first converted like the greater-than-
28
% relation (>) and then, after backtracking, like the lower-than% relation (<)
geRelations([], []).
geRelations([Var|VarRelations], GERelations) :geRel(Var, GERel),
geRelations(VarRelations, GERels),
append(GERel, GERels, GERelations).
% geRel(+Relation, -GreaterEqualRelations)
% converts one relation into a list of equivalent relations
geRel(X\=Y, R) :- geRel(X>Y, R).
geRel(X\=Y, R) :- geRel(Y>X, R).
geRel(X=Y, [X>=Y, Y>=X]).
geRel(X>=Y, [X>=Y]).
geRel(X=<Y, [Y>=X]).
geRel(X<Y, R) :- geRel(Y>X, R).
geRel(X>Y, [X>=Z]) :(number(Y) -> Z is Y+1;
Y=v(_) -> Z=Y+1;
Y=v(V)+A -> B is A+1, Z=v(V)+B;
Y=A+v(V), B is A+1, Z=B+v(V)).
%
%
%
%
geRelations(+Relations, -GreaterEqualRelations, -Inequalities)
a different version that separates the inequalities from the other
relations and leaves them unconverted
this predicate is unused but intended for future use
geRelations([],[],[]).
geRelations([X\=Y|VarRelations], GERelations, [X\=Y|INRelations]) :!, geRelations(VarRelations, GERelations, INRelations).
geRelations([Var|VarRelations], GERelations, INRelations) :geRel(Var, GERel),
geRelations(VarRelations, GERels, INRelations),
append(GERel, GERels, GERelations).
%
%
%
%
------------------------------------------------------------------mpwGraph(+GreaterEqualRelations, +NNodes, -Graph)
constructs a graph with NNodes nodes from the list of greater-orequal-relations for the MaxPathWeights algorithm
mpwGraph([], _, []).
mpwGraph([X>=Y|GERelations], N, Graph) :(number(X) -> Origin=N, LWeight=X ;
(X=v(Origin) -> LWeight=0 ;
(X=v(Origin)+LWeight ; X=LWeight+v(Origin)), !)),
(number(Y) -> Target=N, RWeight=Y ;
(Y=v(Target) -> RWeight=0 ;
(Y=v(Target)+RWeight ; Y=RWeight+v(Target)), !)),
Weight is RWeight-LWeight,
mpwGraph(GERelations, N, G),
(append(Head, [mpw(Origin, Target, W)|Tail], G) ->
(W<Weight ->
append(Head, [mpw(Origin, Target, Weight)|Tail], Graph) ;
Graph=G) ;
Graph=[mpw(Origin, Target, Weight)|G]).
% ------------------------------------------------------------------% maxPathWeights(+Graph, +NNodes)
% searches for a positive cycle in the graph with NNodes nodes and
29
% succeeds, iff there is no positive cycle
% I, J, and K are variables used for the for-loop
maxPathWeights(Graph, N) :- maxPathWeights(Graph, N, 1, 1, 1).
maxPathWeights(Graph, N, K, I, J) :(K>N, ! ;
(I>N -> K1 is K+1, maxPathWeights(Graph, N, K1, 1, 1) ;
(J>N -> I1 is I+1, maxPathWeights(Graph, N, K, I1, 1) ;
NegInf = [], % negative infinity
(append(Head, [mpw(I,J,Aij)|Tail], Graph), ! ;
Aij=NegInf, Head=[], Tail=Graph),
(append(_, [mpw(I,K,Aik)|_], Graph), ! ; Aik=NegInf),
(append(_, [mpw(K,J,Akj)|_], Graph), ! ; Akj=NegInf),
((Aik=NegInf;Akj=NegInf) -> NAij=Aij ;
(Aij=NegInf -> NAij is Aik+Akj ;
NAij is max(Aij, Aik+Akj))),
(NAij=NegInf -> NGraph=Graph ;
append(Head, [mpw(I,J,NAij)|Tail], NGraph)),
(J\=N, ! ;
(append(_, [mpw(I,I,Aii)|_], NGraph) -> Aii=<0 ; true)),
J1 is J+1, maxPathWeights(NGraph, N, K, I, J1)
) ) ).
30
Herunterladen