C ++ - một cải tiến đối với giải pháp FUZxxl
Tôi hoàn toàn xứng đáng không có tín dụng cho chính phương pháp tính toán và nếu không có cách tiếp cận nào tốt hơn xuất hiện trong thời gian trung bình, tiền thưởng nên được chuyển đến FUZxxl ngay.
#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;
}
Tôi chỉ đơn giản là sử dụng Kasai et al. thuật toán để tính toán các LCP trong O (n).
Phần còn lại chỉ là sự điều chỉnh mã FUZxxl, sử dụng các tính năng C ++ ngắn gọn hơn ở đây và đó.
Tôi để lại mã tính toán ban đầu để cho phép so sánh.
Vì các quy trình chậm nhất là xây dựng SA và đếm LCP, tôi đã loại bỏ hầu hết các tối ưu hóa khác để tránh làm lộn xộn mã cho lợi ích không đáng kể.
Tôi đã mở rộng bảng SA để bao gồm tiền tố có độ dài bằng không. Điều đó làm cho tính toán LCP dễ dàng hơn.
Tôi đã không cung cấp tùy chọn giới hạn độ dài, quá trình chậm nhất hiện nay là tính toán SA không thể thu nhỏ (hoặc ít nhất là tôi không thấy nó có thể như thế nào).
Tôi cũng loại bỏ tùy chọn đầu ra nhị phân và hiển thị giới hạn trong 10 giá trị đầu tiên.
Tôi giả sử mã này chỉ là một bằng chứng về khái niệm, vì vậy không cần phải lộn xộn hiển thị hoặc đĩa bão hòa.
Xây dựng thực thi
Tôi đã phải biên dịch toàn bộ dự án (bao gồm cả phiên bản rút gọndivsufsort
) cho x64 để vượt qua giới hạn phân bổ Win32 2Gb.
divsufsort
mã đưa ra một loạt các cảnh báo do sử dụng nhiều int
s thay vì size_t
s, nhưng đó sẽ không phải là vấn đề đối với các đầu vào dưới 2Gb (dù sao cũng sẽ cần 26Gb RAM: D).
Xây dựng Linux
biên dịch main.cpp
và divsufsort.c
sử dụng các lệnh:
g++ -c -O3 -fomit-frame-pointer divsufsort.c
g++ -O3 main.cpp -o kmers divsufsort.o
Tôi cho rằng divsufsort
thư viện thông thường sẽ hoạt động tốt trên Linux gốc, miễn là bạn có thể phân bổ nhiều hơn một chút so với 3Gb.
Biểu diễn
Thuật toán Kasai yêu cầu bảng SA nghịch đảo, ăn thêm 4 byte cho mỗi ký tự cho tổng số 13 (thay vì 9 với phương thức FUZxxl).
Do đó, mức tiêu thụ bộ nhớ cho đầu vào tham chiếu cao hơn 3Gb.
Mặt khác, thời gian tính toán được cải thiện đáng kể và toàn bộ thuật toán hiện ở 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.)
Cải tiến hơn nữa
SA xây dựng là quá trình chậm nhất.
Một số bit của divsufsort
thuật toán có nghĩa là song song với bất kỳ tính năng dựng sẵn nào của trình biên dịch mà tôi không biết, nhưng nếu cần, mã sẽ dễ dàng thích ứng với đa luồng cổ điển hơn ( ví dụ như la C ++ 11).
Lib cũng có một tải các tham số, bao gồm các kích cỡ thùng khác nhau và số lượng các ký hiệu riêng biệt trong chuỗi đầu vào. Tôi chỉ có một cái nhìn khó hiểu về chúng, nhưng tôi nghi ngờ việc nén bảng chữ cái có thể đáng để thử nếu chuỗi của bạn là hoán vị ACTG vô tận ( và bạn đang tuyệt vọng cho các buổi biểu diễn).
Cũng tồn tại một số phương thức song song để tính LCP từ SA, nhưng vì mã sẽ chạy dưới một phút trên bộ xử lý mạnh hơn một chút so với i3-2100@3.1GHz của tôi và toàn bộ thuật toán nằm trong O (n), tôi nghi ngờ điều này sẽ có giá trị nỗ lực.
J
, một giải pháp ngây thơ đơn giản sẽ là `[: ~.]` Nhưng đoán rằng sẽ không cắt nó.