Przejdź do treści

Część 3: Hello Workflow

Tłumaczenie wspomagane przez AI - dowiedz się więcej i zasugeruj ulepszenia

Większość rzeczywistych workflow składa się z więcej niż jednego kroku. W tym module szkoleniowym nauczysz się łączyć procesy w wieloetapowy workflow.

Poznasz następujące techniki Nextflow:

  1. Przekazywanie danych z jednego procesu do następnego
  2. Zbieranie wyjść z wielu wywołań procesu do pojedynczego wywołania
  3. Przekazywanie więcej niż jednego wejścia do procesu
  4. Obsługa wielu wyjść z procesu

Dla demonstracji będziemy kontynuować rozbudowę domenowo-agnostycznego przykładu Hello World z Części 1 i 2. Tym razem wprowadzimy następujące zmiany w naszym workflow, aby lepiej odzwierciedlić sposób budowania rzeczywistych pipeline'ów:

  1. Dodamy drugi krok, który konwertuje pozdrowienie na wielkie litery.
  2. Dodamy trzeci krok, który zbiera wszystkie przekształcone pozdrowienia i zapisuje je do pojedynczego pliku.
  3. Dodamy parametr do nazwania końcowego pliku wyjściowego i przekażemy go jako dodatkowe wejście do kroku zbierania.
  4. Sprawimy, że krok zbierania będzie również raportował prostą statystykę o tym, co zostało przetworzone.
Jak zacząć od tej sekcji

Ta sekcja kursu zakłada, że ukończyłeś Części 1-2 kursu Hello Nextflow, ale jeśli znasz podstawy omówione w tych sekcjach, możesz zacząć od tego miejsca bez żadnych dodatkowych przygotowań.


0. Rozgrzewka: Uruchom hello-workflow.nf

Użyjemy skryptu workflow hello-workflow.nf jako punktu wyjścia. Jest on równoważny skryptowi utworzonemu podczas pracy nad Częścią 2 tego szkolenia, z tą różnicą, że usunęliśmy instrukcje view() i zmieniliśmy miejsce docelowe wyjścia:

hello-workflow.nf
output {
    first_output {
        path 'hello_workflow'
        mode 'copy'
    }
}

Aby upewnić się, że wszystko działa, uruchom skrypt raz przed wprowadzeniem jakichkolwiek zmian:

nextflow run hello-workflow.nf
Wynik polecenia
 N E X T F L O W   ~  version 25.10.2

Launching `hello-workflow.nf` [admiring_lamarr] DSL2 - revision: 4d4053520d

executor >  local (3)
[b1/5826b5] process > sayHello (2) [100%] 3 of 3 ✔

Jak poprzednio, pliki wyjściowe znajdziesz w lokalizacji określonej w bloku output. W tym rozdziale jest to katalog results/hello_workflow/.

Zawartość katalogu
results/hello_workflow
├── Bonjour-output.txt
├── Hello-output.txt
└── Holà-output.txt

Jeśli to zadziałało, jesteś gotowy do nauki składania wieloetapowego workflow.


1. Dodaj drugi krok do workflow

Dodamy krok konwertujący każde pozdrowienie na wielkie litery.

sayHello*-output.txtconvertToUpperUPPER-*Hello,English,123 Bonjour,French,456Holà,Spanish,789greetings.csvHELLOBONJOURHOLàUPPER-Hello-output.txtUPPER-Bonjour-output.txtUPPER-Holà-output.txt

W tym celu musimy wykonać trzy rzeczy:

  • Zdefiniować polecenie, którego użyjemy do konwersji na wielkie litery.
  • Napisać nowy proces opakowujący polecenie konwersji.
  • Wywołać nowy proces w bloku workflow i skonfigurować go tak, aby przyjmował wyjście procesu sayHello() jako wejście.

1.1. Zdefiniuj polecenie konwersji na wielkie litery i przetestuj je w terminalu

Do konwersji pozdrowień na wielkie litery użyjemy klasycznego narzędzia UNIX o nazwie tr (od 'text replacement'), z następującą składnią:

Składnia
tr '[a-z]' '[A-Z]'

Jest to bardzo prosty one-liner do zamiany tekstu, który nie uwzględnia liter z akcentami, więc na przykład 'Holà' zostanie zamienione na 'HOLà', ale wystarczająco dobrze posłuży do demonstracji koncepcji Nextflow i to jest najważniejsze.

Aby to przetestować, możemy uruchomić polecenie echo 'Hello World' i przekierować jego wyjście do polecenia tr:

echo 'Hello World' | tr '[a-z]' '[A-Z]' > UPPER-output.txt

Wyjściem jest plik tekstowy o nazwie UPPER-output.txt, który zawiera wersję ciągu Hello World napisaną wielkimi literami.

Zawartość pliku
UPPER-output.txt
HELLO WORLD

To właśnie zamierzamy zrobić w naszym workflow.

