Content from Ejecutando comandos con Snakemake
Última actualización: 2025-04-05 | Mejora esta página
Hoja de ruta
Preguntas
- “¿Cómo ejecuto un comando simple con Snakemake?”
Objetivos
- “Crea una receta de Snakemake (un Snakefile)”
¿Cuál es el flujo de trabajo que me interesa?
En esta lección haremos un experimento que toma una aplicación que corre en paralelo e investigará su escalabilidad. Para ello necesitaremos recopilar datos, en este caso eso significa ejecutar la aplicación varias veces con diferentes números de núcleos de CPU y registrar el tiempo de ejecución. Una vez hecho esto, tenemos que crear una visualización de los datos para ver cómo se compara con el caso ideal.
A partir de la visualización podemos decidir a qué escala tiene más sentido ejecutar la aplicación en producción para maximizar el uso de nuestra asignación de CPU en el sistema.
Podríamos hacer todo esto manualmente, pero existen herramientas útiles que nos ayudan a gestionar pipelines de análisis de datos como el que tenemos en nuestro experimento. Hoy vamos a aprender acerca de uno de ellos: Snakemake.
Con el fin de empezar con Snakemake, vamos a empezar por tomar un
comando simple y ver cómo podemos ejecutar que a través de Snakemake.
Elijamos el comando hostname
que imprime el nombre del host
donde se ejecuta el comando:
SALIDA
node1.int.jetstream2.hpc-carpentry.org
Eso imprime el resultado pero Snakemake se basa en archivos para conocer el estado de su flujo de trabajo, así que vamos a redirigir la salida a un archivo:
Creando un archivo Snakefile
Edita un nuevo archivo de texto llamado Snakefile
.
Contenido de Snakefile
:
PYTHON
rule hostname_login:
output: "hostname_login.txt"
input:
shell:
"hostname > hostname_login.txt"
Puntos clave sobre este archivo
- El archivo se llama
Snakefile
- con mayúsculaS
y sin extensión de archivo. - Algunas líneas tienen sangría. Las sangrías deben ser con caracteres de espacio, no tabuladores. Consulte la sección de configuración para saber cómo hacer que su editor de texto haga esto.
- La definición de la regla comienza con la palabra clave
rule
seguida por el nombre de la regla, luego dos puntos. - Llamamos a la regla
hostname_login
. Puede utilizar letras, números o guiones bajos, pero el nombre de la regla debe comenzar con una letra y no puede ser una palabra clave. - Las palabras clave
input
,output
, yshell
van todas seguidas de dos puntos (“:”). - Los nombres de los archivos y el comando shell están todos en
"quotes"
. - El nombre del archivo de salida se da antes del nombre del archivo de entrada. De hecho, a Snakemake no le importa en qué orden aparecen, pero en este curso daremos primero el de salida. Pronto veremos por qué.
- En este caso de uso no hay fichero de entrada para el comando por lo que lo dejamos en blanco.
De vuelta en el shell ejecutaremos nuestra nueva regla. En este punto, si faltaran comillas, sangrías incorrectas, etc., podríamos ver un error.
bash: snakemake: command not found...
Si tu shell te dice que no puede encontrar el comando
snakemake
entonces tenemos que hacer que el software esté
disponible de alguna manera. En nuestro caso, esto significa buscar el
módulo que necesitamos cargar:
SALIDA
[ocaisa@node1 ~]$ module spider snakemake
--------------------------------------------------------------------------------------------------------
snakemake:
--------------------------------------------------------------------------------------------------------
Versions:
snakemake/8.2.1-foss-2023a
snakemake/8.2.1 (E)
Names marked by a trailing (E) are extensions provided by another module.
--------------------------------------------------------------------------------------------------------
For detailed information about a specific "snakemake" package (including how to load the modules) use the module's full name.
Note that names that have a trailing (E) are extensions provided by other modules.
For example:
$ module spider snakemake/8.2.1
--------------------------------------------------------------------------------------------------------
Ahora queremos el módulo, así que vamos a cargarlo para que el paquete esté disponible
y luego asegúrate de que tenemos el comando snakemake
disponible
SALIDA
/cvmfs/software.eessi.io/host_injections/2023.06/software/linux/x86_64/amd/zen3/software/snakemake/8.2.1-foss-2023a/bin/snakemake
Ejecutando Snakemake
Ejecute snakemake --help | less
para ver la ayuda de
todas las opciones disponibles. ¿Qué hace la opción -p
en
el comando snakemake
anterior?
- Protege los archivos de salida existentes
- Imprime en el terminal los comandos shell que se están ejecutando
- Le dice a Snakemake que sólo ejecute un proceso a la vez
- Solicita al usuario el fichero de entrada correcto
Puedes buscar en el texto pulsando /, y salir de nuevo al shell con q.
- Imprime en el terminal los comandos shell que se están ejecutando
¡Esto es tan útil que no sabemos por qué no está por defecto! La
opción -j1
es lo que le dice a Snakemake que sólo ejecute
un proceso a la vez, y nos quedaremos con esto por ahora ya que hace las
cosas más simples. La respuesta 4 es totalmente falsa, ya que Snakemake
nunca solicita interactivamente la entrada del usuario.
Puntos Clave
- “Antes de ejecutar Snakemake necesitas escribir un Snakefile”
- “Un Snakefile es un archivo de texto que define una lista de reglas”
- “Las reglas tienen entradas, salidas y comandos de shell a ejecutar”
- “Le dices a Snakemake qué archivo hacer y ejecutará el comando shell definido en la regla apropiada”
Content from Ejecutando Snakemake en el cluster
Última actualización: 2025-04-05 | Mejora esta página
Hoja de ruta
Preguntas
- “¿Cómo ejecuto mi regla de Snakemake en el cluster?”
Objetivos
- “Define reglas para ejecutar localmente y en el cluster”
¿Qué ocurre cuando queremos que nuestra regla se ejecute en el cluster en lugar de en el nodo de inicio de sesión? El cluster que estamos usando utiliza Slurm, y sucede que Snakemake tiene soporte integrado para Slurm, solo necesitamos decirle que queremos usarlo.
Snakemake utiliza la opción executor
para permitirte
seleccionar el plugin que deseas que ejecute la regla. La forma más
rápida de aplicar esto a tu Snakefile es definirlo en la línea de
comandos. Vamos a probarlo
SALIDA
Building DAG of jobs...
Retrieving input from storage.
Nothing to be done (all requested files are present and up to date).
¡No ha pasado nada! ¿Por qué? Cuando se le pide que construya un objetivo, Snakemake comprueba la ‘última hora de modificación’ tanto del objetivo como de sus dependencias. Si alguna dependencia ha sido actualizada desde el objetivo, entonces las acciones se vuelven a ejecutar para actualizar el objetivo. Usando este enfoque, Snakemake sabe que sólo debe reconstruir los archivos que, directa o indirectamente, dependen del archivo que cambió. Esto se llama una construcción incremental.
Las construcciones incrementales mejoran la eficiencia
Al reconstruir los archivos sólo cuando es necesario, Snakemake hace que su procesamiento sea más eficiente.
Ejecutando en el cluster
Ahora necesitamos otra regla que ejecute el hostname
en
el cluster. Crea una nueva regla en tu Snakefile e intenta
ejecutarla en el cluster con la opción --executor slurm
a
snakemake
.
La regla es casi idéntica a la regla anterior excepto por el nombre de la regla y el archivo de salida:
PYTHON
rule hostname_remote:
output: "hostname_remote.txt"
input:
shell:
"hostname > hostname_remote.txt"
A continuación, puede ejecutar la regla con
SALIDA
Building DAG of jobs...
Retrieving input from storage.
Using shell: /cvmfs/software.eessi.io/versions/2023.06/compat/linux/x86_64/bin/bash
Provided remote nodes: 1
Job stats:
job count
--------------- -------
hostname_remote 1
total 1
Select jobs to execute...
Execute 1 jobs...
[Mon Jan 29 18:03:46 2024]
rule hostname_remote:
output: hostname_remote.txt
jobid: 0
reason: Missing output files: hostname_remote.txt
resources: tmpdir=<TBD>
hostname > hostname_remote.txt
No SLURM account given, trying to guess.
Guessed SLURM account: def-users
No wall time information given. This might or might not work on your cluster.
If not, specify the resource runtime in your rule or as a reasonable default
via --default-resources. No job memory information ('mem_mb' or
'mem_mb_per_cpu') is given - submitting without.
This might or might not work on your cluster.
Job 0 has been submitted with SLURM jobid 326 (log: /home/ocaisa/.snakemake/slurm_logs/rule_hostname_remote/326.log).
[Mon Jan 29 18:04:26 2024]
Finished job 0.
1 of 1 steps (100%) done
Complete log: .snakemake/log/2024-01-29T180346.788174.snakemake.log
Observe todas las advertencias que Snakemake nos está dando sobre el
hecho de que la regla puede no ser capaz de ejecutarse en nuestro
cluster ya que puede que no hayamos dado suficiente información. Por
suerte para nosotros, esto funciona en nuestro cluster y podemos echar
un vistazo al archivo de salida que crea la nueva regla,
hostname_remote.txt
:
SALIDA
tmpnode1.int.jetstream2.hpc-carpentry.org
Perfil de Snakemake
Adaptar Snakemake a un entorno particular puede implicar muchas banderas y opciones. Por lo tanto, es posible especificar un perfil de configuración que se utilizará para obtener las opciones por defecto. Esto se parece a
La carpeta del perfil debe contener un archivo llamado
config.yaml
que es el que almacenará nuestras opciones. La
carpeta también puede contener otros archivos necesarios para el perfil.
Vamos a crear el archivo cluster_profile/config.yaml
e
insertar algunas de nuestras opciones existentes:
Ahora debemos ser capaces de volver a ejecutar nuestro flujo de
trabajo apuntando al perfil en lugar de la lista de las opciones. Para
obligar a nuestro flujo de trabajo a volver a ejecutarse, primero
tenemos que eliminar el archivo de salida
hostname_remote.txt
, y luego podemos probar nuestro nuevo
perfil
BASH
[ocaisa@node1 ~]$ rm hostname_remote.txt
[ocaisa@node1 ~]$ snakemake --profile cluster_profile hostname_remote
El perfil es extremadamente útil en el contexto de nuestro cluster, ya que el ejecutor Slurm tiene muchas opciones, y a veces necesitas usarlas para poder enviar trabajos al cluster al que tienes acceso. Desafortunadamente, los nombres de las opciones en Snakemake no son exactamente los mismos que los de Slurm, por lo que necesitamos la ayuda de una tabla de traducción:
SLURM | Snakemake | Description |
---|---|---|
--partition |
slurm_partition |
the partition a rule/job is to use |
--time |
runtime |
the walltime per job in minutes |
--constraint |
constraint |
may hold features on some clusters |
--mem |
mem, mem_mb |
memory a cluster node must |
provide (mem: string with unit), mem_mb: int | ||
--mem-per-cpu |
mem_mb_per_cpu |
memory per reserved CPU |
--ntasks |
tasks |
number of concurrent tasks / ranks |
--cpus-per-task |
cpus_per_task |
number of cpus per task (in case of SMP, rather use
threads ) |
--nodes |
nodes |
number of nodes |
Las advertencias dadas por Snakemake dieron a entender que podríamos
necesitar proporcionar estas opciones. Una forma de hacerlo es
proporcionarlas como parte de la regla de Snakemake utilizando la
palabra clave resources
, por ejemplo,
y también podemos usar el perfil para definir valores por defecto
para estas opciones para usar con nuestro proyecto, usando la palabra
clave default-resources
. Por ejemplo, la memoria disponible
en nuestro cluster es de unos 4GB por núcleo, así que podemos añadir eso
a nuestro perfil:
Desafío
Sabemos que nuestro problema se ejecuta en muy poco tiempo. Cambia la duración por defecto de nuestros trabajos a dos minutos para Slurm.
Existen varias opciones sbatch
no soportadas
directamente por las definiciones de recursos de la tabla anterior.
Puede utilizar el recurso slurm_extra
para especificar
cualquiera de estas opciones adicionales a sbatch
:
Ejecución local de reglas
Nuestra regla inicial era obtener el nombre de host del nodo de inicio de sesión. Siempre queremos ejecutar esa regla en el nodo de inicio de sesión para que tenga sentido. Si le decimos a Snakemake que ejecute todas las reglas a través del ejecutor Slurm (que es lo que estamos haciendo a través de nuestro nuevo perfil) esto ya no sucederá. Entonces, ¿cómo forzamos la regla para que se ejecute en el nodo de inicio de sesión?
Bueno, en el caso de que una regla de Snakemake realice una tarea
trivial, el envío de trabajos podría ser excesivo (por ejemplo, menos de
1 minuto de tiempo de computación). Similar a nuestro caso, sería una
mejor idea hacer que estas reglas se ejecuten localmente (es decir,
donde se ejecuta el comando snakemake
) en lugar de como un
trabajo. Snakemake le permite indicar qué reglas deben ejecutarse
siempre localmente con la palabra clave localrules
. Vamos a
definir hostname_login
como una regla local cerca de la
parte superior de nuestro Snakefile
.
Puntos Clave
- “Snakemake genera y envía sus propios scripts por lotes para su planificador.”
- “Puedes almacenar parámetros de configuración por defecto en un perfil de Snakemake”
- “
localrules
define reglas que son ejecutadas localmente, y nunca enviadas a un cluster.”
Content from Marcadores de posición
Última actualización: 2025-04-05 | Mejora esta página
Hoja de ruta
Preguntas
- “¿Cómo hago una regla genérica?”
Objetivos
- “Mira cómo Snakemake trata algunos errores”
Nuestro Snakefile tiene algunos duplicados. Por ejemplo, los nombres de los archivos de texto se repiten en algunos lugares a lo largo de las reglas del Snakefile. Los Snakefiles son una forma de código y, en cualquier código, la repetición puede dar lugar a problemas (por ejemplo, renombramos un archivo de datos en una parte del Snakefile pero olvidamos renombrarlo en otro lugar).
D.R.Y. (Don’t Repeat Yourself) (No te repitas)
En muchos lenguajes de programación, la mayor parte de las características del lenguaje están ahí para permitir al programador describir rutinas computacionales largas como código corto, expresivo y bonito. Las características de Python, R o Java, como las variables y funciones definidas por el usuario, son útiles en parte porque nos evitan tener que escribir (o pensar) en todos los detalles una y otra vez. Este buen hábito de escribir las cosas sólo una vez se conoce como el principio de “No te repitas” o D.R.Y. (por sus siglas en inglés).
Vamos a eliminar algunas de las repeticiones de nuestro archivo Snakefile.
Marcadores de posición
Para hacer una regla más genérica necesitamos marcadores de posición. Veamos qué aspecto tiene un marcador de posición
Como recordatorio, aquí está la versión anterior del último episodio:
PYTHON
rule hostname_remote:
output: "hostname_remote.txt"
input:
shell:
"hostname > hostname_remote.txt"
La nueva regla ha reemplazado nombres de archivo explícitos con cosas
en {curly brackets}
, específicamente {output}
(pero también podría haber sido {input}
…si eso tuviera un
valor y fuera útil).
{input}
y {output}
son marcadores
de posición
Los marcadores de posición se utilizan en la sección
shell
de una regla, y Snakemake los reemplazará con los
valores apropiados - {input}
con el nombre completo del
archivo de entrada, y {output}
con el nombre completo del
archivo de salida - antes de ejecutar el comando.
{resources}
también es un marcador de posición, y
podemos acceder a un elemento con nombre del {resources}
con la notación {resources.runtime}
(por ejemplo).
Puntos Clave
- “Las reglas de Snakemake se hacen más genéricas con marcadores de posición”
- “Los marcadores de posición en la parte shell de la regla se sustituyen por valores basados en los comodines elegidos”
Content from Aplicaciones MPI y Snakemake
Última actualización: 2025-07-01 | Mejora esta página
Hoja de ruta
Preguntas
- “¿Cómo puedo ejecutar una aplicación MPI a través de Snakemake en el cluster?”
Objetivos
- “Definir reglas para ejecutar localmente y en el cluster”
Ahora es el momento de volver a nuestro flujo de trabajo real.
Podemos ejecutar un comando en el cluster, pero ¿qué pasa con la
ejecución de la aplicación MPI que nos interesa? Nuestra aplicación se
llama amdahl
y está disponible como módulo de entorno.
Desafío
Localiza y carga el módulo amdahl
y luego
reemplaza nuestra regla hostname_remote
con una
versión que ejecute amdahl
. (No te preocupes por el MPI
paralelo todavía, ejecútalo con una sola CPU,
mpiexec -n 1 amdahl
).
¿Su regla se ejecuta correctamente? Si no es así, revise los archivos de registro para averiguar por qué
localizará y cargará el módulo amdahl
. Podemos entonces
actualizar/reemplazar nuestra regla para ejecutar la aplicación
amdahl
:
Sin embargo, cuando intentamos ejecutar la regla obtenemos un error
(a menos que ya tengas una versión diferente de amdahl
disponible en tu ruta). Snakemake informa de la ubicación de los logs y
si miramos dentro podemos (eventualmente) encontrar
SALIDA
...
mpiexec -n 1 amdahl > amdahl_run.txt
--------------------------------------------------------------------------
mpiexec was unable to find the specified executable file, and therefore
did not launch the job. This error was first reported for process
rank 0; it may have occurred for other processes as well.
NOTE: A common cause for this error is misspelling a mpiexec command
line parameter option (remember that mpiexec interprets the first
unrecognized command line token as the executable).
Node: tmpnode1
Executable: amdahl
--------------------------------------------------------------------------
...
Así que, aunque cargamos el módulo antes de ejecutar el flujo de trabajo, nuestra regla Snakemake no encontró el ejecutable. Esto se debe a que la regla de Snakemake se ejecuta en un entorno de ejecución limpio, y tenemos que decirle de alguna manera que cargue el módulo de entorno necesario antes de intentar ejecutar la regla.
Snakemake y módulos de entorno
Nuestra aplicación se llama amdahl
y está disponible en
el sistema a través de un módulo de entorno, por lo que necesitamos
decirle a Snakemake que cargue el módulo antes de que intente ejecutar
la regla. Snakemake es consciente de los módulos de entorno, y estos
pueden ser especificados a través de (otra) opción:
PYTHON
rule amdahl_run:
output: "amdahl_run.txt"
input:
envmodules:
"mpi4py",
"amdahl"
input:
shell:
"mpiexec -n 1 amdahl > {output}"
Sin embargo, añadir estas líneas no es suficiente para que la regla se ejecute. No sólo tienes que decirle a Snakemake qué módulos cargar, sino que también tienes que decirle que use módulos de entorno en general (ya que se considera que el uso de módulos de entorno hace que tu entorno de ejecución sea menos reproducible, ya que los módulos disponibles pueden diferir de un cluster a otro). Esto requiere que le des a Snakemake una opción adicional
Desafío
Utilizaremos módulos de entorno durante el resto del tutorial, así
que conviértalo en una opción por defecto de nuestro perfil
(estableciendo su valor en True
)
Snakemake y MPI
En realidad no hemos ejecutado una aplicación MPI en la última sección, ya que sólo lo hemos hecho en un núcleo. ¿Cómo solicitamos que se ejecute en varios núcleos para una única regla?
Snakemake tiene soporte general para MPI, pero el único ejecutor que actualmente soporta explícitamente MPI es el ejecutor Slurm (¡por suerte para nosotros!). Si volvemos a mirar nuestra tabla de traducción de Slurm a Snakemake nos daremos cuenta de que las opciones relevantes aparecen cerca de la parte inferior:
SLURM | Snakemake | Description |
---|---|---|
… | … | … |
--ntasks |
tasks |
number of concurrent tasks / ranks |
--cpus-per-task |
cpus_per_task |
number of cpus per task (in case of SMP, rather use
threads ) |
--nodes |
nodes |
number of nodes |
La que nos interesa es tasks
ya que sólo vamos a
aumentar el número de rangos. Podemos definirlas en una sección
resources
de nuestra regla y referirnos a ellas usando
marcadores de posición:
PYTHON
rule amdahl_run:
output: "amdahl_run.txt"
input:
envmodules:
"amdahl"
resources:
mpi='mpiexec',
tasks=2
input:
shell:
"{resources.mpi} -n {resources.tasks} amdahl > {output}"
Eso funcionó pero ahora tenemos un pequeño problema. Queremos hacer
esto para algunos valores diferentes de tasks
lo que
significaría que necesitaríamos un archivo de salida diferente para cada
ejecución. Sería genial si de alguna manera podemos indicar en el
output
el valor que queremos utilizar para
tasks
… y que Snakemake lo recoja.
Podríamos utilizar un wildcard en output
para
poder definir el tasks
que deseamos utilizar. La sintaxis
de este comodín es la siguiente
donde parallel_tasks
es nuestro comodín.
Comodines
Los comodines se utilizan en las líneas input
y
output
de la regla para representar partes de nombres de
archivo. Al igual que el patrón *
del intérprete de
comandos, el comodín puede sustituir a cualquier texto para formar el
nombre de archivo deseado. Al igual que con el nombre de sus reglas,
puede elegir cualquier nombre que desee para sus comodines, así que aquí
hemos utilizado parallel_tasks
. El uso de los mismos
comodines en la entrada y la salida es lo que le dice a Snakemake cómo
hacer coincidir los archivos de entrada con los archivos de salida.
Si dos reglas usan un comodín con el mismo nombre entonces Snakemake las tratará como entidades diferentes - las reglas en Snakemake son autocontenidas de esta manera.
En la línea shell
puede hacer referencia al comodín con
{wildcards.parallel_tasks}
Orden de operaciones de Snakemake
Sólo estamos empezando con algunas reglas simples, pero vale la pena pensar en lo que Snakemake está haciendo exactamente cuando lo ejecutas. Hay tres fases distintas:
- Prepara la ejecución:
- Lee todas las definiciones de reglas del archivo Snakefile
- Planea qué hacer:
- Ve qué fichero(s) le estás pidiendo que haga
- Busca una regla coincidente mirando las
output
s de todas las reglas que conoce - Rellena los comodines para calcular el
input
de esta regla - Comprueba que este fichero de entrada (si es necesario) está realmente disponible
- Ejecuta los pasos:
- Crea el directorio para el fichero de salida, si es necesario
- Elimina el fichero de salida antiguo si ya está ahí
- Sólo entonces, ejecuta el comando shell con los marcadores de posición sustituidos
- Comprueba que el comando se ejecuta sin errores y crea el nuevo fichero de salida como se esperaba
La cantidad de comprobaciones puede parecer pedante ahora mismo, pero a medida que el flujo de trabajo gane más pasos esto nos resultará realmente muy útil.
Usando comodines en nuestra regla
Nos gustaría utilizar un comodín en output
para
permitirnos definir el número de tasks
que deseamos
utilizar. Basándonos en lo que hemos visto hasta ahora, se podría
imaginar que esto podría tener el siguiente aspecto
PYTHON
rule amdahl_run:
output: "amdahl_run_{parallel_tasks}.txt"
input:
envmodules:
"amdahl"
resources:
mpi="mpiexec",
tasks="{parallel_tasks}"
input:
shell:
"{resources.mpi} -n {resources.tasks} amdahl > {output}"
pero hay dos problemas con esto:
- La única forma de que Snakemake conozca el valor del comodín es que el usuario solicite explícitamente un archivo de salida concreto (en lugar de llamar a la regla):
Esto es perfectamente válido, ya que Snakemake puede averiguar que tiene una regla que puede coincidir con ese nombre de archivo.
-
El mayor problema es que incluso haciendo eso no funciona, parece que no podemos usar un comodín para
tasks
:SALIDA
WorkflowError: SLURM job submission failed. The error message was sbatch: error: Invalid numeric value "{parallel_tasks}" for --ntasks.
Desafortunadamente para nosotros, no hay forma directa de acceder a
los comodines para tasks
. La razón de esto es que Snakemake
intenta utilizar el valor de tasks
durante su etapa de
inicialización, que es antes de que sepamos el valor del comodín.
Necesitamos aplazar la determinación de tasks
para más
adelante. Esto se puede conseguir especificando una función de entrada
en lugar de un valor para este escenario. La solución entonces es
escribir una función de un solo uso para manipular Snakemake para que
haga esto por nosotros. Dado que la función es específicamente para la
regla, podemos utilizar una función de una sola línea sin nombre. Este
tipo de funciones se llaman funciones anónimas o funciones lamdba (ambas
significan lo mismo), y son una característica de Python (y otros
lenguajes de programación).
Para definir una función lambda en python, la sintaxis general es la siguiente:
Dado que nuestra función puede tomar los comodines como
argumentos, podemos usarlos para establecer el valor de
tasks
:
PYTHON
rule amdahl_run:
output: "amdahl_run_{parallel_tasks}.txt"
input:
envmodules:
"amdahl"
resources:
mpi="mpiexec",
# No hay una forma directa de acceder al comodín en las tareas, así que necesitamos hacerlo
# de forma indirecta declarando una función breve que reciba los comodines como argumento
tasks=lambda wildcards: int(wildcards.parallel_tasks)
input:
shell:
"{resources.mpi} -n {resources.tasks} amdahl > {output}"
Ahora tenemos una regla que puede utilizarse para generar resultados de ejecuciones de un número arbitrario de tareas paralelas.
Comentarios en Snakefiles
En el código anterior, la línea que empieza por #
es una
línea de comentario. Esperemos que ya tenga el hábito de añadir
comentarios a sus propios scripts. Los buenos comentarios hacen que
cualquier script sea más legible, y esto es igualmente cierto con
Snakefiles.
Como nuestra regla es ahora capaz de generar un número arbitrario de
ficheros de salida las cosas podrían llenarse mucho en nuestro
directorio actual. Probablemente sea mejor entonces poner las
ejecuciones en una carpeta separada para mantener las cosas ordenadas.
Podemos añadir la carpeta directamente a nuestro output
y
Snakemake se encargará de crear el directorio por nosotros:
PYTHON
rule amdahl_run:
output: "runs/amdahl_run_{parallel_tasks}.txt"
input:
envmodules:
"amdahl"
resources:
mpi="mpiexec",
# No hay una forma directa de acceder al comodín en las tareas, así que necesitamos hacerlo
# de forma indirecta declarando una función breve que reciba los comodines como argumento
tasks=lambda wildcards: int(wildcards.parallel_tasks)
input:
shell:
"{resources.mpi} -n {resources.tasks} amdahl > {output}"
Desafío
Crea un fichero de salida (en la carpeta runs
) para el
caso en que tengamos 6 tareas paralelas
(SUGERENCIA: Recuerde que Snakemake tiene que ser capaz de hacer
coincidir el archivo solicitado con el output
de una
regla)
Otra cosa sobre nuestra aplicación amdahl
es que en
última instancia queremos procesar la salida para generar nuestro
gráfico de escalado. La salida en este momento es útil para la lectura,
pero hace que el procesamiento más difícil. amdahl
tiene
una opción que realmente hace esto más fácil para nosotros. Para ver las
opciones de amdahl
podemos utilizar
SALIDA
usage: amdahl [-h] [-p [PARALLEL_PROPORTION]] [-w [WORK_SECONDS]] [-t] [-e]
options:
-h, --help show this help message and exit
-p [PARALLEL_PROPORTION], --parallel-proportion [PARALLEL_PROPORTION]
Parallel proportion should be a float between 0 and 1
-w [WORK_SECONDS], --work-seconds [WORK_SECONDS]
Total seconds of workload, should be an integer greater than 0
-t, --terse Enable terse output
-e, --exact Disable random jitter
La opción que estamos buscando es --terse
, y eso hará
que amdahl
imprima la salida en un formato que es mucho más
fácil de procesar, JSON. El formato JSON en un archivo normalmente
utiliza la extensión de archivo .json
así que vamos a
añadir esa opción a nuestro comando shell
y
cambiar el formato de archivo de la output
para que
coincida con nuestro nuevo comando:
PYTHON
rule amdahl_run:
output: "runs/amdahl_run_{parallel_tasks}.json"
input:
envmodules:
"amdahl"
resources:
mpi="mpiexec",
# No hay una forma directa de acceder al comodín en las tareas, así que necesitamos hacerlo
# de forma indirecta declarando una función breve que reciba los comodines como argumento
tasks=lambda wildcards: int(wildcards.parallel_tasks)
input:
shell:
"{resources.mpi} -n {resources.tasks} amdahl --terse > {output}"
Había otro parámetro para amdahl
que me llamó la
atención. amdahl
tiene una opción
--parallel-proportion
(o -p
) que puede
interesarnos cambiar, ya que modifica el comportamiento del código y,
por tanto, influye en los valores que obtenemos en nuestros resultados.
Vamos a añadir otra capa de directorio a nuestro formato de salida para
reflejar una elección particular para este valor. Podemos utilizar un
comodín para no tener que elegir el valor de inmediato:
PYTHON
rule amdahl_run:
output: "p_{parallel_proportion}/runs/amdahl_run_{parallel_tasks}.json"
input:
envmodules:
"amdahl"
resources:
mpi="mpiexec",
# No hay una forma directa de acceder al comodín en las tareas, así que necesitamos hacerlo
# de forma indirecta declarando una función breve que reciba los comodines como argumento
tasks=lambda wildcards: int(wildcards.parallel_tasks)
input:
shell:
"{resources.mpi} -n {resources.tasks} amdahl --terse -p {wildcards.parallel_proportion} > {output}"
Desafío
Crear un fichero de salida para un valor de -p
de 0.999
(el valor por defecto es 0.8) para el caso en que tengamos 6 tareas
paralelas.
Puntos Clave
- “Snakemake elige la regla apropiada sustituyendo los comodines de forma que la salida coincida con el objetivo”
- “Snakemake comprueba varias condiciones de error y se detendrá si ve un problema”
Content from Encadenamiento de reglas
Última actualización: 2025-04-05 | Mejora esta página
Hoja de ruta
Preguntas
- “¿Cómo combino reglas en un flujo de trabajo?”
- “¿Cómo hago una regla con múltiples entradas y salidas?”
Objetivos
- “”
Una cadena de múltiples reglas
Ahora tenemos una regla que puede generar una salida para cualquier
valor de -p
y cualquier número de tareas, sólo tenemos que
llamar a Snakemake con los parámetros que queremos:
Aunque eso no es exactamente conveniente, para generar un conjunto de datos completo tenemos que ejecutar Snakemake muchas veces con diferentes objetivos de archivos de salida. En lugar de eso, vamos a crear una regla que puede generar los archivos para nosotros.
Encadenar reglas en Snakemake es cuestión de elegir patrones de nombres de archivos que conecten las reglas. Hay algo de arte en ello - la mayoría de las veces hay varias opciones que funcionarán:
PYTHON
rule generate_run_files:
output: "p_{parallel_proportion}_runs.txt"
input: "p_{parallel_proportion}/runs/amdahl_run_6.json"
shell:
"echo {input} done > {output}"
Desafío
La nueva regla no está haciendo ningún trabajo, sólo se está asegurando de que creamos el archivo que queremos. No vale la pena ejecutarla en el cluster. ¿Cómo asegurarse de que se ejecuta sólo en el nodo de inicio de sesión?
Ahora vamos a ejecutar la nueva regla (recuerda que tenemos que
solicitar el archivo de salida por su nombre ya que output
en nuestra regla contiene un patrón comodín):
SALIDA
Using profile cluster_profile/ for setting default command line arguments.
Building DAG of jobs...
Retrieving input from storage.
Using shell: /cvmfs/software.eessi.io/versions/2023.06/compat/linux/x86_64/bin/bash
Provided remote nodes: 3
Job stats:
job count
------------------ -------
amdahl_run 1
generate_run_files 1
total 2
Select jobs to execute...
Execute 1 jobs...
[Tue Jan 30 17:39:29 2024]
rule amdahl_run:
output: p_0.999/runs/amdahl_run_6.json
jobid: 1
reason: Missing output files: p_0.999/runs/amdahl_run_6.json
wildcards: parallel_proportion=0.999, parallel_tasks=6
resources: mem_mb=1000, mem_mib=954, disk_mb=1000, disk_mib=954,
tmpdir=<TBD>, mem_mb_per_cpu=3600, runtime=2, mpi=mpiexec, tasks=6
mpiexec -n 6 amdahl --terse -p 0.999 > p_0.999/runs/amdahl_run_6.json
No SLURM account given, trying to guess.
Guessed SLURM account: def-users
Job 1 has been submitted with SLURM jobid 342 (log: /home/ocaisa/.snakemake/slurm_logs/rule_amdahl_run/342.log).
[Tue Jan 30 17:47:31 2024]
Finished job 1.
1 of 2 steps (50%) done
Select jobs to execute...
Execute 1 jobs...
[Tue Jan 30 17:47:31 2024]
localrule generate_run_files:
input: p_0.999/runs/amdahl_run_6.json
output: p_0.999_runs.txt
jobid: 0
reason: Missing output files: p_0.999_runs.txt;
Input files updated by another job: p_0.999/runs/amdahl_run_6.json
wildcards: parallel_proportion=0.999
resources: mem_mb=1000, mem_mib=954, disk_mb=1000, disk_mib=954,
tmpdir=/tmp, mem_mb_per_cpu=3600, runtime=2
echo p_0.999/runs/amdahl_run_6.json done > p_0.999_runs.txt
[Tue Jan 30 17:47:31 2024]
Finished job 0.
2 of 2 steps (100%) done
Complete log: .snakemake/log/2024-01-30T173929.781106.snakemake.log
Mira los mensajes de registro que Snakemake imprime en la terminal. ¿Qué ha ocurrido aquí?
- Snakemake busca una regla para hacer
p_0.999_runs.txt
- Determina que “generate_run_files” puede hacer esto si
parallel_proportion=0.999
- Por lo tanto, ve que la entrada necesaria es
p_0.999/runs/amdahl_run_6.json
- Snakemake busca una regla para hacer
p_0.999/runs/amdahl_run_6.json
- Determina que “amdahl_run” puede hacer esto si
parallel_proportion=0.999
yparallel_tasks=6
- Ahora que Snakemake ha alcanzado un archivo de entrada disponible (en este caso, no se requiere ningún archivo de entrada), ejecuta ambos pasos para obtener la salida final
Así, en pocas palabras, es como construimos flujos de trabajo en Snakemake.
- Definir reglas para todos los pasos de procesamiento
- Elija
input
youtput
patrones de nomenclatura que permitan a Snakemake vincular las reglas - Indicar a Snakemake que genere los archivos de salida finales
Si usted está acostumbrado a escribir scripts regulares esto toma un poco de tiempo para acostumbrarse. En lugar de enumerar los pasos en orden de ejecución, siempre se está trabajando hacia atrás desde el resultado final deseado. El orden de las operaciones viene determinado por la aplicación de las reglas de concordancia de patrones a los nombres de archivo, no por el orden de las reglas en el archivo Snakefile.
¿Salidas primero?
El enfoque de Snakemake de trabajar hacia atrás desde la salida
deseada para determinar el flujo de trabajo es la razón por la que
estamos poniendo las líneas output
primero en todas
nuestras reglas - ¡para recordarnos que esto es lo que Snakemake mira
primero!
Muchos usuarios de Snakemake, y de hecho la documentación oficial,
prefieren tener el input
primero, por lo que en la práctica
se debe utilizar cualquier orden que tenga sentido para usted.
log
salidas en Snakemake
Snakemake tiene un campo de regla dedicado para las salidas que son
archivos
de registro, y estos son tratados en su mayoría como salidas
regulares, excepto que los archivos de registro no se eliminan si el
trabajo produce un error. Esto significa que usted puede mirar el
registro para ayudar a diagnosticar el error. En un flujo de trabajo
real esto puede ser muy útil, pero en términos de aprendizaje de los
fundamentos de Snakemake nos quedaremos con los campos regulares
input
y output
aquí.
Los errores son normales
No te desanimes si ves errores cuando pruebes por primera vez tus nuevos pipelines de Snakemake. Hay muchas cosas que pueden salir mal al escribir un nuevo flujo de trabajo, y normalmente necesitarás varias iteraciones para hacer las cosas bien. Una ventaja del enfoque de Snakemake en comparación con los scripts regulares es que Snakemake falla rápidamente cuando hay un problema, en lugar de seguir adelante y potencialmente ejecutar cálculos basura sobre datos parciales o corruptos. Otra ventaja es que cuando un paso falla, podemos reanudarlo con seguridad desde donde lo dejamos.
Puntos Clave
- “Snakemake enlaza reglas buscando iterativamente reglas que tengan entradas faltantes”
- “Las reglas pueden tener múltiples entradas y/o salidas con nombre”
- “Si un comando del shell no produce una salida esperada entonces Snakemake lo considerará como un fallo”
Content from Procesamiento de listas de entradas
Última actualización: 2025-04-05 | Mejora esta página
Hoja de ruta
Preguntas
- “¿Cómo puedo procesar varios archivos a la vez?”
- “¿Cómo combino varios archivos?”
Objetivos
- “Usa Snakemake para procesar todas nuestras muestras a la vez”
- “Haz un gráfico de escalabilidad que agrupe nuestros resultados”
Hemos creado una regla que puede generar un único archivo de salida, pero no vamos a crear varias reglas para cada archivo de salida. Queremos generar todos los archivos de ejecución con una sola regla si pudiéramos, bueno Snakemake puede efectivamente tomar una lista de archivos de entrada:
PYTHON
rule generate_run_files:
output: "p_{parallel_proportion}_runs.txt"
input: "p_{parallel_proportion}/runs/amdahl_run_2.json", "p_{parallel_proportion}/runs/amdahl_run_6.json"
shell:
"echo {input} done > {output}"
Eso está muy bien, pero no queremos tener que listar todos los archivos que nos interesan individualmente. ¿Cómo podemos hacerlo?
Definición de una lista de muestras a procesar
Para ello, podemos definir algunas listas como variables globales de Snakemake.
Las variables globales deben añadirse antes de las reglas en el Snakefile.
- A diferencia de lo que ocurre con las variables en los scripts de
shell, podemos poner espacios alrededor del signo
=
, pero no son obligatorios. - Las listas de cadenas entrecomilladas van entre corchetes y separadas por comas. Si sabes algo de Python reconocerás esto como sintaxis de listas de Python.
- Una buena convención es utilizar nombres en mayúsculas para estas variables, pero no es obligatorio.
- Aunque éstas se denominan variables, en realidad no se pueden cambiar los valores una vez que el flujo de trabajo se está ejecutando, por lo que las listas definidas de esta manera son más como constantes.
Usando una regla de Snakemake para definir un lote de salidas
Ahora vamos a actualizar nuestro Snakefile para aprovechar la nueva variable global y crear una lista de archivos:
PYTHON
rule generate_run_files:
output: "p_{parallel_proportion}_runs.txt"
input: expand("p_{{parallel_proportion}}/runs/amdahl_run_{count}.json", count=NTASK_SIZES)
shell:
"echo {input} done > {output}"
La función expand(...)
de esta regla genera una lista de
nombres de archivo, tomando como plantilla lo primero que aparece entre
paréntesis y sustituyendo {count}
por todos los
NTASK_SIZES
. Dado que hay 5 elementos en la lista, esto
producirá 5 archivos que queremos hacer. Tenga en cuenta que tuvimos que
proteger nuestro comodín en un segundo conjunto de paréntesis para que
no fuera interpretado como algo que necesitaba ser expandido.
En nuestro caso actual seguimos dependiendo del nombre de fichero
para definir el valor del comodín parallel_proportion
, por
lo que no podemos llamar a la regla directamente, seguimos necesitando
solicitar un fichero específico:
Si no especifica un nombre de regla de destino o cualquier nombre de archivo en la línea de comandos cuando se ejecuta Snakemake, el valor predeterminado es utilizar ** la primera regla ** en el Snakefile como el objetivo.
Reglas como destinos
Dar el nombre de una regla a Snakemake en la línea de comandos sólo funciona cuando esa regla tiene sin comodines en las salidas, porque Snakemake no tiene forma de saber cuáles podrían ser los comodines deseados. Verá el error “Las reglas de destino no pueden contener comodines” Esto también puede ocurrir cuando no se proporciona ningún objetivo explícito en la línea de comandos, y Snakemake intenta ejecutar la primera regla definida en el archivo Snakefile.
Reglas que combinan varias entradas
Nuestra regla generate_run_files
es una regla que toma
una lista de archivos de entrada. La longitud de esa lista no está
fijada por la regla, sino que puede cambiar en función de
NTASK_SIZES
.
En nuestro flujo de trabajo el paso final es tomar todos los archivos
generados y combinarlos en un gráfico. Para ello, es posible que haya
oído que algunas personas utilizan una biblioteca de Python llamada
matplotlib
. Está más allá del alcance de este tutorial
escribir el script de python para crear un gráfico final, así que te
proporcionamos el script como parte de esta lección. Puede descargarlo
con
El script plot_terse_amdahl_results.py
necesita una
línea de comandos parecida a:
BASH
python plot_terse_amdahl_results.py --output <output image filename> <1st input file> <2nd input file> ...
Introduzcámoslo en nuestra regla generate_run_files
:
PYTHON
rule generate_run_files:
output: "p_{parallel_proportion}_runs.txt"
input: expand("p_{{parallel_proportion}}/runs/amdahl_run_{count}.json", count=NTASK_SIZES)
shell:
"python plot_terse_amdahl_results.py --output {output} {input}"
Desafío
Este script depende de matplotlib
, ¿está disponible como
módulo de entorno? Añada este requisito a nuestra regla.
¡Por fin podemos generar un gráfico de escala! Ejecute el último comando de Snakemake:
Desafío
Genera el gráfico de escalabilidad para todos los valores de 1 a 10 núcleos.
Desafío
Vuelva a ejecutar el flujo de trabajo para un valor p
de
0.8
Ronda de bonificación
Cree una regla final que pueda llamarse directamente y genere un
gráfico de escala para 3 valores diferentes de p
.
Puntos Clave
- “Utilice la función
expand()
para generar listas de nombres de archivo que desee combinar” - “Cualquier
{input}
de una regla puede ser una lista de longitud variable”