From 1cb0ea4ab99f1512060f07394f20882fa5e955cc Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Thu, 19 Mar 2026 20:42:27 +0000 Subject: [PATCH 1/2] cpp_edge_to_splits_batch --- DESCRIPTION | 2 +- NEWS.md | 8 +++ R/RcppExports.R | 4 ++ R/Splits.R | 35 +++++++++- src/RcppExports.cpp | 14 ++++ src/splits.cpp | 113 +++++++++++++++++++++++-------- tests/testthat/test-Splits.R | 21 ++++++ tests/testthat/test-splits.cpp.R | 53 +++++++++++++++ 8 files changed, 220 insertions(+), 30 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index cd750cb39..cf12df238 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 541b04d3e..802f608d0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,11 @@ +# TreeTools 2.2.0.9001 # + +## Performance + +- `as.Splits.multiPhylo()` uses a batch C++ path (`cpp_edge_to_splits_batch`) + when all trees share the same tip label order, eliminating per-tree R→C++ + overhead. + # TreeTools 2.2.0 (2026-03-18) # ## New functionality diff --git a/R/RcppExports.R b/R/RcppExports.R index ee96e8d01..70a28a643 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -101,6 +101,10 @@ cpp_edge_to_splits <- function(edge, order, nTip) { .Call(`_TreeTools_cpp_edge_to_splits`, edge, order, nTip) } +cpp_edge_to_splits_batch <- function(edge_list, order_list, n_tip) { + .Call(`_TreeTools_cpp_edge_to_splits_batch`, edge_list, order_list, n_tip) +} + duplicated_splits <- function(splits, fromLast) { .Call(`_TreeTools_duplicated_splits`, splits, fromLast) } diff --git a/R/Splits.R b/R/Splits.R index bd57e1ff4..1cd06ae79 100644 --- a/R/Splits.R +++ b/R/Splits.R @@ -119,7 +119,40 @@ edge_to_splits <- function(edge, edgeOrder, tipLabels = NULL, asSplits = TRUE, #' @export as.Splits.multiPhylo <- function(x, tipLabels = unique(unlist(TipLabels(x))), asSplits = TRUE, ...) { - lapply(x, as.Splits.phylo, tipLabels = tipLabels, asSplits = asSplits) + nTip <- length(tipLabels) + + # Batch path: single C++ call when all trees share the same tip label order + treeLabels <- TipLabels(x) + allMatch <- all(vapply(treeLabels, identical, logical(1), tipLabels)) + + if (allMatch && length(x) > 0L) { + edges <- lapply(x, `[[`, "edge") + # cpp_edge_to_splits_batch expects 0-based order indices + orders <- lapply(x, function(tr) { + edge <- tr[["edge"]] + nEdge <- nrow(edge) + ord <- attr(tr, "order")[[1]] + if (length(ord) == 0) { + postorder_order(edge) - 1L + } else { + switch(ord, + "preorder" = rev(seq_len(nEdge)) - 1L, + "postorder" = seq_len(nEdge) - 1L, + postorder_order(edge) - 1L) + } + }) + rawList <- cpp_edge_to_splits_batch(edges, orders, nTip) + + if (asSplits) { + lapply(rawList, function(sp) { + structure(sp, nTip = nTip, tip.label = tipLabels, class = "Splits") + }) + } else { + rawList + } + } else { + lapply(x, as.Splits.phylo, tipLabels = tipLabels, asSplits = asSplits) + } } #' @rdname Splits diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp index 123c171a6..fa5ff378c 100644 --- a/src/RcppExports.cpp +++ b/src/RcppExports.cpp @@ -325,6 +325,19 @@ BEGIN_RCPP return rcpp_result_gen; END_RCPP } +// cpp_edge_to_splits_batch +Rcpp::List cpp_edge_to_splits_batch(const Rcpp::List& edge_list, const Rcpp::List& order_list, const int n_tip); +RcppExport SEXP _TreeTools_cpp_edge_to_splits_batch(SEXP edge_listSEXP, SEXP order_listSEXP, SEXP n_tipSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< const Rcpp::List& >::type edge_list(edge_listSEXP); + Rcpp::traits::input_parameter< const Rcpp::List& >::type order_list(order_listSEXP); + Rcpp::traits::input_parameter< const int >::type n_tip(n_tipSEXP); + rcpp_result_gen = Rcpp::wrap(cpp_edge_to_splits_batch(edge_list, order_list, n_tip)); + return rcpp_result_gen; +END_RCPP +} // duplicated_splits LogicalVector duplicated_splits(const RawMatrix splits, const LogicalVector fromLast); RcppExport SEXP _TreeTools_duplicated_splits(SEXP splitsSEXP, SEXP fromLastSEXP) { @@ -548,6 +561,7 @@ static const R_CallMethodDef CallEntries[] = { {"_TreeTools_path_lengths", (DL_FUNC) &_TreeTools_path_lengths, 3}, {"_TreeTools_renumber_tips_batch", (DL_FUNC) &_TreeTools_renumber_tips_batch, 4}, {"_TreeTools_cpp_edge_to_splits", (DL_FUNC) &_TreeTools_cpp_edge_to_splits, 3}, + {"_TreeTools_cpp_edge_to_splits_batch", (DL_FUNC) &_TreeTools_cpp_edge_to_splits_batch, 3}, {"_TreeTools_duplicated_splits", (DL_FUNC) &_TreeTools_duplicated_splits, 2}, {"_TreeTools_mask_splits", (DL_FUNC) &_TreeTools_mask_splits, 1}, {"_TreeTools_not_splits", (DL_FUNC) &_TreeTools_not_splits, 1}, diff --git a/src/splits.cpp b/src/splits.cpp index 0e698bf9c..d449b79fa 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,85 @@ 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])); +} + +// Batch version: single C++ call for N trees sharing the same tip count. +// [[Rcpp::export]] +Rcpp::List cpp_edge_to_splits_batch( + const Rcpp::List& edge_list, + const Rcpp::List& order_list, + const int n_tip +) { + const int n_tree = edge_list.size(); + if (n_tree != order_list.size()) { + Rcpp::stop("`edge_list` and `order_list` must have the same length"); + } + if (n_tip < 1) { + if (n_tip == 0) { + Rcpp::List ret(n_tree); + for (int i = 0; i < n_tree; ++i) { + ret[i] = RawMatrix(0, 0); + } + return ret; + } else { + Rcpp::stop("Tree must contain non-negative number of tips."); + } + } + + Rcpp::List ret(n_tree); + const uintx nt = static_cast(n_tip); + + for (int i = 0; i < n_tree; ++i) { + const IntegerMatrix edge = edge_list[i]; + const IntegerVector order = order_list[i]; + + if (edge.cols() != 2) { + Rcpp::stop("Edge matrix %d must contain two columns", i + 1); + } + const uintx n_edge = edge.rows(); + if (n_edge + 1 >= NOT_TRIVIAL) { + Rcpp::stop("Too many edges in tree %d for edge_to_splits", i + 1); // # nocov + } + if (n_edge != static_cast(order.length())) { + Rcpp::stop("Length of `order` must equal number of edges in tree %d", + i + 1); + } + + ret[i] = edge_to_splits_impl(edge, order, nt); + } + + return ret; +} + // [[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 aaa20792e..7c9a6371c 100644 --- a/tests/testthat/test-Splits.R +++ b/tests/testthat/test-Splits.R @@ -151,6 +151,27 @@ test_that("as.Splits.multiPhylo()", { expect_equal(as.Splits(randomTrees[[1]]), as.Splits(randomTrees)[[1]]) + + # Batch path: uniform labels, asSplits = FALSE + rawResults <- as.Splits(randomTrees, asSplits = FALSE) + for (i in seq_along(randomTrees)) { + expect_equal(rawResults[[i]], + as.Splits(randomTrees[[i]], asSplits = FALSE)) + } + + # Preorder trees also hit the batch path + preorderTrees <- lapply(randomTrees, Preorder) + class(preorderTrees) <- "multiPhylo" + expect_equal(as.Splits(preorderTrees)[[3]], + as.Splits(preorderTrees[[3]])) + + # Fallback: trees with different tip label order require renumbering + 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()", { diff --git a/tests/testthat/test-splits.cpp.R b/tests/testthat/test-splits.cpp.R index 52b8346f8..7fa1fbe32 100644 --- a/tests/testthat/test-splits.cpp.R +++ b/tests/testthat/test-splits.cpp.R @@ -1,3 +1,56 @@ +test_that("cpp_edge_to_splits_batch() matches per-tree results", { + trees <- as.phylo(c(30899669, 9149275, 12823175, 19740197), 11L, + seq_len(11L)) + nTip <- 11L + edges <- lapply(trees, `[[`, "edge") + orders <- lapply(trees, function(tr) { + postorder_order(tr[["edge"]]) - 1L + }) + + batchResult <- cpp_edge_to_splits_batch(edges, orders, nTip) + perTree <- lapply(seq_along(trees), function(i) { + cpp_edge_to_splits(edges[[i]], orders[[i]], nTip) + }) + + expect_length(batchResult, length(trees)) + for (i in seq_along(trees)) { + expect_equal(batchResult[[i]], perTree[[i]]) + } +}) + +test_that("cpp_edge_to_splits_batch() handles edge cases", { + # Empty list + expect_length(cpp_edge_to_splits_batch(list(), list(), 5L), 0) + + # n_tip == 0 + result <- cpp_edge_to_splits_batch( + list(matrix(1L, 10, 2)), list(0:9), 0L + ) + expect_equal(result[[1]], matrix(raw(0), 0, 0)) + + # Negative n_tip + expect_error(cpp_edge_to_splits_batch(list(), list(), -1L), + "non-negative number of tips") + + # Mismatched list lengths + expect_error( + cpp_edge_to_splits_batch(list(matrix(1L, 3, 2)), list(), 5L), + "same length" + ) + + # Bad edge matrix (3 columns) + expect_error( + cpp_edge_to_splits_batch(list(matrix(1L, 3, 3)), list(0:2), 3L), + "two columns" + ) + + # Mismatched order length + expect_error( + cpp_edge_to_splits_batch(list(matrix(1L, 10, 2)), list(0:8), 5L), + "number of edges" + ) +}) + test_that("Bad data results in error", { skip_if(Sys.getenv("USING_ASAN") != "") expect_error(cpp_edge_to_splits(matrix(1, 3, 3), 1:3, 3), From a5613d9b2be9a31fa6b979dccded4170f6aa1e0d Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Fri, 20 Mar 2026 06:10:02 +0000 Subject: [PATCH 2/2] Roll back --- NEWS.md | 6 ---- R/RcppExports.R | 4 --- R/Splits.R | 35 +-------------------- src/RcppExports.cpp | 14 --------- src/splits.cpp | 48 ----------------------------- tests/testthat/test-Splits.R | 10 ++---- tests/testthat/test-splits.cpp.R | 53 -------------------------------- 7 files changed, 3 insertions(+), 167 deletions(-) diff --git a/NEWS.md b/NEWS.md index 802f608d0..6d837c308 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,11 +1,5 @@ # TreeTools 2.2.0.9001 # -## Performance - -- `as.Splits.multiPhylo()` uses a batch C++ path (`cpp_edge_to_splits_batch`) - when all trees share the same tip label order, eliminating per-tree R→C++ - overhead. - # TreeTools 2.2.0 (2026-03-18) # ## New functionality diff --git a/R/RcppExports.R b/R/RcppExports.R index 70a28a643..ee96e8d01 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -101,10 +101,6 @@ cpp_edge_to_splits <- function(edge, order, nTip) { .Call(`_TreeTools_cpp_edge_to_splits`, edge, order, nTip) } -cpp_edge_to_splits_batch <- function(edge_list, order_list, n_tip) { - .Call(`_TreeTools_cpp_edge_to_splits_batch`, edge_list, order_list, n_tip) -} - duplicated_splits <- function(splits, fromLast) { .Call(`_TreeTools_duplicated_splits`, splits, fromLast) } diff --git a/R/Splits.R b/R/Splits.R index 1cd06ae79..bd57e1ff4 100644 --- a/R/Splits.R +++ b/R/Splits.R @@ -119,40 +119,7 @@ edge_to_splits <- function(edge, edgeOrder, tipLabels = NULL, asSplits = TRUE, #' @export as.Splits.multiPhylo <- function(x, tipLabels = unique(unlist(TipLabels(x))), asSplits = TRUE, ...) { - nTip <- length(tipLabels) - - # Batch path: single C++ call when all trees share the same tip label order - treeLabels <- TipLabels(x) - allMatch <- all(vapply(treeLabels, identical, logical(1), tipLabels)) - - if (allMatch && length(x) > 0L) { - edges <- lapply(x, `[[`, "edge") - # cpp_edge_to_splits_batch expects 0-based order indices - orders <- lapply(x, function(tr) { - edge <- tr[["edge"]] - nEdge <- nrow(edge) - ord <- attr(tr, "order")[[1]] - if (length(ord) == 0) { - postorder_order(edge) - 1L - } else { - switch(ord, - "preorder" = rev(seq_len(nEdge)) - 1L, - "postorder" = seq_len(nEdge) - 1L, - postorder_order(edge) - 1L) - } - }) - rawList <- cpp_edge_to_splits_batch(edges, orders, nTip) - - if (asSplits) { - lapply(rawList, function(sp) { - structure(sp, nTip = nTip, tip.label = tipLabels, class = "Splits") - }) - } else { - rawList - } - } else { - lapply(x, as.Splits.phylo, tipLabels = tipLabels, asSplits = asSplits) - } + lapply(x, as.Splits.phylo, tipLabels = tipLabels, asSplits = asSplits) } #' @rdname Splits diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp index fa5ff378c..123c171a6 100644 --- a/src/RcppExports.cpp +++ b/src/RcppExports.cpp @@ -325,19 +325,6 @@ BEGIN_RCPP return rcpp_result_gen; END_RCPP } -// cpp_edge_to_splits_batch -Rcpp::List cpp_edge_to_splits_batch(const Rcpp::List& edge_list, const Rcpp::List& order_list, const int n_tip); -RcppExport SEXP _TreeTools_cpp_edge_to_splits_batch(SEXP edge_listSEXP, SEXP order_listSEXP, SEXP n_tipSEXP) { -BEGIN_RCPP - Rcpp::RObject rcpp_result_gen; - Rcpp::RNGScope rcpp_rngScope_gen; - Rcpp::traits::input_parameter< const Rcpp::List& >::type edge_list(edge_listSEXP); - Rcpp::traits::input_parameter< const Rcpp::List& >::type order_list(order_listSEXP); - Rcpp::traits::input_parameter< const int >::type n_tip(n_tipSEXP); - rcpp_result_gen = Rcpp::wrap(cpp_edge_to_splits_batch(edge_list, order_list, n_tip)); - return rcpp_result_gen; -END_RCPP -} // duplicated_splits LogicalVector duplicated_splits(const RawMatrix splits, const LogicalVector fromLast); RcppExport SEXP _TreeTools_duplicated_splits(SEXP splitsSEXP, SEXP fromLastSEXP) { @@ -561,7 +548,6 @@ static const R_CallMethodDef CallEntries[] = { {"_TreeTools_path_lengths", (DL_FUNC) &_TreeTools_path_lengths, 3}, {"_TreeTools_renumber_tips_batch", (DL_FUNC) &_TreeTools_renumber_tips_batch, 4}, {"_TreeTools_cpp_edge_to_splits", (DL_FUNC) &_TreeTools_cpp_edge_to_splits, 3}, - {"_TreeTools_cpp_edge_to_splits_batch", (DL_FUNC) &_TreeTools_cpp_edge_to_splits_batch, 3}, {"_TreeTools_duplicated_splits", (DL_FUNC) &_TreeTools_duplicated_splits, 2}, {"_TreeTools_mask_splits", (DL_FUNC) &_TreeTools_mask_splits, 1}, {"_TreeTools_not_splits", (DL_FUNC) &_TreeTools_not_splits, 1}, diff --git a/src/splits.cpp b/src/splits.cpp index d449b79fa..5700eebc4 100644 --- a/src/splits.cpp +++ b/src/splits.cpp @@ -149,54 +149,6 @@ Rcpp::RawMatrix cpp_edge_to_splits(const Rcpp::IntegerMatrix& edge, return edge_to_splits_impl(edge, order, static_cast(nTip[0])); } -// Batch version: single C++ call for N trees sharing the same tip count. -// [[Rcpp::export]] -Rcpp::List cpp_edge_to_splits_batch( - const Rcpp::List& edge_list, - const Rcpp::List& order_list, - const int n_tip -) { - const int n_tree = edge_list.size(); - if (n_tree != order_list.size()) { - Rcpp::stop("`edge_list` and `order_list` must have the same length"); - } - if (n_tip < 1) { - if (n_tip == 0) { - Rcpp::List ret(n_tree); - for (int i = 0; i < n_tree; ++i) { - ret[i] = RawMatrix(0, 0); - } - return ret; - } else { - Rcpp::stop("Tree must contain non-negative number of tips."); - } - } - - Rcpp::List ret(n_tree); - const uintx nt = static_cast(n_tip); - - for (int i = 0; i < n_tree; ++i) { - const IntegerMatrix edge = edge_list[i]; - const IntegerVector order = order_list[i]; - - if (edge.cols() != 2) { - Rcpp::stop("Edge matrix %d must contain two columns", i + 1); - } - const uintx n_edge = edge.rows(); - if (n_edge + 1 >= NOT_TRIVIAL) { - Rcpp::stop("Too many edges in tree %d for edge_to_splits", i + 1); // # nocov - } - if (n_edge != static_cast(order.length())) { - Rcpp::stop("Length of `order` must equal number of edges in tree %d", - i + 1); - } - - ret[i] = edge_to_splits_impl(edge, order, nt); - } - - return ret; -} - // [[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 7c9a6371c..14e270bfb 100644 --- a/tests/testthat/test-Splits.R +++ b/tests/testthat/test-Splits.R @@ -152,20 +152,14 @@ test_that("as.Splits.multiPhylo()", { expect_equal(as.Splits(randomTrees[[1]]), as.Splits(randomTrees)[[1]]) - # Batch path: uniform labels, asSplits = FALSE + # 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)) } - # Preorder trees also hit the batch path - preorderTrees <- lapply(randomTrees, Preorder) - class(preorderTrees) <- "multiPhylo" - expect_equal(as.Splits(preorderTrees)[[3]], - as.Splits(preorderTrees[[3]])) - - # Fallback: trees with different tip label order require renumbering + # Trees with different tip label order handled correctly relabelled <- randomTrees relabelled[[1]] <- RenumberTips(relabelled[[1]], rev(seq_len(11L))) fallbackResults <- as.Splits(relabelled) diff --git a/tests/testthat/test-splits.cpp.R b/tests/testthat/test-splits.cpp.R index 7fa1fbe32..52b8346f8 100644 --- a/tests/testthat/test-splits.cpp.R +++ b/tests/testthat/test-splits.cpp.R @@ -1,56 +1,3 @@ -test_that("cpp_edge_to_splits_batch() matches per-tree results", { - trees <- as.phylo(c(30899669, 9149275, 12823175, 19740197), 11L, - seq_len(11L)) - nTip <- 11L - edges <- lapply(trees, `[[`, "edge") - orders <- lapply(trees, function(tr) { - postorder_order(tr[["edge"]]) - 1L - }) - - batchResult <- cpp_edge_to_splits_batch(edges, orders, nTip) - perTree <- lapply(seq_along(trees), function(i) { - cpp_edge_to_splits(edges[[i]], orders[[i]], nTip) - }) - - expect_length(batchResult, length(trees)) - for (i in seq_along(trees)) { - expect_equal(batchResult[[i]], perTree[[i]]) - } -}) - -test_that("cpp_edge_to_splits_batch() handles edge cases", { - # Empty list - expect_length(cpp_edge_to_splits_batch(list(), list(), 5L), 0) - - # n_tip == 0 - result <- cpp_edge_to_splits_batch( - list(matrix(1L, 10, 2)), list(0:9), 0L - ) - expect_equal(result[[1]], matrix(raw(0), 0, 0)) - - # Negative n_tip - expect_error(cpp_edge_to_splits_batch(list(), list(), -1L), - "non-negative number of tips") - - # Mismatched list lengths - expect_error( - cpp_edge_to_splits_batch(list(matrix(1L, 3, 2)), list(), 5L), - "same length" - ) - - # Bad edge matrix (3 columns) - expect_error( - cpp_edge_to_splits_batch(list(matrix(1L, 3, 3)), list(0:2), 3L), - "two columns" - ) - - # Mismatched order length - expect_error( - cpp_edge_to_splits_batch(list(matrix(1L, 10, 2)), list(0:8), 5L), - "number of edges" - ) -}) - test_that("Bad data results in error", { skip_if(Sys.getenv("USING_ASAN") != "") expect_error(cpp_edge_to_splits(matrix(1, 3, 3), 1:3, 3),