1.2. Napisz krok konwersji jako proces Nextflow

Możemy wzorować nasz nowy proces na pierwszym, ponieważ chcemy użyć wszystkich tych samych komponentów.

Dodaj następującą definicję procesu do skryptu workflow, tuż pod pierwszym procesem:

hello-workflow.nf
/*
 * Użyj narzędzia zamiany tekstu do przekształcenia pozdrowienia na wielkie litery
 */
process convertToUpper {

    input:
    path input_file

    output:
    path "UPPER-${input_file}"

    script:
    """
    cat '$input_file' | tr '[a-z]' '[A-Z]' > 'UPPER-${input_file}'
    """
}

W tym procesie komponujemy nazwę drugiego pliku wyjściowego na podstawie nazwy pliku wejściowego, podobnie jak zrobiliśmy to oryginalnie dla wyjścia pierwszego procesu.

1.3. Dodaj wywołanie nowego procesu w bloku workflow

Teraz musimy powiedzieć Nextflow, aby faktycznie wywołał proces, który właśnie zdefiniowaliśmy.

W bloku workflow wprowadź następującą zmianę w kodzie:

hello-workflow.nf
workflow {

    main:
    // utwórz kanał dla danych wejściowych z pliku CSV
    greeting_ch = channel.fromPath(params.input)
                        .splitCsv()
                        .map { line -> line[0] }
    // wyemituj pozdrowienie
    sayHello(greeting_ch)
    // przekształć pozdrowienie na wielkie litery
    convertToUpper()

    publish:
    first_output = sayHello.out
}
hello-workflow.nf
workflow {

    main:
    // utwórz kanał dla danych wejściowych z pliku CSV
    greeting_ch = channel.fromPath(params.input)
                        .splitCsv()
                        .map { line -> line[0] }
    // wyemituj pozdrowienie
    sayHello(greeting_ch)

    publish:
    first_output = sayHello.out
}

To jeszcze nie jest funkcjonalne, ponieważ nie określiliśmy, co powinno być wejściem do procesu convertToUpper().

1.4. Przekaż wyjście pierwszego procesu do drugiego procesu

Teraz musimy sprawić, aby wyjście procesu sayHello() trafiało do procesu convertToUpper().

Wygodnie jest to, że Nextflow automatycznie pakuje wyjście procesu do kanału o nazwie <process>.out. Więc wyjście procesu sayHello to kanał o nazwie sayHello.out, który możemy bezpośrednio podłączyć do wywołania convertToUpper().

W bloku workflow wprowadź następującą zmianę w kodzie:

hello-workflow.nf
    // przekształć pozdrowienie na wielkie litery
    convertToUpper(sayHello.out)
hello-workflow.nf
    // przekształć pozdrowienie na wielkie litery
    convertToUpper()

Dla prostego przypadku takiego jak ten (jedno wyjście do jednego wejścia), to wszystko, co musimy zrobić, aby połączyć dwa procesy!

1.5. Skonfiguruj publikowanie wyjść workflow

Na koniec zaktualizujmy wyjścia workflow, aby publikować również wyniki z drugiego procesu.

1.5.1. Zaktualizuj sekcję publish: bloku workflow

W bloku workflow wprowadź następującą zmianę w kodzie:

hello-workflow.nf
    publish:
    first_output = sayHello.out
    uppercased = convertToUpper.out
}
hello-workflow.nf
    publish:
    first_output = sayHello.out
}

Logika jest taka sama jak poprzednio.

1.5.2. Zaktualizuj blok output

W bloku output wprowadź następującą zmianę w kodzie:

hello-workflow.nf
output {
    first_output {
        path 'hello_workflow'
        mode 'copy'
    }
    uppercased {
        path 'hello_workflow'
        mode 'copy'
    }
}
hello-workflow.nf
output {
    first_output {
        path 'hello_workflow'
        mode 'copy'
    }
}

Ponownie logika jest taka sama jak wcześniej.

To pokazuje, że możesz kontrolować ustawienia wyjścia na bardzo szczegółowym poziomie, dla każdego indywidualnego wyjścia. Spróbuj zmienić ścieżki lub tryb publikacji dla jednego z procesów, aby zobaczyć, co się stanie.

Oczywiście oznacza to, że powtarzamy tutaj pewne informacje, co może stać się niewygodne, jeśli chcielibyśmy zaktualizować lokalizację dla wszystkich wyjść w ten sam sposób. Później w kursie dowiesz się, jak konfigurować te ustawienia dla wielu wyjść w uporządkowany sposób.

1.6. Uruchom workflow z -resume

Przetestujmy to używając flagi -resume, ponieważ już pomyślnie uruchomiliśmy pierwszy krok workflow.

nextflow run hello-workflow.nf -resume
Wynik polecenia
 N E X T F L O W   ~  version 25.10.2

