From a4f5ac8cce85c43b27923e0646a5f787dc23d26c Mon Sep 17 00:00:00 2001 From: LTLA Date: Tue, 18 Jul 2023 17:06:21 -0700 Subject: [PATCH] Attempt to get the matrix initialization code running. --- js/ScranMatrix.js | 29 ----- js/initializeSparseMatrix.js | 109 ++++-------------- src/initialize_sparse_matrix.cpp | 20 ++-- src/read_hdf5_matrix.cpp | 18 ++- tests/initializeSparseMatrix.test.js | 165 +++++++++++---------------- 5 files changed, 104 insertions(+), 237 deletions(-) diff --git a/js/ScranMatrix.js b/js/ScranMatrix.js index 8f4ff54d..6ce0e161 100644 --- a/js/ScranMatrix.js +++ b/js/ScranMatrix.js @@ -146,33 +146,4 @@ export class ScranMatrix { isSparse() { return this.#matrix.sparse(); } - - // Internal use only, by initialize* functions. - isReorganized() { - return this.#matrix.reorganized(); - } - - // Internal use only, by initialize* functions. - identities({ buffer = null } = {}) { - if (buffer != null) { - this.#matrix.identities(buffer.offset); - return buffer.array(); - } else { - var output; - buffer = utils.createInt32WasmArray(this.#matrix.nrow()); - try { - this.#matrix.identities(buffer.offset); - output = buffer.slice(); - } finally { - buffer.free(); - } - return output; - } - } - - // Internal use only, by initialize* functions. - wipeIdentities() { - this.#matrix.wipe_identities(); - return; - } } diff --git a/js/initializeSparseMatrix.js b/js/initializeSparseMatrix.js index 8ee1c280..cc9a5d75 100644 --- a/js/initializeSparseMatrix.js +++ b/js/initializeSparseMatrix.js @@ -12,23 +12,15 @@ import { ScranMatrix } from "./ScranMatrix.js"; * This is generally expected to contain non-negative integers; otherwise, users should set `forceInteger = false`. * @param {object} [options={}] - Optional parameters. * @param {boolean} [options.forceInteger=true] - Whether to coerce `values` to integers via truncation. - * @param {boolean} [options.layered=true] - Whether to create a layered sparse matrix, which reorders the rows of the loaded matrix for better memory efficiency. + * @param {boolean} [options.layered=true] - Whether to create a layered sparse matrix, see [**tatami_layered**](https://github.com/tatami-inc/tatami_layered) for more details. * Only used if `values` contains an integer type and/or `forceInteger = true`. * Setting to `true` assumes that `values` contains only non-negative integers. * - * @return {object} An object containing: - * - `matrix`, a {@linkplain ScranMatrix} containing the sparse matrix data. - * If layering is enabled, rows are shuffled to enable use of smaller integer types for low-abundance features. - * - `row_ids`, an Int32Array specifying the identity of each row in `matrix`. - * This can be interpreted as the row slicing that was applied to the original matrix to obtain `matrix`. - * If layering is not enabled, this is `null`. - * - * Layering is enabled if the matrix contains integer data (either directly or via `forceInteger = true`) and `layered = true`. + * @return {ScranMatrix} Matrix containing sparse data. */ export function initializeSparseMatrixFromDenseArray(numberOfRows, numberOfColumns, values, { forceInteger = true, layered = true } = {}) { var val_data; var output; - var ids = null; try { val_data = utils.wasmifyArray(values, null); @@ -48,11 +40,6 @@ export function initializeSparseMatrixFromDenseArray(numberOfRows, numberOfColum ScranMatrix ); - if (output.isReorganized()) { - ids = output.identities(); - output.wipeIdentities(); - } - } catch (e) { utils.free(output); throw e; @@ -61,7 +48,7 @@ export function initializeSparseMatrixFromDenseArray(numberOfRows, numberOfColum utils.free(val_data); } - return { "matrix": output, "row_ids": ids }; + return output; } /** @@ -76,28 +63,20 @@ export function initializeSparseMatrixFromDenseArray(numberOfRows, numberOfColum * @param {WasmArray} pointers Pointers specifying the start of each column in `indices`. * This should have length equal to `numberOfColumns + 1`. * @param {object} [options={}] - Optional parameters. - * @param {boolean} [options.byColumn=true] - Whether the input arrays are supplied in the compressed sparse column format. + * @param {boolean} [options.byRow=true] - Whether the input arrays are supplied in the compressed sparse column format. * If `true`, `indices` should contain column indices and `pointers` should specify the start of each row in `indices`. * @param {boolean} [options.forceInteger=true] - Whether to coerce `values` to integers via truncation. - * @param {boolean} [options.layered=true] - Whether to create a layered sparse matrix, which reorders the rows of the loaded matrix for better memory efficiency. + * @param {boolean} [options.layered=true] - Whether to create a layered sparse matrix, see [**tatami_layered**](https://github.com/tatami-inc/tatami_layered) for more details. * Only used if `values` contains an integer type and/or `forceInteger = true`. * Setting to `true` assumes that `values` contains only non-negative integers. * - * @return {object} An object containing: - * - `matrix`, a {@linkplain ScranMatrix} containing the sparse matrix data. - * If layering is enabled, rows are shuffled to enable use of smaller integer types for low-abundance features. - * - `row_ids`, an Int32Array specifying the identity of each row in `matrix`. - * This can be interpreted as the row slicing that was applied to the original matrix to obtain `matrix`. - * If layering is not enabled, this is `null`. - * - * Layering is enabled if the matrix contains integer data (either directly or via `forceInteger = true`) and `layered = true`. + * @return {ScranMatrix} Matrix containing sparse data. */ -export function initializeSparseMatrixFromCompressedVectors(numberOfRows, numberOfColumns, values, indices, pointers, { byColumn = true, forceInteger = true, layered = true } = {}) { +export function initializeSparseMatrixFromCompressedVectors(numberOfRows, numberOfColumns, values, indices, pointers, { byRow = true, forceInteger = true, layered = true } = {}) { var val_data; var ind_data; var indp_data; var output; - var ids = null; try { val_data = utils.wasmifyArray(values, null); @@ -106,7 +85,7 @@ export function initializeSparseMatrixFromCompressedVectors(numberOfRows, number if (val_data.length != ind_data.length) { throw new Error("'values' and 'indices' should have the same length"); } - if (indp_data.length != (byColumn ? numberOfColumns : numberOfRows) + 1) { + if (indp_data.length != (byRow ? numberOfRows : numberOfColumns) + 1) { throw new Error("'pointers' does not have an appropriate length"); } @@ -121,18 +100,13 @@ export function initializeSparseMatrixFromCompressedVectors(numberOfRows, number ind_data.constructor.className.replace("Wasm", ""), indp_data.offset, indp_data.constructor.className.replace("Wasm", ""), - byColumn, + byRow, forceInteger, layered ), ScranMatrix ); - if (output.isReorganized()) { - ids = output.identities(); - output.wipeIdentities(); - } - } catch (e) { utils.free(output); throw e; @@ -143,7 +117,7 @@ export function initializeSparseMatrixFromCompressedVectors(numberOfRows, number utils.free(indp_data); } - return { "matrix": output, "row_ids": ids }; + return output; } /** @@ -157,19 +131,13 @@ export function initializeSparseMatrixFromCompressedVectors(numberOfRows, number * @param {object} [options={}] - Optional parameters. * @param {?boolean} [options.compressed=null] - Whether the buffer is Gzip-compressed. * If `null`, we detect this automatically from the magic number in the header. - * @param {boolean} [options.layered=true] - Whether to create a layered sparse matrix, which reorders the rows of the loaded matrix for better memory efficiency. + * @param {boolean} [options.layered=true] - Whether to create a layered sparse matrix, see [**tatami_layered**](https://github.com/tatami-inc/tatami_layered) for more details. * - * @return {object} An object containing: - * - `matrix`, a {@linkplain ScranMatrix} containing the sparse matrix data. - * If `layered = true`, rows are shuffled to enable use of smaller integer types for low-abundance features. - * - `row_ids`, an Int32Array specifying the identity of each row in `matrix`. - * This can be interpreted as the row slicing that was applied to the original matrix to obtain `matrix`. - * If `layered = false`, this is `null`. + * @return {ScranMatrix} Matrix containing sparse data. */ export function initializeSparseMatrixFromMatrixMarket(x, { compressed = null, layered = true } = {}) { var buf_data; var output; - var ids = null; try { compressed = convert_compressed(compressed); @@ -186,11 +154,6 @@ export function initializeSparseMatrixFromMatrixMarket(x, { compressed = null, l ); } - if (output.isReorganized()) { - ids = output.identities(); - output.wipeIdentities(); - } - } catch(e) { utils.free(output); throw e; @@ -199,7 +162,7 @@ export function initializeSparseMatrixFromMatrixMarket(x, { compressed = null, l utils.free(buf_data); } - return { "matrix": output, "row_ids": ids }; + return output; } function convert_compressed(compressed) { @@ -263,27 +226,17 @@ export function extractMatrixMarketDimensions(x, { compressed = null } = {}) { * For the latter, both H5AD and 10X-style sparse formats are supported. * @param {object} [options={}] - Optional parameters. * @param {boolean} [options.forceInteger=true] - Whether to coerce all elements to integers via truncation. - * @param {boolean} [options.layered=true] - Whether to create a layered sparse matrix, which reorders the rows of the loaded matrix for better memory efficiency. + * @param {boolean} [options.layered=true] - Whether to create a layered sparse matrix, see [**tatami_layered**](https://github.com/tatami-inc/tatami_layered) for more details. * Only used if the relevant HDF5 dataset contains an integer type and/or `forceInteger = true`. * Setting to `true` assumes that the matrix contains only non-negative integers. * @param {?(Array|TypedArray|Int32WasmArray)} [options.subsetRow=null] - Row indices to extract. * All indices must be non-negative integers less than the number of rows in the sparse matrix. * @param {?(Array|TypedArray|Int32WasmArray)} [options.subsetColumn=null] - Column indices to extract. * All indices must be non-negative integers less than the number of columns in the sparse matrix. - * @param {number} [options.cacheSize=100000000] - Size of the cache for loading chunks from HDF5 files. - * Only really relevant when reading dense matrices, where a larger cache size may be necessary for handling large chunk dimensions efficiently. - * - * @return {object} An object containing: - * - `matrix`, a {@linkplain ScranMatrix} containing the sparse matrix data. - * If layering is enabled, rows are shuffled to enable use of smaller integer types for low-abundance features. - * - `row_ids`, an Int32Array specifying the identity of each row in `matrix`. - * This can be interpreted as the row slicing that was applied to the original matrix to obtain `matrix`. - * If layering is not enabled, this is `null`. - * If `subsetRow` was provided, `row_ids` contains indices into `subsetRow`, i.e., the i-th row in `matrix` is the `subsetRow[row_ids[i]]` row in the original matrix. * - * Layering is enabled if the matrix contains integer data (either directly or via `forceInteger = true`) and `layered = true`. + * @return {ScranMatrix} Matrix containing sparse data. */ -export function initializeSparseMatrixFromHDF5(file, name, { forceInteger = true, layered = true, subsetRow = null, subsetColumn = null, cacheSize = 100000000 } = {}) { +export function initializeSparseMatrixFromHDF5(file, name, { forceInteger = true, layered = true, subsetRow = null, subsetColumn = null } = {}) { var ids = null; var output; let wasm_row, wasm_col; @@ -306,15 +259,10 @@ export function initializeSparseMatrixFromHDF5(file, name, { forceInteger = true } output = gc.call( - module => module.read_hdf5_matrix(file, name, forceInteger, layered, use_row_subset, row_offset, row_length, use_col_subset, col_offset, col_length, cacheSize), + module => module.read_hdf5_matrix(file, name, forceInteger, layered, use_row_subset, row_offset, row_length, use_col_subset, col_offset, col_length), ScranMatrix ); - if (output.isReorganized()) { - ids = output.identities(); - output.wipeIdentities(); - } - } catch(e) { utils.free(output); throw e; @@ -323,7 +271,7 @@ export function initializeSparseMatrixFromHDF5(file, name, { forceInteger = true utils.free(wasm_col); } - return { "matrix": output, "row_ids": ids }; + return output; } /** @@ -404,7 +352,7 @@ export function initializeDenseMatrixFromDenseArray(numberOfRows, numberOfColumn } /** - * Initialize a layered sparse matrix from an RDS file. + * Initialize a sparse matrix from an RDS file. * * @param {RdsObject} x - Handle to an object inside an RDS file. * This should be an integer/numeric matrix, `dgCMatrix` or `dgTMatrix` object. @@ -412,18 +360,11 @@ export function initializeDenseMatrixFromDenseArray(numberOfRows, numberOfColumn * @param {boolean} [options.consume=false] - Whether to consume the values in `x` when creating the output sparse matrix. * Setting this to `true` improves memory efficiency at the cost of preventing any further use of `x`. * @param {boolean} [options.forceInteger=true] - Whether to coerce all elements to integers via truncation. - * @param {boolean} [options.layered=true] - Whether to create a layered sparse matrix, which reorders the rows of the loaded matrix for better memory efficiency. + * @param {boolean} [options.layered=true] - Whether to create a layered sparse matrix, see [**tatami_layered**](https://github.com/tatami-inc/tatami_layered) for more details. * Only used if the R matrix is of an integer type and/or `forceInteger = true`. * Setting to `true` assumes that the matrix contains only non-negative integers. * - * @return {object} An object containing: - * - `matrix`, a {@linkplain ScranMatrix} containing the sparse matrix data. - * If layering is enabled, rows are shuffled to enable use of smaller integer types for low-abundance features. - * - `row_ids`, an Int32Array specifying the identity of each row in `matrix`. - * This can be interpreted as the row slicing that was applied to the original matrix to obtain `matrix`. - * If layering is not enabled, this is `null`. - * - * Layering is enabled if the matrix contains integer data (either directly or via `forceInteger = true`) and `layered = true`. + * @return {ScranMatrix} Sparse matrix. */ export function initializeSparseMatrixFromRds(x, { consume = false, forceInteger = true, layered = true } = {}) { var ids = null; @@ -434,16 +375,10 @@ export function initializeSparseMatrixFromRds(x, { consume = false, forceInteger module => module.initialize_sparse_matrix_from_rds(x.object.$$.ptr, forceInteger, layered, consume), ScranMatrix ); - - if (output.isReorganized()) { - ids = output.identities(); - output.wipeIdentities(); - } - } catch(e) { utils.free(output); throw e; } - return { "matrix": output, "row_ids": ids }; + return output; } diff --git a/src/initialize_sparse_matrix.cpp b/src/initialize_sparse_matrix.cpp index b7a60f9b..7e9fbf06 100644 --- a/src/initialize_sparse_matrix.cpp +++ b/src/initialize_sparse_matrix.cpp @@ -70,23 +70,23 @@ NumericMatrix initialize_sparse_matrix_internal(size_t nrows, size_t ncols, size uintptr_t values, const std::string& value_type, uintptr_t indices, const std::string& index_type, uintptr_t indptrs, const std::string& indptr_type, - bool csc, bool layered) + bool by_row, bool layered) { auto val = create_SomeNumericArray(values, nelements, value_type); auto idx = create_SomeNumericArray(indices, nelements, index_type); - if (csc && !layered) { - // Directly creating a CSC matrix. + if (by_row && !layered) { + // Directly creating a CSR matrix. auto ind = create_SomeNumericArray(indptrs, ncols + 1, indptr_type); return copy_into_sparse(nrows, ncols, val, idx, ind); } else { std::shared_ptr > mat; - if (csc) { - auto ind = create_SomeNumericArray(indptrs, ncols + 1, indptr_type); - mat.reset(new tatami::CompressedSparseColumnMatrix(nrows, ncols, val, idx, ind)); - } else { + if (by_row) { auto ind = create_SomeNumericArray(indptrs, nrows + 1, indptr_type); mat.reset(new tatami::CompressedSparseRowMatrix(nrows, ncols, val, idx, ind)); + } else { + auto ind = create_SomeNumericArray(indptrs, ncols + 1, indptr_type); + mat.reset(new tatami::CompressedSparseColumnMatrix(nrows, ncols, val, idx, ind)); } return sparse_from_tatami(mat.get(), layered); } @@ -96,12 +96,12 @@ NumericMatrix initialize_sparse_matrix(size_t nrows, size_t ncols, size_t neleme uintptr_t values, std::string value_type, uintptr_t indices, std::string index_type, uintptr_t indptrs, std::string indptr_type, - bool csc, bool force_integer, bool layered) + bool by_row, bool force_integer, bool layered) { if (force_integer || is_type_integer(value_type)) { - return initialize_sparse_matrix_internal(nrows, ncols, nelements, values, value_type, indices, index_type, indptrs, indptr_type, csc, layered); + return initialize_sparse_matrix_internal(nrows, ncols, nelements, values, value_type, indices, index_type, indptrs, indptr_type, by_row, layered); } else { - return initialize_sparse_matrix_internal(nrows, ncols, nelements, values, value_type, indices, index_type, indptrs, indptr_type, csc, false); + return initialize_sparse_matrix_internal(nrows, ncols, nelements, values, value_type, indices, index_type, indptrs, indptr_type, by_row, false); } } diff --git a/src/read_hdf5_matrix.cpp b/src/read_hdf5_matrix.cpp index 514b8397..95aea793 100644 --- a/src/read_hdf5_matrix.cpp +++ b/src/read_hdf5_matrix.cpp @@ -126,8 +126,7 @@ NumericMatrix read_hdf5_matrix_internal( int row_length, bool col_subset, uintptr_t col_offset, - int col_length, - int cache_size) + int col_length) { if (!is_dense && csc && !layered && !row_subset && !col_subset) { return NumericMatrix(new tatami::CompressedSparseRowMatrix >( @@ -137,14 +136,12 @@ NumericMatrix read_hdf5_matrix_internal( } else { std::shared_ptr > mat; try { - tatami_hdf5::Hdf5Options opt; - opt.maximum_cache_size = cache_size; if (is_dense) { - mat.reset(new tatami_hdf5::Hdf5DenseMatrix(path, name, opt)); + mat.reset(new tatami_hdf5::Hdf5DenseMatrix(path, name)); } else if (csc) { - mat.reset(new tatami_hdf5::Hdf5CompressedSparseMatrix(nr, nc, path, name + "/data", name + "/indices", name + "/indptr", opt)); + mat.reset(new tatami_hdf5::Hdf5CompressedSparseMatrix(nr, nc, path, name + "/data", name + "/indices", name + "/indptr")); } else { - mat.reset(new tatami_hdf5::Hdf5CompressedSparseMatrix(nr, nc, path, name + "/data", name + "/indices", name + "/indptr", opt)); + mat.reset(new tatami_hdf5::Hdf5CompressedSparseMatrix(nr, nc, path, name + "/data", name + "/indices", name + "/indptr")); } } catch (H5::Exception& e) { @@ -179,8 +176,7 @@ NumericMatrix read_hdf5_matrix( int row_length, bool col_subset, uintptr_t col_offset, - int col_length, - int cache_size) + int col_length) { auto details = extract_hdf5_matrix_details_internal(path, name); const auto& is_dense = details.is_dense; @@ -189,9 +185,9 @@ NumericMatrix read_hdf5_matrix( const auto& nc = details.nc; if (force_integer || details.is_integer) { - return read_hdf5_matrix_internal(nr, nc, is_dense, csc, path, name, layered, row_subset, row_offset, row_length, col_subset, col_offset, col_length, cache_size); + return read_hdf5_matrix_internal(nr, nc, is_dense, csc, path, name, layered, row_subset, row_offset, row_length, col_subset, col_offset, col_length); } else { - return read_hdf5_matrix_internal(nr, nc, is_dense, csc, path, name, false, row_subset, row_offset, row_length, col_subset, col_offset, col_length, cache_size); + return read_hdf5_matrix_internal(nr, nc, is_dense, csc, path, name, false, row_subset, row_offset, row_length, col_subset, col_offset, col_length); } } diff --git a/tests/initializeSparseMatrix.test.js b/tests/initializeSparseMatrix.test.js index df008178..226f3e29 100644 --- a/tests/initializeSparseMatrix.test.js +++ b/tests/initializeSparseMatrix.test.js @@ -19,43 +19,39 @@ test("initialization from dense array works correctly", () => { vals.set([1, 5, 0, 0, 7, 0, 0, 10, 4, 2, 0, 0, 0, 5, 8]); var mat = scran.initializeSparseMatrixFromDenseArray(nr, nc, vals); - expect(mat.matrix.numberOfRows()).toBe(nr); - expect(mat.matrix.numberOfColumns()).toBe(nc); - expect(mat.matrix.isSparse()).toBe(true); - expect(mat.row_ids.length).toBe(nr); + expect(mat.numberOfRows()).toBe(nr); + expect(mat.numberOfColumns()).toBe(nc); + expect(mat.isSparse()).toBe(true); // Compare to a non-layered initialization. var mat2 = scran.initializeSparseMatrixFromDenseArray(nr, nc, vals, { layered: false }); - expect(mat2.matrix.numberOfRows()).toBe(nr); - expect(mat2.matrix.numberOfColumns()).toBe(nc); - expect(mat2.matrix.isSparse()).toBe(true); - expect(mat2.row_ids).toBeNull(); + expect(mat2.numberOfRows()).toBe(nr); + expect(mat2.numberOfColumns()).toBe(nc); + expect(mat2.isSparse()).toBe(true); // Compare to a dense initialization. var dense = scran.initializeDenseMatrixFromDenseArray(nr, nc, vals); expect(dense.numberOfRows()).toBe(nr); expect(dense.numberOfColumns()).toBe(nc); - expect(dense.isReorganized()).toBe(false); - expect(dense.isSparse()).toBe(false); // Properly column-major. for (var i = 0; i < nc; i++) { let ref = vals.slice(i * nr, (i + 1) * nr); - expect(compare.equalArrays(mat.matrix.column(i), ref)).toBe(true); - expect(compare.equalArrays(mat2.matrix.column(i), ref)).toBe(true); + expect(compare.equalArrays(mat.column(i), ref)).toBe(true); + expect(compare.equalArrays(mat2.column(i), ref)).toBe(true); expect(compare.equalArrays(dense.column(i), ref)).toBe(true); } // Extraction works with pre-supplied buffers. let row_buf = scran.createFloat64WasmArray(nc); - expect(compare.equalArrays(mat.matrix.row(1, { buffer: row_buf }), [5, 7, 10, 0, 5])).toBe(true); + expect(compare.equalArrays(mat.row(1, { buffer: row_buf }), [5, 7, 10, 0, 5])).toBe(true); let col_buf = scran.createFloat64WasmArray(nr); - expect(compare.equalArrays(mat.matrix.column(2, { buffer: col_buf }), [0, 10, 4])).toBe(true); + expect(compare.equalArrays(mat.column(2, { buffer: col_buf }), [0, 10, 4])).toBe(true); // freeing everything. vals.free(); - mat.matrix.free(); - mat2.matrix.free(); + mat.free(); + mat2.free(); dense.free(); col_buf.free(); row_buf.free(); @@ -77,26 +73,17 @@ test("forced integers from dense array works correctly", () => { let ref = vals.slice(i * nr, (i + 1) * nr); let trunc = ref.map(Math.trunc); - expect(compare.equalArrays(smat1.matrix.column(i), trunc)).toBe(true); - expect(compare.equalArrays(smat2.matrix.column(i), ref)).toBe(true); + expect(compare.equalArrays(smat1.column(i), trunc)).toBe(true); + expect(compare.equalArrays(smat2.column(i), ref)).toBe(true); expect(compare.equalArrays(dmat1.column(i), trunc)).toBe(true); expect(compare.equalArrays(dmat2.column(i), ref)).toBe(true); expect(compare.equalArrays(dmat_default.column(i), ref)).toBe(true); } - // Layering is automatically performed if the values are already integers, - // even if forceInteger = false. - var vals2 = scran.createInt32WasmArray(15); - let arr2 = vals2.array(); - vals.forEach((y, i) => { arr2[i] = Math.trunc(y) }); - var smat3 = scran.initializeSparseMatrixFromDenseArray(nr, nc, vals2, { forceInteger: false }); - expect(smat3.row_ids.length).toBe(nr); - // Cleaning up. vals.free(); - smat1.matrix.free(); - smat2.matrix.free(); - smat3.matrix.free(); + smat1.free(); + smat2.free(); dmat1.free(); dmat2.free(); dmat_default.free(); @@ -110,25 +97,24 @@ test("initialization from compressed values works correctly", () => { var indptrs = scran.createInt32WasmArray(11); indptrs.set([0, 2, 3, 6, 9, 11, 11, 12, 12, 13, 15]); - var mat = scran.initializeSparseMatrixFromCompressedVectors(11, 10, vals, indices, indptrs, { layered: false }); - expect(mat.matrix.numberOfRows()).toBe(11); - expect(mat.matrix.numberOfColumns()).toBe(10); - expect(mat.matrix.isSparse()).toBe(true); - expect(mat.row_ids).toBeNull(); + var mat = scran.initializeSparseMatrixFromCompressedVectors(10, 11, vals, indices, indptrs, { layered: false }); + expect(mat.numberOfRows()).toBe(10); + expect(mat.numberOfColumns()).toBe(11); + expect(mat.isSparse()).toBe(true); - // Extracting the first and last columns to check for correctness. - expect(compare.equalArrays(mat.matrix.column(0), [0, 0, 0, 1, 0, 5, 0, 0, 0, 0, 0])).toBe(true); - expect(compare.equalArrays(mat.matrix.column(9), [0, 0, 0, 0, 0, 0, 5, 0, 0, 8, 0])).toBe(true); + // Extracting the first and last rows to check for correctness. + expect(compare.equalArrays(mat.row(0), [0, 0, 0, 1, 0, 5, 0, 0, 0, 0, 0])).toBe(true); + expect(compare.equalArrays(mat.row(9), [0, 0, 0, 0, 0, 0, 5, 0, 0, 8, 0])).toBe(true); // Doing the same for the rows. - expect(compare.equalArrays(mat.matrix.row(0), [0, 0, 3, 0, 0, 0, 0, 0, 0, 0])).toBe(true); - expect(compare.equalArrays(mat.matrix.row(9), [0, 0, 8, 0, 0, 0, 0, 0, 0, 8])).toBe(true); + expect(compare.equalArrays(mat.column(0), [0, 0, 3, 0, 0, 0, 0, 0, 0, 0])).toBe(true); + expect(compare.equalArrays(mat.column(9), [0, 0, 8, 0, 0, 0, 0, 0, 0, 8])).toBe(true); // Cleaning up. vals.free(); indices.free(); indptrs.free(); - mat.matrix.free(); + mat.free(); }) test("initialization from compressed values works with forced integers", () => { @@ -139,36 +125,28 @@ test("initialization from compressed values works with forced integers", () => { var indptrs = scran.createInt32WasmArray(11); indptrs.set([0, 2, 3, 6, 9, 11, 11, 12, 12, 13, 15]); - var mat1 = scran.initializeSparseMatrixFromCompressedVectors(9, 10, vals, indices, indptrs, { forceInteger: false }); - expect(mat1.row_ids).toBeNull(); // no layering is performed when we're not forcing integers. - expect(compare.equalArrays(mat1.matrix.column(0), [0, 0, 0, 1.2, 0, 5.3, 0, 0, 0])).toBe(true); + var mat1 = scran.initializeSparseMatrixFromCompressedVectors(10, 9, vals, indices, indptrs, { forceInteger: false }); + expect(compare.equalArrays(mat1.matrix.row(0), [0, 0, 0, 1.2, 0, 5.3, 0, 0, 0])).toBe(true); + expect(compare.equalArrays(mat1.matrix.row(10), [0, 0, 0, 0, 0, 0, 5.7, 8.8, 0])).toBe(true); var mat2 = scran.initializeSparseMatrixFromCompressedVectors(9, 10, vals, indices, indptrs, { layered: false }); for (var i = 0; i < 10; i++) { - let col1 = mat1.matrix.column(i); + let col1 = mat1.column(i); let trunc = col1.map(Math.trunc); - let col2 = mat2.matrix.column(i); + let col2 = mat2.column(i); expect(compare.equalArrays(col2, trunc)).toBe(true); } - // Layering is automatically performed if the values are already integers, - // even if forceInteger = false. - var vals2 = scran.createInt32WasmArray(15); - let arr2 = vals2.array(); - vals.forEach((y, i) => { arr2[i] = Math.trunc(y) }); - var mat3 = scran.initializeSparseMatrixFromCompressedVectors(9, 10, vals2, indices, indptrs, { forceInteger: false }); - expect(mat3.row_ids.length).toBe(9); - // Cleaning up. vals.free(); indices.free(); indptrs.free(); - mat1.matrix.free(); - mat2.matrix.free(); - mat3.matrix.free(); + mat1.free(); + mat2.free(); + mat3.free(); }) -test("initialization from compressed values works with reorganization", () => { +test("initialization from compressed values works with layering", () => { var vals = scran.createInt32WasmArray(15); vals.set([1, 5, 2, 1000000, 10, 8, 1000, 10, 4, 2, 1, 1, 3, 5, 8]); // first two rows contain elements beyond the range. var indices = scran.createInt32WasmArray(15); @@ -176,25 +154,20 @@ test("initialization from compressed values works with reorganization", () => { var indptrs = scran.createInt32WasmArray(11); indptrs.set([0, 2, 3, 6, 9, 11, 11, 12, 12, 13, 15]); - var mat = scran.initializeSparseMatrixFromCompressedVectors(11, 10, vals, indices, indptrs); - expect(mat.matrix.numberOfRows()).toBe(11); - expect(mat.matrix.numberOfColumns()).toBe(10); - expect(mat.row_ids.length).toBe(11); - - // Extracting the row identities. - var ids = mat.row_ids; - expect(compare.equalArrays(ids, [2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 0])).toBe(true); + var mat = scran.initializeSparseMatrixFromCompressedVectors(11, 10, vals, indices, indptrs, { byRow: false }); + expect(mat.numberOfRows()).toBe(11); + expect(mat.numberOfColumns()).toBe(10); // Checking the contents. - expect(compare.equalArrays(mat.matrix.row(0), [0, 0, 10, 10, 0, 0, 0, 0, 0, 0])).toBe(true); // basically gets row 2, which has been promoted to the first row. - expect(compare.equalArrays(mat.matrix.row(9), [0, 0, 0, 1000, 0, 0, 0, 0, 0, 0])).toBe(true); // gets row 1, which has been demoted to the second-last row. - expect(compare.equalArrays(mat.matrix.row(10), [0, 0, 1000000, 0, 0, 0, 0, 0, 0, 0])).toBe(true); // gets row 0, which has been demoted to the last row. + expect(compare.equalArrays(mat.row(2), [0, 0, 10, 10, 0, 0, 0, 0, 0, 0])).toBe(true); + expect(compare.equalArrays(mat.row(1), [0, 0, 0, 1000, 0, 0, 0, 0, 0, 0])).toBe(true); + expect(compare.equalArrays(mat.row(0), [0, 0, 1000000, 0, 0, 0, 0, 0, 0, 0])).toBe(true); // Cleaning up. vals.free(); indices.free(); indptrs.free(); - mat.matrix.free(); + mat.free(); }) function convertToMatrixMarket(nr, nc, data, indices, indptrs) { @@ -222,29 +195,20 @@ test("simple initialization from MatrixMarket works correctly", () => { buffer.set(raw_buffer); var mat = scran.initializeSparseMatrixFromMatrixMarket(buffer, { layered: false }); - expect(mat.matrix.numberOfRows()).toBe(nr); - expect(mat.matrix.numberOfColumns()).toBe(nc); - expect(mat.row_ids).toBeNull(); + expect(mat.numberOfRows()).toBe(nr); + expect(mat.numberOfColumns()).toBe(nc); // Also works if we dump it into a file. const path = dir + "/test.mtx"; fs.writeFileSync(path, content); var mat2 = scran.initializeSparseMatrixFromMatrixMarket(path, { layered: false }); - expect(mat2.matrix.numberOfRows()).toBe(nr); - expect(mat2.matrix.numberOfColumns()).toBe(nc); - expect(mat2.row_ids).toBeNull(); + expect(mat2.numberOfRows()).toBe(nr); + expect(mat2.numberOfColumns()).toBe(nc); // Works with layered matrices. var lmat = scran.initializeSparseMatrixFromMatrixMarket(buffer); - let ids = lmat.row_ids; - expect(ids.length).toBe(nr); var lmat2 = scran.initializeSparseMatrixFromMatrixMarket(path); - expect(lmat2.row_ids.length).toBe(nr); - - expect(compare.equalArrays(ids, lmat2.row_ids)).toBe(true); - expect(compare.areIndicesConsecutive(ids)).toBe(false); - expect(compare.areIndicesConsecutive(ids.slice().sort())).toBe(true); // Same results compared to naive iteration. for (var c = 0; c < nc; c++) { @@ -253,15 +217,15 @@ test("simple initialization from MatrixMarket works correctly", () => { for (var j = indptrs[c]; j < indptrs[c+1]; j++) { ref[indices[j]] = data[j]; } - expect(compare.equalArrays(mat.matrix.column(c), ref)).toBe(true); - expect(compare.equalArrays(mat2.matrix.column(c), ref)).toBe(true); + expect(compare.equalArrays(mat.column(c), ref)).toBe(true); + expect(compare.equalArrays(mat2.column(c), ref)).toBe(true); let lref = new Array(nr); ids.forEach((x, i) => { lref[i] = ref[x]; }); - expect(compare.equalArrays(lmat.matrix.column(c), lref)).toBe(true); - expect(compare.equalArrays(lmat2.matrix.column(c), lref)).toBe(true); + expect(compare.equalArrays(lmat.column(c), lref)).toBe(true); + expect(compare.equalArrays(lmat2.column(c), lref)).toBe(true); } // Inspection of dimensions works correctly. @@ -269,10 +233,12 @@ test("simple initialization from MatrixMarket works correctly", () => { expect(deets).toEqual({ rows: nr, columns: nc, lines: data.length }); let deets2 = scran.extractMatrixMarketDimensions(buffer); expect(deets).toEqual(deets2); - + // Cleaning up. - mat.matrix.free(); - mat2.matrix.free(); + mat.free(); + lmat.free(); + mat2.free(); + lmat2.free(); buffer.free(); }) @@ -284,18 +250,17 @@ test("initialization from Gzipped MatrixMarket works correctly with Gzip", () => buffer.set(raw_buffer); var mat = scran.initializeSparseMatrixFromMatrixMarket(buffer); - expect(mat.matrix.numberOfRows()).toBe(11); - expect(mat.matrix.numberOfColumns()).toBe(5); - expect(mat.row_ids.length).toBe(11); + expect(mat.numberOfRows()).toBe(11); + expect(mat.numberOfColumns()).toBe(5); - expect(compare.equalArrays(mat.matrix.row(0), [0, 5, 0, 0, 8])).toBe(true); - expect(compare.equalArrays(mat.matrix.column(2), [0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0])).toBe(true); + expect(compare.equalArrays(mat.row(0), [0, 5, 0, 0, 8])).toBe(true); + expect(compare.equalArrays(mat.column(2), [0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0])).toBe(true); // Just checking that the it's actually compressed. var mat2 = scran.initializeSparseMatrixFromMatrixMarket(buffer, { compressed: true }); - expect(mat2.matrix.numberOfRows()).toBe(11); - expect(mat2.matrix.numberOfColumns()).toBe(5); - expect(compare.equalArrays(mat2.matrix.row(4), mat.matrix.row(4))).toBe(true); + expect(mat2.numberOfRows()).toBe(11); + expect(mat2.numberOfColumns()).toBe(5); + expect(compare.equalArrays(mat2.row(4), mat.row(4))).toBe(true); // Also works if we dump it into a file. const path = dir + "/test.mtx.gz"; @@ -304,10 +269,10 @@ test("initialization from Gzipped MatrixMarket works correctly with Gzip", () => expect(mat3.matrix.numberOfRows()).toBe(11); expect(mat3.matrix.numberOfColumns()).toBe(5); - expect(compare.equalArrays(mat3.matrix.row(5), mat.matrix.row(5))).toBe(true); + expect(compare.equalArrays(mat3.matrix.row(5), mat.row(5))).toBe(true); // Cleaning up. - mat.matrix.free(); - mat2.matrix.free(); + mat.free(); + mat2.free(); buffer.free(); })