C ++ - peningkatan dari solusi FUZxxl
Saya sama sekali tidak layak kredit untuk metode perhitungan itu sendiri, dan jika tidak ada pendekatan yang lebih baik muncul pada saat itu, karunia harus pergi ke FUZxxl dengan benar.
#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;
}
Saya hanya menggunakan Kasai et al. algoritma untuk menghitung LCP di O (n).
Sisanya hanyalah adaptasi dari kode FUZxxl, menggunakan fitur C ++ yang lebih ringkas di sana-sini.
Saya meninggalkan kode perhitungan asli untuk memungkinkan perbandingan.
Karena proses yang paling lambat adalah konstruksi SA dan jumlah LCP, saya menghapus sebagian besar optimasi lainnya untuk menghindari kekacauan kode untuk mendapatkan keuntungan yang dapat diabaikan.
Saya memperpanjang tabel SA untuk menyertakan awalan nol panjang. Itu membuat perhitungan LCP lebih mudah.
Saya tidak memberikan opsi batasan panjang, proses paling lambat saat ini adalah perhitungan SA yang tidak dapat dirampingkan (atau setidaknya saya tidak melihat bagaimana itu bisa dilakukan).
Saya juga menghapus opsi output biner dan tampilan terbatas pada 10 nilai pertama.
Saya menganggap kode ini hanya bukti konsep, jadi tidak perlu mengacaukan tampilan atau jenuh disk.
Membangun executable
Saya harus mengkompilasi seluruh proyek (termasuk versi litedivsufsort
) untuk x64 untuk mengatasi batas alokasi Win32 2Gb.
divsufsort
kode melempar banyak peringatan karena penggunaan int
s, bukan size_t
s, tetapi itu tidak akan menjadi masalah untuk input di bawah 2Gb (yang tetap membutuhkan 26Gb RAM: D).
Linux membangun
kompilasi main.cpp
dan divsufsort.c
gunakan perintah:
g++ -c -O3 -fomit-frame-pointer divsufsort.c
g++ -O3 main.cpp -o kmers divsufsort.o
Saya menganggap divsufsort
perpustakaan biasa harus bekerja dengan baik di Linux asli, selama Anda dapat mengalokasikan sedikit lebih dari 3Gb.
Pertunjukan
Algoritma Kasai membutuhkan tabel SA terbalik, yang memakan hingga 4 byte lebih per karakter untuk total 13 (bukan 9 dengan metode FUZxxl).
Konsumsi memori untuk input referensi dengan demikian di atas 3Gb.
Di sisi lain, waktu perhitungan ditingkatkan secara dramatis, dan keseluruhan algoritma sekarang dalam 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.)
Perbaikan lebih lanjut
Konstruksi SA sekarang merupakan proses paling lambat.
Beberapa bit dari divsufsort
algoritma dimaksudkan untuk diparalelkan dengan fitur bawaan apa pun dari kompiler yang tidak saya ketahui, tetapi jika perlu kodenya harus mudah beradaptasi dengan multithreading yang lebih klasik ( à la C ++ 11, misalnya).
Lib juga memiliki satu truk penuh parameter, termasuk berbagai ukuran ember dan jumlah simbol yang berbeda dalam string input. Saya hanya melihat sekilas pada mereka, tapi saya curiga mengompresi alfabet mungkin patut dicoba jika string Anda adalah permutasi ACTG yang tak ada habisnya ( dan Anda sangat membutuhkan pertunjukan).
Ada beberapa metode yang dapat diparalelkan untuk menghitung LCP dari SA juga, tetapi karena kode harus berjalan di bawah satu menit pada prosesor yang sedikit lebih kuat daripada i3-2100@3.1GHz saya yang kecil dan seluruh algoritme dalam O (n), saya ragu ini akan sepadan dengan usaha.
J
, solusi naif hanya akan menjadi `[: ~.]` Tetapi tebak itu tidak akan memotongnya.