Pour commencer
Vous savez déjà tout sur les pipelines et les bonnes pratiques de développement. Vous faites bien évidemment de la recherche reproductive. Vous travaillez peut-être avec un cluster. Vous avez écrit votre propre pipeline en Bash, Python ou même en Perl qui gérait les appels à différents scripts et outils (voire même l'appel à l'ordonnanceur). Vous développez un pipeline, ou le maintenez. Vous êtes intéressé par la découverte de nouveaux 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 classique de réinventer la roue, et je pense pouvoir affirmer sans me tromper qu'on a tous tenté de le faire à un moment ou à un autre. Dans mon cas je n'ai malheureusement découvert Make que sur le tard, et n'ai commencé à m'en servir que pour compiler ma thèse sous LaTeX (et depuis chaque document LaTeX). Mais déjà je savais que le prochain pipeline ne serait pas fait à la main. Probablement en utilisant Make, car il me parle d'aventures. Je me suis longtemps posé aussi la question de Snakemake. Et puis nouveau boulot, nouveau pouvoir projet, et c'est donc Nextflow qui a été choisi pour développer le pipeline. Donc je n'ai que peu d'expérience avec Make, aucune avec Snakemake. Mais ayant découvert Nextflow, je vais donc tenter de vous le présenter et d'expliquer ce qui m'a plu dans ce framework.
La philosophie
Nextflow est basé sur un modèle de programmation orienté en flux de données et deux concepts clés : Process et Channel. En plus clair, ce sont les données qui contrôlent le pipeline. Le Process est le processus de base qui va exécuter un script. Pour chaque Process, on peut définir un Input et un Output (entrée/sortie). Ces Inputs/Outputs peuvent être une valeur, un fichier, un ensemble de valeurs et/ou fichiers, ou plusieurs fichiers, valeurs et ensembles. Les différents Processes communiquent au moyen de Channel (Chaque Input/Output de votre Process est en fait une Channel). Ces dernières sont utilisées pour faire les manipulations sur les données, qu'elles soient valeurs ou fichiers.
Et c'est tout.
La magie
Vous êtes plutôt Python ou plutôt Perl ? Pas de soucis, pour chaque Process, vous pouvez directement écrire vos scripts dans le langage que vous désirez (même R…). Ou les lignes de commandes en Bash pour appeler les scripts. Pas besoin de spécifier les liens entre les différents Processes, Nextflow s'en occupe, grâce au nom de la Channel (l'Output d'un Process est l'Input d'un autre). Paralléliser votre code ? Pas de problème, c'est implicite avec Nextflow. Il suffit juste de définir vos Inputs et Outputs, Nextflow se charge de paralléliser l’exécution. Vous bossez sur différentes machines, Nextflow fournit une couche entre le pipeline et l'exécution, et s'en occupe, que ce soit sur un cluster, ou un ordi classique. Nextflow se charge bien évidemment de lancer les différents Processes au fur et à mesure. Pas besoin d'adapter votre code à votre machine… Vous voulez un café pendant que vos scripts tournent, bon là par contre il va falloir le faire vous même… (enfin sauf si vous avez un script pour votre machine à café), par contre aucun souci pour une poképause.
Reproductibilité
Tous les résultats intermédiaires sont automatiquement enregistrés, il est donc facile de recommencer à partir de la dernière étape réussie. Il est possible d'avoir les temps d'exécution (et d'occupation de la mémoire) pour chacun des Processes (avec les paramètres -with-trace et/ou -with-timeline). Chaque Process est automatiquement enregistré, et on peut donc retrouver les différentes étapes, et appels à chacun des scripts, variables d’environnement, version des outils et programmes utilisés… Vous pouvez aussi facilement automatiser une gestion des erreurs, et relancer automatiquement un Process (qui par exemple aurait échoué faute de mémoire tout en lui allouant plus de mémoire/CPU pour un essai suivant (et limiter le nombre d'essais par la même occasion)). Si votre code est versionné avec Git, vous pouvez lancer une version en particulier de votre pipeline avec un simple paramètre -r nomdelaversion, ce qui vous assure d'utiliser le même code. Nextflow supporte nativement Docker, et vous pouvez donc utiliser un container pour pouvoir englober tout ce qu'il vous faut.
Un exemple ?
De très bons exemples se trouvent sur internet, et donc même si j'aime bien refaire la roue, je vais juste en expliquer un.
1 |
#!/usr/bin/env nextflow<br>// Initialise le fichier d'entrée avec un fichier par défaut<br>// Peut être changé en ajoutant à la ligne de commande : –in \<monfichier\><br>params.in = "sample.fa"<br>// Crée une Channel contenant le fichier d'entrée<br>sequences = file(params.in)<br><br>// Process qui découpe un fichier fasta en plusieurs fichiers<br>process SplitSequences {<br><br>// Définit l'entrée du Process<br>// Le fichier reçu de la Channel sequences sera nommé input.fa<br>input : file 'input.fa' from sequences<br><br>// Définit la sortie du Process<br>// Tout fichier commençant par seq_ sera dans la Channel record<br>output : file 'seq_*' into record<br><br>// La partie script du Process commence<br>"""<br>awk '/^>/{f="seq_"++d} {print > f}' < ; input.fa<br>"""<br>}<br><br>// record étant la Channel sortie du Process SplitSequences<br>// Nextflow cherche quel Process prend record en entrée et lui envoie<br>// L'ordre dans lequel les Processes sont écrits n'a pas d'importance<br><br>// Process qui renverse les séquences<br>process ReverseSequence {<br><br>input : file x from record<br><br>// Ici nous récupérons la sortie standard (stdout) en valeur pour notre sortie<br>output : stdout result<br><br>"""<br>cat $x | rev<br>"""<br>}<br><br>// Imprime le contenu de la Channel result<br>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 :
1 2 |
nextflow run monScript.nf<br> devrait suffire à faire marcher ce simple exemple et vous donner un résultat : |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
┌[max]═(~/cloud/BioinfoFR/2016/11)<br> └> ; N E X T F L O W ~ version 0.22.5<br> Launching `monScript.nf` [amazing_easley] - revision : bc37d23cde<br> [warm up] executor > ; local<br> [c0/54ec89] Submitted process > ; SplitSequences (1)<br> [36/d4d215] Submitted process > ; ReverseSequence (1)<br> Aoba1> ;<br> SPVWGQGNKTQAECWEGNHNYGLVRLKEGKTISLTNDGSAVFDYLAVFLN<br> NVPTIYN<br> Bscy1> ;<br> YGEKDNLRAWWWEIEDEDERHIITMCDGEKMPLEDDNQPEYDWLAYIVGK<br> PYLGLLNRPV<br> thp1> ;<br> GIEEPRAEQGDSFGLAVLSGKNVTLIDGLHLDIDEEREKKYDYLARYQYG<br> PSIKKRGIYEVYTGPFDGREGTTENYGNLW<br> eiv1> ;<br> IRELAAVPYIQVSGPHAESEVAYGEPTLNTCYWGVIQGQWAAGSKKRVRD<br> N<br> Avhi1> ;<br> DRIIKAKRRPVVKIDSNDQIVVAGEGKWLLKAPGKWVPDRSDRYYVRFN<br> <br> 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 <em>Process</em>, 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 <em>Process</em>. |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
┌[max]═(~/cloud/BioinfoFR/2016/11)<br> └> ; ll work/36/d4d215698288de4ff42414d0cf2885<br> total 20K<br> lrwxrwxrwx 1 max max 52 Nov 18 17 :18 input.fa -> ; /home/max/cloud/BioinfoFR/2016/11/sample.fa<br> -rw-r–r– 1 max max 66 Nov 18 17 :18 seq_1<br> -rw-r–r– 1 max max 69 Nov 18 17 :18 seq_2<br> -rw-r–r– 1 max max 88 Nov 18 17 :18 seq_3<br> -rw-r–r– 1 max max 59 Nov 18 17 :18 seq_4<br> -rw-r–r– 1 max max 57 Nov 18 17 :18 seq_5<br> ┌[max]═(~/cloud/BioinfoFR/2016/11)<br> └> ; ll work/36/d4d215698288de4ff42414d0cf2885<br> total 0<br> lrwxrwxrwx 1 max max 54 Nov 18 17 :18 seq_1 -> ; /home/max/temp/c0/54ec89c073dcadbbc80a9ba8bd3939/seq_1<br> lrwxrwxrwx 1 max max 54 Nov 18 17 :18 seq_2 -> ; /home/max/temp/c0/54ec89c073dcadbbc80a9ba8bd3939/seq_2<br> lrwxrwxrwx 1 max max 54 Nov 18 17 :18 seq_3 -> ; /home/max/temp/c0/54ec89c073dcadbbc80a9ba8bd3939/seq_3<br> lrwxrwxrwx 1 max max 54 Nov 18 17 :18 seq_4 -> ; /home/max/temp/c0/54ec89c073dcadbbc80a9ba8bd3939/seq_4<br> lrwxrwxrwx 1 max max 54 Nov 18 17 :18 seq_5 -> ; /home/max/temp/c0/54ec89c073dcadbbc80a9ba8bd3939/seq_5<br> <br> 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. |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
┌[max]═(~/cloud/BioinfoFR/2016/11)<br> └> ; nextflow run monScript.nf -resume<br> N E X T F L O W ~ version 0.22.5<br> Launching `monScript.nf` [angry_cori] - revision : bc37d23cde<br> [warm up] executor > ; local<br> [c0/54ec89] Cached process > ; SplitSequences (1)<br> [36/d4d215] Cached process > ; ReverseSequence (1)<br> Aoba1> ;<br> SPVWGQGNKTQAECWEGNHNYGLVRLKEGKTISLTNDGSAVFDYLAVFLN<br> NVPTIYN<br> Bscy1> ;<br> YGEKDNLRAWWWEIEDEDERHIITMCDGEKMPLEDDNQPEYDWLAYIVGK<br> PYLGLLNRPV<br> thp1> ;<br> GIEEPRAEQGDSFGLAVLSGKNVTLIDGLHLDIDEEREKKYDYLARYQYG<br> PSIKKRGIYEVYTGPFDGREGTTENYGNLW<br> eiv1> ;<br> IRELAAVPYIQVSGPHAESEVAYGEPTLNTCYWGVIQGQWAAGSKKRVRD<br> N<br> Avhi1> ;<br> DRIIKAKRRPVVKIDSNDQIVVAGEGKWLLKAPGKWVPDRSDRYYVRFN<br> <br> Avec l'option ‑resume, on remarque que les <em>Processes</em> 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 <em>Process</em>, et chaque dossier contient toutes les informations utilisées pour l'exécution du <em>Process</em> : |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
┌[max]═(~/cloud/BioinfoFR/2016/11)<br> └> ; ll -la work/c0/54ec89c073dcadbbc80a9ba8bd3939/<br> total 40K<br> drwxr-xr-x 2 max max 4,0K Nov 18 17 :18 .<br> drwxr-xr-x 52 max max 4,0K Nov 18 17 :18 ..<br> -rw-r–r– 1 max max 0 Nov 18 17 :18 .command.begin<br> -rw-r–r– 1 max max 0 Nov 18 17 :18 .command.err<br> -rw-r–r– 1 max max 0 Nov 18 17 :18 .command.log<br> -rw-r–r– 1 max max 0 Nov 18 17 :18 .command.out<br> -rw-r–r– 1 max max 1,8K Nov 18 17 :18 .command.run<br> -rw-r–r– 1 max max 63 Nov 18 17 :18 .command.sh<br> -rw-r–r– 1 max max 1 Nov 18 17 :18 .exitcode<br> lrwxrwxrwx 1 max max 52 Nov 18 17 :18 input.fa -> ; /home/max/cloud/BioinfoFR/2016/11/sample.fa<br> -rw-r–r– 1 max max 66 Nov 18 17 :18 seq_1<br> -rw-r–r– 1 max max 69 Nov 18 17 :18 seq_2<br> -rw-r–r– 1 max max 88 Nov 18 17 :18 seq_3<br> -rw-r–r– 1 max max 59 Nov 18 17 :18 seq_4<br> -rw-r–r– 1 max max 57 Nov 18 17 :18 seq_5<br> <br> .command.run initialise le répertoire temporaire et lie symboliquement les fichiers d'entrées.<br> .command.sh contient le script qui sera exécuté par le <em>Process</em>.<br> 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 :
1 2 |
nextflow run nextflow-io/hello<br> 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.
Laisser un commentaire