Đếm k-mers


8

Nhiệm vụ là đếm số lượng các chuỗi con riêng biệt có độ dài k, với k = 1,2,3,4, .....

Đầu ra

Bạn nên xuất một dòng trên mỗi dòng kbạn quản lý để hoàn thành với một số trên mỗi dòng đầu ra. Đầu ra của bạn nên theo thứ tự tăng dần kcho đến khi bạn hết thời gian.

Ghi bàn

Điểm của bạn là k cao nhất bạn có thể nhận được trên máy tính của tôi trong vòng dưới 1 phút.

Bạn nên sử dụng http://hgdoad.cse.ucsc.edu/goldenPath/hg38/chromosomes/chr2.fa.gz làm đầu vào của bạn và bỏ qua các dòng mới. Mã của bạn, tuy nhiên, phải phân biệt chữ hoa chữ thường.

Bạn có thể giải nén đầu vào trước khi bắt đầu thời gian.

Mã sau (không hiệu quả) đếm số lượng 4-bit khác nhau.

awk -v substr_length=4 '(len=length($0))>=substr_length{for (i=1; (i-substr_length)<len; i++) substrs[substr($0,i,substr_length)]++}; END{for (i in substrs) print substrs[i], i}' file.txt|wc

Giới hạn bộ nhớ

Để làm cho mã của bạn hoạt động tốt trên máy tính của tôi và để thực hiện nhiệm vụ khó khăn hơn, tôi sẽ giới hạn RAM bạn có thể sử dụng ở mức 2GB bằng cách sử dụng

ulimit -v 2000000

trước khi chạy mã của bạn. Tôi biết rằng đây không phải là một cách chắc chắn để hạn chế việc sử dụng RAM, vì vậy vui lòng không sử dụng các cách tưởng tượng để vượt qua giới hạn này, ví dụ, tạo ra các quy trình mới. Tất nhiên bạn có thể viết mã đa luồng nhưng nếu có ai đó, tôi sẽ phải học cách giới hạn tổng RAM được sử dụng cho điều đó.

Tie Breaker

Trong trường hợp hòa nhau tối đa, ktôi sẽ tính thời gian cần bao lâu để đưa ra kết quả đầu ra k+1và nhanh nhất sẽ thắng. Trong trường hợp họ chạy cùng một lúc trong vòng một giây k+1, lần gửi đầu tiên sẽ thắng.

Ngôn ngữ và thư viện

Bạn có thể sử dụng bất kỳ ngôn ngữ nào có trình biên dịch / trình thông dịch / có sẵn miễn phí. cho Linux và bất kỳ thư viện nào cũng có sẵn miễn phí cho Linux.

Máy của tôi Thời gian sẽ được chạy trên máy của tôi. Đây là bản cài đặt Ubuntu tiêu chuẩn trên Bộ xử lý tám lõi AMD FX-8350 trên Bo mạch chủ Asus M5A78L-M / USB3 (Ổ cắm AM3 +, 8GB DDR3). Điều này cũng có nghĩa là tôi cần để có thể chạy mã của bạn. Do đó, chỉ sử dụng phần mềm miễn phí có sẵn dễ dàng và vui lòng bao gồm các hướng dẫn đầy đủ về cách biên dịch và chạy mã của bạn.


Đầu ra thử nghiệm

Mã của FUZxxl xuất ra các thông tin sau (nhưng không phải tất cả trong vòng 1 phút) mà tôi tin là chính xác.

14
92
520
2923
15714
71330
265861
890895
2482912
5509765
12324706
29759234

bảng xếp hạng

  • k> = 4000 FUZxxl (C)
  • k = 16 của Keith Randall (C ++)
  • k = 10 bởi FUZxxl (C)

Bao nhiêu bạn có thể chuyên mã của bạn để đầu vào?

  • Rõ ràng nó sẽ phá hỏng sự cạnh tranh nếu bạn chỉ cần tính toán trước các câu trả lời và mã của bạn xuất ra chúng. Đừng làm vậy.
  • Lý tưởng nhất, bất cứ điều gì mã của bạn cần để tìm hiểu về dữ liệu mà nó sẽ sử dụng để chạy nhanh hơn nó có thể học vào thời gian chạy.
  • Tuy nhiên bạn có thể giả định đầu vào sẽ trông giống như các dữ liệu trong các tập tin .fa * tại http://hgdownload.cse.ucsc.edu/goldenPath/hg38/chromosomes/ .

