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
38 changes: 38 additions & 0 deletions .github/workflows/R-CMD-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,21 @@ jobs:
needs: |
check
coverage
# MaxMin url:: -- this is the Windows leg, so it cannot use the Linux
# tarball the `runner.os != 'Windows'` step below selects. Without a
# url:: reference the solve fails outright ("Can't find package called
# MaxMin"): pak does not read `Additional_repositories`, so the drat's
# indexed contrib/ layout is invisible to it. See ms609/packages'
# tools/publish-maxmin.R, which publishes this flat alias for exactly
# this purpose.
extra-packages: |
url::https://ms609.github.io/packages/bin/windows/MaxMin_latest.zip
# install-pandoc must stay explicit -- see the long note in
# agent-check.yml's windows leg. Auto-detect runs a second, fresh
# `pak::pkg_deps(".")` solve that cannot see extra-packages, so it
# fails on MaxMin by name; the branch only fires when pandoc is off
# PATH, which is why only the non-Linux legs hit it.
install-pandoc: true
# cache-version bumped to 2: the v1 caches were built while a
# project-level .Rprofile shadowed ~/.Rprofile, so every package
# in them was compiled from source. Retire them once.
Expand Down Expand Up @@ -327,8 +342,18 @@ jobs:
with:
needs: |
check
# MaxMin url:: -- same reason as the core Windows leg. Added
# pre-emptively: this job was skipped in the run that diagnosed the
# failure (it is gated on `detect app changes`), so unlike the core leg
# it is not yet observed failing -- but it resolves the same
# `needs: check` Suggests on the same platform, so it would.
extra-packages: |
local::.
url::https://ms609.github.io/packages/bin/windows/MaxMin_latest.zip
# install-pandoc explicit for the same reason as the core Windows leg
# (auto-detect's `pak::pkg_deps(".")` solve cannot see extra-packages).
# `false` here: this job runs shinytest2, not vignettes.
install-pandoc: false
# cache-version bumped to 2: the v1 caches were built while a
# project-level .Rprofile shadowed ~/.Rprofile, so every package
# in them was compiled from source. Retire them once.
Expand Down Expand Up @@ -410,6 +435,19 @@ jobs:
with:
needs: |
check
# MaxMin url:: -- same mechanism as the Windows and Linux legs, picked
# per architecture (macOS-latest is arm64; macos-15-intel is x86_64).
# Added pre-emptively and NOT yet observed failing: this job is
# `needs: core`, so it was skipped while the Windows leg was red.
# Fixing that leg unskips this one, which would then hit the identical
# unresolvable-Suggests error -- note `_R_CHECK_FORCE_SUGGESTS_: false`
# does not help, since it governs R CMD check, not pak's solve.
extra-packages: |
${{ matrix.config.os == 'macos-15-intel' && 'url::https://ms609.github.io/packages/bin/macosx/big-sur-x86_64/MaxMin_latest.tgz' || 'url::https://ms609.github.io/packages/bin/macosx/big-sur-arm64/MaxMin_latest.tgz' }}
# install-pandoc explicit for the same reason as the Windows legs; also
# pre-emptive, since whether a macOS runner ships pandoc on PATH is
# exactly the condition that decides whether the bad branch fires.
install-pandoc: true
# cache-version bumped to 2: the v1 caches were built while a
# project-level .Rprofile shadowed ~/.Rprofile, so every package
# in them was compiled from source. Retire them once.
Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/agent-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,28 @@ jobs:
uses: r-lib/actions/setup-r-dependencies@v2
with:
needs: check
# MaxMin url:: -- the Windows counterpart of the arm64 leg's line, and
# the reason this leg used to fail before compiling anything. MaxMin is
# not on CRAN, and pak does NOT read `Additional_repositories` from
# DESCRIPTION (confirmed empirically 2026-08-03, recorded in
# ms609/packages' tools/publish-maxmin.R), so the indexed drat layout
# alone leaves it unresolvable and the solve dies with "Can't find
# package called MaxMin". Naming the flat, unversioned alias directly
# is the mechanism that publish script exists to provide; it always
# points at the current build, so it needs no edit when MaxMin moves.
extra-packages: |
url::https://ms609.github.io/packages/bin/windows/MaxMin_latest.zip
# install-pandoc MUST stay explicit here, and the url:: above is not
# sufficient without it. Left unset, setup-r-dependencies auto-detects
# by running `pak::pkg_deps(".", dependencies = list(direct = "all"))`
# -- a SECOND, fresh solve that sees neither the lockfile nor
# extra-packages, so it looks MaxMin up by name and dies with
# "* local::.: Can't install dependency MaxMin" even though MaxMin has
# just been installed successfully. That branch only runs when pandoc
# is absent from PATH, which is exactly why the Linux legs never hit it
# (their runners ship pandoc) and this one did. `true` rather than
# `false` because this leg checks with vignettes.
install-pandoc: true
# cache-version bumped to 2: the v1 caches were built while a
# project-level .Rprofile shadowed ~/.Rprofile, so every package
# in them was compiled from source. Retire them once.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ dataset <- ReadAsPhyDat(dataFile)
treeFile <- dataFile # Test mode
trees <- read.nexus(treeFile)
set.seed(<SEED>)
trees <- WideSample(trees[1:125], 48)
trees <- WideSample(trees[77:125], 48)

