From 45c82265d349935bcea5697a8c27ab46f5364d17 Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Sun, 14 Sep 2025 07:40:21 +0100 Subject: [PATCH 01/14] `Cherries()` --- DESCRIPTION | 2 +- NAMESPACE | 3 ++ NEWS.md | 3 +- R/Cherries.R | 26 +++++++++++ R/RcppExports.R | 4 ++ codemeta.json | 8 ++-- inst/include/TreeTools/n_cherries.h | 69 +++++++++++++++++++++++++++++ man/Cherries.Rd | 46 +++++++++++++++++++ man/ConsensusWithout.Rd | 1 + man/LongBranch.Rd | 1 + man/MatchEdges.Rd | 1 + man/NSplits.Rd | 1 + man/NTip.Rd | 1 + man/NodeNumbers.Rd | 1 + man/PathLengths.Rd | 1 + man/SplitsInBinaryTree.Rd | 1 + man/TipLabels.Rd | 1 + man/TreeIsRooted.Rd | 1 + man/Treeness.Rd | 1 + src/RcppExports.cpp | 14 ++++++ src/n_cherries.cpp | 29 ++++++++++++ tests/testthat/test-Cherries.R | 9 ++++ 22 files changed, 218 insertions(+), 6 deletions(-) create mode 100644 R/Cherries.R create mode 100644 inst/include/TreeTools/n_cherries.h create mode 100644 man/Cherries.Rd create mode 100644 src/n_cherries.cpp create mode 100644 tests/testthat/test-Cherries.R diff --git a/DESCRIPTION b/DESCRIPTION index 6eeebafd4..e9a321c6d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: TreeTools Title: Create, Modify and Analyse Phylogenetic Trees -Version: 1.16.1.9001 +Version: 1.16.1.9002 Authors@R: c( person("Martin R.", 'Smith', role = c("aut", "cre", "cph"), email = "martin.smith@durham.ac.uk", diff --git a/NAMESPACE b/NAMESPACE index a7f7b19cc..398349ee3 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -31,6 +31,8 @@ S3method(ApePostorder,multiPhylo) S3method(ApePostorder,phylo) S3method(ArtificialExtinction,matrix) S3method(ArtificialExtinction,phyDat) +S3method(Cherries,numeric) +S3method(Cherries,phylo) S3method(Cladewise,"NULL") S3method(Cladewise,list) S3method(Cladewise,matrix) @@ -257,6 +259,7 @@ export(ArtEx) export(ArtificialExtinction) export(BalancedTree) export(CharacterInformation) +export(Cherries) export(CladeSizes) export(Cladewise) export(CladisticInfo) diff --git a/NEWS.md b/NEWS.md index 7fa9e101e..cb1a4aae5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ -# TreeTools 1.16.1.9001` (development) # +# TreeTools 1.16.1.9002` (development) # +- `Cherries()` counts the cherries in a binary tree. - New method `as.Splits.integer()`. - Fix `RoguePlot(sort = TRUE)` [Rogue#33](https://github.com/ms609/Rogue/issues/33). diff --git a/R/Cherries.R b/R/Cherries.R new file mode 100644 index 000000000..dcb4ea8ee --- /dev/null +++ b/R/Cherries.R @@ -0,0 +1,26 @@ +#' Number of cherries in a phylogenetic tree +#' +#' @param tree A binary phylogenetic tree, of class `phylo`; or a matrix +#' corresponding to its edge matrix. +#' @param nTip Number of leaves in tree. +#' @return `Cherries()` returns an integer specifying the number of nodes whose +#' children are both leaves. +#' @family tree properties +#' @template MRS +#' @export +Cherries <- function(tree, nTip) UseMethod("Cherries") + +#' @rdname Cherries +#' @export +Cherries.phylo <- function(tree, nTip = NTip(tree)) { + n_cherries_wrapper(tree[["edge"]][, 1], tree[["edge"]][, 2], nTip) +} + +#' @rdname Cherries +#' @export +Cherries.numeric <- function(tree, nTip) { + if (is.null(dim(tree)) || dim(tree)[[2]] != 2) { + stop("`tree` must be the edge matrix of a tree of class phylo") + } + n_cherries_wrapper(tree[, 1], tree[, 2], nTip) +} diff --git a/R/RcppExports.R b/R/RcppExports.R index 3dcca5f13..65c8e627f 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -69,6 +69,10 @@ minimum_spanning_tree <- function(order) { .Call(`_TreeTools_minimum_spanning_tree`, order) } +n_cherries_wrapper <- function(parent, child, nTip) { + .Call(`_TreeTools_n_cherries_wrapper`, parent, child, nTip) +} + path_lengths <- function(edge, weight, init_nas) { .Call(`_TreeTools_path_lengths`, edge, weight, init_nas) } diff --git a/codemeta.json b/codemeta.json index 49b576c18..fcd48a4f3 100644 --- a/codemeta.json +++ b/codemeta.json @@ -8,13 +8,13 @@ "codeRepository": "https://github.com/ms609/TreeTools/", "issueTracker": "https://github.com/ms609/TreeTools/issues/", "license": "https://spdx.org/licenses/GPL-3.0", - "version": "1.16.1.9001", + "version": "1.16.1.9002", "programmingLanguage": { "@type": "ComputerLanguage", "name": "R", "url": "https://r-project.org" }, - "runtimePlatform": "R Under development (unstable) (2025-04-08 r88126 ucrt)", + "runtimePlatform": "R Under development (unstable) (2025-06-17 r88325 ucrt)", "provider": { "@id": "https://cran.r-project.org", "@type": "Organization", @@ -275,7 +275,7 @@ }, "SystemRequirements": "C++17" }, - "fileSize": "1804.775KB", + "fileSize": "1852.862KB", "citation": [ { "@type": "SoftwareSourceCode", @@ -290,7 +290,7 @@ ], "name": "TreeTools: create, modify and analyse phylogenetic trees", "identifier": "10.32614/CRAN.package.TreeTools", - "description": "R package version 1.16.1.9001", + "description": "R package version 1.16.1.9002", "@id": "https://doi.org/10.32614/CRAN.package.TreeTools", "sameAs": "https://doi.org/10.32614/CRAN.package.TreeTools" } diff --git a/inst/include/TreeTools/n_cherries.h b/inst/include/TreeTools/n_cherries.h new file mode 100644 index 000000000..6b498ffa6 --- /dev/null +++ b/inst/include/TreeTools/n_cherries.h @@ -0,0 +1,69 @@ +#ifndef TreeTools_n_cherries_ +#define TreeTools_n_cherries_ + +#include /* for errors */ +#include + +#include "assert.h" /* for ASSERT */ + +namespace TreeTools{ + +// Number of cherries in a binary phylogenetic tree +inline int n_cherries(const int* parent, + const int* child, + const size_t n_edge, + const int n_tip) { + + const size_t n_node = n_edge / 2; + std::vector internal(n_node); + + const bool unrooted = n_edge % 2; + if (unrooted) { + std::vector is_child(n_node + n_tip + 1); + + for (size_t ed = 0; ed < n_edge; ++ed) { + is_child[child[ed]] = true; + } + + const int i_limit = n_tip + n_node + 1; + int root_node = n_tip + 1; + for (; root_node <= i_limit; ++root_node) { + if (!is_child[root_node]) break; + } + + if (root_node == i_limit) { + throw std::runtime_error("Tree must be acyclic"); + } + + bool root_internal_found; + for (size_t ed = 0; ed < n_edge; ++ed) { + const int child_i = child[ed]; + if (child_i > n_tip) { + const int node = parent[ed]; + if (!root_internal_found && node == root_node) { + root_internal_found = true; + } else { + internal[node - n_tip] = true; + } + } + } + + } else { + for (size_t ed = 0; ed < n_edge; ++ed) { + if (child[ed] > n_tip) { + const size_t node_idx = parent[ed] - n_tip; + internal[node_idx] = true; + } + } + } + + int n_cherries = 0; + for (const auto& is_internal : internal) { + if (!is_internal) ++n_cherries; + } + return n_cherries; +} + +} + +#endif \ No newline at end of file diff --git a/man/Cherries.Rd b/man/Cherries.Rd new file mode 100644 index 000000000..67b95eb09 --- /dev/null +++ b/man/Cherries.Rd @@ -0,0 +1,46 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/Cherries.R +\name{Cherries} +\alias{Cherries} +\alias{Cherries.phylo} +\alias{Cherries.numeric} +\title{Number of cherries in a phylogenetic tree} +\usage{ +Cherries(tree, nTip) + +\method{Cherries}{phylo}(tree, nTip = NTip(tree)) + +\method{Cherries}{numeric}(tree, nTip) +} +\arguments{ +\item{tree}{A binary phylogenetic tree, of class \code{phylo}; or a matrix +corresponding to its edge matrix.} + +\item{nTip}{Number of leaves in tree.} +} +\value{ +\code{Cherries()} returns an integer specifying the number of nodes whose +children are both leaves. +} +\description{ +Number of cherries in a phylogenetic tree +} +\seealso{ +Other tree properties: +\code{\link{ConsensusWithout}()}, +\code{\link{LongBranch}()}, +\code{\link{MatchEdges}()}, +\code{\link{NSplits}()}, +\code{\link{NTip}()}, +\code{\link{NodeNumbers}()}, +\code{\link{PathLengths}()}, +\code{\link{SplitsInBinaryTree}()}, +\code{\link{TipLabels}()}, +\code{\link{TreeIsRooted}()}, +\code{\link{Treeness}()} +} +\author{ +\href{https://orcid.org/0000-0001-5660-1727}{Martin R. Smith} +(\href{mailto:martin.smith@durham.ac.uk}{martin.smith@durham.ac.uk}) +} +\concept{tree properties} diff --git a/man/ConsensusWithout.Rd b/man/ConsensusWithout.Rd index 7bcb89177..e0350a18e 100644 --- a/man/ConsensusWithout.Rd +++ b/man/ConsensusWithout.Rd @@ -81,6 +81,7 @@ Other tree manipulation: \code{\link{TrivialTree}} Other tree properties: +\code{\link{Cherries}()}, \code{\link{LongBranch}()}, \code{\link{MatchEdges}()}, \code{\link{NSplits}()}, diff --git a/man/LongBranch.Rd b/man/LongBranch.Rd index 79165652c..f8bf310c8 100644 --- a/man/LongBranch.Rd +++ b/man/LongBranch.Rd @@ -45,6 +45,7 @@ tree$tip.label[lb > threshold] } \seealso{ Other tree properties: +\code{\link{Cherries}()}, \code{\link{ConsensusWithout}()}, \code{\link{MatchEdges}()}, \code{\link{NSplits}()}, diff --git a/man/MatchEdges.Rd b/man/MatchEdges.Rd index 732ac6eca..0f6cd0e3c 100644 --- a/man/MatchEdges.Rd +++ b/man/MatchEdges.Rd @@ -48,6 +48,7 @@ Other tree navigation: \code{\link{RootNode}()} Other tree properties: +\code{\link{Cherries}()}, \code{\link{ConsensusWithout}()}, \code{\link{LongBranch}()}, \code{\link{NSplits}()}, diff --git a/man/NSplits.Rd b/man/NSplits.Rd index b27e306ea..5e900f62c 100644 --- a/man/NSplits.Rd +++ b/man/NSplits.Rd @@ -53,6 +53,7 @@ NSplits(as.Splits(BalancedTree(8))) } \seealso{ Other tree properties: +\code{\link{Cherries}()}, \code{\link{ConsensusWithout}()}, \code{\link{LongBranch}()}, \code{\link{MatchEdges}()}, diff --git a/man/NTip.Rd b/man/NTip.Rd index 84a072c8c..0bc777fa5 100644 --- a/man/NTip.Rd +++ b/man/NTip.Rd @@ -41,6 +41,7 @@ objects of class \code{Splits} and \code{list}, and edge matrices } \seealso{ Other tree properties: +\code{\link{Cherries}()}, \code{\link{ConsensusWithout}()}, \code{\link{LongBranch}()}, \code{\link{MatchEdges}()}, diff --git a/man/NodeNumbers.Rd b/man/NodeNumbers.Rd index a836b4d8b..4e25fe858 100644 --- a/man/NodeNumbers.Rd +++ b/man/NodeNumbers.Rd @@ -22,6 +22,7 @@ Numeric index of each node in a tree } \seealso{ Other tree properties: +\code{\link{Cherries}()}, \code{\link{ConsensusWithout}()}, \code{\link{LongBranch}()}, \code{\link{MatchEdges}()}, diff --git a/man/PathLengths.Rd b/man/PathLengths.Rd index 19da3c665..2f355430e 100644 --- a/man/PathLengths.Rd +++ b/man/PathLengths.Rd @@ -39,6 +39,7 @@ PathLengths(tree) } \seealso{ Other tree properties: +\code{\link{Cherries}()}, \code{\link{ConsensusWithout}()}, \code{\link{LongBranch}()}, \code{\link{MatchEdges}()}, diff --git a/man/SplitsInBinaryTree.Rd b/man/SplitsInBinaryTree.Rd index 26b812bc0..1e753b8c3 100644 --- a/man/SplitsInBinaryTree.Rd +++ b/man/SplitsInBinaryTree.Rd @@ -48,6 +48,7 @@ SplitsInBinaryTree(list(tree, tree)) } \seealso{ Other tree properties: +\code{\link{Cherries}()}, \code{\link{ConsensusWithout}()}, \code{\link{LongBranch}()}, \code{\link{MatchEdges}()}, diff --git a/man/TipLabels.Rd b/man/TipLabels.Rd index 7616a85cf..617891096 100644 --- a/man/TipLabels.Rd +++ b/man/TipLabels.Rd @@ -93,6 +93,7 @@ AllTipLabels(c(BalancedTree(4), PectinateTree(8))) } \seealso{ Other tree properties: +\code{\link{Cherries}()}, \code{\link{ConsensusWithout}()}, \code{\link{LongBranch}()}, \code{\link{MatchEdges}()}, diff --git a/man/TreeIsRooted.Rd b/man/TreeIsRooted.Rd index 921138d11..c81103706 100644 --- a/man/TreeIsRooted.Rd +++ b/man/TreeIsRooted.Rd @@ -23,6 +23,7 @@ TreeIsRooted(UnrootTree(BalancedTree(6))) } \seealso{ Other tree properties: +\code{\link{Cherries}()}, \code{\link{ConsensusWithout}()}, \code{\link{LongBranch}()}, \code{\link{MatchEdges}()}, diff --git a/man/Treeness.Rd b/man/Treeness.Rd index 1463c9a89..3a1072a00 100644 --- a/man/Treeness.Rd +++ b/man/Treeness.Rd @@ -37,6 +37,7 @@ Treeness(c(lowTree, highTree)) } \seealso{ Other tree properties: +\code{\link{Cherries}()}, \code{\link{ConsensusWithout}()}, \code{\link{LongBranch}()}, \code{\link{MatchEdges}()}, diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp index 8501d3b91..ea8d209df 100644 --- a/src/RcppExports.cpp +++ b/src/RcppExports.cpp @@ -223,6 +223,19 @@ BEGIN_RCPP return rcpp_result_gen; END_RCPP } +// n_cherries_wrapper +Rcpp::IntegerVector n_cherries_wrapper(const Rcpp::IntegerVector parent, const Rcpp::IntegerVector child, const int nTip); +RcppExport SEXP _TreeTools_n_cherries_wrapper(SEXP parentSEXP, SEXP childSEXP, SEXP nTipSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< const Rcpp::IntegerVector >::type parent(parentSEXP); + Rcpp::traits::input_parameter< const Rcpp::IntegerVector >::type child(childSEXP); + Rcpp::traits::input_parameter< const int >::type nTip(nTipSEXP); + rcpp_result_gen = Rcpp::wrap(n_cherries_wrapper(parent, child, nTip)); + return rcpp_result_gen; +END_RCPP +} // path_lengths NumericMatrix path_lengths(const IntegerMatrix edge, const DoubleVector weight, const LogicalVector init_nas); RcppExport SEXP _TreeTools_path_lengths(SEXP edgeSEXP, SEXP weightSEXP, SEXP init_nasSEXP) { @@ -452,6 +465,7 @@ static const R_CallMethodDef CallEntries[] = { {"_TreeTools_mixed_base_to_parent", (DL_FUNC) &_TreeTools_mixed_base_to_parent, 2}, {"_TreeTools_kept_vertices", (DL_FUNC) &_TreeTools_kept_vertices, 2}, {"_TreeTools_minimum_spanning_tree", (DL_FUNC) &_TreeTools_minimum_spanning_tree, 1}, + {"_TreeTools_n_cherries_wrapper", (DL_FUNC) &_TreeTools_n_cherries_wrapper, 3}, {"_TreeTools_path_lengths", (DL_FUNC) &_TreeTools_path_lengths, 3}, {"_TreeTools_cpp_edge_to_splits", (DL_FUNC) &_TreeTools_cpp_edge_to_splits, 3}, {"_TreeTools_duplicated_splits", (DL_FUNC) &_TreeTools_duplicated_splits, 2}, diff --git a/src/n_cherries.cpp b/src/n_cherries.cpp new file mode 100644 index 000000000..902a50fb2 --- /dev/null +++ b/src/n_cherries.cpp @@ -0,0 +1,29 @@ +#include +#include /* for errors */ +#include /* for errors */ +#include "../inst/include/TreeTools/n_cherries.h" + +// [[Rcpp::export]] +Rcpp::IntegerVector n_cherries_wrapper(const Rcpp::IntegerVector parent, + const Rcpp::IntegerVector child, + const int nTip) { + try { + const size_t n_edge = parent.size(); + if (child.size() != (int)n_edge) { + Rcpp::stop("`parent` and `child` must be the same length"); + } + + // Call your C++ function + int result = TreeTools::n_cherries(parent.begin(), child.begin(), + n_edge, nTip); + + // Return the result as an R integer + return Rcpp::wrap(result); + } catch (const std::exception& e) { + // Catch any standard exception and throw it as an R error + Rcpp::stop(e.what()); + } catch (...) { + // Catch any other exceptions + Rcpp::stop("An unknown error occurred in n_cherries_wrapper."); + } +} \ No newline at end of file diff --git a/tests/testthat/test-Cherries.R b/tests/testthat/test-Cherries.R new file mode 100644 index 000000000..ac4c923d7 --- /dev/null +++ b/tests/testthat/test-Cherries.R @@ -0,0 +1,9 @@ +test_that("Cherries works", { + expect_equal(Cherries(BalancedTree(8)), 4L) + expect_equal(Cherries(PectinateTree(8)), 1L) + expect_equal(Cherries(UnrootTree(PectinateTree(8))$edge, 8L), 2L) + expect_error(Cherries(matrix(4, 4, 4)), "edge matrix") + expect_error(Cherries(1:3), "edge matrix") + + expect_no_error(Cherries(BalancedTree(144))) +}) From d56a72b798c03f616553e3762ba3b7a1b70d2928 Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Sun, 14 Sep 2025 08:23:24 +0100 Subject: [PATCH 02/14] Hmisc@5.0-0 --- .github/workflows/R-CMD-check.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/R-CMD-check.yml b/.github/workflows/R-CMD-check.yml index a6ee183c8..508f75e5d 100644 --- a/.github/workflows/R-CMD-check.yml +++ b/.github/workflows/R-CMD-check.yml @@ -114,6 +114,7 @@ jobs: uses: r-lib/actions/setup-r-dependencies@v2 with: extra-packages: | + Hmisc@5.0-0 ggplot2@3.5.2 pkgKitten@0.2.3 Matrix@1.6-4 From 81431e76b616870df89aa4fda46f89e13f0531c5 Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Sun, 14 Sep 2025 08:28:42 +0100 Subject: [PATCH 03/14] Initialize root_internal_found --- inst/include/TreeTools/n_cherries.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/include/TreeTools/n_cherries.h b/inst/include/TreeTools/n_cherries.h index 6b498ffa6..df7af9f59 100644 --- a/inst/include/TreeTools/n_cherries.h +++ b/inst/include/TreeTools/n_cherries.h @@ -35,7 +35,7 @@ inline int n_cherries(const int* parent, throw std::runtime_error("Tree must be acyclic"); } - bool root_internal_found; + bool root_internal_found = false; for (size_t ed = 0; ed < n_edge; ++ed) { const int child_i = child[ed]; if (child_i > n_tip) { From abb2ac9241a6c0d7cc1b041012dd277061bc91fb Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Sun, 14 Sep 2025 08:38:10 +0100 Subject: [PATCH 04/14] std::unique_ptr --- inst/include/TreeTools/n_cherries.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/inst/include/TreeTools/n_cherries.h b/inst/include/TreeTools/n_cherries.h index df7af9f59..0cffa77ec 100644 --- a/inst/include/TreeTools/n_cherries.h +++ b/inst/include/TreeTools/n_cherries.h @@ -1,6 +1,7 @@ #ifndef TreeTools_n_cherries_ #define TreeTools_n_cherries_ +#include /* for std::unique_ptr */ #include /* for errors */ #include @@ -15,11 +16,11 @@ inline int n_cherries(const int* parent, const int n_tip) { const size_t n_node = n_edge / 2; - std::vector internal(n_node); + std::unique_ptr internal(new bool[n_node]()); const bool unrooted = n_edge % 2; if (unrooted) { - std::vector is_child(n_node + n_tip + 1); + std::unique_ptr is_child(new bool[n_node + n_tip + 1]()); for (size_t ed = 0; ed < n_edge; ++ed) { is_child[child[ed]] = true; @@ -58,8 +59,8 @@ inline int n_cherries(const int* parent, } int n_cherries = 0; - for (const auto& is_internal : internal) { - if (!is_internal) ++n_cherries; + for (size_t i = 0; i < n_node; ++i) { + if (!internal[i]) ++n_cherries; } return n_cherries; } From d03c3744721990166245e0632406e7e931a60c05 Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Sun, 14 Sep 2025 15:19:39 +0100 Subject: [PATCH 05/14] code coverage --- .github/workflows/R-CMD-check.yml | 43 ++++++++++++++++++++++++++++- inst/include/TreeTools/n_cherries.h | 2 +- tests/testthat/test-Cherries.R | 2 ++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/.github/workflows/R-CMD-check.yml b/.github/workflows/R-CMD-check.yml index 508f75e5d..4f091f6a2 100644 --- a/.github/workflows/R-CMD-check.yml +++ b/.github/workflows/R-CMD-check.yml @@ -114,7 +114,48 @@ jobs: uses: r-lib/actions/setup-r-dependencies@v2 with: extra-packages: | - Hmisc@5.0-0 + tidymodels@0.1.4 + rmcorr@0.4.5 + hardhat@0.1.6 + WRS2@1.1-3 + dotCall64@1.0-1 + fields@13.3 + correlation@0.7.1 + ggdist@3.0.1 + ggside@0.2.2 + randomForest@4.6-14 + fastICA@1.2-3 + parsnip@0.2.1 + workflows@0.2.5 + effectsize@0.6.0.1 + performance@0.8.0 + see@0.6.7 + gamlss@5.3-1 + gamm4@0.2-6 + unmarked@1.1.1 + datawizard@0.2.1 + spam@2.7-0 + GGally@2.1.2 + bayesplot@1.9.0 + estimability@1.3 + MuMIn@1.43.17 + pbkrtest@0.5.1 + recipes@0.1.17 + slider@0.2.2 + emmeans@1.7.5 + ergm@4.0.1 + gam@1.20.2 + metafor@3.4-0 + modeldata@1.0.0 + rsample@1.0.0 + survey@4.1-1 + broom@1.0.8 + Matrix@1.6-5 + VGAMextra@0.0-5 + VGAMdata@1.1-6 + VGAM@1.1-6 + duckdb@1.2.1 + Hmisc@5.1-1 ggplot2@3.5.2 pkgKitten@0.2.3 Matrix@1.6-4 diff --git a/inst/include/TreeTools/n_cherries.h b/inst/include/TreeTools/n_cherries.h index 0cffa77ec..24305ef64 100644 --- a/inst/include/TreeTools/n_cherries.h +++ b/inst/include/TreeTools/n_cherries.h @@ -33,7 +33,7 @@ inline int n_cherries(const int* parent, } if (root_node == i_limit) { - throw std::runtime_error("Tree must be acyclic"); + throw std::runtime_error("Tree must be acyclic"); // nocov } bool root_internal_found = false; diff --git a/tests/testthat/test-Cherries.R b/tests/testthat/test-Cherries.R index ac4c923d7..fcad86bb2 100644 --- a/tests/testthat/test-Cherries.R +++ b/tests/testthat/test-Cherries.R @@ -5,5 +5,7 @@ test_that("Cherries works", { expect_error(Cherries(matrix(4, 4, 4)), "edge matrix") expect_error(Cherries(1:3), "edge matrix") + expect_error(n_cherries_wrapper(1:2, 1:3, 4), "same length") + expect_no_error(Cherries(BalancedTree(144))) }) From 5fb8a2c1463a8d697f69739897b751066e0fad67 Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Sun, 14 Sep 2025 18:51:22 +0100 Subject: [PATCH 06/14] +R4.0 --- .github/workflows/R-CMD-check.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/R-CMD-check.yml b/.github/workflows/R-CMD-check.yml index 0b42d734c..2b86e7376 100644 --- a/.github/workflows/R-CMD-check.yml +++ b/.github/workflows/R-CMD-check.yml @@ -52,6 +52,7 @@ jobs: - {os: windows-latest, r: 'release'} - {os: macOS-latest, r: 'release'} - {os: ubuntu-24.04, r: '3.6', rspm: "https://packagemanager.posit.co/cran/2020-03-30"} + - {os: ubuntu-24.04, r: '4.0', rspm: "https://packagemanager.posit.co/cran/2020-03-30"} - {os: ubuntu-24.04, r: 'release', rspm: "https://packagemanager.posit.co/cran/__linux__/noble/latest"} - {os: ubuntu-24.04, r: 'devel', rspm: "https://packagemanager.posit.co/cran/__linux__/noble/latest"} @@ -116,6 +117,10 @@ jobs: pandoc-version: "latest" needs: | check + extra-packages: | + TreeDist=?ignore-before-r=4.0.0 + vdiffr=?ignore-before-r=4.0.0 + phangorn=?ignore-before-r=4.1.0 - name: Check package uses: r-lib/actions/check-r-package@v2 From d64fc5c21e24eac6a288dba8c6a08d8d7e49744a Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Tue, 23 Sep 2025 08:06:50 +0100 Subject: [PATCH 07/14] rm installation checks --- .github/workflows/R-CMD-check.yml | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/.github/workflows/R-CMD-check.yml b/.github/workflows/R-CMD-check.yml index e14b01698..170a3f921 100644 --- a/.github/workflows/R-CMD-check.yml +++ b/.github/workflows/R-CMD-check.yml @@ -86,7 +86,7 @@ jobs: fi echo "Current package version is now: $(grep "Version:" DESCRIPTION | awk '{print $2}')" shell: bash - + - name: Set up R uses: r-lib/actions/setup-r@v2 with: @@ -97,7 +97,6 @@ jobs: run: | sudo apt-get install texlive-latex-base texlive-fonts-recommended - - name: Install system dependencies (R 3.x) if: matrix.config.r < '4.0' run: | @@ -127,11 +126,6 @@ jobs: sed -i '1i#include ' vdiffr-src/src/devSVG.cpp R CMD INSTALL --preclean --no-multiarch --with-keep.source vdiffr-src rm -rf vdiffr-src - - - name: Show installed R packages - run: | - Rscript -e 'sessionInfo()' - Rscript -e 'installed.packages()[, c("Package", "Version")]' - name: Set up R dependencies (R 3.x) if: matrix.config.r < '4.0' @@ -161,11 +155,6 @@ jobs: needs: | check coverage - - - name: Show installed R packages - run: | - Rscript -e 'sessionInfo()' - Rscript -e 'installed.packages()[, c("Package", "Version")]' - name: Set up R dependencies (Non-Windows) if: ${{runner.os != 'Windows' && matrix.config.r >= '4.0' }} From 0bd461e898b520956794751fcdb625c548cb704e Mon Sep 17 00:00:00 2001 From: RevBayes analysis <1695515+ms609@users.noreply.github.com> Date: Tue, 23 Sep 2025 08:14:12 +0100 Subject: [PATCH 08/14] Deprecations --- NAMESPACE | 7 --- NEWS.md | 3 +- R/tree_descendants.R | 13 ----- R/tree_generation.R | 70 +------------------------- R/tree_information.R | 11 ---- R/tree_numbering.R | 3 +- man/AddTip.Rd | 1 - man/CladisticInfo.Rd | 6 --- man/CollapseNode.Rd | 1 - man/ConsensusWithout.Rd | 1 - man/DescendantEdges.Rd | 8 --- man/DropTip.Rd | 1 - man/EnforceOutgroup.Rd | 66 ------------------------ man/ImposeConstraint.Rd | 1 - man/KeptPaths.Rd | 1 - man/KeptVerts.Rd | 1 - man/LeafLabelInterchange.Rd | 1 - man/MakeTreeBinary.Rd | 1 - man/Renumber.Rd | 1 - man/RenumberTips.Rd | 1 - man/Reorder.Rd | 5 +- man/RootTree.Rd | 1 - man/SortTree.Rd | 1 - man/Subtree.Rd | 1 - man/TipTimedTree.Rd | 1 - man/TrivialTree.Rd | 1 - man/match.Splits.Rd | 3 -- tests/testthat/test-tree_descendants.R | 2 - 28 files changed, 5 insertions(+), 208 deletions(-) delete mode 100644 man/EnforceOutgroup.Rd diff --git a/NAMESPACE b/NAMESPACE index d0dac537f..038b761fc 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -51,8 +51,6 @@ S3method(DropTip,Splits) S3method(DropTip,list) S3method(DropTip,multiPhylo) S3method(DropTip,phylo) -S3method(EnforceOutgroup,character) -S3method(EnforceOutgroup,phylo) S3method(KeepTip,"NULL") S3method(KeepTip,Splits) S3method(KeepTip,list) @@ -264,7 +262,6 @@ export(AddTip) export(AddTipEverywhere) export(AddUnconstrained) export(AllAncestors) -export(AllDescendantEdges) export(AllTipLabels) export(AncestorEdge) export(ApePostorder) @@ -293,7 +290,6 @@ export(DropTipPhylo) export(EdgeAncestry) export(EdgeDistances) export(EndSentence) -export(EnforceOutgroup) export(ExtractTaxa) export(Hamming) export(IC1Spr) @@ -375,8 +371,6 @@ export(PhyDatToMatrix) export(PhyDatToString) export(PhyToString) export(PhydatToString) -export(PhylogeneticInfo) -export(PhylogeneticInformation) export(PolarizeSplits) export(Postorder) export(PostorderOrder) @@ -476,7 +470,6 @@ importFrom(RCurl,url.exists) importFrom(Rdpack,reprompt) importFrom(ape,all.equal.phylo) importFrom(ape,as.phylo) -importFrom(ape,bind.tree) importFrom(ape,cophenetic.phylo) importFrom(ape,edgelabels) importFrom(ape,is.rooted) diff --git a/NEWS.md b/NEWS.md index 26e01e2bf..a1707fc1c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,7 +7,8 @@ - Require R 3.6 - Remove R.cache dependency: `UnrootedKeys()` now uses a native cache implementation. - Remove deprecated functions `TreeSplits()`, `ForestSplits()`, `SplitNumber()`, - `in.Splits()`. + `in.Splits()`, `AllDescendantEdges()`, `PhylogeneticInfo()`, + `.EnforceOutgroup()`. # TreeTools 1.16.1 (2025-08-24) # diff --git a/R/tree_descendants.R b/R/tree_descendants.R index 322241057..56f94027c 100644 --- a/R/tree_descendants.R +++ b/R/tree_descendants.R @@ -100,19 +100,6 @@ DescendantTips <- function(parent, child, edge = NULL, } } -#' @rdname DescendantEdges -#' -#' @return `AllDescendantEdges()` is deprecated; use `DescendantEdges()` -#' instead. -#' It returns a matrix of class logical, with row _N_ specifying whether each -#' edge is a descendant of edge _N_ (or the edge itself). -#' -#' @export -AllDescendantEdges <- function(parent, child, nEdge = length(parent)) { - .Deprecated("DescendantEdges") - .AllDescendantEdges(parent, child, nEdge) -} - .AllDescendantEdges <- function(parent, child, nEdge = length(parent)) { ret <- diag(nEdge) == 1 blankLogical <- logical(nEdge) diff --git a/R/tree_generation.R b/R/tree_generation.R index 8011eed45..5dcdd4dd8 100644 --- a/R/tree_generation.R +++ b/R/tree_generation.R @@ -482,75 +482,7 @@ ConstrainedNJ <- function(dataset, constraint, weight = 1L, tree } -#nocov begin -#' Generate a tree with a specified outgroup -#' -#' **Deprecated.** This function will be removed in a future version of -#' \pkg{TreeTools}. -#' Use `RootTree()` instead. -#' -#' Given a tree or a list of taxa, `EnforceOutgroup()` rearranged the ingroup -#' and outgroup taxa such that the two are sister taxa across the root, without -#' changing the relationships within the ingroup or within the outgroup. -#' -#' @param tree Either a tree of class \code{phylo}; or (for `EnforceOutgroup()`) -#' a character vector listing the names of all the taxa in the tree, from which -#' a random tree will be generated. -#' @param outgroup Character vector containing the names of taxa to include in -#' the outgroup. -#' -#' @return `EnforceOutgroup()` returned a tree of class `phylo` where all -#' outgroup taxa are sister to all remaining taxa, without modifying the -#' ingroup topology. -#' -# @examples -# tree <- EnforceOutgroup(letters[1:9], letters[1:3]) -# plot(tree) -# -#' @seealso For a more robust implementation, see [`RootTree()`], which will -#' eventually replace this function -#' ([#30](https://github.com/ms609/TreeTools/issues/30)). -#' -#' @template MRS -#' @family tree manipulation -#' @export -EnforceOutgroup <- function(tree, outgroup) UseMethod("EnforceOutgroup") - -#' @importFrom ape bind.tree -.EnforceOutgroup <- function(tree, outgroup, taxa) { - .Deprecated("RootTree") - if (length(outgroup) == 1L) { - return(RootTree(tree, outgroup)) - } - - ingroup <- taxa[!(taxa %fin% outgroup)] - if (!all(outgroup %fin% taxa) || - length(ingroup) + length(outgroup) != length(taxa)) { - stop("All outgroup taxa must occur in tree") - } - - ingroup.branch <- DropTip(tree, outgroup) - outgroup.branch <- DropTip(tree, ingroup) - - result <- RootTree(bind.tree(outgroup.branch, ingroup.branch, 0, 1), - outgroup) - RenumberTips(Renumber(result), taxa) -} - -#' @rdname EnforceOutgroup -#' @export -EnforceOutgroup.phylo <- function(tree, outgroup) { - .EnforceOutgroup(tree, outgroup, tree[["tip.label"]]) -} - -#' @rdname EnforceOutgroup -#' @export -EnforceOutgroup.character <- function(tree, outgroup) { - taxa <- tree - .EnforceOutgroup(RandomTree(taxa, taxa[1]), outgroup, taxa) -} -#nocov end - +# Wrap an edge matrix as a tree in preorder .PreorderTree <- function(edge, tip.label, Nnode = dim(edge)[[1]] + 1 - length(tip.label)) { diff --git a/R/tree_information.R b/R/tree_information.R index f84a829d3..dbfc5e199 100644 --- a/R/tree_information.R +++ b/R/tree_information.R @@ -78,13 +78,6 @@ Log2TreesMatchingTree <- function(tree) { #' @export CladisticInfo <- function(x) UseMethod("CladisticInfo") -#' @rdname CladisticInfo -#' @export -PhylogeneticInfo <- function(x) { # nocov start - .Deprecated("CladisticInfo()") - UseMethod("CladisticInfo") -} # nocov end - #' @rdname CladisticInfo #' @export CladisticInfo.phylo <- function(x) { @@ -105,10 +98,6 @@ CladisticInfo.list <- function(x) vapply(x, CladisticInfo, 0) #' @export CladisticInfo.multiPhylo <- CladisticInfo.list - -#' @rdname CladisticInfo -#' @export -PhylogeneticInformation <- PhylogeneticInfo #' @rdname CladisticInfo #' @export CladisticInformation <- CladisticInfo diff --git a/R/tree_numbering.R b/R/tree_numbering.R index e746a83af..40a1fdc27 100644 --- a/R/tree_numbering.R +++ b/R/tree_numbering.R @@ -74,12 +74,11 @@ RenumberTree <- function(parent, child, weight) { #' @rdname Reorder #' -#' @param \dots Deprecated; included for compatibility with previous versions. #' @return `RenumberEdges()` formats the output of `RenumberTree()` into a list #' whose two entries correspond to the new parent and child vectors, #' in preorder. #' @export -RenumberEdges <- function(parent, child, ...) { +RenumberEdges <- function(parent, child) { oenn <- .Call(`_TreeTools_preorder_edges_and_nodes`, parent, child) # Return: diff --git a/man/AddTip.Rd b/man/AddTip.Rd index 47923431a..9ec98e237 100644 --- a/man/AddTip.Rd +++ b/man/AddTip.Rd @@ -118,7 +118,6 @@ Other tree manipulation: \code{\link{CollapseNode}()}, \code{\link{ConsensusWithout}()}, \code{\link{DropTip}()}, -\code{\link{EnforceOutgroup}()}, \code{\link{ImposeConstraint}()}, \code{\link{KeptPaths}()}, \code{\link{KeptVerts}()}, diff --git a/man/CladisticInfo.Rd b/man/CladisticInfo.Rd index 6b559b892..eefd6d4b0 100644 --- a/man/CladisticInfo.Rd +++ b/man/CladisticInfo.Rd @@ -2,19 +2,15 @@ % Please edit documentation in R/tree_information.R \name{CladisticInfo} \alias{CladisticInfo} -\alias{PhylogeneticInfo} \alias{CladisticInfo.phylo} \alias{CladisticInfo.Splits} \alias{CladisticInfo.list} \alias{CladisticInfo.multiPhylo} -\alias{PhylogeneticInformation} \alias{CladisticInformation} \title{Cladistic information content of a tree} \usage{ CladisticInfo(x) -PhylogeneticInfo(x) - \method{CladisticInfo}{phylo}(x) \method{CladisticInfo}{Splits}(x) @@ -23,8 +19,6 @@ PhylogeneticInfo(x) \method{CladisticInfo}{multiPhylo}(x) -PhylogeneticInformation(x) - CladisticInformation(x) } \arguments{ diff --git a/man/CollapseNode.Rd b/man/CollapseNode.Rd index b7da762a9..38f0ab27b 100644 --- a/man/CollapseNode.Rd +++ b/man/CollapseNode.Rd @@ -61,7 +61,6 @@ Other tree manipulation: \code{\link{AddTip}()}, \code{\link{ConsensusWithout}()}, \code{\link{DropTip}()}, -\code{\link{EnforceOutgroup}()}, \code{\link{ImposeConstraint}()}, \code{\link{KeptPaths}()}, \code{\link{KeptVerts}()}, diff --git a/man/ConsensusWithout.Rd b/man/ConsensusWithout.Rd index 7bcb89177..8cc77fe93 100644 --- a/man/ConsensusWithout.Rd +++ b/man/ConsensusWithout.Rd @@ -65,7 +65,6 @@ Other tree manipulation: \code{\link{AddTip}()}, \code{\link{CollapseNode}()}, \code{\link{DropTip}()}, -\code{\link{EnforceOutgroup}()}, \code{\link{ImposeConstraint}()}, \code{\link{KeptPaths}()}, \code{\link{KeptVerts}()}, diff --git a/man/DescendantEdges.Rd b/man/DescendantEdges.Rd index 9cbc2ff0a..970674d73 100644 --- a/man/DescendantEdges.Rd +++ b/man/DescendantEdges.Rd @@ -3,7 +3,6 @@ \name{DescendantEdges} \alias{DescendantEdges} \alias{DescendantTips} -\alias{AllDescendantEdges} \title{Identify descendant edges} \usage{ DescendantEdges( @@ -16,8 +15,6 @@ DescendantEdges( ) DescendantTips(parent, child, edge = NULL, node = NULL, nEdge = length(parent)) - -AllDescendantEdges(parent, child, nEdge = length(parent)) } \arguments{ \item{parent}{Integer vector corresponding to the first column of the edge @@ -48,11 +45,6 @@ or one of its descendants. \code{DescendantTips()} returns a logical vector stating whether each leaf in turn is a descendant of the specified edge. - -\code{AllDescendantEdges()} is deprecated; use \code{DescendantEdges()} -instead. -It returns a matrix of class logical, with row \emph{N} specifying whether each -edge is a descendant of edge \emph{N} (or the edge itself). } \description{ \code{DescendantEdges()} efficiently identifies edges that are "descended" from diff --git a/man/DropTip.Rd b/man/DropTip.Rd index 173e2bda4..fc9debfaa 100644 --- a/man/DropTip.Rd +++ b/man/DropTip.Rd @@ -113,7 +113,6 @@ Other tree manipulation: \code{\link{AddTip}()}, \code{\link{CollapseNode}()}, \code{\link{ConsensusWithout}()}, -\code{\link{EnforceOutgroup}()}, \code{\link{ImposeConstraint}()}, \code{\link{KeptPaths}()}, \code{\link{KeptVerts}()}, diff --git a/man/EnforceOutgroup.Rd b/man/EnforceOutgroup.Rd deleted file mode 100644 index 28c740aec..000000000 --- a/man/EnforceOutgroup.Rd +++ /dev/null @@ -1,66 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/tree_generation.R -\name{EnforceOutgroup} -\alias{EnforceOutgroup} -\alias{EnforceOutgroup.phylo} -\alias{EnforceOutgroup.character} -\title{Generate a tree with a specified outgroup} -\usage{ -EnforceOutgroup(tree, outgroup) - -\method{EnforceOutgroup}{phylo}(tree, outgroup) - -\method{EnforceOutgroup}{character}(tree, outgroup) -} -\arguments{ -\item{tree}{Either a tree of class \code{phylo}; or (for \code{EnforceOutgroup()}) -a character vector listing the names of all the taxa in the tree, from which -a random tree will be generated.} - -\item{outgroup}{Character vector containing the names of taxa to include in -the outgroup.} -} -\value{ -\code{EnforceOutgroup()} returned a tree of class \code{phylo} where all -outgroup taxa are sister to all remaining taxa, without modifying the -ingroup topology. -} -\description{ -\strong{Deprecated.} This function will be removed in a future version of -\pkg{TreeTools}. -Use \code{RootTree()} instead. -} -\details{ -Given a tree or a list of taxa, \code{EnforceOutgroup()} rearranged the ingroup -and outgroup taxa such that the two are sister taxa across the root, without -changing the relationships within the ingroup or within the outgroup. -} -\seealso{ -For a more robust implementation, see \code{\link[=RootTree]{RootTree()}}, which will -eventually replace this function -(\href{https://github.com/ms609/TreeTools/issues/30}{#30}). - -Other tree manipulation: -\code{\link{AddTip}()}, -\code{\link{CollapseNode}()}, -\code{\link{ConsensusWithout}()}, -\code{\link{DropTip}()}, -\code{\link{ImposeConstraint}()}, -\code{\link{KeptPaths}()}, -\code{\link{KeptVerts}()}, -\code{\link{LeafLabelInterchange}()}, -\code{\link{MakeTreeBinary}()}, -\code{\link{Renumber}()}, -\code{\link{RenumberTips}()}, -\code{\link{RenumberTree}()}, -\code{\link{RootTree}()}, -\code{\link{SortTree}()}, -\code{\link{Subtree}()}, -\code{\link{TipTimedTree}()}, -\code{\link{TrivialTree}} -} -\author{ -\href{https://orcid.org/0000-0001-5660-1727}{Martin R. Smith} -(\href{mailto:martin.smith@durham.ac.uk}{martin.smith@durham.ac.uk}) -} -\concept{tree manipulation} diff --git a/man/ImposeConstraint.Rd b/man/ImposeConstraint.Rd index a5039f5ad..f74a1438b 100644 --- a/man/ImposeConstraint.Rd +++ b/man/ImposeConstraint.Rd @@ -54,7 +54,6 @@ Other tree manipulation: \code{\link{CollapseNode}()}, \code{\link{ConsensusWithout}()}, \code{\link{DropTip}()}, -\code{\link{EnforceOutgroup}()}, \code{\link{KeptPaths}()}, \code{\link{KeptVerts}()}, \code{\link{LeafLabelInterchange}()}, diff --git a/man/KeptPaths.Rd b/man/KeptPaths.Rd index 3430770fd..8ccf292af 100644 --- a/man/KeptPaths.Rd +++ b/man/KeptPaths.Rd @@ -46,7 +46,6 @@ Other tree manipulation: \code{\link{CollapseNode}()}, \code{\link{ConsensusWithout}()}, \code{\link{DropTip}()}, -\code{\link{EnforceOutgroup}()}, \code{\link{ImposeConstraint}()}, \code{\link{KeptVerts}()}, \code{\link{LeafLabelInterchange}()}, diff --git a/man/KeptVerts.Rd b/man/KeptVerts.Rd index 477dfb36a..9c3a53914 100644 --- a/man/KeptVerts.Rd +++ b/man/KeptVerts.Rd @@ -49,7 +49,6 @@ Other tree manipulation: \code{\link{CollapseNode}()}, \code{\link{ConsensusWithout}()}, \code{\link{DropTip}()}, -\code{\link{EnforceOutgroup}()}, \code{\link{ImposeConstraint}()}, \code{\link{KeptPaths}()}, \code{\link{LeafLabelInterchange}()}, diff --git a/man/LeafLabelInterchange.Rd b/man/LeafLabelInterchange.Rd index 378ad4ab8..724f58530 100644 --- a/man/LeafLabelInterchange.Rd +++ b/man/LeafLabelInterchange.Rd @@ -39,7 +39,6 @@ Other tree manipulation: \code{\link{CollapseNode}()}, \code{\link{ConsensusWithout}()}, \code{\link{DropTip}()}, -\code{\link{EnforceOutgroup}()}, \code{\link{ImposeConstraint}()}, \code{\link{KeptPaths}()}, \code{\link{KeptVerts}()}, diff --git a/man/MakeTreeBinary.Rd b/man/MakeTreeBinary.Rd index 74223c947..9de537e87 100644 --- a/man/MakeTreeBinary.Rd +++ b/man/MakeTreeBinary.Rd @@ -34,7 +34,6 @@ Other tree manipulation: \code{\link{CollapseNode}()}, \code{\link{ConsensusWithout}()}, \code{\link{DropTip}()}, -\code{\link{EnforceOutgroup}()}, \code{\link{ImposeConstraint}()}, \code{\link{KeptPaths}()}, \code{\link{KeptVerts}()}, diff --git a/man/Renumber.Rd b/man/Renumber.Rd index 24a276778..6b8e435a7 100644 --- a/man/Renumber.Rd +++ b/man/Renumber.Rd @@ -39,7 +39,6 @@ Other tree manipulation: \code{\link{CollapseNode}()}, \code{\link{ConsensusWithout}()}, \code{\link{DropTip}()}, -\code{\link{EnforceOutgroup}()}, \code{\link{ImposeConstraint}()}, \code{\link{KeptPaths}()}, \code{\link{KeptVerts}()}, diff --git a/man/RenumberTips.Rd b/man/RenumberTips.Rd index bbafed0a1..c7f48bfe5 100644 --- a/man/RenumberTips.Rd +++ b/man/RenumberTips.Rd @@ -46,7 +46,6 @@ Other tree manipulation: \code{\link{CollapseNode}()}, \code{\link{ConsensusWithout}()}, \code{\link{DropTip}()}, -\code{\link{EnforceOutgroup}()}, \code{\link{ImposeConstraint}()}, \code{\link{KeptPaths}()}, \code{\link{KeptVerts}()}, diff --git a/man/Reorder.Rd b/man/Reorder.Rd index 3b99878d5..210be3e78 100644 --- a/man/Reorder.Rd +++ b/man/Reorder.Rd @@ -46,7 +46,7 @@ \usage{ RenumberTree(parent, child, weight) -RenumberEdges(parent, child, ...) +RenumberEdges(parent, child) Cladewise(tree, nTip, edge) @@ -136,8 +136,6 @@ matrix of a tree of class \code{\link[ape]{phylo}}, i.e. \item{weight}{Optional vector specifying the weight of each edge, corresponding to the \code{edge.length} property of a \code{phylo} object.} -\item{\dots}{Deprecated; included for compatibility with previous versions.} - \item{tree}{A tree of class \code{\link[ape:read.tree]{phylo}}.} \item{nTip}{Integer specifying number of tips (leaves).} @@ -250,7 +248,6 @@ Other tree manipulation: \code{\link{CollapseNode}()}, \code{\link{ConsensusWithout}()}, \code{\link{DropTip}()}, -\code{\link{EnforceOutgroup}()}, \code{\link{ImposeConstraint}()}, \code{\link{KeptPaths}()}, \code{\link{KeptVerts}()}, diff --git a/man/RootTree.Rd b/man/RootTree.Rd index d075b3d89..890e0a44d 100644 --- a/man/RootTree.Rd +++ b/man/RootTree.Rd @@ -75,7 +75,6 @@ Other tree manipulation: \code{\link{CollapseNode}()}, \code{\link{ConsensusWithout}()}, \code{\link{DropTip}()}, -\code{\link{EnforceOutgroup}()}, \code{\link{ImposeConstraint}()}, \code{\link{KeptPaths}()}, \code{\link{KeptVerts}()}, diff --git a/man/SortTree.Rd b/man/SortTree.Rd index 59f88ceae..c2d64f8a4 100644 --- a/man/SortTree.Rd +++ b/man/SortTree.Rd @@ -72,7 +72,6 @@ Other tree manipulation: \code{\link{CollapseNode}()}, \code{\link{ConsensusWithout}()}, \code{\link{DropTip}()}, -\code{\link{EnforceOutgroup}()}, \code{\link{ImposeConstraint}()}, \code{\link{KeptPaths}()}, \code{\link{KeptVerts}()}, diff --git a/man/Subtree.Rd b/man/Subtree.Rd index 57fda5c15..0da4e5ea4 100644 --- a/man/Subtree.Rd +++ b/man/Subtree.Rd @@ -41,7 +41,6 @@ Other tree manipulation: \code{\link{CollapseNode}()}, \code{\link{ConsensusWithout}()}, \code{\link{DropTip}()}, -\code{\link{EnforceOutgroup}()}, \code{\link{ImposeConstraint}()}, \code{\link{KeptPaths}()}, \code{\link{KeptVerts}()}, diff --git a/man/TipTimedTree.Rd b/man/TipTimedTree.Rd index ecb23f4a4..b7f021909 100644 --- a/man/TipTimedTree.Rd +++ b/man/TipTimedTree.Rd @@ -53,7 +53,6 @@ Other tree manipulation: \code{\link{CollapseNode}()}, \code{\link{ConsensusWithout}()}, \code{\link{DropTip}()}, -\code{\link{EnforceOutgroup}()}, \code{\link{ImposeConstraint}()}, \code{\link{KeptPaths}()}, \code{\link{KeptVerts}()}, diff --git a/man/TrivialTree.Rd b/man/TrivialTree.Rd index b6e6622d7..4f1281ed3 100644 --- a/man/TrivialTree.Rd +++ b/man/TrivialTree.Rd @@ -38,7 +38,6 @@ Other tree manipulation: \code{\link{CollapseNode}()}, \code{\link{ConsensusWithout}()}, \code{\link{DropTip}()}, -\code{\link{EnforceOutgroup}()}, \code{\link{ImposeConstraint}()}, \code{\link{KeptPaths}()}, \code{\link{KeptVerts}()}, diff --git a/man/match.Splits.Rd b/man/match.Splits.Rd index 954396227..b163dc5eb 100644 --- a/man/match.Splits.Rd +++ b/man/match.Splits.Rd @@ -2,15 +2,12 @@ % Please edit documentation in R/match.R \name{match,Splits,Splits-method} \alias{match,Splits,Splits-method} -\alias{in.Splits} \alias{match} \alias{\%in\%,Splits,Splits-method} \title{Split matching} \usage{ \S4method{match}{Splits,Splits}(x, table, nomatch = NA_integer_, incomparables = NULL) -in.Splits(x, table) - match(x, table, nomatch = NA_integer_, incomparables = NULL) \S4method{\%in\%}{Splits,Splits}(x, table) diff --git a/tests/testthat/test-tree_descendants.R b/tests/testthat/test-tree_descendants.R index 5222b0796..d79ccef5c 100644 --- a/tests/testthat/test-tree_descendants.R +++ b/tests/testthat/test-tree_descendants.R @@ -23,8 +23,6 @@ test_that("DescendantEdges() works", { expect_equal( DescendantEdges(edge = NULL, pec5$edge[, 1], pec5$edge[, 2]), answer) - expect_warning(AllDescendantEdges(pec5$edge[, 1], pec5$edge[, 2]), - "deprecated") expect_equal( apply(DescendantEdges(node = 0, pec5$edge[, 1], pec5$edge[, 2]), 1, which), list(1:7, 4:7, 6:7) From 5ae52bf0bee51dfa4285933daae88d083f3d00e4 Mon Sep 17 00:00:00 2001 From: "Martin R. Smith" <1695515+ms609@users.noreply.github.com> Date: Tue, 23 Sep 2025 09:01:46 +0100 Subject: [PATCH 09/14] skip_if_not_installed("testthat", "3.1.5") for expect_no_error --- tests/testthat/test-Cherries.R | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/testthat/test-Cherries.R b/tests/testthat/test-Cherries.R index fcad86bb2..4e6a5415f 100644 --- a/tests/testthat/test-Cherries.R +++ b/tests/testthat/test-Cherries.R @@ -7,5 +7,6 @@ test_that("Cherries works", { expect_error(n_cherries_wrapper(1:2, 1:3, 4), "same length") + skip_if_not_installed("testthat", "3.1.5") # for expect_no_error expect_no_error(Cherries(BalancedTree(144))) }) From de0d2b321a0fb30048f4ffd5353af72ca6a4e039 Mon Sep 17 00:00:00 2001 From: "Martin R. Smith" <1695515+ms609@users.noreply.github.com> Date: Tue, 23 Sep 2025 09:02:45 +0100 Subject: [PATCH 10/14] try testthat@3.1.5 --- .github/workflows/R-CMD-check.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/R-CMD-check.yml b/.github/workflows/R-CMD-check.yml index 170a3f921..4161bbc5e 100644 --- a/.github/workflows/R-CMD-check.yml +++ b/.github/workflows/R-CMD-check.yml @@ -51,8 +51,8 @@ jobs: config: - {os: windows-latest, r: 'release'} - {os: macOS-latest, r: 'release'} - - {os: ubuntu-24.04, r: '3.6', rspm: "https://packagemanager.posit.co/cran/2022-04-01"} - - {os: ubuntu-24.04, r: '4.0', rspm: "https://packagemanager.posit.co/cran/2022-04-01"} + - {os: ubuntu-24.04, r: '3.6', rspm: "https://packagemanager.posit.co/cran/2022-10-11"} + - {os: ubuntu-24.04, r: '4.0', rspm: "https://packagemanager.posit.co/cran/2022-10-11"} - {os: ubuntu-24.04, r: 'release', rspm: "https://packagemanager.posit.co/cran/__linux__/noble/latest"} - {os: ubuntu-24.04, r: 'devel', rspm: "https://packagemanager.posit.co/cran/__linux__/noble/latest"} @@ -136,7 +136,7 @@ jobs: github::ms609/PlotTools@v0.3.1 rcmdcheck@1.3.3 waldo@0.4.0 - testthat@3.0.4 + testthat@3.1.5 pkgload@1.2.4 pkgdown@2.0.1 bslib@0.3.1 From 07270703025d602d6ef3a4240ba9d3dd59ee5b34 Mon Sep 17 00:00:00 2001 From: "Martin R. Smith" <1695515+ms609@users.noreply.github.com> Date: Tue, 23 Sep 2025 09:05:17 +0100 Subject: [PATCH 11/14] Remove `SpectrumLegend()` --- NAMESPACE | 5 --- NEWS.md | 2 +- R/RoguePlot.R | 2 +- R/helper_functions.R | 89 ------------------------------------------- R/match.R | 3 -- man/SpectrumLegend.Rd | 76 ------------------------------------ man/match.Splits.Rd | 4 -- 7 files changed, 2 insertions(+), 179 deletions(-) delete mode 100644 man/SpectrumLegend.Rd diff --git a/NAMESPACE b/NAMESPACE index 038b761fc..e79c399f0 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -405,7 +405,6 @@ export(SampleOne) export(SingleTaxonTree) export(SisterSize) export(SortTree) -export(SpectrumLegend) export(SplitConflicts) export(SplitConsistent) export(SplitFrequency) @@ -488,10 +487,6 @@ importFrom(grDevices,rgb) importFrom(graphics,legend) importFrom(graphics,lines) importFrom(graphics,par) -importFrom(graphics,segments) -importFrom(graphics,strheight) -importFrom(graphics,strwidth) -importFrom(graphics,text) importFrom(methods,new) importFrom(methods,setClass) importFrom(methods,setMethod) diff --git a/NEWS.md b/NEWS.md index a1707fc1c..679f7ffed 100644 --- a/NEWS.md +++ b/NEWS.md @@ -8,7 +8,7 @@ - Remove R.cache dependency: `UnrootedKeys()` now uses a native cache implementation. - Remove deprecated functions `TreeSplits()`, `ForestSplits()`, `SplitNumber()`, `in.Splits()`, `AllDescendantEdges()`, `PhylogeneticInfo()`, - `.EnforceOutgroup()`. + `.EnforceOutgroup()`, `SpectrumLegend()`. # TreeTools 1.16.1 (2025-08-24) # diff --git a/R/RoguePlot.R b/R/RoguePlot.R index e0e85b03a..4be4eaba7 100644 --- a/R/RoguePlot.R +++ b/R/RoguePlot.R @@ -247,7 +247,7 @@ RoguePlot <- function(trees, tip, p = 1, plot = TRUE, fat, thin), ...) if (legend != "none") { - PlotTools::SpectrumLegend( + SpectrumLegend( legend, bty = "n", palette = pal, diff --git a/R/helper_functions.R b/R/helper_functions.R index 8a9a668cc..aee7a1ef8 100644 --- a/R/helper_functions.R +++ b/R/helper_functions.R @@ -98,92 +98,3 @@ replicate64 <- function(n, expr, simplify = "array") { sapply64(integer(n), eval.parent(substitute(function(...) expr)), simplify = simplify) } - -#nocov start -#' Produce a legend for continuous gradient scales -#' -#' Prints an annotated vertical bar coloured according to a continuous palette. -#' -#' This function is now deprecated; it has been superseded by the more capable -#' [`PlotTools::SpectrumLegend()`] and will be removed in a future release. -# Deprecation notice added in TreeTools 1.9.2 (2023-04-25) -#' -#' @param x0,y0,x1,y1 Coordinates of the bottom-left and top-right end of the -#' bar. -#' @param absolute Logical specifying whether `x` and `y` values denote -#' coordinates (`TRUE`) or relative position, where (0, 0) denotes the -#' bottom-left of the plot area and (1, 1) the top right. -#' @param legend Character vector with which to label points on `palette`. -#' @param palette Colour palette to depict. -#' @param lwd,lty,lend Additional parameters to [`segments()`], -#' controlling line style. -#' @param cex Character expansion factor relative to current `par("cex")`. -#' @param text.col Colour used for the legend text. -#' @param font,text.font Font used for the legend text; see [`text()`]. -#' @param title Text to display -#' @param title.col Colour for title; defaults to `text.col[1]`. -#' @param title.cex Expansion factor(s) for the title, defaults to `cex[1]`. -#' @param title.adj Horizontal adjustment for title: see the help for -#' `par("adj")`. -#' @param title.font Font used for the legend title. -#' @param pos,\dots Additional parameters to [`text()`]. -#' -#' @template MRS -#' @importFrom graphics segments strheight strwidth text -#' @keywords internal -#' @export -SpectrumLegend <- function(x0 = 0.05, y0 = 0.05, - x1 = x0, y1 = y0 + 0.2, - absolute = FALSE, - legend = character(0), palette, - lwd = 4, lty = 1, lend = "square", cex = 1, - text.col = par("col"), - font = NULL, text.font = font, - title = NULL, title.col = text.col[1], - title.cex = cex[1], title.adj = 0.5, title.font = 2, - pos = 4, - ...) { - - .Deprecated("PlotTools::SpectrumLegend", package = "PlotTools") - - nCol <- length(palette) - - if (!absolute) { - corners <- par("usr") # x0 x1 y0 y1 - xRange <- corners[2] - corners[1] - yRange <- corners[4] - corners[3] - - # Order is important: lazy evaluation will set x1 = modified x0 - x1 <- corners[1] + (x1 * xRange) - x0 <- corners[1] + (x0 * xRange) - y1 <- corners[3] + (y1 * yRange) - y0 <- corners[3] + (y0 * yRange) - } - - segX <- x0 + ((x1 - x0) * 0:nCol / nCol) - segY <- y0 + ((y1 - y0) * 0:nCol / nCol) - - nPlus1 <- nCol + 1L - segments(segX[-nPlus1], segY[-nPlus1], - segX[-1], segY[-1], - col = palette, - lwd = lwd, lty = lty, lend = lend) - text(seq(x0, x1, length.out = length(legend)), - seq(y0, y1, length.out = length(legend)), - col = text.col, - cex = cex, - font = text.font, - legend, pos = pos, ...) - if (!is.null(title)) { - text(mean(x0, x1) + (max(strwidth(legend)) / ifelse(pos == 2, -2, 2)), - max(y0, y1) + prod( - par("lheight"), - strheight("") - ), - title, - pos = 3, - cex = title.cex, adj = title.adj, font = title.font, col = title.col, - ...) - } -} -#nocov end diff --git a/R/match.R b/R/match.R index 5ff3bba3c..840bec1d4 100644 --- a/R/match.R +++ b/R/match.R @@ -6,9 +6,6 @@ #' a logical vector indicating whether there is a match or not for each #' split in its left operand. #' -#' `in.Splits()` is an alias for `%in%`, included for backwards compatibility. -#' It is deprecated and will be removed in a future release. -#' #' @param x,table Object of class `Splits`. #' @param nomatch Integer value that will be used in place of `NA` in the case #' where no match is found. diff --git a/man/SpectrumLegend.Rd b/man/SpectrumLegend.Rd deleted file mode 100644 index d39a70efa..000000000 --- a/man/SpectrumLegend.Rd +++ /dev/null @@ -1,76 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/helper_functions.R -\name{SpectrumLegend} -\alias{SpectrumLegend} -\title{Produce a legend for continuous gradient scales} -\usage{ -SpectrumLegend( - x0 = 0.05, - y0 = 0.05, - x1 = x0, - y1 = y0 + 0.2, - absolute = FALSE, - legend = character(0), - palette, - lwd = 4, - lty = 1, - lend = "square", - cex = 1, - text.col = par("col"), - font = NULL, - text.font = font, - title = NULL, - title.col = text.col[1], - title.cex = cex[1], - title.adj = 0.5, - title.font = 2, - pos = 4, - ... -) -} -\arguments{ -\item{x0, y0, x1, y1}{Coordinates of the bottom-left and top-right end of the -bar.} - -\item{absolute}{Logical specifying whether \code{x} and \code{y} values denote -coordinates (\code{TRUE}) or relative position, where (0, 0) denotes the -bottom-left of the plot area and (1, 1) the top right.} - -\item{legend}{Character vector with which to label points on \code{palette}.} - -\item{palette}{Colour palette to depict.} - -\item{lwd, lty, lend}{Additional parameters to \code{\link[=segments]{segments()}}, -controlling line style.} - -\item{cex}{Character expansion factor relative to current \code{par("cex")}.} - -\item{text.col}{Colour used for the legend text.} - -\item{font, text.font}{Font used for the legend text; see \code{\link[=text]{text()}}.} - -\item{title}{Text to display} - -\item{title.col}{Colour for title; defaults to \code{text.col[1]}.} - -\item{title.cex}{Expansion factor(s) for the title, defaults to \code{cex[1]}.} - -\item{title.adj}{Horizontal adjustment for title: see the help for -\code{par("adj")}.} - -\item{title.font}{Font used for the legend title.} - -\item{pos, \dots}{Additional parameters to \code{\link[=text]{text()}}.} -} -\description{ -Prints an annotated vertical bar coloured according to a continuous palette. -} -\details{ -This function is now deprecated; it has been superseded by the more capable -\code{\link[PlotTools:SpectrumLegend]{PlotTools::SpectrumLegend()}} and will be removed in a future release. -} -\author{ -\href{https://orcid.org/0000-0001-5660-1727}{Martin R. Smith} -(\href{mailto:martin.smith@durham.ac.uk}{martin.smith@durham.ac.uk}) -} -\keyword{internal} diff --git a/man/match.Splits.Rd b/man/match.Splits.Rd index b163dc5eb..fd4352e8a 100644 --- a/man/match.Splits.Rd +++ b/man/match.Splits.Rd @@ -31,10 +31,6 @@ its first argument in its second. a logical vector indicating whether there is a match or not for each split in its left operand. } -\details{ -\code{in.Splits()} is an alias for \code{\%in\%}, included for backwards compatibility. -It is deprecated and will be removed in a future release. -} \examples{ splits1 <- as.Splits(BalancedTree(7)) splits2 <- as.Splits(PectinateTree(7)) From da810dbabe5c8a4acb440e9355322d4145731051 Mon Sep 17 00:00:00 2001 From: "Martin R. Smith" <1695515+ms609@users.noreply.github.com> Date: Tue, 23 Sep 2025 09:06:22 +0100 Subject: [PATCH 12/14] sp --- R/tree_shape.R | 2 +- man/TreeShape.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/tree_shape.R b/R/tree_shape.R index 3d330bac5..dd5fe6e0c 100644 --- a/R/tree_shape.R +++ b/R/tree_shape.R @@ -237,7 +237,7 @@ UnrootedTreeKey <- function(tree, asInteger = FALSE) { .unrooted_keys_cache <- new.env(parent = emptyenv()) #' @rdname TreeShape -#' @param \dots Depreciated (2025-09); retained for backward compatibility. +#' @param \dots Deprecated (2025-09); retained for backward compatibility. #' @return `UnrootedKeys()` returns a vector of integers corresponding to the #' keys (not shape numbers) of unrooted tree shapes with `nTip` tips. #' It is a wrapper to `.UnrootedKeys()`, with memoization, meaning that results diff --git a/man/TreeShape.Rd b/man/TreeShape.Rd index 6fee3bd13..2fdd9f6bd 100644 --- a/man/TreeShape.Rd +++ b/man/TreeShape.Rd @@ -60,7 +60,7 @@ in a tree, perhaps obtained using \code{\link[=TipLabels]{TipLabels()}}.} mode \code{integer}: only possible for values < 2^31. If \code{FALSE}, values will have class \code{integer64}.} -\item{\dots}{Depreciated (2025-09); retained for backward compatibility.} +\item{\dots}{Deprecated (2025-09); retained for backward compatibility.} } \value{ \code{TreeShape()} returns an integer specifying the shape of a tree, From 45d83593f14c3de143df755099720e02319267f4 Mon Sep 17 00:00:00 2001 From: "Martin R. Smith" <1695515+ms609@users.noreply.github.com> Date: Tue, 23 Sep 2025 09:07:58 +0100 Subject: [PATCH 13/14] -TREETOOLS_SPLITLIST_INIT --- NEWS.md | 1 + inst/include/TreeTools/SplitList.h | 12 ------------ 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/NEWS.md b/NEWS.md index 679f7ffed..22ac2dfd9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,6 +9,7 @@ - Remove deprecated functions `TreeSplits()`, `ForestSplits()`, `SplitNumber()`, `in.Splits()`, `AllDescendantEdges()`, `PhylogeneticInfo()`, `.EnforceOutgroup()`, `SpectrumLegend()`. +- Remove deprecated C++ macro `TREETOOLS_SPLITLIST_INIT`. # TreeTools 1.16.1 (2025-08-24) # diff --git a/inst/include/TreeTools/SplitList.h b/inst/include/TreeTools/SplitList.h index f0cbc6fce..4e83cb538 100644 --- a/inst/include/TreeTools/SplitList.h +++ b/inst/include/TreeTools/SplitList.h @@ -25,18 +25,6 @@ using splitbit = uint_fast64_t; splitbit(x(split, ((bin) * input_bins_per_bin) + (offset))) #define INBIN(r_bin, bin) ((INSUBBIN((bin), (r_bin))) << (R_BIN_SIZE * (r_bin))) -// Retained for backward compatibility; not required since 1.15.0.9006 -#define TREETOOLS_SPLITLIST_INIT __attribute__((constructor)) \ -void _treetools_initialize_bitcounts() { \ - for (int i = 65536; i--; ) { \ - int16 n_bits = 0; \ - for (int j = 16; j--; ) { \ - if (i & (1 << j)) n_bits += 1; \ - } \ - TreeTools::bitcounts[i] = n_bits; \ - } \ -} \ - namespace TreeTools { constexpr int input_bins_per_bin = SL_BIN_SIZE / R_BIN_SIZE; From b41fb4962b6c46967d654723c6b7d1db208140f3 Mon Sep 17 00:00:00 2001 From: "Martin R. Smith" <1695515+ms609@users.noreply.github.com> Date: Tue, 23 Sep 2025 10:20:15 +0100 Subject: [PATCH 14/14] Hone documentation --- R/Cherries.R | 9 ++++++--- man/Cherries.Rd | 9 +++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/R/Cherries.R b/R/Cherries.R index dcb4ea8ee..aa9088d2f 100644 --- a/R/Cherries.R +++ b/R/Cherries.R @@ -1,7 +1,10 @@ -#' Number of cherries in a phylogenetic tree +#' Count cherries in a tree #' -#' @param tree A binary phylogenetic tree, of class `phylo`; or a matrix -#' corresponding to its edge matrix. +#' `Cherries()` counts the number of vertices in a binary tree whose children +#' are both leaves. +#' +#' @param tree A binary tree, of class `phylo`; or a matrix corresponding to its +#' edge matrix. #' @param nTip Number of leaves in tree. #' @return `Cherries()` returns an integer specifying the number of nodes whose #' children are both leaves. diff --git a/man/Cherries.Rd b/man/Cherries.Rd index 67b95eb09..b267d8f84 100644 --- a/man/Cherries.Rd +++ b/man/Cherries.Rd @@ -4,7 +4,7 @@ \alias{Cherries} \alias{Cherries.phylo} \alias{Cherries.numeric} -\title{Number of cherries in a phylogenetic tree} +\title{Count cherries in a tree} \usage{ Cherries(tree, nTip) @@ -13,8 +13,8 @@ Cherries(tree, nTip) \method{Cherries}{numeric}(tree, nTip) } \arguments{ -\item{tree}{A binary phylogenetic tree, of class \code{phylo}; or a matrix -corresponding to its edge matrix.} +\item{tree}{A binary tree, of class \code{phylo}; or a matrix corresponding to its +edge matrix.} \item{nTip}{Number of leaves in tree.} } @@ -23,7 +23,8 @@ corresponding to its edge matrix.} children are both leaves. } \description{ -Number of cherries in a phylogenetic tree +\code{Cherries()} counts the number of vertices in a binary tree whose children +are both leaves. } \seealso{ Other tree properties: