Skip to content

Commit 5bc9c04

Browse files
authored
Merge pull request #2026 from ComparativeGenomicsToolkit/pecan-speedup
Speed up bar's base aligners: pecan, minipoa and abPOA
2 parents df597d1 + d429818 commit 5bc9c04

14 files changed

Lines changed: 195 additions & 68 deletions

File tree

‎.gitmodules‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
url = https://github.com/ComparativeGenomicsToolkit/kyoto.git
2222
[submodule "submodules/abPOA"]
2323
path = submodules/abPOA
24-
url = https://github.com/yangao07/abPOA.git
24+
url = https://github.com/ComparativeGenomicsToolkit/abPOA.git
2525
[submodule "submodules/lastz"]
2626
path = submodules/lastz
2727
url = https://github.com/ComparativeGenomicsToolkit/lastz.git

‎Makefile‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,14 @@ evolver_test_minipoa_local: all ${CWD}/test/primates-truth.maf
221221
evolver_test_minipoa_mammals_local: all ${CWD}/test/mammals-truth.maf
222222
PYTHONPATH="${CWD}/submodules/" CACTUS_BINARIES_MODE=local CACTUS_DOCKER_MODE=0 ${PYTHON} -m pytest ${pytestOpts} -s test/evolverTest.py::TestCase::testEvolverMinipoaMammalsLocal
223223

224+
# pecan, same dataset and tolerance as evolver_test_poa_local, so the two are comparable
225+
evolver_test_pecan_local: all ${CWD}/test/primates-truth.maf
226+
PYTHONPATH="${CWD}/submodules/" CACTUS_BINARIES_MODE=local CACTUS_DOCKER_MODE=0 ${PYTHON} -m pytest ${pytestOpts} -s test/evolverTest.py::TestCase::testEvolverPecanLocal
227+
228+
# the mammals head-to-head against evolver_test_local, which runs the same data through abpoa
229+
evolver_test_pecan_mammals_local: all ${CWD}/test/mammals-truth.maf
230+
PYTHONPATH="${CWD}/submodules/" CACTUS_BINARIES_MODE=local CACTUS_DOCKER_MODE=0 ${PYTHON} -m pytest ${pytestOpts} -s test/evolverTest.py::TestCase::testEvolverPecanMammalsLocal
231+
224232
evolver_test_refmap_local: all ${CWD}/test/primates-truth.maf
225233
PYTHONPATH="${CWD}/submodules/" CACTUS_BINARIES_MODE=local CACTUS_DOCKER_MODE=0 ${PYTHON} -m pytest ${pytestOpts} -s test/evolverTest.py::TestCase::testEvolverRefmapLocal
226234

@@ -258,7 +266,7 @@ evolver_test_primates_pangenome_resume_local: all ${CWD}/test/primates-truth.maf
258266
evolver_test_primates_pangenome_extend_local: all ${CWD}/test/primates-truth.maf
259267
PYTHONPATH="${CWD}/submodules/" CACTUS_BINARIES_MODE=local CACTUS_DOCKER_MODE=0 ${PYTHON} -m pytest ${pytestOpts} -s test/evolverTest.py::TestCase::testEvolverPrimatesPangenomeExtendLocal
260268

261-
evolver_test_all_local: evolver_test_local evolver_test_prepare_toil evolver_test_decomposed_local evolver_test_prepare_no_outgroup_local evolver_test_poa_local evolver_test_minipoa_local evolver_test_minipoa_mammals_local evolver_test_refmap_local evolver_test_minigraph_local
269+
evolver_test_all_local: evolver_test_local evolver_test_prepare_toil evolver_test_decomposed_local evolver_test_prepare_no_outgroup_local evolver_test_poa_local evolver_test_minipoa_local evolver_test_minipoa_mammals_local evolver_test_pecan_local evolver_test_pecan_mammals_local evolver_test_refmap_local evolver_test_minigraph_local
262270

263271
yeast_test_local:
264272
PYTHONPATH="${CWD}/submodules/" CACTUS_BINARIES_MODE=local CACTUS_DOCKER_MODE=0 ${PYTHON} -m pytest ${pytestOpts} -s test/evolverTest.py::TestCase::testYeastPangenomeLocal

