Repository navigation
Optional lastz scoring by divergence: a table, or per-pair last-train models (prototype) - #2027
Closed
glennhickey wants to merge 6 commits into
Closed
glennhickey wants to merge 6 commits into
glennhickey wants to merge 6 commits into
Conversation
glennhickey
force-pushed
the
lastz-score-models
branch
5 times, most recently
from
October 7, 2026 17:22
ac5c512 to
2bb35e1
Compare
lastz scored every pair with its default HOXD70, however diverged. <blast> can now hold any number of <lastzScoreModel maxDistance= matrix= gapOpen= gapExtend=>. A pair is aligned with the model of smallest maxDistance its distance is within -- the distance that picks its lastzArguments -- written to a scores file and passed as --scores, and with HOXD70 past them all. lastz lets the command line override a scores file, so each divergence class keeps its thresholds. None are configured by default, so nothing changes unless a config adds them. KegAlign does not take them, and a run that asks for both stops rather than ignoring the models. HOXD70 given as a model gives byte-identical lastz output to the default. On evolver mammals, with bar stock, a table of last-train models trained on evolver pairs from 0.013 to 0.6 apart, rescaled to HOXD70's lambda, raised pooled leaf-pair precision from 0.816 to 0.829 and recall from 0.740 to 0.743; ancestor precision rose and recall fell, by up to 0.9 points. Evolver primates were unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With <blast lastzTrain="1">, each ancestor's blast step first trains a model with last-train on every pair of genomes it will align with lastz -- the ingroup pairs, and each ingroup against each outgroup -- and every lastz job for the pair scores with it, rescaled to HOXD70's lambda so that lastz's thresholds and the chaining's mean what they did. Training uses the whole genomes: the ingroup aligned to an outgroup after the first is only what earlier outgroups left of it. A pair whose smaller genome has under 500kb, or whose training fails, falls back to the lastzScoreModel table, else HOXD70. lastzTrainGaps="0" keeps HOXD70's gap penalties with the trained matrix; lastzTrainCores sets the threads. last-train is given --tolerance=1 when it has it (a proposed LAST patch). last_scoring.py gets the conversion: last-train's fine-scale model, which parse_train_file's integer one is too coarse to rescale, put on HOXD70's scale. It gives the evolver ladder's models exactly as the table experiments had them. On evolver mammals all 18 pairs trained, about 9s each. Per-pair models matched or beat the table built from the same evolver pairs: pooled leaf-pair precision/recall 0.830/0.744 against the table's 0.829/0.743 with stock bar, and 0.856/0.760 against 0.854/0.759 with pecan's per-pair models (HOXD70 with stock bar: 0.816/0.740). Where they differ most is pairs with a reconstructed ancestor: the two ancestors at the root trained to 92.9% identity, where the table, going by a distance with both ancestors' heights added, gave them its most distant model, for ~57%. Training is reproducible: the first ancestors' blast output was byte-identical between two runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The training job's memory, disk and walltime were asked for by file size, which for a gzipped fasta (as an ancestor's is) is about a quarter of its bases, and for a plain one counted the bases four times over in memory. They are now by estimated bases, a gzipped fasta counting four per byte: lastdb's database is about 4.6 bytes per base (6.3 GB for a 1.4 Gb fish genome) and lastdb, and lastal in training, hold it all. The same estimate picks the smaller genome as the database. On evolver mammals the blast output was byte-identical to before. The identity logged with a trained model was the last one last-train prints, which is the one its final integer scores imply: their rounding moved it from the fitted 69.9% to 76.2% for human against mouse. It is now the last pass's, from the alignments it counted. The scores lastz gets are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lastdb -R10 in place of LAST's default -R01: the inputs' soft-masking, which cactus's Red preprocessing puts there and the reconstructed ancestors keep, masks repeats from seeding, as it does for lastz, and lastal follows lastdb's -R. The default uppercased the inputs and masked only simple repeats, with tantan, which was 29% of lastdb's time, and left interspersed repeats seeding. On whole genomes (4 threads) human against mouse took 991 CPU-s in all rather than 4494, the training passes 135 rather than 1916, and the model moved 5.6%, inside the 8.3% two equally good samples differ by; human against a fish took 753 rather than 1144, moving 21.7% against 16.8%. Two mouse assemblies trained to 100.00% identity rather than 99.35%: the default had been fitting alignments between repeat copies. On evolver mammals the alignment was as accurate as before (pooled leaf-pair precision/recall 0.830/0.744 both ways). LAST 1651, as cactus installs it, gives the same models as the patched last-train with --tolerance, in a few more passes that now cost little. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
<blast lastzTrain> is now on. It passes over the mappers it does not apply to rather than stopping them: minimap2 and FastGA quietly, KegAlign with a warning (it used to be an error). A pair it cannot train, as when LAST is missing, falls back to the lastzScoreModel table or HOXD70 as before. On evolver mammals it raised pooled leaf-pair precision from 0.816 to 0.830 and recall from 0.740 to 0.744; on the 577-way it would cost about 0.3% of the lastz CPU. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Toil refuses a job that asks for more cores than --maxCores, or on a single machine than it has, so lastz training's 4 (lastzTrainCores) failed every cactus-blast run with fewer -- as cactus-prepare-toil's, whose blast jobs run cactus-blast with --maxCores 1. cactus now keeps that limit in CACTUS_MAX_CORES, as it does --maxMemory's, and cactus_clamp_cores holds a job to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
glennhickey
force-pushed
the
lastz-score-models
branch
from
October 9, 2026 12:03
2bb35e1 to
2988906
Compare
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.
Prototype for scoring lastz by divergence. lastz scores every pair with its default, HOXD70, whether the two genomes are 0.01 or 1.5 apart. Draft. Per-pair training is on by default (
<blast lastzTrain="1">); the table is opt-in.What it adds
A table of lastz scoring models by distance. Any number of these, in
<blast>:A pair is aligned with the model of smallest
maxDistancethat covers its distance (the same distance that picks itslastzArguments), and with HOXD70 past the last one. The model goes to lastz as a--scoresfile. lastz lets the command line override such a file, so each divergence class keeps its thresholds. Given HOXD70 itself as a model, lastz's output is byte-identical to the default.Per-pair training (
<blast lastzTrain>, on by default).lastzTrainGaps="0"keeps HOXD70's gap costs with the trained matrix;lastzTrainCoressets the threads, up to what Toil allows a job (--maxCores, or a single machine's cores).lastdb -R10, which lastal follows) rather than LAST's default of masking only simple repeats with tantan. Cactus's Red preprocessing provides that masking, the reconstructed ancestors keep it, and it's also what lastz itself doesn't seed in.Training doesn't apply to KegAlign, minimap2 or FastGA; their alignments keep their default scoring, and KegAlign warns once per ancestor. A
lastzScoreModeltable with KegAlign still stops with an error.Evolver mammals
Pooled leaf-pair precision / recall against the truth. bar is stock abPOA unless noted.
Cost on real genomes
Whole genomes from the 577-way tree, 4 threads. The mouse database was split into 4 GB volumes to fit a 31 GB machine.
-R10)"Model change" is the largest relative change among the eight distinct lastz parameters.
Ruled out, as the models moved well beyond that noise:
-uRY4): 11–66% change.A smaller query sample stays within the noise but is no longer worth it, since the passes are now cheap.
LAST
With this PR, progressive cactus needs LAST by default. LAST is now built from a submodule (upstream 1651) rather than by
downloadPangenomeTools, as of #2034, which is merged.This uses only stock LAST: 1651 gives the same models. last-train is given
--tolerance=1when it has it (a patch we drafted, not in LAST). That saves a few passes, which now cost little.Testing
The evolver runs were made on this code on top of the bar work in #2024. It was then rebased onto the current master, which has since gained #2029 (90 Mbp lastz chunks), #2030 (Red
-sketch) and pangenome-only changes. The blast code (local_alignment.py,paf.py,common.py) is identical between the two branches.CI caught one failure:
testEvolverPrepareToil. cactus-prepare's Toil workflow runs each cactus-blast with--maxCores 1, and Toil refused the training job's 4 cores. Training now asks for no more cores than Toil allows a job, through acactus_clamp_coresthat works likecactus_clamp_memory. With that fix, the test's cactus-prepare-toil workflow ran to completion locally and passed the test's accuracy check. That run was on #2024's branch, which carries the same blast code. The test's last step, cactus-phast, wasn't run locally, as it needs phast tools that aren't installed here.Not in this PR
🤖 Generated with Claude Code