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