Launching `hello-workflow.nf` [high_cantor] DSL2 - revision: d746983511

executor >  local (3)
[ab/816321] process > sayHello (3)       [100%] 3 of 3, cached: 3 ✔
[e0/ecf81b] process > convertToUpper (3) [100%] 3 of 3 ✔

W wyniku konsoli pojawia się teraz dodatkowa linia odpowiadająca nowemu procesowi, który właśnie dodaliśmy.

Wyjścia znajdziesz w katalogu results/hello_workflow zgodnie z ustawieniami w bloku output.

Zawartość katalogu
results/hello_workflow/
├── Bonjour-output.txt
├── Hello-output.txt
├── Holà-output.txt
├── UPPER-Bonjour-output.txt
├── UPPER-Hello-output.txt
└── UPPER-Holà-output.txt

To wygodne! Ale warto też zajrzeć do katalogu roboczego jednego z wywołań drugiego procesu.

Zawartość katalogu
work/e0/ecf81b4cacc648b9b994218d5b29d7/
├── Holà-output.txt -> /workspaces/training/hello-nextflow/work/ab/81632178cd37e9e815959278808819/Holà-output.txt
└── UPPER-Holà-output.txt

Zauważ, że są tam dwa pliki *-output: wyjście pierwszego procesu oraz wyjście drugiego.

Wyjście pierwszego procesu znajduje się tam, ponieważ Nextflow przygotował (ang. staged) je tam, aby mieć wszystko potrzebne do wykonania w tym samym podkatalogu.

Jednak w rzeczywistości jest to dowiązanie symboliczne wskazujące na oryginalny plik w podkatalogu wywołania pierwszego procesu. Domyślnie, podczas uruchamiania na pojedynczej maszynie, jak to robimy tutaj, Nextflow używa dowiązań symbolicznych zamiast kopii do przygotowywania plików wejściowych i pośrednich.

Teraz, zanim przejdziemy dalej, pomyśl o tym, jak wszystko, co zrobiliśmy, to połączenie wyjścia sayHello z wejściem convertToUpper. Dzięki temu dwa procesy mogły być uruchomione szeregowo. Nextflow wykonał za nas ciężką pracę związaną z obsługą poszczególnych plików wejściowych i wyjściowych oraz przekazywaniem ich między dwoma poleceniami.

To jeden z powodów, dla których kanały w Nextflow są tak potężne: zajmują się rutynową pracą związaną z łączeniem kroków workflow'u.

Podsumowanie

Wiesz już, jak łączyć procesy w łańcuch, przekazując wyjście jednego kroku jako wejście do następnego.

Co dalej?

Dowiedz się, jak zbierać wyjścia z wielu wywołań procesu i przekazywać je do pojedynczego procesu.


2. Dodaj trzeci krok do zbierania wszystkich pozdrowień

Gdy używamy procesu do zastosowania transformacji na każdym z elementów w kanale, jak tutaj z wieloma pozdrowieniami, czasami zachodzi potrzeba zebrania elementów z kanału wyjściowego. Następnie można je przekazać do innego procesu wykonującego analizę lub podsumowanie.

Dla demonstracji dodamy nowy krok do naszego pipeline, który zbiera wszystkie pozdrowienia zapisane wielkimi literami, wyprodukowane przez proces convertToUpper, i zapisuje je do pojedynczego pliku.

sayHello*-output.txtconvertToUpperUPPER-*collectGreetingsCOLLECTED-output.txtHELLOBONJOURHOLàHello,English,123 Bonjour,French,456Holà,Spanish,789greetings.csvHELLOBONJOURHOLàUPPER-Hello-output.txtUPPER-Bonjour-output.txtUPPER-Holà-output.txt

Nie zdradzając niespodzianki, ale będzie to wymagało użycia bardzo przydatnego operatora.

2.1. Zdefiniuj polecenie zbierania i przetestuj je w terminalu

Krok zbierania, który chcemy dodać do naszego workflow, użyje polecenia cat do połączenia wielu pozdrowień zapisanych wielkimi literami w pojedynczy plik.

Uruchommy polecenie samodzielnie w terminalu, aby sprawdzić, czy działa zgodnie z oczekiwaniami, tak jak robiliśmy to wcześniej.

Uruchom w terminalu:

echo 'Hello' | tr '[a-z]' '[A-Z]' > UPPER-Hello-output.txt
echo 'Bonjour' | tr '[a-z]' '[A-Z]' > UPPER-Bonjour-output.txt
echo 'Holà' | tr '[a-z]' '[A-Z]' > UPPER-Holà-output.txt
cat UPPER-Hello-output.txt UPPER-Bonjour-output.txt UPPER-Holà-output.txt > COLLECTED-output.txt

Wyjściem jest plik tekstowy o nazwie COLLECTED-output.txt, który zawiera wersje oryginalnych pozdrowień zapisane wielkimi literami.

Zawartość pliku
COLLECTED-output.txt
HELLO
BONJOUR
HOLà

To jest wynik, który chcemy osiągnąć za pomocą naszego workflow.

2.2. Utwórz nowy proces do kroku zbierania

Utwórzmy nowy proces i nazwijmy go collectGreetings(). Możemy zacząć go pisać na podstawie tego, co widzieliśmy wcześniej.

2.2.1. Napisz 'oczywiste' części procesu

Dodaj następującą definicję procesu do skryptu workflow:

hello-workflow.nf
/*
 * Zbierz pozdrowienia pisane wielkimi literami do jednego pliku wyjściowego
 */
process collectGreetings {

    input:
    ???

    output:
    path "COLLECTED-output.txt"

    script:
    """
    cat ??? > 'COLLECTED-output.txt'
    """
}

To jest to, co możemy napisać z pewnością na podstawie tego, czego się dotąd nauczyłeś. Ale to nie jest funkcjonalne! Brakuje definicji wejść i pierwszej połowy polecenia skryptu, ponieważ musimy wymyślić, jak to napisać.

2.2.2. Zdefiniuj wejścia do collectGreetings()

Musimy zebrać pozdrowienia ze wszystkich wywołań procesu convertToUpper(). Co wiemy, że możemy uzyskać z poprzedniego kroku workflow?

Channel wyprodukowany przez convertToUpper() będzie zawierał ścieżki do poszczególnych plików zawierających pozdrowienia zapisane wielkimi literami. To odpowiada jednemu slotowi wejściowemu; nazwijmy go input_files dla uproszczenia.

W bloku procesu wprowadź następującą zmianę w kodzie:

hello-workflow.nf
      input:
      path input_files
hello-workflow.nf
      input:
      ???

Zauważ, że używamy przedrostka path, mimo że oczekujemy, że będzie to zawierało wiele plików.

2.2.3. Skomponuj polecenie konkatenacji

Tu sprawa może być nieco trudniejsza, ponieważ musimy być w stanie obsłużyć dowolną liczbę plików wejściowych. Konkretnie, nie możemy napisać polecenia z góry, więc musimy powiedzieć Nextflow, jak je skomponować w czasie wykonania na podstawie tego, jakie wejścia trafią do procesu.

Innymi słowy, jeśli mamy kanał wejściowy zawierający element [file1.txt, file2.txt, file3.txt], potrzebujemy, aby Nextflow zamienił to na cat file1.txt file2.txt file3.txt.

Na szczęście Nextflow z przyjemnością zrobi to za nas, jeśli po prostu napiszemy cat ${input_files} w poleceniu skryptu.

W bloku procesu wprowadź następującą zmianę w kodzie:

hello-workflow.nf
    script:
    """
    cat ${input_files} > 'COLLECTED-output.txt'
    """
hello-workflow.nf
    script:
    """
    cat ??? > 'COLLECTED-output.txt'
    """

W teorii powinno to obsłużyć dowolną liczbę plików wejściowych.

Wskazówka

Niektóre narzędzia wiersza poleceń wymagają podania argumentu (np. -input) dla każdego pliku wejściowego. W takim przypadku musielibyśmy wykonać trochę dodatkowej pracy, aby skomponować polecenie. Przykład tego możesz zobaczyć w kursie Nextflow dla genomiki.

2.3. Dodaj krok zbierania do workflow

Teraz powinniśmy tylko wywołać proces zbierania na wyjściu kroku konwersji na wielkie litery.

2.3.1. Połącz wywołania procesów

W bloku workflow wprowadź następującą zmianę w kodzie:

hello-workflow.nf
    // przekształć pozdrowienie na wielkie litery
    convertToUpper(sayHello.out)

    // zbierz wszystkie pozdrowienia do jednego pliku
    collectGreetings(convertToUpper.out)
}
hello-workflow.nf
    // przekształć pozdrowienie na wielkie litery
    convertToUpper(sayHello.out)
}

To łączy wyjście convertToUpper() z wejściem collectGreetings().

2.3.2. Uruchom workflow z -resume

Spróbujmy.

nextflow run hello-workflow.nf -resume
Wynik polecenia
N E X T F L O W   ~  version 25.10.2

Launching `hello-workflow.nf` [mad_gilbert] DSL2 - revision: 6acfd5e28d

executor >  local (3)
[79/33b2f0] sayHello (2)         | 3 of 3, cached: 3 ✔
[99/79394f] convertToUpper (3)   | 3 of 3, cached: 3 ✔
[47/50fe4a] collectGreetings (1) | 3 of 3 ✔

Uruchomienie zakończyło się pomyślnie, włącznie z trzecim krokiem.

Jednak spójrz na liczbę wywołań collectGreetings() w ostatniej linii. Oczekiwaliśmy tylko jednego, ale są trzy.

Teraz spójrz na zawartość końcowego pliku wyjściowego.