# Compute tree distances
dists <- TreeDist::ClusteringInfoDistance(trees)
Expand Down Expand Up @@ -92,30 +92,77 @@ threshold <- 0.5
bestMethodId <- which.max(c(threshold, pamSil, hSil, kSil))
bestCluster <- c("none", "pam", "hmm", "kmn")[bestMethodId]

# No significant clustering was found.
clustering <- 1 # Assign all trees to single cluster
# Best clustering was part. around medoids:
# Silhouette coefficient = 0.83471

# No clustering structure: Plot consensus tree
cons <- ConsensusWithout(
trees = trees,
tip = c("Ussunia", "Craniops", "Paterimitra", "Yuganotheca_elegans", "Gasconsia"),
p = 1
)
# Store the cluster to which each tree is optimally assigned:
clustering <- switch(bestCluster, pam = pamCluster, hmm = hCluster, kmn = kCluster, 1)
nClusters <- length(unique(clustering))
clusterCol <- c("#7a6c36", "#864885") # Arbitrarily


# Plot consensus of each tree cluster
par(mfrow = c(1, 2)) # Plotting area layout
tipCols <- Rogue::ColByStability(trees) # Colour tips by stability

# Compute all cluster consensus trees:
allCons <- lapply(seq_len(2), function(i) {
clusterTrees <- trees[clustering == i]
cons <- ConsensusWithout(
trees = clusterTrees,
tip = "Yuganotheca_elegans",
p = 1
)

# Root tree
cons <- RootTree(cons, "Namacalathus")
# Set unit edge length
cons$edge.length <- rep.int(1, nrow(cons$edge))
# Rotate nodes, to display clades in order of size
cons <- SortTree(cons, order = names(dataset))
plottedTree <- cons # Store for future reference
tipCols <- Rogue::ColByStability(trees)[cons$tip.label]

# Plot consensus tree
plot(
cons,
edge.width = 2, # Widen lines
font = 3, # Italicize labels
cex = 0.83, # Shrink tip font size
tip.color = tipCols
)
cons$edge.length <- rep.int(1, nrow(cons$edge))
TreeTools::SortTree(cons)
})

# Compare splits across clusters to highlight unique edges
allSplits <- lapply(allCons, TreeTools::as.Splits)

