UNPKG

molstar

Version:

A comprehensive macromolecular library.

391 lines (390 loc) 14.1 kB
/** * Copyright (c) 2017-2024 mol* contributors, licensed under MIT, See LICENSE file for more info. * * Adapted from CIFTools.js (https://github.com/dsehnal/CIFTools.js; MIT) and MMTF (https://github.com/rcsb/mmtf-javascript/; MIT) * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { ChunkedArray } from '../../../mol-data/util'; import { Encoding } from './encoding'; import { classifyIntArray } from './classifier'; export class ArrayEncoderImpl { and(f) { return new ArrayEncoderImpl(this.providers.concat([f])); } encode(data) { const encoding = []; for (const p of this.providers) { const t = p(data); if (!t.encodings.length) { throw new Error('Encodings must be non-empty.'); } data = t.data; for (const e of t.encodings) { encoding.push(e); } } if (!(data instanceof Uint8Array)) { throw new Error('The encoding must result in a Uint8Array. Fix your encoding chain.'); } return { encoding, data }; } constructor(providers) { this.providers = providers; } } export var ArrayEncoder; (function (ArrayEncoder) { function by(f) { return new ArrayEncoderImpl([f]); } ArrayEncoder.by = by; function fromEncoding(encoding) { let e = by(getProvider(encoding[0])); for (let i = 1; i < encoding.length; i++) { if (encoding[i - 1].kind === 'IntegerPacking') break; e = e.and(getProvider(encoding[i])); } return e; } ArrayEncoder.fromEncoding = fromEncoding; function getProvider(e) { switch (e.kind) { case 'ByteArray': return ArrayEncoding.byteArray; case 'FixedPoint': return ArrayEncoding.fixedPoint(e.factor); case 'IntervalQuantization': return ArrayEncoding.intervalQuantizaiton(e.min, e.max, e.numSteps); case 'RunLength': return ArrayEncoding.runLength; case 'Delta': return ArrayEncoding.delta; case 'IntegerPacking': return ArrayEncoding.integerPacking; case 'StringArray': return ArrayEncoding.stringArray; } } })(ArrayEncoder || (ArrayEncoder = {})); export var ArrayEncoding; (function (ArrayEncoding) { function by(f) { return new ArrayEncoderImpl([f]); } ArrayEncoding.by = by; function uint8(data) { return { encodings: [{ kind: 'ByteArray', type: Encoding.IntDataType.Uint8 }], data }; } function int8(data) { return { encodings: [{ kind: 'ByteArray', type: Encoding.IntDataType.Int8 }], data: new Uint8Array(data.buffer, data.byteOffset) }; } const writers = { [Encoding.IntDataType.Int16]: function (v, i, a) { v.setInt16(2 * i, a, true); }, [Encoding.IntDataType.Uint16]: function (v, i, a) { v.setUint16(2 * i, a, true); }, [Encoding.IntDataType.Int32]: function (v, i, a) { v.setInt32(4 * i, a, true); }, [Encoding.IntDataType.Uint32]: function (v, i, a) { v.setUint32(4 * i, a, true); }, [Encoding.FloatDataType.Float32]: function (v, i, a) { v.setFloat32(4 * i, a, true); }, [Encoding.FloatDataType.Float64]: function (v, i, a) { v.setFloat64(8 * i, a, true); } }; const byteSizes = { [Encoding.IntDataType.Int16]: 2, [Encoding.IntDataType.Uint16]: 2, [Encoding.IntDataType.Int32]: 4, [Encoding.IntDataType.Uint32]: 4, [Encoding.FloatDataType.Float32]: 4, [Encoding.FloatDataType.Float64]: 8 }; function byteArray(data) { const type = Encoding.getDataType(data); if (type === Encoding.IntDataType.Int8) return int8(data); else if (type === Encoding.IntDataType.Uint8) return uint8(data); const result = new Uint8Array(data.length * byteSizes[type]); const w = writers[type]; const view = new DataView(result.buffer); for (let i = 0, n = data.length; i < n; i++) { w(view, i, data[i]); } return { encodings: [{ kind: 'ByteArray', type }], data: result }; } ArrayEncoding.byteArray = byteArray; function _fixedPoint(data, factor) { const srcType = Encoding.getDataType(data); const result = new Int32Array(data.length); for (let i = 0, n = data.length; i < n; i++) { result[i] = Math.round(data[i] * factor); } return { encodings: [{ kind: 'FixedPoint', factor, srcType }], data: result }; } function fixedPoint(factor) { return data => _fixedPoint(data, factor); } ArrayEncoding.fixedPoint = fixedPoint; function _intervalQuantizaiton(data, min, max, numSteps, arrayType) { const srcType = Encoding.getDataType(data); if (!data.length) { return { encodings: [{ kind: 'IntervalQuantization', min, max, numSteps, srcType }], data: new Int32Array(0) }; } if (max < min) { const t = min; min = max; max = t; } const delta = (max - min) / (numSteps - 1); const output = new arrayType(data.length); for (let i = 0, n = data.length; i < n; i++) { const v = data[i]; if (v <= min) output[i] = 0; else if (v >= max) output[i] = numSteps - 1; else output[i] = (Math.round((v - min) / delta)) | 0; } return { encodings: [{ kind: 'IntervalQuantization', min, max, numSteps, srcType }], data: output }; } function intervalQuantizaiton(min, max, numSteps, arrayType = Int32Array) { return data => _intervalQuantizaiton(data, min, max, numSteps, arrayType); } ArrayEncoding.intervalQuantizaiton = intervalQuantizaiton; function runLength(data) { let srcType = Encoding.getDataType(data); if (srcType === void 0) { data = new Int32Array(data); srcType = Encoding.IntDataType.Int32; } if (!data.length) { return { encodings: [{ kind: 'RunLength', srcType, srcSize: 0 }], data: new Int32Array(0) }; } // calculate output size let fullLength = 2; for (let i = 1, il = data.length; i < il; i++) { if (data[i - 1] !== data[i]) { fullLength += 2; } } const output = new Int32Array(fullLength); let offset = 0; let runLength = 1; for (let i = 1, il = data.length; i < il; i++) { if (data[i - 1] !== data[i]) { output[offset] = data[i - 1]; output[offset + 1] = runLength; runLength = 1; offset += 2; } else { ++runLength; } } output[offset] = data[data.length - 1]; output[offset + 1] = runLength; return { encodings: [{ kind: 'RunLength', srcType, srcSize: data.length }], data: output }; } ArrayEncoding.runLength = runLength; function delta(data) { if (!Encoding.isSignedIntegerDataType(data)) { throw new Error('Only signed integer types can be encoded using delta encoding.'); } let srcType = Encoding.getDataType(data); if (srcType === void 0) { data = new Int32Array(data); srcType = Encoding.IntDataType.Int32; } if (!data.length) { return { encodings: [{ kind: 'Delta', origin: 0, srcType }], data: new data.constructor(0) }; } const output = new data.constructor(data.length); const origin = data[0]; output[0] = data[0]; for (let i = 1, n = data.length; i < n; i++) { output[i] = data[i] - data[i - 1]; } output[0] = 0; return { encodings: [{ kind: 'Delta', origin, srcType }], data: output }; } ArrayEncoding.delta = delta; function isSigned(data) { for (let i = 0, n = data.length; i < n; i++) { if (data[i] < 0) return true; } return false; } function packingSizeUnsigned(data, upperLimit) { let size = 0; for (let i = 0, n = data.length; i < n; i++) { size += (data[i] / upperLimit) | 0; } size += data.length; return size; } function packingSizeSigned(data, upperLimit) { const lowerLimit = -upperLimit - 1; let size = 0; for (let i = 0, n = data.length; i < n; i++) { const value = data[i]; if (value >= 0) { size += (value / upperLimit) | 0; } else { size += (value / lowerLimit) | 0; } } size += data.length; return size; } function determinePacking(data) { const signed = isSigned(data); const size8 = signed ? packingSizeSigned(data, 0x7F) : packingSizeUnsigned(data, 0xFF); const size16 = signed ? packingSizeSigned(data, 0x7FFF) : packingSizeUnsigned(data, 0xFFFF); if (data.length * 4 < size16 * 2) { // 4 byte packing is the most effective return { isSigned: signed, size: data.length, bytesPerElement: 4 }; } else if (size16 * 2 < size8) { // 2 byte packing is the most effective return { isSigned: signed, size: size16, bytesPerElement: 2 }; } else { // 1 byte packing is the most effective return { isSigned: signed, size: size8, bytesPerElement: 1 }; } ; } function _integerPacking(data, packing) { const upperLimit = packing.isSigned ? (packing.bytesPerElement === 1 ? 0x7F : 0x7FFF) : (packing.bytesPerElement === 1 ? 0xFF : 0xFFFF); const lowerLimit = -upperLimit - 1; const n = data.length; const packed = packing.isSigned ? packing.bytesPerElement === 1 ? new Int8Array(packing.size) : new Int16Array(packing.size) : packing.bytesPerElement === 1 ? new Uint8Array(packing.size) : new Uint16Array(packing.size); let j = 0; for (let i = 0; i < n; i++) { let value = data[i]; if (value >= 0) { while (value >= upperLimit) { packed[j] = upperLimit; ++j; value -= upperLimit; } } else { while (value <= lowerLimit) { packed[j] = lowerLimit; ++j; value -= lowerLimit; } } packed[j] = value; ++j; } const result = byteArray(packed); return { encodings: [{ kind: 'IntegerPacking', byteCount: packing.bytesPerElement, isUnsigned: !packing.isSigned, srcSize: n }, result.encodings[0] ], data: result.data }; } /** * Packs Int32 array. The packing level is determined automatically to either 1-, 2-, or 4-byte words. */ function integerPacking(data) { // if (!(data instanceof Int32Array)) { // throw new Error('Integer packing can only be applied to Int32 data.'); // } const packing = determinePacking(data); if (packing.bytesPerElement === 4) { // no packing done, Int32 encoding will be used return byteArray(data); } return _integerPacking(data, packing); } ArrayEncoding.integerPacking = integerPacking; function stringArray(data) { const map = Object.create(null); const strings = []; const output = new Int32Array(data.length); const offsets = ChunkedArray.create(Int32Array, 1, Math.min(1024, data.length < 32 ? data.length + 1 : Math.round(data.length / 8) + 1)); ChunkedArray.add(offsets, 0); let accLength = 0; let i = 0; for (const s of data) { // handle null strings. if (s === null || s === void 0) { output[i++] = -1; continue; } let index = map[s]; if (index === void 0) { // increment the length accLength += s.length; // store the string and index index = strings.length; strings[index] = s; map[s] = index; // write the offset ChunkedArray.add(offsets, accLength); } output[i++] = index; } const offsetArray = ChunkedArray.compact(offsets); const offsetEncoding = classifyIntArray(offsetArray); const encodedOddsets = offsetEncoding.encode(offsetArray); const dataEncoding = classifyIntArray(output); const encodedData = dataEncoding.encode(output); return { encodings: [{ kind: 'StringArray', dataEncoding: encodedData.encoding, stringData: strings.join(''), offsetEncoding: encodedOddsets.encoding, offsets: encodedOddsets.data }], data: encodedData.data }; } ArrayEncoding.stringArray = stringArray; })(ArrayEncoding || (ArrayEncoding = {}));