Nextflow enables scalable and reproducible scientific workflows using software containers. It allows the adaptation of pipelines written in the most common scripting languages.
Its fluent DSL simplifies the implementation and the deployment of complex parallel and reactive workflows on clouds and clusters.
En el artículo anterior vimos una introducción a Nextflow. En este vamos a profundizar un poco más en los conceptos básicos de este lenguaje
Pipeline
Podemos definir el pipeline como el conjunto de tareas que tiene que realizar Nextflow. Básicamente es asociarlo con el fichero (y sus includes) a ejecutar
Process
El proceso, process, es la unidad básica a ejecutar. Podemos asociarlo con el comando de sistema a ejecutar para completar una tarea. Dicho comando tendrá unos parámetros de entrada y generará una salida.
Así mismo los procesos son independientes y no comparten un estado común sino que se comunican entre sí mediante colas (o canales)
Channel
Un canal es el mecanismo por el cual los procesos se intercambian datos. Cuando definimos un proceso declaramos su input pero no especificamos de donde leer este input. Es mediante los canales (y sus operadores) donde realizamos esta interconexión o dependencia
Flujo de trabajo
Como ya he mencionado, los procesos son unidades "atómicas" e independientes que lo único que necesitan para su ejecución es disponer de los datos de entrada que definan (y producir unos datos de salida también definidos por el propio proceso).
Así pues, podemos pensar que al inicio de nuestro pipeline todos los procesos se encuentran creados y a la espera de "ser alimentados". Dicha alimentación se realiza a través de los diferentes operadores que ofrece un Channel:
hola.nf
process goodbye {
input:
path name
output:
stdout
"""
cat $name
"""
}
process hi {
input:
val name
output:
path "name.txt"
"""
echo Hi $name > name.txt
"""
}
workflow {
Channel.of( 'Jorge' ) | hi | goodbye | view
}
En este ejemplo podemos ver dos procesos definidos (goodbye y hi)
goodbye recibe un fichero como entrada (
path
) y en su contexto lo vamos a referenciar comoname.
cat`) dicho fichero por la consola, emitiendo la salida
Su ejecución va a consistir en volcar (hi por su parte recibe una cadena (
val
) y en su contexto la vamos a referenciar comoname
(no confundir con el de goodbye). Dicho proceso va a concatenar una cadena fija con el parámetro de entrada para generar un fichero de trabajoname.txt
y emitirá la ruta de este fichero
Por último definimos el flujo de trabajo:
- Un canal emitirá una cadena fija, que será enviada al proceso
hi
. La salida de este será enviada por el canal al procesogoodbye
y por ultimo la salida de este será volcada por consola (view
)
Como podemos intuir, el orden de ejecución de los procesos no viene determinado por su posición en el fichero sino por la composición del flujo de trabajo. De esta manera Nextflow nos permitirá definir (y reutilizar) procesos en diferentes ficheros y componerlos a nuestro antojo
En los siguientes artículos iremos viendo, entre otras cosas, cómo ejecutar procesos en paralelo
Executor
Mientras que el proceso define el comando a ejecutar, el executor es el que define cómo se ejecutará dicho comando.
El executor por defecto es la máquina donde se está ejecutando el pipeline pero, sin modificar este, podemos indicarle a Nextflow qué executor usar como por ejemplo un cluster SLURM, un container de Amazon o de Google, etc
Así por ejemplo, cuando le indicamos a Nextflow que un pipeline lo ejecute, por ejemplo, en AWS él se encargará de aprovisionar la instancia, esperar a que se encuentre lista y ejecutar el script en esta (en realidad usando las APIs del executor en concreto)
Programación
La idea principal de Nextflow es que con la sintaxis que ofrece se disponga prácticamente de todas las herramientas para definir pipelines complejos, pero aun así permite incluir scripts con toda la complejidad que se desee.
Al estar desarrollado en Groovy es posible incluir scripts en el propio pipeline que nos ayuden a ampliar el lenguaje. Así mismo permite la inclusión de librerías Java (jar) simplemente colocándolas en la carpeta lib
random.nf
`
def cadenaRandom(){
new Random().with {(1..9).collect {(('a'..'z')).join()[nextInt((('a'..'z')).join().length())]}.join()}
}
process hi {
input:
val name
output:
stdout
"""
echo $name is a random string
"""
}
workflow {
Channel.of( cadenaRandom() ) | hi | view
}
nextflow run ./random.nf
N E X T F L O W ~ version 22.04.5
Launching ./dos.nf
[friendly_fourier] DSL2 - revision: d0b1349f31
executor > local (1)
[65/ee7786] process > hi (1) [100%] 1 of 1 ✔
lswwkzafv is a random string
`
Conclusion
En este artículo hemos visto las piezas básicas que componen un flujo de trabajo así como una breve introducción a la definición y ejecución de los procesos
Top comments (0)