From 67359ddc9ec934531a397b207b03e5817cc95345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Art=C3=BCr=20Manukyan?= Date: Thu, 24 Oct 2024 23:11:08 +0200 Subject: [PATCH] Orthogonal selection (#108) --- DESCRIPTION | 2 +- NAMESPACE | 2 + R/array-nested.R | 69 +++-- R/atomic.R | 8 +- R/filters.R | 52 ++++ R/indexing.R | 341 +++++++++++++++++++++ R/int.R | 61 ++++ R/normalize.R | 11 +- R/zarr-array.R | 64 ++-- man/IntArrayDimIndexer.Rd | 109 +++++++ man/OIndex.Rd | 21 ++ man/Order.Rd | 68 ++++ man/OrthogonalIndexer.Rd | 85 +++++ man/int.Rd | 19 ++ man/zb_int.Rd | 16 + pkgdown/_pkgdown.yml | 2 + tests/testthat/test-get.R | 1 - tests/testthat/test-indexing-basic.R | 22 -- tests/testthat/test-indexing-orthogonal.R | 105 +++++++ tests/testthat/test-indexing.R | 58 ++++ tests/testthat/test-int.R | 21 ++ tests/testthat/test-nested-array.R | 26 +- tests/testthat/test-normalize.R | 4 + tests/testthat/test-orthogonal-selection.R | 40 +++ tests/testthat/test-parallel.R | 6 +- tests/testthat/test-s3.R | 28 +- 26 files changed, 1119 insertions(+), 122 deletions(-) create mode 100644 R/filters.R create mode 100644 R/int.R create mode 100644 man/IntArrayDimIndexer.Rd create mode 100644 man/Order.Rd create mode 100644 man/OrthogonalIndexer.Rd create mode 100644 man/int.Rd create mode 100644 man/zb_int.Rd create mode 100644 tests/testthat/test-indexing-orthogonal.R create mode 100644 tests/testthat/test-indexing.R create mode 100644 tests/testthat/test-int.R create mode 100644 tests/testthat/test-orthogonal-selection.R diff --git a/DESCRIPTION b/DESCRIPTION index 3646faa..27de6b4 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -28,7 +28,7 @@ Imports: Encoding: UTF-8 LazyData: true Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.1 +RoxygenNote: 7.3.2 Suggests: testthat, knitr, diff --git a/NAMESPACE b/NAMESPACE index 2e33938..16d9a0f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -23,6 +23,7 @@ export(ZarrGroup) export(ZlibCodec) export(ZstdCodec) export(as_scalar) +export(int) export(is_key_error) export(is_scalar) export(is_slice) @@ -39,4 +40,5 @@ export(zarr_open) export(zarr_open_array) export(zarr_open_group) export(zarr_save_array) +export(zb_int) export(zb_slice) diff --git a/R/array-nested.R b/R/array-nested.R index 02145e5..79ee933 100644 --- a/R/array-nested.R +++ b/R/array-nested.R @@ -1,40 +1,53 @@ #' @keywords internal zero_based_to_one_based <- function(selection, shape) { - if(!all(vapply(selection, is_slice, logical(length = 1)))) - stop("selection must be a list of slices") + # drop this since we could do it for arbitrary indices + # if(!all(vapply(selection, is_slice, logical(length = 1)))) + # stop("selection must be a list of slices") selection_list <- list() for(i in seq_len(length(selection))) { + + # get selection sel <- selection[[i]] - # We assume the selection uses zero-based indexing, - # and internally convert to R-based / 1-based indexing - # before accessing data on the internal self$data. - sel_start <- sel$start + 1 # Add one, since R indexing is zero-based. - sel_stop <- sel$stop # Do not subtract one, since R indexing is inclusive. - sel_step <- sel$step - if(is.na(sel_step)) sel_step <- 1 - # TODO: convert these warnings to errors once we know internals do indexing correctly - if(sel_start < 1) { - sel_start <- 1 - message("IndexError: NestedArray$get() received slice with start index out of bounds - too low") - } - if(sel_start > shape[i]) { - sel_start <- shape[i] - message("IndexError: NestedArray$get() received slice with start index out of bounds - too high") - } - if(sel_stop < 1) { - sel_stop <- 1 - message("IndexError: NestedArray$get() received slice with stop index out of bounds - too low") - } - if(sel_stop > shape[i]) { - sel_stop <- shape[i] - message("IndexError: NestedArray$get() received slice with stop index out of bounds - too high") + + # for slice + if(inherits(sel, "Slice")){ + + # We assume the selection uses zero-based indexing, + # and internally convert to R-based / 1-based indexing + # before accessing data on the internal self$data. + sel_start <- sel$start + 1 # Add one, since R indexing is zero-based. + sel_stop <- sel$stop # Do not subtract one, since R indexing is inclusive. + sel_step <- sel$step + if(is.na(sel_step)) sel_step <- 1 + # TODO: convert these warnings to errors once we know internals do indexing correctly + if(sel_start < 1) { + sel_start <- 1 + message("IndexError: NestedArray$get() received slice with start index out of bounds - too low") + } + if(sel_start > shape[i]) { + sel_start <- shape[i] + message("IndexError: NestedArray$get() received slice with start index out of bounds - too high") + } + if(sel_stop < 1) { + sel_stop <- 1 + message("IndexError: NestedArray$get() received slice with stop index out of bounds - too low") + } + if(sel_stop > shape[i]) { + sel_stop <- shape[i] + message("IndexError: NestedArray$get() received slice with stop index out of bounds - too high") + } + selection_list <- append(selection_list, list(seq(from = sel_start, + to = sel_stop, + by = sel_step))) + } else if(is.numeric(sel)) { + sel <- sel + 1 + selection_list <- append(selection_list, list(sel)) + } else { + stop("Unsupported selection type") } - selection_list <- append(selection_list, list(seq(from = sel_start, - to = sel_stop, - by = sel_step))) } return(selection_list) } diff --git a/R/atomic.R b/R/atomic.R index da721a1..c6fc68a 100644 --- a/R/atomic.R +++ b/R/atomic.R @@ -23,7 +23,7 @@ is_scalar <- function(s) { #' Check if a value is an integer R vector or scalar. #' @keywords internal is_integer <- function(s) { - if(is.atomic(s) && is.numeric(s) && all(s %% 1 == 0)) { + if(is.atomic(s) && is.numeric(s) && all(s %% 1 == 0) && length(s) == 1) { return(TRUE) } return(FALSE) @@ -42,8 +42,10 @@ is_integer_scalar <- function(s) { #' explicitly tagged as a scalar. #' @keywords internal is_integer_vec <- function(s) { - if(!is_scalar(s) && is_integer(s)) { - return(TRUE) + if(!is_scalar(s) && is.vector(s) && !is.list(s) && all(sapply(s,is_integer))) { + if(length(s) > 1){ + return(TRUE) + } } return(FALSE) } diff --git a/R/filters.R b/R/filters.R new file mode 100644 index 0000000..b226980 --- /dev/null +++ b/R/filters.R @@ -0,0 +1,52 @@ +# transforming filters to be passed to ZarrArray$get_orthogonal_selection() +# +# a:b => slice(a,b) +# seq(from, to, by) => slice(start, stop, step) ? for now indices of seq(from, to, by) are passed to get_orthogonal_selection (check below, TODO) +# c(a,b,c) => c(a,b,c), combine elements are passed as indices +# empty dimension => return everything +# +manage_filters <- function(filters) { + lapply(filters, function(x) { + # Proceed based on type of filter + if(typeof(x) == "symbol") { + # When empty dimension, return everything + if(x == "") { + return(NULL) + } else { + stop("Unsupported filter '", as.character(x), "' supplied") + } + } else if(typeof(x) == "double") { + # Return single value for dimension + return(slice(x, x)) + } else if(typeof(x) == "language") { + x <- as.list(x) + # Return a range (supplied via : or seq()) + if(x[[1]] == ":") { + return(slice(x[[2]], x[[3]])) + } else if(x[[1]] == "seq") { + # TODO: do we need slicing for this case ? otherwise implement slice(start, stop, step) + arg_names <- names(x) + from <- ifelse("from" %in% arg_names, x[[which("from" == arg_names)]], x[[2]]) + to <- ifelse("to" %in% arg_names, x[[which("to" == arg_names)]], x[[3]]) + if(length(x) > 3) { + by <- ifelse("by" %in% arg_names, x[[which("by" == arg_names)]], x[[4]]) + return(int(seq(from, to, by))) + } else { + by <- NA + return(int(seq(from, to))) + } + return(int(seq(from, to, by))) + } else if(x[[1]] == "c") { + # return elements of the combine function as indices + check_func <- sapply(x, function(y) { + !is.function(eval(y)) + }) + return(int(floor(unlist(x[check_func])))) + } else { + stop("Unsupported filter '", as.character(x), "' supplied") + } + } else { + stop("Unsupported filter '", as.character(x), "' supplied") + } + }) +} \ No newline at end of file diff --git a/R/indexing.R b/R/indexing.R index cf3072c..d0d530d 100644 --- a/R/indexing.R +++ b/R/indexing.R @@ -1,3 +1,33 @@ +# Reference: https://github.com/zarr-developers/zarr-python/blob/5dd4a0/zarr/indexing.py#L61 + +# Check whether a selection contains only integer array-likes +# This is used to determine whether zarr.array$get_item() calls vectorized (inner) indexing +is_pure_fancy_indexing <- function(selection, ndim = length(selection)) { + + # one dimensions case + if(ndim == 1){ + if(is_integer_vec(selection) | is_integer_list(selection)){ + return(TRUE) + } + } + + # check if there are any slice objects + no_slicing <- (length(selection) == ndim) & !(any(sapply(selection, function(s) inherits(s, "Slice")))) + + # check for integer vectors + all_integer <- all(sapply(selection, function(sel){ + (is_integer(sel) | is_integer_list(sel)) | is_integer_vec(sel) + })) + any_integer <- any(sapply(selection, function(sel){ + # is_integer_list(sel) | is_integer_vec(sel) + is_integer_list(sel) | is_integer_vec(sel) | is_integer(sel) + })) + + # return + return((no_slicing & all_integer) & any_integer) +} + + # Reference: https://github.com/zarr-developers/zarr-python/blob/5dd4a0/zarr/indexing.py#L655 #' The Zarr OIndex class. @@ -18,6 +48,14 @@ OIndex <- R6::R6Class("OIndex", #' @return An `OIndex` instance. initialize = function(array) { self$array <- array + }, + #' @description + #' get method for the Oindex instance + #' @param selection selection + #' @return An `OIndex` instance. + get_item = function(selection) { + # self$array <- array$get + self$array$get_orthogonal_selection(selection) } ) ) @@ -322,3 +360,306 @@ BasicIndexer <- R6::R6Class("BasicIndexer", } ) ) + +# Reference: https://github.com/zarr-developers/zarr-python/blob/5dd4a0e6cdc04c6413e14f57f61d389972ea937c/zarr/indexing.py#L585 + +#' The Zarr OrthogonalIndexer class. +#' @title OrthogonalIndexer Class +#' @docType class +#' @description +#' An indexer class to normalize a selection of an array and provide an iterator +#' of indexes over the dimensions of an array. +#' @param selection selection as with ZarrArray, scalar, string, or Slice. "..." and ":" supported for string +#' @param array ZarrArray object that will be indexed +#' @rdname OrthogonalIndexer +#' @keywords internal +OrthogonalIndexer <- R6::R6Class("OrthogonalIndexer", + inherit = Indexer, + public = list( + #' @field dim_indexers TODO + #' @keywords internal + dim_indexers = NULL, + #' @description + #' Create a new VIndex instance. + #' @return A `VIndex` instance. + initialize = function(selection, array) { + + shape <- array$get_shape() + chunks <- array$get_chunks() + + # Normalize + selection <- normalize_list_selection(selection, shape) + + # Setup per-dimension indexers + dim_indexers <- list() + for(i in seq_along(selection)) { + dim_sel <- selection[[i]] + dim_len <- shape[i] + dim_chunk_len <- chunks[i] + + if(is.null(dim_sel)) { + dim_sel <- zb_slice(NA) + } + + # TODO: for now, normalize_list_selection will get SliceDimIndexer for single integer + if(length(dim_sel) == 1) { + dim_indexer <- IntDimIndexer$new(dim_sel, dim_len, dim_chunk_len) + } else if(is_slice(dim_sel)) { + dim_indexer <- SliceDimIndexer$new(dim_sel, dim_len, dim_chunk_len) + } else if(length(dim_sel) > 1) { + dim_indexer <- IntArrayDimIndexer$new(dim_sel, dim_len, dim_chunk_len) + # TODO: implement BoolArrayDimIndexer and fix if condition here (is_bool_vec) + # } else if(is_bool_vec(dim_sel)) { + # dim_indexer <- BoolArrayDimIndexer$new(dim_sel, dim_len, dim_chunk_len) + } else { + stop('Unsupported selection item for basic indexing, expected integer, slice, vector of integer or boolean') + } + dim_indexers <- append(dim_indexers, dim_indexer) + } + self$shape <- list() + for(d in dim_indexers) { + if(class(d)[[1]] != "IntDimIndexer") { + self$shape <- append(self$shape, d$num_items) + } + } + self$drop_axes <- NA + + self$dim_indexers <- dim_indexers + + }, + #' @description + #' An iterator over the dimensions of an array + #' @return A list of ChunkProjection objects + iter = function() { + + # TODO: use generator/yield features from async package + result <- list() + + # dim_indexers is a list of DimIndexer objects. + # dim_indexer_iterables is a list (one per dimension) + # of lists of IntDimIndexer or SliceDimIndexer objects. + dim_indexer_iterables <- lapply(self$dim_indexers, function(di) di$iter()) + dim_indexer_product <- get_list_product(dim_indexer_iterables) + + + for(row_i in seq_len(length(dim_indexer_product))) { + dim_proj <- dim_indexer_product[[row_i]] + + chunk_coords <- list() + chunk_sel <- list() + out_sel <- list() + + if(!is.list(dim_proj)) { + dim_proj <- list(dim_proj) + } + + for(p in dim_proj) { + chunk_coords <- append(chunk_coords, p$dim_chunk_index) + # chunk_sel <- append(chunk_sel, p$dim_chunk_sel) + chunk_sel <- append(chunk_sel, list(p$dim_chunk_sel)) + if(!is_na(p$dim_out_sel)) { + # out_sel <- append(out_sel, p$dim_out_sel) + out_sel <- append(out_sel, list(p$dim_out_sel)) + } + } + + result <- append(result, ChunkProjection$new( + chunk_coords, + chunk_sel, + out_sel + )) + } + + return(result) + } + ) +) + +# Reference: https://github.com/zarr-developers/zarr-python/blob/5dd4a0e6cdc04c6413e14f57f61d389972ea937c/zarr/indexing.py#L424 + +#' The Order class. +#' @title Order Class +#' @docType class +#' @description +#' TODO +#' @rdname Order +#' @keywords internal +Order <- R6::R6Class("Order", + public = list( + #' @field UNKNOWN UNKNOWN + #' @keywords internal + UNKNOWN = 0, + #' @field INCREASING INCREASING + #' @keywords internal + INCREASING = 1, + #' @field DECREASING DECREASING + #' @keywords internal + DECREASING = 2, + #' @field UNORDERED UNORDERED + #' @keywords internal + UNORDERED = 3, + #' @description + #' checking order of numbers + #' @param a vector of numbers + check = function(a){ + diff_a <- diff(a) + diff_positive <- diff_a >= 0 + n_diff_positive <- sum(diff_positive) + all_increasing <- n_diff_positive == length(diff_positive) + any_increasing <- n_diff_positive > 0 + if(all_increasing){ + return(Order$public_fields$INCREASING) + } else if(any_increasing) { + return(Order$public_fields$UNORDERED) + } else{ + return(Order$public_fields$DECREASING) + } + }) + ) + +# Reference: https://github.com/zarr-developers/zarr-python/blob/5dd4a0e6cdc04c6413e14f57f61d389972ea937c/zarr/indexing.py#L457 + +#' The Zarr IntArrayDimIndexer class. +#' @title IntArrayDimIndexer Class +#' @docType class +#' @description +#' TODO +#' @rdname IntArrayDimIndexer +#' @keywords internal +IntArrayDimIndexer <- R6::R6Class("IntArrayDimIndexer", + inherit = DimIndexer, + public = list( + #' @field dim_len dimension length + #' @keywords internal + dim_len = NULL, + #' @field dim_chunk_len dimension chunk length + #' @keywords internal + dim_chunk_len = NULL, + #' @field num_chunks number of chunks + #' @keywords internal + num_chunks = NULL, + #' @field dim_sel selection on dimension + #' @keywords internal + dim_sel = NULL, + #' @field dim_out_sel TODO + #' @keywords internal + dim_out_sel = NULL, + #' @field order order + #' @keywords internal + order = NULL, + #' @field chunk_nitems number of items per chunk + #' @keywords internal + chunk_nitems = NULL, + #' @field dim_chunk_ixs chunks that should be visited + #' @keywords internal + dim_chunk_ixs = NULL, + #' @field chunk_nitems_cumsum offsets into the output array + #' @keywords internal + chunk_nitems_cumsum = NULL, + #' @description + #' Create a new IntArrayDimIndexer instance. + #' @param dim_sel integer dimention selection + #' @param dim_len integer dimension length + #' @param dim_chunk_len integer dimension chunk length + #' @param sel_order order + #' @return A `IntArrayDimIndexer` instance. + initialize = function(dim_sel, dim_len, dim_chunk_len, sel_order = Order$public_fields$UNKNOWN) { + + # Normalize + dim_sel <- sapply(dim_sel, normalize_integer_selection, dim_len = dim_len) + self$dim_sel <- dim_sel + + # store attributes + self$dim_len <- dim_len + self$dim_chunk_len <- dim_chunk_len + self$num_items <- length(dim_sel) + self$num_chunks <- ceiling(self$dim_len / self$dim_chunk_len) + + # dim_sel_chunk <- ceiling(dim_sel / dim_chunk_len) # pre zb_int() implementation + dim_sel_chunk <- floor(dim_sel / dim_chunk_len) + + # determine order of indices + if(sel_order == Order$public_fields$UNKNOWN) + sel_order <- Order$public_methods$check(dim_sel) + self$order <- sel_order + + if(self$order == Order$public_fields$INCREASING){ + self$dim_sel <- dim_sel + } else if(self$order == Order$public_fields$DECREASING) { + self$dim_sel = rev(dim_sel) + self$dim_out_sel = rev(seq(1,self$num_items)) + # self$dim_out_sel = rev(seq(0,self$num_items-1)) # Python based indexing + } else { + # sort indices to group by chunk + self$dim_out_sel = order(dim_sel_chunk) + self$dim_sel <- dim_sel[self$dim_out_sel] + # self$dim_out_sel <- self$dim_out_sel - 1 # Python based indexing + } + + # precompute number of selected items for each chunk + # self$chunk_nitems <- tabulate(dim_sel_chunk, nbins = self$num_chunks) # pre zb_int() implementation + self$chunk_nitems <- tabulate(dim_sel_chunk + 1, nbins = self$num_chunks) + + # find chunks that we need to visit + self$dim_chunk_ixs = which(self$chunk_nitems != 0) + + # compute offsets into the output array + self$chunk_nitems_cumsum = cumsum(self$chunk_nitems) + + }, + #' @description + #' An iterator over the dimensions of an array + #' @return A list of ChunkProjection objects + iter = function() { + + # Iterate over chunks in range + result <- list() + for(dim_chunk_ix in self$dim_chunk_ixs) { + + # find region in output + # if (dim_chunk_ix == 0) { + if (dim_chunk_ix == 1) { + start <- 0 + } else { + start <- self$chunk_nitems_cumsum[dim_chunk_ix - 1] + } + stop <- self$chunk_nitems_cumsum[dim_chunk_ix] + + # START R-SPECIFIC + if(start == stop) { + stop <- stop + 1 + } + # END R-SPECIFIC + + if (self$order == Order$public_fields$INCREASING) { + dim_out_sel <- seq(start, stop - 1) + } else { + dim_out_sel <- self$dim_out_sel[(start + 1):stop] + # START R-SPECIFIC + dim_out_sel <- dim_out_sel - 1 + # END R-SPECIFIC + } + + # START R-SPECIFIC + dim_chunk_ix <- dim_chunk_ix - 1 + # END R-SPECIFIC + + # find region in chunk + dim_offset <- dim_chunk_ix * self$dim_chunk_len + # dim_chunk_sel <- self$dim_sel[(start + 1):stop] - dim_offset - 1 # pre zb_int implementation() + dim_chunk_sel <- self$dim_sel[(start + 1):stop] - dim_offset + + # # START R-SPECIFIC + # dim_chunk_ix <- dim_chunk_ix - 1 + # # END R-SPECIFIC + + result <- append(result, ChunkDimProjection$new( + dim_chunk_ix, + dim_chunk_sel, + dim_out_sel + )) + } + return(result) + } + ) +) diff --git a/R/int.R b/R/int.R new file mode 100644 index 0000000..6649c97 --- /dev/null +++ b/R/int.R @@ -0,0 +1,61 @@ +# TODO: int() and zb_int() now being used but do we need 'Int' Class ? + +#' Abstract Int object +#' @title Int Class +#' @docType class +#' @description +#' Class representing an indexing of a ZARR store +#' @noRd +#' @keywords internal +Int <- R6::R6Class("Int", + public = list( + #' @field start start index + index = NULL, + #' @description Create a new `Int` object + #' @param index integer index + initialize = function(index) { + self$index <- index + }, + #' @description + #' This method takes a single integer argument `length_param` and computes information about the + #' slice that the slice object would describe if applied to a sequence of `length_param` items. + #' It returns a tuple of three integers; respectively these are the start and stop indices + # and the step or stride length of the slice. Missing or out-of-bounds indices are handled + # in a manner consistent with regular slices. + #' @param length_param integer length parameter for calculation of integer indices + indices = function(length_param) { + + # check length_param + index <- self$index[self$index <= length_param] + + # return + return(c(index, length_param)) + } + ) +) + +#' Convenience function for the internal Int class constructor. +#' @param index The integer index. +#' @param zero_based The index of the dimension. By default, FALSE for R-like behavior. +#' @return A Int instance with the specified parameters. +#' @export +int <- function(index, zero_based = FALSE) { + index_offset <- ifelse(zero_based, 0, -1) + if(!is_na(index) && is.numeric(index)) { + index <- index + index_offset + } + # Assumed to be zero-based + # and stop-inclusive + # return(Int$new( + # index = index + # )) + index +} + +#' Convenience function for the internal Int class constructor +#' with zero-based indexing +#' @param index integer index +#' @export +zb_int <- function(index) { + return(int(index, zero_based = TRUE)) +} \ No newline at end of file diff --git a/R/normalize.R b/R/normalize.R index 91060af..9bfebec 100644 --- a/R/normalize.R +++ b/R/normalize.R @@ -5,14 +5,14 @@ normalize_list_selection <- function(selection, shape, convert_integer_selection for(i in seq_along(selection)) { dim_sel <- selection[[i]] - if(is_integer(dim_sel)) { + if(is_integer(dim_sel)){ if(convert_integer_selection_to_slices) { selection[[i]] <- zb_slice(dim_sel, dim_sel + 1, 1) } else { selection[[i]] <- normalize_integer_selection(dim_sel, shape[i]) } - } else if(is_integer_list(dim_sel)) { # TODO: should this be is_integer_vec? - stop('TypeError(Integer array selections are not supported (yet))') + } else if(is_integer_vec(dim_sel)) { + selection[[i]] <- sapply(dim_sel, normalize_integer_selection, dim_len = shape[i]) } else if(!is.null(dim_sel) && !is.environment(dim_sel) && (is.na(dim_sel) || dim_sel == ":")) { selection[[i]] <- zb_slice(NA, NA, 1) @@ -28,13 +28,16 @@ normalize_integer_selection <- function(dim_sel, dim_len) { # Normalize type to int dim_sel <- as_scalar(dim_sel) + # TODO: do we really need this for R type array indexing ? # handle wraparound if(dim_sel < 0) { dim_sel <- dim_len + dim_sel } - # Handle out of bounds + # TODO: do we need to normalize R indexing or Python indexing here ? + # handle out of bounds if(dim_sel >= dim_len || dim_sel < 0) { + # if(dim_sel > dim_len || dim_sel < 1) { # pre zb_int implementation stop('BoundsCheckError(dim_len)') } diff --git a/R/zarr-array.R b/R/zarr-array.R index 6879488..965500d 100644 --- a/R/zarr-array.R +++ b/R/zarr-array.R @@ -1067,7 +1067,14 @@ ZarrArray <- R6::R6Class("ZarrArray", get_item = function(selection) { # Reference: https://github.com/zarr-developers/zarr-python/blob/5dd4a0/zarr/core.py#L580 # Reference: https://github.com/gzuidhof/zarr.js/blob/master/src/core/index.ts#L266 - return(self$get_basic_selection(selection)) + + if(is_pure_fancy_indexing(selection)){ + # TODO: implement vindex further for vectorized indexing + stop("vectorized indexing is not supported yet") + # return(self$get_vindex()$get_item(selection)) + } else { + return(self$get_basic_selection(selection)) + } }, #' @description #' TODO @@ -1091,7 +1098,14 @@ ZarrArray <- R6::R6Class("ZarrArray", #' @param out TODO #' @param fields TODO get_orthogonal_selection = function(selection = NA, out = NA, fields = NA) { - # TODO + + # Refresh metadata + if(!private$cache_metadata) { + private$load_metadata() + } + + indexer <- OrthogonalIndexer$new(selection, self) + return(private$get_selection(indexer, out = out, fields = fields)) }, #' @description #' TODO @@ -1215,46 +1229,12 @@ ZarrArray <- R6::R6Class("ZarrArray", if(length(filters) != length(private$shape)) { stop("This Zarr object has ", length(private$shape), " dimensions, ", length(filters), " were supplied") } - filters <- lapply(filters, function(x) { - # Proceed based on type of filter - if(typeof(x) == "symbol") { - # When empty dimension, return everything - if(x == "") { - return(NULL) - } else { - stop("Unsupported filter '", as.character(x), "' supplied") - } - - } else if(typeof(x) == "double") { - # Return single value for dimension - return(slice(x, x)) - } else if(typeof(x) == "language") { - x <- as.list(x) - # Return a range (supplied via : or seq()) - if(x[[1]] == ":") { - return(slice(x[[2]], x[[3]])) - } else if(x[[1]] == "seq") { - arg_names <- names(x) - from <- ifelse("from" %in% arg_names, x[[which("from" == arg_names)]], x[[2]]) - to <- ifelse("to" %in% arg_names, x[[which("to" == arg_names)]], x[[3]]) - if(length(x) > 3) { - stop("Slicing with step size is not supported yet") - by <- ifelse("by" %in% arg_names, x[[which("by" == arg_names)]], x[[4]]) - } else { - by <- NA - } - return(slice(from, to, by)) - } else if(x[[1]] == "c") { - stop("Custom vector slicing is not yet supported") - # return(eval(y)) - } else { - stop("Unsupported filter '", as.character(x), "' supplied") - } - } else { - stop("Unsupported filter '", as.character(x), "' supplied") - } - }) - return(self$get_item(filters)) + + # update filters for orthogonal_selection + filters <- manage_filters(filters) + + # return orthogonal selection upon `[.ZarrArray` + return(self$get_orthogonal_selection(filters)) }, #' @description #' Assign values for a selection using bracket notation (for S3 method). diff --git a/man/IntArrayDimIndexer.Rd b/man/IntArrayDimIndexer.Rd new file mode 100644 index 0000000..4189593 --- /dev/null +++ b/man/IntArrayDimIndexer.Rd @@ -0,0 +1,109 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/indexing.R +\docType{class} +\name{IntArrayDimIndexer} +\alias{IntArrayDimIndexer} +\title{IntArrayDimIndexer Class} +\description{ +TODO +} +\details{ +The Zarr IntArrayDimIndexer class. +} +\keyword{internal} +\section{Super class}{ +\code{pizzarr::DimIndexer} -> \code{IntArrayDimIndexer} +} +\section{Public fields}{ +\if{html}{\out{
}} +\describe{ +\item{\code{dim_len}}{dimension length} + +\item{\code{dim_chunk_len}}{dimension chunk length} + +\item{\code{num_chunks}}{number of chunks} + +\item{\code{dim_sel}}{selection on dimension} + +\item{\code{dim_out_sel}}{TODO} + +\item{\code{order}}{order} + +\item{\code{chunk_nitems}}{number of items per chunk} + +\item{\code{dim_chunk_ixs}}{chunks that should be visited} + +\item{\code{chunk_nitems_cumsum}}{offsets into the output array} +} +\if{html}{\out{
}} +} +\section{Methods}{ +\subsection{Public methods}{ +\itemize{ +\item \href{#method-IntArrayDimIndexer-new}{\code{IntArrayDimIndexer$new()}} +\item \href{#method-IntArrayDimIndexer-iter}{\code{IntArrayDimIndexer$iter()}} +\item \href{#method-IntArrayDimIndexer-clone}{\code{IntArrayDimIndexer$clone()}} +} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-IntArrayDimIndexer-new}{}}} +\subsection{Method \code{new()}}{ +Create a new IntArrayDimIndexer instance. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{IntArrayDimIndexer$new( + dim_sel, + dim_len, + dim_chunk_len, + sel_order = Order$public_fields$UNKNOWN +)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{dim_sel}}{integer dimention selection} + +\item{\code{dim_len}}{integer dimension length} + +\item{\code{dim_chunk_len}}{integer dimension chunk length} + +\item{\code{sel_order}}{order} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +A \code{IntArrayDimIndexer} instance. +} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-IntArrayDimIndexer-iter}{}}} +\subsection{Method \code{iter()}}{ +An iterator over the dimensions of an array +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{IntArrayDimIndexer$iter()}\if{html}{\out{
}} +} + +\subsection{Returns}{ +A list of ChunkProjection objects +} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-IntArrayDimIndexer-clone}{}}} +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{IntArrayDimIndexer$clone(deep = FALSE)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} +} diff --git a/man/OIndex.Rd b/man/OIndex.Rd index 24b1843..b582235 100644 --- a/man/OIndex.Rd +++ b/man/OIndex.Rd @@ -22,6 +22,7 @@ The Zarr OIndex class. \subsection{Public methods}{ \itemize{ \item \href{#method-OIndex-new}{\code{OIndex$new()}} +\item \href{#method-OIndex-get_item}{\code{OIndex$get_item()}} \item \href{#method-OIndex-clone}{\code{OIndex$clone()}} } } @@ -46,6 +47,26 @@ An \code{OIndex} instance. } } \if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-OIndex-get_item}{}}} +\subsection{Method \code{get_item()}}{ +get method for the Oindex instance +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{OIndex$get_item(selection)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{selection}}{selection} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +An \code{OIndex} instance. +} +} +\if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-OIndex-clone}{}}} \subsection{Method \code{clone()}}{ diff --git a/man/Order.Rd b/man/Order.Rd new file mode 100644 index 0000000..0f6424d --- /dev/null +++ b/man/Order.Rd @@ -0,0 +1,68 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/indexing.R +\docType{class} +\name{Order} +\alias{Order} +\title{Order Class} +\description{ +TODO +} +\details{ +The Order class. +} +\keyword{internal} +\section{Public fields}{ +\if{html}{\out{
}} +\describe{ +\item{\code{UNKNOWN}}{UNKNOWN} + +\item{\code{INCREASING}}{INCREASING} + +\item{\code{DECREASING}}{DECREASING} + +\item{\code{UNORDERED}}{UNORDERED} +} +\if{html}{\out{
}} +} +\section{Methods}{ +\subsection{Public methods}{ +\itemize{ +\item \href{#method-Order-check}{\code{Order$check()}} +\item \href{#method-Order-clone}{\code{Order$clone()}} +} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Order-check}{}}} +\subsection{Method \code{check()}}{ +checking order of numbers +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{Order$check(a)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{a}}{vector of numbers} +} +\if{html}{\out{
}} +} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Order-clone}{}}} +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{Order$clone(deep = FALSE)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} +} diff --git a/man/OrthogonalIndexer.Rd b/man/OrthogonalIndexer.Rd new file mode 100644 index 0000000..090d911 --- /dev/null +++ b/man/OrthogonalIndexer.Rd @@ -0,0 +1,85 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/indexing.R +\docType{class} +\name{OrthogonalIndexer} +\alias{OrthogonalIndexer} +\title{OrthogonalIndexer Class} +\description{ +An indexer class to normalize a selection of an array and provide an iterator +of indexes over the dimensions of an array. +} +\details{ +The Zarr OrthogonalIndexer class. +} +\keyword{internal} +\section{Super class}{ +\code{pizzarr::Indexer} -> \code{OrthogonalIndexer} +} +\section{Public fields}{ +\if{html}{\out{
}} +\describe{ +\item{\code{dim_indexers}}{TODO} +} +\if{html}{\out{
}} +} +\section{Methods}{ +\subsection{Public methods}{ +\itemize{ +\item \href{#method-OrthogonalIndexer-new}{\code{OrthogonalIndexer$new()}} +\item \href{#method-OrthogonalIndexer-iter}{\code{OrthogonalIndexer$iter()}} +\item \href{#method-OrthogonalIndexer-clone}{\code{OrthogonalIndexer$clone()}} +} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-OrthogonalIndexer-new}{}}} +\subsection{Method \code{new()}}{ +Create a new VIndex instance. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{OrthogonalIndexer$new(selection, array)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{selection}}{selection as with ZarrArray, scalar, string, or Slice. "..." and ":" supported for string} + +\item{\code{array}}{ZarrArray object that will be indexed} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +A \code{VIndex} instance. +} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-OrthogonalIndexer-iter}{}}} +\subsection{Method \code{iter()}}{ +An iterator over the dimensions of an array +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{OrthogonalIndexer$iter()}\if{html}{\out{
}} +} + +\subsection{Returns}{ +A list of ChunkProjection objects +} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-OrthogonalIndexer-clone}{}}} +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{OrthogonalIndexer$clone(deep = FALSE)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} +} diff --git a/man/int.Rd b/man/int.Rd new file mode 100644 index 0000000..e107edb --- /dev/null +++ b/man/int.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/int.R +\name{int} +\alias{int} +\title{Convenience function for the internal Int class constructor.} +\usage{ +int(index, zero_based = FALSE) +} +\arguments{ +\item{index}{The integer index.} + +\item{zero_based}{The index of the dimension. By default, FALSE for R-like behavior.} +} +\value{ +A Int instance with the specified parameters. +} +\description{ +Convenience function for the internal Int class constructor. +} diff --git a/man/zb_int.Rd b/man/zb_int.Rd new file mode 100644 index 0000000..90e7fae --- /dev/null +++ b/man/zb_int.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/int.R +\name{zb_int} +\alias{zb_int} +\title{Convenience function for the internal Int class constructor +with zero-based indexing} +\usage{ +zb_int(index) +} +\arguments{ +\item{index}{integer index} +} +\description{ +Convenience function for the internal Int class constructor +with zero-based indexing +} diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index 1bd8129..1af3d71 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -73,6 +73,8 @@ reference: - slice - zb_slice - is_slice + - int + - zb_int - as_scalar - is_scalar - is_key_error diff --git a/tests/testthat/test-get.R b/tests/testthat/test-get.R index d1d6097..5066d6b 100644 --- a/tests/testthat/test-get.R +++ b/tests/testthat/test-get.R @@ -1,6 +1,5 @@ library(pizzarr) - test_that("get_basic_selection_zd", { # Reference: https://github.com/zarr-developers/zarr-python/blob/5dd4a0e6cdc04c6413e14f57f61d389972ea937c/zarr/tests/test_indexing.py#L70 a <- as_scalar(42) diff --git a/tests/testthat/test-indexing-basic.R b/tests/testthat/test-indexing-basic.R index e63b6b7..2fdf00c 100644 --- a/tests/testthat/test-indexing-basic.R +++ b/tests/testthat/test-indexing-basic.R @@ -117,26 +117,4 @@ test_that("basic indexer for array that spans multiple chunks where shape is not expect_equal(sdi2$dim_chunk_len, 3) expect_equal(sdi2$num_items, 5) expect_equal(sdi2$num_chunks, 4) -}) - -test_that("selection functionality", { - - a <- zarr_volcano()$get_item("volcano") - - sub_a <- a$get_item(list(slice(1, 10), "...")) - - expect_equal(sub_a$shape, c(10, a$get_shape()[2])) - - sub_a <- a$get_item(list(":", slice(1, 10))) - - expect_equal(sub_a$shape, c(a$get_shape()[1], 10)) - - sub_a <- a$get_item(list(1, "...")) - - expect_equal(sub_a$shape, c(1, a$get_shape()[2])) - - sub_a <- a$get_item(list("...", slice(1, 10))) - - expect_equal(sub_a$shape, c(a$get_shape()[1], 10)) - }) \ No newline at end of file diff --git a/tests/testthat/test-indexing-orthogonal.R b/tests/testthat/test-indexing-orthogonal.R new file mode 100644 index 0000000..a606b70 --- /dev/null +++ b/tests/testthat/test-indexing-orthogonal.R @@ -0,0 +1,105 @@ +library(pizzarr) + +MockArray <- R6::R6Class("MockArray", + public = list( + shape = NULL, + chunks = NULL, + initialize = function(shape, chunks) { + self$shape <- shape + self$chunks <- chunks + }, + get_shape = function() { + return(self$shape) + }, + get_chunks = function() { + return(self$chunks) + } + ) +) + +test_that("basic indexer for array that spans multiple chunks", { + z <- MockArray$new(shape = c(10, 10), chunks = c(5, 5)) + + expect_equal(z$get_shape(), c(10, 10)) + expect_equal(z$get_chunks(), c(5, 5)) + + # OrthogonalIndexer supports SliceDimIndexer + bi <- OrthogonalIndexer$new(list(zb_slice(0, 11), zb_slice(0, 11)), z) + expect_equal(as.numeric(bi$shape), c(10, 10)) + expect_equal(length(bi$dim_indexers), 2) + expect_equal("SliceDimIndexer" %in% class(bi$dim_indexers[[1]]), TRUE) + + # combination of IntArrayDimIndexer and SliceDimIndexer + bi <- OrthogonalIndexer$new(list(0:9, zb_slice(0, 11)), z) + expect_equal("IntArrayDimIndexer" %in% class(bi$dim_indexers[[1]]), TRUE) + expect_equal("SliceDimIndexer" %in% class(bi$dim_indexers[[2]]), TRUE) + + # unordered integer vector is a IntArrayDimIndexer + bi <- OrthogonalIndexer$new(list(0:9, c(2,3,5,4,1)), z) + expect_equal("IntArrayDimIndexer" %in% class(bi$dim_indexers[[2]]), TRUE) + + # empty dimension is a slice + bi <- OrthogonalIndexer$new(list(1), z) + expect_equal("SliceDimIndexer" %in% class(bi$dim_indexers[[2]]), TRUE) + + # TODO: add tests for adding BoolArrayDimIndexer +}) + +test_that("basic indexer for array that spans multiple chunks where shape is not a multiple", { + z <- MockArray$new(shape = c(10, 10), chunks = c(3, 3)) + + expect_equal(z$get_shape(), c(10, 10)) + expect_equal(z$get_chunks(), c(3, 3)) + + # OrthogonalIndexer supports SliceDimIndexer + bi <- OrthogonalIndexer$new(list(zb_slice(0, 11), zb_slice(0, 11)), z) + expect_equal(as.numeric(bi$shape), c(10, 10)) + expect_equal(length(bi$dim_indexers), 2) + expect_equal("SliceDimIndexer" %in% class(bi$dim_indexers[[1]]), TRUE) + + # combination of IntArrayDimIndexer and SliceDimIndexer + bi <- OrthogonalIndexer$new(list(0:9, zb_slice(0, 11)), z) + expect_equal("IntArrayDimIndexer" %in% class(bi$dim_indexers[[1]]), TRUE) + expect_equal("SliceDimIndexer" %in% class(bi$dim_indexers[[2]]), TRUE) + + # unordered integer vector is a IntArrayDimIndexer + bi <- OrthogonalIndexer$new(list(0:9, c(2,3,5,4,1)), z) + expect_equal("IntArrayDimIndexer" %in% class(bi$dim_indexers[[2]]), TRUE) + + # empty dimension is a slice + bi <- OrthogonalIndexer$new(list(1), z) + expect_equal("SliceDimIndexer" %in% class(bi$dim_indexers[[2]]), TRUE) + + # TODO: add tests for adding BoolArrayDimIndexer +}) + +test_that("int array dimension indexer", { + + # ordered int array index + # iad <- IntArrayDimIndexer$new(1:10, 10, 3) + iad <- IntArrayDimIndexer$new(0:9, 10, 3) + expect_equal(iad$dim_sel, 0:9) + expect_equal(iad$dim_chunk_ixs, c(1,2,3,4)) + expect_equal(iad$dim_len, 10) + expect_equal(iad$dim_chunk_len, 3) + expect_equal(iad$num_chunks, 4) + expect_equal(iad$chunk_nitems, c(3,3,3,1)) + expect_equal(iad$order, 1) + + # unordered int array index + iad <- IntArrayDimIndexer$new(c(2,3,5,1,2), 6, 3) + expect_equal(iad$dim_sel, c(2,1,2,3,5)) + expect_equal(iad$dim_chunk_ixs, c(1,2)) + expect_equal(iad$dim_len, 6) + expect_equal(iad$dim_chunk_len, 3) + expect_equal(iad$num_chunks, 2) + expect_equal(iad$chunk_nitems, c(3,2)) + expect_equal(iad$order, 3) + + # error for wrong dimension length + expect_error(IntArrayDimIndexer$new(1:10, 6, 3)) + + # missing chunk size + expect_error(IntArrayDimIndexer$new(1:10, 10)) + +}) \ No newline at end of file diff --git a/tests/testthat/test-indexing.R b/tests/testthat/test-indexing.R new file mode 100644 index 0000000..761cb61 --- /dev/null +++ b/tests/testthat/test-indexing.R @@ -0,0 +1,58 @@ + +test_that("selection functionality", { + + a <- zarr_volcano()$get_item("volcano") + + sub_a <- a$get_item(list(slice(1, 10), "...")) + + expect_equal(sub_a$shape, c(10, a$get_shape()[2])) + + sub_a <- a$get_item(list(":", slice(1, 10))) + + expect_equal(sub_a$shape, c(a$get_shape()[1], 10)) + + sub_a <- a$get_item(list(1, "...")) + + expect_equal(sub_a$shape, c(1, a$get_shape()[2])) + + sub_a <- a$get_item(list("...", slice(1, 10))) + + expect_equal(sub_a$shape, c(a$get_shape()[1], 10)) + +}) + +test_that("checking pure fancy indexing", { + + # check integer input, list vector or scalar + expect_equal(is_pure_fancy_indexing(list(1:3, 1, 1:2)), TRUE) + expect_equal(is_pure_fancy_indexing(list(1:5, 1, 1:2)), TRUE) + expect_equal(is_pure_fancy_indexing(list(3, 1, 10)), TRUE) + expect_equal(is_pure_fancy_indexing(c(1,5,2)), TRUE) + expect_equal(is_pure_fancy_indexing(list(3, list(1,2,3), 10)), TRUE) + + # checking slices + expect_equal(is_pure_fancy_indexing(list(1:5, 1, slice(1,2))), FALSE) + expect_equal(is_pure_fancy_indexing(list(1:5, slice(1,3), slice(1,2))), FALSE) + + # checking non-integers + expect_equal(is_pure_fancy_indexing(list(1:5, 1.2, 1:2)), FALSE) + expect_equal(is_pure_fancy_indexing(c(1,5,2.4)), FALSE) + expect_equal(is_pure_fancy_indexing(list(3, list(1,2,3.3), 10)), FALSE) + +}) + +test_that("Order class", { + + # fields + expect_equal(Order$public_fields$UNKNOWN, 0) + expect_equal(Order$public_fields$INCREASING,1) + expect_equal(Order$public_fields$DECREASING,2) + expect_equal(Order$public_fields$UNORDERED,3) + + # methods + expect_equal(Order$public_methods$check(1:5),1) + expect_equal(Order$public_methods$check(seq(2,10,2)),1) + expect_equal(Order$public_methods$check(10:1),2) + expect_equal(Order$public_methods$check(c(1,5,2,6)),3) + expect_error(Order$public_methods$check(c("a", 2))) +}) \ No newline at end of file diff --git a/tests/testthat/test-int.R b/tests/testthat/test-int.R new file mode 100644 index 0000000..35056e6 --- /dev/null +++ b/tests/testthat/test-int.R @@ -0,0 +1,21 @@ +library(pizzarr) + +test_that("int", { + + # is_integer + expect_equal(is_integer(2), TRUE) + expect_equal(is_integer(2.2), FALSE) + expect_equal(is_integer(c(1,2,3)), FALSE) + expect_equal(is_integer(list(1,2,3)), FALSE) + + # is_integer_vec + expect_equal(is_integer_vec(c(2,3)), TRUE) + expect_equal(is_integer_vec(c(2,3.3)), FALSE) + expect_equal(is_integer_vec(2), FALSE) + expect_equal(is_integer_vec(list(2,3)), FALSE) + + # is_integer_list + expect_equal(is_integer_list(list(2,3)), TRUE) + expect_equal(is_integer_list(c(2,3)), FALSE) + expect_equal(is_integer_list(list(2,3.3)), FALSE) +}) \ No newline at end of file diff --git a/tests/testthat/test-nested-array.R b/tests/testthat/test-nested-array.R index 5532745..c07c8a6 100644 --- a/tests/testthat/test-nested-array.R +++ b/tests/testthat/test-nested-array.R @@ -68,34 +68,34 @@ test_that("zero_based_to_one_based", { }) test_that("set array values", { - + d <- zarr_volcano() - + a <- d$get_item("volcano") vals <- a[1:10, 1:20]$as.array() - + new_vals <- vals * 10 - + sub <- a[1:10, 1:20] - + sub$set(list(slice(1,10), slice(1,20)), new_vals) - + expect_equal(sub$as.array(), new_vals) - - # Should this be the case?!? - expect_error(a[1:10, 1:20]$set("...", new_vals), - "selection must be a list of slices") + + # TODO: remove this expect-error once implemented. + expect_error(a[1:10, 1:20]$set("...", new_vals), + "Unsupported selection type") a[1:10, 1:20]$set(list(slice(1,10), slice(1,20)), new_vals) - + # it does not update the original array -- should it? expect_equal(a[1:10, 1:20]$as.array(), vals) - + a$set_item(list(slice(1,10), slice(1,20)), new_vals) - + expect_equal(a[1:10, 1:20]$as.array(), new_vals) }) \ No newline at end of file diff --git a/tests/testthat/test-normalize.R b/tests/testthat/test-normalize.R index f1f76e1..42224c2 100644 --- a/tests/testthat/test-normalize.R +++ b/tests/testthat/test-normalize.R @@ -58,6 +58,10 @@ test_that("normalize_integer_selection with valid input", { test_that("normalize_integer_selection with invalid input", { # Reference: https://github.com/gzuidhof/zarr.js/blob/292804/test/core/indexing.test.ts#L12 + + # TODO: should normalization be performed based R indexing integers or python ? + # Artur: I think it has to be in Python indexing since zero_based_to_one_based() is called later when R arrays are filled from chunk selection + normalize_integer_selection(99, 100) f1 <- function() normalize_integer_selection(100, 100) expect_error(f1()) f2 <- function() normalize_integer_selection(1000, 100) diff --git a/tests/testthat/test-orthogonal-selection.R b/tests/testthat/test-orthogonal-selection.R new file mode 100644 index 0000000..22ccf8f --- /dev/null +++ b/tests/testthat/test-orthogonal-selection.R @@ -0,0 +1,40 @@ +library(pizzarr) + +test_that("orthogonal selection", { + a <- array(data=1:20, dim=c(2, 10)) + # [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] + # [1,] 1 3 5 7 9 11 13 15 17 19 + # [2,] 2 4 6 8 10 12 14 16 18 20 + z <- zarr_create(shape=dim(a), dtype="