Snakemake: избегайте удаления выходных файлов перед выполнением команды оболочки
Есть ли возможность избежать удаления выходных файлов, определенных в правиле snakemake, перед выполнением команды оболочки? Я нашел описание этого поведения здесь: http://snakemake.readthedocs.io/en/stable/project_info/faq.html
Я пытаюсь определить правило для списка входных данных и списка выходных файлов (отношение N:M). Это правило должно срабатывать, если один из входных файлов изменился. Сценарий python, который вызывается в команде оболочки, создает только те выходные данные, которые не существуют или чье содержимое изменилось по сравнению с уже существующими файлами (т. Е. Обнаружение изменений реализовано внутри сценария python). Я ожидал, что что-то вроде следующего правила должно решить эту проблему, но поскольку output.jsons удаляются перед запуском скрипта python, все 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. Я убрал строку self.remove_existing_output()
в jobs.py, чтобы избежать удаления выходных файлов перед выполнением правила. Кроме того, я добавил параметр no_touch=True
когда self.dag.check_and_touch_output() вызывается в executors.handle_job_success. Это прекрасно работало, поскольку выходные файлы теперь не были ни удалены, ни затронуты после выполнения правила. Но следующие правила с файлами json в качестве входных данных по-прежнему активируются для каждого файла json (даже если он не изменился), поскольку Snakemake признает, что файл json был определен как выходной файл до этого, и, следовательно, его необходимо было изменить. Поэтому я думаю, что предотвращение удаления выходных файлов не решит мою проблему, возможно, обходной путь - если он существует - единственный способ...
Обновление 2:
Я также попытался найти обходной путь, не меняя исходный код Snakemake, изменив путь вывода указанного выше правила jsons на transformation/tmp/...
и добавив следующее правило:
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, и поэтому сравнение не будет корректным.
2 ответа
Это немного сложнее, но я думаю, что это будет работать без проблем для вас.
Решение состоит в том, чтобы вызвать snakemake дважды, но вы можете заключить его в сценарий оболочки. В первом звонке вы используете змеиный --dryrun
чтобы выяснить, какие jsons будут обновлены, и во втором вызове эта информация используется для создания группы DAG. я использую --config
переключаться между двумя режимами. Вот Змеиный файл.
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 дважды, вы можете заключить его в скрипт оболочки, 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
, Это работает для вас? Я не проверял все части кода, так что возьмите его с собой.
Я не думаю, что Snakemake в настоящее время имеет решение вашей проблемы. Я думаю, что вы должны вытащить логику ввода / вывода из create_transformation_jsons.py
и написать отдельные правила для каждого отношения в Snakefile
, Вам может быть полезно знать, что анонимные правила могут быть сгенерированы, например, внутри цикла for. Как работать с переменной выходных файлов в правиле.
Недавно Snakemake начал очищать журналы при выполнении правила, и я открыл проблему по этому поводу. Решение этой проблемы может помочь вам тоже. Но это все в неопределенном будущем, поэтому не рассчитывайте на это.
Обновить
Вот другой подход. В вашем правиле нет подстановочных знаков, поэтому я предполагаю, что вы запускаете правило только один раз. Я также предполагаю, что во время выполнения вы можете составить список разделов, которые обновляются. Я назвал список 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 из двух частей, заменив все соответствующими именами правил.
$ snakemake jsons
$ snakemake all