Trong 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ó.
FUZxxl 2/2/2015

@FUZxxl Chà ... nó lớn đến mức nào trong một phút và nó sử dụng bao nhiêu RAM?

Chà, trong 2 giây, nó trở lại trong khoảng 5 giây, trong 3 giây, nó mất 3,2 GB RAM và mất khoảng một phút. Tôi đã không thử những gì nó sẽ làm cho bốn. Hãy để tôi thử ...
FUZxxl 2/2/2015

@FUZxxl Chà ... xin vui lòng gửi nó dưới dạng câu trả lời :) Hãy nhớ rằng tôi đã cắt nó ở mức 2GB RAM.

1
@Lembik Tôi sẽ không nói với bạn. Tôi hiện đang trong quá trình thay đổi giải pháp để nó mang lại kết quả đầu ra (nhưng nhìn chung chậm hơn). Mất 9 phút để xử lý trước dữ liệu trên máy của tôi.
FUZxxl

Câu trả lời:


7

C (≥ 4000)

Mã này không sử dụng ít hơn 2 GB RAM (nó sử dụng nhiều hơn một chút) cũng như không tạo ra bất kỳ đầu ra nào trong phút đầu tiên. Nhưng nếu bạn chờ khoảng sáu phút, nó in ra tất cả các k -mer tội cùng một lúc.

Một tùy chọn được bao gồm để giới hạn k cao nhất mà chúng tôi tính k -mer. Khi k bị giới hạn trong phạm vi hợp lý, mã sẽ chấm dứt sau chưa đầy một phút và sử dụng ít hơn 2 GiB RAM. OP đã đánh giá giải pháp này và nó chấm dứt sau chưa đầy một phút cho giới hạn không cao hơn đáng kể so với 4000.

Làm thế nào nó hoạt động?

Thuật toán có bốn bước.

  1. Đọc tệp đầu vào vào bộ đệm và loại bỏ các dòng mới.
  2. Hậu tố - sắp xếp một mảng các chỉ mục vào bộ đệm đầu vào. Chẳng hạn, các hậu tố của chuỗi mississippilà:

    mississippi
    ississippi
    ssissippi
    sissippi
    issippi
    ssippi
    sippi
    ippi
    ppi
    pi
    i
    

    Các chuỗi được sắp xếp theo thứ tự từ điển là:

    i
    ippi
    issippi
    ississippi
    mississippi
    pi
    ppi
    sippi
    sissippi
    ssippi
    ssissippi
    

    Dễ dàng thấy rằng tất cả các chuỗi con có độ dài k cho tất cả k được tìm thấy trong các mục liền kề của mảng được sắp xếp hậu tố.

  3. Một mảng số nguyên được điền vào trong đó chúng tôi lưu trữ ở mỗi chỉ số k số lượng k -mer riêng biệt . Điều này được thực hiện theo cách hơi phức tạp để tăng tốc quá trình. Xem xét hai mục liền kề mảng được sắp xếp hậu tố.

       p     l
       v     v
    issippi
    ississippi
    

    p biểu thị độ dài của tiền tố chung dài nhất của hai mục nhập, l biểu thị độ dài của mục nhập thứ hai. Đối với một cặp như vậy, chúng ta thấy một chuỗi riêng biệt mới có độ dài k cho p < kl . Vì pl thường giữ, nên việc tăng một số lượng lớn các mục cho mỗi cặp là không thực tế. Thay vào đó, chúng tôi lưu trữ mảng dưới dạng một mảng khác nhau, trong đó mỗi mục k biểu thị sự khác biệt về số lượng k -mer với số lượng ( k  - 1) -mer. Điều này biến một bản cập nhật của mẫu

    0  0  0  0 +1 +1 +1 +1 +1 +1  0  0  0
    

    vào một bản cập nhật nhanh hơn nhiều của mẫu

    0  0  0  0 +1  0  0  0  0  0 -1  0  0
    

    Bằng cách quan sát cẩn thận rằng l luôn khác nhau và trên thực tế, mỗi 0 < l < n sẽ xuất hiện chính xác một lần, chúng ta có thể bỏ qua các phép trừ và thay vào đó trừ 1 từ mỗi khác biệt khi chuyển đổi từ chênh lệch thành số tiền.

  4. Sự khác biệt được chuyển đổi thành số lượng và in ra.

