当两个不同的规则路径可以生成给定的输出时,snakemake 能否避免歧义?

Can snakemake avoid ambiguity when two different rule paths can generate a given output?

初始工作流程

我有一个 snakefile 可以从双端数据生成一些输出。

在这个 snakefile 中,我有一个规则 "installs" 数据给定信息存储在配置文件中 (get_raw_data)。

然后我有一个规则,使用该数据生成其余工作流所依赖的中间文件 (run_tophat)。

下面是这些规则的输入和输出(OPJ代表os.path.join):

rule get_raw_data:
    output:
        OPJ(raw_data_dir, "{lib}_1.fastq.gz"),
        OPJ(raw_data_dir, "{lib}_2.fastq.gz"),

(稍后将详细介绍此规则的实施)

rule run_tophat:
    input:
        transcriptome = OPJ(annot_dir, "dmel-all-r5.9.gff"),
        fq1 = OPJ(raw_data_dir, "{lib}_1.fastq.gz"),
        fq2 = OPJ(raw_data_dir, "{lib}_2.fastq.gz"),
    output:
        junctions = OPJ(output_dir, "{lib}", "junctions.bed"),
        bam = OPJ(output_dir, "{lib}", "accepted_hits.bam"),

并且(简化)我的主要规则是这样的:

rule all:
    input:
        expand(OPJ(output_dir, "{lib}", "junctions.bed"), lib=LIBS),

将工作流扩展到单端数据

我现在必须 运行 我的单端数据工作流程。

我想避免最终输出具有不同的名称模式,具体取决于数据是单端还是双端。

我可以很容易地对上面提到的两条适用于单端数据的规则(get_raw_data_single_endrun_tophat_single_end)进行变体,其输入和输出如下:

rule get_raw_data_single_end:
    output:
        OPJ(raw_data_dir, "{lib}.fastq.gz")

rule run_tophat_single_end:
    input:
        transcriptome = OPJ(annot_dir, "dmel-all-r5.9.gff"),
        fq = OPJ(raw_data_dir, "{lib}.fastq.gz"),
    output:
        junctions = OPJ(output_dir, "{lib}", "junctions.bed"),
        bam = OPJ(output_dir, "{lib}", "accepted_hits.bam"),

如何为 snakemake 提供足够的信息来选择正确的规则路径?

配置文件包含有关 lib 通配符是通过以下方式与单端还是双端数据相关联的信息:库名称是 lib2rawlib2raw 中的键一个 lib2raw_single_end 字典(两个字典都是从配置文件中读取的)。

我不希望同一个库名称成为两个词典中的关键字。因此,从某种意义上说,我要执行工作流的单端还是双端分支是没有歧义的。

函数 lib2data(使用这些词典)被 get_raw_dataget_raw_data_single_end 用来确定 shell 命令对 运行 "install"数据。

这里是这个函数的简化版本(实际的版本包含一个额外的分支来为来自 SRR 标识符的数据生成命令):

def lib2data(wildcards):
    lib = wildcards.lib
    if lib in lib2raw:
        raw = lib2raw[lib]
        link_1 = "ln -s %s %s_1.fastq.gz" % (raw.format(mate="1"), lib)
        link_2 = "ln -s %s %s_2.fastq.gz" % (raw.format(mate="2"), lib)
        return "%s\n%s\n" % (link_1, link_2)
    elif lib in lib2raw_single_end:
        raw = lib2raw_single_end[lib]
        return "ln -s %s %s.fastq.gz\n" % (raw, lib)
    else:
        raise ValueError("Procedure to get raw data for %s unknown." % lib)

除了它们的输出,这两个 get_raw_data* 规则是相同的,并且按以下方式工作:

params:
    shell_command = lib2data,
shell:
    """
    (
    cd {raw_data_dir}
    {params.shell_command}
    )
    """

snakemake 是否能够根据未在规则输入和输出中编码但仅在配置文件和函数中编码的信息确定正确的规则路径?

好像不是这样的。事实上,我正在尝试测试我的新 snakefile(添加了 *_single_end 规则),但是在执行 get_raw_data 规则期间出现了 KeyError,而正在执行规则的库与单端数据相关联.