Zawartość pliku
results/COLLECTED-output.txt
Holà

O nie. Krok zbierania został uruchomiony indywidualnie dla każdego pozdrowienia, co NIE jest tym, czego chcieliśmy.

Musimy coś zrobić, aby wyraźnie powiedzieć Nextflow, że chcemy, aby ten trzeci krok działał na wszystkich elementach w kanale wyprodukowanym przez convertToUpper().

2.4. Użyj operatora do zebrania pozdrowień w pojedyncze wejście

Tak, ponownie odpowiedzią na nasz problem jest operator.

Konkretnie użyjemy trafnie nazwanego operatora collect().

2.4.1. Dodaj operator collect()

Tym razem będzie to wyglądać nieco inaczej, ponieważ nie dodajemy operatora w kontekście fabryki kanałów; dodajemy go do kanału wyjściowego.

Bierzemy convertToUpper.out i dołączamy operator collect(), co daje nam convertToUpper.out.collect(). Możemy to bezpośrednio podłączyć do wywołania procesu collectGreetings().

W bloku workflow wprowadź następującą zmianę w kodzie:

hello-workflow.nf
    // zbierz wszystkie pozdrowienia do jednego pliku
    collectGreetings(convertToUpper.out.collect())
}
hello-workflow.nf
    // zbierz wszystkie pozdrowienia do jednego pliku
    collectGreetings(convertToUpper.out)
}

2.4.2. Dodaj instrukcje view()

Dodajmy również kilka instrukcji view(), aby zwizualizować stany kanału przed i po.

hello-workflow.nf
    // zbierz wszystkie pozdrowienia do jednego pliku
    collectGreetings(convertToUpper.out.collect())

    // opcjonalne instrukcje view
    convertToUpper.out.view { contents -> "Before collect: $contents" }
    convertToUpper.out.collect().view { contents -> "After collect: $contents" }
}
hello-workflow.nf
    // zbierz wszystkie pozdrowienia do jednego pliku
    collectGreetings(convertToUpper.out.collect())
}

Instrukcje view() mogą być umieszczone gdziekolwiek chcesz; umieściliśmy je zaraz po wywołaniu dla czytelności.

2.4.3. Uruchom workflow ponownie z -resume

Spróbujmy:

nextflow run hello-workflow.nf -resume
Wynik polecenia
N E X T F L O W   ~  version 25.10.2

Launching `hello-workflow.nf` [soggy_franklin] DSL2 - revision: bc8e1b2726

[d6/cdf466] sayHello (1)       | 3 of 3, cached: 3 ✔
[99/79394f] convertToUpper (2) | 3 of 3, cached: 3 ✔
[1e/83586c] collectGreetings   | 1 of 1 ✔
Before collect: /workspaces/training/hello-nextflow/work/b3/d52708edba8b864024589285cb3445/UPPER-Bonjour-output.txt
Before collect: /workspaces/training/hello-nextflow/work/99/79394f549e3040dfc2440f69ede1fc/UPPER-Hello-output.txt
Before collect: /workspaces/training/hello-nextflow/work/aa/56bfe7cf00239dc5badc1d04b60ac4/UPPER-Holà-output.txt
After collect: [/workspaces/training/hello-nextflow/work/b3/d52708edba8b864024589285cb3445/UPPER-Bonjour-output.txt, /workspaces/training/hello-nextflow/work/99/79394f549e3040dfc2440f69ede1fc/UPPER-Hello-output.txt, /workspaces/training/hello-nextflow/work/aa/56bfe7cf00239dc5badc1d04b60ac4/UPPER-Holà-output.txt]

Uruchomienie zakończyło się pomyślnie, chociaż wynik w logu może wyglądać nieco bardziej chaotycznie (uporządkowaliśmy go dla czytelności).

Tym razem trzeci krok został wywołany tylko raz!

Patrząc na wynik instrukcji view(), widzimy:

  • Trzy instrukcje Before collect:, po jednej dla każdego pozdrowienia: w tym momencie ścieżki plików są indywidualnymi elementami w kanale.
  • Pojedyncza instrukcja After collect:: trzy ścieżki plików są teraz spakowane w pojedynczy element.

Spójrz na zawartość końcowego pliku wyjściowego.

Zawartość pliku
results/COLLECTED-output.txt
BONJOUR
HELLO
HOLà

Tym razem mamy wszystkie trzy pozdrowienia w końcowym pliku wyjściowym. Sukces!

Uwaga

Jeśli uruchomisz to kilka razy bez -resume, zobaczysz, że kolejność pozdrowień zmienia się od uruchomienia do uruchomienia. To pokazuje, że kolejność, w jakiej elementy przepływają przez wywołania procesów, nie jest gwarantowana jako stała.

2.4.4. Usuń instrukcje view() dla czytelności

