Forschungsinstitut für Diskrete Mathematik

Programmierpraktikum für das Grundstudium

Sommersemester 2004


Thema: Steinerbäume


In diesem Programmierpraktikum soll ein Teilproblem aus der Verdrahtung höchstintegrierter Logikchips behandelt werden: es geht darum, eine Menge von Anschlusspins verschiedener Bauelemente auf einem Chip kürzestmöglich miteinander zu verbinden. Die Konstruktion eines minimalen Spannbaumes führt im allgemeinen nicht zu einer optimalen Lösung - durch das Einfügen zusätzlicher sogenannter Steinerpunkte kann man meist eine kürzere Verdrahtungslänge erreichen. Daraus ergibt sich das Steinerbaumproblem, dass zwar NP-schwer ist, aber in unserem Anwendungsfall nur auf Instanzen mit wenigen Terminalen (Anschlusspins) zu lösen ist. Ziel des Praktikums ist die effiziente Implementierung eines Algorithmus, der eine Verallgemeinerung des wohlbekannten Kürzeste-Wege-Algorithmus von Dijkstra darstellt und das Steinerbaumproblem mittels dynamischer Programmierung auf Graphen mit beliebigen nichtnegativen Kantengewichten exakt löst. Trotz der geringen Größe jeder einzelnen Probleminstanz kommt einer effizienten Implementierung hohe Bedeutung zu, da das Problem auf den größten derzeit entwickelten Chips millionenfach zu lösen ist.


Eine Zwischenbesprechung, in der Ergebnisse aus der Einarbeitungsaufgabe und Fragen zur eigentlichen Programmieraufgabe besprochen werden sollen, findet am

Montag, den 24. Mai 2003 um 17 Uhr c.t.
im Seminarraum des Institutes für Diskrete Mathematik

statt.



Studenten, die Interesse an einem Praktikumsplatz haben, aber nicht an der Vorbesprechung zu dieser Veranstaltung teilnehmen konnten, werden gebeten, sich mit

Dirk Müller
Tel. 0228 / 73 87 86

in Verbindung zu setzen.


Aufgabenstellung

1) Implementieren Sie den Algorithmus von Dijkstra für ungerichtete Graphen. Sie sollten eine Laufzeit von O((n+m) log n) erreichen, wobei n die Anzahl der Knoten und m die Anzahl der Kanten des Eingabegraphen bezeichnen.
Lesen Sie die unten stehenden Testinstanzen ein und berechnen Sie für jedes Paar von Terminalen eines Netzes einen kürzesten Weg, der diese miteinander verbindet (eine Erläuterung des Eingabeformats finden Sie unten bei den Testdaten). Geben Sie jeweils die Nummern der beiden Terminalknoten und die Länge eines kürzesten Weges zwischen diesen durch Leerzeichen getrennt auf einer Zeile aus.
Viele Terminale eines Netzes liegen sehr nah beisammen. Um einen kürzesten Weg zu finden, müssen in einem solchen Fall nur sehr wenige Knoten des Graphen gelabelt werden, insbesondere ist die Initialisierung aller Knotenlabel dann im Vergleich zur eigentlichen Suche sehr teuer. Wie können Sie kürzeste Wege für alle Terminalpaare aller Netze finden, wenn Sie nur einmal zu Beginn alle Knoten initialisieren?

2) Bilden Sie für jedes Netz jeweils den vollständigen Graphen auf der Menge seiner Terminalknoten, und geben Sie jeder Kante als Gewicht die Distanz (d.h. die Länge eines kürzesten Weges) zwischen ihren Endknoten im ursprünglichen Graphen. Implementieren Sie den Algorithmus von Kruskal oder Prim, um einen Spannbaum minimalen Gewichts in dem so konstruierten Graphen zu finden.
Geben Sie für jedes Netz jeweils die Nummer des Netzes und das Gewicht eines minimalen Spannbaums aus.

3) Implementieren Sie den Dijkstra-Steiner-Algorithmus, der in der Ihnen ausgehändigten bzw. zugesandten Literatur beschrieben wird. Statt eines minimalen Spannbaumes soll nun ein minimaler Steinerbaum konstruiert werden.
Berücksichtigen Sie hier zunächst noch keine Future Cost.

4) Erweitern Sie die in 3) entworfene Implementierung um die Berücksichtigung von Future Costs: Verwenden Sie wie im Text beschrieben die halbe Länge eines minimalen Spannbaums als Future Cost, um eine untere Schranke für die Kosten zu berechnen, die zum Anschluss der noch nicht verbundenen Terminale anfallen.

Die Einarbeitungsaufgabe besteht aus Teil 1). Geben Sie bitte bis Donnerstag, den 20.05.2004 eine korrekte und funktionsfähige Implementierung in ANSI-C oder C++ ab.
Abgabeschluss für die übrigen Aufgabenteile ist der 30.07.2004.


Datenformat und Testdaten

Jede Testinstanz besteht aus der Beschreibung eines (gitterförmigen) ungerichteten Graphen und einer Netzliste.
Die Struktur des Graphen ist immer wie folgt: Die Knoten sind in einem ganzzahligen Gitter der Größe nx * ny * nz mit nz = 2 angeordnet. Ein Knoten an Position (px, py, pz), 0 ≤ px ≤ nx - 1, 0 ≤ py ≤ ny - 1, 0 ≤ pz ≤ 1, erhält die Nummer pz * nx * ny + py * nx + px. Kanten verlaufen nur zwischen Knoten, die sich in genau einer Koordinate um 1 unterscheiden, und bezüglich der übrigen Koordinaten an derselben Position liegen. In jeder der beiden Ebenen pz = 0 bzw. pz = 1 verlaufen entweder nur Kanten in x- oder in y-Richtung. Wir können einer Ebene also eine Richtung zuordnen, und alle in dieser Richtung verlaufenden Kanten zwischen benachbarten Knoten in dieser Ebene sind im Graphen enthalten. Außerdem enthält der Graph alle Kanten zwischen in z-Richtung benachbarten Knoten.
Knoten werden durch ihre Nummer referenziert, Kanten durch die Nummern ihrer Endknoten. nx und ny werden in der Eingabedatei angegeben. Darüberhinaus ist die Kenntnis der oben beschriebenen Struktur eigentlich nicht notwendig, um den Graphen einzulesen, ermöglicht Ihnen aber eine effiziente Speicherung (insbesondere den Verzicht auf explizite Speicherung von Inzidenzinformationen).

Das Eingabeformat ist nun wie folgt:

nx ny
v(e1) w(e1) c(e1)
v(e2) w(e2) c(e2)
.
.
.
v(em) w(em) c(em)
Net 0: v0,1 v0,2 ... v0,k0
Net 1: v1,1 v1,2 ... v1,k1
.
.
.
Net p: vp,1 vp,2 ... vp,kp


Für jede Kante ei des Eingabegraphen, 1 ≤ i ≤ m, sind jeweils ihre Endknoten v(ei) und w(ei) sowie die Kosten (oder Länge) c(ei) der Kante angegeben.
Auf die Beschreibung der Kanten folgt die Liste der Netze. Ein Netz besteht aus einer Menge von miteinander zu verbindenden Terminalknoten, deren Nummern jeweils für jedes Netz in einer durch Leerzeichen getrennten Liste angegeben sind.

Hier sind zunächst einige Testdaten, die von kleinen und mittleren Chips stammen:

chip1.data.bz2 (nx = 20, ny = 22, 1292 Nets)
chip2.data.bz2 (nx = 112, ny = 99, 34987 Nets)
chip3.data.bz2 (nx = 200, ny = 204, 334951 Nets)
chip4.data.bz2 (nx = 543, ny = 579, 1531558 Nets)

Es sei angemerkt, dass das eigentliche Routinggitter viel größer ist: das Gitter, auf dem Sie in diesen Testinstanzen arbeiten, wird im Global Routing ("Grobverdrahtungsphase") verwendet und entsteht durch Kontraktion von jeweils ca. 60 x 60 Knoten des eigentlichen Routinggitters zu einem Knoten des Global-Routing-Gitters (innerhalb einer Verdrahtungsebene). Üblicherweise werden heute 6-8 Verdrahtungsebenen verwendet, von denen diejenigen mit gleicher Verdrahtungsrichtung (s. oben) darüberhinaus ebenfalls zusammengefasst werden, so dass das Global-Routing-Gitter nur noch zwei Ebenen hat.

Bei Fragen oder Problemen mit den Daten wenden Sie sich bitte einfach an die oben angegebene E-Mail-Adresse.




Prof. B. Korte
Prof. D. Rautenbach
Prof. J. Vygen