如何实现所需的行为(双分支工作流能够使用配置中的信息来选择正确的分支)?

编辑:KeyError 是由于 lib2data

中的错误

在使用正确的字典获取与库名称关联的数据后,我最终遇到以下错误:

AmbiguousRuleException:
Rules run_tophat and run_tophat_single_end are ambiguous for the file tophat_junction_discovery_revision_supplement/HWT3/junctions.bed.
Expected input files:
        run_tophat: ./HWT3_1.fastq.gz ./HWT3_2.fastq.gz Annotations/dmel-all-r5.9.gff
        run_tophat_single_end: ./HWT3.fastq.gz Annotations/dmel-all-r5.9.gff

编辑 2:向 get_raw_data* 规则添加输入

阅读 this post on the snakemake mailing list 后,我尝试在我的规则中添加一些输入以避免歧义。

def lib2data_input(wildcards):
    lib = wildcards.lib
    if lib in lib2raw:
        raw = lib2raw[lib]
        return [raw.format(mate="1"), raw.format(mate="2")]
    elif lib in lib2raw_single_end:
        raw = lib2raw_single_end[lib]
        return [raw]
    else:
        raise ValueError("Procedure to get raw data for %s unknown." % lib)

rule get_raw_data:
    input:
        lib2data_input
# [same output, params and shell as before]
# [same modification for the single-end case]

这导致 MissingInputException。奇怪的是,据报道丢失的文件确实存在。这个技巧应该有效吗?(无法重现,现在结果是:)

AmbiguousRuleException:
Rules run_tophat_single_end and run_tophat are ambiguous for the file tophat_junction_discovery_revision_supplement/HTW2/junctions.bed.
Expected input files:
        run_tophat_single_end: ./HTW2.fastq.gz Annotations/dmel-all-r5.9.gff
        run_tophat: ./HTW2_1.fastq.gz ./HTW2_2.fastq.gz Annotations/dmel-all-r5.9.gff

我指定 "data installation" 规则输入的方式显然不足以引导 snakemake 到正确的规则。

我不知道它是否有帮助,但您可以使用函数来定义规则的输入。这样您就可以使用相同的规则来处理单端或双端数据,前提是规则的输出是相同的...

def my_inputs(wildcards):
    data_type = config["data_type"]
    if (data_type == "pe"):
        input = ...
    elif (data_type == "se"):
        input = ...
    return input

rule my_rule:
    input: my_inputs
    ...

作为 user1829905 的 ,我曾尝试将 get_raw_data* 规则设为一个,但由于该规则的输出是可变的而失败了。

但是,我可以将 run_tophat* 规则融合为一个:它们具有相同的输出。

rule run_tophat:
    input:
        transcriptome = OPJ(annot_dir, "dmel-all-r5.9.gff"),
        fq = lib2fq,
    output:
        junctions = OPJ(output_dir, "{lib}", "junctions.bed"),
        bam = OPJ(output_dir, "{lib}", "accepted_hits.bam"),

我尝试了以下函数来生成此融合规则的输入:

def lib2fq(wildcards):
    lib = wildcards.lib
    if lib in lib2sr:
        return [OPJ(raw_data_dir, "{lib}_1.fastq.gz"), OPJ(raw_data_dir, "{lib}_2.fastq.gz")]
    elif lib in lib2raw:
        return [OPJ(raw_data_dir, "{lib}_1.fastq.gz"), OPJ(raw_data_dir, "{lib}_2.fastq.gz")]
    elif lib in lib2raw_single_end:
        return [OPJ(raw_data_dir, "{lib}.fastq.gz")]
    else:
        raise ValueError("Procedure to get raw data for %s unknown." % lib)

但是这次尝试失败了 InputFunctionException:

ValueError: Procedure to get raw data for {lib} unknown.

但是,根据第一个规则的输出明确定义第二个规则的输入可以解决问题。

def lib2fq(wildcards):
    lib = wildcards.lib
    if lib in lib2raw_single_end:
        return rules.get_raw_data_single_end.output
    else:
        return rules.get_raw_data.output

我不完全明白为什么会有这种差异。