C ++: una mejora con respecto a la solución FUZxxl
No merezco absolutamente ningún crédito por el método de cálculo en sí, y si no se presenta un mejor enfoque mientras tanto, la recompensa debería ir a FUZxxl por derecho.
#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;
}
Simplemente usé Kasai et al. algoritmo para calcular LCP en O (n).
El resto es una mera adaptación del código FUZxxl, utilizando características C ++ más concisas aquí y allá.
Dejé el código de cálculo original para permitir comparaciones.
Como los procesos más lentos son la construcción de SA y el recuento de LCP, eliminé la mayoría de las otras optimizaciones para evitar saturar el código para obtener ganancias despreciables.
Extendí la tabla SA para incluir el prefijo de longitud cero. Eso facilita el cálculo de LCP.
No proporcioné una opción de limitación de longitud, el proceso más lento ahora es el cálculo de SA que no puede reducirse (o al menos no veo cómo podría ser).
También eliminé la opción de salida binaria y limité la visualización a los primeros 10 valores.
Supongo que este código es solo una prueba de concepto, por lo que no es necesario saturar las pantallas o saturar los discos.
Construyendo el ejecutable
Tuve que compilar todo el proyecto (incluida la versión lite dedivsufsort
) para x64 para superar el límite de asignación de Win32 de 2 Gb.
divsufsort
el código arroja un montón de advertencias debido al uso intensivo de int
s en lugar de size_t
s, pero eso no será un problema para las entradas de menos de 2 Gb (que de todos modos requerirían 26 Gb de RAM: D).
Construcción de Linux
compilar main.cpp
y divsufsort.c
usar los comandos:
g++ -c -O3 -fomit-frame-pointer divsufsort.c
g++ -O3 main.cpp -o kmers divsufsort.o
Supongo que la divsufsort
biblioteca normal debería funcionar bien en Linux nativo, siempre y cuando pueda asignar un poco más de 3Gb.
Actuaciones
El algoritmo Kasai requiere la tabla SA inversa, que consume 4 bytes más por carácter para un total de 13 (en lugar de 9 con el método FUZxxl).
El consumo de memoria para la entrada de referencia es, por lo tanto, superior a 3Gb.
Por otro lado, el tiempo de cálculo se mejora dramáticamente, y todo el algoritmo ahora está en 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.)
Futuras mejoras
La construcción de SA es ahora el proceso más lento.
Algunos bits del divsufsort
algoritmo están destinados a ser paralelos con cualquier característica incorporada de un compilador desconocido para mí, pero si es necesario, el código debería ser fácil de adaptar a subprocesos múltiples más clásicos ( por ejemplo, a la C ++ 11).
La biblioteca también tiene una gran cantidad de parámetros, incluidos varios tamaños de cubetas y la cantidad de símbolos distintos en la cadena de entrada. Solo les eché un vistazo superficial, pero sospecho que valdría la pena intentar comprimir el alfabeto si tus cadenas son interminables permutaciones de ACTG ( y estás desesperado por las actuaciones).
Existen algunos métodos paralelizables para calcular LCP desde SA también, pero dado que el código debería ejecutarse en menos de un minuto en un procesador un poco más potente que mi pequeño i3-2100@3.1GHz y todo el algoritmo está en O (n), dudo que esto Valdría la pena el esfuerzo.
J
, una solución ingenua simplemente sería `[: ~.]` Pero supongo que eso no será suficiente.