diff --git a/DESCRIPTION b/DESCRIPTION index cd750cb3..cf12df23 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: TreeTools Title: Create, Modify and Analyse Phylogenetic Trees -Version: 2.2.0 +Version: 2.2.0.9001 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 541b04d3..6d837c30 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,5 @@ +# TreeTools 2.2.0.9001 # + # TreeTools 2.2.0 (2026-03-18) # ## New functionality diff --git a/src/splits.cpp b/src/splits.cpp index 0e698bf9..5700eebc 100644 --- a/src/splits.cpp +++ b/src/splits.cpp @@ -31,37 +31,15 @@ inline void check_16_bit(double x) { } } +// Core edge-to-splits computation; assumes validated inputs with n_tip >= 1. // Edges must be listed in 'strict' postorder, i.e. two-by-two -// [[Rcpp::export]] -Rcpp::RawMatrix cpp_edge_to_splits(const Rcpp::IntegerMatrix& edge, - const Rcpp::IntegerVector& order, - const Rcpp::IntegerVector& nTip) { - - // Check input is valid - if (edge.cols() != 2) { - Rcpp::stop("Edge matrix must contain two columns"); - } - +static Rcpp::RawMatrix edge_to_splits_impl( + const Rcpp::IntegerMatrix& edge, + const Rcpp::IntegerVector& order, + const uintx n_tip +) { const uintx n_edge = edge.rows(); - if (n_edge + 1 >= NOT_TRIVIAL) { - Rcpp::stop("Too many edges in tree for edge_to_splits: " // # nocov - "Contact maintainer for advice"); // # nocov - } - - if (nTip[0] < 1) { - if (nTip[0] == 0) { - return RawMatrix(0, 0); - } else { - Rcpp::stop("Tree must contain non-negative number of tips."); - } - } - - if (n_edge != static_cast(order.length())) { - Rcpp::stop("Length of `order` must equal number of edges"); - } - const uintx n_node = n_edge + 1; - const uintx n_tip = nTip[0]; const uintx n_bin = ((n_tip - 1) / BIN_SIZE) + 1; if (n_edge == n_tip || n_tip < 4) { @@ -140,6 +118,37 @@ Rcpp::RawMatrix cpp_edge_to_splits(const Rcpp::IntegerMatrix& edge, return ret; } +// [[Rcpp::export]] +Rcpp::RawMatrix cpp_edge_to_splits(const Rcpp::IntegerMatrix& edge, + const Rcpp::IntegerVector& order, + const Rcpp::IntegerVector& nTip) { + + // Check input is valid + if (edge.cols() != 2) { + Rcpp::stop("Edge matrix must contain two columns"); + } + + const uintx n_edge = edge.rows(); + if (n_edge + 1 >= NOT_TRIVIAL) { + Rcpp::stop("Too many edges in tree for edge_to_splits: " // # nocov + "Contact maintainer for advice"); // # nocov + } + + if (nTip[0] < 1) { + if (nTip[0] == 0) { + return RawMatrix(0, 0); + } else { + Rcpp::stop("Tree must contain non-negative number of tips."); + } + } + + if (n_edge != static_cast(order.length())) { + Rcpp::stop("Length of `order` must equal number of edges"); + } + + return edge_to_splits_impl(edge, order, static_cast(nTip[0])); +} + // [[Rcpp::export]] LogicalVector duplicated_splits(const RawMatrix splits, const LogicalVector fromLast) { diff --git a/tests/testthat/test-Splits.R b/tests/testthat/test-Splits.R index aaa20792..14e270bf 100644 --- a/tests/testthat/test-Splits.R +++ b/tests/testthat/test-Splits.R @@ -151,6 +151,21 @@ test_that("as.Splits.multiPhylo()", { expect_equal(as.Splits(randomTrees[[1]]), as.Splits(randomTrees)[[1]]) + + # asSplits = FALSE returns raw matrices + rawResults <- as.Splits(randomTrees, asSplits = FALSE) + for (i in seq_along(randomTrees)) { + expect_equal(rawResults[[i]], + as.Splits(randomTrees[[i]], asSplits = FALSE)) + } + + # Trees with different tip label order handled correctly + relabelled <- randomTrees + relabelled[[1]] <- RenumberTips(relabelled[[1]], rev(seq_len(11L))) + fallbackResults <- as.Splits(relabelled) + expect_equal(as.Splits(relabelled[[2]], + tipLabels = unique(unlist(TipLabels(relabelled)))), + fallbackResults[[2]]) }) test_that("as.Splits.Splits()", {