Zanim przejdziesz do następnej sekcji, zalecamy usunięcie instrukcji view(), aby nie zaśmiecać wyjścia konsoli.

hello-workflow.nf
    // zbierz wszystkie pozdrowienia do jednego pliku
    collectGreetings(convertToUpper.out.collect())
hello-workflow.nf
    // zbierz wszystkie pozdrowienia do jednego pliku
    collectGreetings(convertToUpper.out.collect())

    // opcjonalne instrukcje view
    convertToUpper.out.view { contents -> "Before collect: $contents" }
    convertToUpper.out.collect().view { contents -> "After collect: $contents" }

To jest zasadniczo odwrotna operacja od punktu 2.4.2.

Podsumowanie

Wiesz już, jak zbierać wyjścia z wielu wywołań procesu i przekazywać je do wspólnego kroku analizy lub podsumowania.

Co dalej?

Dowiedz się, jak przekazać więcej niż jedno wejście do procesu.


3. Przekaż więcej niż jedno wejście do procesu

Chcemy mieć możliwość nazwania końcowego pliku wyjściowego konkretną nazwą, aby móc przetwarzać kolejne partie pozdrowień bez nadpisywania poprzednich rezultatów.

W tym celu wprowadzimy następujące udoskonalenia do workflow:

  • Zmodyfikujemy proces zbierający, aby akceptował zdefiniowaną przez użytkownika nazwę pliku wyjściowego
  • Dodamy parametr wiersza poleceń do workflow i przekażemy go do procesu zbierającego

3.1. Zmodyfikuj proces zbierający

Musimy zadeklarować dodatkowe wejście i zintegrować je z nazwą pliku wyjściowego.

3.1.1. Zadeklaruj dodatkowe wejście

Dobra wiadomość: możemy zadeklarować tyle zmiennych wejściowych, ile chcemy w definicji procesu. Nazwijmy tę zmienną batch_name.

W bloku procesu wprowadź następującą zmianę w kodzie:

hello-workflow.nf
    input:
    path input_files
    val batch_name
hello-workflow.nf
    input:
    path input_files

Możesz skonfigurować procesy tak, aby oczekiwały tylu wejść, ile chcesz. W tej chwili wszystkie są ustawione jako wymagane wejścia; musisz podać wartość, aby workflow działał.

Dowiesz się, jak zarządzać wymaganymi i opcjonalnymi wejściami później na swojej drodze z Nextflow.

3.1.2. Użyj zmiennej batch_name w nazwie pliku wyjściowego

Możemy wstawić zmienną do nazwy pliku wyjściowego w ten sam sposób, w jaki komponowaliśmy dynamiczne nazwy plików wcześniej.

W bloku procesu wprowadź następującą zmianę w kodzie:

hello-workflow.nf
    output:
    path "COLLECTED-${batch_name}-output.txt"

    script:
    """
    cat ${input_files} > 'COLLECTED-${batch_name}-output.txt'
    """
hello-workflow.nf
    output:
    path "COLLECTED-output.txt"

    script:
    """
    cat ${input_files} > 'COLLECTED-output.txt'
    """

To konfiguruje proces do używania wartości batch_name do generowania konkretnej nazwy pliku dla końcowego wyjścia workflow.

3.2. Dodaj parametr wiersza poleceń batch

Teraz potrzebujemy sposobu na dostarczenie wartości dla batch_name i przekazanie jej do wywołania procesu.

3.2.1. Użyj params do skonfigurowania parametru

Wiesz już, jak używać systemu params do deklarowania parametrów CLI. Użyjmy tego do zadeklarowania parametru batch (z domyślną wartością, bo jesteśmy leniwi).

W sekcji parametrów pipeline wprowadź następujące zmiany w kodzie:

hello-workflow.nf
/*
 * Pipeline parameters
 */
params {
    input: Path = 'data/greetings.csv'
    batch: String = 'batch'
}
hello-workflow.nf
/*
 * Pipeline parameters
 */
params {
    input: Path = 'data/greetings.csv'
}

Tak jak demonstrowaliśmy dla --input, możesz nadpisać tę domyślną wartość, określając wartość z --batch w wierszu poleceń.

3.2.2. Przekaż parametr batch do procesu

Aby dostarczyć wartość parametru do procesu, musimy dodać ją w wywołaniu procesu.

W bloku workflow wprowadź następującą zmianę w kodzie:

hello-workflow.nf
    // zbierz wszystkie pozdrowienia do jednego pliku
    collectGreetings(convertToUpper.out.collect(), params.batch)
hello-workflow.nf
    // zbierz wszystkie pozdrowienia do jednego pliku
    collectGreetings(convertToUpper.out.collect())

Widzisz, że aby dostarczyć wiele wejść do procesu, po prostu wymieniasz je w nawiasach wywołania, oddzielone przecinkami.

Ostrzeżenie

