【发布时间】:2020-07-09 15:53:44
【问题描述】:
我正在尝试为 Snakemake 工作流程中的输出目录别名创建一个符号链接目录结构。
让我们考虑以下示例:
很久以前,在一个遥远的星系中,有人想找到宇宙中最好的冰淇淋口味并进行了一项调查。我们的示例工作流程旨在通过目录结构表示投票。该调查是用英语进行的(因为在那个外国星系中他们都说英语),但结果也应该被非英语人士理解。符号链接来救援。
为了让我们人类和 Snakemake 都可以解析输入,我们将它们粘贴到 YAML 文件中:
cat config.yaml
flavours:
chocolate:
- vader
- luke
- han
vanilla:
- yoda
- leia
berry:
- windu
translations:
french:
chocolat: chocolate
vanille: vanilla
baie: berry
german:
schokolade: chocolate
vanille: vanilla
beere: berry
为了创建相应的目录树,我从这个简单的 Snakefile 开始:
### Setup ###
configfile: "config.yaml"
### Targets ###
votes = ["english/" + flavour + "/" + voter
for flavour, voters in config["flavours"].items()
for voter in voters]
translations = {language + "_translation/" + translation
for language, translations in config["translations"].items()
for translation in translations.keys()}
### Commands ###
create_file_cmd = "touch '{output}'"
relative_symlink_cmd = "ln --symbolic --relative '{input}' '{output}'"
### Rules ###
rule all:
input: votes, translations
rule english:
output: "english/{flavour}/{voter}"
shell: create_file_cmd
rule translation:
input: lambda wc: "english/" + config["translations"][wc.lang][wc.trans]
output: "{lang}_translation/{trans}"
shell: relative_symlink_cmd
我确信还有更多的“pythonic”方法可以实现我想要的,但这只是一个简单的例子来说明我的问题。
使用snakemake 运行上述工作流程,我收到以下错误:
Building DAG of jobs...
MissingInputException in line 33 of /tmp/snakemake.test/Snakefile
Missing input files for rule translation:
english/vanilla
因此,虽然 Snakemake 足够聪明地在尝试创建 english/<flavour>/<voter> 文件时创建 english/<flavour> 目录,但在将其用作创建 <language>_translation/<flavour> 的输入时,它似乎“忘记”了该目录的存在符号链接。
作为中间步骤,我将以下补丁应用于 Snakefile:
27c27
< input: votes, translations
---
> input: votes#, translations
现在,工作流运行并按预期创建了english 目录(仅限snakemake -q 输出):
Job counts:
count jobs
1 all
6 english
7
现在创建了目标目录,我回到 Snakefile 的初始版本并重新运行它:
Job counts:
count jobs
1 all
6 translation
7
ImproperOutputException in line 33 of /tmp/snakemake.test/Snakefile
Outputs of incorrect type (directories when expecting files or vice versa). Output directories must be flagged with directory(). for rule translation:
french_translation/chocolat
Exiting because a job execution failed. Look above for error message
虽然我不确定指向目录的符号链接是否符合目录的条件,但我继续并应用了一个新补丁来遵循建议:
35c35
< output: "{lang}_translation/{trans}"
---
> output: directory("{lang}_translation/{trans}")
这样,snakemake 终于创建了符号链接:
Job counts:
count jobs
1 all
6 translation
7
作为确认,这是生成的目录结构:
english
├── berry
│ └── windu
├── chocolate
│ ├── han
│ ├── luke
│ └── vader
└── vanilla
├── leia
└── yoda
french_translation
├── baie -> ../english/berry
├── chocolat -> ../english/chocolate
└── vanille -> ../english/vanilla
german_translation
├── beere -> ../english/berry
├── schokolade -> ../english/chocolate
└── vanille -> ../english/vanilla
9 directories, 6 files
但是,除了无法在不运行 snakemake 两次(并在其间修改目标)的情况下创建此结构之外,即使只是简单地重新运行工作流也会导致错误:
Building DAG of jobs...
ChildIOException:
File/directory is a child to another output:
/tmp/snakemake.test/english/berry
/tmp/snakemake.test/english/berry/windu
所以我的问题是:如何在工作的 Snakefile 中实现上述逻辑?
请注意,我不是在寻求更改 YAML 文件和/或 Snakefile 中数据表示的建议。这只是一个示例,以突出(和隔离)我在更复杂的场景中遇到的问题。
遗憾的是,虽然到目前为止我自己无法解决这个问题,但我设法获得了一个可以工作的 GNU make 版本(即使“YAML 解析”充其量是 hackish):
### Setup ###
configfile := config.yaml
### Targets ###
votes := $(shell awk ' \
NR == 1 { next } \
/^[^ ]/ { exit } \
NF == 1 { sub(":", "", $$1); dir = "english/" $$1 "/"; next } \
{ print dir $$2 } \
' '$(configfile)')
translations := $(shell awk ' \
NR == 1 { next } \
/^[^ ]/ { trans = 1; next } \
! trans { next } \
{ sub(":", "", $$1) } \
NF == 1 { dir = $$1 "_translation/"; next } \
{ print dir $$1 } \
' '$(configfile)')
### Commands ###
create_file_cmd = touch '$@'
create_dir_cmd = mkdir --parent '$@'
relative_symlink_cmd = ln --symbolic --relative '$<' '$@'
### Rules ###
all : $(votes) $(translations)
$(sort $(dir $(votes) $(translations))) : % :
$(create_dir_cmd)
$(foreach vote, $(votes), $(eval $(vote) : | $(dir $(vote))))
$(votes) : % :
$(create_file_cmd)
translation_targets := $(shell awk ' \
NR == 1 { next } \
/^[^ ]/ { trans = 1; next } \
! trans { next } \
NF != 1 { print "english/" $$2 "/"} \
' '$(configfile)')
define translation
$(word $(1), $(translations)) : $(word $(1), $(translation_targets)) | $(dir $(word $(1), $(translations)))
$$(relative_symlink_cmd)
endef
$(foreach i, $(shell seq 1 $(words $(translations))), $(eval $(call translation, $(i))))
在此运行 make 效果很好:
mkdir --parent 'english/chocolate/'
touch 'english/chocolate/vader'
touch 'english/chocolate/luke'
touch 'english/chocolate/han'
mkdir --parent 'english/vanilla/'
touch 'english/vanilla/yoda'
touch 'english/vanilla/leia'
mkdir --parent 'english/berry/'
touch 'english/berry/windu'
mkdir --parent 'french_translation/'
ln --symbolic --relative 'english/chocolate/' 'french_translation/chocolat'
ln --symbolic --relative 'english/vanilla/' 'french_translation/vanille'
ln --symbolic --relative 'english/berry/' 'french_translation/baie'
mkdir --parent 'german_translation/'
ln --symbolic --relative 'english/chocolate/' 'german_translation/schokolade'
ln --symbolic --relative 'english/vanilla/' 'german_translation/vanille'
ln --symbolic --relative 'english/berry/' 'german_translation/beere'
生成的树与上图相同。
此外,再次运行 make 也可以:
make: Nothing to be done for 'all'.
所以我真的希望解决方案不是回到老式的 GNU make 以及我多年来内化的所有不可读的黑客,而是有一种方法可以说服 Snakemake 也按照我的要求去做。 ;-)
以防万一:这是使用 Snakemake 版本 5.7.1 测试的。
编辑:
- 修复了 GNU 根据@MadScientist 的comment 发出警告。
- 由于目前的一般反馈表明 Snakemake 无法做到这一点,I cross-posted this as a feature request over on Snakemake's GitHub(在赏金到期之前)。
- 根据@Nick 的comment 简化
relative_symlink_cmd。
【问题讨论】:
-
您应该删除
makefile标签,因为这不是make / makefile 问题。需要注意的是,解决“不止一次”错误的方法是引入一个$(sort ...),它的副作用也是唯一的。 -
@MadScientist:好吧,我没有使用
GNU make标签,因为我认为snakemake 只是make的另一种变体(可以说,我理解)。关于“不止一次”错误:我知道(我什至写它是可以修复的),我只是为了这个例子而没有打扰。但是谢谢你提醒我$(sort ...)有这种副作用,这会比我通常保留订单的 Marco 更简单。所以我想社区通过“错误”标签吸引你的注意力是有好处的。感谢您的反馈。 -
我个人认为
makefile是 POSIX 派生的 makefile 的标签,snakemake 不是...但我不拥有 SO 并且意见不一:)。很高兴我能帮上忙;干杯! -
我不知道
snakemake,但我的结论是否正确,您的translation任务并没有真正咨询文件系统,而是从YAML 结构创建符号链接?这个任务是否以某种我看不到的方式以编程方式依赖于“英语”任务? -
snakemake.readthedocs.io/en/stable/project_info/… 建议在
ln命令上使用-r标志。这是我在您的示例和他们的示例之间看到的唯一区别。它还指出,snakemake将符号链接视为文件
标签: python makefile directory symlink snakemake