是否有可能避免在执行shell命令之前删除snakemake规则中定义的输出文件?我在这里找到了对此行为的描述:http://snakemake.readthedocs.io/en/stable/project_info/faq.html#can-the-output-of-a-rule-be-a-symlink
我要做的是为输入列表和输出文件列表(N:M关系)定义规则。如果其中一个输入文件已更改,则应触发此规则。然后,在shell命令中调用的python脚本仅创建那些与现有文件相比不存在或内容已更改的输出(即,在python脚本中实现更改检测)。我期望类似下面的规则应该解决这个问题,但是在运行python脚本之前删除了output.jsons,所有的output.jsons都将使用新的时间戳创建,而不是只有那些已经更改的时间戳。
rule jsons:
"Create transformation files out of landmark correspondences."
input:
matchfiles = ["matching/%04i-%04i.h5" % (SECTIONS[i], SECTIONS[i+1]) for i in range(len(SECTIONS)-1)]
output:
jsons = ["transformation/{section}_transformation.json".format(section=s) for s in SECTIONS]
shell:
"python create_transformation_jsons.py --matchfiles {input.matchfiles} --outfiles {output.jsons}"
如果没有可能避免删除Snakemake中的输出文件,是否还有人知道如何将此工作流映射到snakemake规则而不更新所有输出文件?
更新:
我试图通过更改Snakemake源代码来解决这个问题。我删除了jobs.py中的行self.remove_existing_output()
,以避免在执行规则之前删除输出文件。此外,我在executors.handle_job_success中调用self.dag.check_and_touch_output()时添加了参数no_touch=True
。这非常有效,因为输出文件现在既不会在执行规则之前被删除也不会被触及。但是仍然会针对每个json文件触发遵循json文件作为输入的规则(即使它没有更改),因为Snakemake认识到json文件之前被定义为输出,并且必须已经更改。
所以我认为避免删除输出文件并不能解决我的问题,也许解决方法 - 如果存在 - 是唯一的方法......
更新2 :
我还尝试通过将上面定义的jsons规则的输出路径更改为transformation/tmp/...
并添加以下规则来查找解决方法而不更改Snakemake源代码:
def cmp_jsons(wildcards):
section = int(wildcards.section)
# compare json for given section in transformation/ with json in transformation/tmp/
# return [] if json did not change
# return path to tmp json filename if json has changed
rule copy:
input:
json_tmp = cmp_jsons
output:
jsonfile = "transformation/B21_{section,\d+}_affine_transformation.json"
shell:
"cp {input.json_tmp} {output.jsonfile}"
但是,由于在工作流程开始之前评估输入函数,因此tmp-jsons尚未存在或尚未由jsons规则更新,因此比较不会正确。
答案 0 :(得分:0)
我认为Snakemake目前无法解决您的问题。我认为您必须从create_transformation_jsons.py
中提取输入/输出逻辑,并为Snakefile
中的每个关系编写单独的规则。了解可以生成匿名规则可能对您有所帮助,例如:在for循环中。
How to deal with a variable of output files in a rule
最近Snakemake在执行规则时开始清除日志,我打开了issue on that。该问题的解决方案也可能对您有所帮助。但这都是在不确定的未来,所以不要指望它。
<强>更新强>
这是另一种方法。您的规则中没有任何通配符,因此我假设您只运行一次规则。我还假设在执行时您可以列出正在更新的部分。我打电话给列表SECTIONS_PRUNED
。然后,您可以制定一个仅将这些文件标记为输出文件的规则。
rule jsons:
"Create transformation files out of landmark correspondences."
input:
matchfiles = ["matching/%04i-%04i.h5" % (SECTIONS[i], SECTIONS[i+1]) for i in range(len(SECTIONS)-1)]
output:
jsons = ["transformation/{section}_transformation.json".format(section=s) for s in SECTIONS_PRUNED]
params:
jsons = [f"transformation/{s}_transformation.json" for s in SECTIONS]
run:
shell("python create_transformation_jsons.py --matchfiles {input.matchfiles} --outfiles {params.jsons}")
我最初认为使用shadow: "minimal"
来确保SECTIONS_PRUNED
未能声明的任何文件都不会虚假更新。但是,带阴影的情况可能更糟:错过的文件会被更新并留在阴影目录中(并且会被删除而不被注意)。使用shadow,您还需要将json文件复制到shadow目录中,让脚本找出要生成的内容。
所以更好的解决方案可能是不使用阴影。如果SECTIONS_PRUNED
未能声明所有更新的文件,则第二次执行snakemake将高位(并修复)此并确保所有下游分析都正确完成。
更新2
另一种更简单的方法是将工作流分成两部分,不要让snakemake知道json规则产生输出文件。
rule jsons:
"Create transformation files out of landmark correspondences."
input:
matchfiles = ["matching/%04i-%04i.h5" % (SECTIONS[i], SECTIONS[i+1]) for i in range(len(SECTIONS)-1)]
params:
jsons = [f"transformation/{s}_transformation.json" for s in SECTIONS]
shell:
"python create_transformation_jsons.py --matchfiles {input.matchfiles} --outfiles {params.jsons}"
分两部分运行snakemake,用相关的规则名称替换 all 。
$ snakemake jsons
$ snakemake all
答案 1 :(得分:0)
这涉及更多,但我认为它可以无缝地为您服务。
解决方案涉及两次调用snakemake,但您可以将其包装在shell脚本中。在第一次调用中,您在--dryrun
中使用snakemake来确定哪些jsons将被更新,在第二次调用中,此信息用于生成DAG。我使用--config
在两种模式之间切换。这是Snakefile。
def get_match_files(wildcards):
"""Used by jsons_fake to figure which match files each json file depend on"""
section = wildcards.section
### Do stuff to figure out what matching files this json depend on
# YOUR CODE GOES HERE
idx = SECTIONS.index(int(section)) # I have no idea if this is what you need
matchfiles = ["matching/%04i-%04i.h5" % (SECTIONS[idx], SECTIONS[idx + 1])]
return matchfiles
def get_json_output_files(fn):
"""Used by jsons. Read which json files will be updated from fn"""
try:
json_files = []
with open(fn, 'r') as fh:
for line in fh:
if not line:
continue # skip empty lines
split_line = line.split(maxsplit=1)
if split_line[0] == "output:":
json_files.append(split_line[1]) # Assumes there is only 1 output file pr line. If more, modify.
except FileNotFoundError:
print(f"Warning, could not find {fn}. Updating all json files.")
json_files = expand("transformation/{section}_transformation.json", section=SECTIONS)
return json_files
if "configuration_run" in config:
rule jsons_fake:
"Fake rule used for figuring out which json files will be created."
input:
get_match_files
output:
jsons = "transformation/{section}_transformation.json"
run:
raise NotImplementedError("This rule is not meant to be executed")
rule jsons_all:
input: expand("transformation/{s}_transformation.json", s=SECTIONS]
else:
rule jsons:
"Create transformation files out of landmark correspondences."
input:
matchfiles = ["matching/%04i-%04i.h5" % (SECTIONS[i], SECTIONS[i+1]) for i in range(len(SECTIONS)-1)]
output:
jsons = get_json_output_files('json_dryrun') # This is called at rule creation
params:
jsons=expand("transformation/{s}_transformation.json", s=SECTIONS]
run:
shell("python create_transformation_jsons.py --matchfiles {input.matchfiles} --outfiles {params.jsons}")
为避免两次调用Snakemake,您可以将其包装在shell脚本中mysnakemake
#!/usr/bin/env bash
snakemake jsons_all --dryrun --config configuration_run=yes | grep -A 2 'jsons_fake:' > json_dryrun
snakemake $@
并像调用snakemake一样调用脚本,例如:mysnakemake all -j 2
。这对你有用吗?我还没有对代码的所有部分进行测试,所以请耐心等待。