MUSISZ dostarczyć wejścia do procesu w DOKŁADNIE TEJ SAMEJ KOLEJNOŚCI, w jakiej są wymienione w bloku definicji wejść procesu.

3.3. Uruchom workflow

Spróbujmy uruchomić to z nazwą partii w wierszu poleceń.

nextflow run hello-workflow.nf -resume --batch trio
Wynik polecenia
N E X T F L O W   ~  version 25.10.2

Launching `hello-workflow.nf` [confident_rutherford] DSL2 - revision: bc58af409c

executor >  local (1)
[79/33b2f0] sayHello (2)       | 3 of 3, cached: 3 ✔
[99/79394f] convertToUpper (2) | 3 of 3, cached: 3 ✔
[b5/f19efe] collectGreetings   | 1 of 1 ✔

Uruchomienie zakończyło się pomyślnie i wygenerowało oczekiwane wyjście:

Zawartość pliku
results/COLLECTED-trio-output.txt
HELLO
BONJOUR
HOLà

Teraz, o ile odpowiednio określimy parametr, kolejne uruchomienia na innych partiach wejść nie nadpiszą poprzednich wyników.

Podsumowanie

Wiesz już, jak przekazać więcej niż jedno wejście do procesu.

Co dalej?

Dowiedz się, jak emitować wiele wyjść i wygodnie je obsługiwać.


4. Dodaj wyjście do kroku zbierającego

Do tej pory używaliśmy procesów, które produkowały tylko jedno wyjście każdy. Mogliśmy uzyskać dostęp do ich wyjść bardzo wygodnie za pomocą składni <process>.out, której używaliśmy zarówno w kontekście przekazywania wyjścia do następnego procesu (np. convertToUpper(sayHello.out)), jak i w kontekście sekcji publish: (np. first_output = sayHello.out).

Co się dzieje, gdy proces produkuje więcej niż jedno wyjście? Jak obsługujemy wiele wyjść? Czy możemy wybrać i użyć konkretnego wyjścia?

Wszystko to doskonałe pytania, a krótka odpowiedź brzmi: tak, możemy!

Wiele wyjść zostanie spakowanych w oddzielne kanały. Tym strumieniom wyjściowym można nadać nazwy, co ułatwia późniejsze odwoływanie się do nich indywidualnie, albo uzyskać do nich dostęp przez indeks.

Zagłębmy się w przykład.

Dla celów demonstracyjnych załóżmy, że chcemy policzyć liczbę pozdrowień zbieranych dla danej partii wejść i zaraportować to w pliku.

4.1. Zmodyfikuj proces, aby liczył i wyprowadzał liczbę pozdrowień

To będzie wymagało dwóch kluczowych zmian w definicji procesu: potrzebujemy sposobu na policzenie pozdrowień i zapisanie pliku raportu, a następnie musimy dodać ten plik raportu do bloku output procesu.

4.1.1. Policz liczbę zebranych pozdrowień

Wygodnie jest to, że Nextflow pozwala nam dodawać dowolny kod w bloku skryptu (script:) definicji procesu, co jest bardzo przydatne do robienia takich rzeczy jak ta.

Oznacza to, że możemy użyć wbudowanej funkcji Nextflow size(), aby uzyskać liczbę plików w tablicy input_files, i zapisać wynik do pliku za pomocą polecenia echo.

W bloku procesu collectGreetings wprowadź następujące zmiany w kodzie:

hello-workflow.nf
    script:
    count_greetings = input_files.size()
    """
    cat ${input_files} > 'COLLECTED-${batch_name}-output.txt'
    echo 'There were ${count_greetings} greetings in this batch.' > '${batch_name}-report.txt'
    """
hello-workflow.nf
    script:
    """
    cat ${input_files} > 'COLLECTED-${batch_name}-output.txt'
    """

Zmienna count_greetings zostanie obliczona w czasie wykonania.

4.1.2. Wyemituj plik raportu i nazwij wyjścia

W zasadzie wszystko, co musimy zrobić, to dodać plik raportu do bloku output:.

Jednak przy okazji dodamy również tagi emit: do naszych deklaracji wyjść. Pozwoli nam to wybierać wyjścia po nazwie zamiast używać indeksów pozycyjnych.

W bloku procesu wprowadź następującą zmianę w kodzie:

hello-workflow.nf
    output:
    path "COLLECTED-${batch_name}-output.txt", emit: outfile
    path "${batch_name}-report.txt", emit: report
hello-workflow.nf
    output:
    path "COLLECTED-${batch_name}-output.txt"

Tagi emit: są opcjonalne i mogliśmy dodać tag tylko do jednego z wyjść. Ale jak mówi powiedzenie, czemu nie obu?

Wskazówka

Jeśli nie nazwiesz wyjść procesu za pomocą emit:, nadal możesz uzyskać do nich dostęp indywidualnie, używając ich odpowiedniego indeksu (liczonego od zera). Na przykład, użyłbyś <process>.out[0], aby uzyskać pierwsze wyjście, <process>.out[1], aby uzyskać drugie wyjście, i tak dalej.

