Ejemplos

Ejemplo: Descarga de secuencias de GenBank

Antes de ejecutar análisis más complejos, veremos tres formas de descargar secuencias de GenBank con Entrez Direct, Apptainer y Slurm.

Los tres ejemplos realizan prácticamente la misma tarea, pero muestran distintas formas de organizar el trabajo:

Ejemplo ¿Qué hace? Ejecución
Una accesión Descarga una secuencia Un trabajo de Slurm
Bucle while Recorre una lista de accesiones Un trabajo; las descargas se realizan una tras otra
Array de Slurm Asigna una accesión a cada tarea Varias tareas pueden ejecutarse simultáneamente
ImportantEl bucle while no paraleliza

El bucle permite automatizar el procesamiento de varias muestras, pero las procesa secuencialmente dentro de un solo trabajo. El array es el ejemplo que realmente permite procesar varias accesiones al mismo tiempo.

Preparar la práctica

Crea una carpeta con subdirectorios para el contenedor, los scripts, los registros y las secuencias descargadas:

mkdir -p efetch_test/{sif,scripts,logs,fasta}
cd efetch_test
Note

La carpeta logs/ debe existir antes de ejecutar sbatch. Slurm abre los archivos de salida y error antes de comenzar el script.

Descargar el contenedor

Para esta práctica utilizaremos la imagen de Entrez Direct disponible en BioContainers:

apptainer pull \
  sif/entrez-direct_25.3.sif \
  docker://quay.io/biocontainers/entrez-direct:25.3--he881be0_0

Comprueba que efetch está disponible dentro del contenedor:

apptainer exec sif/entrez-direct_25.3.sif efetch -help

Descargar la imagen una sola vez evita que cada trabajo o cada tarea del array tenga que consultar nuevamente el registro de contenedores.

Ejemplo 1: descargar una secuencia

Este primer script descarga una sola secuencia de la base de datos nuccore de NCBI en formato FASTA.

Crea el archivo:

nano scripts/efetch_single.sbatch

Contenido:

#!/bin/bash
#SBATCH --job-name=efetch_single
#SBATCH --partition=ib
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=1
#SBATCH --mem=1G
#SBATCH --time=00:10:00
#SBATCH --output=logs/%x-%j.out
#SBATCH --error=logs/%x-%j.err

set -euo pipefail

cd "$SLURM_SUBMIT_DIR"

IMG="sif/entrez-direct_25.3.sif"
ACCESSION="KY293680.1"
OUTPUT="fasta/${ACCESSION}.fasta"

echo "Descargando: $ACCESSION"

apptainer exec "$IMG" \
  efetch -db nuccore -id "$ACCESSION" -format fasta \
  > "$OUTPUT"

echo "Archivo creado: $OUTPUT"

Envía el trabajo:

sbatch scripts/efetch_single.sbatch

Revisa el resultado cuando termine:

squeue -u "$USER"
head fasta/KY293680.1.fasta

Crear una lista de accesiones

Para los siguientes ejemplos se necesita un archivo de texto con una accesión de GenBank por línea. En este contexto conviene llamarlas accesiones o identificadores de GenBank; un voucher es el ejemplar biológico de referencia y no necesariamente corresponde al identificador de la secuencia.

Crea list.txt:

NC_001477.1
NC_001498.1
NC_001542.1
NC_001802.1
NC_001806.2
NC_002549.1
NC_003977.2
NC_004102.1
NC_007605.1
NC_045512.2

Comprueba el número de accesiones:

wc -l list.txt

Para el array, evita líneas vacías y espacios adicionales en esta lista.

Ejemplo 2: procesar una lista con while

Este script utiliza un bucle while para leer list.txt. Todas las accesiones forman parte de un solo trabajo y se descargan una después de otra.

Crea el archivo:

nano scripts/efetch_loop.sbatch

Contenido:

#!/bin/bash
#SBATCH --job-name=efetch_loop
#SBATCH --partition=ib
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=1
#SBATCH --mem=1G
#SBATCH --time=00:20:00
#SBATCH --output=logs/%x-%j.out
#SBATCH --error=logs/%x-%j.err

set -euo pipefail

cd "$SLURM_SUBMIT_DIR"

LIST="list.txt"
OUTPUT_DIR="fasta"
IMG="sif/entrez-direct_25.3.sif"

mkdir -p "$OUTPUT_DIR"

