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