Hypergraphengenerators

Werbung
Hypergraphengenerator färbungsrelevanter
Isomorphieklassenrepräsentanten
Stephan Lukits
2. Oktober 2013
Zusammenfassung
Es wird ein Algorithmus entwickelt, der zu jeder Isomorphieklasse färbungsrelevanter Hypergraphen genau einen Repräsentanten erzeugt. Mathematischen Grundlagen zu Hypergraphen und deren Färbung finden sich in [Vol02] und [Vol09].
Inhaltsverzeichnis
1 Praeliminarien
2
2 Hypergraphengenerator
3
2.1
Kandidatenreduktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
2.2
Isomorphietest vs kanonischer Repräsentant . . . . . . . . . . . . . . . . . . . . . .
8
2.3
Grundlagen der Implementierung . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10
2.4
Kantenerzeugung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12
2.5
Hypergraphenerzeugung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16
C Generator Code
18
C.1 Klassische Methode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18
C.1.1 Kantenerzeugung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
19
C.1.2 Hypergraphenerzeugung . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
20
C.2 Klassische Methode mit Korollar 1 . . . . . . . . . . . . . . . . . . . . . . . . . . .
22
C.3 Klassische Methode mit Ordnung . . . . . . . . . . . . . . . . . . . . . . . . . . . .
22
C.3.1 Kantenerzeugung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23
C.3.2 Hypergraphenerzeugung . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
24
C.4 Orderly-Methode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
25
1
1
Praeliminarien
Zunächst sollen die allgemeinen zentralen Begriffe und Bezeichnungen kurz zusammengetragen
werden, die in dieser Arbeit Verwendung finden. Die Begriffe Menge, Relation, Funktion und
Folge wie injektiv, surjektiv und bijektiv werden in ihrer üblichen Bedeutung verwendet. Sei f eine
Funktion, dann bezeichnen dom(f ), ran(f ) und img(f ) die Definitionsmenge, den Wertebereich und
das Bild von f . Wie üblich bezeichnet f ◦ g die Verkettung bzw. Verknüpfung zweier Funktionen f
und g mit img(g) ⊂ dom(f ), wobei dom(f ◦g) = dom(g), ran(f ◦g) = ran(f ) und f ◦g(x) = f (g(x)).
Die Natürlichen Zahlen beginnen mit 1 und wir bezeichnen die ersten n natürlichen Zahlen mit
Nn := {1, 2, 3, . . . , n}. Sei M eine endliche Menge. Dann heißt eine Bijektion von M auf M
Permutation von M . In einem algorithmischen Kontext ist es von Nutzen eine Permutation von
M in ein Paar Bijektionen zu zerlegen, von denen eine als |M |-Tupel aufgefasst werden kann.
Sei M eine endliche Menge und p : M → M eine Permutation von M . Weiter sei (f : M →
N|M | , g : N|M | → M ) ein Paar Bijektionen mit p = g ◦ f . Sicher existiert solch ein Paar aber es
ist selbstredend nicht eindeutig bestimmt. Um nun z.B. alle Permutationen zu erzeugen, fixieren
wir f auf eine beliebige Bijektion von M auf N|M | . Weiter können wir ein g, definiert durch 1 7→
g(1), . . . , |M | 7→ g(|M |), eindeutig als |M |-Tupel (m1 , . . . , m|M | ) interpretieren. Umgekehrt können
wir jedes |M |-Tupel (m1 , . . . , m|M | ) mit paarweise verschiedenen Elementen aus M eindeutig als
eine Funktion g mit 1 7→ m1 , . . . , |M | 7→ m|M | auffassen. Listen wir nun all diese |M |-Tupel auf,
erhalten wir alle Bijektionen von N|M | auf M und verknüpft mit f alle Permutationen von M .
Da wir in der Bibliothek der hier verwendeten Programmiersprache schon einen Permutationsalgorithmus für indizierte Datentypen vorfinden, können wir so mit geringem Programmieraufwand
Aufwand alle Permutationen eines |M |-Tupels und damit auch von M selbst durchlaufen.
Ein Paar H := (V, E) endlicher Mengen wollen wir genau dann einen Hypergraph nennen, wenn
E ⊂ pot(V ). V heißt Knotenmenge, ein v ∈ V Knoten und |V | heißt Ordnung von H. Die Knoten eines Hypergraphen H = (V, E) werden im Weiteren mit den ersten |V | natürlichen Zahlen
bezeichnet, d.h. V = N|V | . E heißt Kantenmenge und ein e ∈ E Kante. |E| heißt die Größe
des Hypergraphen. Eine Kante e ∈ E ist in einer Kante e0 ∈ E enthalten, wenn e ⊂ e0 . Ein
Hypergraph heißt einfach, wenn keine seiner Kanten in einer anderen enthalten ist. Weiterhin
bezeichne H, auch mit Indizierung versehen oder dekoriert wie z.B. in H̃, H̄, H 0 , stets einen einfachen Hypergraphen, V (H) dessen Knotenmenge und E(H) dessen Kantenmenge. Ein Hypergraph
mit leerer Kantenmenge heißt leerer Hypergraph. Bezüglich einer festgelegten Knotenmenge ist der
leere Hypergraph eindeutig bestimmt. Sei e ⊂ V (H). Dann bezeichnet H + e den Hypergraphen
(V (H), E(H) ∪ {e}). Analog H − e := (V (H), E(H) \ {e}). Weiter führen wir die Formalismen
e ⊂ E(H) und E(H) ⊂ e ein. Ersteres ist eine Abkürzung von: „e ist in einer Kante aus E(H)
enthalten“; letzteres ist eine Abkürzung von „Es gibt eine Kante in E(H), die in e enthalten ist“.
Anstelle von E(H) schreiben wir in diesem Zusammenhang auch nur H.
Die Anzahl der Kanten, in denen ein Knoten v ∈ V (H) vorkommt, heißt Grad des Knoten in H
und wird mit grad(v) bezeichnet. Sei e ∈ E(H). Dann heißt |e| Grad von e in H. Der größte Grad
aller Kanten eines Hypergraphen heißt Rang des Hypergraphen. Haben alle Knoten den gleichen
Grad k, dann sprechen wir von einem k-regulären Hypergraph. Haben alle Kanten den gleichen
Grad r, dann sprechen wir von einem r-uniformen Hypergraph.
Wir wollen zwei (einfache) Hypergraphen H und H 0 genau dann isomorph nennen, wenn es eine
Bijektion f von V (H) auf V (H 0 ) gibt, sodass mit k ∈ N|V (H)| für alle Kanten gilt
{v1 , . . . , vk } ∈ E(H) ⇐⇒ {f (v1 ), . . . , f (vk )} ∈ E(H 0 ).
2
dom(f ), ran(f ), img(f )
Nn
Permutation
Knoten, Ordnung
Kante, Größe
einfach
V (H), E(H)
leerer Hypergraph
H ±e
e ⊂ E(H),
E(H) ⊂ e
Grad, grad(v)
Rang
k-regulären
r-uniformen
isomorph
Offenbar induziert jede Permutation von Nn einen Isomorphismus eines Hypergraphen der Ordnung
n und umgekehrt. Im Weiteren bezeichne Aut(H) die Menge der isomorphen Hypergraphen von H,
die durch alle Permutationen von Nn induziert werden. Aut(H) heißt die Automorphismengruppe
von H.
Definition 1. Eine Funktion f : V (H) → Nk wollen wir genau dann k-Färbung von H nennen,
wenn f surjektiv ist und es für jede Kante e ∈ E(H) zwei Knoten v, w ∈ e gibt mit f (v) 6=
f (w), also wenn e nicht monochromatisch ist. H heißt k-färbbar, wenn zu H eine k-Färbung
existiert. Eine surjektive Funktion f : W → Nk mit W ⊂ V (H) heißt k-Teilfärbung, wenn sie
das Färbungskriterium erfüllt. Eine k-Teilfärbung eines Hypergraphen nennen wir maximal, wenn
kein Knoten mehr gefärbt werden kann, ohne eine monochromatische Kante in dem Hypergraph
zu erzeugen.
_
Automorphismengruppe
k-Färbung
k-färbbar
k-Teilfärbung
maximale
k-Teilfärbung
Trivialerweise sind alle k-Färbungen eines Hypergraphen auch maximale k-Teilfärbungen. Um zu
betonen, dass eine k-Teilfärbung eines Hypergraph keine Färbung von diesem ist, sprechen wir wie
üblich von einer echten k-Teilfärbung des Hypergraph.
Offenbar kann eine Kante mit weniger als zwei Knoten nicht gefärbt werden. Des weiteren gilt: ist
eine Kante e ∈ E(H) korrekt gefärbt, sind auch alle Kanten e0 ∈ E(H) korrekt gefärbt, in denen
e enthalten ist.
Definition 2 (Färbungsrelevanz). Deshalb wollen wir einen Hypergraph genau dann färbungsrelevant nennen, wenn er einfach ist und jede seiner Kante wenigstens den Grad 2 hat. Entsprechend
bezeichne En alle potentiellen Kanten, färbungsrelevante Hypergraphen mit n Knoten. Eine Kante
e ∈ En heißt färbungsrelevant bezüglich eines Hypergraphen H, wenn H + e ein färbungsrelevanter
Hypergraph ist.
_
Im weiteren spielen nur noch färbungsrelevante Hypergraphen eine Rolle. Der leere Hypergraph
(bezügliche einer Knotenmenge) ist trivialerweise färbungsrelevant, und kommt uns mitunter als
Induktionsanfang gelegen.
2
Hypergraphengenerator
Der Hypergraphengenerator soll eine Auflistung färbungsrelevanter Hypergraphen der Ordnung n
konstruieren, die für jede Isomorphieklasse der färbungsrelevanten Hypergraphen der Ordnung n
genau einen Repräsentanten enthält. Zunächst überlegen wir uns ein möglichst einfaches rekursives
Verfahren, auch die klassische Methode genannt, um diese Repräsentanten zu konstruieren. Dieses
Verfahren werden wir dann schrittweise verbessern.
1. Erzeuge En
2. C0 auf den leeren Graph (mit n Knoten) und erzeuge daraus L0 die Menge der Repräsentanten
mit 0 Kanten. Setze L auf L0 .
3. Seien Ci die färbungsrelevanten Hypergraphen mit i Kanten. Dann erzeuge zu jedem Hypergraph hk ∈ {h1 , . . . , h|Ci | } = Ci die Menge
Chk = {hk + e | e ∈ En und e ist färbungsrelevant zu hk }.
3
färbungsrelevant
En
4. Erzeuge die Menge
[
Ci+1 =
Chk .
1≤k≤|Ci |
und daraus dann die Menge Li+1 der Repräsentanten mit i + 1 Kanten.
5. Setze L = L ∪ Li+1 und gehe zurück zu 2., so langen Ci+1 nicht leer ist.
6. L enthält nun die Menge der gesuchten Repräsentanten.
Wir erzeugen also (1.) zunächst die Menge der potentiellen Kanten En eines Hypergraphen der
Ordnung n. Dann erzeugen wir als Rekursionsanfang (2.) die Färbungsrelevanten Hypergraphen
mit 0 Kanten C0 und erzeugen aus dieser Menge die Repräsentanten mit 0 Kanten L0 . Im Rekursionsschritt (3. und 4.) erzeugen wir dann aus den färbungsrelevanten Hypergraphen mit i Kanten
die färbungsrelevanten Hypergraphen mit (i + 1)-Kanten Ci+1 . Aus dieser Menge filtern wir dann
die Repräsentanten mit (i + 1)-Kanten Li+1 . Offensichtlich erhalten wir irgend wann ein leeres
Ci+1 . In diesem Fall enthält L durch die sukzessive Ergänzung der gefundenen Repräsentanten
alle gesuchten Repräsentanten.
Erstens können wir z.B. umsetzen indem wir die Knoten mit den selben Indizes indizieren, mit
denen wir die Stellen aller Binärvektoren der Länge n indizieren. Dann können wir einen solchen
Binärvektor als Kante auffassen, die genau die Knoten mit den Indizes enthält, die genau die
1-Stellen des Vektors indizieren. Wollen wir nun alle Kanten erzeugen beginnen wir mit dem
Nullvektor der Länge n und addieren so lange binär 1, bis wir den 1-Vektor erhalten. Alle dabei
erzeugten Vektoren mit wenigstens zwei 1-Stellen induzieren Kanten aus En .
In den Punkten 2. – 4. ist die einzige Schwierigkeit aus einem Ci+1 die Repräsentanten Li+1
zu gewinnen. Dazu durchlaufen wir die färbungsrelevanten Hypergraphen aus Ci . Für einen
Hypergraph h dieses Durchlaufes bilden wir die Automorphismengruppe Aut(h). Wir fügen h
genau dann in Li ein, wenn es in Li keinen Hypergraph gibt, der in Aut(h) enthalten ist. Die
Automorphismengruppe eines Hypergraph erhalten wir, indem wir über die indizierten Knoten
permutieren.
Da es zu n Knoten schon 2n − (n + 1) Kanten in En gibt, wächst die Anzahl der Hypergraphen in
den Ci exponentiell in Abhängigkeit der Knoten. Des weiteren ist die Erzeugung einer Automorphismengruppe eines Hypergraphen von fakultativer Komplexität. Es ist also für das Laufzeitverhalten hilfreich, wenn wir die auf Isomorphie zu untersuchenden Hypergraphen der Ci so gering
wie möglich halten. Wir werden die folgenden Sätze verwenden, um die Anzahl der Kandidaten
zu reduzieren, die als Repräsentanten in Frage kommen.
2.1
Kandidatenreduktion
Zunächst stellen wir fest, dass wir aus einer vollständigen Menge von Repräsentanten mit i Kanten
wieder eine vollständige Menge von Repräsentanten mit i + 1 Kanten erhalten.
Satz 1 (Isomorphieerhalt). Seien H und H 0 zwei färbungsrelevante isomorphe Hypergraphen mit
n Knoten. Dann existiert zu jeder färbungsrelevanten Kante e ∈ En zu H eine färbungsrelevante
Kante e0 ∈ En zu H 0 , sodass H + e ∼
= H 0 + e0 .
Beweis. Seien H und H 0 zwei färbungsrelevante isomorphe Hypergraphen mit n Knoten und e =
{v1 , . . . , vk } ∈ En eine färbungsrelevante Kante zu H. Dann existiert eine strukturerhaltende
4
Bijektion f von V (H) auf V (H 0 ) und ein e0 = {f (v1 ), . . . , f (vk )} ∈ En . Per Konstruktion gilt
H+e ∼
= H 0 + e0 . Angenommen e0 wäre nicht zu H 0 färbungsrelevant, dann gäbe es ein ē0 =
{v̄1 , . . . v̄j } ∈ E(H 0 ) mit ē0 ⊂ e0 . Dann ist aber ē = {f −1 (v̄1 ), . . . , f −1 (v̄j )} ∈ E(H) und ē ⊂ e, was
der Färbungsrelevanz von e zu H widerspricht.
Korollar 1. Sei Li eine Repräsentantenmenge färbungsrelevanter Hypergraphen mit i Kanten.
Dann enthält
Ci+1 = {h + e | h ∈ Li , e ∈ En und e ist färbungsrelevant zu h}
von jeder Isomorphiklasse färbungsrelevanter Hypergraphen mit i + 1 Kanten wenigstens einen
Repräsentanten.
Das heißt für unseren Algorithmus, dass wir im 3. Punkt Ci durch Li ersetzen können. Wir
können noch weiter gehen und müssen gar nicht alle färbungsrelevanten Kanten durchprobieren,
wenn wir auf den Kanten eine Ordnung einführen. Wählen wir im weiteren eine Kante zu einem
Hypergraph aus, der um diese ergänzt werden soll, setzen wir stillschweigend voraus, dass es sich
um eine färbungsrelevante Kante zu diesem Hypergraph handelt.
Definition 3 (Kantenordnung). Seien e, e0 ∈ En Kanten, die durch Tupel ihrer natürlich geordneten Knoten repräsentiert werden. Dann heißt e größer als e0 , wenn e länger ist als e0 , oder wenn
sie gleich lang sind, bis zu einer Stelle i komponentenweise übereinstimmen und der Knoten von e
an der Stelle i größer als der von e0 ist.
Kantenordnung
Diese Ordnung lässt sich analog auf Hypergraphen fortsetzen, indem wir einen Hypergraphen als
entsprechend geordnetes Kantentupel auffassen.
Ausserdem setzen wir für ein e ∈ En
H<e
H < e :⇔ alle Kanten e0 ∈ E(H) sind kleiner als e.
beziehungsweise
e<H
e < H :⇔ es gibt eine Kante e0 ∈ E(H) die größer ist als e.
In diesem Sinne wollen wir anstelle von der Größe eines Graphen oder dem Grad einer Kante auch
von der Länge eines Graphen oder der Länge einer Kante sprechen. Die Indizierung geordneter
Objekte wird nun doppeldeutig verwendet. Je nach Zusammenhang bezeichnet der Index wie
üblich eine aufzählende Nummerierung; er kann aber auch die Länge des Objekts bezeichnen, also
bei einem Graphen z.B. die Anzahl der Kanten. Zählen wir die Elemente eines geordneten Objekts
auf, gehen wir immer davon aus, dass die Elemente geordnet aufgezählt werden. Bezeichnet Hk
einen Hypergraph der Länge k, dann bezeichnet Hk−1 den Hypergraphen Hk − ek , wobei ek , wie
gerade erwähnt, die größte Kante von Hk ist. H0 bezeichnet in diesem Zusammenhang den leeren
Hypergraph.
_
Korollar 2. Seien Hi und Hi0 Hypergraphen mit i Kanten und e, e0 ∈ En mit Hi < e sowie Hi0 < e0 .
Dann gilt:
Hi < Hi0 =⇒ Hi + e < Hi0 + e0 .
Nach der trivialen Konstruktion von L0 erhalten wir, falls wir C1 aufsteigend geordneter durchlaufen, das folgende L1 :
L1 = {{1, 2}}, {{1, 2, 3}}, {{1, 2, 3, 4}}, . . . , {{1, 2, 3, . . . , n}}
5
Länge
In L0 sind folglich die kleinsten Hypergraphen mit 0 Kanten und indem wir Graphen und Kanten
geordnet durchlaufen, gewinnen wir aus L0 die kleinsten Repräsentanten mit genau einer Kante.
Es stellt sich also die Frage, ob wir, durchlaufen wir die aus Li gewonnenen Hypergraphen Ci+1
aufsteigend geordnet, ein Li+1 mit den minimalen (i+1)-Repräsentanten erhalten? Dies ist offenbar
der Fall, wenn diese in Ci+1 enthalten sind. Was wiederum genau dann der Fall ist, wenn zu jedem
minimalen Repräsentanten H mit i + 1 Kanten ein minimaler Repräsentant H 0 ∈ Li mit einer
Kante e ∈ En existiert, sodass H 0 + e = H. Für L1 gilt dies trivialer weise.
Nehmen wir an, es gäbe einen minimalen Repräsentanten Hi+1 mit i + 1 Kanten zu dem es keinen
Hypergraphen H 0 ∈ Li mit einer Kante e ∈ En gibt, sodass H 0 + e = Hi+1 . Also ist insbesondere
Hi 6∈ Li . Dann existiert mit Korollar 1 und voraussetzungsgemäß ein minimaler Hi0 ∈ Li mit Hi0 ∼
=
Hi und Hi0 < Hi zu dem mit Satz 1 eine Kante e0 ∈ En existiert, sodass Hi0 +e0 ∼
= Hi +ei+1 = Hi+1 .
Voraussetzungsgemäß ist Hi0 + e0 > Hi+1 . D.h. mit Korollar 2 ist e0 < Hi0 . Es existiert also ein
maximales k ∈ {0, . . . , i − 1} mit Hk0 < e0 . Ist H̄i = {e01 , . . . , e0k , e0 , e0k+2 , . . . , e0i−1 } ∈ Li , dann ist
H̄i < Hi0 und H̄i + e0i ∼
= Hi+1 . Dies widerspricht unserer minimalen Wahl von Hi0 . Ist H̄i 6∈ Li ,
dann existiert per Induktionsvoraussetzung ein H̃i ∈ Li mit H̃i ∼
= H̄i und H̃i < H̄i < Hi0 . Da es
0
0 ∼
mit ei eine Kante gibt, sodass H̄i + ei = Hi+1 , gibt es wieder mit Satz 1 eine färbungsrelevante
Kante ẽ ∈ En , sodass H̃i + ẽ ∼
= Hi+1 . Also erhalten wir auch in diesem Fall einen Widerspruch zur
minimalen Wahl von Hi0 und können festhalten:
Satz 2 (Minimumfortsetzung). Sei Li die Menge der kleinsten Repräsentanten der Isomorphieklassen färbungsrelevanter Hypergraphen mit i Kanten. Dann gibt es zu jedem kleinsten derartigen
Repräsentanten H mit i + 1 Kanten einen Repräsentanten H 0 ∈ Li mit einem e ∈ En , sodass
H 0 + e = H.
Weiter folgt wie besprochen für unseren Algorithmus:
Korollar 3. Seien Li die kleinsten Repräsentanten der Isomorphieklassen färbungsrelevanter Hypergraphen mit i Kanten und Ci+1 die gemäß unseres Algorithmus aus Li resultierenden Hypergraphen mit i + 1 Kanten. Verarbeiten wir die Hypergraphen aus Ci+1 aufsteigend in eben dieser
Ordnung um Li+1 zu gewinnen, dann enthält Li+1 die minimalen Repräsentanten mit i + 1 Kanten.
Nehmen wir nun an, wir bekämen schon alle minimalen Repräsentanten für Ci+1 , wenn wir die
Hypergraphen aus Li nur mit Kanten ergänzten, die größer sind als die jeweiligen Hypergraphen,
also für ein hk ∈ Li :
Chk = {hk + e | e ∈ En , hk < e}.
Dann könnten wir uns durch ein aufsteigend geordnetes Durchlaufen der Hypergraphen von Li zur
Konstruktion der Kandidaten für Li+1 nicht nur die Sortierung von Ci+1 sparen, wir könnten uns
den Umweg über Ci+1 ganz sparen. Denn jeder so erzeugte neue Hypergraph mit i + 1 Kanten
ist wegen dem aufsteigend geordnetem Probieren der Kanten und Korollar 2 größer als die zuvor
erzeugten. D.h. wir könnten sofort nach dem oben vorgeschlagenen Verfahren entscheiden, ob ein
so erzeugter Kandidat in Li+1 aufgenommen wird, oder nicht.
Vermuten wir also, dass es zu jedem färbungsrelevanten H + e mit H ∈ Li und e < H einen
H 0 ∈ Li mit einem e0 ∈ En derart gibt, dass H 0 < H, H 0 < e0 und H 0 + e0 ∼
= H + e. Da es in L0
nur den Hypergraph H0 gibt und zu diesem keine kleinere Kante existiert, ergibt sich für diesen
Fall die Bestätigung unserer Vermutung trivialer weise.
Gelte nun unsere Vermutung bis zu einem beliebigen Index i und sei weiter H = {e1 , . . . , ei } aus
Li und e ∈ En , sodass e < H und H + e ein Kandidat für Li+1 ist. Dann gibt es einen größten
6
Index j ∈ {0, . . . , i − 1} mit Hj < e. Sei H ∗ = {e1 , . . . , ej , e, . . . , ei−1 }. Ist H ∗ ∈ Li , dann gilt
offenbar H ∗ < H, H ∗ < ei sowie H ∗ + ei ∼
= H + e und wir sind fertig.
Ist H ∗ 6∈ Li , dann existiert ein H̃ ∗ ∈ Li mit H̃ ∗ ∼
= H ∗ und H̃ ∗ < H ∗ < H. Da es mit ei zu H ∗ eine
∗
∼
Kante gibt, sodass H + ei = H + e, gibt es mit Satz 1 auch ein ẽ ∈ En derart, dass H̃ ∗ + ẽ ∼
= H + e.
∗
0
0
0
0 ∼
Also ist mit H̃ und ẽ die Existenz eines Hypergraphen H und einer Kante e mit H + e = H + e
und H 0 < H gesichert.
Sein nun H 0 = {e01 , . . . , e0i } ∈ Li der kleinste Hypergraph, der vorstehende Bedingung erfüllt und
e0 ∈ En eine Kante, sodass H 0 + e0 ∼
= H + e. Angenommen e0 < H 0 . Dann gibt es einen weiteren
größten Index j ∈ {0, . . . , i − 1} mit Hj0 < e0 . Sei H ∗ = {e01 , . . . , e0j , e0 , e0j+2 , . . . , e0i }. Da H ∗ < H 0
widerspräche H ∗ ∈ Li der minimalen Wahl von H 0 , also H ∗ 6∈ Li . Dann aber gibt es ein H̃ ∗ ∈ Li
mit H̃ ∗ ∼
= H ∗ und H̃ ∗ < H ∗ < H 0 wegen der Induktionsvoraussetzung. Da es mit e0i eine Kante
gibt, sodass H ∗ + e0i = H 0 + e0 ∼
= H + e, existiert mit Satz 1 zu H̃ ∗ eine Kante ẽ ∈ En derart, dass
∗
∗
0
H̃ + ẽ ∼
= H + e. Da H̃ < H , erhalten wir wieder einen Widerspruch zur minimalen Wahl von
0
H . Es folgt H 0 < e0 . Unsere Vermutung bestätigt sich also und wir können einen entsprechenden
Satz notieren.
Satz 3 (Kantenergänzung). Sei Li die Menge der kleinsten Repräsentant der Isomorphieklassen
färbungsrelevanter Hypergraphen mit i Kanten bezüglich einer lexikographischen Ordnung, die aus
einer lexikographischen Kantenordnung fortgesetzt wurde. Sei H ∈ Li , e ∈ En , e < H und H +e ein
färbungsrelevanter Hypergraph. Dann existiert ein kleinerer Hypergraph H 0 ∈ Li und eine Kante
e0 ∈ En mit H 0 < e0 , sodass
H 0 + e0 ∼
= H + e.
Korollar 4. Sei En aufsteigend lexikalisch geordnet. Sei Li die Menge der kleinsten Repräsentanten mit i Kanten bezüglich der Fortsetzung der Kantenordnung auf Hypergraphen. Die Hypergraphen aus Li werden aufsteigend durchlaufen. Sei h ein Hypergraph dieses Durchlaufes. h wird
aufsteigend um Kanten ergänzt die größer sind als h. Für jede solche Kante e > h gilt, dass h + e
genau dann in Li+1 aufgenommen wird, falls keiner der Hypergraphen Hi+1 in Li+1 Element von
Aut(h + e) ist.
Dann Enthält Li+1 von jeder Isomorphieklasse Hypergraphen mit i + 1 Kanten genau einen Repräsentanten. Dieser Repräsentant ist minimal.
Wir erhalten damit auch noch eine Vereinfachung der Berechnung der Färbungsrelevanz einer
Kante e ∈ En bezüglich eines Hypergraphen. Denn aus Definition 3 folgt:
Korollar 5. Sei Hi ein Hypergraph mit i Kanten.
Hi < e =⇒ e 6⊂ Hi
(e ∈ En )
Damit müssen wir für die Färbungsrelevanz einer Kante e bezüglich eines Hypergraph H mit H < e
nur noch prüfen, ob H 6⊂ e.
Die Korollare 1 – 4 spiegeln die Modifikationen unseres Algorithmus wieder, um die Kandidaten
zu reduzieren. Als nächstes wollen wir das Entscheidungsverfahren, nach dem ein Kandidat als
Repräsentant in Li+1 aufgenommen wird, genauer untersuchen.
7
2.2
Isomorphietest vs kanonischer Repräsentant
Gemäß des im letzten Abschnitt vorgestellten Algorithmus, müssen wir für einen Kandidaten H
der (i + 1)-Repräsentantenliste Li+1 bezüglich aller schon in Li+1 befindlichen Repräsentanten auf
Isomorphie prüfen, um nach der klassischen Methode zu entscheiden, ob H in Li+1 aufgenommen
wird oder nicht. Ein weiterer Ansatz, der die Isomorphietests umgeht, ist die sogenannte Orderly
Method. Unter der Voraussetzung, dass sich die zu erzeugende kombinatorische Struktur entsprechend ordnen lässt und der Konstruktionsalgorithmus gewisse Bedingungen erfüllt, schlug Read in
[Rea78] vor, dass die Repräsentanten der Isomorphieklassen sogenannte kanonische Repräsentanten
der jeweiligen Isomorphieklasse sein sollen. Wählen wir dann unter geeigneten Voraussetzungen
zum Beispiel das größte Objekt einer Isomorphieklasse, wie Read es tat, als kanonischen Repräsentanten, dann müssen wir einen Kandidaten H für Li+1 „nur“ in seinen kanonischen Repräsentanten
H c umformen und prüfen, ob dieser größer ist als der Letzte in Li+1 . Wobei Li+1 die geordnete
Liste der kanonischen (i + 1)-Repräsentanten bezeichnet. Ist H c größer, dann fügen wir H c in
Li+1 ein, anderenfalls wird H c verworfen. Im Wesentlichen funktioniert ein Orderly Algorithmus
wie folgt:
1. Jede Repräsentanten-Liste Li besteht ausschließlich aus kanonischen Repräsentanten, die
entsprechend der definierten Ordnung aufsteigend geordnet sind.
2. Ein durch einen Konstruktionsalgorithmus KA konstruierter (i + 1)-Kandidat wird genau
dann Li+1 hinzugefügt, wenn dies unter Einhaltung der Kriterien in 1. geschehen kann.
Damit sich die orderly Methode erfolgreich umsetzen lässt, muss der Konstruktionsalgorithmus
zusammen mit der Wahl des kanonischen Repräsentanten und der Ordnung gewisse Bedingungen
erfüllen. Bezeichne KA einen Konstruktionsalgorithmus, der aus einer Konfiguration H ∈ Li Kandidaten für Li+1 erzeugt, während KA(H) die Menge der erzeugten Kandidaten aus H bezeichnet.
Read nannte dann die folgenden Bedingungen:
1. Für jeden kanonischen Repräsentanten Hi+1 ∈ Li+1 existiert ein Hi ∈ Li , sodass Hi+1 ∈
KA(Hi ).
2. Sei f : Li+1 −→ Li ein Abbildung, die einer Konfiguration Hi+1 ∈ Li+1 den ersten iRepräsentanten Hi zuordnet, für den gilt Hi+1 ∈ KA(Hi ). Dann ist f schwach monoton
X < Y =⇒ f (X) ≤ f (Y )
(X, Y ∈ Li+1 )
3. Die Ausgabe von KA(Hi ) ist bezüglich unserer Ordnung aufsteigend sortiert.
Das ersteres der Fall sein muss, ist offensichtlich, da sonst die Vollständigkeit unserer Aufzählung
nicht gewährleistet ist. Wäre 2. nicht erfüllt, dann könnte ein kanonischer Repräsentant Hi+1
0
0
vor einem kleineren kanonischen Repräsentanten Hi+1
konstruiert werden, sodass Hi+1
der Liste
Li+1 gemäß der Orderly Method nicht mehr hinzugefügt würde. Entstehen schließlich zwei (i + 1)Repräsentanten aus dem selben i-Repräsentanten, dann muss noch sicher gestellt werden, dass der
kleinere von den Beiden zuerst konstruiert wird. Dies erledigt die 3. Bedingung.
Read formuliert dann den Satz: Erfüllt die Wahl eins Konstruktionsalgorithmus, des kanonischen
Repräsentanten und einer Ordnung auf den Repräsentanten die Bedingungen 1. – 3., dann ist der
Algorithmus effektiv. Denn 1. sichert uns die Vollständigkeit des Ergebnisses sowie 2. und 3.
zusammen die Korrektheit.
8
Orderly Method
Gehen wir zu unseren färbungsrelevanten Hypergraphen zurück, stell wir fest, dass wir im letzten
Abschnitt schon alle Bedingungen bewiesen haben, die hier gefordert werden. Tatsächlich folgt aus
Korollar 4, dass wir immer den kleinsten Hypergraphen einer Isomorphieklasse als Repräsentanten
wählen und die Hypergraphen gemäß der Ordnung aus Definition 3 konstruiert werden. Also
wählen wir für unsere kombinatorische Struktur sinnvollerweise den kleinsten Hypergraphen als
kanonischen Repräsentanten.
Definition 4 (kanonischer Repräsentant). Sie H ein Hypergraph und Aut(H) dessen Automorphismengruppe. H heißt genau dann kanonischer Repräsentant (einer Isomorphieklasse), wenn er
bezüglich der Ordnung aus Definition 3 kleiner als alle übrigen Automorphismen in Aut(H) ist. _
Damit können wir uns bei einem potentiellen Kandidaten Hi+1 ∈ KA(Hi ) einer Isomorphieklasse
für Li+1 auf die Frage beschränken: Ist Hi+1 kanonischer Repräsentant? Oder anders formuliert,
gibt es in der Automorphismengruppe von Hi+1 keinen kleineren Automorphismus? Falls nicht
nehmen wir Hi+1 in Li+1 auf, falls schon verwerfen wir ihn. Offensichtlich müssen wir dann nicht
mehr die ganze Liste durchlaufen und Isomorphietests vollziehen, was zu einer bemerkenswerten
Speicherersparnis führt. Dies war 1978, als Read sein Paper publizierte, natürlich noch von anderer
Wichtigkeit als heute. Außerdem konnten wir durch Korollar 4 das Vorgehen von Read noch
ein wenig verbessern, weil wir nun völlig unabhängig von Li+1 prüfen können, ob ein Hi+1 ein
geeigneter Kandidat für Li+1 ist. Dies hat den praktischen Vorteil, dass wir diese so adaptierte
Methode leichter parallelisieren können als das Readsche Vorgehen.
Was den Aufwand für eine nahe liegende Umformung zum kanonischen Repräsentanten anbelangt, ist diese auch von fakultativer Komplexität, sodass für die Komplexitätsbetrachtungen aus
dieser Vorgehensweise vermutlich kaum ein nennenswerter Vorteil erwachsen wird. Betrachten wir
eine nahe liegende Methode, um von einem Hypergraphen H zu überprüfen, ob er kanonischer
Repräsentant ist:
• Bilde alle Knotenpermutationen bezüglich der Knotenordnung.
• Bilde die Automorphismengruppe von H.
• Prüfe, ob in dieser ein kleinerer Graph als H enthalten ist.
Für die Isomorphietest-Variante müssten wir stattdessen prüfen, ob einer der schon in Li+1 enthaltenen Hypergraphen Element der Automorphismengruppe ist. Verwenden wir in Python den
Typ set um die Automorphismengruppe zu speichern, dann können wir im Durchschnitt in konstanter Zeit auf Enthaltensein prüfen. Sei k die Anzahl der Hypergraphen, die schon in Li+1 sind.
Dann stehen k Tests auf Enthaltensein n! Vergleichen gegenüber, wobei n die Anzahl der Knoten
ist. Ein Zusammenhang zwischen n, k und i könnte für differenziertere Komplexitätsbetrachtungen von Interesse sein. (Dabei ist i die Anzahl der Kanten der untersuchten Hypergraphen.) Wir
wollen hier nicht weiter darauf eingehen und uns für die Orderly Method entscheiden, da diese speichereffizienter und leichter zu parallelisieren ist. Last but not least legen Publikation wie [McK81],
[HG03] und [BL83] nahe, dass es noch effizientere Möglichkeiten gibt einen Hypergraphen in seinen kanonischen Repräsentanten umzuformen, als dessen vollständige Automorphismengruppe zu
erzeugen.
9
kanonischer
Repräsentant
2.3
Grundlagen der Implementierung
An dieser Stelle soll kurz in die Besonderheiten der Sprache Python eingeführt werden und die
Ergebnisse von fünf verschiedenen Implementierungsvarianten zusammengefasst werden, bevor wir
die effizienteste Implementierung im Detail betrachten. Es wurde zum einen der am Anfang von
Abschnitt 2 vorgestellte klassische Algorithmus C implementiert und eine Variante C1 davon,
die Korollar 1 berücksichtigt, da dieser eine erste signifikante Effizienzsteigerung einbringt. Dann
wurde eine Variante C5 des klassischen Algorithmus implementiert, die auch die übrigen Ergebnisse
bis Korollar 5 berücksichtigt und schließlich wurde C5 so zu Co modifiziert, dass die OrderlyMethode umgesetzt wurde. Co konnte dann durch einen Implementierungstrick Cot noch einmal
beschleunigt werden.
Betrachten wir die durchschnittliche Länge von 10 Läufen für die Erzeugung der Repräsentanten
mit 5 bzw. 6 Knoten, dann erhalten wir die Geschwindigkeitsentwicklung aus Abbildung 1 bzw.
2.
sec.
6
◦
20.83
◦
2, 16
◦
0, 97
0, 59
0, 19
◦
◦
-
C1
C
C5
Co
Cot
Algorithmus
Abbildung 1: Effizienz der Repräsentantenerzeugung für n = 5
min.
6
67, 86
◦
◦
10, 82
◦
8, 39
◦
2, 23
-
C1
C5
Co
Cot
Abbildung 2: Effizienz der Repräsentantenerzeugung für n = 6
10
Algorithmus
Wir haben also eine signifikante Verbesserung der klassischen Methode durch die Kandidatenreduktion und die orderly Methode erreicht. Für n = 5 konnte die Effizienz in etwa verhundertfacht
werden während n = 6 (Abb. 2) überhaupt erst durch diese Verbesserungen zugänglich wurde. In
Abbildung 2 können wir ebenfalls sehen, dass sich der Algorithmus C5 und Co für den Fall n = 6
annähern. Während für n = 5 noch eine Performancegewinn von gut 33 Prozent möglich war, ist
für n = 6 nur noch ein Gewinn von gut 20 Prozent festzustellen.
Betrachten wir nun die Umsetzung der effizientesten Implementierung. In den ersten beiden Zeilen
Code eines Python Moduls wird der Interpreter des Codes und dessen Codierung festgelegt.
1
2
#! / u s r / b i n / p y t h o n
# −∗− c o d i n g : u t f −8 −∗−
Als erstes erzeugen wir eine aufsteigend geordnete Aufzählung der potentiellen färbungsrelevanten Kanten. Im Kontext dieser geordneten Aufzählung erhält jede Knotenmenge, die eine Kante
repräsentiert, einen eindeutigen Index, der die Ordnung widerspiegelt. Dann erzeugen wir eine
Zuordnung, die einer (färbungsrelevanten) Kontenmenge in konstanter Zeit ihren Index in der geordneten Kantenaufzählung zuordnet. Schließlich erzeugen wir noch die Knotenpermutationen,
um später die Automorphismengruppe durchlaufen zu können.
3
4
5
6
d e f g e n e r a t e _ h y p e r g r a p h s ( numberOfNodes ) :
e d g e s = c r e a t e _ e d g e s ( numberOfNodes )
mapToIndex = c r e a t e _ e d g e s _ i n d e x _ m a p p i n g ( e d g e s )
n o d e P e r m u t a t i o n s = c r e a t e _ n o d e _ p e r m u t a t i o n s ( numberOfNodes )
Um von einem Hypergraphen H festzustellen, ob er ein kanonischer Repräsentant ist, ob also in
der Automorphismengruppe von H kein kleinerer Hypergraph als H ist, müssen wir die Automorphismengruppe von H durchlaufen. Dazu durchlaufen wir die Knotenpermutationen, die in Zeile
6 erzeugt werden, und bilden die Knoten der Kanten von H unter diesen ab. Eine so gefundene
Bildkanten ist aber schon in den erzeugten Kanten edges aus Zeile 4 enthalten. Um nun diese
Knotenmenge in konstanter Zeit der entsprechenden bereits erzeugten Kante zuordnen zu können,
berechnen wir in Zeile 5 die Zuordnung mapToIndex, die einer Knotenmenge den entsprechenden
Kantenindex aus edges zuordnet. So erzeugen wir alle Kanten der Hypergraphen der Automorphismengruppe eines Kandidaten nicht wieder neu, sondern können die schon berechneten Kanten für
diese Hypergraphen recyceln.
Dann berechnen wir aus praktischen Gründen als Rekursionsanfang die Repräsentanten mit einer Kante anstelle von L0 . Denn im Weiteren wollen wir einen Repräsentanten Ri+1 mit i + 1
Kanten aus einem entsprechenden Repräsentanten Ri mit i Kanten berechnen, indem wir Ri mit
den färbungsrelevanten Kanten kombinieren, die größer als die aus Ri sind. Wir müssen also die
größte Kante von Ri ermitteln. Fassen wir einen Hypergraphen als aufsteigend geordnetes Tupel
von Kantenindizes auf, wobei diese Indizes die Kantenordnung widerspiegeln, dann repräsentiert
der letzte Kantenindex im Tupel, die größte Kante des Hypergraphen. Nähmen wir nun als Rekursionsanfang an, dass L_i nur den leeren Hypergraphen enthält, dann würde ein Zugriff auf den
größten Index zu einer Ausnahme führen aufgrund einer Bereichsüberschreitung des leeren Tupels.
Um nun nicht während der gesamten Hypergraphenerzeugung jeden Repräsentanten auf den leeren
Hypergraph prüfen zu müssen, berechnen wir als Rekursionsanfang die Hypergraphen mit einer
Kante und stellen damit sicher, dass immer eine größte Kante in einem Repräsentanten existiert.
11
L_i = [ ]
f o r hg i n g e n e r a t e _ L 1 ( e d g e s ) :
L_i . append ( hg )
y i e l d hg
7
8
9
10
In Zeile 7 initialisieren wir die Variable L_i mit der leeren Liste [] . Eine Liste ist ein vordefinierter
indizierter Datentyp Pythons. Das i-te Element wird mit der [ i ] -Operation angesprochen, d.h.
L_i[ i ] gibt das i-te Element der Liste L_i zurück, wobei i eine ganze Zahl ist. Ist i eine positive ganze
Zahl, dann gibt L_i[−i] das i-letzte Element der Liste L_i zurück. L_i[−1] ist z.B. dementsprechend
das letzte Element von L_i. Die Indizierung einer Liste startet mit 0. Im Unterschied zu einem
typischen Array ist die Größe einer Liste nicht festgelegt und die Listenelemente sind nicht typisiert.
Zugriff, Wertzuweisung und Ergänzung eines Listenelemnts sind, wie für einen Array-Typ üblich, in
konstanter Zeit implementiert. Mit for element in indexedVar: werden die Elemente einer Variable
eines indizierter Datentyps durchlaufen, also insbesondere auch die einer Listenvariable (Zeile 8).
Eine Liste wird mit der Methode append(element) um ein neues Element ergänzt. Also wird in Zeile
9 die Liste L_i um ein Element der Aufzählung generate_L1(edges) ergänzt. Das Schlüsselwort yield
in Zeile 10 macht eine Funktion zu einer sogenannten Generator-Funktion. generate_L1(edges) aus
Zeile 8 ist ebenfalls eine Generator-Funktion. Dabei hat yield zum einen die Funktion eines returnAusdrucks und liefert den Wert des nachfolgenden Ausdrucks zurück. Im Falle von Zeile 10 wird
also der Hypergraph hg zurückgegeben. Zum anderen wird der Zustand der Funktion konserviert
und beim nächsten Aufruf der Funktion wird direkt nach dem zurück gegebenen Ausdruck wieder
eingesprungen, und die Funktion mit der selben Variablenbelegung wie beim Verlassen fortgesetzt.
w h i l e L_i :
L_i_plus_1 = [ ]
f o r hg i n g e n e r a t e _ L _ i _ p l u s _ 1 ( L_i , n o d e P e r m u t a t i o n s ,
mapToIndex , e d g e s ) :
L_i_plus_1 . append ( hg )
y i e l d hg
L_i = L_i_plus_1
11
12
13
14
15
16
17
Ist die for -Schleife aus Zeile 8 beendet enthält L_i die kleinsten Isomorphieklassenrepräsentanten
der färbungsrelevanten Hypergraphen mit einer Kante. So lange L_i nicht leer ist (Zeile 11) werden
aus den Hypergraphen von L_i die Repräsentanten mit i + 1 Kanten gewonnen (Zeile 12–15). Diese
werden, nachdem sie jeweils im Sinne einer Generator-Funktion zurück gegeben wurden (Zeile 16),
für den nächsten Durchlauf der while-Schleife L_i zugewiesen (Zeile 17). In den nächsten beiden
Teilabschnitten besprechen wir die Kanten- und Hypergraphenerzeugung im Detail.
2.4
Kantenerzeugung
Wir überlegten uns im Zuge der Umsetzung des ersten Algorithmus schon ein Vorgehen zur Kantenerzeugung. Da wir die Kanten nun aber geordnet wollen, können wir auch gleich ein Verfahren
wählen, das uns die Kanten entsprechend geordnet liefert. Dazu setzen wir die Knoten {1, . . . , n}
natürlich geordnet voraus, sodass wir jeden Knotenindex in eineindeutiger Weise als Index eines
binären n-Tupels t auffassen können. Dann können wir ein t als die Kante interpretieren, die genau
die Knoten enthält, deren Indizes 1-Stellen von t indizieren. Es gibt dann für jede färbungsrelevante Kante genau ein solches n-Tupel und für jedes Tupel mit wenigstens zwei 1-Stellen genau eine
färbungsrelevante Kante. Betrachten wir zunächst — quasi als Induktionsanfang — die Kanten
mit zwei Knoten, d.h. die binären n-Tupel mit genau zwei 1-Stellen. Bezeichne 11 die erste und
12
12 die zweite 1-Stelle. Wir beginnen mit der kleinsten 2-Kante: (110 , 121 , 02 , . . . , 0n−1 ) = {1, 2}. Sei
i der Index von 11 und j der von 12 . Dann funktioniert der Algorithmus für 2-Kanten wie folgt:
j < n − 1: Setze die Stelle j (des n-Tupels) auf 0 und die Stelle j + 1 auf 12 , d.h.
(. . . , 11i , . . . , 12j , . . . , 0n−1 ) geht über in
(. . . , 11i , . . . , 0j , 12j+1 , . . .).
Setze (den Index) j auf j + 1.
j = n − 1: Berechne i.
i < n − 2: Setze die Stellen i und j auf 0 sowie die Stelle i + 1 auf 11 und i + 2 auf 12 , d.h.
(. . . , 11i , . . . , 12n−1 ) geht über in
(00 , . . . , 0i , 11i+1 , 12i+2 , . . . , 0n−1 ).
Setze j auf i + 2.
i = n − 2: Wir sind bei (00 , . . . , 0n−3 , 11n−2 , 12n−1 ) angelangt und fertig.
Die beiden Konstruktionsregeln für ein neues Tupel haben gemein, dass 11 in dem konstruierten
Tupel auf einer kleineren Stelle ist als 12 , also ist 11 immer auf einer kleineren Stelle als 12 . Ebenso
offensichtlich durchläuft 11 alle Stellen von 0 bis einschl. n−2 und für jede dieser Stellen i durchläuft
12 die Stellen i + 1 bis einschl. n − 1, d.h. es werden alle 2-Kanten gebildet. Schließlich sehen wir,
dass beide Konstruktionsregeln zu größeren Kanten führen als die Ausgangskante (gemäß unserer
Ordnung). Damit sind die erhaltenen Kanten durch die Reihenfolge ihrer Konstruktion aufsteigend
geordnet.
Verallgemeinern wir nun 2 auf m, d.h. wir konstruieren alle m-Kanten aus n Knoten, dann ist
m
unsere Ausgangskanten (110 , . . . 1m
und für den Fall, dass
m−1 , 0m , . . . , 0n−1 ). j ist die Stelle von 1
j = n − 1, bezeichnet k die Anzahl der 1-Stellen am Ende des Tupels. Nehmen wir z.B. m = 3 und
n = 5, dann gilt im Falle (110 , 01 , 02 , 123 , 134 ), dass k = 2. i bezeichne jetzt die größte Stelle mit einer
1, der eine 0 folgt und die kleiner als j ist. Wir haben nun wieder zwei Konstruktionsregeln und eine
Abbruchbedingung, wobei i nur in der Konstruktionsregel mit der Voraussetzung j = n − 1, k < m
erscheint. Es ist klar, dass unter dieser Vorraussetzung i existiert.
j < n − 1: Setze die Stelle j auf 0 und die Stelle j + 1 auf 1m . Setze j auf j + 1.
j = n − 1 Berechne k.
k < m: Berechne i. Es gilt nun also
(. . . , 1m−k
, 0i+1 , . . . , 0n−(k+1) , 1m−k+1
, . . . , 1m
n−1 ).
i
n−k
|
{z
}
falls k=1
Setze die Stellen n − k bis einschließlich n − 1 sowie i auf 0 und setze die Stellen i + 1
bis einschl. i + k + 1 auf 1m−k , . . . , 1m . Setze j auf i + k + 1.
k = m: Wir sind bei (00 , . . . , 0n−(m+1) , 11n−m , . . . , 1m
n−1 ) angelangt und fertig.
Die zweite Transformation bedeutet also in etwa: „Wenn 1m am Ende des Tupels ist und es noch
wenigstens eine 1pi gibt, die nicht an den Einsen am Ende des Tupels hängt, dann ersetze die k
Einsen am Ende des Tupels durch 0, schiebe 1pi um eine Stelle nach rechts und hänge die k Einsen
vom Ende des Tupels an 1pi+1 an.
13
Auch im allgemeinen Fall sehen wir leicht, dass das erstmalige Anwenden der beiden Konstruktionsregeln zu Tupeln führt, in denen die 11 bis einschl. 1m ihre Reihenfolge behalten und das
Resultat einer Konstruktion stets größer ist als das vorhergehende Tupel. Nehmen wir uns zwei
beliebige n-Tupel mit m Einsen, wobei auf das eine der erste und das andere der zweite Fall zutrifft. Bezeichnen wir die Einsen von links nach rechts mit 11 bis 1m , und wenden die jeweilige
Konstruktionsregel an, dann können wir auch sehe, dass die resultierenden Tupel größere Tupel
sind und das die Einsen in ihrer Reihenfolge bleiben. Sodass wir mit Struktureller Induktion sagen können, dass die Einsen immer in der Ausgangsreihenfolge bleiben und, dass die induzierten
Kanten aufsteigend geordnet erzeugt werden.
Nun müssen wir noch die Frage klären, ob tatsächlich alle m-Kanten erzeugt werden. Setzen wir
m = 2 und k = 1, dann erhalten wir den schon untersuchten Fall der 2-Kanten für ein beliebiges
n. Nehmen wir nun an, dass unsere Behauptung bis zu (m − 1)-Kanten für beliebiges n gilt. Dann
können wir in einem beliebigen n Tupel mit m Einsen diese von links nach rechts mit 11 , . . . , 1m
bezeichnen. Ausserdem gibt es Indizes i1 bis einschl. im von 11 bis einschl. 1m . Beginnen wir mit
unsrem Ausgangstupel: (110 , . . . , 1m
m−1 , 0m , . . . , 0n−1 ), dann gilt voraussetzungsgemäß, dass die m−
2
m
1 Einsen 1 , . . . , 1 in den Stellen 1 bis n − 1 jede mögliche Position einnehmen, also insbesondere
auch die Positionen n − (m − 1) bis n − 1. Wenden wir nun unsere zweite Transformationsregel
an, dann geht
1
m
(110 , 01 , . . . , 0n−m , 12n−(m−1) , . . . 1m
n−1 ) in (01 , 11 , . . . , 1m , 0m+1 , . . . 0n−1 ) über.
Dies können wir wiederholen bis der Index von 11 der Index n − m ist. Voraussetzungsgemäß folgt
sofort, dass 12 bis einschl. 1m die nachfolgenden Stellen geordnet einnehmen werden, also werden
alle m-Kanten von n Knoten aufsteigend erzeugt. Nun müssen wir nur noch die 2- bis einschl.
n-Kanten aus n Knoten aufsteigend Zusammenfassen, um En aufsteigend geordnet zu erhalten.
Zur Umsetzung in Python definieren wir die Funktion
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
d e f c r e a t e _ e d g e s ( numberOfNodes ) :
d e f m_edges (m, n ) :
edge , j , k , e d g e s = ( [ 1 ] ∗m + [ 0 ] ∗ ( n−m) , m−1, 0 , [ ] )
w h i l e not ( k == m) : # b r e a k c o n d i t i o n
e d g e s . append ( b i n a r y _ v e c t o r _ t o _ e d g e ( e d g e ) )
i f j < n −1: # f i r s t c o n s t r u c t i o n r u l e
edge [ j ] = 0
j += 1
edge [ j ] = 1
e l s e : # j == n−1
k = 1 # c o u n t t h e o n e s a t t h e end o f t u p e l
try :
w h i l e e d g e [ n−k − 1 ] :
k += 1
e x c e p t I n d e x E r r o r : # m == n
return edges
i = n − k − 1 # calculate i
try :
w h i l e not e d g e [ i ] :
i −= 1
e x c e p t I n d e x E r r o r : # k == m
pass
e l s e : # non e x c e p t i o n b r an c h , s e c o n d c o n s t r u c t i o n r u l e
14
41
42
43
44
45
46
47
48
edge [ i ] = 0
e d g e [ n−k : n ] = [ 0 ] ∗ k
e d g e [ i +1: i+k +2] = [ 1 ] ∗ ( k+1)
j = i+k+1
return edges
def binary_vector_to_edge ( binVector ) :
return f r o z e n s e t ( pos f o r pos i n range ( l e n ( b i n V e c t o r ) )
i f b i n V e c t o r [ pos ] )
49
50
51
52
53
edges = [ ]
f o r m i n r a n g e ( 2 , numberOfNodes +1):
e d g e s += m_edges (m, numberOfNodes )
return edges
Die innere Funktion m_edges(m, n) (Zeile 18) erzeugt die m-Kanten von n Knoten und
binary_vector_to_edge(binVector) (Zeile 46) konvertiert einen binären Vektor zur Menge der Indizes der 1-Komponenten des Vektors. Die zurück gegebene Kantenmenge wird in Zeile 50 mit der
leeren liste initialisiert. Die Funktion range(m, n) liefert eine Aufzählung von einschließlich m bis
ausschließlich n. In Zeile 51 werden also alle möglichen färbungsrelevanten Kantengrade eines Hypergraph mit n Knoten durchlaufen, um dann in der darauffolgenden Zeile die Kanten eines solchen
Grads zu erzeugen und an die schon erzeugten Kanten anzuhängen. Der +-Operator verknüpft
also im Kontext zweier Listen diese Listen. Schließlich werden die erzeugten geordneten Kanten
edges zurück gegeben.
Eine Kante ist vom Datentyp frozenset . Eine Objekt dieses Typs ist nach dessen Definition im Vergleich zum Typ set nicht mehr änderbar. Deshalb können Objekte dieses Typs als „Schlüssel“ für
Schlüssel 7→ Wert Zuordnungen dienen. In den Zeilen 45 – 48 wird eine Binärvektor-Repräsentation
einer Kante in eine frozenset -Repräsentation konvertiert. Dabei liefert ein Ausdruck der Form
„ f (elem) for elem in iteration if g(elem)“ eine Aufzählung der Elemente elem unter der Funktion f
aus der Aufzählung iteration , welche die Bedingung g(elem) erfüllen. In den Zeilen 47 – 48 werden
also die positionen eines Binär-Vektors aufgezählt deren Wert nicht 0 ist. (f ist in diesem Beispiel
offenbar die Identität.) Diese Aufzählung wird dann dem Konstruktor frozenset (...) übergeben.
Die eigentliche Kantenerzeugung, wie wir sie gerade oben besprochen haben, findet dann in der
inneren Funktion m_edges(m, n) statt. Das Idiom [m]∗n erzeugt eine Liste der Länge n deren n
Elemente alle mit m initialisiert sind. Also werden in dem Ausdruck [1]∗ m + [0]∗(n−m) zwei Listen
verknüpft. Erstere dieser beiden Listen hat die Länge m und ihre Elemente sind mit 1 initialisiert,
während letztere von der Länge (n-m) ist und ihre Elemente mit 0 initialisiert sind. Offenbar
kann das Resultat als Binärvektor der länge n aufgefasst werden, dessen ersten m Stellen 1 sind
während die übrigen verschwinden. Wir initialisieren also edge mit der kleinsten Kanten mit m
Knoten und j mit der größten Stelle, die mit einer 1 besetzt ist. Da am Ende des Vektors (falls
m < n) noch keine 1 ist, wird k mit 0 initialisiert und die zurückzugebende Kantenliste wird mit
der leeren Liste initialisiert. Der Index von 1m ist nun dementsprechend m − 1 und der von 11 ist
0. So lange nicht 11 bis einschl. 1m am Ende des Tupels sind, d.h. k == m, führen wir je nach
dem eine der beiden oben beschriebenen Konstruktionen aus. Für die Zeilen 42 und 43 sei noch
angemerkt, dass myList[m:n] die Teilliste vom einschl. m-ten bis zum ausschließlich n-ten Element
bezeichnet. Dementsprechend setzt z.B. edge[n−k:n] = [0]∗k die letzten k Stellen des binärvektors
edge auf 0.
15
Nun erzeugen wir noch die Zuordnung, die einem Kanten-Objekt dessen Index in der Kantenaufzählung zuordnet.
55
56
57
58
59
def create_edges_index_mapping ( edges ) :
mapToIndex = d i c t ( )
for idx in range ( len ( edges ) ) :
mapToIndex [ e d g e s [ i d x ] ] = i d x
r e t u r n mapToIndex
Wir durchlaufen in 57 alle Kantenindizes und ordnen mit mapToIndex[edges[idx]] = idx in der darauffolgenden Zeile einer Kante ihren Index zu.
2.5
Hypergraphenerzeugung
Da die Kanten geordnet vorliegen, ist es zur Erzeugung der kleinsten Repräsentanten mit einer
Kante ausreichend alle Kanten zu durchlaufen und bei jedem Gradwechsel einen entsprechenden
Hypergraphen mit der kleinsten Kante eines bestimmten Grads zu erzeugen.
60
61
62
63
64
65
def generate_L1 ( edges ) :
lastLength = 0
for edgeIdx in range ( len ( edges ) ) :
i f len ( edges [ edgeIdx ] ) > lastLength :
lastLength = len ( edges [ edgeIdx ] )
y i e l d tuple ( [ edgeIdx ] )
Am Rückgabewert in Zeile 65 wird deutlich, dass ein Hypergraph durch ein (geordnetes) Tupel
von Kantenindizes repräsentiert wird. Für die Erzeugung von Li+1 benötigen wir zunächst noch
die Knotenpermutationen. Dazu bedienen wir uns eines permutations-Objekts, das bei Python
mitgeliefert wird.
66
67
68
69
70
71
72
from i t e r t o o l s import p e r m u t a t i o n s
d e f c r e a t e _ n o d e _ p e r m u t a t i o n s ( numberOfNodes ) :
nodePermutations = [ ]
f o r p e r m u t a t i o n i n p e r m u t a t i o n s ( r a n g e ( numberOfNodes ) ) :
n o d e P e r m u t a t i o n s . append (
t u p l e ( [ permutation , d i c t ( ) ] ) )
return nodePermutations
permutations(range(numberOfNodes)) liefert in Zeile 69 einen Generator für alle n-Tupel der Zahlen
{0, . . . , n−1} deren Stellen paarweise verschieden sind. Eine Besonderheit unsrer Implementierung
ist, dass wir eine Permutation aus nodePermutations nicht direkt verwenden sondern ein Paar definieren, dessen erste Komponente das Permutationstupel ist und dessen zweite Komponente eine leere
Zuordnung ist (Zeile 71). Da bei der Erzeugung der Automorphismengruppe eines Kandidaten
immer wieder die selben Kanten abgebildet werden, wird eine einmal berechnete Zuordnungen von
einer Kante zu deren Bild unter einer Permutation in der zweiten Komponente der entsprechenden
Permutation gespeichert. Dadurch steigt zwar der Speicherbedarf wieder spürbar an. Er bleibt
aber in einem praktikablem Rahmen und der Effizienzgewinn ist für n = 5 z.B. eine Verdreifachung
wie an dem Unterschied zwischen den Implementierungen Co und Cot in Abbildung 1 zu sehen ist.
Abschließend ist noch die Generator-Funktion zur Erzeugung der Repräsentanten mit i + 1 Kanten
aus den Repräsentanten mit i Kanten zu implementieren.
16
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
d e f g e n e r a t e _ L _ i _ p l u s _ 1 ( L_i , n o d e P e r m u t a t i o n s , mapToIndex , e d g e s ) :
def i s _ c a n o n i c a l ( c a n d i d a t e ) :
d e f i s _ s m a l l e r _ t h e n ( agHg , c a n d i d a t e ) :
f o r i d x i n r a n g e ( l e n ( agHg ) ) :
i f agHg [ i d x ] > c a n d i d a t e [ i d x ] : r e t u r n F a l s e
i f agHg [ i d x ] < c a n d i d a t e [ i d x ] : r e t u r n True
return False # i d e n t i c a l
f o r np i n n o d e P e r m u t a t i o n s :
agHg = t u p l e ( ) # new h y p e r g r a p h o f Aut ( h y p e r g r a p h )
for edgeIdx in candidate :
t r y : agHg += np [ 1 ] [ e d g e I d x ]
except :
np [ 1 ] [ e d g e I d x ] = t u p l e ( [ # we r e c y c l e e d g e s
mapToIndex [ f r o z e n s e t (
np [ 0 ] [ n i ] f o r n i i n e d g e s [ e d g e I d x ] ) ] ] )
agHg += np [ 1 ] [ e d g e I d x ]
i f i s _ s m a l l e r _ t h e n ( s o r t e d ( agHg ) , c a n d i d a t e ) :
return False
r e t u r n True
d e f i s _ c o l o r i n g _ r e l e v a n t _ t o ( edge , h y p e r g r a p h ) :
for edgeIdx in hypergraph :
i f edges [ e d g e I d x ] . i s s u b s e t ( edge ) : retu rn F a l s e
r e t u r n True
96
97
98
99
100
101
102
f o r h y p e r g r a p h i n L_i :
f o r e d g e I d x i n r a n g e ( ( h y p e r g r a p h [ −1]+1) , l e n ( e d g e s ) ) :
i f i s _ c o l o r i n g _ r e l e v a n t _ t o ( edges [ edgeIdx ] , hypergraph ) :
candidate = hypergraph + tuple ( [ edgeIdx ] )
i f is_canonical ( candidate ):
y i e l d ( candidate )
Die inner Funktion is_canonical (candidate) (Zeile 74) berechnet, ob der übergebene Kandidate kanonisch ist. Die inner Funktion is_coloring_relevant_to (edge, hypergraph) prüft, ob die Kante edge
zum Hypergraph hypergraph aus Li färbungsrelevant ist. Wir durchlaufen also alle Hypergraphen
H aus Li (Zeile 97). Wir prüfen dann zu jedem H jede Kanten e, die größer als die größte Kante
in H ist, ob sie färbungsrelevant zu H ist (Zeilen 98f). Für diesen Fall (Zeile 100) haben wir einen
neuen Kandidaten H + e. Von diesem berechnen wir, ob er kanonisch ist und falls ja, geben wir
ihn zurück. Anderenfalls machen wir mit der nächsten Kante bzw. mit dem nächsten Hypergraph
weiter.
Zur Berechnung von is_coloring_relevant_to (edge, hypergraph) prüfen wir entsprechend Korollar 5
nur, ob der aktuelle Hypergraph H aus Li eine Kante enthält (Zeile 93), die eine Teilmenge der
übergebenen Kante e ist (Zeile 94). Im letzteren Fall wird entsprechend Definition 2 und der
mangelnden Einfachheit von H + e False zurück gegeben; stellt sich H + e als einfach heraus, wird
True zurück gegeben.
Wurde so ein neuer Kandidate H + e gefunden, dann werden in den Zeilen 80–88 alle Automorphismen durchlaufen und in den Zeilen 75–79 wird von jedem Automorphismus getestet, ob er kleiner
ist als der aktuelle Kandidat. Findet sich kein Automorphismus, der kleiner ist als der Kandidat,
dann ist der Kandidat offenbar kanonisch.
17
Anhang
C
Generator Code
Jede der folgenden Implementierungen beginnt mit den Zeilen
103
104
#! / u s r / b i n / p y t h o n
# −∗− c o d i n g : u t f −8 −∗−
105
106
107
108
109
d e f g e n e r a t e _ h y p e r g r a p h s ( numberOfNodes ) :
e d g e s = c r e a t e _ e d g e s ( numberOfNodes )
mapToIndex = c r e a t e _ e d g e s _ i n d e x _ m a p p i n g ( e d g e s )
n o d e P e r m u t a t i o n s = c r e a t e _ n o d e _ p e r m u t a t i o n s ( numberOfNodes )
In Zeile 5 wird eine Liste von Kanten erzeugt und in der darauffolgenden Zeile eine Zuordnung,
die einer Kante ihren Index in der zuvor berechneten Liste zuordnet. Denn wenn die Automorphismengruppe eines Hypergraphen, der als Kantenmenge implementiert ist, erzeugt wird, werden
die Knoten einer Kante durch eine Knoten-Permutation auf eine andere Kante abgebildet. Um
nun eine Kante nicht vielfach im Speicher zu halten, finden wir durch diese Zuordnung den Index
dieser Kante in unserer Kantenliste und verwenden dann die Kante aus der edges-Liste. In Zeile
7 erzeugen wir dann noch eine Liste der Kontenpermutationen, die wir für jede Erzeugung einer
Automorphismengruppe benötigen.
Ausserdem finden wir in einer generate_hypergraphs-Funktion wenigstens eine yield hg-Anweisung
(wobei hg einen Hypergraph enthält). Das Schlüsselwort yield hat neben der Funktionsweise
des return-Schlüsselworts noch die Eigenschaften, dass der Zustand (des Rahmens) der Funktion
auch nach der Rückgabe eines Wertes durch yield im Speicher gehalten wird und dass ein darauf folgender Aufruf der Funktion direkt nach dem Rückgabewert der yield -Anweisung fortgesetzt
wird. Das Schlüsselwort yield macht eine Funktion in Python zu einer sogenannten GeneratorFunktion. Diese haben z.B. den Vorteil, dass aufwendig rekursiv erzeugte Objekte einzeln zurück
gegeben werden können, ohne gleich alle Objekte berechnen zu müssen. So können wir z.B. mit
for hg in generate_hypergraphs(5): ... Hypergraph für Hypergraph Isomorphieklassenrepräsentanten der Hypergraphen mit 5 Knoten berechnen.
C.1
1
2
Klassische Methode
#! / u s r / b i n / p y t h o n
# −∗− c o d i n g : u t f −8 −∗−
3
4
5
6
7
8
9
10
11
12
d e f g e n e r a t e _ h y p e r g r a p h s ( numberOfNodes ) :
e d g e s = c r e a t e _ e d g e s ( numberOfNodes )
mapToIndex = c r e a t e _ e d g e s _ i n d e x _ m a p p i n g ( e d g e s )
n o d e P e r m u t a t i o n s = c r e a t e _ n o d e _ p e r m u t a t i o n s ( numberOfNodes )
C_i = s e t ( [ f r o z e n s e t ( ) ] ) # i n i t i a l i z e C_i w i t h empty h y p e r g r a p h
w h i l e C_i :
f o r hg i n g e n e r a t e _ L _ i ( C_i , n o d e P e r m u t a t i o n s , mapToIndex , e d g e s ) :
y i e l d hg
C_i = c r e a t e _ C _ i _ p l u s _ 1 ( C_i , e d g e s )
18
Nachdem die Zeilen 5–7 klar sind, können wir in 8 Ci mit dem leeren Hypergraph initialisieren
und mit der Erzeugung der Hypergraphen beginnen. Dies geschieht in dem wir mittels des Generators generate_L_i in Zeile 10 die Isomophieklassen-Repräsentanten aus Ci heraus filtern. In der
darauffolgenden Zeile wird jeder so gefundene Repräsentant mit dem Schlüsselwort yield zurück
gegeben. Schließlich erzeugen wir mit der Funktion create_C_i_plus_1 in 12 aus Ci die nächsten
potentiellen Repräsentanten mit i + 1 Kanten. So lange diese Funktion potentielle Repräsentanten
zurück liefert fahren wir fort, aus diesen Repräsentanten herauszufiltern.
Ci wird mittels des Datentyps set implementiert während Hypergraphen und Kanten durch den
Datentyp frozenset umgesetzt werden. Objekte letzteren Typs sind unveränderbar. Deshalb können sie einen hash-Wert haben, der Voraussetzung ist, um Element einer Menge (Typ: set ) oder
Schlüssel einer Zuordnung (Typ: dict ) sein zu können. Da Hypergraphen als Elemente der Ci Menge Elemente einer Menge sind und Kanten in der Zuordnung mapToIndex als Schlüssel dienen,
werden beide als Objekte vom Typ frozenset implementiert.
C.1.1
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Kantenerzeugung
d e f c r e a t e _ e d g e s ( numberOfNodes ) :
def add_binary_one_to_binVector ( ) :
for p o s i t i o n in range ( len ( binVector ) ) :
i f not b i n V e c t o r [ p o s i t i o n ] :
binVector [ position ] = 1
break
else :
binVector [ position ] = 0
def hase_binVector_two_nodes ( ) :
oneNode = F a l s e
for p o s i t i o n in range ( len ( binVector ) ) :
i f binVector [ position ] :
i f oneNode : r e t u r n True
e l s e : oneNode = True
def add_binVector_to_edges ( ) :
e d g e s . append (
f r o z e n s e t ( p o s i t i o n for p o s i t i o n in range ( len ( binVector ))
i f binVector [ position ] ) )
31
32
33
34
35
36
37
38
39
b i n V e c t o r = [ 0 ] ∗ numberOfNodes
e n d V e c t o r = [ 1 ] ∗ numberOfNodes
edges = [ ]
w h i l e b i n V e c t o r != e n d V e c t o r :
add_binary_one_to_binVector ()
i f hase_binVector_two_nodes ( ) :
add_binVector_to_edges ( )
return edges
Um den Algorithmus möglichst kompakt formulieren zu können, werden in den Zeilen 21–37 Funktionen definiert, die auf der Funktionsvariable binVector arbeiten, die in Zeile 39 mit dem (binären)
0-Vektor initialisiert wird. Der in der darauf folgenden Zeile definierte 1-Vektor dient zwei Zeilen
weiter als Abbruchbedingung. Die Variable edges wird mit der leeren Liste initialisiert und wird
im folgenden um die erzeugten Kanten ergänz und schließlich als Ergebnis der Berechnung zurück
gegeben.
19
In den Zeilen 42-45 werden dann die Kanten erzeugt, so lange bis binVector mit endVector identisch
ist. Die Abbruchbedingung wird erreicht, da in Zeile 43 zu binVector 1 binär hinzugezählt wird.
Dann wird geprüft, ob der so entstandene Binärvektor wenigstens zwei 1 enthält. Ist dies der Fall
wird er in eine Kante konvertiert und der Kantenliste hinzugefügt.
Noch ein paar Bemerkungen zum Verständnis des Codes. Die Anweisung range(n) erzeugt
die Aufzählung (0, 1, . . . , n − 1), sodass for position in range(len (binVector )) alle Indizes der Liste binVector durchläuft. Die 0 wird in einem Konditionalen Zusammenhang als falsch ( False )
interpretiert und alle Zahlen ungleich 0 als wahr (True), sodass if not binVector [ position ]: genau dann wahr ist, wenn binVector an der Stelle position eine 0 stehen hat. Das Idiom
[ f (item) for item in enumeration if g(item)] erzeugt ein Aufzählung der f (item) für die g(item) wahr
ist, sodass in den Zeilen 36 und 37 eine Aufzählung der Stellen des Binärvektors erzeugt werden,
die ungleich 0 sind. Diese Aufzählung wird dann dem Konstruktor der forzenset -Klasse übergeben. Die so erzeugte Kante wird dann mit der Methode append in 35 der Kantenliste hinzugefügt.
Schließlich erzeugt das Idiom [m]∗n eine Liste der Länge n deren Elemente alle mit m initialisiert
werden, also erzeugt [0]∗ numberOfNodes eine Liste der Länge numberOfNodes deren Elemente alle
mit 0 initialisiert sind.
In diesem Zusammenhang können wir auch noch die Erzeugung der Kanten-Index-Zuordnung betrachten:
40
41
42
43
44
def create_edges_index_mapping ( edges ) :
mapToIndex = d i c t ( )
for idx in range ( len ( edges ) ) :
mapToIndex [ e d g e s [ i d x ] ] = i d x
r e t u r n mapToIndex
Der Datentyp dict liefert eine Zuordnung. In Zeile 49 werden alle Kantenindizes der zuvor erzeugten
Liste durchlaufen und in der darauf folgenden Zeile wird einer Kante ihr Index zugeordnet.
C.1.2
Hypergraphenerzeugung
Bevor wir die Kandidaten für die Isomorphieklassenrepräsentanten erzeugen betrachten wir kurz
die Erzeugung der Knotenpermutationen. Diese ist denkbar einfach, da wir uns des bei Python
mitgelieferten permutations-Modul bedienen können:
45
46
47
48
49
50
from i t e r t o o l s import p e r m u t a t i o n s
d e f c r e a t e _ n o d e _ p e r m u t a t i o n s ( numberOfNodes ) :
nodePermutations = [ ]
f o r p e r m u t a t i o n i n p e r m u t a t i o n s ( r a n g e ( numberOfNodes ) ) :
n o d e P e r m u t a t i o n s . append ( p e r m u t a t i o n )
return nodePermutations
der permutations generator erzeugt zu einem n-Tupel (0, 1, . . . , n − 1) alle dessen Permutationen.
51
52
53
54
55
56
d e f c r e a t e _ C _ i _ p l u s _ 1 ( C_i , e d g e s ) :
d e f i s _ c o l o r i n g _ r e l e v a n t _ t o ( edge , hg ) :
f o r e i n hg :
i f edge . i s s u b s e t ( e ) or e . i s s u b s e t ( edge ) :
return False
r e t u r n True
57
58
C_i_plus_1 = s e t ( )
20
59
60
61
62
63
f o r hg i n C_i :
f o r edge i n edges :
i f i s _ c o l o r i n g _ r e l e v a n t _ t o ( edge , hg ) :
C_i_plus_1 . add ( hg | f r o z e n s e t ( [ e d g e ] ) )
r e t u r n C_i_plus_1
Die Erzeugung der Menge Ci+1 in Abhängigkeit von Ci (und den Kanten) geschieht indem zu jedem
Hypergraph hg aus Ci (Zeile 66) bezüglich einer jeden Kante edge (Zeile 67) geprüft wird, ob sie
zu hg Färbungsrelevant ist (Zeile 68). Ist edge färbungsrelevant zu hg, dann wird der Hypergraph
hg + edge der Menge Ci+1 hinzugefügt (Zeile 69). Sind m1 und m2 beide vom Typ frozenset , dann
liefert m1 | m2 ein frozenset , das die Vereinigung von m1 und m2 ist. Der Ausdruck frozenset ([ edge])
erzeugt ein Objekt vom Typ frozenset , das als einziges Element edge enthält.
64
65
66
67
68
69
70
71
72
73
74
d e f g e n e r a t e _ L _ i ( C_i , n o d e P e r m u t a t i o n s , mapToIndex , e d g e s ) :
d e f Aut ( h y p e r g r a p h ) :
automorphismGroup = s e t ( )
f o r np i n n o d e P e r m u t a t i o n s :
agHg = f r o z e n s e t ( ) # new h y p e r g r a p h f o r Aut ( h y p e r g r a p h )
f o r edge i n hypergraph :
agHg = agHg | f r o z e n s e t ( [ # we r e c y c l e e d g e s
e d g e s [ mapToIndex [ f r o z e n s e t (
np [ node ] f o r node i n e d g e ) ] ] ] )
automorphismGroup . add ( agHg )
r e t u r n automorphismGroup
75
76
77
78
79
80
81
82
83
84
85
86
87
L_i = s e t ( )
f o r c a n d i d a t e i n C_i :
A u t C a n d i d a t e = Aut ( c a n d i d a t e )
c a n d i d a t e S u c c e e d e d = True
f o r h y p e r g r a p h _ f r o m _ L _ i i n L_i :
i f hypergraph_from_L_i i n AutCandidate :
candidateSucceeded = False
break
i f candidateSucceeded :
L_i . add ( c a n d i d a t e )
y i e l d candidate
return
Zur Erzeugung der Repräsentanten mit i Kanten müssen wir uns die schon gefundenen Repräsentanten merken, um von einem weiteren Kandidaten entscheiden zu können, ob er aufgenommen
wird. Deshalb initialisieren wir in Zeile 83 die Variable L_i mit der leeren Menge und verwenden
sie, um uns die schon erzeugten Repräsentanten zu merken (Zeile 92). Um nun die Repräsentanten
zu gewinnen, durchlaufen wir die Kandidaten mit i Kanten (Zeile 84). Zu jedem Kandidaten erzeugen wir dessen Automorphismengruppe (Zeile 85) und prüfen, ob sich einer der Hypergraphen
aus L_i in dieser Automorphismengruppe befindet (Zeilen 87–90). Ist dies nicht der Fall (Zeile 91),
dann ergänze L_i um diesen Kandidaten und gib ihn zurück.
Für die Erzeugung der Automorphismengruppe eines Kandidaten durchlaufen wir alle Knotenpermutationen (Zeile 74). Für jede dieser Permutationen bilden wir alle Kanten des Hypergraphen
(Zeile 76) unter der Permutation ab (Zeile 79) und fassen diese Bildkanten zu einem Hypergraphen
der Automorphismengruppe zusammen (Zeile 77).
21
C.2
Klassische Methode mit Korollar 1
Hier ändert sich auf der Code-Ebene kaum etwas, aber die Geschwindigkeit für die Berechnung
der Isomorphieklassenrepräsentanten der Hypergraphen mit 5 Knoten verzehnfacht sich.
1
2
#! / u s r / b i n / p y t h o n
# −∗− c o d i n g : u t f −8 −∗−
3
4
5
6
7
8
9
10
11
12
13
14
d e f g e n e r a t e _ h y p e r g r a p h s ( numberOfNodes ) :
e d g e s = c r e a t e _ e d g e s ( numberOfNodes )
mapToIndex = c r e a t e _ e d g e s _ i n d e x _ m a p p i n g ( e d g e s )
n o d e P e r m u t a t i o n s = c r e a t e _ n o d e _ p e r m u t a t i o n s ( numberOfNodes )
C_i = s e t ( [ f r o z e n s e t ( ) ] ) # i n i t i a l i z e C_i w i t h empty h y p e r g r a p h
w h i l e C_i :
L_i = s e t ( )
f o r hg i n g e n e r a t e _ L _ i ( C_i , n o d e P e r m u t a t i o n s , mapToIndex , e d g e s ) :
L_i . add ( hg )
y i e l d hg
C_i = c r e a t e _ C _ i _ p l u s _ 1 ( L_i , e d g e s )
Wir benutzen jetzt in Zeile 14 zur der Berechnung der Kandidaten mit i + 1 Kanten nicht mehr Ci
als Ausgangsmenge sondern die Repräsentanten mit i Kanten, also Li . Aus diesem Grund müssen
wir uns die Repräsentanten mit i Kanten merken, was zu den zusätzlichen Zeilen 10 und 12 führt.
C.3
Klassische Methode mit Ordnung
Unter Ausnutzung von Definition 3 und der übrigen Korollare von Korollar 1 bis Korollar 5 erhalten
wir den folgenden Code.
#! / u s r / b i n / p y t h o n
2 # −∗− c o d i n g :
u t f −8 −∗−
1
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
d e f g e n e r a t e _ h y p e r g r a p h s ( numberOfNodes ) :
e d g e s = c r e a t e _ e d g e s ( numberOfNodes )
mapToIndex = c r e a t e _ e d g e s _ i n d e x _ m a p p i n g ( e d g e s )
n o d e P e r m u t a t i o n s = c r e a t e _ n o d e _ p e r m u t a t i o n s ( numberOfNodes )
L_i = [ ]
f o r hg i n g e n e r a t e _ L 1 ( e d g e s ) :
L_i . append ( hg )
y i e l d hg
w h i l e L_i :
L_i_plus_1 = [ ]
f o r hg i n g e n e r a t e _ L _ i _ p l u s _ 1 ( L_i , n o d e P e r m u t a t i o n s ,
mapToIndex , e d g e s ) :
L_i_plus_1 . append ( hg )
y i e l d hg
L_i = L_i_plus_1
Hier ändert sich nun einiges. Li wird nun nicht mehr als Menge sondern als Liste implementiert,
damit die Ordnung garantiert werden kann. Wir erzeugen nun den Rekursionsanfang — die Repräsentanten mit einer Kante — separat. Der Grund dafür ist, dass der Code zur Erzeugung der
22
Repräsentanten mit i + 1 Kanten auf größte Kante eines Repräsentanten mit i Kanten zugreift.
Dies ist natürlich beim leeren Hypergraph nicht möglich. Um nun nicht ständig auf den leeren Hypergraph prüfen zu müssen erzeugen wir L1 separat und haben dann für alle übrigen Li garantiert,
dass die Graphen aus Li−1 wenigstens eine Kante, also insbesondere eine größte Kanten, enthalten.
Nachdem in den Zeilen 9-11 L1 erzeugt wurde, werden die übrigen Hypergraphen erzeugt, so lange
es zu einem Hypergraph aus Li noch wenigstens eine färbungsrelevante Kante existiert. Ci müssen
wir in dieser Variante nicht mehr erzeugen, wir erzeugen Li+1 direkt aus Li (Zeile 14). Wurde
Li+1 erzeugt, dann setzen wir Li auf Li+1 und setzen Li+1 für die nächste Runde wieder auf die
leere Liste (Zeilen 18 und 13).
C.3.1
Kantenerzeugung
Die Kantenerzeugung gerät nun etwas aufwändiger und wird schon in Abschnitt 2.4 ausführlich
besprochen. Da wir die Kanten geordnet erzeugen, können wir das Größenverhältnis zweier Kanten
durch ihre Indizes in der geordneten Kantenauflistung feststellen. Deshalb können wir eine Kante
weiterhin als ungeordnete Knotenmenge mit dem Typ frozenset speichern (Zeile 21).
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
d e f c r e a t e _ e d g e s ( numberOfNodes ) :
def binary_vector_to_edge ( binVector ) :
return f r o z e n s e t ( pos f o r pos i n range ( l e n ( b i n V e c t o r ) )
i f b i n V e c t o r [ pos ] )
d e f m_edges (m, n ) :
edge , j , k , e d g e s = ( [ 1 ] ∗m + [ 0 ] ∗ ( n−m) , m−1, 0 , [ ] )
w h i l e not ( k == m) :
e d g e s . append ( b i n a r y _ v e c t o r _ t o _ e d g e ( e d g e ) )
i f j < n −1:
edge [ j ] = 0
j += 1
edge [ j ] = 1
else :
k = 1 # c o u n t t h e o n e s a t t h e end o f t u p e l
try :
w h i l e e d g e [ n−k − 1 ] :
k += 1
e x c e p t I n d e x E r r o r : # k == 0 : m == n
return edges
i = n − k − 1 # f i n d b i g g e s t i d x o f one n o t a t end
try :
w h i l e not e d g e [ i ] :
i −= 1
e x c e p t I n d e x E r r o r : # k == m
pass
else :
edge [ i ] = 0
e d g e [ n−k : n ] = [ 0 ] ∗ k
e d g e [ i +1: i+k +2] = [ 1 ] ∗ ( k+1)
j = i+k+1
return edges
50
51
edges = [ ]
23
52
53
54
f o r m i n r a n g e ( 2 , numberOfNodes +1):
e d g e s += m_edges (m, numberOfNodes )
return edges
Die Zuordnung der Kanten zu ihren Indizes erzeugen wir wie gehabt.
55
56
57
58
59
def create_edges_index_mapping ( edges ) :
mapToIndex = d i c t ( )
for idx in range ( len ( edges ) ) :
mapToIndex [ e d g e s [ i d x ] ] = i d x
r e t u r n mapToIndex
C.3.2
Hypergraphenerzeugung
An der Verfahrensweise zur Erzeugung der Permutationen ändert sich auch nichts.
60
61
62
63
64
65
from i t e r t o o l s import p e r m u t a t i o n s
d e f c r e a t e _ n o d e _ p e r m u t a t i o n s ( numberOfNodes ) :
nodePermutations = [ ]
f o r p e r m u t a t i o n i n p e r m u t a t i o n s ( r a n g e ( numberOfNodes ) ) :
n o d e P e r m u t a t i o n s . append ( p e r m u t a t i o n )
return nodePermutations
Die Erzeugung von L1 setzen wir um, indem wir die Kanten geordnet durchlaufen und jedes mal,
wenn sich die Länge einer Kante ändert erzeugen wir einen neuen Repräsentanten mit der kleinsten
Kanten ihrer Länge.
66
67
68
69
70
71
def generate_L1 ( edges ) :
lastLength = 0
for edgeIdx in range ( len ( edges ) ) :
i f len ( edges [ edgeIdx ] ) > lastLength :
lastLength = len ( edges [ edgeIdx ] )
y i e l d tuple ( [ edgeIdx ] )
Hier zeigt sich schon, dass wir Hypergraphen weiterhin als Tuple, also als geordnete Mengen
von Kanten, auffassen (Zeile 71). Wir benötigen zwar noch keinen Größenvergleich zwischen zwei
Hypergraphen, aber zur Erzeugung neuer Kandidaten aus einem schon gewonnenen Repräsentanten
benötigen wir dessen größte Kante. Diese ist einfach die letzte Kante des Repräsentanten (Zeile 90),
wenn wir Hypergraphen entsprechend ihrer Konstruktion als geordnete Kantenmengen auffassen.
72
73
74
75
76
77
78
79
80
81
82
83
d e f g e n e r a t e _ L _ i _ p l u s _ 1 ( L_i , n o d e P e r m u t a t i o n s , e i d i c t , e d g e s ) :
d e f Aut ( h y p e r g r a p h ) :
automorphismGroup = s e t ( )
f o r np i n n o d e P e r m u t a t i o n s :
agHg = t u p l e ( ) # new h y p e r g r a p h f o r Aut ( h y p e r g r a p h )
for edgeIdx in hypergraph :
agHg = agHg + t u p l e ( [ # we r e c y c l e e d g e s
eidict [ frozenset (
np [ e i ] f o r e i i n e d g e s [ e d g e I d x ] ) ] ] )
automorphismGroup . add ( t u p l e ( s o r t e d ( agHg ) ) )
r e t u r n automorphismGroup
d e f i s _ c o l o r i n g _ r e l e v a n t _ t o ( edge , h y p e r g r a p h ) :
24
for edgeIdx in hypergraph :
i f edges [ e d g e I d x ] . i s s u b s e t ( edge ) : ret urn F a l s e
r e t u r n True
84
85
86
87
L_i_plus_1 = s e t ( )
f o r h y p e r g r a p h i n L_i :
f o r e d g e I d x i n r a n g e ( ( h y p e r g r a p h [ −1]+1) , l e n ( e d g e s ) ) :
i f i s _ c o l o r i n g _ r e l e v a n t _ t o ( edges [ edgeIdx ] , hypergraph ) :
candidate = hypergraph + tuple ( [ edgeIdx ] )
A u t C a n d i d a t e = Aut ( c a n d i d a t e )
candidateFailed = False
f o r h y p e r g r a p h _ f r o m _ L _ i _ p l u s _ 1 i n L_i_plus_1 :
i f hypergraph_from_L_i_plus_1 i n AutCandidate :
c a n d i d a t e F a i l e d = True
break
i f not c a n d i d a t e F a i l e d :
L_i_plus_1 . add ( c a n d i d a t e )
y i e l d candidate
88
89
90
91
92
93
94
95
96
97
98
99
100
101
C.4
Orderly-Methode
Hier ändert sich zum vorhergehenden Abschnitt nur noch die Funktion generate_L_i_plus_1.
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
d e f g e n e r a t e _ L _ i _ p l u s _ 1 ( L_i , n o d e P e r m u t a t i o n s , mapToIndex , e d g e s ) :
def i s _ c a n d i d a t e _ c a n o n i c a l ( ) :
d e f i s _ s m a l l e r _ t h e n _ c a n d i d a t e ( agHg ) :
idx = 0
f o r e d g e I d x i n agHg :
i f edgeIdx > candidate [ idx ] :
return False
i f edgeIdx < candidate [ idx ] :
r e t u r n True
i d x += 1
return False
f o r np i n n o d e P e r m u t a t i o n s :
agHg = t u p l e ( ) # new h y p e r g r a p h f o r Aut ( h y p e r g r a p h )
for edgeIdx in candidate :
agHg = agHg + t u p l e ( [ # we r e c y c l e e d g e s
mapToIndex [ f r o z e n s e t (
np [ node ] f o r node i n e d g e s [ e d g e I d x ] ) ] ] )
i f i s _ s m a l l e r _ t h e n _ c a n d i d a t e ( s o r t e d ( agHg ) ) :
return False
r e t u r n True
def i s _ c o l o r i n g _ r e l e v a n t _ t o _ h y p e r g r a p h ( edge ) :
for edgeIdx in hypergraph :
i f edges [ e d g e I d x ] . i s s u b s e t ( edge ) : retu rn F a l s e
r e t u r n True
96
97
98
99
f o r h y p e r g r a p h i n L_i :
f o r e d g e I d x i n r a n g e ( ( h y p e r g r a p h [ −1]+1) , l e n ( e d g e s ) ) :
i f is_coloring_relevant_to_hypergraph ( edges [ edgeIdx ] ) :
25
100
101
102
candidate = hypergraph + tuple ( [ edgeIdx ] )
i f is_candidate_canonical ():
y i e l d ( candidate )
Hier fällt sofort ins Auge, dass im Vergleich zur klassischen Methode die Menge L_i_plus_1 nicht
mehr benötigt wird und der damit verbundene Test, ob schon ein anderer Repräsentant erzeugt
wurde, fällt ebenfalls weg. Dafür muss nun die Eigenschaft is_canonical berechnet werden.
Setzen wir schließlich etwas des durch die Orderly-Methode gewonnenen Speichers an der richtigen
Stelle wieder ein, dann erhalten wir noch eine weitere spürbare Effizienzsteigerung. Dazu wird
neben der generate_L_i_plus_1-Funktion auch noch die Erzeugung der permutationen angepasst.
Ein Element der Permutationen-Liste ist nun ein Paar bestehend aus der Permutation und einer
leeren Zuordnung. In dieser Zuordnung wird im weiteren Verlauf ein Kantenindex dem Kantenindex seines Bildes unter der Permutation zugeordnet. Dies geschieht natürlich nicht für alle Kanten
im Vorfeld sondern immer nur für die Kanten die auch tatsächlich unter einer Permutation schon
einmal zugeordnet wurden, sodass die sich wiederholenden Berechnungen der Bildkanten für die
Berechnung der Automorphismengruppe weg fallen.
60
61
62
63
64
65
66
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
from i t e r t o o l s import p e r m u t a t i o n s
d e f c r e a t e _ n o d e _ p e r m u t a t i o n s ( numberOfNodes ) :
nodePermutations = [ ]
f o r p e r m u t a t i o n i n p e r m u t a t i o n s ( r a n g e ( numberOfNodes ) ) :
n o d e P e r m u t a t i o n s . append (
t u p l e ( [ permutation , d i c t ( ) ] ) )
return nodePermutations
d e f g e n e r a t e _ L _ i _ p l u s _ 1 ( L_i , n o d e P e r m u t a t i o n s , mapToIndex , e d g e s ) :
d e f i s _ s m a l l e r _ t h e n ( agHg , c a n d i d a t e ) :
f o r i d x i n r a n g e ( l e n ( agHg ) ) :
i f agHg [ i d x ] > c a n d i d a t e [ i d x ] : r e t u r n F a l s e
i f agHg [ i d x ] < c a n d i d a t e [ i d x ] : r e t u r n True
return False # i d e n t i c a l
f o r np i n n o d e P e r m u t a t i o n s :
agHg = t u p l e ( ) # new h y p e r g r a p h o f Aut ( h y p e r g r a p h )
for edgeIdx in candidate :
t r y : agHg += np [ 1 ] [ e d g e I d x ]
except :
np [ 1 ] [ e d g e I d x ] = t u p l e ( [ # we r e c y c l e e d g e s
mapToIndex [ f r o z e n s e t (
np [ 0 ] [ n i ] f o r n i i n e d g e s [ e d g e I d x ] ) ] ] )
agHg += np [ 1 ] [ e d g e I d x ]
i f i s _ s m a l l e r _ t h e n ( s o r t e d ( agHg ) , c a n d i d a t e ) :
return False
r e t u r n True
d e f i s _ c o l o r i n g _ r e l e v a n t _ t o ( edge , h y p e r g r a p h ) :
for edgeIdx in hypergraph :
i f edges [ e d g e I d x ] . i s s u b s e t ( edge ) : retu rn F a l s e
r e t u r n True
94
95
96
97
f o r h y p e r g r a p h i n L_i :
f o r e d g e I d x i n r a n g e ( ( h y p e r g r a p h [ −1]+1) , l e n ( e d g e s ) ) :
i f i s _ c o l o r i n g _ r e l e v a n t _ t o ( edges [ edgeIdx ] , hypergraph ) :
26
98
99
100
candidate = hypergraph + tuple ( [ edgeIdx ] )
i f is_canonical ( candidate ):
y i e l d ( candidate )
Nun versuchen wir in Zeile 81 einem Kantenindex direkt dessen Bild bezüglich einer Permutation zuzuordnen. Gelingt dies nicht, wird eine Ausnahme ausgelöst und wir Speichern die im
Ausnahmeblock berechnete Zuordnung (Zeilen 83–86).
27
Literatur
[BL83]
László Babai and Eugene M. Luks. Canonical labeling of graphs. In 15th ACM Symp.
on Theory of Computing, pages 171 – 183. ACM, 1983.
[HG03]
Thomas F. Hain and Geof Goldbogen. Determination of graph isomorphism by the greatest characteristic string invariant. In 41st Annual ACM Southeast Conference, Georgia,
USA, March 2003.
[McK81] B. D. McKay. Practical graph isomorphism. In 10th. Manitoba Conference on Numerical
Mathematics and Computing, volume 30 of Congressus Numerantium, pages 45 – 87,
1981.
[Rea78] Ronald C. Read. Every one a winner or how to avoid isomorphism search when cataloguing combinatorial configurations. In P. Hell B. Alspach and D.J. Miller, editors,
Algorithmic Aspects of Combinatorics, volume 2 of Annals of Discrete Mathematics, pages
107 – 120. Elsevier, 1978.
[Vol02]
Vitaly I. Voloshin. Color mixed hypergraphs: theory, algorithms, and applications. American Mathematical Society, 2002.
[Vol09]
Vitaly I. Voloshin. Introduction to Graph and Hypergraph Theory. Nova Science Publishers Inc, 2009.
28
Index
E(H), 4
E(H) ⊂ e, 5
H + e, 5
H < e, 11
Nn , 3
V (H), 4
dom(f ), 3
En , 7
img(f ), 3
ran(f ), 3
e < H, 12
e ⊂ E(H), 5
Automorphismengruppe, 6
einfach, Hypergraph, 4, 7
färbbar, 6
Färbung, 6
färbungsrelevant, 7
Größe, Hypergraph, 4
Grad, Kante, 5
Grad, Knoten, 5
isomorph, 5
kanonischer Repräsentant, 21
Kante, 4
Kantenmenge, 4
Kantenordnung, 11
Knoten, 4
Knotenmenge, 4
Länge, 12
leerer Hypergraph, 4
maximal, Teilfärbung, 6
Orderly Method, 19
Ordnung, 4
Permutation, 3
Rang, 5
regulär, 5
Teilfärbung, 6
uniform, 5
29
Herunterladen