Una vez hecha la introducción a Nextflow (ver artículo anterior) vamos a empezar a explorar algunas de las funcionalides que nos ofrece.
Supongamos que tenemos un flujo de trabajo que requiere que ciertas partes se realicen usando un programa (o lenguaje de programación) y otras partes otros.
Por ejemplo existe una librería Python que realiza unos cálculos y tenemos otra en Java que realiza otros.
Probablemente usaríamos un bash donde ejecutaríamos cada fase, usando las salidas de unos como entradas de otros.
El problema es que esto nos obliga a tener instalados los diferentes programas (o runtimes) con el engorro de controlar versiones, instalaciones, etc. Una forma de mitigar este problema sería usando, por ejemplo, Docker, montando volumenes, compartiendo ficheros, etc.
Caso de uso
En este ejemplo vamos a desarrollar un pipeline tal que un Python va a crear un número indeterminado de ficheros ( le pasaremos por párametro cuántos queremos generar), cada uno de ello va a ser leído y "modificado" por un comando bash y por último un proceso Java va a pasar a mayúsculas cada fichero (como ves, un ejemplo sin mucha utilidad)
La "gracia" va a ser que:
cada fichero se va a tratar de forma paralela e independiente
vamos a usar Docker "sin manos" y hacer que cada proceso se ejecute en una imagen de Docker diferente (Python y Java)
Procesos
Para usar la capacidad de ejecutar containers necesitamos activar docker. Para ello crearemos el fichero de configuraciónnextflow.config
con el siguiente contenido:
nextflow.config
docker{
enabled = true
}
Generar ficheros (Python)
process generarFicheros {
container 'python:latest'
input:
val size
output:
path 'example.*'
"""
#!/usr/bin/env python
import sys
total = int($size)
for x in range(0, total):
with open('example.'+str(x), 'w') as f:
f.write('line 1 '+str(x)+'\\n')
f.write('line 2 '+str(x)+'\\n')
f.write('line 3 '+str(x)+'\\n')
"""
}
El proceso generarFicheros
se va a ejecutar en un container usando la imagen python:latest
Va a recibir un párametro de entrada size
que usará en el programa a ejecutar (total=int($size)
)
Cada fichero consistirá en 3 lineas que contendrán entre otras cosas, el índice del fichero simplemente a modo de demostrar que se están procesando en paralelo
Por cada fichero que se cree con el nombre example.XXX
el proceso emitirá un output
Limpieza de fichero (bash)
process eachFile{
input:
path file
output:
stdout
"""
head -n 1 $file
"""
}
El proceso eachFile
recibe un fichero y simplemente muestra su contenido por consola.
Supongamos que este proceso fuera por ejemplo una limpieza del fichero, quitar líneas mal formateadas, etc. En nuestro ejemplo simplemente va a mostrar la primera línea de cada uno (es decir mostrará "line 1 XX" donde XX es el índice del fichero que generó el proceso Python)
Procesado (Java)
Por último vamos a ejecutar por cada fichero un Java que lo único que hace es pasar a mayusculas el argumento proporcionado:
import java.util.stream.Collectors;
import java.util.stream.Stream;
class MyJava{
public static void main(String[]args){
System.out.println( Stream.of(args).collect(Collectors.joining(" ")).toUpperCase());
}
}
En un proyecto típico, este código lo compilaríamos y generaríamos un Jar para ejecutarlo con java -jar myproject.jar
Para este ejemplo lo que vamos a usar es JBang, una funcionalidad de Java que permite que le pasemos el código y él se encarga de compilarlo y ejecutarlo directamente.
Así pues el proceso a definir en nuestro pipeline quedaría como:
process toUpperString{
container 'jbangdev/jbang-action'
containerOptions "--volume $projectDir:/ws"
input:
val str
output:
stdout
"""
jbang /ws/src/MyJava.java $str
"""
}
En este process usamos una característica más de Nextflow. Además de indicarle la imagen que queremos usar en el process, podemos proporcionar una cadena de configuración con containerOptions
que usaremos para montar el directorio de trabajo
Nextflow se encarga de descargar, montar volumenes y ejecutar en el container creado el comando
jbang /ws/src/MyJava.java $str
donde $str
va a ser una cadena que se reciba como input
Workflow
Por último nos queda definir el workflow que une a estos tres process
workflow{
generarFicheros(params.size).flatMap() | eachFile | toUpperString | view
}
El workflow llama en primer lugar a generarFicheros
y mediante el operador flatMap
convertimos la salida (una lista de ficheros) en una emisión en paralelo
Cada fichero será procesado por eachFile y cada salida de eachFile se enviará a una instancia de toUpperString
Pipeline
El pipeline queda pues de esta forma:
process generarFicheros {
container 'python'
input:
val size
output:
path 'example.*'
"""
#!/usr/bin/env python
import sys
size = int($size)
for x in range(0, size):
with open('example.'+str(x), 'w') as f:
f.write('line 1 '+str(x)+'\\n')
f.write('line 2 '+str(x)+'\\n')
f.write('line 3 '+str(x)+'\\n')
"""
}
process eachFile{
input:
path file
output:
stdout
"""
head -n 1 $file
"""
}
process toUpperString{
container 'jbangdev/jbang-action'
containerOptions "--volume $projectDir:/ws"
input:
val str
output:
stdout
"""
jbang /ws/src/MyJava.java $str
"""
}
workflow{
generarFicheros(params.size).flatMap() | eachFile | toUpperString | view
}
Y lo ejecutaremos:
nextflow run ./multilang.nf --size=5
Si todo va bien iremos viendo por consola las primeras lineas de los ficheros convertidas a mayúsculas.
N E X T F L O W ~ version 22.04.5
Launching `./multilang.nf` [hungry_laplace] DSL2 - revision: bd507594b6
executor > local (11)
[2a/65dd40] process > generarFicheros [100%] 1 of 1 ✔
[79/0c4662] process > eachFile (2) [100%] 5 of 5 ✔
[94/01790c] process > toUpperString (2) [100%] 5 of 5 ✔
LINE 1 0
LINE 1 4
LINE 1 3
LINE 1 1
LINE 1 2
Es interesante observar en esta salida cómo generarFicheros se ha ejecutado 1 of 1
mientras que las otras tareas se han ejecutado 5 of 5
(ya que indicamos size=5 como parámetro de ejecución)
Conclusión
En este post hemos visto cómo usar los process para ejecutar aplicaciones diversas, como en este caso Python y Java, así como el procesado en paralelo de las tareas
Así mismo hemos visto cómo podemos usar Docker incluso con imágenes diferentes en cada proceso junto con una pequeña introducción al fichero de configuración nextflow.config
el cual explicaré en un futuro post en más detalle
Top comments (0)