FAKULTÄT FÜR INFORMATIK

Werbung
FAKULTÄT FÜR INFORMATIK
DER TECHNISCHE UNIVERSITÄT MÜNCHEN
Lehrstuhl für Effiziente Algorithmen
WS 2010 / 2011
Systementwicklungsprojekt
Konstruktion von Suffix-Bäumen
mit dem DiGeST-Algorithmus
İlker Hatipoğlu
Betreuer
Dipl.-Inform. Johannes Krugel
Prof. Dr. Ernst W. Mayr
Inhalt
I. Einleitung
1
II. Suffix-Bäume und Suffix-Arrays
1
1. Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1
2. Suffix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1
3. Definition Suffix-Baum. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2
4. Komprimierte Suffix-Bäume (Compressed Suffix Tree). . . . . . . . . . . . .
3
5. Der naive Konstruktionsalgorithmus. . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
6. Suffix-Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
III.DiGeST (Disk-Based Genomic Suffix Tree)
5
1. Vorbearbeitungsphase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
2. Sortieren der Suffixe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
3. Zusammenfügen der Suffix-Arrays (Merge-Phase). . . . . . . . . . . . . . . . .
7
4. Ein Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
IV. Implementierung
11
1. Die verwendeten Datenstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
2. Vorbearbeitungsphase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12
3. Sortieren der Suffixe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12
4. Zusammenfügen der Suffixe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12
Literaturverzeichnis. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
I. Einleitung
Seitdem verschiedene Genomen sequenziert werden können und die gesammelten Genom-Daten
täglich wachsen, interessieren sich Genomforscher für die Analyse und den Vergleich dieser
Genom-Strings. Allerdings sind solche Strings sind in der Regel recht groß, so dass dessen Umgang
einen enormen technischen Aufwand erfordert. Allein ein einfacher Vergleich zweier Strings dieser
Länge könnte z.B. mit einem naiven Verfahren Jahre lang dauern.
In diesem Engpass helfen die effektiven Methoden der Informatik und Mathematik, die zum StringVergleich entwickelt sind. Diese Methoden basieren meist auf Suffix-Bäume (Suffix-Tree) oder
Suffix-Arrays. [Kleffe2001]
Allerdings auch bei diesen Methoden, hat man ein Problem mit der Größe des zu bearbeitenden
Strings: Selbst die Suffix-Bäume von kleineren Genom-Strings können sehr groß werden, so dass
sie leicht den verfügbaren Arbeitsspeicher überschreiten. z.B. für einen Eingabe-String der Größe 6
GB würde man mindestens 60 GB Arbeitsspeicher brauchen, um seinen Suffix-Baum zu behalten.
Das legt die Verwendung eines Festplatte-Basierten-Algorithmus zur Konstruktion eines SuffixBaums nahe. [Barsky2008]
Diese Arbeit beschäftigt sich mit einer solchen Methoden, nämlich der „DiGeST“ (Disk-Based
Genomic Suffix Tree), die von Marina Barsky, Ulrike Stege, Alex Thomo und Chris Upton
entwickelt wurde.
II. Suffix-Bäume und Suffix-Arrays
1. Motivation
Problem:
Gegeben ist ein String S der Länge n. Gesucht ist das Vorkommen eines beliebigen Strings s
der Länge m in S. (m <= n)
→ Die klassische Suche mit naivem Zeichenvergleich ist für die extrem großen Strings viel
zu aufwändig.
Lösungsidee:
Das Ziel ist eine andere Repräsentation von S zu finden, die
– in linearer Zeit hergestellt werden kann,
– lineare Speicherkomplexität hat,
– eine Suche mit der Zeitkomplexität O(|m|) ermöglicht.
Ein Suffix-Baum ist eine solche Indizierung des Strings und ermöglicht es, Suchaufgaben in sehr
kurzer Zeit abzuarbeiten. [Penkova2005]
2. Suffix
Ein Suffix eines Strings ist ein Teilstring, der an einer beliebigen Buchstabe anfängt und bis zum
Ende des ganzen Strings läuft. Ein String hat genau so viele Suffixe wie ihre einzelne Zeichen.
1
String:
'CGCATA'
Suffixe:
CGCATA
GCATA
CATA
ATA
TA
A
Ein Suffix kann über seine Anfangsposition im String eindeutig bestimmt werden, da alle Suffixe
bis zum Ende des Strings laufen:
CGCATA → 0
GCATA → 1
CATA → 2
ATA → 3
TA → 4
A→5
Man sieht gleich, dass der Suffix '0' der String selbst ist.
3. Definition Suffix-Baum
Einen Suffix-Baum erhält man, indem man alle Suffixe eines Strings in eine Baumstruktur einträgt.
z.B. für den String 'CGCATA':
A
C
TA
GCATA
TA
3
ATA
2
GCATA
0
1
4
2
In dieser Baumansicht entspricht jedes Blatt ein Suffix von 'CGCATA'. So sind alle Suffixe mit den
Blättern des Baums repräsentiert, bis auf den letzten Suffix 'A'. Hier tritt der Fall auf, dass der
Suffix 'A' gleichzeitig auch ein Präfix von einem anderen Suffix 'ATA' ist. Um diesen Fall
umzugehen, fügt man am Ende des Strings ein Sonderzeichen ein, das sonst in dem String nicht
auftaucht. Dadurch können wir in der Baumansicht zeigen, dass auch der Knoten von 'A' zum Ende
des Strings führen kann und somit 'A' auch ein Suffix von 'CGCATA' ist.
Nach dieser Änderung sieht der Suffix-Baum für den String 'CGCATAɛ' folgendermaßen aus:
A
C
TAɛ
GCATAɛ
TAɛ
ɛ
3
5
ATAɛ
2
GCATAɛ
0
1
4
Ein solcher Baum T für einen String S der Länge n hat dann folgende Eigenschaften
[Penkova2005]:
– T hat genau n Blätter.
– Jeder innere Knoten hat mindestens zwei Kinder.
– Die Kanten haben nicht-leere Beschriftungen (Repräsentiert Substrings).
– Ein Knoten darf nicht mehr als ein Kind mit derselben Beschriftung haben.
– Jeder Pfad vom Wurzel bis zu einem Blatt entspricht einen Suffix des Strings.
4. Komprimierte Suffix-Bäume (Compressed Suffix Tree)
Wie oben dargestellt, entspricht jede Kante des Baums einen Teilstring. Aber das Speichern dieser
Teilstrings selbst an den Kanten ist sehr platzaufwendig. Besonders bei sehr großen Strings wie
Genom-Strings ist das nicht tolerierbar. Allerdings, da diese Teilstrings durch ihre Anfangs- und
Endpositionen bestimmt werden können, kann man statt den Teilstrings ihre Anfangs- und
Endpositioen an den Kanten speichern, wofür man viel weniger Platz braucht. So ein Baum wird als
„komprimierter Suffix Baum“ benannt.
3
Der komprimierte Suffix-Baum für 'CGCATAɛ':
3-3
0-0
4-6
1-6
4–6
3-6
6 -6
3
2
5
1-6
0
1
4
5. Der naive Konstruktionsalgorithmus
Wir nehmen an, dass uns ein String S der Länge n vorliegt. Um davon einen Suffix-Baum zu
konstruieren, fügen wir im Grunde alle Suffixe von S hintereinander in den Baum ein:
– Wir fangen mit dem Suffix mit dem Index '0', also S selbst.
– Für den nächsten Suffix, prüfen wir, ob der neu einzufügende Suffix mit einem der schon
eingefügten Suffixen einen gemeinsamen Präfix hat, also ob einige Anfangszeichen
übereinstimmen.
– Falls nein: Der neue Suffix wird einfach an den Wurzel angehängt.
– Falls ja: Der Pfad von dem Suffix im Baum mit dem gemeinsamen Präfix wird verfolgt,
bis ein erreicht wird, die beide Suffixe nicht gemeinsam haben. An dieser Stelle wird
ein neuer innerer Knoten an dem Pfad eingefügt und von diesem inneren Knoten noch
ein Blatt, das den neuen Suffix repräsentiert.
– Das Verfahren läuft weiter, bis alle Suffixe in den Baum eingefügt worden sind.
Dieser naive Algorithmus braucht O(n2) Zeit, daher ist in der Praxis nicht einsetzbar. Im folgenden
schauen wir uns das DiGeST Verfahren an, das den Suffix-Baum in linearer Zeit bildet. Dafür
erklären wir zuerst den Begriff Suffix-Array.
6. Suffix-Arrays
Ein Suffix-Array von einem String S der Länge n ist ein Array, der die Suffixe von S in
lexikographischer Reihenfolge behält. Dabei werden im Array nicht die Suffixe selbst sondern ihre
Anfangspositionen gespeichert. z.B. das Suffix-Array für den String 'CGCATAɛ':
suffixArray[0] = 5
→
Aɛ
suffixArray[1] = 3
→
ATAɛ
suffixArray[2] = 2
→
CATAɛ
suffixArray[3] = 0
→
CGCATAɛ
suffixArray[4] = 1
→
GCATAɛ
suffixArray[5] = 4
→
TAɛ
4
Suffix-Arrays haben eine Speicherkomplexität von O(n) und können mit der Zeitkomplexität O(n)
erstellt werden.
III. DiGeST (Disk-Based Genomic Suffix Tree)
Der DiGeST-Algorithmus ist von Barsky, Stege, Thomo und Upton entwickelt worden. Er ist ein
Festplattenbasierter Suffix-Baum Konstruktionsalgorithmus für Eingabe-Strings, die größer als der
verfügbare Arbeitsspeicher sind [Barsky2008].
Der Algorithmus läuft in drei Phasen:
– Vorbearbeitung:
Partitionieren des Eingabe-Strings in kleinere Teilstrings und Enkodieren der Teilstrings mit
einem Binäralphabet, um den Speicherplatz besser auszunutzen.
– Sortieren der Suffixe
Erstellen von Suffix-Arrays für jeden Teilstring. (Larsson-Algorithmus fürs Sortieren)
[Barsky2008]
– Zusammenfügen der Suffix-Arrays
Zusammenfügen der sortierten Suffix-Arrays in einen Suffix-Baum.
Bei der Entwicklung des Algorithmus wurde besonders Wert darauf gelegt, dass die Festplattenzugriffe auf den Eingabe-String optimiert werden. Also so dass während der obigen drei Schritten
möglichst wenige Festplattenzugriffe passieren.
1. Vorbearbeitungsphase
Der Eingabestring wird in Partitionen aufgeteilt, deren Größe von dem verfügbaren Arbeitsspeicher
und von dem Algorithmus benötigten Speicherplatz abhängt. z.B. der Larsson-Algorithmus braucht
8 x N (N = Die ausgewählte Partitiongröße) Bytes für den Aufbau eines Suffix-Arrays für einen
Teilstring der Größe N. In [Barsky2008] wird es erwähnt, dass der Algorithmus, nach den
Resultaten der Experimente, die Partitionen der Größe 100MB schnell bearbeiten kann.
Die Teilstrings werden auch noch mit einem geeigneten Binäralphabet enkodiert, wodurch der
benötigte Speicher zum Speichern der Strings reduziert wird. In diesem Fall können die Zeichen des
DNA-Alphabets {a, c, g, t} mit einem 2-Bit Binärstring repräsentiert werden.
2. Sortieren der Suffixe
Für jeden Teilstring wird ein eigenes Suffix Array durch den 'Larsson-Algorithmus' (Ein SuffixArray Sortierungsalgorithmus, siehe [Barsky2008]) erstellt. Dabei werden beim Suffix-Vergleich
ein Präfix der nächsten Teilstring, das sogenannte 'Tail', mitberücksichtigt, damit die Reihenfolge in
einzelnen Teiltring-Suffix-Arrays mit der Suffix-Reihenfolge von dem ursprünglichen ganzen
String übereinstimmt. Mit einem Beispiel erklären wir den Gebrauch von Tails deutlicher:
S = 'ACAGCACCG' sei der Eingabe-String,
S1 = 'ACA' der erste Teilstring,
S2 = 'GCA' der zweite Teilstring und
S3 = 'CCG' der dritte Teilstring nach der Partitionierung.
5
Dann wären die erstellten Suffix-Arrays für die Teilstrings:
array1 [0] = 2 →
A
array2 [0] = 2 →
A
array2 [0] = 2 →
A
array1 [1] = 0 →
ACA
array2 [1] = 1 →
CA
array2 [1] = 1 →
CA
array1 [2] = 1 →
CA
array2 [2] = 0 →
GCA
array2 [2] = 0 →
GCA
Diese Partitionierung findet aber nur deswegen statt, weil der Eingabe-String selbst normalerweise
zu groß für den Arbeitsspeicher ist.
Das eigentliche Ziel ist im nächsten Merge-Schritt des DiGeST-Algorithmus, aus diesen TeilstringSuffix-Arrays einen Suffix-Baum für den gesamten Eingabe-String aufzubauen.
Also diese Teilstring-Suffix-Arrays und die Suffixe, die darin sortiert sind, nur im Bezug auf dem
ganzen String von Bedeutung. Genauer gesagt, wenn wir für S2 die Teilstring-Suffixe 'A' und 'CA'
mit den „lokalen“ Positionen '2' und '1' sortieren wollen, wollen wir eigentlich die Suffixe von S
mit den „globalen“ Positionen '5' und '4' sortieren, nämlich 'ACCG' und 'CACCG'.
In dieser Hinsicht, wollen wir beim Sortieren von array1 eigentlich die globalen Suffixen
'ACAGCACCG' (Position 0) , 'CAGCACCG' (Position 1) und 'AGCACCG' (Position 2) sortieren,
was in die folgende lexikographische Reihenfolge resultieren sollte:
'ACAGCACCG'
→
0
'AGCACCG'
→
2
'CAGCACCG'
→
1
Was aber im array1 nach einer „lokalen“ Sortierung ohne Berücksichtigung des ganzen EingabeStrings anders aussieht:
'A(GCACCG)'
→
2
'ACA(GCACCG)'
→
0
'CA(GCACCG)'
→
1
Daher erweitern wir die Suffixe eines Teilstrings beim Vergleichen um einen Präfix des nächsten
Teilstrings mit einer vordefinierten Länge (Das 'Tail') und somit sichern, dass die „lokale“ SuffixReihenfolge in den Suffix-Arrays mit der „globalen“ Reihenfolge übereinstimmt.
Üblicherweise werden in den Suffix-Arrays die Anfangspositionen der Suffixe gespeichert, statt der
Suffixe selbst. Das würde aber später beim Zusammenfügen der Suffixe mehrfache Festplattenzugriffe verursachen, da die Suffixe miteinander verglichen werden müssen. Um das zu verhindern,
hängt man beim DiGeST an den Anfangspositionen auch ein Präfix des Suffixes an.
Dieser Präfix wird später beim Suffix-Vergleich verwendet und erst wenn diese angehängten
Präfixen der zwei Suffixe gleich sind, findet ein Festplattenzugriff auf den ursprünglichen EingabeString statt, um die Reste der Suffixe zu vergleichen.
6
3. Zusammenfügen der Suffix-Arrays (Merge-Phase)
In der Merge-Phase werden die ersten (lexikographisch kleinsten) Elemente dieser Suffix-Arrays
miteinander verglichen (Wieder unter Berücksichtigung der 'Tails'!). Der kleinste Suffix wird
seinem Suffix-Array entnommen und wird in den Suffix-Baum geschrieben. Der Vergleich geht mit
den verbliebenen Suffixen weiter. Dieser Schritt wiederholt sich so lange, bis alle Suffix-Arrays leer
sind, also alle Suffixe in den Baum eingetragen worden sind.
Das einfügen eines Suffixes in den Baum basiert auf folgenden Prinzipien:
– Da die Suffixe im Voraus über Suffix-Arrays lexikographisch sortiert sind, ist es sicher
gestellt, dass der neu einzufügende Suffix lexikographisch größer als der zuletzt
eingefügte ist.
– Der lexikographisch größte Suffix im Baum, also anders gesagt der zuletzt eingefügte,
wird mit der äußersten Kante des Baums repräsentiert. (Äußerst rechte Kante, falls die
rechten Kinder eines Knotens die lexikographisch größeren sind.)
Nach diesen zwei Prinzipien reicht es, um im Baum die richtige Stelle fürs Einfügen zu finden,
wenn man für einen neuen Suffix den „längsten gemeinsamen Präfix“ (LCP- Longest common
prefix) mit dem zuletzt eingefügten Suffix berechnet, und die äußerste Kante des Baums vom
Wurzel um die Länge des LCP verfolgen. An der Stelle, wo LCP aufhört, treten dann zwei Fälle auf:
– Der LCP hört an einem Knoten auf. Dann fügt man einfach einen neues Blatt, das den
neuen Suffix repräsentiert.
– Der LCP hört auf der Kante zwischen zwei Knoten auf. Da wird die Kante aufgeteilt,
indem ein neuer innerer Knoten an der Stelle eingefügt wird. Dann wird ein Blatt an
diesen neuen inneren Knoten angehängt, das den neuen Suffix repräsentiert.
Somit vermeidet man den Aufwand, den man beim naiven Suffix-Baum Konstruktionsalgorithmus
hätte, wo man die Kanten traversieren und dabei die einzelne Zeichen vergleichen würde.
Wenn der verfügbare Speicherplatz während der Suffix-Baum-Aufbau voll wird, wird der
aufgebaute Baum in die Festplatte geschrieben. Das Zusammenfügen der Suffixe wird mit einem
neuen Baum fortgesetzt. So entsteht zum Schluss ein „Suffix-Wald“ auf der Platte.
Für jeden auf die Festplatte geschriebenen Baum, wird auch der zuletzt eingetragene Suffix (Also
der lexikographisch größte in diesem Baum) in einer seperaten Datenstruktur für Suffix-BaumDividers geschrieben. In dieser Datenstruktur wird mit jedem solchen Divider-Suffix auch ein
Referenz auf den dazugehörigen Suffix-Baum gespeichert.
Bei einer String-Suche über den gebauten Index wird dann der Suchstring zuerst mit den DividerSuffixen verglichen, um zu finden, in welchem Baum des Waldes nach diesem String gesucht
werden soll.
4. Ein Beispiel
Mit einem Beispiel veranschaulichen wir das DiGeST Verfahren:
– S = 'CGCATAɛ' sei der Eingabe-String
– S1 = 'CGC' und S2 = 'ATCɛ' seien die Teilstrings nach der Partitionierung.
– Der 'Tail', also ein Präfix der S2, der beim Suffix-Vergleich an S1-Suffixe angehängt wird,
sei zwei Zeichen lang. Also 'AT'.
– Für S2 braucht man kein 'Tail' weil er der letzte Teilstring ist.
7
Dann würden die Teilstring-Suffix-Arrays so aussehen:
array1 [0] = 2 →
C
array2 [0] = 0 →
ATCɛ
array1 [1] = 0 →
CGC
array2 [1] = 2 →
Cɛ
array1 [2] = 1 →
GC
array2 [2] = 1 →
TCɛ
Wir nehmen die obersten (Lexikographisch kleinsten) Elemente von jedem Array zum Vergleich:
array1 [0] = 2 →
C
array2 [0] = 0 →
ATCɛ
Daraus wird wiederum der kleinste Suffix ausgewählt um in den Suffix Baum geschrieben zu
werden (Hinweis: Auch hier werden beim Vergleichen die 'Tails' in Betracht gezogen!!):
array1 [0] = 2 →
C(AT)
array2 [1] = 0 →
ATCɛ
Der Suffix 'ATCɛ' mit der globalen Position '3' wird in den leeren Suffix-Baum eingetragen. Die
dadurch entstandene äußerste Kante (Im Moment die einzige Kante im Baum) markieren wir mit
Rot:
ATCɛ
3
Jetzt werden die Vergleichsfelder „nachgefüllt“, also der nächste Suffix vom array2 wird eingelesen:
array1 [0] = 2 →
C
array2 [1] = 2 →
Cɛ
array2 [1] = 2 →
Cɛ
Der kleinste Suffix wird ausgewählt:
array1 [0] = 2 →
C(AT)
Der Suffix 'Cɛ' mit der globalen Position '5' muss in den Baum eingetragen werden. Jetzt muss der
längste gemeinsame Präfix (LCP) von der äußersten Kante (Markiert mit rot) und dem neuen Suffix
untersucht werden. Da 'ATCɛ' und 'Cɛ' keinen gemeinsamen Präfix haben, hört die Suche nach dem
LCP schon da auf, wo sie angefangen hat, nämlich am Wurzelknoten. So wird der neue Suffix
einfach als ein neues Blatt hinzugefügt (Neue äußerste Kante mit Rot markiert):
ATCɛ
3
Cɛ
5
8
Also nochmal wird der nächste Suffix vom array2 nachgeladen, und der kleinste Suffix wird
ausgewählt:
array1 [0] = 2 →
array1 [0] = 2 →
C
array2 [2] = 1 →
TCɛ
C(AT)
array2 [2] = 1 →
TCɛ
Jetzt muss wieder der LCP mit der äußersten Kante untersucht werden. Der Suffix 'Cɛ', den die
äußerste Kante repräsentiert, und der neue globale Suffix 'CATCɛ' (Die „globalen“ Suffixen werden
in den Baum eingetragen. Hier steht der Teilstring-Suffix 'C' für den globalen String 'CATCɛ' mit
der globalen Position '2' !!) haben einen gemeinsamen Präfix → 'C'. Also die LCP-Suche endet auf
der Kante, auf der Stelle zwischen den Zeichen 'C' und 'ɛ'. Die Kante wird durch einen neuen
inneren Knoten aufgeteilt und ein neues Blatt für den neuen Suffix wird eingefügt:
C
ATCɛ
ATCɛ
ɛ
3
2
5
Das Verfahren wird weitergeführt, bis alle Suffixe in den Suffix-Arrays in den Baum eingetragen
worden sind:
array1 [1] = 0 →
array1 [1] = 0 →
CGC
array2 [2] = 1 →
TCɛ
CGC(AT)
array2 [2] = 1 →
TCɛ
LCP von 'CGC' (Global 'CGCATCɛ', Position '0') und 'CATCɛ' → 'C'.
LCP endet an einem Knoten → Neues Blatt einfügen:
C
ATCɛ
ɛ
3
5
ATCɛ
2
GCATCɛ
0
9
array1 [2] = 1 →
array1 [2] = 1 →
GC
array2 [2] = 1 →
TCɛ
GC(AT)
array2 [2] = 1 →
TCɛ
LCP von 'GC' (Global 'GCATCɛ', Position '1') mit 'CGCATCɛ' ist → leer
Suffix wird an den Wurzel angehängt.
C
GCATCɛ
ATCɛ
ɛ
3
GCATCɛ
ATCɛ
1
0
2
5
-
array2 [2] = 1 →
TCɛ
-
array2 [2] = 1 →
TCɛ
LCP von 'TCɛ' mit 'GCATCɛ' → leer
Suffix wird an den Wurzel angehängt:
C
GCATCɛ
ATCɛ
ɛ
3
5
TCɛ
GCATCɛ
ATCɛ
2
0
1
4
10
IV. Implementierung
In dieser Arbeit wurde es versucht eine DiGeST-Implementierung für die C++ Sequenz-AnalyseBibliothek „SeqAn“ [Gogol-Doering2009] zu erstellen.
Unten sind die in der Implementierung verwendeten SeqAn-Datenstrukturen gelistet. Für eine
detaillierte Erklärung dieser Datenstrukturen und Klassen siehe [Gogol-Doering2009].
1. Die verwendeten Datenstrukturen
Für die Realisierung des Algorithmus ist eine Spezialisierung der SeqAn Klasse 'Index'
implementiert. Nämlich „Index_DIGEST“:
template<typename TText, typename TSpec>
class Index<TText, Index_DIGEST<TSpec> >
Die Member-Variablen der Klasse:
Ein 'StringSet' zum Speichern des Eingabe-Strings (In Partitionen):
typedef StringSet<Dna5String, Owner<ConcatDirect<> > > TDigestStringSet;
Ein 'SeqAn-Triple' für einen Suffix (Teilstring Nummer, Suffix Nummer im Teilstring, Präfix):
typedef Triple<unsigned, unsigned, Dna5String> TDigestSuffix;
Ein 'SeqAn-String' als ein Suffix-Array:
typedef String<TDigestSuffix> TDigestSAString;
Noch ein 'SeqAn-String' zum Speichern aller Suffix-Arrays:
String<TDigestSAString, External<> > suffixArrays;
Ein 'StringSet' für Tails:
StringSet<Dna5String> suffixArrayTails;
Ein 'struct' für einen Suffix-Baum-Knoten:
typedef struct DigestSTNode
{
int startPos;
int endPos;
unsigned nodeDepth;
unsigned edgeLength;
bool isLeaf;
Dna5 firstChar;
unsigned children[5];
} TdigestSTNode;
Ein 'SeqAn-String' aus Suffix-Baum-Knoten als Suffix-Baum:
String<TDigestSTNode> TDigestSuffixTree
Ein 'SeqAn-String' zum Speichern aller Suffix-Bäume in einem „Suffix-Wald“:
11
typedef String<TDigestSuffixTree, External<> > TDigestSuffixForest;
Ein 'SeqAn-Triple' als ein Divider-Suffix (Teilstring Nummer, Suffix Nummer im Teilstring,
Präfix):
typedef Triple<unsigned, unsigned, Dna5String> TDigestSTDivider;
Ein 'SeqAn-String' zum Speichern von Dividers:
String<TDigestSTDivider> STDividers;
Die drei Phasen des DiGeST-Algorithmus sind in der Spezialisierung wie folgt realisiert worden.
2. Vorbearbeitungsphase
Findet in den Funktionen
readFileInStringSet(...)
oder
readMultiSeqFileInStringSet(...)
statt, wobei die erste Funktion einen rohen Eingabestring in Partitionen mit einer vorgegebenen
Größe aufteilt, die dem Index-Objekt im Konstruktor übergeben wird. Die zweite Funktion dagegen
liest nur einen bereits partitionierten String aus einer Multi-Sequenz-Datei (z.B. in 'fasta' Format) in
ein StringSet<> Objekt.
Das binäre Enkodieren des Eingabestrings wurde der Einfachheit halber ausgelassen.
3. Sortieren der Suffixe
Findet in der Funktion
createSuffixArrays(...)
statt. In der Implementierung wird ein standard C-Sortieralgorithmus statt des Larsson-Algorithmus
verwendet, um beim Erstellen der Arrays die 'Tails' berücksichtigen zu können.
4. Zusammenfügen der Suffixe
Findet in der Funktion
buildSuffixTree(...)
statt.
12
Literaturverzeichnis
Article [Kleffe2001] Kleffe J. Sequenzvergleiche mit Suffixbäumen
Article [Penkova2005] Penkova S.; Tchorbadjiiski A. Suffix Trees und Suffix Arrays
Article [Barsky2008] Barsky, M.; Stege U.; Thomo A.; Upton C. A New Method for Indexing
Genomes Using On-Disk Suffix Trees
Article [Doering2008] Döring, A.; Weese, D.; Rausch, T. & Reinert, K. SeqAn An efficient, generic
C++ library for sequence analysis BMC bioinformatics, BioMed Central, 2008, 9, 1471-2105,
Website: http://www.seqan.de
Book [Gogol-Doering2009] Gogol-Döring, A. & Reinert, K. Biological Sequence Analysis Using
the SeqAn C++ Library Chapman & Hall / CRC Press, 2009
13
Herunterladen