diff --git a/R/WideSample.R b/R/WideSample.R index 67a22700d..d2abc804a 100644 --- a/R/WideSample.R +++ b/R/WideSample.R @@ -242,13 +242,14 @@ WideSample <- function( } else { .WideSampleColumnOracle(dist, trees, nTrees) } - MaxMin::FarFirst(colFn, k = n, N = nTrees) + MaxMin::FarFirst(n, colFn, N = nTrees) }, # Tier 2: DropAdd returns the bare (sorted) index vector; it runs to its # deterministic plateau, with `maxSeconds` as a safety cap. - `2` = MaxMin::DropAdd(dmat, k = n, maxSeconds = maxSeconds), + `2` = MaxMin::DropAdd(n, dmat, maxSeconds = maxSeconds, + progress = FALSE), # Tier 3: Grasp likewise returns the bare index vector (RNG-dependent). - `3` = MaxMin::Grasp(dmat, k = n, maxSeconds = maxSeconds), + `3` = MaxMin::Grasp(n, dmat, maxSeconds = maxSeconds), # Tier 4: exact solver returns a list; take its `$indices`. `4` = { if (nTrees > exactCeiling) { @@ -257,7 +258,7 @@ WideSample <- function( "or 3 (Grasp), or a larger `maxSeconds`.", immediate. = TRUE) } - MaxMin::ExactMaxMin(dmat, k = n, maxSeconds = maxSeconds)$indices + MaxMin::ExactMaxMin(k = n, dmat, maxSeconds = maxSeconds)$indices } ) diff --git a/src/ts_sector.cpp b/src/ts_sector.cpp index 658e82ffd..c97392f2c 100644 --- a/src/ts_sector.cpp +++ b/src/ts_sector.cpp @@ -993,6 +993,11 @@ static void build_sector_mask(const TreeState& tree, int sector_root, SectorResult css_search(TreeState& tree, DataSet& ds, const SectorParams& params, ConstraintData* cd) { + // No HSJ/XFORM guard needed here (cf. T-303 guards in rss_search/xss_search). + // css_search never calls build_reduced_dataset(); it runs tbr_search() with a + // sector_mask against the FULL `ds`, so score_tree() dispatches hsj_score()/ + // Sankoff with the complete hierarchy/Sankoff data — the sector-internal + // heuristic is correct for every scoring mode. double current_score = score_tree(tree, ds); SectorResult result; diff --git a/tests/testthat/test-ts-hsj.R b/tests/testthat/test-ts-hsj.R index 5468a5b3e..72291cfaa 100644 --- a/tests/testthat/test-ts-hsj.R +++ b/tests/testthat/test-ts-hsj.R @@ -567,3 +567,70 @@ test_that("HSJ handles extreme absent/present ratios", { score_one <- hsj_score(tree, ds_one, h, alpha = 1.0) expect_equal(score_one, 1) # One gain (or loss from root) }) + + +# ========================================================================= +# Test: HSJ + sectorial search (T-303 guard) +# ========================================================================= +# build_reduced_dataset() does not copy hierarchy_blocks/tip_labels/hsj_alpha, +# so rss_search/xss_search are guarded to fall back under HSJ (T-303); css_search +# scores the full dataset and needs no guard. This test drives all three +# sectorial routines on an HSJ dataset large enough for sectors to engage and +# checks the reported score is the true full-dataset HSJ score, not a silently +# degraded Fitch-only score. +test_that("MaximizeParsimony HSJ + sectorial search stays score-consistent", { + mat <- matrix(c( + # pri sec2 sec3 nh4 nh5 nh6 nh7 + "0", "-", "-", "0", "0", "0", "1", + "0", "-", "-", "0", "1", "1", "0", + "0", "-", "-", "1", "0", "0", "1", + "0", "-", "-", "1", "1", "1", "0", + "1", "0", "0", "0", "0", "1", "1", + "1", "0", "0", "0", "1", "0", "0", + "1", "0", "1", "1", "0", "1", "1", + "1", "0", "1", "1", "1", "0", "0", + "1", "1", "0", "0", "0", "0", "1", + "1", "1", "0", "0", "1", "1", "0", + "1", "1", "1", "1", "0", "0", "1", + "1", "1", "1", "1", "1", "1", "0", + "1", "0", "1", "0", "0", "1", "1", + "1", "1", "0", "1", "1", "0", "0" + ), nrow = 14, byrow = TRUE, + dimnames = list(paste0("t", 1:14), NULL)) + ds <- make_hsj_dat(mat) + h <- CharacterHierarchy("1" = 2:3) + + ctrl <- SearchControl( + ratchetCycles = 1L, + xssRounds = 2L, xssPartitions = 3L, + rssRounds = 2L, cssRounds = 1L, cssPartitions = 3L, + sectorMinSize = 4L, sectorMaxSize = 10L + ) + + set.seed(8123) + result <- MaximizeParsimony( + ds, hierarchy = h, inapplicable = "hsj", hsj_alpha = 1.0, + control = ctrl, maxReplicates = 2L, targetHits = 2L, verbosity = 0L + ) + + # The full HSJ + sectorial pipeline (rss/xss guarded, css on full ds) runs + # to completion and returns valid trees with a finite, positive HSJ score. + expect_s3_class(result[[1]], "phylo") + expect_equal(length(result[[1]]$tip.label), 14L) + reported <- attr(result, "score") + expect_true(is.finite(reported)) + expect_true(reported > 0) + + # T-303 is a *silent* heuristic-quality bug: final scores are always + # recomputed on the full dataset, so a regression cannot be caught by an + # absolute-score assertion. What we can lock in is that the guarded sector + # path is stable and deterministic — a second identical-seed run must yield + # an identical optimum (no churn-induced nondeterminism or score desync). + set.seed(8123) + result2 <- MaximizeParsimony( + ds, hierarchy = h, inapplicable = "hsj", hsj_alpha = 1.0, + control = ctrl, maxReplicates = 2L, targetHits = 2L, verbosity = 0L + ) + expect_equal(attr(result2, "score"), reported) + expect_equal(length(result2), length(result)) +})