Я пытаюсь создать структуру каталогов символических ссылок для псевдонимов выходных каталогов в рабочем процессе Snakemake.
Давайте рассмотрим следующий пример:
Давно go в галактике далеко-далеко кто-то хотел найти лучший вкус мороженого во Вселенной и провел опрос. Наш пример рабочего процесса направлен на представление голосов в виде структуры каталогов. Опрос проводился на английском sh (потому что так все говорят в той чужой галактике), но результаты должны быть поняты и людьми, не говорящими на английском sh. Ссылки Symboli c приходят на помощь.
Чтобы сделать вводимые данные доступными для нас, людей, а также 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
To создать соответствующее дерево каталогов, я начал с этого простого файла 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
Я уверен, что есть и другие способы 'pythoni c' для достижения того, что я хотел, но это всего лишь быстрый пример для иллюстрации моя проблема.
Запуская вышеуказанный рабочий процесс с 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>
при попытке сделать файл english/<flavour>/<voter>
, кажется, что он «забывает» о существовании этого каталога при использовании его в качестве входных данных для создания символической ссылки <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
Итак, мой вопрос: Как я могу реализовать приведенный выше лог c в рабочем Snakefile?
Обратите внимание, что я не ищу совета по изменению представления данных в файле YAML и / или Snakefile. Это просто пример, чтобы выделить (и изолировать) проблему, с которой я столкнулся в более сложном сценарии.
К сожалению, хотя я пока не мог понять это самостоятельно, мне удалось получить рабочую версию GNU make (даже несмотря на то, что «синтаксический анализ YAML» в лучшем случае - hacki sh):
### 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'.
Так что я действительно надеюсь, что решение не в том, чтобы go вернуться к старомодному GNU make со всеми нечитаемыми хаками, которые я усвоил за эти годы, но есть способ убедить Snakemake сделать то, что я написал. ; -)
На всякий случай: Тестировался с помощью Snakemake версии 5.7.1.
правки: