Search code examples
snakemake

How to express explicit and implicit rules in Snakemake?


In order to understand Snakemake, I want to compare a traditional Makefile example with Snakemake. There is a nice introduction page to make with an educational example for prerequisites:

# source: http://www.jfranken.de/homepages/johannes/vortraege/make_inhalt.en.html

# Declaration of a variable
clothes = coat shoes mobile sweater socks\
        trousers shirt pants undershirt

all: $(clothes)

# An explicit rule assigns the commands for several targets
$(clothes):
    @echo put on $@; touch $@

# Implicit rules state the prerequisites
coat:      shoes mobile sweater
shoes:     socks trousers
mobile:    trousers
sweater:   shirt
trousers:  pants shirt
shirt:     undershirt

# Additional feature
.PHONY: naked
naked:      ; @-rm $(clothes)

I tried to translate this into a Snakemake file, see the end of this post. It works, but I have to repeat the touch command for every article. I tried to copy make's approach by distinguishung between explicit and implicit rules (using wildcards), but didn't succeed. Is there a more elegant way than my current one?

# Snakemake version of http://www.jfranken.de/homepages/johannes/vortraege/make_inhalt.en.html#ToC6

CLOTHES = ["coat", "shoes", "mobile", "sweater", "socks", "trousers", "shirt", "pants", "undershirt"]

rule all:
    input: CLOTHES

rule coat:
    input: "shoes", "mobile", "sweater"
    output: "coat"
    shell: "touch coat"

rule shoes:
    input: "socks", "trousers"
    output: "shoes"
    shell: "touch shoes"

rule mobile:
    input: "trousers"
    output: "mobile"
    shell: "touch mobile"

rule sweater:
    input: "shirt"
    output: "sweater"
    shell: "touch sweater"

rule trousers:
    input: "pants", "shirt"
    output: "trousers"
    shell: "touch trousers"

rule shirt:
    input: "undershirt"
    output: "shirt"
    shell: "touch shirt"

rule undershirt:
    output: "undershirt"
    shell: "touch undershirt"

rule pants:
    output: "pants"
    shell: "touch pants"

rule socks:
    output: "socks"
    shell: "touch socks"


### Additional feature

rule naked:
    run:
        for ds in CLOTHES:
            shell("rm -f {}".format(ds)) 

Solution

  • In Snakemake, one would use input functions and a config file for varying dependencies, e.g.:

    configfile: "config.yaml"
    
    
    def get_prereqs(wildcards):
        """Lookup prerequisites in config file."""
        # return prereqs as list of strings
        return config["clothes"][wildcards.type]
    
    
    # target rule for creating all desired files
    rule all:
        input:
            config["clothes"]
    
    
    rule clothes:
        input:
            # refer to function that will be called with the inferred wildcards as single argument
            get_prereqs
        output:
            # in general, rule output should be more specific than in this example
            "{type}"
        shell:
            "touch {output}"
    

    And the config.yaml:

    clothes:
      coat:
        - shoes
        - mobile
        - sweater
      shoes:
        - socks
        - trousers
      mobile:
        - trousers
      sweater:
        - shirt
      trousers:
        - pants
        - shirt
      shirt:
        - undershirt
      pants: []
      undershirt: []
      socks: []