Files
obikmer/benchmark/Makefile
T
Eric Coissac 52f26809af
ci.yml / build (pull_request) Successful in 3m49s
refactor(benchmark): consolidate generated artifacts under run/ directory
Restructure the benchmark pipeline to direct all simulated data, indices, statistics, and query outputs into a unified `run/` directory. Update Makefile targets, shell scripts, and Python utilities to resolve paths relative to this new base. Adjust documentation and dependency tracking to match the revised layout, and remove outdated temporary artifacts.
2026-08-29 00:19:31 +02:00

326 lines
15 KiB
Makefile

# Requires GNU Make >= 4.3 (grouped targets &:) — use gmake on macOS
BINARY := ../src/target/release/obikmer
VENV_PY := ../.venv/bin/python3
# All generated/downloaded artifacts live under RUN/ so the whole tree can be
# gitignored with a single entry (benchmark/run/) — see benchmark/README.md.
RUN := run
GENOMES := $(wildcard $(RUN)/genomes/*.fna.gz)
# SPECIMENS, SPECIES, and the full dependency graph are generated by
# make_deps.py from the genome FASTA headers — like .d files in C.
# Make rebuilds deps.mk whenever genomes/ changes and restarts.
-include deps.mk
REF_NPZS := $(SPECIMENS:%=$(RUN)/reference_index/%.npz)
REF_DIST_CSVS := $(addprefix $(RUN)/reference_dist/, \
shared_kmers.csv hamming_dist.csv jaccard_dist.csv \
bray_curtis_dist.csv relfreq_bray_curtis_dist.csv \
euclidean_dist.csv relfreq_euclidean_dist.csv \
hellinger_dist.csv hellinger_euclidean_dist.csv)
OBIKMER_PRESENCE_DIST := $(addprefix $(RUN)/obikmer_dist/presence/, \
jaccard_dist.csv jaccard_shared.csv jaccard_nj.nwk \
hamming_dist.csv hamming_nj.nwk)
OBIKMER_COUNT_DIST := $(addprefix $(RUN)/obikmer_dist/count/, \
jaccard_dist.csv jaccard_shared.csv jaccard_nj.nwk \
bray_curtis_dist.csv bray_curtis_nj.nwk \
relfreq_bray_curtis_dist.csv relfreq_bray_curtis_nj.nwk \
euclidean_dist.csv euclidean_nj.nwk \
relfreq_euclidean_dist.csv relfreq_euclidean_nj.nwk \
hellinger_dist.csv hellinger_nj.nwk \
hellinger_euclidean_dist.csv hellinger_euclidean_nj.nwk)
DIST_COMPARISON := $(RUN)/stats/dist_comparison/summary.csv
PRESENCE_DONE := $(SPECIMENS:%=$(RUN)/specimen_index_presence/%/index.done)
PRESENCE_STATS := $(SPECIMENS:%=$(RUN)/stats/indexing_presence/%.stats)
COUNT_DONE := $(SPECIMENS:%=$(RUN)/specimen_index_count/%/index.done)
COUNT_STATS := $(SPECIMENS:%=$(RUN)/stats/indexing_count/%.stats)
VERIFY_PRESENCE_STATS := $(SPECIMENS:%=$(RUN)/stats/verify_presence/%.stats)
VERIFY_COUNT_STATS := $(SPECIMENS:%=$(RUN)/stats/verify_count/%.stats)
SPECIFIC_PRESENCE_DONE := $(SPECIES:%=$(RUN)/specific_index_presence/%/index.done)
SPECIFIC_PRESENCE_STATS := $(SPECIES:%=$(RUN)/stats/specific_kmer_presence/%.stats)
SPECIFIC_COUNT_DONE := $(SPECIES:%=$(RUN)/specific_index_count/%/index.done)
SPECIFIC_COUNT_STATS := $(SPECIES:%=$(RUN)/stats/specific_kmer_count/%.stats)
SIMULATED_READS := $(foreach s,$(SPECIMENS),$(RUN)/simulated_data/$(subst --,/,$s)/reads_R1.fastq.gz)
QUERY_READS := $(foreach s,$(QUERY_SPECIMENS),$(RUN)/query_data/$(subst --,/,$s)/reads_R1.fastq.gz)
QUERY_PRESENCE_DENSE_DONE := $(QUERY_SPECIMENS:%=$(RUN)/query_presence_dense/%.fasta.gz)
QUERY_PRESENCE_DENSE_STATS := $(QUERY_SPECIMENS:%=$(RUN)/stats/query_presence_dense/%.stats)
QUERY_PRESENCE_SPARSE_DONE := $(QUERY_SPECIMENS:%=$(RUN)/query_presence_sparse/%.fasta.gz)
QUERY_PRESENCE_SPARSE_STATS := $(QUERY_SPECIMENS:%=$(RUN)/stats/query_presence_sparse/%.stats)
QUERY_COUNT_DENSE_DONE := $(QUERY_SPECIMENS:%=$(RUN)/query_count_dense/%.fasta.gz)
QUERY_COUNT_DENSE_STATS := $(QUERY_SPECIMENS:%=$(RUN)/stats/query_count_dense/%.stats)
QUERY_COUNT_SPARSE_DONE := $(QUERY_SPECIMENS:%=$(RUN)/query_count_sparse/%.fasta.gz)
QUERY_COUNT_SPARSE_STATS := $(QUERY_SPECIMENS:%=$(RUN)/stats/query_count_sparse/%.stats)
VERIFY_QUERY_STATS := $(QUERY_SPECIMENS:%=$(RUN)/stats/verify_query/%.stats)
.NOTPARALLEL:
.PHONY: all simulate reference reference_dist \
obikmer_dist obikmer_dist_presence obikmer_dist_count \
dist_comparison \
index_presence index_count \
aggregate_index_presence aggregate_index_count \
merge_presence merge_count \
verify_presence verify_count \
aggregate_verify_presence aggregate_verify_count \
verify_merge_presence verify_merge_count \
filter_presence filter_count \
aggregate_filter_presence aggregate_filter_count \
pack_dense_presence pack_dense_count simulate_query \
query_presence_dense query_presence_sparse \
query_count_dense query_count_sparse \
aggregate_query_presence_dense aggregate_query_presence_sparse \
aggregate_query_count_dense aggregate_query_count_sparse \
verify_query aggregate_verify_query
verify_merge_presence: $(RUN)/stats/verify_merge_presence/current.csv
verify_merge_count: $(RUN)/stats/verify_merge_count/current.csv
all: aggregate_verify_presence aggregate_verify_count \
verify_merge_presence verify_merge_count \
aggregate_filter_presence aggregate_filter_count \
dist_comparison \
aggregate_query_presence_dense aggregate_query_presence_sparse \
aggregate_query_count_dense aggregate_query_count_sparse \
aggregate_verify_query
# ── dependency file ───────────────────────────────────────────────────────────
deps.mk: $(GENOMES) make_deps.py
$(VENV_PY) make_deps.py $(GENOMES) > $@
# ── simulation ────────────────────────────────────────────────────────────────
# Prerequisites (genome → reads) are in deps.mk; $< is the genome file.
$(SIMULATED_READS):
bash simulate_one.sh $< $(dir $@)
simulate: $(SIMULATED_READS)
# ── query read simulation (fixed size, independent draw) ───────────────────────
# Prerequisites (genome → reads) are in deps.mk; $< is the genome file.
$(QUERY_READS):
bash simulate_query_one.sh $< $(dir $@)
simulate_query: $(QUERY_READS)
# ── reference kmer sets ───────────────────────────────────────────────────────
# Prerequisites (reads → npz) are in deps.mk.
$(RUN)/reference_index/%.npz:
bash build_reference.sh $*
reference: $(REF_NPZS)
# ── reference distance matrices ───────────────────────────────────────────────
$(REF_DIST_CSVS) &: $(REF_NPZS) build_reference_dist.py
$(VENV_PY) build_reference_dist.py \
--ref-dir $(RUN)/reference_index --out-dir $(RUN)/reference_dist
reference_dist: $(REF_DIST_CSVS)
# ── obikmer phylo (presence index) ──────────────────────────────────────────
$(OBIKMER_PRESENCE_DIST) &: $(RUN)/global_index_presence/index.done $(BINARY)
mkdir -p $(RUN)/obikmer_dist/presence
$(BINARY) phylo \
--output $(RUN)/obikmer_dist/presence/jaccard \
--distance jaccard --csv --shared-kmers --nj \
$(RUN)/global_index_presence
$(BINARY) phylo \
--output $(RUN)/obikmer_dist/presence/hamming \
--distance hamming --csv --nj \
$(RUN)/global_index_presence
obikmer_dist_presence: $(OBIKMER_PRESENCE_DIST)
# ── obikmer phylo (count index) ─────────────────────────────────────────────
$(OBIKMER_COUNT_DIST) &: $(RUN)/global_index_count/index.done $(BINARY)
mkdir -p $(RUN)/obikmer_dist/count
$(BINARY) phylo \
--output $(RUN)/obikmer_dist/count/jaccard \
--distance jaccard --csv --shared-kmers --nj \
$(RUN)/global_index_count
$(BINARY) phylo \
--output $(RUN)/obikmer_dist/count/bray_curtis \
--distance bray-curtis --csv --nj \
$(RUN)/global_index_count
$(BINARY) phylo \
--output $(RUN)/obikmer_dist/count/relfreq_bray_curtis \
--distance relfreq-bray-curtis --csv --nj \
$(RUN)/global_index_count
$(BINARY) phylo \
--output $(RUN)/obikmer_dist/count/euclidean \
--distance euclidean --csv --nj \
$(RUN)/global_index_count
$(BINARY) phylo \
--output $(RUN)/obikmer_dist/count/relfreq_euclidean \
--distance relfreq-euclidean --csv --nj \
$(RUN)/global_index_count
$(BINARY) phylo \
--output $(RUN)/obikmer_dist/count/hellinger \
--distance hellinger --csv --nj \
$(RUN)/global_index_count
$(BINARY) phylo \
--output $(RUN)/obikmer_dist/count/hellinger_euclidean \
--distance hellinger-euclidean --csv --nj \
$(RUN)/global_index_count
obikmer_dist_count: $(OBIKMER_COUNT_DIST)
obikmer_dist: obikmer_dist_presence obikmer_dist_count
# ── distance comparison ───────────────────────────────────────────────────────
$(DIST_COMPARISON): $(REF_DIST_CSVS) $(OBIKMER_PRESENCE_DIST) $(OBIKMER_COUNT_DIST) compare_all_dist.py
$(VENV_PY) compare_all_dist.py --run-dir $(RUN) --out $(DIST_COMPARISON)
dist_comparison: $(DIST_COMPARISON)
# ── per-specimen indexing ─────────────────────────────────────────────────────
# Prerequisites (reads → index.done + .stats) are in deps.mk.
$(RUN)/specimen_index_presence/%/index.done \
$(RUN)/stats/indexing_presence/%.stats &: $(BINARY)
bash index_one_presence.sh $*
$(RUN)/specimen_index_count/%/index.done \
$(RUN)/stats/indexing_count/%.stats &: $(BINARY)
bash index_one_count.sh $*
index_presence: $(PRESENCE_DONE)
index_count: $(COUNT_DONE)
# ── indexing stats aggregation ────────────────────────────────────────────────
aggregate_index_presence: $(PRESENCE_STATS)
bash aggregate_stats.sh indexing_presence
aggregate_index_count: $(COUNT_STATS)
bash aggregate_stats.sh indexing_count
# ── global merge ──────────────────────────────────────────────────────────────
$(RUN)/global_index_presence/index.done: $(PRESENCE_DONE) $(BINARY)
bash merge_presence.sh
$(RUN)/global_index_count/index.done: $(COUNT_DONE) $(BINARY)
bash merge_count.sh
merge_presence: $(RUN)/global_index_presence/index.done
merge_count: $(RUN)/global_index_count/index.done
# ── per-specimen verification ─────────────────────────────────────────────────
# Prerequisites (index.done + npz → .stats) are in deps.mk.
$(RUN)/stats/verify_presence/%.stats:
bash verify_one_presence.sh $*
$(RUN)/stats/verify_count/%.stats:
bash verify_one_count.sh $*
verify_presence: $(VERIFY_PRESENCE_STATS)
verify_count: $(VERIFY_COUNT_STATS)
# ── verification stats aggregation ───────────────────────────────────────────
aggregate_verify_presence: $(VERIFY_PRESENCE_STATS)
bash aggregate_stats.sh verify_presence
aggregate_verify_count: $(VERIFY_COUNT_STATS)
bash aggregate_stats.sh verify_count
# ── species-specific indexes ──────────────────────────────────────────────────
# Prerequisites (global index → specific index) are in deps.mk.
$(RUN)/specific_index_presence/%/index.done \
$(RUN)/stats/specific_kmer_presence/%.stats &: $(BINARY)
bash filter_one_presence.sh $*
$(RUN)/specific_index_count/%/index.done \
$(RUN)/stats/specific_kmer_count/%.stats &: $(BINARY)
bash filter_one_count.sh $*
filter_presence: $(SPECIFIC_PRESENCE_DONE)
filter_count: $(SPECIFIC_COUNT_DONE)
aggregate_filter_presence: $(SPECIFIC_PRESENCE_STATS)
bash aggregate_stats.sh specific_kmer_presence
aggregate_filter_count: $(SPECIFIC_COUNT_STATS)
bash aggregate_stats.sh specific_kmer_count
# ── merged index verification ─────────────────────────────────────────────────
$(RUN)/stats/verify_merge_presence/current.csv: $(REF_NPZS) $(RUN)/global_index_presence/index.done
bash verify_merge_presence.sh
$(RUN)/stats/verify_merge_count/current.csv: $(REF_NPZS) $(RUN)/global_index_count/index.done
bash verify_merge_count.sh
# ── dense variants (query benchmark) ────────────────────────────────────────────
# `merge` packs sparse by default (2026-08-28) — global_index_presence/
# global_index_count *are* the sparse variants already; the dense ones are
# built explicitly here, from a hard-link-based copy (see
# copy_index_hardlink.sh) rather than a full `cp -r`.
$(RUN)/global_index_presence_dense/index.done: $(RUN)/global_index_presence/index.done $(BINARY)
bash pack_dense.sh presence
# Rebuilt from the per-specimen count sources directly (via `merge --dense`),
# not repacked from global_index_count — see pack_dense.sh's own comment.
$(RUN)/global_index_count_dense/index.done: $(COUNT_DONE) $(BINARY)
bash pack_dense.sh count
pack_dense_presence: $(RUN)/global_index_presence_dense/index.done
pack_dense_count: $(RUN)/global_index_count_dense/index.done
# ── query: dense vs sparse, presence and count ──────────────────────────────────
# Prerequisites (reads + index → output + .stats) are in deps.mk.
$(RUN)/query_presence_dense/%.fasta.gz \
$(RUN)/stats/query_presence_dense/%.stats &: $(BINARY) $(RUN)/global_index_presence_dense/index.done
bash query_one.sh presence dense $*
$(RUN)/query_presence_sparse/%.fasta.gz \
$(RUN)/stats/query_presence_sparse/%.stats &: $(BINARY) $(RUN)/global_index_presence/index.done
bash query_one.sh presence sparse $*
$(RUN)/query_count_dense/%.fasta.gz \
$(RUN)/stats/query_count_dense/%.stats &: $(BINARY) $(RUN)/global_index_count_dense/index.done
bash query_one.sh count dense $*
$(RUN)/query_count_sparse/%.fasta.gz \
$(RUN)/stats/query_count_sparse/%.stats &: $(BINARY) $(RUN)/global_index_count/index.done
bash query_one.sh count sparse $*
query_presence_dense: $(QUERY_PRESENCE_DENSE_DONE)
query_presence_sparse: $(QUERY_PRESENCE_SPARSE_DONE)
query_count_dense: $(QUERY_COUNT_DENSE_DONE)
query_count_sparse: $(QUERY_COUNT_SPARSE_DONE)
aggregate_query_presence_dense: $(QUERY_PRESENCE_DENSE_STATS)
bash aggregate_stats.sh query_presence_dense
aggregate_query_presence_sparse: $(QUERY_PRESENCE_SPARSE_STATS)
bash aggregate_stats.sh query_presence_sparse
aggregate_query_count_dense: $(QUERY_COUNT_DENSE_STATS)
bash aggregate_stats.sh query_count_dense
aggregate_query_count_sparse: $(QUERY_COUNT_SPARSE_STATS)
bash aggregate_stats.sh query_count_sparse
# ── query: dense/sparse regression ──────────────────────────────────────────────
$(RUN)/stats/verify_query/%.stats:
bash verify_query_one.sh $*
verify_query: $(VERIFY_QUERY_STATS)
aggregate_verify_query: $(VERIFY_QUERY_STATS)
bash aggregate_stats.sh verify_query