WDL - 任务执行路径

从上一篇文章,我们已经知道怎么构建一个简单的 workflow,接下来我们看看一个个任务在wdl中是如何构建形成一个流程的。以及我们如何让这个流程运行起来。

workflow 中,我们通过 call 调用了一系列的 task,但是这些被调用的 task 是如何执行的呢。毕竟当这些任务有些可以同时分析,有些任务则需要在其他任务完成后开始分析。
这时候,细心的朋友如果稍微注意,会发现,我们之前在介绍 call 的时候,提到过 call 可以在不同任务之间进行参数的传递,而着就是wdl 内部进行任务连接到依据。

1
2
3
4
5
6
workflow myWorkflow {
call taskA{...}
call taskB{
input_file = taskA.output
}
}

在我们上述示例中,taskB的运行需要taskA的输出作为输入。所以我们基于这样的输入输出关系,就可以构建出 taskAtaskB 之间的线性运行关系。
当然除了简单的线性关系,我们在实际应用中,还会出现并串联的情况、流程分支选择的情况。这些特性都可以在 wdl 中实现。接下来,我们来了解下wdl支持的管道类型

wdl的管道类型介绍

一个 workflow 里面包含多个 task,task 之前的串行或并行关系主要有下面三种情况:

Linear Chaining

在工作流中将任务链接在一起的最简单的方法就是线性链,我们将一个任务的输出提供给下一个任务的输入,如下所示:

image

1
2
3
4
5
6
7
8
call stepB { 
input:
in = stepA.out
}
call stepC {
input:
in = stepB.out
}

因为 WDL 允许我们使用语法在另一个任务(实际上是块 workflow 中的其他任何地方)的 call 语句中引用任何任务的输出 task_name.output_variable (在任务 output 块中适当声明)。我们只需在调用中 stepB 时指定使用 stepA 指定的输出结果 stepA.out 作为 stepB 的输入值 in ,并且它与 的规则 stepC 相同。

Multi-input / Multi-output

当然有时候,可能我们需要传递的输入/输出值并不是只有一个,这是 task 也可以定义多个输入和输出,比如上面的例子,taskB 有两个输出,作为 taskC 的输入。

image

1
2
3
4
5
call stepC { 
input:
in1 = stepB.out1,
in2 = stepB.out2
}

分支及合并

输出连接到线性链接和多输入/多输出中描述的输入的能力依赖于分层命名,可以进一步扩展以将任务的输出定向到单独的路径,对它们执行某些操作,然后将分支路径合并在一起。
alt text

1
2
3
4
5
6
7
8
9
10
11
12
13
call stepB {
input:
in = stepA.out
}
call stepC {
input:
in = stepA.out
}
call stepD {
input:
in1 = stepC.out,
in2 = stepB.out
}

在这里你可以看到,输出的 stepA 分别输入 stepB stepC 后产生不同的输出,然后我们一起输入 stepD 。

条件任务

针对不同的数据进行分析时,有些步骤也许我们不想一直运行。这意味着我们需要根据某个判断标准在两条提供的两条路径之间切换(在模式 A 中运行工具与在模式 B 中运行工具)或完全跳过一个步骤(运行工具与不运行工具)。在这种情况下,我们将使用条件语句。
alt text

1
2
3
4
5
6
if (shouldICallStepB) {
call stepB {
input:
in = stepA.out
}
}

和其他编程语法一样,我们需要使用 if 进行判断,该 if 语句可以显式控制,就像我们在上面的示例中使用布尔变量所做的那样。除了作为开关机制之外,还可以通过测试其他变量的值来隐式控制它,例如:if (myVar>0)

并行及合并

并行性是一种通过并行执行多个操作而不是按顺序执行(即等待每个操作完成然后再开始下一个操作)来使程序更快地完成的方法。分支及合并提供了一种并行方案,但是其针对的时执行不同任务的情况。但是另一种场景时,我们具有一系列相同的输入文件(一个属组类型的输入)需要执行相同的操作。那么我们可以使用 scatter 并行执行。

image

1
2
3
4
5
6
7
8
9
10
11
12
input {
Array[File] inputFiles
}
scatter (oneFile in inputFiles) {
call stepA {
input:
in = oneFile
}
call stepB {
input:
files = stepA.out
}

场景是用于 task 的并发执行。例如一个 task 有多个样本需要并发处理,可以使用数组的方式将样本传入,然后使用 scatter 并发的处理每个样本,每个执行的单元称为一个 shard。所有的 shard 执行完成,则当前 task 执行完成,所有 shard 的输出,又作为一个数组,可以传递到下一个 task 处理。

利用别名重复调用任务

工作流中多次调用任务时,可以使用任务别名。复制粘贴任务的定义并在每次需要在工作流中再次使用它时更改名称会很乏味。这种方法称为复制和粘贴编程,前期足够简单,但从长远来看很难维护。想象一下,您在一项任务中发现了一个错别字 - 您需要在每个粘贴的任务中修复该错别字!但是,使用 WDL 的内置任务别名功能 call taskName as aliasName,您可以调用相同的任务代码并为其分配别名。然后,遵循分层命名的原则,为了访问别名任务的输出,我们使用别名,而不是原始任务名称。
image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
call stepA as firstInput {
input:
in = in1
}
call stepA as secondInput {
input:
in = in2
}
call stepB {
input:
in = firstInput.out
}
call stepC {
input:
in = secondInput.out
}

参考文档

WDL参考文档

Broad WDL 官方论坛

WDL项目仓库

OpenWDL

WDL-readthedoc

标准流程描述语言 WDL 阿里云最佳实践

在学习编写 WDL 的过程中,可以参考 Broad 官方的一些 GATK工作流 借鉴和学习 WDL 的用法。

-------------本文结束感谢您的阅读-------------