const codeToType = { 0: Int8Array, 1: Uint8Array, 2: Int16Array, 3: Uint16Array, 4: Int32Array, 5: Uint32Array, 7: Float32Array, 8: Float64Array, 9: BigInt64Array, 10: BigUint64Array }; const typeToBytes = { Int8Array: 1, Uint8Array: 1, Int16Array: 2, Uint16Array: 2, Int32Array: 4, Uint32Array: 4, Float32Array: 4, Float64Array: 8, BigInt64Array: 8, BigUint64Array: 8 }; function readTypedValue(view, offset, type) { switch (type) { case Int8Array: return view.getInt8(offset); case Uint8Array: return view.getUint8(offset); case Int16Array: return view.getInt16(offset, true); case Uint16Array: return view.getUint16(offset, true); case Int32Array: return view.getInt32(offset, true); case Uint32Array: return view.getUint32(offset, true); case Float32Array: return view.getFloat32(offset, true); case Float64Array: return view.getFloat64(offset, true); case BigInt64Array: return view.getBigInt64(offset, true); case BigUint64Array: return view.getBigUint64(offset, true); default: throw new Error(`Unsupported type: ${type.name}`); } } function writeTypedValue(view, offset, value, type) { switch (type) { case Int8Array: view.setInt8(offset, value); break; case Uint8Array: view.setUint8(offset, value); break; case Int16Array: view.setInt16(offset, value, true); break; case Uint16Array: view.setUint16(offset, value, true); break; case Int32Array: view.setInt32(offset, value, true); break; case Uint32Array: view.setUint32(offset, value, true); break; case Float32Array: view.setFloat32(offset, value, true); break; case Float64Array: view.setFloat64(offset, value, true); break; case BigInt64Array: view.setBigInt64(offset, BigInt(value), true); break; case BigUint64Array: view.setBigUint64(offset, BigInt(value), true); break; default: throw new Error(`Unsupported type: ${type.name}`); } } class DougalBinaryBundle extends ArrayBuffer { static HEADER_LENGTH = 4; // Length of a bundle header /** Clone an existing ByteArray into a DougalBinaryBundle */ static clone (buffer) { const clone = new DougalBinaryBundle(buffer.byteLength); const uint8Array = new Uint8Array(buffer); const uint8ArrayClone = new Uint8Array(clone); uint8ArrayClone.set(uint8Array); return clone; } constructor (length, options) { super (length, options); } /** Get the count of bundles in this ByteArray. * * Stops at the first non-bundle looking offset */ get bundleCount () { let count = 0; let currentBundleOffset = 0; const view = new DataView(this); while (currentBundleOffset < this.byteLength) { const currentBundleHeader = view.getUint32(currentBundleOffset, true); if ((currentBundleHeader & 0xff) !== 0x1c) { // This is not a bundle return count; } let currentBundleLength = currentBundleHeader >>> 8; currentBundleOffset += currentBundleLength + DougalBinaryBundle.HEADER_LENGTH; count++; } return count; } /** Get the number of chunks in the bundles of this ByteArray */ get chunkCount () { let count = 0; let bundleOffset = 0; const view = new DataView(this); while (bundleOffset < this.byteLength) { const header = view.getUint32(bundleOffset, true); if ((header & 0xFF) !== 0x1C) break; const length = header >>> 8; if (bundleOffset + 4 + length > this.byteLength) break; let chunkOffset = bundleOffset + 4; // relative to buffer start while (chunkOffset < bundleOffset + 4 + length) { const chunkType = view.getUint8(chunkOffset); if (chunkType !== 0x11 && chunkType !== 0x12) break; const cCount = view.getUint16(chunkOffset + 2, true); const ΔelemC = view.getUint8(chunkOffset + 10); const elemC = view.getUint8(chunkOffset + 11); let localOffset = 12; // header size localOffset += ΔelemC + elemC; // preface // initial values for (let k = 0; k < ΔelemC; k++) { const typeByte = view.getUint8(chunkOffset + 12 + k); const baseCode = typeByte & 0xF; const baseType = codeToType[baseCode]; if (!baseType) throw new Error('Invalid base type code'); localOffset += typeToBytes[baseType.name]; } // pad after initial while (localOffset % 4 !== 0) localOffset++; if (chunkType === 0x11) { // Sequential // record data: Δelems incrs for (let k = 0; k < ΔelemC; k++) { const typeByte = view.getUint8(chunkOffset + 12 + k); const incrCode = typeByte >> 4; const incrType = codeToType[incrCode]; if (!incrType) throw new Error('Invalid incr type code'); localOffset += cCount * typeToBytes[incrType.name]; } // elems for (let k = 0; k < elemC; k++) { const typeCode = view.getUint8(chunkOffset + 12 + ΔelemC + k); const type = codeToType[typeCode]; if (!type) throw new Error('Invalid elem type code'); localOffset += cCount * typeToBytes[type.name]; } } else { // Interleaved // Compute exact stride for interleaved record data let ΔelemStride = 0; for (let k = 0; k < ΔelemC; k++) { const typeByte = view.getUint8(chunkOffset + 12 + k); const incrCode = typeByte >> 4; const incrType = codeToType[incrCode]; if (!incrType) throw new Error('Invalid incr type code'); ΔelemStride += typeToBytes[incrType.name]; } let elemStride = 0; for (let k = 0; k < elemC; k++) { const typeCode = view.getUint8(chunkOffset + 12 + ΔelemC + k); const type = codeToType[typeCode]; if (!type) throw new Error('Invalid elem type code'); elemStride += typeToBytes[type.name]; } const recordStride = ΔelemStride + elemStride; localOffset += cCount * recordStride; } // pad after record while (localOffset % 4 !== 0) localOffset++; chunkOffset += localOffset; count++; } bundleOffset += 4 + length; } return count; } /** Return an array of DougalBinaryChunkSequential or DougalBinaryChunkInterleaved instances */ chunks () { const chunks = []; let bundleOffset = 0; const view = new DataView(this); while (bundleOffset < this.byteLength) { const header = view.getUint32(bundleOffset, true); if ((header & 0xFF) !== 0x1C) break; const length = header >>> 8; if (bundleOffset + 4 + length > this.byteLength) break; let chunkOffset = bundleOffset + 4; while (chunkOffset < bundleOffset + 4 + length) { const chunkType = view.getUint8(chunkOffset); if (chunkType !== 0x11 && chunkType !== 0x12) break; const cCount = view.getUint16(chunkOffset + 2, true); const ΔelemC = view.getUint8(chunkOffset + 10); const elemC = view.getUint8(chunkOffset + 11); let localOffset = 12; localOffset += ΔelemC + elemC; // initial values for (let k = 0; k < ΔelemC; k++) { const typeByte = view.getUint8(chunkOffset + 12 + k); const baseCode = typeByte & 0xF; const baseType = codeToType[baseCode]; if (!baseType) throw new Error('Invalid base type code'); localOffset += typeToBytes[baseType.name]; } // pad after initial while (localOffset % 4 !== 0) localOffset++; if (chunkType === 0x11) { // Sequential // record data: Δelems incrs for (let k = 0; k < ΔelemC; k++) { const typeByte = view.getUint8(chunkOffset + 12 + k); const incrCode = typeByte >> 4; const incrType = codeToType[incrCode]; if (!incrType) throw new Error('Invalid incr type code'); localOffset += cCount * typeToBytes[incrType.name]; } // elems for (let k = 0; k < elemC; k++) { const typeCode = view.getUint8(chunkOffset + 12 + ΔelemC + k); const type = codeToType[typeCode]; if (!type) throw new Error('Invalid elem type code'); localOffset += cCount * typeToBytes[type.name]; } } else { // Interleaved // Compute exact stride for interleaved record data let ΔelemStride = 0; for (let k = 0; k < ΔelemC; k++) { const typeByte = view.getUint8(chunkOffset + 12 + k); const incrCode = typeByte >> 4; const incrType = codeToType[incrCode]; if (!incrType) throw new Error('Invalid incr type code'); ΔelemStride += typeToBytes[incrType.name]; } let elemStride = 0; for (let k = 0; k < elemC; k++) { const typeCode = view.getUint8(chunkOffset + 12 + ΔelemC + k); const type = codeToType[typeCode]; if (!type) throw new Error('Invalid elem type code'); elemStride += typeToBytes[type.name]; } const recordStride = ΔelemStride + elemStride; localOffset += cCount * recordStride; } // pad after record while (localOffset % 4 !== 0) localOffset++; switch (chunkType) { case 0x11: chunks.push(new DougalBinaryChunkSequential(this, chunkOffset, localOffset)); break; case 0x12: chunks.push(new DougalBinaryChunkInterleaved(this, chunkOffset, localOffset)); break; default: throw new Error('Invalid chunk type'); } chunkOffset += localOffset; } bundleOffset += 4 + length; } return chunks; } /** Return a ByteArray containing all data from all * chunks including reconstructed i, j and incremental * values as follows: * * // i values (constant) * // j values (j0 + Δj*i) * <Δelem_0_0> <Δelem_0_1> … <Δelem_0_x> // reconstructed Δelem0 (uses baseType) * <Δelem_1_0> <Δelem_1_1> … <Δelem_1_x> // reconstructed Δelem1 * … * <Δelem_y_0> <Δelem_y_1> … <Δelem_y_x> // reconstructed Δelem1 * // First elem * // Second elem * … * // Last elem * * It does not matter whether the underlying chunks are * sequential or interleaved. This function will transform * as necessary. * */ getDataSequentially () { const chunks = this.chunks(); if (chunks.length === 0) return new ArrayBuffer(0); const firstChunk = chunks[0]; const ΔelemC = firstChunk.ΔelemCount; const elemC = firstChunk.elemCount; // Check consistency across chunks for (const chunk of chunks) { if (chunk.ΔelemCount !== ΔelemC || chunk.elemCount !== elemC) { throw new Error('Inconsistent chunk structures'); } } // Get types from first chunk const view = new DataView(firstChunk); const ΔelemBaseTypes = []; for (let k = 0; k < ΔelemC; k++) { const typeByte = view.getUint8(12 + k); const baseCode = typeByte & 0xF; const baseType = codeToType[baseCode]; if (!baseType) throw new Error('Invalid base type code'); ΔelemBaseTypes.push(baseType); } const elemTypes = []; for (let k = 0; k < elemC; k++) { const typeCode = view.getUint8(12 + ΔelemC + k); const type = codeToType[typeCode]; if (!type) throw new Error('Invalid elem type code'); elemTypes.push(type); } // Compute total records const totalN = chunks.reduce((sum, c) => sum + c.jCount, 0); // Compute sizes const size_i = totalN * 2; // Uint16 for i const size_j = totalN * 4; // Int32 for j let size_Δelems = 0; for (const t of ΔelemBaseTypes) { size_Δelems += totalN * typeToBytes[t.name]; } let size_elems = 0; for (const t of elemTypes) { size_elems += totalN * typeToBytes[t.name]; } const totalSize = size_i + size_j + size_Δelems + size_elems; const ab = new ArrayBuffer(totalSize); const dv = new DataView(ab); // Write i's let off = 0; for (const chunk of chunks) { const i = chunk.i; for (let idx = 0; idx < chunk.jCount; idx++) { dv.setUint16(off, i, true); off += 2; } } // Write j's off = size_i; for (const chunk of chunks) { const j0 = chunk.j0; const Δj = chunk.Δj; for (let idx = 0; idx < chunk.jCount; idx++) { const j = j0 + idx * Δj; dv.setInt32(off, j, true); off += 4; } } // Write Δelems off = size_i + size_j; for (let m = 0; m < ΔelemC; m++) { const type = ΔelemBaseTypes[m]; const bytes = typeToBytes[type.name]; for (const chunk of chunks) { const arr = chunk.Δelem(m); for (let idx = 0; idx < chunk.jCount; idx++) { writeTypedValue(dv, off, arr[idx], type); off += bytes; } } } // Write elems for (let m = 0; m < elemC; m++) { const type = elemTypes[m]; const bytes = typeToBytes[type.name]; for (const chunk of chunks) { const arr = chunk.elem(m); for (let idx = 0; idx < chunk.jCount; idx++) { writeTypedValue(dv, off, arr[idx], type); off += bytes; } } } return ab; } /** Return a ByteArray containing all data from all * chunks including reconstructed i, j and incremental * values, interleaved as follows: * * <Δelem_0_0> <Δelem_1_0> … <Δelem_y_0> * <Δelem_0_1> <Δelem_1_1> … <Δelem_y_1> * <Δelem_0_x> <Δelem_1_x> … <Δelem_y_x> * * It does not matter whether the underlying chunks are * sequential or interleaved. This function will transform * as necessary. * */ getDataInterleaved () { const chunks = this.chunks(); if (chunks.length === 0) return new ArrayBuffer(0); const firstChunk = chunks[0]; const ΔelemC = firstChunk.ΔelemCount; const elemC = firstChunk.elemCount; // Check consistency across chunks for (const chunk of chunks) { if (chunk.ΔelemCount !== ΔelemC || chunk.elemCount !== elemC) { throw new Error('Inconsistent chunk structures'); } } // Get types from first chunk const view = new DataView(firstChunk); const ΔelemBaseTypes = []; for (let k = 0; k < ΔelemC; k++) { const typeByte = view.getUint8(12 + k); const baseCode = typeByte & 0xF; const baseType = codeToType[baseCode]; if (!baseType) throw new Error('Invalid base type code'); ΔelemBaseTypes.push(baseType); } const elemTypes = []; for (let k = 0; k < elemC; k++) { const typeCode = view.getUint8(12 + ΔelemC + k); const type = codeToType[typeCode]; if (!type) throw new Error('Invalid elem type code'); elemTypes.push(type); } // Compute total records const totalN = chunks.reduce((sum, c) => sum + c.jCount, 0); // Compute record size const recordSize = 2 + 4 + // i (Uint16) + j (Int32) ΔelemBaseTypes.reduce((sum, t) => sum + typeToBytes[t.name], 0) + elemTypes.reduce((sum, t) => sum + typeToBytes[t.name], 0); const totalSize = totalN * recordSize; const ab = new ArrayBuffer(totalSize); const dv = new DataView(ab); let off = 0; for (const chunk of chunks) { const i = chunk.i; const j0 = chunk.j0; const Δj = chunk.Δj; for (let idx = 0; idx < chunk.jCount; idx++) { dv.setUint16(off, i, true); off += 2; const j = j0 + idx * Δj; dv.setInt32(off, j, true); off += 4; for (let m = 0; m < ΔelemC; m++) { const type = ΔelemBaseTypes[m]; const bytes = typeToBytes[type.name]; const arr = chunk.Δelem(m); writeTypedValue(dv, off, arr[idx], type); off += bytes; } for (let m = 0; m < elemC; m++) { const type = elemTypes[m]; const bytes = typeToBytes[type.name]; const arr = chunk.elem(m); writeTypedValue(dv, off, arr[idx], type); off += bytes; } } } return ab; } } class DougalBinaryChunkSequential extends ArrayBuffer { constructor (buffer, offset, length) { super(length); new Uint8Array(this).set(new Uint8Array(buffer, offset, length)); this._ΔelemCaches = new Array(this.ΔelemCount); this._elemCaches = new Array(this.elemCount); this._ΔelemBlockOffsets = null; this._elemBlockOffsets = null; this._recordOffset = null; } _getRecordOffset() { if (this._recordOffset !== null) return this._recordOffset; const view = new DataView(this); const ΔelemC = this.ΔelemCount; const elemC = this.elemCount; let recordOffset = 12 + ΔelemC + elemC; for (let k = 0; k < ΔelemC; k++) { const tb = view.getUint8(12 + k); const bc = tb & 0xF; const bt = codeToType[bc]; recordOffset += typeToBytes[bt.name]; } while (recordOffset % 4 !== 0) recordOffset++; this._recordOffset = recordOffset; return recordOffset; } _initBlockOffsets() { if (this._ΔelemBlockOffsets !== null) return; const view = new DataView(this); const count = this.jCount; const ΔelemC = this.ΔelemCount; const elemC = this.elemCount; const recordOffset = this._getRecordOffset(); this._ΔelemBlockOffsets = []; let o = recordOffset; for (let k = 0; k < ΔelemC; k++) { this._ΔelemBlockOffsets[k] = o; const tb = view.getUint8(12 + k); const ic = tb >> 4; const it = codeToType[ic]; o += count * typeToBytes[it.name]; } this._elemBlockOffsets = []; for (let k = 0; k < elemC; k++) { this._elemBlockOffsets[k] = o; const tc = view.getUint8(12 + ΔelemC + k); const t = codeToType[tc]; o += count * typeToBytes[t.name]; } } /** Return the user-defined value */ get udv () { return new DataView(this).getUint8(1); } /** Return the number of j elements in this chunk */ get jCount () { return new DataView(this).getUint16(2, true); } /** Return the i value in this chunk */ get i () { return new DataView(this).getUint16(4, true); } /** Return the j0 value in this chunk */ get j0 () { return new DataView(this).getUint16(6, true); } /** Return the Δj value in this chunk */ get Δj () { return new DataView(this).getInt16(8, true); } /** Return the Δelem_count value in this chunk */ get ΔelemCount () { return new DataView(this).getUint8(10); } /** Return the elem_count value in this chunk */ get elemCount () { return new DataView(this).getUint8(11); } /** Return a TypedArray (e.g., Uint16Array, …) for the n-th Δelem in the chunk */ Δelem (n) { if (this._ΔelemCaches[n]) return this._ΔelemCaches[n]; if (n < 0 || n >= this.ΔelemCount) throw new Error(`Invalid Δelem index: ${n}`); const view = new DataView(this); const count = this.jCount; const ΔelemC = this.ΔelemCount; const typeByte = view.getUint8(12 + n); const baseCode = typeByte & 0xF; const incrCode = typeByte >> 4; const baseType = codeToType[baseCode]; const incrType = codeToType[incrCode]; if (!baseType || !incrType) throw new Error('Invalid type codes for Δelem'); // Find offset for initial value of this Δelem let initialOffset = 12 + ΔelemC + this.elemCount; for (let k = 0; k < n; k++) { const tb = view.getUint8(12 + k); const bc = tb & 0xF; const bt = codeToType[bc]; initialOffset += typeToBytes[bt.name]; } let current = readTypedValue(view, initialOffset, baseType); // Advance to start of record data (after all initials and pad) const recordOffset = this._getRecordOffset(); // Find offset for deltas of this Δelem (skip previous Δelems' delta blocks) this._initBlockOffsets(); const deltaOffset = this._ΔelemBlockOffsets[n]; // Reconstruct the array const arr = new baseType(count); const isBigInt = baseType === BigInt64Array || baseType === BigUint64Array; arr[0] = current; for (let idx = 1; idx < count; idx++) { let delta = readTypedValue(view, deltaOffset + idx * typeToBytes[incrType.name], incrType); if (isBigInt) { delta = BigInt(delta); current += delta; } else { current += delta; } arr[idx] = current; } this._ΔelemCaches[n] = arr; return arr; } /** Return a TypedArray (e.g., Uint16Array, …) for the n-th elem in the chunk */ elem (n) { if (this._elemCaches[n]) return this._elemCaches[n]; if (n < 0 || n >= this.elemCount) throw new Error(`Invalid elem index: ${n}`); const view = new DataView(this); const count = this.jCount; const ΔelemC = this.ΔelemCount; const elemC = this.elemCount; const typeCode = view.getUint8(12 + ΔelemC + n); const type = codeToType[typeCode]; if (!type) throw new Error('Invalid type code for elem'); // Find offset for this elem's data block this._initBlockOffsets(); const elemOffset = this._elemBlockOffsets[n]; // Create and populate the array const arr = new type(count); const bytes = typeToBytes[type.name]; for (let idx = 0; idx < count; idx++) { arr[idx] = readTypedValue(view, elemOffset + idx * bytes, type); } this._elemCaches[n] = arr; return arr; } getRecord (index) { if (index < 0 || index >= this.jCount) throw new Error(`Invalid record index: ${index}`); const arr = [thid.udv, this.i, this.j0 + index * this.Δj]; for (let m = 0; m < this.ΔelemCount; m++) { const values = this.Δelem(m); arr.push(values[index]); } for (let m = 0; m < this.elemCount; m++) { const values = this.elem(m); arr.push(values[index]); } return arr; } } class DougalBinaryChunkInterleaved extends ArrayBuffer { constructor(buffer, offset, length) { super(length); new Uint8Array(this).set(new Uint8Array(buffer, offset, length)); this._incrStrides = []; this._elemStrides = []; this._incrOffsets = []; this._elemOffsets = []; this._recordStride = 0; this._recordOffset = null; this._initStrides(); this._ΔelemCaches = new Array(this.ΔelemCount); this._elemCaches = new Array(this.elemCount); } _getRecordOffset() { if (this._recordOffset !== null) return this._recordOffset; const view = new DataView(this); const ΔelemC = this.ΔelemCount; const elemC = this.elemCount; let recordOffset = 12 + ΔelemC + elemC; for (let k = 0; k < ΔelemC; k++) { const tb = view.getUint8(12 + k); const bc = tb & 0xF; const bt = codeToType[bc]; recordOffset += typeToBytes[bt.name]; } while (recordOffset % 4 !== 0) recordOffset++; this._recordOffset = recordOffset; return recordOffset; } _initStrides() { const view = new DataView(this); const ΔelemC = this.ΔelemCount; const elemC = this.elemCount; // Compute incr strides and offsets let incrOffset = 0; for (let k = 0; k < ΔelemC; k++) { const typeByte = view.getUint8(12 + k); const incrCode = typeByte >> 4; const incrType = codeToType[incrCode]; if (!incrType) throw new Error('Invalid incr type code'); this._incrOffsets.push(incrOffset); const bytes = typeToBytes[incrType.name]; this._incrStrides.push(bytes); incrOffset += bytes; this._recordStride += bytes; } // Compute elem strides and offsets let elemOffset = incrOffset; for (let k = 0; k < elemC; k++) { const typeCode = view.getUint8(12 + ΔelemC + k); const type = codeToType[typeCode]; if (!type) throw new Error('Invalid elem type code'); this._elemOffsets.push(elemOffset); const bytes = typeToBytes[type.name]; this._elemStrides.push(bytes); elemOffset += bytes; this._recordStride += bytes; } } get udv() { return new DataView(this).getUint8(1); } get jCount() { return new DataView(this).getUint16(2, true); } get i() { return new DataView(this).getUint16(4, true); } get j0() { return new DataView(this).getUint16(6, true); } get Δj() { return new DataView(this).getInt16(8, true); } get ΔelemCount() { return new DataView(this).getUint8(10); } get elemCount() { return new DataView(this).getUint8(11); } Δelem(n) { if (this._ΔelemCaches[n]) return this._ΔelemCaches[n]; if (n < 0 || n >= this.ΔelemCount) throw new Error(`Invalid Δelem index: ${n}`); const view = new DataView(this); const count = this.jCount; const ΔelemC = this.ΔelemCount; const typeByte = view.getUint8(12 + n); const baseCode = typeByte & 0xF; const incrCode = typeByte >> 4; const baseType = codeToType[baseCode]; const incrType = codeToType[incrCode]; if (!baseType || !incrType) throw new Error('Invalid type codes for Δelem'); // Find offset for initial value of this Δelem let initialOffset = 12 + ΔelemC + this.elemCount; for (let k = 0; k < n; k++) { const tb = view.getUint8(12 + k); const bc = tb & 0xF; const bt = codeToType[bc]; initialOffset += typeToBytes[bt.name]; } let current = readTypedValue(view, initialOffset, baseType); // Find offset to start of record data const recordOffset = this._getRecordOffset(); // Use precomputed offset for this Δelem const deltaOffset = recordOffset + this._incrOffsets[n]; // Reconstruct the array const arr = new baseType(count); const isBigInt = baseType === BigInt64Array || baseType === BigUint64Array; arr[0] = current; for (let idx = 1; idx < count; idx++) { let delta = readTypedValue(view, deltaOffset + idx * this._recordStride, incrType); if (isBigInt) { delta = BigInt(delta); current += delta; } else { current += delta; } arr[idx] = current; } this._ΔelemCaches[n] = arr; return arr; } elem(n) { if (this._elemCaches[n]) return this._elemCaches[n]; if (n < 0 || n >= this.elemCount) throw new Error(`Invalid elem index: ${n}`); const view = new DataView(this); const count = this.jCount; const ΔelemC = this.ΔelemCount; const typeCode = view.getUint8(12 + ΔelemC + n); const type = codeToType[typeCode]; if (!type) throw new Error('Invalid type code for elem'); // Find offset to start of record data const recordOffset = this._getRecordOffset(); // Use precomputed offset for this elem (relative to start of record data) const elemOffset = recordOffset + this._elemOffsets[n]; // Create and populate the array const arr = new type(count); const bytes = typeToBytes[type.name]; for (let idx = 0; idx < count; idx++) { arr[idx] = readTypedValue(view, elemOffset + idx * this._recordStride, type); } this._elemCaches[n] = arr; return arr; } getRecord (index) { if (index < 0 || index >= this.jCount) throw new Error(`Invalid record index: ${index}`); const arr = [this.udv, this.i, this.j0 + index * this.Δj]; for (let m = 0; m < this.ΔelemCount; m++) { const values = this.Δelem(m); arr.push(values[index]); } for (let m = 0; m < this.elemCount; m++) { const values = this.elem(m); arr.push(values[index]); } return arr; } } module.exports = { DougalBinaryBundle, DougalBinaryChunkSequential, DougalBinaryChunkInterleaved }