Nextflow, pour votre prochain pipeline ?

Pour commencer

Vous savez déjà tout sur les pipe­lines et les bonnes pra­tiques de déve­lop­pe­ment. Vous faites bien évi­dem­ment de la recherche repro­duc­tive. Vous tra­vaillez peut-être avec un clus­ter. Vous avez écrit votre propre pipe­line en Bash, Python ou même en Perl qui gérait les appels à dif­fé­rents scripts et outils (voire même l'appel à l'ordonnanceur). Vous déve­lop­pez un pipe­line, ou le main­te­nez. Vous êtes inté­res­sé par la décou­verte de nou­veaux outils. Vous avez vu la lumière et vous êtes entrés. Bref, cet article est là pour vous.

Ma petite expérience

C'est un grand clas­sique de réin­ven­ter la roue, et je pense pou­voir affir­mer sans me trom­per qu'on a tous ten­té de le faire à un moment ou à un autre. Dans mon cas je n'ai mal­heu­reu­se­ment décou­vert Make que sur le tard, et n'ai com­men­cé à m'en ser­vir que pour com­pi­ler ma thèse sous LaTeX (et depuis chaque docu­ment LaTeX). Mais déjà je savais que le pro­chain pipe­line ne serait pas fait à la main. Pro­ba­ble­ment en uti­li­sant Make, car il me parle d'aventures. Je me suis long­temps posé aus­si la ques­tion de Sna­ke­make. Et puis nou­veau bou­lot, nou­veau pou­voir pro­jet, et c'est donc Next­flow qui a été choi­si pour déve­lop­per le pipe­line. Donc je n'ai que peu d'expérience avec Make, aucune avec Sna­ke­make. Mais ayant décou­vert Next­flow, je vais donc ten­ter de vous le pré­sen­ter et d'expliquer ce qui m'a plu dans ce fra­me­work.

La philosophie

Next­flow est basé sur un modèle de pro­gram­ma­tion orien­té en flux de don­nées et deux concepts clés : Pro­cess et Chan­nel. En plus clair, ce sont les don­nées qui contrôlent le pipe­line. Le Pro­cess est le pro­ces­sus de base qui va exé­cu­ter un script. Pour chaque Pro­cess, on peut défi­nir un Input et un Out­put (entrée/​sortie). Ces Inputs/​Out­puts peuvent être une valeur, un fichier, un ensemble de valeurs et/​ou fichiers, ou plu­sieurs fichiers, valeurs et ensembles. Les dif­fé­rents Pro­cesses com­mu­niquent au moyen de Chan­nel (Chaque Input/​Out­put de votre Pro­cess est en fait une Chan­nel). Ces der­nières sont uti­li­sées pour faire les mani­pu­la­tions sur les don­nées, qu'elles soient valeurs ou fichiers.
Et c'est tout.

La magie

Next­flow is Doge — http://​dogr​.io/

Vous êtes plu­tôt Python ou plu­tôt Perl ? Pas de sou­cis, pour chaque Pro­cess, vous pou­vez direc­te­ment écrire vos scripts dans le lan­gage que vous dési­rez (même R…). Ou les lignes de com­mandes en Bash pour appe­ler les scripts. Pas besoin de spé­ci­fier les liens entre les dif­fé­rents Pro­cesses, Next­flow s'en occupe, grâce au nom de la Chan­nel (l'Out­put d'un Pro­cess est l'Input d'un autre). Paral­lé­li­ser votre code ? Pas de pro­blème, c'est impli­cite avec Next­flow. Il suf­fit juste de défi­nir vos Inputs et Out­puts, Next­flow se charge de paral­lé­li­ser l’exécution. Vous bos­sez sur dif­fé­rentes machines, Next­flow four­nit une couche entre le pipe­line et l'exécution, et s'en occupe, que ce soit sur un clus­ter, ou un ordi clas­sique. Next­flow se charge bien évi­dem­ment de lan­cer les dif­fé­rents Pro­cesses au fur et à mesure. Pas besoin d'adapter votre code à votre machine… Vous vou­lez un café pen­dant que vos scripts tournent, bon là par contre il va fal­loir le faire vous même… (enfin sauf si vous avez un script pour votre machine à café), par contre aucun sou­ci pour une poké­pause.

Reproductibilité

-with-timeline
Avec le para­mètre ‑with-time­line