while IFS= read -r ACCESSION; do
  [[ -z "$ACCESSION" ]] && continue

  echo "Descargando: $ACCESSION"

  apptainer exec "$IMG" \
    efetch -db nuccore -id "$ACCESSION" -format fasta \
    > "$OUTPUT_DIR/${ACCESSION}.fasta"
done < "$LIST"

echo "Todas las descargas terminaron"

Envía el trabajo:

sbatch scripts/efetch_loop.sbatch

Cuando termine, revisa cuántos archivos se crearon:

find fasta -maxdepth 1 -name '*.fasta' | wc -l

Ejemplo 3: paralelizar con un array de Slurm

Un array crea varias tareas a partir del mismo script. La variable SLURM_ARRAY_TASK_ID indica qué línea de list.txt debe procesar cada tarea.

Primero obtén el número de accesiones:

N=$(wc -l < list.txt)
echo "$N"

Crea el archivo:

nano scripts/efetch_array.sbatch

Contenido:

#!/bin/bash
#SBATCH --job-name=efetch_array
#SBATCH --partition=ib
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=1
#SBATCH --mem=1G
#SBATCH --time=00:10:00
#SBATCH --output=logs/%x-%A_%a.out
#SBATCH --error=logs/%x-%A_%a.err

set -euo pipefail

cd "$SLURM_SUBMIT_DIR"

LIST="list.txt"
OUTPUT_DIR="fasta"
IMG="sif/entrez-direct_25.3.sif"

mkdir -p "$OUTPUT_DIR"

ACCESSION=$(sed -n "${SLURM_ARRAY_TASK_ID}p" "$LIST")

if [[ -z "$ACCESSION" ]]; then
  echo "No se encontró una accesión para la tarea ${SLURM_ARRAY_TASK_ID}" >&2
  exit 1
fi

echo "Tarea ${SLURM_ARRAY_TASK_ID}: descargando $ACCESSION"

apptainer exec "$IMG" \
  efetch -db nuccore -id "$ACCESSION" -format fasta \
  > "$OUTPUT_DIR/${ACCESSION}.fasta"

echo "Tarea ${SLURM_ARRAY_TASK_ID}: terminó $ACCESSION"

Envía una tarea por cada línea y permite ejecutar como máximo cinco al mismo tiempo:

N=$(wc -l < list.txt)
sbatch --array="1-${N}%5" scripts/efetch_array.sbatch

En 1-${N}%5:

  • 1-${N} crea una tarea por cada línea de la lista.
  • %5 limita el array a un máximo de cinco tareas ejecutándose simultáneamente.
  • Cada tarea solicita 1 CPU y 1 GB de memoria.
  • Por lo tanto, cinco tareas simultáneas pueden utilizar en conjunto hasta 5 CPU y 5 GB.

Revisa las tareas del array:

squeue -u "$USER"

Al finalizar, revisa los archivos y los registros:

ls -lh fasta/
ls -lh logs/

Comparación de los tres métodos

Método Trabajos o tareas Orden de procesamiento Uso recomendado
Una accesión 1 Una sola descarga Probar el contenedor y el comando
Bucle while 1 Secuencial Listas pequeñas o tareas muy breves
Array Una tarea por accesión Paralelo, sujeto a los recursos disponibles Muchas muestras independientes
Tip¿Por qué usar %5?

El límite evita enviar demasiadas descargas simultáneas a NCBI y reduce la presión sobre el clúster. Aumentarlo no garantiza que el trabajo termine antes: Slurm solamente iniciará tareas cuando existan recursos disponibles.

Ejemplo: Análisis filogenético con BEAST2

Esta sección utiliza imágenes públicas del repositorio de Docker Hub:

https://hub.docker.com/repositories/ccervantess

Estas imágenes fueron preparadas y probadas para BEAGLE, con el objetivo de facilitar el uso de software bioinformático sin compilar programas ni modificar el sistema principal del clúster.

Para esta práctica se usará únicamente el archivo testGTR.xml, una prueba pequeña de BEAST2 que permite verificar que Apptainer, Slurm y el contenedor funcionan correctamente en BEAGLE.

El objetivo no es hacer un análisis biológico final ni comparar formalmente CPU contra GPU. Debido a que testGTR.xml es pequeño, la GPU puede no ser más rápida.

Se recomienda descargar imágenes solo de repositorios confiables, como Docker Hub o Quay.io, usando etiquetas de versión específicas en lugar de latest.

Archivo de prueba