Mã nguồn

Nguồn này sử dụng libdivsufsort để sắp xếp các mảng hậu tố. Mã tạo đầu ra theo đặc tả khi được biên dịch với lời gọi này.

cc -O -o dsskmer dsskmer.c -ldivsufsort

cách khác, mã có thể tạo đầu ra nhị phân khi được biên dịch với lời gọi sau.

cc -O -o dsskmer -DBINOUTPUT dsskmer.c -ldivsufsort

Để giới hạn k cao nhất mà k -mer sẽ được tính, cung cấp -DICAP = k trong đó k là giới hạn:

cc -O -o dsskmer -DICAP=64 dsskmer.c -ldivsufsort

Biên dịch với -O3nếu trình biên dịch của bạn cung cấp tùy chọn này.

#include <stdlib.h>
#include <stdio.h>

#include <divsufsort.h>

#ifndef BINOUTPUT
static char stdoutbuf[1024*1024];
#endif

/*
 * load input from stdin and remove newlines. Save length in flen.
 */
static unsigned char*
input(flen)
    int *flen;
{
    size_t n, len, pos, i, j;
    off_t slen;
    unsigned char *buf, *sbuf;

    if (fseek(stdin, 0L, SEEK_END) != 0) {
        perror("Cannot seek stdin");
        abort();
    }

    slen = ftello(stdin);
    if (slen == -1) {
        perror("Cannot tell stdin");
        abort();
    }

    len = (size_t)slen;

    rewind(stdin);

    /* Prepare for one extra trailing \0 byte */
    buf = malloc(len + 1);
    if (buf == NULL) {
        perror("Cannot malloc");
        abort();
    }

    pos = 0;

    while ((n = fread(buf + pos, 1, len - pos, stdin)) != 0)
        pos += n;

    if (ferror(stdin)) {
        perror("Cannot read from stdin");
        abort();
    }

    /* remove newlines */
    for (i = j = 0; i < len; i++)
        if (buf[i] != '\n')
            buf[j++] = buf[i];

    /* try to reclaim some memory */
    sbuf = realloc(buf, j);
    if (sbuf == NULL)
        sbuf = buf;

    *flen = (int)j;
    return sbuf;
}

/*
 * 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(buf, sa, kmers, n)
    const unsigned char *buf;
    const int *sa;
    int *kmers;
{
    int i, cl, cp;

    /* the first item needs special treatment */
    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]++;
    }
}

extern int
main()
{
    unsigned char *buf;
    int blen, ilen, *sa, *kmers, i;

    buf = input(&blen);

    sa = malloc(blen * sizeof *sa);

    if (divsufsort(buf, sa, blen) != 0) {
        puts("Cannot divsufsort");
        abort();
    }

#ifdef ICAP
    ilen = ICAP;
    kmers = calloc(ilen + 1, sizeof *kmers);
#else
    ilen = blen;
    kmers = calloc(ilen, sizeof *kmers);
#endif

    if (kmers == NULL) {
        perror("Cannot malloc");
        abort();
    }

    count(buf, sa, kmers, blen);

#ifndef BINOUTPUT
    /* sum up kmers differences */
    for (i = 1; i < ilen; i++)
        kmers[i] += kmers[i-1] - 1;


    /* enlarge buffer of stdout for better IO performance */
    setvbuf(stdout, stdoutbuf, _IOFBF, sizeof stdoutbuf);

    /* human output */
    for (i = 0; i < ilen; i++)
        printf("%d\n", kmers[i]);
#else
    /* binary output in host endianess */
    fprintf(stderr, "writing out result...\n");
    fwrite(kmers, sizeof *kmers, ilen, stdout);
#endif

    return 0;
}

Định dạng tệp nhị phân có thể được chuyển đổi thành định dạng đầu ra có thể đọc được bằng chương trình sau:

#include <stdlib.h>
#include <stdio.h>

static int inbuf[BUFSIZ];
static char outbuf[BUFSIZ];