Tous les résul­tats inter­mé­diaires sont auto­ma­ti­que­ment enre­gis­trés, il est donc facile de recom­men­cer à par­tir de la der­nière étape réus­sie. Il est pos­sible d'avoir les temps d'exécution (et d'occupation de la mémoire) pour cha­cun des Pro­cesses (avec les para­mètres -with-trace et/​ou -with-time­line). Chaque Pro­cess est auto­ma­ti­que­ment enre­gis­tré, et on peut donc retrou­ver les dif­fé­rentes étapes, et appels à cha­cun des scripts, variables d’environnement, ver­sion des outils et pro­grammes uti­li­sés… Vous pou­vez aus­si faci­le­ment auto­ma­ti­ser une ges­tion des erreurs, et relan­cer auto­ma­ti­que­ment un Pro­cess (qui par exemple aurait échoué faute de mémoire tout en lui allouant plus de mémoire/​CPU pour un essai sui­vant (et limi­ter le nombre d'essais par la même occa­sion)). Si votre code est ver­sion­né avec Git, vous pou­vez lan­cer une ver­sion en par­ti­cu­lier de votre pipe­line avec un simple para­mètre -r nom­de­la­ver­sion, ce qui vous assure d'utiliser le même code. Next­flow sup­porte nati­ve­ment Docker, et vous pou­vez donc uti­li­ser un contai­ner pour pou­voir englo­ber tout ce qu'il vous faut.

Un exemple ?

De très bons exemples se trouvent sur inter­net, et donc même si j'aime bien refaire la roue, je vais juste en expli­quer un.

	#!/usr/bin/env nextflow

// Initialise le fichier d'entrée avec un fichier par défaut
// Peut être changé en ajoutant à la ligne de commande : --in \<monfichier\>
params.in = "sample.fa"
// Crée une Channel contenant le fichier d'entrée
sequences = file(params.in)

// Process qui découpe un fichier fasta en plusieurs fichiers
process SplitSequences {

// Définit l'entrée du Process
// Le fichier reçu de la Channel sequences sera nommé input.fa
input: file 'input.fa' from sequences

// Définit la sortie du Process
// Tout fichier commençant par seq_ sera dans la Channel record
output: file 'seq_*' into record

// La partie script du Process commence
"""
awk '/^>/{f="seq_"++d} {print > f}' < input.fa
"""
}

// record étant la Channel sortie du Process SplitSequences
// Nextflow cherche quel Process prend record en entrée et lui envoie
// L'ordre dans lequel les Processes sont écrits n'a pas d'importance

// Process qui renverse les séquences
process ReverseSequence {

input: file x from record

// Ici nous récupérons la sortie standard (stdout) en valeur pour notre sortie
output: stdout result

"""
cat $x | rev
"""
}

// Imprime le contenu de la Channel result
result.subscribe { println it }

Pour le faire marcher, il suffit juste d'installer Nextflow, de télécharger le fichier sample.fa et de créer votre script monScript.nf dans votre éditeur de texte préféré. Un simple :

nextflow run monScript.nf
devrait suffire à faire marcher ce simple exemple et vous donner un résultat :
	┌[max]═(~/cloud/BioinfoFR/2016/11)
└> N E X T F L O W ~ version 0.22.5
Launching monScript.nf [amazing_easley] - revision: bc37d23cde
[warm up] executor > local
[c0/54ec89] Submitted process > SplitSequences (1)
[36/d4d215] Submitted process > ReverseSequence (1)
Aoba1>
SPVWGQGNKTQAECWEGNHNYGLVRLKEGKTISLTNDGSAVFDYLAVFLN
NVPTIYN
Bscy1>
YGEKDNLRAWWWEIEDEDERHIITMCDGEKMPLEDDNQPEYDWLAYIVGK
PYLGLLNRPV
thp1>
GIEEPRAEQGDSFGLAVLSGKNVTLIDGLHLDIDEEREKKYDYLARYQYG
PSIKKRGIYEVYTGPFDGREGTTENYGNLW
eiv1>
IRELAAVPYIQVSGPHAESEVAYGEPTLNTCYWGVIQGQWAAGSKKRVRD
N
Avhi1>
DRIIKAKRRPVVKIDSNDQIVVAGEGKWLLKAPGKWVPDRSDRYYVRFN

Un dossier work a été créé dans le répertoire courant, c'est le dossier temporaire contenant la trace de tout ce qu'il s'est passé. Pour chaque Process, un dossier principal et un sous-dossier sont crées. Et dans ce dernier se trouve les liens symboliques vers les fichiers d'entrées et les fichiers de sorties qui eux seront liés symboliquement dans le prochain Process.
	┌[max]═(~/cloud/BioinfoFR/2016/11)
