From e63726700028bce0aa29041be77870e64556bd68 Mon Sep 17 00:00:00 2001 From: ptajvar Date: Wed, 17 Jun 2026 14:13:42 +0000 Subject: [PATCH 01/11] allow fewer cell counts and markers in downsample --- R/read_data.R | 98 ++++++++++++++++++++++++++++++++++++------ man/downsample_data.Rd | 9 +++- 2 files changed, 93 insertions(+), 14 deletions(-) diff --git a/R/read_data.R b/R/read_data.R index f9f7d45..15da5a7 100644 --- a/R/read_data.R +++ b/R/read_data.R @@ -277,14 +277,19 @@ merge_data <- #' Downsample data to a specified number of cells and markers #' -#' Downsamples the data to a specified number of cells and markers, ensuring that control markers are always included. +#' Downsamples the data to a specified number of cells and markers, +#' ensuring that control markers are always included. +#' If a sample has fewer than `n_cells`, all available cells are kept for that sample. +#' If fewer non-control markers are available than requested, all available non-control markers are kept. +#' If `length(control_markers) > n_markers`, all control markers are kept. +#' In these cases, the function warns and proceeds instead of failing. #' #' @param pg_data A Seurat object containing the data to be downsampled. #' @param control_markers A character vector of control markers to always include in the downsampled data. #' @param n_cells An integer specifying the number of cells to keep in each sample. #' @param n_markers An integer specifying the total number of markers to keep in the downsampled data. #' -#' @return A downsampled Seurat object with the specified number of cells and markers. +#' @return A downsampled Seurat object with selected cells and markers. #' #' @export #' @@ -295,24 +300,93 @@ downsample_data <- n_markers = 20) { set.seed(37) - keep_cells <- + if (is.null(control_markers)) { + control_markers <- character(0) + } + + control_markers <- unique(control_markers) + + cell_data <- FetchData(pg_data, "sample_alias") %>% - as_tibble(rownames = "cell_id") %>% + as_tibble(rownames = "cell_id") + + available_cells <- + cell_data %>% + count(sample_alias, name = "n_available") + + low_cell_samples <- + available_cells %>% + filter(n_available < n_cells) + + if (nrow(low_cell_samples) > 0) { + warning( + paste0( + "Requested ", n_cells, + " cells per sample, but some samples have fewer cells. ", + "Using all available cells for those samples." + ), + call. = FALSE + ) + } + + keep_cells <- + cell_data %>% group_by(sample_alias) %>% - slice_sample(n = n_cells) %>% + group_modify(~ slice_sample(.x, n = min(nrow(.x), n_cells))) %>% + ungroup() %>% pull(cell_id) - pixelatorR:::assert_x_in_y(control_markers, rownames(pg_data)) + if (length(control_markers) > 0) { + pixelatorR:::assert_x_in_y(control_markers, rownames(pg_data)) + } - keep_markers <- + non_control_markers <- rownames(pg_data) %>% { .[!. %in% control_markers] - } %>% - { - .[sample(seq_along(.), size = n_markers - length(control_markers), replace = FALSE)] - } %>% - union(control_markers) + } + + requested_non_control <- n_markers - length(control_markers) + + if (requested_non_control <= 0) { + if (length(control_markers) > n_markers) { + warning( + paste0( + "Requested n_markers = ", n_markers, + " but control_markers has length ", length(control_markers), + ". Keeping all control markers." + ), + call. = FALSE + ) + } + sampled_non_control <- character(0) + } else { + n_non_control <- min(length(non_control_markers), requested_non_control) + + if (n_non_control < requested_non_control) { + warning( + paste0( + "Requested ", requested_non_control, + " non-control markers, but only ", n_non_control, + " are available. Using all available non-control markers." + ), + call. = FALSE + ) + } + + sampled_non_control <- + if (n_non_control > 0) { + non_control_markers[ + sample(seq_along(non_control_markers), size = n_non_control, replace = FALSE) + ] + } else { + character(0) + } + } + + keep_markers <- + c(control_markers, sampled_non_control) %>% + unique() pg_data <- pg_data[keep_markers, keep_cells] diff --git a/man/downsample_data.Rd b/man/downsample_data.Rd index 842dfaf..cfcc934 100644 --- a/man/downsample_data.Rd +++ b/man/downsample_data.Rd @@ -16,8 +16,13 @@ downsample_data(pg_data, control_markers = NULL, n_cells = 50, n_markers = 20) \item{n_markers}{An integer specifying the total number of markers to keep in the downsampled data.} } \value{ -A downsampled Seurat object with the specified number of cells and markers. +A downsampled Seurat object with selected cells and markers. } \description{ -Downsamples the data to a specified number of cells and markers, ensuring that control markers are always included. +Downsamples the data to a specified number of cells and markers, +ensuring that control markers are always included. +If a sample has fewer than \code{n_cells}, all available cells are kept for that sample. +If fewer non-control markers are available than requested, all available non-control markers are kept. +If \code{length(control_markers) > n_markers}, all control markers are kept. +In these cases, the function warns and proceeds instead of failing. } From d582e3a98cc503b708d48e32edc50c8d79428654 Mon Sep 17 00:00:00 2001 From: ptajvar Date: Wed, 17 Jun 2026 14:37:49 +0000 Subject: [PATCH 02/11] disable downsampling markers when a component's total count becomes zero. --- R/read_data.R | 20 ++++++++++++++++++++ man/downsample_data.Rd | 2 ++ 2 files changed, 22 insertions(+) diff --git a/R/read_data.R b/R/read_data.R index 15da5a7..29d6d8c 100644 --- a/R/read_data.R +++ b/R/read_data.R @@ -282,6 +282,8 @@ merge_data <- #' If a sample has fewer than `n_cells`, all available cells are kept for that sample. #' If fewer non-control markers are available than requested, all available non-control markers are kept. #' If `length(control_markers) > n_markers`, all control markers are kept. +#' If marker downsampling makes any selected component have zero total counts, +#' marker downsampling is disabled and all markers are kept. #' In these cases, the function warns and proceeds instead of failing. #' #' @param pg_data A Seurat object containing the data to be downsampled. @@ -388,6 +390,24 @@ downsample_data <- c(control_markers, sampled_non_control) %>% unique() + candidate_data <- + pg_data[keep_markers, keep_cells] + + marker_counts <- + GetAssayData(candidate_data, slot = "counts") + + if (any(Matrix::colSums(marker_counts) == 0)) { + warning( + paste0( + "Marker downsampling produced components with zero total counts. ", + "Disabling marker downsampling and keeping all markers." + ), + call. = FALSE + ) + + keep_markers <- rownames(pg_data) + } + pg_data <- pg_data[keep_markers, keep_cells] diff --git a/man/downsample_data.Rd b/man/downsample_data.Rd index cfcc934..1f85d78 100644 --- a/man/downsample_data.Rd +++ b/man/downsample_data.Rd @@ -24,5 +24,7 @@ ensuring that control markers are always included. If a sample has fewer than \code{n_cells}, all available cells are kept for that sample. If fewer non-control markers are available than requested, all available non-control markers are kept. If \code{length(control_markers) > n_markers}, all control markers are kept. +If marker downsampling makes any selected component have zero total counts, +marker downsampling is disabled and all markers are kept. In these cases, the function warns and proceeds instead of failing. } From 8dbc57693f1467ed567f895dc448b1d274b5af39 Mon Sep 17 00:00:00 2001 From: Pouria Tajvar <31826342+ptajvar@users.noreply.github.com> Date: Wed, 17 Jun 2026 16:56:05 +0200 Subject: [PATCH 03/11] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- R/read_data.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/read_data.R b/R/read_data.R index 29d6d8c..e7d1528 100644 --- a/R/read_data.R +++ b/R/read_data.R @@ -282,7 +282,7 @@ merge_data <- #' If a sample has fewer than `n_cells`, all available cells are kept for that sample. #' If fewer non-control markers are available than requested, all available non-control markers are kept. #' If `length(control_markers) > n_markers`, all control markers are kept. -#' If marker downsampling makes any selected component have zero total counts, +#' If marker downsampling makes any selected cell have zero total counts, #' marker downsampling is disabled and all markers are kept. #' In these cases, the function warns and proceeds instead of failing. #' @@ -399,7 +399,7 @@ downsample_data <- if (any(Matrix::colSums(marker_counts) == 0)) { warning( paste0( - "Marker downsampling produced components with zero total counts. ", + "Marker downsampling produced cells with zero total counts. ", "Disabling marker downsampling and keeping all markers." ), call. = FALSE From c8bd0e8886b7fdfdd059965895bdaa473559e333 Mon Sep 17 00:00:00 2001 From: ptajvar Date: Wed, 17 Jun 2026 15:12:58 +0000 Subject: [PATCH 04/11] adding tests --- tests/testthat/test_read_data.R | 60 +++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/tests/testthat/test_read_data.R b/tests/testthat/test_read_data.R index 3359098..813f8d8 100644 --- a/tests/testthat/test_read_data.R +++ b/tests/testthat/test_read_data.R @@ -209,6 +209,66 @@ test_that("File reading works as expected", { expect_s4_class(seur_down, "Seurat") expect_equal(dim(seur_down), c(5, 6)) + # Edge case 1: fewer cells than requested in at least one sample + expect_warning( + seur_down_low_cells <- downsample_data( + seur_comb, + control_markers = c("mIgG1", "mIgG2a", "mIgG2b"), + n_cells = 1000, + n_markers = 5 + ), + "fewer cells" + ) + expect_equal(ncol(seur_down_low_cells), ncol(seur_comb)) + + # Edge case 2: fewer non-control markers available than requested + all_markers <- rownames(seur_comb) + expect_gt(length(all_markers), 1) + control_set <- all_markers[-length(all_markers)] + expect_warning( + seur_down_low_markers <- downsample_data( + seur_comb, + control_markers = control_set, + n_cells = 3, + n_markers = nrow(seur_comb) + 5 + ), + "non-control markers" + ) + expect_equal(nrow(seur_down_low_markers), nrow(seur_comb)) + + # Edge case 3: marker downsampling would produce zero-total-count components, + # so all markers should be kept. + sparse_counts <- + matrix( + c( + 1, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 2 + ), + nrow = 3, + byrow = TRUE, + dimnames = list(c("m1", "m2", "m3"), c("c1", "c2", "c3", "c4")) + ) + sparse_seur <- SeuratObject::CreateSeuratObject(counts = sparse_counts) + sparse_seur <- SeuratObject::AddMetaData( + sparse_seur, + metadata = data.frame( + sample_alias = c("S1", "S1", "S2", "S2"), + row.names = colnames(sparse_seur) + ) + ) + + expect_warning( + sparse_down <- downsample_data( + sparse_seur, + control_markers = c("m1", "m2"), + n_cells = 2, + n_markers = 2 + ), + "zero total counts" + ) + expect_equal(nrow(sparse_down), nrow(sparse_seur)) + # Sample sheet reading expect_no_error(sample_sheet <- read_samplesheet(test_samplesheet())) expect_equal( From 8639d5aec8f8a054908926ccd28002a788f80f07 Mon Sep 17 00:00:00 2001 From: ptajvar Date: Wed, 17 Jun 2026 15:13:15 +0000 Subject: [PATCH 05/11] fixing documentation --- man/downsample_data.Rd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/downsample_data.Rd b/man/downsample_data.Rd index 1f85d78..fc69ab2 100644 --- a/man/downsample_data.Rd +++ b/man/downsample_data.Rd @@ -24,7 +24,7 @@ ensuring that control markers are always included. If a sample has fewer than \code{n_cells}, all available cells are kept for that sample. If fewer non-control markers are available than requested, all available non-control markers are kept. If \code{length(control_markers) > n_markers}, all control markers are kept. -If marker downsampling makes any selected component have zero total counts, +If marker downsampling makes any selected cell have zero total counts, marker downsampling is disabled and all markers are kept. In these cases, the function warns and proceeds instead of failing. } From 9ef620cb0e64a3da576a633711570f2e18834f8e Mon Sep 17 00:00:00 2001 From: ptajvar Date: Wed, 17 Jun 2026 15:13:51 +0000 Subject: [PATCH 06/11] use layer instead of deprecated slot in GetAssayData --- R/read_data.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/read_data.R b/R/read_data.R index e7d1528..c56f8d2 100644 --- a/R/read_data.R +++ b/R/read_data.R @@ -334,7 +334,7 @@ downsample_data <- keep_cells <- cell_data %>% group_by(sample_alias) %>% - group_modify(~ slice_sample(.x, n = min(nrow(.x), n_cells))) %>% + slice_sample(n = min(dplyr::n(), n_cells)) %>% ungroup() %>% pull(cell_id) @@ -394,7 +394,7 @@ downsample_data <- pg_data[keep_markers, keep_cells] marker_counts <- - GetAssayData(candidate_data, slot = "counts") + GetAssayData(candidate_data, layer = "counts") if (any(Matrix::colSums(marker_counts) == 0)) { warning( From 440d8db8cea3615bba71317ba85d03578db53682 Mon Sep 17 00:00:00 2001 From: ptajvar Date: Wed, 17 Jun 2026 15:22:14 +0000 Subject: [PATCH 07/11] applying comments from the PR review. --- R/read_data.R | 83 ++++++++++++++++++++++----------------------------- 1 file changed, 36 insertions(+), 47 deletions(-) diff --git a/R/read_data.R b/R/read_data.R index c56f8d2..627f14f 100644 --- a/R/read_data.R +++ b/R/read_data.R @@ -321,13 +321,11 @@ downsample_data <- filter(n_available < n_cells) if (nrow(low_cell_samples) > 0) { - warning( - paste0( - "Requested ", n_cells, - " cells per sample, but some samples have fewer cells. ", - "Using all available cells for those samples." - ), - call. = FALSE + cli::cli_warn( + c( + "Requested {.val {n_cells}} cells per sample, but some samples have fewer cells.", + "i" = "Using all available cells for those samples." + ) ) } @@ -348,44 +346,36 @@ downsample_data <- .[!. %in% control_markers] } - requested_non_control <- n_markers - length(control_markers) - - if (requested_non_control <= 0) { - if (length(control_markers) > n_markers) { - warning( - paste0( - "Requested n_markers = ", n_markers, - " but control_markers has length ", length(control_markers), - ". Keeping all control markers." - ), - call. = FALSE - ) - } - sampled_non_control <- character(0) - } else { - n_non_control <- min(length(non_control_markers), requested_non_control) - - if (n_non_control < requested_non_control) { - warning( - paste0( - "Requested ", requested_non_control, - " non-control markers, but only ", n_non_control, - " are available. Using all available non-control markers." - ), - call. = FALSE + target_non_control <- max(n_markers - length(control_markers), 0) + available_non_control <- length(non_control_markers) + + if (length(control_markers) > n_markers) { + cli::cli_warn( + c( + "Requested {.val {n_markers}} total markers, but {.val {length(control_markers)}} control markers were provided.", + "i" = "Keeping all control markers." ) - } + ) + } - sampled_non_control <- - if (n_non_control > 0) { - non_control_markers[ - sample(seq_along(non_control_markers), size = n_non_control, replace = FALSE) - ] - } else { - character(0) - } + if (target_non_control > available_non_control) { + cli::cli_warn( + c( + "Requested {.val {target_non_control}} non-control markers, but only {.val {available_non_control}} are available.", + "i" = "Using all available non-control markers." + ) + ) } + n_non_control <- min(target_non_control, available_non_control) + + sampled_non_control <- + if (n_non_control > 0) { + sample(non_control_markers, size = n_non_control, replace = FALSE) + } else { + character(0) + } + keep_markers <- c(control_markers, sampled_non_control) %>% unique() @@ -397,12 +387,11 @@ downsample_data <- GetAssayData(candidate_data, layer = "counts") if (any(Matrix::colSums(marker_counts) == 0)) { - warning( - paste0( - "Marker downsampling produced cells with zero total counts. ", - "Disabling marker downsampling and keeping all markers." - ), - call. = FALSE + cli::cli_warn( + c( + "Marker downsampling produced cells with zero total counts.", + "i" = "Disabling marker downsampling and keeping all markers." + ) ) keep_markers <- rownames(pg_data) From 40f1a4aefe5588c444850b8fa9bad1f5ebf520a2 Mon Sep 17 00:00:00 2001 From: ptajvar Date: Wed, 17 Jun 2026 15:25:15 +0000 Subject: [PATCH 08/11] fix dplyr import --- R/read_data.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/read_data.R b/R/read_data.R index 627f14f..2c338fa 100644 --- a/R/read_data.R +++ b/R/read_data.R @@ -332,7 +332,7 @@ downsample_data <- keep_cells <- cell_data %>% group_by(sample_alias) %>% - slice_sample(n = min(dplyr::n(), n_cells)) %>% + dplyr::group_modify(~ slice_sample(.x, n = min(nrow(.x), n_cells))) %>% ungroup() %>% pull(cell_id) From 9416d0ace6a6f33584d9e64682bfbe966fde0016 Mon Sep 17 00:00:00 2001 From: ptajvar Date: Wed, 17 Jun 2026 15:30:57 +0000 Subject: [PATCH 09/11] simplify and fix linting. --- R/read_data.R | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/R/read_data.R b/R/read_data.R index 2c338fa..de130e6 100644 --- a/R/read_data.R +++ b/R/read_data.R @@ -349,19 +349,12 @@ downsample_data <- target_non_control <- max(n_markers - length(control_markers), 0) available_non_control <- length(non_control_markers) - if (length(control_markers) > n_markers) { - cli::cli_warn( - c( - "Requested {.val {n_markers}} total markers, but {.val {length(control_markers)}} control markers were provided.", - "i" = "Keeping all control markers." - ) - ) - } if (target_non_control > available_non_control) { cli::cli_warn( c( - "Requested {.val {target_non_control}} non-control markers, but only {.val {available_non_control}} are available.", + "Requested {.val {target_non_control}} non-control markers,", + "but only {.val {available_non_control}} are available.", "i" = "Using all available non-control markers." ) ) From 6a974cbf148fa486710e8806ed6c035a54cfa76b Mon Sep 17 00:00:00 2001 From: maxkarlsson Date: Thu, 18 Jun 2026 09:43:56 +0200 Subject: [PATCH 10/11] fix: final touches --- R/read_data.R | 41 ++++++++++----------------------- man/downsample_data.Rd | 4 +--- tests/testthat/test_read_data.R | 33 -------------------------- 3 files changed, 13 insertions(+), 65 deletions(-) diff --git a/R/read_data.R b/R/read_data.R index de130e6..9ff1fe1 100644 --- a/R/read_data.R +++ b/R/read_data.R @@ -281,9 +281,7 @@ merge_data <- #' ensuring that control markers are always included. #' If a sample has fewer than `n_cells`, all available cells are kept for that sample. #' If fewer non-control markers are available than requested, all available non-control markers are kept. -#' If `length(control_markers) > n_markers`, all control markers are kept. -#' If marker downsampling makes any selected cell have zero total counts, -#' marker downsampling is disabled and all markers are kept. +#' `control_markers` are always kept. #' In these cases, the function warns and proceeds instead of failing. #' #' @param pg_data A Seurat object containing the data to be downsampled. @@ -302,12 +300,18 @@ downsample_data <- n_markers = 20) { set.seed(37) - if (is.null(control_markers)) { - control_markers <- character(0) + pixelatorR:::assert_class(pg_data, "Seurat") + pixelatorR:::assert_vector(control_markers, "character", allow_null = TRUE) + pixelatorR:::assert_single_value(n_cells, "integer") + pixelatorR:::assert_single_value(n_markers, "integer") + + if (length(control_markers) > 0) { + pixelatorR:::assert_x_in_y(control_markers, rownames(pg_data)) } control_markers <- unique(control_markers) + # Downsample cells cell_data <- FetchData(pg_data, "sample_alias") %>% as_tibble(rownames = "cell_id") @@ -336,15 +340,11 @@ downsample_data <- ungroup() %>% pull(cell_id) - if (length(control_markers) > 0) { - pixelatorR:::assert_x_in_y(control_markers, rownames(pg_data)) - } + # Downsample markers + markers <- rownames(pg_data) non_control_markers <- - rownames(pg_data) %>% - { - .[!. %in% control_markers] - } + setdiff(markers, control_markers) target_non_control <- max(n_markers - length(control_markers), 0) available_non_control <- length(non_control_markers) @@ -373,23 +373,6 @@ downsample_data <- c(control_markers, sampled_non_control) %>% unique() - candidate_data <- - pg_data[keep_markers, keep_cells] - - marker_counts <- - GetAssayData(candidate_data, layer = "counts") - - if (any(Matrix::colSums(marker_counts) == 0)) { - cli::cli_warn( - c( - "Marker downsampling produced cells with zero total counts.", - "i" = "Disabling marker downsampling and keeping all markers." - ) - ) - - keep_markers <- rownames(pg_data) - } - pg_data <- pg_data[keep_markers, keep_cells] diff --git a/man/downsample_data.Rd b/man/downsample_data.Rd index fc69ab2..99c8b8f 100644 --- a/man/downsample_data.Rd +++ b/man/downsample_data.Rd @@ -23,8 +23,6 @@ Downsamples the data to a specified number of cells and markers, ensuring that control markers are always included. If a sample has fewer than \code{n_cells}, all available cells are kept for that sample. If fewer non-control markers are available than requested, all available non-control markers are kept. -If \code{length(control_markers) > n_markers}, all control markers are kept. -If marker downsampling makes any selected cell have zero total counts, -marker downsampling is disabled and all markers are kept. +\code{control_markers} are always kept. In these cases, the function warns and proceeds instead of failing. } diff --git a/tests/testthat/test_read_data.R b/tests/testthat/test_read_data.R index 813f8d8..5ae3816 100644 --- a/tests/testthat/test_read_data.R +++ b/tests/testthat/test_read_data.R @@ -236,39 +236,6 @@ test_that("File reading works as expected", { ) expect_equal(nrow(seur_down_low_markers), nrow(seur_comb)) - # Edge case 3: marker downsampling would produce zero-total-count components, - # so all markers should be kept. - sparse_counts <- - matrix( - c( - 1, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 2 - ), - nrow = 3, - byrow = TRUE, - dimnames = list(c("m1", "m2", "m3"), c("c1", "c2", "c3", "c4")) - ) - sparse_seur <- SeuratObject::CreateSeuratObject(counts = sparse_counts) - sparse_seur <- SeuratObject::AddMetaData( - sparse_seur, - metadata = data.frame( - sample_alias = c("S1", "S1", "S2", "S2"), - row.names = colnames(sparse_seur) - ) - ) - - expect_warning( - sparse_down <- downsample_data( - sparse_seur, - control_markers = c("m1", "m2"), - n_cells = 2, - n_markers = 2 - ), - "zero total counts" - ) - expect_equal(nrow(sparse_down), nrow(sparse_seur)) - # Sample sheet reading expect_no_error(sample_sheet <- read_samplesheet(test_samplesheet())) expect_equal( From 92e8ccdc13af30e600208d39ed4cc3ea3f925e1d Mon Sep 17 00:00:00 2001 From: maxkarlsson Date: Thu, 18 Jun 2026 14:50:21 +0200 Subject: [PATCH 11/11] chore: changelog --- CHANGELOG.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdfba55..d16a87f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Fixed + + - `downsample_data` will now default to the number of markers or cells in the data if `n_markers` or `n_cells` are higher than available in the data. + ## [0.11.2] 2026-06-15 -### Changes +### Changed - `component_hashing` now returns a sample confidence plot with either hash purity or hash enrichment factor, depending on which metric is present in the data. - ### Removed +### Removed - `harmony` has been removed, and `do_harmonize` is no longer an option.