From e6c8245fcdc7850b1fdd0d5cf709a0231723c697 Mon Sep 17 00:00:00 2001 From: Sanaz Agarwal Date: Sat, 9 Mar 2024 17:53:18 -0800 Subject: [PATCH] improved grammar and visual appearance --- 01-intro.Rmd | 68 +++++++++--------- .../figure-html/unnamed-chunk-2-1.png | Bin .../figure-html/unnamed-chunk-3-1.png | Bin 02-workflow-plan.Rmd | 2 +- 03-first-task.Rmd | 59 ++++++++------- 04-linear-chain.Rmd | 18 ++--- 06-arrays.Rmd | 7 +- 07-task-aliasing.Rmd | 14 ++-- docs/404.html | 13 ++-- docs/introduction-to-wdl.html | 61 ++++++++-------- docs/libs/gitbook-2.6.7/css/style.css | 10 ++- .../libs/gitbook-2.6.7/js/plugin-clipboard.js | 6 +- docs/search_index.json | 2 +- ...dix\357\200\272-Backends-and-Executors.md" | 0 14 files changed, 131 insertions(+), 129 deletions(-) rename {_bookdown_files/02-chapter_of_course_files => 02-chapter_of_course_files}/figure-html/unnamed-chunk-2-1.png (100%) rename {_bookdown_files/02-chapter_of_course_files => 02-chapter_of_course_files}/figure-html/unnamed-chunk-3-1.png (100%) rename manuscript/8-Appendix:-Backends-and-Executors.md => "manuscript/8-Appendix\357\200\272-Backends-and-Executors.md" (100%) diff --git a/01-intro.Rmd b/01-intro.Rmd index 58f626e..8f0df2b 100644 --- a/01-intro.Rmd +++ b/01-intro.Rmd @@ -12,16 +12,13 @@ To make sure that we are on the same page, this guide assumes that you are able ## Review of basic WDL syntax -We will do some review of the WDL syntax. A WDL workflow consists of at least one task. +We will do some review of the WDL syntax. A WDL workflow consists of at least one `task`. +
version 1.0
 
-
-```         
-version 1.0
-
-task do_something {
+task do_something {
     command <<<
         exit 0
     >>>
@@ -30,17 +27,16 @@ task do_something {
 workflow my_workflow {
     call do_something
 }
-```
+
-A workflow, and the tasks it calls, generally has inputs. +A workflow, and the tasks it calls, generally has an `input` section. -``` -version 1.0 +
version 1.0
 
 task do_something {
-    input {
+    input {
         File fastq
     }
     command <<<
@@ -49,24 +45,23 @@ task do_something {
 }
 
 workflow my_workflow {
-    input {
+    input {
         File fq
     }
     call do_something {
-        input:
+        input:
             fastq = fq
     }
 }
-```
+
-The input `fq` is defined to be a File variable type. WDL supports various variable types, such as String, Integer, Float, and Boolean. For more information on types in WDL, we recommend [OpenWDL's documentation on variable types](https://docs.openwdl.org/en/stable/WDL/variable_types/). +The input `fq` is defined to be a **File** variable type. WDL supports various variable types, such as String, Integer, Float, and Boolean. For more information on types in WDL, we recommend [OpenWDL's documentation on variable types](https://docs.openwdl.org/en/stable/WDL/variable_types/). To access a task-level input variable in a task's command section, it is usually referenced using \~{this} notation. To access a workflow-level variable in a workflow, it is referenced just by its name without any special notation. To access a workflow-level variable in a task, it must be passed into the task as an input. -``` -version 1.0 +
version 1.0
 
 task do_something {
     input {
@@ -74,7 +69,7 @@ task do_something {
         String basename_of_fq
     }
     command <<<
-        echo "First ten lines of ~{basename_of_fq}: "
+        echo "First ten lines of ~{basename_of_fq}: "
         head ~{fastq}
     >>>
 }
@@ -92,14 +87,13 @@ workflow my_workflow {
             basename_of_fq = basename_of_fq
     }
 }
-```
+
-Tasks and workflows also typically have outputs. The task-level outputs can be accessed by the workflow or any subsequent tasks. The workflow-level outputs represent the final output of the overall workflow. +Tasks and workflows also typically have an `output` section. The task-level outputs can be accessed by the workflow or any subsequent tasks. The workflow-level outputs represent the final output of the overall workflow. -``` -version 1.0 +
version 1.0
 
 task do_something {
     input {
@@ -110,7 +104,7 @@ task do_something {
         echo "First ten lines of ~{basename_of_fq}: " >> output.txt
         head ~{fastq} >> output.txt
     >>>
-    output {
+    output {
         File first_ten_lines = "output.txt"
     }
 }
@@ -128,15 +122,18 @@ workflow my_workflow {
             basename_of_fq = basename_of_fq
     }
     
-    output {
+    output {
         File ten_lines = do_something.first_ten_lines
     }
 }
-```
+
## Using JSONs to control workflow inputs -Running a WDL workflow generally requires two files: A .wdl file, which contains the actual workflow, and a .json file, which provides the inputs for the workflow. +Running a WDL workflow generally requires two files: + ++ **.wdl** file - it contains the actual workflow ++ **.json** file - it provides the inputs for the workflow. In the example we showed earlier, the workflow takes in a file referred to by the variable `fq`. This needs to be provided by the user. Typically, this is done with a JSON file. Here's what a JSON file for this workflow might look like: @@ -150,20 +147,21 @@ In the example we showed earlier, the workflow takes in a file referred to by th JSON files consist of key-value pairs. In this case, the key is `"my_workflow.fq"` and the value is the path `"./data/example.fq"`. The first part of the key is the name of the workflow as written in the WDL file, in this case `my_workflow`. The variable being represented is referred to its name, in this case, `fq`. So, the file located at the path `./data/example.fq` is being input as a variable called `fq` into the workflow named `my_workflow`. +One can use this methodolgy to name key:value pairs `"name_of_workflow.name_of_variable":"path_to_variable"`. + Files aren't the only type of variable you can refer to when using JSONs. Here's an example JSON for every common WDL variable type. -``` -{ - "some_workflow.file": "./data/example.fq", - "some_workflow.string": "Hello world!", - "some_workflow.integer": 1965, - "some_workflow.float": 3.1415, - "some_workflow.boolean": true, - "some_workflow.array_of_files": ["./data/example01.fq", "./data/example02.fq"] +
{
+    "some_workflow.file": "./data/example.fq",                                       # file_path
+    "some_workflow.string": "Hello world!",                                          # string
+    "some_workflow.integer": 1965,                                                   # int
+    "some_workflow.float": 3.1415,                                                   # float
+    "some_workflow.boolean": true,                                                   # boolean
+    "some_workflow.array_of_files": ["./data/example01.fq", "./data/example02.fq"]   # array
 }
-```
+
::: {.notice data-latex="notice"} Resources: diff --git a/_bookdown_files/02-chapter_of_course_files/figure-html/unnamed-chunk-2-1.png b/02-chapter_of_course_files/figure-html/unnamed-chunk-2-1.png similarity index 100% rename from _bookdown_files/02-chapter_of_course_files/figure-html/unnamed-chunk-2-1.png rename to 02-chapter_of_course_files/figure-html/unnamed-chunk-2-1.png diff --git a/_bookdown_files/02-chapter_of_course_files/figure-html/unnamed-chunk-3-1.png b/02-chapter_of_course_files/figure-html/unnamed-chunk-3-1.png similarity index 100% rename from _bookdown_files/02-chapter_of_course_files/figure-html/unnamed-chunk-3-1.png rename to 02-chapter_of_course_files/figure-html/unnamed-chunk-3-1.png diff --git a/02-workflow-plan.Rmd b/02-workflow-plan.Rmd index 2044374..913c10e 100644 --- a/02-workflow-plan.Rmd +++ b/02-workflow-plan.Rmd @@ -55,4 +55,4 @@ CALU1 is a lung cancer cell line that has a mutation in the gene *KRAS* (Kirsten MOLM 13 is a human leukemia cell line commonly used in research. While it is also a cancer cell line for the purposes of this workflow example we are going to consider it as a "normal". This cell line does not have mutations in *EGFR* nor in *KRAS* and therefore is a practical surrogate in lieu of a conventional normal sample ### Test data details -Fastq files for all these three samples were derived from their respective whole exome sequencing. However for the purpose of this guide we have limited the sequencing reads to span +/- 200 bp around the mutation sites for both genes. In doing so we are able to shrink the data files for quick testing. +FASTQ files for all these three samples were derived from their respective whole exome sequencing. However for the purpose of this guide we have limited the sequencing reads to span +/- 200 bps around the mutation sites for both genes. In doing so we are able to shrink the data files for quick testing. diff --git a/03-first-task.Rmd b/03-first-task.Rmd index 2acb4a8..8dc696e 100644 --- a/03-first-task.Rmd +++ b/03-first-task.Rmd @@ -8,7 +8,7 @@ Before we write any sort of WDL -- whether it is for somatic mutation calling li As mentioned in the first part of this course, every WDL workflow is made up of at least one task. A task typically has inputs, outputs, runtime attributes, and a command section. You can think of a task as a discrete step in a workflow. It can involve a single call to a single bioinformatics tool, a sequence of bash commands, an inline Python script... almost anything you can do non-interactively in a terminal, you can do in a WDL task. In this section, we will go over the parts of a WDL task in more detail to help us write a task for somatic mutation calling. ## Inputs -The inputs of a task are the files and/or variables you will passing into your task's command section. Typically, you will want to include at least one File input in a task, but that isn't a requirement. You can pass most WDL variable types into a task. In our example workflow, we are starting with a single fastq file per sample, and we know we will need to convert it into a sam file. A sam file is an alignment, so we will need a reference genome to align our fastqs to. We also want to be able to control the threading for this task. Our first task's inputs will therefore start out looking like this: +The inputs of a task are the files and/or variables you pass into your task's command section. Typically, you will want to include at least one 'File' input in a task, but that isn't a requirement. You can pass most WDL variable types into a task. In our example workflow, we are starting with a single FASTQ file per sample, and we know we will need to convert it into a SAM file. A SAM file is an alignment, so we will need a reference genome to align our FASTQs to. We also want to be able to control the threading for this task. Our first task's inputs will therefore start out looking like this: ``` task some_aligner { input { @@ -20,7 +20,7 @@ task some_aligner { } ``` -For some aligners, this would be a sufficient set of inputs, but we have decided to use bwa mem in particular to take us from fastq to sam. bwa mem requires a lot of index files, which we will also need to input. This can be done via an array, but for now we'll list everything separately to make sure nothing is being left out. +For some aligners, this would be a sufficient set of inputs, but we have decided to use BWA MEM in particular to take us from FASTQ to SAM format. BWA MEM requires a lot of index files, which we will also need to input. This can be done via an array, but for now we'll list everything separately to make sure nothing is being left out. We also want to define a default value for `threads` so that someone who does not know much about threading can still use the workflow. We want to use this workflow on human data, so we'll go a little high for the default number of threads and set it to sixteen. In WDL, we do this by declaring `Int threads = 16`. Make sure to put this in the task (or workflow) inputs section -- if you put it elsewhere, that variable cannot be changed from its default value, so it will always be 16. @@ -56,7 +56,7 @@ Within the command section, we refer to those variables using `~{this}` syntax. A WDL task's input variables are generally referred to in the command section using a tilde (~) and curly braces, using heredoc syntax.
- Why we use heredox syntax. + Why we use heredox syntax? You may see WDLs that use this notation for the command section in a task: @@ -92,21 +92,20 @@ Heredoc-style syntax for command sections can be clearer than the alternative, a
-To prevent issues with spaces in String and File types, it is often a good idea to put quotation marks around a String or File variabls, like so: +To prevent issues with spaces in String and File types, it is often a good idea to put quotation marks around a String or File variables, like so: -``` -task cowsay { +
task cowsay {
   input {
     String some_string
   }
   command <<<
-    cowsay -t "~{some_string}"
+    cowsay -t "~{some_string}"
   >>>
 }
-```
+
-Why we put quotation marks around a String or File variables in Commands. +Why we put quotation marks around a String or File variables in Commands? If `some_string` is "hello world" then the command section of this task is interpreted as the following: @@ -152,16 +151,16 @@ When running a WDL, a WDL executor will typically place duplicates of the input For example, if you were to run this workflow on a laptop using miniwdl, `~{ref_fasta}` would likely end up turning into `./_miniwdl_inputs/0/ref.fa` at runtime. On the other hand, if you were running the exact same workflow with Cromwell, `~{ref_fasta}` would turn into something like `/cromwell-executions/BwaMem/97c9341e-9322-9a2f-4f54-4114747b8fff/call-test_localization/inputs/-2022115965/ref.fa`. Keep in mind that these are the paths of *copies* of the input files, and that sometimes input files can be in different subfolders. For example, it's possible `~{input_fastq}` would be `./_miniwdl_inputs/0/sample.fastq` while `~{ref_fasta}` may be `./_miniwdl_inputs/1/ref.fa`. -For many programs, an input file being at `./ref.fa` versus `/_miniwdl_inputs/0/ref.fa` is inconsequential. However, this aspect of WDL can occasionally cause issues. bwa mem is a great example of the type of command where this sort of thing can go haywire without proper planning, due to the program making an assumption about some of your input files. Specifically, bwa mem assumes that the reference fasta that you pass in shares the same folder as the other reference files (ref_amb, ref_ann, ref_bwt, etc), and it does not allow you to specify otherwise. +For many programs, an input file being at `./ref.fa` versus `/_miniwdl_inputs/0/ref.fa` is inconsequential. However, this aspect of WDL can occasionally cause issues. BWA MEM is a great example of the type of command where this sort of thing can go haywire without proper planning, due to the program making an assumption about some of your input files. Specifically, BWA MEM assumes that the reference fasta that you pass in shares the same folder as the other reference files (ref_amb, ref_ann, ref_bwt, etc), and it does not allow you to specify otherwise.
-Another example of file localization issue. +Another example of file localization issue -bwa is not the only program that makes assumptions about where files are located, and assumptions being made do not only affect reference genome files. Bioinformatics programs that take in some sort of index file frequently assume that index file is located in the same directory as the non-index input. For example, if you were to pass in `SAMN1234.bam` into [covstats](https://github.com/brentp/goleft/tree/master/covstats), it would expect an index file named `SAMN1234.bam.bai` or `SAMN1234.bai` in the same directory as the bam file, [as seen in the source code here](https://github.com/brentp/goleft/blob/fa6b00d20d1f73a068ffbab49a5769d173cae56d/covstats/covstats.go#L239). As there is no way to specify that the index file manually, you need to take that into consideration when writing WDLs involving covstats, bwa, and other similar tools. +BWA is not the only program that makes assumptions about where files are located, and assumptions being made do not only affect reference genome files. Bioinformatics programs that take in some sort of index file frequently assume that index file is located in the same directory as the non-index input. For example, if you were to pass in `SAMN1234.bam` into [covstats](https://github.com/brentp/goleft/tree/master/covstats), it would expect an index file named `SAMN1234.bam.bai` or `SAMN1234.bai` in the same directory as the bam file, [as seen in the source code here](https://github.com/brentp/goleft/blob/fa6b00d20d1f73a068ffbab49a5769d173cae56d/covstats/covstats.go#L239). As there is no way to specify that the index file manually, you need to take that into consideration when writing WDLs involving covstats, bwa, and other similar tools.
-Thankfully, the solution here is simple: Move all of the input files directly into the working directory. +Thankfully, the solution here is simple: _Move all of the input files directly into the working directory_. ``` task BwaMem { @@ -201,20 +200,20 @@ task BwaMem { Some backends/executors do not support `mv` acting on input files. If you are running into problems with this and are working with miniwdl, the `--copy-input-files` flag will usually allow `mv` to work. You could also simply use `cp` to copy the files instead of move them, although this may not be an efficient use of disk space, so consider using `mv` if your target backends and executors can handle it. ::: -With our files now all in the working directory, we can turn our attention to the bwa task itself. We can no longer directly pass in `~{ref_fasta}` or any of the other files we mved into the working directory, because those variables will point to a non-existent file in a now-empty input directory. There are several ways to solve this problem: +With our files now all in the working directory, we can turn our attention to the alignment (BWA) task itself. We can no longer directly pass in `~{ref_fasta}` or any of the other files we moved into the working directory, because those variables will point to a non-existent file in a now-empty input directory. There are several ways to solve this problem: * Assuming the filename of an input is constant, which might be a safe assumption for reference files * Using the bash built-in basename function -* Using the WDL built-in basename() function along with private variables +* Using the WDL built-in `basename()` function along with private variables We recommend using the last option, as it works for essentially any input and may be more intuitive than the bash basename function. [OpenWDL explains](https://docs.openwdl.org/en/stable/WDL/basename/) how `basename()` works. The next section will provide an example of using it alongside private variables. ### Private variables -Is there a variable you wish to use in your task section that is based on another input variable, or do not want people using your workflow to be able to directly overwrite? You can define variables outside the `input {}` section to create variables that function like private variables. In our case, we create `String ref_fasta_local` as `ref_fasta`'s file base name to refer to the files we have moved to the working directory. We also create `String base_file_name` as `input_fastq`'s file base name and use it to name our output files, such as `"~{base_file_name}.sorted_query_aligned.bam"`. +Is there a variable you wish to use in your task section that is based on another input variable, or do not want people using your workflow to be able to directly overwrite? You can define variables outside the `input {}` section to create variables that function like *private variables*. In our case, we create `String ref_fasta_local` as `ref_fasta`'s file base name to refer to the files we have moved to the working directory. We also create `String base_file_name` as `input_fastq`'s file base name and use it to name our output files, such as `"~{base_file_name}.sorted_query_aligned.bam"`. -``` +

 task BwaMem {
   input {
     File input_fastq
@@ -229,7 +228,7 @@ task BwaMem {
     Int threads = 16
   }
   
-  # basename() is a built-in WDL function that acts like bash's basename
+  # basename() is a built-in WDL function that acts like bash's basename
   String base_file_name = basename(input_fastq, ".fastq")
   String ref_fasta_local = basename(ref_fasta)
   String read_group_id = "ID:" + base_file_name
@@ -258,10 +257,10 @@ task BwaMem {
 
   >>>
 }
-```
+
## Runtime attributes -The runtime attributes of a task tell the WDL executor important information about how to run the task. For a bwa mem task, we want to make sure we have plenty of hardware resources available. We also need to include a reference to the docker image we want the task to actually run in. +The runtime attributes of a task tell the WDL executor important information about how to run the task. For a BWA MEM task, we want to make sure we have plenty of hardware resources available. We also need to include a reference to the docker image we want the task to actually run in. ``` runtime { @@ -284,16 +283,16 @@ This can lead to some pitfalls: * The same runtime attribute working differently on different backends, such as `disks` acting differently on Cromwell depending on whether it is running on AWS or GCP -When writing WDL 1.0 workflows with specific hardware requirements, keep in mind what your backend and executor is able to interpret. It is also helpful to consider that other people running your workflow may be doing so on different backends and executors. More information can be found in the appendix, where we talk about designing WDLs for specific backends. For now, we will stick with `memory`, `cpu`, `docker`, and `disks` as this group of four runtime attributes will help us run this workflow on the majority of backends and executors. Even though the Fred Hutch HPC will ignore the `memory` and `disks` attributes, for instance, their inclusion will not cause the workflow to fail, but they will allow the workflow to run on Terra. +When writing WDL 1.0 workflows with specific hardware requirements, keep in mind what your backend and executor is able to interpret. It is also helpful to consider that other people running your workflow may be doing so on different backends and executors. More information can be found in the [Appendix](https://hutchdatascience.org/WDL_Workflows_Guide/appendix-backends-and-executors.html), where we talk about designing WDLs for specific backends. For now, we will stick with `memory`, `cpu`, `docker`, and `disks` as this group of four runtime attributes will help us run this workflow on the majority of backends and executors. Even though the Fred Hutch HPC will ignore the `memory` and `disks` attributes, for instance, their inclusion will not cause the workflow to fail, but they will allow the workflow to run on Terra.
-Some differences between WDL 1.0 and 1.1 on Runtime attributes. +Some differences between WDL 1.0 and 1.1 on Runtime attributes Although the focus of this course is on WDL 1.0, it is worth noting that in the [WDL 1.1 spec](https://github.com/openwdl/wdl/blob/main/versions/1.1/SPEC.md#runtime-section), a very different approach to runtime attributes is taken: > There are a set of reserved attributes (described below) that must be supported by the execution engine, and which have well-defined meanings and default values. Default values for all optional standard attributes are directly defined by the WDL specification in order to encourage portability of workflows and tasks; execution engines should NOT provide additional mechanisms to set default values for when no runtime attributes are defined. -If you are writing WDLs under the WDL 1.1 standard, you may have more flexibility with runtime attributes. Be aware that as of February 2024, Cromwell does not support WDL 1.1. +If you are writing WDLs under the WDL 1.1 standard, you may have more flexibility with runtime attributes. **_Beware_** that as of February 2024, Cromwell does not support WDL 1.1.
@@ -308,12 +307,12 @@ WDL is built to make use of Docker as it makes handling software dependencies mu * You may not have permission to install software if you are using an institute HPC or other shared resource -When you run a WDL task that has a `docker` runtime attribute, your task will be executed in a Docker container sandbox environment. This container sandbox is derived from a template called a Docker image, which packages installed software in a special file system. This is one of the main features of a Docker image -- because a Docker image packages the software you need, you can skip much of the installation and dependency issues associated with using new software, and because you take actions within a Docker container sandbox, it's unlikely for you to "mess up" your main system's files. Although a Docker container is, strictly speaking, not the same as a virtual machine, it is helpful to think of it as one if you are new to Docker. Docker containers are managed by Docker Engine, and the official Docker GUI is called Docker Desktop. +When you run a WDL task that has a `docker` runtime attribute, your task will be executed in a Docker container sandbox environment. This container sandbox is derived from a template called a *Docker image*, which packages installed software in a special file system. This is one of the main features of a Docker image -- because a Docker image packages the software you need, you can skip much of the installation and dependency issues associated with using new software, and because you take actions within a Docker container sandbox, it's unlikely for you to "mess up" your main system's files. Although a Docker container is, strictly speaking, not the same as a virtual machine, it is helpful to think of it as one if you are new to Docker. Docker containers are managed by Docker Engine, and the official Docker GUI is called Docker Desktop.
-More information on finding and developing Docker images. +More information on finding and developing Docker images -Although you will generally need to be able to run Docker in order to run WDLs, you do not need to know how to create Dockerfiles -- plaintext files which compile Docker images when run via `docker build` -- to write your own WDLs. Most popular bioinformatic software packages already have ready-to-use Docker images available, which you can typically find on [Docker Hub](https://hub.docker.com/search?q=). Other registries include quay.io and the Google Container Registry. With that being said, if you would like to create your own Docker images, there are many tutorials and [guidelines](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/) available online. You can also learn more about the details of Docker (and why they technically aren't virtual machines) in [Docker's official curriculum](https://docker-curriculum.com/#introduction). +Although you will generally need to be able to run Docker in order to run WDLs, you do not need to know how to create Dockerfiles -- plain text files which compile Docker images when run via `docker build` -- to write your own WDLs. Most popular bioinformatic software packages already have ready-to-use Docker images available, which you can typically find on [Docker Hub](https://hub.docker.com/search?q=). Other registries include quay.io and the Google Container Registry. With that being said, if you would like to create your own Docker images, there are many tutorials and [guidelines](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/) available online. You can also learn more about the details of Docker (and why they technically aren't virtual machines) in [Docker's official curriculum](https://docker-curriculum.com/#introduction).
@@ -322,7 +321,7 @@ Although you will generally need to be able to run Docker in order to run WDLs, ## Outputs -The outputs of a task are defined in the `output` section of your task. Typically, this will take the form of directly outputting a file that was created in the command section. When these file outputs are referenced in the `output` section, you can refer to their path in the Docker container directly. You can also make outputs a function of input variables, including private input variables. This can be helpful if you intend on running this WDL on many different files -- each one will get a unique filename based on the input fastq, instead of every sample ending up being named something generic like "converted.sam". For our bwa mem task, one way to write the output section would be as follows: +The outputs of a task are defined in the `output` section of your task. Typically, this will take the form of directly outputting a file that was created in the command section. When these file outputs are referenced in the `output` section, you can refer to their path in the Docker container directly. You can also make output a function of input variables, including private input variables. This can be helpful if you intend on running this WDL on many different files -- each one will get a unique filename based on the input FASTQ, instead of every sample ending up being named something generic like "converted.sam". For our BWA MEM task, one way to write the output section would be as follows: ``` output { @@ -343,7 +342,7 @@ If the output was not in the working directory, we would need to change the outp Below are some some additional ways you can handle task outputs.
-Ouputs as functions of other outputs in the same task. +Ouputs as functions of other outputs in the same task Outputs can (generally, see warning below) also be functions of other outputs in the same task, as long as those outputs are declared first. @@ -374,7 +373,7 @@ Cromwell does not fully support outputs being a function of the same task's othe
Grabbing multiple outputs at the same time -To grab multiple outputs at the same time, use glob() to create an array of files. We'll also take this opportunity to demonstrate iterating through a bash array created from an Array[String] input -- for more information on this data type, see chapter six of this course. +To grab multiple outputs at the same time, use `glob()` to create an array of files. We'll also take this opportunity to demonstrate iterating through a bash array created from an Array[String] input -- for more information on this data type, see chapter six of this course. ``` task one_word_per_file { @@ -403,7 +402,7 @@ task one_word_per_file { ## The whole task -We've now designed a bwa mem task that can run on essentially any backend that supports WDL and can handle the hardware requirements. Issues involving bwa mem expecting reference files to be in the same folder and/or putting output files into input folders have been sidestepped thanks to careful design and consideration. The runtime section clearly defines the expected hardware requirements, and the outputs section defines what we expect the task to give us when all is said and done. We're now ready to continue with the rest of our workflow. +We've now designed a BWA MEM task that can run on essentially any backend that supports WDL and can handle the hardware requirements. Issues involving BWA MEM expecting reference files to be in the same folder and/or putting output files into input folders have been sidestepped thanks to careful design and consideration. The runtime section clearly defines the expected hardware requirements, and the outputs section defines what we expect the task to give us when all is said and done. We're now ready to continue with the rest of our workflow. ``` task BwaMem { diff --git a/04-linear-chain.Rmd b/04-linear-chain.Rmd index 7f8065a..370e1bb 100644 --- a/04-linear-chain.Rmd +++ b/04-linear-chain.Rmd @@ -4,13 +4,13 @@ ottrpal::set_knitr_image_path() # Connecting multiple tasks together in a linear chain -Now that you have a first task in a workflow up and running, the next step is to continue building out the workflow as described in our Workflow Plan: +Now that you have a first task in a workflow up and running, the next step is to continue building out the workflow as described in our Workflow Plan: -1. `BwaMem` aligns the samples to the reference genome. -2. `MarkDuplicates` marks PCR duplicates. -3. `ApplyBaseRecalibrator` applies base recalibration. -4. `Mutect2` performs somatic mutation calling. For this current iteration, we start with `Mutect2TumorOnly` which only uses the tumor sample. -5. `annovar` annotates the called somatic mutations. +1. `BwaMem` aligns the samples to the reference genome +2. `MarkDuplicates` marks PCR duplicates +3. `ApplyBaseRecalibrator` applies base recalibration +4. `Mutect2` performs somatic mutation calling. For this current iteration, we start with `Mutect2TumorOnly` which only uses the tumor sample +5. `annovar` annotates the called somatic mutations We do this via a **linear chain**, in which we feed the output of one task to the input of the next task. Let's see how to build a linear chain in a workflow. @@ -27,7 +27,7 @@ output { } ``` -The File variables `analysisReadyBam` and `analysisReadySorted` can now be accessed anywhere in the workflow block after the `BwaMem` task as `BwaMem.analysisReadyBam` and `BwaMem.analysisReadySorted`, respectively. Therefore, when we call the `MarkDuplicates` task, we can pass it the input `BwaMem.analysisReadySorted` from the `BwaMem` task: +The File variables `analysisReadyBam` and `analysisReadySorted` can now be accessed anywhere in the workflow block after the `BwaMem` task as `BwaMem.analysisReadyBam` and `BwaMem.analysisReadySorted` respectively. Therefore, when we call the `MarkDuplicates` task, we can pass it the input `BwaMem.analysisReadySorted` from the `BwaMem` task: ``` @@ -88,7 +88,7 @@ Of course, the task `MarkDuplicates` hasn't been written yet! Let's go through i ### Input -The task takes an input bam file that has been aligned to the reference genome. It needs to be a File `input_bam` based on how we introduced it in the workflow above. That is easy to write up: +The task takes an input BAM file that has been aligned to the reference genome. It needs to be a File `input_bam` based on how we introduced it in the workflow above. That is easy to write up: ``` task MarkDuplicates { @@ -137,7 +137,7 @@ task MarkDuplicates { ### Runtime and Output -We specify a different Docker image that contains the GATK software, and the relevant computing needs. We also specify three different output files, two of which are specified in the command section, and the third is a bam index file that is automatically created by the command section. +We specify a different Docker image that contains the GATK software, and the relevant computing needs. We also specify three different output files, two of which are specified in the command section, and the third is a BAM index file that is automatically created by the command section. Below is the task all together. It has a form very similar to our first task `BwaMem`. diff --git a/06-arrays.Rmd b/06-arrays.Rmd index 50a65a9..fb63026 100644 --- a/06-arrays.Rmd +++ b/06-arrays.Rmd @@ -46,8 +46,7 @@ When we originally wrote our workflow, we designed it to only run on a single sa For starters, we'll want to change our workflow-level sample variables from File to Array[File]. However, we don't need to change any of the reference genome files, because every instance of our tasks will be taking in the same reference genome files. In other words, our struct is unchanged. -``` -version 1.0 +
version 1.0
 
 struct referenceGenome {
     File ref_fasta
@@ -64,7 +63,7 @@ struct referenceGenome {
 
 workflow minidata_mutation_calling_chapter6 {
   input {
-    Array[File] tumorSamples
+    Array[File] tumorSamples
 
     referenceGenome refGenome
     
@@ -104,7 +103,7 @@ Next, we will want to look at our chain of tasks. Each of these tasks are design
         refGenome = refGenome
     }
   }
-```
+
A scatter is essentially the WDL version of a [for loop](https://en.wikipedia.org/wiki/For_loop). Every task within that loop will have access to a single File within the Array[File] that it is looping through. Within the scatter, downstream tasks can access outputs of upstream tasks like normal. They can only "see" one file at a time. However, outside the scatter, every task is considered in the context of every sample, so every output of those scattered tasks become arrays. As a result, our outputs are now Array[File] instead of just File. diff --git a/07-task-aliasing.Rmd b/07-task-aliasing.Rmd index f98ce38..d394871 100644 --- a/07-task-aliasing.Rmd +++ b/07-task-aliasing.Rmd @@ -19,7 +19,7 @@ The major advantages of using task aliasing are: **3. Enhances Readability and Clarity**: A shorter workflow is easier to read but task aliasing also helps to contextualize the workflow ( for example are you doing this task for Sample set A or Sample set B) -**4. Facilitates Modular Workflow Design**: Task aliasing help to make your workflow modular. This is easier to adopt by +**4. Facilitates Modular Workflow Design**: Task aliasing help to make your workflow modular. This is easier to adopt. **5. Improves Workflow Scalability**: Using task aliasing it is much easier to scale the workflow across different inputs. For example you want to run a task on different sample groups (Sample set A and B) will allow the same task to be run parallely and if you choose with different modifications. @@ -94,9 +94,7 @@ call MarkDuplicates as normalMarkDuplicates { Now adding these steps to the workflow we will have our tumor and normal sample aligned and recalibrated and suitable for ingestion into the mutation calling step for a paired mutation calling using MuTect2. -``` - -# The full workflow with task alias +
# The full workflow with task alias
 
 version 1.0
 ## WDL 101 example workflow
@@ -195,18 +193,18 @@ workflow mutation_calling {
 }
   
   # Do for normal sample
-  call BwaMem as normalBwaMem {
+  call BwaMem as normalBwaMem {
     input:
       input_fastq = normalSamples,
       refGenome = refGenome
   }
   
-  call MarkDuplicates as normalMarkDuplicates {
+  call MarkDuplicates as normalMarkDuplicates {
     input:
       input_bam = normalBwaMem.analysisReadySorted
   }
 
-  call ApplyBaseRecalibrator as normalApplyBaseRecalibrator {
+  call ApplyBaseRecalibrator as normalApplyBaseRecalibrator {
     input:
       input_bam = normalMarkDuplicates.markDuplicates_bam,
       input_bam_index = normalMarkDuplicates.markDuplicates_bai,
@@ -472,5 +470,5 @@ task annovar {
     File output_annotated_table = "${base_vcf_name}.${ref_name}_multianno.txt"
   }
 }
-```
+
diff --git a/docs/404.html b/docs/404.html index d9f1400..14f2d32 100644 --- a/docs/404.html +++ b/docs/404.html @@ -6,12 +6,11 @@ Page not found | Developing WDL Workflows - + - @@ -31,7 +30,6 @@ - @@ -49,11 +47,16 @@ - - + + + + diff --git a/docs/introduction-to-wdl.html b/docs/introduction-to-wdl.html index e9ad1ab..b7f52ee 100644 --- a/docs/introduction-to-wdl.html +++ b/docs/introduction-to-wdl.html @@ -6,12 +6,11 @@ Chapter 1 Introduction to WDL | Developing WDL Workflows - + - @@ -31,7 +30,6 @@ - @@ -49,11 +47,16 @@ - - + + + + @@ -208,15 +211,15 @@

-
-

Chapter 1 Introduction to WDL

-
-

1.1 Introduction

+
+

Chapter 1 Introduction to WDL

+
+

1.1 Introduction

Welcome to building your first WDL workflow! This guide will help you strategically develop and scale up a WDL workflow that is iterative, reproducible, and efficient in terms of time and resource used.

To make sure that we are on the same page, this guide assumes that you are able to run a WDL on a computing engine of your choice, such as Cromwell, miniwdl, or a cloud computing environment such as Terra, AnVIL, or Dockstore. This guide also assumes that you have a beginner’s understanding of the WDL syntax, and we will link out to additional resources to fill in the knowledge gap as needed! If you have never seen the WDL language in action, a great place to start is OpenWDL docs – it teaches you the basic syntax and showcases WDL features via concrete examples.

-
-

1.2 Review of basic WDL syntax

+
+

1.2 Review of basic WDL syntax

We will do some review of the WDL syntax. A WDL workflow consists of at least one task.

version 1.0
@@ -317,8 +320,8 @@ 

1.2 Review of basic WDL syntax

-
-

1.3 Using JSONs to control workflow inputs

+
+

1.3 Using JSONs to control workflow inputs

Running a WDL workflow generally requires two files: A .wdl file, which contains the actual workflow, and a .json file, which provides the inputs for the workflow.

In the example we showed earlier, the workflow takes in a file referred to by the variable fq. This needs to be provided by the user. Typically, this is done with a JSON file. Here’s what a JSON file for this workflow might look like:

@@ -351,8 +354,8 @@

1.3 Using JSONs to control workfl

-
-

1.4 Running WDL via a computing engine

+
+

1.4 Running WDL via a computing engine

In order to run a WDL workflow, we need a computing engine to execute it. The two most popular WDL executors are miniwdl and Cromwell. Both can run WDLs on a local machine, High Performance Computing (HPC), or cloud computing backend. If you are trying to run WDL at Fred Hutch Cancer Center’s HPC system, you should use the PROOF executor.

If you are computing on a HPC or the Cloud, you should find the best practice of running a WDL computing engine based on your institution’s information technology system.

If you are computing locally on your computer, below is a short guide on how to set that up.

@@ -363,19 +366,19 @@

1.4 Running WDL via a computing e

Not every WDL workflow will run well on a laptop, but it can be helpful to have a basic setup for testing and catching simple syntax errors. Let’s quickly set up a WDL executor to run our WDLs.

In this course, we will be using miniwdl, but everything in this course will also be compatible with Cromwell unless explicitly stated otherwise. Additionally, almost all WDLs use Docker images, so you will also need to install Docker or a Docker-like alternative.

Installing Docker and miniwdl is not required to use this course. We don’t want anybody to get stuck here! If you already have a method for submitting workflows, such as Terra, feel free to use that for this course instead of running workflows directly on your local machine. If you don’t have any way of running workflows at the moment, that’s also okay – we have provided plenty of examples for following along.

-
-

1.4.1 Installing Docker

+
+

1.4.1 Installing Docker

Note: Although Docker’s own docs recommend installing Docker Desktop for Linux, it has been reported that some WDL executors work better on Linux when installing only Docker Engine (aka Docker CE). To install Docker on your machine, follow the instructions specific to your operating system on Docker’s website. To specifically install only Docker Engine, use these instructions instead.

If you are unable to install Docker on your machine, Dockstore (not affiliated with Docker) provides some experimental alternatives. Dockstore also provides a comprehensive introduction to Docker itself, including how to write a Dockerfile. Much of that information is outside the scope of this WDL-focused course, but it may be helpful for those looking to eventually create their own Docker images.

-
-

1.4.2 Installing miniwdl

+
+

1.4.2 Installing miniwdl

miniwdl is based on Python. If you do not already have Python 3.6 or higher installed, you can install Python from here.

Once Python is installed on your system, you can run pip3 install miniwdl from the command line to install miniwdl. For those who prefer to use conda, use conda install -c conda-forge miniwdl instead. Once miniwdl is installed, you can verify it works properly by running miniwdl run_self_test. This will run a built-in hello world workflow.

For more information, see miniwdl’s GitHub repository.

-
-

1.4.3 Launching a workflow locally with miniwdl

+
+

1.4.3 Launching a workflow locally with miniwdl

The generic method for running a WDL with miniwdl is the following:

miniwdl run [path_to_wdl_file] -i [path_to_inputs_json]

If you have successfully installed miniwdl, create the following WDL file and name it greetings.wdl:

@@ -427,16 +430,16 @@

1.4.3 Launching a workflow locall } }

-
-

1.4.4 Troubleshooting

-
-

1.4.4.1 DockerException

+
+

1.4.4 Troubleshooting

+
+

1.4.4.1 DockerException

If you are seeing a verbose error message that begins with text like this:

2023-12-27 13:43:37.525 wdl.w:my_workflow.t:call-greet task greet (greetings.wdl Ln 3 Col 1) failed :: dir: "/Users/sammy/github/WDL_Workflows_Guide/resources/20231227_134337_my_workflow/call-greet", error: "DockerException", message: "Error while fetching server API version: ('Connection aborted.', FileNotFoundError(2, 'No such file or directory'))", traceback: ["Traceback (most recent call last):", "  File \"/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/urllib3/connectionpool.py\", line 790, in urlopen", "    response = self._make_request(", "               ^^^^^^^^^^^^^^^^^^^", "  File \"/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/urllib3/connectionpool.py\",

This is likely caused by miniwdl being unable to connect to Docker Daemon, the underlying technology that runs Docker images. This is necessary with miniwdl even though our example WDL does not specify a Docker image. Make sure you have Docker installed correctly, and make sure Docker is actively running on your machine. If you installed Docker Desktop, simply opening the Docker Desktop app should start Docker Engine. If you installed Docker without Docker Desktop, running dockerd in your command-line should start it. Be aware that starting the Docker Daemon may take a few minutes.

-
-

1.4.4.2 Missing required inputs

+
+

1.4.4.2 Missing required inputs

If you forget to add -i greetings.json to your call, you will see something like this:

my_workflow (greetings.wdl)
 ---------------------------
@@ -450,8 +453,8 @@ 

1.4.4.2 Missing required inputs

You may also see this error if you remember to include a JSON file, but it is missing a required input.

-
-

1.4.4.3 Check JSON input

+
+

1.4.4.3 Check JSON input

If you see an error message like this:

check JSON input; unknown input/output: greetings.username

Double-check your input JSON. The first part of your JSON’s keys refer to the name of the workflow in the WDL file, not the filename of the WDL itself. Even though our WDL is saved as greetings.wdl, within that file, the workflow is named my_workflow. This means that the input JSON must say "my_workflow.username", not "greetings.username".

diff --git a/docs/libs/gitbook-2.6.7/css/style.css b/docs/libs/gitbook-2.6.7/css/style.css index 1b0c622..cba69b2 100644 --- a/docs/libs/gitbook-2.6.7/css/style.css +++ b/docs/libs/gitbook-2.6.7/css/style.css @@ -1,15 +1,13 @@ -/*! normalize.css v2.1.0 | MIT License | git.io/normalize */img,legend{border:0}*,.fa{-webkit-font-smoothing:antialiased}.fa-ul>li,sub,sup{position:relative}.book .book-body .page-wrapper .page-inner section.normal hr:after,.book-langs-index .inner .languages:after,.buttons:after,.dropdown-menu .buttons:after{clear:both}body,html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}.hidden,[hidden]{display:none}audio:not([controls]){display:none;height:0}html{font-family:sans-serif}body,figure{margin:0}a:focus{outline:dotted thin}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em}pre{white-space:pre-wrap}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}svg:not(:root){overflow:hidden}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{padding:0}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button{margin-right:10px;}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}/*! +/*! normalize.css v2.1.0 | MIT License | git.io/normalize */img,legend{border:0}*{-webkit-font-smoothing:antialiased}sub,sup{position:relative}.book .book-body .page-wrapper .page-inner section.normal hr:after,.book-langs-index .inner .languages:after,.buttons:after,.dropdown-menu .buttons:after{clear:both}body,html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}.hidden,[hidden]{display:none}audio:not([controls]){display:none;height:0}html{font-family:sans-serif}body,figure{margin:0}a:focus{outline:dotted thin}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em}pre{white-space:pre-wrap}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}svg:not(:root){overflow:hidden}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{padding:0}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button{margin-right:10px;}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}/*! * Preboot v2 * * Open sourced under MIT license by @mdo. * Some variables and mixins from Bootstrap (Apache 2 license). - */.link-inherit,.link-inherit:focus,.link-inherit:hover{color:inherit}.fa,.fa-stack{display:inline-block}/*! + */.link-inherit,.link-inherit:focus,.link-inherit:hover{color:inherit}/*! * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:FontAwesome;src:url(./fontawesome/fontawesome-webfont.ttf?v=4.7.0) format('truetype');font-weight:400;font-style:normal}.fa{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1;-moz-osx-font-smoothing:grayscale}.book .book-header,.book .book-summary{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0)}100%{-o-transform:rotate(359deg)}}@keyframes spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1,1);-moz-transform:scale(-1,1);-ms-transform:scale(-1,1);-o-transform:scale(-1,1);transform:scale(-1,1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1,-1);-moz-transform:scale(1,-1);-ms-transform:scale(1,-1);-o-transform:scale(1,-1);transform:scale(1,-1)}.fa-stack{position:relative;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-cog:before,.fa-gear:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-repeat:before,.fa-rotate-right:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-exclamation-triangle:before,.fa-warning:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-cogs:before,.fa-gears:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-floppy-o:before,.fa-save:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-sort:before,.fa-unsorted:before{content:"\f0dc"}.fa-sort-desc:before,.fa-sort-down:before{content:"\f0dd"}.fa-sort-asc:before,.fa-sort-up:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-gavel:before,.fa-legal:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-bolt:before,.fa-flash:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-clipboard:before,.fa-paste:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-chain-broken:before,.fa-unlink:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:"\f150"}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:"\f151"}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:"\f152"}.fa-eur:before,.fa-euro:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-inr:before,.fa-rupee:before{content:"\f156"}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:"\f157"}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:"\f158"}.fa-krw:before,.fa-won:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-try:before,.fa-turkish-lira:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-bank:before,.fa-institution:before,.fa-university:before{content:"\f19c"}.fa-graduation-cap:before,.fa-mortar-board:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-square:before,.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:"\f1c5"}.fa-file-archive-o:before,.fa-file-zip-o:before{content:"\f1c6"}.fa-file-audio-o:before,.fa-file-sound-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-empire:before,.fa-ge:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-paper-plane:before,.fa-send:before{content:"\f1d8"}.fa-paper-plane-o:before,.fa-send-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.book-langs-index{width:100%;height:100%;padding:40px 0;margin:0;overflow:auto}@media (max-width:600px){.book-langs-index{padding:0}}.book-langs-index .inner{max-width:600px;width:100%;margin:0 auto;padding:30px;background:#fff;border-radius:3px}.book-langs-index .inner h3{margin:0}.book-langs-index .inner .languages{list-style:none;padding:20px 30px;margin-top:20px;border-top:1px solid #eee}.book-langs-index .inner .languages:after,.book-langs-index .inner .languages:before{content:" ";display:table;line-height:0}.book-langs-index .inner .languages li{width:50%;float:left;padding:10px 5px;font-size:16px}@media (max-width:600px){.book-langs-index .inner .languages li{width:100%;max-width:100%}}.book .book-header{overflow:visible;height:50px;padding:0 8px;z-index:2;font-size:.85em;color:#7e888b;background:0 0}.book .book-header .btn{display:block;height:50px;padding:0 15px;border-bottom:none;color:#ccc;text-transform:uppercase;line-height:50px;-webkit-box-shadow:none!important;box-shadow:none!important;position:relative;font-size:14px}.book .book-header .btn:hover{position:relative;text-decoration:none;color:#444;background:0 0}.book .book-header h1{margin:0;font-size:20px;font-weight:200;text-align:center;line-height:50px;opacity:0;padding-left:200px;padding-right:200px;-webkit-transition:opacity .2s ease;-moz-transition:opacity .2s ease;-o-transition:opacity .2s ease;transition:opacity .2s ease;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.book .book-header h1 a,.book .book-header h1 a:hover{color:inherit;text-decoration:none}@media (max-width:1000px){.book .book-header h1{display:none}}.book .book-header h1 i{display:none}.book .book-header:hover h1{opacity:1}.book.is-loading .book-header h1 i{display:inline-block}.book.is-loading .book-header h1 a{display:none}.dropdown{position:relative}.dropdown-menu{position:absolute;top:100%;left:0;z-index:100;display:none;float:left;min-width:160px;padding:0;margin:2px 0 0;list-style:none;font-size:14px;background-color:#fafafa;border:1px solid rgba(0,0,0,.07);border-radius:1px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175);background-clip:padding-box}.dropdown-menu.open{display:block}.dropdown-menu.dropdown-left{left:auto;right:4%}.dropdown-menu.dropdown-left .dropdown-caret{right:14px;left:auto}.dropdown-menu .dropdown-caret{position:absolute;top:-8px;left:14px;width:18px;height:10px;float:left;overflow:hidden}.dropdown-menu .dropdown-caret .caret-inner,.dropdown-menu .dropdown-caret .caret-outer{display:inline-block;top:0;border-left:9px solid transparent;border-right:9px solid transparent;position:absolute}.dropdown-menu .dropdown-caret .caret-outer{border-bottom:9px solid rgba(0,0,0,.1);height:auto;left:0;width:auto;margin-left:-1px}.dropdown-menu .dropdown-caret .caret-inner{margin-top:-1px;top:1px;border-bottom:9px solid #fafafa}.dropdown-menu .buttons{border-bottom:1px solid rgba(0,0,0,.07)}.dropdown-menu .buttons:after,.dropdown-menu .buttons:before{content:" ";display:table;line-height:0}.dropdown-menu .buttons:last-child{border-bottom:none}.dropdown-menu .buttons .button{border:0;background-color:transparent;color:#a6a6a6;width:100%;text-align:center;float:left;line-height:1.42857143;padding:8px 4px}.alert,.dropdown-menu .buttons .button:hover{color:#444}.dropdown-menu .buttons .button:focus,.dropdown-menu .buttons .button:hover{outline:0}.dropdown-menu .buttons .button.size-2{width:50%}.dropdown-menu .buttons .button.size-3{width:33%}.alert{padding:15px;margin-bottom:20px;background:#eee;border-bottom:5px solid #ddd}.alert-success{background:#dff0d8;border-color:#d6e9c6;color:#3c763d}.alert-info{background:#d9edf7;border-color:#bce8f1;color:#31708f}.alert-danger{background:#f2dede;border-color:#ebccd1;color:#a94442}.alert-warning{background:#fcf8e3;border-color:#faebcc;color:#8a6d3b}.book .book-summary{position:absolute;top:0;left:-300px;bottom:0;z-index:1;width:300px;color:#364149;background:#fafafa;border-right:1px solid rgba(0,0,0,.07);-webkit-transition:left 250ms ease;-moz-transition:left 250ms ease;-o-transition:left 250ms ease;transition:left 250ms ease}.book .book-summary ul.summary{position:absolute;top:0;left:0;right:0;bottom:0;overflow-y:auto;list-style:none;margin:0;padding:0;-webkit-transition:top .5s ease;-moz-transition:top .5s ease;-o-transition:top .5s ease;transition:top .5s ease}.book .book-summary ul.summary li{list-style:none}.book .book-summary ul.summary li.divider{height:1px;margin:7px 0;overflow:hidden;background:rgba(0,0,0,.07)}.book .book-summary ul.summary li i.fa-check{display:none;position:absolute;right:9px;top:16px;font-size:9px;color:#3c3}.book .book-summary ul.summary li.done>a{color:#364149;font-weight:400}.book .book-summary ul.summary li.done>a i{display:inline}.book .book-summary ul.summary li a,.book .book-summary ul.summary li span{display:block;padding:10px 15px;border-bottom:none;color:#364149;background:0 0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;position:relative}.book .book-summary ul.summary li span{cursor:not-allowed;opacity:.3;filter:alpha(opacity=30)}.book .book-summary ul.summary li a:hover,.book .book-summary ul.summary li.active>a{color:#008cff;background:0 0;text-decoration:none}.book .book-summary ul.summary li ul{padding-left:20px}@media (max-width:600px){.book .book-summary{width:calc(100% - 60px);bottom:0;left:-100%}}.book.with-summary .book-summary{left:0}.book.without-animation .book-summary{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;transition:none!important}.book{position:relative;width:100%;height:100%}.book .book-body,.book .book-body .body-inner{position:absolute;top:0;left:0;overflow-y:auto;bottom:0;right:0}.book .book-body{color:#000;background:#fff;-webkit-transition:left 250ms ease;-moz-transition:left 250ms ease;-o-transition:left 250ms ease;transition:left 250ms ease}.book .book-body .page-wrapper{position:relative;outline:0}.book .book-body .page-wrapper .page-inner{max-width:800px;margin:0 auto;padding:20px 0 40px}.book .book-body .page-wrapper .page-inner section{margin:0;padding:5px 15px;background:#fff;border-radius:2px;line-height:1.7;font-size:1.6rem}.book .book-body .page-wrapper .page-inner .btn-group .btn{border-radius:0;background:#eee;border:0}@media (max-width:1240px){.book .book-body{-webkit-transition:-webkit-transform 250ms ease;-moz-transition:-moz-transform 250ms ease;-o-transition:-o-transform 250ms ease;transition:transform 250ms ease;padding-bottom:20px}.book .book-body .body-inner{position:static;min-height:calc(100% - 50px)}}@media (min-width:600px){.book.with-summary .book-body{left:300px}}@media (max-width:600px){.book.with-summary{overflow:hidden}.book.with-summary .book-body{-webkit-transform:translate(calc(100% - 60px),0);-moz-transform:translate(calc(100% - 60px),0);-ms-transform:translate(calc(100% - 60px),0);-o-transform:translate(calc(100% - 60px),0);transform:translate(calc(100% - 60px),0)}}.book.without-animation .book-body{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;transition:none!important}.buttons:after,.buttons:before{content:" ";display:table;line-height:0}.button{border:0;background:#eee;color:#666;width:100%;text-align:center;float:left;line-height:1.42857143;padding:8px 4px}.button:hover{color:#444}.button:focus,.button:hover{outline:0}.button.size-2{width:50%}.button.size-3{width:33%}.book .book-body .page-wrapper .page-inner section{display:none}.book .book-body .page-wrapper .page-inner section.normal{display:block;word-wrap:break-word;overflow:hidden;color:#333;line-height:1.7;text-size-adjust:100%;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%}.book .book-body .page-wrapper .page-inner section.normal *{box-sizing:border-box;-webkit-box-sizing:border-box;}.book .book-body .page-wrapper .page-inner section.normal>:first-child{margin-top:0!important}.book .book-body .page-wrapper .page-inner section.normal>:last-child{margin-bottom:0!important}.book .book-body .page-wrapper .page-inner section.normal blockquote,.book .book-body .page-wrapper .page-inner section.normal code,.book .book-body .page-wrapper .page-inner section.normal figure,.book .book-body .page-wrapper .page-inner section.normal img,.book .book-body .page-wrapper .page-inner section.normal pre,.book .book-body .page-wrapper .page-inner section.normal table,.book .book-body .page-wrapper .page-inner section.normal tr{page-break-inside:avoid}.book .book-body .page-wrapper .page-inner section.normal h2,.book .book-body .page-wrapper .page-inner section.normal h3,.book .book-body .page-wrapper .page-inner section.normal h4,.book .book-body .page-wrapper .page-inner section.normal h5,.book .book-body .page-wrapper .page-inner section.normal p{orphans:3;widows:3}.book .book-body .page-wrapper .page-inner section.normal h1,.book .book-body .page-wrapper .page-inner section.normal h2,.book .book-body .page-wrapper .page-inner section.normal h3,.book .book-body .page-wrapper .page-inner section.normal h4,.book .book-body .page-wrapper .page-inner section.normal h5{page-break-after:avoid}.book .book-body .page-wrapper .page-inner section.normal b,.book .book-body .page-wrapper .page-inner section.normal strong{font-weight:700}.book .book-body .page-wrapper .page-inner section.normal em{font-style:italic}.book .book-body .page-wrapper .page-inner section.normal blockquote,.book .book-body .page-wrapper .page-inner section.normal dl,.book .book-body .page-wrapper .page-inner section.normal ol,.book .book-body .page-wrapper .page-inner section.normal p,.book .book-body .page-wrapper .page-inner section.normal table,.book .book-body .page-wrapper .page-inner section.normal ul{margin-top:0;margin-bottom:.85em}.book .book-body .page-wrapper .page-inner section.normal a{color:#4183c4;text-decoration:none;background:0 0}.book .book-body .page-wrapper .page-inner section.normal a:active,.book .book-body .page-wrapper .page-inner section.normal a:focus,.book .book-body .page-wrapper .page-inner section.normal a:hover{outline:0;text-decoration:underline}.book .book-body .page-wrapper .page-inner section.normal img{border:0;max-width:100%}.book .book-body .page-wrapper .page-inner section.normal hr{height:4px;padding:0;margin:1.7em 0;overflow:hidden;background-color:#e7e7e7;border:none}.book .book-body .page-wrapper .page-inner section.normal hr:after,.book .book-body .page-wrapper .page-inner section.normal hr:before{display:table;content:" "}.book .book-body .page-wrapper .page-inner section.normal h1,.book .book-body .page-wrapper .page-inner section.normal h2,.book .book-body .page-wrapper .page-inner section.normal h3,.book .book-body .page-wrapper .page-inner section.normal h4,.book .book-body .page-wrapper .page-inner section.normal h5,.book .book-body .page-wrapper .page-inner section.normal h6{margin-top:1.275em;margin-bottom:.85em;}.book .book-body .page-wrapper .page-inner section.normal h1{font-size:2em}.book .book-body .page-wrapper .page-inner section.normal h2{font-size:1.75em}.book .book-body .page-wrapper .page-inner section.normal h3{font-size:1.5em}.book .book-body .page-wrapper .page-inner section.normal h4{font-size:1.25em}.book .book-body .page-wrapper .page-inner section.normal h5{font-size:1em}.book .book-body .page-wrapper .page-inner section.normal h6{font-size:1em;color:#777}.book .book-body .page-wrapper .page-inner section.normal code,.book .book-body .page-wrapper .page-inner section.normal pre{font-family:Consolas,"Liberation Mono",Menlo,Courier,monospace;direction:ltr;border:none;color:inherit}.book .book-body .page-wrapper .page-inner section.normal pre{overflow:auto;word-wrap:normal;margin:0 0 1.275em;padding:.85em 1em;background:#f7f7f7}.book .book-body .page-wrapper .page-inner section.normal pre>code{display:inline;max-width:initial;padding:0;margin:0;overflow:initial;line-height:inherit;font-size:.85em;white-space:pre;background:0 0}.book .book-body .page-wrapper .page-inner section.normal pre>code:after,.book .book-body .page-wrapper .page-inner section.normal pre>code:before{content:normal}.book .book-body .page-wrapper .page-inner section.normal code{padding:.2em;margin:0;font-size:.85em;background-color:#f7f7f7}.book .book-body .page-wrapper .page-inner section.normal code:after,.book .book-body .page-wrapper .page-inner section.normal code:before{letter-spacing:-.2em;content:"\00a0"}.book .book-body .page-wrapper .page-inner section.normal ol,.book .book-body .page-wrapper .page-inner section.normal ul{padding:0 0 0 2em;margin:0 0 .85em}.book .book-body .page-wrapper .page-inner section.normal ol ol,.book .book-body .page-wrapper .page-inner section.normal ol ul,.book .book-body .page-wrapper .page-inner section.normal ul ol,.book .book-body .page-wrapper .page-inner section.normal ul ul{margin-top:0;margin-bottom:0}.book .book-body .page-wrapper .page-inner section.normal ol ol{list-style-type:lower-roman}.book .book-body .page-wrapper .page-inner section.normal blockquote{margin:0 0 .85em;padding:0 15px;opacity:0.75;border-left:4px solid #dcdcdc}.book .book-body .page-wrapper .page-inner section.normal blockquote:first-child{margin-top:0}.book .book-body .page-wrapper .page-inner section.normal blockquote:last-child{margin-bottom:0}.book .book-body .page-wrapper .page-inner section.normal dl{padding:0}.book .book-body .page-wrapper .page-inner section.normal dl dt{padding:0;margin-top:.85em;font-style:italic;font-weight:700}.book .book-body .page-wrapper .page-inner section.normal dl dd{padding:0 .85em;margin-bottom:.85em}.book .book-body .page-wrapper .page-inner section.normal dd{margin-left:0}.book .book-body .page-wrapper .page-inner section.normal .glossary-term{cursor:help;text-decoration:underline}.book .book-body .navigation{position:absolute;top:50px;bottom:0;margin:0;max-width:150px;min-width:90px;display:flex;justify-content:center;align-content:center;flex-direction:column;font-size:40px;color:#ccc;text-align:center;-webkit-transition:all 350ms ease;-moz-transition:all 350ms ease;-o-transition:all 350ms ease;transition:all 350ms ease}.book .book-body .navigation:hover{text-decoration:none;color:#444}.book .book-body .navigation.navigation-next{right:0}.book .book-body .navigation.navigation-prev{left:0}@media (max-width:1240px){.book .book-body .navigation{position:static;top:auto;max-width:50%;width:50%;display:inline-block;float:left}.book .book-body .navigation.navigation-unique{max-width:100%;width:100%}}.book .book-body .page-wrapper .page-inner section.glossary{margin-bottom:40px}.book .book-body .page-wrapper .page-inner section.glossary h2 a,.book .book-body .page-wrapper .page-inner section.glossary h2 a:hover{color:inherit;text-decoration:none}.book .book-body .page-wrapper .page-inner section.glossary .glossary-index{list-style:none;margin:0;padding:0}.book .book-body .page-wrapper .page-inner section.glossary .glossary-index li{display:inline;margin:0 8px;white-space:nowrap}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-overflow-scrolling:touch;-webkit-tap-highlight-color:transparent;-webkit-text-size-adjust:none;-webkit-touch-callout:none}a{text-decoration:none}body,html{height:100%}html{font-size:62.5%}body{text-rendering:optimizeLegibility;font-smoothing:antialiased;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;letter-spacing:.2px;text-size-adjust:100%} + */@font-face{font-family:'FontAwesome';src:url('./fontawesome/fontawesome-webfont.ttf?v=4.7.0') format('truetype');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} +.book .book-header,.book .book-summary{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}.book-langs-index{width:100%;height:100%;padding:40px 0;margin:0;overflow:auto}@media (max-width:600px){.book-langs-index{padding:0}}.book-langs-index .inner{max-width:600px;width:100%;margin:0 auto;padding:30px;background:#fff;border-radius:3px}.book-langs-index .inner h3{margin:0}.book-langs-index .inner .languages{list-style:none;padding:20px 30px;margin-top:20px;border-top:1px solid #eee}.book-langs-index .inner .languages:after,.book-langs-index .inner .languages:before{content:" ";display:table;line-height:0}.book-langs-index .inner .languages li{width:50%;float:left;padding:10px 5px;font-size:16px}@media (max-width:600px){.book-langs-index .inner .languages li{width:100%;max-width:100%}}.book .book-header{overflow:visible;height:50px;padding:0 8px;z-index:2;font-size:.85em;color:#7e888b;background:0 0}.book .book-header .btn{display:block;height:50px;padding:0 15px;border-bottom:none;color:#ccc;text-transform:uppercase;line-height:50px;-webkit-box-shadow:none!important;box-shadow:none!important;position:relative;font-size:14px}.book .book-header .btn:hover{position:relative;text-decoration:none;color:#444;background:0 0}.book .book-header h1{margin:0;font-size:20px;font-weight:200;text-align:center;line-height:50px;opacity:0;padding-left:200px;padding-right:200px;-webkit-transition:opacity .2s ease;-moz-transition:opacity .2s ease;-o-transition:opacity .2s ease;transition:opacity .2s ease;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.book .book-header h1 a,.book .book-header h1 a:hover{color:inherit;text-decoration:none}@media (max-width:1000px){.book .book-header h1{display:none}}.book .book-header h1 i{display:none}.book .book-header:hover h1{opacity:1}.book.is-loading .book-header h1 i{display:inline-block}.book.is-loading .book-header h1 a{display:none}.dropdown{position:relative}.dropdown-menu{position:absolute;top:100%;left:0;z-index:100;display:none;float:left;min-width:160px;padding:0;margin:2px 0 0;list-style:none;font-size:14px;background-color:#fafafa;border:1px solid rgba(0,0,0,.07);border-radius:1px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175);background-clip:padding-box}.dropdown-menu.open{display:block}.dropdown-menu.dropdown-left{left:auto;right:4%}.dropdown-menu.dropdown-left .dropdown-caret{right:14px;left:auto}.dropdown-menu .dropdown-caret{position:absolute;top:-8px;left:14px;width:18px;height:10px;float:left;overflow:hidden}.dropdown-menu .dropdown-caret .caret-inner,.dropdown-menu .dropdown-caret .caret-outer{display:inline-block;top:0;border-left:9px solid transparent;border-right:9px solid transparent;position:absolute}.dropdown-menu .dropdown-caret .caret-outer{border-bottom:9px solid rgba(0,0,0,.1);height:auto;left:0;width:auto;margin-left:-1px}.dropdown-menu .dropdown-caret .caret-inner{margin-top:-1px;top:1px;border-bottom:9px solid #fafafa}.dropdown-menu .buttons{border-bottom:1px solid rgba(0,0,0,.07)}.dropdown-menu .buttons:after,.dropdown-menu .buttons:before{content:" ";display:table;line-height:0}.dropdown-menu .buttons:last-child{border-bottom:none}.dropdown-menu .buttons .button{border:0;background-color:transparent;color:#a6a6a6;width:100%;text-align:center;float:left;line-height:1.42857143;padding:8px 4px}.alert,.dropdown-menu .buttons .button:hover{color:#444}.dropdown-menu .buttons .button:focus,.dropdown-menu .buttons .button:hover{outline:0}.dropdown-menu .buttons .button.size-2{width:50%}.dropdown-menu .buttons .button.size-3{width:33%}.alert{padding:15px;margin-bottom:20px;background:#eee;border-bottom:5px solid #ddd}.alert-success{background:#dff0d8;border-color:#d6e9c6;color:#3c763d}.alert-info{background:#d9edf7;border-color:#bce8f1;color:#31708f}.alert-danger{background:#f2dede;border-color:#ebccd1;color:#a94442}.alert-warning{background:#fcf8e3;border-color:#faebcc;color:#8a6d3b}.book .book-summary{position:absolute;top:0;left:-300px;bottom:0;z-index:1;width:300px;color:#364149;background:#fafafa;border-right:1px solid rgba(0,0,0,.07);-webkit-transition:left 250ms ease;-moz-transition:left 250ms ease;-o-transition:left 250ms ease;transition:left 250ms ease}.book .book-summary ul.summary{position:absolute;top:0;left:0;right:0;bottom:0;overflow-y:auto;list-style:none;margin:0;padding:0;-webkit-transition:top .5s ease;-moz-transition:top .5s ease;-o-transition:top .5s ease;transition:top .5s ease}.book .book-summary ul.summary li{list-style:none}.book .book-summary ul.summary li.divider{height:1px;margin:7px 0;overflow:hidden;background:rgba(0,0,0,.07)}.book .book-summary ul.summary li i.fa-check{display:none;position:absolute;right:9px;top:16px;font-size:9px;color:#3c3}.book .book-summary ul.summary li.done>a{color:#364149;font-weight:400}.book .book-summary ul.summary li.done>a i{display:inline}.book .book-summary ul.summary li a,.book .book-summary ul.summary li span{display:block;padding:10px 15px;border-bottom:none;color:#364149;background:0 0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;position:relative}.book .book-summary ul.summary li span{cursor:not-allowed;opacity:.3;filter:alpha(opacity=30)}.book .book-summary ul.summary li a:hover,.book .book-summary ul.summary li.active>a{color:#008cff;background:0 0;text-decoration:none}.book .book-summary ul.summary li ul{padding-left:20px}@media (max-width:600px){.book .book-summary{width:calc(100% - 60px);bottom:0;left:-100%}}.book.with-summary .book-summary{left:0}.book.without-animation .book-summary{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;transition:none!important}.book{position:relative;width:100%;height:100%}.book .book-body,.book .book-body .body-inner{position:absolute;top:0;left:0;overflow-y:auto;bottom:0;right:0}.book .book-body{color:#000;background:#fff;-webkit-transition:left 250ms ease;-moz-transition:left 250ms ease;-o-transition:left 250ms ease;transition:left 250ms ease}.book .book-body .page-wrapper{position:relative;outline:0}.book .book-body .page-wrapper .page-inner{max-width:800px;margin:0 auto;padding:20px 0 40px}.book .book-body .page-wrapper .page-inner section{margin:0;padding:5px 15px;background:#fff;border-radius:2px;line-height:1.7;font-size:1.6rem}.book .book-body .page-wrapper .page-inner .btn-group .btn{border-radius:0;background:#eee;border:0}@media (max-width:1240px){.book .book-body{-webkit-transition:-webkit-transform 250ms ease;-moz-transition:-moz-transform 250ms ease;-o-transition:-o-transform 250ms ease;transition:transform 250ms ease;padding-bottom:20px}.book .book-body .body-inner{position:static;min-height:calc(100% - 50px)}}@media (min-width:600px){.book.with-summary .book-body{left:300px}}@media (max-width:600px){.book.with-summary{overflow:hidden}.book.with-summary .book-body{-webkit-transform:translate(calc(100% - 60px),0);-moz-transform:translate(calc(100% - 60px),0);-ms-transform:translate(calc(100% - 60px),0);-o-transform:translate(calc(100% - 60px),0);transform:translate(calc(100% - 60px),0)}}.book.without-animation .book-body{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;transition:none!important}.buttons:after,.buttons:before{content:" ";display:table;line-height:0}.button{border:0;background:#eee;color:#666;width:100%;text-align:center;float:left;line-height:1.42857143;padding:8px 4px}.button:hover{color:#444}.button:focus,.button:hover{outline:0}.button.size-2{width:50%}.button.size-3{width:33%}.book .book-body .page-wrapper .page-inner section{display:none}.book .book-body .page-wrapper .page-inner section.normal{display:block;word-wrap:break-word;overflow:hidden;color:#333;line-height:1.7;text-size-adjust:100%;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%}.book .book-body .page-wrapper .page-inner section.normal *{box-sizing:border-box;-webkit-box-sizing:border-box;}.book .book-body .page-wrapper .page-inner section.normal>:first-child{margin-top:0!important}.book .book-body .page-wrapper .page-inner section.normal>:last-child{margin-bottom:0!important}.book .book-body .page-wrapper .page-inner section.normal blockquote,.book .book-body .page-wrapper .page-inner section.normal code,.book .book-body .page-wrapper .page-inner section.normal figure,.book .book-body .page-wrapper .page-inner section.normal img,.book .book-body .page-wrapper .page-inner section.normal pre,.book .book-body .page-wrapper .page-inner section.normal table,.book .book-body .page-wrapper .page-inner section.normal tr{page-break-inside:avoid}.book .book-body .page-wrapper .page-inner section.normal h2,.book .book-body .page-wrapper .page-inner section.normal h3,.book .book-body .page-wrapper .page-inner section.normal h4,.book .book-body .page-wrapper .page-inner section.normal h5,.book .book-body .page-wrapper .page-inner section.normal p{orphans:3;widows:3}.book .book-body .page-wrapper .page-inner section.normal h1,.book .book-body .page-wrapper .page-inner section.normal h2,.book .book-body .page-wrapper .page-inner section.normal h3,.book .book-body .page-wrapper .page-inner section.normal h4,.book .book-body .page-wrapper .page-inner section.normal h5{page-break-after:avoid}.book .book-body .page-wrapper .page-inner section.normal b,.book .book-body .page-wrapper .page-inner section.normal strong{font-weight:700}.book .book-body .page-wrapper .page-inner section.normal em{font-style:italic}.book .book-body .page-wrapper .page-inner section.normal blockquote,.book .book-body .page-wrapper .page-inner section.normal dl,.book .book-body .page-wrapper .page-inner section.normal ol,.book .book-body .page-wrapper .page-inner section.normal p,.book .book-body .page-wrapper .page-inner section.normal table,.book .book-body .page-wrapper .page-inner section.normal ul{margin-top:0;margin-bottom:.85em}.book .book-body .page-wrapper .page-inner section.normal a{color:#4183c4;text-decoration:none;background:0 0}.book .book-body .page-wrapper .page-inner section.normal a:active,.book .book-body .page-wrapper .page-inner section.normal a:focus,.book .book-body .page-wrapper .page-inner section.normal a:hover{outline:0;text-decoration:underline}.book .book-body .page-wrapper .page-inner section.normal img{border:0;max-width:100%}.book .book-body .page-wrapper .page-inner section.normal hr{height:4px;padding:0;margin:1.7em 0;overflow:hidden;background-color:#e7e7e7;border:none}.book .book-body .page-wrapper .page-inner section.normal hr:after,.book .book-body .page-wrapper .page-inner section.normal hr:before{display:table;content:" "}.book .book-body .page-wrapper .page-inner section.normal h1,.book .book-body .page-wrapper .page-inner section.normal h2,.book .book-body .page-wrapper .page-inner section.normal h3,.book .book-body .page-wrapper .page-inner section.normal h4,.book .book-body .page-wrapper .page-inner section.normal h5,.book .book-body .page-wrapper .page-inner section.normal h6{margin-top:1.275em;margin-bottom:.85em;}.book .book-body .page-wrapper .page-inner section.normal h1{font-size:2em}.book .book-body .page-wrapper .page-inner section.normal h2{font-size:1.75em}.book .book-body .page-wrapper .page-inner section.normal h3{font-size:1.5em}.book .book-body .page-wrapper .page-inner section.normal h4{font-size:1.25em}.book .book-body .page-wrapper .page-inner section.normal h5{font-size:1em}.book .book-body .page-wrapper .page-inner section.normal h6{font-size:1em;color:#777}.book .book-body .page-wrapper .page-inner section.normal code,.book .book-body .page-wrapper .page-inner section.normal pre{font-family:Consolas,"Liberation Mono",Menlo,Courier,monospace;direction:ltr;border:none;color:inherit}.book .book-body .page-wrapper .page-inner section.normal pre{overflow:auto;word-wrap:normal;margin:0 0 1.275em;padding:.85em 1em;background:#f7f7f7}.book .book-body .page-wrapper .page-inner section.normal pre>code{display:inline;max-width:initial;padding:0;margin:0;overflow:initial;line-height:inherit;font-size:.85em;white-space:pre;background:0 0}.book .book-body .page-wrapper .page-inner section.normal pre>code:after,.book .book-body .page-wrapper .page-inner section.normal pre>code:before{content:normal}.book .book-body .page-wrapper .page-inner section.normal code{padding:.2em;margin:0;font-size:.85em;background-color:#f7f7f7}.book .book-body .page-wrapper .page-inner section.normal code:after,.book .book-body .page-wrapper .page-inner section.normal code:before{letter-spacing:-.2em;content:"\00a0"}.book .book-body .page-wrapper .page-inner section.normal ol,.book .book-body .page-wrapper .page-inner section.normal ul{padding:0 0 0 2em;margin:0 0 .85em}.book .book-body .page-wrapper .page-inner section.normal ol ol,.book .book-body .page-wrapper .page-inner section.normal ol ul,.book .book-body .page-wrapper .page-inner section.normal ul ol,.book .book-body .page-wrapper .page-inner section.normal ul ul{margin-top:0;margin-bottom:0}.book .book-body .page-wrapper .page-inner section.normal ol ol{list-style-type:lower-roman}.book .book-body .page-wrapper .page-inner section.normal blockquote{margin:0 0 .85em;padding:0 15px;opacity:0.75;border-left:4px solid #dcdcdc}.book .book-body .page-wrapper .page-inner section.normal blockquote:first-child{margin-top:0}.book .book-body .page-wrapper .page-inner section.normal blockquote:last-child{margin-bottom:0}.book .book-body .page-wrapper .page-inner section.normal dl{padding:0}.book .book-body .page-wrapper .page-inner section.normal dl dt{padding:0;margin-top:.85em;font-style:italic;font-weight:700}.book .book-body .page-wrapper .page-inner section.normal dl dd{padding:0 .85em;margin-bottom:.85em}.book .book-body .page-wrapper .page-inner section.normal dd{margin-left:0}.book .book-body .page-wrapper .page-inner section.normal .glossary-term{cursor:help;text-decoration:underline}.book .book-body .navigation{position:absolute;top:50px;bottom:0;margin:0;max-width:150px;min-width:90px;display:flex;justify-content:center;align-content:center;flex-direction:column;font-size:40px;color:#ccc;text-align:center;-webkit-transition:all 350ms ease;-moz-transition:all 350ms ease;-o-transition:all 350ms ease;transition:all 350ms ease}.book .book-body .navigation:hover{text-decoration:none;color:#444}.book .book-body .navigation.navigation-next{right:0}.book .book-body .navigation.navigation-prev{left:0}@media (max-width:1240px){.book .book-body .navigation{position:static;top:auto;max-width:50%;width:50%;display:inline-block;float:left}.book .book-body .navigation.navigation-unique{max-width:100%;width:100%}}.book .book-body .page-wrapper .page-inner section.glossary{margin-bottom:40px}.book .book-body .page-wrapper .page-inner section.glossary h2 a,.book .book-body .page-wrapper .page-inner section.glossary h2 a:hover{color:inherit;text-decoration:none}.book .book-body .page-wrapper .page-inner section.glossary .glossary-index{list-style:none;margin:0;padding:0}.book .book-body .page-wrapper .page-inner section.glossary .glossary-index li{display:inline;margin:0 8px;white-space:nowrap}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-overflow-scrolling:auto;-webkit-tap-highlight-color:transparent;-webkit-text-size-adjust:none;-webkit-touch-callout:none}a{text-decoration:none}body,html{height:100%}html{font-size:62.5%}body{text-rendering:optimizeLegibility;font-smoothing:antialiased;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;letter-spacing:.2px;text-size-adjust:100%} .book .book-summary ul.summary li a span {display:inline;padding:initial;overflow:visible;cursor:auto;opacity:1;} /* show arrow before summary tag as in bootstrap */ details > summary {display:list-item;cursor:pointer;} -/*add whatsapp icon from FA 5.1.1 -TODO: remove when updating fontawesome*/ -.fa-whatsapp:before{content:"\f232"} diff --git a/docs/libs/gitbook-2.6.7/js/plugin-clipboard.js b/docs/libs/gitbook-2.6.7/js/plugin-clipboard.js index 9a7d2e7..f0880be 100644 --- a/docs/libs/gitbook-2.6.7/js/plugin-clipboard.js +++ b/docs/libs/gitbook-2.6.7/js/plugin-clipboard.js @@ -9,7 +9,9 @@ gitbook.require(["gitbook", "jQuery"], function(gitbook, $) { // the page.change event is thrown twice: before and after the page changes if (clipboard) { - // clipboard is already defined + // clipboard is already defined but we are on the same page + if (clipboard._prevPage === window.location.pathname) return; + // clipboard is already defined and url path change // we can deduct that we are before page changes clipboard.destroy(); // destroy the previous events listeners clipboard = undefined; // reset the clipboard object @@ -24,6 +26,8 @@ gitbook.require(["gitbook", "jQuery"], function(gitbook, $) { } }); + clipboard._prevPage = window.location.pathname + }); }); diff --git a/docs/search_index.json b/docs/search_index.json index 5b0b52a..1b00b5e 100644 --- a/docs/search_index.json +++ b/docs/search_index.json @@ -1 +1 @@ -[["index.html", "Developing WDL Workflows About this Course 0.1 Target Audience 0.2 Why WDL? 0.3 Curriculum 0.4 Relevant resources", " Developing WDL Workflows February, 2024 About this Course “Developing WDL Workflows” shows a bioinformatics workflow developer how to strategically develop and scale up a WDL workflow that is iterative, reproducible, and efficient in terms of time and resource used. This guide is flexible regardless of where the data is, what computing resources are being used, and what software is being used. 0.1 Target Audience The course is intended for first time developers of the WDL workflow language, who wants to iteratively develop a WDL bioinformatics workflow. In order to use this guide the audience should be able to comprehend introductory WDL syntax, and should be able to run a WDL workflow on a computing engine of their choice, such as Cromwell, miniWDL, or a cloud computing environment such as Terra, AnVIL, or Dockstore. 0.2 Why WDL? You may have encountered other workflow tools, such as Snakemake or Nextflow, and those are highly capable. Why learn a brand new workflow language? Let’s review some WDL Pros and Cons: 0.2.1 WDL Pros WDL has some really helpful advantages compared to other frameworks: Portability. WDL can run on nearly any system, whether it be your local computer, or on an HPC cluster, or on the Cloud, with platforms such as DNAnexus. In fact, a lot of developers will prototype a WDL workflow on their own local computer before moving it to the cloud. Reproducibility. Ever have the headache of having to reproduce the exact package versions to get your workflow to work again? If you use Docker containers to specify your software environment, you do not have to worry about this headache. A workflow will run identically locally, on HPC, or the cloud. Sharing. A WDL workflow is much easier to share with colleagues and is a good way to get credit for work you do everyday. If you spent time building it, why not share it? WDL is also an open standard and supported by a number of software tools. Running and Making WDL workflows is a transferrable skill. Genomics and Pharma companies rely on WDL workflows to process thousands of FASTA/VCF files for their studies. They need more experts. It makes you more hireable within both Academia and Industry. 0.2.2 WDL Cons Of course, nothing is free. WDL does require you to understand the basic concepts and terminologies including: - Basics of Docker - Understanding the WDL framework - WDLizing your bash scripts into tasks 0.3 Curriculum The course covers the following: how to write an effective WDL task, link multiple WDL tasks together in a workflow, organize variables via structs, scale multiple samples via Arrays, reuse repeated tasks via task aliasing, and configure settings for the execution engine. 0.4 Relevant resources An excellent WDL companion resource that is not platform dependent is OpenWDL Docs. OpenWDL Docs focuses on the basic grammar of WDL as well as providing excellent cookbook recipes of common WDL workflow structures. In this guide we will reference these basic grammar structures and common workflow cookbook recipes. "],["introduction-to-wdl.html", "Chapter 1 Introduction to WDL 1.1 Introduction 1.2 Review of basic WDL syntax 1.3 Using JSONs to control workflow inputs 1.4 Running WDL via a computing engine", " Chapter 1 Introduction to WDL 1.1 Introduction Welcome to building your first WDL workflow! This guide will help you strategically develop and scale up a WDL workflow that is iterative, reproducible, and efficient in terms of time and resource used. To make sure that we are on the same page, this guide assumes that you are able to run a WDL on a computing engine of your choice, such as Cromwell, miniwdl, or a cloud computing environment such as Terra, AnVIL, or Dockstore. This guide also assumes that you have a beginner’s understanding of the WDL syntax, and we will link out to additional resources to fill in the knowledge gap as needed! If you have never seen the WDL language in action, a great place to start is OpenWDL docs – it teaches you the basic syntax and showcases WDL features via concrete examples. 1.2 Review of basic WDL syntax We will do some review of the WDL syntax. A WDL workflow consists of at least one task. version 1.0 task do_something { command <<< exit 0 >>> } workflow my_workflow { call do_something } A workflow, and the tasks it calls, generally has inputs. version 1.0 task do_something { input { File fastq } command <<< exit 0 >>> } workflow my_workflow { input { File fq } call do_something { input: fastq = fq } } The input fq is defined to be a File variable type. WDL supports various variable types, such as String, Integer, Float, and Boolean. For more information on types in WDL, we recommend OpenWDL’s documentation on variable types. To access a task-level input variable in a task’s command section, it is usually referenced using ~{this} notation. To access a workflow-level variable in a workflow, it is referenced just by its name without any special notation. To access a workflow-level variable in a task, it must be passed into the task as an input. version 1.0 task do_something { input { File fastq String basename_of_fq } command <<< echo "First ten lines of ~{basename_of_fq}: " head ~{fastq} >>> } workflow my_workflow { input { File fq } String basename_of_fq = basename(fq) call do_something { input: fastq = fq, basename_of_fq = basename_of_fq } } Tasks and workflows also typically have outputs. The task-level outputs can be accessed by the workflow or any subsequent tasks. The workflow-level outputs represent the final output of the overall workflow. version 1.0 task do_something { input { File fastq String basename_of_fq } command <<< echo "First ten lines of ~{basename_of_fq}: " >> output.txt head ~{fastq} >> output.txt >>> output { File first_ten_lines = "output.txt" } } workflow my_workflow { input { File fq } String basename_of_fq = basename(fq) call do_something { input: fastq = fq, basename_of_fq = basename_of_fq } output { File ten_lines = do_something.first_ten_lines } } 1.3 Using JSONs to control workflow inputs Running a WDL workflow generally requires two files: A .wdl file, which contains the actual workflow, and a .json file, which provides the inputs for the workflow. In the example we showed earlier, the workflow takes in a file referred to by the variable fq. This needs to be provided by the user. Typically, this is done with a JSON file. Here’s what a JSON file for this workflow might look like: { "my_workflow.fq": "./data/example.fq" } JSON files consist of key-value pairs. In this case, the key is \"my_workflow.fq\" and the value is the path \"./data/example.fq\". The first part of the key is the name of the workflow as written in the WDL file, in this case my_workflow. The variable being represented is referred to its name, in this case, fq. So, the file located at the path ./data/example.fq is being input as a variable called fq into the workflow named my_workflow. Files aren’t the only type of variable you can refer to when using JSONs. Here’s an example JSON for every common WDL variable type. { "some_workflow.file": "./data/example.fq", "some_workflow.string": "Hello world!", "some_workflow.integer": 1965, "some_workflow.float": 3.1415, "some_workflow.boolean": true, "some_workflow.array_of_files": ["./data/example01.fq", "./data/example02.fq"] } Resources: For more basic examples of WDL workflow with a single task, we recommend OpenWDL’s documentation on WDL’s base structure. For more information on types in WDL, we recommend OpenWDL’s documentation on variable types. If you are having difficulty writing valid JSON files, considering using https://jsonlint.com/ to check your JSON for any errors. 1.4 Running WDL via a computing engine In order to run a WDL workflow, we need a computing engine to execute it. The two most popular WDL executors are miniwdl and Cromwell. Both can run WDLs on a local machine, High Performance Computing (HPC), or cloud computing backend. If you are trying to run WDL at Fred Hutch Cancer Center’s HPC system, you should use the PROOF executor. If you are computing on a HPC or the Cloud, you should find the best practice of running a WDL computing engine based on your institution’s information technology system. If you are computing locally on your computer, below is a short guide on how to set that up. How to run simple workflows locally Not every WDL workflow will run well on a laptop, but it can be helpful to have a basic setup for testing and catching simple syntax errors. Let’s quickly set up a WDL executor to run our WDLs. In this course, we will be using miniwdl, but everything in this course will also be compatible with Cromwell unless explicitly stated otherwise. Additionally, almost all WDLs use Docker images, so you will also need to install Docker or a Docker-like alternative. Installing Docker and miniwdl is not required to use this course. We don’t want anybody to get stuck here! If you already have a method for submitting workflows, such as Terra, feel free to use that for this course instead of running workflows directly on your local machine. If you don’t have any way of running workflows at the moment, that’s also okay – we have provided plenty of examples for following along. 1.4.1 Installing Docker Note: Although Docker’s own docs recommend installing Docker Desktop for Linux, it has been reported that some WDL executors work better on Linux when installing only Docker Engine (aka Docker CE). To install Docker on your machine, follow the instructions specific to your operating system on Docker’s website. To specifically install only Docker Engine, use these instructions instead. If you are unable to install Docker on your machine, Dockstore (not affiliated with Docker) provides some experimental alternatives. Dockstore also provides a comprehensive introduction to Docker itself, including how to write a Dockerfile. Much of that information is outside the scope of this WDL-focused course, but it may be helpful for those looking to eventually create their own Docker images. 1.4.2 Installing miniwdl miniwdl is based on Python. If you do not already have Python 3.6 or higher installed, you can install Python from here. Once Python is installed on your system, you can run pip3 install miniwdl from the command line to install miniwdl. For those who prefer to use conda, use conda install -c conda-forge miniwdl instead. Once miniwdl is installed, you can verify it works properly by running miniwdl run_self_test. This will run a built-in hello world workflow. For more information, see miniwdl’s GitHub repository. 1.4.3 Launching a workflow locally with miniwdl The generic method for running a WDL with miniwdl is the following: miniwdl run [path_to_wdl_file] -i [path_to_inputs_json] If you have successfully installed miniwdl, create the following WDL file and name it greetings.wdl: version 1.0 task greet { input { String user } command <<< echo "Hello ~{user}!" > greets.txt >>> output { String greeting = read_string("greets.txt") } } workflow my_workflow { input { String username } call greet { input: user = username } } Next, use this JSON file (or create one of your own) to provide the string that the workflow expects, and call the JSON file greetings.json: { "my_workflow.username": "Ash" } On the command line, run the following: miniwdl run greetings.wdl -i greetings.json Once the task completes, you should see something like this in your command line: [timestamp] wdl.w:my_workflow finish :: job: "call-greet" [timestamp] wdl.w:my_workflow done { "dir": "[working directory]/[timestamp]_my_workflow", "outputs": { "my_workflow.greet.greeting": "Hello Ash!" } } Where [timestamp] is the date and time that you are running the workflow, and [working directory] is the working directory that you are running the workflow from. For example: 2023-12-27 13:54:12.209 wdl.w:my_workflow finish :: job: "call-greet" 2023-12-27 13:54:12.210 wdl.w:my_workflow done { "dir": "/Users/ash/github/WDL_Workflows_Guide/resources/20231227_135400_my_workflow", "outputs": { "my_workflow.greet.greeting": "Hello Ash!" } } 1.4.4 Troubleshooting 1.4.4.1 DockerException If you are seeing a verbose error message that begins with text like this: 2023-12-27 13:43:37.525 wdl.w:my_workflow.t:call-greet task greet (greetings.wdl Ln 3 Col 1) failed :: dir: "/Users/sammy/github/WDL_Workflows_Guide/resources/20231227_134337_my_workflow/call-greet", error: "DockerException", message: "Error while fetching server API version: ('Connection aborted.', FileNotFoundError(2, 'No such file or directory'))", traceback: ["Traceback (most recent call last):", " File \\"/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/urllib3/connectionpool.py\\", line 790, in urlopen", " response = self._make_request(", " ^^^^^^^^^^^^^^^^^^^", " File \\"/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/urllib3/connectionpool.py\\", This is likely caused by miniwdl being unable to connect to Docker Daemon, the underlying technology that runs Docker images. This is necessary with miniwdl even though our example WDL does not specify a Docker image. Make sure you have Docker installed correctly, and make sure Docker is actively running on your machine. If you installed Docker Desktop, simply opening the Docker Desktop app should start Docker Engine. If you installed Docker without Docker Desktop, running dockerd in your command-line should start it. Be aware that starting the Docker Daemon may take a few minutes. 1.4.4.2 Missing required inputs If you forget to add -i greetings.json to your call, you will see something like this: my_workflow (greetings.wdl) --------------------------- required inputs: String username outputs: String greet.greeting missing required inputs for my_workflow: username You may also see this error if you remember to include a JSON file, but it is missing a required input. 1.4.4.3 Check JSON input If you see an error message like this: check JSON input; unknown input/output: greetings.username Double-check your input JSON. The first part of your JSON’s keys refer to the name of the workflow in the WDL file, not the filename of the WDL itself. Even though our WDL is saved as greetings.wdl, within that file, the workflow is named my_workflow. This means that the input JSON must say \"my_workflow.username\", not \"greetings.username\". Other common issues with JSON files are mistyping input variables (such as \"my_workflow.ussername\") or forgetting to enclose strings in quotation marks. When in doubt, try using https://jsonlint.com/ to check your input JSON, and double-check the name of your input variables. "],["defining-a-workflow-plan.html", "Chapter 2 Defining a workflow plan 2.1 Somatic mutation calling workflow 2.2 Workflow testing strategy 2.3 Test samples", " Chapter 2 Defining a workflow plan Our WDL guide will center around building a workflow from scratch. As we build out this workflow step-by-step, you will see what strategies and resources are used to develop a workflow that is iterative, reproducible, and efficient in terms of time and resource used. The goal is to use this workflow to illustrate common lessons in writing WDL workflows. 2.1 Somatic mutation calling workflow The workflow used as the example here is tailored to detect somatic mutations in two tumor samples. Initially, the workflow takes as input FASTQ-formatted sequencing data from two tumor specimens and one normal sample (a single normal sample is used here, but typically each tumor might have its own associated normal). Subsequently, it aligns the FASTQ files of each sample with the human reference genome (hg19), proceeds to identify and mark PCR duplicates, and conducts base quality recalibration. Following these steps, the workflow engages in somatic mutation calling, operating in a paired mode, to pinpoint mutations unique to the tumor samples in comparison to the normal one. Concluding the process, the workflow undertakes the annotation of the identified mutations, enriching the dataset with additional insights into the nature of the mutations detected. The workflow diagram: The tasks involved: BwaMem aligns the samples to the reference genome (hg19). MarkDuplicates marks PCR duplicates. ApplyBaseRecalibrator perform base quality recalibration. Mutect2 performs paired somatic mutation calling. annovar annotates the called somatic mutations. 2.2 Workflow testing strategy As we build out our workflow, how do we know it is running correctly besides getting a message such as “Workflow finished with status ‘Succeeded’” or an exit code 0? In software development, it is essential to test your code to see whether it generates the expected output given a specified input. This principle applies into bioinformatics workflow development also: Unit Testing: We need to incorporate tests to ensure that each task we develop is correct. End-to-end testing: When we connect all the tasks together to form a workflow, we test that the workflow running end-to-end is correct. Here are some guidelines for any form of testing: The data you use for testing is representative of “real” data. You have an expectation of what the resulting output is before you run your workflow on it. It can be as specific as a MD5 checksum, or vague such as a certain file format. The process is quick to run, ideally in the range of just a few minutes. This often means using a small subset of actual data. The data you use for testing is ideally open access so others can verify your workflow also. 2.3 Test samples To serve as an example we use here whole exome sequencing data from three cell lines from the Cancer Cell Line Encyclopedia. 2.3.1 Tumor 1 : HCC4006 HCC4006 is a lung cancer cell line that has a mutation in the gene EGFR (Epithelial Growth Factor Receptor) a proto-oncogene. Mutations in EGFR result in the abnormal constitutive activation of the EGFR signaling pathway and drive cancer. In this cell-line specifically the EGFR mutation is an in-frame deletion in Exon 19. This mutation results in the constitutive activation of the EGFR protein and is therefore oncogenic. 2.3.2 Tumor 2 : CALU1 CALU1 is a lung cancer cell line that has a mutation in the gene KRAS (Kirsten rat sarcoma viral oncogene homolog) . KRAS is also a proto-oncogene and the most common cancer-causing mutations lock the protein in an active conformation. Constitutive activation of KRAS results in carcinogenesis. In this cell-line KRAS has a point/missense mutation resulting in the substitution of the amino acid glycine (G) with cysteine (C) at position 12 of the KRAS protein (commonly known as the KRAS G12C mutation). This mutation results in the constitutive activation of KRAS and drives carcinogenesis. 2.3.3 Normal : MOLM13 MOLM 13 is a human leukemia cell line commonly used in research. While it is also a cancer cell line for the purposes of this workflow example we are going to consider it as a “normal”. This cell line does not have mutations in EGFR nor in KRAS and therefore is a practical surrogate in lieu of a conventional normal sample 2.3.4 Test data details Fastq files for all these three samples were derived from their respective whole exome sequencing. However for the purpose of this guide we have limited the sequencing reads to span +/- 200 bp around the mutation sites for both genes. In doing so we are able to shrink the data files for quick testing. "],["the-first-task.html", "Chapter 3 The first task 3.1 Inputs 3.2 Runtime attributes 3.3 Outputs 3.4 The whole task 3.5 Putting the workflow together 3.6 Testing your first task", " Chapter 3 The first task Before we write any sort of WDL – whether it is for somatic mutation calling like we will be going over, or any other bioinformatics task – we need to understand the building blocks of WDL: Tasks! As mentioned in the first part of this course, every WDL workflow is made up of at least one task. A task typically has inputs, outputs, runtime attributes, and a command section. You can think of a task as a discrete step in a workflow. It can involve a single call to a single bioinformatics tool, a sequence of bash commands, an inline Python script… almost anything you can do non-interactively in a terminal, you can do in a WDL task. In this section, we will go over the parts of a WDL task in more detail to help us write a task for somatic mutation calling. 3.1 Inputs The inputs of a task are the files and/or variables you will passing into your task’s command section. Typically, you will want to include at least one File input in a task, but that isn’t a requirement. You can pass most WDL variable types into a task. In our example workflow, we are starting with a single fastq file per sample, and we know we will need to convert it into a sam file. A sam file is an alignment, so we will need a reference genome to align our fastqs to. We also want to be able to control the threading for this task. Our first task’s inputs will therefore start out looking like this: task some_aligner { input { File input_fastq File ref_fasta Int threads } [...] } For some aligners, this would be a sufficient set of inputs, but we have decided to use bwa mem in particular to take us from fastq to sam. bwa mem requires a lot of index files, which we will also need to input. This can be done via an array, but for now we’ll list everything separately to make sure nothing is being left out. We also want to define a default value for threads so that someone who does not know much about threading can still use the workflow. We want to use this workflow on human data, so we’ll go a little high for the default number of threads and set it to sixteen. In WDL, we do this by declaring Int threads = 16. Make sure to put this in the task (or workflow) inputs section – if you put it elsewhere, that variable cannot be changed from its default value, so it will always be 16. task BwaMem { input { # main input File input_fastq # options Int threads = 16 # reference files File ref_fasta File ref_fasta_index File ref_dict File ref_amb File ref_ann File ref_bwt File ref_pac File ref_sa } [...] } 3.1.1 Referencing inputs in the command section The command section of a WDL task is a bash script that will be run non-interactively by the WDL executor. Although it is helpful to think of tasks as discrete steps in a workflow, that does not mean each task needs to be a single line. You could, for example, call a bioinformatics tool and then reprocess the outputs in the same WDL task. Within the command section, we refer to those variables using ~{this} syntax. For instance, if the user sets threads to 8, then the -t ~{threads} part of the command section below will be interpreted as -t 8. A WDL task’s input variables are generally referred to in the command section using a tilde (~) and curly braces, using heredoc syntax. Why we use heredox syntax. You may see WDLs that use this notation for the command section in a task: task do_something_curly_braces { input { String some_string } command { some_other_string="FOO" echo ${some_string} echo $some_other_string } } We recommend using heredoc-style syntax instead: task do_something_carrots { input { String some_string } command <<< some_other_string="FOO" echo ~{some_string} echo $some_other_string >>> } Heredoc-style syntax for command sections can be clearer than the alternative, as it makes a clearer distinction between bash variables and WDL variables. This is especially helpful for complicated bash scripts. Heredoc-style syntax is also what the WDL 1.1 spec recommends using in most cases. However, the older non-heredoc style is still perfectly functional for a lot of use cases. To prevent issues with spaces in String and File types, it is often a good idea to put quotation marks around a String or File variabls, like so: task cowsay { input { String some_string } command <<< cowsay -t "~{some_string}" >>> } Why we put quotation marks around a String or File variables in Commands. If some_string is “hello world” then the command section of this task is interpreted as the following: cowsay -t "hello world" What happens if we had not wrapped ~{some_string} in quotation marks? If some_string was just “hello”, it wouldn’t matter. But because some_string is two words with a space in between, then the script would be interpreted as cowsay -t hello world and cause an error, because the cowsay program thinks world is another argument. By including quotation marks, cowsay -t \"~{some_string}\" can be interpreted as cowsay -t \"hello world\" and you will correctly get a cow’s greeting instead of an error. Let’s see how we can reference our inputs in the command section of our task. task BwaMem { input { File input_fastq File ref_fasta Int threads = 16 # these variables may look as though they are unused... but bwa mem needs them! File ref_fasta_index File ref_dict File ref_amb File ref_ann File ref_bwt File ref_pac File ref_sa } command <<< # warning: this will not run on all backends! see below for an explanation! bwa mem \\ -p -v 3 -t ~{threads} -M -R '@RG\\tID:foo\\tSM:foo2' \\ "~{ref_fasta}" "~{input_fastq}" > my_nifty_output.sam >>> } If we were to run this task in a workflow as-is, we might expect it to run on any backend that can handle the hardware requirements. Those hardware requirements are a bit steep – the -t 16 part specifically requests 16 threads, for example – but besides that, it may look like a perfectly functional task. Unfortunately, even on backends that can provide the necessary computing power, it is quite likely this task will not run as expected. This is because of how inputs work in WDL – or, more specifically, how input files get localized when working with WDL. 3.1.2 File localization When running a WDL, a WDL executor will typically place duplicates of the input files in a brand-new subfolder of the task’s working directory. Typically, you don’t know the name of the directory before runtime – they vary depending on the backend you are running and the WDL executor itself. Thankfully, at runtime, File-type variables such as ~{input_fastq} and ~{ref_fasta} will be replaced with paths to their respective files. For example, if you were to run this workflow on a laptop using miniwdl, ~{ref_fasta} would likely end up turning into ./_miniwdl_inputs/0/ref.fa at runtime. On the other hand, if you were running the exact same workflow with Cromwell, ~{ref_fasta} would turn into something like /cromwell-executions/BwaMem/97c9341e-9322-9a2f-4f54-4114747b8fff/call-test_localization/inputs/-2022115965/ref.fa. Keep in mind that these are the paths of copies of the input files, and that sometimes input files can be in different subfolders. For example, it’s possible ~{input_fastq} would be ./_miniwdl_inputs/0/sample.fastq while ~{ref_fasta} may be ./_miniwdl_inputs/1/ref.fa. For many programs, an input file being at ./ref.fa versus /_miniwdl_inputs/0/ref.fa is inconsequential. However, this aspect of WDL can occasionally cause issues. bwa mem is a great example of the type of command where this sort of thing can go haywire without proper planning, due to the program making an assumption about some of your input files. Specifically, bwa mem assumes that the reference fasta that you pass in shares the same folder as the other reference files (ref_amb, ref_ann, ref_bwt, etc), and it does not allow you to specify otherwise. Another example of file localization issue. bwa is not the only program that makes assumptions about where files are located, and assumptions being made do not only affect reference genome files. Bioinformatics programs that take in some sort of index file frequently assume that index file is located in the same directory as the non-index input. For example, if you were to pass in SAMN1234.bam into covstats, it would expect an index file named SAMN1234.bam.bai or SAMN1234.bai in the same directory as the bam file, as seen in the source code here. As there is no way to specify that the index file manually, you need to take that into consideration when writing WDLs involving covstats, bwa, and other similar tools. Thankfully, the solution here is simple: Move all of the input files directly into the working directory. task BwaMem { input { File input_fastq File ref_fasta File ref_fasta_index File ref_dict File ref_amb File ref_ann File ref_bwt File ref_pac File ref_sa Int threads = 16 } command <<< set -eo pipefail # This can also be done by creating an array and then looping that array, # but we'll do it one line at a time or clarity's sake. mv "~{ref_fasta}" . mv "~{ref_fasta_index}" . mv "~{ref_dict}" . mv "~{ref_amb}" . mv "~{ref_ann}" . mv "~{ref_bwt}" . mv "~{ref_pac}" . mv "~{ref_sa}" . bwa mem \\ [...] >>> } Some backends/executors do not support mv acting on input files. If you are running into problems with this and are working with miniwdl, the --copy-input-files flag will usually allow mv to work. You could also simply use cp to copy the files instead of move them, although this may not be an efficient use of disk space, so consider using mv if your target backends and executors can handle it. With our files now all in the working directory, we can turn our attention to the bwa task itself. We can no longer directly pass in ~{ref_fasta} or any of the other files we mved into the working directory, because those variables will point to a non-existent file in a now-empty input directory. There are several ways to solve this problem: Assuming the filename of an input is constant, which might be a safe assumption for reference files Using the bash built-in basename function Using the WDL built-in basename() function along with private variables We recommend using the last option, as it works for essentially any input and may be more intuitive than the bash basename function. OpenWDL explains how basename() works. The next section will provide an example of using it alongside private variables. 3.1.3 Private variables Is there a variable you wish to use in your task section that is based on another input variable, or do not want people using your workflow to be able to directly overwrite? You can define variables outside the input {} section to create variables that function like private variables. In our case, we create String ref_fasta_local as ref_fasta’s file base name to refer to the files we have moved to the working directory. We also create String base_file_name as input_fastq’s file base name and use it to name our output files, such as \"~{base_file_name}.sorted_query_aligned.bam\". task BwaMem { input { File input_fastq File ref_fasta File ref_fasta_index File ref_dict File ref_amb File ref_ann File ref_bwt File ref_pac File ref_sa Int threads = 16 } # basename() is a built-in WDL function that acts like bash's basename String base_file_name = basename(input_fastq, ".fastq") String ref_fasta_local = basename(ref_fasta) String read_group_id = "ID:" + base_file_name String sample_name = "SM:" + base_file_name String platform = "illumina" String platform_info = "PL:" + platform # Create the platform information command <<< set -eo pipefail mv "~{ref_fasta}" . mv "~{ref_fasta_index}" . mv "~{ref_dict}" . mv "~{ref_amb}" . mv "~{ref_ann}" . mv "~{ref_bwt}" . mv "~{ref_pac}" . mv "~{ref_sa}" . bwa mem \\ -p -v 3 -t ~{threads} -M -R '@RG\\t~{read_group_id}\\t~{sample_name}\\t~{platform_info}' \\ ~{ref_fasta_local} ~{input_fastq} > ~{base_file_name}.sam samtools view -1bS -@ 15 -o ~{base_file_name}.aligned.bam ~{base_file_name}.sam samtools sort -@ 15 -o ~{base_file_name}.sorted_query_aligned.bam ~{base_file_name}.aligned.bam >>> } 3.2 Runtime attributes The runtime attributes of a task tell the WDL executor important information about how to run the task. For a bwa mem task, we want to make sure we have plenty of hardware resources available. We also need to include a reference to the docker image we want the task to actually run in. runtime { memory: "48 GB" cpu: 16 docker: "ghcr.io/getwilds/bwa:0.7.17" disks: "local-disk 100 SSD" } In WDL 1.0, the interpretation of runtime attributes by different executors and backends is extremely varied. The WDL 1.0 spec allows for arbitrary values here: Individual backends will define which keys they will inspect so a key/value pair may or may not actually be honored depending on how the task is run. Values can be any expression and it is up to the engine to reject keys and/or values that do not make sense in that context. This can lead to some pitfalls: Some of the attributes in your task’s runtime section may be silently ignored, such as the memory attribute when running Cromwell on the Fred Hutch HPC (as of Feb 2024) Some runtime attributes that are unique to particular backends, such as the Fred Hutch HPC’s walltime attribute The same runtime attribute working differently on different backends, such as disks acting differently on Cromwell depending on whether it is running on AWS or GCP When writing WDL 1.0 workflows with specific hardware requirements, keep in mind what your backend and executor is able to interpret. It is also helpful to consider that other people running your workflow may be doing so on different backends and executors. More information can be found in the appendix, where we talk about designing WDLs for specific backends. For now, we will stick with memory, cpu, docker, and disks as this group of four runtime attributes will help us run this workflow on the majority of backends and executors. Even though the Fred Hutch HPC will ignore the memory and disks attributes, for instance, their inclusion will not cause the workflow to fail, but they will allow the workflow to run on Terra. Some differences between WDL 1.0 and 1.1 on Runtime attributes. Although the focus of this course is on WDL 1.0, it is worth noting that in the WDL 1.1 spec, a very different approach to runtime attributes is taken: There are a set of reserved attributes (described below) that must be supported by the execution engine, and which have well-defined meanings and default values. Default values for all optional standard attributes are directly defined by the WDL specification in order to encourage portability of workflows and tasks; execution engines should NOT provide additional mechanisms to set default values for when no runtime attributes are defined. If you are writing WDLs under the WDL 1.1 standard, you may have more flexibility with runtime attributes. Be aware that as of February 2024, Cromwell does not support WDL 1.1. 3.2.1 Docker images and containers WDL is built to make use of Docker as it makes handling software dependencies much simpler. Docker images can help address all of these situations: Some software is difficult to install or compile on certain systems Some programs have conflicting dependencies You may not want to directly install software on your system to prevent it from breaking existing software You may not have permission to install software if you are using an institute HPC or other shared resource When you run a WDL task that has a docker runtime attribute, your task will be executed in a Docker container sandbox environment. This container sandbox is derived from a template called a Docker image, which packages installed software in a special file system. This is one of the main features of a Docker image – because a Docker image packages the software you need, you can skip much of the installation and dependency issues associated with using new software, and because you take actions within a Docker container sandbox, it’s unlikely for you to “mess up” your main system’s files. Although a Docker container is, strictly speaking, not the same as a virtual machine, it is helpful to think of it as one if you are new to Docker. Docker containers are managed by Docker Engine, and the official Docker GUI is called Docker Desktop. More information on finding and developing Docker images. Although you will generally need to be able to run Docker in order to run WDLs, you do not need to know how to create Dockerfiles – plaintext files which compile Docker images when run via docker build – to write your own WDLs. Most popular bioinformatic software packages already have ready-to-use Docker images available, which you can typically find on Docker Hub. Other registries include quay.io and the Google Container Registry. With that being said, if you would like to create your own Docker images, there are many tutorials and guidelines available online. You can also learn more about the details of Docker (and why they technically aren’t virtual machines) in Docker’s official curriculum. 3.3 Outputs The outputs of a task are defined in the output section of your task. Typically, this will take the form of directly outputting a file that was created in the command section. When these file outputs are referenced in the output section, you can refer to their path in the Docker container directly. You can also make outputs a function of input variables, including private input variables. This can be helpful if you intend on running this WDL on many different files – each one will get a unique filename based on the input fastq, instead of every sample ending up being named something generic like “converted.sam”. For our bwa mem task, one way to write the output section would be as follows: output { File analysisReadySorted = "~{base_file_name}.sorted_query_aligned.bam" } Another way of writing this is with string concatenation. This is equivalent to what we wrote above – choose whichever version you prefer. output { File analysisReadySorted = base_file_name + ".sorted_query_aligned.bam" } If the output was not in the working directory, we would need to change the output to point to the file’s path relative to the working directory, such as File analysisReadySorted = \"some_folder/~{base_file_name}.sorted_query_aligned.bam\". Below are some some additional ways you can handle task outputs. Ouputs as functions of other outputs in the same task. Outputs can (generally, see warning below) also be functions of other outputs in the same task, as long as those outputs are declared first. task add_one { input { Int some_integer } command <<< echo ~{some_integer} > a.txt echo "1" > b.txt >>> output { Int a = read_int("a.txt") Int b = read_int("b.txt") Int c = a + b } } Cromwell does not fully support outputs being a function of the same task’s other outputs. On the Terra backend, the above code example would cause an error. Grabbing multiple outputs at the same time To grab multiple outputs at the same time, use glob() to create an array of files. We’ll also take this opportunity to demonstrate iterating through a bash array created from an Array[String] input – for more information on this data type, see chapter six of this course. task one_word_per_file { input { Array[String] a_big_sentence } command <<< ARRAY_OF_WORDS=(~{sep=" " a_big_sentence}) i=0 for word in "${!ARRAY_OF_WORDS[@]}" do i=$((i+1)) echo $word >> $i.txt done >>> output { Array[File] several_words = glob("*.txt") } } glob() can also be used to grab just one file via glob(\"*.txt\")[0] to grab the first thing that matches the glob. This is usually only necessary if you know the extension of an output, but do not have a way of predicting the rest of its filename. Be aware that if anything else in the working directory has the extension you are searching for, you might accidentally grab that one instead of the one you are looking for! 3.4 The whole task We’ve now designed a bwa mem task that can run on essentially any backend that supports WDL and can handle the hardware requirements. Issues involving bwa mem expecting reference files to be in the same folder and/or putting output files into input folders have been sidestepped thanks to careful design and consideration. The runtime section clearly defines the expected hardware requirements, and the outputs section defines what we expect the task to give us when all is said and done. We’re now ready to continue with the rest of our workflow. task BwaMem { input { File input_fastq referenceGenome refGenome Int threads = 16 } String base_file_name = basename(input_fastq, ".fastq") String ref_fasta_local = basename(refGenome.ref_fasta) String read_group_id = "ID:" + base_file_name String sample_name = "SM:" + base_file_name String platform = "illumina" String platform_info = "PL:" + platform # Create the platform information command <<< set -eo pipefail #can we iterate through a struct?? mv ~{refGenome.ref_fasta} . mv ~{refGenome.ref_fasta_index} . mv ~{refGenome.ref_dict} . mv ~{refGenome.ref_amb} . mv ~{refGenome.ref_ann} . mv ~{refGenome.ref_bwt} . mv ~{refGenome.ref_pac} . mv ~{refGenome.ref_sa} . bwa mem \\ -p -v 3 -t ~{threads} -M -R '@RG\\t~{read_group_id}\\t~{sample_name}\\t~{platform_info}' \\ ~{ref_fasta_local} ~{input_fastq} > ~{base_file_name}.sam samtools view -1bS -@ 15 -o ~{base_file_name}.aligned.bam ~{base_file_name}.sam samtools sort -@ 15 -o ~{base_file_name}.sorted_query_aligned.bam ~{base_file_name}.aligned.bam >>> output { File analysisReadySorted = "~{base_file_name}.sorted_query_aligned.bam" } runtime { memory: "48 GB" cpu: 16 docker: "ghcr.io/getwilds/bwa:0.7.17" } } 3.5 Putting the workflow together A workflow is needed to run the BwaMem task we just built. The workflow’s input variables are defined by the workflow JSON metadata, and are then passed on as inputs in our BwaMem call. When the BwaMem call is complete, the workflow’s output File variable is defined based on the task’s output. Lastly, we have a parameter_meta component in our workflow that describes each workflow input variable as documentation. For the workflow to actually “see” the task, the task will either need to be imported at the top of the workflow (just under the version 1.0 string), or included in the same file as the workflow. For simplicity, we will put the workflow and the task in the same file. version 1.0 workflow mutation_calling { input { # Sample info File tumorFastq # Reference Genome information File ref_fasta File ref_fasta_index File ref_dict File ref_amb File ref_ann File ref_bwt File ref_pac File ref_sa #Optional BwaMem threading variable Int? bwa_mem_threads } # Map reads to reference call BwaMem { input: input_fastq = input_fastq, ref_fasta = ref_fasta, ref_fasta_index = ref_fasta_index, ref_dict = ref_dict, ref_amb = ref_amb, ref_ann = ref_ann, ref_bwt = ref_bwt, ref_pac = ref_pac, ref_sa = ref_sa, threads = bwa_mem_threads } # Outputs that will be retained when execution is complete output { File alignedBamSorted = BwaMem.analysisReadySorted } parameter_meta { sampleFastq: "Sample .fastq (expects Illumina)" ref_fasta: "Reference genome to align reads to" ref_fasta_index: "Reference genome index file (created by bwa index) ref_dict: "Reference genome dictionary file (created by bwa index)" ref_amb: "Reference genome non-ATCG file (created by bwa index)" ref_ann: "Reference genome ref seq annotation file (created by bwa index)" ref_bwt: "Reference genome binary file (created by bwa index)" ref_pac: "Reference genome binary file (created by bwa index)" ref_sa: "Reference genome binary file (created by bwa index)" } # End workflow } task BwaMem { input { File input_fastq File ref_fasta File ref_fasta_index File ref_dict File ref_amb File ref_ann File ref_bwt File ref_pac File ref_sa Int threads = 16 } String read_group_id = "ID:" + base_file_name String sample_name = "SM:" + base_file_name String platform = "illumina" String platform_info = "PL:" + platform # Create the platform information command <<< set -eo pipefail mv "~{ref_fasta}" . mv "~{ref_fasta_index}" . mv "~{ref_dict}" . mv "~{ref_amb}" . mv "~{ref_ann}" . mv "~{ref_bwt}" . mv "~{ref_pac}" . mv "~{ref_sa}" . bwa mem \\ -p -v 3 -t ~{threads} -M -R '@RG\\t~{read_group_id}\\t~{sample_name}\\t~{platform_info}' \\ "~{ref_fasta_local}" "~{input_fastq}" > "~{base_file_name}.sam" samtools view -1bS -@ 15 -o "~{base_file_name}.aligned.bam" "~{base_file_name}.sam" samtools sort -n -@ 15 -o "~{base_file_name}.sorted_query_aligned.bam" "~{base_file_name}.aligned.bam" >>> output { File analysisReadyBam = "~{base_file_name}.aligned.bam" File analysisReadySorted = "~{base_file_name}.sorted_query_aligned.bam" } runtime { memory: "48 GB" cpu: 16 docker: "ghcr.io/getwilds/bwa:0.7.17" disks: "local-disk 100 SSD" } } 3.6 Testing your first task To test your first task and your workflow, you should have expectation of output is. For this first BwaMem task, we just care that the BAM file is created with aligned reads. You can use samtools view output.sorted_query_aligned.bam to examine the reads and pipe it to wordcount wc to get the number of total reads. This number should be almost identical as the number of reads from your input FASTQ file if you run wc input.fastq. In other tasks, we might have a more precise expectation of what the output file should be, such as containing the specific somatic mutation call that we have curated. Here is an example JSON with the test data needed to run this single-task workflow: { "mutation_calling.tumorFastq": "/fh/fast/paguirigan_a/pub/ReferenceDataSets/workflow_testing_data/WDL/wdl_101/HCC4006_final.fastq", "mutation_calling.ref_fasta": "/fh/fast/paguirigan_a/pub/ReferenceDataSets/genome_data/human/hg19/Homo_sapiens_assembly19.fasta", "mutation_calling.ref_fasta_index": "/fh/fast/paguirigan_a/pub/ReferenceDataSets/genome_data/human/hg19/Homo_sapiens_assembly19.fasta.fai", "mutation_calling.ref_dict": "/fh/fast/paguirigan_a/pub/ReferenceDataSets/genome_data/human/hg19/Homo_sapiens_assembly19.dict", "mutation_calling.ref_pac": "/fh/fast/paguirigan_a/pub/ReferenceDataSets/genome_data/human/hg19/Homo_sapiens_assembly19.fasta.pac", "mutation_calling.ref_sa": "/fh/fast/paguirigan_a/pub/ReferenceDataSets/genome_data/human/hg19/Homo_sapiens_assembly19.fasta.sa", "mutation_calling.ref_amb": "/fh/fast/paguirigan_a/pub/ReferenceDataSets/genome_data/human/hg19/Homo_sapiens_assembly19.fasta.amb", "mutation_calling.ref_ann": "/fh/fast/paguirigan_a/pub/ReferenceDataSets/genome_data/human/hg19/Homo_sapiens_assembly19.fasta.ann", "mutation_calling.ref_bwt": "/fh/fast/paguirigan_a/pub/ReferenceDataSets/genome_data/human/hg19/Homo_sapiens_assembly19.fasta.bwt", "mutation_calling.ref_name": "hg19" } If you are not running on the Fred Hutch HPC, you’ll need to modify your JSON file to point to wherever you have the data files stored. You can download the same fastq we’re using from our sandbox repo, and the reference files can be generated via samtools index or downloaded from the Broad Institute’s mirror. "],["connecting-multiple-tasks-together-in-a-linear-chain.html", "Chapter 4 Connecting multiple tasks together in a linear chain 4.1 How to connect tasks together in a workflow 4.2 Writing MarkDuplicates task 4.3 The rest of the linear chain workflow", " Chapter 4 Connecting multiple tasks together in a linear chain Now that you have a first task in a workflow up and running, the next step is to continue building out the workflow as described in our Workflow Plan: BwaMem aligns the samples to the reference genome. MarkDuplicates marks PCR duplicates. ApplyBaseRecalibrator applies base recalibration. Mutect2 performs somatic mutation calling. For this current iteration, we start with Mutect2TumorOnly which only uses the tumor sample. annovar annotates the called somatic mutations. We do this via a linear chain, in which we feed the output of one task to the input of the next task. Let’s see how to build a linear chain in a workflow. 4.1 How to connect tasks together in a workflow We can easily connect tasks together because of the following: The output variables of a task can be accessed at the workflow level as inputs for the subsequent task. For instance, let’s see the output of our BwaMem task. output { File analysisReadyBam = "~{base_file_name}.aligned.bam" File analysisReadySorted = "~{base_file_name}.sorted_query_aligned.bam" } The File variables analysisReadyBam and analysisReadySorted can now be accessed anywhere in the workflow block after the BwaMem task as BwaMem.analysisReadyBam and BwaMem.analysisReadySorted, respectively. Therefore, when we call the MarkDuplicates task, we can pass it the input BwaMem.analysisReadySorted from the BwaMem task: workflow minidata_mutation_calling_v1 { input { # Sample info File sampleFastq # Reference Genome information File ref_fasta File ref_fasta_index File ref_dict File ref_amb File ref_ann File ref_bwt File ref_pac File ref_sa } # Map reads to reference call BwaMem { input: input_fastq = sampleFastq, ref_fasta = ref_fasta, ref_fasta_index = ref_fasta_index, ref_dict = ref_dict, ref_amb = ref_amb, ref_ann = ref_ann, ref_bwt = ref_bwt, ref_pac = ref_pac, ref_sa = ref_sa } call MarkDuplicates { input: input_bam = BwaMem.analysisReadySorted } } Resources: For a basic introduction to linear chain, see OpenWDL Docs’ introduction. To see other examples of linear chain and variations, see OpenWDL Docs’s section on workflow plumbing. 4.2 Writing MarkDuplicates task Of course, the task MarkDuplicates hasn’t been written yet! Let’s go through it together: 4.2.1 Input The task takes an input bam file that has been aligned to the reference genome. It needs to be a File input_bam based on how we introduced it in the workflow above. That is easy to write up: task MarkDuplicates { input { File input_bam } } 4.2.2 Private variables in the task Similar to the BwaMem task, we will name our output files based on the base name of the original input file in the workflow. Therefore, it makes sense to create a private String variable base_file_name that contains this base name of input_bam. We will use base_file_name in the Command section to specify the output bam and metrics files. task MarkDuplicates { input { File input_bam } String base_file_name = basename(input_bam, ".sorted_query_aligned.bam") } 4.2.3 Command As we have been doing all along, we refer to any defined variables from the input or private variables using ~{this} syntax. task MarkDuplicates { input { File input_bam } String base_file_name = basename(input_bam, ".sorted_query_aligned.bam") command <<< gatk MarkDuplicates \\ --INPUT "~{input_bam}" \\ --OUTPUT "~{base_file_name}.duplicates_marked.bam" \\ --METRICS_FILE "~{base_file_name}.duplicate_metrics" \\ --CREATE_INDEX true \\ --OPTICAL_DUPLICATE_PIXEL_DISTANCE 100 \\ --VALIDATION_STRINGENCY SILENT >>> } 4.2.4 Runtime and Output We specify a different Docker image that contains the GATK software, and the relevant computing needs. We also specify three different output files, two of which are specified in the command section, and the third is a bam index file that is automatically created by the command section. Below is the task all together. It has a form very similar to our first task BwaMem. task MarkDuplicates { input { File input_bam } String base_file_name = basename(input_bam, ".sorted_query_aligned.bam") command <<< gatk MarkDuplicates \\ --INPUT "~{input_bam}" \\ --OUTPUT "~{base_file_name}.duplicates_marked.bam" \\ --METRICS_FILE "~{base_file_name}.duplicate_metrics" \\ --CREATE_INDEX true \\ --OPTICAL_DUPLICATE_PIXEL_DISTANCE 100 \\ --VALIDATION_STRINGENCY SILENT >>> runtime { docker: "broadinstitute/gatk:4.1.4.0" memory: "48 GB" cpu: 4 } output { File markDuplicates_bam = "~{base_file_name}.duplicates_marked.bam" File markDuplicates_bai = "~{base_file_name}.duplicates_marked.bai" File duplicate_metrics = "~{base_file_name}.duplicate_metrics" } } 4.2.5 Testing the workflow As before, when you add a new task to the workflow, you should always test that it works on your test sample. To check that MarkDuplicates is indeed marking PCR duplicates, you could check for the presence of the PCR duplicate flag in reads, which has a decimal value of 1024 in the SAM Flags Field. 4.3 The rest of the linear chain workflow We build out the rest of the tasks in a very similar fashion. Tasks ApplyBaseRecalibrator and Mutect2TumorOnly both have files that need to be localized, but otherwise all the tasks have a very similar form as BwaMem and MarkDuplicates. For this current iteration, we use only the tumor sample for mutation calling. In the following chapters, we will use additional WDL features to make use of tumor and normal samples for mutation calling. version 1.0 workflow minidata_mutation_calling_v1 { input { # Sample info File sampleFastq # Reference Genome information File ref_fasta File ref_fasta_index File ref_dict File ref_amb File ref_ann File ref_bwt File ref_pac File ref_sa File dbSNP_vcf File dbSNP_vcf_index File known_indels_sites_VCFs File known_indels_sites_indices File af_only_gnomad File af_only_gnomad_index File annovarTAR String annovar_protocols String annovar_operation String ref_name } # Map reads to reference call BwaMem { input: input_fastq = sampleFastq, ref_fasta = ref_fasta, ref_fasta_index = ref_fasta_index, ref_dict = ref_dict, ref_amb = ref_amb, ref_ann = ref_ann, ref_bwt = ref_bwt, ref_pac = ref_pac, ref_sa = ref_sa } call MarkDuplicates { input: input_bam = BwaMem.analysisReadySorted } call ApplyBaseRecalibrator { input: input_bam = MarkDuplicates.markDuplicates_bam, input_bam_index = MarkDuplicates.markDuplicates_bai, dbSNP_vcf = dbSNP_vcf, dbSNP_vcf_index = dbSNP_vcf_index, known_indels_sites_VCFs = known_indels_sites_VCFs, known_indels_sites_indices = known_indels_sites_indices, ref_dict = ref_dict, ref_fasta = ref_fasta, ref_fasta_index = ref_fasta_index } call Mutect2TumorOnly { input: input_bam = ApplyBaseRecalibrator.recalibrated_bam, input_bam_index = ApplyBaseRecalibrator.recalibrated_bai, ref_dict = ref_dict, ref_fasta = ref_fasta, ref_fasta_index = ref_fasta_index, genomeReference = af_only_gnomad, genomeReferenceIndex = af_only_gnomad_index } call annovar { input: input_vcf = Mutect2TumorOnly.output_vcf, ref_name = ref_name, annovarTAR = annovarTAR, annovar_operation = annovar_operation, annovar_protocols = annovar_protocols } # Outputs that will be retained when execution is complete output { File alignedBamSorted = BwaMem.analysisReadySorted File markDuplicates_bam = MarkDuplicates.markDuplicates_bam File markDuplicates_bai = MarkDuplicates.markDuplicates_bai File analysisReadyBam = ApplyBaseRecalibrator.recalibrated_bam File analysisReadyIndex = ApplyBaseRecalibrator.recalibrated_bai File Mutect_Vcf = Mutect2TumorOnly.output_vcf File Mutect_VcfIndex = Mutect2TumorOnly.output_vcf_index File Mutect_AnnotatedVcf = annovar.output_annotated_vcf File Mutect_AnnotatedTable = annovar.output_annotated_table } } # TASK DEFINITIONS # Align fastq file to the reference genome task BwaMem { input { File input_fastq File ref_fasta File ref_fasta_index File ref_dict File ref_amb File ref_ann File ref_bwt File ref_pac File ref_sa } String base_file_name = basename(input_fastq, ".fastq") String ref_fasta_local = basename(ref_fasta) String read_group_id = "ID:" + base_file_name String sample_name = "SM:" + base_file_name String platform = "illumina" String platform_info = "PL:" + platform # Create the platform information command <<< set -eo pipefail mv ~{ref_fasta} . mv ~{ref_fasta_index} . mv ~{ref_dict} . mv ~{ref_amb} . mv ~{ref_ann} . mv ~{ref_bwt} . mv ~{ref_pac} . mv ~{ref_sa} . bwa mem \\ -p -v 3 -t 16 -M -R '@RG\\t~{read_group_id}\\t~{sample_name}\\t~{platform_info}' \\ ~{ref_fasta_local} ~{input_fastq} > ~{base_file_name}.sam samtools view -1bS -@ 15 -o ~{base_file_name}.aligned.bam ~{base_file_name}.sam samtools sort -@ 15 -o ~{base_file_name}.sorted_query_aligned.bam ~{base_file_name}.aligned.bam >>> output { File analysisReadySorted = "~{base_file_name}.sorted_query_aligned.bam" } runtime { memory: "48 GB" cpu: 16 docker: "fredhutch/bwa:0.7.17" } } # Mark duplicates task MarkDuplicates { input { File input_bam } String base_file_name = basename(input_bam, ".sorted_query_aligned.bam") command <<< gatk MarkDuplicates \\ --INPUT ~{input_bam} \\ --OUTPUT ~{base_file_name}.duplicates_marked.bam \\ --METRICS_FILE ~{base_file_name}.duplicate_metrics \\ --CREATE_INDEX true \\ --OPTICAL_DUPLICATE_PIXEL_DISTANCE 100 \\ --VALIDATION_STRINGENCY SILENT >>> runtime { docker: "broadinstitute/gatk:4.1.4.0" memory: "48 GB" cpu: 4 } output { File markDuplicates_bam = "~{base_file_name}.duplicates_marked.bam" File markDuplicates_bai = "~{base_file_name}.duplicates_marked.bai" File duplicate_metrics = "~{base_file_name}.duplicates_marked.bai" } } # Base quality recalibration task ApplyBaseRecalibrator { input { File input_bam File input_bam_index File dbSNP_vcf File dbSNP_vcf_index File known_indels_sites_VCFs File known_indels_sites_indices File ref_dict File ref_fasta File ref_fasta_index } String base_file_name = basename(input_bam, ".duplicates_marked.bam") String ref_fasta_local = basename(ref_fasta) String dbSNP_vcf_local = basename(dbSNP_vcf) String known_indels_sites_VCFs_local = basename(known_indels_sites_VCFs) command <<< set -eo pipefail mv ~{ref_fasta} . mv ~{ref_fasta_index} . mv ~{ref_dict} . mv ~{dbSNP_vcf} . mv ~{dbSNP_vcf_index} . mv ~{known_indels_sites_VCFs} . mv ~{known_indels_sites_indices} . samtools index ~{input_bam} gatk --java-options "-Xms8g" \\ BaseRecalibrator \\ -R ~{ref_fasta_local} \\ -I ~{input_bam} \\ -O ~{base_file_name}.recal_data.csv \\ --known-sites ~{dbSNP_vcf_local} \\ --known-sites ~{known_indels_sites_VCFs_local} \\ gatk --java-options "-Xms8g" \\ ApplyBQSR \\ -bqsr ~{base_file_name}.recal_data.csv \\ -I ~{input_bam} \\ -O ~{base_file_name}.recal.bam \\ -R ~{ref_fasta_local} \\ #finds the current sort order of this bam file samtools view -H ~{base_file_name}.recal.bam | grep @SQ | sed 's/@SQ\\tSN:\\|LN://g' > ~{base_file_name}.sortOrder.txt >>> output { File recalibrated_bam = "~{base_file_name}.recal.bam" File recalibrated_bai = "~{base_file_name}.recal.bai" File sortOrder = "~{base_file_name}.sortOrder.txt" } runtime { memory: "36 GB" cpu: 2 docker: "broadinstitute/gatk:4.1.4.0" } } # Mutect 2 calling task Mutect2TumorOnly { input { File input_bam File input_bam_index File ref_dict File ref_fasta File ref_fasta_index File genomeReference File genomeReferenceIndex } String base_file_name = basename(input_bam, ".recal.bam") String ref_fasta_local = basename(ref_fasta) String genomeReference_local = basename(genomeReference) command <<< set -eo pipefail mv ~{ref_fasta} . mv ~{ref_fasta_index} . mv ~{ref_dict} . mv ~{genomeReference} . mv ~{genomeReferenceIndex} . gatk --java-options "-Xms16g" Mutect2 \\ -R ~{ref_fasta_local} \\ -I ~{input_bam} \\ -O preliminary.vcf.gz \\ --germline-resource ~{genomeReference_local} \\ gatk --java-options "-Xms16g" FilterMutectCalls \\ -V preliminary.vcf.gz \\ -O ~{base_file_name}.mutect2.vcf.gz \\ -R ~{ref_fasta_local} \\ --stats preliminary.vcf.gz.stats \\ >>> runtime { docker: "broadinstitute/gatk:4.1.4.0" memory: "24 GB" cpu: 1 } output { File output_vcf = "${base_file_name}.mutect2.vcf.gz" File output_vcf_index = "${base_file_name}.mutect2.vcf.gz.tbi" } } # annotate with annovar task annovar { input { File input_vcf String ref_name File annovarTAR String annovar_protocols String annovar_operation } String base_vcf_name = basename(input_vcf, ".vcf.gz") command <<< set -eo pipefail tar -xzvf ~{annovarTAR} perl annovar/table_annovar.pl ~{input_vcf} annovar/humandb/ \\ -buildver ~{ref_name} \\ -outfile ~{base_vcf_name} \\ -remove \\ -protocol ~{annovar_protocols} \\ -operation ~{annovar_operation} \\ -nastring . -vcfinput >>> runtime { docker : "perl:5.28.0" cpu: 1 memory: "2GB" } output { File output_annotated_vcf = "${base_vcf_name}.${ref_name}_multianno.vcf" File output_annotated_table = "${base_vcf_name}.${ref_name}_multianno.txt" } } "],["organizing-variables-via-structs.html", "Chapter 5 Organizing variables via Structs", " Chapter 5 Organizing variables via Structs In our workflow so far, we see that certain variables are always used together, even for different tasks. For example, variables related to the reference genome are always used for the same purpose and passed on to tasks in almost the same way. This leads to quite a bit of coding redundancy, as when we write down the large set of variables related to the reference genome as task inputs, we are just thinking about one entity. We don’t make distinctions of the reference genome files until the task body itself. To improve code organization and readability, we can package all variables related to the reference genome into a compound data structure called a struct. With a struct variable, we can refer all the packaged variables as one single variable, and also refer to specific variables within the struct without losing any information. OpenWDL Docs also has an excellent introduction and examples on structs. To define a struct, we must declare it outside of a workflow and task: struct referenceGenome { File ref_fasta File ref_fasta_index File ref_dict File ref_amb File ref_ann File ref_bwt File ref_pac File ref_sa String ref_name } workflow minidata_mutation_calling_v1 { input { File sampleFastq referenceGenome refGenome ... } # Map reads to reference call BwaMem { input: input_fastq = sampleFastq, refGenome = refGenome } } The referenceGenome struct contains all the variables related to the reference genome, but values cannot be defined here. The struct definition merely lays the skeleton components of the data structure, but contains no actual values. In our workflow inputs, we remove all of the File variables associated with reference genome definitions, but keep anything that isn’t related to the reference genome, such as sampleFastq. We instead declare a referenceGenome struct variable called refGenome via referenceGenome refGenome. We can access the variables within a struct by the following syntax: structVar.varName, such as refGenome.ref_name. The WDL spec has more information on how to define and use structs. To give values to refGenome, we need to modify our JSON metadata file. We define the refGenome variable in a nested structure that corresponds to the referenceGenome struct. Let’s take a look: { "minidata_mutation_calling_v1.refGenome": { "ref_fasta": "/fh/fast/paguirigan_a/pub/ReferenceDataSets/genome_data/human/hg19/Homo_sapiens_assembly19.fasta", "ref_fasta_index": "/fh/fast/paguirigan_a/pub/ReferenceDataSets/genome_data/human/hg19/Homo_sapiens_assembly19.fasta.fai", "ref_dict": "/fh/fast/paguirigan_a/pub/ReferenceDataSets/genome_data/human/hg19/Homo_sapiens_assembly19.dict", "ref_pac": "/fh/fast/paguirigan_a/pub/ReferenceDataSets/genome_data/human/hg19/Homo_sapiens_assembly19.fasta.pac", "ref_sa": "/fh/fast/paguirigan_a/pub/ReferenceDataSets/genome_data/human/hg19/Homo_sapiens_assembly19.fasta.sa", "ref_amb": "/fh/fast/paguirigan_a/pub/ReferenceDataSets/genome_data/human/hg19/Homo_sapiens_assembly19.fasta.amb", "ref_ann": "/fh/fast/paguirigan_a/pub/ReferenceDataSets/genome_data/human/hg19/Homo_sapiens_assembly19.fasta.ann", "ref_bwt": "/fh/fast/paguirigan_a/pub/ReferenceDataSets/genome_data/human/hg19/Homo_sapiens_assembly19.fasta.bwt", "ref_name": "hg19" }, "minidata_mutation_calling_v1.dbSNP_vcf_index": "/fh/fast/paguirigan_a/pub/ReferenceDataSets/genome_data/human/hg19/dbsnp_138.b37.vcf.gz.tbi", ... } Now refGenome has all the values it needs for our tasks. In addition, we have replaced all the reference genome inputs in call BwaMem with refGenome in order to pass information to a task via structs. Within the BwaMem task, we must refer to variables inside the struct, such as refGenome.ref_name (which has a value of “hg19” using this JSON metadata): task BwaMem { input { File input_fastq referenceGenome refGenome } String base_file_name = basename(input_fastq, ".fastq") String ref_fasta_local = basename(refGenome.ref_fasta) String read_group_id = "ID:" + base_file_name String sample_name = "SM:" + base_file_name String platform = "illumina" String platform_info = "PL:" + platform # Create the platform information command <<< set -eo pipefail #can we iterate through a struct?? mv ~{refGenome.ref_fasta} . mv ~{refGenome.ref_fasta_index} . mv ~{refGenome.ref_dict} . mv ~{refGenome.ref_amb} . mv ~{refGenome.ref_ann} . mv ~{refGenome.ref_bwt} . mv ~{refGenome.ref_pac} . mv ~{refGenome.ref_sa} . bwa mem \\ -p -v 3 -t 16 -M -R '@RG\\t~{read_group_id}\\t~{sample_name}\\t~{platform_info}' \\ ~{ref_fasta_local} ~{input_fastq} > ~{base_file_name}.sam samtools view -1bS -@ 15 -o ~{base_file_name}.aligned.bam ~{base_file_name}.sam samtools sort -@ 15 -o ~{base_file_name}.sorted_query_aligned.bam ~{base_file_name}.aligned.bam >>> output { File analysisReadySorted = "~{base_file_name}.sorted_query_aligned.bam" } runtime { memory: "48 GB" cpu: 16 docker: "fredhutch/bwa:0.7.17" } } Other tasks in the workflow, such as ApplyBaseRecalibrator and Mutect2TumorOnly also make use of the reference genome, so we pass refGenome to it. The final task annovar only requires the reference genome name, and none of the files in the referenceGenome struct. We make a stylistic choice to pass only refGenome.ref_name to the input of annovar task call, as the task doesn’t need the full information of the struct. This stylistic choice is based on the principle of passing on the minimally needed information for a modular piece of code to run, which makes the task annovar depend on the minimal amount of inputs. This will also save us time and disk space by not having to localize several gigabytes of reference files into the Docker container that annovar will be running in. call annovar { input: input_vcf = Mutect2TumorOnly.output_vcf, ref_name = refGenome.ref_name, annovarTAR = annovarTAR, annovar_operation = annovar_operation, annovar_protocols = annovar_protocols } Putting everything together in the workflow: struct referenceGenome { File ref_fasta File ref_fasta_index File ref_dict File ref_amb File ref_ann File ref_bwt File ref_pac File ref_sa String ref_name } workflow minidata_mutation_calling_v1 { input { File sampleFastq referenceGenome refGenome File dbSNP_vcf File dbSNP_vcf_index File known_indels_sites_VCFs File known_indels_sites_indices File af_only_gnomad File af_only_gnomad_index File annovarTAR String annovar_protocols String annovar_operation } # Map reads to reference call BwaMem { input: input_fastq = sampleFastq, refGenome = refGenome } call MarkDuplicates { input: input_bam = BwaMem.analysisReadySorted } call ApplyBaseRecalibrator { input: input_bam = MarkDuplicates.markDuplicates_bam, input_bam_index = MarkDuplicates.markDuplicates_bai, dbSNP_vcf = dbSNP_vcf, dbSNP_vcf_index = dbSNP_vcf_index, known_indels_sites_VCFs = known_indels_sites_VCFs, known_indels_sites_indices = known_indels_sites_indices, refGenome = refGenome } call Mutect2TumorOnly { input: input_bam = ApplyBaseRecalibrator.recalibrated_bam, input_bam_index = ApplyBaseRecalibrator.recalibrated_bai, refGenome = refGenome, genomeReference = af_only_gnomad, genomeReferenceIndex = af_only_gnomad_index } call annovar { input: input_vcf = Mutect2TumorOnly.output_vcf, ref_name = refGenome.ref_name, annovarTAR = annovarTAR, annovar_operation = annovar_operation, annovar_protocols = annovar_protocols } # Outputs that will be retained when execution is complete output { File alignedBamSorted = BwaMem.analysisReadySorted File markDuplicates_bam = MarkDuplicates.markDuplicates_bam File markDuplicates_bai = MarkDuplicates.markDuplicates_bai File analysisReadyBam = ApplyBaseRecalibrator.recalibrated_bam File analysisReadyIndex = ApplyBaseRecalibrator.recalibrated_bai File Mutect_Vcf = Mutect2TumorOnly.output_vcf File Mutect_VcfIndex = Mutect2TumorOnly.output_vcf_index File Mutect_AnnotatedVcf = annovar.output_annotated_vcf File Mutect_AnnotatedTable = annovar.output_annotated_table } } # TASK DEFINITIONS # Align fastq file to the reference genome task BwaMem { input { File input_fastq referenceGenome refGenome } String base_file_name = basename(input_fastq, ".fastq") String ref_fasta_local = basename(refGenome.ref_fasta) String read_group_id = "ID:" + base_file_name String sample_name = "SM:" + base_file_name String platform = "illumina" String platform_info = "PL:" + platform # Create the platform information command <<< set -eo pipefail #can we iterate through a struct?? mv ~{refGenome.ref_fasta} . mv ~{refGenome.ref_fasta_index} . mv ~{refGenome.ref_dict} . mv ~{refGenome.ref_amb} . mv ~{refGenome.ref_ann} . mv ~{refGenome.ref_bwt} . mv ~{refGenome.ref_pac} . mv ~{refGenome.ref_sa} . bwa mem \\ -p -v 3 -t 16 -M -R '@RG\\t~{read_group_id}\\t~{sample_name}\\t~{platform_info}' \\ ~{ref_fasta_local} ~{input_fastq} > ~{base_file_name}.sam samtools view -1bS -@ 15 -o ~{base_file_name}.aligned.bam ~{base_file_name}.sam samtools sort -@ 15 -o ~{base_file_name}.sorted_query_aligned.bam ~{base_file_name}.aligned.bam >>> output { File analysisReadySorted = "~{base_file_name}.sorted_query_aligned.bam" } runtime { memory: "48 GB" cpu: 16 docker: "fredhutch/bwa:0.7.17" } } # Mark duplicates (not SPARK, for some reason that does something weird) task MarkDuplicates { input { File input_bam } String base_file_name = basename(input_bam, ".sorted_query_aligned.bam") String output_bam = "~{base_file_name}.duplicates_marked.bam" String output_bai = "~{base_file_name}.duplicates_marked.bai" String metrics_file = "~{base_file_name}.duplicate_metrics" command <<< gatk MarkDuplicates \\ --INPUT ~{input_bam} \\ --OUTPUT ~{output_bam} \\ --METRICS_FILE ~{metrics_file} \\ --CREATE_INDEX true \\ --OPTICAL_DUPLICATE_PIXEL_DISTANCE 100 \\ --VALIDATION_STRINGENCY SILENT >>> runtime { docker: "broadinstitute/gatk:4.1.4.0" memory: "48 GB" cpu: 4 } output { File markDuplicates_bam = "~{output_bam}" File markDuplicates_bai = "~{output_bai}" File duplicate_metrics = "~{metrics_file}" } } # Base quality recalibration task ApplyBaseRecalibrator { input { File input_bam File input_bam_index File dbSNP_vcf File dbSNP_vcf_index File known_indels_sites_VCFs File known_indels_sites_indices referenceGenome refGenome } String base_file_name = basename(input_bam, ".duplicates_marked.bam") String ref_fasta_local = basename(refGenome.ref_fasta) String dbSNP_vcf_local = basename(dbSNP_vcf) String known_indels_sites_VCFs_local = basename(known_indels_sites_VCFs) command <<< set -eo pipefail mv ~{refGenome.ref_fasta} . mv ~{refGenome.ref_fasta_index} . mv ~{refGenome.ref_dict} . mv ~{dbSNP_vcf} . mv ~{dbSNP_vcf_index} . mv ~{known_indels_sites_VCFs} . mv ~{known_indels_sites_indices} . samtools index ~{input_bam} #redundant? markduplicates already does this? gatk --java-options "-Xms8g" \\ BaseRecalibrator \\ -R ~{ref_fasta_local} \\ -I ~{input_bam} \\ -O ~{base_file_name}.recal_data.csv \\ --known-sites ~{dbSNP_vcf_local} \\ --known-sites ~{known_indels_sites_VCFs_local} \\ gatk --java-options "-Xms8g" \\ ApplyBQSR \\ -bqsr ~{base_file_name}.recal_data.csv \\ -I ~{input_bam} \\ -O ~{base_file_name}.recal.bam \\ -R ~{ref_fasta_local} \\ #finds the current sort order of this bam file samtools view -H ~{base_file_name}.recal.bam | grep @SQ | sed 's/@SQ\\tSN:\\|LN://g' > ~{base_file_name}.sortOrder.txt >>> output { File recalibrated_bam = "~{base_file_name}.recal.bam" File recalibrated_bai = "~{base_file_name}.recal.bai" File sortOrder = "~{base_file_name}.sortOrder.txt" } runtime { memory: "36 GB" cpu: 2 docker: "broadinstitute/gatk:4.1.4.0" } } # Mutect 2 calling task Mutect2TumorOnly { input { File input_bam File input_bam_index referenceGenome refGenome File genomeReference File genomeReferenceIndex } String base_file_name = basename(input_bam, ".recal.bam") String ref_fasta_local = basename(refGenome.ref_fasta) String genomeReference_local = basename(genomeReference) command <<< set -eo pipefail mv ~{refGenome.ref_fasta} . mv ~{refGenome.ref_fasta_index} . mv ~{refGenome.ref_dict} . mv ~{genomeReference} . mv ~{genomeReferenceIndex} . gatk --java-options "-Xms16g" Mutect2 \\ -R ~{ref_fasta_local} \\ -I ~{input_bam} \\ -O preliminary.vcf.gz \\ --germline-resource ~{genomeReference_local} \\ gatk --java-options "-Xms16g" FilterMutectCalls \\ -V preliminary.vcf.gz \\ -O ~{base_file_name}.mutect2.vcf.gz \\ -R ~{ref_fasta_local} \\ --stats preliminary.vcf.gz.stats \\ >>> runtime { docker: "broadinstitute/gatk:4.1.4.0" memory: "24 GB" cpu: 1 } output { File output_vcf = "${base_file_name}.mutect2.vcf.gz" File output_vcf_index = "${base_file_name}.mutect2.vcf.gz.tbi" } } # annotate with annovar task annovar { input { File input_vcf String ref_name File annovarTAR String annovar_protocols String annovar_operation } String base_vcf_name = basename(input_vcf, ".vcf.gz") command <<< set -eo pipefail tar -xzvf ~{annovarTAR} perl annovar/table_annovar.pl ~{input_vcf} annovar/humandb/ \\ -buildver ~{ref_name} \\ -outfile ~{base_vcf_name} \\ -remove \\ -protocol ~{annovar_protocols} \\ -operation ~{annovar_operation} \\ -nastring . -vcfinput >>> runtime { docker : "perl:5.28.0" cpu: 1 memory: "2GB" } output { File output_annotated_vcf = "${base_vcf_name}.${ref_name}_multianno.vcf" File output_annotated_table = "${base_vcf_name}.${ref_name}_multianno.txt" } } "],["using-arrays-for-parallelization-and-other-use-cases.html", "Chapter 6 Using Arrays For Parallelization and Other Use Cases 6.1 The array type 6.2 Scattered tasks 6.3 Making our workflow run on multiple samples at once using scattered tasks and arrays 6.4 Referencing an array in a task 6.5 The workflow so far", " Chapter 6 Using Arrays For Parallelization and Other Use Cases We have a workflow that runs on a single sample. What if we want to process multiple samples at once? Let’s look at the various ways we can run our workflow more efficiently, as well as processing many samples in parallel. This is where WDL really shines. In this chapter, we’ll be going over: How to use scattered tasks to run a workflow on multiple samples at once How to use arrays effectively How to reference arrays in a task’s command section How arrays differ from Structs 6.1 The array type Arrays are essentially lists of another primitive type. It is most common to see Array[File] in WDLs, but an array can contain integers, floats, strings, and the like. An array can only have one of a given primitive type. For example, an Array[String] could contain the strings “cat” and “dog” but not the integer 1965 (however, it could have “1965” as a string). In chapter 4, we went over the struct data type and used it to handle a myriad of reference genome files. Arrays differ from structs in that arrays are numerically indexed, which means that a member of the array can be accessed by its position in the array. On the other hand, each variable within a struct has its own name, and you use that name to reference it rather than a numerical index. In WDL, arrays are 0 indexed, so the “first” value in an array is referenced by [0]. As per the WDL spec, arrays retain their order and are immutable – if you explicitly define an Array[String] with the members [“foo”, “bar”, “bizz”], you can be confident that “foo” will always be at index 0. Array[String] foobarbizz = ["foo", "bar", "bizz"] String foo = foobarbizz[0] # will always be "foo" Because arrays are immutable, if you wish to add values to an array, you will need to define a new array. 6.2 Scattered tasks Scattered tasks allow us to run a WDL task in parallel. This is especially useful on highly scalable backends such as HPCs or the cloud, as it allows us to potentially run hundreds or even thousands of instances of a task at the same time. The most common use case for this is processing many samples at the same time, but it can also be used for processing a single sample’s chromosomes in parallel, or similar situations where breaking up data into discrete “chunks” makes sense. It should be noted that a scattered task does not work the same way as multithreading, nor does it correlate with the cpu WDL runtime attribute. Every instance of a scattered task takes place in a new Docker image, and is essentially “unaware” of all other instances of that scattered task, with one exception: If an instance of a scattered task errors out, a WDL executor may attempt to shut down other ongoing instances of that scattered task. 6.2.1 Troubleshooting Scattered tasks are relatively simple in theory, but the way they interact with optional types can be unintuitive. As a general rule, you should avoid using optional types as the input of a scattered task whenever possible. Generally speaking, a WDL executor will try to run as many instances of a scattered task as it thinks your backend’s hardware can handle at once. Sometimes the WDL executor will overestimate what the backend is capable of and run too many instances of a scattered task at once. This almost never happens on scalable cloud-based backends such as Terra, but isn’t uncommon when running scattered tasks on a local machine. 6.3 Making our workflow run on multiple samples at once using scattered tasks and arrays When we originally wrote our workflow, we designed it to only run on a single sample at a time. However, we’ll likely want to run this workflow on multiple samples at the same time. For some workflows, this is a great way to directly compare samples to each other, but for our purposes we simply want to avoid running a workflow 100 times if we can instead run one workflow that handles 100 samples at once. For starters, we’ll want to change our workflow-level sample variables from File to Array[File]. However, we don’t need to change any of the reference genome files, because every instance of our tasks will be taking in the same reference genome files. In other words, our struct is unchanged. version 1.0 struct referenceGenome { File ref_fasta File ref_fasta_index File ref_dict File ref_amb File ref_ann File ref_bwt File ref_pac File ref_sa String ref_name } workflow minidata_mutation_calling_chapter6 { input { Array[File] tumorSamples referenceGenome refGenome File dbSNP_vcf File dbSNP_vcf_index File known_indels_sites_VCFs File known_indels_sites_indices } Next, we will want to look at our chain of tasks. Each of these tasks are designed to take in a single sample. In theory, we could rewrite each task to iterate through an array of multiple samples. However, it’s much simpler to keep those tasks as single-sample tasks, and simply run them on one sample at a time. To do this, we encapsulate the task calls in the workflow document with scatter. scatter (tumorFastq in tumorSamples) { call BwaMem as tumorBwaMem { input: input_fastq = tumorFastq, refGenome = refGenome } call MarkDuplicates as tumorMarkDuplicates { input: input_bam = tumorBwaMem.analysisReadySorted } call ApplyBaseRecalibrator as tumorApplyBaseRecalibrator{ input: input_bam = tumorMarkDuplicates.markDuplicates_bam, input_bam_index = tumorMarkDuplicates.markDuplicates_bai, dbSNP_vcf = dbSNP_vcf, dbSNP_vcf_index = dbSNP_vcf_index, known_indels_sites_VCFs = known_indels_sites_VCFs, known_indels_sites_indices = known_indels_sites_indices, refGenome = refGenome } } A scatter is essentially the WDL version of a for loop. Every task within that loop will have access to a single File within the Array[File] that it is looping through. Within the scatter, downstream tasks can access outputs of upstream tasks like normal. They can only “see” one file at a time. However, outside the scatter, every task is considered in the context of every sample, so every output of those scattered tasks become arrays. As a result, our outputs are now Array[File] instead of just File. output { Array[File] tumoralignedBamSorted = tumorBwaMem.analysisReadySorted Array[File] tumorMarkDuplicates_bam = tumorMarkDuplicates.markDuplicates_bam Array[File] tumorMarkDuplicates_bai = tumorMarkDuplicates.markDuplicates_bai Array[File] tumoranalysisReadyBam = tumorApplyBaseRecalibrator.recalibrated_bam Array[File] tumoranalysisReadyIndex = tumorApplyBaseRecalibrator.recalibrated_bai } You can reference a full copy of this workflow at the end of this chapter. 6.4 Referencing an array in a task Because each task only takes in one sample, we are not directly inputting arrays into a file. However, it’s important to know how to do this. If a task’s input variable is an array, we must include an array separator. In WDL 1.0, this is done using the sep= expression placeholder. Every value in the WDL Array[String] will be separated by whatever value is declared via sep. In this example, that is a simple space, as that is one way how to construct a bash variable. task count_words { input { Array[String] a_big_sentence } command <<< ARRAY_OF_WORDS=(~{sep=" " a_big_sentence}) echo ${#ARRAY_OF_FILES[@]} >> length.txt # Note how the bash array uses ${} syntax, which could quickly get # confusing if we used that syntax for our WDL variables. This is # why we recommend using tilde + {} for your WDL variables. >>> } It’s usually unnecessary to declare an Array[String], because a single String can have many words in it. That being said, an Array[String] can sometimes come in handy if it is made up of outputs from other tasks. We’ll talk more about chaining tasks together in upcoming chapters. The WDL 1.1 spec added a new built-in function, sep(), which replaces the sep= expression placeholder for arrays. This same version of the spec also notes that the sep= expression placeholder are deprecated and will be removed from future versions of WDL. For the time being, we recommend sticking with sep= as it is compatible with both WDL 1.0 and WDL 1.1, even though it is technically deprecated in WDL 1.1. If you’re not used to working in bash, the syntax for interacting with bash arrays can be unintuitive, but you don’t have to write a WDL’s command section only using bash. In fact, working in another language besides bash within a WDL can be a great way to write code quickly, or perform tasks that are more advanced than what a typical bash script can handle. Just be sure to set sep properly to ensure that your array is interpreted correctly. In this example, we place quotation marks before and after the variable to ensure that the first and last value of the list are given beginning and ending quotation marks respectively. task count_words_python { input { Array[String] a_big_sentence } command <<< python << CODE sentence = [ "~{sep='", "' a_big_sentence}" ] print(len(sentence)) CODE >>> runtime { docker: "python:latest" } } 6.5 The workflow so far version 1.0 ## Workflow developed by Sitapriya Moorthi @ Fred Hutch LMD: 01/10/24 for use by DaSL @ Fred Hutch. ## Workflow updated on: 02/28/24 by Ash O'Farrell (UCSC) struct referenceGenome { File ref_fasta File ref_fasta_index File ref_dict File ref_amb File ref_ann File ref_bwt File ref_pac File ref_sa String ref_name } workflow minidata_mutation_calling_chapter6 { input { Array[File] tumorSamples referenceGenome refGenome File dbSNP_vcf File dbSNP_vcf_index File known_indels_sites_VCFs File known_indels_sites_indices } # Scatter for tumor samples scatter (tumorFastq in tumorSamples) { call BwaMem as tumorBwaMem { input: input_fastq = tumorFastq, refGenome = refGenome } call MarkDuplicates as tumorMarkDuplicates { input: input_bam = tumorBwaMem.analysisReadySorted } call ApplyBaseRecalibrator as tumorApplyBaseRecalibrator{ input: input_bam = tumorMarkDuplicates.markDuplicates_bam, input_bam_index = tumorMarkDuplicates.markDuplicates_bai, dbSNP_vcf = dbSNP_vcf, dbSNP_vcf_index = dbSNP_vcf_index, known_indels_sites_VCFs = known_indels_sites_VCFs, known_indels_sites_indices = known_indels_sites_indices, refGenome = refGenome } } output { Array[File] tumoralignedBamSorted = tumorBwaMem.analysisReadySorted Array[File] tumorMarkDuplicates_bam = tumorMarkDuplicates.markDuplicates_bam Array[File] tumorMarkDuplicates_bai = tumorMarkDuplicates.markDuplicates_bai Array[File] tumoranalysisReadyBam = tumorApplyBaseRecalibrator.recalibrated_bam Array[File] tumoranalysisReadyIndex = tumorApplyBaseRecalibrator.recalibrated_bai } } # TASK DEFINITIONS # Align fastq file to the reference genome task BwaMem { input { File input_fastq referenceGenome refGenome Int threads = 16 } String base_file_name = basename(input_fastq, ".fastq") String ref_fasta_local = basename(refGenome.ref_fasta) String read_group_id = "ID:" + base_file_name String sample_name = "SM:" + base_file_name String platform = "illumina" String platform_info = "PL:" + platform # Create the platform information command <<< set -eo pipefail mv ~{refGenome.ref_fasta} . mv ~{refGenome.ref_fasta_index} . mv ~{refGenome.ref_dict} . mv ~{refGenome.ref_amb} . mv ~{refGenome.ref_ann} . mv ~{refGenome.ref_bwt} . mv ~{refGenome.ref_pac} . mv ~{refGenome.ref_sa} . bwa mem \\ -p -v 3 -t ~{threads} -M -R '@RG\\t~{read_group_id}\\t~{sample_name}\\t~{platform_info}' \\ ~{ref_fasta_local} ~{input_fastq} > ~{base_file_name}.sam samtools view -1bS -@ 15 -o ~{base_file_name}.aligned.bam ~{base_file_name}.sam samtools sort -@ 15 -o ~{base_file_name}.sorted_query_aligned.bam ~{base_file_name}.aligned.bam >>> output { File analysisReadySorted = "~{base_file_name}.sorted_query_aligned.bam" } runtime { memory: "48 GB" cpu: 16 docker: "ghcr.io/getwilds/bwa:0.7.17" } } # Mark duplicates task MarkDuplicates { input { File input_bam } String base_file_name = basename(input_bam, ".sorted_query_aligned.bam") String output_bam = "~{base_file_name}.duplicates_marked.bam" String output_bai = "~{base_file_name}.duplicates_marked.bai" String metrics_file = "~{base_file_name}.duplicate_metrics" command <<< gatk MarkDuplicates \\ --INPUT ~{input_bam} \\ --OUTPUT ~{output_bam} \\ --METRICS_FILE ~{metrics_file} \\ --CREATE_INDEX true \\ --OPTICAL_DUPLICATE_PIXEL_DISTANCE 100 \\ --VALIDATION_STRINGENCY SILENT >>> runtime { docker: "ghcr.io/getwilds/gatk:4.3.0.0" memory: "48 GB" cpu: 4 } output { File markDuplicates_bam = "~{output_bam}" File markDuplicates_bai = "~{output_bai}" File duplicate_metrics = "~{metrics_file}" } } # Base quality recalibration task ApplyBaseRecalibrator { input { File input_bam File input_bam_index File dbSNP_vcf File dbSNP_vcf_index File known_indels_sites_VCFs File known_indels_sites_indices referenceGenome refGenome } String base_file_name = basename(input_bam, ".duplicates_marked.bam") String ref_fasta_local = basename(refGenome.ref_fasta) String dbSNP_vcf_local = basename(dbSNP_vcf) String known_indels_sites_VCFs_local = basename(known_indels_sites_VCFs) command <<< set -eo pipefail mv ~{refGenome.ref_fasta} . mv ~{refGenome.ref_fasta_index} . mv ~{refGenome.ref_dict} . mv ~{dbSNP_vcf} . mv ~{dbSNP_vcf_index} . mv ~{known_indels_sites_VCFs} . mv ~{known_indels_sites_indices} . samtools index ~{input_bam} #redundant? markduplicates already does this? gatk --java-options "-Xms8g" \\ BaseRecalibrator \\ -R ~{ref_fasta_local} \\ -I ~{input_bam} \\ -O ~{base_file_name}.recal_data.csv \\ --known-sites ~{dbSNP_vcf_local} \\ --known-sites ~{known_indels_sites_VCFs_local} \\ gatk --java-options "-Xms8g" \\ ApplyBQSR \\ -bqsr ~{base_file_name}.recal_data.csv \\ -I ~{input_bam} \\ -O ~{base_file_name}.recal.bam \\ -R ~{ref_fasta_local} \\ #finds the current sort order of this bam file samtools view -H ~{base_file_name}.recal.bam | grep @SQ | sed 's/@SQ\\tSN:\\|LN://g' > ~{base_file_name}.sortOrder.txt >>> output { File recalibrated_bam = "~{base_file_name}.recal.bam" File recalibrated_bai = "~{base_file_name}.recal.bai" File sortOrder = "~{base_file_name}.sortOrder.txt" } runtime { memory: "36 GB" cpu: 2 docker: "ghcr.io/getwilds/gatk:4.3.0.0" } } "],["task-aliasing.html", "Chapter 7 Task Aliasing 7.1 Advantages of aliasing 7.2 Start to add a task alias 7.3 Add the task alias 7.4 Modify your output 7.5 Alias for other tasks", " Chapter 7 Task Aliasing We’ve already gone over running a task multiple times in the context of scattered tasks. However, you may also want a task to run more than one time if that task is to run on multiple sets of inputs. In our case, we want to run a similar analysis on tumor samples and samples taken from normal tissue. WDL has a sophisticated feature that allows one to reuse the same task repeatedly through your workflow: task aliasing Simply put task aliasing allows for the re-use of task definitions within the same workflow under different names, or “aliases”. 7.1 Advantages of aliasing The major advantages of using task aliasing are: 1. Reduces Redundancy: You don’t need to copy and paste the same task definition multiple times and your workflows will be more concise and organized. 2. Simplifies Maintenance : If you decide to change/update/fix a task, using task aliasing will make life easy as you need to update only once in your workflow. 3. Enhances Readability and Clarity: A shorter workflow is easier to read but task aliasing also helps to contextualize the workflow ( for example are you doing this task for Sample set A or Sample set B) 4. Facilitates Modular Workflow Design: Task aliasing help to make your workflow modular. This is easier to adopt by 5. Improves Workflow Scalability: Using task aliasing it is much easier to scale the workflow across different inputs. For example you want to run a task on different sample groups (Sample set A and B) will allow the same task to be run parallely and if you choose with different modifications. 6. Ensures Consistency: Task aliasing assures that there is consistency in replicated tasks and helps the reader easily identify where changes are expected in a task. 7.2 Start to add a task alias You can only alias a task that is already defined, so we will start with the BwaMem task rather than writing a new one. Note: In the real world, typically two samples would be processed from a patient: One tumor and one normal. However, we are writing a workflow that only takes in one normal sample and multiple tumor samples. This implies that we have taken multiple tumor samples from the same patient, and we’re comparing all of them against a single normal sample. Here we are creating an alias for the task BwaMem. We want to do this so it can run this task on the “normal” samples and store them seperately. First, make sure that in your workflow input, you reference to the normal samples as input. workflow mutation_calling { input { ... File normalSamples ... } Next all that you need to do is call the task you want to alias and use as to the alias_of_your_choice. But don’t forget to make sure that all the inputs reflect actually different things we want to run this task on. In this case we will be using a different sample and therefore the input_fastq is directed to the appropriate file source. 7.3 Add the task alias call BwaMem as normalBwaMem { input: input_fastq = normalSamples, refGenome = refGenome } 7.4 Modify your output And finally you will also want to make sure that in your outputs section you are saving the appropriate outputs to reflect the task alias. output { File normalalignedBamSorted = normalBwaMem.analysisReadySorted } 7.5 Alias for other tasks We can do this for the other two tasks in our workflow as well call MarkDuplicates as normalMarkDuplicates { input: input_bam = normalBwaMem.analysisReadySorted } call ApplyBaseRecalibrator as normalApplyBaseRecalibrator { input: input_bam = normalMarkDuplicates.markDuplicates_bam, input_bam_index = normalMarkDuplicates.markDuplicates_bai, dbSNP_vcf = dbSNP_vcf, dbSNP_vcf_index = dbSNP_vcf_index, known_indels_sites_VCFs = known_indels_sites_VCFs, known_indels_sites_indices = known_indels_sites_indices, refGenome = refGenome } Now adding these steps to the workflow we will have our tumor and normal sample aligned and recalibrated and suitable for ingestion into the mutation calling step for a paired mutation calling using MuTect2. # The full workflow with task alias version 1.0 ## WDL 101 example workflow ## ## This WDL workflow is intended to be used along with the WDL 101 docs. ## This workflow should be used for inspiration purposes only. ## ## We use three samples ## Samples: ## MOLM13: Normal sample ## CALU1: KRAS G12C mutant ## HCC4006: EGFR Ex19 deletion mutant ## ## Input requirements: ## - combined fastq files for chromosome 12 and 7 +/- 200bp around the sites of mutation only ## ## Output Files: ## - An aligned bam for all 3 samples (with duplicates marked and base quality recalibrated) ## ## Workflow developed by Sitapriya Moorthi, Chris Lo and Taylor Firman @ Fred Hutch and Ash (Aisling) O'Farrell @ UCSC LMD: 02/28/24 for use @ Fred Hutch. struct referenceGenome { File ref_fasta File ref_fasta_index File ref_dict File ref_amb File ref_ann File ref_bwt File ref_pac File ref_sa String ref_name } workflow mutation_calling { input { Array[File] tumorSamples File normalSamples referenceGenome refGenome File dbSNP_vcf File dbSNP_vcf_index File known_indels_sites_VCFs File known_indels_sites_indices File af_only_gnomad File af_only_gnomad_index String annovar_protocols String annovar_operation } # Scatter for "tumor" samples scatter (tumorFastq in tumorSamples) { call BwaMem as tumorBwaMem { input: input_fastq = tumorFastq, refGenome = refGenome } call MarkDuplicates as tumorMarkDuplicates { input: input_bam = tumorBwaMem.analysisReadySorted } call ApplyBaseRecalibrator as tumorApplyBaseRecalibrator{ input: input_bam = tumorMarkDuplicates.markDuplicates_bam, input_bam_index = tumorMarkDuplicates.markDuplicates_bai, dbSNP_vcf = dbSNP_vcf, dbSNP_vcf_index = dbSNP_vcf_index, known_indels_sites_VCFs = known_indels_sites_VCFs, known_indels_sites_indices = known_indels_sites_indices, refGenome = refGenome } call Mutect2Paired { input: tumor_bam = tumorApplyBaseRecalibrator.recalibrated_bam, tumor_bam_index = tumorApplyBaseRecalibrator.recalibrated_bai, normal_bam = normalApplyBaseRecalibrator.recalibrated_bam, normal_bam_index = normalApplyBaseRecalibrator.recalibrated_bai, refGenome = refGenome, genomeReference = af_only_gnomad, genomeReferenceIndex = af_only_gnomad_index } call annovar { input: input_vcf = Mutect2Paired.output_vcf, ref_name = refGenome.ref_name, annovar_operation = annovar_operation, annovar_protocols = annovar_protocols } } # Do for normal sample call BwaMem as normalBwaMem { input: input_fastq = normalSamples, refGenome = refGenome } call MarkDuplicates as normalMarkDuplicates { input: input_bam = normalBwaMem.analysisReadySorted } call ApplyBaseRecalibrator as normalApplyBaseRecalibrator { input: input_bam = normalMarkDuplicates.markDuplicates_bam, input_bam_index = normalMarkDuplicates.markDuplicates_bai, dbSNP_vcf = dbSNP_vcf, dbSNP_vcf_index = dbSNP_vcf_index, known_indels_sites_VCFs = known_indels_sites_VCFs, known_indels_sites_indices = known_indels_sites_indices, refGenome = refGenome } output { Array[File] tumoralignedBamSorted = tumorBwaMem.analysisReadySorted Array[File] tumorMarkDuplicates_bam = tumorMarkDuplicates.markDuplicates_bam Array[File] tumorMarkDuplicates_bai = tumorMarkDuplicates.markDuplicates_bai Array[File] tumoranalysisReadyBam = tumorApplyBaseRecalibrator.recalibrated_bam Array[File] tumoranalysisReadyIndex = tumorApplyBaseRecalibrator.recalibrated_bai File normalalignedBamSorted = normalBwaMem.analysisReadySorted File normalmarkDuplicates_bam = normalMarkDuplicates.markDuplicates_bam File normalmarkDuplicates_bai = normalMarkDuplicates.markDuplicates_bai File normalanalysisReadyBam = normalApplyBaseRecalibrator.recalibrated_bam File normalanalysisReadyIndex = normalApplyBaseRecalibrator.recalibrated_bai Array[File] Mutect2Paired_Vcf = Mutect2Paired.output_vcf Array[File] Mutect2Paired_VcfIndex = Mutect2Paired.output_vcf_index Array[File] Mutect2Paired_AnnotatedVcf = annovar.output_annotated_vcf Array[File] Mutect2Paired_AnnotatedTable = annovar.output_annotated_table } } # TASK DEFINITIONS # Align fastq file to the reference genome task BwaMem { input { File input_fastq referenceGenome refGenome Int threads = 16 } String base_file_name = basename(input_fastq, ".fastq") String ref_fasta_local = basename(refGenome.ref_fasta) String read_group_id = "ID:" + base_file_name String sample_name = "SM:" + base_file_name String platform = "illumina" String platform_info = "PL:" + platform # Create the platform information command <<< set -eo pipefail #can we iterate through a struct?? mv ~{refGenome.ref_fasta} . mv ~{refGenome.ref_fasta_index} . mv ~{refGenome.ref_dict} . mv ~{refGenome.ref_amb} . mv ~{refGenome.ref_ann} . mv ~{refGenome.ref_bwt} . mv ~{refGenome.ref_pac} . mv ~{refGenome.ref_sa} . bwa mem \\ -p -v 3 -t ~{threads} -M -R '@RG\\t~{read_group_id}\\t~{sample_name}\\t~{platform_info}' \\ ~{ref_fasta_local} ~{input_fastq} > ~{base_file_name}.sam samtools view -1bS -@ 15 -o ~{base_file_name}.aligned.bam ~{base_file_name}.sam samtools sort -@ 15 -o ~{base_file_name}.sorted_query_aligned.bam ~{base_file_name}.aligned.bam >>> output { File analysisReadySorted = "~{base_file_name}.sorted_query_aligned.bam" } runtime { memory: "48 GB" cpu: 16 docker: "ghcr.io/getwilds/bwa:0.7.17" } } # Mark duplicates (not SPARK, for some reason that does something weird) task MarkDuplicates { input { File input_bam } String base_file_name = basename(input_bam, ".sorted_query_aligned.bam") String output_bam = "~{base_file_name}.duplicates_marked.bam" String output_bai = "~{base_file_name}.duplicates_marked.bai" String metrics_file = "~{base_file_name}.duplicate_metrics" command <<< gatk MarkDuplicates \\ --INPUT ~{input_bam} \\ --OUTPUT ~{output_bam} \\ --METRICS_FILE ~{metrics_file} \\ --CREATE_INDEX true \\ --OPTICAL_DUPLICATE_PIXEL_DISTANCE 100 \\ --VALIDATION_STRINGENCY SILENT >>> runtime { docker: "ghcr.io/getwilds/gatk:4.3.0.0" memory: "48 GB" cpu: 4 } output { File markDuplicates_bam = "~{output_bam}" File markDuplicates_bai = "~{output_bai}" File duplicate_metrics = "~{metrics_file}" } } # Base quality recalibration task ApplyBaseRecalibrator { input { File input_bam File input_bam_index File dbSNP_vcf File dbSNP_vcf_index File known_indels_sites_VCFs File known_indels_sites_indices referenceGenome refGenome } String base_file_name = basename(input_bam, ".duplicates_marked.bam") String ref_fasta_local = basename(refGenome.ref_fasta) String dbSNP_vcf_local = basename(dbSNP_vcf) String known_indels_sites_VCFs_local = basename(known_indels_sites_VCFs) command <<< set -eo pipefail mv ~{refGenome.ref_fasta} . mv ~{refGenome.ref_fasta_index} . mv ~{refGenome.ref_dict} . mv ~{dbSNP_vcf} . mv ~{dbSNP_vcf_index} . mv ~{known_indels_sites_VCFs} . mv ~{known_indels_sites_indices} . samtools index ~{input_bam} #redundant? markduplicates already does this? gatk --java-options "-Xms8g" \\ BaseRecalibrator \\ -R ~{ref_fasta_local} \\ -I ~{input_bam} \\ -O ~{base_file_name}.recal_data.csv \\ --known-sites ~{dbSNP_vcf_local} \\ --known-sites ~{known_indels_sites_VCFs_local} \\ gatk --java-options "-Xms8g" \\ ApplyBQSR \\ -bqsr ~{base_file_name}.recal_data.csv \\ -I ~{input_bam} \\ -O ~{base_file_name}.recal.bam \\ -R ~{ref_fasta_local} \\ #finds the current sort order of this bam file samtools view -H ~{base_file_name}.recal.bam | grep @SQ | sed 's/@SQ\\tSN:\\|LN://g' > ~{base_file_name}.sortOrder.txt >>> output { File recalibrated_bam = "~{base_file_name}.recal.bam" File recalibrated_bai = "~{base_file_name}.recal.bai" File sortOrder = "~{base_file_name}.sortOrder.txt" } runtime { memory: "36 GB" cpu: 2 docker: "ghcr.io/getwilds/gatk:4.3.0.0" } } # Mutect 2 calling tumor-normal task Mutect2Paired { input { File tumor_bam File tumor_bam_index File normal_bam File normal_bam_index referenceGenome refGenome File genomeReference File genomeReferenceIndex } String base_file_name_tumor = basename(tumor_bam, ".recal.bam") String base_file_name_normal = basename(normal_bam, ".recal.bam") String ref_fasta_local = basename(refGenome.ref_fasta) String genomeReference_local = basename(genomeReference) command <<< set -eo pipefail mv ~{refGenome.ref_fasta} . mv ~{refGenome.ref_fasta_index} . mv ~{refGenome.ref_dict} . mv ~{genomeReference} . mv ~{genomeReferenceIndex} . gatk --java-options "-Xms16g" Mutect2 \\ -R ~{ref_fasta_local} \\ -I ~{tumor_bam} \\ -I ~{normal_bam} \\ -O preliminary.vcf.gz \\ --germline-resource ~{genomeReference_local} \\ gatk --java-options "-Xms16g" FilterMutectCalls \\ -V preliminary.vcf.gz \\ -O ~{base_file_name_tumor}.mutect2.vcf.gz \\ -R ~{ref_fasta_local} \\ --stats preliminary.vcf.gz.stats \\ >>> runtime { docker: "ghcr.io/getwilds/gatk:4.3.0.0" memory: "24 GB" cpu: 1 } output { File output_vcf = "${base_file_name_tumor}.mutect2.vcf.gz" File output_vcf_index = "${base_file_name_tumor}.mutect2.vcf.gz.tbi" } } # annotate with annovar mutation calling outputs task annovar { input { File input_vcf String ref_name String annovar_protocols String annovar_operation } String base_vcf_name = basename(input_vcf, ".vcf.gz") command <<< set -eo pipefail perl /annovar/table_annovar.pl ~{input_vcf} /annovar/humandb/ \\ -buildver ~{ref_name} \\ -outfile ~{base_vcf_name} \\ -remove \\ -protocol ~{annovar_protocols} \\ -operation ~{annovar_operation} \\ -nastring . -vcfinput >>> runtime { docker : "ghcr.io/getwilds/annovar:${ref_name}" cpu: 1 memory: "2GB" } output { File output_annotated_vcf = "${base_vcf_name}.${ref_name}_multianno.vcf" File output_annotated_table = "${base_vcf_name}.${ref_name}_multianno.txt" } } "],["appendix-backends-and-executors.html", "Chapter 8 Appendix: Backends and Executors 8.1 Commonly used runtime attributes 8.2 General advice 8.3 Executor-specific notes 8.4 Backend-specific notes", " Chapter 8 Appendix: Backends and Executors Generally speaking, WDL workflows are quite portable thanks to their usage of Docker images to maintain software dependencies. However, the executor used to run WDLs and what backend they are being run upon can lead to specific scenarios where minor tweaks to your WDL are necessary to ensure portability. 8.1 Commonly used runtime attributes Runtime attributes do not behave the same on all platforms. Here are how some of the most commonly used runtime attributes work on some of the most common WDL setups. Attribute Fred Hutch HPC Local Cromwell Local miniwdl Terra bootDiskSizeGB n/a n/a n/a Request a disk of this size to boot the Docker image (useful for very large Docker images) cpu Reserve at most this many cores n/a Reserve at most this many cores Request a minimum of this many cores (scales with memory) disks n/a n/a n/a Request this much disk size - soft requirement, if not specified, will request 10 GB docker Run the task in this Docker image Run the task in this Docker image Run the task in this Docker image Run the task in this Docker image memory Maximum amount of memory to use n/a Maximum amount of memory to use Minimum amount of memory to use (scales with CPU) preemptible n/a n/a n/a Attempt running on a preemptible instance this many times, then switch to a non-preemptible walltime How much walltime to request for a task n/a n/a n/a 8.2 General advice Consider using more runtime attributes, not fewer. miniwdl and Cromwell will ignore runtime attributes as necessary, so including a runtime attribute that only applies to a particular backend will not harm portability on other backends. The Dockstore CLI, as of v1.15, uses Cromwell to run WDLs. Advice about Cromwell will therefore also apply to the Dockstore CLI. If running a workflow with a scattered task on a local compute, consider using miniwdl instead of Cromwell. Whether you are using miniwdl or Cromwell locally, make sure Docker has enough resources to be able to download and run the Docker images specified in your WDL tasks. Workflow systems with a UI like Terra may become unresponsive if you run a task scattered more than 1000x. Outputs may need to be interacted with using an API specific to that backend. 8.3 Executor-specific notes 8.3.1 Cromwell Cromwell running on a local machine has a tendency to heavily use system resources, especially when running scattered tasks. Sometimes, it will use too many resources at once. When this happens, your workflow’s tasks will tend to fail with exit code 137, or you will see hints about running out of memory in the logs of your tasks. You may also observe Docker becoming unresponsive. You can fix Docker by restarting Docker or your machine, but you will likely want to prevent this issue rather than keep having to restart Docker. To prevent this, modify concurrent-job-limit for your backend in the Cromwell configuration file. Other notes: Cromwell runs as a jar file, so it is very portable and does not need to be “installed” provided you have a modern Java runtime environment. If you just want to check workflows are valid, you can run womtool as a jar file (available on Cromwell’s GitHub page). This will check not only the WDL file you pass it, but also any WDLs it imports. Cromwell does not use call caching on most backends, but it is the default on Terra. For non-Terra backends, it can be enabled in the Cromwell configuration file. Cromwell supports the gpu and disks runtime attributes on certain backends. If using the gpu runtime attribute, make sure your task is set up correctly to properly use this resource. Generally speaking, Cromwell will be able to directly modify input files by default without causing permission errors. 8.3.2 miniwdl The authors of this course have informally observed that miniwdl appears to be “safer” in terms of using up too many resources than Cromwell. However, if you are sharing a compute with other users, it is still worth looking at the miniwdl configuration file and limiting how many Docker images you spin up at the same time. Other notes: miniwdl is a Python program, and it must be installed via pip or pip3 to use. miniwdl’s equivalent to womtool is miniwdl check, but it also includes shellcheck to check the command section of your tasks. miniwdl supports call caching, but it is turned off by default. miniwdl does not support the gpu or disks runtime attributes and will ignore them if present in a task’s runtime section. By default, miniwdl does not duplicate input files. If your workflow only needs to read input files, this helps save disk space, but if your workflow directly modifies input files, this can result in permission errors. A simple fix is to run miniwdl with the --copy-input-files flag. 8.4 Backend-specific notes 8.4.1 GCP 8.4.1.1 Disk space Running Cromwell on GCP is one of the few times that the disks runtime attribute is a soft requirement. Due to how Cromwell works, it must request a certain amount of disk space from GCP before running the task’s command section. In other words, you must know roughly how much disk space your task will use before it runs. If not specified in the runtime attribute of a task, Cromwell will request 10 GB of SSD space. A helpful way to handle disk space in WDLs you anticipate will be run on GCP/Terra is to make disk size a function of the size of your task inputs. The task inputs and any private variable declarations made outside of the command section are all calculated before Cromwell requests a disk from GCP, so you can use this section to define a disk size function. For example: version 1.0 task do_something { input { Array[File] some_array_of_files File one_file } Int predicted_disk_space = ceil(size(some_array_of_files), "GB") + ceil(size(one_file), "GB") + 1 # size(x, "GB") returns a float representing the size of x in gigabytes # ceil() is a WDL built-in function that rounds a float up into an integer command <<< pass >>> runtime { docker: "ubuntu-latest" disks: "local-disk " + predicted_disk_space " HDD" } } Common pitfalls: Not wrapping size() in ceil() – the disks: runtime attribute requires an integer, not a float Not using \"GB\" when calling size() – if you don’t specify units for size() it will return bytes! Forgetting the space between “local-disk” and the integer (or the integer and “HDD”/“SSD”) 8.4.1.2 Preemptibles Preemptible machines are an excellent way to save money when running workflows. A preemptible machine is a Google Cloud machine that is significantly cheaper (often less than half the price) than a standard one, at the cost of potentially being stopped suddenly. When running a task on a preemptible machine using Cromwell, if the preemptible is preempted (stopped suddenly), Cromwell will automatically retry the task. This does mean that in a worst case scenario, such a task could take about twice as long to run as normal and end up slightly more expensive, so you will want to weigh the costs and benefits. As a general rule of thumb, if you expect a task to take less than 2 hours, it is usually worth trying to use preemptible machines. 8.4.2 HPCs It is difficult to provide specific advice on HPCs, as they can vary greatly. Some general notes: Some HPCs do not support the use of Docker due to security concerns HPCs that do not allow the use of Docker may be able to run WDLs using alternative container technologies such as podman or rootless Docker Some HPCs will use disks to determine which disk to run on, which can be useful for managing disk space 8.4.2.1 Fred Hutch HPC The Fred Hutch HPC supports the use of Docker, so the docker runtime attribute works as expected The memory and gpu runtime attributes are ignored, but cpu works as expected The Fred Hutch HPC supports the use of multiple JSON files going into the same workflow "],["about-the-authors.html", "About the Authors", " About the Authors These credits are based on our course contributors table guidelines.     Credits Names Lead Content Instructor(s) Ash O’Farrell, Sitapriya Moorthi, Chris Lo Content Editor(s)/Reviewer(s) Amy Paguirigan, Carrie Wright, Ted Laderas Content Director(s) Amy Paguirigan Content Consultants Ash O’Farrell Production Content Publisher(s) Carrie Wright Technical Template Publishing Engineers Candace Savonen, Carrie Wright, Ava Hoffman Publishing Maintenance Engineer Candace Savonen Technical Publishing Stylists Carrie Wright, Ava Hoffman, Candace Savonen Package Developers (ottrpal) Candace Savonen, John Muschelli, Carrie Wright   ## ─ Session info ─────────────────────────────────────────────────────────────── ## setting value ## version R version 4.0.2 (2020-06-22) ## os Ubuntu 20.04.5 LTS ## system x86_64, linux-gnu ## ui X11 ## language (EN) ## collate en_US.UTF-8 ## ctype en_US.UTF-8 ## tz Etc/UTC ## date 2024-02-29 ## ## ─ Packages ─────────────────────────────────────────────────────────────────── ## package * version date lib source ## assertthat 0.2.1 2019-03-21 [1] RSPM (R 4.0.5) ## bookdown 0.24 2023-03-28 [1] Github (rstudio/bookdown@88bc4ea) ## bslib 0.4.2 2022-12-16 [1] CRAN (R 4.0.2) ## cachem 1.0.7 2023-02-24 [1] CRAN (R 4.0.2) ## callr 3.5.0 2020-10-08 [1] RSPM (R 4.0.2) ## cli 3.6.1 2023-03-23 [1] CRAN (R 4.0.2) ## crayon 1.3.4 2017-09-16 [1] RSPM (R 4.0.0) ## desc 1.2.0 2018-05-01 [1] RSPM (R 4.0.3) ## devtools 2.3.2 2020-09-18 [1] RSPM (R 4.0.3) ## digest 0.6.25 2020-02-23 [1] RSPM (R 4.0.0) ## ellipsis 0.3.1 2020-05-15 [1] RSPM (R 4.0.3) ## evaluate 0.20 2023-01-17 [1] CRAN (R 4.0.2) ## fastmap 1.1.1 2023-02-24 [1] CRAN (R 4.0.2) ## fs 1.5.0 2020-07-31 [1] RSPM (R 4.0.3) ## glue 1.4.2 2020-08-27 [1] RSPM (R 4.0.5) ## htmltools 0.5.5 2023-03-23 [1] CRAN (R 4.0.2) ## jquerylib 0.1.4 2021-04-26 [1] CRAN (R 4.0.2) ## jsonlite 1.7.1 2020-09-07 [1] RSPM (R 4.0.2) ## knitr 1.33 2023-03-28 [1] Github (yihui/knitr@a1052d1) ## magrittr 2.0.3 2022-03-30 [1] CRAN (R 4.0.2) ## memoise 2.0.1 2021-11-26 [1] CRAN (R 4.0.2) ## pkgbuild 1.1.0 2020-07-13 [1] RSPM (R 4.0.2) ## pkgload 1.1.0 2020-05-29 [1] RSPM (R 4.0.3) ## prettyunits 1.1.1 2020-01-24 [1] RSPM (R 4.0.3) ## processx 3.4.4 2020-09-03 [1] RSPM (R 4.0.2) ## ps 1.4.0 2020-10-07 [1] RSPM (R 4.0.2) ## R6 2.4.1 2019-11-12 [1] RSPM (R 4.0.0) ## remotes 2.2.0 2020-07-21 [1] RSPM (R 4.0.3) ## rlang 1.1.0 2023-03-14 [1] CRAN (R 4.0.2) ## rmarkdown 2.10 2023-03-28 [1] Github (rstudio/rmarkdown@02d3c25) ## rprojroot 2.0.3 2022-04-02 [1] CRAN (R 4.0.2) ## sass 0.4.5 2023-01-24 [1] CRAN (R 4.0.2) ## sessioninfo 1.1.1 2018-11-05 [1] RSPM (R 4.0.3) ## stringi 1.5.3 2020-09-09 [1] RSPM (R 4.0.3) ## stringr 1.4.0 2019-02-10 [1] RSPM (R 4.0.3) ## testthat 3.0.1 2023-03-28 [1] Github (R-lib/testthat@e99155a) ## usethis 1.6.3 2020-09-17 [1] RSPM (R 4.0.2) ## withr 2.3.0 2020-09-22 [1] RSPM (R 4.0.2) ## xfun 0.26 2023-03-28 [1] Github (yihui/xfun@74c2a66) ## yaml 2.2.1 2020-02-01 [1] RSPM (R 4.0.3) ## ## [1] /usr/local/lib/R/site-library ## [2] /usr/local/lib/R/library "],["references.html", "Chapter 9 References", " Chapter 9 References "],["404.html", "Page not found", " Page not found The page you requested cannot be found (perhaps it was moved or renamed). You may want to try searching to find the page's new location, or use the table of contents to find the page you are looking for. "]] +[["introduction-to-wdl.html", "Chapter 1 Introduction to WDL 1.1 Introduction 1.2 Review of basic WDL syntax 1.3 Using JSONs to control workflow inputs 1.4 Running WDL via a computing engine", " Chapter 1 Introduction to WDL 1.1 Introduction Welcome to building your first WDL workflow! This guide will help you strategically develop and scale up a WDL workflow that is iterative, reproducible, and efficient in terms of time and resource used. To make sure that we are on the same page, this guide assumes that you are able to run a WDL on a computing engine of your choice, such as Cromwell, miniwdl, or a cloud computing environment such as Terra, AnVIL, or Dockstore. This guide also assumes that you have a beginner’s understanding of the WDL syntax, and we will link out to additional resources to fill in the knowledge gap as needed! If you have never seen the WDL language in action, a great place to start is OpenWDL docs – it teaches you the basic syntax and showcases WDL features via concrete examples. 1.2 Review of basic WDL syntax We will do some review of the WDL syntax. A WDL workflow consists of at least one task. version 1.0 task do_something { command <<< exit 0 >>> } workflow my_workflow { call do_something } A workflow, and the tasks it calls, generally has inputs. version 1.0 task do_something { input { File fastq } command <<< exit 0 >>> } workflow my_workflow { input { File fq } call do_something { input: fastq = fq } } The input fq is defined to be a File variable type. WDL supports various variable types, such as String, Integer, Float, and Boolean. For more information on types in WDL, we recommend OpenWDL’s documentation on variable types. To access a task-level input variable in a task’s command section, it is usually referenced using ~{this} notation. To access a workflow-level variable in a workflow, it is referenced just by its name without any special notation. To access a workflow-level variable in a task, it must be passed into the task as an input. version 1.0 task do_something { input { File fastq String basename_of_fq } command <<< echo "First ten lines of ~{basename_of_fq}: " head ~{fastq} >>> } workflow my_workflow { input { File fq } String basename_of_fq = basename(fq) call do_something { input: fastq = fq, basename_of_fq = basename_of_fq } } Tasks and workflows also typically have outputs. The task-level outputs can be accessed by the workflow or any subsequent tasks. The workflow-level outputs represent the final output of the overall workflow. version 1.0 task do_something { input { File fastq String basename_of_fq } command <<< echo "First ten lines of ~{basename_of_fq}: " >> output.txt head ~{fastq} >> output.txt >>> output { File first_ten_lines = "output.txt" } } workflow my_workflow { input { File fq } String basename_of_fq = basename(fq) call do_something { input: fastq = fq, basename_of_fq = basename_of_fq } output { File ten_lines = do_something.first_ten_lines } } 1.3 Using JSONs to control workflow inputs Running a WDL workflow generally requires two files: A .wdl file, which contains the actual workflow, and a .json file, which provides the inputs for the workflow. In the example we showed earlier, the workflow takes in a file referred to by the variable fq. This needs to be provided by the user. Typically, this is done with a JSON file. Here’s what a JSON file for this workflow might look like: { "my_workflow.fq": "./data/example.fq" } JSON files consist of key-value pairs. In this case, the key is \"my_workflow.fq\" and the value is the path \"./data/example.fq\". The first part of the key is the name of the workflow as written in the WDL file, in this case my_workflow. The variable being represented is referred to its name, in this case, fq. So, the file located at the path ./data/example.fq is being input as a variable called fq into the workflow named my_workflow. Files aren’t the only type of variable you can refer to when using JSONs. Here’s an example JSON for every common WDL variable type. { "some_workflow.file": "./data/example.fq", "some_workflow.string": "Hello world!", "some_workflow.integer": 1965, "some_workflow.float": 3.1415, "some_workflow.boolean": true, "some_workflow.array_of_files": ["./data/example01.fq", "./data/example02.fq"] } Resources: For more basic examples of WDL workflow with a single task, we recommend OpenWDL’s documentation on WDL’s base structure. For more information on types in WDL, we recommend OpenWDL’s documentation on variable types. If you are having difficulty writing valid JSON files, considering using https://jsonlint.com/ to check your JSON for any errors. 1.4 Running WDL via a computing engine In order to run a WDL workflow, we need a computing engine to execute it. The two most popular WDL executors are miniwdl and Cromwell. Both can run WDLs on a local machine, High Performance Computing (HPC), or cloud computing backend. If you are trying to run WDL at Fred Hutch Cancer Center’s HPC system, you should use the PROOF executor. If you are computing on a HPC or the Cloud, you should find the best practice of running a WDL computing engine based on your institution’s information technology system. If you are computing locally on your computer, below is a short guide on how to set that up. How to run simple workflows locally Not every WDL workflow will run well on a laptop, but it can be helpful to have a basic setup for testing and catching simple syntax errors. Let’s quickly set up a WDL executor to run our WDLs. In this course, we will be using miniwdl, but everything in this course will also be compatible with Cromwell unless explicitly stated otherwise. Additionally, almost all WDLs use Docker images, so you will also need to install Docker or a Docker-like alternative. Installing Docker and miniwdl is not required to use this course. We don’t want anybody to get stuck here! If you already have a method for submitting workflows, such as Terra, feel free to use that for this course instead of running workflows directly on your local machine. If you don’t have any way of running workflows at the moment, that’s also okay – we have provided plenty of examples for following along. 1.4.1 Installing Docker Note: Although Docker’s own docs recommend installing Docker Desktop for Linux, it has been reported that some WDL executors work better on Linux when installing only Docker Engine (aka Docker CE). To install Docker on your machine, follow the instructions specific to your operating system on Docker’s website. To specifically install only Docker Engine, use these instructions instead. If you are unable to install Docker on your machine, Dockstore (not affiliated with Docker) provides some experimental alternatives. Dockstore also provides a comprehensive introduction to Docker itself, including how to write a Dockerfile. Much of that information is outside the scope of this WDL-focused course, but it may be helpful for those looking to eventually create their own Docker images. 1.4.2 Installing miniwdl miniwdl is based on Python. If you do not already have Python 3.6 or higher installed, you can install Python from here. Once Python is installed on your system, you can run pip3 install miniwdl from the command line to install miniwdl. For those who prefer to use conda, use conda install -c conda-forge miniwdl instead. Once miniwdl is installed, you can verify it works properly by running miniwdl run_self_test. This will run a built-in hello world workflow. For more information, see miniwdl’s GitHub repository. 1.4.3 Launching a workflow locally with miniwdl The generic method for running a WDL with miniwdl is the following: miniwdl run [path_to_wdl_file] -i [path_to_inputs_json] If you have successfully installed miniwdl, create the following WDL file and name it greetings.wdl: version 1.0 task greet { input { String user } command <<< echo "Hello ~{user}!" > greets.txt >>> output { String greeting = read_string("greets.txt") } } workflow my_workflow { input { String username } call greet { input: user = username } } Next, use this JSON file (or create one of your own) to provide the string that the workflow expects, and call the JSON file greetings.json: { "my_workflow.username": "Ash" } On the command line, run the following: miniwdl run greetings.wdl -i greetings.json Once the task completes, you should see something like this in your command line: [timestamp] wdl.w:my_workflow finish :: job: "call-greet" [timestamp] wdl.w:my_workflow done { "dir": "[working directory]/[timestamp]_my_workflow", "outputs": { "my_workflow.greet.greeting": "Hello Ash!" } } Where [timestamp] is the date and time that you are running the workflow, and [working directory] is the working directory that you are running the workflow from. For example: 2023-12-27 13:54:12.209 wdl.w:my_workflow finish :: job: "call-greet" 2023-12-27 13:54:12.210 wdl.w:my_workflow done { "dir": "/Users/ash/github/WDL_Workflows_Guide/resources/20231227_135400_my_workflow", "outputs": { "my_workflow.greet.greeting": "Hello Ash!" } } 1.4.4 Troubleshooting 1.4.4.1 DockerException If you are seeing a verbose error message that begins with text like this: 2023-12-27 13:43:37.525 wdl.w:my_workflow.t:call-greet task greet (greetings.wdl Ln 3 Col 1) failed :: dir: "/Users/sammy/github/WDL_Workflows_Guide/resources/20231227_134337_my_workflow/call-greet", error: "DockerException", message: "Error while fetching server API version: ('Connection aborted.', FileNotFoundError(2, 'No such file or directory'))", traceback: ["Traceback (most recent call last):", " File \\"/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/urllib3/connectionpool.py\\", line 790, in urlopen", " response = self._make_request(", " ^^^^^^^^^^^^^^^^^^^", " File \\"/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/urllib3/connectionpool.py\\", This is likely caused by miniwdl being unable to connect to Docker Daemon, the underlying technology that runs Docker images. This is necessary with miniwdl even though our example WDL does not specify a Docker image. Make sure you have Docker installed correctly, and make sure Docker is actively running on your machine. If you installed Docker Desktop, simply opening the Docker Desktop app should start Docker Engine. If you installed Docker without Docker Desktop, running dockerd in your command-line should start it. Be aware that starting the Docker Daemon may take a few minutes. 1.4.4.2 Missing required inputs If you forget to add -i greetings.json to your call, you will see something like this: my_workflow (greetings.wdl) --------------------------- required inputs: String username outputs: String greet.greeting missing required inputs for my_workflow: username You may also see this error if you remember to include a JSON file, but it is missing a required input. 1.4.4.3 Check JSON input If you see an error message like this: check JSON input; unknown input/output: greetings.username Double-check your input JSON. The first part of your JSON’s keys refer to the name of the workflow in the WDL file, not the filename of the WDL itself. Even though our WDL is saved as greetings.wdl, within that file, the workflow is named my_workflow. This means that the input JSON must say \"my_workflow.username\", not \"greetings.username\". Other common issues with JSON files are mistyping input variables (such as \"my_workflow.ussername\") or forgetting to enclose strings in quotation marks. When in doubt, try using https://jsonlint.com/ to check your input JSON, and double-check the name of your input variables. "],["404.html", "Page not found", " Page not found The page you requested cannot be found (perhaps it was moved or renamed). You may want to try searching to find the page's new location, or use the table of contents to find the page you are looking for. "]] diff --git a/manuscript/8-Appendix:-Backends-and-Executors.md "b/manuscript/8-Appendix\357\200\272-Backends-and-Executors.md" similarity index 100% rename from manuscript/8-Appendix:-Backends-and-Executors.md rename to "manuscript/8-Appendix\357\200\272-Backends-and-Executors.md"