# 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