Preferujemy nazywanie wyjść, ponieważ w przeciwnym razie zbyt łatwo jest przez pomyłkę użyć złego indeksu, szczególnie gdy proces produkuje wiele wyjść.

4.2. Zaktualizuj wyjścia workflow

Teraz, gdy mamy dwa wyjścia z procesu collectGreetings, wyjście collectGreetings.out zawiera dwa kanały:

  • collectGreetings.out.outfile zawiera końcowy plik wyjściowy
  • collectGreetings.out.report zawiera plik raportu

Musimy odpowiednio zaktualizować wyjścia workflow.

4.2.1. Zaktualizuj sekcję publish:

W bloku workflow wprowadź następującą zmianę w kodzie:

hello-workflow.nf
    publish:
    first_output = sayHello.out
    uppercased = convertToUpper.out
    collected = collectGreetings.out.outfile
    batch_report = collectGreetings.out.report
hello-workflow.nf
    publish:
    first_output = sayHello.out
    uppercased = convertToUpper.out
    collected = collectGreetings.out

Jak widać, odnoszenie się do konkretnych wyjść procesu jest teraz trywialne. Gdy dodamy jeszcze jeden krok do naszego pipeline w Części 5 (Kontenery), będziemy mogli łatwo odnieść się do collectGreetings.out.outfile i przekazać go do nowego procesu (spoiler: nowy proces nazywa się cowpy).

Ale na razie dokończmy aktualizację wyjść na poziomie workflow.

4.2.2. Zaktualizuj blok output

W bloku output wprowadź następującą zmianę w kodzie:

hello-workflow.nf
output {
    first_output {
        path 'hello_workflow'
        mode 'copy'
    }
    uppercased {
        path 'hello_workflow'
        mode 'copy'
    }
    collected {
        path 'hello_workflow'
        mode 'copy'
    }
    batch_report {
        path 'hello_workflow'
        mode 'copy'
    }
}
hello-workflow.nf
output {
    first_output {
        path 'hello_workflow'
        mode 'copy'
    }
    uppercased {
        path 'hello_workflow'
        mode 'copy'
    }
    collected {
        path 'hello_workflow'
        mode 'copy'
    }
}

Nie musimy aktualizować definicji wyjścia collected, ponieważ ta nazwa się nie zmieniła. Musimy tylko dodać nowe wyjście.

4.3. Uruchom workflow

Spróbujmy uruchomić to z bieżącą partią pozdrowień.

nextflow run hello-workflow.nf -resume --batch trio
Wynik polecenia
 N E X T F L O W   ~  version 25.10.2

Launching `hello-workflow.nf` [ecstatic_wilson] DSL2 - revision: c80285f8c8

executor >  local (1)
[c5/4c6ca9] sayHello (3)       [100%] 3 of 3, cached: 3 ✔
[0e/6cbc59] convertToUpper (3) [100%] 3 of 3, cached: 3 ✔
[02/61ead2] collectGreetings   [100%] 1 of 1 ✔

Jeśli zajrzysz do katalogu results/hello_workflow/, znajdziesz nowy plik raportu trio-report.txt. Otwórz go, aby sprawdzić, czy workflow poprawnie zaraportował liczbę przetworzonych pozdrowień.

Zawartość pliku
trio-report.txt
There were 3 greetings in this batch.

Możesz dodać więcej pozdrowień do pliku CSV i przetestować, co się stanie.

Podsumowanie

Wiesz już, jak sprawić, aby proces emitował wiele nazwanych wyjść i jak odpowiednio obsługiwać je na poziomie workflow.

Ogólnie rzecz biorąc, rozumiesz kluczowe zasady związane z łączeniem procesów na typowe sposoby.

Co dalej?

Weź dodatkową długą przerwę, zasłużyłeś na to.

Gdy będziesz gotowy, przejdź do Część 4: Hello Modules, aby dowiedzieć się, jak modularyzować Swój kod dla lepszej utrzymywalności i efektywności.


Quiz

#

Jak uzyskujesz dostęp do wyjścia procesu w bloku workflow?

#

Co determinuje kolejność wykonywania procesów w Nextflow?

#

Który operator powinien zastąpić ???, aby zebrać wszystkie wyjścia w pojedynczą listę dla procesu następczego?

workflow {
    greetings_ch = Channel.of('Hello', 'Bonjour', 'Hola')
    SAYHELLO(greetings_ch)
    GATHER_ALL(SAYHELLO.out.???)
}
#

Kiedy należy używać operatora collect()?

#

Jak uzyskujesz dostęp do nazwanego wyjścia z procesu?

#

Jaka jest poprawna składnia do nazywania wyjścia w procesie?

#

Przy dostarczaniu wielu wejść do procesu, co musi być prawdą?