El archivo testGTR.xml está disponible en el repositorio oficial de BEAST2, dentro de la carpeta examples: https://github.com/CompEvol/beast2/blob/master/examples/testGTR.xml.

En esta práctica, el archivo se descargará dentro de la carpeta de trabajo beast2_testGTR/data/, para mantener juntos los datos, scripts, contenedores, logs y resultados del ejercicio.

Imágenes usadas en esta práctica

Las imágenes se descargan desde Docker Hub usando Apptainer. Para reproducibilidad, se recomienda usar etiquetas específicas y evitar latest.

Uso Imagen de Docker Hub Etiqueta Archivo .sif sugerido
BEAST2 en CPU ccervantess/beast2 2.7.8 beast2_2.7.8.sif
BEAST2 + BEAGLE + CUDA para V100 ccervantess/beast2-beagle-cuda 2.7.8-gpu-v100 beast2-beagle-cuda_2.7.8-gpu-v100.sif

Preparar la carpeta de trabajo

Desde la raíz del repositorio del manual, crea la carpeta de la práctica:

mkdir -p beast2_testGTR/{data,sif,scripts,logs,runs/cpu,runs/gpu}

Después entra a la carpeta de la práctica:

cd beast2_testGTR

Descarga el archivo testGTR.xml dentro de la carpeta data/ de la práctica:

wget -O data/testGTR.xml \
  https://raw.githubusercontent.com/CompEvol/beast2/master/examples/testGTR.xml

Verifica que el archivo se descargó correctamente:

ls -lh data/testGTR.xml

Copia el archivo testGTR.xml a las carpetas de corrida:

cp data/testGTR.xml runs/cpu/
cp data/testGTR.xml runs/gpu/

La estructura esperada será:

beast2_testGTR/
├── data/
│   └── testGTR.xml
├── logs/
├── runs/
│   ├── cpu/
│   │   └── testGTR.xml
│   └── gpu/
│       └── testGTR.xml
├── scripts/
└── sif/
Note¿Por qué separar CPU y GPU?

BEAST2 escribirá archivos como primate-mtDNA.log, primate.trees y testGTR.xml.state. Si se ejecutan las pruebas CPU y GPU en la misma carpeta, una corrida puede sobrescribir los resultados de la otra.

Descargar la imagen de BEAST2 para CPU

Descarga la imagen de BEAST2 desde Docker Hub y conviértela a formato .sif:

apptainer pull \
  sif/beast2_2.7.8.sif \
  docker://ccervantess/beast2:2.7.8

Revisa que el archivo se haya creado:

ls -lh sif/

Prueba que el ejecutable beast está disponible dentro del contenedor:

apptainer exec sif/beast2_2.7.8.sif beast -help

Ejecutar testGTR.xml en CPU con Slurm

Crea el script:

nano scripts/beast2_gtr_cpu.sbatch

Contenido:

#!/bin/bash
#SBATCH --job-name=beast2_gtr_cpu
#SBATCH --partition=ib
#SBATCH --nodes=1
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=1
#SBATCH --mem=4G
#SBATCH --time=01:00:00
#SBATCH --output=logs/%x-%j.out
#SBATCH --error=logs/%x-%j.err

set -euo pipefail

cd "$SLURM_SUBMIT_DIR"

# Cargar token y chat_id de Telegram 
if [[ -f "$HOME/.telegram" ]]; then
  source "$HOME/.telegram"
fi

mkdir -p logs

send_telegram() {
  local MESSAGE="$1"

  if [[ -n "${TELEGRAM_BOT_TOKEN:-}" && -n "${TELEGRAM_CHAT_ID:-}" ]]; then
    curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
      -d chat_id="${TELEGRAM_CHAT_ID}" \
      -d text="${MESSAGE}" > /dev/null || true
  fi
}

send_telegram "BEAST2 CPU inició. Job: ${SLURM_JOB_NAME} (${SLURM_JOB_ID}) en $(hostname)."

START_TIME=$(date +%s)

finish() {
  EXIT_CODE=$?
  END_TIME=$(date +%s)
  ELAPSED=$((END_TIME - START_TIME))

  if [[ "$EXIT_CODE" -eq 0 ]]; then
    send_telegram "BEAST2 CPU terminó correctamente. Job: ${SLURM_JOB_NAME} (${SLURM_JOB_ID}). Tiempo: ${ELAPSED} s."
  else
    send_telegram "BEAST2 CPU terminó con error. Job: ${SLURM_JOB_NAME} (${SLURM_JOB_ID}). Código: ${EXIT_CODE}. Revisa logs/."
  fi

  exit "$EXIT_CODE"
}

