UNPKG

higlass

Version:

HiGlass Hi-C / genomic / large data viewer

276 lines (234 loc) 7.77 kB
import { NUM_PRECOMP_SUBSETS_PER_1D_TTILE } from '../configs/dense-data-extrema-config'; /** * @template {ArrayLike<number>} [T=ArrayLike<number>] */ class DenseDataExtrema1D { /** * This module efficiently computes extrema of arbitrary subsets of a given data array. * The array is subdivided into 'numSubsets' subsets where extrema are precomputed. * These values are used to compute extrema given arbitrary start and end indices via * the getMinNonZeroInSubset and getMaxNonZeroInSubset methods. * @param {T} data */ constructor(data) { /** @type {number} */ this.epsilon = 1e-6; /** @type {T} */ this.data = data; /** @type {number} */ this.tileSize = this.data.length; // might not be a power of 2 /** @type {number} */ this.paddedTileSize = 2 ** Math.ceil(Math.log2(this.tileSize)); // This controls how many subsets are created and precomputed. // Setting numSubsets to 1, is equivalent to no precomputation in // most cases /** @type {number} */ this.numSubsets = Math.min( NUM_PRECOMP_SUBSETS_PER_1D_TTILE, this.paddedTileSize, ); /** @type {number} */ this.subsetSize = this.paddedTileSize / this.numSubsets; this.subsetMinimums = this.computeSubsetNonZeroMinimums(); this.subsetMaximums = this.computeSubsetNonZeroMaximums(); this.minNonZeroInTile = this.getMinNonZeroInTile(); this.maxNonZeroInTile = this.getMaxNonZeroInTile(); } /** * Computes the non-zero minimum in a subset using precomputed values, * if possible. data[end] is not considered. * * @param {[start: number, end: number]} indexBounds * @return {number} non-zero minium of the subset */ getMinNonZeroInSubset(indexBounds) { const start = indexBounds[0]; const end = indexBounds[1]; let curMin = Number.MAX_SAFE_INTEGER; if (start === 0 && end === this.tileSize) { return this.minNonZeroInTile; } const firstSubsetIndex = Math.ceil(start / this.subsetSize); const lastSubsetIndex = Math.floor((end - 1) / this.subsetSize); if (firstSubsetIndex >= lastSubsetIndex) { // No precomputation was found. return this.minNonZero(this.data, start, end); } // Compute from original data if the beginning is not covered by precomputations if (start < firstSubsetIndex * this.subsetSize) { curMin = Math.min( curMin, this.minNonZero(this.data, start, firstSubsetIndex * this.subsetSize), ); } // Use the precomputed values curMin = Math.min( curMin, this.minNonZero(this.subsetMinimums, firstSubsetIndex, lastSubsetIndex), ); // Compute from original data if the end is not covered by precomputations if (end > lastSubsetIndex * this.subsetSize) { curMin = Math.min( curMin, this.minNonZero(this.data, lastSubsetIndex * this.subsetSize, end), ); } return curMin; } /** * Computes the non-zero maximum in a subset using precomputed values, if possible * * @param {[start: number, end: number]} indexBounds * @return {number} non-zero maxium of the subset */ getMaxNonZeroInSubset(indexBounds) { const start = indexBounds[0]; const end = indexBounds[1]; let curMax = Number.MIN_SAFE_INTEGER; if (start === 0 && end === this.tileSize) { return this.maxNonZeroInTile; } const firstSubsetIndex = Math.ceil(start / this.subsetSize); const lastSubsetIndex = Math.floor((end - 1) / this.subsetSize); if (firstSubsetIndex >= lastSubsetIndex) { // No precomputation was found. return this.maxNonZero(this.data, start, end); } // Compute from original data if the beginning is not covered by precomputations if (start < firstSubsetIndex * this.subsetSize) { curMax = Math.max( curMax, this.maxNonZero(this.data, start, firstSubsetIndex * this.subsetSize), ); } // Use the precomputed values curMax = Math.max( curMax, this.maxNonZero(this.subsetMaximums, firstSubsetIndex, lastSubsetIndex), ); // Compute from original data if the end is not covered by precomputations if (end > lastSubsetIndex * this.subsetSize) { curMax = Math.max( curMax, this.maxNonZero(this.data, lastSubsetIndex * this.subsetSize, end), ); } return curMax; } /** * Precomputes non-zero minimums of subsets of the given data vector * * @returns {Array<number>} - Minimums of the regularly subdivided data vector */ computeSubsetNonZeroMinimums() { /** @type {Array<number>} */ const minimums = []; for (let i = 0; i < this.numSubsets; i++) { let curMin = Number.MAX_SAFE_INTEGER; for (let j = 0; j < this.subsetSize; j++) { const x = this.data[i * this.subsetSize + j]; // if the tilesize is not a power of 2 we might access // a value that is not there if (x === undefined) { continue; } if (x < this.epsilon && x > -this.epsilon) { continue; } if (x < curMin) { curMin = x; } } minimums.push(curMin); } return minimums; } /** * Precomputes non-zero maximums of subsets of the given data vector * @return {Array<number>} Maximums of the regularly subdivided data vector */ computeSubsetNonZeroMaximums() { /** @type {Array<number>} */ const maximums = []; for (let i = 0; i < this.numSubsets; i++) { let curMax = Number.MIN_SAFE_INTEGER; for (let j = 0; j < this.subsetSize; j++) { const x = this.data[i * this.subsetSize + j]; // if the tilesize is not a power of 2 we might access // a value that is not there if (x === undefined) { continue; } if (x < this.epsilon && x > -this.epsilon) { continue; } if (x > curMax) { curMax = x; } } maximums.push(curMax); } return maximums; } /** * Computes the non-zero minimum in the entire data array using precomputed values * * @return {number} Non-zeros maximum of the data */ getMinNonZeroInTile() { return Math.min(...this.subsetMinimums); } /** * Computes the non-zero maximum in the entire data array using precomputed values * * @return {number} Non-zeros maximum of the data */ getMaxNonZeroInTile() { return Math.max(...this.subsetMaximums); } /** * Calculate the minimum non-zero value in the data from start * to end. No precomputations are used to compute the min. * * @param {ArrayLike<number>} data * @param {number} start * @param {number} end * @return {number} non-zero min in subset */ minNonZero(data, start, end) { let minNonZeroNum = Number.MAX_SAFE_INTEGER; for (let i = start; i < end; i++) { const x = data[i]; if (x < this.epsilon && x > -this.epsilon) { continue; } if (x < minNonZeroNum) { minNonZeroNum = x; } } return minNonZeroNum; } /** * Calculate the maximum non-zero value in the data from start * to end. No precomputations are used to compute the max. * * @param {ArrayLike<number>} data * @param {number} start * @param {number} end * @return {number} non-zero max in subset */ maxNonZero(data, start, end) { let maxNonZeroNum = Number.MIN_SAFE_INTEGER; for (let i = start; i < end; i++) { const x = data[i]; if (x < this.epsilon && x > -this.epsilon) { continue; } if (x > maxNonZeroNum) { maxNonZeroNum = x; } } return maxNonZeroNum; } } export default DenseDataExtrema1D;