Remove software carpentries logo
[rnaseq-cwl-training.git] / _episodes / 06-expressions.md
1 ---
2 title: "Dynamic Workflow Behavior"
3 teaching: 20
4 exercises: 10
5 questions:
6 - "What kind of custom logic can happen between steps?"
7 objectives:
8 - "Customize the STAR output filename to use the input filename."
9 - "Organize files into directories."
10 keypoints:
11 - "CWL expressions allow you to use custom logic to determine input parameter values."
12 - "CWL ExpressionTool can be used to reshape data, such as declaring directories that should contain output files."
13 ---
14
15 # Expressions on step inputs
16
17 You might have noticed that the output bam files are all named
18 `Aligned.sortedByCoord.out.bam`.  This happens because because when we
19 call STAR, it gives the output a default file name.
20
21 During workflow execution, this is usually not a problem.  The
22 workflow runner is smart enough to know that these files are different
23 and keep them separate.  This can even make development easier by not
24 having to worry about assigning unique file names to every file.
25 Also, if we intend to discard the BAM files as intermediate results
26
27 However, it is a problem for humans interpreting the output.  We can
28 fix this by setting the parameter `OutFileNamePrefix` on STAR.  We
29 want the output filename to be based on the input filename.
30
31 In `alignment.cwl`, we can use `valueFrom` on the `OutFileNamePrefix`
32 input parameter to construct the output prefix from the input
33 filename.
34
35 ```
36 requirements:
37   StepInputExpressionRequirement: {}
38
39 steps:
40   ...
41   STAR:
42     ...
43     in:
44       ForwardReads: fq
45       ...
46       OutFileNamePrefix: {valueFrom: "$(inputs.ForwardReads.nameroot)."}
47 ```
48 {: .language-yaml }
49
50 The code between `$(...)` is called an "expression".  It is evaluated
51 when setting up the step to run, and the expression is replaced by the
52 result to get the parameter value.
53
54 An expression can refer to other inputs to the step that are either
55 directly connected to another value, or have a default value.  Here,
56 we refer to the input parameter ForwardReads, which is our fastq input
57 file.
58
59 ForwardReads is a File object, not a plain file name, so it has some
60 fields of its own.  The file object has a number of fields that we can
61 use.  These include `basename` (the name of the file, without a
62 directory), `size` (file size, in bytes), `nameext` (the last file
63 extension) and `nameroot` (the name with `nameext` removed).  Using
64
65 Finally, our expression is embedded in a string, so after replacing
66 the expression with the value of `inputs.ForwardReads.nameroot`, it
67 adds the remainder of the string, which just is a dot `.`.  This is to
68 separate the leading part of our filename from the "Aligned.bam"
69 extension that will be added by STAR.
70
71 # Organizing output files into Directories
72
73 You probably noticed that all the output files appear in the same
74 directory.  You might prefer that each file appears in its own
75 directory.  This will show you how to do that.
76
77 Unlike shell scripts, in CWL you cannot call `mkdir` and `mv` to
78 organize files into directories.  This is because the output files, at
79 this point, do not actually exist together in one directory.  They may
80 exist on different nodes, in different directories, or different cloud
81 buckets.  In CWL, instead of moving files around directly, you tell
82 the runner you want your directory to look like, and it will create it
83 for you.
84
85 We can use an "expression" to create a `Directory` object describing
86 each of our directories.  An expression is a piece of Javascript code
87 which is executed by the workflow runner to produce values that affect
88 the workflow.  These can be a simple as substituting the value of an
89 input variable, or as complex as en entire function that generates new
90 objects.
91
92 Javscript code must be bracketed inside `$(...)` or `${...}`. The
93 difference comes down to syntax.  The `$()` form is more compact but
94 can only include code that can appear on the right side of an
95 assignment (`=`), which cannot include control blocks like `if` or
96 `for`.  The `${}` form is a Javascript function, which can include
97 control blocks, and must end in a `return` statement.
98
99 Expressions can both appear in `valueFrom` fields as well as some
100 other fields, or in an `ExpressionTool` which, like `Workflow` or
101 `CommandLineTool` has explicitly defined `inputs` and `outputs`
102 sections.
103
104 The approach here is to define an expression tool which takes a
105
106 The `Directory` object has two fields, `basename` and `listing`.  The
107 `basename` is the name of the directory, and the `listing` is the
108 contents, which consists of other File and Directory objects.
109
110 Create `subdirs.cwl`:
111
112 ```
113 cwlVersion: v1.2
114 class: ExpressionTool
115 requirements:
116   InlineJavascriptRequirement: {}
117 inputs:
118   fq: File[]
119   bams: File[]
120   qc: File[]
121 outputs:
122   dirs: Directory[]
123 expression: |-
124   ${
125   var dirs = [];
126   for (var i = 0; i < inputs.bams.length; i++) {
127     dirs.push({
128       "class": "Directory",
129       "basename": inputs.fq[i].nameroot,
130       "listing": [inputs.bams[i], inputs.qc[i]]
131     });
132   }
133   return {"dirs": dirs};
134   }
135 ```
136 {: .language-yaml }
137
138 Then change `main.cwl`:
139
140 ```
141 steps:
142   ...
143   output-subdirs:
144     run: subdirs.cwl
145     in:
146       fq: fq
147       bams: alignment/bam_sorted_indexed
148       qc: alignment/qc_html
149     out: [dirs]
150 outputs:
151   dirs:
152     type: Directory[]
153     outputSource: output-subdirs/dirs
154   featurecounts:
155     type: File
156     outputSource: featureCounts/featurecounts
157 ```
158 {: .language-yaml }
159
160 > ## Running the workflow
161 >
162 > Run the workflow.  Look at the output.  The BAM and fastqc files
163 > should now be organized into directories, with better naming of the
164 > bam files.
165 >
166 {: .challenge }
167
168 > ## Episode solution
169 > * <a href="../assets/answers/ep6/main.cwl">main.cwl</a>
170 > * <a href="../assets/answers/ep6/alignment.cwl">alignment.cwl</a>
171 > * <a href="../assets/answers/ep6/featureCounts.cwl">featureCounts.cwl</a>
172 > * <a href="../assets/answers/ep6/subdirs.cwl">subdirs.cwl</a>
173 {: .solution}