Diplomarbeit ”Anpassung von BLAST für Genom-Datenbanken” eingereicht am Institut für Informatik der Humboldt-Universität zu Berlin von Matthias Weh geb. am 5. Januar 1976 in Berlin Matrikelnummer 134764 Betreuer: Chokri Ben Necib eingereicht am: 19. Februar 2002 Inhaltsverzeichnis 1 Einleitung 3 2 Biologische Grundlagen 2.1 Typen von Biosequenzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2 Zusammenhang von DNA, RNA und Proteinen . . . . . . . . . . . . . . . . 4 4 6 3 Sequenzvergleiche 3.1 Bedeutung von Sequenzvergleichen . . . . . 3.2 Bewertungsschemata . . . . . . . . . . . . . 3.3 Alignierungen . . . . . . . . . . . . . . . . . 3.4 Algorithmen zur Bestimmung der optimalen 3.5 Approximative Alignierungsalgorithmen . . . . . . . . . . . . . . . . . . . . . . . Alignierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 9 10 10 12 13 4 Analyse des BLAST-Programmcodes 4.1 Das NCBI-Toolkit . . . . . . . . . . . . . . 4.2 Die Module des Programms BLAST . . . . 4.3 Die ”Datenbank”-Schnittstelle von BLAST 4.4 Der Ablauf von blastall . . . . . . . . . . . 4.5 Analyse des multithreading in BLAST . . . 4.6 Die Datenstruktur SeqAlign . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 19 20 22 28 33 36 5 Verwendete Datenbankkonzepte 5.1 Datenmodellierung der Biosequenzen . . . . . . . . . . . . . . . . . . . . . . 5.2 Anwendungsprogrammierung mit DB2 . . . . . . . . . . . . . . . . . . . . . 5.3 Benutzerdefinierte Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . 42 42 43 48 6 Anpassung von BLAST 6.1 Implementation der Datenbankschnittstelle von BLAST . . . . . . . . . . . 6.2 BLAST als benutzerdefinierte Funktion . . . . . . . . . . . . . . . . . . . . 54 54 68 7 Ausblick 82 A blastall -Kommandozeilenoptionen A.1 Genetische Codetabellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 87 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . INHALTSVERZEICHNIS B Aufbau der BLAST-Reportdateien B.1 Das FASTA-Format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . B.2 FormatDB -Ausgabedateien . . . . . . . . . . . . . . . . . . . . . . . . . . . B.3 BLAST-Reportdateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 88 88 91 C UDF-Entwurfsdetails C.1 Typen von Alignierungen . . . C.2 DenseDiag-Alignierungen . . . C.3 DenseSeg-Alignierungen . . . . C.4 Weitere Anpassungen der UDF 95 95 95 96 97 . . . . . . . . . . . . D Relationales Datenmodell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 Literaturverzeichnis 102 2 Kapitel 1 Einleitung Seit Ende der 1980er Jahre wird systematisch der Aufbau der gesamten Erbinformation lebender Organismen experimentell ermittelt und erfasst. Der ”Bauplan” eines jeden Lebewesens kann dabei durch eine Sequenz von Basenpaaren, die DNA, beschrieben werden. Die funktionale Ausprägung der Erbinformation, die Proteine, sind ebenfalls als Sequenz von chemischen Bestandteilen, den Aminosäuren, beschreibbar. Die Auswertung und Interpretation der Sequenzen ist Aufgabe der Bioinformatik. Sie ist eine sehr junge Forschungsrichtung, die die Disziplinen Molekularbiologie und Informationstechnik zusammenführt. Die Bioinformatik muss mehrere Aufgaben mit Hilfe der Sequenzanalyse lösen: • Auswertung von Sequenz-Rohdaten Die bei der Sequenzierung gewonnenen Rohdaten werden auf ihre Korrektheit überprüft. • Vorhersage von Genen Die für Proteine kodierenden Abschnitte der DNA müssen von den weniger relevanten Abschnitten getrennt werden. Sie sind Voraussetzung für die Proteintranslation. • Vorhersage der Proteinstruktur und -funktion Die dreidimensionale Struktur der Proteine determiniert die Wirkungsweise von Proteinen. Diese ist Voraussetzung für das Verständnis biologischer Prozesse. • Aufklärung der evolutionären Verwandtschaft von Sequenzen Die Biosequenzen heute analysierter Organismen werden in Beziehung zueinander gesetzt, um Auskunft über die Verwandtschaft der Organismen zu erhalten. Zur Bewältigung dieser Aufgaben werden hauptsächlich Algorithmen zum Vergleich und zur Alignierung von Sequenzen verwendet. Ein populärer Vertreter dieser Methoden ist das Basic Local Alignment Search Tool (BLAST). Gegenstand dieser Arbeit ist die Integration von BLAST in ein relationales Datenbanksystem. Relationale Datenbanken bieten für die Genomforschung die Möglichkeit, die Biosequenzen in ein Modell einzubetten, das verschiedene biologische Informationen in Beziehung setzt. Die Biosequenzen können dann unter verschiedenen Gesichtspunkten mit Hilfe relationaler Anfragesprachen analysiert werden. 3 Kapitel 2 Biologische Grundlagen 2.1 Typen von Biosequenzen Gegenstand dieser Diplomarbeit ist die Anpassung des Alignierungsalgorithmus BLAST zur Anwendung in einem objektrelationalen Datenbanksystem. Dieser Algorithmus stammt aus dem Bereich der Genanalyse. Deshalb sollen im Folgenden die wichtigsten Begriffe aus der Genanalyse erläutert werden. DNA (Deoxyribonucleic Acid, Desoxyribonukleinsäure) und RNA (Ribonucleic Acid, Ribonukleinsäure) sind das Erbmaterial lebender Materie. Sie bilden das Genom, die Gesamtheit aller in einer Zelle vorhandenen Erbanlagen. Die Vererbung besteht in der Speicherung, Weitergabe, Rekombination und Realisierung der Erbinformation (des genetischen Material s). Träger der DNA sind die Chromosomen. Auf die Weitergabe der Erbinformation wird in [23] eingegangen. DNA und RNA sind makromolekulare Nukleinsäuren, die in Form einer Kette — einem Polynukleotid — aufgebaut sind. Die Bausteine der Ketten sind die Nukleotide, die aus Zuckern, Basen und Phosphatresten bestehen. Anhand der Basen lassen sich fünf Nukleotide unterscheiden: Adenin, Cytosin, Guanin, Thymin und Uracil. Deren hauptsächliches Vorkommen kann Tabelle 2.1 entnommen werden. In Abhängigkeit von der Nukleinsäure sind jeweils vier Nukleotide zu unterscheiden. Der grundlegende Unterschied zwischen DNA und RNA ist der enthaltene Zucker: im Fall der DNA ist es Desoxyribose, im Fall der RNA Ribose. Desoxyribose und Ribose kommen nie gleichzeitig im selben Polynukleotid vor. Die DNA ist als Doppelstrang zweier sich gegenüberliegender Nukleotidketten aufgebaut. In diesem von James D. Watson und Francis Crick 1953 vorgeschlagenen (und bereits auf seine Richtigkeit überprüften) Strukturmodell bilden die Paare Adenin und Thymin sowie Cytosin und Guanin Wasserstoffbrücken aus, wobei eine der Basen auf dem einen Strang, die andere auf dem anderen Strang liegt. Folglich kann aus einer der beiden Ketten die komplementäre Kette bestimmt werden, was unter anderem für die Replikation der Erbinformation von Bedeutung ist. Die Ketten der DNA sind in einer rechtsdrehenden Doppelspirale (Helix ) angeordnet. Im Gegensatz dazu ist die RNA aus nur einer Kette aufgebaut. Deren Besonderheit besteht darin, dass die Nukleotide des Strangs untereinander Basenpaare ausbilden können (Adenin mit Uracil, Cytosin mit Guanin), was die Sekundärstruktur von RNA komplizierter als die von DNA macht. Der genaue Aufbau der RNA hängt von ihrer Funktion ab und wird hier nicht weiter vertieft (siehe dazu [24]). 4 KAPITEL 2. BIOLOGISCHE GRUNDLAGEN Nukleotid Adenin Cytosin Guanin Thymin Uracil Symbol A C G T U Vorkommen DNA / RNA DNA / RNA DNA / RNA DNA RNA Komplement T/U G C A A Tabelle 2.1: Alphabet der Nukleotide Symbol A C M G R S V T W Y H K D B N/X Bedeutung Adenin Cytosin A oder C Guanin A oder G C oder G A oder C oder G Thymin/Uracil A oder T C oder T A oder C oder T G oder T A oder G oder T C oder G oder T A oder C oder G oder T Komplement T G K C Y S B A W R D M H V N/X Tabelle 2.2: Alphabet der Nukleotide mit Mehrdeutigkeiten. Die komplementären Residuen ergeben sich, wenn man die komplementären Elementarresiduen verknüpft. Die Anordnung der Stickstoffbasen auf den Ketten wird als Sequenz bezeichnet. Mit der Darstellung eines Nukleotids durch den Buchstaben seiner Stickstoffbase kann eine Sequenz als Zeichenkette repräsentiert werden. Damit können DNA- und RNA-Sequenzen informationstechnisch verarbeitet werden. Zur Vereinheitlichung von RNA und DNA und zur Darstellung sogenannter ”Mehrdeutigkeitsresiduen” (ambiguity residues) wird das Alphabet aus Tabelle 2.2 verwendet, das von Cornish-Bowden [10] eingeführt wurde. Die vier eindeutigen Residuen werden im Folgenden als Elementarresiduen bezeichnet, DNA- und RNA-Sequenzen werden zum Begriff NA-Sequenzen zusammengefasst. Die dritte Art der hier behandelten Sequenzen sind die Aminosäure- oder Proteinsequenzen. Ein Protein ist ein aus Aminosäuren zusammengesetztes Makromolekül. Proteine sind die funktionale Realisierung der Erbinformation und werden aus der DNA synthetisiert. Obwohl heute über 100 Aminosäuren bekannt sind, bilden nur 20 von Ihnen den Bausatz zur Bildung von Proteinen. Zur Darstellung dieser proteinogenen Aminosäuren wird das in [20] eingeführte Alphabet verwendet. Tabelle 2.3 zählt die Aminosäuren auf. Die Aminosäuren in einem Protein sind, wie die Nukleotide der DNA, als Sequenz (Polypeptidkette) angeordnet. Die Sequenz bildet die Primärstruktur. Die Sekundär- und Tertiärstruktur ergibt sich, wenn man die Wechselwirkung der Aminosäuren innerhalb eines Proteins betrachtet. Diese dreidimensionale Struktur ist maßgebend für die Funktion 5 KAPITEL 2. BIOLOGISCHE GRUNDLAGEN Einbuchstabencode A C D E F G H I K L M N P Q R S T V W Y Dreibuchstabencode Ala Cys Asp Glu Phe Gly His Ile Lys Leu Met Asn Pro Gln Arg Ser Thr Val Trp Tyr Aminosäure Alanin Cystein Asparaginsäure Glutaminsäure Phenylalanin Glycin Histidin Isoleucin Lysin Leucin Methionin Asparagin Prolin Glutamin Arginin Serin Threonin Valin Tryptophan Tyrosin Tabelle 2.3: Alphabet der Aminosäuren des Proteins und deshalb von besonderem Interesse. Es ist bekannt, dass der dreidimensionale Aufbau durch die Sequenz determiniert ist, allerdings ist bisher nicht geklärt, welche Information den Prozess der Proteinfaltung (d.h. der Ausbildung der 3D-Struktur) steuert. 2.2 Zusammenhang von DNA, RNA und Proteinen Die im vorangegangenen Abschnitt behandelten Biosequenzen stehen in einem biologischen Zusammenhang. Die DNA ist, wie bereits erwähnt, Träger des Erbguts eines Organismus. Jedem Gen, der kleinsten vererbbaren Einheit auf einem DNA-Molekül, kann eine Peptidkette (also eine Aminosäuresequenz) zugeordnet werden. In Experimenten wurde die Erkenntnis gewonnen, dass die Gene auf der DNA in einer linearen Sequenz angeordnet sind. Sie überlappen sich normalerweise nicht, die Ausnahme bildet das Erbgut einiger Viren. Da ein Gen demnach als Sequenz von Nukleotiden beschreibbar ist, folgt, dass die Aminosäuresequenz eines Peptids mittels eines eindeutigen Codes aus dem Gen ermittelbar ist. Dieser wird als genetischer Code (Tabelle 2.4) bezeichnet. Die kleinste Informationseinheit ist dabei eine Gruppe aus drei Basen (Basentriplett), die als Codon bezeichnet wird. Eine Abbildung von drei aufeinanderfolgenden Nukleotiden auf eine Aminosäure ist vollständig, da mit drei Nukleotiden 43 = 64 verschiedene Kombinationen möglich sind. Mit zwei Nukleotiden könnten nicht alle 20 Aminosäuren abgebildet werden (42 = 16). Das in Abbildung 2.1 dargestellte zentrale Dogma der Molekularbiologie veranschaulicht den Zusammenhang der hier betrachteten Sequenzen und die Vorgänge, an denen diese beteiligt sind. Im zentralen Dogma wird der Vorgang der Informationsübertragung nur von der DNA zum Protein dargestellt, nicht umgekehrt. Es sei hier erwähnt, dass bei 6 KAPITEL 2. BIOLOGISCHE GRUNDLAGEN 1. Position U (A) C (G) A (T) G (C) U (A) Phe Phe Leu Leu Leu Leu Leu Leu Ile Ile Ile Met Val Val Val Val 2. Position C (G) A (T) Ser Tyr Ser Tyr Ser Stop Ser Stop Pro His Pro His Pro Gln Pro Gln Thr Asn Thr Asn Thr Lys Thr Lys Ala Asp Ala Asp Ala Glu Ala Glu 3. Position G (C) Cys Cys Stop Trp Arg Arg Arg Arg Ser Ser Arg Arg Gly Gly Gly Gly U (A) C (G) A (T) G (C) U (A) C (G) A (T) G (C) U (A) C (G) A (T) G (C) U (A) C (G) A (T) G (C) Tabelle 2.4: Der genetische Code. Die Nukleotidsymbole bezeichnen Residuen der transkribierten mRNA, in Klammern sind die entsprechenden Basen der DNA angegeben. Abbildung 2.1: Zentrales Dogma der Molekularbiologie 7 KAPITEL 2. BIOLOGISCHE GRUNDLAGEN bestimmten Viren, den Retroviren, die Synthese der DNA aus der RNA möglich ist. Die Übersetzung der DNA in Proteine (Proteinbiosynthese), also die Anwendung des genetischen Codes, erfolgt in zwei Schritten: 1. Transkription Für die Proteinbiosynthese werden nur Einzelteile des DNA-Strangs benötigt, die als RNA-Molekül kopiert werden. Dazu wird der DNA-Doppelstrang enzymatisch getrennt und einer der Stränge komplementär auf die RNA kopiert: • Adenin in der DNA entspricht Uracil in der RNA • Cytosin entspricht Guanin • Guanin entspricht Cytosin • Thymin entspricht Adenin Das Produkt dieses ersten Transkriptionsschritts wird in einem zweiten Schritt weiter modifiziert. Die wichtigsten Veränderungen sind die Verkürzung der Sequenz an einem Ende (untranslatierte Region) sowie die Entfernung nicht kodierender Teilsequenzen aus der RNA. Die nichtkodierenden Sequenzen (Introns) werden aus der RNA entfernt, die dazwischen liegenden Sequenzen (Exons) werden verbunden (verspleißt). Das Vorkommen nichtkodierender Sequenzen wurde entdeckt, weil bei der Lokalisierung der Gene in der DNA deren diskontinuierliche Verteilung aufgefallen war. Das Ergebnis ist die Messenger RNA (mRNA), die zur Translation benötigt wird. 2. Translation Der zweite Schritt der Proteinbiosynthese sorgt für die Übersetzung der mRNA in ein Protein. Mit Hilfe des genetischen Codes (Tabelle 2.4) wird aus den ersten drei Nukleotiden der Sequenz eine Aminosäure synthetisiert, aus den nächsten drei Nukleotiden die zweite und so fort. Die Translationsprodukte hintereinander liegender mRNA-Basentripletts liegen im entstehenden Protein ebenfalls hintereinander. Der Translationsvorgang wird beendet, wenn eines der Stoppcodons gefunden wird. In der Praxis liegt meist nur ein mRNA-Fragment vor, bei dem das Startcodon nicht mit Sicherheit bestimmbar ist. Folglich gibt es drei verschiedene Leseraster zum Starten der Translation (Beginn an den ersten drei Basen), die unterschiedliche Aminosäuresequenzen ergeben. Zwei Eigenschaften des genetischen Codes sind in diesem Zusammenhang von Bedeutung: 1. Universalität Der genetische Code ist für fast alle Spezies gleich. Die Ausnahme bilden Organismen mit sehr kleinen Genomen, die nur wenige Proteine kodieren. 2. Degeneriertheit Der genetische Code ist nicht eineindeutig, d.h. einer Aminosäure kann meistens kein kodierendes Basentriplett zugeordnet werden, da fast alle Aminosäuren mindestens zwei Basentripletts besitzen. Deshalb darf bei einer relativen Unähnlichkeit zweier DNA-Sequenzen nicht gefolgert werden, dass die kodierten Proteine keine Ähnlichkeit besitzen. 8 Kapitel 3 Sequenzvergleiche 3.1 Bedeutung von Sequenzvergleichen Nachdem im letzten Kapitel der Begriff Sequenz im biologischen Kontext geklärt wurde, soll es hier um die Bewertung von Ähnlichkeiten und Unterschieden von Biosequenzen gehen. Ziel dieser Betrachtungen ist die Möglichkeit, Beziehungen zwischen den Sequenzen und den dazugehörigen Organismen abzuleiten: • strukturelle Beziehungen Da die 3D-Struktur durch die Primärstruktur (die Sequenz) determiniert ist, ist die starke Ähnlichkeit von bestimmten Bereichen zweier Sequenzen ein Hinweis auf eine ähnliche räumliche Struktur der dazugehörigen Proteine. • funktionale Beziehungen Wenn sich die 3D-Struktur zweier Proteine in Teilen stark ähnelt, so liegt deren funktionale Verwandtschaft nahe. In Kombination mit obiger Implikation ist damit die Funktion eines Proteins aus dessen Sequenz ableitbar, sofern bereits die Funktion eines Vergleichsproteins auf experimentellem Wege ermittelt wurde. • evolutionäre Beziehungen Ein drittes Ziel von Sequenzvergleichen ist der Nachweis der Homologie. Zwei Sequenzen sind homolog, falls sie einen gemeinsamen evolutionären Ursprung, d.h. die gleiche Sequenz als Vorfahren haben [22]. Homologie kann in zwei Formen auftreten: 1. Orthologie: Die betrachteten Sequenzen haben eine ähnliche Funktion, stammen aber aus verschiedenen Spezies. Homologe Sequenzen dieser Art zeigen deshalb die Differenzierung und Verwandtschaft von Spezies an. 2. Paralogie: Die betrachteten Sequenzen haben unterschiedliche, aber verwandte Funktionen innerhalb desselben Organismus. Paraloge Sequenzen entstehen durch Gen-Duplikation. Sie geben Hinweise auf die Entwicklung des Genoms einer einzelnen Spezies. Da die Vorfahren-Sequenz, d.h. der gemeinsame evolutionäre Ursprung der Sequenzen, oft nicht bekannt ist, wird versucht, mittels Sequenzvergleichen die Homologie nachzuweisen. Ziel ist es, die Ähnlichkeit von Sequenzen bewertbar zu machen. Anhand 9 KAPITEL 3. SEQUENZVERGLEICHE der Bewertung ist man dann in der Lage, Ähnlichkeiten zu vergleichen. Homologie wird geschlussfolgert, wenn das Ähnlichkeitsmaß der zu untersuchenden Sequenzen signifikant höher als das zweier zufälliger Sequenzen ist. Die umgekehrte Implikation gilt dagegen nicht: bestimmte Sequenzpaare sind zwar homolog, zeigen jedoch keine signifikante Verwandtschaft auf Sequenzniveau. 3.2 Bewertungsschemata Sequenzvergleichsalgorithmen verarbeiten Zeichenketten (strings) und berechnen bewertete Alignierungen. Eine Alignierung (engl.: to align – ausrichten, in Übereinstimmung bringen) zweier Zeichenketten ist eine Ausrichtung der Zeichen des einen strings zu denen des anderen. Eine solche Anordnung kann numerisch bewertet werden. Bevor auf konkrete Alignierungsalgorithmen eingegangen wird, führt dieser Abschnitt den Begriff des Bewertungsschemas ein. Die Berechnung der Ähnlichkeit zweier Sequenzen wird in den hier vorgestellten Algorithmen auf die Substitution einzelner Zeichen reduziert. Die Zuordnung eines Werts s(a, b) zu einem Zeichenpaar (a, b) kann als Maß für die Ähnlichkeit der beiden Zeichen gelten: je höher der Wert, desto ähnlicher die Zeichen. s wird als Bewertungs- oder scoring-Schema (engl.: to score – benoten, Punkte vergeben) bezeichnet. Ist das Alphabet, auf dem die Bewertung definiert wird, endlich, so kann das Schema als Matrix M dargestellt werden. Dabei gilt für jedes Matrixelement ma,b : ma,b = s(a, b) Die Aufgabe von Alignierungsalgorithmen besteht darin, eine Alignierung mit möglichst hoher Bewertung zu ermitteln. Deshalb kann es nötig sein, dass einige Zeichen des einen strings zu Lücken (engl.: gaps oder indels) im anderen string zugeordnet werden, falls dadurch eine hoch bewertete Alignierung gebildet werden kann. Im Alphabet Σ ist deshalb oft eines der Zeichen ε, ∗ oder - zur Repräsentation einer Lücke enthalten. Folglich müssen auch Bewertungen der Form s(a, ε) bzw. s(ε, b) Teil des Bewertungsschemas sein. Bewertungsmatrizen werden auch als Substitutionsmatrizen bezeichnet, da die Zuordnung zweier Zeichen auch als Ersetzung des einen Zeichens durch das andere interpretiert werden kann. Da Protein- und NA-Sequenzen Zeichenketten auf endlichen Alphabeten sind, werden zu ihrer Alignierung Bewertungsmatrizen verwendet. Die Bewertungsmatrix hat eine herausragende Bedeutung, weil sie als einziges Element des Alignierungsalgorithmus Wissen aus der Anwendungsdomäne in den Algorithmus überträgt. Unterschiedliche Anwendungen benötigen dabei verschiedene Bewertungsschemata. Einen Überblick über die wichtigsten Schemata gibt Barton [4]. In der Praxis werden für Proteinsequenzen meistens die Substitutionsmatrizen der PAM - (point-accepted mutations, [11]) und der BLOSUM Familie (BLOCKS substitution matrix, [13]) verwendet, bei NA-Sequenzen wird häufig nur zwischen matches (engl.: match – Ebenbild, Gegenstück) und mismatches unterschieden, d.h. Paaren identischer bzw. nicht-identischer Residuen. 3.3 Alignierungen Das Ziel von Vergleichsalgorithmen ist die Bestimmung der Ähnlichkeit von Sequenzen. Die hier betrachteten Verfahren untersuchen nur jeweils zwei Sequenzen, eine Erweiterung auf 10 KAPITEL 3. SEQUENZVERGLEICHE S1 S2 Score S 0 (S1 , S2 ) C D −4 A A +5 B B +5 A C D – B D −3 P−4 +5 = 11 B B +5 – D −3 C C +5 Abbildung 3.1: Bewertung einer Beispielalignierung der Zeichenketten CABACDBC und DABBDBDC. Das Zeichen ”-” steht für eine Lücke. mehrere gleichzeitig anzuordnende Sequenzen (engl.: multiple alignment) ist aber möglich. Die Bestimmung der Ähnlichkeit besteht darin, eine möglichst hoch bewertete Alignierung der Sequenzen, oder Teilen davon, zu erreichen. Die einzelnen Zeichen einer Sequenz behalten nach der Ausrichtung zur anderen Sequenz ihre Reihenfolge. Zeichen der ersten Sequenz können dabei zu Zeichen der anderen Sequenz oder zu Lücken in dieser Sequenz zugeordnet werden. Der umgekehrte Fall gilt entsprechend. Abbildung 3.1 stellt eine mögliche Alignierung der Zeichenketten CABACDBC und DABBDBDC dar. Die zugeordneten Paare von Zeichen werden zur Veranschaulichung nach einem einfachen Schema bewertet: +5 für matches, −4 für mismatches und −3 für Zeichen-Lücken-Zuordnungen. Der Wert einer Alignierung ergibt sich als Summe der Werte der Zeichenpaare. Seien • x und y zwei Zeichenketten, • xi und yi daraus durch Lückeneinfügen entstandene Sequenzen, • a[j] das Zeichen an der j-ten Position einer Sequenz a, • ni die Länge der Alignierung von xi und yi , dann berechnet folgende Formel den Wert der Alignierung: 0 S (xi , yi ) = ni X s(xi [j], yi [j]) (3.1) j=1 Über die Ähnlichkeit der beiden Sequenzen x und y kann erst dann eine Aussage getroffen werden, wenn alle möglichen Alignierungen gebildet und bewertet werden. Die bestbewertete Alignierung ist dann ein Maß für die Ähnlichkeit der beiden Sequenzen. Der Wert S(x, y) einer solchen optimalen Alignierung wird deshalb als Maximum über die Werte aller möglichen Alignierungen (Formel 3.1) definiert: S(x, y) = max S 0 (xi , yi ) i (3.2) Alignierungen können unter verschiedenen Gesichtspunkten klassifiziert werden. Die gebräuchlichste Klassifizierung unterscheidet zwischen globaler und lokaler Alignierung. Obiges Beispiel ist eine globale Alignierung, weil die gesamten Zeichenketten zur Anordnung herangezogen werden. Lokale Alignierung bedeutet die Anordnung zweier Sequenzausschnitte. Es müssen alle Subsequenzen für eine Alignierung geprüft werden. Die optimale lokale Alignierung ist unter allen möglichen diejenige mit der höchsten Bewertung. Eine zweite Klassifikation unterscheidet zwischen lückenbehafteten (gapped ) Alignierungen und solchen ohne Lücken (ungapped ). Obiges Beispiel ist eine lückenbehaftete Alignierung. Es ist einsichtig, dass eine globale Alignierung lückenbehaftet sein muss, weil 11 KAPITEL 3. SEQUENZVERGLEICHE es sonst nur eine Möglichkeit gäbe, die beiden Sequenzen anzuordnen. Für alle anderen Kombinationen gibt es entsprechende Algorithmen. Die Bestimmung globaler Alignierungen dient unter anderem dazu, die evolutionäre Entwicklung einer Proteinfamilie zu rekonstruieren, wenn bekannt ist, dass beide Sequenzen zu dieser Familie gehören. Die häufigere Problemstellung ist jedoch die lokale Alignierung. Sie findet bei der Identifikation von Genen in langen DNA-Sequenzen Anwendung. Auch Proteine sind aus strukturellen und funktionalen Untereinheiten aufgebaut, deren Position in der Sequenz nur durch lokale Alignierung bestimmt werden kann, wenn eine Sequenz mit einer verwandten Funktion zum Sequenzvergleich zur Verfügung steht. 3.4 Algorithmen zur Bestimmung der optimalen Alignierung Sowohl für die globale als auch die lokale Alignierung existieren Algorithmen, die die optimale Ausrichtung und damit das Maß für die Ähnlichkeit zweier Sequenzen finden. Sie sollen hier kurz vorgestellt werden, da sie den Ausgangspunkt für die approximativen Algorithmen bilden. 3.4.1 Der Algorithmus von Needleman und Wunsch Zur optimalen globalen Alignierung zweier Sequenzen wird der Algorithmus von Needleman und Wunsch [25] verwendet. Der Algorithmus ist ein Beispiel für die Technik der dynamischen Programmierung [5], bei der Teilergebnisse so in ihrer zeitlichen Reihenfolge berechnet werden, dass sie zum benötigten Zeitpunkt vorliegen. Die Berechnung jedes Teilergebnisses greift auf eine konstante Anzahl bereits berechneter Teilergebnisse zurück. Ausgenommen sind die Initialwerte, die sich unabhängig von anderen Werten berechnen lassen. Seien: • x und y zwei Sequenzen der Längen |x| = n und |y| = m, • Si,j = S(x[1, i], y[1, j]) der Wert der besten Anordnung der entsprechenden Präfixe von x und y mit • x[p1 , p2 ] als Teilsequenz von x, die bei Position p1 beginnt und bei p2 endet; analoges gilt für y Die Felder Si,j werden wie folgt berechnet, Sn,m ist dann der Wert der optimalen Alignierung der Sequenzen x und y: S0,0 = 0 (3.3) S0,j = S0,j−1 + s(ε, y[j]) für 1≤j≤m (3.4) Si,0 = Si−1,0 + s(x[i], ε) + s(ε ,y[j]), Si−1,j Si−1,j−1 + s(x[i],y[j]), Si,j = max Si,j−1 + s(x[i],ε ) für 1≤i≤n (3.5) für i, j 6= 0 (3.6) Die Optimalität des Ausdrucks wird induktiv bewiesen. Der Induktionsschritt besteht in der Überlegung, dass die Alignierung der Sequenzen x[1, i] und y[1, j] auf drei Arten enden kann: 12 KAPITEL 3. SEQUENZVERGLEICHE x[1,i] y[1,j] Fall 1 . . . x[i] ...ε Fall 2 . . . x[i] . . . y[j] Fall 3 ...ε . . . y[j] Folglich greift die Berechnung des Zwischenergebnisses Si,j auf die Teilergebnisse Si−1,j , Si−1,j−1 und Si,j−1 zurück, und der Algorithmus sorgt dafür, dass diese vor Si,j berechnet werden. Zur Ermittlung der optimalen Alignierung aus der S-Matrix muss vom Element Sn,m der Berechnungsweg des jeweils maximalen Werts zurückgegangen werden. Die Berechnung eines jeden Elements der Matrix besteht aus konstant vielen Schritten. Da sie aus n · m Elementen besteht, hat der Algorithmus eine Komplexität von O(n · m). Die Zeilen der Matrix können als Repräsentanten für die Präfixe der Sequenz x betrachtet werden, die Spalten als Repräsentanten für die Präfixe von y. Eine von Gotoh [12] eingeführte und häufig verwendete Variation des Algorithmus unterscheidet zwischen dem Einfügen und der Verlängerung einer Lücke. Der Wert einer alignierten Lücke ist dabei eine affine Funktion, die von der Länge abhängt. Die Komplexität dieses modifizierten Algorithmus beträgt O(n · m · (n + m)). 3.4.2 Der Algorithmus von Smith und Waterman Für die lokale Sequenzalignierung existiert ebenfalls ein optimaler Algorithmus, der von Smith und Waterman [28] entwickelt wurde. Dieser kann vom Needleman-Wunsch-Algorithmus hergeleitet werden. Voraussetzung für diese Modifikation ist ein Bewertungsschema, das Ähnlichkeit positiv und Unähnlichkeit negativ bewertet. Ist ein solches Schema gegeben, sorgt der Algorithmus dafür, dass die optimale lokale Alignierung nicht mit negativen Werten beginnen oder enden kann. Eine Alignierung ist nicht optimal, wenn noch positive Werte an einem der beiden Enden zu einem höheren Wert führen würden. Die Berechnung sogenannter ”affiner Lücken” geschieht, wie bei Needleman-Wunsch, durch die Modifikation von Gotoh [12]. 3.5 Approximative Alignierungsalgorithmen Der Algorithmus von Smith und Waterman bestimmt die optimale lokale Alignierung zweier Sequenzen. Dieser Eigenschaft steht ein Berechnungsaufwand von O(n2 ) bzw. O(n3 ) gegenüber. Zum Durchsuchen von Sequenz-Datensammlungen ist dieser Aufwand zu groß. Für diesen mittlerweile sehr häufig vorkommenden Anwendungsfall bedient man sich heuristischer Verfahren zur Approximation des Smith-Waterman-Algorithmus. Durch Heuristiken wird der Lösungsraum, der durch dynamische Programmierung bearbeitet werden muss, begrenzt und dadurch die Laufzeit verbessert. Die wichtigsten approximativen Methoden für den paarweisen Sequenzvergleich sind die Algorithmen der FASTA- und BLAST -Programmpakete. Das von Pearson und Lipman entwickelte FASTA [26, 27] war der erste wichtige Ansatz zur näherungsweisen Lösung der optimalen lokalen Alignierung. Schwerpunkt dieser Arbeit ist jedoch BLAST. 3.5.1 BLAST BLAST (Basic Local Alignment Search Tool ) [1, 2, 30] wurde von Altschul et al. am NCBI (National Center for Biotechnology Information) entwickelt. Der Algorithmus zeichnet 13 KAPITEL 3. SEQUENZVERGLEICHE sich gegenüber FASTA durch eine geringere Laufzeit bei gleicher Sensitivität aus. Von dem ursprünglichen Algorithmus gibt es eine Vielzahl von Erweiterungen, von denen hier einige vorgestellt werden. BLAST ist als Web-Applikation beim NCBI unter http://www.ncbi.nlm.nih.gov verfügbar. Dort kann jedes BLAST-Programm mit eigenen Anfragesequenzen gegen eine Vielzahl von Datensammlungen getestet werden. 3.5.2 Grundidee des Algorithmus BLAST ist für den Vergleich einer Anfragesequenz Anfragesequenz Q mit einer SequenzDatensammlung ausgelegt. Die Grundidee des Algorithmus besteht darin, in den Datensammlungssequenzen (im Folgenden Vergleichssequenzen) nach Teilstücken zu suchen, die ”gute Kandidaten” (Hits) für Alignierungen mit Teilstücken der Anfragesequenz sind. Die Hits werden dann zu Alignierungen expandiert, die bewertet werden. Vor dem Algorithmus wird die Anfragesequenz gefiltert, d.h. es werden Regionen geringer Komplexität maskiert. Für Nukleotidsequenzen wird zur Filterung DUST1 , für Aminosäuresequenzen SEG [29] verwendet. Der BLAST-Algorithmus selbst führt auf jeder Vergleichssequenz D die folgenden drei Schritte aus: 1. Lokalisierung der Hits. In der Vergleichssequenz werden Teilwörter der Länge w gesucht, die mit gleich langen Teilwörtern der Anfragesequenz eine Alignierung mit einem Wert größer T bilden. Eine derartige Alignierung wird Hit genannt. 2. Expansion eines Hits. Ein Hit wird zu einer größeren lückenfreien Alignierung expandiert. Dazu wird die jeweils aktuelle Alignierung schrittweise nach links bzw. rechts um ein Zeichen erweitert. Die Erweiterung wird solange vorangetrieben, bis die entstehende Alignierung um einen festgelegten Wert X vom erweiterungslokalen Maximum abfällt. X wird als dropoff -Wert bezeichnet (engl.: to drop off – nachlassen, zurückgehen). Dann stellt das lokale Maximum das Ergebnis dar und wird mit HSP (High-scoring Segment Pair ) bezeichnet. 3. Ausgabe der HSPs. Hat ein HSP einen Wert größer als S, wird er als lokale Alignierung ausgegeben. BLAST hat damit die Möglichkeit, mehrere lokale Alignierungen zu berechnen und auszugeben. Die Schritte beschreiben bereits die von BLAST benutzten Parameter, die unterschiedliche Auswirkungen auf Selektivität und Sensitivität des Algorithmus haben. Sensitivität ist die Fähigkeit des Algorithmus, tatsächlich verwandte Sequenzen zu finden und hoch zu bewerten. Selektivität beschreibt die Fähigkeit, nicht verwandte Sequenz niedrig zu bewerten und damit nicht zu betrachten. Folgende BLAST-Parameter dienen der Steuerung des Algorithmus: 1 Der Algorithmus wurde von Roman L. Tatusov und David J. Lipman am NCBI entwickelt. Zu DUST gibt es keine Veröffentlichungen. 14 KAPITEL 3. SEQUENZVERGLEICHE • w ist die Wortlänge eines Hits. Von den BLAST-Autoren empfohlene Werte sind 2 oder 3 für Proteinvergleiche und 11 für DNA-Vergleiche. Eine Erhöhung des Werts geht mit der Erhöhung der Selektivität einher. • T ist der Schwellwert für die Entscheidung, welche Alignierung des ersten Schritts ein Hit ist, also im zweiten Schritt weiterverarbeitet wird. Beim Festlegen dieses Parameters ist zu beachten, dass ein niedriger Wert mehr Hits produziert und damit ein höheres Potenzial für erfolgreiche Alignierungen bietet (höhere Sensitivität), dabei allerdings auch mehr Rechenzeit aufgewendet wird. • X ist der sogenannte dropoff -Parameter, der bestimmt, ob eine Expansion abgebrochen oder weiterverfolgt wird. Ein höherer Wert geht mit einer Erhöhnung der Sensitivität einher. • S entscheidet darüber, ob ein HSP als Ergebnis von BLAST ausgegeben wird. Je niedriger der Wert, desto mehr Ergebnis-Alignierungen (höhere Sensitivität). Heutzutage wird diese Entscheidung allerdings anhand abgeleiteter Variablen gefällt. Aus dem hier definierten Wert einer Alignierung, dem nominalen Wert, wird ein normalisierter Wert berechnet, der die Charakteristika des jeweiligen Bewertungsschemas sowie die Größe des Suchraums mit einbezieht [21]. Mit Hilfe des normalisierten Werts können verschiedene BLAST-Suchläufe mit unterschiedlichen Bewertungsschemata untereinander verglichen werden. Aus dem normalisierten Wert wird ein Erwartungswert berechnet, der Auskunft darüber gibt, wieviele Sequenzalignierungen mit dem gleichen oder einem besseren normalisierten Wert im entsprechenden Suchraum zu erwarten sind, ohne den Inhalt in Betracht zu ziehen. Je niedriger der Erwartungswert der Alignierung, desto signifikanter ist die Ähnlichkeit der alignierten Sequenzen. Die Ausgabe von HSPs wird in den zuletzt entwickelten BLAST-Programmvarianten mit dem Erwartungswert gesteuert. Die Lokalisierung selbst besteht aus zwei Teilen: 1. Es werden alle Wörter der Länge w bestimmt, die mit einem Teilwort der Anfragesequenz das T -Kriterium erfüllen. Diese Wörter werden als w-mere (engl.: w-mers) bezeichnet. Dieser Schritt ist unabhängig von den Vergleichssequenzen und wird deshalb nur einmal vor der gesamten BLAST-Suche durchgeführt. Die Wörter werden zusammen mit der Teilwort-Position aus der Anfragesequenz in einer Liste gespeichert. 2. Die jeweilige Vergleichssequenz wird nach den Wörtern in der Liste durchsucht. Im ersten Schritt ist zu beachten, dass alle möglichen Wortkombinationen für jedes der Teilwörter durchsucht werden müssen (bei w = 3 und einer Protein-Anfragesequenz müssen demzufolge 203 = 8000 Wörter geprüft werden). Der Algorithmus für DNA-Sequenzen ist dahingehend einfacher, dass der erste Schritt der Hit-Lokalisierung wegfällt. Die Liste der w-mere besteht dort einfach aus allen Teilwörtern der Anfragesequenz selbst, also genau |Q| − w + 1 Elementen. Durch die Beschreibung des Expansionsschritts wird deutlich, dass BLAST ohne Lücken aligniert. Dieses Vorgehen leistet einen gewissen Beitrag zur Geschwindigkeitssteigerung 15 KAPITEL 3. SEQUENZVERGLEICHE gegenüber einem rigorosen Algorithmus. Es kann allerdings festgehalten werden, dass der eigentliche Vorteil von BLAST in der Anwendung zweier Heuristiken liegt: 1. Suche von Hits als Kandidaten Die Chance einer gut bewerteten Alignierung ist an Stellen höher, an denen bereits ein Teilwort-Paar mit einem hohen Wert zu finden ist. Anschaulich werden durch den ersten BLAST-Schritt Elemente des Suchraums vorgeben, durch den eine Alignierung laufen muss. Dieser Vorteil hat allerdings nur dann Bestand, wenn sich nicht zu viele Alignierungen unterschiedlicher Hits überlappen, weil dann bestimmte Zeichenpaare mehrfach untersucht werden. Die Heuristik kann durch den Parameter T gesteuert werden. 2. Abbruchbedingung für die alignment-Expansion Die Idee der Abbruchbedingung bei der Expansion ist, dass ein langer Abschnitt schlecht bewerteter Residuenpaare selten durch einen daran anschließenden Abschnitt hoher Ähnlichkeit wieder ausgeglichen wird. Deshalb wird die Alignierung abgebrochen. Die Heuristik kann durch den Parameter X gesteuert werden. 3.5.3 Erweiterungen des ursprünglichen Algorithmus Die im vorigen Abschnitt beschriebene ursprüngliche Variante von BLAST hat einige Unzulänglichkeiten, die zu Erweiterungen des Algorithmus geführt haben: • Oftmals werden mehrere Hits gefunden, die bei der Expansion die gleichen Residuenpaare bearbeiten und letztlich fast dieselben Alignierungen finden. Diese ”Doppelarbeit” vergrößert den Rechenaufwand ohne positiven Effekt auf das Ergebnis. • Zur Berechnung vernünftiger Ergebnisse müssen die Parameter so eingestellt werden, dass der Algorithmus viele Hits findet und jeden von ihnen expandieren muss. Da der Erweiterungsschritt den größten Teil der Rechenzeit konsumiert, liegt es nahe, die Zahl der Expansionen ohne Beeinträchtigung der Sensitivität zu reduzieren. • Der Algorithmus aligniert Sequenzen ohne Lücken. Oftmals könnten signifikantere Alignierungen gebildet werden, wenn zwei oder mehr herkömmliche Alignierungen durch Einfügen von Lücken vereinigt würden. Zur Lösung dieser Probleme wurden zwei Erweiterungen von BLAST entwickelt [2]: 1. Für das Problem der mehrfachen Betrachtung gleicher Teilsequenzpaare und zur Reduktion der Expansionen wird die Zwei-Hit-Methode vorgeschlagen. Sie besteht darin, nur diejenigen Hits zu erweitern, die in ihrer Nachbarschaft einen weiteren, nicht überlappenden Hit haben. Der Abstand der Hits ist auf beiden Sequenzen gleich und darf einen festgelegten Wert nicht überschreiten. Um eine vergleichbare Sensitivität zu erreichen, muss der T -Parameter gegenüber dem Originalalgorithmus verringert werden. Das führt zwar zu mehr Hits, aber zu weniger Expansionen. Die Methode dient hauptsächlich der Geschwindigkeitsverbesserung. 2. Zur lückenbehafteten Alignierung wird zunächst der normale Algorithmus ausgeführt. Nach der Expansion wird der Wert eines HSP mit einem Schwellwert verglichen. 16 KAPITEL 3. SEQUENZVERGLEICHE Liegt das HSP über der Schwelle, so wird eine lückenbehaftete Alignierung mittels dynamischer Programmierung angestoßen. Der Unterschied zum Smith-WatermanAlgorithmus ist, dass ein Residuenpaar durch das HSP festgelegt wird, durch das die Alignierung laufen muss. Damit wird nicht der gesamte Suchraum betrachtet. Außerdem wird auch hier ein dropoff -Wert zum Abbruch der lückenbehafteten Alignierung verwendet. Dieses Gapped BLAST genannte Verfahren dient im Gegensatz zur Zwei-Hit-Methode nicht dazu, die Geschwindigkeit zu steigern, sondern signifikante Alignierungen zu detektieren, die dem ursprünglichen BLAST verborgen geblieben wären. 3.5.4 Programmvarianten von BLAST BLAST ist sowohl für den Vergleich von sowohl Protein- als auch DNA-Sequenzen geeignet. Deshalb wurden mehrere Programmvarianten entwickelt: • blastp Diese Variante vergleicht eine Protein-Anfragesequenz mit Sequenzen einer Proteindatensammlung. Alle Erweiterungen des Algorithmus sind anwendbar. • blastn blastn vergleicht eine DNA-Anfragesequenz mit Sequenzen einer DNA-Datensammlung. Eine DNA-Sequenz besitzt zwei Leserichtungen (strands): die normale Sequenz sowie die umgekehrte Reihenfolge der Residuen der Komplementärsequenz (vergleiche Abschnitt 2.1). Es kann eingestellt werden, welche der Leserichtungen der Anfragesequenz analysiert werden sollen. Auch für diese Variante sind alle Erweiterungen von BLAST anwendbar. • blastx Dieses BLAST-Programm vergleicht alle Übersetzungen einer DNA-Anfragesequenz mit allen Sequenzen einer Proteindatensammlung. Dabei wird die DNA-Sequenz in alle sechs möglichen Proteinsequenzkodierungen (Leseraster, engl.: reading frames) übersetzt. Jede dieser Kodierungen stellt eine Protein-Anfragesequenz im oben genannten Sinne dar. Die Anzahl der verschiedenen Leseraster ergibt sich aus der Kombination der drei Übersetzungsraster einer Sequenz bei der Translation (vergleiche Abschnitt 2.2) mit den beiden Leserichtungen einer DNA-Sequenz. Als zusätzlicher Parameter für diese BLAST-Variante ist der zu verwendende genetische Code für die Translation anzugeben. Alle Erweiterungen von BLAST sind anwendbar. • tblastn tblastn vergleicht eine Protein-Anfragesequenz mit den Leserastern aller Sequenzen einer DNA-Datensammlung. Es stellt die Umkehrung von blastx dar. Auch hier ist der genetische Code für die DNA-Sequenzen anzugeben. Alle Erweiterungen von BLAST sind anwendbar. • tblastx Dieses Programm vergleicht alle Leseraster einer DNA-Anfragesequenz mit den Leserastern aller Sequenzen einer DNA-Datensammlung und fasst damit blastx und tblastn zusammen. Durch die Leseraster von sowohl Anfrage- als auch Vergleichssequenz sind hier 6 · 6 = 36 BLAST-Durchläufe pro Sequenzpaar durchzuführen. 17 KAPITEL 3. SEQUENZVERGLEICHE Deshalb wird die Anwendung dieser Variante nur für spezielle Problemstellungen empfohlen. Für die Anfrage- und die Vergleichssequenz(en) muss jeweils der genetische Code übergeben werden. Die lückenbehaftete Alignierung wird für tblastx nicht unterstützt. 3.5.5 PSI-BLAST Altschul et al. [2] haben eine Erweiterung von BLAST entwickelt, die zur Detektion schwacher Homologien entwickelt wurde. Diese PSI-BLAST (Position-Specific Iterated BLAST ) genannte Erweiterung erzeugt eine positionsspezifische Bewertungsmatrix (PSSM, PositionSpecific Scoring-Matrix ) aus einem BLAST-Lauf, um mit deren Hilfe weitere BLAST-Läufe zu starten und weitere verwandte Sequenzen zu entdecken. Ausgangspunkt für eine Anwendung von PSI-BLAST ist ein normaler BLAST-Lauf. Das Ergebnis, eine nach aufsteigendem Erwartungswert geordnete Liste von alignierten Sequenzen, wird zur Bildung einer PSSM herangezogen. Eine solche Matrix unterscheidet sich von einer herkömmlichen Bewertungsmatrix dadurch, dass die Elemente der Matrix nicht Bewertungen von Residuenpaaren, sondern Residuum-Position-Paaren sind. Folglich werden darin Werte der Form s(Residuum, Position in der Query Q) gespeichert. Die PSSM hat die Dimension 20 × |Q|. Zur Bildung der Matrix sei auf [2] verwiesen. 3.5.6 PHI-BLAST Proteinfamilien werden oft über spezielle Sequenzmuster charakterisiert. Ein Beispiel ist die PROSITE -Datenbank [3], bei deren Aufbau das Ziel bestand, diejenigen Muster zu speichern, die für die Funktion einer Proteinfamilie relevant sind. Solche Muster oder Signaturen können als reguläre Ausdrücke notiert werden [6] und stellen damit eine Art Grammatik für Segmente aus den Proteinsequenzen der entsprechenden Familie dar. Hauptziel ist es, mit einer Signatur möglichst alle Proteine der Familie darstellen zu können und keine Sequenzen mit völlig anderer Funktion abzubilden. Eine Weiterentwicklung von BLAST, PHI-BLAST (Pattern-Hit Initiated BLAST ) [30], basiert auf der Idee solcher Muster (engl.: patterns). Dabei wird dem Programm zusätzlich zu einer Anfragesequenz ein Muster übergeben, das in dieser Sequenz enthalten sein muss. Der PHI-BLAST-Algorithmus sucht dann in den Vergleichssequenzen nach dem Muster. Alle Sequenzen, die das Muster enthalten, werden mit dessen Hilfe zur Anfragesequenz aligniert. Die Alignierung kann nun als HSP im Sinne von Gapped BLAST betrachtet werden, bildet also den Ausgangspunkt für eine lückenbehaftete Alignierung. 18 Kapitel 4 Analyse des BLAST-Programmcodes Für die Integration von BLAST in ein objektrelationales Datenbanksystem ist es notwendig, dessen Quelltext zu analysieren. Dabei werden speziell die Module betrachtet, die für die Integration modifiziert werden müssen. Die Anpassung von BLAST umfasst die in Abschnitt 3.5.4 vorgestellten Varianten. 4.1 Das NCBI-Toolkit Die Algorithmen von BLAST werden in mehreren Programmen und Softwarewerkzeugen benutzt, die zum NCBI Software Development Toolkit gehören. Im Rahmen dieser Diplomarbeit wird die Version 2.1.2 des Toolkits verwendet. Die darin enthaltenen Werkzeuge dienen der Analyse verschiedener biologischer Daten. Sie sind in der Programmiersprache C geschrieben und basieren auf der Funktionsbibliothek NCBI CoreLib, die es erlaubt, plattformunabhängig Software zu entwickeln. Folgende Funktionsgruppen werden in CoreLib implementiert: • Funktionen zum Setzen und Auslesen von Programmparametern und Umgebungsvariablen • Funktionen für graphische Benutzeroberflächen • Funktionen zur Verwaltung von Konfigurationsdateien • Fehlerbehandlungsfunktionen • Dateisystemfunktionen • Speicherverwaltungsfunktionen • Zeichenkettenfunktionen • Mathematische Funktionen • Funktionen zur Verwaltung von Prozessen und Threads 19 KAPITEL 4. ANALYSE DES BLAST-PROGRAMMCODES Abbildung 4.1: Die von BLAST verwendeten Dateien Einige der hier aufgeführten Funktionen bilden lediglich die entsprechenden ANSI-C -Funktionen ab. Im Rahmen des Toolkits wird ein Datenmodell für biotechnologisch relevante Daten verwendet, das in der Sprache ASN.1 (Abstract Syntax Notation, [19]) spezifiziert ist. ASN.1Dokumente beschreiben konkrete Datensätze und können zum Datenaustausch benutzt werden. Zum Einlesen und Speichern von ASN.1-Spezifikationen stellt die Toolbox die Funktionsbibliothek AsnLib zur Verfügung, die aus den Spezifikationen Parse-Bäume erstellt. Mit ihrer Hilfe lassen sich Daten in ASN.1 kodieren und dekodieren. Im Kontext von BLAST kann die Vergleichssequenzdatei das ASN.1-Format haben, außerdem können die Ergebnisalignierungen als ASN.1-Datei ausgegeben werden. Im Normalfall wird für die Sequenzdatensammlung das FASTA-Format verwendet, und die Ergebnisse werden in Form des BLAST-Reports ausgegeben. Deshalb soll hier nicht näher auf ASN.1 eingegangen werden. Das Toolkit verwendet eine Setup-Datei namens .ncbirc (Unix) bzw. ncbi.ini (Windows). Sie spezifiziert einen Verzeichnispfad, der unter anderem die BewertungsmatrixDateien beherbergt. Die Setup-Datei muss sich im Arbeitsverzeichnis desjenigen Benutzers befinden, der ein Werkzeug aus dem Toolkit aufruft. 4.2 Die Module des Programms BLAST Die BLAST-Varianten blastp, blastn, blastx, tblastn und tblastx sind im Werkzeug blastall zusammengefasst. blastall ist ein Kommandozeilenprogramm, das mit Hilfe von Aufrufparametern (siehe Anhang A) gesteuert wird. Als Eingabe verwendet das Programm zwei Dateien, die Anfragesequenzdatei und die Datensammlungs-Datei. blastall produziert als Ausgabe eine Report-Datei, die die Alignierungen enthält. Abbildung 4.1 stellt das Zusammenspiel der verschiedenen Dateien dar. Zur Anpassung des Programms wird zunächst auf die Kern-Dateien von BLAST eingegangen: 20 KAPITEL 4. ANALYSE DES BLAST-PROGRAMMCODES • blastall.c Diese Datei enthält die Main()-Funktion des Programms. Sie wird von der eigentlichen main()-Funktion der CoreLib-Bibliothek aufgerufen, die eine genormte Schnittstelle für verschiedene Programme und deren Parameterverarbeitung darstellt. • blast.c fasst die Funktionen für den BLAST-Algorithmus zusammen. Dazu gehören Funktionen zur Initialisierung von BLAST-Datenstrukturen sowie zur Ausführung der einzelnen Algorithmusschritte. • blastkar.c In dieser Datei sind Funktionen zur Bewertung von Alignierungen und zur Berechnung von Entscheidungsvariablen enthalten. Unter anderem wird damit die Relevanz von Alignierungen bewertet. • In blastutl.c befinden sich Hilfsfunktionen zur Reservierung und Freigabe von BLASTDatenstrukturen sowie zur Auswertung der Programmparameter. Die Funktionen bilden das Gerüst für den BLAST-Algorithmus und werden deshalb nicht nur von blastall, sondern auch von anderen Werkzeugen verwendet. • blastool.c umfasst mehrere Funktionsgruppen. Zunächst enthält es Funktionen zum Setzen von Standardwerten für die Eingabeparameter und für statistische Parameter von BLAST. Das Modul ist außerdem für die Formatierung des BLAST-Reports verantwortlich. • gapxdrop.c Dieses Modul enthält die Implementation für die lückenbehaftete Alignierung und für deren Darstellung im BLAST-Report. • Die Funktionen des Moduls lookup.c implementieren den BLAST-Vorverarbeitungsschritt der w-mer-Bildung. Sie erzeugen und verwalten eine Indexstruktur für die w-mere, mit deren Hilfe die Hits in den Vergleichssequenzen gesucht werden. • readdb.c Das Modul readdb ist die ”Datenbank”-Schnittstelle zu den Sequenzdateien. Da es maßgeblicher Gegenstand der BLAST-Anpassung an eine relationale Datenbank ist, wird sein Aufbau im folgenden Abschnitt 4.3.3 detailliert erläutert. • dust.c Gegenstand dieses Moduls ist der Filteralgorithmus DUST, der für die Filterung der Nukleotid-Anfragesequenz verwendet wird. • seg.c Dieses Modul behandelt den Filteralgorithmus SEG [29], der für die Filterung der Aminosäure-Anfragesequenz verwendet wird. SEG wurde als eigenständiges Programm entwickelt und ist mittlerweile Bestandteil des Toolkits. • tofasta.c dient der Bearbeitung von Dateien im FASTA-Format (siehe Anhang B.1). Die Aufzählung beinhaltet ausschließlich Dateien, die unmittelbar für den Ablauf von BLAST benötigt werden. 21 KAPITEL 4. ANALYSE DES BLAST-PROGRAMMCODES 4.3 4.3.1 Die ”Datenbank”-Schnittstelle von BLAST BLAST-Datensammlungen Bei einer BLAST-Sequenzdatensammlung handelt es sich um eine Datei im FASTA-Format, in der Sequenzen und deren Kennungen gespeichert sind. Damit BLAST diese Datensammlung verwenden kann, wird sie vom Werkzeug FormatDB, das Bestandteil des NCBIToolkits ist, formatiert. Der Dateiname <Datensammlungsname>.nt deutet auf eine Nukleotid-Datensammlung hin, und <Datensammlungsname>.aa steht für eine Aminosäure-Datensammlung. Nach der Formatierung durch FormatDB entstehen aus den FASTA-Dateien folgende Dateien (als Beispiel dient hier die Datensammlung ecoli.aa): • ecoli.aa.phr enthält die Kennungen der Vergleichssequenzen. Für Nukleotid-Datensammlungen ist die Dateiendung .nhr. • ecoli.aa.psq enthält die eigentlichen Sequenzen. Diese sind nicht im ASCII-Format der ursprünglichen FASTA-Datei abgelegt, sondern werden kodiert gespeichert. Die Dateiendung für Nukleotid-Datensammlungen ist .nsq. • In ecoli.aa.pin sind allgemeine Kennwerte der Datensammlung sowie mehrere Indizes abgelegt. Die Indizes verweisen auf die Positionen der Sequenzkennungen und der Sequenzen in den anderen beiden Dateien. Die Dateiendung für NukleotidDatensammlungen ist .nin. Die konkreten Dateiformate beschreibt Anhang B.2. Diese Dateien dienen als Eingabe für blastall (Abbildung 4.1). Dabei gibt es mehrere Möglichkeiten, dem Programm mitzuteilen, welche Sequenzen für den Vergleich mit einer Anfragesequenz benutzt werden sollen: 1. Auf der Kommandozeile werden mit der blastall -Option -d eine oder mehrere Datensammlungsnamen angegeben. Die Namen entsprechen denen der FASTA-Dateien, obwohl BLAST nur die daraus formatierten Dateien verwendet. Das Konzept zum Umgang mit mehreren Datensammlungen besteht darin, alle Sequenzen der übergebenen Datensammlungen zu nummerieren, beginnend beim Wert 0. Die Ordnung der Sequenzen entspricht der in den Datensammlungsdateien, die Ordnung der Datensammlungsdateien entspricht der Reihenfolge bei der Kommandozeilenoption -d. 2. Zur Erzeugung einer virtuellen Datensammlung kann eine Alias-Datei erzeugt werden, die mehrere reale Datensammlungen unter einem Namen zusammenfasst. Der Name der Datei ist <Aliasname>.nal für Nukleotid- bzw. <Aliasname>.pal für Proteindatensammlungen. Die Angabe von <Aliasname> bei der Option -d ist gleichbedeutend mit der Angabe der darin enthaltenen realen Datensammlungsnamen. Alias-Dateien können außerdem zwei spezielle Abschnitte enthalten: (a) Die Angabe einer Liste von Ordnungszahlen (OIDList) schränkt die BLASTSuche auf die Vergleichssequenzen mit den entsprechenden Ordnungszahlen ein. (b) Die Angabe einer Liste von GenInfo-Kennungen (GI-IDs) schränkt die BLASTSuche auf die Vergleichssequenzen mit den entsprechenden Kennungen ein. Auf die GI-IDs wird im folgenden Absatz eingegangen. 22 KAPITEL 4. ANALYSE DES BLAST-PROGRAMMCODES 3. Mit der Option -l wird eine Datei angegeben, die eine Liste von GenInfo-IDs umfasst (GI-Datei ). Die BLAST-Suche beschränkt sich dann auf die Vergleichssequenzen mit den entsprechenden GI-IDs. Die Angabe mehrerer Datensammlungen kann auf mehreren Ebenen erfolgen, d.h. Realund Alias-Datensammlungen können gemischt bei -d angegeben werden. Außerdem können Aliasdateien andere Aliasdatensammlungen enthalten. Die GenInfo-IDs entstammen einer Datenbank am NCBI, der ”ID”-Datenbank. Sie wird immer dann aktualisiert, wenn beim NCBI eine neue Sequenz von einer der großen Sequenzdatenbanken (z.B. PIR, SWISSPROT ) registriert wird. Diese Sequenzen besitzen gemäß einer Nomenklatur eine Kennung, die unter anderem ihre Datenbank-Herkunft angibt. Die GenInfo-Kennungen (GI-IDs) des NCBI dienen dazu, auf alle Sequenzen über eine einheitliche Nummerierung zuzugreifen. Der Aufbau einer GI-Kennung ist der folgende: gi|<ID in der ID-Datenbank> Die Kennung einer Sequenz in einer FASTA-Datei ist meist eine Aneinanderkettung der GI-ID und der Kennung, die vom ”Erzeuger” der Sequenz (z.B. PIR) vergeben wurde. 4.3.2 Die interne Kodierung von Sequenzen Sowohl die Anfragesequenz als auch die Vergleichssequenzen werden für die Benutzung im BLAST-Algorithmus kodiert. Da die Kodierung von Aminosäure- und Nukleotidsequenzen unterschiedlich ist, werden sie in getrennten Unterabschnitten behandelt. 4.3.2.1 Die Kodierung von Nukleotidsequenzen Nukleotid-Sequenzen werden in einer FASTA-Datei ebenfalls mit einem Buchstaben (siehe Tabelle 2.2) pro Nukleotid dargestellt. Für diese Sequenzen werden zwei Kodierungen verwendet. Die erste Kodierung orientiert sich an der Überlegung, dass fast alle Residuen in einer Sequenz Elementarresiduen sind. Die meisten Sequenzen enthalten demnach nur die eindeutigen Nukleotide A, C, G und T. Für solche Sequenzen genügt die NCBI2naKodierung (Tabelle 4.1). Die Kodierung verwendet 2 bit pro Residuum, somit können vier Residuen in einem Byte kodiert werden. Die Residuen aus der Sequenz werden zuerst in den signifikantesten Bits gespeichert, d.h. das erste Residuum einer Sequenz wird in den Bits 7 und 8 des ersten Bytes abgelegt und so fort. Für das Ende der Sequenz werden zwei Fälle unterschieden: 1. Falls das letzte Byte vollständig belegt wird, bedeutet dies, dass die Länge der ursprünglichen Sequenz ein Vielfaches von vier ist. In diesem Fall wird ein Null-Byte an die kodierte Sequenz angehängt. 2. Falls das letzte Byte unvollständig belegt wird, bedeutet dies, dass die Länge der ursprünglichen Sequenz nicht durch vier teilbar ist. Weiterhin hat das die Auswirkung, dass die Bits 1 und 0 des letzten Byte in jedem Fall unbelegt sind. In diesen Bits wird deshalb die Anzahl der Residuen in dem Byte eingetragen, also der Rest der Division der Länge durch vier (ein Wert zwischen 1 und 3). Damit kann die tatsächliche 23 KAPITEL 4. ANALYSE DES BLAST-PROGRAMMCODES Symbol A C G T NCBI2na-Code 0 1 2 3 Name Adenin Cytosin Guanin Thymin/Uracil Tabelle 4.1: NCBI2na-Kodierung von Nukleotiden Symbol A C M G R S V T W Y H K D B N/X NCBI4na-Code 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 BLASTna-Code 15 0 1 6 2 4 9 13 3 8 5 12 7 11 10 14 Tabelle 4.2: Mehrdeutigkeitskodierungen NCBI4na und BLASTna von Nukleotiden Länge der Sequenz aus diesem Byte und der Länge der kodierten Sequenz determiniert werden. Da die meisten Sequenzen nur Elementarresiduen enthalten, können sie mit einem Viertel des ursprünglichen Speicherbedarfs gespeichert werden. Der BLAST-Algorithmus ist auf diese Kodierung abgestimmt, benötigt also weniger Zeit als ein vergleichbarer Algorithmus auf der Basis von ”Ein-Residuum-Bytes”. Die NCBI2na-Kodierung hat allerdings den Nachteil, dass Mehrdeutigkeitsresiduen nicht eindeutig dargestellt werden können. Sie werden durch Zufallswerte im Bereich {0, . . . , 3} repräsentiert. Sequenzen mit Mehrdeutigkeitsresiduen müssen deshalb eine zusätzliche Kodierung erfahren. Bei dieser zweiten Kodierung werden zusammenhängende, gleichartige Residuen (also Ketten gleicher Buchstaben) gemeinsam in einem 4-Byte-Block kodiert. Die einzelnen Bits haben folgende Bedeutung: 31 · · · 28 Code 27 · · · 24 Anzahl 23 ··· Position in der Sequenz 0 Als Code für die entsprechenden Mehrdeutigkeitsresiduen wird NCBI4na aus Tabelle 4.2 verwendet. Die beiden Sequenzen werden im BLAST-Algorithmus nacheinander verwendet: Zunächst wird mittels der NCBI2na-Kodierung aligniert, im Fall von Mehrdeutigkeiten wird die Alignierung mittels NCBI4na und BLASTna neu berechnet (”reevaluiert”). 24 KAPITEL 4. ANALYSE DES BLAST-PROGRAMMCODES Symbol A B C D E F G H I K L M N P Q R S T V W X Y Z U * Wert 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 Name Lücke Alanin Asparaginsäure oder Asparagin Cystein Asparaginsäure Glutaminsäure Phenylalanin Glycin Histidin Isoleucin Lysin Leucin Methionin Asparagin Prolin Glutamin Arginin Serin Threonin Valin Tryptophan nicht festgelegtes oder atypisches Residuum Tyrosin Glutaminsäure oder Glutamin Selenocystein1 Ende einer Sequenz Tabelle 4.3: NCBIstdaa-Kodierung von Aminosäuren 4.3.2.2 Die Kodierung von Proteinsequenzen Proteinsequenzen sind im FASTA-Format als Buchstabenfolgen gespeichert. Würde man mittels des dort verwendeten Alphabets (siehe Tabelle 2.3) den Wert eines Residuenpaares berechnen, müsste man als Indizes für die Substitutionsmatrix die ASCII-Werte der beiden Symbole verwenden. Zur Verkleinerung der Matrix und zur Vereinfachung von deren Initialisierung wurde die NCBIstdaa-Kodierung (Tabelle 4.3) eingeführt, die jedem Symbol einen Wert im Bereich {0, . . . , 25} zuordnet. Der Speicherbedarf einer Sequenz bleibt durch die Kodierung unverändert, da jedes Residuum auch nach der Kodierung ein Byte belegt. 4.3.3 Dateischnittstellenfunktionen Das Modul readdb wurde bereits als Dateischnittstelle von BLAST identifiziert. In diesem Abschnitt werden vor allem diejenigen Funktionen beleuchtet, die im Rahmen einer Anpassung an relationale Datenbanken modifiziert werden müssen. Grundlage des Moduls ist die Datenstruktur ReadDBFILE. Sie speichert alle relevanten Informationen einer BLAST-Datensammlung. Falls mehrere Datensammlungen von 1 Selenocystein ist im ursprünglichen Alphabet nicht enthalten. Es wird in seltenen Fällen aus dem Basentriplett UGA kodiert, das eigentlich ein Stoppcodon ist [24]. 25 KAPITEL 4. ANALYSE DES BLAST-PROGRAMMCODES blastall durchsucht werden, so wird jede von ihnen durch eine eigene ReadDBFILE-Struktur repräsentiert. Die Strukturen werden als verkettete Liste gespeichert. In readdb wird das Konzept von Hauptspeicherdateien (memory-mapped files, im Folgenden kurz MMFs) verwendet. Der Zugriff auf Hauptspeicherdateien ist analog zu dem auf normale Dateien. Intern werden die Dateizugriffsfunktionen allerdings auf Zeigeroperationen im Hauptspeicher abgebildet. In readdb werden folgende Funktionen zum Zugriff auf MMFs zur Verfügung gestellt • Öffnen (NlmOpenMFILE()) eines MMF • Schließen (NlmCloseMFILE()) des MMF • Lesen (NlmReadMFILE()) von Daten aus dem MMF und gleichzeitiges Verschieben des Dateizeigers • Abfragen des Dateizeigers (NlmTellMFile()) • Setzen des Dateizeigers (NlmSeekInMFile()) Das Schreiben in MMFs ist nicht implementiert, da es nicht benötigt wird. Alle drei Dateien (Sequenz-, Kennungs- und Indexdatei) einer BLAST-Datensammlung werden als MMF verwendet. Die Sequenz- und die Kennungsdatei werden dabei ausschließlich über die Funktionen NlmReadMFile() und NlmTellMFile() angesteuert. Die Index-Datei wird hingegen ausgelesen und die in ihr enthaltenen Kennwerte in ReadDBFILE-Variablen gespeichert, ebenso die Index-Positionen im Hauptspeicher. Im Zusammenhang mit den MMFs müssen noch die Funktionen • ReadDBOpenMHdrAndSeqFiles() und • ReadDBCloseMHdrAndSeqFiles() erwähnt werden. Diese Funktionen öffnen bzw. schließen die Kennungs- und die Sequenzdatei durch Aufruf obiger MMF-Funktionen. Im Rahmen einer BLAST-Anpassung sollten die hier aufgeführten Funktionen nicht aufgerufen werden, da die entsprechenden Dateien durch die relationale Datenbank ersetzt werden. Die folgenden Funktionen rufen die bisher genannten Funktionen auf oder greifen auf die Indizes zu. Sie sind deshalb Kandidaten für eine Modifikation im Kontext der blastall -Anpassung: • readdb_new_internal() ist die zentrale Initialisierungsfunktion für die ReadDBFILEStruktur. Unter anderem öffnet sie die Datensammlungsdateien und liest Kennwerte und Indizes aus. • readdb_attach() dient der Vervielfältigung der ReadDBFILE-Datenstruktur. Unter anderem werden dabei die Verwaltungsstrukturen für die MMFs kopiert. • readdb_get_link() erhält als Argument die Ordnungszahl einer Sequenz. Die Funktion hat die Aufgabe, in der verketteten Liste der ReadDBFILE-Strukturen nach demjenigen Element (derjenigen Datensammlung) zu suchen, das die Sequenz mit der übergebenen Ordnungszahl enthält. 26 KAPITEL 4. ANALYSE DES BLAST-PROGRAMMCODES • readdb_destruct_element() gibt Speicherbereiche, die von ReadDBFILE-Elementen belegt werden, wieder frei. Dabei werden auch die MMFs geschlossen. • readdb_destruct() führt zunächst ReadDBCloseMHdrAndSeqFiles() aus und ruft dann für jedes Element der ReadDBFILE-Liste readdb_destruct_element() auf. • readdb_get_sequence() wird mit der Ordnungszahl einer Sequenz aufgerufen und gibt die (kodierte) Sequenz und deren unkodierte Länge zurück. Zur Rückgabe der Sequenz und zur Bestimmung von deren Länge wird auf den Index und das entsprechende MMF zugegriffen. Nukleotidsequenzen werden in der 2bit-Kodierung zurückgegeben. • readdb_get_sequence_length() erhält als Parameter die Ordnungszahl einer Sequenz. Die Funktion gibt die unkodierte Länge der Sequenz zurück. Dabei wird auf den Sequenz-Index zugegriffen. • readdb_get_ambchar() gibt die 4-Byte-Kodierung der Mehrdeutigkeitsresiduen einer Sequenz zurück. Zur Identifikation der Sequenz wird deren Ordnungszahl übergeben. Die Funktion wird nur für Nukleotidsequenzen verwendet. Besitzt die Sequenz keine Mehrdeutigkeitsresiduen, wird NULL zurückgegeben. • readdb_ambchar_present() wird mit der Ordnungszahl einer Sequenz aufgerufen und gibt Auskunft darüber, ob eine Sequenz Mehrdeutigkeitsresiduen enthält. In diesem Fall gibt sie TRUE zurück, im negativen Fall FALSE. • Die Funktion readdb_get_defline_ex() gibt die Kennung der Sequenz zurück, deren Ordnungszahl übergeben wird. Dazu wird auf den Kennungsindex zugegriffen. • readdb_get_header() hat eine ähnliche Funktion wie readdb_get_defline_ex(). Sie wird im Kontext von Alias-Dateien bzw. GI-Dateien (Option -l) verwendet und ist deshalb für die BLAST-Anpassung unkritisch, da im Rahmen einer relationalen Datenbank keine Alias-Datensammlungsdateien bzw. GI-Dateien verwendet werden. • ReadOIDList() und OIDListFree() werden im Fall einer Alias-Datensammlung verwendet. Die Funktionen sind deshalb unkritisch für die BLAST-Anpassung. • GetGisFromFile() (Modul blast.c) ruft die ReadDBOpen/CloseMHdrAndSeqFiles()Funktionen auf. GetGisFromFile() wird nur im Zusammenhang mit einer GI-Datei verwendet und ist deshalb bei der Anpassung nicht von Interesse. • do_the_blast_run() und BLASTSetUpSearchWithReadDbInternal() aus blast.c rufen ebenfalls ReadDBOpen/CloseMHdrAndSeqFiles() auf. Eine Reihe von readdb-Funktionen liest Kennwerte einer Datensammlung bzw. aller beteiligten Datensammlungen aus den Elementen der ReadDBFILE-Struktur. Diese Funktionen sind unkritisch, da die entsprechenden Variablen durch readdb_new_internal() gesetzt werden. Der Vollständigkeit halber werden die Kennwerte-Funktionen im Anhang B.2 aufgelistet. readdb-Funktionen, die in diesem Abschnitt nicht erwähnt werden, sind bei der Anpassung von blastall unkritisch in Bezug auf den MMF-Zugriff. 27 KAPITEL 4. ANALYSE DES BLAST-PROGRAMMCODES 4.4 Der Ablauf von blastall Die Verwendung der Dateischnittstelle ist in den Ablauf des gesamten Programms eingebettet. Dieser Abschnitt analysiert die wichtigsten Funktionen von blastall und geht der Fragestellung nach, in welcher Weise die Funktionen die Dateischnittstelle nutzen und welche Auswirkungen das auf die Anpassung von BLAST hat. Die dafür verwendeten Ablaufschemata einiger Funktionen sind in einem an die Sprache C angelehnten Pseudocode beschrieben. 4.4.1 Die Main()-Funktion Die Einstiegsfunktion des Programms blastall ist die Funktion Main(). Sie führt schematisch die folgenden Schritte aus: 1. Lesen der Aufrufparameter, Initialisierung einiger BLAST_OptionsBlk-Elemente 2. Öffnen der Eingabe- (Anfragesequenz) und der Ausgabe-Datei (BLAST-Report) 3. Initialisierung weiterer BLAST_OptionsBlk-Elemente mit Aufrufparameterwerten 4. Lesen und Dekodieren der Anfragesequenz 5. Ausgabe allgemeiner Informationen und Datensammlungskennwerte in die ReportDatei 6. Aufruf der BLAST-Hauptroutine BioseqBlastEngine() (Modul blastutl.c) 7. Ausgabe des BLAST-Reports (siehe Anhang B.3) in die Ausgabedatei 8. Freigabe dynamischer Variablen Zur Speicherung der Programmparameter sowie davon abgeleiteter Variablen wird eine Datenstruktur vom Typ BLAST_OptionsBlk verwendet. Sie dient zur Initialisierung der Funktion BioseqBlastEngine(), die den Einstiegspunkt in die konkrete BLAST-Implementierung bildet. 4.4.2 Die Funktion BioseqBlastEngineByLocEx() Die Funktion BioseqBlastEngineByLocEx() wird mittels folgender Aufrufkette erreicht: BioseqBlastEngine() ↓ BioseqBlastEngineByLoc() ↓ BioseqBlastEngineByLocEx() BioseqBlastEngineByLocEx() hat folgenden schematischen Ablauf: 1. Validierung der BLAST_OptionsBlk-Struktur. BLASTOptionValidateEx() überprüft, ob die Werte von Programmparametern und davon abgeleitete Werte gültig sind und zur verwendeten Programmvariante passen. 28 KAPITEL 4. ANALYSE DES BLAST-PROGRAMMCODES BioseqBlastEngineCore(searchBlk, optionsBlk) { if (PSI-BLAST) [...] [...] do_the_blast_run(searchBlk); if (BLASTN && GAPPED) { [...] for(i = 0; i < #hits; ++i) { length = readdb_get_sequence_ex(...,&sequence,...) seqalign = SumBlastGetGappedAlignmentWithTraceback(searchBlk,i,..., sequence,length); [...] } [seqaligns zu Liste verketten] } else if (GAPPED) { [...] for(i = 0; i < #hits; ++i) { seqalign = BlastGetGapAlgnTbckWithReaddb(search, index, ...); } [seqaligns zu Liste verketten] } else { if (PSI-BLAST) [...] else seqalign = GetSeqAlignForResultHitList(searchBlk,...); } return seqalign; } Abbildung 4.2: schematischer Ablauf von BioseqBlastEngineCore() 2. Initialisierung der BlastSearchBlk-Struktur. Diese Datenstruktur wird mit Hilfe der Funktion BLASTSetUpSearchByLocWithReadDbEx() vorbereitet. Zur Initialisierung gehören unter anderem der Aufruf von readdb_new_internal() sowie der BLAST-Vorberechnungsschritt zur Bestimmung der w-mere. Die w-mer-Bildung wird für Protein-Anfragesequenzen mit der Funktion BlastFindWords(), für NukleotidAnfragesequenzen mit BlastNtFindWords() durchgeführt. 3. Ausführung des eigentlichen Algorithmus. Die dafür verantwortliche Funktion ist BioseqBlastEngineCore() (siehe folgender Abschnitt). Die Datenstruktur BlastSearchBlk umfasst sämtliche Parameter, die für den Algorithmus benötigt werden. In der Struktur werden auch die Ergebnisse zwischengespeichert. 4.4.3 Die Funktion BioseqBlastEngineCore() Die Funktion BioseqBlastEngineCore() umfasst die Aufrufe all jener Funktionen, die den eigentlichen Algorithmus ausführen. Der im Kontext von blastall relevante Teil der 29 KAPITEL 4. ANALYSE DES BLAST-PROGRAMMCODES Funktion ist in Abbildung 4.2 dargestellt. Der Ablauf besteht aus den folgenden Schritten: 1. Die Routine do_the_blast_run() führt den Schritt der lückenfreien Alignierung der Anfragesequenz mit allen Vergleichssequenzen durch. Die Funktion nutzt die Fähigkeit des multithreading zur parallelen Ausführung des Algorithmus auf mehreren Sequenzen. Der Aufbau von do_the_blast_run() wird im Abschnitt 4.5 im Detail erläutert. 2. Nach dem Aufruf von do_the_blast_run() sind die HSPs der lückenfreien Alignierungen in der BlastSearchBlk-Struktur abgelegt. Wurde das Programm als Gapped BLAST gestartet (Programmparameter -g), so wird an dieser Stelle für jedes HSP eine der folgenden Funktionen zur Bildung einer lückenbehafteten Alignierung aufgerufen: • SumBlastGetGappedAlignmentWithTraceback() für die Variante blastn • BlastGetGapAlgnTbckWithReaddb() für alle anderen Programmvarianten Das Ergebnis beider Funktion ist eine verkettete Liste von SeqAlign-Elementen. 3. Ohne Angabe der -g-Option wird GetSeqAlignForResultHitList() aufgerufen, das die Zwischenergebnisse aus der BlastSearchBlk-Struktur in eine verkettete Liste von SeqAlign-Elementen konvertiert. Die Datenstruktur SeqAlign dient der Speicherung von Alignierungen jeden Typs. Da sie bei der Anpassung von Bedeutung ist, wird sie im Abschnitt 4.6 detailliert beschrieben. 4.4.4 Die Funktion do blast search() Zur Funktion do_blast_search() gelangt man mittels folgender Aufrufkette: BioseqBlastEngineCore() ↓ do_the_blast_run() ↓ do_blast_search() do_blast_search() wird entweder direkt oder durch den Start eines thread s aufgerufen. Die Funktion ermittelt lückenfreie Alignierungen der Anfragesequenz mit den Vergleichssequenzen. Zum schematischen Ablauf der Funktion (siehe Abbildung 4.3) gibt es die folgenden Bemerkungen: • Die Funktion BlastGetDbChunk() liefert den von der Funktion do_blast_search() zu bearbeitenden Bereich der Datensammlung. startID ist die Ordnungszahl der ersten zu bearbeitenden Sequenz, und stopID zeigt hinter die letzte zu bearbeitende Sequenz. Die Bedeutung dieser Funktion liegt in der Verteilung von Datensammlungschunks (engl.: chunk – Stück, Klotz) für den Fall, dass mehrere threads verwendet werden. Im Normalfall gibt BlastGetDbChunk() die gesamte Datensammlung als Bereich zurück. 30 KAPITEL 4. ANALYSE DES BLAST-PROGRAMMCODES do_blast_search(searchBlk) { while(BlastGetDbChunk(searchBlk, &startID, &stopID)) { [...] for(index = startID; index < stopID; ++index) { BLASTPerformSearchWithReadDb(searchBlk, index); [...] BlastReapHitListByEvalue(searchBlk); BlastReevaluateWithAmbiguities(searchBlk, index); BlastSaveCurrentHitlist(searchBlk); } } [...] } Abbildung 4.3: schematischer Ablauf von do blast search() • Die for-Schleife iteriert über alle Sequenzen im durch startID und stopID festgelegten Bereich. Folglich werden immer zusammenhängende Stücke der Datensammlung bearbeitet. • Die Funktion BLASTPerformSearchWithReadDb() liest die Sequenz mit der Ordnungszahl i ein und führt den BLAST-Algorithmus aus. • BlastReapHitListByEvalue() entfernt Alignierungen zwischen Vergleichs- und Anfragesequenz aus der Ergebnisstruktur, falls deren berechneter Erwartungswert größer ist als der dem Programm übergebene Schwellwert (Programmparameter -e). • Die Funktion BlastReevaluateWithAmbiguities() betrachtet noch einmal jene Sequenzen gesondert, die Mehrdeutigkeitsresiduen enthalten, da deren Alignierungen wegen der Zufallskodierung in NCBI2na möglicherweise fehlerhafte Werte liefern. Die Funktion ist nur für Nukleotidsequenzen relevant. • BlastSaveCurrentHitlist() integriert die aus der Sequenz gewonnene Liste von Alignierungen (hitlist) in die HSP-Liste aller Sequenzen. Die hitlist ist eine Zwischenstruktur und darf nicht mit der Datenstruktur SeqAlign verwechselt werden. Das Pendant von do_blast_search() ist do_gapped_blast_search(). Diese Funktion wird auf die gleiche Art und Weise aufgerufen. Sie führt keine lückenbehaftete Alignierung durch (obwohl der Name das suggeriert), sondern bildet lediglich die HSPs für eine anschließende Ausrichtung mit Lücken. Die Funktionen do_blast_search() und do_gapped_blast_search() haben einen ähnlichen Aufbau. Deswegen wird letztere nicht gesondert erläutert. In den folgenden Abschnitten werden diese Funktionen unter dem Namen ..._blast_search() gemeinsam betrachtet, sofern nicht nur eine von ihnen gemeint ist. 31 KAPITEL 4. ANALYSE DES BLAST-PROGRAMMCODES BLASTPerformSearchWithReadDb(searchBlk,seqNum) { length = readdb_get_sequence(searchBlk->rdfp, seqNum, &seq); [...] BLASTPerformSearch(searchBlk, length, seq); } Abbildung 4.4: schematischer Ablauf von BLASTPerformSearchWithReadDb() BlastReevaluateWithAmbiguities(searchBlk,seqNum) { if (searchBlk->prog_number == {BLASTP || BLASTX} ) exit; if (GAPPED BLAST) exit; if (! HITS FOUND) exit; if (readdb_ambchar_present(searchBlk->rdfp, seqNum)) == FALSE) exit; [...] bsp = readdb_get_bioseq(searchBlk->rdfp, seqNum); [Reevaluation...] } Abbildung 4.5: schematischer Ablauf von BlastReevaluateWithAmbiguities() 4.4.5 Die Funktion BLASTPerformSearchWithReadDb() Von ..._blast_search() wird die Funktion BLASTPerformSearchWithReadDb() aufgerufen. Diese liest mit Hilfe von readdb_get_sequence() die Sequenz mit der übergebenen Ordnungszahl und übergibt diese der Funktion BLASTPerformSearch(), die die Sequenz mit der Anfragesequenz aligniert. Das Ergebnis wird in BlastSearchBlk-Variablen gespeichert. Abbildung 4.4 stellt den schematischen Aufbau der Funktion dar. 4.4.6 Die Funktion BlastReevaluateWithAmbiguities() BlastReevaluateWithAmbiguities() ist für die Alignierung von Nukleotidsequenzen von Bedeutung, die Mehrdeutigkeitsresiduen enthalten. In Abschnitt 4.3.2.1 wurde bereits darauf hingewiesen, dass Mehrdeutigkeiten durch die NCBI2na-Kodierung einen Zufallswert erhalten. Aus diesem Grund werden Sequenzen mit Mehrdeutigkeiten durch die Funktion BlastReevaluateWithAmbiguities() neu aligniert. 32 KAPITEL 4. ANALYSE DES BLAST-PROGRAMMCODES Abbildung 4.5 zeigt das Ablaufschema von BlastReevaluateWithAmbiguities(). Nach dem Test auf Mehrdeutigkeitsresiduen in der Sequenz (readdb_ambchar_present()) wird die Funktion readdb_get_bioseq() ausgeführt, die wiederum zu den folgenden readdbFunktionen verzweigt: • readdb_get_descriptor() • readdb_get_sequence() • readdb_get_ambchar() Die sich anschließende Reevaluation ruft keine weiteren Funktionen der Dateischnittstelle auf. Eine Besonderheit der Reevaluation ist, dass das Nukleotid-Bewertungsschema, das nur zwischen matches und mismatches unterscheidet, hier nicht angewendet werden kann. Da ein Mehrdeutigkeitsresiduum für mehrere Elementarresiduen stehen kann, muss eine Substitutionsmatrix verwendet werden. Die Substitutionsmatrix wird während der Initialisierung aus den Werten für match und mismatch berechnet. Für die Indizierung der Matrix wird die Kodierung BLASTna (siehe Tabelle 4.2) verwendet. Deshalb stellt das Programm die Konvertierungsfunktionen ncbi4na_to_blastna[] und blastna_to_ncbi4na[] zur Verfügung, die die beiden Kodierungen NCBI4na und BLASTna ineinander umwandeln. Die Konvertierungsfunktionen sind als Feldvariablen realisiert. 4.5 Analyse des multithreading in BLAST Das Programm blastall unterstützt sogenanntes multithreading. Das bedeutet, dass mehrere Instanzen der gleichen Funktion (threads) parallel zueinander abgearbeitet werden können. Damit kann das Vorhandensein mehrerer Prozessoren auf einer SMP -Maschine (Symmetric Multiprocessing) ausgenutzt werden. Die Verwendung von mehr als einem Prozessor wird bei blastall durch den Programmparameter -a eingestellt. Die Anzahl der Prozessoren wird in diesem Abschnitt mit n bezeichnet. Grundlage der Parallelisierung ist die Funktion do_the_blast_run(). Die Funktion führt folgende Schritte aus (siehe Abbildung 4.6): 1. Zur Verwendung gemeinsamer Ressourcen durch die threads werden Mutexe initialisiert (NlmMutexInit()). Auf das Mutex-Konzept wird im folgenden Absatz näher eingegangen. 2. Zur Initialisierung des multithreading wird die BlastSearchBlk-Struktur n − 1 mal dupliziert (BlastSearchBlkDuplicate()). Jeder thread arbeitet damit auf seiner eigenen Struktur. Beim Kopieren der Struktur werden die Zeiger auf die Indizes und die MMFs mit kopiert. Die Indizes und die MMFs stellen somit geteilte Ressourcen dar, die durch Mutexe verwaltet werden. 3. Die Funktion NlmThreadCreateEx() erzeugt jeweils einen thread, dem eine der beiden ..._blast_search()-Funktionen und eine BlastSearchBlk-Struktur zugeordnet wird. Die ..._blast_search()-Funktion wird dann im Kontext des threads mit der BlastSearchBlk-Struktur als Parameter aufgerufen. Der thread ist beendet, 33 KAPITEL 4. ANALYSE DES BLAST-PROGRAMMCODES wenn die ihm zugeordnete Funktion beendet ist. Die threads laufen parallel sowohl zueinander als auch zur Funktion do_the_blast_run(). 4. NlmThreadJoin() ist die Synchronisationsfunktion für die threads. Die Funktion kehrt erst zu do_the_blast_run() zurück, wenn alle threads beendet sind. 5. Die am Anfang initialisierten Mutex-Variablen werden wieder freigegeben. In der Aufzählung wurde bereits das Mutex-Konzept erwähnt. Mutexe sind Synchronisationsobjekte. Sie dienen zur Zugriffssteuerung auf Ressourcen, die von mehreren threads gemeinsam genutzt werden. Der Einsatz von Mutexen verhindert den gleichzeitigen Zugriff zweier threads auf die gleiche Ressource. Mutexe haben folgende Eigenschaften: 1. Ein Mutex ist nicht an eine Ressource gebunden, sondern wird über Anweisungen an beliebiger Stelle im Quelltext reserviert (lock ) und freigegeben (unlock ). Die Logik der Mutex-Reservierung und -Freigabe obliegt dem thread und nicht einem zentralen Mechanismus. 2. Ein thread erhält eine Mutex-Reservierung, wenn kein anderer thread den Mutex reserviert hat. 3. Schafft ein thread es, einen Mutex zu reservieren, bleibt dieser solange reserviert, bis der gleiche thread ihn wieder freigibt. Zwischen den Anweisungen für Reservierung und Freigabe sollten die Anweisungen zur Bearbeitung der zugriffskritischen Ressource stehen. 4. Versucht thread 1, einen Mutex zu reservieren, der durch thread 2 reserviert ist, so wartet thread 1 solange mit seiner Ausführung, bis er den Mutex selbst reserviert hat. Die von BLAST verwendeten Mutexe gehören zum BlastSearchBlk-Element thr_info, das eine Struktur zur Verwaltung der multithreading-relevanten Daten bereithält. Folgende Mutexvariablen werden mit thr_info verwaltet: • db_mutex Dieser Mutex verwaltet die Zuteilung der Datensammlungsabschnitte. Bei der Beschreibung der Funktion do_blast_search() wurde bereits BlastGetDbChunk() erläutert. Diese Funktion teilt die Datensammlung auf und weist jedem thread einen Bereich zum Bearbeiten zu. BlastGetDbChunk() führt in einer allen threads gemeinsamen Variable thr_info->db_chunk_last Buch darüber, welcher Datensammlungsbereich als nächstes berechnet werden muss. Der Zugriff auf diese Variable wird mit dem Mutex gesteuert. • results_mutex Die Funktion BlastSaveCurrentHitlist() verwendet diesen Mutex, um die Alignierungen verschiedener Vergleichssequenzen in einer gemeinsamen Datenstruktur (searchBlk->result_struct) abzulegen. • ambiguities_mutex BlastReevaluateWithAmbiguities() benötigt diesen Mutex zum Zugriff auf die Mehrdeutigkeitskodierungen der Sequenzen. 34 KAPITEL 4. ANALYSE DES BLAST-PROGRAMMCODES do_the_blast_run(searchBlk) { [...] if (NlmThreadsAvailable() && searchBlk->pbp->process_num > 1) { /*** 1. Fall: mehrere Threads ***/ [...] /*** INITIALISIERUNG Mutexe ***/ NlmMutexInit(searchBlk->thr_info->db_mutex); [ ...thr_info->results_mutex, ...thr_info->ambiguities_mutex ] /*** DUPLIKATION der SearchBlk-Struktur ***/ array = malloc((searchBlk->pbp->process_num)*sizeof(BlastSearchBlkPtr)); array[0] = searchBlk; for (index=1; index<searchBlk->pbp->process_num; index++) array[index] = BlastSearchBlkDuplicate(searchBlk); /*** ERZEUGEN von n Threads ***/ thread_array = malloc((searchBlk->pbp->process_num)*sizeof(TNlmThread)); for (index=0; index<searchBlk->pbp->process_num; index++) { if (GAPPED BLAST) thread_array[index] = NlmThreadCreateEx(do_gapped_blast_search, array[index], [...] ); else thread_array[index] = NlmThreadCreateEx(do_blast_search, array[index], [...] ); } /*** WARTEN, bis alle Threads beendet sind ***/ for (index=0; index<searchBlk->pbp->process_num; index++) NlmThreadJoin(thread_array[index], [...] ); for (index=1; index<searchBlk->pbp->process_num; index++) { [SAMMELN statistischer Werte aus den verschiedenen SearchBlk-Variablen...] array[index] = BlastSearchBlkDestruct(array[index]); } free(array); free(thread_array); NlmMutexDestroy(searchBlk->thr_info->db_mutex); [ ...thr_info->results_mutex, ...thr_info->ambiguities_mutex ] } else /*** 2. Fall: EIN Thread ***/ if (GAPPED BLAST) do_gapped_blast_search(searchBlk); else do_blast_search(searchBlk); [...] } Abbildung 4.6: Schematischer Ablauf von do the blast run(). Die Variable array ist ein Feld von SearchBlk-Variablen, thread array ist ein Feld von threads. 35 KAPITEL 4. ANALYSE DES BLAST-PROGRAMMCODES typedef struct seqalign { char type, segtype; short int dim; Score *score; void *segs; struct seqalign *next; SeqLoc *bounds; SeqId *master; SeqAlignIndex *saip; GatherIndex idx; short int alignID; } SeqAlign, *SeqAlignPtr; Abbildung 4.7: Aufbau der Datenstruktur SeqAlign 4.6 Die Datenstruktur SeqAlign Im bisherigen Verlauf der Analyse wurde bereits der Datentyp SeqAlign angeführt. Mit SeqAlign werden verschiedene Alignierungsarten einheitlich dargestellt. Abbildung 4.7 zeigt den Aufbau des Datentyps als C-Struktur. Im Hinblick auf die Anpassung von BLAST sind die folgenden Elemente von Interesse: • next Die Alignierungen der Sequenzen sind in einer verketteten Liste gespeichert. Die Variable next zeigt auf das nachfolgende Element der Kette. • type Man unterscheidet vier Grundtypen von SeqAlign’s, wovon im Rahmen von blastall zwei von Interesse sind: 1. Im Fall type == 2 (SAT_DIAGS) sind alle Alignierungen einer Vergleichssequenz in einem einzigen SeqAlign-Element abgelegt. Die Alignierungen sind in einer Liste gespeichert, die mit dem Element segs beginnt. 2. Der Wert type == 3 (SAT_PARTIAL) steht dafür, dass ein SeqAlign-Element genau eine Alignierung enthält. Weitere Alignierungen der Vergleichssequenz findet man in den nachfolgenden SeqAlign-Elementen. • segtype, segs Es werden sechs Möglichkeiten zur Speicherung von Alignierungen unterschieden. Im blastall -Kontext sind davon drei von Interesse. Der Wert der Variablen segtype bestimmt dabei, von welchem Datentyp das Element segs ist: 1. Im Fall segtype == 1 (SAS_DENDIAG) zeigt das Element segs auf eine Struktur vom Typ DenseDiag. Dieser Typ von Alignierung wird von den lückenfreien Varianten von blastn und blastp verwendet. 2. Der Fall segtype == 2 (SAS_DENSEG) wird von den lückenbehafteten Varianten von blastn und blastp verwendet. segs zeigt dabei auf eine DenseSeg-Variable. 36 KAPITEL 4. ANALYSE DES BLAST-PROGRAMMCODES typedef struct stdseg { short int dim; SeqId *ids; SeqLoc *loc; Score *scores; struct stdseg *next; } StdSeg, *StdSegPtr; Abbildung 4.8: Aufbau der Datenstruktur StdSeg 3. Die BLAST-Translationsvarianten blastx, tblastn und tblastx verwenden den Fall segtype == 3 (SAS_STD). segs ist hier ein Zeiger auf eine StdSeg-Variable. • dim Dieses Element enthält die Anzahl der alignierten Sequenzen. BLAST bildet immer Alignierungen von jeweils zwei Sequenzen (dim == 2). • score Für den Fall type == 3 ist in dieser Variablen die Bewertung der Alignierung gespeichert. 4.6.1 StdSeg-Alignierungen Die Datenstruktur StdSeg ist unter den oben genannten diejenige mit der größten Komplexität. Deshalb wird sie hier stellvertretend für die anderen beiden Alignierungstypen im Detail erläutert. Der Alignierungstyp StdSeg wird von den BLAST-Varianten blastx, tblastn und tblastx für die Speicherung von sowohl lückenfreien als auch lückenbehafteten Alignierungen verwendet. Der Aufbau von StdSeg kann Abbildung 4.8 entnommen werden. Die Bedeutung einiger Elemente von StdSeg hängt davon ab, welchen Wert die type-Variable des zugehörigen SeqAlign-Elements hat. Im Fall type == SAT_DIAGS speichert StdSeg eine lückenfreie Alignierung der beteiligten Sequenzen. Die Elemente haben dann folgende Bedeutung: • dim Das Element dim hat die gleiche Bedeutung wie das gleichnamige SeqAlign-Element. Im Fall von BLAST hat es immer den Wert 2. Die Bedeutung von dim ist unabhängig vom type-Wert. • scores Dieses Element beinhaltet die Bewertung der Alignierung. • loc Die Variable ist ein Zeiger auf eine Liste mit zwei Elementen vom Typ SeqLoc. Die beiden Elemente beschreiben die Alignierung. Die Bedeutung der SeqLoc-Struktur wird nach der type-Fallunterscheidung im Detail erläutert. • next Der next-Zeiger verweist auf die nächste Alignierung der gleichen Sequenz. 37 KAPITEL 4. ANALYSE DES BLAST-PROGRAMMCODES Abbildung 4.9: Die beiden Möglichkeiten, StdSeg-Alignierungen zu speichern. Der obere Fall stellt die Verknüpfungsstruktur für lückenfreie Alignierungen dar, die untere Abbildung die für lückenbehaftete Alignierungen. Zu beachten ist dabei, an welcher Stelle die Bewertung der Alignierung jeweils abgelegt ist. Liegt der Fall type == SAT_PARTIAL vor, so speichert StdSeg nur ein Segment einer lückenbehafteten Alignierung. Dabei ändert sich auch die Bedeutung der Elemente scores, loc und next: • Das Element scores wird in diesem Fall nicht benutzt, da die Bewertung der Alignierung im SeqAlign-Element score zu finden ist. • loc ist auch in diesem Fall eine Liste zweier SeqLoc-Elemente, allerdings speichern sie hier ein Segment einer lückenbehafteten Alignierung. • next zeigt auf das nächste Segment der Alignierung. Für den Fall der lückenbehafteten Alignierung muss der Begriff Segment einer Alignierung geklärt werden. Ein Segment ist ein zusammenhängender Bereich von Residuenpaaren oder Residuum-Lücken-Paaren mit genau einer der folgenden Eigenschaften: 1. Beide Sequenzen besitzen ausschließlich Residuen in dem Bereich. 38 KAPITEL 4. ANALYSE DES BLAST-PROGRAMMCODES typedef struct seqint { long int from, to; char strand; SeqId *id; IntFuzz *if_from, *if_to; } SeqInt, PNTR SeqIntPtr; typedef int (*Nlm_FnPtr)(void); typedef union dataval { void *ptrvalue; long int *intvalue; double *realvalue; short int *boolvalue; Nlm_FnPtr funcvalue; long long *bigintvalue; } DataVal, *DataValPtr; typedef struct seqloc { char choice; char extended; DataVal data; struct seqloc *next; } SeqLoc, PNTR SeqLocPtr; Abbildung 4.10: Aufbau der Datenstrukturen SeqInt, DataVal und SeqLoc 2. Die Anfragesequenz (Sequenz 0) enthält eine Lücke über den gesamten Bereich. 3. Die Vergleichssequenz (Sequenz 1) enthält eine Lücke über den gesamten Bereich. Der Unterschied zwischen den beiden type-Fällen wird in Abbildung 4.9 am Beispiel des Alignierungstyps StdSeg dargestellt. Zur Speicherung eines Segments bzw. einer lückenfreien Alignierung wird der loc-Zeiger verwendet. Er zeigt auf das erste von zwei Variablen vom Typ SeqLoc. Der Aufbau der Datenstruktur ist in Abbildung 4.10 dargestellt. Die Elemente von SeqLoc haben folgende Bedeutung: • Mit Hilfe des next-Zeigers verweist das erste Element der Liste auf das zweite Element. Für das zweite Element ist dieser Wert nicht gesetzt. Die beiden Elemente repräsentieren das Segment bzw. die Alignierung für die Anfrage- und die Vergleichssequenz. Folgende Darstellung soll dies verdeutlichen: StdSeg.loc → Anfragesequenz-SeqLoc .next → Vergleichssequenz-SeqLoc • Die Elemente choice und data dienen der Speicherung von Daten dynamischer Da39 KAPITEL 4. ANALYSE DES BLAST-PROGRAMMCODES typedef struct score { ObjectId *id; char choice; DataVal value; struct score *next; } Score, *ScorePtr; Abbildung 4.11: Aufbau der Datenstruktur Score tentypen. choice legt dabei fest, welche Art von Daten in data gespeichert werden. Für die Speicherung von Alignierungen werden zwei choice’s unterschieden: 1. choice == SEQLOC_INT bedeutet, dass data.ptrvalue auf eine Variable vom Typ SeqInt zeigt. 2. choice == SEQLOC_EMPTY steht dafür, dass die entsprechende Sequenz im aktuellen Segment eine Lücke hat. Die Länge der Lücke muss der anderen Sequenz entnommen werden. Dieser Fall tritt nur bei lückenbehafteten Alignierungen auf. Die SeqInt-Struktur beinhaltet die konkreten Informationen, die eine Sequenz zu einer Alignierung oder einem Segment beisteuert. Von Interesse sind in diesem Zusammenhang die folgenden Elemente: • from und to bezeichnen den Anfangs- und End-Offset der Alignierung in der Sequenz. • strand bezeichnet die Leserichtung der Sequenz. Bei Proteinsequenzen steht hier immer eine 0, für Nukleotidsequenzen existieren zwei Leserichtungen. Bei lückenbehafteten Alignierungen müssen nur die Werte der SeqLoc’s der ersten StdSeg-Struktur betrachtet werden, da die Leserichtung der Sequenzen zwischen den Segmenten nicht wechselt. Es sei erwähnt, dass mit Hilfe dieser Struktur die Alignierungen aller BLAST-Varianten dargestellt werden können. Die Programme blastn und blastp nutzen allerdings die einfacher strukturierten Typen DenseDiag und DenseSeg. Deren Aufbau kann Anhang C.1 entnommen werden. 4.6.2 Die Speicherung von Alignment-Bewertungen In den Strukturen SeqAlign und StdSeg ist neben den Alignierungen auch deren Bewertung gespeichert. Da eine Alignierung mehrere Bewertungsmaße haben kann, müssen diese in einer dynamischen Struktur abgelegt werden. Die Elemente score bzw. scores der oben genannten Strukturen zeigen auf eine verkettete Liste verschiedener Bewertungsmaße, die jede in einer Score-Datenstruktur abgelegt sind. Deren Aufbau kann Abbildung 4.11 entnommen werden. Die Elemente haben folgende Bedeutung: • Das Element id ist vom Typ ObjectId, das ein Datenelement str enthält. Dieses speichert den Namen der Bewertung. 40 KAPITEL 4. ANALYSE DES BLAST-PROGRAMMCODES • Die Elemente choice und value stehen im gleichen Zusammenhang wie choice und data in der SeqLoc-Datenstruktur. Im Zusammenhang mit Bewertungen gibt es zwei sinnvolle choice-Werte. Hat choice den Wert 1, so ist in value der Ganzzahlwert (long int) gesetzt, im Fall choice == 2 beinhaltet value einen Fließkommawert (double). • Der next-Zeiger verweist auf weitere Bewertungsmaße. 41 Kapitel 5 Verwendete Datenbankkonzepte Ziel dieser Arbeit ist es, BLAST im Rahmen eines relationalen Datenbanksystems zu verwenden. Voraussetzung dafür ist eine relational modellierte Genomdatenbank. Die Verknüpfung von BLAST mit einer relationalen Datenbank wird mit dem Datenbanksystem DB2 UDB realisiert. DB2 UDB (Universal Database) ist ein objektrelationales Datenbanksystem und wurde vom Unternehmen IBM entwickelt. Die im Kontext dieser Diplomarbeit verwendete Version des Datenbanksystems ist DB2 Version 7.1 Enterprise Extended Edition für das Betriebssystem Solaris. Das Kapitel fasst die Konzepte zusammen, die aus Datenbanksicht für die Anpassung von BLAST benötigt werden. 5.1 Datenmodellierung der Biosequenzen Ein relationales Datenbanksystem zeichnet sich dadurch aus, dass die darin zu speichernden Daten relational modelliert [9] werden müssen. Dieser Abschnitt geht auf den Teil des Datenmodells ein, der die Biosequenzen beschreibt. Das Modell (Abbildung 5.1) verwendet dafür die folgenden fünf Relationen: 1. Die Relation BioSequence fasst alle Sequenzen zusammen. Jede in der Datenbank gespeicherte Sequenz erhält eine Kennung. Die Kennungen der Sequenzen sind im Attribut BIOSEQID vom Typ BIGINT abgelegt. 2. NASequence fasst alle Nukleotidsequenzen zusammen. Die Relation umfasst die Attribute NASEQID vom Typ BIGINT und SEQTEXT vom Typ LONG VARCHAR. NASEQID ist zugleich Fremdschlüssel auf das Attribut BIOSEQID aus der Relation BioSequence. SEQTEXT enthält die Sequenzen als Zeichenketten. Die Zeichenketten verwenden das Nukleotid-Alphabet [10]. 3. Protein umfasst die Aminosäuresequenzen. Die Relation besteht aus den Attributen PROTEINID vom Typ BIGINT und SEQTEXT vom Typ LONG VARCHAR. PROTEINID ist zugleich Fremdschlüssel auf das Attribut BIOSEQID aus BioSequence. SEQTEXT enthält die Aminosäuresequenzen als Zeichenketten. Die Zeichenketten verwenden das Alphabet der Aminosäuren [20]. 42 KAPITEL 5. VERWENDETE DATENBANKKONZEPTE Abbildung 5.1: Ausschnitt des Genom-Datenmodells. Die unterstrichenen Attribute sind Primärschlüssel, die kursiv gesetzten sind Fremdschlüssel. 4. In der Relation DNA sind die DNA-Sequenzen abgelegt. Sie werden über das einzige Attribut DNASEQID vom Typ BIGINT identifiziert. Dieses ist zugleich Fremdschlüssel auf das Attribut NASEQID der Relation NASequence. 5. In RNA werden die RNA-Sequenzen modelliert. Das Schlüsselattribut ist RNASEQID vom Typ BIGINT, das zugleich Fremdschlüssel auf das Attribut NASEQID aus NASequence ist. Zwei weitere Fremdschlüsselattribute bilden die Beziehungen des zentralen Dogmas (vergleiche Abbildung 2.1) ab: DNASEQID ist ein Fremdschlüssel auf das gleichnamige Attribut der Relation DNA und enthält für ein Tupel die Kennung der DNA-Sequenz, aus der die RNA-Sequenz erzeugt wurde. PROTEINID ist ein Fremdschlüssel auf das gleichnamige Protein-Attribut und verweist auf die Aminosäuresequenz, in die die RNA-Sequenz übersetzt wurde. Im Rahmen von BLAST sind die Relationen NASequence und Protein von besonderem Interesse, da sie die Zeichenketten der Sequenzen enthalten. Das gesamte Datenmodell der verwendeten Datenbank befindet sich in Anhang D. Dort ist zu ersehen, welche Verknüpfungen zu anderen biologischen Informationen bestehen. 5.2 Anwendungsprogrammierung mit DB2 Anfragen an relationale Datenbanken erfolgen heutzutage überwiegend mit der Anfragesprache SQL (Structured Query Language). Sie ist aus der Sprache SEQUEL [8] entstanden. SQL ist ein ISO-Standard [17] und wird von vielen Herstellern von Datenbankmanagementsystemen (DBMS en) implementiert. Deshalb sind auf SQL basierende Anwendungen relativ portabel. Ursprünglich wurde SQL als interaktive Anfragesprache entwickelt. Die Benutzer kommunizieren dabei direkt mit dem Datenbanksystem, indem sie eine SQL-Anfrage formulieren und die Antwort sofort bekommen, wenn die Anfrage vollständig verarbeitet wurde. 43 KAPITEL 5. VERWENDETE DATENBANKKONZEPTE Bei dieser Benutzung muss nicht extra ein Programm geschrieben werden, vielmehr gibt es verschiedene Werkzeuge, die interaktiv SQL verarbeiten, wie z.B. der Kommandozeilenprozessor (Command Line Processor, CLP ) von DB2. Häufig jedoch kommen Datenbanksysteme auch in Bereichen zur Anwendung, in denen die meisten Anwender kein SQL formulieren können, sondern auf andere Möglichkeiten der Eingabe zurückgreifen, wie zum Beispiel grafische Formulare und Eingabefelder. Für diese Zwecke werden von Datenbanksystemen Schnittstellen definiert, um die Programmierung solcher Anwendungssysteme zu ermöglichen. In den folgenden Unterkapiteln werden zwei Schnittstellen vorgestellt, die DB2 zur Verfügung stellt: Embedded SQL sowie das Call Level Interface. 5.2.1 Eingebettetes SQL Die erste Möglichkeit, SQL in Anwendungsprogrammen zu verarbeiten, besteht in der Einbettung der SQL-Anweisungen in den Quelltext der Anwendung. Diese Möglichkeit kann in Programmen verwendet werden, die in den ”Wirtssprachen” C, C++, FORTRAN und COBOL. Programme dieser Art müssen zunächst von einem SQL-Precompiler verarbeitet werden, der die eingebetteten (embedded ) SQL-Anweisungen durch Aufrufe von Laufzeitroutinen ersetzt. Das Ergebnis sind Programme, die von normalen Compilern übersetzt werden können. Im Fall der Sprache C müssen einer eingebetteten SQL-Anweisung die Wörter EXEC SQL vorangestellt werden, damit der Precompiler die Anweisung erkennt. Um Variablen des Programms innerhalb einer SQL-Anweisung zu benutzen, müssen sie in einem vom Precompiler erkennbaren Teil des Programms deklariert werden (BEGIN/END DECLARE SECTION). Die Variablen können dann in SQL-Anweisungen benutzt werden, indem ihnen ein Doppelpunkt vorangestellt wird. Zum Auslesen der Tupel einer SELECT-Anweisung wird eine Positionsmarke (engl.: cursor ) deklariert, die jedes Tupel mittels einer EXEC SQL FETCHAnweisung ausliest. Die Attribute des Tupels werden dann in vorher deklarierte Variablen geschrieben, die im Anwendungsprogramm weiter verarbeitet werden können. Nach dem Lesen eines Tupels wird der cursor auf das nächste zu lesende Tupel ”gesetzt”. Der hier angedeutete Mechanismus erzeugt sogenanntes statisches SQL. Dieses zeichnet sich dadurch aus, dass die SQL-Anweisungen vom Anwendungsentwickler selbst in das Programm hineingeschrieben werden. Das hat den Vorteil, dass während der PrecompilerPhase der Anfrage-Optimierer des DBMS einen Zugriffsplan in der Datenbank ablegt, so dass diese Phase der Anfrageoptimierung während der Laufzeit entfällt. Falls das Programm dynamisch SQL-Anweisungen erzeugen soll, so kann das nicht mehr mit den Mitteln von statischem SQL bewerkstelligt werden. Zu diesem Zweck existieren andere EXEC SQL-Befehle. Sie können string-Variablen verarbeiten, die die eigentlichen SQL-Befehle enthalten. Diese Zeichenketten können während der Laufzeit erzeugt werden. Deshalb spricht man hier von eingebettetem dynamischem SQL. Eingebettetes SQL ist im Detail in [7] beschrieben. Hier soll nicht näher darauf eingegangen werden, da die BLAST-Anpassung mit dem Call Level Interface realisiert wird. 5.2.2 Das Call Level Interface Die Verwendung von eingebettetem SQL hat den Nachteil, dass ein Precompiler bemüht werden muss, bevor das Programm mit dem eigentlichen Compiler erzeugt werden kann. 44 KAPITEL 5. VERWENDETE DATENBANKKONZEPTE Das so genannte Call Level Interface (CLI, engl.; etwa: Schnittstelle auf der Ebene von Funktionsaufrufen) geht einen anderen Weg. Hierbei werden die SQL-Anweisungen als Zeichenkettenvariablen abgelegt. Dies kann während der Laufzeit oder bereits durch den Entwickler passieren. Mittels einer standardisierten Schnittstelle von Funktionsaufrufen können diese SQL-Anweisungen vorbereitet und ausgeführt werden. Im Folgenden werden die wesentlichen CLI-Funktionen und der schematische Ablauf eines CLI-Programms vorgestellt. Ein Vorteil der Benutzung von CLI liegt, wie bereits erwähnt, in der Vermeidung eines zusätzlichen Precompiler-Aufrufs. Durch die Trennung der SQL-Anweisungen (in Funktionsaufrufen) von den Anweisungen in der eigentlichen Programmiersprache ist auch die Fehlersuche einfacher. CLI erlaubt es überdies, portable Datenbankanwendungen zu schreiben, da es auf den Standards SQL Call Level Interface (SQL/CLI ) [18] und Open Database Connectivity (ODBC ) basiert. Ein Nachteil von CLI ist, dass es nur in den Sprachen C und C++ verwendet werden kann. 5.2.2.1 Handles Im CLI wird das Konzept von Handles (hier: Kennungen) verwendet. Kennungen sind C-Variablen, die dazu dienen, Zustände von Objekten zu repräsentieren. Folgende Typen von Kennungen werden unterschieden: 1. Eine Umgebungskennung beschreibt den Zustand der Anwendung. Zu Beginn eines Programms wird eine Umgebungsvariable reserviert und am Ende wieder freigegeben. Ein Programm sollte nur eine einzige Umgebungskennung besitzen. 2. Eine Verbindungskennung repräsentiert die Verbindung des Programms zu einer Datenbank. Diese Kennung wird zum Auf- und Abbau der Datenbankverbindung verwendet. Vor dem Aufbau der Verbindung sollte die Kennung reserviert und nach dem Abbau wieder freigegeben werden. Eine Anwendung kann Verbindungen zu mehreren Datenbanken unterhalten; für jede von Ihnen ist eine eigene Kennung notwendig. 3. Anweisungskennungen dienen zur Verwaltung des Status von SQL-Anweisungen. In einem Programm können mehrere Anweisungskennungen verwendet werden. Mittels einer solchen Kennung wird eine SQL-Anweisung vorbereitet und ausgeführt. 4. Deskriptorkennungen dienen dazu, bestimmte Informationen über die Rückgabeattribute einer SQL-Anweisung oder die in einer Anweisung verwendeten Parametermarker zu erhalten. Diese Informationen sind zum Teil auch über Anweisungskennungen zugänglich, weshalb Deskriptorvariablen relativ selten verwendet werden. Die folgenden beiden Funktionen dienen der Reservierung und Freigabe von Kennungsvariablen: • SQLAllocHandle() reserviert eine Kennung. Zusätzlich zur Kennung werden noch ihr Typ und ihre Kontextkennung übergeben. Die Kontextkennungen können Tabelle 5.1 entnommen werden. Die Kontextkennung selbst muss bereits reserviert sein. • SQLFreeHandle() gibt eine Kennungsvariable wieder frei. Auch hier wird der Typ der Kennung übergeben. 45 KAPITEL 5. VERWENDETE DATENBANKKONZEPTE Kennung Umgebung Verbindung Anweisung Deskriptor Kontextkennung — Umgebung Verbindung Verbindung Tabelle 5.1: CLI-Kennungen und ihre Kontexte 5.2.2.2 Die Verbindung mit der Datenbank Nach der Reservierung von Umgebungs- und Verbindungskennung wird die Verbindung mit der Datenbank hergestellt. Dazu dient hauptsächlich die Funktion SQLConnect(), der die Verbindungskennung, der Name der Datenbank, der Benutzername und das BenutzerKennwort übergeben werden. Werden Benutzername und Kennwort freigelassen, so verwendet SQLConnect() die Zugangsdaten desjenigen Benutzers, der das Programm ausführt. Zum Abbau der Verbindung wird die Funktion SQLDisconnect() benutzt, die als Parameter die Verbindungskennung erhält. 5.2.2.3 Ausführung von SQL-Anweisungen Vor der Ausführung von SQL-Anweisungen müssen Anweisungshandles mit der Funktion SQLAllocHandle() reserviert werden. Im Fall von BLAST genügen SELECT-Anweisungen, deren zurückgegebene Tupel nacheinander ausgelesen und verarbeitet werden. Für diesen Anwendungsfall wird die folgende Abfolge von Funktionsaufrufen verwendet: 1. SQLPrepare() bereitet eine SQL-Anweisung vor. Die Anweisung wird als Zeichenkette an die Funktion übergeben. Das DBMS erzeugt in der Datenbank einen Zugriffsplan für die Anweisung. 2. SQLBindParameter() Bei der Ausführung einer SQL-Anweisung ist es möglich, Variablenwerte des Anwendungsprogramms in der Anweisung zu verwenden. Dies geschieht durch Parametermarker, die als Fragezeichen in der SQL-Anweisung repräsentiert werden. Mit der Funktion SQLBindParameter() werden diesen Markern Variablen des Programms zugeordnet. Für jeden Marker ist ein Aufruf erforderlich. 3. SQLExecute() führt die SQL-Anweisung aus. Im Fall von Parametermarkern werden die Werte aus den Programmvariablen gelesen und in die Anweisung eingefügt. 4. SQLBindCol() Die Ausführung einer SELECT- oder VALUES-Anweisung liefert null oder mehr Ergebnistupel. Für jedes in einer SELECT- oder VALUES-Anweisung angegebene Attribut muss eine Variable festgelegt werden, die die Attributwerte der Ergebnistupel aufnimmt. Die Zuordnung einer Programmvariablen zu einem Attribut erfolgt mit Hilfe der Funktion SQLBindCol(). Die Funktion erhält die Anweisungskennung, die Attributnummer (zu deren Identifikation), den Typ der Programmvariablen und deren Länge (bei Zeichenketten). Außerdem kann eine NULL-Indikator-Variable angegeben 46 KAPITEL 5. VERWENDETE DATENBANKKONZEPTE werden. Sie gibt Auskunft darüber, ob der Attributwert im aktuellen Tupel NULL ist. 5. SQLFetch() liest das nächste Tupel der Ergebnismenge und schreibt die Werte der einzelnen Attribute in die Variablen, die mittels SQLBindCol() deklariert wurden. Intern ist dieses Vorgehen als cursor wie bei eingebettetem SQL implementiert. An dieser Stelle sei darauf hingewiesen, dass die Funktionsfolge SQLPrepare() ↓ SQLBindParameter() ↓ SQLExecute() durch die Folge SQLBindParameter() ↓ SQLExecDirect() ersetzt werden kann. Die Zeichenkette mit der SQL-Anweisung wird in diesem Fall an SQLExecDirect() übergeben. Die erste Funktionsfolge ist dann zu bevorzugen, wenn die Anweisung mit unterschiedlichen Variablenwerten mehrmals ausgeführt werden soll. Der Zugriffsplan wird in diesem Fall genau einmal mit SQLPrepare() erzeugt, die Funktionen SQLBindParameter() und SQLExecute() können danach beliebig oft aufgerufen werden. Wird die Anweisung nur einmal ausgeführt, sind die beiden Funktionsfolgen gleichberechtigt. Zur Ausführung und zur Analyse von SQL-Anweisungen existieren noch weitere CLIFunktionen [7, 15], etwa zum Zurücksetzen eines cursor s oder zum Transaktionsmanagement. Die hier vorgestellten Funktionen genügen jedoch, die im Rahmen von BLAST notwendigen Anpassungen durchzuführen. 5.2.2.4 CLI-Rückgabewerte Fast alle CLI-Funktionen geben einen Wert zurück, der Auskunft über den Erfolg der Funktionsausführung gibt. Die folgende Aufzählung beschreibt nur die wichtigsten SQLRückgabewerte: • SQL_SUCCESS zeigt an, dass die Funktion erfolgreich war. • SQL_SUCCESS_WITH_INFO zeigt ebenfalls den Erfolg der Funktion an. Die Funktion liefert aber gleichzeitig eine Warnung oder eine andere Information. Durch den Aufruf der Funktion SQLGetDiagRec() kann diese Information ausgewertet werden. • SQL_INVALID_HANDLE deutet auf eine nicht initialisierte Kennung hin. • SQL_ERROR bedeutet, dass die Funktion nicht erfolgreich ausgeführt wurde. Mittels SQLGetDiagRec() kann der Grund für den Fehler in Erfahrung gebracht werden. 47 KAPITEL 5. VERWENDETE DATENBANKKONZEPTE • SQL_NO_DATA_FOUND ist ein Rückgabewert von SQLFetch(). Er gibt an, dass der cursor hinter dem letzten Tupel der Ergebnismenge positioniert ist. Folglich sind keine weiteren SQLFetch()-Aufrufe zur Verarbeitung der Ergebnisse notwendig. Das Auslesen der Ergebnismenge einer SELECT- oder VALUES-Anweisung kann in C so implementiert werden, dass eine Schleife so lange SQLFetch() aufruft und die Ergebnisse verarbeitet, bis die Funktion den Wert SQL_NO_DATA_FOUND zurückliefert. 5.3 Benutzerdefinierte Funktionen In SQL-Anweisungen werden häufig Funktionen verwendet. Die Anweisung SELECT count(attribut2) FROM tabelle1 ruft beispielsweise die Funktion count auf, die die Anzahl der Tupel der tabelle1 -Relation zurückgibt. DB2 unterscheidet vier Typen von Funktionen [16]: 1. Skalare Funktionen Skalare Funktionen bilden eine Argumentliste skalarer Werte auf einen skalaren Rückgabewert (auch NULL) ab. Eine solche Funktion kann überall dort verwendet werden, wo ein Ausdruck stehen kann, etwa in der SELECT- oder WHERE-Klausel einer SQL-Anweisung. Ein Beispiel für eine skalare Funktion ist LN, die den natürlichen Logarithmus des übergebenen Arguments berechnet. 2. Spaltenfunktionen Das Argument einer Spaltenfunktion ist eine Menge gleicher skalarer Werte. Betrachtet man eine Relation als Tabelle, so nimmt eine Spaltenfunktion eine Spalte als Ganzes entgegen und berechnet daraus einen skalaren Wert. Spaltenfunktionen können überall dort verwendet werden, wo ein Ausdruck stehen kann. Ein Beispiel für eine Spaltenfunktion ist MAX, die den größten Wert der übergebenen Spalte zurückgibt. 3. Zeilenfunktionen Eine Zeilenfunktion nimmt einen strukturierten Typ als Argument entgegen und liefert ein Tupel von vordefinierten Typen zurück. Zeilenfunktionen dienen lediglich als Transformationsfunktionen für strukturierte Typen. 4. Tabellenfunktionen Tabellenfunktionen bilden eine Liste skalarer Werte auf eine Tabelle ab. Diese Funktionen liefern also eine Menge von Tupeln vordefinierter Struktur. Sie können nur in der FROM-Klausel einer SQL-Anweisung stehen. Die einzige vordefinierte Tabellenfunktion von DB2 ist SQLCACHE_SNAPSHOT. Neben der Verwendung bereits vordefinierter Funktionen ist es Benutzern von DB2 möglich, selbst Funktionen zu erstellen. Diese werden als benutzerdefinierte Funktionen (engl.: user-defined functions, UDF ) bezeichnet. UDFs [14] können wie folgt klassifiziert werden: 1. Quellenbasierte Funktionen DB2 implementiert das Konzept einzigartiger Typen. Diese zeichnen sich dadurch aus, dass sie von einem vordefinierten Typ mittels einer 1:1-Abbildung abgeleitet 48 KAPITEL 5. VERWENDETE DATENBANKKONZEPTE werden und einen eigenen Namen erhalten. Der Name dient dazu, die Semantik des Datentyps hervorzuheben. Als Beispiel kann ein Typ WINKEL zur Darstellung geometrischer Winkel definiert werden, der von DOUBLE abgeleitet ist. Um Funktionen, die auf Argumenten vom Typ DOUBLE definiert sind, im Kontext von WINKEL zu verwenden, müssen sie als quellenbasierte Funktionen vom jeweiligen Original abgeleitet werden. Beispielsweise kann eine Funktion SIN definiert werden, die Werte vom Typ WINKEL als Argumente entgegennimmt und die gleiche Semantik hat wie die SIN-Funktion für DOUBLE-Werte. 2. Externe skalare Funktionen Eine externe skalare Funktion ist eine skalare Funktion, die von einem Benutzer in einer Programmiersprache geschrieben wurde. Externe skalare Funktionen können in C, C++ oder Java implementiert werden. Der Programmcode wird dem Datenbanksystem in einer kompilierten Funktionsbibliothek zur Verfügung gestellt. Externe skalare Funktionen dürfen kein SQL enthalten und können deshalb nicht auf die Datenbank zugreifen. 3. Externe Tabellenfunktionen Eine externe Tabellenfunktion ist eine Tabellenfunktion, die von einem Benutzer in einer der Programmiersprachen C, C++ oder Java implementiert ist. Auch hier ist es nicht erlaubt, SQL in der Funktion zu verwenden. Da eine Tabellenfunktion mehrere Tupel als Ergebnis liefern kann, wird sie intern mehrmals aufgerufen. Benutzerdefinierte Funktionen werden genauso verwendet wie die vordefinierten. Es gelten die gleichen Einschränkungen. Für die Anpassung von BLAST ist die Verwendung einer externen Tabellenfunktion von besonderem Interesse. Deswegen wird auf deren Aufbau näher eingegangen. 5.3.1 Deklaration einer externen Tabellenfunktion Ausgangspunkt für die Behandlung von Tabellenfunktionen ist deren Deklaration. Dies geschieht mit der SQL-Anweisung CREATE FUNCTION. Sie hat den folgenden Aufbau: CREATE FUNCTION <Funktionsname> (<Parameterliste>) RETURNS TABLE (<Liste der Ergebnisattribute>) EXTERNAL NAME <Dateiname>!<externer Funktionsname> LANGUAGE <Implementationssprache> FENCED | NOT FENCED EXTERNAL ACTION | NO EXTERNAL ACTION FINAL CALL | NO FINAL CALL SCRATCHPAD | NO SCRATCHPAD RETURNS NULL ON NULL INPUT | CALL ON NULL INPUT NO SQL DISALLOW PARALLEL <weitere Optionen> Die Anweisungsoptionen haben folgende Bedeutung: • Die Funktion wird in SQL-Anweisungen mit dem <Funktionsname>n aufgerufen. 49 KAPITEL 5. VERWENDETE DATENBANKKONZEPTE • <Parameterliste> enthält die Typen der Funktionsparameter. Die Parameter können optional einen Namen erhalten. • Die komma-separierte <Liste der Ergebnisattribute> enthält Einträge der Form <Attributname> <Datentyp>. Der Attributname kann in der SELECT-Klausel verwendet werden. • <Dateiname> ist der Name der Funktionsbibliotheksdatei, die den Code der UDF enthält. Bei der Ausführung der CREATE FUNCTION-Anweisung muss die Datei nicht vorhanden sein. Die Funktion in der Bibliothek wird erst bei der Ausführung der UDF in einer SELECT- oder VALUES-Anweisung aufgerufen. • <externer Funktionsname> ist der Name der UDF in der Funktionsbibliothek. • Die <Implementationssprache> ist C, JAVA oder OLE. OLE bedeutet, dass die benutzerdefinierte Funktion eine Methode eines OLE Automationsobjekts ist. Diese Möglichkeit kann nur in den 32-Bit-Versionen des Betriebssystems Windows verwendet werden. • Bei der Option FENCED wird die Funktion in einem eigenen Prozess ausgeführt. Das Gegenstück ist NOT FENCED, bei dem die aufgerufene Funktion im gleichen Prozess wie das DBMS läuft. FENCED hat den Vorteil, dass bei schweren Fehlern (Speicherschutz- oder Zugriffsfehlern) die Funktion abnormal beendet wird, ohne das DBMS zu beeinträchtigen. Ein derartiger Fehler in einer NOT FENCED-Funktion führt im schlimmsten Fall zur Beendigung des DBMS-Prozesses. Dadurch würden alle Verbindungen zu den vom DBMS verwalteten Datenbanken beendet. Der Vorteil von NOT FENCED liegt in der schnelleren Ausführung einer Funktion gegenüber ihrem FENCED-Pendant. • EXTERNAL ACTION wird angegeben, wenn die Funktion externe Ressourcen verwendet, wie z.B. das Dateisystem oder Gerätetreiber. • Der Aufruf einer Tabellenfunktion stellt sich für den Benutzer von SQL so dar, dass für jede Kombination von Aufrufargumenten die UDF einmal aufgerufen wird. Intern wird die Funktion für das Liefern jedes Ergebnistupels separat aufgerufen. Ist die Option FINAL CALL gesetzt, unterscheidet die UDF fünf Aufruftypen, im Fall NO FINAL CALL drei. Die Aufruftypen werden im nächsten Abschnitt detailliert erläutert. • Da eine UDF mehrmals aufgerufen wird, kann es nötig sein, bestimmte Variablenwerte und Datenstrukturen zwischen den verschiedenen Aufrufen zu erhalten. Zu diesem Zweck wird das scratchpad -Konzept (engl.; Notizzettel) verwendet. Im scratchpad können Referenzen (Zeiger) auf Speicherbereiche gespeichert werden, die zwischen den verschiedenen Aufrufen einer UDF konstant bleiben sollen. Die Option SCRATCHPAD stellt einen solchen Bereich zur Verfügung. • Oftmals ist es sinnvoll, eine UDF nicht aufzurufen, wenn ihr NULL-Werte übergeben werden. Dieses Verhalten kann mit der Option RETURNS NULL ON NULL INPUT eingestellt werden. In diesem Fall wird vom DBMS auch NULL zurückgegeben. Dabei 50 KAPITEL 5. VERWENDETE DATENBANKKONZEPTE genügt es, wenn nur eines der übergebenen Argumente NULL ist. Soll die Funktion in jedem Fall aufgerufen werden, so ist die Option CALL ON NULL INPUT anzugeben. • Die Optionen NO SQL und DISALLOW PARALLEL sind in jedem Fall anzugeben. NO SQL bedeutet, dass die Funktion nicht mittels eingebettetem SQL oder CLI auf die Datenbank zugreifen darf. DISALLOW PARALLEL bedeutet, dass der Aufruf der Funktion nicht parallelisiert werden kann. 5.3.2 Aufbau einer externen Tabellenfunktion Tabellenfunktionen können in JAVA oder C implementiert werden. In diesem Abschnitt soll der Aufbau einer C-Tabellenfunktion beschrieben werden. Schematisch sieht der Kopf einer solchen Funktion wie folgt aus: void SQL_API_FN <Funktionsname>( <Eingabeparameter>, <Rueckgabevariablen>, <NULL-Indikatoren der Eingabeparameter>, <NULL-Indikatoren der Rueckgabewerte>, SQLSTATE, SQL-Funktionsname, spezifischer Name, Fehlermeldungsvariable, Notizzettel-Variable, Aufruftyp, DBInfo-Struktur ) /* /* /* /* /* /* /* /* /* /* /* IN OUT IN OUT OUT IN IN OUT IN IN IN */ */ */ */ */ */ */ */ */ */ */ Die Eingabeparametervariablen entsprechen der ”Parameterliste” aus der Funktionsdeklaration. Die Rückgabevariablen entsprechen der ”Liste der Ergebnisattribute”. Jedem Eingabeparameter wird zusätzlich ein NULL-Indikator zur Seite gestellt. Die Rückgabevariablen besitzen ebenfalls NULL-Indikatoren, die von der Funktion gesetzt werden können. Die SQLSTATE-Variable wird von der Funktion gesetzt und zeigt den Rückgabestatus der Funktion an. Die Variable ist eine Zeichenkette der Länge 5, die eine Zahl repräsentiert. Folgende Werte können dabei zurückgegeben werden: • ”00000” zeigt den Erfolg der Funktion an. • Werte im Bereich ”38600” bis ”38999” deuten auf Fehler bei der Ausführung hin. Die Funktion kann dann in der Zeichenkette Fehlermeldungsvariable eine Fehlermeldung eintragen, die von der SQL-Benutzerschnittstelle ausgegeben wird. • Der Wert ”02000” kann nur bei einem Aufruf vom Typ FETCH zurückgegeben werden und zeigt an, dass keine Ergebnistupel mehr folgen. Andere Werte sollten nicht verwendet werden, da sie dem DBMS vorbehalten sind. Die anderen Parameter haben die folgende Bedeutung: 1. SQL-Funktionsname ist der Name der UDF in SQL. 2. spezifischer Name ist ein Name, den DB2 bei der Funktionsdeklaration vergibt. 51 KAPITEL 5. VERWENDETE DATENBANKKONZEPTE 3. Die Notizzettel-Variable ist ein Speicherbereich, dessen Inhalt zwischen den Aufrufen der Funktion bestehen bleibt. 4. Die DBInfo-Struktur enthält einige weiterführende Informationen des Funktionsaufrufs, wie z.B. die aktuelle Datenbank oder die Benutzerkennung. Die Notizzettel- und die DBInfo-Struktur sind nur vorhanden, falls die entsprechenden Optionen bei der Deklaration gesetzt wurden. Eine UDF wird innerhalb einer SQL-Anweisung mehrfach intern aufgerufen. Dabei werden die folgenden Aufruftypen unterschieden: 1. FIRST Der FIRST call (erster Aufruf) der Funktion wird durchgeführt, falls die Option FINAL CALL bei der Deklaration der Funktion gesetzt wurde. Die Funktion wird nur einmal pro SQL-Anweisung mit dem Typ FIRST aufgerufen. Der Aufruf dient dazu, Ressourcen und ähnliches zu initialisieren, die für alle weiteren Aufrufe benötigt werden. Der FIRST-Aufruf sollte kein Ergebnistupel zurückgeben. Ist die NotizzettelVariable vorhanden, wird deren Inhalt vor dem Aufruf mit Nullen initialisiert. Im Fall eines Fehlers im FIRST-Aufruf werden keine weiteren Aufrufe der Funktion getätigt. 2. OPEN Der OPEN-Aufruf wird für jede Parameterkombination der UDF einmal aufgerufen. Er dient dazu, Initialisierungen und Berechnungen für diese Parameterkombination durchzuführen. Auch bei OPEN sollte kein Ergebnistupel zurückgegeben werden. Ist ein Notizzettel vorhanden und wurde FINAL CALL nicht deklariert, initialisiert der OPEN-Aufruf den Notizzettel-Speicherbereich. Falls der OPEN-Aufruf einen Fehler meldet, wird danach nur der FINAL-Aufruf der Funktion ausgeführt. 3. FETCH Der FETCH-Aufruf dient der Rückgabe eines Ergebnistupels. Es gibt zwei Möglichkeiten zur Ausführung der Funktion: (a) Falls intern Ergebnistupel vorliegen, müssen die entsprechenden Rückgabevariablen gesetzt werden. Die Funktion muss dann die Variable SQLSTATE auf den Wert ”00000” setzen. (b) Falls keine Ergebnistupel mehr zurückgegeben werden können, muss die Variable SQLSTATE auf den Wert ”02000” gesetzt werden. Damit wird dem DBMS angezeigt, dass keine weiteren Ergebnistupel folgen. Im Falle eines Fehlers bei FETCH werden nur noch CLOSE und FINAL aufgerufen und keine weiteren FETCH’es. 4. CLOSE Jedem OPEN-Aufruf entspricht ein CLOSE-Aufruf, der Initialisierungen wieder freigibt, die im OPEN-Aufruf vorgenommen wurden. Der CLOSE-Aufruf folgt demjenigen FETCH-Aufruf, der den Wert ”02000” in SQLSTATE zurückgegeben hat, oder einem fehlerhaften FETCH-Aufruf. 52 KAPITEL 5. VERWENDETE DATENBANKKONZEPTE 5. FINAL Jedem FIRST-Aufruf entspricht ein FINAL-Aufruf. Hier werden Initialisierungen wieder zurückgesetzt, die für alle Funktionsaufrufe benötigt wurden. Ein FINALAufruf erfolgt nur, falls FINAL CALL bei der SQL-Deklaration der Funktion gesetzt wurde. 53 Kapitel 6 Anpassung von BLAST Dieses Kapitel führt die Erkenntnisse der letzten beiden Kapitel zusammen. In den folgenden Abschnitten wird das Programm blastall in der Form modifiziert, dass es im Kontext einer relationalen Datenbank eingesetzt werden kann. Zu diesem Zweck werden zwei verschiedene Wege beschritten. Die erste Anpassungsvariante von BLAST basiert auf der Programmierschnittstelle Call Level Interface von DB2. Bei dieser Modifikation geht es vor allem darum, Zugriffe auf blastall -Datensammlungsdateien durch entsprechende SQL-Anweisungen zu ersetzen. Die zweite Anpassung besteht in der Implementation von blastall als benutzerdefinierte Funktion. Das Augenmerk bei dieser Umwandlung liegt darauf, den Ablauf des Programms geeignet auf den einer UDF abzubilden. 6.1 Implementation der Datenbankschnittstelle von BLAST Bei der Anpassung von blastall mit Hilfe des CLI besteht die Hauptaufgabe in der Modifikation der Datenbankschnittstelle. Dabei werden die Datensammlungsdateien, die die Vergleichssequenzen enthalten, durch die Anbindung an eine relationale Datenbank ersetzt (Abbildung 6.1). Die Anfragesequenz und der BLAST-Reports werden auch nach der Anpassung in Dateien abgelegt. Das modifizierte Programm wird mit db2blast bezeichnet, um es von blastall abzuheben. 6.1.1 Initialisierung der Datenbankschnittstelle Vor der Nutzung der Datenbankschnittstelle steht deren Initialisierung. Für die grundlegenden Initialisierungen wird die Funktion db2init() implementiert, die die folgenden Schritte ausführt: 1. Allokation der Umgebungskennung mittels SQLAllocHandle() 2. Allokation der Datenbank-Verbindungskennung mittels SQLAllocHandle() 3. Aufbau der Datenbank-Verbindung mittels SQLConnect() 4. Allokation der Anweisungskennungen für die verwendeten SQL-Anweisungen mittels SQLAllocHandle() 54 KAPITEL 6. ANPASSUNG VON BLAST Abbildung 6.1: Integration von BLAST in eine relationale Datenbank. Es ist zu beachten, dass das Format der Anfragesequenz- und der Reportdatei unverändert bleibt. Die Kennungen sind globale Variablen, da sie in anderen Funktionen verwendet werden müssen. db2init() wird ein Zeichenketten-Parameter übergeben, der den Datenbanknamen enthält. Legt man den schematischen Ablauf der Main()-Funktion zugrunde (siehe Abschnitt 4.4.1), so wird db2init() vor dem Einlesen der Anfragesequenz-Datei ausgeführt. Das Gegenstück von db2init() ist db2destruct(). Diese Funktion gibt die Allokationen wieder frei und baut die Datenbankverbindung ab: 1. Freigabe des Sequenz-Speicherbereich Dieser Speicherbereich wird nicht von db2init() ermittelt, sondern erst in einer danach aufgerufenen Funktion reserviert. 2. Freigabe der Anweisungskennungen mittels SQLFreeHandle() 3. Abbau der Datenbank-Verbindung mittels SQLDisconnect() 4. Freigabe der Datenbank-Verbindungskennung mittels SQLFreeHandle() 5. Freigabe der Umgebungskennung mittels SQLFreeHandle() db2destruct() wird am Ende der Main()-Funktion ausgeführt. Im weiteren Verlauf werden die von BLAST benötigten SQL-Anweisungen vorbereitet und teilweise ausgeführt. Zu diesem Zweck werden zwei weitere Funktionen implementiert: • db2prepareStatements() bereitet die SQL-Anweisungen vor (SQLPrepare()). Die verwendeten SQL-Anweisungen sind abhängig von der BLAST-Variante, da die entsprechenden Sequenzen entweder in der NASequence- (blastn, tblastn, tblastx) oder der Protein-Relation (blastp, blastx) zu finden sind. 55 KAPITEL 6. ANPASSUNG VON BLAST • db2executeAndBindConstStatements() führt die Anweisungen aus (SQLExecute()), die keine Parametermarker enthalten und nicht mehrfach ausgeführt werden. Diesen Anweisungen werden auch die Ergebnisvariablen zugewiesen (SQLBindCol()). Die SQLBindCol()-Variablen sind global, weil ihre Werte in BLAST-Funktionen verwendet werden. Einige der SQL-Anweisungen geben nur ein Ergebnistupel zurück. Dieses wird an dieser Stelle gleich in die Programmvariablen geschrieben (SQLFetch()). Über die tatsächlich benötigten SQL-Anweisungen geben die folgenden Abschnitte Auskunft. Folgt man dem Schema der Main()-Funktion, so werden die beiden eben beschriebenen Funktionen vor dem Lesen der Anfragesequenz ausgeführt. Ihnen geht der Aufruf der Funktion BlastGetTypes() voraus. Sie bestimmt anhand der BLAST-Programmvariante, von welchem Typ Anfragesequenz und Vergleichssequenzen sind. 6.1.2 Initialisierung des Moduls readdb Die Funktion readdb_new_internal() ist die zentrale Initialisierungsfunktion des Moduls readdb. Sie führt schematisch die folgenden Schritte aus: 1. Suche nach dem Pfad der Index-Datei der BLAST-Datensammlung 2. Erzeugen der ReadDBFILE-Datenstruktur 3. Öffnen der FormatDB -Dateien 4. Lesen der Kennwerte aus der Index-Datei und Speicherung in ReadDBFILE 5. Reservierung des Speicherbereichs für die zu lesenden Sequenzen 6. Setzen von Zeigern auf die Hauptspeicherdateien (MMF) 7. Suche nach speziellen Index-Dateien (deren Erzeugung bei FormatDB mit angegeben werden kann) Die Schritte 1, 3, 6 und 7 werden in der Anpassung nicht verwendet, da die entsprechenden Dateien nicht vorhanden sind. Die Vergleichssequenzen kommen stattdessen aus der relationalen Datenbank. Die Kennwerte aus Schritt 4 werden im Folgenden aufgezählt. In Klammern stehen die Elemente von ReadDBFILE, die die Werte speichern: • Versionsnummer von FormatDB (formatdb_ver) • Titel der Datensammlung (title; kann bei FormatDB mit angegeben werden) • Erstellungszeitpunkt der Datensammlungsdateien durch FormatDB (date) • Länge der längsten Vergleichssequenz (maxlen) • Anzahl der Vergleichssequenzen (num_seqs) 56 KAPITEL 6. ANPASSUNG VON BLAST • Summe über die Längen aller Vergleichssequenzen (totlen) Um db2blast mit blastall zu vergleichen, müssen beim angepassten Programm entsprechende Kennwerte in ReadDBFILE abgelegt werden. Die Elemente dieser Struktur werden im Rahmen von db2blast wie folgt belegt: • formatdb_ver erhält den Wert 3, da das die Versionsnummer des verwendeten FormatDB -Dateiformats ist. • title wird auf den Wert ”PROTEIN” gesetzt, wenn die Protein-Relation verwendet wird (Proteinsuche), und auf ”DNA”, wenn die NASequence-Relation verwendet wird. • date wird im db2blast-Kontext das Erstellungsdatum der jeweiligen Tabelle übergeben. Für den Fall einer Proteinsuche wird dazu folgende SQL-Anweisung ausgeführt: SELECT create_time FROM syscat.tables WHERE tabname=’PROTEIN’ Nach der Ausführung der Anweisung wird das Ergebnis in die Variable db2date vom CLI-Typ TIMESTAMP_STRUCT gespeichert. Erst in readdb_new_internal() gelangt der Wert in das date-Element. • Die Elemente maxlen, num_seqs und totlen werden gemeinsam ermittelt. Dazu wird folgende SQL-Anweisung ausgeführt: SELECT MAX(LENGTH(seqtext)), COUNT(*), SUM(LENGTH(seqtext)) FROM sequences.protein Die drei Attribute werden beim Holen des Tupels in die globalen Variablen db2maxlen, db2count und db2totlen gespeichert und gelangen erst in readdb_new_internal() in die entsprechenden ReadDBFILE-Elemente. db2blast führt zusätzlich die folgenden neuen ReadDBFILE-Elemente ein, die ebenfalls in readdb_new_internal() initialisiert werden: • currSeq enthält die unkodierte, aktuell bearbeitete Sequenz. • currSeqNum speichert die Kennung der aktuellen Sequenz. • In seqLength ist die Länge der aktuellen Sequenz abgelegt. • isAmb hat den Wert TRUE, falls die aktuell bearbeitete Sequenz Mehrdeutigkeitsresiduen beinhaltet. • Die Mehrdeutigkeitskodierung der aktuellen Sequenz ist in ambchars gespeichert. Die Bedeutung dieser Elemente wird in den folgenden Abschnitten deutlich, wenn es um die Modifizierung von bestehenden BLAST-Funktionen geht. Das Gegenstück von readdb_new_internal() ist die Funktion readdb_destruct(). Sie ruft ReadDBCloseMHdrAndSeqFiles() auf, was im Fall von db2blast unterbunden werden muss. Außerdem wird readdb_destruct_element() für jedes ReadDBFILE-Listenelement aufgerufen. Diese Funktion hat folgenden Aufbau: 57 KAPITEL 6. ANPASSUNG VON BLAST 1. Freigabe der Speicherbereiche der ReadDBFILE-Zeichenketten-Elemente 2. Freigabe der Speicherbereiche für die verschiedenen Indizes auf die MMFs 3. Schließen der MMFs, falls diese noch offen sind Der erste Schritt wird in db2blast dahingehend erweitert, dass auch die neu hinzugekommenen ReadDBFILE-Elemente freigegeben werden. Die anderen beiden Schritte werden nicht ausgeführt, da die entsprechenden FormatDB -Dateien nicht existieren. Die neuen Elemente von ReadDBFILE haben auch Auswirkung auf die ”Initialisierungsfunktion” readdb_attach() (im Kontext von C++ würde man von copy constructor sprechen). Bei der Duplikation der ReadDBFILE-Struktur müssen die neu eingeführten Elemente ebenfalls initialisiert werden. 6.1.3 Der Zugriff auf die Datenbankschnittstelle durch BLAST Nach der Initialisierung wird die Datenbankschnittstelle im gesamten Ablauf von blastall verwendet. Folgende readdb-Funktionen wurden bei der Analyse (Abschnitt 4.3.3) als kritisch bewertet: • readdb_new_internal() • readdb_attach() • readdb_destruct() • readdb_destruct_element() • readdb_get_link() • readdb_get_defline_ex() • readdb_get_sequence() • readdb_ambchar_present() • readdb_get_ambchar() • readdb_get_sequence_length() Von diesen Funktionen wurden die ersten vier bereits im letzten Abschnitt modifiziert. Die restlichen Funktionen haben die Eigenschaft gemeinsam, dass sie auf genau einer Vergleichssequenz operieren. Zur Identifikation der Sequenz wird deren Ordnungszahl übergeben. readdb_get_link() ist im Vergleich relativ einfach anzupassen: Im Fall von db2blast gibt es nur eine einzige ReadDBFILE-Struktur. Folglich wird ein Zeiger auf diese zurückgegeben. Die anderen fünf Funktionen werden im Folgenden als die Menge der kritischen Funktionen bezeichnet. Im Fall von readdb_get_defline_ex() besteht das Problem darin, dass keine Kennungen wie in blastall ’s FASTA-Dateien zur Verfügung stehen. Die anderen vier Funktionen müssen ihre Informationen aus dem Attribut SEQTEXT der entsprechenden Datenbank-Sequenz generieren. Die Anpassung der kritischen Funktion hängt davon ab, wie der Ordnungszahl-Parameter im db2blast-Kontext interpretiert wird: 58 KAPITEL 6. ANPASSUNG VON BLAST 1. Wird den Funktionen die tatsächliche ID aus der Relation übergeben, könnten die Funktionen das in die SQL-Anweisung SELECT seqtext FROM sequences.protein WHERE proteinid=? übersetzen (am Beispiel der Proteinsuche). Die IDs müssten dazu vorher bekannt sein. Bei der initialen BLAST-Suche, wie sie in den Funktionen ..._blast_search() durchgeführt wird, ist dies nicht der Fall. 2. Wird die tatsächliche Ordnungszahl der Sequenz übergeben (etwa in einer Schleife über alle Sequenzen), müsste das in eine SQL-Anweisung der Art ”Ermittle Informationen zur i-ten Sequenz” transformiert werden. Im Rahmen einer relationalen Datenbank sind solche Ordnungszahlen allerdings nicht gegeben. Auch die entsprechenden ID-Attribute (NASeqID und ProteinID) bilden keine zusammenhängende Zahlenreihe, weshalb sie nicht zu Ordnungszahlen uminterpretiert werden können. Bei den kritischen Funktionen muss also der Zusammenhang untersucht werden, indem sie aufgerufen werden. Zur Lösung dieses Problems wird der Begriff des Aufrufkontexts eingeführt. Dabei werden die Aufrufe dieser speziellen readdb-Funktionen zueinander in Beziehung gesetzt. Eine kritische Funktion kann in drei Aufrufkontexten stehen: 1. ”nächste Sequenz”-Kontext In einer Schleife über alle Vergleichssequenzen wird die Funktion als erste der kritischen Funktionen aufgerufen. Sie muss also die neue oder nächste Sequenz holen und diese auswerten. Da die Schleife über alle Vergleichssequenzen iteriert, kann in der Funktion das nächste Tupel der SQL-Anweisung (Beispiel Proteinsuche) SELECT proteinid,seqtext FROM sequences.protein geholt werden (SQLFetch()). Die Sequenz wird ausgewertet. Sie und die ID werden zwischengespeichert. Bei der Implementation dieses Kontexts ist es wichtig, dass die betroffene Schleife nach dem Aufruf der kritischen Funktion die ID überall dort verwendet, wo vorher die Ordnungszahl (die Schleifenvariable) benutzt wurde. Die ID von db2blast ersetzt die Ordnungszahl von blastall. Dies ist für die anderen Kontexte von Bedeutung. 2. wahlfreier Kontext Die Funktion wird nach der über alle Sequenzen laufenden Schleife aufgerufen. Da die Schleife den Ordnungszahlen-Bezug durch einen ID-Bezug ersetzt hat, ist sichergestellt, dass die übergebene Zahl eine ID der Datenbank-Relation ist. Deshalb kann diese Funktion die SQL-Anweisung SELECT seqtext FROM sequences.protein WHERE proteinid=? ausführen und aus der Sequenz die nötigen Informationen generieren. Die Sequenz wird zwischengespeichert. 59 KAPITEL 6. ANPASSUNG VON BLAST 3. ”aktuelle Sequenz”-Kontext Die Funktion wird in einem Kontext aufgerufen, in dem bereits eine andere kritische Funktion auf die gleiche Sequenz zugegriffen hat. Dabei ist es unerheblich, ob die andere Funktion im wahlfreien oder im ”nächste Sequenz”-Kontext steht. Die andere Funktion hat bereits die Sequenz aus der Datenbank geholt. Somit kann die aktuelle Funktion auf die zwischengespeicherte Sequenz zurückgreifen. Die Analyse von blastall (Abschnitt 4.4) wird nun zur Bestimmung der Aufrufkontexte der kritischen Funktionen herangezogen: • readdb_get_sequence()-Aufrufe: 1. BLASTPerformSearchWithReaddb() steht am Anfang der for-Schleife der Funktion ..._blast_search(). → ”nächste Sequenz”-Kontext 2. readdb_get_bioseq_ex() wird von BlastReevaluateWithAmbiguities() aufgerufen, das in der for-Schleife steht. → ”aktuelle Sequenz”-Kontext 3. readdb_get_sequence_ex() wird von BioseqBlastEngineCore() aufgerufen. Die Schleife über alle Sequenzen ist an dieser Stelle bereits beendet. → wahlfreier Kontext 4. BlastGetGapAlgnTbckWithReaddb() wird von BioseqBlastEngineCore() nach Beendigung der Schleife aufgerufen. → wahlfreier Kontext • readdb_ambchar_present() wird von BlastReevaluateWithAmbiguities() aufgerufen, das bereits dem ”aktuelle Sequenz”-Kontext zugeordnet wurde. • readdb_get_ambchar()-Aufrufe: 1. readdb_get_bioseq_ex() wurde bereits dem ”aktuelle Sequenz”-Kontext zugeordnet. 2. readdb_get_sequence_ex() ruft zuerst readdb_get_sequence() für dieselbe Sequenz auf. Damit steht der Aufruf im ”aktuelle Sequenz”-Kontext. • readdb_get_sequence_length()-Aufrufe: 1. GetSeqAlignForResultHitList() wird von BioseqBlastEngineCore() nach Beendigung der Schleife aufgerufen. Der Aufruf steht im wahlfreien Kontext. 2. FillInStdSegInfo() wird von GetSeqAlignForResultHitList() aufgerufen. Deshalb steht auch dieser Aufruf im wahlfreien Kontext. • Für den Aufruf von readdb_get_defline_ex() ist nur relevant, ob die übergebene Zahl eine gültige ID der Datenbankrelation ist. Im wahlfreien und im ”aktuelle Sequenz”-Kontext ist die Bedingung erfüllt. Keiner der readdb_get_defline_ex()Aufrufe steht im ”nächste Sequenz”-Kontext, weil nur die ..._blast_search()Funktionen Schleifen über alle Sequenzen enthalten. Dort steht allerdings schon der readdb_get_sequence()-Aufruf im ”nächste Sequenz”-Kontext. Keine zweite Funktion kann zusätzlich in diesem Kontext stehen. Nach der Identifikation der Funktionskontexte beschreiben die folgenden Unterabschnitte die Implementation der kritischen Funktionen. 60 KAPITEL 6. ANPASSUNG VON BLAST 6.1.3.1 Die Modifikation von readdb get sequence() readdb_get_sequence()-Aufrufe stehen in allen drei Kontexten. Deshalb muss die Implementation zweigeteilt werden: • Für den ”nächste Sequenz”-Kontext wird die Funktion db2_get_sequence() neu eingeführt. • readdb_get_sequence() wird für die Verwendung im wahlfreien und im ”aktuelle Sequenz”-Kontext angepasst. Die Funktion db2_get_sequence() holt bei jedem Aufruf das nächste Tupel der SQLAnweisung (Beispiel Proteinsuche) SELECT proteinid,seqtext FROM sequences.protein . Die Attributwerte werden in die globalen Variablen db2rawseq (Sequenz) und db2seqNum (ID) geschrieben. Die Sequenzen in der Datenbank sind Wörter auf dem Nukleotid- bzw. dem AminosäureAlphabet. Da die blastall -Sequenzen kodiert vorliegen, müssen auch die relationalen Datenbanksequenzen kodiert werden. Dafür ist db2convertSeqToNCBIFormat() verantwortlich. Die Funktion wird von db2blast neu eingeführt. Sie prüft unter anderem auch, ob eine übergebene Nukleotidsequenz Mehrdeutigkeitsresiduen enthält. Die folgenden ReadDBFILEElement werden beschrieben: • buffer speichert die kodierte Sequenz. • currSeq speichert die unkodierte Sequenz. • In seqLength wird die Länge der Sequenz abgelegt. • Falls die Sequenz eine Nukleotidsequenz ist und Mehrdeutigkeiten enthält, wird das Element isAmb auf TRUE gesetzt. Im anderen Fall erhält es den Wert FALSE. db2_get_sequence() wird von db2BLASTPerformSearch() aufgerufen. Diese Funktion ist eine Modifikation von BLASTPerformSearchWithReaddb(). Die Funktion hat in den wesentlichen Punkten den gleichen Aufbau (vergleiche Abbildung 6.2) wie das Original. Sie unterscheiden sich darin, dass die neue Funktion die von SQLFetch() gewonnene SequenzID an die aufrufende Funktion ..._blast_search() zurückgibt. Die ursprüngliche Funktion readdb_get_sequence() muss die beiden anderen Kontexte abdecken. Um den ”aktuelle Sequenz”-Kontext vom wahlfreien zu unterscheiden, ermittelt ein Test, ob die übergebene ID der zwischengespeicherten entspricht. Ist dies der Fall, liegt der ”aktuelle Sequenz”-Kontext vor. Dann wird die Sequenz aus dem ReadDBFILE-Element buffer genommen, die Länge aus seqLength. Im anderen Fall liegt der wahlfreie Kontext vor. Für diesen wird die SQL-Anweisung (Beispiel Proteinsuche) SELECT seqtext FROM sequences.protein WHERE proteinid=? 61 KAPITEL 6. ANPASSUNG VON BLAST db2BLASTPerformSearch(searchBlk,*pSeqNum) { /*** den DB-Mutex setzen, falls Multithreading ***/ NlmMutexLock(searchBlk->thr_info->db_mutex); db2_retval = <RUECKGABEWERT der letzten SQLFetch()-Operation> if (db2_retval INDICATES SUCCESS) { /*** Kopieren der SequenzID in ***/ *pSeqNum = search->rdfp->currSeqNum = db2seqNum; /*** mit BLASTPerformSearchWithReadDb identischer Teil ***/ length = db2_get_sequence(searchBlk->rdfp, searchBlk->thr_info->db_mutex, &seq); [...] BLASTPerformSearch(searchBlk, length, seq); } else { /*** Mutex bei Fehler freigeben ***/ NlmMutexUnlock(searchBlk->thr_info->db_mutex); } /*** RUECKGABEWERT = SQLFetch-RUECKGABE ***/ return db2_retval; } Abbildung 6.2: schematischer Ablauf von db2BLASTPerformSearch(). Die Abbildung steht in Beziehung mit Abbildung 4.4. benötigt. Die dafür erforderliche Funktionsfolge SQLBindParameter() → SQLExecute() → SQLBindCol() sowie der SQLFetch()-Aufruf werden in readdb_get_sequence() ausgeführt. Die Sequenz wird hier ebenfalls mittels db2convertSeqToNCBIFormat() kodiert. Außerdem werden die Zwischenergebnisse wie bei db2_get_sequence() in den ReadDBFILE-Elementen gespeichert. Der Aufbau von db2convertSeqToNCBIFormat() wird hier nicht vertieft. Die Funktion bildet lediglich die Sequenzkodierung als Programmcode ab (Abschnitt 4.3.2). 6.1.3.2 Die Modifikation von readdb ambchar present() readdb_ambchar_present() wird ausschließlich im ”aktuelle Sequenz”-Kontext aufgerufen. Die Modifikation der Funktion besteht lediglich darin, den Wert der Variablen isAmb der ReadDBFILE-Struktur zurückzugeben. 6.1.3.3 Die Modifikation von readdb get ambchar() readdb_get_ambchar() wird ebenfalls ausschließlich im ”aktuelle Sequenz”-Kontext aufgerufen, kann also auf Zwischenergebnisse zurückgreifen. Falls isAmb aus der ReadDBFILEStruktur auf Mehrdeutigkeiten hinweist, ruft readdb_get_ambchar() die von db2blast neu 62 KAPITEL 6. ANPASSUNG VON BLAST eingeführte Funktion db2ConstructAmbInfo() auf. Diese liefert zu einer unkodierten Sequenz die Mehrdeutigkeitskodierung (siehe Abschnitt 4.3.2), die im ReadDBFILE-Element ambChars gespeichert wird. 6.1.3.4 Die Modifikation von readdb get sequence length() readdb_get_sequence_length()-Aufrufe stehen ausschließlich im wahlfreien Kontext. Die Funktion verwendet folgende SQL-Anweisung (Beispiel Proteinsuche): SELECT length(seqtext) FROM sequences.protein WHERE proteinid=? Die dafür nötige Funktionsfolge SQLBindParameter() → SQLExecute() → SQLBindCol() → SQLFetch() wird in readdb_get_sequence_length() ausgeführt. Das Ergebnis wird in der Variablen db2seqlen gespeichert. 6.1.3.5 Die Modifikation von readdb get defline ex() Die Funktion gibt in blastall die Kennungszeile einer Sequenz zurück. Im Rahmen von db2blast muss eine solche Kennungszeile so gut wie möglich simuliert werden, um die Ergebnisse von blastall und db2blast vergleichbar zu machen. Der Vergleich geschieht mit Hilfe des BLAST-Reports, wo den Vergleichssequenzkennungen die Alignierungen zugeordnet werden. Bei der Konstruktion einer Kennungszeile müssen zwei Dinge beachtet werden: 1. Um db2blast und blastall zu vergleichen, müssen sie auf denselben Vergleichssequenzen arbeiten. Zu diesem Zweck wird die Datenbankrelation als Datei exportiert und in das FASTA-Format (Anhang B.1) konvertiert. Zur Identifikation der Sequenzen wird die spezielle Kennung ”lcl” (die soviel wie ”lokale Sequenz” bedeutet) verwendet. Die Sequenz mit der ID x (NASeqID bzw. ProteinID) erhält dann folgende FASTAKennung: lcl|x . 2. Während der Formatierung dieser FASTA-Datensammlung durch FormatDB werden die Kennungen modifiziert. Diese Modifikation schlägt sich in der ID-Datei nieder. Die Kennung sieht danach wie folgt aus: gnl|BL_ORD_ID|y lcl|x y ist hier die Ordnungszahl der Sequenz, beginnend bei 0. Diese Ordnungszahl wird intern von BLAST verwendet und erscheint nicht im BLAST-Report. Damit die Kennungen der alignierten Sequenzen eines blastall - und eines db2blast-Lauf vergleichbar sind, wird die Kennung bei db2blast wie folgt konstruiert: gnl|BL_ORD_ID|x lcl|x Das x steht in beiden Teilkennungen für die ID in der jeweiligen Datenbankrelation. Die ID wird der Funktion als Parameter übergeben. Da der gnl-Teil der Kennung im BLASTReport nicht verwendet wird, sind die Reports von blastall und db2blast vergleichbar. 63 KAPITEL 6. ANPASSUNG VON BLAST do_blast_search(searchBlk) { do { db2_retval = db2BLASTPerformSearch(searchBlk,&seq_num); if (db2_retval INDICATES SUCCESS) { BlastReapHitListByEvalue(searchBlk); BlastReevaluateWithAmbiguities(searchBlk, seq_num); BlastSaveCurrentHitlist(searchBlk); } } while (db2_retval INDICATES SUCCESS); [...] } Abbildung 6.3: Schematischer Ablauf von do blast search() im Kontext der db2blastAnpassung. Die Abbildung steht in Beziehung zu Abbildung 4.3. 6.1.4 Anpassungen der BLAST-Hauptroutine Im vorangegangenen Abschnitt wurde darauf hingewiesen, dass eine über alle Vergleichssequenzen laufende Schleife im Rahmen der Anpassung ebenfalls modifiziert werden muss. Dies ist notwendig, damit die Aufrufkontexte der kritischen readdb-Funktionen gültig sind. Die ..._blast_search()-Funktionen enthalten eine solche for-Schleife. Für db2blast wird die for-Schleife durch eine do-while-Schleife ersetzt. Die Funktionen, die in der Schleife nach dem db2BLASTPerformSearch()-Aufruf folgen, erhalten die ID der Sequenz anstatt ihrer Ordnungszahl, welche im blastall -Original durch die Schleifenvariable repräsentiert wird. Der Aufbau der modifizierten do_blast_search-Funktion kann Abbildung 6.3 entnommen werden. Der Wegfall des BlastGetDbChunk()-Funktionsaufrufs ist deshalb sinnvoll, weil in der Datenbankrelation keine Abschnitte einem einzelnen thread zugewiesen werden können. Die restlichen db2blast-Modifikationen rühren daher, dass zwei Funktionen des Moduls blast.c die MMF-Funktion ReadDBCloseMHdrAndSeqFiles() aufrufen: • BLASTSetUpSearchWithReadDbInternal() • do_the_blast_run() Der Aufruf von ReadDBCloseMHdrAndSeqFiles() kann in beiden Fällen einfach weggelassen werden, da die entsprechenden Dateien nicht existieren. 6.1.5 Parallelverarbeitung in db2blast Durch das Entfernen des BlastGetDbChunk()-Aufrufs in den ..._blast_search()-Funktionen ist dem Programm die Verwaltungsstelle des Datenbank-Mutex und damit die Möglichkeit zum multithreading ”genommen”. Dieser Abschnitt analysiert, wie multithreading im Rahmen von db2blast realisiert werden kann. db2blast hat, wie das Originalprogramm, eine geteilte Ressource, und zwar die Datenbankschnittstelle. Konkret ist es die folgende SQL-Anweisung: 64 KAPITEL 6. ANPASSUNG VON BLAST SELECT proteinid,seqtext FROM sequences.protein Im Fall der Ausführung mehrerer threads wird die Anweisung von den Instanzen der Funktion db2_get_sequence() verwendet. Diese wiederum werden von einer konkreten Instanz von ..._blast_search() gerufen. Bei jedem SQLFetch()-Aufruf in db2_get_sequence() werden die gleichen globalen Variablen beschrieben. Der Zugriff auf diese Variablen muss deshalb mittels eines Mutex serialisiert werden. Der dafür in Frage kommende Mutex ist thrinfo->db_mutex aus der BlastSearchBlk-Struktur. Dieser kann verwendet werden, da die ursprüngliche Verwaltungsfunktion BlastGetDbChunk() in db2blast nicht mehr verwendet wird. Der Mutex wird in db2BLASTPerformSearch() gesetzt. Nach dem SQLFetch()-Aufruf und dem Kopieren der Ergebnisattribute in thread -lokale Variablen wird der Mutex in db2_get_sequence() wieder freigegeben. Die thread -lokalen Variablen sind die Elemente von ReadDBFILE, da jeder thread eine eigene Instanz dieser Struktur besitzt. Im Parallelbetrieb arbeiten nur die ..._blast_search()-Funktionen. In diesen Funktionen wird nur die obige SQL-Anweisung benötigt. Deshalb sind die anderen SQL-Anweisungen vom multithreading nicht betroffen. 6.1.6 Die praktische Umsetzung von db2blast Dieser Abschnitt gibt einen kurzen Überblick darüber, wie die in den vorangegangenen Abschnitten beschriebenen Modifikationen praktisch umgesetzt werden. Ausgangspunkt sind zwei neue Quelltextdateien, die die neuen Funktionen beherbergen: 1. db2conn.c enthält zunächst die globalen Variablen sowie die Initialisierungs- und Freigaberoutinen: • db2init() • db2prepareStatements() • db2executeAndBindConstStatements() • db2destruct() Außerdem sind db2_get_sequence() und db2BLASTPerformSearch() enthalten. 2. db2seqHandl.c enthält von der Datenbankverbindung unabhängige Funktionen zum Umgang mit Sequenzen: • db2convertSeqToNCBIFormat() • db2ConstructAmbInfo() Die Funktionen in db2seqHandl.c werden, im Gegensatz zu denen in db2conn.c, auch in der zweiten Anpassung verwendet. Die Funktionsschnittstellen und die globalen Variablendeklarationen werden durch die Datei db2conn.h exportiert. Aus diesem Grund muss diese Datei per #include in die Dateien blast.c und readdb.c eingebunden sein. Die in den vorangegangenen Abschnitten beschriebenen Modifikationen am BLASTQuelltext werden wie folgt integriert: 65 KAPITEL 6. ANPASSUNG VON BLAST • Wird neuer Programmcode in den Quelltext eingefügt, so geschieht dies mittels einer Präprozessor-Definitionsabfrage namens DB2BLAST. Programmcode, der zwischen den Präprozessor-Direktiven #ifdef DB2BLAST und dem dazugehörigen #endif steht, ist eine Modifikation von db2blast. • Wird alter Programmcode im Quelltext durch neuen ersetzt, so wird nach dem neuen Programmcode ein #else eingefügt. Der neue Programmcode endet vor der #elseDirektive, der alte Programmcode endet vor der #endif-Direktive. Mit diesem Schema ist es möglich, sowohl das Original-Programm als auch db2blast zu erzeugen. Beim zweiten Fall wird als Compiler-Option die Präprozessor-Definition DB2BLAST angegeben. Außerdem muss in diesem Fall die Bibliothek db2 mit eingebunden werden, damit eine CLI-Anwendung erzeugt werden kann. 6.1.7 Vergleich des Laufzeitverhaltens von blastall und db2blast Zum Abschluss der CLI-Anpassung soll die Laufzeit von blastall und db2blast verglichen werden. Dazu werden verschiedene Sequenzen (Anfragesequenzen) mit den Sequenzen der Testdatenbank aligniert. Der erste Test führt die Programmvariante blastp aus, die die Sequenzen aus der Relation PROTEIN aus dem Datenbankschema (siehe Abbildung 5.1) als Vergleichssequenzen verwendet. Die PROTEIN-Relation umfasst 261440 Sequenzen. Daraus werden vier Sequenzen verschiedener Länge ausgewählt, die als Anfragesequenzen verwendet werden. Um die Sequenzen auch im Kontext von blastall verwenden zu können, müssen sie aus der Relation in eine Datei exportiert und dort in’s FASTA-Format gebracht werden. Diese Datei wird dann mittels formatdb bearbeitet, damit BLAST die Vergleichssequenzen verwenden kann. Für den Vergleich der beiden Programme wurden nur die Optionen -p, -d, -i und -o (vergleiche Anhang A) verwendet, es wird also standardmäßig eine lückenbehaftete Alignierung durchgeführt. Folgende Tabelle zeigt den Vergleich von blastall und db2blast am Beispiel eines blastp-Laufs mit einem Prozessor (Längen in Bytes, Laufzeiten in Sekunden): Länge der Anfragesequenz 3419 2065 778 240 Laufzeit blastall 231 69 33 18 Laufzeit db2blast 279 121 84 73 Der Unterschied zwischen den Laufzeiten von blastall und db2blast kommt dadurch zustande, dass das modifizierte Programm zum Lesen der Sequenzen auf die relationale Datenbank zugreift. Außerdem müssen die Sequenzen kodiert werden. Die Laufzeitdifferenz liegt bei den betrachteten Sequenzen zwischen 48 und 55 Sekunden. Die Variabilität des Werts rührt daher, dass während der lückenbehafteten Nachbearbeitung noch einmal diejenigen Sequenzen per SQL-Anweisung geholt werden, welche HSPs enthalten. Die Anzahl der betroffenen Sequenzen hängt von der Anfragesequenz ab. blastall und db2blast haben die Fähigkeit, im multithreading-Betrieb ausgeführt zu werden. In beiden Programmen wird dazu die lückenfreie Alignierung parallel ausgeführt. Die folgende Tabelle zeigt den Vergleich der Programme unter Verwendung von acht Prozessoren (Längen in Bytes, Laufzeiten in Sekunden): 66 KAPITEL 6. ANPASSUNG VON BLAST Länge der Anfragesequenz 3419 2065 778 240 Laufzeit blastall 85 10 6 5 Laufzeit db2blast 132 58 57 60 Da der Zugriff auf die Datenbank serialisiert werden muss, ist der Laufzeitunterschied der beiden Programme fast identisch mit dem im Ein-Prozessor-Fall. Im Mehrprozessorfall ist der Unterschied etwas geringer. Das kann damit erklärt werden, dass ein Teil der Berechnungszeit der lückenfreien Alignierung sowie die Kodierung der Sequenzen parallel zum Datenbankzugriff anderer Sequenzen ausgeführt werden. Der zweite Test ist ein blastn-Lauf, der vier Anfragesequenzen unterschiedlicher Länge mit den DNA-Sequenzen der Datenbank vergleicht. Die Sequenzen sind als Tupel in der Relation NASEQUENCE gespeichert, und über einen Join mit der DNA-Relation werden die DNA-Sequenzen ermittelt: SELECT SeqText,NASeqID FROM sequences.nasequence,sequences.dna WHERE naseqid=dnaseqid; Die Relation NASEQUENCE enthält 70398 DNA-Sequenzen. Für den Vergleich von blastall und db2blast müssen auch die DNA-Sequenzen als BLAST-Datensammlung gespeichert und formatiert werden. Für den blastn-Lauf wurden außer den notwendigen Parametern -p, -d, -i und -o keine weiteren Optionen gesetzt. Standardmäßig wird also eine lückenbehaftete Alignierung durchgeführt. Folgende Tabelle zeigt den Vergleich von blastall und db2blast am Beispiel eines blastpLaufs mit einem Prozessor (Längen in Bytes, Laufzeiten in Sekunden): Länge der Anfragesequenz 8152 2566 734 162 Laufzeit blastall 3.98 2.61 1.81 1.87 Laufzeit db2blast 39.41 37.94 36.56 36.90 Interpretiert man den Laufzeitunterschied zwischen den beiden Programmen als Zugriffszeit auf die relationale Datenbank, so beträgt die Ausführungszeit aller Datenbankanweisungen und die Kodierung der Sequenzen rund 35 Sekunden. Für die längste Sequenz ergibt das eine Verzehnfachung der Ausführungszeit, für kürzere Sequenzen liegt der Faktor noch höher. Die Berechnungszeit des Algorithmus ist deutlich kleiner als bei blastp, was primär zwei Gründe hat: 1. Die Kodierung der Residuen als 2bit-Werte führt zu einer kompakteren Speicherung der Nukleotidsequenzen. Da der Algorithmus darauf eingestellt ist, ist auch dessen Laufzeit entsprechend niedriger. 2. Bei der Alignierung der Proteinsequenzen wird eine Bewertungsmatrix, wie z.B. BLOSUM oder PAM, verwendet. Das erfordert zweidimensionale Indexzugriffe. Im Fall von Nukleotidsequenz-Vergleichen wird nur zwischen Übereinstimmungen (matches) und Unterschieden (mismatches) differenziert. Letzteres erfordert nur einfache Vergleichsoperationen, was die Laufzeit ebenfalls günstig beeinflusst. 67 KAPITEL 6. ANPASSUNG VON BLAST Für blastn wird ebenfalls der Multiprozessorvergleich mit acht Prozessoren durchgeführt. Folgende Tabelle zeigt die Laufzeiten (in Sekunden, Längen der Sequenzen in Bytes): Länge der Anfragesequenz 8152 2566 734 162 Laufzeit blastall 0.97 0.62 0.46 0.67 Laufzeit db2blast 26.44 26.22 26.02 26.30 Auffallend ist hier, dass die Laufzeitdifferenz zwischen den beiden Versionen erheblich kleiner ist als im Einprozessorfall. Der Grund liegt darin, dass nur der Zugriff auf die Datenbank serialisiert wird (mittels db_mutex), die Kodierung der Sequenzen aber nicht. Die Kodierung in einem thread läuft also parallel zum Datenbankzugriff in einem anderen thread. Der Aufwand für die Kodierung der Nukleotidsequenzen beträgt also ca. 10 Sekunden. Es ist plausibel, dass der Kodierungsaufwand für eine Nukleotidsequenz höher ist als der für eine gleich lange Proteinsequenz, da im ersteren Fall einzelne Bits belegt und bei Mehrdeutigkeiten zwei Kodierungen durchgeführt werden müssen. Zusammenfassend lässt sich sagen, dass für die BLAST-Varianten blastp und blastn, die die Hauptanwendungen darstellen, der Aufwand für den Datenbankzugriff die Laufzeit von BLAST erheblich erhöht. Im Fall von blastp fällt dieser Aufwand weniger in’s Gewicht, da der Aufwand für die Kodierung relativ gering ist und der Protein-Algorithmus eine relativ hohe Laufzeit hat. Bei blastn führen die Datenbankzugriffe zu deutlich mehr Laufzeit, was an der komplizierteren Kodierung der Nukleotidsequenzen und der niedrigeren Laufzeit des Algorithmus selbst liegt. 6.2 BLAST als benutzerdefinierte Funktion Die Umwandlung von blastall zu einer Call Level Interface-Anwendung ermöglicht es einem Benutzer, BLAST wie bisher als eigenständiges Programm auszuführen. Der Nutzungskontext bleibt gleich. Folgende Einschränkungen müssen dabei gemacht werden: 1. db2blast kann nur verwendet werden, wenn das Programm für die entsprechende Nutzungsumgebung, also die Kombination aus Betriebssystem und Datenbank-Managementsystem, übersetzt worden ist. 2. Die Anwendung von BLAST ist auf ein Datenmodell zugeschnitten. Modifikationen am Datenmodell müssen sich in Änderungen der im Programm enthaltenen SQLAnweisungen niederschlagen. 3. Die Anwendung des Programms ist auf die Fälle beschränkt, die im Programm vorgesehen sind. Sollen beispielsweise nur die RNA-Sequenzen aus der Datenbank untersucht werden, die zu einem bestimmten Organismus gehören, so müsste der BLASTReport manuell untersucht werden. Die Verwendung von db2blast schöpft also die Möglichkeiten nicht aus, die SQL-Anfragen bieten. Aus diesem Grund beschreibt dieser Abschnitt die Umsetzung von BLAST als benutzerdefinierte Funktion (UDF ). Auf diesem Weg wird BLAST stärker in die Datenbank 68 KAPITEL 6. ANPASSUNG VON BLAST UDF-Parameter progType filterQuery gappedBlast expectValue nuclMismatchScore nuclMatchScore matrixName wordSize hits_n_passes queryStrands gapOpenCost gapExtCost dropoffExt dropoffGapped dropoffGappedFinal expandThresh queryGenCode subjGenCode SQL-Datentyp VARCHAR(10) INTEGER INTEGER DOUBLE INTEGER INTEGER VARCHAR(30) INTEGER INTEGER INTEGER INTEGER INTEGER DOUBLE INTEGER INTEGER INTEGER INTEGER INTEGER blastall -Option -p -F -g -e -q -r -M -W -P -S -G -E -y -X -Z -f -Q -D Tabelle 6.1: udfblast-Parameter, die sich direkt aus blastall -Optionen ergeben integriert. Das entstehende ”Produkt” wird im Folgenden mit udfblast bezeichnet, um es von blastall und db2blast zu unterscheiden. Zunächst geht es darum, BLAST als externe Tabellenfunktion zu modellieren. Da eine externe UDF nicht auf die Datenbank zugreifen darf, müssen alle Parameter beim Aufruf oder mittels Dateien übergeben werden. Die Funktion wird so konzipiert, dass jeder (externe) Aufruf der UDF genau eine Vergleichssequenz bearbeitet. Zur Analyse einer kompletten Relation durch BLAST muss eine entsprechende SQL-Anweisung formuliert werden. Darauf wird am Ende des Kapitels eingegangen. Eine Tabellenfunktion eignet sich für die Implementation von BLAST aus dem Grund, dass eine Vergleichssequenz mit der Anfragesequenz mehrere Alignierungen bilden kann, die alle gleichartig strukturiert sind. Eine derartige Struktur kann durch die Attribute (Rückgabespalten) der Funktion abgebildet werden. 6.2.1 Eingabeparameter von udfblast Ausgangspunkt für die Frage nach den Parametern von udfblast sind die Optionen, die blastall auf der Kommandozeile übergeben werden. Die im Anhang A zu findende Optionenliste muss möglichst vollständig in udfblast übernommen werden. Es werden lediglich die Optionen ausgespart, die im Zusammenhang mit udfblast keine Bedeutung haben. Tabelle 6.1 beinhaltet die Parameter, die sich direkt aus blastall -Optionen abbilden lassen. Die Anfrage- und die Vergleichssequenz werden ebenfalls als Parameter übergeben. Da die Datenbank die Vergleichssequenzen als LONG VARCHAR speichert, wird dieser Datentyp auch für die UDF-Parameter verwendet. Die Parameter werden mit querySequence und subjSequence bezeichnet. blastall und db2blast verwenden eine Setup-Datei, die im Arbeitsverzeichnis des Benutzers steht. In dieser Datei ist der Verzeichnis-Pfad der Bewertungsmatrix-Datei abgelegt. 69 KAPITEL 6. ANPASSUNG VON BLAST Die Setup-Datei kann im UDF-Kontext nicht verwendet werden, da die UDF unter einer Benutzerkennung läuft, die vom DBMS reserviert ist. Um dennoch auf die Matrix-Datei zugreifen zu können, wird der Verzeichnispfad als weiterer UDF-Parameter übergeben. Er wird mit matrixFilePath bezeichnet und ist vom Typ VARCHAR(255). Bei der Analyse von blastall als auch bei db2blast werden einige Kennwerte erwähnt, die entweder aus der Index-Datei (blastall ) bzw. mittels einer SQL-Anweisung (db2blast) ermittelt werden: 1. Anzahl der Vergleichssequenzen 2. Summe der Längen aller Vergleichssequenzen 3. Länge der längsten Vergleichssequenz Die ersten beiden dienen der Berechnung normalisierter Alignierungsbewertungen. Der dritte Wert wird zur Reservierung von Speicherbereichen benötigt, die die Vergleichssequenzen aufnehmen. Insbesondere die ersten beiden Kennwerte sind notwendig, um die Ergebnisse von udfblast mit db2blast vergleichbar zu machen. Folglich werden diese drei Kennwerte ebenfalls als Parameter an die benutzerdefinierte Funktion übergeben. Sie heißen count, totlen und maxlen (in der gleichen Reihenfolge wie in obiger Aufzählung) und sind vom SQL-Typ INTEGER. 6.2.2 Rückgabewerte von udfblast Das Ergebnis eines BLAST-Vergleichs von Anfrage- und Vergleichssequenz ist im allgemeinen Fall eine Liste mehrerer Alignierungen. Jede Alignierung ist dabei auf die gleiche Art und Weise strukturiert. Die Struktur einer Alignierung wird im Folgenden als Menge von Attributen mit entsprechenden SQL-Datentypen zu modelliert. Ausgangspunkt ist der im Anhang B.3 beschriebene BLAST-Report, dessen wichtigste Informationen hier übernommen werden. Da ein Ergebnistupel der UDF eine Alignierung darstellt, werden nur die einer Alignierung zuordenbaren Informationen ausgewertet. Tabelle 6.2 enthält die Rückgabeattribute der UDF. Zu den Attributen muss Folgendes angemerkt werden: 1. Bei den Lücken (gaps) werden im Original-BLAST zwei Fälle unterschieden. Wurde mit blastp oder blastn lückenbehaftet aligniert, so werden die Lücken sowohl in der Anfrage- als auch der Vergleichssequenz gezählt. Bei den Varianten blastx und tblastn werden nur die Lücken in der Anfragesequenz gezählt. Dieses Verhalten wird bei udfblast beibehalten, da die Ergebnisse mit blastall vergleichbar sein sollen. 2. blastn gibt im BLAST-Report die Leserichtung von Anfrage- und Vergleichssequenz zurück. Bei der UDF werden entsprechende Werte in queryFrame und subjectFrame zurückgegeben. Der Wert 1 steht dabei für die positive Leserichtung und -1 für die negative. 3. Die BLAST-Varianten blastx, tblastn und tblastx geben das Leseraster von Anfrageund Vergleichssequenz zurück. Bei der UDF werden die entsprechenden Werte in den Attributen queryFrame und subjectFrame zurückgegeben. 70 KAPITEL 6. ANPASSUNG VON BLAST UDF-Attribut bitScore score expectValue alignLength queryOffset subjectOffset queryLength subjectLength queryFrame SQL-Datentyp DOUBLE INTEGER DOUBLE INTEGER INTEGER INTEGER INTEGER INTEGER SMALLINT subjectFrame SMALLINT identPairs positivePairs gaps queryString transitString subjectString INTEGER INTEGER INTEGER LONG VARCHAR LONG VARCHAR LONG VARCHAR Beschreibung normalisierter Wert der Alignierung nominaler Wert der Alignierung Erwartungswert der Alignmentbewertung Länge der Alignierung (Residuen) Startposition der Alignierung in der Anfragesequenz Startposition der Alignierung in der Vergleichssequenz Länge der Alignierung in der Anfragesequenz Länge der Alignierung in der Vergleichssequenz Leserichtung (blastn) bzw. Leseraster (blastx, tblastx) der Anfragesequenz Leserichtung (blastn) bzw. Leseraster (tblastn, tblastx) der Vergleichssequenz Anzahl von Paaren identischer Residuen Anzahl Residuenpaare mit positiver Bewertung Anzahl Lücken alignierter Bereich der Anfragesequenz Ähnlichkeitssequenz der alignierten Sequenzen alignierter Bereich der Vergleichssequenz Tabelle 6.2: udfblast-Parameter, die sich direkt aus blastall -Optionen ergeben 4. transitString stellt eine Art ”Vergleichszeichenkette” dar. Aus ihr kann ersehen werden, welche Residuen der beiden Sequenzen identisch zugeordnet wurden und welche mit positiver Bewertung. 6.2.3 SQL-Deklaration der UDF Die letzten beiden Abschnitte haben die Ein- und Ausgabeparameter beschrieben, die zur Deklaration der benutzerdefinierten Funktion BLAST benötigt werden. Folgende SQLAnweisung deklariert die UDF: CREATE FUNCTION sequences.blast(<Eingabeparameter>) RETURNS TABLE (<Rueckgabewerte>) EXTERNAL NAME ’udfblast!UDF_blast’ LANGUAGE C PARAMETER STYLE DB2SQL NOT DETERMINISTIC FENCED NULL CALL NO SQL EXTERNAL ACTION SCRATCHPAD FINAL CALL DISALLOW PARALLEL NO DBINFO Die Eingabeparameter und die Rückgabewerte wurden in den letzten beiden Abschnitten aufgezählt. Zu den Optionen gibt es folgende Anmerkungen zu machen: • Die Option NULL CALL wird gesetzt, damit die Funktion auch dann aufgerufen wird, wenn einige der Parameter NULL sind. Das Design der Funktion ist von der Art, dass 71 KAPITEL 6. ANPASSUNG VON BLAST einige Parameter NULL sein können und die Funktion trotzdem vernünftige Resultate liefert. • EXTERNAL ACTION wird angegeben, weil die Funktion auf die Bewertungsmatrix-Datei zugreift (matrixName und matrixFilePath). • Das SCRATCHPAD wird verwendet, um zwischen den verschiedenen Aufruftypen der UDF bestimmte Informationen zu konservieren. • FINAL CALL wird in der Implementationsbeschreibung motiviert. Im Wesentlichen werden die Funktionsaufrufe FIRST und FINAL dazu verwendet, einen BLAST-Lauf als Ganzen zu verwalten. 6.2.4 Implementation der UDF Für die Implementation von BLAST als UDF wird zunächst der Ablauf von blastall grob schematisiert. Das Ablaufschema lässt sich wie folgt beschreiben: 1. Initialisierung Der Schritt der Initialisierung wird dadurch charakterisiert, dass Datenstrukturen reserviert und initialisiert werden. In diesem Schritt werden keine Aktionen ausgeführt, die von konkreten Vergleichssequenzen abhängen. Der Schritt der Initialisierung reicht vom Anfang der Main()-Funktion bis zum Aufruf von ..._blast_search() in der Funktion do_the_blast_run(). Die ..._blast_search()-Funktionen selbst können nicht mehr zur Initialisierung gezählt werden, da in ihnen BLAST auf den Vergleichssequenzen ausgeführt wird. 2. BLAST-Alignmentsuche Der Schritt der BLAST-Alignmentsuche ist dadurch gekennzeichnet, dass alle Vergleichssequenzen nach lückenfreien Alignierungen durchsucht werden. Der Schritt umfasst nur die Funktion do_blast_search() bzw. do_gapped_blast_search(). In diesen wird eine Schleife über alle Sequenzen ausgeführt, in der nach den Alignierungen gesucht wird. 3. Nachbearbeitung Die Nachbearbeitung konvertiert die in der BlastSearchBlk-Struktur gespeicherten lückenfreien Alignierungen in eine SeqAlign-Liste. Falls blastall lückenbehaftete Alignierungen bilden soll, werden die lückenfreien HSPs dabei entsprechend erweitert. Dieser Schritt erstreckt sich über den Teil der Funktion BioseqBlastEngineCore(), der nach dessen Aufruf von do_the_blast_run() folgt. 4. Ausgabe der Ergebnisse und Freigabe von Datenstrukturen Dieser Schritt dient dazu, die SeqAlign-Liste für den BLAST-Report aufzubereiten und diesen auszugeben. Gleichzeitig werden die im Programm reservierten und initialisierten Datenstrukturen wieder freigegeben. Dieser Schritt beginnt nach Beendigung von BioseqBlastEngineCore() und reicht bis zum Ende der Main()-Funktion. Zur Implementation dieses Schemas als UDF müssen die einzelnen Schritte den UDFAufruftypen zugeordnet werden. Dabei muss analysiert werden, welche Informationen zwischen den Schritten kommuniziert werden müssen. Die dazu erforderlichen Datenstrukturen 72 KAPITEL 6. ANPASSUNG VON BLAST Abbildung 6.4: Änderung des Ablaufschemas zur Implementation von BLAST als UDF. können mittels des schon erwähnten scratchpad s verwaltet werden. Folgender (Abbildung 6.4) Ablauf wird für die UDF vorgeschlagen: 1. Für den FIRST -Aufruf der UDF eignet sich der Initialisierungsschritt, da dieser von den konkreten Sequenzen unabhängig ist und Initialisierungen für alle Vergleichssequenzen vornimmt. Beim FIRST-Aufruf werden der UDF diejenigen Parameter übergeben, die der ersten Parameter-Kombination in der SQL-Anweisung entsprechen. Der Aufruf initialisiert die BLAST_OptionsBlk- und die BlastSearchBlk-Struktur. Beide werden in den nachfolgenden Aufrufen benötigt. 2. Die UDF ist so konzipiert, dass nur jeweils eine Vergleichssequenz übergeben wird. Soll ein BLAST-Lauf über mehrere Vergleichssequenzen gehen, muss eine entsprechende SQL-Anweisung formuliert werden. Für jede Vergleichssequenz wird dann genau einmal ein OPEN -Aufruf gestartet. Es bietet sich an, die BLAST-Alignmentsuche von genau einer Sequenz im OPEN-Aufruf laufen zu lassen. Dafür muss der Inhalt der Schleife aus ..._blast_search() für die Sequenz ausgeführt werden. Die Nachbearbeitung erfolgt bei blastall nach der Schleife. Dieser Schritt wird ebenfalls in OPEN ausgeführt. Es muss dafür gesorgt werden, dass er nur auf einer Sequenz läuft. Die SeqAlign-Liste wird in den nachfolgenden FETCH -Aufrufen ausgewertet. 3. Das Ergebnis der Nachbearbeitung ist die Kette von SeqAlign-Elementen. Im UDFKontext darf diese Kette nur Alignierungen einer einzigen Vergleichssequenz mit der Anfragesequenz enthalten. Das Traversieren der einzelnen Alignierungen erfolgt im FETCH -Aufruf. Dabei wertet jeder FETCH-Aufruf eine Alignierung aus, formatiert diese und gibt die Alignierung als Ergebnistupel zurück. Der folgende FETCH-Aufruf wertet die nächste Alignierung der gleichen Sequenz aus und so fort. Enthält die SeqAlign-Liste keine unausgewerteten Elemente mehr, gibt die Funktion den Spezialstatus ”keine weiteren Zeilen” (SQLSTATE ”02000”) zurück. Der blastall -Schritt ”Ausgabe der Ergebnisse” wird hier nicht ausgeführt, da er für die Formatierung des 73 KAPITEL 6. ANPASSUNG VON BLAST BLAST-Reports konzipiert ist. Stattdessen wird eine eigene SeqAlign-Auswertung implementiert. 4. Da der Schritt der Nachbearbeitung in der UDF für jede Sequenz einzeln vollzogen wird, müssen entsprechende Datenstrukturen wieder freigegeben werden. Der nächste OPEN-Aufruf reserviert die Strukturen erneut. Zur Freigabe dieser Ressourcen bietet sich der CLOSE -Aufruf an. 5. Die Freigabe von Datenstrukturen ist sinnvollerweise Gegenstand des FINAL-Aufrufs, da erst hier sicher ist, dass in der aktuellen SQL-Anweisung keine weiteren Aufrufe mehr kommen, die die Datenstrukturen eventuell noch benötigen könnten. 6.2.4.1 Der FIRST -Aufruf von udfblast Der FIRST-Aufruf enthält den Initialisierungsteil von BLAST. Die UDF muss so verwendet werden, dass subjSequence (die Vergleichssequenz) der einzige sich ändernde Parameter zwischen zwei OPEN-Aufrufen ist. Die anderen Parameter können bereits beim FIRST-Aufruf ausgewertet werden. Die meisten von ihnen dürfen einen NULL-Wert übergeben. Ausgenommen sind folgende Parameter (es werden die Bezeichner aus der SQLFunktionsdeklaration verwendet): • progType — die Programmvariante • querySequence — die Anfragesequenz • count — Kennwert für die gesamte BLAST-Suche • totLen — Kennwert für die gesamte BLAST-Suche • maxLen — Kennwert für die gesamte BLAST-Suche • matrixFilePath — Verzeichnispfad zur Bewertungsmatrix-Datei Wird für einen dieser Parameter NULL übergeben, so gibt die benutzerdefinierte Funktion einen Fehler in SQLSTATE und eine entsprechende Fehlermeldung zurück. Die Angabe von NULL für einen der anderen Parameter bedeutet, dass für diesen der jeweilige Standardwert (default) von udfblast verwendet wird. Dies ist äquivalent mit dem Verhalten von blastall, wenn der Parameter nicht gesetzt wird. An die Prüfung der Eingabeparameter schließt sich der Initialisierungsteil von BLAST an. Um ihn mit wenig Aufwand aus blastall zu übernehmen, werden die zum Initialisierungsteil gehörenden Anweisungen in den FIRST-Code übernommen. Davon sind diejenigen Funktionen betroffen, die nur zum Teil zur Initialisierung beitragen: 1. Main() aus blastall.c 2. BioseqBlastEngine() 3. BioseqBlastEngineByLoc() 4. BioseqBlastEngineByLocEx() 5. BioseqBlastEngineCore() 74 KAPITEL 6. ANPASSUNG VON BLAST 6. do_the_blast_run() Beim Kopieren des Codes in die UDF müssen einige Änderungen vorgenommen werden: • Es werden drei globale Variablen db2count, db2maxlen und db2totlen eingeführt, die die Werte der UDF-Parameter count, maxLen und totLen aufnehmen. Sie werden von readdb_new_internal() zur Initialisierung der ReadDBFILE-Struktur verwendet. • Die Ein- und Ausgabedateien (Anfragesequenz und BLAST-Report) müssen hier nicht geöffnet werden (wie in Main()), da die entsprechenden Informationen über die UDFSchnittstelle ”übertragen” werden. • Der Test auf die Gültigkeit bestimmter Parameterkombinationen (Main()) kann entfallen, wenn die Parameter nicht Teil der UDF sind. • Im Fall eines Fehlers in einer der BLAST-Funktionen muss die SQLSTATE-Variable gesetzt und eine Fehlermeldung formuliert werden. • Die Einführung des neuen Parameters matrixFilePath zieht ein gleichnamiges Element in der BLAST_OptionsBlk-Struktur nach sich. • In der Main()-Funktion wird mit Hilfe der Funktion FastaToSeqEntryEx() die Anfragesequenz aus der Datei gelesen und verarbeitet. Dies ist im UDF-Kontext nicht möglich. Deshalb wird das Programm so modifiziert, dass die Anfragesequenz aus dem Hauptspeicher gelesen und danach verarbeitet wird. • Bei bestimmten Fallunterscheidungen in Unterfunktionen von Main() werden nur die Teile des Programmcodes übernommen, die ausgeführt werden, wenn blastall die entsprechenden Funktionen aufrufen würde. • Vom Programmcode von do_the_blast_run() wird nur der Teil übernommen, der im Ein-thread -Betrieb verwendet würde. Das multithreading von blastall arbeitet auf der Basis mehrerer Sequenzen. Die Bearbeitung nur einer Sequenz kann nicht parallelisiert werden. Deshalb ist der multithreading-Programmcode in der UDF nicht anwendbar. 6.2.4.2 Der OPEN -Aufruf von udfblast Im OPEN -Aufruf werden die BLAST-Alignmentsuche sowie die Nachbearbeitung vereinigt. Der Aufruf setzt sich aus folgenden Schritten zusammen: 1. Zunächst wird geprüft, ob subjSequence keinen NULL-Wert übergeben hat. 2. Es wird die neue Funktion calcBlast() aufgerufen, die die BLAST-Alignmentsuche und die Nachbearbeitung vereinigt. Der Rückgabewert der Funktion ist ein Zeiger auf die SeqAlign-Liste. 3. Ist die SeqAlign-Liste nicht leer, wird eine udfblast-eigene Nachbearbeitung durchgeführt. Um in den nachfolgenden FETCH-Aufrufen die Alignierungstupel zu erzeugen, müssen die Anfrage- und die Vergleichssequenz vorliegen, und zwar in allen Leserastern. Für die Anfragesequenz existieren Leseraster-Sequenzen in Unterstrukturen der BlastSearchBlk-Struktur. Für die entsprechende Vergleichssequenz 75 KAPITEL 6. ANPASSUNG VON BLAST calcBlast(searchBlk,optionsBlk,sequence,seqLength) { [ angepasster Code aus db2_get_sequence() ] [ angepasster Code aus db2BlastPerformSearch() ] if (GAPPED && !BLASTN) [ angepasster Code aus do_gapped_blast_search() ] else [ angepasster Code aus do_blast_search() ] [ angepasster Code aus BioseqBlastEngineCore, nach dem do_the_blast_run()-Aufruf ] } Abbildung 6.5: Schematisierter Ablauf von calcBlast() müssen diese gebildet werden. Die übersetzten Sequenzen werden in einem Feldelement namens subjSeqsTransl[] des scratchpad s abgelegt, weil sie in den nachfolgenden FETCH-Aufrufen benötigt werden. Je nach BLAST-Programmvariante wird die Übersetzung wie folgt durchgeführt: • Bei blastp und blastx zeigt das 1. Element des subjSeqsTransl[]-Felds auf das ReadDBFILE-Element buffer, da es die kodierte Sequenz enthält. Bei diesen Programmvarianten werden keine Übersetzungen der Vergleichssequenz durchgeführt. • Im Fall von blastn werden die ersten beiden Elemente des subjSeqsTransl[]Felds mit den beiden Leserichtungen der Vergleichssequenz beschrieben. Für die positive Leserichtung wird der UDF-Parameter subjSequence herangezogen und byteweise in das BLASTna-Format kodiert. Die negative Leserichtung der Sequenz wird, am hinteren Ende beginnend, mit den komplementären Residuen beschrieben. Dazu wird ein Konvertierungsfeld namens NAcompl_tab[] verwendet, dass zu jedem BLASTna-Wert (als Feldindex) den BLASTna-Wert des komplementären Residuums verwendet. • tblastn und tblastx müssen die Vergleichssequenz in die sechs Leseraster übersetzen. Dazu wird die Funktion BlastTranslateUnambiguousSequence() aus dem Modul blastutl.c verwendet. Der schematische Aufbau der Funktion calcBlast() kann Abbildung 6.5 entnommen werden. Dazu seien einige Bemerkungen gemacht: • Der angepasste db2_get_sequence()-Code unterscheidet sich vom Original im Wesentlichen darin, dass hier die CLI-Funktionsaufrufe und deren Behandlung weggelassen werden. Die Mutex-Funktionen werden ebenfalls nicht aufgerufen. • Die db2BlastPerformSearch()-Modifikation besteht darin, die Mutex-Funktionsaufrufe und die Prüfung von CLI-Funktionsergebnissen zu entfernen. Außerdem wird die Funktion db2_get_sequence() nicht aufgerufen, da deren äquivalenter Code bereits vorher abgearbeitet wurde. 76 KAPITEL 6. ANPASSUNG VON BLAST UDF_blast([...]) { [...] switch(UDF_CALLTYPE) { [...] case FETCH: if (ALIGNMENT AVAILABLE?) { switch(SeqAlign.segtype) { case SAS_DENDIAG: getScoresFromSeqAlign(...); getAlignScalarsFromDD(...); getAuxAlignStringsFromDD(...); compileResidueStatsAndTransStr(...); [TRAVERSIEREN der SeqAlign-Kette] case SAS_DENSEG: [...] case SAS_STDSEG: [...] } strcpy(SQLSTATE,"00000"); } else strcpy(SQLSTATE,"02000"); break; [...] } } Abbildung 6.6: Ablauf des FETCH-Aufrufs am Beispiel von DenseDiag-Alignierungen. Für die anderen Alignierungstypen werden gleichartige Funktionen aufgerufen. • Aus den Funktionen ..._blast_search() wird nur derjenige Teil des Quelltexts übernommen, der bei einem Aufruf durch blastall ausgeführt würde. Die Abarbeitung von db2BLASTPerformSearch() wird aus der Anpassung herausgenommen, da dessen äquivalenter Code bereits vor der Fallunterscheidung abgearbeitet wurde. • Der BioseqBlastEngineCore()-Code, der nach dem do_the_blast_run()-Aufruf ausgeführt wird, ändert sich bei der Anpassung dahingehend, dass die Fälle, die für blastall nicht relevant gewesen wären, hier ebenfalls wegfallen. 6.2.4.3 Der FETCH -Aufruf von udfblast Der FETCH-Aufruf dient dazu, die SeqAlign-Liste auszuwerten und bei jedem Aufruf ein Alignierungstupel zu liefern. Der Aufbau der SeqAlign-Struktur wurde bereits ausführlich im Abschnitt 4.6 erläutert. Deshalb werden an dieser Stelle lediglich die neu eingeführten Funktionen und ihr Zusammenspiel im FETCH-Aufruf (Abbildung 6.6) beleuchtet. Die aufgerufenen Funktionen haben folgende Aufgaben: • getScoresFromSeqAlign() ist eine vom Alignmenttyp unabhängige Funktion, die aus einer Score-Struktur die verschiedenen alignment-Bewertungen ausliest. Ergeb77 KAPITEL 6. ANPASSUNG VON BLAST nis dieses Aufrufs ist die Belegung der UDF-Rückgabewerte score, bitScore und expectValue. • getAlignScalarsFromDD() ermittelt die folgenden UDF-Rückgabewerte aus einer DenseDiag-Alignierung: 1. alignLength 2. queryOffset und subjectOffset 3. queryLength und subjectLength 4. queryFrame und subjectFrame Für die Alignmenttypen DenseSeg und StdSeg werden entsprechende Funktionen implementiert (getAlignScalarsFromDS() und getAlignScalarsFromSS()). • getAuxAlignStringsFromDD() ermittelt für den Typ DenseDiag die Zeichenketten für Anfrage- und Vergleichssequenz, die an der Alignierung beteiligt sind. Die Ergebnisse sind allerdings noch kodiert, da sie in der nachfolgenden Funktion zur Berechnung weiterer Werte verwendet werden. Für DenseSeg und StdSeg werden entsprechende Funktion implementiert. • compileResidueStatsAndTransStr() berechnet aus den kodierten Zeichenketten die folgenden UDF-Rückgabewerte: 1. queryString und subjectString 2. transitString 3. identPairs, positivePairs und gaps Der Ablauf ist für alle Alignierungstypen gleich mit dem Unterschied, dass die jeweils zum Datentyp passenden Funktionen aufgerufen werden. Die Traversierung zur nächsten Alignierung hängt von den Elementen type und segtype des aktuellen SeqAlign-Listenelements ab. Zeiger auf das erste und das aktuelle Element der SeqAlign-Liste sind Teil des scratchpad s, da sie die Information von einem FETCH-Aufruf zum nächsten weitergeben. 6.2.4.4 Der CLOSE -Aufruf von udfblast Im CLOSE-Aufruf werden diejenigen Ressourcen wieder freigegeben bzw. zurückgesetzt, die bei OPEN reserviert und initialisiert wurden. CLOSE besteht aus folgenden Schritten: 1. Rücksetzen des Elements result_struct aus der BlastSearchBlk-Struktur Während des Ausführung der ..._blast_search()-Funktionen wird diese Variable zur Zwischenspeicherung von Alignierungen verwendet. Da calcblast() für jede Sequenz neu aufgerufen wird, müssen die Speicherbereiche jedesmal freigegeben werden. 2. Freigabe der Übersetzungen der Vergleichssequenz Die Feldelemente der Variablen subjSeqsTransl geben ihren Speicherplatz frei. 3. Freigabe aller Elemente der SeqAlign-Liste Da der Nachbearbeitungsschritt für jede Vergleichssequenz erneut aufgerufen wird, muss die SeqAlign-Listenstruktur an dieser Stelle freigegeben werden. 78 KAPITEL 6. ANPASSUNG VON BLAST 6.2.4.5 Der FINAL-Aufruf von udfblast Der FINAL-Aufruf dient der Freigabe von Ressourcen, die in allen Zwischenaufrufen der UDF verwendet wurden. Im wesentlichen sind dies die Strukturen BlastSearchBlk und BLAST_OptionsBlk. Nach dem FINAL-Aufruf werden von der UDF oder dem scratchpad keine Ressourcen mehr verwaltet. 6.2.5 Die konkrete Umsetzung von udfblast Neben den hier beschriebenen Änderungen zur Anpassung enthält Anhang C.4 noch weitere Details, die zur Lauffähigkeit der UDF notwendig sind. Basis der udfblast-Implementation ist die Quelltextdatei udfblast.c, die die neuen Funktionen beherbergt: • calcBlast() • getScoresFromSeqAlign() • compileResidueStatsAndTransStr() • getAlignScalarsFromDD() • getAuxAlignStringsFromDD() • getAlignScalarsFromDS() • getAuxAlignStringsFromDS() • getAlignScalarsFromSS() • getAuxAlignStringsFromSS() • die UDF UDF_blast() Die Funktionsschnittstellen und die globalen Variablendeklarationen werden durch die Datei udfblast.h exportiert. Aus diesem Grund muss diese Datei per #include in die Dateien blast.c und readdb.c eingebunden sein. In den vorangegangenen Abschnitten wurden einige Modifikationen an BLAST-Funktionen vorgenommen. Mit dem Programmcode wird wie folgt verfahren: • Wird neuer Programmcode in den Quelltext eingefügt, so geschieht dies mittels einer Präprozessor-Definitionsabfrage namens UDFBLAST. Programmcode, welcher zwischen einer der Präprozessor-Direktiven #ifdef UDFBLAST oder #ifdef DB2BLAST und dem dazugehörigen #endif steht, gehört zu udfblast. • Wird alter Programmcode im Quelltext durch neuen ersetzt, so wird nach dem neuen Programmcode ein #else eingefügt. Der neue Programmcode endet vor der #elseDirektive, der alte Programmcode vor der #endif-Direktive. Um UDFs verwenden zu können, müssen sie Teil einer dynamischen Funktionsbibliothek (shared library) sein. Diese wird im Verzeichnis der DB2-Instanz im Unterverzeichnis /sqllib/function/ abgelegt. Am Beispiel des cc-Compilers von SUN sollen die Optionen erläutert werden, die zur Erzeugung von udfblast benötigt werden: 79 KAPITEL 6. ANPASSUNG VON BLAST 1. Zur Übersetzung von Quelltextdateien für eine dynamische Bibliothek muss beim ccCompiler die Option -Kpic angegeben werden. Diese sorgt für die Erzeugung von positionsunabhängigem Code. Damit kann dieser von einer beliebigen Anwendung dynamisch gebunden und aufgerufen werden. Die Anzahl Funktionen, die mit der Option -Kpic übersetzt werden kann, ist allerdings begrenzt (in der verwendeten Umgebung 2048). udfblast enthält insgesamt über 7000 Funktionen. Es genügt, diejenige Datei mit -Kpic zu übersetzen, die die benutzerdefinierte Funktion enthält, also udfblast.c. 2. Es müssen zwei Präprozessordefinitionen gesetzt werden: -DDB2BLAST -DUDFBLAST 3. Für das Binden der übersetzten Objektdateien zu einer dynamischen Bibliothek muss die Option -G verwendet werden. 4. Um die Anzahl der globalen Symbole zu reduzieren, wird die Option -M <mapfile> gesetzt. <mapfile> ist eine Datei, die angibt, welche Funktionen in der globalen Symboltabelle stehen sollen. Im vorliegenden Fall betrifft dies nur die Funktion UDF_blast(), die als UDF verwendet werden soll. 5. Es müssen die Bibliotheken db2 und db2apie als Optionen übergeben werden, um eine UDF-Bibliothek zu erzeugen. 6.2.6 Verwendung von udfblast In diesem Abschnitt wird ein Beispiel für den Einsatz von udfblast gegeben. Zu beachten ist, dass der einzige variable UDF-Parameter innerhalb der SELECT-Anweisung die Vergleichssequenz subjSequence sein darf. Desweiteren werden bei der Initialisierung der UDF einige Kennwerte übergeben, die vorher ermittelt werden müssen. Folgendes SQL-Skript demonstriert die Anwendung der UDF: DECLARE GLOBAL TEMPORARY TABLE seq_stats (count INTEGER, totLen INTEGER, maxLen INTEGER) NOT LOGGED IN usertemptabspace ON COMMIT PRESERVE ROWS WITH REPLACE; INSERT INTO session.seq_stats SELECT COUNT(*), SUM(LENGTH(seqtext)), MAX(LENGTH(seqtext)) FROM sequences.protein; SELECT FROM SeqT.proteinid, <all attributes from FctT> sequences.protein as SeqT, session.seq_stats as StatT, table( sequences.blast( ’blastp’, <query sequence> <further blast options> 80 KAPITEL 6. ANPASSUNG VON BLAST StatT.count, StatT.totLen, StatT.maxLen, SeqT.seqtext ) ) as FctT; Dazu müssen einige Bemerkungen gemacht werden: 1. Die Kennwerte der BLAST-Suche werden vor dem ersten Aufruf bestimmt und in einer temporären Tabelle abgespeichert. Eine solche Tabelle muss in einem benutzerdefinierten, temporären Tabellenbereiche (USER TEMPORARY SPACE ) abgelegt werden. Dieser wird zweckmäßigerweise bei der Initialisierung der Datenbank angelegt. Das Schema einer solchen Tabelle ist, sofern nicht anders angegeben, session. 2. Die Kennwerte der BLAST-Suche erhält man mittels der Anweisung SELECT COUNT(*), SUM(LENGTH(seqtext)), MAX(LENGTH(seqtext)) FROM ... Dabei ist zu beachten, dass genau der gleiche Suchraum benutzt wird wie in der nachfolgenden SQL-Anweisung mit UDF-Aufruf. Das Ergebnis dieser Anweisung ist genau ein Tupel, das in der temporären Tabelle abgelegt wird (INSERT INTO). 3. Die SELECT-Anweisung sollte neben allen Attributen der Tabellenfunktion auch die ID der jeweiligen Vergleichssequenz zurückgeben, damit jede Alignierung einer Sequenz zuordenbar ist. 4. Die Tabellenfunktion wird extern für jede Vergleichssequenz einmal aufgerufen, da die temporäre Tabelle session.seq_stats nur ein Tupel besitzt. Damit ist die Bedingung erfüllt, dass der Parameter subjSequence der einzig variable während der SQLAnweisung ist. Jeder dieser Aufrufe liefert eine Tabelle von Alignierungen zurück, die von der Anweisung zu einer gemeinsamen Tabellen vereinigt werden. 5. Die Parameter <query sequence> und <further blast options> müssen konstant sein und dürfen von keiner der anderen beiden Tabellen in der FROM-Klausel abhängen. Das Ergebnis der letzten Anweisung ist eine Liste aller Alignierungen der übergebenen Anfragesequenz mit allen Vergleichssequenzen. Mittels einer WHERE-Bedingung oder einer verschachtelten SQL-Anweisung könnten weitere Einschränkungen des Suchraums gemacht werden. Dabei ist zu beachten, dass die gleichen Einschränkungen bei der Ermittlung der drei Kennwerte count, totLen und maxLen gemacht werden. Ist das SQL-Skript beendet, wird auch die temporäre Tabelle wieder freigegeben. Die temporäre Tabelle ist sitzungslokal, wird also von anderen Anwendungen oder Skripten nicht ”gesehen”. 81 Kapitel 7 Ausblick BLAST ist ein approximativer Algorithmus zur Bestimmung von Alignierungen jeweils zweier Sequenzen. Dazu wird eine Anfragesequenz mit einer Menge von Vergleichssequenzen aligniert. Die Vergleichssequenzen sind als Datei gespeichert. Im Rahmen dieser Diplomarbeit wurden zwei Anpassung vorgestellt, die den Algorithmus in ein relationales Datenbanksystem integriert. Die erste Anpassung hat die Integration mittels der Programmierschnittstelle Call Level Interface implementiert. Folgende Erweiterungen werden für die CLI-Modifikation vorgeschlagen: 1. Im Originalprogramm blastall sind die Sequenzen mit Kennungen gespeichert, die Auskunft über die Herkunft der Sequenzen gibt. Im Datenmodell der Genomdatenbank (siehe Anhang D) sind ebenfalls Informationen gespeichert, die die Sequenzherkunft festlegen. Diese Informationen könnten durch spezielle SQL-Anweisungen ebenfalls ausgewertet werden, so dass sie im BLAST-Report dargestellt werden können. 2. Im Programm db2blast müssen die Vergleichssequenzen bei jedem Aufruf des Programms kodiert werden, da sie in den Datenbankrelationen ”im Klartext” abgelegt sind. Um die Kodierung nicht in jedem BLAST-Lauf durchführen zu müssen, bietet es sich an, die Sequenzen aus den Relationen PROTEIN und NASEQUENCE nur einmal zu kodieren und die so modifizierten Sequenzen in speziellen Relationen abzulegen. Für die Kodierung könnte eine externe skalare UDF implementiert werden. 3. Die Laufzeiten von db2blast bestehen zu einem wesentlichen Teil aus der Datenbankzugriffszeit. Zu dessen Minimierung können Konzepte zum Zugriff auf die entsprechenden Relationen verwendet werden, wie z.B. die Partitionierung der Datenbank. 4. db2blast hat keine höhere Flexibilität als blastall. Wie das Original durchsucht auch db2blast alle Vergleichssequenzen. Dazu werden fest implementierte SQL-Anweisungen verwendet. Das Programm könnte eine höhere Flexibilität erfahren, indem ihm beispielsweise eine WHERE-Klausel als Parameter übergeben wird. Die WHEREKlausel würde dazu dienen, den Suchraum einzuschränken. Die zweite Anpassung bestand in der Implementation von blastall als benutzerdefinierte Funktion. Für die UDF werden folgende Erweiterungen vorgeschlagen: 82 KAPITEL 7. AUSBLICK 1. Das Paradigma der verschiedenen Aufruftypen sorgt dafür, dass bei jedem Aufruf einer UDF die gleichen Parameter übergeben werden. Bei udfblast werden die BLASTParameter nur für den FIRST-Aufruf benötigt. Die OPEN-Aufrufe benötigen lediglich die jeweilige Vergleichssequenzen. Zur Verbesserung der Ausführungsgeschwindigkeit böte sich deshalb die Implementation von BLAST mit Hilfe von drei UDFs an. Die Funktionen implementieren folgende Schritte von BLAST: • Die erste Funktion (”init”) führt die Initialisierung von BLAST aus. Diese Funktion würde den FIRST-Aufruf der ursprünglichen Funktion ersetzen. Sie benötigt die BLAST-Parameter und liefert eine Referenz auf die Initialisierungsstrukturen zurück. • Die Referenz der ersten Funktion wird der zweiten Funktion (”calc”) übergeben. Diese Funktion erhält zusätzlich die jeweilige Vergleichssequenz. Sie implementiert die Schritte OPEN, FETCH und CLOSE der ursprünglichen UDF und hat die Alignierungen als Rückgabewerte. • Die dritte Funktion (”destruct”) erhält ebenfalls die Rückgabereferenz der ersten Funktion und implementiert den FINAL-Schritt der ursprünglichen UDF. Die Implementation dient dazu, die Laufzeit eines BLAST-Laufs dadurch zu verbessern, dass nicht bei jedem Aufruf die gesamte Menge der Parameter übergeben wird. Voraussetzung ist, dass die drei Funktionen als NOT FENCED deklariert werden, damit sie im Betriebssystem-Prozess ausgeführt werden und damit die DatenstrukturReferenz zwischen den Aufrufen gültig bleibt. 2. Einige Parameter der UDF könnten direkter übergeben werden. Folgende Parameter sind davon betroffen: • Die Parameter matrixFilePath und matrixName spezifizieren eine Datei, die die Bewertungsmatrix enthält. Um die UDF enger in das Datenbanksystem zu integrieren, könnte das Datenmodell dahingehend erweitert werden, dass die Bewertungsmatrizen in einer speziellen Relation als Large Objects (LOB s) gespeichert werden. Sie können dann auch der UDf als LOBs übergeben werden. • Die Parameter für die genetischen Codes sind Zahlen, die einen bestimmten Code repräsentieren (vergleiche Anhang A.1). Für die genetischen Codes existiert im Datenmodell bereits eine Relation. Deshalb könnten diese direkt an das Programm übergeben werden. Die beiden Anpassungen stellen zwei Extreme der Anpassung dar: In einem Fall werden über einfachste SQL-Anweisungen die Vergleichssequenzen aus der Datenbank entnommen, um sie in einem vollständig externen Programm zu verwenden. Im zweiten Fall wird das komplette Programm als eine benutzerdefinierte Funktion implementiert, die jeweils eine Anfrage- mit einer Vergleichssequenz aligniert. Beide Varianten belassen die BLASTSchritte in ihrem funktionalen Zusammenhang. Als Erweiterung wäre es allerdings denkbar, die einzelnen Schritte von BLAST getrennt, d.h. in eigenen UDFs, zu implementieren, und diese dann über SQL-Anweisungen zu verknüpfen. Dies bietet dem Datenbankmanagementsystem die Möglichkeit, mehr Einfluss auf den den Ablauf auszuüben und diesen damit aus Datenbanksicht zu optimieren. 83 Anhang A blastall -Kommandozeilenoptionen Parameter -p Name Programmvariante Typ string -d Datenbankname string -i Anfragesequenzdatei string -o BLAST-Report-Datei string -e Erwartungswert double -F T/F -g Anfrage filtern blastn = DUST andere = SEG gapped alignments -q Nukleotid-mismatch int -r Nukleotid-match int -M Matrix string T/F 84 Bedeutung mögliche Werte sind: blastp blastn blastx tblastn tblastx Name der Datenbankdatei(en) (z.B. ecoli.aa). Hier können auch Aliasdatenbanken angegeben werden. Dateiname der Anfragesequenz Standard : stdin Dateiname des BLAST-Reports Standard : stdout Für den Nominalwert jedes Sequenzalignments wird ein Erwartungswert berechnet. Sequenzen, deren ”beste” Alignierung einen kleineren Erwartungswert hat als der hier angegebene Wert, werden mit ihren Alignierungen im BLAST-Report ausgegeben. Standard : 10.0 Soll die Anfragesequenz gefiltert werden? (T = ja, F = nein) Standard : T Sollen lückenbehaftete Alignierungen gebildet werden? (nicht für tblastx) (T = ja, F = nein) Standard : T Kosten für alignierte, nicht-identische Residuen (nur blastn) Standard : -3 Wert für alignierte, identische Residuen (nur blastn) Standard : 1 Bewertungsmatrix für Proteinalignierungen Standard : BLOSUM62 ANHANG A. BLASTALL-KOMMANDOZEILENOPTIONEN -G Wert Lücke-Öffnen int -E Wert Lücke-Erweitern int -W Wortgröße int -f Hit-Expansionsschwelle int -Q gen.Code Anfrage int -D gen.Code DB int int -P -S Leserichtungen Anfrage int -y dropoff double -X dropoff gapped int -Z dropoff gapped final int -U Kleinbuchstabenfilter T/F -z Effektive DB-Länge int 85 Kosten, im Alignment eine Lücke zu öffnen (0 bedeutet Standardverhalten). Standard : 0 Kosten, im Alignment eine Lücke zu erweitern (0 bedeutet Standardverhalten). Standard : 0 Vor der Expansion werden Hits dieser Länge in der Vergleichssequenz gesucht (0 bedeutet Standardverhalten). Standard : 0 Ein w-mer der Länge -W hat mindestens diesen Wert mit einem Teilwort der Anfragesequenz. (0 bedeutet Standardverhalten) Standard : 0 Genetischer Code für die Übersetzung der Anfragesequenz (nur blastx und tblastx). Über die möglichen Werte gibt der nächste Abschnitt Auskunft. Standard : 1 Genetischer Code, der für die Übersetzung jeder Vergleichssequenz wird (nur tblastn und tblastx). Die möglichen Werte können dem folgenden Abschnitt entnommen werden. Standard : 1 0 = 1-Pass, mehrere benachbarte Hits für Expansion benötigt (Standard ) 1 = 1-Pass, ein Hit für Expansion genügt 2 = 2-Pass Leserichtungen der Anfragesequenz, die bearbeitet werden sollen (nur blastn, blastx und tblastx). 1 = positive Leserichtung 2 = negative Leserichtung 3 = beide (Standard ) Wert, um den die aktuelle Erweiterung kleiner ist als der während dieser Erweiterung gefundene maximale Wert (0.0 bedeutet Standardverhalten). Standard : 0.0 wie -y, für angehängte lückenbehaftete Alignierungen (0 bedeutet Standardverhalten). Standard : 0 wie -y, allerdings während der lückenbehafteten Alignierung (0 bedeutet Standardverhalten). Standard : 0 Ist diese Option gesetzt (T), dann werden Kleinbuchstaben in der Anfragesequenz als Regionen geringer Komplexität betrachtet und entsprechend vom Vorfilter (SEG) behandelt. Sie können als keine Hits bilden, werden in der späteren Expansion aber wieder als normale Residuen betrachtet. Standard : F Gibt die effektive Größe der Datenbank als Anzahl Residuen an. 0 bedeutet die tatsächliche Größe. Standard : 0 ANHANG A. BLASTALL-KOMMANDOZEILENOPTIONEN -Y Effektive Suchraumgröße int -K culling int -I GI’s im BLAST-Report string -l GI-Datei string -m BLAST-Report-Ausgabe int -v #DB-Seq.-Beschreibung int -b #DB-Seq.-Alignments int -T HTML-Ausgabe T/F -J korrekte Anfragekennung T/F -O SeqAlign-Datei string -a Anzahl Prozessoren int 86 Gibt die effektive Größe des Suchraums als Anzahl Residuen an. 0 bedeutet die tatsächliche Größe. Standard : 0 Anzahl der besten hits einer Region, die als Ergebnis bleiben. Falls mehr als -K HSPs einen Hit in der gleichen Region haben, werden die ”schlechteren” aus dem Ergebnis genommen. Falls der Parameter verwendet werden soll, wird 100 empfohlen. 0 bedeutet, daß der Parameter nicht verwendet wird. Standard : 0 (bedeutet AUS) Die Kennung einer Sequenz besteht aus mehreren Komponenten. Eine ist meist die GI-ID. Wenn diese im BLAST-Report neben den anderen IDs erscheinen soll, muß hier T übergeben werden, sonst F. Standard : F Dieser Parameter zeigt auf eine Datei, in der pro Zeile eine GI-ID steht. In BLAST werden dann nur die Vergleichssequenzen mit diesen GI-IDs verwendet. Darstellung der Alignierungen im BLAST-Report. Standard : 0 (paarweise Darstellung) Anzahl Vergleichssequenzen, für die deren Alignmentbewertung im BLAST-Report ausgegeben werden. Standard : 500 Anzahl Vergleichssequenzen, für die die konkreten Alignments im BLAST-Report ausgegeben werden. Standard : 250 Ist die Option gesetzt (T), wird ein BLASTHTML-Report erstellt. Standard : F Ist die Option gesetzt, so wird die Kennung der Anfragesequenz, die in der FASTA-Datei steht, als korrekte Kennung anerkannt. Diese Option ist für die folgende Option von Bedeutung. Standard : F Die Alignierungen können ASN.1-kodiert in eine Datei geschrieben werden. Dazu muß die Option -J auf ”T” gesetzt sein. Da BLAST multithreadingfähig ist, kann hier die Anzahl zu verwendender Prozessoren übergeben werden. Standard : 1 ANHANG A. BLASTALL-KOMMANDOZEILENOPTIONEN A.1 Genetische Codetabellen Folgende Tabelle stellt die verschiedenen genetischen Codes dar. Die Nummer wird bei den blastall -Optionen -Q und -D angegeben. Nummer 1 2 3 4 5 6 9 10 11 12 13 14 15 16 21 22 23 Name Standard Vertebrate Mitochondrial Yeast Mitochondrial Mold Mitochondrial, Protozoan Mitochondrial, Coelenterate Mitochondrial, Mycoplasma, Spiroplasma Invertebrate Mitochondrial Ciliate Nuclear, Dasycladacean Nuclear, Hexamita Nuclear Echinoderm Mitochondrial Euplotid Nuclear Bacterial Alternative Yeast Ascidian Mitochondrial Flatworm Mitochondrial Blepharisma Macronuclear Chlorophycean Mitochondrial Trematode Mitochondrial TAG-Leu, TCA-Stop Thraustochytrium mitochondrial code 87 Anhang B Aufbau der BLAST-Reportdateien Sowohl im ursprünglichen BLAST -Programm als auch in der modifizierten Version werden einige Dateiformate verwendet, deren Aufbau Gegenstand dieses Anhangs sein soll. B.1 Das FASTA-Format Das FASTA-Dateiformat wurde vom Alignment-Algorithmus FASTA [27] eingeführt und etabliert. Im BLAST-Kontext wird es von ”Datenbank”-Dateien verwendet und dient BLAST als Eingabeformat, welches vom Programm FormatDB gelesen und für die Benutzung durch BLAST formatiert wird. Das FASTA-Format ist ASCII-lesbar und wird zeilenweise bearbeitet. Jeweils zwei aufeinanderfolgende Zeilen spezifizieren eine Biosequenz. In der ersten Zeile steht die Kennung der Sequenz, in der zweiten die Sequenz selbst. Eine FASTA-Datei hat demnach folgendes Aussehen: 1. Zeile: >[Kennung von Sequenz 1] 2. Zeile: [Sequenz 1] 3. Zeile: >[Kennung von Sequenz 2] 4. Zeile: [Sequenz 2] usf. Die Sequenzen sind Buchstabenfolgen, d.h. es werden die Alphabete der Tabellen 2.2 für Nukleinsäure-Residuen und 2.3 für Aminosäure-Residuen verwendet. Für Nukleinsäuren ist zu beachten, daß das Zeichen ”X” für das entsprechende Mehrdeutigkeitsresiduum nicht erlaubt ist, hier muß stattdessen ”N” benutzt werden, ansonsten werden die ”X”e aus den Sequenzen gelöscht. B.2 FormatDB-Ausgabedateien Nach der Formatierung einer FASTA-Datenbank-Datei mit FormatDB entstehen drei Dateien, die die Eingabe für BLAST bilden. Die folgenden Unterabschnitte beschreiben diese Dateitypen. 88 ANHANG B. AUFBAU DER BLAST-REPORTDATEIEN Abbildung B.1: Zusammenhang der von FormatDB formatierten Dateien B.2.1 Die FormatDB-Kennungendatei In dieser Datei werden die Kennungen der Sequenzen aus der FASTA-Datei abgelegt. Jeder Eintrag bezieht sich auf eine Sequenz. Sei y die Kennunge der x. Sequenz (wobei x bei 0 beginnt), so ist der Kennungseintrag dieser Sequenz gnl|BL_ORD_ID|x y. Die Einträge stehen hintereinander in der Datei, es gibt keine Trennungszeichen und keine Zeilenumbrüche. Um einen Eintrag zu lesen, muß zuvor auf die entsprechende Index-Datei zugegriffen werden (siehe dazu Abschnitt B.2.3). Die Reihenfolge der Kennungseinträge in dieser Datei entspricht der Sequenz-Reihenfolge in der FASTA-Datei. B.2.2 Die FormatDB-Sequenzdatei In dieser Datei werden die Sequenzen aus der FASTA-Datei abgelegt. Die Sequenzen sind allerdings nicht im ASCII-Format abgelegt, sondern werden vor der Speicherung in diese Datei kodiert. Zur Kodierung sei auf Abschnitt 4.3.1 verwiesen. Die kodierten Sequenzen stehen hintereinander und sind nicht durch spezielle Trennungszeichen oder Zeilenumbrüche separiert. Um einen Eintrag zu lesen, muß zuvor auf die entsprechende Index-Datei zugegriffen werden (siehe dazu folgenden Abschnitt). Die Reihenfolge der Sequenzeinträge entspricht der Sequenz-Reihenfolge in der FASTA-Datei. 89 ANHANG B. AUFBAU DER BLAST-REPORTDATEIEN B.2.3 Die FormatDB-Indexdatei Diese Datei enthält Datenbankkennwerte sowie Dateizeiger-Indizes auf die Kennungs- und die Sequenz-Dateien (Abbildung B.1). Wie diese beiden Dateien ist auch die Index-Datei eine Binärdatei. Die folgende Tabelle zählt zunächst die Kennwerte und ihren Speicherbedarf in der Datei auf. Die Kennwerte stehen in ebendieser Reihenfolge in der Datei. Länge 4 Bytes 4 Bytes 4 n 4 m 4 4 4 Bytes Bytes Bytes Bytes Bytes Bytes Bytes Datentyp Integer Integer Integer String Integer String Integer Integer Integer Beschreibung FormatDB-Versionsnummer Art der Sequenzen (0 = Nukleotid, 1 = Protein) Länge des Titels (n) Titel der Datenbank Länge des Erstellungsdatums (m) Erstellungszeitpunkt der Datenbank durch FormatDB Anzahl Sequenzen (k) Länge der Datenbank (Anzahl Residuen) Länge der längsten Sequenz Darauf folgen k + 1 Dateizeigereinträge in die zugehörige Kennungsdatei (im Folgenden IDZeiger). Die ersten k Einträge zeigen auf die Kennungen der k Sequenzen, der (k + 1)te Eintrag zeigt hinter die Kennung der letzten Sequenz. Die Länge der Kennung der i-ten Sequenz kann demnach mit der Formel IDZeiger[i + 1] − IDZeiger[i] berechnet werden. Jeder Zeiger-Eintrag ist ein Integerwert von vier Byte Größe. Auf die Zeiger in die Kennungsdatei folgen die Dateizeigereinträge in die Sequenz-Datei (im Folgenden SeqZeiger). Dies sind ebenfalls k + 1 Einträge, welche auf die Sequenzen selbst zeigen. Der (k + 1)-te Eintrag zeigt hinter die letzte Sequenz. Die Länge der (kodierten) Sequenz kann somit nach der Formel SeqZeiger[i + 1] − SeqZeiger[i] berechnet werden. Jeder Zeiger-Eintrag ist ein Integerwert von vier Byte Größe. Falls in der ”Datenbank” Nukleotid-Sequenzen gespeichert sind, folgt ein weiterer Block von Dateizeigereinträgen, welche ebenfalls in die Sequenz-Datei zeigen (AmbZeiger). Solche Sequenzen können sogenannte Mehrdeutigkeitsresiduen enthalten, also solche, die für mehrere Elementarresiduen (vergleiche Tabelle 2.2) stehen können. Eine Nukleotid-Sequenz ist daher zweigeteilt kodiert. Diese Kodierungen sind in Abschnitt 4.3.2 erklärt. Der zweite Teil, die Mehrdeutigkeitskodierung, wird mit diesen AmbZeigern referenziert. Auch hier gibt es k + 1 Einträge. Mit der Formel SeqZeiger[i + 1] − AmbZeiger[i] kann die Länge der Mehrdeutigkeitskodierung der Sequenz i bestimmt werden; ist diese 0, dann besitzt die Sequenz nur eindeutige Residuen. Jeder Zeiger-Eintrag ist ein Integerwert von vier Byte. An dieser Stelle sei darauf hingewiesen, daß die Integerwerte in dieser Datei als Big Endian gespeichert sind. Das bedeutet, daß das höchstwertige Byte zuerst gespeichert ist und das niedrigstwertige zuletzt. Liest man, wie hier nötig, einen 4-Byte-Wert aus einer Datei, so wird normalerweise das erste gelesene Byte als niedrigstwertiges und das vierte Byte als höchstwertiges Byte interpretiert (Little Endian). Folglich muß jeder 4-Byte-Wert nach dem Lesen byteweise gedreht werden. 90 ANHANG B. AUFBAU DER BLAST-REPORTDATEIEN B.2.4 readdb-Kennwertefunktionen Die Dateien der vorangegangenen Unterabschnitte werden von readdb_new_internal() gelesen. Insbesondere die Indexdatei-Kennwerte dienen der ReadDBFILE-Initialisierung. Die folgenden readdb-Funktionen liefern die Kennwerte für eine oder mehrere Datenbank(en): • readdb_get_dblen() – Gesamtlänge aller Sequenzen (Anzahl Residuen) • readdb_get_totals_ex() – liefert Gesamtlänge aller Sequenzen und deren Anzahl • readdb_get_totals() – wie readdb_get_totals_ex(), berücksichtigt keine AliasDatenbanken • readdb_get_num_entries_total() – Anzahl der Sequenzen aller Datenbanken in der verketteten ReadDBFILE-Liste • readdb_get_num_entries_total_real() – wie readdb_get_num_entries_total(), berücksichtigt keine virtuellen Datenbanken • readdb_get_num_entries() – Anzahl der Sequenzen der Datenbank, welche zur übergebenen ReadDBFILE-Struktur gehört • readdb_get_maxlen() – Länge der längsten Sequenz • readdb_get_filename() – Dateiname der Datenbank • readdb_get_title() – Titel der Datenbank • readdb_get_date() – Erstellungsdatum der Datenbank-Datei • readdb_is_prot() – TRUE, falls Datenbank Proteinsequenzen enthält, FALSE, sonst • readdb_get_formatdb_version() – Versionsnummer von FormatDB, welches die Datenbankdatei formatiert hat B.3 BLAST-Reportdateien Dieser Abschnitt beschreibt das Aussehen des BLAST-Reports, wenn blastall mit der Standard-Ausgabeoption -m 0 aufgerufen wird. Ein BLAST-Report (Abbildung B.2) besteht aus drei Arten von Informationen: 1. einer Vergleichssequenz zuordenbare Informationen 2. einer Alignierung zuordenbare Informationen 3. dem BLAST-Lauf zuordenbare Informationen Die einer Vergleichssequenz zuordenbaren Informationen stellen eine Zusammenfassung der Alignierungen dar. Jede Sequenz, die relevante Alignierungen gebilet hat, wird in einer Liste dargestellt. Die Reihenfolge der Sequenzen wird dabei durch den Erwartungswert von deren jeweils ”bester” Alignierung bestimmt: je kleiner der Erwartungswert, desto 91 ANHANG B. AUFBAU DER BLAST-REPORTDATEIEN [...] Query= TestProtein (8192 letters) Database: DNA 1407 sequences; 1,551,788 total letters Score (bits) Sequences producing significant alignments: [... ERSTER TEIL ...] lcl|480001170 43 E Value 4e-004 [... ZWEITER TEIL ...] >lcl|480001170 Length = 3524 Score = 32.1 bits (71), Expect = 0.90 Identities = 20/84 (23%), Positives = 43/84 (50%), Gaps = 7/84 (8%) Frame = +3 Query: 7521 QQKANEVEQMIRDLEASIARYKEEYAVLISEAQAIKADLAAVEAKV--NRSTA-----LL 7573 ++ +N + + I++LE+ + K + L++E +K LA + + + S+A + Sbjct: 75 KENSNTLSEQIKNLESELNSSKIKNESLLNERNLLKEMLATSRSSILSHNSSAGNIDDKM 254 Query: 7574 KSLSAERERWEKTSETFKNQMSTI 7597 KS+ EK E ++N+M+ I Sbjct: 255 KSIDESTRELEKNYEVYRNEMTAI 326 [... DRITTER TEIL ...] Database: DNA Posted date: 20.1.2002, 18:24:28 Number of letters in database: 1,551,788 Number of sequences in database: 1407 Lambda 0.319 Gapped Lambda 0.270 K H 0.135 0.392 K H 0.0470 0.230 Matrix: BLOSUM62 Gap Penalties: Existence: 11, Extension: 1 Number of Hits to DB: 13760138 [...] Abbildung B.2: Aufbau der einzelnen Abschnitte des BLAST-Reports am Beispiel eines lückenbehafteten tblastn-Laufs 92 ANHANG B. AUFBAU DER BLAST-REPORTDATEIEN weiter oben in der Liste. Jede Zeile der Liste enthält die Kennung einer Sequenz, den normalisierten Wert und den Erwartungswert von deren ”bester” Alignierung. Daran schließen sich die Alignierungsinformationen an. Der Liste der Alignierungen gehen die Zeilen >x Length = y voraus, die die Kennung (x) und die Länge (y) der Vergleichssequenz enthalten. Jede Alignierung wird dabei durch die folgenden Informationen beschrieben: 1. Der mit ”Score” bezeichnete Wert mit der Einheit ”bits” ist die normalisierte Bewertung der Alignierung dar. 2. Hinter dem ”bits”-Wert steht in Klammern der nominale Wert der Alignierung. 3. ”Expect” ist die Anzahl der zufällig zu erwartenden Alignierungen mit dem angegebenen normalisierten Wert oder einem besseren. 4. Bei ”Identities: x/y” ist x die Anzahl von identischen Residuenpaaren und y die Länge der Alignierung. 5. Das x in ”Positives: x/y” (wird nicht von blastn verwendet) ist die Anzahl der Residuenpaare, die einen positiven Zuordnungswert haben. 6. Bei ”Gaps: x/y” ist x die Anzahl der Lücken in der Alignment. Dieser Wert steht nur in Reports von lückenbehafteten BLAST-Läufen. Bei blastn und blastp werden die Lücken in beiden beteiligten Sequenzen gezählt, bei blastx und tblastn zählen nur die Lücken in der Anfragesequenz. 7. Die Angabe von ”Frame: x” steht bei blastx für das Leseraster der Anfragesequenz und bei tblastn für das der Vergleichssequenz. 8. Bei tblastx gibt es Angabe ”Frame: x/y”. Sie steht für die Leseraster der beteiligten Sequenzen. x steht für das Leseraster der Anfragesequenz und y für das der Vergleichssequenz. 9. Bei blastn wird die Angabe ”Strands: x/y” gemacht. Sie steht für die Leserichtungen der Sequenzen. x ist die Leserichtung der Anfragesequenz in dieser Alignierung, y die der Vergleichssequenz. ”Plus” steht für die positive und ”Minus” für die negative Leserichtung. 10. Eine Alignierung, deren Länge mehr als 60 Zeichen beträgt, wird zur besseren Lesbarkeit im BLAST-Report umgebrochen. Würde man die einzelnen Stücke wieder zusammenfügen, erhielte man die Alignierung. Die oberste Zeichenkette ist der alignierte Teil der Anfragesequenz, die unterste Zeichenkette der der Vergleichssequenz. Dazwischen steht eine ”Ähnlichkeitszeichenkette”, die Auskunft über die einzelnen alignierten Zeichen gibt. Steht in diesem string ein Buchstabe, so ist das Residuum in den beiden Sequenz identisch, steht hier ein Plus, so wird das Residuenpaar von der Bewertungsmatrix positiv bewertet. In blastn wird nur zwischen Leerzeichen und ”|” unterschieden. Letzteres steht dort für identische Residuen. 93 ANHANG B. AUFBAU DER BLAST-REPORTDATEIEN 11. Vor und hinter jeder Zeile (maximal 60 Zeichen) der Alignierung stehen Zahlen, die auf die Position der Alignierung innerhalb der Sequenzen hinweist. Von vorn nach hinten sind die Zahlen aufsteigend, wenn blastp oder in den anderen Varianten eine positive Leserichtung vorliegt, und absteigend, falls die Leserichtung (und damit bei den translatierten Versionen auch das Leseraster) negativ sind. Nach den Alignierungen folgt der dritte Teil des BLAST-Reports. Dieser gibt einen statistischen Überblick über den gesamten BLAST-Laufs. 94 Anhang C UDF-Entwurfsdetails Dieser Anhang behandelt Details, die bei Entwurf und Implementation der benutzerdefinierten Funktion verwendet wurden. Er komplettiert damit sowohl die Analyse als auch die Umsetzung der UDF-Anpassung. C.1 Typen von Alignierungen Im Abschnitt 4.6 wurde der Datentyp StdSeg erläutert, weil er unter den Alignierungstypen derjenige mit der größten Komplexität ist. Dieser Abschnitt komplettiert lediglich die Analyse, indem die beiden anderen Alignierungstypen dargestellt werden. C.2 DenseDiag-Alignierungen DenseDiag-Elemente sind die einfachste Form, Alignments zu speichern. Die Struktur hat den in Abbildung C.1 dargestellten Aufbau. • dim hat die gleiche Bedeutung wie das gleichnamige SeqAlign-Element. • Das Feld starts besteht aus dim Elementen (hier 2) und enthält die Start-Offsets der Alignierung der beiden Sequenzen. Der erste Wert bezeichnet den Offset in der Anfragesequenz, der zweite Wert den in der Datenbank-Vergleichssequenz. typedef struct dendiag { short int dim; SeqId *id; long int *starts; long int len; char *strands; Score *scores; struct dendiag *next; } DenseDiag, *DenseDiagPtr; Abbildung C.1: Aufbau der Datenstruktur DenseDiag 95 ANHANG C. UDF-ENTWURFSDETAILS typedef struct denseg { short int dim, numseg; SeqId *ids; long int *starts; long int *lens; char *strands; Score *scores; } DenseSeg, *DenseSegPtr; Abbildung C.2: Aufbau der Datenstruktur DenseSeg • Die Länge der Alignierung ist im Element len gespeichert. Hier genügt für beide beteiligten Sequenzen ein gemeinsamer Wert, da DenseDiag nur lückenfreie Alignierungen verwendet wird. Folglich haben die beiden Subsequenzen die gleiche Länge in der Alignierung. • strands ist wie starts ein Feld mit dim Elementen, das die Leserichtungen der beiden Sequenzen in Alignierung enthält. Für blastp sind diese Werte 0. Für blastn sei auf den Abschnitt ist ein Element 1, falls die positive Leserichtung vorliegt, und 2 für die negative Leserichtung. • Die Variable scores zeigt auf eine verkettete Score-Liste. Der Aufbau von Score wird im Abschnitt 4.6.2 erläutert. Die Verwendung von DenseDiag geht bei BLAST immer mit dem Wert type = 2 aus SeqAlign einher. Deshalb stehen die Alignments einer Sequenz in einer gemeinsamen DenseDiag-Liste. Der hier vorhandene next-Zeiger zeigt auf die nächste Alignierung der Vergleichssequenz. Ist keine weitere Alignierung vorhanden, hat next den Wert NULL. C.3 DenseSeg-Alignierungen Die Datenstruktur DenseSeg dient der Speicherung lückenbehafteter Alignierungen und wird von blastn und blastp verwendet. Ihr Aufbau ist in Abbildung C.2 dargestellt. dim hat die gleiche Bedeutung wie bei DenseDiag. numseg enthält die Anzahl der Segmente der Alignierung. Zum Begriff des Segments sei auf den Abschnitt 4.6.1 verwiesen. Die anderen Elemente haben folgende Bedeutung: • starts ist ein Feld mit 2 · numseg Elementen. starts[2*k] ist der Offset des Segments k in der Anfragesequenz, starts[2*k+1] der Offset in der Vergleichssequenz. Besteht für eine der beiden Sequenzen das Segment aus einer Lücke, so ist der Wert des entsprechenden start-Feldelements −1. • len ist ein Feld von numseg Elementen und enthält die Längen der Segmente. • strands enthält wie starts ebenfalls 2 · numseg Elemente. Jedes Segment könnte also pro Sequenz eine eigene Leserichtung besitzen. In der Praxis ist das nicht der 96 ANHANG C. UDF-ENTWURFSDETAILS Fall. Folglich kann die Leserichtung der beiden Sequenzen aus den Feldelementen strands[0] und strands[1] ermittelt werden. Zu den Werten dieser Elemente sei auf den vorigen Abschnitt verwiesen. • Das Feld scores ist hier nicht von Interesse, da der Alignmenttyp DenseSeg bei BLAST immer zusammen mit dem SeqAlign-Elementwert type = 3 auftritt und deshalb der Score in der SeqAlign-Struktur selbst zu finden ist. Die weiteren Alignierungen der Vergleichssequenz können durch Traversieren der SeqAlignListe erreicht werden. C.4 Weitere Anpassungen der UDF Damit UDFBLAST einsetzbar ist, müssen neben den im Abschnitt 6.2.4 beschriebenen Modifikationen noch weitere Änderungen vorgenommen werden: 1. Einige Funktionen von BLAST sind als static deklariert. Diese sind nur von anderen Funktionen verwendbar, die im gleichen Modul definiert sind. Durch die Übernahme von BLAST-Code in den FIRST- oder OPEN-Aufruf werden diese Funktionen nun von ”außerhalb” aufgerufen, und durch ihre Deklaration nicht mehr gefunden. Deshalb muss für folgende Funktionen die Deklaration geändert werden: • FastaReadSequenceInternalEx() • BlastReevaluateWithAmbiguities() • BLASTResultFreeHsp() 2. Einige der von db2blast eingeführten Präprozessordirektiven der Art #ifdef DB2BLAST müssen modifiziert werden, da der dort enthaltene Code auf CLI-Typen und -Funktionen zurückgreift. Auch wenn der Code niemals im Rahmen der UDF ausgeführt würde, darf dieser nicht kompiliert werden, weil die CLI-Typen und -Funktionen nicht bekannt sind. Die Direktiven ändern sich zu #if defined(DB2BLAST) && !defined(UDFBLAST) 3. In BLASTSetUpSearchWithReadDbInternal() wird die Variable matrixFilePath von der BLAST_OptionsBlk-Struktur in eine Unterstruktur von BlastSearchBlk kopiert. 4. Die Funktion BlastSaveCurrentHitlist(), welche im Rahmen der BLAST-Alignierung ausgeführt wird, fügt die Alignierungen einer Vergleichssequenz in das Feld result_struct->results ein, welches ein Element der BlastSearchBlk-Struktur ist. In diesem Feld stehen die Alignierungen der bisher betrachteten Vergleichssequenzen. Dieses Feld ist immer nach der Bewertung der Alignments sortiert. Da die UDF nur eine Vergleichssequenz zu verarbeiten hat, wird die Suche nach dem Ort der Einsortierung in das Feld nicht ausgeführt, stattdessen wird immer der Index 0 für das Feld gesetzt. 5. Die Funktion BlastScoreBlkMatFill() wird dazu verwendet, die Matrix-Datei auszulesen. Der Verzeichnispfad, der sich in matrixFilePath befindet, muß vorn an den Dateinamen angehängt werden. 97 ANHANG C. UDF-ENTWURFSDETAILS 6. Während der Anpassung von db2blast werden einige Funktionen als kritisch betrachtet, die auf die Index-Dateien von blastall zugreifen. Diese Funktionen könnten in Rahmen von udfblast ebenfalls als kritisch gelten, wenn sie im ”nächste Sequenz”Kontext oder im ”wahlfreien”-Kontext stehen würden. Die einzigen Funktionsaufrufe, die dafür in Frage kommen, sind der von readdb_get_sequence() am Anfang der Funktionen ..._blast_search() sowie der von readdb_get_sequence_length(). Der erste Aufruf wird ersetzt durch die explizite Sequenzverarbeitung in calcBlast, und die Verwendung von readdb_get_sequence_length() kann sich nur auf die aktuelle Sequenz beziehen, da nur diese verarbeitet wird. Die Funktion wird deshalb so modifiziert, dass sie bei udfblast immer den Wert des ReadDBFILE-Elements seqLength zurückgibt. 98 Anhang D Relationales Datenmodell 99 Literaturverzeichnis [1] Altschul, S. F., W. Gish, W. Miller, E. W. Myers und D. J. Lipman: Basic Local Alignment Search Tool. Journal of Molecular Biology, 215:403–410, 1990. [2] Altschul, S. F., T. L. Madden, A. A. Schäffer, J. Zhang, Z. Zhang, W. Miller und D. J. Lipman: Gapped BLAST and PSI-BLAST: a new generation of protein database search programs. Nucleic Acids Research, 25(17):3389–3402, 1997. [3] Bairoch, A.: The PROSITE dictionary of sites and patterns, its current status. Nucleic Acids Research, 21(13):3097–3103, Juli 1993. [4] Barton, G. J.: Protein Sequence Alignment and Database Scanning. erschienen in: M. J. E. Sternberg (Hrsg.): Protein Structure Prediction — a practical approach, 1997. [5] Bellman, R. E.: Dynamic Programming. Princeton University Press, Princeton, N.J., 1957. [6] Bucher, P. und A. Bairoch: A generalized profile syntax for biomolecular sequence motifs and its function in automatic sequence interpretation. In: Proceedings of the 2nd International Conference on Intelligent Systems for Molecular Biology, Seiten 53–61, Menlo Park, CA, 1994. AAAI Press. [7] Chamberlin, D. D.: A Complete Guide to DB2 Universal Database. Morgan Kaufmann Publishers, Inc., San Francisco, California, 1998. [8] Chamberlin, D. D. und R. F. Boyce: SEQUEL: A Structured English Query Language. In: Proceedings of the ACM SIGFIDET Workshop on Data Description, Access, and Control, Seiten 249–264, Ann Arbor, MI, Mai 1974. ACM. [9] Codd, E. F.: A Relational Model of Data for Large Shared Data Banks. Communications of the ACM, 13(6):377–387, Juni 1970. [10] Cornish-Bowden, A.: Nomenclature for incompletely specified bases in nucleic acid sequences: recommendations 1984. Nucleic Acids Research, 13(9):3021–3030, Mai 1985. [11] Dayhoff, M. O., R. M. Schwartz und B. C. Orcutt: A Model of Evolutionary Change in Proteins. Atlas of Protein Sequence and Structure, 5(3):345–352, 1978. [12] Gotoh, O.: An improved algorithm for matching biological sequences. Journal of Molecular Biology, 162(3):705–708, 1982. 100 LITERATURVERZEICHNIS [13] Henikoff, S. und J. G. Henikoff: Amino acid substitution matrices from protein blocks. In: Proceedings of the National Academy of Sciences of the USA, Band 89, Seiten 10915–10919, November 1992. [14] International Business Machines: IBM DB2 Universal Database: Application Development Guide, 2000. [15] International Business Machines: IBM DB2 Universal Database: Call Level Interface Guide And Reference, 2000. [16] International Business Machines: IBM DB2 Universal Database: SQL Reference, 2000. [17] International Organization for Standardization: ISO/IEC 9075:1992/: Information Technology — Database Language SQL. International Organization for Standardization, Genf, 1992. [18] International Organization for Standardization: ISO/IEC 9075-3:1995/: Information Technology — Database Languages — SQL — Part3: Call Level Interface (SQL/CLI). International Organization for Standardization, Genf, 1995. [19] International Organization for Standardization: ISO/IEC 8824-1:1998/: Information Technology — Abstract Syntax Notation One (ASN.1) — Specification of basic notation. International Organization for Standardization, Genf, Zweite Auflage, 1998. [20] IUPAC-IUB Commission On Biochemical Nomenclature: A one-letter notation for amino-acid sequences, tentative rules. Journal of Biological Chemistry, 243:3557–3559, 1968. [21] Karlin, S. und S. F. Altschul: Methods for assessing the statistical significance of molecular sequence features by using general scoring schemes. In: Proceedings of the National Academy of Sciences of the USA, Band 87, Seiten 2264–2268, 1990. [22] Lackie, J. M. und J. Dow (Herausgeber): The Dictionary of Cell & Molecular Biology. Academic Press, London, Dritte Auflage, 1999. [23] Lewin, B.: Genes. Oxford University Press, Oxford, Fünfte Auflage, 1994. [24] Löffler, G. und P. E. Petrides: Biochemie und Pathobiochemie. Springer-Verlag, Berlin Heidelberg New York, Fünfte Auflage, 1999. [25] Needleman, S. B. und C. D. Wunsch: A general method applicable to the search for similarities in the amino acid sequence of two proteins. Journal of Molecular Biology, 48:443–453, 1970. [26] Pearson, W. R. und D. J. Lipman: Rapid and sensitive protein similarity searches. Science, 22:1435–1441, März 1985. [27] Pearson, W. R. und D. J. Lipman: Improved tools for biological sequence comparison. In: Proceedings of the National Academy of Sciences of the USA, Band 85, Seiten 2444–2448, 1988. 101 LITERATURVERZEICHNIS [28] Smith, T. F. und M. S. Waterman: Identification of common molecular subsequences. Journal of Molecular Biology, 147:195–197, 1981. [29] Wootton, J. C. und S. Federhen: Statistics of local complexity in amino acid sequences and sequence databases. Computers and Chemistry, 17(2):149–163, 1993. [30] Zhang, Z., A. A. Schäffer, W. Miller, T. L. Madden, D. J. Lipman, E. V. Koonin und S. F. Altschul: Protein sequence similarity searches using patterns as seeds. Nucleic Acids Research, 26(17):3986–3990, 1998. 102 Danksagungen Bei der Anfertigung dieser Arbeit haben mir viele Leute mit Rat und Tat zur Seite gestanden. Zuallererst danke ich Prof. Johann-Christoph Freytag für die Zuweisung des Themas und wichtige Anregungen und Hinweise. Ferner möchte ich mich bei Chokri Ben Necib für die Betreuung und die Diskussionen bedanken. Peter Rieger hat mir mit seinen Hinweisen zu benutzerdefinierten Funktionen viel Zeit und Frust bei der Fehlersuche erspart. Jeannine Rettschlag danke ich für ihre Einführung in die Biochemie und die Beantwortung diesbezüglicher Fragen. Sebastian Marek hat meine drängenden LATEX-Probleme oft mit einer Antwort oder einem \usepackage aus der Welt geschafft. Oliver Bierwagen hat mir ein paar wichtige Tips für die Präsentation gegeben. Nicht zuletzt möchte ich meinen Eltern für die Hinweise, die Stil und Gesamtbild meiner Arbeit betreffen, danken. 103 Ich erkläre, diese Diplomarbeit selbständig und nur unter Verwendung der angegebenen Literatur und Hilfsmittel angefertigt zu haben. Ich bin mit der Auslage der Arbeit in der Bibliothek der HumboldtUniversität zu Berlin einverstanden. Berlin, den 19. Februar 2002 104