Dokumentation zum Softwarepraktikum Abzählen und Konstruktion der Strukturisomere von Alkanen, Alkenen und Alkinen Bearbeitet von: Sabine Böhm Florian Häberlein Betreuer: Dr. Axel Kohnert Dipl.-math. Sascha Kurz Universität Bayreuth Wintersemester 2004/2005 1. Abzählen der Strukturisomere von Alkanen, Alkenen und Alkinen Basierend auf dem Artikel „Anzahl von Strukturisomeren der Alkane“ von Sascha Kurz haben wir seine Implementierung des Algorithmus geändert und den Algorithmus auf das Abzählen von Alkenen und Alkinen erweitert. Die Vorgehensweise wird im folgenden Kapitel beschrieben. 1.1 Änderung der Implementierung des Algorithmus zum Abzählen der Strukturisomere der Alkane Die ursprüngliche Implementierung des Algorithmus 1.14 (counting alkanes) aus dem oben genannten Artikel basiert auf der Idee, dass Polynome von erzeugenden Funktionen der Alkane als Vektoren dargestellt werden können, in deren i-ten Komponente der i-te Koeffizient des Polynoms steht, wobei nur ganzzahlige Koeffizienten vorkommen. Unsere Aufgabe war es nun die Datenstruktur der Polynome so umzuändern, dass ein Polynom in einer ganzen Zahl gespeichert werden kann, da die Multiplikation von Polynomen dargestellt durch Zahlen eine geringere Komplexität und damit eine geringere Laufzeit hat als die Multiplikation der Polynome dargestellt durch Vektoren. Diese Tatsache beweist folgendes Diagramm, das die Rechenzeiten eines Programms darstellt, das zwei Polynome 10 mal multipliziert. 900 Zeit in Sekunden 800 700 600 500 400 300 200 100 Grad des Polynoms 0 10 50 100 200 300 400 500 750 1000 Rechenzeit in Sekunden bei der Implementierung mit Vektoren 0,01 0,37 1,53 7,26 18,63 41,05 85,61 217,17 814,36 Rechenzeit in Sekunden bei der Implementierung mit Zahlen 0 0,01 0,16 1,65 3,53 7,19 7,44 36,07 37,35 Unter der Annahme, dass alle Koeffizienten des Polynoms kleiner als 4n sind, wobei n die maximale Anzahl der C-Atome der abzuzählenden Alkane ist, kann man also eine bijektive Funktion vector2number: INn IN mit der zugehörigen Umkehrfunktion number2vector: IN INn definieren, die die geforderten Ansprüche erfüllt. Da wir diese Funktionen nach der kompletten Umänderung der Implementierung nicht mehr benötigten, verzichten wir hier auf die genaue Funktionsdefinition und richten unser Augenmerk auf das Ergebnis der Abbildung: Bei einer gegebenen Funktion f(x) = a0 + a1x1 + a2x2 + . . . + anxn werden die Koeffizienten in einer Zahl gespeichert, die sich aus n+1 Blöcken der jeweiligen Größe 2n-Bit zusammensetzt. In jedem Block können daher Zahlen bis zu einer Größe von 22n = 4n gespeichert werden. Da jeder Koeffizient des Polynoms kleiner 4n ist, ist somit die Abbildung eindeutig, wohldefiniert und bijektiv. Zur Veranschaulichung ist hier der allgemeine Aufbau der Zahl dargestellt: 011 . . . 0001 110 . . .1011 . . . . . . . . . . . . . . . . 011 . . .1101 110 . . . 1001 (n+1)-ter Block, der den Koeffizienten an mit 2n-Bits darstellt n-ter Block, der den Koeffizienten an-1 mit 2nBits darstellt 2. Block, der den Koeffizienten a1 mit 2nBits darstellt 1. Block, der den Koeffizienten a0 mit 2nBits darstellt Nachdem diese erste Hürde geschafft war, haben wir die verwendeten Funktionen im Algorithmus der neuen Datenstruktur angepasst. Die Funktion add, die vorher zwei Polynome in n+1 Schritten addierte, addiert nun die beiden Polynome, dargestellt durch Zahlen, in einem Schritt. Ebenso die Funktion multiply, die vorher zwei Polynome komponentenweise in mehreren Schritten multiplizierte, leistet nun dasselbe, jedoch in einem Schritt. Da bei der Multiplikation von zwei Polynomen vom Grad n auch Koeffizienten am 0 mit m>n auftreten, uns aber bei der Berechnung nur Koeffizienten mit m n interessieren, haben wir ein „Gesamtschema“ eingeführt. Dies ist eine Zahl, die aus (n+1)*2n-Bit 1-ern besteht und mit deren Hilfe man durch den Bitweise-Und-Operator die überflüssigen Koeffizienten „abschneiden“ kann. Die Änderung der Funktionen CycleIndex_2, CycleIndex_3 und CycleIndex_4 war nun der nächste Schritt. In diesen Funktionen wird neben dem Addieren und Multiplizieren von Polynomen auch die Multiplikation eines Polynoms mit einer natürlichen Zahl, das Teilen eines Polynoms durch eine natürliche Zahl und die Berechnung von f(xm) durchgeführt. Das Multiplizieren und das Dividieren durch eine natürliche Zahl wird für jeden Koeffizienten einzeln vorgenommen, indem man die Zahl mit invertierter Blockreihenfolge in einer temporären Zahl speichert und dann beim wiederholten Rückspeichern mit erneut invertierter, also der ursprünglichen Blockreihenfolge, für jeden Block mit der natürlichen Zahl multipliziert bzw. durch die natürliche Zahl dividiert. Die Berechnung von f(xm) wird wieder mit Hilfe von Bitoperatoren durchgeführt. Dazu wird das Polynom bis zum Grad n / m in umgekehrter Reihenfolge der Blöcke, welche die Koeffizienten beschreiben, in einer temporären Zahl gespeichert. Beim Rückspeichern der Zahl werden nun zwischen den Blöcken immer m-1 Blöcke bestehend aus 2n 0-ern eingefügt. Nun steht in der neuen Zahl im 1-ten Block gerade a0, im m-ten Block steht a1 usw. Insgesamt steht nun in der neuen Zahl das Polynom f(xm), was gerade gewünscht war. Nachdem nun die Datenstruktur geändert wurde und die Funktionen der neuen Datenstruktur angepasst wurden, ergab sich noch ein Problem, dass vor allem bei der Berechnung für kleinere n auftrat: In den Funktionen CycleIndex_2, CycleIndex_3 und CycleIndex_4 können die Koeffizienten von Zwischenergebnissen größer als 4n werden. Damit vergrößerte sich der Platzbedarf für die Koeffizienten in der Zahl und der Platz des zugewiesenen Blockes reicht nicht mehr aus. Die Folge war, dass dadurch andere Koeffizienten verändert wurden und es zu falschen Ergebnissen kam. Um dies zu verhindern wurde die alte Blockgröße 2n vergrößert, so dass auch für Zwischenergebnisse die größer als 4n sind ausreichend Platz vorhanden ist. Das folgende Diagramm zeigt die durchschnittlichen Rechenzeiten der Programme für die beiden Implementierungen und für verschiedene n. Dabei wurde für die Implementierung mit Zahlen eine von Sascha Kurz verbesserte Version verwendet. 10000 Zeit in Sekunden (logarithmisch) 1000 100 10 1 0,1 Anzahl der C-Atome 0,01 20 40 80 160 320 640 Rechenzeit in Sekunden bei der Implementierung mit Vektoren 0,01 0,09 1,26 11,27 103,58 1125,15 Rechenzeit in Sekunden bei der Implementierung mit Zahlen 0,01 0,06 0,58 8,81 157,3 3360,84 Man sieht deutlich, dass für mehr als 160 C-Atome die Implementierung mit Vektoren wieder schneller ist. Dies liegt daran, dass im Originalprogramm sehr speicher- und rechenzeitsparend gearbeitet wurde. Die durch uns veränderte Version birgt noch weitere Zeiteinsparmöglichkeiten, sodass die theoretisch schnellere Implementierung mit Zahlen (siehe Diagramm oben) auch praktisch umgesetzt wirklich schneller ist. 1.2 Erweiterung des Algorithmus auf das Abzählen der Strukturisomere der Alkene Ausgehend von der neuen Implementierung mit Zahlen haben wir den Algorithmus 1.14 (counting alkanes) aus dem oben genannten Artikel so geändert, dass nun die Strukturisomere der Alkene gezählt werden. Dazu wurde die Grenze der maximalen Höhe der Teilbäume von (n+1)/2 auf n-2 erhöht, da bei einem Alken mit n C-Atomen die größte Höhe für einen Teilbaum gerade n-2 ist. Die Berechnung der Wurzelbäume blieb natürlich gleich. Lediglich das Zusammensetzen der Wurzelbäume zu einem Alken hat sich verändert. Dazu haben wir 5 Fälle unterschieden, wobei wir im folgenden Modell annehmen, dass das jeweilige Isomer so gedreht oder gespiegelt ist, dass einer Wurzelbäume mit der aktuell größten Höhe der Wurzelbaum W1 ist. W1 W3 W2 W4 1. Wurzelbaum W1 ist der einzige Wurzelbaum mit der aktuell größten Höhe, alle anderen Wurzelbaum haben eine kleinere Höhe 2. Die Wurzelbäume W1 und W2 haben beide die aktuell größte Höhe, die Wurzelbäume W3 und W4 haben kleinere Höhe 3. Wurzelbaum W1 und genau einer der beiden Wurzelbäume W3 oder W4 haben die aktuell größte Höhe, die beiden anderen haben eine kleinere Höhe 4. Drei der vier Wurzelbäume haben die aktuell größte Höhe, der vierte hat eine kleinere Höhe 5. Alle vier Wurzelbäume haben die aktuell größte Höhe 1.3 Erweiterung des Algorithmus auf das Abzählen der Strukturisomere der Alkine Wie bei der Änderung des Algorithmus 1.14 (counting alkanes) für Alkene haben wir die Grenze der maximalen Höhe der Teilbäume der Alkine auf n-2 erhöht. Die Berechnung der Wurzelbäume blieb natürlich ebenfalls gleich. Beim Zusammensetzen der Wurzelbäume zu Alkinen haben wir zwei Fälle unterschieden, wobei wir im folgenden Modell annehmen, dass das jeweilige Isomer so gedreht ist, dass W1 ein Wurzelbaum mit der aktuell größten Höhe ist. W1 W2 1. Wurzelbaum W1 und Wurzelbaum W2 haben die aktuell größte Höhe 2. Wurzelbaum W1 hat die aktuell größte Höhe und Wurzelbaum W2 hat eine kleinere Höhe 2. Konstruktion der Strukturisomere der Alkene und Alkine Basierend auf dem Artikel „Anzahl von Strukturisomeren der Alkane“ von Sascha Kurz haben wir seine Implementierung der Algorithmen zur Konstruktion von Alkanen geändert und den Algorithmus auf die Konstruktion von Alkenen und Alkinen erweitert. Die Vorgehensweise wird im folgenden Kapitel beschrieben. 2.1 Erweiterung des Algorithmus auf die Konstruktion der Alkene Ausgehend von der Implementierung der Algorithmen 1.9, 1.12 und 1.13 von Sascha Kurz haben wir die Implementierung auf die Konstruktion von Alkene erweitert. Wir benutzen wieder das gleiche Modell eines Alkens wie in Kapitel 1.2. Die Datenstruktur eines Alkens beschränkt sich somit auf die Speicherung der vier Wurzelbäume. Eine Unterscheidung ob die längste Kette der C-Atome gerade oder ungerade ist wird nicht mehr benötigt. Die Konstruktion ist in acht Fälle gegliedert um alle möglichen Moleküle zu konstruieren. Dabei läuft der Wurzelbaum W1 alle möglichen Wurzelbäume der aktuellen maximalen Höhe durch. Alle anderen Wurzelbäume laufen alle restlichen Höhen inklusive der aktuellen maximalen Höhe durch, wobei folgende Fälle unterschieden werden: 1. 2. 3. 4. 5. 6. 7. 8. Alle Wurzelbäume sind ungleich: Zwei Wurzelbäume sind gleich (erster Fall): Zwei Wurzelbäume sind gleich (zweiter Fall): Zwei Wurzelbäume sind gleich (zweiter Fall): Je zwei Wurzelbäume sind gleich: Drei Wurzelbäume sind gleich (erster Fall): Drei Wurzelbäume sind gleich (zweiter Fall): Alle vier Wurzelbäume sind gleich: W1 > W2 > W3 > W4 W1 > W2 > W3 = W4 W1 = W2 > W3 > W4 W1 > W2 = W3 > W4 W1 = W2 > W3 = W4 W1 > W2 = W3 = W4 W1 = W2 = W3 > W4 W1 = W2 = W3 = W4 In jedem der acht Fälle werden dann alle möglichen Stellungen der Wurzelbäume zu der Liste der Alkene hinzugefügt. Zur graphischen Ausgabe der Alkene wird eine XML-Datei im GRAPHDB-Format erstellt. Dabei werden die Wurzelbäume Knoten für Knoten durchgelaufen und anschließend werden noch die zugehörigen H-Atome in die Datei ausgegeben. Die Dokumentation zum GRAPHDB-Format findet man unter http://btm2xg.mat.uni-bayreuth.de/GRAPHDB/. 2.2 Erweiterung des Algorithmus auf die Konstruktion der Alkine Ausgehend von der Implementierung der Algorithmen 1.9, 1.12 und 1.13 von Sascha Kurz haben wir die Implementierung auf die Konstruktion von Alkine erweitert. Wir benutzen wieder das gleiche Modell eines Alkins wie in Kapitel 1.3. Die Datenstruktur eines Alkins beschränkt sich somit auf die Speicherung der zwei Wurzelbäume. Eine Unterscheidung ob die längste Kette der C-Atome gerade oder ungerade ist wird nicht mehr benötigt. Bei der Konstruktion werden für den Teilbaum W1 alle möglichen Bäume mit der aktuellen maximalen Höhe h durchlaufen. Für den rechten Teilbaum werden alle Teilbäume bis zur Höhe h durchlaufen. Die jeweiligen Alkine werden dann zur Liste der Alkine hinzugefügt. Zur graphischen Ausgabe der Alkene wird eine XML-Datei im GRAPHDB-Format erstellt. Dabei werden die Wurzelbäume Knoten für Knoten durchgelaufen und anschließend werden noch die zugehörigen H-Atome in die Datei ausgegeben. Die Dokumentation zum GRAPHDB-Format findet man unter http://btm2xg.mat.uni-bayreuth.de/GRAPHDB/.