Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions R/WideSample.R
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
}
)

Expand Down
5 changes: 5 additions & 0 deletions src/ts_sector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
67 changes: 67 additions & 0 deletions tests/testthat/test-ts-hsj.R
Original file line number Diff line number Diff line change
Expand Up @@ -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))
})
Loading