From fe21dfc3986e246b44c8976d02d0651d31103cf7 Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Tue, 17 Mar 2026 11:36:44 +0000 Subject: [PATCH] Fix PhyDatToMatrix crash on zero-character phyDat PhyDatToMatrix() crashed when given a phyDat with nr=0 (e.g. from a star tree) because matrix() could not reconcile empty data with non-empty dimnames. Add an early return for the nr=0 case, returning a 0-column matrix with correct row names. This also fixes AddUnconstrained() when passed such a phyDat, since it delegates to PhyDatToMatrix() internally. --- DESCRIPTION | 2 +- NEWS.md | 6 ++++++ R/parse_files.R | 4 ++++ tests/testthat/test-ImposeConstraint.R | 21 +++++++++++++++++++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 55995a7f3..be6dff373 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: TreeTools Title: Create, Modify and Analyse Phylogenetic Trees -Version: 2.1.0.9007 +Version: 2.1.0.9008 Authors@R: c( person("Martin R.", 'Smith', role = c("aut", "cre", "cph"), email = "martin.smith@durham.ac.uk", diff --git a/NEWS.md b/NEWS.md index cd6b20bbe..9eea28c4f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,9 @@ +# TreeTools 2.1.0.9008 (2026-03-17) # + +- `PhyDatToMatrix()` no longer crashes on zero-character `phyDat` objects + (e.g. from a star tree); returns a 0-column matrix with correct row names. +- `AddUnconstrained()` handles zero-character `phyDat` input gracefully. + # TreeTools 2.1.0.9007 (2026-03-13) # - `duplicated.Splits()` uses hash-based O(n) de-duplication, replacing diff --git a/R/parse_files.R b/R/parse_files.R index c1288b4db..8c6fa50f7 100644 --- a/R/parse_files.R +++ b/R/parse_files.R @@ -809,6 +809,10 @@ PhyDatToMatrix <- function(dataset, ambigNA = FALSE, inappNA = ambigNA, } at <- attributes(dataset) + if (at[["nr"]] == 0L) { + return(matrix(character(0), nrow = length(at[["names"]]), ncol = 0, + dimnames = list(at[["names"]], NULL))) + } allLevels <- as.character(at[["allLevels"]]) if (inappNA) { allLevels[allLevels == "-"] <- NA_character_ diff --git a/tests/testthat/test-ImposeConstraint.R b/tests/testthat/test-ImposeConstraint.R index d4d2f67fe..4e28f291d 100644 --- a/tests/testthat/test-ImposeConstraint.R +++ b/tests/testthat/test-ImposeConstraint.R @@ -1,3 +1,24 @@ +test_that("AddUnconstrained() handles zero-character phyDat", { + star <- ape::read.tree(text = "(a,b,c,d);") + empty_pd <- MatrixToPhyDat(t(as.matrix(star))) + + # PhyDatToMatrix returns a correctly-dimensioned 0-column matrix + mat <- PhyDatToMatrix(empty_pd) + expect_equal(dim(mat), c(4L, 0L)) + expect_equal(rownames(mat), c("a", "b", "c", "d")) + + # AddUnconstrained returns 0-character phyDat with extra taxa + result <- AddUnconstrained(empty_pd, c("e", "f", "g")) + expect_s3_class(result, "phyDat") + expect_equal(names(result), c("a", "b", "c", "d", "e", "f", "g")) + expect_equal(attr(result, "nr"), 0L) + + # asPhyDat = FALSE returns a 0-column matrix + result_mat <- AddUnconstrained(empty_pd, c("e", "f", "g"), asPhyDat = FALSE) + expect_equal(dim(result_mat), c(7L, 0L)) + expect_equal(rownames(result_mat), c("a", "b", "c", "d", "e", "f", "g")) +}) + test_that("AddUnconstrained() works", { tips <- letters[1:9] constraint <- StringToPhyDat("0000?1111 000111111 0000??110", tips, FALSE)