La tarea es contar el número de subcadenas distintas de longitud k, para k = 1,2,3,4, .....
Salida
Debe generar una línea por k
logro que complete con un número por línea de salida. Su producción debe estar en orden de aumento k
hasta que se acabe el tiempo.
Puntuación
Su puntaje es el k más alto que puede alcanzar en mi computadora en menos de 1 minuto.
Debe usar http://hgdownload.cse.ucsc.edu/goldenPath/hg38/chromosomes/chr2.fa.gz como su entrada e ignorar las nuevas líneas. Sin embargo, su código debe ser sensible a mayúsculas y minúsculas.
Puede descomprimir la entrada antes de comenzar la sincronización.
El siguiente código (ineficiente) cuenta el número de 4 mers distintos.
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
Límites de memoria
Para hacer que su código se comporte bien en mi computadora y para que la tarea sea más desafiante, limitaré la RAM que puede usar a 2GB usando
ulimit -v 2000000
antes de ejecutar su código. Soy consciente de que esta no es una forma sólida de limitar el uso de RAM, así que no utilice formas imaginativas para superar este límite, por ejemplo, generando nuevos procesos. Por supuesto, puede escribir código multiproceso, pero si alguien lo hace, tendré que aprender a limitar la RAM total utilizada para eso.
Desempate
En el caso de un empate por un máximo k
, calcularé cuánto tiempo se tarda en dar los resultados k+1
y gana el más rápido. En el caso de que se ejecuten al mismo tiempo hasta dentro de un segundo k+1
, la primera presentación gana.
Idiomas y bibliotecas
Puede usar cualquier idioma que tenga un compilador / intérprete / etc. para Linux y cualquier biblioteca que también esté disponible gratuitamente para Linux.
Mi máquina Los tiempos se ejecutarán en mi máquina. Esta es una instalación estándar de ubuntu en un procesador AMD FX-8350 de ocho núcleos en una placa base Asus M5A78L-M / USB3 (Socket AM3 +, 8GB DDR3). Esto también significa que necesito poder ejecutar su código. Como consecuencia, solo use software gratuito fácilmente disponible e incluya instrucciones completas sobre cómo compilar y ejecutar su código.
Prueba de salida
El código de FUZxxl genera lo siguiente (pero no todo en 1 minuto) que creo que es correcto.
14
92
520
2923
15714
71330
265861
890895
2482912
5509765
12324706
29759234
tabla de la liga
- k> = 4000 FUZxxl (C)
- k = 16 por Keith Randall (C ++)
- k = 10 por FUZxxl (C)
¿Cuánto puede especializar su código a la entrada?
- Claramente, arruinaría la competencia si solo calculara previamente las respuestas y su código las enviara. No hagas eso.
- Idealmente, cualquier cosa que su código necesite para aprender sobre los datos que usará para ejecutarse más rápidamente, puede aprender en tiempo de ejecución.
- Sin embargo, puede suponer que la entrada se verá como los datos en los archivos * .fa en http://hgdownload.cse.ucsc.edu/goldenPath/hg38/chromosomes/ .
J
, una solución ingenua simplemente sería `[: ~.]` Pero supongo que eso no será suficiente.Respuestas:
C (≥ 4000)
Este código no usa menos de 2 GB de RAM (usa un poco más) ni produce ningún resultado en el primer minuto. Pero si espera unos seis minutos, imprime todos los recuentos de k -mer a la vez.
Se incluye una opción para limitar la k más alta para la cual contamos los k -mers. Cuando k se limita a un rango razonable, el código termina en menos de un minuto y usa menos de 2 GiB de RAM. OP ha calificado esta solución y finaliza en menos de un minuto para un límite no significativamente superior a 4000.
¿Como funciona?
El algoritmo tiene cuatro pasos.
Sufijo-ordena una matriz de índices en el búfer de entrada. Por ejemplo, los sufijos de la cadena
mississippi
son:Estas cadenas ordenadas en orden lexicográfico son:
Es fácil ver que todas las subcadenas iguales de longitud k para todas las k se encuentran en entradas adyacentes de la matriz ordenada por sufijo.
Se rellena una matriz entera en la que almacenamos en cada índice k el número de k -mers distintos . Esto se hace de una manera ligeramente complicada para acelerar el proceso. Considere dos entradas adyacentes como la matriz ordenada por sufijo.
p denota la longitud del prefijo común más largo de las dos entradas, l denota la longitud de la segunda entrada. Para tal par, encontramos una nueva subcadena distinta de longitud k para p < k ≤ l . Como p ≪ l a menudo se mantiene, no es práctico incrementar una gran cantidad de entradas de matriz para cada par. En su lugar, almacenamos la matriz como una matriz de diferencia, donde cada entrada k denota la diferencia al número de k -mers y el número de ( k - 1) -mers. Esto convierte una actualización del formulario
en una actualización mucho más rápida del formulario
Al observar cuidadosamente que l siempre es diferente y, de hecho, cada 0 < l < n aparecerá exactamente una vez, podemos omitir las restas y restar 1 de cada diferencia al convertir las diferencias en cantidades.
El código fuente
Esta fuente utiliza libdivsufsort para ordenar las matrices de sufijos. El código genera resultados de acuerdo con la especificación cuando se compila con esta invocación.
alternativamente, el código puede generar una salida binaria cuando se compila con la siguiente invocación.
Para limitar la k más alta para la cual se deben contar k -mers, suministre -DICAP = k donde k es el límite:
Compile con
-O3
si su compilador proporciona esta opción.El formato de archivo binario se puede convertir al formato de salida legible por humanos con el siguiente programa:
salida de muestra
Aquí
chr22.fa
puede encontrar una salida de muestra en formato binario para el archivo . Descomprima con primero. La salida se proporciona en formato binario solo porque se comprime mucho mejor (3,5 kB frente a 260 MB) que la salida en formato legible para humanos. Sin embargo, tenga en cuenta que la salida de referencia tiene un tamaño de 924 MB cuando se descomprime. Es posible que desee utilizar una tubería como esta:bzip2 -d
fuente
cat
. Utilice la redirección de shell como en./dsskmer <~/Downloads/chr2.fs
. El código necesita saber cuánto dura el archivo de entrada y eso no es posible con una tubería.C ++, k = 16, 37 segundos
Calcula todos los 16 mers en la entrada. Cada 16 meros se empaqueta 4 bits a un símbolo en una palabra de 64 bits (con un patrón de un bit reservado para EOF). Las palabras de 64 bits se ordenan. La respuesta para cada k puede leerse observando con qué frecuencia cambian los 4 * k bits superiores de las palabras ordenadas.
Para la entrada de prueba, uso aproximadamente 1.8 GB, justo debajo del cable.
Estoy seguro de que la velocidad de lectura podría mejorarse.
Salida:
Programa:
Compilar
g++ -O3 kmer.cc -o kmer
y ejecutar con./kmer chr2.fa
.fuente
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.
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 de
divsufsort
) 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 deint
s en lugar desize_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
ydivsufsort.c
usar los comandos: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):
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 [email protected] y todo el algoritmo está en O (n), dudo que esto Valdría la pena el esfuerzo.
fuente
C (puede resolver hasta 10 en un minuto en mi máquina)
Esta es una solución muy simple. Construye un árbol de los k -mers encontrados y los cuenta. Para conservar la memoria, los caracteres se convierten primero en enteros de 0 a n - 1 donde n es el número de caracteres diferentes en la entrada, por lo que no necesitamos proporcionar espacio para los caracteres que nunca aparecen. Además, se asigna menos memoria para las hojas que para otros nodos para conservar más memoria. Esta solución utiliza alrededor de 200 MiB de RAM durante su tiempo de ejecución en mi máquina. Todavía lo estoy mejorando, ¡así que quizás en la iteración podría ser aún más rápido!
Para compilar, guarde el siguiente código en un archivo llamado
kmers.c
y luego ejecútelo en un sistema operativo similar a POSIX:Es posible que desee sustituir
-O3
por-O
si sus soportes compilador eso. Para ejecutar, desembalar por primera vezchr2.fa.gz
enchr2.fa
y vuelva a ejecutar:Esto produce la salida paso a paso. Es posible que desee limitar tanto el tiempo como el espacio. Usa algo como
para reducir recursos según sea necesario.
Mejoras
fuente
timeout 60s
funciona bien para mí, así que no es necesario construir de una manera que elimine el código después de 1 minuto.ulimit -t 60
.