└> ll work/36/d4d215698288de4ff42414d0cf2885
total 20K
lrwxrwxrwx 1 max max 52 Nov 18 17:18 input.fa -> /home/max/cloud/BioinfoFR/2016/11/sample.fa
-rw-r--r-- 1 max max 66 Nov 18 17:18 seq_1
-rw-r--r-- 1 max max 69 Nov 18 17:18 seq_2
-rw-r--r-- 1 max max 88 Nov 18 17:18 seq_3
-rw-r--r-- 1 max max 59 Nov 18 17:18 seq_4
-rw-r--r-- 1 max max 57 Nov 18 17:18 seq_5
┌[max]═(~/cloud/BioinfoFR/2016/11)
└> ll work/36/d4d215698288de4ff42414d0cf2885
total 0
lrwxrwxrwx 1 max max 54 Nov 18 17:18 seq_1 -> /home/max/temp/c0/54ec89c073dcadbbc80a9ba8bd3939/seq_1
lrwxrwxrwx 1 max max 54 Nov 18 17:18 seq_2 -> /home/max/temp/c0/54ec89c073dcadbbc80a9ba8bd3939/seq_2
lrwxrwxrwx 1 max max 54 Nov 18 17:18 seq_3 -> /home/max/temp/c0/54ec89c073dcadbbc80a9ba8bd3939/seq_3
lrwxrwxrwx 1 max max 54 Nov 18 17:18 seq_4 -> /home/max/temp/c0/54ec89c073dcadbbc80a9ba8bd3939/seq_4
lrwxrwxrwx 1 max max 54 Nov 18 17:18 seq_5 -> /home/max/temp/c0/54ec89c073dcadbbc80a9ba8bd3939/seq_5

Vous pouvez bien évidemment paramétrer Nextflow pour que ce dossier work temporaire soit dans un dossier temporaire (du genre /scratch), et décider d'un répertoire de sortie pour vos fichiers finaux. Ce repertoire temporaire a pour intérêt de garder tous les fichiers intermédiaires, sans encombrer le répertoire courant.
	┌[max]═(~/cloud/BioinfoFR/2016/11)
└> nextflow run monScript.nf -resume
N E X T F L O W ~ version 0.22.5
Launching monScript.nf [angry_cori] - revision: bc37d23cde
[warm up] executor > local
[c0/54ec89] Cached process > SplitSequences (1)
[36/d4d215] Cached process > ReverseSequence (1)
Aoba1>
SPVWGQGNKTQAECWEGNHNYGLVRLKEGKTISLTNDGSAVFDYLAVFLN
NVPTIYN
Bscy1>
YGEKDNLRAWWWEIEDEDERHIITMCDGEKMPLEDDNQPEYDWLAYIVGK
PYLGLLNRPV
thp1>
GIEEPRAEQGDSFGLAVLSGKNVTLIDGLHLDIDEEREKKYDYLARYQYG
PSIKKRGIYEVYTGPFDGREGTTENYGNLW
eiv1>
IRELAAVPYIQVSGPHAESEVAYGEPTLNTCYWGVIQGQWAAGSKKRVRD
N
Avhi1>
DRIIKAKRRPVVKIDSNDQIVVAGEGKWLLKAPGKWVPDRSDRYYVRFN

Avec l'option -resume, on remarque que les Processes déjà effectués sont en mémoire, et ne sont donc pas exécutés, mais réutilisés. Un dossier temporaire correspond donc à un Process, et chaque dossier contient toutes les informations utilisées pour l'exécution du Process :
	┌[max]═(~/cloud/BioinfoFR/2016/11)