# Plot each consensus tree in turn:
for (i in seq_len(2)) {
cons <- allCons[[i]]
nTip <- ape::Ntip(cons)
mySplits <- allSplits[[i]]
otherIdx <- setdiff(seq_len(2), i)
shared <- integer(length(mySplits))
for (j in otherIdx) {
if (length(allSplits[[j]]) > 0)
shared <- shared + (mySplits %in% allSplits[[j]])
}
uniqueness <- 1 - shared / length(otherIdx)
greyRgb <- col2rgb("grey70")[, 1]
colRgb <- col2rgb(clusterCol[i])[, 1]
edgeCol <- rep(clusterCol[i], nrow(cons$edge))
splitNodes <- as.integer(names(mySplits))
for (e in seq_len(nrow(cons$edge))) {
child <- cons$edge[e, 2]
if (child > nTip) {
si <- match(child, splitNodes)
if (!is.na(si)) {
bl <- greyRgb + (colRgb - greyRgb) * uniqueness[si]
edgeCol[e] <- rgb(bl[1], bl[2], bl[3], maxColorValue = 255)
}
}
}
plot(
cons,
edge.width = 2,
font = 3,
cex = 0.83,
edge.color = edgeCol,
tip.color = tipCols[cons$tip.label]
)
legend(
"bottomright",
paste("Cluster", i),
pch = 15,
pt.cex = 1.5,
col = clusterCol[i],
bty = "n"
)
}
25 changes: 23 additions & 2 deletions inst/Parsimony/tests/testthat/setup.R
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,32 @@ new_app_driver <- function(name, ...) {
# Retry a couple of times before propagating, so a transient chromote hiccup
# doesn't fail an otherwise-healthy run. A first-try success is the common path
# and incurs no delay.
#
# debounceWait is load-bearing, not padding. mod_data's nTree / treeRange
# watchers are debounce()d (aJiffy = 42 ms, typingJiffy = 105 ms), and a pending
# debounce timer does NOT make Shiny busy -- there is nothing to recompute until
# it expires, so wait_for_idle() can return before a debounced watcher has even
# seen the input the test just set. A download captured at that point encodes the
# state BEFORE the last set_inputs(); on a machine where the timer does fire in
# time it encodes the state after. The same test then yields different snapshots
# on different machines. Sleeping past the longest debounce window and waiting
# again lets that work start and finish, which is what makes these baselines
# reproducible rather than timing-dependent.
#
# This is how the Distribution baseline came to record `trees[1:125]` for a
# state its test had set to c(77, 125) -- noticed only once the MaxMin
# dependency fix let CI reach the suite at all.
# ---------------------------------------------------------------------------
wait_stable <- function(app, timeout = 30000, attempts = 3L) {
wait_stable <- function(app, timeout = 30000, attempts = 3L,
debounceWait = 0.25) {
for (i in seq_len(attempts)) {
ok <- tryCatch(
{ app$wait_for_idle(timeout = timeout); TRUE },
{
app$wait_for_idle(timeout = timeout)
Sys.sleep(debounceWait)
app$wait_for_idle(timeout = timeout)
TRUE
},
error = function(e) if (i == attempts) stop(e) else FALSE
)
if (isTRUE(ok)) break
Expand Down
9 changes: 9 additions & 0 deletions inst/Parsimony/tests/testthat/test-Distribution.R
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ test_that("Distribution and concordance plots render correctly", {
}

app$set_inputs(`data-dataSource` = "Sun2018")
# Let the dataset load finish before setting anything that depends on it.
# UpdateAllTrees() resets treeRange to the full 1:nTrees span whenever the tree
# count changes (mod_data.R), so a `data-treeRange` that lands mid-load is
# silently clobbered by the reset -- and set_inputs()' default 4 s wait is not
# long enough for Sun2018's 125 trees, as the "Server did not update any output
# values within 4 seconds" warning on this line reported. Whether the clobber
# beat the capture varied by machine, which is what made this snapshot record
# trees[1:125] on some runs and trees[77:125] on others.
wait_stable(app)
app$set_inputs(plotFormat = "clus")
app$set_inputs(`data-treeRange` = c(77, 125))
wait_stable(app)
Expand Down
Loading