‎bar/impl/bar.c‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,28 @@ PairwiseAlignmentParameters *pairwiseAlignmentParameters_constructFromCactusPara
4949
p->alignAmbiguityCharacters = cactusParams_get_int(params, 3, "bar", "pecan", "alignAmbiguityCharacters");
5050
p->useMumAnchors = cactusParams_get_int(params, 3, "bar", "pecan", "useMumAnchors");
5151
p->recursiveMums = cactusParams_get_int(params, 3, "bar", "pecan", "recursiveMums");
52+
// anchorMethod supersedes useMumAnchors when it is given; without it, useMumAnchors decides as it always has
53+
if (cactusParams_has(params, 3, "bar", "pecan", "anchorMethod")) {
54+
char *method = cactusParams_get_string(params, 3, "bar", "pecan", "anchorMethod");
55+
if (strcmp(method, "seed") == 0) {
56+
p->anchorMethod = PAIRWISE_ANCHOR_SEED;
57+
} else if (strcmp(method, "mum") == 0 || strcmp(method, "lastz") == 0) {
58+
p->anchorMethod = PAIRWISE_ANCHOR_LEGACY;
59+
p->useMumAnchors = strcmp(method, "mum") == 0;
60+
} else {
61+
st_errAbort("Unknown <pecan anchorMethod=\"%s\">; expected seed, mum or lastz", method);
62+
}
63+
free(method);
64+
}
65+
if (cactusParams_has(params, 3, "bar", "pecan", "seedHspThreshold")) {
66+
p->seedHspThreshold = cactusParams_get_int(params, 3, "bar", "pecan", "seedHspThreshold");
67+
}
68+
if (cactusParams_has(params, 3, "bar", "pecan", "seedHspThresholdMin")) {
69+
p->seedHspThresholdMin = cactusParams_get_int(params, 3, "bar", "pecan", "seedHspThresholdMin");
70+
}
71+
if (cactusParams_has(params, 3, "bar", "pecan", "seedRecursionDepth")) {
72+
p->seedRecursionDepth = cactusParams_get_int(params, 3, "bar", "pecan", "seedRecursionDepth");
73+
}
5274
return p;
5375
}
5476

@@ -110,7 +132,9 @@ void bar(stList *flowers, CactusParams *params, CactusDisk *cactusDisk, stList *
110132
st_logInfo("bar: base aligner %s, window %" PRIi64 ", maskFilter %" PRIi64 ", bandingLimit %" PRIi64 "\n",
111133
baseAligner_toString(engine), poaWindow, maskFilter, maximumLength);
112134
} else {
113-
st_logInfo("bar: base aligner pecan, bandingLimit %" PRIi64 "\n", maximumLength);
135+
st_logInfo("bar: base aligner pecan, anchors %s, bandingLimit %" PRIi64 "\n",
136+
pairwiseAlignmentParameters->anchorMethod == PAIRWISE_ANCHOR_SEED ? "seed" :
137+
(pairwiseAlignmentParameters->useMumAnchors ? "mum" : "lastz"), maximumLength);
114138
}
115139

116140
//////////////////////////////////////////////

