ShoppingTour

Werbung
ShoppingTour
Einkaufsoptimierung
informatiCup 2012 Aufgabe 1
Kai Fabian
Hasso-Plattner-Institut, IT-Systems Engineering, 3. Semester
[email protected]
Dominik Moritz
Hasso-Plattner-Institut, IT-Systems Engineering, 3. Semester
[email protected]
Matthias Springer
Hasso-Plattner-Institut, IT-Systems Engineering, 3. Semester
[email protected]
Malte Swart
Hasso-Plattner-Institut, IT-Systems Engineering, 3. Semester
[email protected]
16. Januar 2012
1
Einkaufsoptimierung Kai Fabian, Dominik Moritz, Matthias Springer, Malte Swart
Inhaltsverzeichnis
1 Einleitung
2
2 Installation
2.1 ShoppingTour . . . . . .
2.2 Python 2.7 . . . . . . . .
2.3 PyQt 4.9 . . . . . . . . .
2.4 Clingo (part of Potassco)
.
.
.
.
3
3
3
3
4
3 Programmverwendung
3.1 Verwendung der graphischen Ansicht . . . . . . . . . . . . . . . . . . . .
5
5
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4 Ein NP-schweres Problem
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
10
5 Pre- und Postprocessing
11
5.1 Preprocessing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
5.2 Implementierung des Preprocessing . . . . . . . . . . . . . . . . . . . . . 12
5.3 Postprocessing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
6 Optimierungsalgorithmen
12
6.1 ASP solver clingo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
6.2 Genetischer Algorithmus . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
7 Analyse der Performance
17
7.1 Einwicklung der Lösungsgüte . . . . . . . . . . . . . . . . . . . . . . . . 17
7.2 Abhängigkeit von k . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
7.3 Abhängigkeit von n . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1 Einleitung
ShoppingTour ist ein in Python geschriebenes Programm zum Optimieren von Einkaufsfahrten, welches im Rahmen des InformatiCup 2012 erstellt wurde. Das Programm kann
sowohl mit der in Qt geschrieben Oberfläche, als auch vollständig über die Kommandozeile benutzt werden. Bei Der Programmierung haben wir auch Wiederverwendbarkeit
von Programmteilen geachtet und eine Architektur entwickelt, die Erweiterungen sehr
einfach macht. Diese können zum Beispiel die Grafische Darstellung der Qualität des Suchergebnisses oder einfache Navigation durch die gefundenen Lösungen sein. Es werden
zwei ausgereifte Verfahren zur Optimierung verwendet, wobei eines sogar Optimalität
garantieren kann!
Die Architektur unseres Programms ist so aufgebaut, dass Programmlogik, Daten
und die Gui getrennt wurden. Die Datenstrukturen liegen im Verzeichnis data. Die
Programmlogik findet sich im Verzeichnis program. Die gesamte UI-Implementierung
2
Einkaufsoptimierung Kai Fabian, Dominik Moritz, Matthias Springer, Malte Swart
findet sich im Verzeichnis gui. Da die Ui teilweise generiert wird, finden sich im Ordner
gen die generierten Ui-Dateien. Das verwendete Programm clingo findet sich in einem
eigenen Verzeichnis dist/clingo, da es in C++ geschrieben ist und somit verschiedene
Binaries für verschiedene Betriebssysteme ausgeliefert werden müssen.
Wir wünschen dem Leser an dieser Stelle viel Freude beim Studium dieser Dokumentation und des Quellcodes sowie der Benutzung des Programms.
2 Installation
Windows-Nutzer können weitgehende Installationsschritte durch die Verwendung unserer Windows One-Click Distribution in der beigelegten Datei shoppingtour-w32.zip
vermeiden. Hierzu wird diese Datei in einen beliebigen Ordner entpackt und die enthaltene shoppingtour.exe-Datei ausgeführt.
Da dieses Projekt ausschließlich in der plattformunabhängigen Skriptsprache Python
geschrieben wurde, ist eine Distribution auf fast alle Betriebssysteme möglich. Notwendige Voraussetzung hierfür ist jedoch, dass die im Projekt verwendeten notwendigen
Bibliotheken für das Zielsystem verfügbar sind. Da dies meist nur für verbreitete Betriebssysteme der Fall ist, können wir eine Lauffähigkeit zum aktuellen Zeitpunkt nur
für Microsoft Windows (2000, XP, Vista, 7, 8 Developer Preview), Apple Mac OS X
(>= 10.4) und Linux (Kernel 2.6/3.0, insbesondere Distributionen ”Debian”, ”Ubuntu”
und ”Gentoo”) garantieren.
Im Folgenden werden die notwendigen Softwarepakete aufgeführt, die für die Verwendung von ShoppingTour erforderlich sind.
2.1 ShoppingTour
http://www.myhpi.de/~kai.fabian/shoppingtour/
ShoppingTour benötigt keine Installation und kann nach dem Entpacken in ein beliebiges Verzeichnis, welches das Ausführen von Anwendungen erlaubt, verwendet werden.
2.2 Python 2.7
http://www.python.org/
Als Interpreter für die verwendete Programmiersprache empfehlen wir die Referenzimplementierung CPython in der Version 2.7, wobei auch andere kompatible PythonImplementierungen verwendbar sein sollten, solange die weiteren Abhängigkeiten Kompatibilität zu diesen aufweisen.
Bezüglich der Python-Prozessorarchitekturen konnten die Intel 32-bit-Architektur (x86)
sowie die AMD 64-bit-Architektur (x64) erfolgreich erprobt werden.
2.3 PyQt 4.9
http://www.riverbankcomputing.co.uk/software/pyqt/download
3
Einkaufsoptimierung Kai Fabian, Dominik Moritz, Matthias Springer, Malte Swart
Das Benutzer-Interface verwendet Nokias Qt-Bibliothek. Die hierfür notwendigen PythonBindungen werden durch Riverbank Computing Limited bereitgestellt. Für Windows
existieren vorkompilierte Pakete, während Linux- und Mac-Nutzer die Bibliothek selbst
kompilieren müssen, sofern nicht bereits vorgefertigte Pakete für das Betriebssystem
existieren (bspw. das Paket py27-pyqt4 für Mac OS X unter Verwendung von MacPorts).
Anzumerken ist hierbei, dass zur Verwendung (und eventuellen manuellen Übersetzung) der PyQt-Bibliothek Nokias Qt-Bibliothek neben weiteren Abhängigkeiten (hierzu
sei an die Seiten des PyQt-Distributors verwiesen) auf dem System vorhanden sein müssen.
2.4 Clingo (part of Potassco)
http://potassco.sourceforge.net/
Das von der Universität Potsdam gepflegte Potassco-Projekt stellt Softwarewerkzeuge
für ASP-Programmierung (Answer Set Programmung) bereit. ShoppingTour verwendet
Clingo, welches die Verbindung von clasp, einem Lösungsproblem für logische Probleme,
und Gringo, einem Konvertierer zur Erzeugung von variablen-freien logischen Problembeschreibungen, darstellt. Vorkompilierte Binärdateien für Windows, Mac OS/Darwin
und Linux sind von der Anbieterseite ebenso zu erhalten wie der Software-Quellcode.
4
Einkaufsoptimierung Kai Fabian, Dominik Moritz, Matthias Springer, Malte Swart
3 Programmverwendung
3.1 Verwendung der graphischen Ansicht
Nach dem Programmstart befindet sich der Benutzer üblicherweise in der graphischen
Ansicht.
5
Einkaufsoptimierung Kai Fabian, Dominik Moritz, Matthias Springer, Malte Swart
Der Benutzer öffnet nun eine Problemdefinition, in dem er den Open-Button in der
Toolbar, oder wahlweise den gleichnamigen Menüpunkt im File-Menü, verwendet. Hierbei hat er die Wahl zwischen den beiden implementierten Algorithmen.
In der folgenden Ansicht wählt der Benutzer die Problembeschreibungsdateien über
den Select-Button oder durch manuelle Eingabe in das Textfeld aus.
Für die Verwendung von Clingo zur Problemlösung muss der Pfad zur ausführbaren Programmdatei angegeben werden. In der Windows One-Click Distribution befinden sich diese im dist/clingo-Ordner; Windows-Benutzer müssen hier also den Pfad zur
w32clingo.exe-Datei, also beispielsweise dist/clingo/w32clingo.exe, angeben. Der
Pfad kann hierbei absolut oder relativ zum aktuellen Arbeitsverzeichnis angegeben werden.
Alternativ kann der Benutzer den genetischen Algorithmus wählen. Hierbei kann er
die Eckdaten des Algorithmenverhaltens bestimmen. Die Beschreibung der Buttonfunktionen kann über den Help-Button erfahren werden.
6
Einkaufsoptimierung Kai Fabian, Dominik Moritz, Matthias Springer, Malte Swart
Der Benutzer sieht nun eine graphische Darstellung des Problembereiches. Er kann
per Klicken und Ziehen die einzelnen Knoten des Graphen verschieben (optional kann
er die Funktion der elastischen Knoten im ”Tools”-Menü aktivieren).
7
Einkaufsoptimierung Kai Fabian, Dominik Moritz, Matthias Springer, Malte Swart
Um die Berechnung zu starten, klickt der Benutzer auf den Run-Button in der Toolbar.
Während des Algorithmen-Durchlaufs wird ihm der aktuelle Programmzustand, also die
aktuell berechnete (Teil-) Lösung, graphisch und in Listenform dargestellt.
8
Einkaufsoptimierung Kai Fabian, Dominik Moritz, Matthias Springer, Malte Swart
Der Benutzer kann den Berechnungsvorgang selbstverständlich auch abbrechen. Hierzu
wählt er entweder in der Toolbar die Cancel -Option oder alternativ die gleichnamige im
Tools-Menü.
In der Endansicht wird der Benutzer über die beste gefundene Lösung informiert.
Dies geschieht auch hier einerseits in graphischer Form und andererseits in Listenform.
Die Listenform wird als Shopping list bezeichnet und beschreibt, in welcher Reihenfolge
welche Geschäfte besucht werden sollen (erste Baumebene) und welche Gegenstände in
welcher Anzahl dort gekauft werden sollen (zweite Baumebene).
9
Einkaufsoptimierung Kai Fabian, Dominik Moritz, Matthias Springer, Malte Swart
4 Ein NP-schweres Problem
Bei dem vorliegenden Problem handelt es sich um NP-schweres Problem. Das ist deshalb
interessant, weil es somit nahezu ausgeschlossen ist, einen Algorithmus zu finden, der
die optimale Lösung des Problems effizient (in polynomieller Zeit) berechnet. Durch
polynomielle Reduktion des Travelling Salesman Problems (TSP) auf das vorliegende
Problem kann die NP-Schwierigkeit gezeigt werden.
Beim TSP geht man von einem ungerichteten, gewichteten Graphen aus, wobei die
Knoten des Graphen Städte und die Kanten des Graphen Verkehrswege zwischen den
Städten repräsentieren. Das Gewicht einer Kante gibt Aufschluss über die Länge des
Weges zwischen zwei Städten. Nun soll eine Rundreise, also ein Pfad mit dem gleichen
Start- und Endknoten, mit dem kleinstmöglichen Reiseweg gesucht werden, wobei alle
Städte einmal besucht werden müssen.
Das TSP lässt sich auf das Problem der Einkaufsplanung reduzieren, indem man den
Graphen ohne Veränderung übernimmt1 . Wenn S = {s1 , s2 , . . .} die Menge der Städte
ist, erzeugen wir für jede Stadt si einen Artikel ai , den es nur in dieser Stadt zu kaufen
gibt. Für die letzte Stadt sn gibt es keinen Artikel, denn das soll unser Start- und
Endpunkt sein, an dem die Reise beginnt. Der Preis der Artikel spielt keine Rolle, solange
er endlich ist, also setzen wir ihn einfach auf 0.
Jeder Artikel soll nun einmal eingekauft werden. Durch die Lösung des Einkaufsplanungsproblems erhalten wir eine optimale Lösung des TSP, denn es werden die Gesamtkosten minimiert, die in diesem Fall nur aus den Reisekosten bestehen. Die Reisekosten
aber ist die Länge der Rundreise. Um jeden Artikel einzukaufen, muss des weiteren jede
Stadt einmal besucht werden. Als Ergebnis erhalten wir u.a. die Reihenfolge, in der die
Städte besucht werden sollen, was der Lösung des TSP entspricht. Die Transformation
des Problems ist trivialerweise in polynomieller Zeit möglich, denn es muss lediglich für
jede Stadt ein Artikel erstellt werden (linearer Zeitaufwand).
Um zu zeigen, dass es sich außerdem um ein NP-vollständiges Problem handelt, muss
nachgewiesen werden, dass das Problem in der Menge NP enthalten ist. Es muss also
gezeigt werden, dass eine Lösung in polynomieller Zeit verifiziert werden kann. An dieser
Stelle soll nur das Entscheidungsproblem betrachtet werden: Gibt es eine Lösung des des
Einkaufsplanungsproblems, deren Gesamtkosten unter dem Betrag n liegen? Eine mögliche Lösung kann durch Aufsummieren der Reisekosten zwischen den Einkaufsläden2 und
dem Addieren der Artikelpreise bei den entsprechenden Einkaufsläden in polynomieller
Zeit3 verifiziert werden. Somit handelt es sich um ein NP-schweres Problem, das selbst
in NP liegt, also folglich um ein NP-vollständiges Problem.
1
Einkaufsgeschäfte sind Städte.
Die Reisekosten ergeben sich aus den Kantengewichten.
3
Wie man sich leicht überlegen kann, macht es keinen Sinn, eine Kante mehr als zweimal zu benutzen.
2
10
Einkaufsoptimierung Kai Fabian, Dominik Moritz, Matthias Springer, Malte Swart
5 Pre- und Postprocessing
Um die Implementierung unserer Algorithmen zu verbessern und den Zustandsraum zu
verkleinern, wird vor der Ausführung ein Preprocessing-Schritt und nach der Ausführung
ein Postprocessing-Schritt durchgeführt.
5.1 Preprocessing
Als Eingabe erhält das Programm u.a. eine Adjazenzmatrix eines gewichteten, ungerichteten Graphen, in dem die Geschäfte auf Knoten und die Wege zwischen den Geschäften
auf Kanten abgebildet werden. Das Gewicht einer Kante ist der Abstand zwischen zwei
Geschäften bzw. die Kosten, die bei der Fahrt entstehen. Es ist auch möglich, dass
zwischen Geschäften gar keine Verbindung existiert, was entweder bedeutet, dass das
Geschäft nur über ein anderes Geschäft erreichbar ist, oder dass der Graph nicht zusammenhängend ist.
Falls der Graph nicht zusammenhängend ist, können die Knoten, die nicht vom Startund Endknoten aus erreichbar sind, einfach verworfen werden. Diese sind dann nämlich
nicht relevant für die Berechnung, weil sie in keiner validen Lösung auftauchen können.
Sie würden den Suchraum nur unnötig vergrößern. Nicht erreichbare Knoten können mit
einer Tiefensuche in linearer Zeit4 gefunden und gelöscht werden.
Die eigentlichen Algorithmen entscheiden nur, ob und in welcher Reihenfolge bestimmte Knoten angesteuert werden sollen. Es wird also nicht direkt betrachtet, welchen Preis
die Einkaufsartikel haben. Die Lösung, die ein Algorithmus generiert, ist nur eine Liste
mit den anzusteuernden Knoten. Welche Artikel bei welchem Geschäft gekauft werden,
muss nicht gespeichert werden. Dazu geht man einfach alle Artikel durch ein wählt für
jeden Artikel das Geschäft unter den besuchten Geschäften aus, das den Artikel zum
niedrigsten Preis anbietet.
Eine solche Rundreise kann aber sehr groß sein, da es passieren kann, dass ein Geschäft
mehrmals besucht werden muss5 . Durch eine Optimierung im Preprocessing kann der
Suchraum aber stark verkleinert werden.
Dazu wird der Graph in einen vollständigen Graphen umgewandelt. Jeder Knoten ist
dann von jedem anderen Knoten aus direkt erreichbar. Als Kantengewicht einer hinzugefügten Kante wählt man den minimalen Abstand zwischen den beiden betroffenen
Knoten. In einem solchen Graphen muss kein Knoten mehr als einmal besucht werden.
Um einen Artikel in einem bestimmten Geschäft zu kaufen, reicht es aus, den Knoten ein
einziges Mal zu besuchen. Von diesem Knoten aus ist aber jeder andere Knoten direkt
erreichbar. Man steuert nun also nur Geschäfte an, in denen man auch etwas kaufen will.
Fälle, in denen man einen Knoten nur ansteuert, um einen anderen Knoten zu erreichen,
gibt es nicht mehr.
4
5
O(|V | + |E|), wobei |V | die Anzahl der Knoten und |E| die Anzahl der Kanten ist.
Man denke z.B. an einen Fall, wo ein Geschäft A nur über ein einziges anderes Geschäft B erreichbar
ist. Dann muss man dieses Geschäft B mindestens zweimal besuchen, einmal um zu A zu gelangen
und einmal um A wieder zu verlassen. Analog lassen sich Fälle konstruieren, bei denen ein Geschäft
beliebig oft besucht werden muss.
11
Einkaufsoptimierung Kai Fabian, Dominik Moritz, Matthias Springer, Malte Swart
Der Suchraum kann durch diese Optimierung verkleinert werden, da nur noch zyklenfreie Rundreisen6 betrachtet werden. Ein Algorithmus muss nur noch entscheiden,
welche Geschäfte er überhaupt besuchen will und dann eine optimale TSP-Rundreise
finden.
5.2 Implementierung des Preprocessing
Um den Graphen in einen vollständigen Graphen zu überführen, wird zunächst mit
dem Floyd-Warshall Algorithmus der kürzeste Weg zwischen allen Paaren von Knoten
berechnet7 . Es wird sowohl die Länge als auch der eigentliche Weg gespeichert. Die
Adjazenzmatrix des Graphen kann nun mit den berechneten Längen vervollständigt
werden.
5.3 Postprocessing
Die generierte Lösung eines Algorithmus kann Kanten benutzen, die im vollständigen
Graphen zwar enthalten sind, nicht aber im ursprünglichen Graphen. Solche Kanten
müssen im Postprocessing identifiziert werden und durch den kürzesten Pfad zwischen
den beiden betroffenen Knoten ersetzt werden. Das ist mit wenig Aufwand möglich, weil
während der Berechnung der minimalen Pfade im Floyd-Warshall Algorithmus auch die
Knoten, aus denen ein Pfad besteht, gespeichert wurden.
Die grafische Oberfläche zeigt Zwischenlösungen der Algorithmen an. Kanten, die nur
im vollständigen Graphen existieren, nicht aber im originalen Graphen, sind gestrichelt
eingezeichnet. Die endgültige Lösung enthält keine solchen Kanten mehr. Stattdessen
kann es sein, dass Knoten dort mehrmals besucht werden.
6 Optimierungsalgorithmen
Nach dem Preprocessing wird auf die Daten ein Optimierungsalgorithmus angewendet,
welcher iterativ bessere Lösungen findet und anschließend die beste gefundene präsentiert. Wir haben aus den Erfahrungen des letzten Informaticups und erneuten Analysen verschiedene Algorithmen evaluiert. Um die Komplexität nicht unnötig zu steigern
wollten wir uns auf maximal zwei Algorithmen beschränken. Um trotzdem ausrechend
Flexibilität zu behalten sollen die Parameter der Algorithmen aber so weit wie möglich
anpassbar sein. Da die Probleminstanzen im Allgemeinen eine geringe Größe (ca. 10
Läden und ebensoviele Produkte) aufweisen und wir den Suchraum durch unser Preprocessing schon weit einschränken konnten, haben wir uns dazu entschieden auch die
Möglichkeit zur Berechnung von optimalen Lösungen zu implementieren.
Nachdem wir verschiedene Metaheurstiken wie Simulierte Abkühlung, Greedy-Suche
und Tabusuche untersucht haben, haben wir uns dazu entschieden einen genetischen
6
7
Eine Rundreise ist natürlich auch ein Zyklus, aber dieser Zyklus zählt hier nicht.
Laufzeit O(|V |3 ) bei |V | Geschäften.
12
Einkaufsoptimierung Kai Fabian, Dominik Moritz, Matthias Springer, Malte Swart
Algorithmus für die Lösung des Problems zu nutzen. Greedy-Suche hätte leicht in einem lokalen Minimum landen können, sodass dieser Algorithmus für uns ausschied. Alle
genannten Metaheuristiken, einschließlich dem genetischen Algorithmus, hätten das Problem der lokalen Minima gelöst, aber die Performance von genetischen Algorithmen ist
im Allgemeinen höher.
Da Meteheuristiken zwar oft sehr gute und manchmal auch optimale Lösungen finden
können, aber diese nicht garantieren oder auch nur nachweisen können, haben wir einen
weiteren Algorithmus implementiert. Das Problem der Optimalität liegt darin, dass Metaheuristiken mit Zufall arbeiten und den Suchraum nicht vollständig explorieren. Da
das Problem aus der Aufgabenstellung NP-vollständig ist und selbst der Nachweis der
Optimalität einer Lösung NP-vollständig ist, kann es passieren, dass eine Metaheuristik nur eine unzureichend gute Lösung findet8 . Die Nutzung von Clingo ermöglicht es
uns, in relativ kurzer Zeit sehr gute Lösungen zu finden und bei der letzten Lösung
auch Optimalität zu garantieren! Damit ist unser Programm in diesem Punkt allen
Implementierungen die nur auf Metaheuristiken aufbauen (wie auch unser genetischer
Algorithmus), überlegen.
An dieser Stelle möchten wir noch anmerken, dass auch die Metaheuristik Simulierte
Abkühlung implementiert wurde. Jedoch nicht zur Berechnung einer Lösung des Problems sondern zur Verteilung der Knoten des Graphen auf der grafischen Ansicht. Der
Algorithmus befindet sich in der Klasse PositionCities in der gleichnamigen Datei. Da
die Funktionsweise des Algorithmus allgemein bekannt ist und der Algorithmus zudem
nur zur grafischen Darstellung dient, möchten wir an dieser Stelle nur beschreiben, wie
die Bewertung einer Lösung im Groben berechnet wird. Zu jeder Lösung wird für jedes
Paar von Knoten der Abstand der Knoten auf der grafischen Ansicht mit dem Abstand
der Knoten in der Adjazenzmatrix verglichen. Diese Abstände von grafischer Ansicht
und Adjazenzmatrix verwenden subtrahiert, gewichtet und dann aufsummiert. Diesen
Wert gilt es zu minimieren. Die Verteilung der Knoten mit diesem Algorithmus ist dann
interessant, wenn es sich um echte, sinnvolle Abstände, d.h. um einen metrischen Graphen handelt. Bildet man beispielsweise ein existierendes Straßennnetz als Graphen ab
und berechnet dann die grafische Darstellung mit diesem Algorithmus, erhält man ein
sinnvolles Abbild, wo sich z.B. Straßen nicht überschneiden, wenn an dieser Stelle keine
Kreuzung ist. Und dazu muss die Position der Knoten nicht gespeichert werden, es wird
lediglich die Graphstruktur benötigt.
Bei der Implementierung haben wir darauf geachtet, dass die Algorithmen als Generatoren/ Iteratoren implementiert sind. Das heißt, es kann ganz einfach von einer Lösung
zu einer weiteren gewechselt werden. Des Weiteren kann die Fortsetzung leicht verzögert
oder sogar abgebrochen werden.
Wir haben des Weiteren bei unserer Implementierung nicht auch Randfälle, wie widersprüchliche Daten, fehlerhafte Eingabedateien oder nicht zusammenpassende Eingabedateien geachtet, um den Aufwand nicht zu sehr in die Höhe zu treiben.
8
In unseren Tests funktionierte der genetische Algorithmus aber recht gut.
13
Einkaufsoptimierung Kai Fabian, Dominik Moritz, Matthias Springer, Malte Swart
6.1 ASP solver clingo
Clingo ist Programm, das ähnlich wie Prolog Logische Probleme löst. Zum vollständigen
Verständnis dieses Abschnitts sind Grundlagen aus der Logikprogrammierung und KI
erforderlich. Unabhängig vom Verständnis des Programms ist festzustellen, dass mit
dieser Lösung Optimalität garantiert werden kann und gleichzeitig relativ kurz gerechnet
wird.
Die Stelle im Programm, an der die Implementierung zu finden ist, lautet program/clingo.py.
Das Eingabeformat von Clingo (bzw in diesem Fall Gringo) ähnelt dem von Prolog,
wobei die Syntax erweitert wurde und auch Minimierungskriterien ermöglicht. Da die
Programmbeschreibung von Clingo keine induktive wie Python, C++ oder Java ist,
müssen auch andere Paradigmen angewendet werden.
Clingo steht für clasp on Gringo und kombiniert dadurch die beiden Systeme zu einem
einfach zu verwendenden Programm. clasp ist ein Problemlöser für erweiterte Logikprobleme. Es kombiniert abstrakte Modellierungsmöglichkeiten des answer set programming
(ASP) mit aktuellen lösungsalgorithmen aus dem Bereich des boolschen ConstraintLösens. Dabei werden besonders die sehr effizienten konfliktgetriebenen Nachbarschaftssuchen verwendet. Dies ist eine Technik die sich in der Vergangenheit als sehr effizient
herausgestellt hat. Clasp hängt dabei nicht von bestehenden Programmen ab sondern
wurde von Grund auf neu entwickelt. Da clasp variablenfrei arbeitet, wird ein grounder benötigt, der eine bestehende Problemrepräsentation in eine gegroundete Version
umwandelt. Ein solcher grounder ist Gringo.
Für uns von Interesse sind allerdings weniger die technischen Details, als die tatsächliche Nutzbarkeit von Clingo für unser Problem. Da Clingo einen besonderen Teil unseres
Programms darstellt, haben wir die leicht verständliche Dokumentation mit angefügt
(clingo_guide.pdf).
Unsere Problembeschreibung besteht aus vier Teilen. Der erste Teil ist die Definition
des Graphs, wobei diese in Abhängigkeit vom Problem automatisch generiert wird. Ein
Beispiel kann so aussehen.
1 % Nodes
2 node (0..3).
4 % ( Directed ) Edges
5 edge (X , Y ) : - node ( X ) , node ( Y ) , X != Y .
7 % Products
8 product (1..6).
Der zweite Teil ist die Definition der Kosten ,die ebenfalls automatisch generiert wird.
Dabei werden die Pfadkosten E für die Reise von X nach Y als expenses(X,Y,E)
und die Produktkosten C des Produkts P im Shop X als cost(P,X,C) dargestellt.
expenses(X,Y,E) :- expenses(Y,X,E). definiert die Rückrichtung der Kanten. Kosten werden allgemein in Teilen von 100 Repräsentiert um Fließkommaoperationen zu
14
Einkaufsoptimierung Kai Fabian, Dominik Moritz, Matthias Springer, Malte Swart
verhindern.
1
2
3
4
% Edge Costs / Traveling Expenses
expenses (0 ,1 ,300). expenses (0 ,2 ,400). expenses (0 ,3 ,600).
expenses (1 ,2 ,200). expenses (1 ,3 ,500).
expenses (2 ,3 ,300).
6 expenses (X ,Y , E ) : - expenses (Y ,X , E ).
8
9
10
11
12
13
14
% Product Costs
cost (1 ,1 ,1495). cost (1 ,2 ,1699). cost (1 ,3 ,1150).
cost (2 ,1 ,499). cost (2 ,2 ,459). cost (2 ,3 ,499).
cost (3 ,2 ,1290). cost (3 ,3 ,1199).
cost (4 ,1 ,599). cost (4 ,2 ,455). cost (4 ,3 ,495).
cost (5 ,1 ,1990). cost (5 ,2 ,1995).
cost (6 ,2 ,819). cost (6 ,3 ,799).
Der Dritte Teil ist die Definition der Lösung. selected(X). beschreibt, welche Nodes betrachtet werden, also in der Lösung auftauchen sollen. cycle(X,Y) beschreibt,
dass es eine Verbindung von X nach Y gibt. canReach(X,Y) definiert Erreichbarkeit im
Graphen. minimum_cost(P,CC) definiert die geringsten Kosten CC für das Produkt P.
Hier tritt die Optimierung zum Vorschein, dass wie nicht betrachten, wo ein Produkt
gekauft wird, sondern nur, zu welchem Preis (Erklärung im Abschnitt Preprocessing).
first_sorted_cost, sorted_costs und not_minimum_cost sind Optimierungen für die
Ermittlung der minimalen Kosten. Dabei wird vor dem eigentlichen Grounden der Lösung eine Sortierung der Preise erstellt, sodass die Suche anschließend um ein Vielfaches
schneller wird. An sich hätte auch eine einfache Definition gerecht, die Beschreibt, dass
es kein günstigeres gibt. Diese Version wäre allerdings um einiges langsamer. Am Ende wird mit der Definition von bought sichergestellt, dass auch wirklich jedes Produkt
gekauft wird.
1
2
3
4
5
% Generate
selected (0).
{ selected ( X ) } : - node ( X ).
1 { cycle (X , Y ) : edge (X , Y ) } 1 : - selected ( X ). % outgoing
1 { cycle (X , Y ) : edge (X , Y ) } 1 : - selected ( Y ). % incoming
7 % Define
8 : - cycle (X , Y ) , cycle (Z , Y ) , Z != X . % just a single outgoing
9 : - cycle (X , Y ) , cycle (X , Z ) , Y != Z . % just a single incoming
11
12
13
14
15
canReach (X , Y ) : - cycle (X , Y ).
canReach (X , Y ) : - cycle (X , Z ) , canReach (Z , Y ).
: - not canReach (0 ,0). % there has to be a cycle from 0 to 0
% every selected node has to be reachable
: - not canReach (0 , X ) , selected ( X ).
15
Einkaufsoptimierung Kai Fabian, Dominik Moritz, Matthias Springer, Malte Swart
17 cost (P , C ) : - cost (P ,Y , C ). % simplification
18 selected_costs (P , C ) : - selected ( Y ) , cost (P ,Y , C ).
20
21
22
23
24
25
26
27
sorted_costs (P , C1 , C2 ) : - cost (P , C1 ) , cost (P , C2 ) , C1 < C2 ,
not cost (P , C3 ): cost (P , C3 ): C1 < C3 : C3 < C2 .
first_sorted_cost (P , C ) : - product ( P ) ,
C = # min [ cost (P , CC ) = CC ] , C != # supremum .
not_minimum_cost (P , C ) : - first_sorted_cost (P , C ) ,
not selected_costs (P , C ).
not_minimum_cost (P , C ) : - not_minimum_cost (P , C2 ) ,
sorted_costs (P , C2 , C ) , not selected_costs (P , C ).
29 minimum_cost (P , C ) : - first_sorted_cost (P , C ) ,
30
not not_minimum_cost (P , C ).
31 minimum_cost (P , CC ) : - not_minimum_cost (P , C ) ,
32
sorted_costs (P ,C , CC ) , not not_minimum_cost (P , CC ).
34 % buy every product
35 bought ( P ) : - cost (P ,Y , C ) , selected ( Y ).
36 : - not bought ( P ) , product ( P ).
Als letztes wird noch definiert, was minimiert werden soll.
1 % Optimize
2 # minimize [ cycle (X , Y ) : expenses (X ,Y , E ) = E , minimum_cost (P , C ) = C ].
Anschließend muss Clingo nur noch mit den Beschreibungen als Eingabe ausgeführt
werden und die Ausgabe entsprechend geparsed und Nachbearbeitet (Postprocessing)
werden.
6.2 Genetischer Algorithmus
Beim vorliegenden Problem handelt es sich um eine leicht modifizierte Variante des TSP.
Genetische Algorithmen lassen sich leicht auf das TSP-Problem anwenden. Genetische
Algorithmen sind recht bekannt, deshalb wollen wir an dieser Stelle nur auf die Besonderheiten unserer Implementierung eingehen.
Eine dieser Besonderheiten ist die Darstellung der Individuen. Dabei wird nicht einfach
eine Liste von besuchten Shops benutzt, da diese Repräsentation nach einem Crossover
nicht notwendigerweise eine gültige Lösung generiert. So würde bei den naiven Liste
[0,1,3,2] und [0,3,1,2] mit einem Crossover in der Mitte ein ungültiges Individuum
[[0,1,1,2]] entstehen. Es ist ungültig, da die 1 zweimal besucht würde.
Aus diesem Grund repräsentieren wir die Individuen als Liste, in der die einzelnen
Werte für Indizes stehen. Beim Auflösen werden diese nacheinander an auf eine sortierte Liste angewendet und das jeweilige Element entfernt. Nehmen wir beispielsweise die
16
Einkaufsoptimierung Kai Fabian, Dominik Moritz, Matthias Springer, Malte Swart
interne Darstellung [2,0,0]. Nun soll diese in die die normale Repräsentation überführt werden. Dazu nehmen wir die sortierte Liste [1,2,3] und entfernen zuerst das
dritte (Beginn bei 0, also Index 2). Nun haben wir eine Liste mit einem Element [3]
und den Rest der sortierten Liste [2,3]. So verfahren wir weiter und erhalten die Liste [3,1,2]. Vorteil dieser Darstellung ist, dass sie beim Crossover immer eine gültige
Lösung generiert und der Algorithmus wesentlich effizienter arbeiten kann.
Mutation tritt auf einem bestimmten Individuum mit einer bestimmten Wahrscheinlichkeit auf. Zuerst wird zufällig ein Gen, also ein Element der Liste ausgewählt. Dieses
Element wird dann zufällig gesetzt. Die Anzahl der Gene, die pro Indiviuduum verändert
werden, kann variiert werden.
Eine Besonderheit in unserem genetischen Algorithmus ist das Auftreten von Katastrophen. Bei einer Katastrophe wird eine große Anzahl an Individuen durch Mutation
verändert, während die normale Mutation nur sehr wenige Individuen betrifft. Das führt
zwar dazu, dass unmittelbar nach der Mutation die Individuen insgesamt schlechter werden. Allerdings konnten wir feststellen, dass die endgültige Lösung dadurch etwas besser
wird.
Die Abbruchbedingung unseres genetischen Algorithmus ist das Durchlaufen einer
großen Anzahl von Iterationen (Generationen), ohne dass ein besseres Individuum gefunden wurde. Der Vorteil gegenüber einer festen Anzahl an Iterationen liegt darin, dass
der Algorithmus auch bei größeren Probleminstanzen gut funktioniert.
Der Code zum genetischen Algorithmus befindet sich in der Klasse Genetic in der
Datei program_genetic.py.
7 Analyse der Performance
Es wurden ein paar sehr grundlegende Untersuchungen bezüglich der Abhängigkeiten
des Programs von n und k durchgeführt.
7.1 Einwicklung der Lösungsgüte
Für verschiedene n und k Paare wurde zufällig ein Problem generiert und anschließend
untersucht wie lange die Algorithmen benötigen, um Lösungen für dieses Problem zu
finden. Die Güte der Lösungen wird dabei im Verhältnis zur optimalen Lösung angegeben
(Die mittels clingo gefunden werden kann).
17
Einkaufsoptimierung Kai Fabian, Dominik Moritz, Matthias Springer, Malte Swart
Algorithm compare n=4, k=8
2
clingo
genetic
Costs [min costs]
1.5
1
0.5
0
0
0.05
0.1
0.15
0.2
0.25
0.3
0.35
0.4
t [s]
Algorithm compare n=8, k=12
2.5
clingo
genetic
Costs [min costs]
2
1.5
1
0.5
0
0
2
4
6
8
t [s]
18
10
12
14
Einkaufsoptimierung Kai Fabian, Dominik Moritz, Matthias Springer, Malte Swart
Algorithm compare n=10, k=12
2.5
clingo
genetic
Costs [min costs]
2
1.5
1
0.5
0
0
1
2
3
4
5
6
7
8
9
10
t [s]
Es ist gut zu erkennen, dass die Algorithmen mit einer relativ schlechten Lösung anfangen und sich dann sehr schnell der optimalen Lösung annähern. Die Laufzeit von Clingo
hängt sehr stark vom Umfang des Problems ab, während der genetische Algorithmus
deutlich gleichmäßiger in der Laufzeit ist.
7.2 Abhängigkeit von k
Times (n=6)
6
first solution (clingo)
first solution (genetic)
last (new) solution (clingo)
last (new) solution (genetic)
5
t [s]
4
3
2
1
0
3
4
5
6
7
8
9
k
19
10
11
12
Einkaufsoptimierung Kai Fabian, Dominik Moritz, Matthias Springer, Malte Swart
Times (n=12)
20
first solution (clingo)
first solution (genetic)
last (new) solution (clingo)
last (new) solution (genetic)
18
16
14
t [s]
12
10
8
6
4
2
0
3
4
5
6
7
8
9
10
11
12
k
7.3 Abhängigkeit von n
Times (k=6)
0.2
last (new) solution (clingo)
last (new) solution (genetic)
0.18
0.16
0.14
t [s]
0.12
0.1
0.08
0.06
0.04
0.02
0
3
4
5
6
7
8
9
n
20
10
11
12
Einkaufsoptimierung Kai Fabian, Dominik Moritz, Matthias Springer, Malte Swart
Times (k=12)
70
last (new) solution (clingo)
last (new) solution (genetic)
60
t [s]
50
40
30
20
10
0
3
4
5
6
7
8
n
21
9
10
11
12
Herunterladen