└> ll -la work/c0/54ec89c073dcadbbc80a9ba8bd3939/
total 40K
drwxr-xr-x 2 max max 4,0K Nov 18 17:18 .
drwxr-xr-x 52 max max 4,0K Nov 18 17:18 ..
-rw-r--r-- 1 max max 0 Nov 18 17:18 .command.begin
-rw-r--r-- 1 max max 0 Nov 18 17:18 .command.err
-rw-r--r-- 1 max max 0 Nov 18 17:18 .command.log
-rw-r--r-- 1 max max 0 Nov 18 17:18 .command.out
-rw-r--r-- 1 max max 1,8K Nov 18 17:18 .command.run
-rw-r--r-- 1 max max 63 Nov 18 17:18 .command.sh
-rw-r--r-- 1 max max 1 Nov 18 17:18 .exitcode
lrwxrwxrwx 1 max max 52 Nov 18 17:18 input.fa -> /home/max/cloud/BioinfoFR/2016/11/sample.fa
-rw-r--r-- 1 max max 66 Nov 18 17:18 seq_1
-rw-r--r-- 1 max max 69 Nov 18 17:18 seq_2
-rw-r--r-- 1 max max 88 Nov 18 17:18 seq_3
-rw-r--r-- 1 max max 59 Nov 18 17:18 seq_4
-rw-r--r-- 1 max max 57 Nov 18 17:18 seq_5

.command.run initialise le répertoire temporaire et lie symboliquement les fichiers d'entrées.
.command.sh contient le script qui sera exécuté par le Process.
C'est donc assez pratique pour débugger.

Partage et Cloud

Nextflow est très facilement partageable, et vous pouvez directement exécuter un script Nextflow se trouvant sur Github :

nextflow run nextflow-io/hello
Ce qui est très pratique quand vous voulez que d'autres personnes moins versées que vous en programmation testent votre pipeline. Nextflow marche aussi dans le cloud et supporte Amazon AWS et DNAnexus.

Une communauté active

Paolo Di Tommaso, le lead dev sur Nextflow est hyper réactif, que ce soit sur le chat Gitter, ou le Google group. Un certain nombre de pipelines sont également awesomement listés sur GitHub, c'est une très bonne source d'inspiration.

Pourquoi Nextflow ?

Plusieurs se sont déjà posé la question de quel framework utiliser pour gérer un pipeline, ici aussi. Julian Mazzitelli a fait une comparaison entre Bash, Make, SnakeMake et Nextflow dans un article de blog que je conseille de lire. Dans notre groupe la décision finale était entre SnakeMake et Nextflow. Et comme dit précédemment, ça c'est décidé sur Nextflow pour mon projet.

Inconvénients

Écrit en Groovy, et même si certains diront que Groovy n'aurait jamais du exister (notamment James Strachan l'un des créateurs), personnellement le seul inconvénient que j'ai rencontré a été de devoir apprendre une nouvelle syntaxe, et je n'ai pas eu trop de mal à m'y faire. Je trouve à mon humble avis que ça reste moins obscur qu'un Makefile.

Conflits d'intérêts

Aucun.

Remerciements

Je remercie Yoann M. pour sa motivation concernant cet article, Estel pour m'avoir redonné la motivation de réécrire des articles, ainsi que Lelouar, _NiGoPol_ et Norore bien aimés relecteurs pour leurs avis et discussions.



Pour continuer la lecture :


Commentaires

5 réponses à “Nextflow, pour votre prochain pipeline ?”

  1. Salut,

    C'est avec un peu de retard que je dis mer­ci pour cet article. Je me mets à Next­flow et ton article per­met de reca­drer (je trouve) une doc un peu com­plexe et sans assez d'exemples (avis per­so tou­jours).

    J'ai une ques­tion : connais tu un forum fré­quen­té par des uti­li­sa­teurs de Next­flow ? SeqAns­wers, Red­dit sont un peu pauvres sur le sujet.
    Mer­ci !

  2. Alors niveau com­mu­ni­ca­tion, comme dit dans l'article, y'a un google group ou un chat git­ter.
    Peu d'activité sur Red­dit en effet.

    Niveau exemples y'a https://​github​.com/​n​e​x​t​f​l​o​w​-​i​o​/​e​x​a​m​p​les qui donne une bonne liste pour démar­rer.
    Sinon après per­son­nel­le­ment pour notre pipe­line, on s'est beau­coup ins­pi­ré de https://​github​.com/​n​e​x​t​f​l​o​w​-​i​o​/​a​w​e​s​o​m​e​-​n​e​x​t​f​low et main­te­nant on est dans les fea­tu­red pipe­lines.

    Après si t'as des ques­tions plus pré­cises, hésite pas à me contac­ter.

  3. Avatar de Lucotte

    Bon­jour
    après avoir lu votre article, je vou­lais tes­ter votre exemple, mais mal­heu­reu­se­ment votre fichier sample.fa n'est plus dis­po­nible. Pour­riez vous m'en envoyer une copie ?
    mer­ci d'avance pour votre réponse
    bonne jour­née
    Georges

Laisser un commentaire