import * as utils from "./utils.js";
import { ScranMatrix } from "./ScranMatrix.js";
import * as gc from "./gc.js";
import * as subset from "./subset.js";
function harvest_matrices(x) {
let output = utils.createBigUint64WasmArray(x.length);
let arr = output.array();
for (var i = 0; i < x.length; i++) {
arr[i] = BigInt(x[i].matrix.$$.ptr);
}
return output;
}
/**
* Combine matrices by column, where all matrices contain data for the same features, in the same order.
*
* @param {Array} inputs - Array of one or more {@linkplain ScranMatrix} objects.
* All of these should have the same number and order of features.
*
* @return {ScranMatrix} A {@linkplain ScranMatrix} containing the matrices after combining them by column.
*/
export function cbind(inputs) {
let mat_ptrs;
let output;
try {
mat_ptrs = harvest_matrices(inputs);
output = gc.call(
module => module.cbind(mat_ptrs.length, mat_ptrs.offset),
ScranMatrix
);
} catch (e) {
utils.free(output);
throw e;
} finally {
utils.free(mat_ptrs);
}
return output;
}
/**
* Combine matrices by row, where all matrices contain data for the same cells, in the same order.
*
* @param {Array} inputs - Array of one or more {@linkplain ScranMatrix} objects.
* All of these should have the same number and order of cells.
*
* @return {ScranMatrix} A {@linkplain ScranMatrix} containing the matrices after combining them by row.
*/
export function rbind(inputs) {
let mat_ptrs;
let output;
try {
mat_ptrs = harvest_matrices(inputs);
output = gc.call(
module => module.rbind(mat_ptrs.length, mat_ptrs.offset),
ScranMatrix
);
} catch (e) {
utils.free(output);
throw e;
} finally {
utils.free(mat_ptrs);
}
return output;
}
/**
* Combine matrices by column, after subsetting each matrix to the intersection of common features.
*
* @param {Array} inputs - Array of one or more {@linkplain ScranMatrix} objects.
* @param {Array} names - Array of length equal to `inputs`.
* Each entry should be an Array containing the row names of the corresponding entry of `inputs`.
* Names should correspond to the rows of that entry of `inputs`.
* Any `null` names are ignored.
* If names are duplicated within each array, only the first occurrence is considered in the intersection.
*
* @return {object} An object containing:
* - `matrix`, a {@linkplain ScranMatrix} containing the combined matrices.
* - `indices`, an Int32Array of length equal to the number of rows in `matrix`.
* This contains the index of the row in the first entry of `inputs` corresponding to each row of `matrix`,
* i.e., the gene at the `i`-th row of `matrix` is the same as the gene at the `indices[i]`-th row of `inputs[0]`.
* This is guaranteed to be sorted.
* - `names`, an array of names identifying the rows of `matrix`.
* This is constructed by indexing the first entry of `names` with `indices`.
*/
export function cbindWithNames(x, names) {
// Find the intersection of names, following the order of the first entry.
// We do so to try to improve the chance of getting an ordered subset for efficient extraction.
let ordered_intersection = [];
let remapping = new Map;
if (names.length > 0) {
let intersection = new Set;
for (var n = 0; n < names.length; ++n) {
let current = new Set();
for (const name of names[n]) {
if (name !== null) {
if (n == 0 || intersection.has(name)) {
current.add(name);
}
}
}
intersection = current;
}
for (const name of names[0]) {
if (name !== null && intersection.has(name)) {
let candidate = remapping.get(name);
if (candidate == undefined) { // only consider the first occurence.
remapping.set(name, ordered_intersection.length);
ordered_intersection.push(name);
}
}
}
}
// Actually generating the combined matrix.
let output = {};
let tmp_subset = []
let tmp_sliced = []
try {
for (var n = 0; n < names.length; ++n) {
let survivors = utils.createInt32WasmArray(ordered_intersection.length);
survivors.fill(-1);
let sarray = survivors.array();
names[n].forEach((x, i) => {
let candidate = remapping.get(x);
if (candidate !== undefined) {
if (sarray[candidate] < 0) { // only consider the first occurrence.
sarray[candidate] = i;
}
}
});
tmp_subset.push(survivors);
tmp_sliced.push(subset.subsetRows(x[n], survivors));
}
output.matrix = cbind(tmp_sliced);
output.indices = tmp_subset[0].slice();
output.names = ordered_intersection;
} catch (e) {
utils.free(output.matrix);
throw e;
} finally {
for (const x of tmp_subset) {
utils.free(x);
}
for (const x of tmp_sliced) {
utils.free(x);
}
}
return output;
}