Recore Recore

Docs / How it works

Limits and comparisons

What Recore does not claim#

Recore is an audit and correction layer, not an oracle.

This is exactly why every run writes both a corrected binary matrix and audit-grade evidence tables. The binary matrix is what a downstream tool consumes; the evidence tables are what a reviewer reads.

Ambiguity is reported as ambiguity#

With --tree, Fitch parsimony root states that come out ambiguous are reported as ambiguous rather than being forced into a single ancestral story. Carrier-MRCA purity and Dollo-like origin and loss metrics are there to separate a compact clade-associated event from a blinking pattern that happens to have the same count.

If tree tips do not cover every matrix genome, the unmatched genomes are ignored and Recore says so explicitly.

Compared with Panaroo refinding#

FeaturePanaroo --refind_prop_matchRecore
Timingduring graph cleaningpost-hoc on the final matrix
AlignerBLAST-style refindingbatch minimap2
Outputbinary found / not foundbinary matrix plus multistate audit
Classificationlimiteddropped, split, fragment, start variant, paralog, pseudogene
Pseudogene handlingnot explicitstops, frameshifts, partial deletions
Paralog controllimitedsynteny and context rejection
Cluster redundancynot addressedoptional nucleotide similarity clustering
Reference-specific auditnoyes, via --reference-genome
Corrected GFF exportnooptional

The two are not alternatives. Panaroo refinding happens while the graph is being built; Recore runs afterwards on whatever matrix you ended up with, from any pipeline that writes a Panaroo-style matrix.

Benchmark results#

Recore includes a synthetic benchmark that creates known false absences, true absences, and partial pseudogenes, then computes precision/recall/F1 from the actual corrected matrices.

Synthetic benchmark precision/recallSynthetic benchmark precision/recall

Current synthetic benchmark:

PolicyTPFPFNPrecisionRecallF1
strict005NA0.0000.000
balanced5001.0001.0001.000
sensitive5001.0001.0001.000

Interpretation:

Reproduce:

python3 benchmarks/synthetic_benchmark.py

If minimap2 is outside PATH:

python3 benchmarks/synthetic_benchmark.py --minimap2 /path/to/minimap2

The biological event benchmark adds a small GFF/synteny truth set. It checks that a same-locus missing CDS is rescued, an identical paralog in the wrong context is rejected, and the evolutionary audit table contains quantitative synteny, ORF, phylogenetic-reliability, and paralog-risk columns.

python3 benchmarks/biological_event_benchmark.py

The synthetic benchmark validates policy behavior on matrix ground truth; the biological benchmark validates the locus-context guardrails. Neither is a species-specific biological truth benchmark.

Run time and memory#

Measured with benchmarks/search_scaling.py on an Apple M4 Pro (14 cores, 24 GB) with 8 threads and minimap2 2.31: synthetic MTBC-sized genomes (one 4.9 Mb contig and 4,000 genes each), 80 matrix gaps per genome of which 40 are false absences, GFFs and --synteny. The GFFs give each CDS an ID and a locus tag; the last two columns are the same runs with PGAP-style GFFs (--rich-gff), six names per CDS plus a product and a note.

GenomesGaps searchedRun timeWarm --search-cachePeak memoryPGAP-style GFFs: run timePGAP-style GFFs: peak memory
1008,0002.4 s1.3 s0.24 GB2.2 s0.32 GB
20016,0004.7 s2.8 s0.30 GB4.2 s0.43 GB
40032,00010.7 s6.3 s0.38 GB8.8 s0.64 GB
80064,00021.4 s13.5 s0.56 GB20.7 s1.05 GB

Every planted false absence was recovered, except one at 100 genomes that synteny rejected because the random deletions had changed its neighbourhood.

Memory grows by about 0.5 MB per genome, or 1 MB with PGAP-style GFFs, nearly all of it the annotation synteny reads for every genome: 5,000 genomes like these would need about 2.5 GB, or 5.5 GB with PGAP-style GFFs. Each name is stored once per genome and the gene-to-cluster lookup keeps one cluster per name; when every name was a String in several maps, memory grew by 3.4 MB per genome (6.2 MB with PGAP-style GFFs). Time grows with the genome count, about 0.027 s per genome, and the minimap2 search is about 9 of the 21 seconds at 800 genomes. The rest is synteny, and the audit tables in cluster_synteny_blocks.tsv, whose size follows the number of distinct gene neighbourhoods. These synthetic genomes are a worst case for that: every gap reshuffles the context of ten neighbours, and at 800 genomes the table reached 2.3 GB. Assemblies with conserved synteny write far less, and the table is written a bounded chunk of clusters at a time, so its size does not show in the memory.

--search-cache removes the search from a rerun (21 seconds to 13.5 at 800 genomes), so it pays off most where the search is the expensive part: many gaps per genome (--min-prevalence 0), --multi-allele, or larger genomes. The cache directory can be deleted at any time; the next run simply searches again.

Reproducible review#

Three Python utilities ship in benchmarks/ for checking a run rather than trusting it:

The synthetic benchmark itself, which creates known false absences, true absences and partial pseudogenes and then computes precision, recall and F1 from the actual corrected matrices, is benchmarks/synthetic_benchmark.py.

Edit this page on GitHub