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:

BASH

[ocaisa@node1 ~]$ hostname

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:

BASH

[ocaisa@node1 ~]$ hostname > hostname_login.txt

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

  1. El archivo se llama Snakefile - con mayúscula S y sin extensión de archivo.
  2. 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.
  3. La definición de la regla comienza con la palabra clave rule seguida por el nombre de la regla, luego dos puntos.
  4. 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.
  5. Las palabras clave input, output, y shell van todas seguidas de dos puntos (“:”).
  6. Los nombres de los archivos y el comando shell están todos en "quotes".
  7. 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é.
  8. 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 -j1 -p hostname_login

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:

BASH

module spider snakemake

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

BASH

[ocaisa@node1 ~]$ module load snakemake

y luego asegúrate de que tenemos el comando snakemake disponible

BASH

[ocaisa@node1 ~]$ which snakemake

SALIDA

/cvmfs/software.eessi.io/host_injections/2023.06/software/linux/x86_64/amd/zen3/software/snakemake/8.2.1-foss-2023a/bin/snakemake

BASH

snakemake -j1 -p hostname_login

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?

  1. Protege los archivos de salida existentes
  2. Imprime en el terminal los comandos shell que se están ejecutando
  3. Le dice a Snakemake que sólo ejecute un proceso a la vez
  4. Solicita al usuario el fichero de entrada correcto

Puedes buscar en el texto pulsando /, y salir de nuevo al shell con q.

  1. 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

BASH

[ocaisa@node1 ~]$ snakemake -j1 -p --executor slurm hostname_login

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

BASH

[ocaisa@node1 ~]$ snakemake -j1 -p --executor slurm hostname_remote

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:

BASH

[ocaisa@node1 ~]$ cat 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

BASH

snakemake --profile myprofileFolder ...

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:

YAML

printshellcmds: True
jobs: 3
executor: slurm

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,

PYTHON

rule:
    input: ...
    output: ...
    resources:
        partition = <partition name>
        runtime = <some number>

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:

YAML

printshellcmds: True
jobs: 3
executor: slurm
default-resources:
  - mem_mb_per_cpu=3600

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.

YAML

printshellcmds: True
jobs: 3
executor: slurm
default-resources:
  - mem_mb_per_cpu=3600
  - runtime=2

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:

PYTHON

rule myrule:
    input: ...
    output: ...
    resources:
        slurm_extra="--mail-type=ALL --mail-user=<your email>"

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.

PYTHON

localrules: hostname_login

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

PYTHON

rule hostname_remote:
    output: "hostname_remote.txt"
    input:
    shell:
        "hostname > {output}"

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é

BASH

module spider amdahl
module load amdahl

localizará y cargará el módulo amdahl. Podemos entonces actualizar/reemplazar nuestra regla para ejecutar la aplicación amdahl:

PYTHON

rule amdahl_run:
    output: "amdahl_run.txt"
    input:
    shell:
        "mpiexec -n 1 amdahl > {output}"

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

BASH

snakemake --profile cluster_profile --use-envmodules amdahl_run

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)

Actualiza el perfil de nuestro cluster a

YAML

printshellcmds: True
jobs: 3
executor: slurm
default-resources:
  - mem_mb_per_cpu=3600
  - runtime=2
use-envmodules: True

Si quieres probarlo, tienes que borrar el fichero de salida de la regla y volver a ejecutar Snakemake.

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

PYTHON

output: "amdahl_run_{parallel_tasks}.txt"

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:

  1. Prepara la ejecución:
    1. Lee todas las definiciones de reglas del archivo Snakefile
  2. Planea qué hacer:
    1. Ve qué fichero(s) le estás pidiendo que haga
    2. Busca una regla coincidente mirando las outputs de todas las reglas que conoce
    3. Rellena los comodines para calcular el input de esta regla
    4. Comprueba que este fichero de entrada (si es necesario) está realmente disponible
  3. Ejecuta los pasos:
    1. Crea el directorio para el fichero de salida, si es necesario
    2. Elimina el fichero de salida antiguo si ya está ahí
    3. Sólo entonces, ejecuta el comando shell con los marcadores de posición sustituidos
    4. Comprueba que el comando se ejecuta sin errores y crea el nuevo fichero de salida como se esperaba

Modo de ejecución en seco (-n)

A menudo es útil ejecutar sólo las dos primeras fases, de modo que Snakemake planificará los trabajos a ejecutar, y los imprimirá en la pantalla, pero nunca los ejecutará realmente. Esto se hace con la bandera -n, eg:

BASH

> $ snakemake -n ...

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):

BASH

  snakemake --profile cluster_profile amdahl_run_2.txt

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:

PYTHON

lambda x: x + 54

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)

BASH

snakemake --profile cluster_profile runs/amdahl_run_6.txt

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

BASH

[ocaisa@node1 ~]$ module load amdahl
[ocaisa@node1 ~]$ amdahl --help

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.

BASH

snakemake --profile cluster_profile p_0.999/runs/amdahl_run_6.json

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:

BASH

snakemake --profile cluster_profile p_0.999/runs/amdahl_run_6.json

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?

Necesitamos añadir la nueva regla a nuestro localrules:

PYTHON

localrules: hostname_login, generate_run_files

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):

BASH

[ocaisa@node1 ~]$ snakemake --profile cluster_profile/ p_0.999_runs.txt

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í?

  1. Snakemake busca una regla para hacer p_0.999_runs.txt
  2. Determina que “generate_run_files” puede hacer esto si parallel_proportion=0.999
  3. Por lo tanto, ve que la entrada necesaria es p_0.999/runs/amdahl_run_6.json
  4. Snakemake busca una regla para hacer p_0.999/runs/amdahl_run_6.json
  5. Determina que “amdahl_run” puede hacer esto si parallel_proportion=0.999 y parallel_tasks=6
  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.

  1. Definir reglas para todos los pasos de procesamiento
  2. Elija input y output patrones de nomenclatura que permitan a Snakemake vincular las reglas
  3. 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.

PYTHON

# Task sizes we wish to run
NTASK_SIZES = [1, 2, 3, 4, 5]
  • 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:

BASH

snakemake --profile cluster_profile/ p_0.999_runs.txt

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

BASH

curl -O https://ocaisa.github.io/hpc-workflows/files/plot_terse_amdahl_results.py

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.

PYTHON

rule generate_run_files:
    output: "p_{parallel_proportion}_scalability.jpg"
    input:  expand("p_{{parallel_proportion}}/runs/amdahl_run_{count}.json", count=NTASK_SIZES)
    envmodules:
      "matplotlib"
    shell:
        "python plot_terse_amdahl_results.py --output {output} {input}"

¡Por fin podemos generar un gráfico de escala! Ejecute el último comando de Snakemake:

BASH

snakemake --profile cluster_profile/ p_0.999_scalability.jpg

Desafío

Genera el gráfico de escalabilidad para todos los valores de 1 a 10 núcleos.

PYTHON

NTASK_SIZES = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Desafío

Vuelva a ejecutar el flujo de trabajo para un valor p de 0.8

BASH

snakemake --profile cluster_profile/ p_0.8_scalability.jpg

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”