‎bar/impl/flowerAligner.c‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
#include "endAligner.h"
8+
#include "flowerAligner.h" // so the compiler holds the definitions here to the declarations
89
#include "cactus.h"
910
#include "sonLib.h"
1011
#include "adjacencySequences.h"
@@ -505,7 +506,11 @@ static stSortedSet *getEndsToAlign(Flower *flower, int64_t maxSequenceLength) {
505506
*/
506507
stSortedSet *endsToAlign = stSortedSet_construct();
507508
End *dominantEnd = getDominantEnd(flower); //an end to which all adjacencies are incident
508-
if (dominantEnd != NULL && getMaxAdjacencyLength(flower) <= 2 * maxSequenceLength) {
509+
// Aligning only the dominant end is a saving only if it loses nothing. Its alignment reaches
510+
// maxSequenceLength into each adjacency, from its own side, where aligning every end reaches
511+
// that far from both sides -- so an adjacency longer than maxSequenceLength (this used to allow
512+
// twice that) would have its far part left unaligned. The poa path makes the same test.
513+
if (dominantEnd != NULL && getMaxAdjacencyLength(flower) <= maxSequenceLength) {
509514
stSortedSet_insert(endsToAlign, dominantEnd);
510515
} else {
511516
End *end;
@@ -555,7 +560,7 @@ static void computeMissingEndAlignments(StateMachine *sM, Flower *flower, stHash
555560

556561
stSortedSet *makeFlowerAlignment(StateMachine *sM, Flower *flower, int64_t spanningTrees, int64_t maxSequenceLength,
557562
bool useProgressiveMerging, float gapGamma,
558-
PairwiseAlignmentParameters *pairwiseAlignmentBandingParameters, bool pruneOutStubAlignments, int64_t poaWindow) {
563+
PairwiseAlignmentParameters *pairwiseAlignmentBandingParameters, bool pruneOutStubAlignments) {
559564
stHash *endAlignments = stHash_construct2(NULL, (void(*)(void *)) stSortedSet_destruct);
560565
computeMissingEndAlignments(sM, flower, endAlignments, spanningTrees, maxSequenceLength,
561566
useProgressiveMerging, gapGamma, pairwiseAlignmentBandingParameters);

‎bar/impl/poaBarAligner.c‎

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -661,15 +661,14 @@ static void run_abpoa_window(Msa *msa, uint8_t **bseqs, PoaParameters *poa_param
661661
* last_scoring.py's learned matrix reaches both (it writes into <poa>, which local_alignment.py
662662
* also reads to score FastGA PAFs).
663663
*
664-
* The gaps have to be separate. abPOA's gap model is convex -- min(open1 + L*ext1, open2 +
665-
* L*ext2) -- so with the shipped 400/30 and 1200/1 its effective extension beyond L~28 is 1.
666-
* minipoa has a single affine piece, so handing it abPOA's first-piece extension of 30 prices
667-
* long gaps about 30x above what abPOA charges, and it responds by packing bases into shared
668-
* columns instead of opening a gap. That cost about six points of mafComparator accuracy on
669-
* evolver mammals.
664+
* The gaps are separate so each engine can be priced for itself. Both can be convex --
665+
* min(open1 + L*ext1, open2 + L*ext2) -- and <minipoa> ships abPOA's 400/30 and 1200/1. With
666+
* the second piece off, minipoa has a single affine piece, and abPOA's first piece alone prices
667+
* long gaps about 30x above what abPOA charges: it packs bases into shared columns instead of
668+
* opening a gap, which cost about six points of mafComparator accuracy on evolver mammals.
670669
*
671-
* last-train reaches minipoa too: it fits a single affine model, which is minipoa's model
672-
* exactly, so last_scoring.py writes the learned open/extend straight into <minipoa>. The
670+
* last-train reaches minipoa too: it fits a single affine model, so last_scoring.py writes the
671+
* learned open/extend into <minipoa>'s first piece and turns the second off. The
673672
* GapOpen2/GapExtend2 pair it synthesises for abPOA is a stability workaround for that aligner
674673
* and is deliberately not passed on.
675674
*/
@@ -704,14 +703,16 @@ static minipoa_para_t *minipoaParameters_constructFromCactusParams(CactusParams
704703
}
705704
free(submat_string);
706705

707-
/*
708-
* Only the first gap piece. abPOA takes min(open1 + L*ext1, open2 + L*ext2) and minipoa has
709-
* no second piece at all, so gaps longer than where the two cross (~28bp with the shipped
710-
* 400/30 and 1200/1) are penalised more heavily here than abPOA would.
711-
*/
712706
out->gapOpen = cactusParams_get_int(params, 3, "bar", "minipoa", "minipoaGapOpenPenalty");
713707
out->gapExt = cactusParams_get_int(params, 3, "bar", "minipoa", "minipoaGapExtensionPenalty");
714708
minipoa_set_gap(mpt, out->gapOpen, out->gapExt);
709+
// The optional second piece, which makes the gap model convex, as abPOA's is. Absent or 0 is
710+
// the single piece above.
711+
out->gapOpen2 = cactusParams_has(params, 3, "bar", "minipoa", "minipoaGapOpenPenalty2") ?
712+
cactusParams_get_int(params, 3, "bar", "minipoa", "minipoaGapOpenPenalty2") : 0;
713+
out->gapExt2 = cactusParams_has(params, 3, "bar", "minipoa", "minipoaGapExtensionPenalty2") ?
714+
cactusParams_get_int(params, 3, "bar", "minipoa", "minipoaGapExtensionPenalty2") : 0;
715+
minipoa_set_gap2(mpt, out->gapOpen2, out->gapExt2);
715716

716717
out->bandConstant = cactusParams_get_int(params, 3, "bar", "minipoa", "minipoaBandConstant");
717718
out->bandFraction = cactusParams_get_float(params, 3, "bar", "minipoa", "minipoaBandFraction");
@@ -726,10 +727,9 @@ static minipoa_para_t *minipoaParameters_constructFromCactusParams(CactusParams
726727
minipoa_set_seeding(mpt, out->seeding, out->minimizerK, out->minimizerW, out->anchorWindow);
727728

728729
/*
729-
* Progressive ordering, on by default, because abPOA runs with it on
730-
* (<poa partialOrderAlignmentProgressiveMode>) and the order sequences are added to a POA
731-
* graph changes the alignment -- markedly so on diverged input. Leaving it off here was worth
732-
* about six points of mafComparator accuracy on the evolver mammals set.
730+
* Progressive ordering, as abPOA does it (<poa partialOrderAlignmentProgressiveMode>): the
731+
* order sequences are added to a POA graph changes the alignment, markedly so on diverged
732+
* input, and adding the closest pair first is what a guide tree buys.
733733
*/
734734
out->progressive = cactusParams_get_int(params, 3, "bar", "minipoa", "minipoaProgressiveMode");
735735
out->progressiveMaxRows = cactusParams_get_int(params, 3, "bar", "minipoa", "minipoaProgressiveMaxRows");
@@ -762,6 +762,11 @@ static char *dump_minipoa_input(Msa *msa, PoaParameters *pp, uint8_t **bseqs, ch
762762
char *command = st_malloc(4096 * sizeof(char));
763763
sprintf(command, "minipoa %s -m %s -O -%d -E -%d -b %d -f %d -r 1 -t 1",
764764
input_path, matrix_path, pp->gapOpen, pp->gapExt, pp->bandConstant, f);
765+
if (pp->gapOpen2 != 0 || pp->gapExt2 != 0) {
766+
char gap2_opts[128];
767+
sprintf(gap2_opts, " --gap_open2 -%d --gap_ext2 -%d", pp->gapOpen2, pp->gapExt2);
768+
strcat(command, gap2_opts);
769+
}
765770
if (pp->seeding) {
766771
char kw_opts[128];
767772
sprintf(kw_opts, " -S -k %d -w %d", pp->minimizerK, pp->minimizerW);

‎bar/inc/poaBarAligner.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ typedef struct _PoaParameters {
7878
*/
7979
int mat[25];
8080
int gapOpen, gapExt;
81+
int gapOpen2, gapExt2; /* minipoa's optional convex second piece; 0 = off */
8182
int bandConstant;
8283
double bandFraction;
8384
bool seeding;

‎doc/progressive.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ These are the most relevant options for running on a cluster
427427
* `--consMemory`: Override the memory for each `cactus_consolidated` job. Can be useful if Cactus's estimates are wrong, but a job that runs out of memory is already retried with double the memory by default, so this is rarely needed.
428428
* `--consRetainPages`: Whether `cactus_consolidated` keeps the memory pages it frees rather than returning them to the system (`auto`, `1` or `0`). Keeping them is much faster but takes 2-3x the peak memory. The default, `auto`, keeps them when twice the memory estimate, plus one further estimate of headroom, fits what the job can be given (the system memory on a single machine, or `--maxMemory`), and then requests twice the estimate; otherwise the pages are returned and the request is the estimate itself. Where that ceiling is not known, the pages are returned. Corresponds to `<consolidated retain_pages>` in the configuration.
429429
* **Large, repeat-rich genomes**: turn on `partialOrderAlignmentMaskFilter` in the `<bar><poa>` section of the config (e.g. `1000`; the default `-1` is off). On 20-30 Gb salamander genomes, leaving it off cost ~38x the BAR time and ran out of memory on a 2 TB node; with it on, BAR took under an hour. `partialOrderAlignmentWindow` is a far cheaper lever: halving it to `5000` saved ~25% memory and ~30% BAR time.
430-
* **Which base aligner**: `<bar baseAligner>` selects it -- `abpoa` (the default), `minipoa`, or the deprecated `pecan`. The two levers above live in `<bar><poa>` and apply to **abpoa only**; under `minipoa` the equivalents are `minipoaMaskFilter` and `minipoaWindow` in `<bar><minipoa>` (and `--barMaskFilter` writes to whichever engine is selected). minipoa uses roughly half abpoa's memory at the *same* window, and ships the same `10000`: its memory is quadratic-ish in the window as well, and on human/chimp chr10 a 50 kb window is already OOM-killed while BAR time is flat from 10 kb to 30 kb. Raising `minipoaWindow` buys nothing measured and costs a lot; if you do raise it, note the `cactus_consolidated` memory estimate uses abpoa's fitted exponent, which under-requests badly for minipoa at larger windows. minipoa shares abpoa's substitution matrix (from `<bar><poa>`) but has its own gap penalties, `minipoaGapOpenPenalty`/`minipoaGapExtensionPenalty` in `<bar><minipoa>`. They are deliberately much lower than abpoa's: abpoa's gap model is convex, so past roughly 28bp its effective extension is 1 rather than 30, and giving minipoa's single affine piece abpoa's first-piece value of 30 prices long gaps about 30x too high. `--lastTrain` reaches both engines: it fits a single affine gap model, which is minipoa's model exactly, so minipoa gets the learned open/extend verbatim while abPOA additionally gets the synthesised second piece it needs to stay stable. minipoa inherits the learned substitution matrix too, via the empty `minipoaSubMatrix`.
430+
* **Which base aligner**: `<bar baseAligner>` selects it -- `abpoa` (the default), `minipoa`, or the deprecated `pecan`. The two levers above live in `<bar><poa>` and apply to **abpoa only**; under `minipoa` the equivalents are `minipoaMaskFilter` and `minipoaWindow` in `<bar><minipoa>` (and `--barMaskFilter` writes to whichever engine is selected). minipoa uses roughly half abpoa's memory at the *same* window, and ships the same `10000`: its memory is quadratic-ish in the window as well, and on human/chimp chr10 a 50 kb window is already OOM-killed while BAR time is flat from 10 kb to 30 kb. Raising `minipoaWindow` buys nothing measured and costs a lot; if you do raise it, note the `cactus_consolidated` memory estimate uses abpoa's fitted exponent, which under-requests badly for minipoa at larger windows. minipoa shares abpoa's substitution matrix (from `<bar><poa>`) but has its own gap penalties in `<bar><minipoa>`: `minipoaGapOpenPenalty`/`minipoaGapExtensionPenalty`, and a second piece, `minipoaGapOpenPenalty2`/`minipoaGapExtensionPenalty2`, that makes the gap cost convex exactly as abpoa's is. They ship with abpoa's values (400/30 and 1200/1), and together with the guide tree (`minipoaProgressiveMode`) that makes minipoa as accurate as abpoa on the evolver tests (mammals 0.854/0.652 against 0.853/0.652). Setting the second piece to 0 leaves a single affine piece, about a quarter cheaper in time and memory; it then needs a much lower extension than abpoa's first piece (the best found was 600/4), since past roughly 28bp abpoa's effective extension is 1 rather than 30. `--lastTrain` reaches both engines: it fits a single affine gap model, so minipoa gets the learned open/extend as its only piece, while abPOA additionally gets the synthesised second piece it needs to stay stable. minipoa inherits the learned substitution matrix too, via the empty `minipoaSubMatrix`.
431431

432432
On a cluster with partitions and/or time limits, make sure to use
433433

0 commit comments

Comments
 (0)