From 3be1a48f988a9653969f0844aef8ce578ca7671b Mon Sep 17 00:00:00 2001 From: Alex Hayes Date: Tue, 4 Nov 2025 15:51:50 -0800 Subject: [PATCH 1/5] Start on precise block size control --- R/undirected_dcsbm.R | 64 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/R/undirected_dcsbm.R b/R/undirected_dcsbm.R index 6233950..89f0fcc 100644 --- a/R/undirected_dcsbm.R +++ b/R/undirected_dcsbm.R @@ -110,11 +110,19 @@ validate_undirected_dcsbm <- function(x) { #' symmetrized via the update `B := B + t(B)`. Defaults to `NULL`. #' You must specify either `k` or `B`, but not both. #' -#' @param pi (relative block probabilities) Relative block +#' @param block_sizes (block sizes) Number of nodes in each block, +#' as a vector of integers. Must match the dimensions of `B`, or `k` and +#' must sum to `n`. Defaults to `NULL`, in which case blocks are made +#' to be as balanced as possible. You can specify either `pi` or +#' `block_sizes`, but not both. +#' +#' @param pi (block sizes) Relative block #' probabilities. Must be positive, but do not need to sum #' to one, as they will be normalized internally. -#' Must match the dimensions of `B` or `k`. Defaults to -#' `rep(1 / k, k)`, or a balanced blocks. +#' Must match the dimensions of `B` or `k`. Defaults to `NULL`, in which +#' case the `block_sizes` argument will take precedence. Note that +#' you can specify either `pi` or `block_sizes`, but should not +#' specify both. #' #' @param sort_nodes Logical indicating whether or not to sort the nodes #' so that they are grouped by block and by `theta`. Useful for plotting. @@ -250,7 +258,8 @@ dcsbm <- function( n = NULL, theta = NULL, k = NULL, B = NULL, ..., - pi = rep(1 / k, k), + block_sizes = NULL, + pi = NULL, sort_nodes = TRUE, force_identifiability = FALSE, poisson_edges = TRUE, @@ -273,6 +282,8 @@ dcsbm <- function( theta <- stats::rlnorm(n, meanlog = 2, sdlog = 1) } else if (is.null(n)) { n <- length(theta) + } else { + stop("Must specify only one of `n` and `theta`, not both.", call. = FALSE) } ### mixing matrix @@ -297,18 +308,48 @@ dcsbm <- function( } k <- nrow(B) + } else { + stop("Must specify only one of `B` and `k`, not both.", call. = FALSE) } ### block membership - if (length(pi) != nrow(B) || length(pi) != ncol(B)) { - stop("Length of `pi` must match dimensions of `B`.", call. = FALSE) - } + if (is.null(block_sizes) && is.null(pi)) { + + TODO BALANCED BLOCKS + + } else if (!is.null(block_sizes)) { + + TODO BLOCK SIZES PER ARGUMENT + + sum(block_sizes) == n + + rep(1:k, each = block_sizes) + + # for sorting by block size later + pi <- block_sizes / n + + } else if (!is.null(pi)) { + if (!is.null(pi) && !is.null(block_sizes)) { + stop("Length of `pi` must match dimensions of `B`.", call. = FALSE) + } - if (any(pi < 0)) { - stop("All elements of `pi` must be >= 0.", call. = FALSE) + if (length(pi) != nrow(B) || length(pi) != ncol(B)) { + stop("Length of `pi` must match dimensions of `B`.", call. = FALSE) + } + + if (any(pi < 0)) { + stop("All elements of `pi` must be >= 0.", call. = FALSE) + } + + + z <- sample(k, n, replace = TRUE, prob = pi) + } else { + stop("Must at most one of `block_sizes` and `pi`.", call. = FALSE) } + z <- factor(z, levels = 1:k, labels = paste0("block", 1:k)) + # order mixing matrix by expected group size if (k > 1 && sort_nodes) { @@ -319,11 +360,6 @@ dcsbm <- function( pi <- pi / sum(pi) - # sample block memberships - - z <- sample(k, n, replace = TRUE, prob = pi) - z <- factor(z, levels = 1:k, labels = paste0("block", 1:k)) - if (sort_nodes) { z <- sort(z) } From 5dd5f1632b1ee414fc233c83448a7bd3ea438d0a Mon Sep 17 00:00:00 2001 From: Alex Hayes Date: Thu, 6 Nov 2025 11:22:49 -0800 Subject: [PATCH 2/5] Implement block_size argument for dcsbm() --- NEWS.md | 3 +++ R/expected-degrees.R | 2 +- R/undirected_dcsbm.R | 33 +++++++++++++++++++++++---- R/undirected_planted_partition.R | 6 +++-- R/undirected_sbm.R | 22 ++++++++++++++++-- man/dcsbm.Rd | 29 ++++++++++++++++++++---- man/expected_edges.Rd | 2 +- man/planted_partition.Rd | 17 ++++++++++---- man/sbm.Rd | 34 ++++++++++++++++++++++++---- tests/testthat/test-degree-scaling.R | 10 ++++---- 10 files changed, 130 insertions(+), 28 deletions(-) diff --git a/NEWS.md b/NEWS.md index dda9bff..b7427dc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,8 @@ # fastRG (development version) +- Added option to specify precise number of nodes in each block of a `dcsbm()` or `sbm()` via the `block_sizes` argument. This makes it easier to construct blockmodels with exactly repeated eigenvalues. +- Specifying both `k` and `B` in `dcsbm()` and `sbm()` now results in an error; only specify one of these arguments. + # fastRG 0.3.3 - Improve cross-linking to documentation of other packages for CRAN diff --git a/R/expected-degrees.R b/R/expected-degrees.R index c533bee..c36d53d 100644 --- a/R/expected-degrees.R +++ b/R/expected-degrees.R @@ -25,7 +25,7 @@ #' #' B <- matrix(c(a, b, b, a), nrow = 2) #' -#' b_model <- sbm(n = n, k = 2, B = B, poisson_edges = FALSE) +#' b_model <- sbm(n = n, B = B, poisson_edges = FALSE) #' #' b_model #' diff --git a/R/undirected_dcsbm.R b/R/undirected_dcsbm.R index 89f0fcc..e320ddb 100644 --- a/R/undirected_dcsbm.R +++ b/R/undirected_dcsbm.R @@ -247,6 +247,17 @@ validate_undirected_dcsbm <- function(x) { #' edgelist <- sample_edgelist(custom_dcsbm) #' edgelist #' +#' +#' dcsbm_explicit_block_sizes <- dcsbm( +#' theta = rexp(100, 1 / 3) + 1, +#' B = B, +#' block_sizes = c(13, 17, 40, 14, 16), +#' expected_degree = 5 +#' ) +#' +#' # respects block sizes +#' summary(dcsbm_explicit_block_sizes$z) +#' #' # efficient eigendecompostion that leverages low-rank structure in #' # E(A) so that you don't have to form E(A) to find eigenvectors, #' # as E(A) is typically dense. computation is @@ -254,6 +265,7 @@ validate_undirected_dcsbm <- function(x) { #' #' population_eigs <- eigs_sym(custom_dcsbm) #' +#' dcsbm <- function( n = NULL, theta = NULL, k = NULL, B = NULL, @@ -316,15 +328,26 @@ dcsbm <- function( if (is.null(block_sizes) && is.null(pi)) { - TODO BALANCED BLOCKS + base_value <- floor(n / k) + remainder <- n %% k + num_base_values <- k - remainder - } else if (!is.null(block_sizes)) { + upper_values <- rep(base_value + 1, remainder) + base_values <- rep(base_value, num_base_values) - TODO BLOCK SIZES PER ARGUMENT + block_sizes <- c(upper_values, base_values) - sum(block_sizes) == n + z <- sample(rep(1:k, times = block_sizes)) + + # for sorting by block size later + pi <- block_sizes / n + } else if (!is.null(block_sizes)) { + + if(sum(block_sizes) != n) { + stop("Sum of `block_sizes` must equal `n` or `length(theta)`.", call. = FALSE) + } - rep(1:k, each = block_sizes) + z <- sample(rep(1:k, times = block_sizes)) # for sorting by block size later pi <- block_sizes / n diff --git a/R/undirected_planted_partition.R b/R/undirected_planted_partition.R index 2aa5d07..c40d4bb 100644 --- a/R/undirected_planted_partition.R +++ b/R/undirected_planted_partition.R @@ -127,7 +127,8 @@ planted_partition <- function( between_block = NULL, a = NULL, b = NULL, - pi = rep(1 / k, k), + block_sizes = NULL, + pi = NULL, sort_nodes = TRUE, poisson_edges = TRUE, allow_self_loops = TRUE) { @@ -144,9 +145,10 @@ planted_partition <- function( pp <- sbm( n = n, - k = k, + k = NULL, # implicit in dimensions of B B = B, ..., + block_sizes = block_sizes, pi = pi, sort_nodes = sort_nodes, poisson_edges = poisson_edges, diff --git a/R/undirected_sbm.R b/R/undirected_sbm.R index 3ca3e7f..cc511ca 100644 --- a/R/undirected_sbm.R +++ b/R/undirected_sbm.R @@ -82,20 +82,38 @@ validate_undirected_sbm <- function(x) { #' # only zeroes and ones! #' sign(A) #' +#' +#' # sbm with repeated eigenvalues +#' +#' # block sizes equal by default, needed to prevent variation in spectrum +#' # from variation in block sizes. also need B to have a single repeated +#' # eigenvalue +#' +#' repeated_eigen <- sbm( +#' n = 100, +#' B = diag(rep(0.8, 5)), +#' expected_degree = 10 +#' ) +#' +#' # exactly repeated eigenvalues in the population +#' e <- eigs_sym(repeated_eigen) +#' e$values +#' sbm <- function( n, k = NULL, B = NULL, ..., - pi = rep(1 / k, k), + block_sizes = NULL, + pi = NULL, sort_nodes = TRUE, poisson_edges = TRUE, allow_self_loops = TRUE) { sbm <- dcsbm( - n = n, theta = rep(1, n), k = k, B = B, ..., + block_sizes = block_sizes, pi = pi, sort_nodes = sort_nodes, force_identifiability = FALSE, diff --git a/man/dcsbm.Rd b/man/dcsbm.Rd index 4e882a6..b3661e9 100644 --- a/man/dcsbm.Rd +++ b/man/dcsbm.Rd @@ -10,7 +10,8 @@ dcsbm( k = NULL, B = NULL, ..., - pi = rep(1/k, k), + block_sizes = NULL, + pi = NULL, sort_nodes = TRUE, force_identifiability = FALSE, poisson_edges = TRUE, @@ -64,11 +65,19 @@ to achieve this. Defaults to \code{NULL}. Do not specify both \code{expected_degree} and \code{expected_density} at the same time.} }} -\item{pi}{(relative block probabilities) Relative block +\item{block_sizes}{(block sizes) Number of nodes in each block, +as a vector of integers. Must match the dimensions of \code{B}, or \code{k} and +must sum to \code{n}. Defaults to \code{NULL}, in which case blocks are made +to be as balanced as possible. You can specify either \code{pi} or +\code{block_sizes}, but not both.} + +\item{pi}{(block sizes) Relative block probabilities. Must be positive, but do not need to sum to one, as they will be normalized internally. -Must match the dimensions of \code{B} or \code{k}. Defaults to -\code{rep(1 / k, k)}, or a balanced blocks.} +Must match the dimensions of \code{B} or \code{k}. Defaults to \code{NULL}, in which +case the \code{block_sizes} argument will take precedence. Note that +you can specify either \code{pi} or \code{block_sizes}, but should not +specify both.} \item{sort_nodes}{Logical indicating whether or not to sort the nodes so that they are grouped by block and by \code{theta}. Useful for plotting. @@ -211,6 +220,17 @@ custom_dcsbm edgelist <- sample_edgelist(custom_dcsbm) edgelist + +dcsbm_explicit_block_sizes <- dcsbm( + theta = rexp(100, 1 / 3) + 1, + B = B, + block_sizes = c(13, 17, 40, 14, 16), + expected_degree = 5 +) + +# respects block sizes +summary(dcsbm_explicit_block_sizes$z) + # efficient eigendecompostion that leverages low-rank structure in # E(A) so that you don't have to form E(A) to find eigenvectors, # as E(A) is typically dense. computation is @@ -218,6 +238,7 @@ edgelist population_eigs <- eigs_sym(custom_dcsbm) + } \seealso{ Other stochastic block models: diff --git a/man/expected_edges.Rd b/man/expected_edges.Rd index d8f9528..0de11eb 100644 --- a/man/expected_edges.Rd +++ b/man/expected_edges.Rd @@ -51,7 +51,7 @@ b <- .05 B <- matrix(c(a, b, b, a), nrow = 2) -b_model <- sbm(n = n, k = 2, B = B, poisson_edges = FALSE) +b_model <- sbm(n = n, B = B, poisson_edges = FALSE) b_model diff --git a/man/planted_partition.Rd b/man/planted_partition.Rd index 1940db4..86f126a 100644 --- a/man/planted_partition.Rd +++ b/man/planted_partition.Rd @@ -12,7 +12,8 @@ planted_partition( between_block = NULL, a = NULL, b = NULL, - pi = rep(1/k, k), + block_sizes = NULL, + pi = NULL, sort_nodes = TRUE, poisson_edges = TRUE, allow_self_loops = TRUE @@ -58,11 +59,19 @@ between blocks. Useful for sparse graphs. Must specify either \code{within_block} and \code{between_block}, or \code{a} and \code{b} to determine edge probabilities.} -\item{pi}{(relative block probabilities) Relative block +\item{block_sizes}{(block sizes) Number of nodes in each block, +as a vector of integers. Must match the dimensions of \code{B}, or \code{k} and +must sum to \code{n}. Defaults to \code{NULL}, in which case blocks are made +to be as balanced as possible. You can specify either \code{pi} or +\code{block_sizes}, but not both.} + +\item{pi}{(block sizes) Relative block probabilities. Must be positive, but do not need to sum to one, as they will be normalized internally. -Must match the dimensions of \code{B} or \code{k}. Defaults to -\code{rep(1 / k, k)}, or a balanced blocks.} +Must match the dimensions of \code{B} or \code{k}. Defaults to \code{NULL}, in which +case the \code{block_sizes} argument will take precedence. Note that +you can specify either \code{pi} or \code{block_sizes}, but should not +specify both.} \item{sort_nodes}{Logical indicating whether or not to sort the nodes so that they are grouped by block and by \code{theta}. Useful for plotting. diff --git a/man/sbm.Rd b/man/sbm.Rd index c4127d9..eb61e21 100644 --- a/man/sbm.Rd +++ b/man/sbm.Rd @@ -9,7 +9,8 @@ sbm( k = NULL, B = NULL, ..., - pi = rep(1/k, k), + block_sizes = NULL, + pi = NULL, sort_nodes = TRUE, poisson_edges = TRUE, allow_self_loops = TRUE @@ -48,11 +49,19 @@ to achieve this. Defaults to \code{NULL}. Do not specify both \code{expected_degree} and \code{expected_density} at the same time.} }} -\item{pi}{(relative block probabilities) Relative block +\item{block_sizes}{(block sizes) Number of nodes in each block, +as a vector of integers. Must match the dimensions of \code{B}, or \code{k} and +must sum to \code{n}. Defaults to \code{NULL}, in which case blocks are made +to be as balanced as possible. You can specify either \code{pi} or +\code{block_sizes}, but not both.} + +\item{pi}{(block sizes) Relative block probabilities. Must be positive, but do not need to sum to one, as they will be normalized internally. -Must match the dimensions of \code{B} or \code{k}. Defaults to -\code{rep(1 / k, k)}, or a balanced blocks.} +Must match the dimensions of \code{B} or \code{k}. Defaults to \code{NULL}, in which +case the \code{block_sizes} argument will take precedence. Note that +you can specify either \code{pi} or \code{block_sizes}, but should not +specify both.} \item{sort_nodes}{Logical indicating whether or not to sort the nodes so that they are grouped by block and by \code{theta}. Useful for plotting. @@ -124,6 +133,23 @@ A <- sample_sparse(bernoulli_sbm) # only zeroes and ones! sign(A) + +# sbm with repeated eigenvalues + +# block sizes equal by default, needed to prevent variation in spectrum +# from variation in block sizes. also need B to have a single repeated +# eigenvalue + +repeated_eigen <- sbm( + n = 100, + B = diag(rep(0.8, 5)), + expected_degree = 10 +) + +# exactly repeated eigenvalues in the population +e <- eigs_sym(repeated_eigen) +e$values + } \seealso{ Other stochastic block models: diff --git a/tests/testthat/test-degree-scaling.R b/tests/testthat/test-degree-scaling.R index 5dbaf1f..5dba464 100644 --- a/tests/testthat/test-degree-scaling.R +++ b/tests/testthat/test-degree-scaling.R @@ -37,7 +37,7 @@ test_that("undirected expected degree computed consistently", { B <- matrix(c(a, b, b, a), nrow = 2) b_model <- sbm( - n = n, k = 2, + n = n, B = B, poisson_edges = FALSE ) @@ -63,7 +63,7 @@ test_that("undirected expected degree computed consistently", { tolerance = 5 ) - model2 <- sbm(n = n, k = 2, B = B, poisson_edges = FALSE, expected_degree = 75) + model2 <- sbm(n = n, B = B, poisson_edges = FALSE, expected_degree = 75) expect_equal( expected_degree(model2), # computed @@ -92,7 +92,7 @@ test_that("undirected density computed consistently", { B <- matrix(c(a, b, b, a), nrow = 2) b_model <- sbm( - n = n, k = 2, + n = n, B = B, poisson_edges = FALSE ) @@ -119,7 +119,7 @@ test_that("undirected density computed consistently", { tolerance = 0.05 ) - model2 <- sbm(n = n, k = 2, B = B, expected_density = 0.15) + model2 <- sbm(n = n, B = B, expected_density = 0.15) expect_equal( expected_density(model2), # computed @@ -150,7 +150,7 @@ test_that("undirected factor model", { B <- matrix(data = 0.5, nrow = k, ncol = k) diag(B) <- 0 - ufm <- sbm(n = n, k = k, B = B, expected_degree = 10) + ufm <- sbm(n = n, B = B, expected_degree = 10) expect_equal(expected_degree(ufm), 10) expect_equal(expected_density(ufm), 0.02, tolerance = 0.05) # tolerance should be relative here From 807a70d1c77d4f3c30218f0b46a8f846592ffd74 Mon Sep 17 00:00:00 2001 From: Alex Hayes Date: Thu, 6 Nov 2025 11:23:08 -0800 Subject: [PATCH 3/5] Migrate get.adjacency() -> as_adjacency_matrix() --- tests/testthat/test-allow_self_loops.R | 8 ++++---- tests/testthat/test-poisson_edges.R | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/testthat/test-allow_self_loops.R b/tests/testthat/test-allow_self_loops.R index 5367283..c81bc50 100644 --- a/tests/testthat/test-allow_self_loops.R +++ b/tests/testthat/test-allow_self_loops.R @@ -22,12 +22,12 @@ test_that("undirected graphs allow_self_loops = FALSE", { expect_false(any(diag(A) > 0)) igraph <- sample_igraph(ufm) - expect_false(any(diag(get.adjacency(igraph)) > 0)) + expect_false(any(diag(as_adjacency_matrix(igraph)) > 0)) ### sampling graphs as tidygraph graphs --------------- tbl_graph <- sample_tidygraph(ufm) - expect_false(any(diag(get.adjacency(tbl_graph)) > 0)) + expect_false(any(diag(as_adjacency_matrix(tbl_graph)) > 0)) }) test_that("directed graphs allow_self_loops = FALSE", { @@ -53,10 +53,10 @@ test_that("directed graphs allow_self_loops = FALSE", { expect_false(any(diag(A) > 0)) igraph <- sample_igraph(fm) - expect_false(any(diag(get.adjacency(igraph)) > 0)) + expect_false(any(diag(as_adjacency_matrix(igraph)) > 0)) ### sampling graphs as tidygraph graphs --------------- tbl_graph <- sample_tidygraph(fm) - expect_false(any(diag(get.adjacency(tbl_graph)) > 0)) + expect_false(any(diag(as_adjacency_matrix(tbl_graph)) > 0)) }) diff --git a/tests/testthat/test-poisson_edges.R b/tests/testthat/test-poisson_edges.R index 6b1d4e4..32cb63a 100644 --- a/tests/testthat/test-poisson_edges.R +++ b/tests/testthat/test-poisson_edges.R @@ -30,12 +30,12 @@ test_that("undirected graphs poisson_edges = FALSE", { expect_equal(max(A), 1) igraph <- sample_igraph(ufm) - expect_equal(max(get.adjacency(igraph)), 1) + expect_equal(max(as_adjacency_matrix(igraph)), 1) ### sampling graphs as tidygraph graphs --------------- tbl_graph <- sample_tidygraph(ufm) - expect_equal(max(get.adjacency(tbl_graph)), 1) + expect_equal(max(as_adjacency_matrix(tbl_graph)), 1) }) test_that("directed graphs poisson_edges = FALSE", { @@ -69,10 +69,10 @@ test_that("directed graphs poisson_edges = FALSE", { expect_equal(max(A), 1) igraph <- sample_igraph(fm) - expect_equal(max(get.adjacency(igraph)), 1) + expect_equal(max(as_adjacency_matrix(igraph)), 1) ### sampling graphs as tidygraph graphs --------------- tbl_graph <- sample_tidygraph(fm) - expect_equal(max(get.adjacency(tbl_graph)), 1) + expect_equal(max(as_adjacency_matrix(tbl_graph)), 1) }) From 24a7ab101a821a707b4bd21a83a48249e4148963 Mon Sep 17 00:00:00 2001 From: Alex Hayes Date: Fri, 7 Nov 2025 09:44:23 -0800 Subject: [PATCH 4/5] Update NEWS --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index b7427dc..eb4161f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,7 @@ # fastRG (development version) - Added option to specify precise number of nodes in each block of a `dcsbm()` or `sbm()` via the `block_sizes` argument. This makes it easier to construct blockmodels with exactly repeated eigenvalues. +- The default behavior of `dcsbm()`, `sbm()` and `planted_partition()` has changed: when `block_sizes` or `pi` is unspecified, the new default is to balance block sizes as evenly as possible. Previously, `pi` was set to a constant vector, balancing block sizes in expectation only. - Specifying both `k` and `B` in `dcsbm()` and `sbm()` now results in an error; only specify one of these arguments. # fastRG 0.3.3 From 8cbd20a5e49686c70fa463ce6c5c7556e47e8932 Mon Sep 17 00:00:00 2001 From: Alex Hayes Date: Fri, 7 Nov 2025 09:47:25 -0800 Subject: [PATCH 5/5] Update GHA --- .github/workflows/R-CMD-check.yaml | 24 +++++++++++++----------- .github/workflows/pkgdown.yaml | 9 +++++---- .github/workflows/pr-commands.yaml | 12 +++++++++--- .github/workflows/test-coverage.yaml | 28 ++++++++++++++++++++-------- 4 files changed, 47 insertions(+), 26 deletions(-) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index d9fced2..69cfc6a 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -8,9 +8,10 @@ on: push: branches: [main, master] pull_request: - branches: [main, master] -name: R-CMD-check +name: R-CMD-check.yaml + +permissions: read-all jobs: R-CMD-check: @@ -25,22 +26,22 @@ jobs: - {os: macos-latest, r: 'release'} - {os: windows-latest, r: 'release'} - # use 4.1 to check with rtools40's older compiler - - {os: windows-latest, r: '4.1'} + # use 4.0 or 4.1 to check with rtools40's older compiler + - {os: windows-latest, r: 'oldrel-4'} - - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} - - {os: ubuntu-latest, r: 'release'} - - {os: ubuntu-latest, r: 'oldrel-1'} - - {os: ubuntu-latest, r: 'oldrel-2'} - - {os: ubuntu-latest, r: 'oldrel-3'} - - {os: ubuntu-latest, r: 'oldrel-4'} + - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} + - {os: ubuntu-latest, r: 'release'} + - {os: ubuntu-latest, r: 'oldrel-1'} + - {os: ubuntu-latest, r: 'oldrel-2'} + - {os: ubuntu-latest, r: 'oldrel-3'} + - {os: ubuntu-latest, r: 'oldrel-4'} env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} R_KEEP_PKG_SOURCE: yes steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: r-lib/actions/setup-pandoc@v2 @@ -58,3 +59,4 @@ jobs: - uses: r-lib/actions/check-r-package@v2 with: upload-snapshots: true + build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index ed7650c..bfc9f4d 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -4,12 +4,13 @@ on: push: branches: [main, master] pull_request: - branches: [main, master] release: types: [published] workflow_dispatch: -name: pkgdown +name: pkgdown.yaml + +permissions: read-all jobs: pkgdown: @@ -22,7 +23,7 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: r-lib/actions/setup-pandoc@v2 @@ -41,7 +42,7 @@ jobs: - name: Deploy to GitHub pages 🚀 if: github.event_name != 'pull_request' - uses: JamesIves/github-pages-deploy-action@v4.4.1 + uses: JamesIves/github-pages-deploy-action@v4.5.0 with: clean: false branch: gh-pages diff --git a/.github/workflows/pr-commands.yaml b/.github/workflows/pr-commands.yaml index 71f335b..2edd93f 100644 --- a/.github/workflows/pr-commands.yaml +++ b/.github/workflows/pr-commands.yaml @@ -4,7 +4,9 @@ on: issue_comment: types: [created] -name: Commands +name: pr-commands.yaml + +permissions: read-all jobs: document: @@ -13,8 +15,10 @@ jobs: runs-on: ubuntu-latest env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: r-lib/actions/pr-fetch@v2 with: @@ -50,8 +54,10 @@ jobs: runs-on: ubuntu-latest env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: r-lib/actions/pr-fetch@v2 with: diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 2c5bb50..0ab748d 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -4,9 +4,10 @@ on: push: branches: [main, master] pull_request: - branches: [main, master] -name: test-coverage +name: test-coverage.yaml + +permissions: read-all jobs: test-coverage: @@ -15,7 +16,7 @@ jobs: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: r-lib/actions/setup-r@v2 with: @@ -23,28 +24,39 @@ jobs: - uses: r-lib/actions/setup-r-dependencies@v2 with: - extra-packages: any::covr + extra-packages: any::covr, any::xml2 needs: coverage - name: Test coverage run: | - covr::codecov( + cov <- covr::package_coverage( quiet = FALSE, clean = FALSE, - install_path = file.path(Sys.getenv("RUNNER_TEMP"), "package") + install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") ) + print(cov) + covr::to_cobertura(cov) shell: Rscript {0} + - uses: codecov/codecov-action@v5 + with: + # Fail if error if not on PR, or if on PR and token is given + fail_ci_if_error: ${{ github.event_name != 'pull_request' || secrets.CODECOV_TOKEN }} + files: ./cobertura.xml + plugins: noop + disable_search: true + token: ${{ secrets.CODECOV_TOKEN }} + - name: Show testthat output if: always() run: | ## -------------------------------------------------------------------- - find ${{ runner.temp }}/package -name 'testthat.Rout*' -exec cat '{}' \; || true + find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true shell: bash - name: Upload test results if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: coverage-test-failures path: ${{ runner.temp }}/package