2026-08-19 20:22:01 +02:00
|
|
|
# Benchmark: query-path testing
|
2026-08-19 20:18:46 +02:00
|
|
|
|
|
|
|
|
`benchmark/Makefile` exercises indexing, merge, and phylo distance
|
2026-08-19 20:22:01 +02:00
|
|
|
reconstruction against simulated bacterial genomes. It now also covers
|
|
|
|
|
`obikmer query` — the read-matching path — and the sparse packed
|
|
|
|
|
presence-matrix format (`obikmer pack --sparse`), previously untested by
|
|
|
|
|
this pipeline.
|
2026-08-19 20:18:46 +02:00
|
|
|
|
|
|
|
|
## Motivation
|
|
|
|
|
|
2026-08-19 20:22:01 +02:00
|
|
|
- `query` had no end-to-end coverage. A regression there would not be caught
|
|
|
|
|
by `verify_presence`/`verify_merge_presence`, which only check index
|
|
|
|
|
*content* against the `.npz` truth, never the query API.
|
2026-08-19 20:18:46 +02:00
|
|
|
- `pack --sparse` produces a presence-matrix format documented (see
|
|
|
|
|
[siblings.md](../architecture/siblings.md)) as faster for single-row
|
|
|
|
|
access (query) and slower for column-oriented access (phylo `--metric`).
|
2026-08-19 20:22:01 +02:00
|
|
|
`global_index_presence/` built by `merge_presence.sh` is always packed
|
|
|
|
|
dense (packing is a stage inside `merge`, not a separate `pack`
|
|
|
|
|
invocation) — there was no dense/sparse regression check.
|
2026-08-19 20:18:46 +02:00
|
|
|
|
2026-08-19 20:22:01 +02:00
|
|
|
## Query read source
|
2026-08-19 20:18:46 +02:00
|
|
|
|
2026-08-19 20:22:01 +02:00
|
|
|
Query reads are independent of `simulated_data/` (which is folded into the
|
|
|
|
|
index being queried): reusing those reads would test against the exact
|
|
|
|
|
error draw the index was built from. `query_data/<species>/<strain>/` holds
|
|
|
|
|
a *second*, independent `iss generate` run against the same reference
|
|
|
|
|
genome, via `simulate_query_one.sh` — unseeded, so a second draw picks up
|
|
|
|
|
different sequencing errors than `simulate_one.sh`'s draw for the same
|
|
|
|
|
genome. Fixed at 100,000 read pairs per genome (not coverage-proportional
|
|
|
|
|
like the 15x used for `simulated_data/`), so wall/RSS numbers stay
|
|
|
|
|
comparable across genomes of very different sizes.
|
2026-08-19 20:18:46 +02:00
|
|
|
|
2026-08-19 20:22:01 +02:00
|
|
|
Two query-source specimens, hardcoded as `QUERY_SPECIMENS` in
|
|
|
|
|
`make_deps.py`: `Escherichia_coli--K-12_MG1655` (common, well-represented
|
|
|
|
|
bacterium) and `Saccharolobus_islandicus--M.16.4` (the only archaeon in
|
|
|
|
|
`SPECIES` — distant lineage, stresses the query path differently from a
|
|
|
|
|
close-relative match). Two is enough to catch a dense/sparse regression
|
|
|
|
|
without duplicating the exhaustive per-specimen coverage
|
|
|
|
|
`verify_merge_presence` already provides across all `SPECIMENS`.
|
2026-08-19 20:18:46 +02:00
|
|
|
|
2026-08-19 20:22:01 +02:00
|
|
|
## Sparse global index
|
2026-08-19 20:18:46 +02:00
|
|
|
|
2026-08-19 20:22:01 +02:00
|
|
|
`global_index_presence_sparse/` is built by `pack_sparse.sh`: copy
|
|
|
|
|
`global_index_presence/` wholesale, then `obikmer pack --sparse` in place.
|
|
|
|
|
This works directly because `merge`'s pack stage (`merge.rs:252`,
|
|
|
|
|
`pack_matrices(false)`) keeps the per-genome column files on disk after
|
|
|
|
|
dense-packing — `pack_sparse_bit_matrix` (`obicompactvec/src/bitmatrix/sparse.rs:447`)
|
|
|
|
|
reads those, is idempotent, and removes `matrix.pbmx` once the sparse form
|
|
|
|
|
is written, so `Persistent::open` falls through to the sparse format
|
|
|
|
|
afterward. No separate merge run needed.
|
2026-08-19 20:18:46 +02:00
|
|
|
|
2026-08-19 20:22:01 +02:00
|
|
|
## Query runs
|
2026-08-19 20:18:46 +02:00
|
|
|
|
2026-08-19 20:22:01 +02:00
|
|
|
`query_one.sh dense|sparse SPECIMEN` runs `obikmer query --count-missing`
|
|
|
|
|
against `global_index_presence` or `global_index_presence_sparse`, output
|
|
|
|
|
gzipped to `query_{dense,sparse}/SPECIMEN.fasta.gz`, Reporter wall/RSS
|
|
|
|
|
captured to `stats/query_{dense,sparse}/SPECIMEN.stats` (same
|
|
|
|
|
stderr-parsing convention as `merge_presence.sh`).
|
2026-08-19 20:18:46 +02:00
|
|
|
|
2026-08-19 20:22:01 +02:00
|
|
|
Flags: `--count-missing` only. `--mismatch` is a no-op today
|
|
|
|
|
(`query/mod.rs:212-213`, prints "not yet implemented, ignored") — left off
|
|
|
|
|
rather than tested for a feature that doesn't exist yet.
|
2026-08-19 20:18:46 +02:00
|
|
|
|
2026-08-19 20:22:01 +02:00
|
|
|
## Dense/sparse regression
|
2026-08-19 20:18:46 +02:00
|
|
|
|
2026-08-19 20:22:01 +02:00
|
|
|
`verify_query.py` compares the two query outputs per specimen, matched by
|
|
|
|
|
read id (not stream position — the query pipeline chunks input across
|
|
|
|
|
worker threads and doesn't guarantee output order). Compares `kmer_count`,
|
|
|
|
|
`kmer_missing`, and the full `kmer_strict_matches` map per read. Any
|
|
|
|
|
mismatch is a real regression: dense and sparse must be content-identical,
|
|
|
|
|
only I/O access pattern differs. `.stats` → `stats/verify_query/`,
|
|
|
|
|
aggregated by `aggregate_stats.sh query|verify_query`-style cases
|
|
|
|
|
(`query_dense`, `query_sparse`, `verify_query`).
|
|
|
|
|
|
|
|
|
|
## Performance comparison
|
|
|
|
|
|
|
|
|
|
No dedicated script: the wall/RSS columns from the `query_dense` and
|
|
|
|
|
`query_sparse` aggregated `.stats` CSVs are the dense-vs-sparse performance
|
|
|
|
|
comparison — the expected win for query on sparse, per the `pack --sparse`
|
|
|
|
|
help text.
|
|
|
|
|
|
|
|
|
|
## Scope
|
|
|
|
|
|
|
|
|
|
`count` track excluded from the sparse branch: `pack --sparse` targets
|
|
|
|
|
presence matrices only (per CLI help); `pack_matrices` leaves count
|
|
|
|
|
matrices untouched regardless of the `sparse` flag
|
|
|
|
|
(`obikindex/src/index.rs:308`).
|
|
|
|
|
|
|
|
|
|
## New Makefile targets
|
|
|
|
|
|
|
|
|
|
`simulate_query`, `pack_sparse`, `query_dense`, `query_sparse`,
|
|
|
|
|
`aggregate_query_dense`, `aggregate_query_sparse`, `verify_query`,
|
|
|
|
|
`aggregate_verify_query` — the last three folded into `all`.
|