trap finish EXIT

echo "===================================="
echo "Trabajo: $SLURM_JOB_NAME"
echo "Job ID: $SLURM_JOB_ID"
echo "Nodo: $(hostname)"
echo "Inicio: $(date)"
echo "Directorio: $SLURM_SUBMIT_DIR"
echo "===================================="

cd runs/cpu

/usr/bin/time -v apptainer exec \
  ../../sif/beast2_2.7.8.sif \
  beast \
  -overwrite \
  testGTR.xml

echo "===================================="
echo "Fin: $(date)"
echo "===================================="

Envía el trabajo:

sbatch scripts/beast2_gtr_cpu.sbatch

Revisa si sigue en cola:

squeue -u $USER

Cuando termine, revisa los archivos de salida:

ls -lh logs/
ls -lh runs/cpu/

Revisa el final del archivo .out:

tail -n 40 logs/beast2_gtr_cpu-*.out

Revisa si hubo errores:

tail -n 40 logs/beast2_gtr_cpu-*.err

También puedes revisar el log principal de BEAST2:

tail -n 20 runs/cpu/primate-mtDNA.log

Descargar la imagen de BEAST2 con BEAGLE CUDA para GPU

Descarga la imagen preparada para usar BEAST2 con BEAGLE y CUDA en el nodo con GPU V100:

apptainer pull \
  sif/beast2-beagle-cuda_2.7.8-gpu-v100.sif \
  docker://ccervantess/beast2-beagle-cuda:2.7.8-gpu-v100

Revisa que el archivo se haya creado:

ls -lh sif/

Prueba que el contenedor abre correctamente:

apptainer exec sif/beast2-beagle-cuda_2.7.8-gpu-v100.sif beast -help

Ejecutar testGTR.xml con GPU

WarningSolicita GPU solo cuando la vas a usar

Este ejercicio solicita una GPU con #SBATCH --gres=gpu:1 y usa apptainer exec --nv para que el contenedor tenga acceso a las bibliotecas NVIDIA del nodo.

Crea el script:

nano scripts/beast2_gtr_gpu.sbatch

Contenido:

#!/bin/bash
#SBATCH --job-name=beast2_gtr_gpu
#SBATCH --partition=gpu_V100
#SBATCH --nodes=1
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=4
#SBATCH --mem=16G
#SBATCH --gres=gpu:1
#SBATCH --time=01:00:00
#SBATCH --output=logs/%x-%j.out
#SBATCH --error=logs/%x-%j.err

set -euo pipefail

cd "$SLURM_SUBMIT_DIR"

# Cargar token y chat_id de Telegram 
if [[ -f "$HOME/.telegram" ]]; then
  source "$HOME/.telegram"
fi

mkdir -p logs

send_telegram() {
  local MESSAGE="$1"

  if [[ -n "${TELEGRAM_BOT_TOKEN:-}" && -n "${TELEGRAM_CHAT_ID:-}" ]]; then
    curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
      -d chat_id="${TELEGRAM_CHAT_ID}" \
      -d text="${MESSAGE}" > /dev/null || true
  fi
}

send_telegram "BEAST2 GPU inició. Job: ${SLURM_JOB_NAME} (${SLURM_JOB_ID}) en $(hostname)."

START_TIME=$(date +%s)

finish() {
  EXIT_CODE=$?
  END_TIME=$(date +%s)
  ELAPSED=$((END_TIME - START_TIME))

  if [[ "$EXIT_CODE" -eq 0 ]]; then
    send_telegram "BEAST2 GPU terminó correctamente. Job: ${SLURM_JOB_NAME} (${SLURM_JOB_ID}). Tiempo: ${ELAPSED} s."
  else
    send_telegram "BEAST2 GPU terminó con error. Job: ${SLURM_JOB_NAME} (${SLURM_JOB_ID}). Código: ${EXIT_CODE}. Revisa logs/."
  fi

  exit "$EXIT_CODE"
}

trap finish EXIT

