Repository navigation
Add an indexed regional-access arm for the retrieval comparison - #23
Merged
Merged
Conversation
The whole-file retrieval comparison pits a SPARQL engine against cyvcf2
scanning the file, because none of Q1-Q13 is coordinate-restricted. That is
internally consistent, but it measures the graph only against the one VCF
access mode nobody uses for a selective question. Real VCF work seeks through
a bgzip + tabix/CSI index.
src/validation/regional_runner.py asks five region-restricted versions of the
bioinformatic questions (R1 record count, R2 allele shapes, R3 Ti/Tv, R4
FILTER distribution, R5 per-sample genotype classes, expanded only) of every
access path: cyvcf2 scanning, cyvcf2 and bcftools through the index, and the
same SPARQL on QLever, Comunica, HDT and COTTAS. It reuses validation_runner's
engine abstraction and its classification helpers, so a regional answer is
classified by the same code as a whole-file one.
Three design decisions:
- A window means POS, not overlap. An index seek returns every record whose
span overlaps the region, while a SPARQL ?pos filter does not; every VCF arm
keeps the seek and drops records whose POS falls outside the window, so the
arms agree on indels that straddle a window edge.
- Windows are anchored on real records from a fixed seed, so a sparse
benchmark VCF gives a selectivity ladder rather than a sparsity ladder.
- The scan arm is timed on fewer windows, since it costs the same whichever
window is asked; correctness is still checked on every window.
Every arm's answer is compared against a cyvcf2 reference before any timing is
reported; disagreements go to mismatches.json and the runner exits non-zero.
The image gains the tabix package (bgzip and tabix): the Debian bcftools
package links libhts but does not ship the htslib command-line tools.
Queries under src/validation/queries/regional/, docs in docs/validation.md
("Indexed regional access"), 23 unit tests in test_regional_runner_unit.py.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
The existing regional tests cover agreement between the arms' folding and classification logic, which left the code around it untested: building the bgzip + index copy, dispatching a timed execution to the right arm, and the driver itself -- 49% of regional_runner.py. test_regional_runner_driver_unit.py adds 37 tests: - BGZF detection, including a real bgzip file and a plain gzip that tabix would refuse. - Index kind: a contig past 2^29-1 bp needs CSI; an unparseable length or an unreadable header falls back to TBI. - The tool fallbacks: clear errors when neither bgzip/bcftools or tabix/bcftools exists, an index that a tool claims to have written but did not, and bcftools building both .tbi and .csi when tabix is absent. - prepare_indexed_vcf built for real: plain input is bgzipped, BGZF input is copied rather than recompressed, auto picks CSI for a long contig. - The indexed arms against a real index: cyvcf2's seek and bcftools query -r agree with the scan on every question and window. - The driver end to end: a scan-only run writes all four outputs and agrees; a disagreeing arm exits 1 and is written to mismatches.json; a failing arm is recorded without aborting; unknown arms and questions, a SPARQL arm without a graph, and a record-less VCF are refused cleanly; all three VCF arms agree. - The engine plumbing through a stand-in engine: agreement on every window, per-execution failure, unreadable output, and an engine that cannot start. The binary-dependent tests skip where bgzip, tabix or bcftools are absent and run in full inside the VCF-RDFizer image. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found by running the regional tests inside the image on vcf-bench-1, where bgzip, tabix and bcftools exist: every attempt to index test-1k failed with "[E::hts_idx_push] Chromosome blocks not continuous", from tabix and from the bcftools fallback alike. test-1k interleaves its two contigs record by record, and an index needs coordinate order; a VCF is not obliged to be in it. The existing index test would have caught this, but it skips off a machine without those binaries and so had never run. prepare_indexed_vcf now recognises that failure, coordinate-sorts with bcftools sort, and indexes the sorted copy. The sort is what a user would have to do before seeking, so it is charged to the VCF side's one-time cost -- as its own sortSeconds, not folded into the index time -- and the report says why it happened. Only the sorted copy is kept. Any other index failure is raised as before rather than sorted around, and a missing bcftools is a clear error. The benchmark inputs (the HG005 slices) are already sorted and are unaffected. Tests: the unsorted-input decision is covered without binaries; the sort, the already-sorted path and the fallbacks run against the real tools, now on a sorted synthetic VCF where the test is about something other than sorting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lation Found by running the runner end to end in the image on vcf-bench-1, with every arm against a real test-1k conversion. With the .nt.gz the harness reuses from 13_query_cost as --rdf, three of the four engines failed: qlever-index exited 2, Comunica could not read it (HTTP 400), and the COTTAS builder raised KeyError: '.gz' -- which the runner did not catch, so it ended the whole run. Only HDT, which builds its own artifact, coped. Three fixes: - The artifact is materialized as N-Triples once, before any engine, with validation_runner.materialize_ntriples -- the step the validation stage has always taken. It is timed and reported as setup (rdfMaterialization); a plain .nt is used in place. - engine_options() passes the names the engines read (memory_gb, port, startup_timeout, query_timeout) instead of the runner's own flag names, which the engines had silently ignored, and offers the supplied artifact as artifact_path/artifact_format so HDT and COTTAS can query it natively. - An engine that fails to start, whatever the exception type, is recorded in setup.engineErrors against that engine while the other arms' results stand. The engine loop moves into _run_engine_arm; its behaviour is otherwise unchanged. Four new tests cover the decode, the plain-file path, the option names and a non-RuntimeError start-up failure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The benchmark's derived slices are plain gzip. bgzip -c on one compressed it a second time and tabix could not parse the result, so the first real-slice run on bench-1 ended in a traceback. A gzip input that is not BGZF is now streamed through gzip into bgzip, and a failing external tool is reported as an error exit naming the command. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comunica, HDT and COTTAS cost tens of seconds a question on the 10k-record input; timed on every window, three replicates, they would take about a day and a half per scale. --thin-arms names arms timed on only --scan-windows-per-size windows of each size, and regional.json records which were. The default keeps the old behaviour: only the scan arm is thin. Also corrects the documented scan cost, measured on vcf-bench-1 at 0.64 s a question on the HG005 slice rather than the estimated 11 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous commit justified --thin-arms with the 23-45 s these engines take on 13's whole-file questions. Measured on vcf-bench-1, a region-restricted question costs Comunica, HDT and COTTAS about one second on the 10k-record fixture, so the benchmark times every engine on every window. The option stays for a genuinely slow arm; its default is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The regional runner is a performance investigation, not part of conversion or validation, so its tests should not run in the normal test workflow. They are renamed to miss CI's test_*_unit.py pattern, as cross_engine_agreement.py and test_cottas_tool.py already do, and test/README.md says how to run them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The whole-file retrieval comparison (
13_query_cost) pits SPARQL against cyvcf2 scanning the VCF, because none of Q1–Q13 is coordinate-restricted. That is fair for whole-file questions, but the paper's conclusion that SPARQL wins on selective questions is then shown only against the one VCF access mode nobody uses for them. Real VCF work seeks through a bgzip + tabix/CSI index. This adds that arm.What
src/validation/regional_runner.pyasks five region-restricted questions (R1 record count, R2 allele shapes, R3 Ti/Tv, R4 FILTER distribution, R5 per-sample genotype classes, expanded only). It asks each one of every access path:cyvcf2-scancyvcf2-indexedVCF(path)(region)over the bgzipped, indexed copybcftools-indexedbcftools query -rqlever,comunica,hdt,cottasIt reuses
validation_runner's engines and classification helpers, so a regional answer is classified by the same code as a whole-file one.Queries in
src/validation/queries/regional/{common,expanded}/.{{CHROM}},{{START}}and{{END}}are substituted per window, with an injection guard.Dockerfile adds the
tabixpackage (bgzip,tabix), which Debian'sbcftoolspackage does not ship.Docs:
docs/validation.md, "Indexed regional access".Design decisions
windows.json.--thin-armsgives any other arm the same treatment; the default is the scan arm only.mismatches.jsonand the runner exits 1.Tested on vcf-bench-1, inside an image built from this branch
The image has htslib/tabix 1.22.1, bcftools 1.22 and cyvcf2 0.34.0. Running there found four defects, all fixed in this PR:
An unsorted VCF could not be indexed (
c3815a4).test-1kinterleaves its contigs, sotabixand thebcftools indexfallback both failed with "Chromosome blocks not continuous". The original index test would have caught this, but it skips off-image and had never run. The input is now coordinate-sorted withbcftools sortwhen indexing reports it unsorted. The sort is charged to the VCF side's one-time cost as its ownsortSeconds. Any other index failure still raises.Three of four engines failed on the harness's actual input (
0ca2ff6). With the.nt.gzthat14_regional_access.shreuses from13_query_cost,qlever-indexexited 2, Comunica returned HTTP 400, and the COTTAS builder raisedKeyError: '.gz', which killed the whole run. The artifact is now materialized as N-Triples once, before any engine, withvalidation_runner.materialize_ntriples, the step validation has always taken. It is timed as setup (rdfMaterialization).Engine options were silently ignored (
0ca2ff6). The runner passed its own flag names (qlever_memory_gb, …) where the engines readmemory_gb,portandstartup_timeout, and it never offeredartifact_path/artifact_format. Engines now get the names they read, plus the native artifact, and any engine start-up failure is recorded against that engine rather than ending the run.A plain-gzip VCF was compressed twice (
6ecd528). The benchmark's derived slices (e.g.HG005_GRCh38_r100000.vcf.gz) are plain gzip, not BGZF.bgzip -con one wrapped the gzip bytes in BGZF, and tabix then failed with "Failed to parse TBX_VCF". A gzip input that is not BGZF is now decompressed while being BGZF-compressed. A failing external tool is also reported as an error exit naming the command, rather than a traceback.Smoke run (
test-1k, windows 1 kb–10 Mb, 2 per size, 1 replicate):--rdf.nt.gz.hdt.cottasWith the native
.cottas, COTTAS setup falls from 15.6 s to 6.1 s, because it now queries the supplied file instead of rebuilding it.Through the harness (ecrum19/vcf-rdfizer-testing#1), on the inputs
13_query_costalready converted, 2 windows per size, 1 replicate:test-10k, unsorted, so sorted first)HG005_GRCh38_r100000, plain gzip)Median seconds per question, 10 Mb windows: bcftools-indexed 0.005 (small) / 0.042 (slice), cyvcf2-indexed 0.009 / 0.130, cyvcf2-scan 0.025 / 0.668, qlever 0.024 / 0.045; on small, Comunica 1.08, COTTAS 0.96, HDT 1.11. These are smoke numbers, not results.
Comunica on the 17M-triple slice crashed at start-up. Its 10 s
ASKbind probe timed out while the server was still reading the file, and the fallback probe's redirect killed the server. That isvalidation_runner._await_bind, not this runner, and is left for a separate fix; the investigation runs the heavy engines at small scale only, as13_query_costdoes.Unit tests
The runner is a standalone performance investigation, so its tests are not in CI. They are
test/test_regional_runner.pyandtest/test_regional_runner_driver.py, named to miss thetest_*_unit.pypattern, likecross_engine_agreement.py.test/README.mdsays how to run them. CI's discovery drops from 935 to 860 tests.test/test_regional_runner_driver.py(52 tests, commiteba4371plus additions) covers:bgzip/tabix/bcftools.nt.gzdecode, option names6ecd528, all pass, none skipped (75 locally at the branch head, with the--thin-armstests). Coverage ofregional_runner.pywent from 49% to 97%; the rest is import guards and missing-tool branches.test_shacl_default_unit's vendored-vs-sibling-vocabulary-checkout comparison, which also fails on an untouchedmainwith the same local vocabulary checkout; it is unrelated.Follow-up
The harness (
14_regional_access.sh,bm_run_raw,collect_metrics.py/datasets.py regional) is ecrum19/vcf-rdfizer-testing#1. It needs an image built from this branch.🤖 Generated with Claude Code