C ++ - eine Verbesserung gegenüber der FUZxxl-Lösung
Ich verdiene absolut keine Anerkennung für die Berechnungsmethode selbst, und wenn sich in der Zwischenzeit kein besserer Ansatz zeigt, sollte das Kopfgeld zu Recht an FUZxxl gehen.
#define _CRT_SECURE_NO_WARNINGS // a Microsoft thing about strcpy security issues
#include <vector>
#include <string>
#include <fstream>
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <cstring>
using namespace std;
#include "divsufsort.h"
// graceful exit of sorts
void panic(const char * msg)
{
cerr << msg;
exit(0);
}
// approximative timing of various steps
struct tTimer {
time_t begin;
tTimer() { begin = time(NULL); }
void print(const char * msg)
{
time_t now = time(NULL);
cerr << msg << " in " << now - begin << "s\n";
begin = now;
}
};
// load input pattern
unsigned char * read_sequence (const char * filename, int& len)
{
ifstream file(filename);
if (!file) panic("could not open file");
string str;
std::string line;
while (getline(file, line)) str += line;
unsigned char * res = new unsigned char[str.length() + 1];
len = str.length()+1;
strcpy((char *)res, str.c_str());
return res;
}
#ifdef FUZXXL_METHOD
/*
* Compute for all k the number of k-mers. kmers will contain at index i the
* number of (i + 1) mers. The count is computed as an array of differences,
* where kmers[i] == kmersum[i] - kmersum[i-1] + 1 and then summed up by the
* caller. This algorithm is a little bit unclear, but when you write subsequent
* suffixes of an array on a piece of paper, it's easy to see how and why it
* works.
*/
static void count(const unsigned char *buf, const int *sa, int *kmers, int n)
{
int i, cl, cp;
/* the first item needs special treatment */
/*
kuroi neko: since SA now includes the null string, kmers[0] is indeed 0 instead of 1
*/
// kmers[0]++;
for (i = 1; i < n; i++) {
/* The longest common prefix of the two suffixes */
cl = n - (sa[i - 1] > sa[i] ? sa[i - 1] : sa[i]);
#ifdef ICAP
cl = (cl > ICAP ? ICAP : cl);
#endif
for (cp = 0; cp < cl; cp++)
if (buf[sa[i - 1] + cp] != buf[sa[i] + cp])
break;
/* add new prefixes to the table */
kmers[cp]++;
}
}
#else // Kasai et al. method
// compute kmer cumulative count using Kasai et al. LCP construction algorithm
void compute_kmer_cumulative_sums(const unsigned char * t, const int * sa, int * kmer, int len)
{
// build inverse suffix array
int * isa = new int[len];
for (int i = 0; i != len; i++) isa[sa[i]] = i;
// enumerate common prefix lengths
memset(kmer, 0, len*sizeof(*kmer));
int lcp = 0;
int limit = len - 1;
for (int i = 0; i != limit; i++)
{
int k = isa[i];
int j = sa[k - 1];
while (t[i + lcp] == t[j + lcp]) lcp++;
// lcp now holds the kth longest commpn prefix length, which is just what we need to compute our kmer counts
kmer[lcp]++;
if (lcp > 0) lcp--;
}
delete[] isa;
}
#endif // FUZXXL_METHOD
int main (int argc, char * argv[])
{
if (argc != 2) panic ("missing data file name");
tTimer timer;
int blen;
unsigned char * sequence;
sequence = read_sequence(argv[1], blen);
timer.print("input read");
vector<int>sa;
sa.assign(blen, 0);
if (divsufsort(sequence, &sa[0], blen) != 0) panic("divsufsort failed");
timer.print("suffix table constructed");
vector<int>kmers;
kmers.assign(blen,0);
#ifdef FUZXXL_METHOD
count(sequence, &sa[0], &kmers[0], blen);
timer.print("FUZxxl count done");
#else
compute_kmer_cumulative_sums(sequence, &sa[0], &kmers[0], blen);
timer.print("Kasai count done");
#endif
/* sum up kmers differences */
for (int i = 1; i < blen; i++) kmers[i] += kmers[i - 1] - 1;
timer.print("sum done");
/* human output */
if (blen>10) blen = 10; // output limited to the first few values to avoid cluttering display or saturating disks
for (int i = 0; i != blen; i++) printf("%d ", kmers[i]);
return 0;
}
Ich habe einfach Kasai et al. Algorithmus zur Berechnung von LCPs in O (n).
Der Rest ist eine bloße Anpassung des FUZxxl-Codes, wobei hier und da präzisere C ++ - Funktionen verwendet werden.
Ich habe den ursprünglichen Berechnungscode hinterlassen, um Vergleiche zu ermöglichen.
Da die langsamsten Prozesse die SA-Konstruktion und die LCP-Anzahl sind, habe ich die meisten anderen Optimierungen entfernt, um zu vermeiden, dass der Code für vernachlässigbare Gewinne überladen wird.
Ich habe die SA-Tabelle um das Präfix Null erweitert. Das erleichtert die LCP-Berechnung.
Ich habe keine Option zur Längenbeschränkung bereitgestellt. Der langsamste Prozess ist jetzt die SA-Berechnung, die nicht verkleinert werden kann (oder zumindest sehe ich nicht, wie es sein könnte).
Ich habe auch die Option für die binäre Ausgabe entfernt und die Anzeige auf die ersten 10 Werte beschränkt.
Ich gehe davon aus, dass dieser Code nur ein Proof of Concept ist, sodass keine überfüllten Displays oder gesättigten Festplatten erforderlich sind.
Erstellen der ausführbaren Datei
Ich musste das gesamte Projekt (einschließlich der Lite-Version vondivsufsort ) für x64 kompilieren , um das Win32 2Gb-Zuweisungslimit zu überwinden.
divsufsortCode gibt eine Reihe von Warnungen aus, da ints anstelle von size_ts häufig verwendet wird. Dies ist jedoch kein Problem für Eingaben unter 2 GB (für die ohnehin 26 GB RAM erforderlich wären: D).
Linux Build
Kompilieren main.cppund divsufsort.cVerwenden der Befehle:
g++ -c -O3 -fomit-frame-pointer divsufsort.c
g++ -O3 main.cpp -o kmers divsufsort.o
Ich divsufsortgehe davon aus, dass die reguläre Bibliothek unter nativem Linux einwandfrei funktionieren sollte, solange Sie etwas mehr als 3 GB zuweisen können.
Aufführungen
Der Kasai-Algorithmus erfordert die inverse SA-Tabelle, die 4 weitere Bytes pro Zeichen für insgesamt 13 verbraucht (anstelle von 9 mit der FUZxxl-Methode).
Der Speicherverbrauch für den Referenzeingang liegt somit über 3 GB.
Andererseits wird die Rechenzeit dramatisch verbessert, und der gesamte Algorithmus befindet sich jetzt in O (n):
input read in 5s
suffix table constructed in 37s
FUZxxl count done in 389s
Kasai count done in 27s
14 92 520 2923 15714 71330 265861 890895 2482912 5509765 (etc.)
Weitere Verbesserungen
SA-Bau ist jetzt der langsamste Prozess.
Einige divsufsortTeile des Algorithmus sollen mit den mir unbekannten Funktionen eines Compilers parallelisiert werden. Falls erforderlich, sollte sich der Code jedoch leicht an klassischeres Multithreading anpassen lassen ( z. B. à la C ++ 11).
Die Bibliothek verfügt außerdem über eine Vielzahl von Parametern, darunter verschiedene Bucket-Größen und die Anzahl der unterschiedlichen Symbole in der Eingabezeichenfolge. Ich habe sie nur flüchtig betrachtet, aber ich vermute, dass das Komprimieren des Alphabets einen Versuch wert sein könnte, wenn Ihre Zeichenfolgen endlose ACTG-Permutationen sind ( und Sie verzweifelt nach Auftritten suchen ).
Es gibt auch einige parallelisierbare Methoden zur Berechnung von LCP aus SA, aber da der Code auf einem Prozessor, der etwas leistungsfähiger als mein mickriger i3-2100@3.1GHz ist und der gesamte Algorithmus in O (n) ist, unter einer Minute laufen sollte , bezweifle ich dies wäre die Mühe wert.
Jeiner naive Lösung wäre einfach sein `[: ~.]` Aber vermutet , dass es wird nicht geschnitten.