echo "===================================="
echo "Trabajo: $SLURM_JOB_NAME"
echo "Job ID: $SLURM_JOB_ID"
echo "Nodo: $(hostname)"
echo "Inicio: $(date)"
echo "Directorio: $SLURM_SUBMIT_DIR"
echo "CPUs asignadas: ${SLURM_CPUS_PER_TASK}"
echo "GPUs asignadas: ${SLURM_GPUS:-No disponible}"
echo "===================================="

echo "Revisando GPU asignada:"
nvidia-smi

cd runs/gpu

/usr/bin/time -v apptainer exec \
  --nv \
  ../../sif/beast2-beagle-cuda_2.7.8-gpu-v100.sif \
  beast \
  -beagle \
  -beagle_GPU \
  -beagle_order 1 \
  -overwrite \
  testGTR.xml

echo "===================================="
echo "Fin: $(date)"
echo "===================================="

Envía el trabajo:

sbatch scripts/beast2_gtr_gpu.sbatch

Revisa si sigue en cola:

squeue -u $USER

Cuando termine, revisa los archivos generados:

ls -lh logs/
ls -lh runs/gpu/

Revisa el archivo de salida:

tail -n 60 logs/beast2_gtr_gpu-*.out

Revisa si hubo errores:

tail -n 60 logs/beast2_gtr_gpu-*.err

Revisa el log principal de BEAST2:

tail -n 20 runs/gpu/primate-mtDNA.log

Usar notificaciones por Telegram

Los scripts de esta práctica pueden enviar un mensaje por Telegram al iniciar y otro al terminar el trabajo.

Para no escribir el token directamente dentro del script .sbatch, se recomienda guardar las credenciales en un archivo oculto dentro del directorio personal del usuario.

Crea el archivo ~/.telegram:

nano ~/.telegram

Agrega tus credenciales:

export TELEGRAM_BOT_TOKEN="TU_TOKEN_DEL_BOT"
export TELEGRAM_CHAT_ID="TU_CHAT_ID"

Guarda el archivo y cambia sus permisos:

chmod 600 ~/.telegram

Dentro del script .sbatch, las credenciales se cargan con:

# Cargar credenciales de Telegram si existen
if [[ -f "$HOME/.telegram" ]]; then
  source "$HOME/.telegram"
fi

De esta forma, el script puede usar las variables TELEGRAM_BOT_TOKEN y TELEGRAM_CHAT_ID sin que el token quede escrito dentro del archivo .sbatch.

La función para enviar mensajes puede definirse así:

send_telegram() {
  local MESSAGE="$1"

  if [[ -n "${TELEGRAM_BOT_TOKEN:-}" && -n "${TELEGRAM_CHAT_ID:-}" ]]; then
    curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
      -d chat_id="${TELEGRAM_CHAT_ID}" \
      -d text="${MESSAGE}" > /dev/null || true
  fi
}

Después se puede enviar un mensaje al iniciar el trabajo:

send_telegram "Inicia BEAST2 CPU | Job ${SLURM_JOB_ID} | Nodo $(hostname)"

y otro al terminar:

send_telegram "Terminó BEAST2 CPU | Job ${SLURM_JOB_ID}"

Si el archivo ~/.telegram no existe, el trabajo se ejecutará normalmente, pero no enviará notificaciones.

Revisar resultados

Revisa directamente los archivos .out, .err y los archivos generados por el programa.

Para esta práctica, los archivos importantes son:

logs/beast2_gtr_cpu-JOBID.out
logs/beast2_gtr_cpu-JOBID.err
logs/beast2_gtr_gpu-JOBID.out
logs/beast2_gtr_gpu-JOBID.err
runs/cpu/primate-mtDNA.log
runs/cpu/primate.trees
runs/gpu/primate-mtDNA.log
runs/gpu/primate.trees

Puedes buscar mensajes de error con:

grep -i "error\|exception\|failed" logs/*.out logs/*.err

Si no aparece nada, revisa el final de los archivos .out:

tail -n 40 logs/*.out

Limpiar resultados para repetir la prueba

Si quieres repetir la prueba desde cero, elimina solo los resultados generados dentro de las carpetas de corrida:

rm -f runs/cpu/primate-mtDNA.log runs/cpu/primate.trees runs/cpu/testGTR.xml.state
rm -f runs/gpu/primate-mtDNA.log runs/gpu/primate.trees runs/gpu/testGTR.xml.state

No borres las imágenes .sif a menos que quieras descargarlas de nuevo.

WarningCuidado con rm

rm borra archivos de forma permanente. Antes de borrar, confirma tu ubicación con:

pwd
ls -lh