extern int main()
{
    int i, n, sum = 1;

    setbuf(stdout, outbuf);

    while ((n = fread(inbuf, sizeof *inbuf, BUFSIZ, stdin)) > 0)
        for (i = 0; i < n; i++)
            printf("%d\n", sum += inbuf[i] - 1);

    if (ferror(stdin)) {
        perror("Error reading input");
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

đầu ra mẫu

Đầu ra mẫu ở định dạng nhị phân cho tệp chr22.facó thể được tìm thấy ở đây . Hãy giải nén với bzip2 -dđầu tiên. Đầu ra chỉ được cung cấp ở định dạng nhị phân vì nó nén tốt hơn nhiều (3,5 kB so với 260 MB) so với đầu ra ở định dạng có thể đọc được. Coi chừng rằng đầu ra tham chiếu có kích thước 924 MB khi không nén. Bạn có thể muốn sử dụng một đường ống như thế này:

bzip2 -dc chr2.kmerdiffdat.bz2 | ./unbin | less

Điều này rất thú vị. Bạn có thể chia nhỏ thời gian của từng phần? Có vẻ như việc xây dựng mảng hậu tố ít hơn 10% thời gian.

1
Bước một mất khoảng ba giây, bước hai mất khoảng 20 giây, bước ba mất toàn bộ phần còn lại. Thời gian thực hiện bởi bước bốn là không đáng kể.
FUZxxl

1
@FUZxxl bạn đã vẽ sơ đồ phân phối các giá trị p tối đa để xem bạn có thể tăng tốc độ bước 3 bằng cách cắt if p> k (chỉ tính cho k-mers)? mặc dù nếu bước 2 mất 20 giây thì có thể không còn nhiều chỗ ở đó. btw giải thích tuyệt vời
Randomra 6/2/2015

1
@Lembik Không sử dụng cat. Sử dụng chuyển hướng vỏ như trong ./dsskmer <~/Downloads/chr2.fs. Mã cần biết tệp đầu vào dài bao nhiêu và điều đó là không thể đối với đường ống.
FUZxxl

2
@Lembik Không có vẻ quá chậm đối với tôi. Có lẽ họ chỉ là những lập trình viên tồi.
FUZxxl

7

C ++, k = 16, 37 giây

Tính toán tất cả 16 mers trong đầu vào. Mỗi 16-bit được đóng gói 4 bit cho một ký hiệu thành một từ 64 bit (với một mẫu bit dành riêng cho EOF). Các từ 64 bit sau đó được sắp xếp. Câu trả lời cho mỗi k có thể được đọc ra bằng cách xem tần suất các bit trên 4 * k của các từ được sắp xếp thay đổi.

Đối với đầu vào kiểm tra tôi sử dụng khoảng 1,8 GB, ngay dưới dây.

Tôi chắc rằng tốc độ đọc có thể được cải thiện.

Đầu ra:

14
92
520
2923
15714
71330
265861
890895
2482912
5509765
12324706
29759234
69001539
123930801
166196504
188354964

Chương trình:

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>

int main(int argc, char *argv[]) {
    // read file
    printf("reading\n");
    FILE *f = fopen(argv[1], "rb");
    fseek(f, 0, SEEK_END);
    int32_t size = ftell(f);
    printf("size: %d\n", size);
    fseek(f, 0, SEEK_SET);

    // table to convert 8-bit input into 4-bit tokens.  Reserve 15 for EOF.
    int ntokens = 0;
    int8_t squash[256];
    for(int i = 0; i < 256; i++) squash[i] = -1;

    uint64_t *buf = (uint64_t*)malloc(8*size);

    int32_t n = 0;
    uint64_t z = 0;
    for(int32_t i = 0; i < size; i++) {
        char c = fgetc(f);
        if(c == '\n') continue;
        int8_t s = squash[c];
        if(s == -1) {
            if(ntokens == 15) {
                printf("too many tokens\n");
                exit(1);
            }
            squash[c] = s = ntokens++;
        }
        z <<= 4;
        z += s;
        n++;

        if(n >= 16) buf[n-16] = z;
    }
    for(int32_t i = 1; i < 16; i++) {
        z <<= 4;
        z += 15;
        buf[n-16+i] = z;
    }
    printf("   n: %d\n", n);

    // sort these uint64_t's
    printf("sorting\n");
    std::sort(buf, buf+n);

    for(int32_t k = 1; k <= 16; k++) {
        // count unique entries
        int32_t shift = 64-4*k;
        int32_t cnt = 1;
        int64_t e = buf[0] >> shift;
        for(int32_t i = 1; i < n; i++) {
            int64_t v = buf[i] >> shift;
            if((v & 15) == 15) continue; // ignore EOF entries
            if(v != e) {
                cnt++;
                e = v;
            }
        }

        printf("%d\n", cnt);
    }
}

Biên dịch với g++ -O3 kmer.cc -o kmervà chạy với ./kmer chr2.fa.


Hãy tuân theo định dạng đầu ra; đừng k .
FUZxxl

Đây là một giải pháp tuyệt vời khác.
FUZxxl

@FUZxxl: đã sửa.
Keith Randall

Bạn nghĩ mất bao lâu để tìm ra độ dài dài nhất của bất kỳ chuỗi con lặp lại nào? Tôi đã nghĩ rằng bạn tự động biết tất cả các câu trả lời cho bất kỳ k lâu hơn thế.

@Lembik: Chuỗi con lặp lại dài nhất có thể được thực hiện trong thời gian tuyến tính. Nhưng tôi không nghĩ rằng nó rất hữu ích - có những chuỗi con lặp đi lặp lại thực sự dài trong đầu vào mẫu, ít nhất là hàng ngàn ký hiệu.
Keith Randall

4

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.

divsufsortmã đưa ra một loạt các cảnh báo do sử dụng nhiều ints thay vì size_ts, 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.cppdivsufsort.csử 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 divsufsortthư 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 divsufsortthuậ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 ( 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 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.


2
+1 để cung cấp tín dụng khi tín dụng đáo hạn;) (và tăng tốc tốt đẹp!)
Martin Ender

1
Đây là gọn gàng. Cải thiện tốt đẹp.
FUZxxl

Tôi thậm chí còn không biết về mảng LCP. Kasai et al. thuật toán là thực sự gọn gàng, quá. Có một chút nói rằng nó cần rất nhiều bộ nhớ.
FUZxxl 7/2/2015

@FUZxxl ah tốt, nó luôn luôn là sự đánh đổi tốc độ / bộ nhớ cũ, nhưng tôi nghĩ rằng một mem 45%. tăng chi phí là một mức giá chấp nhận được để đạt được độ phức tạp O (n).

2
@kuroineko Tôi có một ý tưởng làm thế nào để xây dựng dữ liệu chúng ta cần trong thời gian tuyến tính mà không cần sử dụng mảng LCP. Hãy để tôi kiểm tra đào
FUZxxl

1

C (có thể giải quyết tối đa 10 trong một phút trên máy của tôi)

Đây là một giải pháp rất đơn giản. Nó xây dựng một cây của k -mer được tìm thấy và đếm chúng. Để bảo tồn bộ nhớ, các ký tự trước tiên được chuyển đổi thành các số nguyên từ 0 đến n - 1 trong đó n là số lượng ký tự khác nhau trong đầu vào, vì vậy chúng tôi không cần cung cấp không gian cho các ký tự không bao giờ xuất hiện. Ngoài ra, ít bộ nhớ được phân bổ cho các lá hơn so với các nút khác để bảo tồn bộ nhớ thêm. Giải pháp này sử dụng khoảng 200 MiB RAM trong thời gian chạy trên máy của tôi. Tôi vẫn đang cải thiện nó, vì vậy có lẽ trong vòng lặp, nó có thể còn nhanh hơn nữa!

Để biên dịch, lưu mã bên dưới trong một tệp có tên kmers.cvà sau đó thực thi trên hệ điều hành giống POSIX:

cc -O -o kmers kmers.c

Bạn có thể muốn thay thế -O3 cho -Onếu hỗ trợ biên dịch của bạn đó. Để chạy, đầu tiên giải nén chr2.fa.gzvào chr2.favà sau đó chạy:

./kmers <chr2.fa

Điều này tạo ra đầu ra từng bước. Bạn có thể muốn giới hạn cả thời gian và không gian. Sử dụng một cái gì đó như

( ulimit -t 60 -v 2000000 ; ./kmers <chrs.fa )

để giảm tài nguyên khi cần thiết.

#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/*
 * A tree for the k-mers. It contains k layers where each layer is an
 * array of struct tr. The last layer contains a 1 for every kmer found,
 * the others pointers to the next layer or NULL.
 */
struct tr {
    struct tr *n;
};

/* a struct tr to point to */
static struct tr token;

static void     *mem(size_t s);
static int       add(struct tr*, unsigned char*, int, size_t);
static unsigned char    *input(size_t*);
extern int       main(void);

/*
 * Allocate memory, fail if we can't.
 */
static void*
mem(s)
    size_t s;
{
    void *p;

    p = calloc(s, 1);
    if (p != NULL)
        return p;

    perror("Cannot malloc");
    abort();
}

/*
 * add s to b, return 1 if added, 0 if present before. Assume that n - 1 layers
 * of struct tr already exist and only add an n-th layer if needed. In the n-th
 * layer, a NULL pointer denotes non-existance, while a pointer to token denotes
 * existance.
 */
static int
add(b, s, n, is)
    struct tr *b;
    unsigned char *s;
    size_t is;
{
    struct tr **nb;
    int added;
    int i;

    for (i = 0; i < n - 2; i++) {
        b = b[s[i]].n;
    }

    nb = &b[s[n - 2]].n;

    if (*nb == NULL || *nb == &token)
        *nb = mem(is * sizeof *b);

    added = (*nb)[s[n - 1]].n == NULL;
    (*nb)[s[n - 1]].n = &token;

    return (added);
}

/*
 * load input from stdin and remove newlines. Save length in flen.
 */
static unsigned char*
input(flen)
    size_t *flen;
{
    size_t n, len, pos, i, j;
    unsigned char *buf;

    if (fseek(stdin, 0L, SEEK_END) != 0) {
        perror("Cannot seek stdin");
        abort();
    }

    len = ftello(stdin);
    if (len == -1) {
        perror("Cannot tell stdin");
        abort();
    }

    rewind(stdin);

    /* no need to zero out, so no mem() */
    buf = malloc(len);
    if (buf == NULL) {
        perror("Cannot malloc");
        abort();
    }

    pos = 0;

    while ((n = fread(buf + pos, 1, len - pos, stdin)) != 0)
        pos += n;

    if (ferror(stdin)) {
        perror("Cannot read from stdin");
        abort();
    }

    /* remove newlines */
    for (i = j = 0; i < len; i++)
        if (buf[i] != '\n')
            buf[j++] = buf[i];

    *flen = j;
    return buf;
}

extern int
main()
{
    struct tr *b;
    size_t flen, c, i, k, is;
    unsigned char *buf, itab[1 << CHAR_BIT];

    buf = input(&flen);

    memset(itab, 0, sizeof itab);

    /* process 1-mers */
    for (i = 0; i < flen; i++)
        itab[buf[i]] = 1;

    is = 0;
    for (i = 0; i < sizeof itab / sizeof *itab; i++)
        if (itab[i] != 0)
            itab[i] = is++;

    printf("%zd\n", is);

    /* translate characters into indices */
    for (i = 0; i < flen; i++)
        buf[i] = itab[buf[i]];

    b = mem(is * sizeof *b);

    /* process remaining k-mers */
    for (k = 2; k < flen; k++) {
        c = 0;

        for (i = 0; i < flen - k + 1; i++)
            c += add(b, buf + i, k, is);

        printf("%zd\n", c);
    }

    return 0;
}

Cải tiến

  1. 8 → 9: Đọc toàn bộ tệp ngay từ đầu, xử lý trước một lần và giữ nó trong lõi. Điều này cải thiện rất nhiều thông lượng.
  2. Sử dụng ít bộ nhớ, viết đầu ra chính xác.
  3. Sửa định dạng đầu ra một lần nữa.
  4. Sửa lỗi từng cái một.
  5. 9 → 10: Đừng vứt bỏ những gì bạn đã làm.

Đầu ra thực sự phải là một số trên mỗi dòng. Dường như hiện tại các chuỗi đầu ra từ tệp đầu vào.

@Lembik À, tôi hiểu rồi! Có vẻ như tôi đã hiểu nhầm định dạng đầu ra. Hãy cho tôi một phút để sửa nó.
FUZxxl 2/2/2015

@Lembik Định dạng đầu ra cố định lại. Nếu bạn thích, tôi có thể thêm mã giết chương trình sau một phút.
FUZxxl 2/2/2015

Cảm ơn. Bạn hiện đang là người chiến thắng :) timeout 60shoạt động tốt với tôi vì vậy không cần phải xây dựng theo cách để giết mã sau 1 phút.

Bạn cũng có thể sử dụng ulimit -t 60.
FUZxxl 2/2/2015
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.