/**
* eln-plugin - Extract metainfo from chemical file formats into a well defined json structure
* @version v0.27.0
* @link https://github.com/cheminfo/eln-plugin#readme
* @license MIT
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.elnPlugin = factory());
}(this, (function () { 'use strict';
var defaultType = {
process() {
return {};
},
getEmpty() {
return [];
}
};
var reactiongeneral = {
jpath: [],
getEmpty() {
return {
code: '',
date: Date.now(),
procedure: '',
products: [],
reagents: [],
conditions: '',
keywords: [],
remarks: '',
title: '',
reactionRXN: '$RXN\n\n\n\n 0 0\n'
};
}
};
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
function getAugmentedNamespace(n) {
if (n.__esModule) return n;
var a = Object.defineProperty({}, '__esModule', {value: true});
Object.keys(n).forEach(function (k) {
var d = Object.getOwnPropertyDescriptor(n, k);
Object.defineProperty(a, k, d.get ? d : {
enumerable: true,
get: function () {
return n[k];
}
});
});
return a;
}
function createCommonjsModule(fn) {
var module = { exports: {} };
return fn(module, module.exports), module.exports;
}
/*! https://mths.be/utf8js v3.0.0 by @mathias */
var utf8 = createCommonjsModule(function (module, exports) {
(function (root) {
var stringFromCharCode = String.fromCharCode; // Taken from https://mths.be/punycode
function ucs2decode(string) {
var output = [];
var counter = 0;
var length = string.length;
var value;
var extra;
while (counter < length) {
value = string.charCodeAt(counter++);
if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
// high surrogate, and there is a next character
extra = string.charCodeAt(counter++);
if ((extra & 0xFC00) == 0xDC00) {
// low surrogate
output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
} else {
// unmatched surrogate; only append this code unit, in case the next
// code unit is the high surrogate of a surrogate pair
output.push(value);
counter--;
}
} else {
output.push(value);
}
}
return output;
} // Taken from https://mths.be/punycode
function ucs2encode(array) {
var length = array.length;
var index = -1;
var value;
var output = '';
while (++index < length) {
value = array[index];
if (value > 0xFFFF) {
value -= 0x10000;
output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
value = 0xDC00 | value & 0x3FF;
}
output += stringFromCharCode(value);
}
return output;
}
function checkScalarValue(codePoint) {
if (codePoint >= 0xD800 && codePoint <= 0xDFFF) {
throw Error('Lone surrogate U+' + codePoint.toString(16).toUpperCase() + ' is not a scalar value');
}
}
/*--------------------------------------------------------------------------*/
function createByte(codePoint, shift) {
return stringFromCharCode(codePoint >> shift & 0x3F | 0x80);
}
function encodeCodePoint(codePoint) {
if ((codePoint & 0xFFFFFF80) == 0) {
// 1-byte sequence
return stringFromCharCode(codePoint);
}
var symbol = '';
if ((codePoint & 0xFFFFF800) == 0) {
// 2-byte sequence
symbol = stringFromCharCode(codePoint >> 6 & 0x1F | 0xC0);
} else if ((codePoint & 0xFFFF0000) == 0) {
// 3-byte sequence
checkScalarValue(codePoint);
symbol = stringFromCharCode(codePoint >> 12 & 0x0F | 0xE0);
symbol += createByte(codePoint, 6);
} else if ((codePoint & 0xFFE00000) == 0) {
// 4-byte sequence
symbol = stringFromCharCode(codePoint >> 18 & 0x07 | 0xF0);
symbol += createByte(codePoint, 12);
symbol += createByte(codePoint, 6);
}
symbol += stringFromCharCode(codePoint & 0x3F | 0x80);
return symbol;
}
function utf8encode(string) {
var codePoints = ucs2decode(string);
var length = codePoints.length;
var index = -1;
var codePoint;
var byteString = '';
while (++index < length) {
codePoint = codePoints[index];
byteString += encodeCodePoint(codePoint);
}
return byteString;
}
/*--------------------------------------------------------------------------*/
function readContinuationByte() {
if (byteIndex >= byteCount) {
throw Error('Invalid byte index');
}
var continuationByte = byteArray[byteIndex] & 0xFF;
byteIndex++;
if ((continuationByte & 0xC0) == 0x80) {
return continuationByte & 0x3F;
} // If we end up here, it’s not a continuation byte
throw Error('Invalid continuation byte');
}
function decodeSymbol() {
var byte1;
var byte2;
var byte3;
var byte4;
var codePoint;
if (byteIndex > byteCount) {
throw Error('Invalid byte index');
}
if (byteIndex == byteCount) {
return false;
} // Read first byte
byte1 = byteArray[byteIndex] & 0xFF;
byteIndex++; // 1-byte sequence (no continuation bytes)
if ((byte1 & 0x80) == 0) {
return byte1;
} // 2-byte sequence
if ((byte1 & 0xE0) == 0xC0) {
byte2 = readContinuationByte();
codePoint = (byte1 & 0x1F) << 6 | byte2;
if (codePoint >= 0x80) {
return codePoint;
} else {
throw Error('Invalid continuation byte');
}
} // 3-byte sequence (may include unpaired surrogates)
if ((byte1 & 0xF0) == 0xE0) {
byte2 = readContinuationByte();
byte3 = readContinuationByte();
codePoint = (byte1 & 0x0F) << 12 | byte2 << 6 | byte3;
if (codePoint >= 0x0800) {
checkScalarValue(codePoint);
return codePoint;
} else {
throw Error('Invalid continuation byte');
}
} // 4-byte sequence
if ((byte1 & 0xF8) == 0xF0) {
byte2 = readContinuationByte();
byte3 = readContinuationByte();
byte4 = readContinuationByte();
codePoint = (byte1 & 0x07) << 0x12 | byte2 << 0x0C | byte3 << 0x06 | byte4;
if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) {
return codePoint;
}
}
throw Error('Invalid UTF-8 detected');
}
var byteArray;
var byteCount;
var byteIndex;
function utf8decode(byteString) {
byteArray = ucs2decode(byteString);
byteCount = byteArray.length;
byteIndex = 0;
var codePoints = [];
var tmp;
while ((tmp = decodeSymbol()) !== false) {
codePoints.push(tmp);
}
return ucs2encode(codePoints);
}
/*--------------------------------------------------------------------------*/
root.version = '3.0.0';
root.encode = utf8encode;
root.decode = utf8decode;
})(exports);
});
const defaultByteLength = 1024 * 8;
class IOBuffer$1 {
/**
* @param data - The data to construct the IOBuffer with.
* If data is a number, it will be the new buffer's length
* If data is `undefined`, the buffer will be initialized with a default length of 8Kb
* If data is an ArrayBuffer, SharedArrayBuffer, an ArrayBufferView (Typed Array), an IOBuffer instance,
* or a Node.js Buffer, a view will be created over the underlying ArrayBuffer.
* @param options
*/
constructor(data = defaultByteLength, options = {}) {
let dataIsGiven = false;
if (typeof data === 'number') {
data = new ArrayBuffer(data);
} else {
dataIsGiven = true;
this.lastWrittenByte = data.byteLength;
}
const offset = options.offset ? options.offset >>> 0 : 0;
const byteLength = data.byteLength - offset;
let dvOffset = offset;
if (ArrayBuffer.isView(data) || data instanceof IOBuffer$1) {
if (data.byteLength !== data.buffer.byteLength) {
dvOffset = data.byteOffset + offset;
}
data = data.buffer;
}
if (dataIsGiven) {
this.lastWrittenByte = byteLength;
} else {
this.lastWrittenByte = 0;
}
this.buffer = data;
this.length = byteLength;
this.byteLength = byteLength;
this.byteOffset = dvOffset;
this.offset = 0;
this.littleEndian = true;
this._data = new DataView(this.buffer, dvOffset, byteLength);
this._mark = 0;
this._marks = [];
}
/**
* Checks if the memory allocated to the buffer is sufficient to store more
* bytes after the offset.
* @param byteLength - The needed memory in bytes.
* @returns `true` if there is sufficient space and `false` otherwise.
*/
available(byteLength = 1) {
return this.offset + byteLength <= this.length;
}
/**
* Check if little-endian mode is used for reading and writing multi-byte
* values.
* @returns `true` if little-endian mode is used, `false` otherwise.
*/
isLittleEndian() {
return this.littleEndian;
}
/**
* Set little-endian mode for reading and writing multi-byte values.
*/
setLittleEndian() {
this.littleEndian = true;
return this;
}
/**
* Check if big-endian mode is used for reading and writing multi-byte values.
* @returns `true` if big-endian mode is used, `false` otherwise.
*/
isBigEndian() {
return !this.littleEndian;
}
/**
* Switches to big-endian mode for reading and writing multi-byte values.
*/
setBigEndian() {
this.littleEndian = false;
return this;
}
/**
* Move the pointer n bytes forward.
* @param n - Number of bytes to skip.
*/
skip(n = 1) {
this.offset += n;
return this;
}
/**
* Move the pointer to the given offset.
* @param offset
*/
seek(offset) {
this.offset = offset;
return this;
}
/**
* Store the current pointer offset.
* @see {@link IOBuffer#reset}
*/
mark() {
this._mark = this.offset;
return this;
}
/**
* Move the pointer back to the last pointer offset set by mark.
* @see {@link IOBuffer#mark}
*/
reset() {
this.offset = this._mark;
return this;
}
/**
* Push the current pointer offset to the mark stack.
* @see {@link IOBuffer#popMark}
*/
pushMark() {
this._marks.push(this.offset);
return this;
}
/**
* Pop the last pointer offset from the mark stack, and set the current
* pointer offset to the popped value.
* @see {@link IOBuffer#pushMark}
*/
popMark() {
const offset = this._marks.pop();
if (offset === undefined) {
throw new Error('Mark stack empty');
}
this.seek(offset);
return this;
}
/**
* Move the pointer offset back to 0.
*/
rewind() {
this.offset = 0;
return this;
}
/**
* Make sure the buffer has sufficient memory to write a given byteLength at
* the current pointer offset.
* If the buffer's memory is insufficient, this method will create a new
* buffer (a copy) with a length that is twice (byteLength + current offset).
* @param byteLength
*/
ensureAvailable(byteLength = 1) {
if (!this.available(byteLength)) {
const lengthNeeded = this.offset + byteLength;
const newLength = lengthNeeded * 2;
const newArray = new Uint8Array(newLength);
newArray.set(new Uint8Array(this.buffer));
this.buffer = newArray.buffer;
this.length = this.byteLength = newLength;
this._data = new DataView(this.buffer);
}
return this;
}
/**
* Read a byte and return false if the byte's value is 0, or true otherwise.
* Moves pointer forward by one byte.
*/
readBoolean() {
return this.readUint8() !== 0;
}
/**
* Read a signed 8-bit integer and move pointer forward by 1 byte.
*/
readInt8() {
return this._data.getInt8(this.offset++);
}
/**
* Read an unsigned 8-bit integer and move pointer forward by 1 byte.
*/
readUint8() {
return this._data.getUint8(this.offset++);
}
/**
* Alias for {@link IOBuffer#readUint8}.
*/
readByte() {
return this.readUint8();
}
/**
* Read `n` bytes and move pointer forward by `n` bytes.
*/
readBytes(n = 1) {
const bytes = new Uint8Array(n);
for (let i = 0; i < n; i++) {
bytes[i] = this.readByte();
}
return bytes;
}
/**
* Read a 16-bit signed integer and move pointer forward by 2 bytes.
*/
readInt16() {
const value = this._data.getInt16(this.offset, this.littleEndian);
this.offset += 2;
return value;
}
/**
* Read a 16-bit unsigned integer and move pointer forward by 2 bytes.
*/
readUint16() {
const value = this._data.getUint16(this.offset, this.littleEndian);
this.offset += 2;
return value;
}
/**
* Read a 32-bit signed integer and move pointer forward by 4 bytes.
*/
readInt32() {
const value = this._data.getInt32(this.offset, this.littleEndian);
this.offset += 4;
return value;
}
/**
* Read a 32-bit unsigned integer and move pointer forward by 4 bytes.
*/
readUint32() {
const value = this._data.getUint32(this.offset, this.littleEndian);
this.offset += 4;
return value;
}
/**
* Read a 32-bit floating number and move pointer forward by 4 bytes.
*/
readFloat32() {
const value = this._data.getFloat32(this.offset, this.littleEndian);
this.offset += 4;
return value;
}
/**
* Read a 64-bit floating number and move pointer forward by 8 bytes.
*/
readFloat64() {
const value = this._data.getFloat64(this.offset, this.littleEndian);
this.offset += 8;
return value;
}
/**
* Read a 1-byte ASCII character and move pointer forward by 1 byte.
*/
readChar() {
return String.fromCharCode(this.readInt8());
}
/**
* Read `n` 1-byte ASCII characters and move pointer forward by `n` bytes.
*/
readChars(n = 1) {
let result = '';
for (let i = 0; i < n; i++) {
result += this.readChar();
}
return result;
}
/**
* Read the next `n` bytes, return a UTF-8 decoded string and move pointer
* forward by `n` bytes.
*/
readUtf8(n = 1) {
const bString = this.readChars(n);
return utf8.decode(bString);
}
/**
* Write 0xff if the passed value is truthy, 0x00 otherwise and move pointer
* forward by 1 byte.
*/
writeBoolean(value) {
this.writeUint8(value ? 0xff : 0x00);
return this;
}
/**
* Write `value` as an 8-bit signed integer and move pointer forward by 1 byte.
*/
writeInt8(value) {
this.ensureAvailable(1);
this._data.setInt8(this.offset++, value);
this._updateLastWrittenByte();
return this;
}
/**
* Write `value` as an 8-bit unsigned integer and move pointer forward by 1
* byte.
*/
writeUint8(value) {
this.ensureAvailable(1);
this._data.setUint8(this.offset++, value);
this._updateLastWrittenByte();
return this;
}
/**
* An alias for {@link IOBuffer#writeUint8}.
*/
writeByte(value) {
return this.writeUint8(value);
}
/**
* Write all elements of `bytes` as uint8 values and move pointer forward by
* `bytes.length` bytes.
*/
writeBytes(bytes) {
this.ensureAvailable(bytes.length);
for (let i = 0; i < bytes.length; i++) {
this._data.setUint8(this.offset++, bytes[i]);
}
this._updateLastWrittenByte();
return this;
}
/**
* Write `value` as a 16-bit signed integer and move pointer forward by 2
* bytes.
*/
writeInt16(value) {
this.ensureAvailable(2);
this._data.setInt16(this.offset, value, this.littleEndian);
this.offset += 2;
this._updateLastWrittenByte();
return this;
}
/**
* Write `value` as a 16-bit unsigned integer and move pointer forward by 2
* bytes.
*/
writeUint16(value) {
this.ensureAvailable(2);
this._data.setUint16(this.offset, value, this.littleEndian);
this.offset += 2;
this._updateLastWrittenByte();
return this;
}
/**
* Write `value` as a 32-bit signed integer and move pointer forward by 4
* bytes.
*/
writeInt32(value) {
this.ensureAvailable(4);
this._data.setInt32(this.offset, value, this.littleEndian);
this.offset += 4;
this._updateLastWrittenByte();
return this;
}
/**
* Write `value` as a 32-bit unsigned integer and move pointer forward by 4
* bytes.
*/
writeUint32(value) {
this.ensureAvailable(4);
this._data.setUint32(this.offset, value, this.littleEndian);
this.offset += 4;
this._updateLastWrittenByte();
return this;
}
/**
* Write `value` as a 32-bit floating number and move pointer forward by 4
* bytes.
*/
writeFloat32(value) {
this.ensureAvailable(4);
this._data.setFloat32(this.offset, value, this.littleEndian);
this.offset += 4;
this._updateLastWrittenByte();
return this;
}
/**
* Write `value` as a 64-bit floating number and move pointer forward by 8
* bytes.
*/
writeFloat64(value) {
this.ensureAvailable(8);
this._data.setFloat64(this.offset, value, this.littleEndian);
this.offset += 8;
this._updateLastWrittenByte();
return this;
}
/**
* Write the charCode of `str`'s first character as an 8-bit unsigned integer
* and move pointer forward by 1 byte.
*/
writeChar(str) {
return this.writeUint8(str.charCodeAt(0));
}
/**
* Write the charCodes of all `str`'s characters as 8-bit unsigned integers
* and move pointer forward by `str.length` bytes.
*/
writeChars(str) {
for (let i = 0; i < str.length; i++) {
this.writeUint8(str.charCodeAt(i));
}
return this;
}
/**
* UTF-8 encode and write `str` to the current pointer offset and move pointer
* forward according to the encoded length.
*/
writeUtf8(str) {
const bString = utf8.encode(str);
return this.writeChars(bString);
}
/**
* Export a Uint8Array view of the internal buffer.
* The view starts at the byte offset and its length
* is calculated to stop at the last written byte or the original length.
*/
toArray() {
return new Uint8Array(this.buffer, this.byteOffset, this.lastWrittenByte);
}
/**
* Update the last written byte offset
* @private
*/
_updateLastWrittenByte() {
if (this.offset > this.lastWrittenByte) {
this.lastWrittenByte = this.offset;
}
}
}
var IOBuffer$2 = /*#__PURE__*/Object.freeze({
__proto__: null,
IOBuffer: IOBuffer$1
});
/**
* Throws a non-valid NetCDF exception if the statement it's true
* @ignore
* @param {boolean} statement - Throws if true
* @param {string} reason - Reason to throw
*/
function notNetcdf$1(statement, reason) {
if (statement) {
throw new TypeError(`Not a valid NetCDF v3.x file: ${reason}`);
}
}
/**
* Moves 1, 2, or 3 bytes to next 4-byte boundary
* @ignore
* @param {IOBuffer} buffer - Buffer for the file data
*/
function padding(buffer) {
if (buffer.offset % 4 !== 0) {
buffer.skip(4 - buffer.offset % 4);
}
}
/**
* Reads the name
* @ignore
* @param {IOBuffer} buffer - Buffer for the file data
* @return {string} - Name
*/
function readName(buffer) {
// Read name
var nameLength = buffer.readUint32();
var name = buffer.readChars(nameLength); // validate name
// TODO
// Apply padding
padding(buffer);
return name;
}
var notNetcdf_1 = notNetcdf$1;
var padding_1 = padding;
var readName_1 = readName;
var utils = {
notNetcdf: notNetcdf_1,
padding: padding_1,
readName: readName_1
};
const notNetcdf = utils.notNetcdf;
const types = {
BYTE: 1,
CHAR: 2,
SHORT: 3,
INT: 4,
FLOAT: 5,
DOUBLE: 6
};
/**
* Parse a number into their respective type
* @ignore
* @param {number} type - integer that represents the type
* @return {string} - parsed value of the type
*/
function num2str(type) {
switch (Number(type)) {
case types.BYTE:
return 'byte';
case types.CHAR:
return 'char';
case types.SHORT:
return 'short';
case types.INT:
return 'int';
case types.FLOAT:
return 'float';
case types.DOUBLE:
return 'double';
/* istanbul ignore next */
default:
return 'undefined';
}
}
/**
* Parse a number type identifier to his size in bytes
* @ignore
* @param {number} type - integer that represents the type
* @return {number} -size of the type
*/
function num2bytes(type) {
switch (Number(type)) {
case types.BYTE:
return 1;
case types.CHAR:
return 1;
case types.SHORT:
return 2;
case types.INT:
return 4;
case types.FLOAT:
return 4;
case types.DOUBLE:
return 8;
/* istanbul ignore next */
default:
return -1;
}
}
/**
* Reverse search of num2str
* @ignore
* @param {string} type - string that represents the type
* @return {number} - parsed value of the type
*/
function str2num(type) {
switch (String(type)) {
case 'byte':
return types.BYTE;
case 'char':
return types.CHAR;
case 'short':
return types.SHORT;
case 'int':
return types.INT;
case 'float':
return types.FLOAT;
case 'double':
return types.DOUBLE;
/* istanbul ignore next */
default:
return -1;
}
}
/**
* Auxiliary function to read numeric data
* @ignore
* @param {number} size - Size of the element to read
* @param {function} bufferReader - Function to read next value
* @return {Array|number}
*/
function readNumber(size, bufferReader) {
if (size !== 1) {
var numbers = new Array(size);
for (var i = 0; i < size; i++) {
numbers[i] = bufferReader();
}
return numbers;
} else {
return bufferReader();
}
}
/**
* Given a type and a size reads the next element
* @ignore
* @param {IOBuffer} buffer - Buffer for the file data
* @param {number} type - Type of the data to read
* @param {number} size - Size of the element to read
* @return {string|Array|number}
*/
function readType(buffer, type, size) {
switch (type) {
case types.BYTE:
return buffer.readBytes(size);
case types.CHAR:
return trimNull(buffer.readChars(size));
case types.SHORT:
return readNumber(size, buffer.readInt16.bind(buffer));
case types.INT:
return readNumber(size, buffer.readInt32.bind(buffer));
case types.FLOAT:
return readNumber(size, buffer.readFloat32.bind(buffer));
case types.DOUBLE:
return readNumber(size, buffer.readFloat64.bind(buffer));
/* istanbul ignore next */
default:
notNetcdf(true, `non valid type ${type}`);
return undefined;
}
}
/**
* Removes null terminate value
* @ignore
* @param {string} value - String to trim
* @return {string} - Trimmed string
*/
function trimNull(value) {
if (value.charCodeAt(value.length - 1) === 0) {
return value.substring(0, value.length - 1);
}
return value;
}
var types_1 = types;
var num2str_1 = num2str;
var num2bytes_1 = num2bytes;
var str2num_1 = str2num;
var readType_1 = readType;
types_1.num2str = num2str_1;
types_1.num2bytes = num2bytes_1;
types_1.str2num = str2num_1;
types_1.readType = readType_1;
/**
* Read data for the given non-record variable
* @ignore
* @param {IOBuffer} buffer - Buffer for the file data
* @param {object} variable - Variable metadata
* @return {Array} - Data of the element
*/
function nonRecord(buffer, variable) {
// variable type
const type = types_1.str2num(variable.type); // size of the data
var size = variable.size / types_1.num2bytes(type); // iterates over the data
var data = new Array(size);
for (var i = 0; i < size; i++) {
data[i] = types_1.readType(buffer, type, 1);
}
return data;
}
/**
* Read data for the given record variable
* @ignore
* @param {IOBuffer} buffer - Buffer for the file data
* @param {object} variable - Variable metadata
* @param {object} recordDimension - Record dimension metadata
* @return {Array} - Data of the element
*/
function record(buffer, variable, recordDimension) {
// variable type
const type = types_1.str2num(variable.type);
const width = variable.size ? variable.size / types_1.num2bytes(type) : 1; // size of the data
// TODO streaming data
var size = recordDimension.length; // iterates over the data
var data = new Array(size);
const step = recordDimension.recordStep;
for (var i = 0; i < size; i++) {
var currentOffset = buffer.offset;
data[i] = types_1.readType(buffer, type, width);
buffer.seek(currentOffset + step);
}
return data;
}
var nonRecord_1 = nonRecord;
var record_1 = record;
var data = {
nonRecord: nonRecord_1,
record: record_1
};
const ZERO = 0;
const NC_DIMENSION = 10;
const NC_VARIABLE = 11;
const NC_ATTRIBUTE = 12;
/**
* Read the header of the file
* @ignore
* @param {IOBuffer} buffer - Buffer for the file data
* @param {number} version - Version of the file
* @return {object} - Object with the fields:
* * `recordDimension`: Number with the length of record dimension
* * `dimensions`: List of dimensions
* * `globalAttributes`: List of global attributes
* * `variables`: List of variables
*/
function header(buffer, version) {
// Length of record dimension
// sum of the varSize's of all the record variables.
var header = {
recordDimension: {
length: buffer.readUint32()
}
}; // Version
header.version = version; // List of dimensions
var dimList = dimensionsList(buffer);
header.recordDimension.id = dimList.recordId; // id of the unlimited dimension
header.recordDimension.name = dimList.recordName; // name of the unlimited dimension
header.dimensions = dimList.dimensions; // List of global attributes
header.globalAttributes = attributesList(buffer); // List of variables
var variables = variablesList(buffer, dimList.recordId, version);
header.variables = variables.variables;
header.recordDimension.recordStep = variables.recordStep;
return header;
}
const NC_UNLIMITED = 0;
/**
* List of dimensions
* @ignore
* @param {IOBuffer} buffer - Buffer for the file data
* @return {object} - Ojbect containing the following properties:
* * `dimensions` that is an array of dimension object:
* * `name`: String with the name of the dimension
* * `size`: Number with the size of the dimension dimensions: dimensions
* * `recordId`: the id of the dimension that has unlimited size or undefined,
* * `recordName`: name of the dimension that has unlimited size
*/
function dimensionsList(buffer) {
var recordId, recordName;
const dimList = buffer.readUint32();
if (dimList === ZERO) {
utils.notNetcdf(buffer.readUint32() !== ZERO, 'wrong empty tag for list of dimensions');
return [];
} else {
utils.notNetcdf(dimList !== NC_DIMENSION, 'wrong tag for list of dimensions'); // Length of dimensions
const dimensionSize = buffer.readUint32();
var dimensions = new Array(dimensionSize);
for (var dim = 0; dim < dimensionSize; dim++) {
// Read name
var name = utils.readName(buffer); // Read dimension size
const size = buffer.readUint32();
if (size === NC_UNLIMITED) {
// in netcdf 3 one field can be of size unlimmited
recordId = dim;
recordName = name;
}
dimensions[dim] = {
name: name,
size: size
};
}
}
return {
dimensions: dimensions,
recordId: recordId,
recordName: recordName
};
}
/**
* List of attributes
* @ignore
* @param {IOBuffer} buffer - Buffer for the file data
* @return {Array} - List of attributes with:
* * `name`: String with the name of the attribute
* * `type`: String with the type of the attribute
* * `value`: A number or string with the value of the attribute
*/
function attributesList(buffer) {
const gAttList = buffer.readUint32();
if (gAttList === ZERO) {
utils.notNetcdf(buffer.readUint32() !== ZERO, 'wrong empty tag for list of attributes');
return [];
} else {
utils.notNetcdf(gAttList !== NC_ATTRIBUTE, 'wrong tag for list of attributes'); // Length of attributes
const attributeSize = buffer.readUint32();
var attributes = new Array(attributeSize);
for (var gAtt = 0; gAtt < attributeSize; gAtt++) {
// Read name
var name = utils.readName(buffer); // Read type
var type = buffer.readUint32();
utils.notNetcdf(type < 1 || type > 6, `non valid type ${type}`); // Read attribute
var size = buffer.readUint32();
var value = types_1.readType(buffer, type, size); // Apply padding
utils.padding(buffer);
attributes[gAtt] = {
name: name,
type: types_1.num2str(type),
value: value
};
}
}
return attributes;
}
/**
* List of variables
* @ignore
* @param {IOBuffer} buffer - Buffer for the file data
* @param {number} recordId - Id of the unlimited dimension (also called record dimension)
* This value may be undefined if there is no unlimited dimension
* @param {number} version - Version of the file
* @return {object} - Number of recordStep and list of variables with:
* * `name`: String with the name of the variable
* * `dimensions`: Array with the dimension IDs of the variable
* * `attributes`: Array with the attributes of the variable
* * `type`: String with the type of the variable
* * `size`: Number with the size of the variable
* * `offset`: Number with the offset where of the variable begins
* * `record`: True if is a record variable, false otherwise (unlimited size)
*/
function variablesList(buffer, recordId, version) {
const varList = buffer.readUint32();
var recordStep = 0;
if (varList === ZERO) {
utils.notNetcdf(buffer.readUint32() !== ZERO, 'wrong empty tag for list of variables');
return [];
} else {
utils.notNetcdf(varList !== NC_VARIABLE, 'wrong tag for list of variables'); // Length of variables
const variableSize = buffer.readUint32();
var variables = new Array(variableSize);
for (var v = 0; v < variableSize; v++) {
// Read name
var name = utils.readName(buffer); // Read dimensionality of the variable
const dimensionality = buffer.readUint32(); // Index into the list of dimensions
var dimensionsIds = new Array(dimensionality);
for (var dim = 0; dim < dimensionality; dim++) {
dimensionsIds[dim] = buffer.readUint32();
} // Read variables size
var attributes = attributesList(buffer); // Read type
var type = buffer.readUint32();
utils.notNetcdf(type < 1 && type > 6, `non valid type ${type}`); // Read variable size
// The 32-bit varSize field is not large enough to contain the size of variables that require
// more than 2^32 - 4 bytes, so 2^32 - 1 is used in the varSize field for such variables.
const varSize = buffer.readUint32(); // Read offset
var offset = buffer.readUint32();
if (version === 2) {
utils.notNetcdf(offset > 0, 'offsets larger than 4GB not supported');
offset = buffer.readUint32();
}
let record = false; // Count amount of record variables
if (typeof recordId !== 'undefined' && dimensionsIds[0] === recordId) {
recordStep += varSize;
record = true;
}
variables[v] = {
name: name,
dimensions: dimensionsIds,
attributes,
type: types_1.num2str(type),
size: varSize,
offset,
record
};
}
}
return {
variables: variables,
recordStep: recordStep
};
}
var header_1 = header;
function toString$2() {
let result = [];
result.push('DIMENSIONS');
for (let dimension of this.dimensions) {
result.push(` ${dimension.name.padEnd(30)} = size: ${dimension.size}`);
}
result.push('');
result.push('GLOBAL ATTRIBUTES');
for (let attribute of this.globalAttributes) {
result.push(` ${attribute.name.padEnd(30)} = ${attribute.value}`);
}
let variables = JSON.parse(JSON.stringify(this.variables));
result.push('');
result.push('VARIABLES:');
for (let variable of variables) {
variable.value = this.getDataVariable(variable);
let stringify = JSON.stringify(variable.value);
if (stringify.length > 50) stringify = stringify.substring(0, 50);
if (!isNaN(variable.value.length)) {
stringify += ` (length: ${variable.value.length})`;
}
result.push(` ${variable.name.padEnd(30)} = ${stringify}`);
}
return result.join('\n');
}
var toString_1 = toString$2;
var require$$0 = /*@__PURE__*/getAugmentedNamespace(IOBuffer$2);
const {
IOBuffer
} = require$$0;
/**
* Reads a NetCDF v3.x file
* https://www.unidata.ucar.edu/software/netcdf/docs/file_format_specifications.html
* @param {ArrayBuffer} data - ArrayBuffer or any Typed Array (including Node.js' Buffer from v4) with the data
* @constructor
*/
class NetCDFReader {
constructor(data) {
const buffer = new IOBuffer(data);
buffer.setBigEndian(); // Validate that it's a NetCDF file
utils.notNetcdf(buffer.readChars(3) !== 'CDF', 'should start with CDF'); // Check the NetCDF format
const version = buffer.readByte();
utils.notNetcdf(version > 2, 'unknown version'); // Read the header
this.header = header_1(buffer, version);
this.buffer = buffer;
}
/**
* @return {string} - Version for the NetCDF format
*/
get version() {
if (this.header.version === 1) {
return 'classic format';
} else {
return '64-bit offset format';
}
}
/**
* @return {object} - Metadata for the record dimension
* * `length`: Number of elements in the record dimension
* * `id`: Id number in the list of dimensions for the record dimension
* * `name`: String with the name of the record dimension
* * `recordStep`: Number with the record variables step size
*/
get recordDimension() {
return this.header.recordDimension;
}
/**
* @return {Array} - List of dimensions with:
* * `name`: String with the name of the dimension
* * `size`: Number with the size of the dimension
*/
get dimensions() {
return this.header.dimensions;
}
/**
* @return {Array} - List of global attributes with:
* * `name`: String with the name of the attribute
* * `type`: String with the type of the attribute
* * `value`: A number or string with the value of the attribute
*/
get globalAttributes() {
return this.header.globalAttributes;
}
/**
* Returns the value of an attribute
* @param {string} attributeName
* @return {string} Value of the attributeName or null
*/
getAttribute(attributeName) {
const attribute = this.globalAttributes.find(val => val.name === attributeName);
if (attribute) return attribute.value;
return null;
}
/**
* Returns the value of a variable as a string
* @param {string} variableName
* @return {string} Value of the variable as a string or null
*/
getDataVariableAsString(variableName) {
const variable = this.getDataVariable(variableName);
if (variable) return variable.join('');
return null;
}
/**
* @return {Array} - List of variables with:
* * `name`: String with the name of the variable
* * `dimensions`: Array with the dimension IDs of the variable
* * `attributes`: Array with the attributes of the variable
* * `type`: String with the type of the variable
* * `size`: Number with the size of the variable
* * `offset`: Number with the offset where of the variable begins
* * `record`: True if is a record variable, false otherwise
*/
get variables() {
return this.header.variables;
}
toString() {
return toString_1.call(this);
}
/**
* Retrieves the data for a given variable
* @param {string|object} variableName - Name of the variable to search or variable object
* @return {Array} - List with the variable values
*/
getDataVariable(variableName) {
let variable;
if (typeof variableName === 'string') {
// search the variable
variable = this.header.variables.find(function (val) {
return val.name === variableName;
});
} else {
variable = variableName;
} // throws if variable not found
utils.notNetcdf(variable === undefined, `variable not found: ${variableName}`); // go to the offset position
this.buffer.seek(variable.offset);
if (variable.record) {
// record variable case
return data.record(this.buffer, variable, this.header.recordDimension);
} else {
// non-record variable case
return data.nonRecord(this.buffer, variable);
}
}
/**
* Check if a dataVariable exists
* @param {string} variableName - Name of the variable to find
* @return {boolean}
*/
dataVariableExists(variableName) {
const variable = this.header.variables.find(function (val) {
return val.name === variableName;
});
return variable !== undefined;
}
/**
* Check if an attribute exists
* @param {string} attributeName - Name of the attribute to find
* @return {boolean}
*/
attributeExists(attributeName) {
const attribute = this.globalAttributes.find(val => val.name === attributeName);
return attribute !== undefined;
}
}
var src$2 = NetCDFReader;
/* reader.toString() provides the following information
GLOBAL ATTRIBUTES
dataset_completeness = C1+C2
ms_template_revision = 1.0.1
netcdf_revision = 2.3.2
languages = English
administrative_comments = 1% CH2Cl2
dataset_origin = Santa Clara, CA
netcdf_file_date_time_stamp = 20161012052159+0200
experiment_title = P071 Essence super BP
experiment_date_time_stamp = 20070923040800+0200
operator_name = SC
external_file_ref_0 = FIRE_RTL.M
experiment_type = Centroided Mass Spectrum
number_of_times_processed = 1
number_of_times_calibrated = 0
sample_state = Other State
test_separation_type = No Chromatography
test_ms_inlet = Capillary Direct
test_ionization_mode = Electron Impact
test_ionization_polarity = Positive Polarity
test_detector_type = Electron Multiplier
test_resolution_type = Constant Resolution
test_scan_function = Mass Scan
test_scan_direction = Up
test_scan_law = Linear
raw_data_mass_format = Float
raw_data_time_format = Short
raw_data_intensity_format = Float
VARIABLES:
error_log = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 64)
a_d_sampling_rate = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 6401)
a_d_coaddition_factor = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 6402)
scan_acquisition_time = [5.25,5.84,6.428999999999999,7.019,7.609,8.199,8.7 (length: 6401)
scan_duration = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 6401)
inter_scan_time = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 6401)
resolution = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 6401)
actual_scan_number = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 (length: 6401)
total_intensity = [3134,3157,3085,3134,3093,3113,3061,3057,3030,3166 (length: 6401)
mass_range_min = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 6401)
mass_range_max = [206.89999389648438,206.89999389648438,207,207.100 (length: 6401)
time_range_min = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 6401)
time_range_max = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 6401)
scan_index = [0,11,22,33,44,55,66,76,88,99,111,122,134,145,156, (length: 6401)
point_count = [11,11,11,11,11,11,10,12,11,12,11,12,11,11,11,11,1 (length: 6401)
flag_count = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 6401)
mass_values = [16,17,18.100000381469727,28,32,35,36,38,40,44.099 (length: 157201)
time_values = [9.969209968386869e+36,9.969209968386869e+36,9.969 (length: 157201)
intensity_values = [37,293,1243,737,420,45,196,72,22,35,34,28,299,123 (length: 157201)
instrument_name = ["G","a","s"," ","C","h","r","o","m","a","t","o"," (length: 32)
instrument_id = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
instrument_mfr = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
instrument_model = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
instrument_serial_no = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
instrument_sw_version = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
instrument_fw_version = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
instrument_os_version = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
instrument_app_version = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
instrument_comments = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
*/
function agilentGCMS(reader) {
const time = reader.getDataVariable('scan_acquisition_time');
const tic = reader.getDataVariable('total_intensity'); // variables to get the mass-intensity values
const pointCount = reader.getDataVariable('point_count');
const massValues = reader.getDataVariable('mass_values');
const intensityValues = reader.getDataVariable('intensity_values');
let ms = new Array(pointCount.length);
let index = 0;
for (let i = 0; i < ms.length; i++) {
let size = pointCount[i];
ms[i] = [new Array(size), new Array(size)];
for (let j = 0; j < size; j++) {
ms[i][0][j] = massValues[index];
ms[i][1][j] = intensityValues[index++];
}
}
return {
times: time,
series: [{
name: 'tic',
dimension: 1,
data: tic
}, {
name: 'ms',
dimension: 2,
data: ms
}]
};
}
var agilentGCMS_1 = agilentGCMS;
/* reader.toString() provides the following information
GLOBAL ATTRIBUTES
dataset_completeness = C1+C2
ms_template_revision = 1.0.1
netcdf_revision = 2.3.2
languages = English
netcdf_file_date_time_stamp = 20170428032023+0000
experiment_title = MS51762A16
11829FC03_3__60_40
experiment_date_time_stamp = 20160930202145-0001
operator_name = Begemann/Eikenberg/Roettger
pre_experiment_program_name = otofControl 3.4.16.0
post_experiment_program_name = Bruker Compass DataAnalysis 4.2
source_file_reference = X:\2016\MS5\1700\MS51762A16.d
source_file_format = Bruker Daltonics Data File
experiment_type = Centroided Mass Spectrum
sample_state = Other State
test_separation_type = No Chromatography
test_ms_inlet = Direct Inlet Probe
test_ionization_mode = Electrospray Ionization
test_ionization_polarity = Positive Polarity
test_detector_type = Electron Multiplier
test_resolution_type = Proportional Resolution
test_scan_function = Mass Scan
test_scan_direction = Up
test_scan_law = Linear
raw_data_mass_format = Double
raw_data_time_format = Float
raw_data_intensity_format = Float
units = Seconds
scale_factor = 1
VARIABLES:
error_log = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 64)
a_d_sampling_rate = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 4513)
a_d_coaddition_factor = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 4514)
scan_acquisition_time = [0.329,0.73,1.132,1.534,1.936,2.337,2.739,3.14,3.5 (length: 4513)
scan_duration = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 4513)
inter_scan_time = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 4513)
resolution = [106.6623112889557,110.7855343519544,104.407495112 (length: 4513)
actual_scan_number = [0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34, (length: 4513)
total_intensity = [5297.4945068359375,6172.123912811279,5934.7557412 (length: 4513)
mass_range_min = [49.99999997418507,49.99999997418507,49.9999999741 (length: 4513)
mass_range_max = [1599.9999564432276,1599.9999564432276,1599.999956 (length: 4513)
time_range_min = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 4513)
time_range_max = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 4513)
scan_index = [0,60,128,195,265,324,399,472,542,596,671,738,803, (length: 4513)
point_count = [60,68,67,70,59,75,73,70,54,75,67,65,64,73,56,69,6 (length: 4513)
flag_count = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 4513)
mass_values = [51.53516375878996,95.32974890044508,106.334477231 (length: 1176507)
intensity_values = [76.99999237060547,80,90,78.99799346923828,80.9352 (length: 1176507)
instrument_name = ["m","i","c","r","O","T","O","F",""," "," "," "," (length: 32)
instrument_id = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
instrument_mfr = ["B","r","u","k","e","r"," ","D","a","l","t","o"," (length: 32)
instrument_model = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
instrument_serial_no = ["2","1","3","7","5","0",".","1","0","3","5","9"," (length: 32)
instrument_sw_version = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
instrument_fw_version = [""," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
instrument_os_version = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
instrument_app_version = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
instrument_comments = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
*/
function finniganGCMS$1(reader) {
const time = reader.getDataVariable('scan_acquisition_time');
const tic = reader.getDataVariable('total_intensity'); // variables to get the mass-intensity values
let scanIndex = reader.getDataVariable('scan_index');
const massValues = reader.getDataVariable('mass_values');
const intensityValues = reader.getDataVariable('intensity_values');
scanIndex.push(massValues.length);
let ms = new Array(time.length);
let index = 0;
for (let i = 0; i < ms.length; i++) {
let size = scanIndex[i + 1] - scanIndex[i];
ms[i] = [new Array(size), new Array(size)];
for (let j = 0; j < size; j++) {
ms[i][0][j] = massValues[index];
ms[i][1][j] = intensityValues[index++];
}
}
return {
times: time,
series: [{
name: 'tic',
dimension: 1,
data: tic
}, {
name: 'ms',
dimension: 2,
data: ms
}]
};
}
var brukerGCMS = finniganGCMS$1;
/* reader.toString() provides the following information
GLOBAL ATTRIBUTES
dataset_completeness = C1+C2
aia_template_revision = 1.0
netcdf_revision = 2.3
languages = English only
injection_date_time_stamp = 20181030174305+0000
HP_injection_time = 30-Oct-18, 17:43:05
experiment_title = SequenceLine: 1 Inj: 1
operator_name = SYSTEM
separation_experiment_type = liquid chromatography
source_file_reference = C:\CHEM32\1\DATA\MINGMING\MW-1-MEO-I IC-90 2018-10-30 17-42-13\MW-2-6-6 IC 90.D
sample_name = MW-2-6-6 IC 90
sample_id =
detector_unit = mAU
detection_method_name = POS 3 IC 90-10 31 MIN.M
detector_name = DAD1 A, Sig=254,4 Ref=360,100
retention_unit = seconds
VARIABLES:
detector_maximum_value = [130.9263458251953] (length: 1)
detector_minimum_value = [-0.1758841574192047] (length: 1)
actual_run_time_length = [1860] (length: 1)
actual_delay_time = [0.012000000104308128] (length: 1)
actual_sampling_interval = [0.4000000059604645] (length: 1)
ordinate_values = [-0.07588416337966919,-0.07525086402893066,-0.0740 (length: 4651)
peak_retention_time = [196.0651397705078,332.5663757324219,527.549865722 (length: 8)
peak_start_time = [186.81199645996094,239.21200561523438,502.4119873 (length: 8)
peak_end_time = [220.81201171875,471.5176696777344,572.47869873046 (length: 8)
peak_width = [4.974428176879883,62.90694808959961,11.9328641891 (length: 8)
peak_area = [556.7650146484375,419.825439453125,66.56610107421 (length: 8)
peak_area_percent = [7.0321502685546875,5.302552223205566,0.8407546877 (length: 8)
peak_height = [100.07515716552734,5.1860527992248535,4.827196121 (length: 8)
peak_height_percent = [29.76352310180664,1.5423927307128906,1.4356645345 (length: 8)
peak_asymmetry = [1.4555920362472534,0.8351489901542664,1.707817316 (length: 8)
baseline_start_time = [186.81199645996094,239.21200561523438,502.4119873 (length: 8)
baseline_start_value = [1.9561424255371094,0.9857341647148132,1.127734780 (length: 8)
baseline_stop_time = [220.81201171875,471.5176696777344,572.47869873046 (length: 8)
baseline_stop_value = [1.1907591819763184,1.10896897315979,1.18347382545 (length: 8)
peak_start_detection_code = ["B","","B","","B","","B","","V","","B","","B","", (length: 16)
peak_stop_detection_code = ["B","","B","","B","","V","","B","","B","","B","", (length: 16)
migration_time = [196.0651397705078,332.5663757324219,527.549865722 (length: 8)
peak_area_square_root = [23.595869064331055,20.489643096923828,8.158804893 (length: 8)
manually_reintegrated_peaks = [0,0,0,0,0,0,0,0] (length: 8)
*/
function agilentHPLC(reader) {
const intensities = reader.getDataVariable('ordinate_values');
const numberPoints = intensities.length;
const detector = reader.getAttribute('detector_name');
let channel;
if (detector.match(/dad/i)) {
channel = `uv${Number(detector.replace(/.*Sig=([0-9]+).*/, '$1'))}`;
} else if (detector.match(/tic/i)) {
channel = 'tic';
} else {
channel = 'unknown';
}
const delayTime = reader.getDataVariable('actual_delay_time')[0];
const runtimeLength = reader.getDataVariable('actual_run_time_length')[0];
let samplingInterval;
if (reader.dataVariableExists('actual_sampling_interval')) {
samplingInterval = reader.getDataVariable('actual_sampling_interval')[0];
if (Math.abs(delayTime + samplingInterval * numberPoints - runtimeLength) > 3) {
throw new Error('The expected last time does not correspond to the runtimeLength');
}
} else {
samplingInterval = (runtimeLength - delayTime) / numberPoints;
}
let times = [];
let time = delayTime;
for (let i = 0; i < numberPoints; i++) {
times.push(time);
time += samplingInterval;
}
return {
times,
series: [{
name: channel,
dimension: 1,
data: intensities
}]
};
}
var agilentHPLC_1 = agilentHPLC;
/* reader.toString() provides the following information
GLOBAL ATTRIBUTES
dataset_completeness = C1+C2
ms_template_revision = 1.0.1
administrative_comments =
dataset_owner =
experiment_title =
experiment_date_time_stamp = 20150902041002+0100
netcdf_file_date_time_stamp = 20151026063419+0000
experiment_type = Centroided Mass Spectrum
netcdf_revision = 2.3.2
operator_name = DSQ
source_file_reference = G:\FCO\FCO_CIO\K2\MP2013\T1 IL database 2013\9. IL Data Entry\12_HU_HIFS\IL database\RAW files\Lukoil-Disel-150901.RAW
source_file_date_time_stamp = 20150902041002+0100
source_file_format = Finnigan
languages = English
external_file_ref_0 =
instrument_number = 1
sample_prep_comments =
sample_comments = Lukoil Disel 0,5 % CS2 1 % inkt
test_separation_type =
test_ms_inlet =
test_ionization_mode =
test_ionization_polarity = Positive Polarity
test_detector_type = Conversion Dynode Electron Multiplier
test_scan_function = Mass Scan
test_scan_direction =
test_scan_law = Linear
number_of_scans = 11832
raw_data_mass_format = Double
raw_data_intensity_format = Long
actual_run_time = 3519.6410000000005
actual_delay_time = 82.328
global_mass_min = 0
global_mass_max = 450
calibrated_mass_min = 0
calibrated_mass_max = 0
mass_axis_label = M/Z
intensity_axis_label = Abundance
VARIABLES:
error_log = ["","","","","","","","","","","","","","","",""," (length: 64)
instrument_name = ["L","C","Q","","","","","","","","","","","",""," (length: 32)
instrument_id = ["","","","","","","","","","","","","","","",""," (length: 32)
instrument_mfr = ["F","i","n","n","i","g","a","n","-","M","A","T"," (length: 32)
instrument_model = ["","","","","","","","","","","","","","","",""," (length: 32)
instrument_sw_version = ["3",".","1","","","","","","","","","","","",""," (length: 32)
instrument_os_version = ["W","i","n","d","o","w","s"," ","V","i","s","t"," (length: 32)
scan_index = [0,34,74,113,145,177,211,239,267,299,341,374,400,4 (length: 11832)
point_count = [34,40,39,32,32,34,28,28,32,42,33,26,29,34,31,28,2 (length: 11832)
flag_count = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 11832)
a_d_sampling_rate = [1000,1000,1000,1000,1000,1000,1000,1000,1000,1000 (length: 11832)
scan_acquisition_time = [82.328,82.625,82.76599999999999,83.063,83.188,83. (length: 11832)
scan_duration = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 11832)
mass_range_min = [35,16,35,16,35,16,35,16,35,16,35,16,35,16,35,16,3 (length: 11832)
mass_range_max = [450,150,450,150,450,150,450,150,450,150,450,150,4 (length: 11832)
scan_type = [65537,65537,65537,65537,65537,65537,65537,65537,6 (length: 11832)
resolution = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 11832)
total_intensity = [375220,1054339,228245,576718,58280,288629,29815,1 (length: 11832)
mass_values = [36.3023681640625,36.98402404785156,38.08326721191 (length: 1366002)
intensity_values = [335,287,331,266,2423,448,9009,833,261,661,4003,21 (length: 1366002)
*/
function finniganGCMS(reader) {
const time = reader.getDataVariable('scan_acquisition_time');
const tic = reader.getDataVariable('total_intensity'); // variables to get the mass-intensity values
let scanIndex = reader.getDataVariable('scan_index');
const massValues = reader.getDataVariable('mass_values');
const intensityValues = reader.getDataVariable('intensity_values');
scanIndex.push(massValues.length);
let ms = new Array(time.length);
let index = 0;
for (let i = 0; i < ms.length; i++) {
let size = scanIndex[i + 1] - scanIndex[i];
ms[i] = [new Array(size), new Array(size)];
for (let j = 0; j < size; j++) {
ms[i][0][j] = massValues[index];
ms[i][1][j] = intensityValues[index++];
}
}
return {
times: time,
series: [{
name: 'tic',
dimension: 1,
data: tic
}, {
name: 'ms',
dimension: 2,
data: ms
}]
};
}
var finniganGCMS_1 = finniganGCMS;
/* reader.toString() provides the following information
GLOBAL ATTRIBUTES
dataset_completeness = C1+C2
ms_template_revision = 1.0.1
netcdf_revision = 2.3.2
languages = English
administrative_comments =
netcdf_file_date_time_stamp = 20180913165502+0000
experiment_title =
experiment_date_time_stamp = 20180910165319+0000
operator_name = Admin
source_file_reference = D:\GCMSsolution\Data\Chromatograms\Cato\bormann_CB000_Test2.qgd
source_file_format = Shimadzu GCMSsolution
source_file_date_time_stamp = 20180910165319+0000
experiment_type = Centroided Mass Spectrum
sample_internal_id =
sample_comments =
sample_state = Other State
test_separation_type = Gas-Solid Chromatography
test_ms_inlet = Other Probe
test_ionization_mode = Electron Impact
test_ionization_polarity = Positive Polarity
test_electron_energy = 70
test_detector_type = Electron Multiplier
test_resolution_type = Constant Resolution
test_scan_function = Mass Scan
test_scan_direction = Up
test_scan_law = Quadratic
test_scan_time = 0
raw_data_mass_format = Double
raw_data_time_format = Long
raw_data_intensity_format = Long
units = Seconds
scale_factor = 1
long_name = Seconds
starting_scan_number = 0
actual_run_time_length = 1289
actual_delay_time = 0
raw_data_uniform_sampling_flag = 1
VARIABLES:
error_log = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 64)
a_d_sampling_rate = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 3820)
a_d_coaddition_factor = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 3820)
scan_acquisition_time = [144,144.3,144.6,144.9,145.2,145.5,145.8,146.1,146 (length: 3820)
scan_duration = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 3820)
inter_scan_time = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 3820)
resolution = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 3820)
actual_scan_number = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,2 (length: 3820)
total_intensity = [63566,61702,61873,59738,58321,59001,59364,59871,6 (length: 3820)
mass_range_min = [35,35,35,35,35,35,35,35,35,35,35,35,35,35,35,35,3 (length: 3820)
mass_range_max = [500,500,500,500,500,500,500,500,500,500,500,500,5 (length: 3820)
time_range_min = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 3820)
time_range_max = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 3820)
scan_index = [0,466,932,1398,1863,2329,2795,3260,3726,4192,4658 (length: 3820)
point_count = [466,466,466,465,466,466,465,466,466,466,466,466,4 (length: 3820)
flag_count = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 3820)
mass_values = [35,36,37.1,38.1,39.1,40.15,41.1,42.1,43.15,44.1,4 (length: 1779397)
intensity_values = [26,111,412,785,3098,485,5772,7391,11213,711,687,1 (length: 1779397)
instrument_name = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
instrument_id = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
instrument_mfr = ["S","h","i","m","a","d","z","u"," ","C","o","r"," (length: 32)
instrument_model = ["G","C","M","S","","","","","","","","","","","", (length: 32)
instrument_serial_no = ["","","","","","","","","","","","","","","",""," (length: 32)
instrument_sw_version = ["4",".","2","0","","","","","","","","","","","", (length: 32)
instrument_fw_version = ["G","C","M","S","-","Q","P","2","0","1","0","1"," (length: 32)
instrument_os_version = ["W","i","n","d","o","w","s","","","","","","","", (length: 32)
instrument_app_version = ["G","C","M","S","s","o","l","u","t","i","o","n"," (length: 32)
instrument_comments = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
*/
function shimadzuGCMS(reader) {
const time = reader.getDataVariable('scan_acquisition_time');
const tic = reader.getDataVariable('total_intensity'); // variables to get the mass-intensity values
let scanIndex = reader.getDataVariable('scan_index');
const massValues = reader.getDataVariable('mass_values');
const intensityValues = reader.getDataVariable('intensity_values');
scanIndex.push(massValues.length);
let ms = new Array(time.length);
let index = 0;
for (let i = 0; i < ms.length; i++) {
let size = scanIndex[i + 1] - scanIndex[i];
ms[i] = [new Array(size), new Array(size)];
for (let j = 0; j < size; j++) {
ms[i][0][j] = massValues[index];
ms[i][1][j] = intensityValues[index++];
}
}
return {
times: time,
series: [{
name: 'tic',
dimension: 1,
data: tic
}, {
name: 'ms',
dimension: 2,
data: ms
}]
};
}
var shimadzuGCMS_1 = shimadzuGCMS;
/* reader.toString() provides the following information
DIMENSIONS
point_number = size: 0
scan_number = size: 60
error_number = size: 1
_64_byte_string = size: 64
GLOBAL ATTRIBUTES
dataset_completeness = C1
ms_template_revision = 1.0.1
netcdf_revision = 4.2
languages = English
administrative_comments =
netcdf_file_date_time_stamp = 202003031432433600000
experiment_date_time_stamp = 202003031432433600000
source_file_reference = JC-012_cleavage test_Scan1_is1.datx 2020.03.03 14:32:43
source_file_format = Advion ExpressIon Compact Mass Spectrometer Data System
source_file_date_time_stamp = 202003031432433600000
experiment_title =
experiment_type = Continuum Mass Spectrum
test_ionization_mode = Electrospray Ionization
test_ionization_polarity = Positive Polarity
sample_state = Other State
test_separation_type = No Chromatography
test_ms_inlet = Direct Inlet Probe
test_detector_type = Electron Multiplier
test_resolution_type = Constant Resolution
test_scan_function = Mass Scan
test_scan_direction = Up
test_scan_law = Linear
raw_data_mass_format = Float
raw_data_time_format = Double
raw_data_intensity_format = Float
units = Seconds
global_mass_min = 9.949999809265137
global_mass_max = 1199.75
actual_run_time_length = 133.46099853515625
starting_scan_number = 1
actual_delay_time = 0
raw_data_uniform_sampling_flag = 0
VARIABLES:
error_log = ["","","","","","","","","","","","","","","",""," (length: 64)
scan_index = [0,3096,6282,9865,13409,16765,20281,23603,27099,30 (length: 60)
point_count = [3096,3186,3583,3544,3356,3516,3322,3496,3351,3031 (length: 60)
flag_count = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 60)
actual_scan_number = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 (length: 60)
a_d_coaddition_factor = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 60)
a_d_sampling_rate = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 60)
inter_scan_time = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 60)
mass_range_min = [9.949999809265137,9.949999809265137,9.94999980926 (length: 60)
mass_range_max = [164.6999969482422,169.1999969482422,189.050003051 (length: 60)
scan_acquisition_time = [0.08100000023841858,2.3420000076293945,4.60300016 (length: 60)
scan_duration = [2.261000007390976,2.261000156402588,2.25999975204 (length: 60)
resolution = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 60)
time_range_min = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 60)
time_range_max = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 60)
total_intensity = [4498210816,4468554240,5001547264,5405233152,50000 (length: 60)
mass_values = [9.949999809265137,83.5,83.55000305175781,83.59999 (length: 199393)
intensity_values = [0,818716,462148,0,735558,952901,0,165241,421829,0 (length: 199393)
*/
function advionGCMS(reader) {
const time = reader.getDataVariable('scan_acquisition_time');
const tic = reader.getDataVariable('total_intensity'); // variables to get the mass-intensity values
let scanIndex = reader.getDataVariable('scan_index');
const massValues = reader.getDataVariable('mass_values');
const intensityValues = reader.getDataVariable('intensity_values');
scanIndex.push(massValues.length);
let ms = new Array(time.length);
let index = 0;
for (let i = 0; i < ms.length; i++) {
let size = scanIndex[i + 1] - scanIndex[i];
ms[i] = [new Array(size), new Array(size)];
for (let j = 0; j < size; j++) {
ms[i][0][j] = massValues[index];
ms[i][1][j] = intensityValues[index++];
}
}
return {
times: time,
series: [{
name: 'tic',
dimension: 1,
data: tic
}, {
name: 'ms',
dimension: 2,
data: ms
}]
};
}
var advionGCMS_1 = advionGCMS;
function aiaTemplate(reader) {
let time = [];
const tic = reader.getDataVariable('ordinate_values'); // variables to get the time
const delayTime = Number(reader.getDataVariable('actual_delay_time'));
const interval = Number(reader.getDataVariable('actual_sampling_interval'));
let currentTime = delayTime;
for (let i = 0; i < tic.length; i++) {
time.push(currentTime);
currentTime += interval;
}
return {
times: time,
series: [{
name: 'tic',
dimension: 1,
data: tic
}]
};
}
var aiaTemplate_1 = aiaTemplate;
/**
* Reads a NetCDF file and returns a formatted JSON with the data from it
* @param {ArrayBuffer} data - ArrayBuffer or any Typed Array (including Node.js' Buffer from v4) with the data
* @param {object} [options={}]
* @param {boolean} [options.meta] - add meta information
* @param {boolean} [options.variables] -add variables information
* @return {{times, series}} - JSON with the time, TIC and mass spectra values
*/
function netcdfGcms(data, options = {}) {
let reader = new src$2(data);
const globalAttributes = reader.globalAttributes;
let instrument_mfr = reader.dataVariableExists('instrument_mfr') && reader.getDataVariableAsString('instrument_mfr');
let dataset_origin = reader.attributeExists('dataset_origin');
let mass_values = reader.dataVariableExists('mass_values');
let detector_name = reader.getAttribute('detector_name');
let aia_template_revision = reader.attributeExists('aia_template_revision');
let source_file_format = reader.getAttribute('source_file_format');
let ans;
if (mass_values && dataset_origin) {
ans = agilentGCMS_1(reader);
} else if (mass_values && instrument_mfr && instrument_mfr.match(/finnigan/i)) {
ans = finniganGCMS_1(reader);
} else if (mass_values && instrument_mfr && instrument_mfr.match(/bruker/i)) {
ans = brukerGCMS(reader);
} else if (mass_values && instrument_mfr && instrument_mfr.match(/bruker/i)) {
ans = brukerGCMS(reader);
} else if (mass_values && source_file_format && source_file_format.match(/shimadzu/i)) {
ans = shimadzuGCMS_1(reader);
} else if (mass_values && source_file_format && source_file_format.match(/advion/i)) {
ans = advionGCMS_1(reader);
} else if (detector_name && detector_name.match(/(dad|tic)/i)) {
// diode array agilent HPLC
ans = agilentHPLC_1(reader);
} else if (aia_template_revision) {
ans = aiaTemplate_1(reader);
} else {
throw new TypeError('Unknown file format');
}
if (options.meta) {
ans.meta = addMeta(globalAttributes);
}
if (options.variables) {
ans.variables = addVariables(reader);
}
return ans;
}
/**
* Reads a NetCDF file with Agilent GCMS format and returns a formatted JSON with the data from it
* @param {ArrayBuffer} data - ArrayBuffer or any Typed Array (including Node.js' Buffer from v4) with the data
* @return {{times, series}} - JSON with the time, TIC and mass spectra values
*/
function fromAgilentGCMS(data) {
return agilentGCMS_1(new src$2(data));
}
/**
* Reads a NetCDF file with Agilent HPLC format and returns a formatted JSON with the data from it
* @param {ArrayBuffer} data - ArrayBuffer or any Typed Array (including Node.js' Buffer from v4) with the data
* @return {{times, series}} - JSON with the time, TIC and mass spectra values
*/
function fromAgilentHPLC(data) {
return agilentHPLC_1(new src$2(data));
}
/**
* Reads a NetCDF file with Finnigan format and returns a formatted JSON with the data from it
* @param {ArrayBuffer} data - ArrayBuffer or any Typed Array (including Node.js' Buffer from v4) with the data
* @return {{times, series}} - JSON with the time, TIC and mass spectra values
*/
function fromFinniganGCMS(data) {
return finniganGCMS_1(new src$2(data));
}
function fromAiaTemplate(data) {
return aiaTemplate_1(new src$2(data));
}
function addMeta(globalAttributes) {
let ans = {};
for (const item of globalAttributes) {
ans[item.name] = item.value;
}
return ans;
}
function addVariables(reader) {
for (let variable of reader.variables) {
variable.value = reader.getDataVariable(variable);
}
return reader.variables;
}
var src$1 = netcdfGcms;
var fromAgilentGCMS_1 = fromAgilentGCMS;
var fromAgilentHPLC_1 = fromAgilentHPLC;
var fromFinniganGCMS_1 = fromFinniganGCMS;
var fromAiaTemplate_1 = fromAiaTemplate;
src$1.fromAgilentGCMS = fromAgilentGCMS_1;
src$1.fromAgilentHPLC = fromAgilentHPLC_1;
src$1.fromFinniganGCMS = fromFinniganGCMS_1;
src$1.fromAiaTemplate = fromAiaTemplate_1;
var browserAtob = createCommonjsModule(function (module) {
(function (w) {
function findBest(atobNative) {
// normal window
if ('function' === typeof atobNative) {
return atobNative;
} // browserify (web worker)
if ('function' === typeof Buffer) {
return function atobBrowserify(a) {
//!! Deliberately using an API that's deprecated in node.js because
//!! this file is for browsers and we expect them to cope with it.
//!! Discussion: github.com/node-browser-compat/atob/pull/9
return new Buffer(a, 'base64').toString('binary');
};
} // ios web worker with base64js
if ('object' === typeof w.base64js) {
// bufferToBinaryString
// https://git.coolaj86.com/coolaj86/unibabel.js/blob/master/index.js#L50
return function atobWebWorker_iOS(a) {
var buf = w.base64js.b64ToByteArray(a);
return Array.prototype.map.call(buf, function (ch) {
return String.fromCharCode(ch);
}).join('');
};
}
return function () {
// ios web worker without base64js
throw new Error("You're probably in an old browser or an iOS webworker." + " It might help to include beatgammit's base64-js.");
};
}
var atobBest = findBest(w.atob);
w.atob = atobBest;
if (module && module.exports) {
module.exports = atobBest;
}
})(window);
});
var toByteArray_1 = toByteArray;
var lookup = [];
var revLookup = [];
var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array;
var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
for (var i = 0, len = code.length; i < len; ++i) {
lookup[i] = code[i];
revLookup[code.charCodeAt(i)] = i;
} // Support decoding URL-safe base64 strings, as Node.js does.
// See: https://en.wikipedia.org/wiki/Base64#URL_applications
revLookup['-'.charCodeAt(0)] = 62;
revLookup['_'.charCodeAt(0)] = 63;
function getLens(b64) {
var len = b64.length;
if (len % 4 > 0) {
throw new Error('Invalid string. Length must be a multiple of 4');
} // Trim off extra bytes after placeholder bytes are found
// See: https://github.com/beatgammit/base64-js/issues/42
var validLen = b64.indexOf('=');
if (validLen === -1) validLen = len;
var placeHoldersLen = validLen === len ? 0 : 4 - validLen % 4;
return [validLen, placeHoldersLen];
} // base64 is 4/3 + up to two characters of the original data
function _byteLength(b64, validLen, placeHoldersLen) {
return (validLen + placeHoldersLen) * 3 / 4 - placeHoldersLen;
}
function toByteArray(b64) {
var tmp;
var lens = getLens(b64);
var validLen = lens[0];
var placeHoldersLen = lens[1];
var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen));
var curByte = 0; // if there are placeholders, only get up to the last complete 4 chars
var len = placeHoldersLen > 0 ? validLen - 4 : validLen;
var i;
for (i = 0; i < len; i += 4) {
tmp = revLookup[b64.charCodeAt(i)] << 18 | revLookup[b64.charCodeAt(i + 1)] << 12 | revLookup[b64.charCodeAt(i + 2)] << 6 | revLookup[b64.charCodeAt(i + 3)];
arr[curByte++] = tmp >> 16 & 0xFF;
arr[curByte++] = tmp >> 8 & 0xFF;
arr[curByte++] = tmp & 0xFF;
}
if (placeHoldersLen === 2) {
tmp = revLookup[b64.charCodeAt(i)] << 2 | revLookup[b64.charCodeAt(i + 1)] >> 4;
arr[curByte++] = tmp & 0xFF;
}
if (placeHoldersLen === 1) {
tmp = revLookup[b64.charCodeAt(i)] << 10 | revLookup[b64.charCodeAt(i + 1)] << 4 | revLookup[b64.charCodeAt(i + 2)] >> 2;
arr[curByte++] = tmp >> 8 & 0xFF;
arr[curByte++] = tmp & 0xFF;
}
return arr;
}
/**
* Ensure that the data is string. If it is an ArrayBuffer it will be converted to string using TextDecoder.
* @param {string|ArrayBuffer} blob
* @param {object} [options={}]
* @param {string} [options.encoding='utf8']
* @returns {string}
*/
function ensureString(blob, options = {}) {
const {
encoding = 'utf8'
} = options;
if (ArrayBuffer.isView(blob) || blob instanceof ArrayBuffer) {
const decoder = new TextDecoder(encoding);
return decoder.decode(blob);
}
return blob;
}
const GC_MS_FIELDS = ['TIC', '.RIC', 'SCANNUMBER'];
function complexChromatogram(result) {
let spectra = result.spectra;
let length = spectra.length;
let chromatogram = {
times: new Array(length),
series: {
ms: {
dimension: 2,
data: new Array(length)
}
}
};
let existingGCMSFields = [];
for (let i = 0; i < GC_MS_FIELDS.length; i++) {
let label = convertMSFieldToLabel(GC_MS_FIELDS[i]);
if (spectra[0][label]) {
existingGCMSFields.push(label);
chromatogram.series[label] = {
dimension: 1,
data: new Array(length)
};
}
}
for (let i = 0; i < length; i++) {
let spectrum = spectra[i];
chromatogram.times[i] = spectrum.pageValue;
for (let j = 0; j < existingGCMSFields.length; j++) {
chromatogram.series[existingGCMSFields[j]].data[i] = parseFloat(spectrum[existingGCMSFields[j]]);
}
if (spectrum.data) {
chromatogram.series.ms.data[i] = [spectrum.data.x, spectrum.data.y];
}
}
result.chromatogram = chromatogram;
}
function isMSField(canonicDataLabel) {
return GC_MS_FIELDS.indexOf(canonicDataLabel) !== -1;
}
function convertMSFieldToLabel(value) {
return value.toLowerCase().replace(/[^a-z0-9]/g, '');
}
function convertToFloatArray(stringArray) {
let floatArray = [];
for (let i = 0; i < stringArray.length; i++) {
floatArray.push(parseFloat(stringArray[i]));
}
return floatArray;
}
function fastParseXYData(spectrum, value) {
// TODO need to deal with result
// console.log(value);
// we check if deltaX is defined otherwise we calculate it
let yFactor = spectrum.yFactor;
let deltaX = spectrum.deltaX;
spectrum.isXYdata = true;
let currentData = {
x: [],
y: []
};
spectrum.data = currentData;
let currentX = spectrum.firstX;
let currentY = spectrum.firstY; // we skip the first line
//
let endLine = false;
let ascii;
let i = 0;
for (; i < value.length; i++) {
ascii = value.charCodeAt(i);
if (ascii === 13 || ascii === 10) {
endLine = true;
} else {
if (endLine) break;
}
} // we proceed taking the i after the first line
let newLine = true;
let isDifference = false;
let isLastDifference = false;
let lastDifference = 0;
let isDuplicate = false;
let inComment = false;
let currentValue = 0; // can be a difference or a duplicate
let lastValue = 0; // must be the real last value
let isNegative = false;
let inValue = false;
let skipFirstValue = false;
let decimalPosition = 0;
for (; i <= value.length; i++) {
if (i === value.length) ascii = 13;else ascii = value.charCodeAt(i);
if (inComment) {
// we should ignore the text if we are after $$
if (ascii === 13 || ascii === 10) {
newLine = true;
inComment = false;
}
} else {
// when is it a new value ?
// when it is not a digit, . or comma
// it is a number that is either new or we continue
if (ascii <= 57 && ascii >= 48) {
// a number
inValue = true;
if (decimalPosition > 0) {
currentValue += (ascii - 48) / Math.pow(10, decimalPosition++);
} else {
currentValue *= 10;
currentValue += ascii - 48;
}
} else if (ascii === 44 || ascii === 46) {
// a "," or "."
inValue = true;
decimalPosition++;
} else {
if (inValue) {
// need to process the previous value
if (newLine) {
newLine = false; // we don't check the X value
// console.log("NEW LINE",isDifference, lastDifference);
// if new line and lastDifference, the first value is just a check !
// that we don't check ...
if (isLastDifference) skipFirstValue = true;
} else {
// need to deal with duplicate and differences
if (skipFirstValue) {
skipFirstValue = false;
} else {
if (isDifference) {
lastDifference = isNegative ? 0 - currentValue : currentValue;
isLastDifference = true;
isDifference = false;
} else if (!isDuplicate) {
lastValue = isNegative ? 0 - currentValue : currentValue;
}
let duplicate = isDuplicate ? currentValue - 1 : 1;
for (let j = 0; j < duplicate; j++) {
if (isLastDifference) {
currentY += lastDifference;
} else {
currentY = lastValue;
}
currentData.x.push(currentX);
currentData.y.push(currentY * yFactor);
currentX += deltaX;
}
}
}
isNegative = false;
currentValue = 0;
decimalPosition = 0;
inValue = false;
isDuplicate = false;
} // positive SQZ digits @ A B C D E F G H I (ascii 64-73)
if (ascii < 74 && ascii > 63) {
inValue = true;
isLastDifference = false;
currentValue = ascii - 64;
} else if (ascii > 96 && ascii < 106) {
// negative SQZ digits a b c d e f g h i (ascii 97-105)
inValue = true;
isLastDifference = false;
currentValue = ascii - 96;
isNegative = true;
} else if (ascii === 115) {
// DUP digits S T U V W X Y Z s (ascii 83-90, 115)
inValue = true;
isDuplicate = true;
currentValue = 9;
} else if (ascii > 82 && ascii < 91) {
inValue = true;
isDuplicate = true;
currentValue = ascii - 82;
} else if (ascii > 73 && ascii < 83) {
// positive DIF digits % J K L M N O P Q R (ascii 37, 74-82)
inValue = true;
isDifference = true;
currentValue = ascii - 73;
} else if (ascii > 105 && ascii < 115) {
// negative DIF digits j k l m n o p q r (ascii 106-114)
inValue = true;
isDifference = true;
currentValue = ascii - 105;
isNegative = true;
} else if (ascii === 36 && value.charCodeAt(i + 1) === 36) {
// $ sign, we need to check the next one
inValue = true;
inComment = true;
} else if (ascii === 37) {
// positive DIF digits % J K L M N O P Q R (ascii 37, 74-82)
inValue = true;
isDifference = true;
currentValue = 0;
isNegative = false;
} else if (ascii === 45) {
// a "-"
// check if after there is a number, decimal or comma
let ascii2 = value.charCodeAt(i + 1);
if (ascii2 >= 48 && ascii2 <= 57 || ascii2 === 44 || ascii2 === 46) {
inValue = true;
if (!newLine) isLastDifference = false;
isNegative = true;
}
} else if (ascii === 13 || ascii === 10) {
newLine = true;
inComment = false;
} // and now analyse the details ... space or tabulation
// if "+" we just don't care
}
}
}
}
const removeCommentRegExp = /\$\$.*/;
const peakTableSplitRegExp = /[,\t ]+/;
function parsePeakTable(spectrum, value, result) {
spectrum.isPeaktable = true;
if (!spectrum.variables || Object.keys(spectrum.variables) === 2) {
parseXY(spectrum, value, result);
} else {
parseXYZ(spectrum, value, result);
} // we will add the data in the variables
if (spectrum.variables) {
for (let key in spectrum.variables) {
spectrum.variables[key].data = spectrum.data[key];
}
}
}
function parseXY(spectrum, value, result) {
let currentData = {
x: [],
y: []
};
spectrum.data = currentData; // counts for around 20% of the time
let lines = value.split(/,? *,?[;\r\n]+ */);
for (let i = 1; i < lines.length; i++) {
let values = lines[i].trim().replace(removeCommentRegExp, '').split(peakTableSplitRegExp);
if (values.length % 2 === 0) {
for (let j = 0; j < values.length; j = j + 2) {
// takes around 40% of the time to add and parse the 2 values nearly exclusively because of parseFloat
currentData.x.push(parseFloat(values[j]) * spectrum.xFactor);
currentData.y.push(parseFloat(values[j + 1]) * spectrum.yFactor);
}
} else {
result.logs.push(`Format error: ${values}`);
}
}
}
function parseXYZ(spectrum, value, result) {
let currentData = {};
let variables = Object.keys(spectrum.variables);
let numberOfVariables = variables.length;
variables.forEach(variable => currentData[variable] = []);
spectrum.data = currentData; // counts for around 20% of the time
let lines = value.split(/,? *,?[;\r\n]+ */);
for (let i = 1; i < lines.length; i++) {
let values = lines[i].trim().replace(removeCommentRegExp, '').split(peakTableSplitRegExp);
if (values.length % numberOfVariables === 0) {
for (let j = 0; j < values.length; j++) {
// todo should try to find a xFactor (y, ...)
currentData[variables[j % numberOfVariables]].push(parseFloat(values[j]));
}
} else {
result.logs.push(`Format error: ${values}`);
}
}
}
function parseXYA(spectrum, value) {
let removeSymbolRegExp = /(\(+|\)+|<+|>+|\s+)/g;
spectrum.isXYAdata = true;
let values;
let currentData = {
x: [],
y: []
};
spectrum.data = currentData;
let lines = value.split(/,? *,?[;\r\n]+ */);
for (let i = 1; i < lines.length; i++) {
values = lines[i].trim().replace(removeSymbolRegExp, '').split(',');
currentData.x.push(parseFloat(values[0]));
currentData.y.push(parseFloat(values[1]));
}
}
const toString$1 = Object.prototype.toString;
function isAnyArray(object) {
return toString$1.call(object).endsWith('Array]');
}
var medianQuickselect_min = createCommonjsModule(function (module) {
(function () {
function a(d) {
for (var e = 0, f = d.length - 1, g = void 0, h = void 0, i = void 0, j = c(e, f); !0;) {
if (f <= e) return d[j];
if (f == e + 1) return d[e] > d[f] && b(d, e, f), d[j];
for (g = c(e, f), d[g] > d[f] && b(d, g, f), d[e] > d[f] && b(d, e, f), d[g] > d[e] && b(d, g, e), b(d, g, e + 1), h = e + 1, i = f; !0;) {
do h++; while (d[e] > d[h]);
do i--; while (d[i] > d[e]);
if (i < h) break;
b(d, h, i);
}
b(d, e, i), i <= j && (e = h), i >= j && (f = i - 1);
}
}
var b = function b(d, e, f) {
var _ref;
return _ref = [d[f], d[e]], d[e] = _ref[0], d[f] = _ref[1], _ref;
},
c = function c(d, e) {
return ~~((d + e) / 2);
};
module.exports ? module.exports = a : window.median = a;
})();
});
function median(input) {
if (!isAnyArray(input)) {
throw new TypeError('input must be an array');
}
if (input.length === 0) {
throw new TypeError('input must not be empty');
}
return medianQuickselect_min(input.slice());
}
function convertTo3DZ(spectra) {
let minZ = spectra[0].data.y[0];
let maxZ = minZ;
let ySize = spectra.length;
let xSize = spectra[0].data.x.length;
let z = new Array(ySize);
for (let i = 0; i < ySize; i++) {
z[i] = spectra[i].data.y;
for (let j = 0; j < xSize; j++) {
let value = z[i][j];
if (value < minZ) minZ = value;
if (value > maxZ) maxZ = value;
}
}
const firstX = spectra[0].data.x[0];
const lastX = spectra[0].data.x[spectra[0].data.x.length - 1]; // has to be -2 because it is a 1D array [x,y,x,y,...]
const firstY = spectra[0].pageValue;
const lastY = spectra[ySize - 1].pageValue; // Because the min / max value are the only information about the matrix if we invert
// min and max we need to invert the array
if (firstX > lastX) {
for (let spectrum of z) {
spectrum.reverse();
}
}
if (firstY > lastY) {
z.reverse();
}
const medians = [];
for (let i = 0; i < z.length; i++) {
const row = Float64Array.from(z[i]);
for (let i = 0; i < row.length; i++) {
if (row[i] < 0) row[i] = -row[i];
}
medians.push(median(row));
}
const median$1 = median(medians);
return {
z: z,
minX: Math.min(firstX, lastX),
maxX: Math.max(firstX, lastX),
minY: Math.min(firstY, lastY),
maxY: Math.max(firstY, lastY),
minZ: minZ,
maxZ: maxZ,
noise: median$1
};
}
function generateContourLines(zData, options) {
let noise = zData.noise;
let z = zData.z;
let povarHeight0, povarHeight1, povarHeight2, povarHeight3;
let isOver0, isOver1, isOver2, isOver3;
let nbSubSpectra = z.length;
let nbPovars = z[0].length;
let pAx, pAy, pBx, pBy;
let x0 = zData.minX;
let xN = zData.maxX;
let dx = (xN - x0) / (nbPovars - 1);
let y0 = zData.minY;
let yN = zData.maxY;
let dy = (yN - y0) / (nbSubSpectra - 1);
let minZ = zData.minZ;
let maxZ = zData.maxZ; // System.out.prvarln('y0 '+y0+' yN '+yN);
// -------------------------
// Povars attribution
//
// 0----1
// | / |
// | / |
// 2----3
//
// ---------------------d------
let iter = options.nbContourLevels * 2;
let contourLevels = new Array(iter);
let lineZValue;
for (let level = 0; level < iter; level++) {
// multiply by 2 for positif and negatif
let contourLevel = {};
contourLevels[level] = contourLevel;
let side = level % 2;
let factor = (maxZ - options.noiseMultiplier * noise) * Math.exp((level >> 1) - options.nbContourLevels);
if (side === 0) {
lineZValue = factor + options.noiseMultiplier * noise;
} else {
lineZValue = 0 - factor - options.noiseMultiplier * noise;
}
let lines = [];
contourLevel.zValue = lineZValue;
contourLevel.lines = lines;
if (lineZValue <= minZ || lineZValue >= maxZ) continue;
for (let iSubSpectra = 0; iSubSpectra < nbSubSpectra - 1; iSubSpectra++) {
let subSpectra = z[iSubSpectra];
let subSpectraAfter = z[iSubSpectra + 1];
for (let povar = 0; povar < nbPovars - 1; povar++) {
povarHeight0 = subSpectra[povar];
povarHeight1 = subSpectra[povar + 1];
povarHeight2 = subSpectraAfter[povar];
povarHeight3 = subSpectraAfter[povar + 1];
isOver0 = povarHeight0 > lineZValue;
isOver1 = povarHeight1 > lineZValue;
isOver2 = povarHeight2 > lineZValue;
isOver3 = povarHeight3 > lineZValue; // Example povar0 is over the plane and povar1 and
// povar2 are below, we find the varersections and add
// the segment
if (isOver0 !== isOver1 && isOver0 !== isOver2) {
pAx = povar + (lineZValue - povarHeight0) / (povarHeight1 - povarHeight0);
pAy = iSubSpectra;
pBx = povar;
pBy = iSubSpectra + (lineZValue - povarHeight0) / (povarHeight2 - povarHeight0);
lines.push(pAx * dx + x0);
lines.push(pAy * dy + y0);
lines.push(pBx * dx + x0);
lines.push(pBy * dy + y0);
} // remove push does not help !!!!
if (isOver3 !== isOver1 && isOver3 !== isOver2) {
pAx = povar + 1;
pAy = iSubSpectra + 1 - (lineZValue - povarHeight3) / (povarHeight1 - povarHeight3);
pBx = povar + 1 - (lineZValue - povarHeight3) / (povarHeight2 - povarHeight3);
pBy = iSubSpectra + 1;
lines.push(pAx * dx + x0);
lines.push(pAy * dy + y0);
lines.push(pBx * dx + x0);
lines.push(pBy * dy + y0);
} // test around the diagonal
if (isOver1 !== isOver2) {
pAx = (povar + 1 - (lineZValue - povarHeight1) / (povarHeight2 - povarHeight1)) * dx + x0;
pAy = (iSubSpectra + (lineZValue - povarHeight1) / (povarHeight2 - povarHeight1)) * dy + y0;
if (isOver1 !== isOver0) {
pBx = povar + 1 - (lineZValue - povarHeight1) / (povarHeight0 - povarHeight1);
pBy = iSubSpectra;
lines.push(pAx);
lines.push(pAy);
lines.push(pBx * dx + x0);
lines.push(pBy * dy + y0);
}
if (isOver2 !== isOver0) {
pBx = povar;
pBy = iSubSpectra + 1 - (lineZValue - povarHeight2) / (povarHeight0 - povarHeight2);
lines.push(pAx);
lines.push(pAy);
lines.push(pBx * dx + x0);
lines.push(pBy * dy + y0);
}
if (isOver1 !== isOver3) {
pBx = povar + 1;
pBy = iSubSpectra + (lineZValue - povarHeight1) / (povarHeight3 - povarHeight1);
lines.push(pAx);
lines.push(pAy);
lines.push(pBx * dx + x0);
lines.push(pBy * dy + y0);
}
if (isOver2 !== isOver3) {
pBx = povar + (lineZValue - povarHeight2) / (povarHeight3 - povarHeight2);
pBy = iSubSpectra + 1;
lines.push(pAx);
lines.push(pAy);
lines.push(pBx * dx + x0);
lines.push(pBy * dy + y0);
}
}
}
}
}
return {
minX: zData.minX,
maxX: zData.maxX,
minY: zData.minY,
maxY: zData.maxY,
segments: contourLevels
};
}
function add2D(result, options) {
let zData = convertTo3DZ(result.spectra);
if (!options.noContour) {
result.contourLines = generateContourLines(zData, options);
delete zData.z;
}
result.minMax = zData;
}
/* eslint-disable camelcase */
const impurities = {
cdcl3: {
tms: [{
proton: 'X',
coupling: 0,
multiplicity: '',
shift: 0
}],
solvent: [{
proton: 'X',
coupling: 0,
multiplicity: 'ds',
shift: 7.26
}],
h2o: [{
proton: 'H2O',
coupling: 0,
multiplicity: 'bs',
shift: 1.56
}],
acetic_acid: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.1
}],
acetone: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.17
}],
acetonitrile: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.1
}],
benzene: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 7.36
}],
'tert-butyl_alcohol': [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 1.28
}],
'tert-butyl_methyl_ether': [{
proton: 'CCH3',
coupling: 0,
multiplicity: 's',
shift: 1.19
}, {
proton: 'OCH3',
coupling: 0,
multiplicity: 's',
shift: 3.22
}],
bhtb: [{
proton: 'ArH',
coupling: 0,
multiplicity: 's',
shift: 6.98
}, {
proton: 'OHc',
coupling: 0,
multiplicity: 's',
shift: 5.01
}, {
proton: 'ArCH3',
coupling: 0,
multiplicity: 's',
shift: 2.27
}, {
proton: 'ArC(CH3)3',
coupling: 0,
multiplicity: 's',
shift: 1.43
}],
chloroform: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 7.26
}],
cyclohexane: [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 1.43
}],
'1,2-dichloroethane': [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 3.73
}],
dichloromethane: [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 5.3
}],
diethyl_ether: [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 1.21
}, {
proton: 'CH2',
coupling: 7,
multiplicity: 'q',
shift: 3.48
}],
diglyme: [{
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 3.65
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 3.57
}, {
proton: 'OCH3',
coupling: 0,
multiplicity: 's',
shift: 3.39
}],
'1,2-dimethoxyethane': [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 3.4
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 3.55
}],
dimethylacetamide: [{
proton: 'CH3CO',
coupling: 0,
multiplicity: 's',
shift: 2.09
}, {
proton: 'NCH3',
coupling: 0,
multiplicity: 's',
shift: 3.02
}, {
proton: 'NCH3',
coupling: 0,
multiplicity: 's',
shift: 2.94
}],
dimethylformamide: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 8.02
}, {
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.96
}, {
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.88
}],
dimethyl_sulfoxide: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.62
}],
dioxane: [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 3.71
}],
ethanol: [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 1.25
}, {
proton: 'CH2',
coupling: 7,
multiplicity: 'q',
shift: 3.72
}, {
proton: 'OH',
coupling: 5,
multiplicity: 's,t',
shift: 1.32
}],
ethyl_acetate: [{
proton: 'CH3CO',
coupling: 0,
multiplicity: 's',
shift: 2.05
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 'q',
shift: 4.12
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 't',
shift: 1.26
}],
ethyl_methyl_ketone: [{
proton: 'CH3CO',
coupling: 0,
multiplicity: 's',
shift: 2.14
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 'q',
shift: 2.46
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 't',
shift: 1.06
}],
ethylene_glycol: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 3.76
}],
'grease^f': [{
proton: 'CH3',
coupling: 0,
multiplicity: 'm',
shift: 0.86
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 'br_s',
shift: 1.26
}],
'n-hexane': [{
proton: 'CH3',
coupling: 0,
multiplicity: 't',
shift: 0.88
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 1.26
}],
hmpag: [{
proton: 'CH3',
coupling: 9.5,
multiplicity: 'd',
shift: 2.65
}],
methanol: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 3.49
}, {
proton: 'OH',
coupling: 0,
multiplicity: 's',
shift: 1.09
}],
nitromethane: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 4.33
}],
'n-pentane': [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 7
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 1.27
}],
'2-propanol': [{
proton: 'CH3',
coupling: 6,
multiplicity: 'd',
shift: 1.22
}, {
proton: 'CH',
coupling: 6,
multiplicity: 'sep',
shift: 4.04
}],
pyridine: [{
proton: 'CH(2)',
coupling: 0,
multiplicity: 'm',
shift: 8.62
}, {
proton: 'CH(3)',
coupling: 0,
multiplicity: 'm',
shift: 7.29
}, {
proton: 'CH(4)',
coupling: 0,
multiplicity: 'm',
shift: 7.68
}],
silicone_greasei: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 0.07
}],
tetrahydrofuran: [{
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 1.85
}, {
proton: 'CH2O',
coupling: 0,
multiplicity: 'm',
shift: 3.76
}],
toluene: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.36
}, {
proton: 'CH(o/p)',
coupling: 0,
multiplicity: 'm',
shift: 7.17
}, {
proton: 'CH(m)',
coupling: 0,
multiplicity: 'm',
shift: 7.25
}],
triethylamine: [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 1.03
}, {
proton: 'CH2',
coupling: 7,
multiplicity: 'q',
shift: 2.53
}]
},
'(cd3)2co': {
tms: [{
proton: 'X',
coupling: 0,
multiplicity: '',
shift: 0
}],
solvent: [{
proton: 'X',
coupling: 0,
multiplicity: '',
shift: 2.05
}],
h2o: [{
proton: 'H2O',
coupling: 0,
multiplicity: 's',
shift: 2.84
}],
acetic_acid: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 1.96
}],
acetone: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.09
}],
acetonitrile: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.05
}],
benzene: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 7.36
}],
'tert-butyl_alcohol': [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 1.18
}],
'tert-butyl_methyl_ether': [{
proton: 'CCH3',
coupling: 0,
multiplicity: 's',
shift: 1.13
}, {
proton: 'OCH3',
coupling: 0,
multiplicity: 's',
shift: 3.13
}],
bhtb: [{
proton: 'ArH',
coupling: 0,
multiplicity: 's',
shift: 6.96
}, {
proton: 'ArCH3',
coupling: 0,
multiplicity: 's',
shift: 2.22
}, {
proton: 'ArC(CH3)3',
coupling: 0,
multiplicity: 's',
shift: 1.41
}],
chloroform: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 8.02
}],
cyclohexane: [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 1.43
}],
'1,2-dichloroethane': [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 3.87
}],
dichloromethane: [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 5.63
}],
diethyl_ether: [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 1.11
}, {
proton: 'CH2',
coupling: 7,
multiplicity: 'q',
shift: 3.41
}],
diglyme: [{
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 3.56
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 3.47
}, {
proton: 'OCH3',
coupling: 0,
multiplicity: 's',
shift: 3.28
}],
'1,2-dimethoxyethane': [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 3.28
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 3.46
}],
dimethylacetamide: [{
proton: 'CH3CO',
coupling: 0,
multiplicity: 's',
shift: 1.97
}, {
proton: 'NCH3',
coupling: 0,
multiplicity: 's',
shift: 3
}, {
proton: 'NCH3',
coupling: 0,
multiplicity: 's',
shift: 2.83
}],
dimethylformamide: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 7.96
}, {
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.94
}, {
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.78
}],
dimethyl_sulfoxide: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.52
}],
dioxane: [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 3.59
}],
ethanol: [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 1.12
}, {
proton: 'CH2',
coupling: 7,
multiplicity: 'q',
shift: 3.57
}, {
proton: 'OH',
coupling: 5,
multiplicity: 's,t',
shift: 3.39
}],
ethyl_acetate: [{
proton: 'CH3CO',
coupling: 0,
multiplicity: 's',
shift: 1.97
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 'q',
shift: 4.05
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 't',
shift: 1.2
}],
ethyl_methyl_ketone: [{
proton: 'CH3CO',
coupling: 0,
multiplicity: 's',
shift: 2.07
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 'q',
shift: 2.45
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 't',
shift: 0.96
}],
ethylene_glycol: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 3.28
}],
'grease^f': [{
proton: 'CH3',
coupling: 0,
multiplicity: 'm',
shift: 0.87
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 'br_s',
shift: 1.29
}],
'n-hexane': [{
proton: 'CH3',
coupling: 0,
multiplicity: 't',
shift: 0.88
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 1.28
}],
hmpag: [{
proton: 'CH3',
coupling: 9.5,
multiplicity: 'd',
shift: 2.59
}],
methanol: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 3.31
}, {
proton: 'OH',
coupling: 0,
multiplicity: 's',
shift: 3.12
}],
nitromethane: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 4.43
}],
'n-pentane': [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 0.88
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 1.27
}],
'2-propanol': [{
proton: 'CH3',
coupling: 6,
multiplicity: 'd',
shift: 1.1
}, {
proton: 'CH',
coupling: 6,
multiplicity: 'sep',
shift: 3.9
}],
pyridine: [{
proton: 'CH(2)',
coupling: 0,
multiplicity: 'm',
shift: 8.58
}, {
proton: 'CH(3)',
coupling: 0,
multiplicity: 'm',
shift: 7.35
}, {
proton: 'CH(4)',
coupling: 0,
multiplicity: 'm',
shift: 7.76
}],
silicone_greasei: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 0.13
}],
tetrahydrofuran: [{
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 1.79
}, {
proton: 'CH2O',
coupling: 0,
multiplicity: 'm',
shift: 3.63
}],
toluene: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.32
}, {
proton: 'CH(o/p)',
coupling: 0,
multiplicity: 'm',
shift: 7.5
}, {
proton: 'CH(m)',
coupling: 0,
multiplicity: 'm',
shift: 7.5
}],
triethylamine: [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 0.96
}, {
proton: 'CH2',
coupling: 7,
multiplicity: 'q',
shift: 2.45
}]
},
dmso: {
tms: [{
proton: 'X',
coupling: 0,
multiplicity: '',
shift: 0
}],
solvent: [{
proton: 'X',
coupling: 0,
multiplicity: 'quint',
shift: 2.5
}],
h2o: [{
proton: 'H2O',
coupling: 0,
multiplicity: 's',
shift: 3.33
}],
acetic_acid: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 1.91
}],
acetone: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.09
}],
acetonitrile: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.07
}],
benzene: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 7.37
}],
'tert-butyl_alcohol': [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 1.11
}, {
proton: 'OHc',
coupling: 0,
multiplicity: 's',
shift: 4.19
}],
'tert-butyl_methyl_ether': [{
proton: 'CCH3',
coupling: 0,
multiplicity: 's',
shift: 1.11
}, {
proton: 'OCH3',
coupling: 0,
multiplicity: 's',
shift: 3.08
}],
bhtb: [{
proton: 'ArH',
coupling: 0,
multiplicity: 's',
shift: 6.87
}, {
proton: 'OHc',
coupling: 0,
multiplicity: 's',
shift: 6.65
}, {
proton: 'ArCH3',
coupling: 0,
multiplicity: 's',
shift: 2.18
}, {
proton: 'ArC(CH3)3',
coupling: 0,
multiplicity: 's',
shift: 1.36
}],
chloroform: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 8.32
}],
cyclohexane: [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 1.4
}],
'1,2-dichloroethane': [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 3.9
}],
dichloromethane: [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 5.76
}],
diethyl_ether: [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 1.09
}, {
proton: 'CH2',
coupling: 7,
multiplicity: 'q',
shift: 3.38
}],
diglyme: [{
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 3.51
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 3.38
}, {
proton: 'OCH3',
coupling: 0,
multiplicity: 's',
shift: 3.24
}],
'1,2-dimethoxyethane': [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 3.24
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 3.43
}],
dimethylacetamide: [{
proton: 'CH3CO',
coupling: 0,
multiplicity: 's',
shift: 1.96
}, {
proton: 'NCH3',
coupling: 0,
multiplicity: 's',
shift: 2.94
}, {
proton: 'NCH3',
coupling: 0,
multiplicity: 's',
shift: 2.78
}],
dimethylformamide: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 7.95
}, {
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.89
}, {
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.73
}],
dimethyl_sulfoxide: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.54
}],
dioxane: [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 3.57
}],
ethanol: [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 1.06
}, {
proton: 'CH2',
coupling: 7,
multiplicity: 'q',
shift: 3.44
}, {
proton: 'OH',
coupling: 5,
multiplicity: 's,t',
shift: 4.63
}],
ethyl_acetate: [{
proton: 'CH3CO',
coupling: 0,
multiplicity: 's',
shift: 1.99
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 'q',
shift: 4.03
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 't',
shift: 1.17
}],
ethyl_methyl_ketone: [{
proton: 'CH3CO',
coupling: 0,
multiplicity: 's',
shift: 2.07
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 'q',
shift: 2.43
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 't',
shift: 0.91
}],
ethylene_glycol: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 3.34
}],
'grease^f': [],
'n-hexane': [{
proton: 'CH3',
coupling: 0,
multiplicity: 't',
shift: 0.86
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 1.25
}],
hmpag: [{
proton: 'CH3',
coupling: 9.5,
multiplicity: 'd',
shift: 2.53
}],
methanol: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 3.16
}, {
proton: 'OH',
coupling: 0,
multiplicity: 's',
shift: 4.01
}],
nitromethane: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 4.42
}],
'n-pentane': [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 0.88
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 1.27
}],
'2-propanol': [{
proton: 'CH3',
coupling: 6,
multiplicity: 'd',
shift: 1.04
}, {
proton: 'CH',
coupling: 6,
multiplicity: 'sep',
shift: 3.78
}],
pyridine: [{
proton: 'CH(2)',
coupling: 0,
multiplicity: 'm',
shift: 8.58
}, {
proton: 'CH(3)',
coupling: 0,
multiplicity: 'm',
shift: 7.39
}, {
proton: 'CH(4)',
coupling: 0,
multiplicity: 'm',
shift: 7.79
}],
silicone_greasei: [],
tetrahydrofuran: [{
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 1.76
}, {
proton: 'CH2O',
coupling: 0,
multiplicity: 'm',
shift: 3.6
}],
toluene: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.3
}, {
proton: 'CH(o/p)',
coupling: 0,
multiplicity: 'm',
shift: 7.18
}, {
proton: 'CH(m)',
coupling: 0,
multiplicity: 'm',
shift: 7.25
}],
triethylamine: [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 0.93
}, {
proton: 'CH2',
coupling: 7,
multiplicity: 'q',
shift: 2.43
}]
},
c6d6: {
tms: [{
proton: 'X',
coupling: 0,
multiplicity: '',
shift: 0
}],
solvent: [{
proton: 'X',
coupling: 0,
multiplicity: '',
shift: 7.16
}],
h2o: [{
proton: 'H2O',
coupling: 0,
multiplicity: 's',
shift: 0.4
}],
acetic_acid: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 1.55
}],
acetone: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 1.55
}],
acetonitrile: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 1.55
}],
benzene: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 7.15
}],
'tert-butyl_alcohol': [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 1.05
}, {
proton: 'OHc',
coupling: 0,
multiplicity: 's',
shift: 1.55
}],
'tert-butyl_methyl_ether': [{
proton: 'CCH3',
coupling: 0,
multiplicity: 's',
shift: 1.07
}, {
proton: 'OCH3',
coupling: 0,
multiplicity: 's',
shift: 3.04
}],
bhtb: [{
proton: 'ArH',
coupling: 0,
multiplicity: 's',
shift: 7.05
}, {
proton: 'OHc',
coupling: 0,
multiplicity: 's',
shift: 4.79
}, {
proton: 'ArCH3',
coupling: 0,
multiplicity: 's',
shift: 2.24
}, {
proton: 'ArC(CH3)3',
coupling: 0,
multiplicity: 's',
shift: 1.38
}],
chloroform: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 6.15
}],
cyclohexane: [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 1.4
}],
'1,2-dichloroethane': [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 2.9
}],
dichloromethane: [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 4.27
}],
diethyl_ether: [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 1.11
}, {
proton: 'CH2',
coupling: 7,
multiplicity: 'q',
shift: 3.26
}],
diglyme: [{
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 3.46
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 3.34
}, {
proton: 'OCH3',
coupling: 0,
multiplicity: 's',
shift: 3.11
}],
'1,2-dimethoxyethane': [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 3.12
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 3.33
}],
dimethylacetamide: [{
proton: 'CH3CO',
coupling: 0,
multiplicity: 's',
shift: 1.6
}, {
proton: 'NCH3',
coupling: 0,
multiplicity: 's',
shift: 2.57
}, {
proton: 'NCH3',
coupling: 0,
multiplicity: 's',
shift: 2.05
}],
dimethylformamide: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 7.63
}, {
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.36
}, {
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 1.86
}],
dimethyl_sulfoxide: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 1.68
}],
dioxane: [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 3.35
}],
ethanol: [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 0.96
}, {
proton: 'CH2',
coupling: 7,
multiplicity: 'q',
shift: 3.34
}],
ethyl_acetate: [{
proton: 'CH3CO',
coupling: 0,
multiplicity: 's',
shift: 1.65
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 'q',
shift: 3.89
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 't',
shift: 0.92
}],
ethyl_methyl_ketone: [{
proton: 'CH3CO',
coupling: 0,
multiplicity: 's',
shift: 1.58
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 'q',
shift: 1.81
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 't',
shift: 0.85
}],
ethylene_glycol: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 3.41
}],
'grease^f': [{
proton: 'CH3',
coupling: 0,
multiplicity: 'm',
shift: 0.92
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 'br_s',
shift: 1.36
}],
'n-hexane': [{
proton: 'CH3',
coupling: 0,
multiplicity: 't',
shift: 0.89
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 1.24
}],
hmpag: [{
proton: 'CH3',
coupling: 9.5,
multiplicity: 'd',
shift: 2.4
}],
methanol: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 3.07
}],
nitromethane: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.94
}],
'n-pentane': [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 0.86
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 1.23
}],
'2-propanol': [{
proton: 'CH3',
coupling: 6,
multiplicity: 'd',
shift: 0.95
}, {
proton: 'CH',
coupling: 6,
multiplicity: 'sep',
shift: 3.67
}],
pyridine: [{
proton: 'CH(2)',
coupling: 0,
multiplicity: 'm',
shift: 8.53
}, {
proton: 'CH(3)',
coupling: 0,
multiplicity: 'm',
shift: 6.66
}, {
proton: 'CH(4)',
coupling: 0,
multiplicity: 'm',
shift: 6.98
}],
silicone_greasei: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 0.29
}],
tetrahydrofuran: [{
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 1.4
}, {
proton: 'CH2O',
coupling: 0,
multiplicity: 'm',
shift: 3.57
}],
toluene: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.11
}, {
proton: 'CH(o/p)',
coupling: 0,
multiplicity: 'm',
shift: 7.02
}, {
proton: 'CH(m)',
coupling: 0,
multiplicity: 'm',
shift: 7.13
}],
triethylamine: [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 0.96
}, {
proton: 'CH2',
coupling: 7,
multiplicity: 'q',
shift: 2.4
}]
},
cd3cn: {
tms: [{
proton: 'X',
coupling: 0,
multiplicity: '',
shift: 0
}],
solvent: [{
proton: 'X',
coupling: 0,
multiplicity: '',
shift: 1.94
}],
h2o: [{
proton: 'H2O',
coupling: 0,
multiplicity: 's',
shift: 2.13
}],
acetic_acid: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 1.96
}],
acetone: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.08
}],
acetonitrile: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 1.96
}],
benzene: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 7.37
}],
'tert-butyl_alcohol': [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 1.16
}, {
proton: 'OHc',
coupling: 0,
multiplicity: 's',
shift: 2.18
}],
'tert-butyl_methyl_ether': [{
proton: 'CCH3',
coupling: 0,
multiplicity: 's',
shift: 1.14
}, {
proton: 'OCH3',
coupling: 0,
multiplicity: 's',
shift: 3.13
}],
bhtb: [{
proton: 'ArH',
coupling: 0,
multiplicity: 's',
shift: 6.97
}, {
proton: 'OHc',
coupling: 0,
multiplicity: 's',
shift: 5.2
}, {
proton: 'ArCH3',
coupling: 0,
multiplicity: 's',
shift: 2.22
}, {
proton: 'ArC(CH3)3',
coupling: 0,
multiplicity: 's',
shift: 1.39
}],
chloroform: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 7.58
}],
cyclohexane: [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 1.44
}],
'1,2-dichloroethane': [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 3.81
}],
dichloromethane: [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 5.44
}],
diethyl_ether: [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 1.12
}, {
proton: 'CH2',
coupling: 7,
multiplicity: 'q',
shift: 3.42
}],
diglyme: [{
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 3.53
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 3.45
}, {
proton: 'OCH3',
coupling: 0,
multiplicity: 's',
shift: 3.29
}],
'1,2-dimethoxyethane': [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 3.28
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 3.45
}],
dimethylacetamide: [{
proton: 'CH3CO',
coupling: 0,
multiplicity: 's',
shift: 1.97
}, {
proton: 'NCH3',
coupling: 0,
multiplicity: 's',
shift: 2.96
}, {
proton: 'NCH3',
coupling: 0,
multiplicity: 's',
shift: 2.83
}],
dimethylformamide: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 7.92
}, {
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.89
}, {
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.77
}],
dimethyl_sulfoxide: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.5
}],
dioxane: [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 3.6
}],
ethanol: [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 1.12
}, {
proton: 'CH2',
coupling: 7,
multiplicity: 'q',
shift: 3.54
}, {
proton: 'OH',
coupling: 5,
multiplicity: 's,t',
shift: 2.47
}],
ethyl_acetate: [{
proton: 'CH3CO',
coupling: 0,
multiplicity: 's',
shift: 1.97
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 'q',
shift: 4.06
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 't',
shift: 1.2
}],
ethyl_methyl_ketone: [{
proton: 'CH3CO',
coupling: 0,
multiplicity: 's',
shift: 2.06
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 'q',
shift: 2.43
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 't',
shift: 0.96
}],
ethylene_glycol: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 3.51
}],
'grease^f': [{
proton: 'CH3',
coupling: 0,
multiplicity: 'm',
shift: 0.86
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 'br_s',
shift: 1.27
}],
'n-hexane': [{
proton: 'CH3',
coupling: 0,
multiplicity: 't',
shift: 0.89
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 1.28
}],
hmpag: [{
proton: 'CH3',
coupling: 9.5,
multiplicity: 'd',
shift: 2.57
}],
methanol: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 3.28
}, {
proton: 'OH',
coupling: 0,
multiplicity: 's',
shift: 2.16
}],
nitromethane: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 4.31
}],
'n-pentane': [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 0.87
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 1.29
}],
'2-propanol': [{
proton: 'CH3',
coupling: 6,
multiplicity: 'd',
shift: 1.09
}, {
proton: 'CH',
coupling: 6,
multiplicity: 'sep',
shift: 3.87
}],
pyridine: [{
proton: 'CH(2)',
coupling: 0,
multiplicity: 'm',
shift: 8.57
}, {
proton: 'CH(3)',
coupling: 0,
multiplicity: 'm',
shift: 7.33
}, {
proton: 'CH(4)',
coupling: 0,
multiplicity: 'm',
shift: 7.73
}],
silicone_greasei: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 0.08
}],
tetrahydrofuran: [{
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 1.8
}, {
proton: 'CH2O',
coupling: 0,
multiplicity: 'm',
shift: 3.64
}],
toluene: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.33
}, {
proton: 'CH(o/p)',
coupling: 0,
multiplicity: 'm',
shift: 7.2
}, {
proton: 'CH(m)',
coupling: 0,
multiplicity: 'm',
shift: 7.2
}],
triethylamine: [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 0.96
}, {
proton: 'CH2',
coupling: 7,
multiplicity: 'q',
shift: 2.45
}]
},
cd3od: {
tms: [{
proton: 'X',
coupling: 0,
multiplicity: '',
shift: 0
}],
solvent: [{
proton: 'X',
coupling: 0,
multiplicity: '',
shift: 3.31
}],
h2o: [{
proton: 'H2O',
coupling: 0,
multiplicity: 's',
shift: 4.87
}],
acetic_acid: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 1.99
}],
acetone: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.15
}],
acetonitrile: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.03
}],
benzene: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 7.33
}],
'tert-butyl_alcohol': [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 1.4
}],
'tert-butyl_methyl_ether': [{
proton: 'CCH3',
coupling: 0,
multiplicity: 's',
shift: 1.15
}, {
proton: 'OCH3',
coupling: 0,
multiplicity: 's',
shift: 3.2
}],
bhtb: [{
proton: 'ArH',
coupling: 0,
multiplicity: 's',
shift: 6.92
}, {
proton: 'ArCH3',
coupling: 0,
multiplicity: 's',
shift: 2.21
}, {
proton: 'ArC(CH3)3',
coupling: 0,
multiplicity: 's',
shift: 1.4
}],
chloroform: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 7.9
}],
cyclohexane: [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 1.45
}],
'1,2-dichloroethane': [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 3.78
}],
dichloromethane: [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 5.49
}],
diethyl_ether: [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 1.18
}, {
proton: 'CH2',
coupling: 7,
multiplicity: 'q',
shift: 3.49
}],
diglyme: [{
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 3.61
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 3.58
}, {
proton: 'OCH3',
coupling: 0,
multiplicity: 's',
shift: 3.35
}],
'1,2-dimethoxyethane': [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 3.35
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 3.52
}],
dimethylacetamide: [{
proton: 'CH3CO',
coupling: 0,
multiplicity: 's',
shift: 2.07
}, {
proton: 'NCH3',
coupling: 0,
multiplicity: 's',
shift: 3.31
}, {
proton: 'NCH3',
coupling: 0,
multiplicity: 's',
shift: 2.92
}],
dimethylformamide: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 7.97
}, {
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.99
}, {
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.86
}],
dimethyl_sulfoxide: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.65
}],
dioxane: [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 3.66
}],
ethanol: [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 1.19
}, {
proton: 'CH2',
coupling: 7,
multiplicity: 'q',
shift: 3.6
}],
ethyl_acetate: [{
proton: 'CH3CO',
coupling: 0,
multiplicity: 's',
shift: 2.01
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 'q',
shift: 4.09
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 't',
shift: 1.24
}],
ethyl_methyl_ketone: [{
proton: 'CH3CO',
coupling: 0,
multiplicity: 's',
shift: 2.12
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 'q',
shift: 2.5
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 't',
shift: 1.01
}],
ethylene_glycol: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 3.59
}],
'grease^f': [{
proton: 'CH3',
coupling: 0,
multiplicity: 'm',
shift: 0.88
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 'br_s',
shift: 1.29
}],
'n-hexane': [{
proton: 'CH3',
coupling: 0,
multiplicity: 't',
shift: 0.9
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 1.29
}],
hmpag: [{
proton: 'CH3',
coupling: 9.5,
multiplicity: 'd',
shift: 2.64
}],
methanol: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 3.34
}],
nitromethane: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 4.34
}],
'n-pentane': [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 0.89
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 1.29
}],
'2-propanol': [{
proton: 'CH3',
coupling: 6,
multiplicity: 'd',
shift: 1.5
}, {
proton: 'CH',
coupling: 6,
multiplicity: 'sep',
shift: 3.92
}],
pyridine: [{
proton: 'CH(2)',
coupling: 0,
multiplicity: 'm',
shift: 8.53
}, {
proton: 'CH(3)',
coupling: 0,
multiplicity: 'm',
shift: 7.44
}, {
proton: 'CH(4)',
coupling: 0,
multiplicity: 'm',
shift: 7.85
}],
silicone_greasei: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 0.1
}],
tetrahydrofuran: [{
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 1.87
}, {
proton: 'CH2O',
coupling: 0,
multiplicity: 'm',
shift: 3.71
}],
toluene: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.32
}, {
proton: 'CH(o/p)',
coupling: 0,
multiplicity: 'm',
shift: 7.16
}, {
proton: 'CH(m)',
coupling: 0,
multiplicity: 'm',
shift: 7.16
}],
triethylamine: [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 1.05
}, {
proton: 'CH2',
coupling: 7,
multiplicity: 'q',
shift: 2.58
}]
},
d2o: {
tms: [{
proton: 'X',
coupling: 0,
multiplicity: '',
shift: 0
}],
solvent: [{
proton: 'X',
coupling: 0,
multiplicity: '',
shift: 4.79
}],
h2o: [],
acetic_acid: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.08
}],
acetone: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.22
}],
acetonitrile: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.06
}],
benzene: [],
'tert-butyl_alcohol': [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 1.24
}],
'tert-butyl_methyl_ether': [{
proton: 'CCH3',
coupling: 0,
multiplicity: 's',
shift: 1.21
}, {
proton: 'OCH3',
coupling: 0,
multiplicity: 's',
shift: 3.22
}],
bhtb: [],
chloroform: [],
cyclohexane: [],
'1,2-dichloroethane': [],
dichloromethane: [],
diethyl_ether: [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 1.17
}, {
proton: 'CH2',
coupling: 7,
multiplicity: 'q',
shift: 3.56
}],
diglyme: [{
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 3.67
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 3.61
}, {
proton: 'OCH3',
coupling: 0,
multiplicity: 's',
shift: 3.37
}],
'1,2-dimethoxyethane': [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 3.37
}, {
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 3.6
}],
dimethylacetamide: [{
proton: 'CH3CO',
coupling: 0,
multiplicity: 's',
shift: 2.08
}, {
proton: 'NCH3',
coupling: 0,
multiplicity: 's',
shift: 3.06
}, {
proton: 'NCH3',
coupling: 0,
multiplicity: 's',
shift: 2.9
}],
dimethylformamide: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 7.92
}, {
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 3.01
}, {
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.85
}],
dimethyl_sulfoxide: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 2.71
}],
dioxane: [{
proton: 'CH2',
coupling: 0,
multiplicity: 's',
shift: 3.75
}],
ethanol: [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 1.17
}, {
proton: 'CH2',
coupling: 7,
multiplicity: 'q',
shift: 3.65
}],
ethyl_acetate: [{
proton: 'CH3CO',
coupling: 0,
multiplicity: 's',
shift: 2.07
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 'q',
shift: 4.14
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 't',
shift: 1.24
}],
ethyl_methyl_ketone: [{
proton: 'CH3CO',
coupling: 0,
multiplicity: 's',
shift: 2.19
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 'q',
shift: 3.18
}, {
proton: 'CH2CH3',
coupling: 7,
multiplicity: 't',
shift: 1.26
}],
ethylene_glycol: [{
proton: 'CH',
coupling: 0,
multiplicity: 's',
shift: 3.65
}],
'grease^f': [],
'n-hexane': [],
hmpag: [{
proton: 'CH3',
coupling: 9.5,
multiplicity: 'd',
shift: 2.61
}],
methanol: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 3.34
}],
nitromethane: [{
proton: 'CH3',
coupling: 0,
multiplicity: 's',
shift: 4.4
}],
'n-pentane': [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 0.9
}],
'2-propanol': [{
proton: 'CH3',
coupling: 6,
multiplicity: 'd',
shift: 1.17
}, {
proton: 'CH',
coupling: 6,
multiplicity: 'sep',
shift: 4.02
}],
pyridine: [{
proton: 'CH(2)',
coupling: 0,
multiplicity: 'm',
shift: 8.52
}, {
proton: 'CH(3)',
coupling: 0,
multiplicity: 'm',
shift: 7.45
}, {
proton: 'CH(4)',
coupling: 0,
multiplicity: 'm',
shift: 7.87
}],
silicone_greasei: [],
tetrahydrofuran: [{
proton: 'CH2',
coupling: 0,
multiplicity: 'm',
shift: 1.88
}, {
proton: 'CH2O',
coupling: 0,
multiplicity: 'm',
shift: 3.74
}],
toluene: [],
triethylamine: [{
proton: 'CH3',
coupling: 7,
multiplicity: 't',
shift: 0.99
}, {
proton: 'CH2',
coupling: 7,
multiplicity: 'q',
shift: 2.57
}]
}
};
const toCheck = ['solvent', 'H2O', 'TMS'];
/**
* Try to remove peaks of impurities.
* @param {array} peakList - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}].
* @param {object} [options={}] - options
* @param {string} [options.solvent=''] - solvent name.
* @param {string} [options.error=0.025] - tolerance in ppm to assign a impurity.
*/
function peaksFilterImpurities(peakList, options = {}) {
let {
solvent = '',
error = 0.025,
remove = false
} = options;
solvent = solvent.toLowerCase();
if (solvent === '(cd3)2so') solvent = 'dmso';
if (solvent === 'meod') solvent = 'cd3od';
let solventImpurities = impurities[solvent];
if (solventImpurities) {
for (let impurity of toCheck) {
let name = impurity.toLowerCase();
let impurityShifts = solventImpurities[name];
checkImpurity(peakList, impurityShifts, {
error,
remove,
name
});
}
}
return peakList;
}
function checkImpurity(peakList, impurity, options) {
let {
name,
error,
remove
} = options;
let j, tolerance, difference;
let i = impurity.length;
while (i--) {
j = peakList.length;
while (j--) {
if (!peakList[j].asymmetric) {
tolerance = error + peakList[j].width;
difference = Math.abs(impurity[i].shift - peakList[j].x);
if (difference < tolerance) {
// && (impurity[i].multiplicity === '' || (impurity[i].multiplicity.indexOf(peakList[j].multiplicity)) { // some impurities has multiplicities like 'bs' but at presents it is unsupported
if (remove) {
peakList.splice(j, 1);
} else {
peakList[j].kind = name;
}
}
}
}
}
}
const GAUSSIAN_EXP_FACTOR = -4 * Math.LN2;
const ROOT_PI_OVER_LN2 = Math.sqrt(Math.PI / Math.LN2);
const ROOT_THREE = Math.sqrt(3);
const ROOT_2LN2 = Math.sqrt(2 * Math.LN2);
const ROOT_2LN2_MINUS_ONE = Math.sqrt(2 * Math.LN2) - 1;
// https://en.wikipedia.org/wiki/Error_function#Inverse_functions
// This code yields to a good approximation
// If needed a better implementation using polynomial can be found on https://en.wikipedia.org/wiki/Error_function#Inverse_functions
function erfinv(x) {
let a = 0.147;
if (x === 0) return 0;
let ln1MinusXSqrd = Math.log(1 - x * x);
let lnEtcBy2Plus2 = ln1MinusXSqrd / 2 + 2 / (Math.PI * a);
let firstSqrt = Math.sqrt(lnEtcBy2Plus2 ** 2 - ln1MinusXSqrd / a);
let secondSqrt = Math.sqrt(firstSqrt - lnEtcBy2Plus2);
return secondSqrt * (x > 0 ? 1 : -1);
}
class Gaussian {
/**
* @param {object} [options = {}]
* @param {number} [options.height=4*LN2/(PI*FWHM)] Define the height of the peak, by default area=1 (normalized)
* @param {number} [options.fwhm = 500] - Full Width at Half Maximum in the number of points in FWHM.
* @param {number} [options.sd] - Standard deviation, if it's defined options.fwhm will be ignored and the value will be computed sd * Math.sqrt(8 * Math.LN2);
*/
constructor(options = {}) {
this.fwhm = options.sd ? Gaussian.widthToFWHM(2 * options.sd) : options.fwhm ? options.fwhm : 500;
this.height = options.height === undefined ? Math.sqrt(-GAUSSIAN_EXP_FACTOR / Math.PI) / this.fwhm : options.height;
}
/**
* Calculate a gaussian shape
* @param {object} [options = {}]
* @param {number} [options.factor = 6] - Number of time to take fwhm to calculate length. Default covers 99.99 % of area.
* @param {number} [options.length = fwhm * factor + 1] - total number of points to calculate
* @return {Float64Array} y values
*/
getData(options = {}) {
let {
length,
factor = this.getFactor()
} = options;
if (!length) {
length = Math.min(Math.ceil(this.fwhm * factor), Math.pow(2, 25) - 1);
if (length % 2 === 0) length++;
}
const center = (length - 1) / 2;
const data = new Float64Array(length);
for (let i = 0; i <= center; i++) {
data[i] = this.fct(i - center) * this.height;
data[length - 1 - i] = data[i];
}
return data;
}
/**
* Return a parameterized function of a gaussian shape (see README for equation).
* @param {number} x - x value to calculate.
* @returns {number} - the y value of gaussian with the current parameters.
*/
fct(x) {
return Gaussian.fct(x, this.fwhm);
}
/**
* Calculate the number of times FWHM allows to reach a specific area coverage
* @param {number} [area=0.9999]
* @returns {number}
*/
getFactor(area = 0.9999) {
return Gaussian.getFactor(area);
}
/**
* Calculate the area of the shape.
* @returns {number} - returns the area.
*/
getArea() {
return Gaussian.getArea(this.fwhm, {
height: this.height
});
}
/**
* Compute the value of Full Width at Half Maximum (FWHM) from the width between the inflection points.
* //https://mathworld.wolfram.com/GaussianFunction.html
* @param {number} width - Width between the inflection points
* @returns {number} fwhm
*/
widthToFWHM(width) {
//https://mathworld.wolfram.com/GaussianFunction.html
return Gaussian.widthToFWHM(width);
}
/**
* Compute the value of width between the inflection points from Full Width at Half Maximum (FWHM).
* //https://mathworld.wolfram.com/GaussianFunction.html
* @param {number} fwhm - Full Width at Half Maximum.
* @returns {number} width
*/
fwhmToWidth(fwhm = this.fwhm) {
return Gaussian.fwhmToWidth(fwhm);
}
/**
* set a new full width at half maximum
* @param {number} fwhm - full width at half maximum
*/
setFWHM(fwhm) {
this.fwhm = fwhm;
}
/**
* set a new height
* @param {number} height - The maximal intensity of the shape.
*/
setHeight(height) {
this.height = height;
}
}
/**
* Return a parameterized function of a gaussian shape (see README for equation).
* @param {number} x - x value to calculate.
* @param {number} fwhm - full width half maximum
* @returns {number} - the y value of gaussian with the current parameters.
*/
Gaussian.fct = function fct(x, fwhm = 500) {
return Math.exp(GAUSSIAN_EXP_FACTOR * Math.pow(x / fwhm, 2));
};
/**
* Compute the value of Full Width at Half Maximum (FWHM) from the width between the inflection points.
* //https://mathworld.wolfram.com/GaussianFunction.html
* @param {number} width - Width between the inflection points
* @returns {number} fwhm
*/
Gaussian.widthToFWHM = function widthToFWHM(width) {
return width * ROOT_2LN2;
};
/**
* Compute the value of width between the inflection points from Full Width at Half Maximum (FWHM).
* //https://mathworld.wolfram.com/GaussianFunction.html
* @param {number} fwhm - Full Width at Half Maximum.
* @returns {number} width
*/
Gaussian.fwhmToWidth = function fwhmToWidth(fwhm) {
return fwhm / ROOT_2LN2;
};
/**
* Calculate the area of a specific shape.
* @param {number} fwhm - Full width at half maximum.
* @param {object} [options = {}] - options.
* @param {number} [options.height = 1] - Maximum y value of the shape.
* @returns {number} - returns the area of the specific shape and parameters.
*/
Gaussian.getArea = function getArea(fwhm, options = {}) {
let {
height = 1
} = options;
return height * ROOT_PI_OVER_LN2 * fwhm / 2;
};
/**
* Calculate the number of times FWHM allows to reach a specific area coverage.
* @param {number} [area=0.9999]
* @returns {number}
*/
Gaussian.getFactor = function getFactor(area = 0.9999) {
return Math.sqrt(2) * erfinv(area);
};
class Lorentzian {
/**
* @param {object} [options = {}]
* @param {number} [options.height=2/(PI*FWHM)] Define the height of the peak, by default area=1 (normalized)
* @param {number} [options.fwhm = 500] - Full Width at Half Maximum in the number of points in FWHM.
* @param {number} [options.sd] - Standard deviation, if it's defined options.fwhm will be ignored and the value will be computed sd * Math.sqrt(8 * Math.LN2);
*/
constructor(options = {}) {
this.fwhm = options.fwhm === undefined ? 500 : options.fwhm;
this.height = options.height === undefined ? 2 / Math.PI / this.fwhm : options.height;
}
/**
* Calculate a lorentzian shape
* @param {object} [options = {}]
* @param {number} [options.factor = Math.tan(Math.PI * (0.9999 - 0.5))] - Number of time to take fwhm to calculate length. Default covers 99.99 % of area.
* @param {number} [options.length = fwhm * factor + 1] - total number of points to calculate
* @return {Float64Array} y values
*/
getData(options = {}) {
let {
length,
factor = this.getFactor()
} = options;
if (!length) {
length = Math.min(Math.ceil(this.fwhm * factor), Math.pow(2, 25) - 1);
if (length % 2 === 0) length++;
}
const center = (length - 1) / 2;
const data = new Float64Array(length);
for (let i = 0; i <= center; i++) {
data[i] = this.fct(i - center) * this.height;
data[length - 1 - i] = data[i];
}
return data;
}
/**
* Return a parameterized function of a lorentzian shape (see README for equation).
* @param {number} x - x value to calculate.
* @returns {number} - the y value of lorentzian with the current parameters.
*/
fct(x) {
return Lorentzian.fct(x, this.fwhm);
}
/**
* Calculate the number of times FWHM allows to reach a specific area coverage
* @param {number} [area=0.9999]
* @returns {number}
*/
getFactor(area = 0.9999) {
return Lorentzian.getFactor(area);
}
/**
* Calculate the area of the shape.
* @returns {number} - returns the area.
*/
getArea() {
return Lorentzian.getArea(this.fwhm, {
height: this.height
});
}
/**
* Compute the value of width between the inflection points of a specific shape from Full Width at Half Maximum (FWHM).
* //https://mathworld.wolfram.com/LorentzianFunction.html
* @param {number} [fwhm] - Full Width at Half Maximum.
* @returns {number} width between the inflection points
*/
fwhmToWidth(fwhm = this.fwhm) {
return Lorentzian.fwhmToWidth(fwhm);
}
/**
* Compute the value of Full Width at Half Maximum (FWHM) of a specific shape from the width between the inflection points.
* //https://mathworld.wolfram.com/LorentzianFunction.html
* @param {number} [width] Width between the inflection points
* @returns {number} fwhm
*/
widthToFWHM(width) {
return Lorentzian.widthToFWHM(width);
}
/**
* set a new full width at half maximum
* @param {number} fwhm - full width at half maximum
*/
setFWHM(fwhm) {
this.fwhm = fwhm;
}
/**
* set a new height
* @param {number} height - The maximal intensity of the shape.
*/
setHeight(height) {
this.height = height;
}
}
/**
* Return a parameterized function of a gaussian shape (see README for equation).
* @param {number} x - x value to calculate.
* @param {number} fwhm - full width half maximum
* @returns {number} - the y value of gaussian with the current parameters.
*/
Lorentzian.fct = function fct(x, fwhm) {
const squareFWHM = fwhm * fwhm;
return squareFWHM / (4 * Math.pow(x, 2) + squareFWHM);
};
/**
* Compute the value of width between the inflection points of a specific shape from Full Width at Half Maximum (FWHM).
* //https://mathworld.wolfram.com/LorentzianFunction.html
* @param {number} [fwhm] - Full Width at Half Maximum.
* @returns {number} width between the inflection points
*/
Lorentzian.fwhmToWidth = function fwhmToWidth(fwhm) {
return fwhm / ROOT_THREE;
};
/**
* Compute the value of Full Width at Half Maximum (FWHM) of a specific shape from the width between the inflection points.
* //https://mathworld.wolfram.com/LorentzianFunction.html
* @param {number} [width] Width between the inflection points
* @returns {number} fwhm
*/
Lorentzian.widthToFWHM = function widthToFWHM(width) {
return width * ROOT_THREE;
};
/**
* Calculate the area of a specific shape.
* @param {number} fwhm - Full width at half maximum.
* @param {*} [options = {}] - options.
* @param {number} [options.height = 1] - Maximum y value of the shape.
* @returns {number} - returns the area of the specific shape and parameters.
*/
Lorentzian.getArea = function getArea(fwhm, options = {}) {
let {
height = 1
} = options;
return height * Math.PI * fwhm / 2;
};
/**
* Calculate the number of times FWHM allows to reach a specific area coverage
* @param {number} [area=0.9999]
* @returns {number}
*/
Lorentzian.getFactor = function getFactor(area = 0.9999) {
return 2 * Math.tan(Math.PI * (area - 0.5));
};
class PseudoVoigt {
/**
* @param {object} [options={}]
* @param {number} [options.height=1/(mu*FWHM/sqrt(4*LN2/PI)+(1-mu)*fwhm*PI*0.5)] Define the height of the peak, by default area=1 (normalized)
* @param {number} [options.fwhm=500] - Full Width at Half Maximum in the number of points in FWHM.
* @param {number} [options.mu=0.5] - ratio of gaussian contribution.
*/
constructor(options = {}) {
this.mu = options.mu === undefined ? 0.5 : options.mu;
this.fwhm = options.fwhm === undefined ? 500 : options.fwhm;
this.height = options.height === undefined ? 1 / (this.mu / Math.sqrt(-GAUSSIAN_EXP_FACTOR / Math.PI) * this.fwhm + (1 - this.mu) * this.fwhm * Math.PI / 2) : options.height;
}
/**
* Calculate a linear combination of gaussian and lorentzian function width an same full width at half maximum
* @param { object } [options = {}]
* @param { number } [options.factor = 2 * Math.tan(Math.PI * (0.9999 - 0.5))] - Number of time to take fwhm in the calculation of the length.Default covers 99.99 % of area.
* @param { number } [options.length = fwhm * factor + 1] - total number of points to calculate
* @return { object } - { fwhm, data} - An with the number of points at half maximum and the array of y values covering the 99.99 % of the area.
*/
getData(options = {}) {
let {
length,
factor = this.getFactor()
} = options;
if (!length) {
length = Math.ceil(this.fwhm * factor);
if (length % 2 === 0) length++;
}
const center = (length - 1) / 2;
let data = new Float64Array(length);
for (let i = 0; i <= center; i++) {
data[i] = this.fct(i - center) * this.height;
data[length - 1 - i] = data[i];
}
return data;
}
/**
* Return a parameterized function of a linear combination of Gaussian and Lorentzian shapes where the full width at half maximum are the same for both kind of shapes (see README for equation).
* @param {number} [x] x value to calculate.
* @returns {number} - the y value of a pseudo voigt with the current parameters.
*/
fct(x) {
return PseudoVoigt.fct(x, this.fwhm, this.mu);
}
/**
* Calculate the number of times FWHM allows to reach a specific area coverage
* @param {number} [area=0.9999] - required area to be coverage
* @param {number} [mu=this.mu] - ratio of gaussian contribution.
* @returns {number}
*/
getFactor(area = 0.9999, mu = this.mu) {
return PseudoVoigt.getFactor(area, mu);
}
/**
* Calculate the area of the shape.
* @returns {number} - returns the area.
*/
getArea() {
return PseudoVoigt.getArea(this.fwhm, {
height: this.height,
mu: this.mu
});
}
/**
* Compute the value of Full Width at Half Maximum (FMHM) from width between the inflection points.
* @param {number} width - width between the inflection points
* @param {number} [mu = 0.5] - ratio of gaussian contribution.
* @returns {number} Full Width at Half Maximum (FMHM).
*/
widthToFWHM(width, mu) {
return PseudoVoigt.widthToFWHM(width, mu);
}
/**
* Compute the value of width between the inflection points from Full Width at Half Maximum (FWHM).
* @param {number} fwhm - Full Width at Half Maximum.
* @param {number} [mu] - ratio of gaussian contribution.
* @returns {number} width between the inflection points.
*/
fwhmToWidth(fwhm = this.fwhm, mu = this.mu) {
return PseudoVoigt.fwhmToWidth(fwhm, mu);
}
/**
* set a new full width at half maximum
* @param {number} fwhm - full width at half maximum
*/
setFWHM(fwhm) {
this.fwhm = fwhm;
}
/**
* set a new height
* @param {number} height - The maximal intensity of the shape.
*/
setHeight(height) {
this.height = height;
}
/**
* set a new mu
* @param {number} mu - ratio of gaussian contribution.
*/
setMu(mu) {
this.mu = mu;
}
}
/**
* Return a parameterized function of a gaussian shape (see README for equation).
* @param {number} x - x value to calculate.
* @param {number} fwhm - full width half maximum
* @param {number} [mu=0.5] - ratio of gaussian contribution.
* @returns {number} - the y value of gaussian with the current parameters.
*/
PseudoVoigt.fct = function fct(x, fwhm, mu = 0.5) {
return (1 - mu) * Lorentzian.fct(x, fwhm) + mu * Gaussian.fct(x, fwhm);
};
/**
* Compute the value of Full Width at Half Maximum (FMHM) from width between the inflection points.
* @param {number} width - width between the inflection points
* @param {number} [mu = 0.5] - ratio of gaussian contribution.
* @returns {number} Full Width at Half Maximum (FMHM).
*/
PseudoVoigt.widthToFWHM = function widthToFWHM(width, mu = 0.5) {
return width * (mu * ROOT_2LN2_MINUS_ONE + 1);
};
/**
* Compute the value of width between the inflection points from Full Width at Half Maximum (FWHM).
* @param {number} fwhm - Full Width at Half Maximum.
* @param {number} [mu = 0.5] - ratio of gaussian contribution.
* @returns {number} width between the inflection points.
*/
PseudoVoigt.fwhmToWidth = function fwhmToWidth(fwhm, mu = 0.5) {
return fwhm / (mu * ROOT_2LN2_MINUS_ONE + 1);
};
/**
* Calculate the area of a specific shape.
* @param {number} fwhm - Full width at half maximum.
* @param {*} [options = {}] - options.
* @param {number} [options.height = 1] - Maximum y value of the shape.
* @param {number} [options.mu = 0.5] - ratio of gaussian contribution.
* @returns {number} - returns the area of the specific shape and parameters.
*/
PseudoVoigt.getArea = function getArea(fwhm, options = {}) {
let {
height = 1,
mu = 0.5
} = options;
return fwhm * height * (mu * ROOT_PI_OVER_LN2 + (1 - mu) * Math.PI) / 2;
};
/**
* Calculate the number of times FWHM allows to reach a specific area coverage
* @param {number} [area=0.9999] - required area to be coverage
* @param {number} [mu=this.mu] - ratio of gaussian contribution.
* @returns {number}
*/
PseudoVoigt.getFactor = function getFactor(area = 0.9999, mu = 0.5) {
return mu < 1 ? Lorentzian.getFactor(area) : Gaussian.getFactor(area);
};
let axis = ['x', 'y'];
class Gaussian2D {
/**
* @param {object} [options = {}]
* @param {number} [options.height=4*LN2/(PI*xFWHM*yFWHM)] Define the height of the peak, by default area=1 (normalized).
* @param {number} [options.fwhm = 500] - Full Width at Half Maximum in the number of points in FWHM used if x or y has not the fwhm property.
* @param {object} [options.x] - Options for x axis.
* @param {number} [options.x.fwhm = fwhm] - Full Width at Half Maximum in the number of points in FWHM for x axis.
* @param {number} [options.x.sd] - Standard deviation for x axis, if it's defined options.x.fwhm will be ignored and the value will be computed sd * Math.sqrt(8 * Math.LN2);
* @param {object} [options.y] - Options for y axis.
* @param {number} [options.y.fwhm = fwhm] - Full Width at Half Maximum in the number of points in FWHM for y axis.
* @param {number} [options.y.sd] - Standard deviation for y axis, if it's defined options.y.fwhm will be ignored and the value will be computed sd * Math.sqrt(8 * Math.LN2);
*/
constructor(options = {}) {
let {
fwhm: globalFWHM = 500
} = options;
for (let i of axis) {
let fwhm;
if (!options[i]) {
fwhm = globalFWHM;
} else {
fwhm = options[i].sd ? Gaussian2D.widthToFWHM(2 * options[i].sd) : options[i].fwhm || globalFWHM;
}
this[i] = {
fwhm
};
}
this.height = options.height === undefined ? -GAUSSIAN_EXP_FACTOR / Math.PI / this.x.fwhm / this.y.fwhm : options.height;
}
/**
* Calculate a Gaussian2D shape
* @param {object} [options = {}]
* @param {number} [options.factor] - Number of time to take fwhm to calculate length. Default covers 99.99 % of area.
* @param {object} [options.x] - parameter for x axis.
* @param {number} [options.x.length=fwhm*factor+1] - length on x axis.
* @param {number} [options.x.factor=factor] - Number of time to take fwhm to calculate length. Default covers 99.99 % of area.
* @param {object} [options.y] - parameter for y axis.
* @param {number} [options.y.length=fwhm*factor+1] - length on y axis.
* @param {number} [options.y.factor=factor] - Number of time to take fwhm to calculate length. Default covers 99.99 % of area.
* @return {Array} - z values.
*/
getData(options = {}) {
let {
x = {},
y = {},
factor = this.getFactor(),
length
} = options;
let xLength = x.length || length;
if (!xLength) {
let {
factor: xFactor = factor
} = x;
xLength = Math.min(Math.ceil(this.x.fwhm * xFactor), Math.pow(2, 25) - 1);
if (xLength % 2 === 0) xLength++;
}
let yLength = y.length || length;
if (!yLength) {
let {
factor: yFactor = factor
} = y;
yLength = Math.min(Math.ceil(this.y.fwhm * yFactor), Math.pow(2, 25) - 1);
if (yLength % 2 === 0) yLength++;
}
const xCenter = (xLength - 1) / 2;
const yCenter = (yLength - 1) / 2;
const data = new Array(xLength);
for (let i = 0; i < xLength; i++) {
data[i] = new Array(yLength);
}
for (let i = 0; i < xLength; i++) {
for (let j = 0; j < yLength; j++) {
data[i][j] = this.fct(i - xCenter, j - yCenter) * this.height;
}
}
return data;
}
/**
* Return the intensity value of a 2D gaussian shape (see README for equation).
* @param {number} x - x value to calculate.
* @param {number} y - y value to calculate.
* @returns {number} - the z value of bi-dimensional gaussian with the current parameters.
*/
fct(x, y) {
return Gaussian2D.fct(x, y, this.x.fwhm, this.y.fwhm);
}
/**
* Calculate the number of times FWHM allows to reach a specific volume coverage.
* @param {number} [volume=0.9999]
* @returns {number}
*/
getFactor(volume = 0.9999) {
return Gaussian2D.getFactor(volume);
}
/**
* Calculate the volume of the shape.
* @returns {number} - returns the volume.
*/
getVolume() {
return Gaussian2D.getVolume(this.x.fwhm, this.y.fwhm, {
height: this.height
});
}
/**
* Compute the value of Full Width at Half Maximum (FWHM) from the width between the inflection points.
* //https://mathworld.wolfram.com/Gaussian2DFunction.html
* @param {number} width - Width between the inflection points
* @returns {number} fwhm
*/
widthToFWHM(width) {
//https://mathworld.wolfram.com/Gaussian2DFunction.html
return Gaussian2D.widthToFWHM(width);
}
/**
* Compute the value of width between the inflection points from Full Width at Half Maximum (FWHM).
* //https://mathworld.wolfram.com/Gaussian2DFunction.html
* @param {number} fwhm - Full Width at Half Maximum.
* @returns {number} width
*/
fwhmToWidth(fwhm = this.x.fwhm) {
return Gaussian2D.fwhmToWidth(fwhm);
}
/**
* set a new full width at half maximum
* @param {number} fwhm - full width at half maximum
* @param {string|Array} axisLabel - label of axis, if it is undefined fwhm is set to both axis.
*/
setFWHM(fwhm, axisLabel) {
if (!axisLabel) axisLabel = axis;
if (!Array.isArray(axisLabel)) axisLabel = [axisLabel];
for (let i of axisLabel) {
let axisName = i.toLowerCase();
if (axisName !== 'y' && axisName !== 'x') {
throw new Error('axis label should be x or y');
}
this[axisName].fwhm = fwhm;
}
}
/**
* set a new height
* @param {number} height - The maximal intensity of the shape.
*/
setHeight(height) {
this.height = height;
}
}
/**
* Return a parameterized function of a Gaussian2D shape (see README for equation).
* @param {number} x - x value to calculate.
* @param {number} y - y value to calculate.
* @param {number} fwhmX - full width half maximum in the x axis.
* @param {number} fwhmY - full width half maximum in the y axis.
* @returns {number} - the z value of bi-dimensional gaussian with the current parameters.
*/
Gaussian2D.fct = function fct(x, y, xFWHM = 500, yFWHM = 500) {
return Math.exp(GAUSSIAN_EXP_FACTOR * (Math.pow(x / xFWHM, 2) + Math.pow(y / yFWHM, 2)));
};
/**
* Compute the value of Full Width at Half Maximum (FWHM) from the width between the inflection points.
* //https://mathworld.wolfram.com/Gaussian2DFunction.html
* @param {number} width - Width between the inflection points
* @returns {number} fwhm
*/
Gaussian2D.widthToFWHM = function widthToFWHM(width) {
return width * ROOT_2LN2;
};
/**
* Compute the value of width between the inflection points from Full Width at Half Maximum (FWHM).
* //https://mathworld.wolfram.com/Gaussian2DFunction.html
* @param {number} fwhm - Full Width at Half Maximum.
* @returns {number} width
*/
Gaussian2D.fwhmToWidth = function fwhmToWidth(fwhm) {
return fwhm / ROOT_2LN2;
};
/**
* Calculate the volume of a specific shape.
* @param {number} xFWHM - Full width at half maximum for x axis.
* @param {number} yFWHM - Full width at half maximum for y axis.
* @param {object} [options = {}] - options.
* @param {number} [options.height = 1] - Maximum z value of the shape.
* @returns {number} - returns the area of the specific shape and parameters.
*/
Gaussian2D.getVolume = function getVolume(xFWHM, yFWHM, options = {}) {
let {
height = 1
} = options;
return height * Math.PI * xFWHM * yFWHM / Math.LN2 / 4;
};
/**@TODO look for a better factor
* Calculate the number of times FWHM allows to reach a specific volume coverage.
* @param {number} [volume=0.9999]
* @returns {number}
*/
Gaussian2D.getFactor = function getFactor(volume = 0.9999) {
return Math.sqrt(2) * erfinv(volume);
};
function getShapeGenerator(options) {
let {
kind = 'Gaussian',
options: shapeOptions
} = options;
switch (kind.toLowerCase().replace(/[^a-z^0-9]/g, '')) {
case 'gaussian':
return new Gaussian(shapeOptions);
case 'lorentzian':
return new Lorentzian(shapeOptions);
case 'pseudovoigt':
return new PseudoVoigt(shapeOptions);
case 'gaussian2d':
return new Gaussian2D(shapeOptions);
default:
throw new Error(`Unknown kind: ${kind}`);
}
}
/**
* Apply Savitzky Golay algorithm
* @param {array} [ys] Array of y values
* @param {array|number} [xs] Array of X or deltaX
* @param {object} [options={}]
* @param {number} [options.windowSize=9]
* @param {number} [options.derivative=0]
* @param {number} [options.polynomial=3]
* @return {array} Array containing the new ys (same length)
*/
function SavitzkyGolay(ys, xs, options = {}) {
let {
windowSize = 9,
derivative = 0,
polynomial = 3
} = options;
if (windowSize % 2 === 0 || windowSize < 5 || !Number.isInteger(windowSize)) {
throw new RangeError('Invalid window size (should be odd and at least 5 integer number)');
}
if (windowSize > ys.length) {
throw new RangeError(`Window size is higher than the data length ${windowSize}>${ys.length}`);
}
if (derivative < 0 || !Number.isInteger(derivative)) {
throw new RangeError('Derivative should be a positive integer');
}
if (polynomial < 1 || !Number.isInteger(polynomial)) {
throw new RangeError('Polynomial should be a positive integer');
}
if (polynomial >= 6) {
// eslint-disable-next-line no-console
console.warn('You should not use polynomial grade higher than 5 if you are' + ' not sure that your data arises from such a model. Possible polynomial oscillation problems');
}
let half = Math.floor(windowSize / 2);
let np = ys.length;
let ans = new Array(np);
let weights = fullWeights(windowSize, polynomial, derivative);
let hs = 0;
let constantH = true;
if (Array.isArray(xs)) {
constantH = false;
} else {
hs = Math.pow(xs, derivative);
} //For the borders
for (let i = 0; i < half; i++) {
let wg1 = weights[half - i - 1];
let wg2 = weights[half + i + 1];
let d1 = 0;
let d2 = 0;
for (let l = 0; l < windowSize; l++) {
d1 += wg1[l] * ys[l];
d2 += wg2[l] * ys[np - windowSize + l];
}
if (constantH) {
ans[half - i - 1] = d1 / hs;
ans[np - half + i] = d2 / hs;
} else {
hs = getHs(xs, half - i - 1, half, derivative);
ans[half - i - 1] = d1 / hs;
hs = getHs(xs, np - half + i, half, derivative);
ans[np - half + i] = d2 / hs;
}
} //For the internal points
let wg = weights[half];
for (let i = windowSize; i <= np; i++) {
let d = 0;
for (let l = 0; l < windowSize; l++) d += wg[l] * ys[l + i - windowSize];
if (!constantH) hs = getHs(xs, i - half - 1, half, derivative);
ans[i - half - 1] = d / hs;
}
return ans;
}
function getHs(h, center, half, derivative) {
let hs = 0;
let count = 0;
for (let i = center - half; i < center + half; i++) {
if (i >= 0 && i < h.length - 1) {
hs += h[i + 1] - h[i];
count++;
}
}
return Math.pow(hs / count, derivative);
}
function GramPoly(i, m, k, s) {
let Grampoly = 0;
if (k > 0) {
Grampoly = (4 * k - 2) / (k * (2 * m - k + 1)) * (i * GramPoly(i, m, k - 1, s) + s * GramPoly(i, m, k - 1, s - 1)) - (k - 1) * (2 * m + k) / (k * (2 * m - k + 1)) * GramPoly(i, m, k - 2, s);
} else {
if (k === 0 && s === 0) {
Grampoly = 1;
} else {
Grampoly = 0;
}
}
return Grampoly;
}
function GenFact(a, b) {
let gf = 1;
if (a >= b) {
for (let j = a - b + 1; j <= a; j++) {
gf *= j;
}
}
return gf;
}
function Weight(i, t, m, n, s) {
let sum = 0;
for (let k = 0; k <= n; k++) {
//console.log(k);
sum += (2 * k + 1) * (GenFact(2 * m, k) / GenFact(2 * m + k + 1, k + 1)) * GramPoly(i, m, k, 0) * GramPoly(t, m, k, s);
}
return sum;
}
/**
*
* @param m Number of points
* @param n Polynomial grade
* @param s Derivative
*/
function fullWeights(m, n, s) {
let weights = new Array(m);
let np = Math.floor(m / 2);
for (let t = -np; t <= np; t++) {
weights[t + np] = new Array(m);
for (let j = -np; j <= np; j++) {
weights[t + np][j + np] = Weight(j, t, np, n, s);
}
}
return weights;
}
/*function entropy(data,h,options){
var trend = SavitzkyGolay(data,h,trendOptions);
var copy = new Array(data.length);
var sum = 0;
var max = 0;
for(var i=0;i} [data.x] - Independent variable
* @param {Array} [data.y] - Dependent variable
* @param {object} [options={}] - Options object
* @param {object} [options.shape={}] - Object that specified the kind of shape to calculate the FWHM instead of width between inflection points. see https://mljs.github.io/peak-shape-generator/#inflectionpointswidthtofwhm
* @param {object} [options.shape.kind='gaussian']
* @param {object} [options.shape.options={}]
* @param {object} [options.sgOptions] - Options object for Savitzky-Golay filter. See https://github.com/mljs/savitzky-golay-generalized#options
* @param {number} [options.sgOptions.windowSize = 9] - points to use in the approximations
* @param {number} [options.sgOptions.polynomial = 3] - degree of the polynomial to use in the approximations
* @param {number} [options.minMaxRatio = 0.00025] - Threshold to determine if a given peak should be considered as a noise
* @param {number} [options.broadRatio = 0.00] - If `broadRatio` is higher than 0, then all the peaks which second derivative
* smaller than `broadRatio * maxAbsSecondDerivative` will be marked with the soft mask equal to true.
* @param {number} [options.noiseLevel = 0] - Noise threshold in spectrum units
* @param {boolean} [options.maxCriteria = true] - Peaks are local maximum(true) or minimum(false)
* @param {boolean} [options.smoothY = true] - Select the peak intensities from a smoothed version of the independent variables
* @param {boolean} [options.realTopDetection = false] - Use a quadratic optimizations with the peak and its 3 closest neighbors
* to determine the true x,y values of the peak?
* @param {number} [options.heightFactor = 0] - Factor to multiply the calculated height (usually 2)
* @param {number} [options.derivativeThreshold = -1] - Filters based on the amplitude of the first derivative
* @return {Array}
*/
function gsd(data, options = {}) {
let {
noiseLevel,
sgOptions = {
windowSize: 9,
polynomial: 3
},
shape = {},
smoothY = true,
heightFactor = 0,
broadRatio = 0.0,
maxCriteria = true,
minMaxRatio = 0.00025,
derivativeThreshold = -1,
realTopDetection = false
} = options;
let {
y: yIn,
x
} = data;
const y = yIn.slice();
let equalSpaced = isEqualSpaced(x);
if (noiseLevel === undefined) {
noiseLevel = equalSpaced ? getNoiseLevel(y) : 0;
}
const yCorrection = {
m: 1,
b: noiseLevel
};
if (!maxCriteria) {
yCorrection.m = -1;
yCorrection.b *= -1;
}
for (let i = 0; i < y.length; i++) {
y[i] = yCorrection.m * y[i] - yCorrection.b;
}
for (let i = 0; i < y.length; i++) {
if (y[i] < 0) {
y[i] = 0;
}
} // If the max difference between delta x is less than 5%, then,
// we can assume it to be equally spaced variable
let yData = y;
let dY, ddY;
const {
windowSize,
polynomial
} = sgOptions;
if (equalSpaced) {
if (smoothY) {
yData = SavitzkyGolay(y, x[1] - x[0], {
windowSize,
polynomial,
derivative: 0
});
}
dY = SavitzkyGolay(y, x[1] - x[0], {
windowSize,
polynomial,
derivative: 1
});
ddY = SavitzkyGolay(y, x[1] - x[0], {
windowSize,
polynomial,
derivative: 2
});
} else {
if (smoothY) {
yData = SavitzkyGolay(y, x, {
windowSize,
polynomial,
derivative: 0
});
}
dY = SavitzkyGolay(y, x, {
windowSize,
polynomial,
derivative: 1
});
ddY = SavitzkyGolay(y, x, {
windowSize,
polynomial,
derivative: 2
});
}
const xData = x;
const dX = x[1] - x[0];
let maxDdy = 0;
let maxY = 0;
for (let i = 0; i < yData.length; i++) {
if (Math.abs(ddY[i]) > maxDdy) {
maxDdy = Math.abs(ddY[i]);
}
if (Math.abs(yData[i]) > maxY) {
maxY = Math.abs(yData[i]);
}
}
let lastMax = null;
let lastMin = null;
let minddY = [];
let intervalL = [];
let intervalR = [];
let broadMask = []; // By the intermediate value theorem We cannot find 2 consecutive maximum or minimum
for (let i = 1; i < yData.length - 1; ++i) {
// filter based on derivativeThreshold
// console.log('pasa', y[i], dY[i], ddY[i]);
if (Math.abs(dY[i]) > derivativeThreshold) {
// Minimum in first derivative
if (dY[i] < dY[i - 1] && dY[i] <= dY[i + 1] || dY[i] <= dY[i - 1] && dY[i] < dY[i + 1]) {
lastMin = {
x: xData[i],
index: i
};
if (dX > 0 && lastMax !== null) {
intervalL.push(lastMax);
intervalR.push(lastMin);
}
} // Maximum in first derivative
if (dY[i] >= dY[i - 1] && dY[i] > dY[i + 1] || dY[i] > dY[i - 1] && dY[i] >= dY[i + 1]) {
lastMax = {
x: xData[i],
index: i
};
if (dX < 0 && lastMin !== null) {
intervalL.push(lastMax);
intervalR.push(lastMin);
}
}
} // Minimum in second derivative
if (ddY[i] < ddY[i - 1] && ddY[i] < ddY[i + 1]) {
minddY.push(i);
broadMask.push(Math.abs(ddY[i]) <= broadRatio * maxDdy);
}
}
let widthProcessor = shape.kind ? getShapeGenerator(shape.kind, shape.options).widthToFWHM : x => x;
let signals = [];
let lastK = -1;
let possible, frequency, distanceJ, minDistance, gettingCloser;
for (let j = 0; j < minddY.length; ++j) {
frequency = xData[minddY[j]];
possible = -1;
let k = lastK + 1;
minDistance = Number.MAX_VALUE;
distanceJ = 0;
gettingCloser = true;
while (possible === -1 && k < intervalL.length && gettingCloser) {
distanceJ = Math.abs(frequency - (intervalL[k].x + intervalR[k].x) / 2); // Still getting closer?
if (distanceJ < minDistance) {
minDistance = distanceJ;
} else {
gettingCloser = false;
}
if (distanceJ < Math.abs(intervalL[k].x - intervalR[k].x) / 2) {
possible = k;
lastK = k;
}
++k;
}
if (possible !== -1) {
if (Math.abs(yData[minddY[j]]) > minMaxRatio * maxY) {
let width = Math.abs(intervalR[possible].x - intervalL[possible].x);
signals.push({
index: minddY[j],
x: frequency,
y: (yData[minddY[j]] + yCorrection.b) / yCorrection.m,
width: widthProcessor(width),
soft: broadMask[j]
});
signals[signals.length - 1].left = intervalL[possible];
signals[signals.length - 1].right = intervalR[possible];
if (heightFactor) {
let yLeft = yData[intervalL[possible].index];
let yRight = yData[intervalR[possible].index];
signals[signals.length - 1].height = heightFactor * (signals[signals.length - 1].y - (yLeft + yRight) / 2);
}
}
}
}
if (realTopDetection) {
determineRealTop(signals, xData, yData);
} // Correct the values to fit the original spectra data
for (let j = 0; j < signals.length; j++) {
signals[j].base = noiseLevel;
}
signals.sort(function (a, b) {
return a.x - b.x;
});
return signals;
}
const isEqualSpaced = x => {
let tmp;
let maxDx = 0;
let minDx = Number.MAX_SAFE_INTEGER;
for (let i = 0; i < x.length - 1; ++i) {
tmp = Math.abs(x[i + 1] - x[i]);
if (tmp < minDx) {
minDx = tmp;
}
if (tmp > maxDx) {
maxDx = tmp;
}
}
return (maxDx - minDx) / maxDx < 0.05;
};
const getNoiseLevel = y => {
let mean = 0;
let stddev = 0;
let length = y.length;
for (let i = 0; i < length; ++i) {
mean += y[i];
}
mean /= length;
let averageDeviations = new Array(length);
for (let i = 0; i < length; ++i) {
averageDeviations[i] = Math.abs(y[i] - mean);
}
averageDeviations.sort((a, b) => a - b);
if (length % 2 === 1) {
stddev = averageDeviations[(length - 1) / 2] / 0.6745;
} else {
stddev = 0.5 * (averageDeviations[length / 2] + averageDeviations[length / 2 - 1]) / 0.6745;
}
return stddev;
};
const determineRealTop = (peakList, x, y) => {
let alpha, beta, gamma, p, currentPoint;
for (let j = 0; j < peakList.length; j++) {
currentPoint = peakList[j].index; // peakList[j][2];
// The detected peak could be moved 1 or 2 units to left or right.
if (y[currentPoint - 1] >= y[currentPoint - 2] && y[currentPoint - 1] >= y[currentPoint]) {
currentPoint--;
} else {
if (y[currentPoint + 1] >= y[currentPoint] && y[currentPoint + 1] >= y[currentPoint + 2]) {
currentPoint++;
} else {
if (y[currentPoint - 2] >= y[currentPoint - 3] && y[currentPoint - 2] >= y[currentPoint - 1]) {
currentPoint -= 2;
} else {
if (y[currentPoint + 2] >= y[currentPoint + 1] && y[currentPoint + 2] >= y[currentPoint + 3]) {
currentPoint += 2;
}
}
}
} // interpolation to a sin() function
if (y[currentPoint - 1] > 0 && y[currentPoint + 1] > 0 && y[currentPoint] >= y[currentPoint - 1] && y[currentPoint] >= y[currentPoint + 1] && (y[currentPoint] !== y[currentPoint - 1] || y[currentPoint] !== y[currentPoint + 1])) {
alpha = 20 * Math.log10(y[currentPoint - 1]);
beta = 20 * Math.log10(y[currentPoint]);
gamma = 20 * Math.log10(y[currentPoint + 1]);
p = 0.5 * (alpha - gamma) / (alpha - 2 * beta + gamma); // console.log(alpha, beta, gamma, `p: ${p}`);
// console.log(x[currentPoint]+" "+tmp+" "+currentPoint);
peakList[j].x = x[currentPoint] + (x[currentPoint] - x[currentPoint - 1]) * p;
peakList[j].y = y[currentPoint] - 0.25 * (y[currentPoint - 1] - y[currentPoint + 1]) * p;
}
}
};
/*!
* assign-symbols
*
* Copyright (c) 2015-present, Jon Schlinkert.
* Licensed under the MIT License.
*/
const toString = Object.prototype.toString;
const isEnumerable = Object.prototype.propertyIsEnumerable;
const getSymbols = Object.getOwnPropertySymbols;
var assignSymbols = (target, ...args) => {
if (!isObject(target)) {
throw new TypeError('expected the first argument to be an object');
}
if (args.length === 0 || typeof Symbol !== 'function' || typeof getSymbols !== 'function') {
return target;
}
for (let arg of args) {
let names = getSymbols(arg);
for (let key of names) {
if (isEnumerable.call(arg, key)) {
target[key] = arg[key];
}
}
}
return target;
};
function isObject(val) {
return typeof val === 'function' || toString.call(val) === '[object Object]' || Array.isArray(val);
}
/*!
* assign-deep
*
* Copyright (c) 2017-present, Jon Schlinkert.
* Released under the MIT License.
*/
var assignDeep = createCommonjsModule(function (module) {
const toString = Object.prototype.toString;
const isValidKey = key => {
return key !== '__proto__' && key !== 'constructor' && key !== 'prototype';
};
const assign = module.exports = (target, ...args) => {
let i = 0;
if (isPrimitive(target)) target = args[i++];
if (!target) target = {};
for (; i < args.length; i++) {
if (isObject(args[i])) {
for (const key of Object.keys(args[i])) {
if (isValidKey(key)) {
if (isObject(target[key]) && isObject(args[i][key])) {
assign(target[key], args[i][key]);
} else {
target[key] = args[i][key];
}
}
}
assignSymbols(target, args[i]);
}
}
return target;
};
function isObject(val) {
return typeof val === 'function' || toString.call(val) === '[object Object]';
}
function isPrimitive(val) {
return typeof val === 'object' ? val === null : typeof val !== 'function';
}
});
function max(input) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
if (!isAnyArray(input)) {
throw new TypeError('input must be an array');
}
if (input.length === 0) {
throw new TypeError('input must not be empty');
}
var _options$fromIndex = options.fromIndex,
fromIndex = _options$fromIndex === void 0 ? 0 : _options$fromIndex,
_options$toIndex = options.toIndex,
toIndex = _options$toIndex === void 0 ? input.length : _options$toIndex;
if (fromIndex < 0 || fromIndex >= input.length || !Number.isInteger(fromIndex)) {
throw new Error('fromIndex must be a positive integer smaller than length');
}
if (toIndex <= fromIndex || toIndex > input.length || !Number.isInteger(toIndex)) {
throw new Error('toIndex must be an integer greater than fromIndex and at most equal to length');
}
var maxValue = input[fromIndex];
for (var i = fromIndex + 1; i < toIndex; i++) {
if (input[i] > maxValue) maxValue = input[i];
}
return maxValue;
}
/**
* This function calculates the spectrum as a sum of linear combination of gaussian and lorentzian functions. The pseudo voigt
* parameters are divided in 4 batches. 1st: centers; 2nd: heights; 3th: widths; 4th: mu's ;
* @param t Ordinate value
* @param p Lorentzian parameters
* @returns {*}
*/
function sumOfGaussianLorentzians(p) {
return function (t) {
let nL = p.length / 4;
let result = 0;
for (let i = 0; i < nL; i++) {
result += p[i + nL] * PseudoVoigt.fct(t - p[i], p[i + nL * 2], p[i + nL * 3]);
}
return result;
};
}
/**
* This function calculates the spectrum as a sum of gaussian functions. The Gaussian
* parameters are divided in 3 batches. 1st: centers; 2nd: height; 3th: widths;
* @param t Ordinate values
* @param p Gaussian parameters
* @returns {*}
*/
function sumOfGaussians(p) {
return function (t) {
let nL = p.length / 3;
let result = 0;
for (let i = 0; i < nL; i++) {
result += p[i + nL] * Gaussian.fct(t - p[i], p[i + nL * 2]);
}
return result;
};
}
/**
* This function calculates the spectrum as a sum of lorentzian functions. The Lorentzian
* parameters are divided in 3 batches. 1st: centers; 2nd: heights; 3th: widths;
* @param t Ordinate values
* @param p Lorentzian parameters
* @returns {*}
*/
function sumOfLorentzians(p) {
return function (t) {
let nL = p.length / 3;
let result = 0;
for (let i = 0; i < nL; i++) {
result += p[i + nL] * Lorentzian.fct(t - p[i], p[i + nL * 2]);
}
return result;
};
}
function checkInput(data, peaks, options) {
let {
shape = {
kind: 'gaussian'
},
optimization = {
kind: 'lm'
}
} = options;
if (typeof shape.kind !== 'string') {
throw new Error('kind should be a string');
}
let kind = shape.kind.toLowerCase().replace(/[^a-z]/g, '');
let paramsFunc;
let defaultParameters;
switch (kind) {
case 'gaussian':
paramsFunc = sumOfGaussians;
defaultParameters = {
x: {
init: peak => peak.x,
max: peak => peak.x + peak.width * 2,
min: peak => peak.x - peak.width * 2,
gradientDifference: peak => peak.width * 2e-3
},
y: {
init: peak => peak.y,
max: () => 1.5,
min: () => 0,
gradientDifference: () => 1e-3
},
width: {
init: peak => peak.width,
max: peak => peak.width * 4,
min: peak => peak.width * 0.25,
gradientDifference: peak => peak.width * 2e-3
}
};
break;
case 'lorentzian':
paramsFunc = sumOfLorentzians;
defaultParameters = {
x: {
init: peak => peak.x,
max: peak => peak.x + peak.width * 2,
min: peak => peak.x - peak.width * 2,
gradientDifference: peak => peak.width * 2e-3
},
y: {
init: peak => peak.y,
max: () => 1.5,
min: () => 0,
gradientDifference: () => 1e-3
},
width: {
init: peak => peak.width,
max: peak => peak.width * 4,
min: peak => peak.width * 0.25,
gradientDifference: peak => peak.width * 2e-3
}
};
break;
case 'pseudovoigt':
paramsFunc = sumOfGaussianLorentzians;
defaultParameters = {
x: {
init: peak => peak.x,
max: peak => peak.x + peak.width * 2,
min: peak => peak.x - peak.width * 2,
gradientDifference: peak => peak.width * 2e-3
},
y: {
init: peak => peak.y,
max: () => 1.5,
min: () => 0,
gradientDifference: () => 1e-3
},
width: {
init: peak => peak.width,
max: peak => peak.width * 4,
min: peak => peak.width * 0.25,
gradientDifference: peak => peak.width * 2e-3
},
mu: {
init: peak => peak.mu !== undefined ? peak.mu : 0.5,
min: () => 0,
max: () => 1,
gradientDifference: () => 0.01
}
};
break;
default:
throw new Error('kind of shape is not supported');
}
let x = data.x;
let maxY = max(data.y);
let y = new Array(x.length);
for (let i = 0; i < x.length; i++) {
y[i] = data.y[i] / maxY;
}
for (let i = 0; i < peaks.length; i++) {
peaks[i].y /= maxY;
}
let parameters = assignDeep({}, optimization.parameters, defaultParameters);
for (let key in parameters) {
for (let par in parameters[key]) {
if (!Array.isArray(parameters[key][par])) {
parameters[key][par] = [parameters[key][par]];
}
if (parameters[key][par].length !== 1 && parameters[key][par].length !== peaks.length) {
throw new Error(`The length of ${key}-${par} is not correct`);
}
for (let index = 0; index < parameters[key][par].length; index++) {
if (typeof parameters[key][par][index] === 'number') {
let value = parameters[key][par][index];
parameters[key][par][index] = () => value;
}
}
}
}
optimization.parameters = parameters;
return {
y,
x,
maxY,
peaks,
paramsFunc,
optimization
};
}
function checkOptions$1(data, parameterizedFunction, options) {
let {
timeout,
minValues,
maxValues,
initialValues,
weights = 1,
damping = 1e-2,
dampingStepUp = 11,
dampingStepDown = 9,
maxIterations = 100,
errorTolerance = 1e-7,
centralDifference = false,
gradientDifference = 10e-2,
improvementThreshold = 1e-3
} = options;
if (damping <= 0) {
throw new Error('The damping option must be a positive number');
} else if (!data.x || !data.y) {
throw new Error('The data parameter must have x and y elements');
} else if (!isAnyArray(data.x) || data.x.length < 2 || !isAnyArray(data.y) || data.y.length < 2) {
throw new Error('The data parameter elements must be an array with more than 2 points');
} else if (data.x.length !== data.y.length) {
throw new Error('The data parameter elements must have the same size');
}
let parameters = initialValues || new Array(parameterizedFunction.length).fill(1);
let nbPoints = data.y.length;
let parLen = parameters.length;
maxValues = maxValues || new Array(parLen).fill(Number.MAX_SAFE_INTEGER);
minValues = minValues || new Array(parLen).fill(Number.MIN_SAFE_INTEGER);
if (maxValues.length !== minValues.length) {
throw new Error('minValues and maxValues must be the same size');
}
if (!isAnyArray(parameters)) {
throw new Error('initialValues must be an array');
}
if (typeof gradientDifference === 'number') {
gradientDifference = new Array(parameters.length).fill(gradientDifference);
} else if (isAnyArray(gradientDifference)) {
if (gradientDifference.length !== parLen) {
gradientDifference = new Array(parLen).fill(gradientDifference[0]);
}
} else {
throw new Error('gradientDifference should be a number or array with length equal to the number of parameters');
}
let filler;
if (typeof weights === 'number') {
let value = 1 / weights ** 2;
filler = () => value;
} else if (isAnyArray(weights)) {
if (weights.length < data.x.length) {
let value = 1 / weights[0] ** 2;
filler = () => value;
} else {
filler = i => 1 / weights[i] ** 2;
}
} else {
throw new Error('weights should be a number or array with length equal to the number of data points');
}
let checkTimeout;
if (timeout !== undefined) {
if (typeof timeout !== 'number') {
throw new Error('timeout should be a number');
}
let endTime = Date.now() + timeout * 1000;
checkTimeout = () => Date.now() > endTime;
} else {
checkTimeout = () => false;
}
let weightSquare = new Array(data.x.length);
for (let i = 0; i < nbPoints; i++) {
weightSquare[i] = filler(i);
}
return {
checkTimeout,
minValues,
maxValues,
parameters,
weightSquare,
damping,
dampingStepUp,
dampingStepDown,
maxIterations,
errorTolerance,
centralDifference,
gradientDifference,
improvementThreshold
};
}
/**
* the sum of the weighted squares of the errors (or weighted residuals) between the data.y
* and the curve-fit function.
* @ignore
* @param {{x:Array, y:Array}} data - Array of points to fit in the format [x1, x2, ... ], [y1, y2, ... ]
* @param {Array} parameters - Array of current parameter values
* @param {function} parameterizedFunction - The parameters and returns a function with the independent variable as a parameter
* @param {Array} weightSquare - Square of weights
* @return {number}
*/
function errorCalculation(data, parameters, parameterizedFunction, weightSquare) {
let error = 0;
const func = parameterizedFunction(parameters);
for (let i = 0; i < data.x.length; i++) {
error += Math.pow(data.y[i] - func(data.x[i]), 2) / weightSquare[i];
}
return error;
}
function min(input) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
if (!isAnyArray(input)) {
throw new TypeError('input must be an array');
}
if (input.length === 0) {
throw new TypeError('input must not be empty');
}
var _options$fromIndex = options.fromIndex,
fromIndex = _options$fromIndex === void 0 ? 0 : _options$fromIndex,
_options$toIndex = options.toIndex,
toIndex = _options$toIndex === void 0 ? input.length : _options$toIndex;
if (fromIndex < 0 || fromIndex >= input.length || !Number.isInteger(fromIndex)) {
throw new Error('fromIndex must be a positive integer smaller than length');
}
if (toIndex <= fromIndex || toIndex > input.length || !Number.isInteger(toIndex)) {
throw new Error('toIndex must be an integer greater than fromIndex and at most equal to length');
}
var minValue = input[fromIndex];
for (var i = fromIndex + 1; i < toIndex; i++) {
if (input[i] < minValue) minValue = input[i];
}
return minValue;
}
function rescale(input) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
if (!isAnyArray(input)) {
throw new TypeError('input must be an array');
} else if (input.length === 0) {
throw new TypeError('input must not be empty');
}
var output;
if (options.output !== undefined) {
if (!isAnyArray(options.output)) {
throw new TypeError('output option must be an array if specified');
}
output = options.output;
} else {
output = new Array(input.length);
}
var currentMin = min(input);
var currentMax = max(input);
if (currentMin === currentMax) {
throw new RangeError('minimum and maximum input values are equal. Cannot rescale a constant array');
}
var _options$min = options.min,
minValue = _options$min === void 0 ? options.autoMinMax ? currentMin : 0 : _options$min,
_options$max = options.max,
maxValue = _options$max === void 0 ? options.autoMinMax ? currentMax : 1 : _options$max;
if (minValue >= maxValue) {
throw new RangeError('min option must be smaller than max option');
}
var factor = (maxValue - minValue) / (currentMax - currentMin);
for (var i = 0; i < input.length; i++) {
output[i] = (input[i] - currentMin) * factor + minValue;
}
return output;
}
const indent = ' '.repeat(2);
const indentData = ' '.repeat(4);
function inspectMatrix() {
return inspectMatrixWithOptions(this);
}
function inspectMatrixWithOptions(matrix, options = {}) {
const {
maxRows = 15,
maxColumns = 10,
maxNumSize = 8
} = options;
return `${matrix.constructor.name} {
${indent}[
${indentData}${inspectData(matrix, maxRows, maxColumns, maxNumSize)}
${indent}]
${indent}rows: ${matrix.rows}
${indent}columns: ${matrix.columns}
}`;
}
function inspectData(matrix, maxRows, maxColumns, maxNumSize) {
const {
rows,
columns
} = matrix;
const maxI = Math.min(rows, maxRows);
const maxJ = Math.min(columns, maxColumns);
const result = [];
for (let i = 0; i < maxI; i++) {
let line = [];
for (let j = 0; j < maxJ; j++) {
line.push(formatNumber(matrix.get(i, j), maxNumSize));
}
result.push(`${line.join(' ')}`);
}
if (maxJ !== columns) {
result[result.length - 1] += ` ... ${columns - maxColumns} more columns`;
}
if (maxI !== rows) {
result.push(`... ${rows - maxRows} more rows`);
}
return result.join(`\n${indentData}`);
}
function formatNumber(num, maxNumSize) {
const numStr = String(num);
if (numStr.length <= maxNumSize) {
return numStr.padEnd(maxNumSize, ' ');
}
const precise = num.toPrecision(maxNumSize - 2);
if (precise.length <= maxNumSize) {
return precise;
}
const exponential = num.toExponential(maxNumSize - 2);
const eIndex = exponential.indexOf('e');
const e = exponential.slice(eIndex);
return exponential.slice(0, maxNumSize - e.length) + e;
}
function installMathOperations(AbstractMatrix, Matrix) {
AbstractMatrix.prototype.add = function add(value) {
if (typeof value === 'number') return this.addS(value);
return this.addM(value);
};
AbstractMatrix.prototype.addS = function addS(value) {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) + value);
}
}
return this;
};
AbstractMatrix.prototype.addM = function addM(matrix) {
matrix = Matrix.checkMatrix(matrix);
if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
throw new RangeError('Matrices dimensions must be equal');
}
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) + matrix.get(i, j));
}
}
return this;
};
AbstractMatrix.add = function add(matrix, value) {
const newMatrix = new Matrix(matrix);
return newMatrix.add(value);
};
AbstractMatrix.prototype.sub = function sub(value) {
if (typeof value === 'number') return this.subS(value);
return this.subM(value);
};
AbstractMatrix.prototype.subS = function subS(value) {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) - value);
}
}
return this;
};
AbstractMatrix.prototype.subM = function subM(matrix) {
matrix = Matrix.checkMatrix(matrix);
if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
throw new RangeError('Matrices dimensions must be equal');
}
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) - matrix.get(i, j));
}
}
return this;
};
AbstractMatrix.sub = function sub(matrix, value) {
const newMatrix = new Matrix(matrix);
return newMatrix.sub(value);
};
AbstractMatrix.prototype.subtract = AbstractMatrix.prototype.sub;
AbstractMatrix.prototype.subtractS = AbstractMatrix.prototype.subS;
AbstractMatrix.prototype.subtractM = AbstractMatrix.prototype.subM;
AbstractMatrix.subtract = AbstractMatrix.sub;
AbstractMatrix.prototype.mul = function mul(value) {
if (typeof value === 'number') return this.mulS(value);
return this.mulM(value);
};
AbstractMatrix.prototype.mulS = function mulS(value) {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) * value);
}
}
return this;
};
AbstractMatrix.prototype.mulM = function mulM(matrix) {
matrix = Matrix.checkMatrix(matrix);
if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
throw new RangeError('Matrices dimensions must be equal');
}
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) * matrix.get(i, j));
}
}
return this;
};
AbstractMatrix.mul = function mul(matrix, value) {
const newMatrix = new Matrix(matrix);
return newMatrix.mul(value);
};
AbstractMatrix.prototype.multiply = AbstractMatrix.prototype.mul;
AbstractMatrix.prototype.multiplyS = AbstractMatrix.prototype.mulS;
AbstractMatrix.prototype.multiplyM = AbstractMatrix.prototype.mulM;
AbstractMatrix.multiply = AbstractMatrix.mul;
AbstractMatrix.prototype.div = function div(value) {
if (typeof value === 'number') return this.divS(value);
return this.divM(value);
};
AbstractMatrix.prototype.divS = function divS(value) {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) / value);
}
}
return this;
};
AbstractMatrix.prototype.divM = function divM(matrix) {
matrix = Matrix.checkMatrix(matrix);
if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
throw new RangeError('Matrices dimensions must be equal');
}
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) / matrix.get(i, j));
}
}
return this;
};
AbstractMatrix.div = function div(matrix, value) {
const newMatrix = new Matrix(matrix);
return newMatrix.div(value);
};
AbstractMatrix.prototype.divide = AbstractMatrix.prototype.div;
AbstractMatrix.prototype.divideS = AbstractMatrix.prototype.divS;
AbstractMatrix.prototype.divideM = AbstractMatrix.prototype.divM;
AbstractMatrix.divide = AbstractMatrix.div;
AbstractMatrix.prototype.mod = function mod(value) {
if (typeof value === 'number') return this.modS(value);
return this.modM(value);
};
AbstractMatrix.prototype.modS = function modS(value) {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) % value);
}
}
return this;
};
AbstractMatrix.prototype.modM = function modM(matrix) {
matrix = Matrix.checkMatrix(matrix);
if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
throw new RangeError('Matrices dimensions must be equal');
}
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) % matrix.get(i, j));
}
}
return this;
};
AbstractMatrix.mod = function mod(matrix, value) {
const newMatrix = new Matrix(matrix);
return newMatrix.mod(value);
};
AbstractMatrix.prototype.modulus = AbstractMatrix.prototype.mod;
AbstractMatrix.prototype.modulusS = AbstractMatrix.prototype.modS;
AbstractMatrix.prototype.modulusM = AbstractMatrix.prototype.modM;
AbstractMatrix.modulus = AbstractMatrix.mod;
AbstractMatrix.prototype.and = function and(value) {
if (typeof value === 'number') return this.andS(value);
return this.andM(value);
};
AbstractMatrix.prototype.andS = function andS(value) {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) & value);
}
}
return this;
};
AbstractMatrix.prototype.andM = function andM(matrix) {
matrix = Matrix.checkMatrix(matrix);
if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
throw new RangeError('Matrices dimensions must be equal');
}
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) & matrix.get(i, j));
}
}
return this;
};
AbstractMatrix.and = function and(matrix, value) {
const newMatrix = new Matrix(matrix);
return newMatrix.and(value);
};
AbstractMatrix.prototype.or = function or(value) {
if (typeof value === 'number') return this.orS(value);
return this.orM(value);
};
AbstractMatrix.prototype.orS = function orS(value) {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) | value);
}
}
return this;
};
AbstractMatrix.prototype.orM = function orM(matrix) {
matrix = Matrix.checkMatrix(matrix);
if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
throw new RangeError('Matrices dimensions must be equal');
}
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) | matrix.get(i, j));
}
}
return this;
};
AbstractMatrix.or = function or(matrix, value) {
const newMatrix = new Matrix(matrix);
return newMatrix.or(value);
};
AbstractMatrix.prototype.xor = function xor(value) {
if (typeof value === 'number') return this.xorS(value);
return this.xorM(value);
};
AbstractMatrix.prototype.xorS = function xorS(value) {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) ^ value);
}
}
return this;
};
AbstractMatrix.prototype.xorM = function xorM(matrix) {
matrix = Matrix.checkMatrix(matrix);
if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
throw new RangeError('Matrices dimensions must be equal');
}
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) ^ matrix.get(i, j));
}
}
return this;
};
AbstractMatrix.xor = function xor(matrix, value) {
const newMatrix = new Matrix(matrix);
return newMatrix.xor(value);
};
AbstractMatrix.prototype.leftShift = function leftShift(value) {
if (typeof value === 'number') return this.leftShiftS(value);
return this.leftShiftM(value);
};
AbstractMatrix.prototype.leftShiftS = function leftShiftS(value) {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) << value);
}
}
return this;
};
AbstractMatrix.prototype.leftShiftM = function leftShiftM(matrix) {
matrix = Matrix.checkMatrix(matrix);
if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
throw new RangeError('Matrices dimensions must be equal');
}
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) << matrix.get(i, j));
}
}
return this;
};
AbstractMatrix.leftShift = function leftShift(matrix, value) {
const newMatrix = new Matrix(matrix);
return newMatrix.leftShift(value);
};
AbstractMatrix.prototype.signPropagatingRightShift = function signPropagatingRightShift(value) {
if (typeof value === 'number') return this.signPropagatingRightShiftS(value);
return this.signPropagatingRightShiftM(value);
};
AbstractMatrix.prototype.signPropagatingRightShiftS = function signPropagatingRightShiftS(value) {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) >> value);
}
}
return this;
};
AbstractMatrix.prototype.signPropagatingRightShiftM = function signPropagatingRightShiftM(matrix) {
matrix = Matrix.checkMatrix(matrix);
if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
throw new RangeError('Matrices dimensions must be equal');
}
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) >> matrix.get(i, j));
}
}
return this;
};
AbstractMatrix.signPropagatingRightShift = function signPropagatingRightShift(matrix, value) {
const newMatrix = new Matrix(matrix);
return newMatrix.signPropagatingRightShift(value);
};
AbstractMatrix.prototype.rightShift = function rightShift(value) {
if (typeof value === 'number') return this.rightShiftS(value);
return this.rightShiftM(value);
};
AbstractMatrix.prototype.rightShiftS = function rightShiftS(value) {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) >>> value);
}
}
return this;
};
AbstractMatrix.prototype.rightShiftM = function rightShiftM(matrix) {
matrix = Matrix.checkMatrix(matrix);
if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
throw new RangeError('Matrices dimensions must be equal');
}
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) >>> matrix.get(i, j));
}
}
return this;
};
AbstractMatrix.rightShift = function rightShift(matrix, value) {
const newMatrix = new Matrix(matrix);
return newMatrix.rightShift(value);
};
AbstractMatrix.prototype.zeroFillRightShift = AbstractMatrix.prototype.rightShift;
AbstractMatrix.prototype.zeroFillRightShiftS = AbstractMatrix.prototype.rightShiftS;
AbstractMatrix.prototype.zeroFillRightShiftM = AbstractMatrix.prototype.rightShiftM;
AbstractMatrix.zeroFillRightShift = AbstractMatrix.rightShift;
AbstractMatrix.prototype.not = function not() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, ~this.get(i, j));
}
}
return this;
};
AbstractMatrix.not = function not(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.not();
};
AbstractMatrix.prototype.abs = function abs() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.abs(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.abs = function abs(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.abs();
};
AbstractMatrix.prototype.acos = function acos() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.acos(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.acos = function acos(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.acos();
};
AbstractMatrix.prototype.acosh = function acosh() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.acosh(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.acosh = function acosh(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.acosh();
};
AbstractMatrix.prototype.asin = function asin() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.asin(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.asin = function asin(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.asin();
};
AbstractMatrix.prototype.asinh = function asinh() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.asinh(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.asinh = function asinh(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.asinh();
};
AbstractMatrix.prototype.atan = function atan() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.atan(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.atan = function atan(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.atan();
};
AbstractMatrix.prototype.atanh = function atanh() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.atanh(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.atanh = function atanh(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.atanh();
};
AbstractMatrix.prototype.cbrt = function cbrt() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.cbrt(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.cbrt = function cbrt(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.cbrt();
};
AbstractMatrix.prototype.ceil = function ceil() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.ceil(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.ceil = function ceil(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.ceil();
};
AbstractMatrix.prototype.clz32 = function clz32() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.clz32(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.clz32 = function clz32(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.clz32();
};
AbstractMatrix.prototype.cos = function cos() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.cos(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.cos = function cos(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.cos();
};
AbstractMatrix.prototype.cosh = function cosh() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.cosh(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.cosh = function cosh(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.cosh();
};
AbstractMatrix.prototype.exp = function exp() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.exp(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.exp = function exp(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.exp();
};
AbstractMatrix.prototype.expm1 = function expm1() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.expm1(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.expm1 = function expm1(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.expm1();
};
AbstractMatrix.prototype.floor = function floor() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.floor(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.floor = function floor(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.floor();
};
AbstractMatrix.prototype.fround = function fround() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.fround(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.fround = function fround(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.fround();
};
AbstractMatrix.prototype.log = function log() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.log(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.log = function log(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.log();
};
AbstractMatrix.prototype.log1p = function log1p() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.log1p(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.log1p = function log1p(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.log1p();
};
AbstractMatrix.prototype.log10 = function log10() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.log10(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.log10 = function log10(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.log10();
};
AbstractMatrix.prototype.log2 = function log2() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.log2(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.log2 = function log2(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.log2();
};
AbstractMatrix.prototype.round = function round() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.round(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.round = function round(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.round();
};
AbstractMatrix.prototype.sign = function sign() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.sign(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.sign = function sign(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.sign();
};
AbstractMatrix.prototype.sin = function sin() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.sin(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.sin = function sin(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.sin();
};
AbstractMatrix.prototype.sinh = function sinh() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.sinh(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.sinh = function sinh(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.sinh();
};
AbstractMatrix.prototype.sqrt = function sqrt() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.sqrt(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.sqrt = function sqrt(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.sqrt();
};
AbstractMatrix.prototype.tan = function tan() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.tan(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.tan = function tan(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.tan();
};
AbstractMatrix.prototype.tanh = function tanh() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.tanh(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.tanh = function tanh(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.tanh();
};
AbstractMatrix.prototype.trunc = function trunc() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.trunc(this.get(i, j)));
}
}
return this;
};
AbstractMatrix.trunc = function trunc(matrix) {
const newMatrix = new Matrix(matrix);
return newMatrix.trunc();
};
AbstractMatrix.pow = function pow(matrix, arg0) {
const newMatrix = new Matrix(matrix);
return newMatrix.pow(arg0);
};
AbstractMatrix.prototype.pow = function pow(value) {
if (typeof value === 'number') return this.powS(value);
return this.powM(value);
};
AbstractMatrix.prototype.powS = function powS(value) {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.pow(this.get(i, j), value));
}
}
return this;
};
AbstractMatrix.prototype.powM = function powM(matrix) {
matrix = Matrix.checkMatrix(matrix);
if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
throw new RangeError('Matrices dimensions must be equal');
}
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, Math.pow(this.get(i, j), matrix.get(i, j)));
}
}
return this;
};
}
/**
* @private
* Check that a row index is not out of bounds
* @param {Matrix} matrix
* @param {number} index
* @param {boolean} [outer]
*/
function checkRowIndex(matrix, index, outer) {
let max = outer ? matrix.rows : matrix.rows - 1;
if (index < 0 || index > max) {
throw new RangeError('Row index out of range');
}
}
/**
* @private
* Check that a column index is not out of bounds
* @param {Matrix} matrix
* @param {number} index
* @param {boolean} [outer]
*/
function checkColumnIndex(matrix, index, outer) {
let max = outer ? matrix.columns : matrix.columns - 1;
if (index < 0 || index > max) {
throw new RangeError('Column index out of range');
}
}
/**
* @private
* Check that the provided vector is an array with the right length
* @param {Matrix} matrix
* @param {Array|Matrix} vector
* @return {Array}
* @throws {RangeError}
*/
function checkRowVector(matrix, vector) {
if (vector.to1DArray) {
vector = vector.to1DArray();
}
if (vector.length !== matrix.columns) {
throw new RangeError('vector size must be the same as the number of columns');
}
return vector;
}
/**
* @private
* Check that the provided vector is an array with the right length
* @param {Matrix} matrix
* @param {Array|Matrix} vector
* @return {Array}
* @throws {RangeError}
*/
function checkColumnVector(matrix, vector) {
if (vector.to1DArray) {
vector = vector.to1DArray();
}
if (vector.length !== matrix.rows) {
throw new RangeError('vector size must be the same as the number of rows');
}
return vector;
}
function checkIndices(matrix, rowIndices, columnIndices) {
return {
row: checkRowIndices(matrix, rowIndices),
column: checkColumnIndices(matrix, columnIndices)
};
}
function checkRowIndices(matrix, rowIndices) {
if (typeof rowIndices !== 'object') {
throw new TypeError('unexpected type for row indices');
}
let rowOut = rowIndices.some(r => {
return r < 0 || r >= matrix.rows;
});
if (rowOut) {
throw new RangeError('row indices are out of range');
}
if (!Array.isArray(rowIndices)) rowIndices = Array.from(rowIndices);
return rowIndices;
}
function checkColumnIndices(matrix, columnIndices) {
if (typeof columnIndices !== 'object') {
throw new TypeError('unexpected type for column indices');
}
let columnOut = columnIndices.some(c => {
return c < 0 || c >= matrix.columns;
});
if (columnOut) {
throw new RangeError('column indices are out of range');
}
if (!Array.isArray(columnIndices)) columnIndices = Array.from(columnIndices);
return columnIndices;
}
function checkRange(matrix, startRow, endRow, startColumn, endColumn) {
if (arguments.length !== 5) {
throw new RangeError('expected 4 arguments');
}
checkNumber('startRow', startRow);
checkNumber('endRow', endRow);
checkNumber('startColumn', startColumn);
checkNumber('endColumn', endColumn);
if (startRow > endRow || startColumn > endColumn || startRow < 0 || startRow >= matrix.rows || endRow < 0 || endRow >= matrix.rows || startColumn < 0 || startColumn >= matrix.columns || endColumn < 0 || endColumn >= matrix.columns) {
throw new RangeError('Submatrix indices are out of range');
}
}
function newArray(length, value = 0) {
let array = [];
for (let i = 0; i < length; i++) {
array.push(value);
}
return array;
}
function checkNumber(name, value) {
if (typeof value !== 'number') {
throw new TypeError(`${name} must be a number`);
}
}
function checkNonEmpty(matrix) {
if (matrix.isEmpty()) {
throw new Error('Empty matrix has no elements to index');
}
}
function sumByRow(matrix) {
let sum = newArray(matrix.rows);
for (let i = 0; i < matrix.rows; ++i) {
for (let j = 0; j < matrix.columns; ++j) {
sum[i] += matrix.get(i, j);
}
}
return sum;
}
function sumByColumn(matrix) {
let sum = newArray(matrix.columns);
for (let i = 0; i < matrix.rows; ++i) {
for (let j = 0; j < matrix.columns; ++j) {
sum[j] += matrix.get(i, j);
}
}
return sum;
}
function sumAll(matrix) {
let v = 0;
for (let i = 0; i < matrix.rows; i++) {
for (let j = 0; j < matrix.columns; j++) {
v += matrix.get(i, j);
}
}
return v;
}
function productByRow(matrix) {
let sum = newArray(matrix.rows, 1);
for (let i = 0; i < matrix.rows; ++i) {
for (let j = 0; j < matrix.columns; ++j) {
sum[i] *= matrix.get(i, j);
}
}
return sum;
}
function productByColumn(matrix) {
let sum = newArray(matrix.columns, 1);
for (let i = 0; i < matrix.rows; ++i) {
for (let j = 0; j < matrix.columns; ++j) {
sum[j] *= matrix.get(i, j);
}
}
return sum;
}
function productAll(matrix) {
let v = 1;
for (let i = 0; i < matrix.rows; i++) {
for (let j = 0; j < matrix.columns; j++) {
v *= matrix.get(i, j);
}
}
return v;
}
function varianceByRow(matrix, unbiased, mean) {
const rows = matrix.rows;
const cols = matrix.columns;
const variance = [];
for (let i = 0; i < rows; i++) {
let sum1 = 0;
let sum2 = 0;
let x = 0;
for (let j = 0; j < cols; j++) {
x = matrix.get(i, j) - mean[i];
sum1 += x;
sum2 += x * x;
}
if (unbiased) {
variance.push((sum2 - sum1 * sum1 / cols) / (cols - 1));
} else {
variance.push((sum2 - sum1 * sum1 / cols) / cols);
}
}
return variance;
}
function varianceByColumn(matrix, unbiased, mean) {
const rows = matrix.rows;
const cols = matrix.columns;
const variance = [];
for (let j = 0; j < cols; j++) {
let sum1 = 0;
let sum2 = 0;
let x = 0;
for (let i = 0; i < rows; i++) {
x = matrix.get(i, j) - mean[j];
sum1 += x;
sum2 += x * x;
}
if (unbiased) {
variance.push((sum2 - sum1 * sum1 / rows) / (rows - 1));
} else {
variance.push((sum2 - sum1 * sum1 / rows) / rows);
}
}
return variance;
}
function varianceAll(matrix, unbiased, mean) {
const rows = matrix.rows;
const cols = matrix.columns;
const size = rows * cols;
let sum1 = 0;
let sum2 = 0;
let x = 0;
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
x = matrix.get(i, j) - mean;
sum1 += x;
sum2 += x * x;
}
}
if (unbiased) {
return (sum2 - sum1 * sum1 / size) / (size - 1);
} else {
return (sum2 - sum1 * sum1 / size) / size;
}
}
function centerByRow(matrix, mean) {
for (let i = 0; i < matrix.rows; i++) {
for (let j = 0; j < matrix.columns; j++) {
matrix.set(i, j, matrix.get(i, j) - mean[i]);
}
}
}
function centerByColumn(matrix, mean) {
for (let i = 0; i < matrix.rows; i++) {
for (let j = 0; j < matrix.columns; j++) {
matrix.set(i, j, matrix.get(i, j) - mean[j]);
}
}
}
function centerAll(matrix, mean) {
for (let i = 0; i < matrix.rows; i++) {
for (let j = 0; j < matrix.columns; j++) {
matrix.set(i, j, matrix.get(i, j) - mean);
}
}
}
function getScaleByRow(matrix) {
const scale = [];
for (let i = 0; i < matrix.rows; i++) {
let sum = 0;
for (let j = 0; j < matrix.columns; j++) {
sum += Math.pow(matrix.get(i, j), 2) / (matrix.columns - 1);
}
scale.push(Math.sqrt(sum));
}
return scale;
}
function scaleByRow(matrix, scale) {
for (let i = 0; i < matrix.rows; i++) {
for (let j = 0; j < matrix.columns; j++) {
matrix.set(i, j, matrix.get(i, j) / scale[i]);
}
}
}
function getScaleByColumn(matrix) {
const scale = [];
for (let j = 0; j < matrix.columns; j++) {
let sum = 0;
for (let i = 0; i < matrix.rows; i++) {
sum += Math.pow(matrix.get(i, j), 2) / (matrix.rows - 1);
}
scale.push(Math.sqrt(sum));
}
return scale;
}
function scaleByColumn(matrix, scale) {
for (let i = 0; i < matrix.rows; i++) {
for (let j = 0; j < matrix.columns; j++) {
matrix.set(i, j, matrix.get(i, j) / scale[j]);
}
}
}
function getScaleAll(matrix) {
const divider = matrix.size - 1;
let sum = 0;
for (let j = 0; j < matrix.columns; j++) {
for (let i = 0; i < matrix.rows; i++) {
sum += Math.pow(matrix.get(i, j), 2) / divider;
}
}
return Math.sqrt(sum);
}
function scaleAll(matrix, scale) {
for (let i = 0; i < matrix.rows; i++) {
for (let j = 0; j < matrix.columns; j++) {
matrix.set(i, j, matrix.get(i, j) / scale);
}
}
}
class AbstractMatrix {
static from1DArray(newRows, newColumns, newData) {
let length = newRows * newColumns;
if (length !== newData.length) {
throw new RangeError('data length does not match given dimensions');
}
let newMatrix = new Matrix(newRows, newColumns);
for (let row = 0; row < newRows; row++) {
for (let column = 0; column < newColumns; column++) {
newMatrix.set(row, column, newData[row * newColumns + column]);
}
}
return newMatrix;
}
static rowVector(newData) {
let vector = new Matrix(1, newData.length);
for (let i = 0; i < newData.length; i++) {
vector.set(0, i, newData[i]);
}
return vector;
}
static columnVector(newData) {
let vector = new Matrix(newData.length, 1);
for (let i = 0; i < newData.length; i++) {
vector.set(i, 0, newData[i]);
}
return vector;
}
static zeros(rows, columns) {
return new Matrix(rows, columns);
}
static ones(rows, columns) {
return new Matrix(rows, columns).fill(1);
}
static rand(rows, columns, options = {}) {
if (typeof options !== 'object') {
throw new TypeError('options must be an object');
}
const {
random = Math.random
} = options;
let matrix = new Matrix(rows, columns);
for (let i = 0; i < rows; i++) {
for (let j = 0; j < columns; j++) {
matrix.set(i, j, random());
}
}
return matrix;
}
static randInt(rows, columns, options = {}) {
if (typeof options !== 'object') {
throw new TypeError('options must be an object');
}
const {
min = 0,
max = 1000,
random = Math.random
} = options;
if (!Number.isInteger(min)) throw new TypeError('min must be an integer');
if (!Number.isInteger(max)) throw new TypeError('max must be an integer');
if (min >= max) throw new RangeError('min must be smaller than max');
let interval = max - min;
let matrix = new Matrix(rows, columns);
for (let i = 0; i < rows; i++) {
for (let j = 0; j < columns; j++) {
let value = min + Math.round(random() * interval);
matrix.set(i, j, value);
}
}
return matrix;
}
static eye(rows, columns, value) {
if (columns === undefined) columns = rows;
if (value === undefined) value = 1;
let min = Math.min(rows, columns);
let matrix = this.zeros(rows, columns);
for (let i = 0; i < min; i++) {
matrix.set(i, i, value);
}
return matrix;
}
static diag(data, rows, columns) {
let l = data.length;
if (rows === undefined) rows = l;
if (columns === undefined) columns = rows;
let min = Math.min(l, rows, columns);
let matrix = this.zeros(rows, columns);
for (let i = 0; i < min; i++) {
matrix.set(i, i, data[i]);
}
return matrix;
}
static min(matrix1, matrix2) {
matrix1 = this.checkMatrix(matrix1);
matrix2 = this.checkMatrix(matrix2);
let rows = matrix1.rows;
let columns = matrix1.columns;
let result = new Matrix(rows, columns);
for (let i = 0; i < rows; i++) {
for (let j = 0; j < columns; j++) {
result.set(i, j, Math.min(matrix1.get(i, j), matrix2.get(i, j)));
}
}
return result;
}
static max(matrix1, matrix2) {
matrix1 = this.checkMatrix(matrix1);
matrix2 = this.checkMatrix(matrix2);
let rows = matrix1.rows;
let columns = matrix1.columns;
let result = new this(rows, columns);
for (let i = 0; i < rows; i++) {
for (let j = 0; j < columns; j++) {
result.set(i, j, Math.max(matrix1.get(i, j), matrix2.get(i, j)));
}
}
return result;
}
static checkMatrix(value) {
return AbstractMatrix.isMatrix(value) ? value : new Matrix(value);
}
static isMatrix(value) {
return value != null && value.klass === 'Matrix';
}
get size() {
return this.rows * this.columns;
}
apply(callback) {
if (typeof callback !== 'function') {
throw new TypeError('callback must be a function');
}
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
callback.call(this, i, j);
}
}
return this;
}
to1DArray() {
let array = [];
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
array.push(this.get(i, j));
}
}
return array;
}
to2DArray() {
let copy = [];
for (let i = 0; i < this.rows; i++) {
copy.push([]);
for (let j = 0; j < this.columns; j++) {
copy[i].push(this.get(i, j));
}
}
return copy;
}
toJSON() {
return this.to2DArray();
}
isRowVector() {
return this.rows === 1;
}
isColumnVector() {
return this.columns === 1;
}
isVector() {
return this.rows === 1 || this.columns === 1;
}
isSquare() {
return this.rows === this.columns;
}
isEmpty() {
return this.rows === 0 || this.columns === 0;
}
isSymmetric() {
if (this.isSquare()) {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j <= i; j++) {
if (this.get(i, j) !== this.get(j, i)) {
return false;
}
}
}
return true;
}
return false;
}
isEchelonForm() {
let i = 0;
let j = 0;
let previousColumn = -1;
let isEchelonForm = true;
let checked = false;
while (i < this.rows && isEchelonForm) {
j = 0;
checked = false;
while (j < this.columns && checked === false) {
if (this.get(i, j) === 0) {
j++;
} else if (this.get(i, j) === 1 && j > previousColumn) {
checked = true;
previousColumn = j;
} else {
isEchelonForm = false;
checked = true;
}
}
i++;
}
return isEchelonForm;
}
isReducedEchelonForm() {
let i = 0;
let j = 0;
let previousColumn = -1;
let isReducedEchelonForm = true;
let checked = false;
while (i < this.rows && isReducedEchelonForm) {
j = 0;
checked = false;
while (j < this.columns && checked === false) {
if (this.get(i, j) === 0) {
j++;
} else if (this.get(i, j) === 1 && j > previousColumn) {
checked = true;
previousColumn = j;
} else {
isReducedEchelonForm = false;
checked = true;
}
}
for (let k = j + 1; k < this.rows; k++) {
if (this.get(i, k) !== 0) {
isReducedEchelonForm = false;
}
}
i++;
}
return isReducedEchelonForm;
}
echelonForm() {
let result = this.clone();
let h = 0;
let k = 0;
while (h < result.rows && k < result.columns) {
let iMax = h;
for (let i = h; i < result.rows; i++) {
if (result.get(i, k) > result.get(iMax, k)) {
iMax = i;
}
}
if (result.get(iMax, k) === 0) {
k++;
} else {
result.swapRows(h, iMax);
let tmp = result.get(h, k);
for (let j = k; j < result.columns; j++) {
result.set(h, j, result.get(h, j) / tmp);
}
for (let i = h + 1; i < result.rows; i++) {
let factor = result.get(i, k) / result.get(h, k);
result.set(i, k, 0);
for (let j = k + 1; j < result.columns; j++) {
result.set(i, j, result.get(i, j) - result.get(h, j) * factor);
}
}
h++;
k++;
}
}
return result;
}
reducedEchelonForm() {
let result = this.echelonForm();
let m = result.columns;
let n = result.rows;
let h = n - 1;
while (h >= 0) {
if (result.maxRow(h) === 0) {
h--;
} else {
let p = 0;
let pivot = false;
while (p < n && pivot === false) {
if (result.get(h, p) === 1) {
pivot = true;
} else {
p++;
}
}
for (let i = 0; i < h; i++) {
let factor = result.get(i, p);
for (let j = p; j < m; j++) {
let tmp = result.get(i, j) - factor * result.get(h, j);
result.set(i, j, tmp);
}
}
h--;
}
}
return result;
}
set() {
throw new Error('set method is unimplemented');
}
get() {
throw new Error('get method is unimplemented');
}
repeat(options = {}) {
if (typeof options !== 'object') {
throw new TypeError('options must be an object');
}
const {
rows = 1,
columns = 1
} = options;
if (!Number.isInteger(rows) || rows <= 0) {
throw new TypeError('rows must be a positive integer');
}
if (!Number.isInteger(columns) || columns <= 0) {
throw new TypeError('columns must be a positive integer');
}
let matrix = new Matrix(this.rows * rows, this.columns * columns);
for (let i = 0; i < rows; i++) {
for (let j = 0; j < columns; j++) {
matrix.setSubMatrix(this, this.rows * i, this.columns * j);
}
}
return matrix;
}
fill(value) {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, value);
}
}
return this;
}
neg() {
return this.mulS(-1);
}
getRow(index) {
checkRowIndex(this, index);
let row = [];
for (let i = 0; i < this.columns; i++) {
row.push(this.get(index, i));
}
return row;
}
getRowVector(index) {
return Matrix.rowVector(this.getRow(index));
}
setRow(index, array) {
checkRowIndex(this, index);
array = checkRowVector(this, array);
for (let i = 0; i < this.columns; i++) {
this.set(index, i, array[i]);
}
return this;
}
swapRows(row1, row2) {
checkRowIndex(this, row1);
checkRowIndex(this, row2);
for (let i = 0; i < this.columns; i++) {
let temp = this.get(row1, i);
this.set(row1, i, this.get(row2, i));
this.set(row2, i, temp);
}
return this;
}
getColumn(index) {
checkColumnIndex(this, index);
let column = [];
for (let i = 0; i < this.rows; i++) {
column.push(this.get(i, index));
}
return column;
}
getColumnVector(index) {
return Matrix.columnVector(this.getColumn(index));
}
setColumn(index, array) {
checkColumnIndex(this, index);
array = checkColumnVector(this, array);
for (let i = 0; i < this.rows; i++) {
this.set(i, index, array[i]);
}
return this;
}
swapColumns(column1, column2) {
checkColumnIndex(this, column1);
checkColumnIndex(this, column2);
for (let i = 0; i < this.rows; i++) {
let temp = this.get(i, column1);
this.set(i, column1, this.get(i, column2));
this.set(i, column2, temp);
}
return this;
}
addRowVector(vector) {
vector = checkRowVector(this, vector);
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) + vector[j]);
}
}
return this;
}
subRowVector(vector) {
vector = checkRowVector(this, vector);
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) - vector[j]);
}
}
return this;
}
mulRowVector(vector) {
vector = checkRowVector(this, vector);
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) * vector[j]);
}
}
return this;
}
divRowVector(vector) {
vector = checkRowVector(this, vector);
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) / vector[j]);
}
}
return this;
}
addColumnVector(vector) {
vector = checkColumnVector(this, vector);
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) + vector[i]);
}
}
return this;
}
subColumnVector(vector) {
vector = checkColumnVector(this, vector);
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) - vector[i]);
}
}
return this;
}
mulColumnVector(vector) {
vector = checkColumnVector(this, vector);
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) * vector[i]);
}
}
return this;
}
divColumnVector(vector) {
vector = checkColumnVector(this, vector);
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
this.set(i, j, this.get(i, j) / vector[i]);
}
}
return this;
}
mulRow(index, value) {
checkRowIndex(this, index);
for (let i = 0; i < this.columns; i++) {
this.set(index, i, this.get(index, i) * value);
}
return this;
}
mulColumn(index, value) {
checkColumnIndex(this, index);
for (let i = 0; i < this.rows; i++) {
this.set(i, index, this.get(i, index) * value);
}
return this;
}
max() {
if (this.isEmpty()) {
return NaN;
}
let v = this.get(0, 0);
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
if (this.get(i, j) > v) {
v = this.get(i, j);
}
}
}
return v;
}
maxIndex() {
checkNonEmpty(this);
let v = this.get(0, 0);
let idx = [0, 0];
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
if (this.get(i, j) > v) {
v = this.get(i, j);
idx[0] = i;
idx[1] = j;
}
}
}
return idx;
}
min() {
if (this.isEmpty()) {
return NaN;
}
let v = this.get(0, 0);
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
if (this.get(i, j) < v) {
v = this.get(i, j);
}
}
}
return v;
}
minIndex() {
checkNonEmpty(this);
let v = this.get(0, 0);
let idx = [0, 0];
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
if (this.get(i, j) < v) {
v = this.get(i, j);
idx[0] = i;
idx[1] = j;
}
}
}
return idx;
}
maxRow(row) {
checkRowIndex(this, row);
if (this.isEmpty()) {
return NaN;
}
let v = this.get(row, 0);
for (let i = 1; i < this.columns; i++) {
if (this.get(row, i) > v) {
v = this.get(row, i);
}
}
return v;
}
maxRowIndex(row) {
checkRowIndex(this, row);
checkNonEmpty(this);
let v = this.get(row, 0);
let idx = [row, 0];
for (let i = 1; i < this.columns; i++) {
if (this.get(row, i) > v) {
v = this.get(row, i);
idx[1] = i;
}
}
return idx;
}
minRow(row) {
checkRowIndex(this, row);
if (this.isEmpty()) {
return NaN;
}
let v = this.get(row, 0);
for (let i = 1; i < this.columns; i++) {
if (this.get(row, i) < v) {
v = this.get(row, i);
}
}
return v;
}
minRowIndex(row) {
checkRowIndex(this, row);
checkNonEmpty(this);
let v = this.get(row, 0);
let idx = [row, 0];
for (let i = 1; i < this.columns; i++) {
if (this.get(row, i) < v) {
v = this.get(row, i);
idx[1] = i;
}
}
return idx;
}
maxColumn(column) {
checkColumnIndex(this, column);
if (this.isEmpty()) {
return NaN;
}
let v = this.get(0, column);
for (let i = 1; i < this.rows; i++) {
if (this.get(i, column) > v) {
v = this.get(i, column);
}
}
return v;
}
maxColumnIndex(column) {
checkColumnIndex(this, column);
checkNonEmpty(this);
let v = this.get(0, column);
let idx = [0, column];
for (let i = 1; i < this.rows; i++) {
if (this.get(i, column) > v) {
v = this.get(i, column);
idx[0] = i;
}
}
return idx;
}
minColumn(column) {
checkColumnIndex(this, column);
if (this.isEmpty()) {
return NaN;
}
let v = this.get(0, column);
for (let i = 1; i < this.rows; i++) {
if (this.get(i, column) < v) {
v = this.get(i, column);
}
}
return v;
}
minColumnIndex(column) {
checkColumnIndex(this, column);
checkNonEmpty(this);
let v = this.get(0, column);
let idx = [0, column];
for (let i = 1; i < this.rows; i++) {
if (this.get(i, column) < v) {
v = this.get(i, column);
idx[0] = i;
}
}
return idx;
}
diag() {
let min = Math.min(this.rows, this.columns);
let diag = [];
for (let i = 0; i < min; i++) {
diag.push(this.get(i, i));
}
return diag;
}
norm(type = 'frobenius') {
let result = 0;
if (type === 'max') {
return this.max();
} else if (type === 'frobenius') {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
result = result + this.get(i, j) * this.get(i, j);
}
}
return Math.sqrt(result);
} else {
throw new RangeError(`unknown norm type: ${type}`);
}
}
cumulativeSum() {
let sum = 0;
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
sum += this.get(i, j);
this.set(i, j, sum);
}
}
return this;
}
dot(vector2) {
if (AbstractMatrix.isMatrix(vector2)) vector2 = vector2.to1DArray();
let vector1 = this.to1DArray();
if (vector1.length !== vector2.length) {
throw new RangeError('vectors do not have the same size');
}
let dot = 0;
for (let i = 0; i < vector1.length; i++) {
dot += vector1[i] * vector2[i];
}
return dot;
}
mmul(other) {
other = Matrix.checkMatrix(other);
let m = this.rows;
let n = this.columns;
let p = other.columns;
let result = new Matrix(m, p);
let Bcolj = new Float64Array(n);
for (let j = 0; j < p; j++) {
for (let k = 0; k < n; k++) {
Bcolj[k] = other.get(k, j);
}
for (let i = 0; i < m; i++) {
let s = 0;
for (let k = 0; k < n; k++) {
s += this.get(i, k) * Bcolj[k];
}
result.set(i, j, s);
}
}
return result;
}
strassen2x2(other) {
other = Matrix.checkMatrix(other);
let result = new Matrix(2, 2);
const a11 = this.get(0, 0);
const b11 = other.get(0, 0);
const a12 = this.get(0, 1);
const b12 = other.get(0, 1);
const a21 = this.get(1, 0);
const b21 = other.get(1, 0);
const a22 = this.get(1, 1);
const b22 = other.get(1, 1); // Compute intermediate values.
const m1 = (a11 + a22) * (b11 + b22);
const m2 = (a21 + a22) * b11;
const m3 = a11 * (b12 - b22);
const m4 = a22 * (b21 - b11);
const m5 = (a11 + a12) * b22;
const m6 = (a21 - a11) * (b11 + b12);
const m7 = (a12 - a22) * (b21 + b22); // Combine intermediate values into the output.
const c00 = m1 + m4 - m5 + m7;
const c01 = m3 + m5;
const c10 = m2 + m4;
const c11 = m1 - m2 + m3 + m6;
result.set(0, 0, c00);
result.set(0, 1, c01);
result.set(1, 0, c10);
result.set(1, 1, c11);
return result;
}
strassen3x3(other) {
other = Matrix.checkMatrix(other);
let result = new Matrix(3, 3);
const a00 = this.get(0, 0);
const a01 = this.get(0, 1);
const a02 = this.get(0, 2);
const a10 = this.get(1, 0);
const a11 = this.get(1, 1);
const a12 = this.get(1, 2);
const a20 = this.get(2, 0);
const a21 = this.get(2, 1);
const a22 = this.get(2, 2);
const b00 = other.get(0, 0);
const b01 = other.get(0, 1);
const b02 = other.get(0, 2);
const b10 = other.get(1, 0);
const b11 = other.get(1, 1);
const b12 = other.get(1, 2);
const b20 = other.get(2, 0);
const b21 = other.get(2, 1);
const b22 = other.get(2, 2);
const m1 = (a00 + a01 + a02 - a10 - a11 - a21 - a22) * b11;
const m2 = (a00 - a10) * (-b01 + b11);
const m3 = a11 * (-b00 + b01 + b10 - b11 - b12 - b20 + b22);
const m4 = (-a00 + a10 + a11) * (b00 - b01 + b11);
const m5 = (a10 + a11) * (-b00 + b01);
const m6 = a00 * b00;
const m7 = (-a00 + a20 + a21) * (b00 - b02 + b12);
const m8 = (-a00 + a20) * (b02 - b12);
const m9 = (a20 + a21) * (-b00 + b02);
const m10 = (a00 + a01 + a02 - a11 - a12 - a20 - a21) * b12;
const m11 = a21 * (-b00 + b02 + b10 - b11 - b12 - b20 + b21);
const m12 = (-a02 + a21 + a22) * (b11 + b20 - b21);
const m13 = (a02 - a22) * (b11 - b21);
const m14 = a02 * b20;
const m15 = (a21 + a22) * (-b20 + b21);
const m16 = (-a02 + a11 + a12) * (b12 + b20 - b22);
const m17 = (a02 - a12) * (b12 - b22);
const m18 = (a11 + a12) * (-b20 + b22);
const m19 = a01 * b10;
const m20 = a12 * b21;
const m21 = a10 * b02;
const m22 = a20 * b01;
const m23 = a22 * b22;
const c00 = m6 + m14 + m19;
const c01 = m1 + m4 + m5 + m6 + m12 + m14 + m15;
const c02 = m6 + m7 + m9 + m10 + m14 + m16 + m18;
const c10 = m2 + m3 + m4 + m6 + m14 + m16 + m17;
const c11 = m2 + m4 + m5 + m6 + m20;
const c12 = m14 + m16 + m17 + m18 + m21;
const c20 = m6 + m7 + m8 + m11 + m12 + m13 + m14;
const c21 = m12 + m13 + m14 + m15 + m22;
const c22 = m6 + m7 + m8 + m9 + m23;
result.set(0, 0, c00);
result.set(0, 1, c01);
result.set(0, 2, c02);
result.set(1, 0, c10);
result.set(1, 1, c11);
result.set(1, 2, c12);
result.set(2, 0, c20);
result.set(2, 1, c21);
result.set(2, 2, c22);
return result;
}
mmulStrassen(y) {
y = Matrix.checkMatrix(y);
let x = this.clone();
let r1 = x.rows;
let c1 = x.columns;
let r2 = y.rows;
let c2 = y.columns;
if (c1 !== r2) {
// eslint-disable-next-line no-console
console.warn(`Multiplying ${r1} x ${c1} and ${r2} x ${c2} matrix: dimensions do not match.`);
} // Put a matrix into the top left of a matrix of zeros.
// `rows` and `cols` are the dimensions of the output matrix.
function embed(mat, rows, cols) {
let r = mat.rows;
let c = mat.columns;
if (r === rows && c === cols) {
return mat;
} else {
let resultat = AbstractMatrix.zeros(rows, cols);
resultat = resultat.setSubMatrix(mat, 0, 0);
return resultat;
}
} // Make sure both matrices are the same size.
// This is exclusively for simplicity:
// this algorithm can be implemented with matrices of different sizes.
let r = Math.max(r1, r2);
let c = Math.max(c1, c2);
x = embed(x, r, c);
y = embed(y, r, c); // Our recursive multiplication function.
function blockMult(a, b, rows, cols) {
// For small matrices, resort to naive multiplication.
if (rows <= 512 || cols <= 512) {
return a.mmul(b); // a is equivalent to this
} // Apply dynamic padding.
if (rows % 2 === 1 && cols % 2 === 1) {
a = embed(a, rows + 1, cols + 1);
b = embed(b, rows + 1, cols + 1);
} else if (rows % 2 === 1) {
a = embed(a, rows + 1, cols);
b = embed(b, rows + 1, cols);
} else if (cols % 2 === 1) {
a = embed(a, rows, cols + 1);
b = embed(b, rows, cols + 1);
}
let halfRows = parseInt(a.rows / 2, 10);
let halfCols = parseInt(a.columns / 2, 10); // Subdivide input matrices.
let a11 = a.subMatrix(0, halfRows - 1, 0, halfCols - 1);
let b11 = b.subMatrix(0, halfRows - 1, 0, halfCols - 1);
let a12 = a.subMatrix(0, halfRows - 1, halfCols, a.columns - 1);
let b12 = b.subMatrix(0, halfRows - 1, halfCols, b.columns - 1);
let a21 = a.subMatrix(halfRows, a.rows - 1, 0, halfCols - 1);
let b21 = b.subMatrix(halfRows, b.rows - 1, 0, halfCols - 1);
let a22 = a.subMatrix(halfRows, a.rows - 1, halfCols, a.columns - 1);
let b22 = b.subMatrix(halfRows, b.rows - 1, halfCols, b.columns - 1); // Compute intermediate values.
let m1 = blockMult(AbstractMatrix.add(a11, a22), AbstractMatrix.add(b11, b22), halfRows, halfCols);
let m2 = blockMult(AbstractMatrix.add(a21, a22), b11, halfRows, halfCols);
let m3 = blockMult(a11, AbstractMatrix.sub(b12, b22), halfRows, halfCols);
let m4 = blockMult(a22, AbstractMatrix.sub(b21, b11), halfRows, halfCols);
let m5 = blockMult(AbstractMatrix.add(a11, a12), b22, halfRows, halfCols);
let m6 = blockMult(AbstractMatrix.sub(a21, a11), AbstractMatrix.add(b11, b12), halfRows, halfCols);
let m7 = blockMult(AbstractMatrix.sub(a12, a22), AbstractMatrix.add(b21, b22), halfRows, halfCols); // Combine intermediate values into the output.
let c11 = AbstractMatrix.add(m1, m4);
c11.sub(m5);
c11.add(m7);
let c12 = AbstractMatrix.add(m3, m5);
let c21 = AbstractMatrix.add(m2, m4);
let c22 = AbstractMatrix.sub(m1, m2);
c22.add(m3);
c22.add(m6); // Crop output to the desired size (undo dynamic padding).
let resultat = AbstractMatrix.zeros(2 * c11.rows, 2 * c11.columns);
resultat = resultat.setSubMatrix(c11, 0, 0);
resultat = resultat.setSubMatrix(c12, c11.rows, 0);
resultat = resultat.setSubMatrix(c21, 0, c11.columns);
resultat = resultat.setSubMatrix(c22, c11.rows, c11.columns);
return resultat.subMatrix(0, rows - 1, 0, cols - 1);
}
return blockMult(x, y, r, c);
}
scaleRows(options = {}) {
if (typeof options !== 'object') {
throw new TypeError('options must be an object');
}
const {
min = 0,
max = 1
} = options;
if (!Number.isFinite(min)) throw new TypeError('min must be a number');
if (!Number.isFinite(max)) throw new TypeError('max must be a number');
if (min >= max) throw new RangeError('min must be smaller than max');
let newMatrix = new Matrix(this.rows, this.columns);
for (let i = 0; i < this.rows; i++) {
const row = this.getRow(i);
if (row.length > 0) {
rescale(row, {
min,
max,
output: row
});
}
newMatrix.setRow(i, row);
}
return newMatrix;
}
scaleColumns(options = {}) {
if (typeof options !== 'object') {
throw new TypeError('options must be an object');
}
const {
min = 0,
max = 1
} = options;
if (!Number.isFinite(min)) throw new TypeError('min must be a number');
if (!Number.isFinite(max)) throw new TypeError('max must be a number');
if (min >= max) throw new RangeError('min must be smaller than max');
let newMatrix = new Matrix(this.rows, this.columns);
for (let i = 0; i < this.columns; i++) {
const column = this.getColumn(i);
if (column.length) {
rescale(column, {
min: min,
max: max,
output: column
});
}
newMatrix.setColumn(i, column);
}
return newMatrix;
}
flipRows() {
const middle = Math.ceil(this.columns / 2);
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < middle; j++) {
let first = this.get(i, j);
let last = this.get(i, this.columns - 1 - j);
this.set(i, j, last);
this.set(i, this.columns - 1 - j, first);
}
}
return this;
}
flipColumns() {
const middle = Math.ceil(this.rows / 2);
for (let j = 0; j < this.columns; j++) {
for (let i = 0; i < middle; i++) {
let first = this.get(i, j);
let last = this.get(this.rows - 1 - i, j);
this.set(i, j, last);
this.set(this.rows - 1 - i, j, first);
}
}
return this;
}
kroneckerProduct(other) {
other = Matrix.checkMatrix(other);
let m = this.rows;
let n = this.columns;
let p = other.rows;
let q = other.columns;
let result = new Matrix(m * p, n * q);
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
for (let k = 0; k < p; k++) {
for (let l = 0; l < q; l++) {
result.set(p * i + k, q * j + l, this.get(i, j) * other.get(k, l));
}
}
}
}
return result;
}
kroneckerSum(other) {
other = Matrix.checkMatrix(other);
if (!this.isSquare() || !other.isSquare()) {
throw new Error('Kronecker Sum needs two Square Matrices');
}
let m = this.rows;
let n = other.rows;
let AxI = this.kroneckerProduct(Matrix.eye(n, n));
let IxB = Matrix.eye(m, m).kroneckerProduct(other);
return AxI.add(IxB);
}
transpose() {
let result = new Matrix(this.columns, this.rows);
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
result.set(j, i, this.get(i, j));
}
}
return result;
}
sortRows(compareFunction = compareNumbers) {
for (let i = 0; i < this.rows; i++) {
this.setRow(i, this.getRow(i).sort(compareFunction));
}
return this;
}
sortColumns(compareFunction = compareNumbers) {
for (let i = 0; i < this.columns; i++) {
this.setColumn(i, this.getColumn(i).sort(compareFunction));
}
return this;
}
subMatrix(startRow, endRow, startColumn, endColumn) {
checkRange(this, startRow, endRow, startColumn, endColumn);
let newMatrix = new Matrix(endRow - startRow + 1, endColumn - startColumn + 1);
for (let i = startRow; i <= endRow; i++) {
for (let j = startColumn; j <= endColumn; j++) {
newMatrix.set(i - startRow, j - startColumn, this.get(i, j));
}
}
return newMatrix;
}
subMatrixRow(indices, startColumn, endColumn) {
if (startColumn === undefined) startColumn = 0;
if (endColumn === undefined) endColumn = this.columns - 1;
if (startColumn > endColumn || startColumn < 0 || startColumn >= this.columns || endColumn < 0 || endColumn >= this.columns) {
throw new RangeError('Argument out of range');
}
let newMatrix = new Matrix(indices.length, endColumn - startColumn + 1);
for (let i = 0; i < indices.length; i++) {
for (let j = startColumn; j <= endColumn; j++) {
if (indices[i] < 0 || indices[i] >= this.rows) {
throw new RangeError(`Row index out of range: ${indices[i]}`);
}
newMatrix.set(i, j - startColumn, this.get(indices[i], j));
}
}
return newMatrix;
}
subMatrixColumn(indices, startRow, endRow) {
if (startRow === undefined) startRow = 0;
if (endRow === undefined) endRow = this.rows - 1;
if (startRow > endRow || startRow < 0 || startRow >= this.rows || endRow < 0 || endRow >= this.rows) {
throw new RangeError('Argument out of range');
}
let newMatrix = new Matrix(endRow - startRow + 1, indices.length);
for (let i = 0; i < indices.length; i++) {
for (let j = startRow; j <= endRow; j++) {
if (indices[i] < 0 || indices[i] >= this.columns) {
throw new RangeError(`Column index out of range: ${indices[i]}`);
}
newMatrix.set(j - startRow, i, this.get(j, indices[i]));
}
}
return newMatrix;
}
setSubMatrix(matrix, startRow, startColumn) {
matrix = Matrix.checkMatrix(matrix);
if (matrix.isEmpty()) {
return this;
}
let endRow = startRow + matrix.rows - 1;
let endColumn = startColumn + matrix.columns - 1;
checkRange(this, startRow, endRow, startColumn, endColumn);
for (let i = 0; i < matrix.rows; i++) {
for (let j = 0; j < matrix.columns; j++) {
this.set(startRow + i, startColumn + j, matrix.get(i, j));
}
}
return this;
}
selection(rowIndices, columnIndices) {
let indices = checkIndices(this, rowIndices, columnIndices);
let newMatrix = new Matrix(rowIndices.length, columnIndices.length);
for (let i = 0; i < indices.row.length; i++) {
let rowIndex = indices.row[i];
for (let j = 0; j < indices.column.length; j++) {
let columnIndex = indices.column[j];
newMatrix.set(i, j, this.get(rowIndex, columnIndex));
}
}
return newMatrix;
}
trace() {
let min = Math.min(this.rows, this.columns);
let trace = 0;
for (let i = 0; i < min; i++) {
trace += this.get(i, i);
}
return trace;
}
clone() {
let newMatrix = new Matrix(this.rows, this.columns);
for (let row = 0; row < this.rows; row++) {
for (let column = 0; column < this.columns; column++) {
newMatrix.set(row, column, this.get(row, column));
}
}
return newMatrix;
}
sum(by) {
switch (by) {
case 'row':
return sumByRow(this);
case 'column':
return sumByColumn(this);
case undefined:
return sumAll(this);
default:
throw new Error(`invalid option: ${by}`);
}
}
product(by) {
switch (by) {
case 'row':
return productByRow(this);
case 'column':
return productByColumn(this);
case undefined:
return productAll(this);
default:
throw new Error(`invalid option: ${by}`);
}
}
mean(by) {
const sum = this.sum(by);
switch (by) {
case 'row':
{
for (let i = 0; i < this.rows; i++) {
sum[i] /= this.columns;
}
return sum;
}
case 'column':
{
for (let i = 0; i < this.columns; i++) {
sum[i] /= this.rows;
}
return sum;
}
case undefined:
return sum / this.size;
default:
throw new Error(`invalid option: ${by}`);
}
}
variance(by, options = {}) {
if (typeof by === 'object') {
options = by;
by = undefined;
}
if (typeof options !== 'object') {
throw new TypeError('options must be an object');
}
const {
unbiased = true,
mean = this.mean(by)
} = options;
if (typeof unbiased !== 'boolean') {
throw new TypeError('unbiased must be a boolean');
}
switch (by) {
case 'row':
{
if (!Array.isArray(mean)) {
throw new TypeError('mean must be an array');
}
return varianceByRow(this, unbiased, mean);
}
case 'column':
{
if (!Array.isArray(mean)) {
throw new TypeError('mean must be an array');
}
return varianceByColumn(this, unbiased, mean);
}
case undefined:
{
if (typeof mean !== 'number') {
throw new TypeError('mean must be a number');
}
return varianceAll(this, unbiased, mean);
}
default:
throw new Error(`invalid option: ${by}`);
}
}
standardDeviation(by, options) {
if (typeof by === 'object') {
options = by;
by = undefined;
}
const variance = this.variance(by, options);
if (by === undefined) {
return Math.sqrt(variance);
} else {
for (let i = 0; i < variance.length; i++) {
variance[i] = Math.sqrt(variance[i]);
}
return variance;
}
}
center(by, options = {}) {
if (typeof by === 'object') {
options = by;
by = undefined;
}
if (typeof options !== 'object') {
throw new TypeError('options must be an object');
}
const {
center = this.mean(by)
} = options;
switch (by) {
case 'row':
{
if (!Array.isArray(center)) {
throw new TypeError('center must be an array');
}
centerByRow(this, center);
return this;
}
case 'column':
{
if (!Array.isArray(center)) {
throw new TypeError('center must be an array');
}
centerByColumn(this, center);
return this;
}
case undefined:
{
if (typeof center !== 'number') {
throw new TypeError('center must be a number');
}
centerAll(this, center);
return this;
}
default:
throw new Error(`invalid option: ${by}`);
}
}
scale(by, options = {}) {
if (typeof by === 'object') {
options = by;
by = undefined;
}
if (typeof options !== 'object') {
throw new TypeError('options must be an object');
}
let scale = options.scale;
switch (by) {
case 'row':
{
if (scale === undefined) {
scale = getScaleByRow(this);
} else if (!Array.isArray(scale)) {
throw new TypeError('scale must be an array');
}
scaleByRow(this, scale);
return this;
}
case 'column':
{
if (scale === undefined) {
scale = getScaleByColumn(this);
} else if (!Array.isArray(scale)) {
throw new TypeError('scale must be an array');
}
scaleByColumn(this, scale);
return this;
}
case undefined:
{
if (scale === undefined) {
scale = getScaleAll(this);
} else if (typeof scale !== 'number') {
throw new TypeError('scale must be a number');
}
scaleAll(this, scale);
return this;
}
default:
throw new Error(`invalid option: ${by}`);
}
}
toString(options) {
return inspectMatrixWithOptions(this, options);
}
}
AbstractMatrix.prototype.klass = 'Matrix';
if (typeof Symbol !== 'undefined') {
AbstractMatrix.prototype[Symbol.for('nodejs.util.inspect.custom')] = inspectMatrix;
}
function compareNumbers(a, b) {
return a - b;
} // Synonyms
AbstractMatrix.random = AbstractMatrix.rand;
AbstractMatrix.randomInt = AbstractMatrix.randInt;
AbstractMatrix.diagonal = AbstractMatrix.diag;
AbstractMatrix.prototype.diagonal = AbstractMatrix.prototype.diag;
AbstractMatrix.identity = AbstractMatrix.eye;
AbstractMatrix.prototype.negate = AbstractMatrix.prototype.neg;
AbstractMatrix.prototype.tensorProduct = AbstractMatrix.prototype.kroneckerProduct;
class Matrix extends AbstractMatrix {
constructor(nRows, nColumns) {
super();
if (Matrix.isMatrix(nRows)) {
// eslint-disable-next-line no-constructor-return
return nRows.clone();
} else if (Number.isInteger(nRows) && nRows >= 0) {
// Create an empty matrix
this.data = [];
if (Number.isInteger(nColumns) && nColumns >= 0) {
for (let i = 0; i < nRows; i++) {
this.data.push(new Float64Array(nColumns));
}
} else {
throw new TypeError('nColumns must be a positive integer');
}
} else if (Array.isArray(nRows)) {
// Copy the values from the 2D array
const arrayData = nRows;
nRows = arrayData.length;
nColumns = nRows ? arrayData[0].length : 0;
if (typeof nColumns !== 'number') {
throw new TypeError('Data must be a 2D array with at least one element');
}
this.data = [];
for (let i = 0; i < nRows; i++) {
if (arrayData[i].length !== nColumns) {
throw new RangeError('Inconsistent array dimensions');
}
this.data.push(Float64Array.from(arrayData[i]));
}
} else {
throw new TypeError('First argument must be a positive number or an array');
}
this.rows = nRows;
this.columns = nColumns;
}
set(rowIndex, columnIndex, value) {
this.data[rowIndex][columnIndex] = value;
return this;
}
get(rowIndex, columnIndex) {
return this.data[rowIndex][columnIndex];
}
removeRow(index) {
checkRowIndex(this, index);
this.data.splice(index, 1);
this.rows -= 1;
return this;
}
addRow(index, array) {
if (array === undefined) {
array = index;
index = this.rows;
}
checkRowIndex(this, index, true);
array = Float64Array.from(checkRowVector(this, array));
this.data.splice(index, 0, array);
this.rows += 1;
return this;
}
removeColumn(index) {
checkColumnIndex(this, index);
for (let i = 0; i < this.rows; i++) {
const newRow = new Float64Array(this.columns - 1);
for (let j = 0; j < index; j++) {
newRow[j] = this.data[i][j];
}
for (let j = index + 1; j < this.columns; j++) {
newRow[j - 1] = this.data[i][j];
}
this.data[i] = newRow;
}
this.columns -= 1;
return this;
}
addColumn(index, array) {
if (typeof array === 'undefined') {
array = index;
index = this.columns;
}
checkColumnIndex(this, index, true);
array = checkColumnVector(this, array);
for (let i = 0; i < this.rows; i++) {
const newRow = new Float64Array(this.columns + 1);
let j = 0;
for (; j < index; j++) {
newRow[j] = this.data[i][j];
}
newRow[j++] = array[i];
for (; j < this.columns + 1; j++) {
newRow[j] = this.data[i][j - 1];
}
this.data[i] = newRow;
}
this.columns += 1;
return this;
}
}
installMathOperations(AbstractMatrix, Matrix);
class WrapperMatrix2D extends AbstractMatrix {
constructor(data) {
super();
this.data = data;
this.rows = data.length;
this.columns = data[0].length;
}
set(rowIndex, columnIndex, value) {
this.data[rowIndex][columnIndex] = value;
return this;
}
get(rowIndex, columnIndex) {
return this.data[rowIndex][columnIndex];
}
}
class LuDecomposition {
constructor(matrix) {
matrix = WrapperMatrix2D.checkMatrix(matrix);
let lu = matrix.clone();
let rows = lu.rows;
let columns = lu.columns;
let pivotVector = new Float64Array(rows);
let pivotSign = 1;
let i, j, k, p, s, t, v;
let LUcolj, kmax;
for (i = 0; i < rows; i++) {
pivotVector[i] = i;
}
LUcolj = new Float64Array(rows);
for (j = 0; j < columns; j++) {
for (i = 0; i < rows; i++) {
LUcolj[i] = lu.get(i, j);
}
for (i = 0; i < rows; i++) {
kmax = Math.min(i, j);
s = 0;
for (k = 0; k < kmax; k++) {
s += lu.get(i, k) * LUcolj[k];
}
LUcolj[i] -= s;
lu.set(i, j, LUcolj[i]);
}
p = j;
for (i = j + 1; i < rows; i++) {
if (Math.abs(LUcolj[i]) > Math.abs(LUcolj[p])) {
p = i;
}
}
if (p !== j) {
for (k = 0; k < columns; k++) {
t = lu.get(p, k);
lu.set(p, k, lu.get(j, k));
lu.set(j, k, t);
}
v = pivotVector[p];
pivotVector[p] = pivotVector[j];
pivotVector[j] = v;
pivotSign = -pivotSign;
}
if (j < rows && lu.get(j, j) !== 0) {
for (i = j + 1; i < rows; i++) {
lu.set(i, j, lu.get(i, j) / lu.get(j, j));
}
}
}
this.LU = lu;
this.pivotVector = pivotVector;
this.pivotSign = pivotSign;
}
isSingular() {
let data = this.LU;
let col = data.columns;
for (let j = 0; j < col; j++) {
if (data.get(j, j) === 0) {
return true;
}
}
return false;
}
solve(value) {
value = Matrix.checkMatrix(value);
let lu = this.LU;
let rows = lu.rows;
if (rows !== value.rows) {
throw new Error('Invalid matrix dimensions');
}
if (this.isSingular()) {
throw new Error('LU matrix is singular');
}
let count = value.columns;
let X = value.subMatrixRow(this.pivotVector, 0, count - 1);
let columns = lu.columns;
let i, j, k;
for (k = 0; k < columns; k++) {
for (i = k + 1; i < columns; i++) {
for (j = 0; j < count; j++) {
X.set(i, j, X.get(i, j) - X.get(k, j) * lu.get(i, k));
}
}
}
for (k = columns - 1; k >= 0; k--) {
for (j = 0; j < count; j++) {
X.set(k, j, X.get(k, j) / lu.get(k, k));
}
for (i = 0; i < k; i++) {
for (j = 0; j < count; j++) {
X.set(i, j, X.get(i, j) - X.get(k, j) * lu.get(i, k));
}
}
}
return X;
}
get determinant() {
let data = this.LU;
if (!data.isSquare()) {
throw new Error('Matrix must be square');
}
let determinant = this.pivotSign;
let col = data.columns;
for (let j = 0; j < col; j++) {
determinant *= data.get(j, j);
}
return determinant;
}
get lowerTriangularMatrix() {
let data = this.LU;
let rows = data.rows;
let columns = data.columns;
let X = new Matrix(rows, columns);
for (let i = 0; i < rows; i++) {
for (let j = 0; j < columns; j++) {
if (i > j) {
X.set(i, j, data.get(i, j));
} else if (i === j) {
X.set(i, j, 1);
} else {
X.set(i, j, 0);
}
}
}
return X;
}
get upperTriangularMatrix() {
let data = this.LU;
let rows = data.rows;
let columns = data.columns;
let X = new Matrix(rows, columns);
for (let i = 0; i < rows; i++) {
for (let j = 0; j < columns; j++) {
if (i <= j) {
X.set(i, j, data.get(i, j));
} else {
X.set(i, j, 0);
}
}
}
return X;
}
get pivotPermutationVector() {
return Array.from(this.pivotVector);
}
}
function hypotenuse(a, b) {
let r = 0;
if (Math.abs(a) > Math.abs(b)) {
r = b / a;
return Math.abs(a) * Math.sqrt(1 + r * r);
}
if (b !== 0) {
r = a / b;
return Math.abs(b) * Math.sqrt(1 + r * r);
}
return 0;
}
class QrDecomposition {
constructor(value) {
value = WrapperMatrix2D.checkMatrix(value);
let qr = value.clone();
let m = value.rows;
let n = value.columns;
let rdiag = new Float64Array(n);
let i, j, k, s;
for (k = 0; k < n; k++) {
let nrm = 0;
for (i = k; i < m; i++) {
nrm = hypotenuse(nrm, qr.get(i, k));
}
if (nrm !== 0) {
if (qr.get(k, k) < 0) {
nrm = -nrm;
}
for (i = k; i < m; i++) {
qr.set(i, k, qr.get(i, k) / nrm);
}
qr.set(k, k, qr.get(k, k) + 1);
for (j = k + 1; j < n; j++) {
s = 0;
for (i = k; i < m; i++) {
s += qr.get(i, k) * qr.get(i, j);
}
s = -s / qr.get(k, k);
for (i = k; i < m; i++) {
qr.set(i, j, qr.get(i, j) + s * qr.get(i, k));
}
}
}
rdiag[k] = -nrm;
}
this.QR = qr;
this.Rdiag = rdiag;
}
solve(value) {
value = Matrix.checkMatrix(value);
let qr = this.QR;
let m = qr.rows;
if (value.rows !== m) {
throw new Error('Matrix row dimensions must agree');
}
if (!this.isFullRank()) {
throw new Error('Matrix is rank deficient');
}
let count = value.columns;
let X = value.clone();
let n = qr.columns;
let i, j, k, s;
for (k = 0; k < n; k++) {
for (j = 0; j < count; j++) {
s = 0;
for (i = k; i < m; i++) {
s += qr.get(i, k) * X.get(i, j);
}
s = -s / qr.get(k, k);
for (i = k; i < m; i++) {
X.set(i, j, X.get(i, j) + s * qr.get(i, k));
}
}
}
for (k = n - 1; k >= 0; k--) {
for (j = 0; j < count; j++) {
X.set(k, j, X.get(k, j) / this.Rdiag[k]);
}
for (i = 0; i < k; i++) {
for (j = 0; j < count; j++) {
X.set(i, j, X.get(i, j) - X.get(k, j) * qr.get(i, k));
}
}
}
return X.subMatrix(0, n - 1, 0, count - 1);
}
isFullRank() {
let columns = this.QR.columns;
for (let i = 0; i < columns; i++) {
if (this.Rdiag[i] === 0) {
return false;
}
}
return true;
}
get upperTriangularMatrix() {
let qr = this.QR;
let n = qr.columns;
let X = new Matrix(n, n);
let i, j;
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
if (i < j) {
X.set(i, j, qr.get(i, j));
} else if (i === j) {
X.set(i, j, this.Rdiag[i]);
} else {
X.set(i, j, 0);
}
}
}
return X;
}
get orthogonalMatrix() {
let qr = this.QR;
let rows = qr.rows;
let columns = qr.columns;
let X = new Matrix(rows, columns);
let i, j, k, s;
for (k = columns - 1; k >= 0; k--) {
for (i = 0; i < rows; i++) {
X.set(i, k, 0);
}
X.set(k, k, 1);
for (j = k; j < columns; j++) {
if (qr.get(k, k) !== 0) {
s = 0;
for (i = k; i < rows; i++) {
s += qr.get(i, k) * X.get(i, j);
}
s = -s / qr.get(k, k);
for (i = k; i < rows; i++) {
X.set(i, j, X.get(i, j) + s * qr.get(i, k));
}
}
}
}
return X;
}
}
class SingularValueDecomposition {
constructor(value, options = {}) {
value = WrapperMatrix2D.checkMatrix(value);
if (value.isEmpty()) {
throw new Error('Matrix must be non-empty');
}
let m = value.rows;
let n = value.columns;
const {
computeLeftSingularVectors = true,
computeRightSingularVectors = true,
autoTranspose = false
} = options;
let wantu = Boolean(computeLeftSingularVectors);
let wantv = Boolean(computeRightSingularVectors);
let swapped = false;
let a;
if (m < n) {
if (!autoTranspose) {
a = value.clone(); // eslint-disable-next-line no-console
console.warn('Computing SVD on a matrix with more columns than rows. Consider enabling autoTranspose');
} else {
a = value.transpose();
m = a.rows;
n = a.columns;
swapped = true;
let aux = wantu;
wantu = wantv;
wantv = aux;
}
} else {
a = value.clone();
}
let nu = Math.min(m, n);
let ni = Math.min(m + 1, n);
let s = new Float64Array(ni);
let U = new Matrix(m, nu);
let V = new Matrix(n, n);
let e = new Float64Array(n);
let work = new Float64Array(m);
let si = new Float64Array(ni);
for (let i = 0; i < ni; i++) si[i] = i;
let nct = Math.min(m - 1, n);
let nrt = Math.max(0, Math.min(n - 2, m));
let mrc = Math.max(nct, nrt);
for (let k = 0; k < mrc; k++) {
if (k < nct) {
s[k] = 0;
for (let i = k; i < m; i++) {
s[k] = hypotenuse(s[k], a.get(i, k));
}
if (s[k] !== 0) {
if (a.get(k, k) < 0) {
s[k] = -s[k];
}
for (let i = k; i < m; i++) {
a.set(i, k, a.get(i, k) / s[k]);
}
a.set(k, k, a.get(k, k) + 1);
}
s[k] = -s[k];
}
for (let j = k + 1; j < n; j++) {
if (k < nct && s[k] !== 0) {
let t = 0;
for (let i = k; i < m; i++) {
t += a.get(i, k) * a.get(i, j);
}
t = -t / a.get(k, k);
for (let i = k; i < m; i++) {
a.set(i, j, a.get(i, j) + t * a.get(i, k));
}
}
e[j] = a.get(k, j);
}
if (wantu && k < nct) {
for (let i = k; i < m; i++) {
U.set(i, k, a.get(i, k));
}
}
if (k < nrt) {
e[k] = 0;
for (let i = k + 1; i < n; i++) {
e[k] = hypotenuse(e[k], e[i]);
}
if (e[k] !== 0) {
if (e[k + 1] < 0) {
e[k] = 0 - e[k];
}
for (let i = k + 1; i < n; i++) {
e[i] /= e[k];
}
e[k + 1] += 1;
}
e[k] = -e[k];
if (k + 1 < m && e[k] !== 0) {
for (let i = k + 1; i < m; i++) {
work[i] = 0;
}
for (let i = k + 1; i < m; i++) {
for (let j = k + 1; j < n; j++) {
work[i] += e[j] * a.get(i, j);
}
}
for (let j = k + 1; j < n; j++) {
let t = -e[j] / e[k + 1];
for (let i = k + 1; i < m; i++) {
a.set(i, j, a.get(i, j) + t * work[i]);
}
}
}
if (wantv) {
for (let i = k + 1; i < n; i++) {
V.set(i, k, e[i]);
}
}
}
}
let p = Math.min(n, m + 1);
if (nct < n) {
s[nct] = a.get(nct, nct);
}
if (m < p) {
s[p - 1] = 0;
}
if (nrt + 1 < p) {
e[nrt] = a.get(nrt, p - 1);
}
e[p - 1] = 0;
if (wantu) {
for (let j = nct; j < nu; j++) {
for (let i = 0; i < m; i++) {
U.set(i, j, 0);
}
U.set(j, j, 1);
}
for (let k = nct - 1; k >= 0; k--) {
if (s[k] !== 0) {
for (let j = k + 1; j < nu; j++) {
let t = 0;
for (let i = k; i < m; i++) {
t += U.get(i, k) * U.get(i, j);
}
t = -t / U.get(k, k);
for (let i = k; i < m; i++) {
U.set(i, j, U.get(i, j) + t * U.get(i, k));
}
}
for (let i = k; i < m; i++) {
U.set(i, k, -U.get(i, k));
}
U.set(k, k, 1 + U.get(k, k));
for (let i = 0; i < k - 1; i++) {
U.set(i, k, 0);
}
} else {
for (let i = 0; i < m; i++) {
U.set(i, k, 0);
}
U.set(k, k, 1);
}
}
}
if (wantv) {
for (let k = n - 1; k >= 0; k--) {
if (k < nrt && e[k] !== 0) {
for (let j = k + 1; j < n; j++) {
let t = 0;
for (let i = k + 1; i < n; i++) {
t += V.get(i, k) * V.get(i, j);
}
t = -t / V.get(k + 1, k);
for (let i = k + 1; i < n; i++) {
V.set(i, j, V.get(i, j) + t * V.get(i, k));
}
}
}
for (let i = 0; i < n; i++) {
V.set(i, k, 0);
}
V.set(k, k, 1);
}
}
let pp = p - 1;
let eps = Number.EPSILON;
while (p > 0) {
let k, kase;
for (k = p - 2; k >= -1; k--) {
if (k === -1) {
break;
}
const alpha = Number.MIN_VALUE + eps * Math.abs(s[k] + Math.abs(s[k + 1]));
if (Math.abs(e[k]) <= alpha || Number.isNaN(e[k])) {
e[k] = 0;
break;
}
}
if (k === p - 2) {
kase = 4;
} else {
let ks;
for (ks = p - 1; ks >= k; ks--) {
if (ks === k) {
break;
}
let t = (ks !== p ? Math.abs(e[ks]) : 0) + (ks !== k + 1 ? Math.abs(e[ks - 1]) : 0);
if (Math.abs(s[ks]) <= eps * t) {
s[ks] = 0;
break;
}
}
if (ks === k) {
kase = 3;
} else if (ks === p - 1) {
kase = 1;
} else {
kase = 2;
k = ks;
}
}
k++;
switch (kase) {
case 1:
{
let f = e[p - 2];
e[p - 2] = 0;
for (let j = p - 2; j >= k; j--) {
let t = hypotenuse(s[j], f);
let cs = s[j] / t;
let sn = f / t;
s[j] = t;
if (j !== k) {
f = -sn * e[j - 1];
e[j - 1] = cs * e[j - 1];
}
if (wantv) {
for (let i = 0; i < n; i++) {
t = cs * V.get(i, j) + sn * V.get(i, p - 1);
V.set(i, p - 1, -sn * V.get(i, j) + cs * V.get(i, p - 1));
V.set(i, j, t);
}
}
}
break;
}
case 2:
{
let f = e[k - 1];
e[k - 1] = 0;
for (let j = k; j < p; j++) {
let t = hypotenuse(s[j], f);
let cs = s[j] / t;
let sn = f / t;
s[j] = t;
f = -sn * e[j];
e[j] = cs * e[j];
if (wantu) {
for (let i = 0; i < m; i++) {
t = cs * U.get(i, j) + sn * U.get(i, k - 1);
U.set(i, k - 1, -sn * U.get(i, j) + cs * U.get(i, k - 1));
U.set(i, j, t);
}
}
}
break;
}
case 3:
{
const scale = Math.max(Math.abs(s[p - 1]), Math.abs(s[p - 2]), Math.abs(e[p - 2]), Math.abs(s[k]), Math.abs(e[k]));
const sp = s[p - 1] / scale;
const spm1 = s[p - 2] / scale;
const epm1 = e[p - 2] / scale;
const sk = s[k] / scale;
const ek = e[k] / scale;
const b = ((spm1 + sp) * (spm1 - sp) + epm1 * epm1) / 2;
const c = sp * epm1 * (sp * epm1);
let shift = 0;
if (b !== 0 || c !== 0) {
if (b < 0) {
shift = 0 - Math.sqrt(b * b + c);
} else {
shift = Math.sqrt(b * b + c);
}
shift = c / (b + shift);
}
let f = (sk + sp) * (sk - sp) + shift;
let g = sk * ek;
for (let j = k; j < p - 1; j++) {
let t = hypotenuse(f, g);
if (t === 0) t = Number.MIN_VALUE;
let cs = f / t;
let sn = g / t;
if (j !== k) {
e[j - 1] = t;
}
f = cs * s[j] + sn * e[j];
e[j] = cs * e[j] - sn * s[j];
g = sn * s[j + 1];
s[j + 1] = cs * s[j + 1];
if (wantv) {
for (let i = 0; i < n; i++) {
t = cs * V.get(i, j) + sn * V.get(i, j + 1);
V.set(i, j + 1, -sn * V.get(i, j) + cs * V.get(i, j + 1));
V.set(i, j, t);
}
}
t = hypotenuse(f, g);
if (t === 0) t = Number.MIN_VALUE;
cs = f / t;
sn = g / t;
s[j] = t;
f = cs * e[j] + sn * s[j + 1];
s[j + 1] = -sn * e[j] + cs * s[j + 1];
g = sn * e[j + 1];
e[j + 1] = cs * e[j + 1];
if (wantu && j < m - 1) {
for (let i = 0; i < m; i++) {
t = cs * U.get(i, j) + sn * U.get(i, j + 1);
U.set(i, j + 1, -sn * U.get(i, j) + cs * U.get(i, j + 1));
U.set(i, j, t);
}
}
}
e[p - 2] = f;
break;
}
case 4:
{
if (s[k] <= 0) {
s[k] = s[k] < 0 ? -s[k] : 0;
if (wantv) {
for (let i = 0; i <= pp; i++) {
V.set(i, k, -V.get(i, k));
}
}
}
while (k < pp) {
if (s[k] >= s[k + 1]) {
break;
}
let t = s[k];
s[k] = s[k + 1];
s[k + 1] = t;
if (wantv && k < n - 1) {
for (let i = 0; i < n; i++) {
t = V.get(i, k + 1);
V.set(i, k + 1, V.get(i, k));
V.set(i, k, t);
}
}
if (wantu && k < m - 1) {
for (let i = 0; i < m; i++) {
t = U.get(i, k + 1);
U.set(i, k + 1, U.get(i, k));
U.set(i, k, t);
}
}
k++;
}
p--;
break;
}
// no default
}
}
if (swapped) {
let tmp = V;
V = U;
U = tmp;
}
this.m = m;
this.n = n;
this.s = s;
this.U = U;
this.V = V;
}
solve(value) {
let Y = value;
let e = this.threshold;
let scols = this.s.length;
let Ls = Matrix.zeros(scols, scols);
for (let i = 0; i < scols; i++) {
if (Math.abs(this.s[i]) <= e) {
Ls.set(i, i, 0);
} else {
Ls.set(i, i, 1 / this.s[i]);
}
}
let U = this.U;
let V = this.rightSingularVectors;
let VL = V.mmul(Ls);
let vrows = V.rows;
let urows = U.rows;
let VLU = Matrix.zeros(vrows, urows);
for (let i = 0; i < vrows; i++) {
for (let j = 0; j < urows; j++) {
let sum = 0;
for (let k = 0; k < scols; k++) {
sum += VL.get(i, k) * U.get(j, k);
}
VLU.set(i, j, sum);
}
}
return VLU.mmul(Y);
}
solveForDiagonal(value) {
return this.solve(Matrix.diag(value));
}
inverse() {
let V = this.V;
let e = this.threshold;
let vrows = V.rows;
let vcols = V.columns;
let X = new Matrix(vrows, this.s.length);
for (let i = 0; i < vrows; i++) {
for (let j = 0; j < vcols; j++) {
if (Math.abs(this.s[j]) > e) {
X.set(i, j, V.get(i, j) / this.s[j]);
}
}
}
let U = this.U;
let urows = U.rows;
let ucols = U.columns;
let Y = new Matrix(vrows, urows);
for (let i = 0; i < vrows; i++) {
for (let j = 0; j < urows; j++) {
let sum = 0;
for (let k = 0; k < ucols; k++) {
sum += X.get(i, k) * U.get(j, k);
}
Y.set(i, j, sum);
}
}
return Y;
}
get condition() {
return this.s[0] / this.s[Math.min(this.m, this.n) - 1];
}
get norm2() {
return this.s[0];
}
get rank() {
let tol = Math.max(this.m, this.n) * this.s[0] * Number.EPSILON;
let r = 0;
let s = this.s;
for (let i = 0, ii = s.length; i < ii; i++) {
if (s[i] > tol) {
r++;
}
}
return r;
}
get diagonal() {
return Array.from(this.s);
}
get threshold() {
return Number.EPSILON / 2 * Math.max(this.m, this.n) * this.s[0];
}
get leftSingularVectors() {
return this.U;
}
get rightSingularVectors() {
return this.V;
}
get diagonalMatrix() {
return Matrix.diag(this.s);
}
}
function inverse(matrix, useSVD = false) {
matrix = WrapperMatrix2D.checkMatrix(matrix);
if (useSVD) {
return new SingularValueDecomposition(matrix).inverse();
} else {
return solve(matrix, Matrix.eye(matrix.rows));
}
}
function solve(leftHandSide, rightHandSide, useSVD = false) {
leftHandSide = WrapperMatrix2D.checkMatrix(leftHandSide);
rightHandSide = WrapperMatrix2D.checkMatrix(rightHandSide);
if (useSVD) {
return new SingularValueDecomposition(leftHandSide).solve(rightHandSide);
} else {
return leftHandSide.isSquare() ? new LuDecomposition(leftHandSide).solve(rightHandSide) : new QrDecomposition(leftHandSide).solve(rightHandSide);
}
}
/**
* Difference of the matrix function over the parameters
* @ignore
* @param {{x:Array, y:Array}} data - Array of points to fit in the format [x1, x2, ... ], [y1, y2, ... ]
* @param {Array} evaluatedData - Array of previous evaluated function values
* @param {Array} params - Array of previous parameter values
* @param {number|array} gradientDifference - The step size to approximate the jacobian matrix
* @param {boolean} centralDifference - If true the jacobian matrix is approximated by central differences otherwise by forward differences
* @param {function} paramFunction - The parameters and returns a function with the independent variable as a parameter
* @return {Matrix}
*/
function gradientFunction(data, evaluatedData, params, gradientDifference, paramFunction, centralDifference) {
const nbParams = params.length;
const nbPoints = data.x.length;
let ans = Matrix.zeros(nbParams, nbPoints);
let rowIndex = 0;
for (let param = 0; param < nbParams; param++) {
if (gradientDifference[param] === 0) continue;
let delta = gradientDifference[param];
let auxParams = params.slice();
auxParams[param] += delta;
let funcParam = paramFunction(auxParams);
if (!centralDifference) {
for (let point = 0; point < nbPoints; point++) {
ans.set(rowIndex, point, (evaluatedData[point] - funcParam(data.x[point])) / delta);
}
} else {
auxParams = params.slice();
auxParams[param] -= delta;
delta *= 2;
let funcParam2 = paramFunction(auxParams);
for (let point = 0; point < nbPoints; point++) {
ans.set(rowIndex, point, (funcParam2(data.x[point]) - funcParam(data.x[point])) / delta);
}
}
rowIndex++;
}
return ans;
}
/**
* Matrix function over the samples
* @ignore
* @param {{x:Array, y:Array}} data - Array of points to fit in the format [x1, x2, ... ], [y1, y2, ... ]
* @param {Array} evaluatedData - Array of previous evaluated function values
* @return {Matrix}
*/
function matrixFunction(data, evaluatedData) {
const m = data.x.length;
let ans = new Matrix(m, 1);
for (let point = 0; point < m; point++) {
ans.set(point, 0, data.y[point] - evaluatedData[point]);
}
return ans;
}
/**
* Iteration for Levenberg-Marquardt
* @ignore
* @param {{x:Array, y:Array}} data - Array of points to fit in the format [x1, x2, ... ], [y1, y2, ... ]
* @param {Array} params - Array of previous parameter values
* @param {number} damping - Levenberg-Marquardt parameter
* @param {number|array} gradientDifference - The step size to approximate the jacobian matrix
* @param {boolean} centralDifference - If true the jacobian matrix is approximated by central differences otherwise by forward differences
* @param {function} parameterizedFunction - The parameters and returns a function with the independent variable as a parameter
* @return {Array}
*/
function step(data, params, damping, gradientDifference, parameterizedFunction, centralDifference, weights) {
let value = damping;
let identity = Matrix.eye(params.length, params.length, value);
const func = parameterizedFunction(params);
let evaluatedData = new Float64Array(data.x.length);
for (let i = 0; i < data.x.length; i++) {
evaluatedData[i] = func(data.x[i]);
}
let gradientFunc = gradientFunction(data, evaluatedData, params, gradientDifference, parameterizedFunction, centralDifference);
let residualError = matrixFunction(data, evaluatedData);
let inverseMatrix = inverse(identity.add(gradientFunc.mmul(gradientFunc.transpose().scale('row', {
scale: weights
}))));
let jacobianWeigthResidualError = gradientFunc.mmul(residualError.scale('row', {
scale: weights
}));
let perturbations = inverseMatrix.mmul(jacobianWeigthResidualError);
return {
perturbations,
jacobianWeigthResidualError
};
}
/**
* Curve fitting algorithm
* @param {{x:Array, y:Array}} data - Array of points to fit in the format [x1, x2, ... ], [y1, y2, ... ]
* @param {function} parameterizedFunction - The parameters and returns a function with the independent variable as a parameter
* @param {object} [options] - Options object
* @param {number|array} [options.weights = 1] - weighting vector, if the length does not match with the number of data points, the vector is reconstructed with first value.
* @param {number} [options.damping = 1e-2] - Levenberg-Marquardt parameter, small values of the damping parameter λ result in a Gauss-Newton update and large
values of λ result in a gradient descent update
* @param {number} [options.dampingStepDown = 9] - factor to reduce the damping (Levenberg-Marquardt parameter) when there is not an improvement when updating parameters.
* @param {number} [options.dampingStepUp = 11] - factor to increase the damping (Levenberg-Marquardt parameter) when there is an improvement when updating parameters.
* @param {number} [options.improvementThreshold = 1e-3] - the threshold to define an improvement through an update of parameters
* @param {number|array} [options.gradientDifference = 10e-2] - The step size to approximate the jacobian matrix
* @param {boolean} [options.centralDifference = false] - If true the jacobian matrix is approximated by central differences otherwise by forward differences
* @param {Array} [options.minValues] - Minimum allowed values for parameters
* @param {Array} [options.maxValues] - Maximum allowed values for parameters
* @param {Array} [options.initialValues] - Array of initial parameter values
* @param {number} [options.maxIterations = 100] - Maximum of allowed iterations
* @param {number} [options.errorTolerance = 10e-3] - Minimum uncertainty allowed for each point.
* @param {number} [options.timeout] - maximum time running before throw in seconds.
* @return {{parameterValues: Array, parameterError: number, iterations: number}}
*/
function levenbergMarquardt(data, parameterizedFunction, options = {}) {
let {
checkTimeout,
minValues,
maxValues,
parameters,
weightSquare,
damping,
dampingStepUp,
dampingStepDown,
maxIterations,
errorTolerance,
centralDifference,
gradientDifference,
improvementThreshold
} = checkOptions$1(data, parameterizedFunction, options);
let error = errorCalculation(data, parameters, parameterizedFunction, weightSquare);
let converged = error <= errorTolerance;
let iteration = 0;
for (; iteration < maxIterations && !converged; iteration++) {
let previousError = error;
let {
perturbations,
jacobianWeigthResidualError
} = step(data, parameters, damping, gradientDifference, parameterizedFunction, centralDifference, weightSquare);
for (let k = 0; k < parameters.length; k++) {
parameters[k] = Math.min(Math.max(minValues[k], parameters[k] - perturbations.get(k, 0)), maxValues[k]);
}
error = errorCalculation(data, parameters, parameterizedFunction, weightSquare);
if (isNaN(error)) break;
let improvementMetric = (previousError - error) / perturbations.transpose().mmul(perturbations.mulS(damping).add(jacobianWeigthResidualError)).get(0, 0);
if (improvementMetric > improvementThreshold) {
damping = Math.max(damping / dampingStepDown, 1e-7);
} else {
error = previousError;
damping = Math.min(damping * dampingStepUp, 1e7);
}
if (checkTimeout()) {
throw new Error(`The execution time is over to ${options.timeout} seconds`);
}
converged = error <= errorTolerance;
}
return {
parameterValues: parameters,
parameterError: error,
iterations: iteration
};
}
const LEVENBERG_MARQUARDT = 1;
function selectMethod(optimizationOptions = {}) {
let {
kind,
options
} = optimizationOptions;
kind = getKind(kind);
switch (kind) {
case LEVENBERG_MARQUARDT:
return {
algorithm: levenbergMarquardt,
optimizationOptions: checkOptions(kind, options)
};
default:
throw new Error(`Unknown kind algorithm`);
}
}
function checkOptions(kind, options = {}) {
// eslint-disable-next-line default-case
switch (kind) {
case LEVENBERG_MARQUARDT:
return Object.assign({}, lmOptions, options);
}
}
function getKind(kind) {
if (typeof kind !== 'string') return kind;
switch (kind.toLowerCase().replace(/[^a-z]/g, '')) {
case 'lm':
case 'levenbergmarquardt':
return LEVENBERG_MARQUARDT;
default:
throw new Error(`Unknown kind algorithm`);
}
}
const lmOptions = {
damping: 1.5,
maxIterations: 100,
errorTolerance: 1e-8
};
// const STATE_MIN = 1;
// const STATE_MAX = 2;
// const STATE_GRADIENT_DIFFERENCE = 3;
// const X = 0;
// const Y = 1;
// const WIDTH = 2;
// const MU = 3;
// const keys = ['x', 'y', 'width', 'mu'];
/**
* Fits a set of points to the sum of a set of bell functions.
* @param {object} data - An object containing the x and y data to be fitted.
* @param {array} peaks - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}].
* @param {object} [options = {}]
* @param {object} [options.shape={}] - it's specify the kind of shape used to fitting.
* @param {string} [options.shape.kind = 'gaussian'] - kind of shape; lorentzian, gaussian and pseudovoigt are supported.
* @param {object} [options.optimization = {}] - it's specify the kind and options of the algorithm use to optimize parameters.
* @param {object} [options.optimization.kind = 'lm'] - kind of algorithm. By default it's levenberg-marquardt.
* @param {object} [options.optimization.parameters] - options of each parameter to be optimized e.g. For a gaussian shape
* it could have x, y and with properties, each of which could contain init, min, max and gradientDifference, those options will define the guess,
* the min and max value of the parameter (search space) and the step size to approximate the jacobian matrix respectively. Those options could be a number,
* array of numbers, callback, or array of callbacks. Each kind of shape has default parameters so it could be undefined.
* @param {object} [options.optimization.parameters.x] - options for x parameter.
* @param {number|callback|array} [options.optimization.parameters.x.init] - definition of the starting point of the parameter (the guess),
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the guess of the first peak and so on.
* @param {number|callback|array} [options.optimization.parameters.x.min] - definition of the lower limit of the parameter,
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the min of the first peak and so on.
* @param {number|callback|array} [options.optimization.parameters.x.max] - definition of the upper limit of the parameter,
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the max of the first peak and so on.
* @param {number|callback|array} [options.optimization.parameters.x.gradientDifference] - definition of the step size to approximate the jacobian matrix of the parameter,
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the gradientDifference of the first peak and so on.
* @param {object} [options.optimization.options = {}] - options for the specific kind of algorithm.
* @param {number} [options.optimization.options.timeout] - maximum time running before break in seconds.
* @param {number} [options.optimization.options.damping=1.5]
* @param {number} [options.optimization.options.maxIterations=100]
* @param {number} [options.optimization.options.errorTolerance=1e-8]
* @returns {object} - A object with fitting error and the list of optimized parameters { parameters: [ {x, y, width} ], error } if the kind of shape is pseudoVoigt mu parameter is optimized.
*/
function optimize(data, peakList, options = {}) {
const {
y,
x,
maxY,
peaks,
paramsFunc,
optimization
} = checkInput(data, peakList, options);
let parameters = optimization.parameters;
let nbShapes = peaks.length;
let parameterKey = Object.keys(parameters);
let nbParams = nbShapes * parameterKey.length;
let pMin = new Float64Array(nbParams);
let pMax = new Float64Array(nbParams);
let pInit = new Float64Array(nbParams);
let gradientDifference = new Float64Array(nbParams);
for (let i = 0; i < nbShapes; i++) {
let peak = peaks[i];
for (let k = 0; k < parameterKey.length; k++) {
let key = parameterKey[k];
let init = parameters[key].init;
let min = parameters[key].min;
let max = parameters[key].max;
let gradientDifferenceValue = parameters[key].gradientDifference;
pInit[i + k * nbShapes] = init[i % init.length](peak);
pMin[i + k * nbShapes] = min[i % min.length](peak);
pMax[i + k * nbShapes] = max[i % max.length](peak);
gradientDifference[i + k * nbShapes] = gradientDifferenceValue[i % gradientDifferenceValue.length](peak);
}
}
let {
algorithm,
optimizationOptions
} = selectMethod(optimization);
optimizationOptions.minValues = pMin;
optimizationOptions.maxValues = pMax;
optimizationOptions.initialValues = pInit;
optimizationOptions.gradientDifference = gradientDifference;
let pFit = algorithm({
x,
y
}, paramsFunc, optimizationOptions);
let {
parameterError: error,
iterations
} = pFit;
let result = {
error,
iterations,
peaks
};
for (let i = 0; i < nbShapes; i++) {
pFit.parameterValues[i + nbShapes] *= maxY;
for (let k = 0; k < parameterKey.length; k++) {
// we modify the optimized parameters
peaks[i][parameterKey[k]] = pFit.parameterValues[i + k * nbShapes];
}
}
return result;
}
/**
* This function returns an array with absolute values
* @param {Array} array
* @return {Number}
*/
function xAbsolute(array) {
let tmpArray = array.slice();
for (let i = 0; i < tmpArray.length; i++) {
if (tmpArray[i] < 0) tmpArray[i] *= -1;
}
return tmpArray;
}
/**
* This function calculates the median after taking the reimAbsolute values of the points
* @param {Array} array - the array that will be rotated
* @return {Number}
*/
function xAbsoluteMedian(array) {
return median(xAbsolute(array));
}
/**
* Returns the closest index of a `target` in an ordered array
* @param {array} array
* @param {number} target
*/
function xFindClosestIndex(array, target) {
let low = 0;
let high = array.length - 1;
let middle = 0;
while (high - low > 1) {
middle = low + (high - low >> 1);
if (array[middle] < target) {
low = middle;
} else if (array[middle] > target) {
high = middle;
} else {
return middle;
}
}
if (low < array.length - 1) {
if (Math.abs(target - array[low]) < Math.abs(array[low + 1] - target)) {
return low;
} else {
return low + 1;
}
} else {
return low;
}
}
/**
* Returns an object with {fromIndex, toIndex} for a specific from / to
* @param {array} x
* @param {object} [options={}]
* @param {number} [options.from] - First value for xyIntegration in the X scale
* @param {number} [options.fromIndex=0] - First point for xyIntegration
* @param {number} [options.to] - Last value for xyIntegration in the X scale
* @param {number} [options.toIndex=x.length-1] - Last point for xyIntegration
*/
function xGetFromToIndex(x, options = {}) {
let {
fromIndex,
toIndex,
from,
to
} = options;
if (fromIndex === undefined) {
if (from !== undefined) {
fromIndex = xFindClosestIndex(x, from);
} else {
fromIndex = 0;
}
}
if (toIndex === undefined) {
if (to !== undefined) {
toIndex = xFindClosestIndex(x, to);
} else {
toIndex = x.length - 1;
}
}
if (fromIndex > toIndex) [fromIndex, toIndex] = [toIndex, fromIndex];
return {
fromIndex,
toIndex
};
}
function _typeof(obj) {
"@babel/helpers - typeof";
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function (obj) {
return typeof obj;
};
} else {
_typeof = function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
}
return _typeof(obj);
}
/**
* Fill an array with sequential numbers
* @param {Array} [input] - optional destination array (if not provided a new array will be created)
* @param {object} [options={}]
* @param {number} [options.from=0] - first value in the array
* @param {number} [options.to=10] - last value in the array
* @param {number} [options.size=input.length] - size of the array (if not provided calculated from step)
* @param {number} [options.step] - if not provided calculated from size
* @return {Array}
*/
function sequentialFill() {
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
if (_typeof(input) === 'object' && !isAnyArray(input)) {
options = input;
input = [];
}
if (!isAnyArray(input)) {
throw new TypeError('input must be an array');
}
var _options = options,
_options$from = _options.from,
from = _options$from === void 0 ? 0 : _options$from,
_options$to = _options.to,
to = _options$to === void 0 ? 10 : _options$to,
_options$size = _options.size,
size = _options$size === void 0 ? input.length : _options$size,
step = _options.step;
if (size !== 0 && step) {
throw new Error('step is defined by the array size');
}
if (!size) {
if (step) {
size = Math.floor((to - from) / step) + 1;
} else {
size = to - from + 1;
}
}
if (!step && size) {
step = (to - from) / (size - 1);
}
if (Array.isArray(input)) {
// only works with normal array
input.length = 0;
for (var i = 0; i < size; i++) {
input.push(from);
from += step;
}
} else {
if (input.length !== size) {
throw new Error('sequentialFill typed array must have the correct length');
}
for (var _i = 0; _i < size; _i++) {
input[_i] = from;
from += step;
}
}
return input;
}
var d3Array = createCommonjsModule(function (module, exports) {
(function (global, factory) {
factory(exports) ;
})(commonjsGlobal, function (exports) {
function ascending(a, b) {
return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
}
function bisector(compare) {
if (compare.length === 1) compare = ascendingComparator(compare);
return {
left: function (a, x, lo, hi) {
if (lo == null) lo = 0;
if (hi == null) hi = a.length;
while (lo < hi) {
var mid = lo + hi >>> 1;
if (compare(a[mid], x) < 0) lo = mid + 1;else hi = mid;
}
return lo;
},
right: function (a, x, lo, hi) {
if (lo == null) lo = 0;
if (hi == null) hi = a.length;
while (lo < hi) {
var mid = lo + hi >>> 1;
if (compare(a[mid], x) > 0) hi = mid;else lo = mid + 1;
}
return lo;
}
};
}
function ascendingComparator(f) {
return function (d, x) {
return ascending(f(d), x);
};
}
var ascendingBisect = bisector(ascending);
var bisectRight = ascendingBisect.right;
var bisectLeft = ascendingBisect.left;
function descending(a, b) {
return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN;
}
function number$1(x) {
return x === null ? NaN : +x;
}
function variance(array, f) {
var n = array.length,
m = 0,
a,
d,
s = 0,
i = -1,
j = 0;
if (f == null) {
while (++i < n) {
if (!isNaN(a = number$1(array[i]))) {
d = a - m;
m += d / ++j;
s += d * (a - m);
}
}
} else {
while (++i < n) {
if (!isNaN(a = number$1(f(array[i], i, array)))) {
d = a - m;
m += d / ++j;
s += d * (a - m);
}
}
}
if (j > 1) return s / (j - 1);
}
function deviation(array, f) {
var v = variance(array, f);
return v ? Math.sqrt(v) : v;
}
function extent(array, f) {
var i = -1,
n = array.length,
a,
b,
c;
if (f == null) {
while (++i < n) if ((b = array[i]) != null && b >= b) {
a = c = b;
break;
}
while (++i < n) if ((b = array[i]) != null) {
if (a > b) a = b;
if (c < b) c = b;
}
} else {
while (++i < n) if ((b = f(array[i], i, array)) != null && b >= b) {
a = c = b;
break;
}
while (++i < n) if ((b = f(array[i], i, array)) != null) {
if (a > b) a = b;
if (c < b) c = b;
}
}
return [a, c];
}
function constant(x) {
return function () {
return x;
};
}
function identity(x) {
return x;
}
function range(start, stop, step) {
start = +start, stop = +stop, step = (n = arguments.length) < 2 ? (stop = start, start = 0, 1) : n < 3 ? 1 : +step;
var i = -1,
n = Math.max(0, Math.ceil((stop - start) / step)) | 0,
range = new Array(n);
while (++i < n) {
range[i] = start + i * step;
}
return range;
}
var e10 = Math.sqrt(50);
var e5 = Math.sqrt(10);
var e2 = Math.sqrt(2);
function ticks(start, stop, count) {
var step = tickStep(start, stop, count);
return range(Math.ceil(start / step) * step, Math.floor(stop / step) * step + step / 2, // inclusive
step);
}
function tickStep(start, stop, count) {
var step0 = Math.abs(stop - start) / Math.max(0, count),
step1 = Math.pow(10, Math.floor(Math.log(step0) / Math.LN10)),
error = step0 / step1;
if (error >= e10) step1 *= 10;else if (error >= e5) step1 *= 5;else if (error >= e2) step1 *= 2;
return stop < start ? -step1 : step1;
}
function sturges(values) {
return Math.ceil(Math.log(values.length) / Math.LN2) + 1;
}
function number(x) {
return +x;
}
function histogram() {
var value = identity,
domain = extent,
threshold = sturges;
function histogram(data) {
var i,
n = data.length,
x,
values = new Array(n); // Coerce values to numbers.
for (i = 0; i < n; ++i) {
values[i] = +value(data[i], i, data);
}
var xz = domain(values),
x0 = +xz[0],
x1 = +xz[1],
tz = threshold(values, x0, x1); // Convert number of thresholds into uniform thresholds.
if (!Array.isArray(tz)) tz = ticks(x0, x1, +tz); // Coerce thresholds to numbers, ignoring any outside the domain.
var m = tz.length;
for (i = 0; i < m; ++i) tz[i] = +tz[i];
while (tz[0] <= x0) tz.shift(), --m;
while (tz[m - 1] >= x1) tz.pop(), --m;
var bins = new Array(m + 1),
bin; // Initialize bins.
for (i = 0; i <= m; ++i) {
bin = bins[i] = [];
bin.x0 = i > 0 ? tz[i - 1] : x0;
bin.x1 = i < m ? tz[i] : x1;
} // Assign data to bins by value, ignoring any outside the domain.
for (i = 0; i < n; ++i) {
x = values[i];
if (x0 <= x && x <= x1) {
bins[bisectRight(tz, x, 0, m)].push(data[i]);
}
}
return bins;
}
histogram.value = function (_) {
return arguments.length ? (value = typeof _ === "function" ? _ : constant(+_), histogram) : value;
};
histogram.domain = function (_) {
return arguments.length ? (domain = typeof _ === "function" ? _ : constant([+_[0], +_[1]]), histogram) : domain;
};
histogram.thresholds = function (_) {
if (!arguments.length) return threshold;
threshold = typeof _ === "function" ? _ : Array.isArray(_) ? constant(Array.prototype.map.call(_, number)) : constant(+_);
return histogram;
};
return histogram;
}
function quantile(array, p, f) {
if (f == null) f = number$1;
if (!(n = array.length)) return;
if ((p = +p) <= 0 || n < 2) return +f(array[0], 0, array);
if (p >= 1) return +f(array[n - 1], n - 1, array);
var n,
h = (n - 1) * p,
i = Math.floor(h),
a = +f(array[i], i, array),
b = +f(array[i + 1], i + 1, array);
return a + (b - a) * (h - i);
}
function freedmanDiaconis(values, min, max) {
values.sort(ascending);
return Math.ceil((max - min) / (2 * (quantile(values, 0.75) - quantile(values, 0.25)) * Math.pow(values.length, -1 / 3)));
}
function scott(values, min, max) {
return Math.ceil((max - min) / (3.5 * deviation(values) * Math.pow(values.length, -1 / 3)));
}
function max(array, f) {
var i = -1,
n = array.length,
a,
b;
if (f == null) {
while (++i < n) if ((b = array[i]) != null && b >= b) {
a = b;
break;
}
while (++i < n) if ((b = array[i]) != null && b > a) a = b;
} else {
while (++i < n) if ((b = f(array[i], i, array)) != null && b >= b) {
a = b;
break;
}
while (++i < n) if ((b = f(array[i], i, array)) != null && b > a) a = b;
}
return a;
}
function mean(array, f) {
var s = 0,
n = array.length,
a,
i = -1,
j = n;
if (f == null) {
while (++i < n) if (!isNaN(a = number$1(array[i]))) s += a;else --j;
} else {
while (++i < n) if (!isNaN(a = number$1(f(array[i], i, array)))) s += a;else --j;
}
if (j) return s / j;
}
function median(array, f) {
var numbers = [],
n = array.length,
a,
i = -1;
if (f == null) {
while (++i < n) if (!isNaN(a = number$1(array[i]))) numbers.push(a);
} else {
while (++i < n) if (!isNaN(a = number$1(f(array[i], i, array)))) numbers.push(a);
}
return quantile(numbers.sort(ascending), 0.5);
}
function merge(arrays) {
var n = arrays.length,
m,
i = -1,
j = 0,
merged,
array;
while (++i < n) j += arrays[i].length;
merged = new Array(j);
while (--n >= 0) {
array = arrays[n];
m = array.length;
while (--m >= 0) {
merged[--j] = array[m];
}
}
return merged;
}
function min(array, f) {
var i = -1,
n = array.length,
a,
b;
if (f == null) {
while (++i < n) if ((b = array[i]) != null && b >= b) {
a = b;
break;
}
while (++i < n) if ((b = array[i]) != null && a > b) a = b;
} else {
while (++i < n) if ((b = f(array[i], i, array)) != null && b >= b) {
a = b;
break;
}
while (++i < n) if ((b = f(array[i], i, array)) != null && a > b) a = b;
}
return a;
}
function pairs(array) {
var i = 0,
n = array.length - 1,
p = array[0],
pairs = new Array(n < 0 ? 0 : n);
while (i < n) pairs[i] = [p, p = array[++i]];
return pairs;
}
function permute(array, indexes) {
var i = indexes.length,
permutes = new Array(i);
while (i--) permutes[i] = array[indexes[i]];
return permutes;
}
function scan(array, compare) {
if (!(n = array.length)) return;
var i = 0,
n,
j = 0,
xi,
xj = array[j];
if (!compare) compare = ascending;
while (++i < n) if (compare(xi = array[i], xj) < 0 || compare(xj, xj) !== 0) xj = xi, j = i;
if (compare(xj, xj) === 0) return j;
}
function shuffle(array, i0, i1) {
var m = (i1 == null ? array.length : i1) - (i0 = i0 == null ? 0 : +i0),
t,
i;
while (m) {
i = Math.random() * m-- | 0;
t = array[m + i0];
array[m + i0] = array[i + i0];
array[i + i0] = t;
}
return array;
}
function sum(array, f) {
var s = 0,
n = array.length,
a,
i = -1;
if (f == null) {
while (++i < n) if (a = +array[i]) s += a; // Note: zero and null are equivalent.
} else {
while (++i < n) if (a = +f(array[i], i, array)) s += a;
}
return s;
}
function transpose(matrix) {
if (!(n = matrix.length)) return [];
for (var i = -1, m = min(matrix, length), transpose = new Array(m); ++i < m;) {
for (var j = -1, n, row = transpose[i] = new Array(n); ++j < n;) {
row[j] = matrix[j][i];
}
}
return transpose;
}
function length(d) {
return d.length;
}
function zip() {
return transpose(arguments);
}
var version = "0.7.1";
exports.version = version;
exports.bisect = bisectRight;
exports.bisectRight = bisectRight;
exports.bisectLeft = bisectLeft;
exports.ascending = ascending;
exports.bisector = bisector;
exports.descending = descending;
exports.deviation = deviation;
exports.extent = extent;
exports.histogram = histogram;
exports.thresholdFreedmanDiaconis = freedmanDiaconis;
exports.thresholdScott = scott;
exports.thresholdSturges = sturges;
exports.max = max;
exports.mean = mean;
exports.median = median;
exports.merge = merge;
exports.min = min;
exports.pairs = pairs;
exports.permute = permute;
exports.quantile = quantile;
exports.range = range;
exports.scan = scan;
exports.shuffle = shuffle;
exports.sum = sum;
exports.ticks = ticks;
exports.tickStep = tickStep;
exports.transpose = transpose;
exports.variance = variance;
exports.zip = zip;
});
});
const {
bisectRight
} = d3Array;
const quincunx = (u, v, w, q) => {
const n = u.length - 1;
u[0] = 0;
v[0] = 0;
w[0] = 0;
v[1] = v[1] / u[1];
w[1] = w[1] / u[1];
for (let i = 2; i < n; ++i) {
u[i] = u[i] - u[i - 2] * w[i - 2] * w[i - 2] - u[i - 1] * v[i - 1] * v[i - 1];
v[i] = (v[i] - u[i - 1] * v[i - 1] * w[i - 1]) / u[i];
w[i] = w[i] / u[i];
}
for (let i = 2; i < n; ++i) {
q[i] = q[i] - v[i - 1] * q[i - 1] - w[i - 2] * q[i - 2];
}
for (let i = 1; i < n; ++i) {
q[i] = q[i] / u[i];
}
q[n - 2] = q[n - 2] - v[n - 2] * q[n - 1];
for (let i = n - 3; i > 0; --i) {
q[i] = q[i] - v[i] * q[i + 1] - w[i] * q[i + 2];
}
};
const smoothingSpline = (x, y, sigma, lambda) => {
const n = x.length - 1;
const h = new Array(n + 1);
const r = new Array(n + 1);
const f = new Array(n + 1);
const p = new Array(n + 1);
const q = new Array(n + 1);
const u = new Array(n + 1);
const v = new Array(n + 1);
const w = new Array(n + 1);
const params = x.map(() => [0, 0, 0, 0]);
params.pop();
const mu = 2 * (1 - lambda) / (3 * lambda);
for (let i = 0; i < n; ++i) {
h[i] = x[i + 1] - x[i];
r[i] = 3 / h[i];
}
q[0] = 0;
for (let i = 1; i < n; ++i) {
f[i] = -(r[i - 1] + r[i]);
p[i] = 2 * (x[i + 1] - x[i - 1]);
q[i] = 3 * (y[i + 1] - y[i]) / h[i] - 3 * (y[i] - y[i - 1]) / h[i - 1];
}
q[n] = 0;
for (let i = 1; i < n; ++i) {
u[i] = r[i - 1] * r[i - 1] * sigma[i - 1] + f[i] * f[i] * sigma[i] + r[i] * r[i] * sigma[i + 1];
u[i] = mu * u[i] + p[i];
}
for (let i = 1; i < n - 1; ++i) {
v[i] = f[i] * r[i] * sigma[i] + r[i] * f[i + 1] * sigma[i + 1];
v[i] = mu * v[i] + h[i];
}
for (let i = 1; i < n - 2; ++i) {
w[i] = mu * r[i] * r[i + 1] * sigma[i + 1];
}
quincunx(u, v, w, q);
params[0][3] = y[0] - mu * r[0] * q[1] * sigma[0];
params[1][3] = y[1] - mu * (f[1] * q[1] + r[1] * q[2]) * sigma[0];
params[0][0] = q[1] / (3 * h[0]);
params[0][1] = 0;
params[0][2] = (params[1][3] - params[0][3]) / h[0] - q[1] * h[0] / 3;
r[0] = 0;
for (let i = 1; i < n; ++i) {
params[i][0] = (q[i + 1] - q[i]) / (3 * h[i]);
params[i][1] = q[i];
params[i][2] = (q[i] + q[i - 1]) * h[i - 1] + params[i - 1][2];
params[i][3] = r[i - 1] * q[i - 1] + f[i] * q[i] + r[i] * q[i + 1];
params[i][3] = y[i] - mu * params[i][3] * sigma[i];
}
return params;
};
class SplineInterpolator {
constructor(xIn, yIn, lambda = 1) {
const indices = xIn.map((_, i) => i);
indices.sort((i, j) => xIn[i] - xIn[j]);
const x = indices.map(i => xIn[i]);
const y = indices.map(i => yIn[i]);
const n = indices.length;
const sigma = indices.map(() => 1);
this.n = n;
this.x = x;
this.y = y;
this.params = smoothingSpline(x, y, sigma, lambda);
}
interpolate(v) {
if (v === this.x[this.n - 1]) {
return this.y[this.n - 1];
}
const i = Math.min(Math.max(0, bisectRight(this.x, v) - 1), this.n - 2);
const [a, b, c, d] = this.params[i];
v = v - this.x[i];
return a * v * v * v + b * v * v + c * v + d;
}
max(step = 100) {
const xStart = this.x[0];
const xStop = this.x[this.n - 1];
const delta = (xStop - xStart) / step;
let maxValue = -Infinity;
for (let i = 0, x = xStart; i < step; ++i, x += delta) {
const y = this.interpolate(x);
if (y > maxValue) {
maxValue = y;
}
}
return maxValue;
}
min(step = 100) {
const xStart = this.x[0];
const xStop = this.x[this.n - 1];
const delta = (xStop - xStart) / step;
let minValue = Infinity;
for (let i = 0, x = xStart; i < step; ++i, x += delta) {
const y = this.interpolate(x);
if (y < minValue) {
minValue = y;
}
}
return minValue;
}
domain() {
return [this.x[0], this.x[this.x.length - 1]];
}
range() {
return [this.min(), this.max()];
}
curve(nInterval, domain = null) {
domain = domain || this.domain();
const delta = (domain[1] - domain[0]) / (nInterval - 1);
const vals = new Array(nInterval);
for (let i = 0; i < nInterval; ++i) {
const x = delta * i + domain[0];
vals[i] = [x, this.interpolate(x)];
}
return vals;
}
}
var splineInterpolator = SplineInterpolator;
/* eslint-disable no-loss-of-precision */
/*
Adapted from: https://github.com/compute-io/erfcinv/blob/aa116e23883839359e310ad41a7c42f72815fc1e/lib/number.js
The MIT License (MIT)
Copyright (c) 2014-2015 The Compute.io Authors. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following:
The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Coefficients for erfcinv on [0, 0.5]:
const Y1 = 8.91314744949340820313e-2;
const P1 = [-5.38772965071242932965e-3, 8.22687874676915743155e-3, 2.19878681111168899165e-2, -3.65637971411762664006e-2, -1.26926147662974029034e-2, 3.34806625409744615033e-2, -8.36874819741736770379e-3, -5.08781949658280665617e-4];
const Q1 = [8.86216390456424707504e-4, -2.33393759374190016776e-3, 7.95283687341571680018e-2, -5.27396382340099713954e-2, -7.1228902341542847553e-1, 6.62328840472002992063e-1, 1.56221558398423026363, -1.56574558234175846809, -9.70005043303290640362e-1, 1]; // Coefficients for erfcinv for 0.5 > 1-x >= 0:
const Y2 = 2.249481201171875;
const P2 = [-3.67192254707729348546, 2.11294655448340526258e1, 1.7445385985570866523e1, -4.46382324441786960818e1, -1.88510648058714251895e1, 1.76447298408374015486e1, 8.37050328343119927838, 1.05264680699391713268e-1, -2.02433508355938759655e-1];
const Q2 = [1.72114765761200282724, -2.26436933413139721736e1, 1.08268667355460159008e1, 4.85609213108739935468e1, -2.01432634680485188801e1, -2.86608180499800029974e1, 3.9713437953343869095, 6.24264124854247537712, 1]; // Coefficients for erfcinv for sqrt( -log(1-x)):
const Y3 = 8.07220458984375e-1;
const P3 = [-6.81149956853776992068e-10, 2.85225331782217055858e-8, -6.79465575181126350155e-7, 2.14558995388805277169e-3, 2.90157910005329060432e-2, 1.42869534408157156766e-1, 3.37785538912035898924e-1, 3.87079738972604337464e-1, 1.17030156341995252019e-1, -1.63794047193317060787e-1, -1.31102781679951906451e-1];
const Q3 = [1.105924229346489121e-2, 1.52264338295331783612e-1, 8.48854343457902036425e-1, 2.59301921623620271374, 4.77846592945843778382, 5.38168345707006855425, 3.46625407242567245975, 1];
const Y4 = 9.3995571136474609375e-1;
const P4 = [2.66339227425782031962e-12, -2.30404776911882601748e-10, 4.60469890584317994083e-6, 1.57544617424960554631e-4, 1.87123492819559223345e-3, 9.50804701325919603619e-3, 1.85573306514231072324e-2, -2.22426529213447927281e-3, -3.50353787183177984712e-2];
const Q4 = [7.64675292302794483503e-5, 2.63861676657015992959e-3, 3.41589143670947727934e-2, 2.20091105764131249824e-1, 7.62059164553623404043e-1, 1.3653349817554063097, 1];
const Y5 = 9.8362827301025390625e-1;
const P5 = [9.9055709973310326855e-17, -2.81128735628831791805e-14, 4.62596163522878599135e-9, 4.49696789927706453732e-7, 1.49624783758342370182e-5, 2.09386317487588078668e-4, 1.05628862152492910091e-3, -1.12951438745580278863e-3, -1.67431005076633737133e-2];
const Q5 = [2.82243172016108031869e-7, 2.75335474764726041141e-5, 9.64011807005165528527e-4, 1.60746087093676504695e-2, 1.38151865749083321638e-1, 5.91429344886417493481e-1, 1];
function polyval(c, x) {
let p = 0;
for (const coef of c) {
p = p * x + coef;
}
return p;
}
/**
* Calculates a rational approximation.
*
* @private
* @param {Number} x
* @param {Number} v
* @param {Array} P - array of polynomial coefficients
* @param {Array} Q - array of polynomial coefficients
* @param {Number} Y
* @returns {Number} rational approximation
*/
function calc(x, v, P, Q, Y) {
const s = x - v;
const r = polyval(P, s) / polyval(Q, s);
return Y * x + r * x;
}
/**
* Evaluates the complementary inverse error function for an input value.
*
* @private
* @param {Number} x - input value
* @returns {Number} evaluated complementary inverse error function
*/
function erfcinv(x) {
let sign = false;
let val;
let q;
let g;
let r; // [1] Special cases...
// NaN:
if (Number.isNaN(x)) {
return NaN;
} // x not on the interval: [0,2]
if (x < 0 || x > 2) {
throw new RangeError(`erfcinv()::invalid input argument. Value must be on the interval [0,2]. Value: \`${x}\`.`);
}
if (x === 0) {
return Number.POSITIVE_INFINITY;
}
if (x === 2) {
return Number.NEGATIVE_INFINITY;
}
if (x === 1) {
return 0;
} // [2] Get the sign and make use of `erfc` reflection formula: `erfc(-z)=2 - erfc(z)`...
if (x > 1) {
q = 2 - x;
x = 1 - q;
sign = true;
} else {
q = x;
x = 1 - x;
} // [3] |x| <= 0.5
if (x <= 0.5) {
g = x * (x + 10);
r = polyval(P1, x) / polyval(Q1, x);
val = g * Y1 + g * r;
return sign ? -val : val;
} // [4] 1-|x| >= 0.25
if (q >= 0.25) {
g = Math.sqrt(-2 * Math.log(q));
q = q - 0.25;
r = polyval(P2, q) / polyval(Q2, q);
val = g / (Y2 + r);
return sign ? -val : val;
}
q = Math.sqrt(-Math.log(q)); // [5] q < 3
if (q < 3) {
return calc(q, 1.125, P3, Q3, Y3);
} // [6] q < 6
if (q < 6) {
return calc(q, 3, P4, Q4, Y4);
} // Note that the smallest number in JavaScript is 5e-324. Math.sqrt( -Math.log( 5e-324 ) ) ~27.2844
return calc(q, 6, P5, Q5, Y5); // Note that in the boost library, they are able to go to much smaller values, as 128 bit long doubles support ~1e-5000; something which JavaScript does not natively support.
}
function rayleighCdf(x, sigma = 1) {
if (x < 0) {
return 0;
}
return -Math.expm1(-Math.pow(x, 2) / (2 * Math.pow(sigma, 2)));
}
/**
* Determine noise level by san plot methodology (https://doi.org/10.1002/mrc.4882)
* @param {Array} data - real or magnitude spectra data.
* @param {object} [options = {}]
* @param {array} [options.mask] - boolean array to filter data, if the i-th element is true then the i-th element of the distribution will be ignored.
* @param {number} [options.scaleFactor=1] - factor to scale the data input[i]*=scaleFactor.
* @param {number} [options.cutOff] - percent of positive signal distribution where the noise level will be determined, if it is not defined the program calculate it.
* @param {number} [options.factorStd=5] - factor times std to determine what will be marked as signals.
* @param {boolean} [options.refine=true] - if true the noise level will be recalculated get out the signals using factorStd.
* @param {boolean} [options.fixOffset=true] - If the baseline is correct, the midpoint of distribution should be zero. if true, the distribution will be centered.
* @param {number} [options.logBaseY=2] - log scale to apply in the intensity axis in order to avoid big numbers.
*/
function xNoiseSanPlot(data, options = {}) {
const {
mask,
cutOff,
refine = true,
magnitudeMode = false,
scaleFactor = 1,
factorStd = 5,
fixOffset = true
} = options;
let input;
if (Array.isArray(mask) && mask.length === data.length) {
input = new Float64Array(data.filter((_e, i) => !mask[i]));
} else {
input = new Float64Array(data);
}
if (scaleFactor > 1) {
for (let i = 0; i < input.length; i++) {
input[i] *= scaleFactor;
}
}
input = input.sort().reverse();
if (fixOffset && !magnitudeMode) {
let medianIndex = Math.floor(input.length / 2);
let median = 0.5 * (input[medianIndex] + input[medianIndex + 1]);
for (let i = 0; i < input.length; i++) {
input[i] -= median;
}
}
let firstNegativeValueIndex = input[input.length - 1] >= 0 ? input.length : input.findIndex(e => e < 0);
let lastPositiveValueIndex = firstNegativeValueIndex - 1;
for (let i = lastPositiveValueIndex; i >= 0; i--) {
if (input[i] > 0) {
lastPositiveValueIndex = i;
break;
}
}
let signPositive = input.slice(0, lastPositiveValueIndex + 1);
let signNegative = input.slice(firstNegativeValueIndex);
let cutOffDist = cutOff || determineCutOff(signPositive, {
magnitudeMode
});
let pIndex = Math.floor(signPositive.length * cutOffDist);
let initialNoiseLevelPositive = signPositive[pIndex];
let skyPoint = signPositive[0];
let initialNoiseLevelNegative;
if (signNegative.length > 0) {
let nIndex = Math.floor(signNegative.length * (1 - cutOffDist));
initialNoiseLevelNegative = -1 * signNegative[nIndex];
} else {
initialNoiseLevelNegative = 0;
}
let noiseLevelPositive = initialNoiseLevelPositive;
let noiseLevelNegative = initialNoiseLevelNegative;
let cloneSignPositive = signPositive.slice();
let cloneSignNegative = signNegative.slice();
let cutOffSignalsIndexPlus = 0;
let cutOffSignalsIndexNeg = 2;
if (refine) {
let cutOffSignals = noiseLevelPositive * factorStd;
cutOffSignalsIndexPlus = signPositive.findIndex(e => e < cutOffSignals);
if (cutOffSignalsIndexPlus > -1) {
cloneSignPositive = signPositive.slice(cutOffSignalsIndexPlus);
noiseLevelPositive = cloneSignPositive[Math.floor(cloneSignPositive.length * cutOffDist)];
}
cutOffSignals = noiseLevelNegative * factorStd;
cutOffSignalsIndexNeg = signNegative.findIndex(e => e < cutOffSignals);
if (cutOffSignalsIndexNeg > -1) {
cloneSignNegative = signNegative.slice(cutOffSignalsIndexNeg);
noiseLevelNegative = cloneSignPositive[Math.floor(cloneSignNegative.length * (1 - cutOffDist))];
}
}
let correctionFactor = -simpleNormInv(cutOffDist / 2, {
magnitudeMode
});
initialNoiseLevelPositive = initialNoiseLevelPositive / correctionFactor;
initialNoiseLevelNegative = initialNoiseLevelNegative / correctionFactor;
let effectiveCutOffDist, refinedCorrectionFactor;
if (refine && cutOffSignalsIndexPlus > -1) {
effectiveCutOffDist = (cutOffDist * cloneSignPositive.length + cutOffSignalsIndexPlus) / (cloneSignPositive.length + cutOffSignalsIndexPlus);
refinedCorrectionFactor = -1 * simpleNormInv(effectiveCutOffDist / 2, {
magnitudeMode
});
noiseLevelPositive /= refinedCorrectionFactor;
if (cutOffSignalsIndexNeg > -1) {
effectiveCutOffDist = (cutOffDist * cloneSignNegative.length + cutOffSignalsIndexNeg) / (cloneSignNegative.length + cutOffSignalsIndexNeg);
refinedCorrectionFactor = -1 * simpleNormInv(effectiveCutOffDist / 2, {
magnitudeMode
});
if (noiseLevelNegative !== 0) {
noiseLevelNegative /= refinedCorrectionFactor;
}
}
} else {
noiseLevelPositive /= correctionFactor;
noiseLevelNegative /= correctionFactor;
}
return {
positive: noiseLevelPositive,
negative: noiseLevelNegative,
snr: skyPoint / noiseLevelPositive,
sanplot: generateSanPlot(input, {
fromTo: {
positive: {
from: 0,
to: lastPositiveValueIndex
},
negative: {
from: firstNegativeValueIndex,
to: input.length
}
}
})
};
}
function determineCutOff(signPositive, options = {}) {
let {
magnitudeMode = false,
considerList = {
from: 0.5,
step: 0.1,
to: 0.9
}
} = options; //generate a list of values for
let cutOff = [];
let indexMax = signPositive.length - 1;
for (let i = 0.01; i <= 0.99; i += 0.01) {
let index = Math.round(indexMax * i);
let value = -signPositive[index] / simpleNormInv([i / 2], {
magnitudeMode
});
cutOff.push([i, value]);
}
let minKi = Number.MAX_SAFE_INTEGER;
let {
from,
to,
step
} = considerList;
let delta = step / 2;
let whereToCutStat = 0.5;
for (let i = from; i <= to; i += step) {
let floor = i - delta;
let top = i + delta;
let elementsOfCutOff = cutOff.filter(e => e[0] < top && e[0] > floor);
let averageValue = elementsOfCutOff.reduce((a, b) => a + Math.abs(b[1]), 0);
let kiSqrt = 0;
for (let j = 0; j < elementsOfCutOff.length; j++) {
kiSqrt += Math.pow(elementsOfCutOff[j][1] - averageValue, 2);
}
if (kiSqrt < minKi) {
minKi = kiSqrt;
whereToCutStat = i;
}
}
return whereToCutStat;
}
function simpleNormInv(data, options = {}) {
const {
magnitudeMode = false
} = options;
if (!Array.isArray(data)) data = [data];
let from = 0;
let to = 2;
let step = 0.01;
let xTraining = createArray(from, to, step);
let result = new Float64Array(data.length);
let yTraining = new Float64Array(xTraining.length);
if (magnitudeMode) {
let factor = 1;
for (let i = 0; i < yTraining.length; i++) {
let finalInput = xTraining[i] * factor;
yTraining[i] = 1 - rayleighCdf(finalInput);
}
let interp = new splineInterpolator(xTraining, yTraining);
for (let i = 0; i < result.length; i++) {
let yValue = 2 * data[i];
result[i] = -1 * interp.interpolate(yValue);
}
} else {
for (let i = 0; i < result.length; i++) {
result[i] = -1 * Math.SQRT2 * erfcinv(2 * data[i]);
}
}
return result.length === 1 ? result[0] : result;
}
function createArray(from, to, step) {
let result = new Array(Math.abs((from - to) / step + 1));
for (let i = 0; i < result.length; i++) {
result[i] = from + i * step;
}
return result;
}
function generateSanPlot(array, options = {}) {
const {
fromTo,
logBaseY = 2
} = options;
let sanplot = {};
for (let key in fromTo) {
let {
from,
to
} = fromTo[key];
sanplot[key] = from !== to ? scale(array.slice(from, to), {
logBaseY
}) : {
x: [],
y: []
};
if (key === 'negative') {
sanplot[key].y.reverse();
}
}
return sanplot;
}
function scale(array, options = {}) {
const {
log10,
abs
} = Math;
const {
logBaseY
} = options;
if (logBaseY) {
array = array.slice();
const logOfBase = log10(logBaseY);
for (let i = 0; i < array.length; i++) {
array[i] = log10(abs(array[i])) / logOfBase;
}
}
const xAxis = sequentialFill({
from: 0,
to: array.length - 1,
size: array.length
});
return {
x: xAxis,
y: array
};
}
/**
* Throw an error in no an object of x,y arrays
* @param {DataXY} [data={}]
*/
function xyCheck(data = {}) {
if (!isAnyArray(data.x) || !isAnyArray(data.y)) {
throw new Error('Data must be an object of x and y arrays');
}
if (data.x.length !== data.y.length) {
throw new Error('The x and y arrays mush have the same length');
}
}
/**
* Normalize an array of zones:
* - ensure than from < to
* - merge overlapping zones
* @param {Array} [zones=[]]
* @param {object} [options={}]
* @param {number} [options.from=Number.MIN_VALUE]
* @param {number} [options.to=Number.MAX_VALUE]
*/
function zonesNormalize(zones = [], options = {}) {
if (zones.length === 0) return [];
zones = JSON.parse(JSON.stringify(zones)).map(zone => zone.from > zone.to ? {
from: zone.to,
to: zone.from
} : zone);
let {
from = Number.NEGATIVE_INFINITY,
to = Number.POSITIVE_INFINITY
} = options;
if (from > to) {
[from, to] = [to, from];
}
zones = zones.sort((a, b) => {
if (a.from !== b.from) return a.from - b.from;
return a.to - b.to;
});
zones.forEach(zone => {
if (from > zone.from) zone.from = from;
if (to < zone.to) zone.to = to;
});
zones = zones.filter(zone => zone.from <= zone.to);
if (zones.length === 0) return [];
let currentZone = zones[0];
let result = [currentZone];
for (let zone of zones) {
if (zone.from <= currentZone.to) {
currentZone.to = zone.to;
} else {
currentZone = zone;
result.push(currentZone);
}
}
return result;
}
/**
* xyExtract zones from a XY data
* @param {DataXY} [data={}] - Object that contains property x (an ordered increasing array) and y (an array)
* @param {object} [options={}]
* @param {Array} [options.zones=[]]
* @return {Array} Array of points
*/
function xyExtract(data = {}, options = {}) {
xyCheck(data);
const {
x,
y
} = data;
let {
zones
} = options;
zones = zonesNormalize(zones);
if (!Array.isArray(zones) || zones.length === 0) return data;
let newX = [];
let newY = [];
let currentZone = zones[0];
let position = 0;
loop: for (let i = 0; i < x.length; i++) {
while (currentZone.to < x[i]) {
position++;
currentZone = zones[position];
if (!currentZone) {
i = x.length;
break loop;
}
}
if (x[i] >= currentZone.from) {
newX.push(x[i]);
newY.push(y[i]);
}
}
return {
x: newX,
y: newY
};
}
/**
* Calculate integration
* @param {DataXY} [data={}] - Object that contains property x (an ordered increasing array) and y (an array)
* @param {object} [options={}]
* @param {number} [options.from] - First value for xyIntegration in the X scale
* @param {number} [options.fromIndex=0] - First point for xyIntegration
* @param {number} [options.to] - Last value for xyIntegration in the X scale
* @param {number} [options.toIndex=x.length-1] - Last point for xyIntegration
* @return {number} xyIntegration value on the specified range
*/
function xyIntegration(data = {}, options = {}) {
xyCheck(data);
const {
x,
y
} = data;
if (x.length < 2) return 0;
const {
fromIndex,
toIndex
} = xGetFromToIndex(x, options);
let currentxyIntegration = 0;
for (let i = fromIndex; i < toIndex; i++) {
currentxyIntegration += (x[i + 1] - x[i]) * (y[i + 1] + y[i]) / 2;
}
return currentxyIntegration;
}
/**
* Group peaks based on factor and add group property in peaks
* @param {array} peakList
* @param {number} factor
*/
function groupPeaks(peakList, factor = 1) {
if (peakList.length === 0) return [];
let peaks = peakList.sort((a, b) => a.x - b.x);
let previousPeak = {
x: Number.NEGATIVE_INFINITY,
width: 1
};
let currentGroup = [previousPeak];
let groups = [];
for (let peak of peaks) {
if ((peak.x - previousPeak.x) / (peak.width + previousPeak.width) <= factor / 2) {
currentGroup.push(peak);
} else {
currentGroup = [peak];
groups.push(currentGroup);
}
peak.group = groups.length - 1;
previousPeak = peak;
}
return groups;
}
/**
* Optimize the position (x), max intensity (y), full width at half maximum (width)
* and the ratio of gaussian contribution (mu) if it's required. It supports three kind of shapes: gaussian, lorentzian and pseudovoigt
* @param {object} data - An object containing the x and y data to be fitted.
* @param {Array} peakList - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}].
* @param {object} [options = {}] -
* @param {number} [options.factorWidth = 1] - times of width to group peaks.
* @param {number} [options.factorLimits = 2] - times of width to use to optimize peaks
* @param {object} [options.shape={}] - it's specify the kind of shape used to fitting.
* @param {string} [options.shape.kind='gaussian'] - kind of shape; lorentzian, gaussian and pseudovoigt are supported.
* @param {string} [options.shape.options={}] - options depending the kind of shape
* @param {object} [options.optimization={}] - it's specify the kind and options of the algorithm use to optimize parameters.
* @param {string} [options.optimization.kind='lm'] - kind of algorithm. By default it's levenberg-marquardt.
* @param {object} [options.optimization.options={}] - options for the specific kind of algorithm.
* @param {number} [options.optimization.options.timeout=10] - maximum time running before break in seconds.
*/
function optimizePeaks(data, peakList, options = {}) {
const {
factorWidth = 1,
factorLimits = 2,
shape = {
kind: 'gaussian'
},
optimization = {
kind: 'lm',
options: {
timeout: 10
}
}
} = options;
if (data.x[0] > data.x[1]) {
data.x.reverse();
data.y.reverse();
}
let groups = groupPeaks(peakList, factorWidth);
let results = [];
for (const peaks of groups) {
const firstPeak = peaks[0];
const lastPeak = peaks[peaks.length - 1];
const from = firstPeak.x - firstPeak.width * factorLimits;
const to = lastPeak.x + lastPeak.width * factorLimits;
const {
fromIndex,
toIndex
} = xGetFromToIndex(data.x, {
from,
to
}); // Multiple peaks
const currentRange = {
x: data.x.slice(fromIndex, toIndex),
y: data.y.slice(fromIndex, toIndex)
};
if (currentRange.x.length > 5) {
let {
peaks: optimizedPeaks
} = optimize(currentRange, peaks, {
shape,
optimization
});
results = results.concat(optimizedPeaks);
} else {
results = results.concat(peaks);
}
}
return results;
}
/**
* This function try to join the peaks that seems to belong to a broad signal in a single broad peak.
* @param {Array} peakList - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}].
* @param {object} [options = {}] - options
* @param {number} [options.width=0.25] - width limit to join peaks.
* @param {object} [options.shape={}] - it's specify the kind of shape used to fitting.
* @param {string} [options.shape.kind = 'gaussian'] - kind of shape; lorentzian, gaussian and pseudovoigt are supported.
* @param {object} [options.optimization = {}] - it's specify the kind and options of the algorithm use to optimize parameters.
* @param {string} [options.optimization.kind = 'lm'] - kind of algorithm. By default it's levenberg-marquardt.
* @param {number} [options.optimization.options.timeout = 10] - maximum time running before break in seconds.
* @param {object} [options.optimization.options = {}] - options for the specific kind of algorithm.
*/
function joinBroadPeaks(peakList, options = {}) {
let {
width = 0.25,
shape = {
kind: 'gaussian'
},
optimization = {
kind: 'lm',
timeout: 10
}
} = options;
let broadLines = []; // Optimize the possible broad lines
let max = 0;
let maxI = 0;
let count = 1;
for (let i = peakList.length - 1; i >= 0; i--) {
if (peakList[i].soft) {
broadLines.push(peakList.splice(i, 1)[0]);
}
} // Push a feke peak
broadLines.push({
x: Number.MAX_VALUE
});
let candidates = {
x: [broadLines[0].x],
y: [broadLines[0].y]
};
let indexes = [0];
for (let i = 1; i < broadLines.length; i++) {
if (Math.abs(broadLines[i - 1].x - broadLines[i].x) < width) {
candidates.x.push(broadLines[i].x);
candidates.y.push(broadLines[i].y);
if (broadLines[i].y > max) {
max = broadLines[i].y;
maxI = i;
}
indexes.push(i);
count++;
} else {
if (count > 2) {
let fitted = optimize(candidates, [{
x: broadLines[maxI].x,
y: max,
width: Math.abs(candidates.x[0] - candidates.x[candidates.x.length - 1])
}], {
shape,
optimization
});
let {
peaks: peak
} = fitted;
peak[0].index = Math.floor(indexes.reduce((a, b) => a + b, 0) / indexes.length);
peak[0].soft = false;
peakList.push(peak[0]);
} else {
// Put back the candidates to the signals list
indexes.forEach(index => {
peakList.push(broadLines[index]);
});
}
candidates = {
x: [broadLines[i].x],
y: [broadLines[i].y]
};
indexes = [i];
max = broadLines[i].y;
maxI = i;
count = 1;
}
}
peakList.sort(function (a, b) {
return a.x - b.x;
});
return peakList;
}
/**
* Implementation of the peak picking method described by Cobas in:
* A new approach to improving automated analysis of proton NMR spectra
* through Global Spectral Deconvolution (GSD)
* http://www.spectrosco-pyeurope.com/images/stories/ColumnPDFs/TD_23_1.pdf
* @param {DataXY} data - Object of kind
* @param {object} [options={}] - options object with some parameter for GSD.
* @param {number} [options.minMaxRatio = 0.01] - Threshold to determine if a given peak should be considered as a noise, bases on its relative height compared to the highest peak.
* @param {number} [options.broadRatio = 0.00025] - If broadRatio is higher than 0, then all the peaks which second derivative smaller than broadRatio * maxAbsSecondDerivative will be marked with the soft mask equal to true.
* @param {number} [options.broadWidth = 0.25] - Threshold to determine if some peak is candidate to clustering into range.
* @param {number} [options.thresholdFactor=3] - the factor that multiplies the noise level to set up a threshold to select peaks with respect to the intensity.
* @param {number} [options.noiseLevel = median(data.y) * (options.thresholdFactor || 3)] - Noise threshold in spectrum y units. Default is three/thresholdFactor times the absolute median of data.y.
* @param {number} [options.factorWidth = 4] - factor to determine the width at the moment to group the peaks in signals in 'GSD.optimizePeaks' function.
* @param {object} [options.shape={}] - it's specify the kind of shape used to fitting.
* @param {string} [options.shape.kind = 'gaussian'] - kind of shape; lorentzian, gaussian and pseudovoigt are supported.
* @param {object} [options.optimization = {}] - it's specify the kind and options of the algorithm use to optimize parameters.
* @param {string} [options.optimization.kind = 'lm'] - kind of algorithm. By default it's levenberg-marquardt.
* @param {object} [options.optimization.options = {}] - options for the specific kind of algorithm.
* @param {Boolean} [options.smoothY = true] - Select the peak intensities from a smoothed version of the independent variables?
* @param {Boolean} [options.optimize = true] - if it's true adjust an train of gaussian or lorentzian shapes to spectrum.
* @return {Array}
*/
function xyAutoPeaksPicking(data, options = {}) {
const {
from,
to,
noiseLevel,
thresholdFactor = 3,
minMaxRatio = 0.05,
broadRatio = 0.00025,
useSanPlot = false,
smoothY = true,
optimize = false,
factorWidth = 4,
realTopDetection = true,
shape = {
kind: 'gaussian'
},
optimization = {
kind: 'lm'
},
broadWidth = 0.25,
lookNegative = false,
sgOptions = {
windowSize: 9,
polynomial: 3
}
} = options;
if (from !== undefined && to !== undefined) {
data = xyExtract(data, [{
from,
to
}]);
}
const cutOff = getCutOff(data.y, {
noiseLevel,
useSanPlot,
thresholdFactor
});
let getPeakOptions = {
shape,
broadWidth,
optimize,
factorWidth,
sgOptions,
minMaxRatio,
broadRatio,
noiseLevel: cutOff.positive,
smoothY,
optimization,
realTopDetection
};
let peaks = getPeakList(data, getPeakOptions);
if (lookNegative) {
getPeakOptions.noiseLevel = cutOff.negative;
peaks.push(...getNegativePeaks(data, getPeakOptions));
}
return peaks;
}
function getPeakList(data, options) {
const {
shape,
broadWidth,
optimize,
factorWidth,
sgOptions,
minMaxRatio,
broadRatio,
noiseLevel,
smoothY,
optimization,
realTopDetection
} = options;
let peakList = gsd(data, {
sgOptions,
minMaxRatio,
broadRatio,
noiseLevel,
smoothY,
realTopDetection
});
if (broadWidth) {
peakList = joinBroadPeaks(peakList, {
width: broadWidth,
shape,
optimization
});
}
if (optimize) {
peakList = optimizePeaks(data, peakList, {
shape,
factorWidth,
optimization
});
}
return peakList;
}
function getNegativePeaks(data, options) {
let {
x,
y
} = data;
let negativeDataY = new Float64Array(data.y.length);
for (let i = 0; i < negativeDataY.length; i++) {
negativeDataY[i] = -1 * y[i];
}
let peakList = getPeakList({
x,
y: negativeDataY
}, options);
for (let i = 0; i < peakList.length; i++) {
peakList[i].y *= -1;
}
return peakList;
}
function getCutOff(data, options = {}) {
const {
noiseLevel,
useSanPlot,
thresholdFactor
} = options;
const formatResult = noiseLevel => typeof noiseLevel === 'number' ? {
positive: noiseLevel,
negative: -noiseLevel
} : noiseLevel;
if (noiseLevel) {
return formatResult(noiseLevel);
} else {
return useSanPlot ? xNoiseSanPlot(data, {
factorStd: thresholdFactor
}) : formatResult(xAbsoluteMedian(data) * thresholdFactor);
}
}
/*
* This library implements the J analyser described by Cobas et al in the paper:
* A two-stage approach to automatic determination of 1H NMR coupling constants
*/
const patterns = ['s', 'd', 't', 'q', 'quint', 'h', 'sept', 'o', 'n'];
let symRatio = 1.5;
let maxErrorIter1 = 2.5; // Hz
let maxErrorIter2 = 1; // Hz
let jAxisKeys = {
jAxis: 'x',
intensity: 'intensity'
};
var jAnalyzer = {
/**
* The compilation process implements at the first stage a normalization procedure described by Golotvin et al.
* embedding in peak-component-counting method described by Hoyes et al.
* @param {object} signal
* @private
*/
compilePattern: function (signal, options = {}) {
let {
jAxisKey = jAxisKeys
} = options;
signal.multiplicity = 'm'; // 1.1 symmetrize
// It will add a set of peaks(signal.peaksComp) to the signal that will be used during
// the compilation process. The unit of those peaks will be in Hz
signal.symRank = symmetrizeChoiseBest(signal, {
maxError: maxErrorIter1,
iteration: 1,
jAxisKey
});
signal.asymmetric = true; // Is the signal symmetric?
if (signal.symRank >= 0.95 && signal.peaksComp.length < 32) {
signal.asymmetric = false;
let P1, n2, maxFlagged;
let k = 1;
let Jc = []; // Loop over the possible number of coupling contributing to the multiplet
for (let n = 0; n < 9; n++) {
// 1.2 Normalize. It makes a deep copy of the peaks before to modify them.
let peaks = normalize(signal, n); // signal.peaksCompX = peaks;
let validPattern = false; // It will change to true, when we find the good patter
// Lets check if the signal could be a singulet.
if (peaks.length === 1 && n === 0) {
validPattern = true;
} else {
if (peaks.length <= 1) {
continue;
}
} // 1.3 Establish a range for the Heights Hi [peaks.intensity*0.85,peaks.intensity*1.15];
let ranges = getRanges(peaks);
n2 = Math.pow(2, n); // 1.4 Find a combination of integer heights Hi, one from each Si, that sums to 2^n.
let heights = null;
let counter = 1;
while (!validPattern && (heights = getNextCombination(ranges, n2)) !== null && counter < 400) {
// 2.1 Number the components of the multiplet consecutively from 1 to 2n,
// starting at peak 1
let numbering = new Array(heights.length);
k = 1;
for (let i = 0; i < heights.length; i++) {
numbering[i] = new Array(heights[i]);
for (let j = 0; j < heights[i]; j++) {
numbering[i][j] = k++;
}
}
Jc = []; // The array to store the detected j-coupling
// 2.2 Set j = 1; J1 = P2 - P1. Flag components 1 and 2 as accounted for.
let j = 1;
Jc.push(peaks[1].x - peaks[0].x);
P1 = peaks[0].x;
numbering[0].splice(0, 1); // Flagged
numbering[1].splice(0, 1); // Flagged
k = 1;
let nFlagged = 2;
maxFlagged = Math.pow(2, n) - 1;
while (Jc.length < n && nFlagged < maxFlagged && k < peaks.length) {
counter += 1; // 4.1. Increment j. Set k to the number of the first unflagged component.
j++;
while (k < peaks.length && numbering[k].length === 0) {
k++;
}
if (k < peaks.length) {
// 4.2 Jj = Pk - P1.
Jc.push(peaks[k].x - peaks[0].x); // Flag component k and, for each sum of the...
numbering[k].splice(0, 1); // Flageed
nFlagged++; // Flag the other components of the multiplet
for (let u = 2; u <= j; u++) {
let jSum = 0;
for (let i = 0; i < u; i++) {
jSum += Jc[i];
}
for (let i = 1; i < numbering.length; i++) {
// Maybe 0.25 Hz is too much?
if (Math.abs(peaks[i].x - (P1 + jSum)) < 0.25) {
numbering[i].splice(0, 1); // Flageed
nFlagged++;
break;
}
}
}
}
} // Calculate the ideal patter by using the extracted j-couplings
let pattern = idealPattern(Jc); // Compare the ideal pattern with the proposed intensities.
// All the intensities have to match to accept the multiplet
validPattern = true;
for (let i = 0; i < pattern.length; i++) {
if (pattern[i].intensity !== heights[i]) {
validPattern = false;
}
}
} // If we found a valid pattern we should inform about the pattern.
if (validPattern) {
updateSignal(signal, Jc);
}
}
} // Before to return, change the units of peaksComp from Hz to PPM again
for (let i = 0; i < signal.peaksComp.length; i++) {
signal.peaksComp[i].x /= signal.observe;
}
}
};
/**
* @private
* update the signal
* @param {*} signal
* @param {*} Jc
*/
function updateSignal(signal, Jc) {
// Update the limits of the signal
let peaks = signal.peaksComp; // Always in Hz
let nbPeaks = peaks.length;
signal.startX = peaks[0].x / signal.observe - peaks[0].width;
signal.stopX = peaks[nbPeaks - 1].x / signal.observe + peaks[nbPeaks - 1].width;
signal.integralData.from = peaks[0].x / signal.observe - peaks[0].width * 3;
signal.integralData.to = peaks[nbPeaks - 1].x / signal.observe + peaks[nbPeaks - 1].width * 3; // Compile the pattern and format the constant couplings
signal.maskPattern = signal.mask2;
signal.multiplicity = abstractPattern(signal, Jc);
signal.pattern = signal.multiplicity; // Our library depends on this parameter, but it is old
}
/**
* Returns the multiplet in the compact format
* @param {object} signal
* @param {object} Jc
* @return {String}
* @private
*/
function abstractPattern(signal, Jc) {
let tol = 0.05;
let pattern = '';
let cont = 1;
let newNmrJs = [];
if (Jc && Jc.length > 0) {
Jc.sort(function (a, b) {
return b - a;
});
for (let i = 0; i < Jc.length - 1; i++) {
if (Math.abs(Jc[i] - Jc[i + 1]) < tol) {
cont++;
} else {
newNmrJs.push({
coupling: Math.abs(Jc[i]),
multiplicity: patterns[cont]
});
pattern += patterns[cont];
cont = 1;
}
}
let index = Jc.length - 1;
newNmrJs.push({
coupling: Math.abs(Jc[index]),
multiplicity: patterns[cont]
});
pattern += patterns[cont];
signal.nmrJs = newNmrJs;
} else {
pattern = 's';
if (Math.abs(signal.startX - signal.stopX) * signal.observe > 16) {
pattern = 'br s';
}
}
return pattern;
}
/**
* This function creates an ideal pattern from the given J-couplings
* @private
* @param {Array} Jc
* @return {*[]}
* @private
*/
function idealPattern(Jc) {
let hsum = Math.pow(2, Jc.length);
let pattern = [{
x: 0,
intensity: hsum
}]; // To split the initial height
for (let i = 0; i < Jc.length; i++) {
for (let j = pattern.length - 1; j >= 0; j--) {
pattern.push({
x: pattern[j].x + Jc[i] / 2,
intensity: pattern[j].intensity / 2
});
pattern[j].x = pattern[j].x - Jc[i] / 2;
pattern[j].intensity = pattern[j].intensity / 2;
}
} // To sum the heights in the same positions
pattern.sort(function compare(a, b) {
return a.x - b.x;
});
for (let j = pattern.length - 2; j >= 0; j--) {
if (Math.abs(pattern[j].x - pattern[j + 1].x) < 0.1) {
pattern[j].intensity += pattern[j + 1].intensity;
pattern.splice(j + 1, 1);
}
}
return pattern;
}
/**
* Find a combination of integer heights Hi, one from each Si, that sums to 2n.
* @param {object} ranges
* @param {Number} value
* @return {*}
* @private
*/
function getNextCombination(ranges, value) {
let half = Math.ceil(ranges.values.length * 0.5);
let lng = ranges.values.length;
let sum = 0;
let ok;
while (sum !== value) {
// Update the indexes to point at the next possible combination
ok = false;
while (!ok) {
ok = true;
ranges.currentIndex[ranges.active]++;
if (ranges.currentIndex[ranges.active] >= ranges.values[ranges.active].length) {
// In this case, there is no more possible combinations
if (ranges.active + 1 === half) {
return null;
} else {
// If this happens we need to try the next active peak
ranges.currentIndex[ranges.active] = 0;
ok = false;
ranges.active++;
}
} else {
ranges.active = 0;
}
} // Sum the heights for this combination
sum = 0;
for (let i = 0; i < half; i++) {
sum += ranges.values[i][ranges.currentIndex[i]] * 2;
}
if (ranges.values.length % 2 !== 0) {
sum -= ranges.values[half - 1][ranges.currentIndex[half - 1]];
}
} // If the sum is equal to the expected value, fill the array to return
if (sum === value) {
let heights = new Array(lng);
for (let i = 0; i < half; i++) {
heights[i] = ranges.values[i][ranges.currentIndex[i]];
heights[lng - i - 1] = ranges.values[i][ranges.currentIndex[i]];
}
return heights;
}
return null;
}
/**
* This function generates the possible values that each peak can contribute
* to the multiplet.
* @param {Array} peaks Array of objects with peaks information {intensity}
* @return {{values: Array, currentIndex: Array, active: number}}
* @private
*/
function getRanges(peaks) {
let ranges = new Array(peaks.length);
let currentIndex = new Array(peaks.length);
let min, max;
ranges[0] = [1];
ranges[peaks.length - 1] = [1];
currentIndex[0] = -1;
currentIndex[peaks.length - 1] = 0;
for (let i = 1; i < peaks.length - 1; i++) {
min = Math.round(peaks[i].intensity * 0.85);
max = Math.round(peaks[i].intensity * 1.15);
ranges[i] = [];
for (let j = min; j <= max; j++) {
ranges[i].push(j);
}
currentIndex[i] = 0;
}
return {
values: ranges,
currentIndex: currentIndex,
active: 0
};
}
/**
* Performs a symmetrization of the signal by using different aproximations to the center.
* It will return the result of the symmetrization that removes less peaks from the signal
* @param {object} signal
* @param {Number} maxError
* @param {Number} iteration
* @return {*}
* @private
*/
function symmetrizeChoiseBest(signal, options = {}) {
let {
maxError,
iteration,
jAxisKey = jAxisKeys
} = options;
let symRank1 = symmetrize(signal, maxError, iteration, jAxisKey);
let tmpPeaks = signal.peaksComp;
let tmpMask = signal.mask;
let cs = signal.delta1;
signal.delta1 = (signal.peaks[0].x + signal.peaks[signal.peaks.length - 1].x) / 2;
let symRank2 = symmetrize(signal, maxError, iteration, jAxisKey);
if (signal.peaksComp.length > tmpPeaks.length) {
return symRank2;
} else {
signal.delta1 = cs;
signal.peaksComp = tmpPeaks;
signal.mask = tmpMask;
return symRank1;
}
}
/**
* This function will return a set of symmetric peaks that will
* be the enter point for the patter compilation process.
* @param {object} signal
* @param {Number} maxError
* @param {Number} iteration
* @return {Number}
* @private
*/
function symmetrize(signal, maxError, iteration, key) {
let {
jAxis,
intensity
} = key; // Before to symmetrize we need to keep only the peaks that possibly conforms the multiplete
let max, min, avg, ratio, avgWidth;
let peaks = new Array(signal.peaks.length); // Make a deep copy of the peaks and convert PPM ot HZ
for (let i = 0; i < peaks.length; i++) {
peaks[i] = {
x: signal.peaks[i][jAxis] * signal.observe,
intensity: signal.peaks[i][intensity],
width: signal.peaks[i].width
};
} // Join the peaks that are closer than 0.25 Hz
for (let i = peaks.length - 2; i >= 0; i--) {
if (Math.abs(peaks[i].x - peaks[i + 1].x) < 0.25) {
peaks[i].x = peaks[i].x * peaks[i].intensity + peaks[i + 1].x * peaks[i + 1].intensity;
peaks[i].intensity = peaks[i].intensity + peaks[i + 1].intensity;
peaks[i].x /= peaks[i].intensity;
peaks[i].intensity /= 2;
peaks[i].width += peaks[i + 1].width;
peaks.splice(i + 1, 1);
}
}
signal.peaksComp = peaks;
let nbPeaks = peaks.length;
let mask = new Array(nbPeaks);
signal.mask = mask;
let left = 0;
let right = peaks.length - 1;
let cs = signal.delta1 * signal.observe;
let middle = [(peaks[0].x + peaks[nbPeaks - 1].x) / 2, 1];
maxError = error(Math.abs(cs - middle[0]));
let heightSum = 0; // We try to symmetrize the extreme peaks. We consider as candidates for symmetricing those which have
// ratio smaller than 3
for (let i = 0; i < nbPeaks; i++) {
mask[i] = true; // heightSum += signal.peaks[i].intensity;
heightSum += peaks[i].intensity;
}
while (left <= right) {
mask[left] = true;
mask[right] = true;
if (left === right) {
if (nbPeaks > 2 && Math.abs(peaks[left].x - cs) > maxError) {
mask[left] = false;
}
} else {
max = Math.max(peaks[left].intensity, peaks[right].intensity);
min = Math.min(peaks[left].intensity, peaks[right].intensity);
ratio = max / min;
if (ratio > symRatio) {
if (peaks[left].intensity === min) {
mask[left] = false;
right++;
} else {
mask[right] = false;
left--;
}
} else {
let diffL = Math.abs(peaks[left].x - cs);
let diffR = Math.abs(peaks[right].x - cs);
if (Math.abs(diffL - diffR) < maxError) {
avg = Math.min(peaks[left].intensity, peaks[right].intensity);
avgWidth = Math.min(peaks[left].width, peaks[right].width);
peaks[left].intensity = peaks[right].intensity = avg;
peaks[left].width = peaks[right].width = avgWidth;
middle = [middle[0] + (peaks[right].x + peaks[left].x) / 2, middle[1] + 1];
} else {
if (Math.max(diffL, diffR) === diffR) {
mask[right] = false;
left--;
} else {
mask[left] = false;
right++;
}
}
}
}
left++;
right--; // Only alter cs if it is the first iteration of the sym process.
if (iteration === 1) {
cs = chemicalShift(peaks, mask); // There is not more available peaks
if (isNaN(cs)) {
return 0;
}
}
maxError = error(Math.abs(cs - middle[0] / middle[1]));
} // To remove the weak peaks and recalculate the cs
for (let i = nbPeaks - 1; i >= 0; i--) {
if (mask[i] === false) {
peaks.splice(i, 1);
}
}
cs = chemicalShift(peaks);
if (isNaN(cs)) {
return 0;
}
signal.delta1 = cs / signal.observe; // Now, the peak should be symmetric in heights, but we need to know if it is symmetric in x
let symFactor = 0;
let weight = 0;
if (peaks.length > 1) {
for (let i = Math.ceil(peaks.length / 2) - 1; i >= 0; i--) {
symFactor += (3 + Math.min(Math.abs(peaks[i].x - cs), Math.abs(peaks[peaks.length - 1 - i].x - cs))) / (3 + Math.max(Math.abs(peaks[i].x - cs), Math.abs(peaks[peaks.length - 1 - i].x - cs))) * peaks[i].intensity;
weight += peaks[i].intensity;
}
symFactor /= weight;
} else {
if (peaks.length === 1) {
symFactor = 1;
}
}
let newSumHeights = 0;
for (let i = 0; i < peaks.length; i++) {
newSumHeights += peaks[i].intensity;
}
symFactor -= (heightSum - newSumHeights) / heightSum * 0.12; // Removed peaks penalty
// Sometimes we need a second opinion after the first symmetrization.
if (symFactor > 0.8 && symFactor < 0.97 && iteration < 2) {
return symmetrize(signal, maxErrorIter2, 2, key);
} else {
// Center the given pattern at cs and symmetrize x
if (peaks.length > 1) {
let dxi;
for (let i = Math.ceil(peaks.length / 2) - 1; i >= 0; i--) {
dxi = (peaks[i].x - peaks[peaks.length - 1 - i].x) / 2.0;
peaks[i].x = cs + dxi;
peaks[peaks.length - 1 - i].x = cs - dxi;
}
}
}
return symFactor;
}
/**
* Error validator
* @param {Number} value
* @return {Number}
* @private
*/
function error(value) {
let maxError = value * 2.5;
if (maxError < 0.75) {
maxError = 0.75;
}
if (maxError > 3) {
maxError = 3;
}
return maxError;
}
/**
* @private
* 2 stages normalizarion of the peaks heights to Math.pow(2,n).
* Creates a new mask with the peaks that could contribute to the multiplete
* @param {object} signal
* @param {Number} n
* @return {*}
*/
function normalize(signal, n) {
// Perhaps this is slow
let peaks = JSON.parse(JSON.stringify(signal.peaksComp));
let norm = 0;
let norm2 = 0;
for (let i = 0; i < peaks.length; i++) {
norm += peaks[i].intensity;
}
norm = Math.pow(2, n) / norm;
signal.mask2 = JSON.parse(JSON.stringify(signal.mask));
let index = signal.mask2.length - 1;
for (let i = peaks.length - 1; i >= 0; i--) {
peaks[i].intensity *= norm;
while (index >= 0 && signal.mask2[index] === false) {
index--;
}
if (peaks[i].intensity < 0.75) {
peaks.splice(i, 1);
signal.mask2[index] = false;
} else {
norm2 += peaks[i].intensity;
}
index--;
}
norm2 = Math.pow(2, n) / norm2;
for (let i = peaks.length - 1; i >= 0; i--) {
peaks[i].intensity *= norm2;
}
return peaks;
}
/**
* @private
* Calculates the chemical shift as the weighted sum of the peaks
* @param {Array} peaks
* @param {Array} mask
* @return {Number}
*/
function chemicalShift(peaks, mask) {
let sum = 0;
let cs = 0;
let area;
if (mask) {
for (let i = 0; i < peaks.length; i++) {
if (mask[i] === true) {
area = getArea(peaks[i]);
sum += area;
cs += area * peaks[i].x;
}
}
} else {
for (let i = 0; i < peaks.length; i++) {
area = getArea(peaks[i]);
sum += area;
cs += area * peaks[i].x;
}
}
return cs / sum;
}
/**
* Return the area of a Lorentzian function
* @param {object} peak - object with peak information
* @return {Number}
* @private
*/
function getArea(peak) {
return Math.abs(peak.intensity * peak.width * 1.57); // 1.772453851);
}
function joinRanges(ranges) {
ranges.sort((a, b) => a.from - b.from);
for (let i = 0; i < ranges.length - 1; i++) {
if (ranges[i].to > ranges[i + 1].from) {
ranges[i].to = Math.max(ranges[i + 1].to, ranges[i].to);
ranges[i].signal = ranges[i].signal.concat(ranges[i + 1].signal);
ranges[i].integral += ranges[i + 1].integral;
ranges.splice(i + 1, 1);
i--;
}
}
return ranges;
}
// import { Ranges } from 'spectra-data-ranges';
/**
* This function clustering peaks and calculate the integral value for each range from the peak list returned from extractPeaks function.
* @param {Object} data - spectra data
* @param {Array} peakList - nmr signals
* @param {Object} [options={}] - options object with some parameter for GSD, detectSignal functions.
* @param {Number} [options.integrationSum=100] - Number of hydrogens or some number to normalize the integral data. If it's zero return the absolute integral value
* @param {String} [options.integralType='sum'] - option to chose between approx area with peaks or the sum of the points of given range ('sum', 'peaks')
* @param {Number} [options.frequencyCluster=16] - distance limit to clustering peaks.
* @param {Number} [options.clean=0.4] - If exits it remove all the signals with integration < clean value
* @param {Boolean} [options.compile=true] - If true, the Janalyzer function is run over signals to compile the patterns.
* @param {Boolean} [options.keepPeaks=false] - If true each signal will contain an array of peaks.
* @param {String} [options.nucleus='1H'] - Nucleus
* @param {String} [options.frequency=400] - Observed frequency
* @returns {Array}
*/
function peaksToRanges(data, peakList, options = {}) {
let {
integrationSum = 100,
joinOverlapRanges = true,
clean = 0.4,
compile = true,
integralType = 'sum',
frequency = 400,
frequencyCluster = 16,
keepPeaks = false,
nucleus = '1H'
} = options;
let signalOptions = {
integrationSum,
integralType,
frequencyCluster,
frequency,
nucleus
};
if (data.x[0] > data.x[1]) {
data.x = data.x.reverse();
data.y = data.y.reverse();
}
let signals = detectSignals(data, peakList, signalOptions);
if (clean) {
for (let i = 0; i < signals.length; i++) {
if (Math.abs(signals[i].integralData.value) < clean) {
signals.splice(i, 1);
}
}
}
if (compile) {
let nHi, sum;
for (let i = 0; i < signals.length; i++) {
jAnalyzer.compilePattern(signals[i]);
if (signals[i].maskPattern && signals[i].multiplicity !== 'm' && signals[i].multiplicity !== '') {
// Create a new signal with the removed peaks
nHi = 0;
sum = 0;
let peaksO = [];
for (let j = signals[i].maskPattern.length - 1; j >= 0; j--) {
sum += computeArea(signals[i].peaks[j]);
if (signals[i].maskPattern[j] === false) {
let peakR = signals[i].peaks.splice(j, 1)[0];
peaksO.push({
x: peakR.x,
y: peakR.intensity,
width: peakR.width
});
signals[i].mask.splice(j, 1);
signals[i].mask2.splice(j, 1);
signals[i].maskPattern.splice(j, 1);
signals[i].nbPeaks--;
nHi += computeArea(peakR);
}
}
if (peaksO.length > 0) {
nHi = nHi * signals[i].integralData.value / sum;
signals[i].integralData.value -= nHi;
let peaks1 = [];
for (let j = peaksO.length - 1; j >= 0; j--) {
peaks1.push(peaksO[j]);
}
signalOptions.integrationSum = Math.abs(nHi);
let ranges = detectSignals(data, peaks1, signalOptions);
for (let j = 0; j < ranges.length; j++) {
signals.push(ranges[j]);
}
}
}
} // it was a updateIntegrals function.
let sumIntegral = 0;
let sumObserved = 0;
for (let i = 0; i < signals.length; i++) {
sumObserved += Math.abs(Math.round(signals[i].integralData.value));
}
if (sumObserved !== integrationSum) {
sumIntegral = integrationSum / sumObserved;
for (let i = 0; i < signals.length; i++) {
signals[i].integralData.value *= sumIntegral;
}
}
}
signals.sort((a, b) => {
return b.delta1 - a.delta1;
});
if (clean) {
for (let i = signals.length - 1; i >= 0; i--) {
if (Math.abs(signals[i].integralData.value) < clean) {
signals.splice(i, 1);
}
}
}
let ranges = []; //new Array(signals.length);
for (let i = 0; i < signals.length; i++) {
let signal = signals[i];
ranges[i] = {
from: signal.integralData.from,
to: signal.integralData.to,
integral: signal.integralData.value,
signal: [{
kind: signal.kind || 'signal',
multiplicity: signal.multiplicity
}]
};
if (keepPeaks) {
ranges[i].signal[0].peak = signal.peaks;
}
if (signal.nmrJs) {
ranges[i].signal[0].j = signal.nmrJs;
}
if (!signal.asymmetric || signal.multiplicity === 'm') {
ranges[i].signal[0].delta = signal.delta1;
}
}
if (joinOverlapRanges) ranges = joinRanges(ranges); // return new Ranges(ranges);
return ranges;
}
/**
* Extract the signals from the peakList and the given spectrum.
* @param {object} data - spectra data
* @param {array} peakList - nmr signals
* @param {object} [options = {}]
* @param {number} [options.integrationSum='100'] - Number of hydrogens or some number to normalize the integration data, If it's zero return the absolute integral value
* @param {string} [options.integralType='sum'] - option to chose between approx area with peaks or the sum of the points of given range
* @param {number} [options.frequencyCluster=16] - distance limit to clustering the peaks.
* range = frequencyCluster / observeFrequency -> Peaks withing this range are considered to belongs to the same signal1D
* @param {string} [options.nucleus='1H'] - - Nucleus
* @param {String} [options.frequency = 400] - Observed frequency
* @return {array} nmr signals
* @private
*/
function detectSignals(data, peakList, options = {}) {
let {
integrationSum = 100,
integralType = 'sum',
frequencyCluster = 16,
frequency = 400,
nucleus = '1H'
} = options;
let signal1D, peaks;
let signals = [];
let prevPeak = {
x: 100000
};
let spectrumIntegral = 0;
frequencyCluster /= frequency;
for (let i = 0; i < peakList.length; i++) {
if (Math.abs(peakList[i].x - prevPeak.x) > frequencyCluster) {
signal1D = {
nbPeaks: 1,
units: 'PPM',
startX: peakList[i].x - peakList[i].width,
stopX: peakList[i].x + peakList[i].width,
multiplicity: '',
pattern: '',
observe: frequency,
nucleus,
integralData: {
from: peakList[i].x - peakList[i].width * 3,
to: peakList[i].x + peakList[i].width * 3
},
peaks: [{
x: peakList[i].x,
intensity: peakList[i].y,
width: peakList[i].width
}]
};
if (peakList[i].kind) signal1D.kind = peakList[i].kind;
signals.push(signal1D);
} else {
let tmp = peakList[i].x + peakList[i].width;
signal1D.stopX = Math.max(signal1D.stopX, tmp);
signal1D.startX = Math.min(signal1D.startX, tmp);
signal1D.nbPeaks++;
signal1D.peaks.push({
x: peakList[i].x,
intensity: peakList[i].y,
width: peakList[i].width
});
signal1D.integralData.from = Math.min(signal1D.integralData.from, peakList[i].x - peakList[i].width * 3);
signal1D.integralData.to = Math.max(signal1D.integralData.to, peakList[i].x + peakList[i].width * 3);
if (peakList[i].kind) signal1D.kind = peakList[i].kind;
}
prevPeak = peakList[i];
}
for (let i = 0; i < signals.length; i++) {
peaks = signals[i].peaks;
let integral = signals[i].integralData;
let chemicalShift = 0;
let integralPeaks = 0;
for (let j = 0; j < peaks.length; j++) {
let area = computeArea(peaks[j]);
chemicalShift += peaks[j].x * area;
integralPeaks += area;
}
signals[i].delta1 = chemicalShift / integralPeaks;
if (integralType === 'sum') {
integral.value = xyIntegration(data, {
from: integral.from,
to: integral.to
});
} else {
integral.value = integralPeaks;
}
spectrumIntegral += integral.value;
}
if (integrationSum > 0) {
let integralFactor = integrationSum / spectrumIntegral;
for (let i = 0; i < signals.length; i++) {
let integral = signals[i].integralData;
integral.value *= integralFactor;
}
}
return signals;
}
/**
* Return the area of a Lorentzian function
* @param {object} peak - object with peak information
* @return {Number}
* @private
*/
function computeArea(peak) {
return Math.abs(peak.intensity * peak.width * 1.57); // todo add an option with this value: 1.772453851
}
/**
* Detect peaks, optimize parameters and compile multiplicity if required.
* @param {DataXY} data - Object of kind
* @param {object} [options={}] - options object with some parameter for GSD.
* @param {object} [options.peakPicking={}] - options to peak detection and optimization.
* @param {number} [options.peakPicking.minMaxRatio=0.01] - Threshold to determine if a given peak should be considered as a noise, bases on its relative height compared to the highest peak.
* @param {number} [options.peakPicking.broadRatio=0.00025] - If broadRatio is higher than 0, then all the peaks which second derivative smaller than broadRatio * maxAbsSecondDerivative will be marked with the soft mask equal to true.
* @param {number} [options.peakPicking.broadWidth=0.25] - Threshold to determine if some peak is candidate to clustering into range.
* @param {number} [options.peakPicking.noiseLevel=median(data.y) * (options.thresholdFactor || 3)] - Noise threshold in spectrum y units. Default is three/thresholdFactor times the absolute median of data.y.
* @param {number} [options.peakPicking.factorWidth=4] - factor to determine the width at the moment to group the peaks in signals in 'GSD.optimizePeaks' function.
* @param {object} [options.peakPicking.shape={}] - it's specify the kind of shape used to fitting.
* @param {string} [options.peakPicking.shape.kind='gaussian'] - kind of shape; lorentzian, gaussian and pseudovoigt are supported.
* @param {object} [options.peakPicking.optimization={}] - it's specify the kind and options of the algorithm use to optimize parameters.
* @param {string} [options.peakPicking.optimization.kind='lm'] - kind of algorithm. By default it's levenberg-marquardt.
* @param {object} [options.peakPicking.optimization.options={}] - options for the specific kind of algorithm.
* @param {Boolean} [options.peakPicking.compile=true] - If true, the Janalyzer function is run over signals to compile the patterns.
* @param {Boolean} [options.peakPicking.smoothY=true] - Select the peak intensities from a smoothed version of the independent variables?
* @param {Boolean} [options.peakPicking.optimize=true] - if it's true adjust an train of gaussian or lorentzian shapes to spectrum.
* @param {Boolean} [options.peakPicking.optimize=true] - if it's true adjust an train of gaussian or lorentzian shapes to spectrum.
* @param {Number} [options.ranges.integrationSum=100] - Number of hydrogens or some number to normalize the integral data. If it's zero return the absolute integral value
* @param {String} [options.ranges.integralType='sum'] - option to chose between approx area with peaks or the sum of the points of given range ('sum', 'peaks')
* @param {Number} [options.ranges.frequencyCluster=16] - distance limit to clustering peaks.
* @param {Number} [options.ranges.clean=0.4] - If exits it remove all the signals with integration < clean value
* @param {Boolean} [options.ranges.compile=true] - If true, the Janalyzer function is run over signals to compile the patterns.
* @param {Boolean} [options.ranges.keepPeaks=false] - If true each signal will contain an array of peaks.
* @param {String} [options.ranges.nucleus='1H'] - Nucleus
* @param {String} [options.ranges.frequency=400] - Observed frequency
* @param {object} [options.impurities={}] - impurities options.
* @param {string} [options.impurities.solvent=''] - solvent name.
* @param {string} [options.impurities.error=0.025] - tolerance in ppm to assign a impurity.
* @returns {array} - Array of ranges with {from, to, integral, signals: [{delta, j, multiplicity, peaks}]}
*/
function xyAutoRangesPicking(data, options = {}) {
let peaks = xyAutoPeaksPicking(data, options.peakPicking);
peaks = peaksFilterImpurities(peaks, options.impurities);
return peaksToRanges(data, peaks, options.ranges);
}
// sources:
// https://en.wikipedia.org/wiki/Gyromagnetic_ratio
// TODO: #13 can we have a better source and more digits ? @jwist
const gyromagneticRatio = {
'1H': 267.52218744e6,
'2H': 41.065e6,
'3H': 285.3508e6,
'3He': -203.789e6,
'7Li': 103.962e6,
'13C': 67.28284e6,
'14N': 19.331e6,
'15N': -27.116e6,
'17O': -36.264e6,
'19F': 251.662e6,
'23Na': 70.761e6,
'27Al': 69.763e6,
'29Si': -53.19e6,
'31P': 108.291e6,
'57Fe': 8.681e6,
'63Cu': 71.118e6,
'67Zn': 16.767e6,
'129Xe': -73.997e6
};
function postProcessingNMR(entriesFlat) {
// specific NMR functions
for (let entry of entriesFlat) {
let observeFrequency = 0;
let shiftOffsetVal = 0;
for (let spectrum of entry.spectra) {
if (entry.ntuples && entry.ntuples.symbol) {
if (!observeFrequency && spectrum.observeFrequency) {
observeFrequency = spectrum.observeFrequency;
}
if (!shiftOffsetVal && spectrum.shiftOffsetVal) {
shiftOffsetVal = spectrum.shiftOffsetVal;
}
} else {
observeFrequency = spectrum.observeFrequency;
shiftOffsetVal = spectrum.shiftOffsetVal;
}
if (observeFrequency) {
if (spectrum.xUnits && spectrum.xUnits.toUpperCase().includes('HZ')) {
spectrum.xUnits = 'PPM';
spectrum.xFactor = spectrum.xFactor / observeFrequency;
spectrum.firstX = spectrum.firstX / observeFrequency;
spectrum.lastX = spectrum.lastX / observeFrequency;
spectrum.deltaX = spectrum.deltaX / observeFrequency;
for (let i = 0; i < spectrum.data.x.length; i++) {
spectrum.data.x[i] /= observeFrequency;
}
}
}
if (shiftOffsetVal) {
let shift = spectrum.firstX - shiftOffsetVal;
spectrum.firstX = spectrum.firstX - shift;
spectrum.lastX = spectrum.lastX - shift;
for (let i = 0; i < spectrum.data.x.length; i++) {
spectrum.data.x[i] -= shift;
}
} // we will check if some nucleus are missing ...
if (entry.ntuples && entry.ntuples.nucleus && entry.ntuples.symbol) {
for (let i = 0; i < entry.ntuples.nucleus.length; i++) {
let symbol = entry.ntuples.symbol[i];
let nucleus = entry.ntuples.nucleus[i];
if (symbol.startsWith('F') && !nucleus) {
if (symbol === 'F1') {
// if F1 is defined we will use F2
if (entry.tmp.$NUC2) {
entry.ntuples.nucleus[i] = entry.tmp.$NUC2;
} else {
let f2index = entry.ntuples.symbol.indexOf('F2');
if (f2index && entry.ntuples.nucleus[f2index]) {
entry.ntuples.nucleus[i] = entry.ntuples.nucleus[f2index];
}
}
}
if (symbol === 'F2') entry.ntuples.nucleus[i] = entry.tmp.$NUC1;
}
if (symbol === 'F2') {
entry.yType = entry.ntuples.nucleus[0];
}
}
}
if (observeFrequency && entry.ntuples && entry.ntuples.symbol && entry.ntuples.nucleus) {
let unit = '';
let pageSymbolIndex = entry.ntuples.symbol.indexOf(spectrum.pageSymbol);
if (entry.ntuples.units && entry.ntuples.units[pageSymbolIndex]) {
unit = entry.ntuples.units[pageSymbolIndex];
}
if (unit !== 'PPM') {
if (pageSymbolIndex !== 0) {
throw Error('Not sure about this ntuples format');
}
let ratio0 = gyromagneticRatio[entry.ntuples.nucleus[0]];
let ratio1 = gyromagneticRatio[entry.ntuples.nucleus[1]];
if (!ratio0 || !ratio1) {
throw Error('Problem with determination of gyromagnetic ratio');
}
let ratio = ratio0 / ratio1 * observeFrequency;
spectrum.pageValue /= ratio;
}
}
}
}
}
function profiling(result, action, options) {
if (result.profiling) {
result.profiling.push({
action,
time: Date.now() - options.start
});
}
}
function simpleChromatogram(result) {
let data = result.spectra[0].data;
result.chromatogram = {
times: data.x.slice(),
series: {
intensity: {
dimension: 1,
data: data.y.slice()
}
}
};
}
function postProcessing(entriesFlat, result, options) {
// converting Hz to ppm
postProcessingNMR(entriesFlat);
for (let entry of entriesFlat) {
if (Object.keys(entry.ntuples).length > 0) {
let newNtuples = [];
let keys = Object.keys(entry.ntuples);
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
let values = entry.ntuples[key];
for (let j = 0; j < values.length; j++) {
if (!newNtuples[j]) newNtuples[j] = {};
newNtuples[j][key] = values[j];
}
}
entry.ntuples = newNtuples;
}
if (entry.twoD && options.wantXY) {
add2D(entry, options);
profiling(result, 'Finished countour plot calculation', options);
if (!options.keepSpectra) {
delete entry.spectra;
}
} // maybe it is a GC (HPLC) / MS. In this case we add a new format
if (options.chromatogram) {
if (entry.spectra.length > 1) {
complexChromatogram(entry);
} else {
simpleChromatogram(entry);
}
profiling(result, 'Finished chromatogram calculation', options);
}
delete entry.tmp;
}
}
function prepareNtuplesDatatable(currentEntry, spectrum, kind) {
let xIndex = -1;
let yIndex = -1;
let firstVariable = '';
let secondVariable = '';
if (kind.indexOf('++') > 0) {
firstVariable = kind.replace(/.*\(([a-zA-Z0-9]+)\+\+.*/, '$1');
secondVariable = kind.replace(/.*\.\.([a-zA-Z0-9]+).*/, '$1');
} else {
kind = kind.replace(/[^a-zA-Z]/g, '');
firstVariable = kind.charAt(0);
secondVariable = kind.charAt(1);
spectrum.variables = {};
for (let symbol of kind) {
let lowerCaseSymbol = symbol.toLowerCase();
let index = currentEntry.ntuples.symbol.indexOf(symbol);
if (index === -1) throw Error(`Symbol undefined: ${symbol}`);
spectrum.variables[lowerCaseSymbol] = {};
for (let key in currentEntry.ntuples) {
if (currentEntry.ntuples[key][index]) {
spectrum.variables[lowerCaseSymbol][key.replace(/^var/, '')] = currentEntry.ntuples[key][index];
}
}
}
}
xIndex = currentEntry.ntuples.symbol.indexOf(firstVariable);
yIndex = currentEntry.ntuples.symbol.indexOf(secondVariable);
if (xIndex === -1) xIndex = 0;
if (yIndex === -1) yIndex = 0;
if (currentEntry.ntuples.first) {
if (currentEntry.ntuples.first.length > xIndex) {
spectrum.firstX = currentEntry.ntuples.first[xIndex];
}
if (currentEntry.ntuples.first.length > yIndex) {
spectrum.firstY = currentEntry.ntuples.first[yIndex];
}
}
if (currentEntry.ntuples.last) {
if (currentEntry.ntuples.last.length > xIndex) {
spectrum.lastX = currentEntry.ntuples.last[xIndex];
}
if (currentEntry.ntuples.last.length > yIndex) {
spectrum.lastY = currentEntry.ntuples.last[yIndex];
}
}
if (currentEntry.ntuples.vardim && currentEntry.ntuples.vardim.length > xIndex) {
spectrum.nbPoints = currentEntry.ntuples.vardim[xIndex];
}
if (currentEntry.ntuples.factor) {
if (currentEntry.ntuples.factor.length > xIndex) {
spectrum.xFactor = currentEntry.ntuples.factor[xIndex];
}
if (currentEntry.ntuples.factor.length > yIndex) {
spectrum.yFactor = currentEntry.ntuples.factor[yIndex];
}
}
if (currentEntry.ntuples.units) {
if (currentEntry.ntuples.units.length > xIndex) {
if (currentEntry.ntuples.varname && currentEntry.ntuples.varname[xIndex]) {
spectrum.xUnits = `${currentEntry.ntuples.varname[xIndex]} [${currentEntry.ntuples.units[xIndex]}]`;
} else {
spectrum.xUnits = currentEntry.ntuples.units[xIndex];
}
}
if (currentEntry.ntuples.units.length > yIndex) {
if (currentEntry.ntuples.varname && currentEntry.ntuples.varname[yIndex]) {
spectrum.yUnits = `${currentEntry.ntuples.varname[yIndex]} [${currentEntry.ntuples.units[yIndex]}]`;
} else {
spectrum.yUnits = currentEntry.ntuples.units[yIndex];
}
}
}
}
function prepareSpectrum(spectrum) {
if (!spectrum.xFactor) spectrum.xFactor = 1;
if (!spectrum.yFactor) spectrum.yFactor = 1;
}
const ntuplesSeparatorRegExp = /[ \t]*,[ \t]*/;
const numberRegExp = /^[-+]?[0-9]*\.?[0-9]+(e[-+]?[0-9]+)?$/;
class Spectrum {}
const defaultOptions$1 = {
keepRecordsRegExp: /^$/,
canonicDataLabels: true,
canonicMetadataLabels: false,
dynamicTyping: true,
withoutXY: false,
chromatogram: false,
keepSpectra: false,
noContour: false,
nbContourLevels: 7,
noiseMultiplier: 5,
profiling: false
};
/**
*
* @param {string|ArrayBuffer} jcamp
* @param {object} [options]
* @param {number} [options.keepRecordsRegExp=/^$/] By default we don't keep meta information
* @param {number} [options.canonicDataLabels=true] Canonize the Labels (uppercase without symbol)
* @param {number} [options.canonicMetadataLabels=false] Canonize the metadata Labels (uppercase without symbol)
* @param {number} [options.dynamicTyping=false] Convert numbers to Number
* @param {number} [options.withoutXY=false] Remove the XY data
* @param {number} [options.chromatogram=false] Special post-processing for GC / HPLC / MS
* @param {number} [options.keepSpectra=false] Force to keep the spectra in case of 2D
* @param {number} [options.noContour=false] Don't calculate countour in case of 2D
* @param {number} [options.nbContourLevels=7] Number of positive / negative contour levels to calculate
* @param {number} [options.noiseMultiplier=5] Define for 2D the level as 5 times the median as default
* @param {number} [options.profiling=false] Add profiling information
*/
function convert(jcamp, options = {}) {
jcamp = ensureString(jcamp);
options = { ...defaultOptions$1,
...options
};
options.wantXY = !options.withoutXY;
options.start = Date.now();
let entriesFlat = [];
let result = {
profiling: options.profiling ? [] : false,
logs: [],
entries: []
};
let tmpResult = {
children: []
};
let currentEntry = tmpResult;
let parentsStack = [];
let spectrum = new Spectrum();
if (typeof jcamp !== 'string') {
throw new TypeError('the JCAMP should be a string');
}
profiling(result, 'Before split to LDRS', options);
let ldrs = jcamp.replace(/[\r\n]+##/g, '\n##').split('\n##');
profiling(result, 'Split to LDRS', options);
if (ldrs[0]) ldrs[0] = ldrs[0].replace(/^[\r\n ]*##/, '');
for (let ldr of ldrs) {
// This is a new LDR
let position = ldr.indexOf('=');
let dataLabel = position > 0 ? ldr.substring(0, position) : ldr;
let dataValue = position > 0 ? ldr.substring(position + 1).trim() : '';
let canonicDataLabel = dataLabel.replace(/[_ -]/g, '').toUpperCase();
if (canonicDataLabel === 'DATATABLE') {
let endLine = dataValue.indexOf('\n');
if (endLine === -1) endLine = dataValue.indexOf('\r');
if (endLine > 0) {
// ##DATA TABLE= (X++(I..I)), XYDATA
// We need to find the variables
let infos = dataValue.substring(0, endLine).split(/[ ,;\t]+/);
prepareNtuplesDatatable(currentEntry, spectrum, infos[0]);
spectrum.datatable = infos[0];
if (infos[1] && infos[1].indexOf('PEAKS') > -1) {
canonicDataLabel = 'PEAKTABLE';
} else if (infos[1] && (infos[1].indexOf('XYDATA') || infos[0].indexOf('++') > 0)) {
canonicDataLabel = 'XYDATA';
spectrum.deltaX = (spectrum.lastX - spectrum.firstX) / (spectrum.nbPoints - 1);
}
}
}
if (canonicDataLabel === 'XYDATA') {
if (options.wantXY) {
prepareSpectrum(spectrum); // well apparently we should still consider it is a PEAK TABLE if there are no '++' after
if (dataValue.match(/.*\+\+.*/)) {
// ex: (X++(Y..Y))
spectrum.deltaX = (spectrum.lastX - spectrum.firstX) / (spectrum.nbPoints - 1);
fastParseXYData(spectrum, dataValue);
} else {
parsePeakTable(spectrum, dataValue, result);
}
currentEntry.spectra.push(spectrum);
spectrum = new Spectrum();
}
continue;
} else if (canonicDataLabel === 'PEAKTABLE') {
if (options.wantXY) {
prepareSpectrum(spectrum);
parsePeakTable(spectrum, dataValue, result);
currentEntry.spectra.push(spectrum);
spectrum = new Spectrum();
}
continue;
}
if (canonicDataLabel === 'PEAKASSIGNMENTS') {
if (options.wantXY) {
if (dataValue.match(/.*(XYA).*/)) {
// ex: (XYA)
parseXYA(spectrum, dataValue);
}
currentEntry.spectra.push(spectrum);
spectrum = new Spectrum();
}
continue;
}
if (canonicDataLabel === 'TITLE') {
let parentEntry = currentEntry;
if (!parentEntry.children) {
parentEntry.children = [];
}
currentEntry = {
spectra: [],
ntuples: {},
info: {},
meta: {},
tmp: {} // tmp information we need to keep for postprocessing
};
parentEntry.children.push(currentEntry);
parentsStack.push(parentEntry);
entriesFlat.push(currentEntry);
currentEntry.title = dataValue;
} else if (canonicDataLabel === 'DATATYPE') {
currentEntry.dataType = dataValue;
if (dataValue.toLowerCase().indexOf('nd') > -1) {
currentEntry.twoD = true;
}
} else if (canonicDataLabel === 'NTUPLES') {
if (dataValue.toLowerCase().indexOf('nd') > -1) {
currentEntry.twoD = true;
}
} else if (canonicDataLabel === 'DATACLASS') {
currentEntry.dataClass = dataValue;
} else if (canonicDataLabel === 'XUNITS') {
spectrum.xUnits = dataValue;
} else if (canonicDataLabel === 'YUNITS') {
spectrum.yUnits = dataValue;
} else if (canonicDataLabel === 'FIRSTX') {
spectrum.firstX = parseFloat(dataValue);
} else if (canonicDataLabel === 'LASTX') {
spectrum.lastX = parseFloat(dataValue);
} else if (canonicDataLabel === 'FIRSTY') {
spectrum.firstY = parseFloat(dataValue);
} else if (canonicDataLabel === 'LASTY') {
spectrum.lastY = parseFloat(dataValue);
} else if (canonicDataLabel === 'NPOINTS') {
spectrum.nbPoints = parseFloat(dataValue);
} else if (canonicDataLabel === 'XFACTOR') {
spectrum.xFactor = parseFloat(dataValue);
} else if (canonicDataLabel === 'YFACTOR') {
spectrum.yFactor = parseFloat(dataValue);
} else if (canonicDataLabel === 'MAXX') {
spectrum.maxX = parseFloat(dataValue);
} else if (canonicDataLabel === 'MINX') {
spectrum.minX = parseFloat(dataValue);
} else if (canonicDataLabel === 'MAXY') {
spectrum.maxY = parseFloat(dataValue);
} else if (canonicDataLabel === 'MINY') {
spectrum.minY = parseFloat(dataValue);
} else if (canonicDataLabel === 'DELTAX') {
spectrum.deltaX = parseFloat(dataValue);
} else if (canonicDataLabel === '.OBSERVEFREQUENCY' || canonicDataLabel === '$SFO1') {
if (!spectrum.observeFrequency) {
spectrum.observeFrequency = parseFloat(dataValue);
}
} else if (canonicDataLabel === '.OBSERVENUCLEUS') {
if (!spectrum.xType) {
currentEntry.xType = dataValue.replace(/[^a-zA-Z0-9]/g, '');
}
} else if (canonicDataLabel === '$OFFSET') {
// OFFSET for Bruker spectra
currentEntry.shiftOffsetNum = 0;
if (!spectrum.shiftOffsetVal) {
spectrum.shiftOffsetVal = parseFloat(dataValue);
}
} else if (canonicDataLabel === '$REFERENCEPOINT') ; else if (canonicDataLabel === 'VARNAME') {
currentEntry.ntuples.varname = dataValue.split(ntuplesSeparatorRegExp);
} else if (canonicDataLabel === 'SYMBOL') {
currentEntry.ntuples.symbol = dataValue.split(ntuplesSeparatorRegExp);
} else if (canonicDataLabel === 'VARTYPE') {
currentEntry.ntuples.vartype = dataValue.split(ntuplesSeparatorRegExp);
} else if (canonicDataLabel === 'VARFORM') {
currentEntry.ntuples.varform = dataValue.split(ntuplesSeparatorRegExp);
} else if (canonicDataLabel === 'VARDIM') {
currentEntry.ntuples.vardim = convertToFloatArray(dataValue.split(ntuplesSeparatorRegExp));
} else if (canonicDataLabel === 'UNITS') {
currentEntry.ntuples.units = dataValue.split(ntuplesSeparatorRegExp);
} else if (canonicDataLabel === 'FACTOR') {
currentEntry.ntuples.factor = convertToFloatArray(dataValue.split(ntuplesSeparatorRegExp));
} else if (canonicDataLabel === 'FIRST') {
currentEntry.ntuples.first = convertToFloatArray(dataValue.split(ntuplesSeparatorRegExp));
} else if (canonicDataLabel === 'LAST') {
currentEntry.ntuples.last = convertToFloatArray(dataValue.split(ntuplesSeparatorRegExp));
} else if (canonicDataLabel === 'MIN') {
currentEntry.ntuples.min = convertToFloatArray(dataValue.split(ntuplesSeparatorRegExp));
} else if (canonicDataLabel === 'MAX') {
currentEntry.ntuples.max = convertToFloatArray(dataValue.split(ntuplesSeparatorRegExp));
} else if (canonicDataLabel === '.NUCLEUS') {
if (currentEntry.ntuples) {
currentEntry.ntuples.nucleus = dataValue.split(ntuplesSeparatorRegExp);
}
} else if (canonicDataLabel === 'PAGE') {
spectrum.page = dataValue.trim();
spectrum.pageValue = parseFloat(dataValue.replace(/^.*=/, ''));
spectrum.pageSymbol = spectrum.page.replace(/[=].*/, '');
} else if (canonicDataLabel === 'RETENTIONTIME') {
spectrum.pageValue = parseFloat(dataValue);
} else if (isMSField(canonicDataLabel)) {
spectrum[convertMSFieldToLabel(canonicDataLabel)] = dataValue;
} else if (canonicDataLabel === 'SAMPLEDESCRIPTION') {
spectrum.sampleDescription = dataValue;
} else if (canonicDataLabel.startsWith('$NUC')) {
if (!currentEntry.tmp[canonicDataLabel] && !dataValue.includes('off')) {
currentEntry.tmp[canonicDataLabel] = dataValue.replace(/[<>]/g, '');
}
} else if (canonicDataLabel === 'END') {
currentEntry = parentsStack.pop();
}
if (currentEntry && currentEntry.info && currentEntry.meta && canonicDataLabel.match(options.keepRecordsRegExp)) {
let value = dataValue.trim();
let target, label;
if (dataLabel.startsWith('$')) {
label = options.canonicMetadataLabels ? canonicDataLabel.substring(1) : dataLabel.substring(1);
target = currentEntry.meta;
} else {
label = options.canonicDataLabels ? canonicDataLabel : dataLabel;
target = currentEntry.info;
}
if (options.dynamicTyping) {
if (value.match(numberRegExp)) {
value = Number.parseFloat(value);
}
}
if (target[label]) {
if (!Array.isArray(target[label])) {
target[label] = [target[label]];
}
target[label].push(value);
} else {
target[label] = value;
}
}
}
profiling(result, 'Finished parsing', options);
postProcessing(entriesFlat, result, options);
profiling(result, 'Total time', options);
/*
if (result.children && result.children.length>0) {
result = { ...result, ...result.children[0] };
}
*/
result.entries = tmpResult.children;
result.flatten = entriesFlat;
return result;
}
const common = {};
common.getBasename = function (filename) {
let base = filename.replace(/.*\//, '');
return base.replace(/\.[0-9]+$/, '');
};
common.getExtension = function (filename) {
let extension = common.getBasename(filename);
return extension.replace(/.*\./, '').toLowerCase();
};
common.getFilename = function (typeEntry) {
let keys = Object.keys(typeEntry);
for (let i = 0; i < keys.length; i++) {
if (typeEntry[keys[i]] && typeEntry[keys[i]].filename) {
return typeEntry[keys[i]].filename;
}
}
return undefined;
};
common.basenameFind = function (typeEntries, filename) {
let reference = common.getBasename(filename);
return typeEntries.find(typeEntry => {
return common.getBasename(common.getFilename(typeEntry)) === reference;
});
};
common.getTargetProperty = function (filename) {
switch (common.getExtension(filename)) {
case 'jdx':
case 'dx':
case 'jcamp':
return 'jcamp';
case 'png':
case 'jpg':
case 'jpeg':
case 'tif':
case 'tiff':
case 'svg':
return 'image';
case 'mp4':
case 'm4a':
case 'avi':
case 'wav':
return 'video';
case 'cif':
return 'cif';
case 'pdb':
return 'pdb';
case 'xml':
case 'mzml':
case 'mzxml':
case 'mzdata':
return 'xml';
case 'cdf':
case 'nc':
case 'netcdf':
return 'cdf';
case 'pdf':
return 'pdf';
case 'txt':
case 'text':
case 'csv':
case 'tsv':
return 'text';
case 'gbk':
case 'gb':
return 'genbank';
default:
return 'file';
}
};
common.getContent = function (content, target) {
switch (target) {
case 'text':
case 'xml':
case 'pdb':
case 'jcamp':
case 'cif':
case 'genbank':
return common.getTextContent(content);
default:
return common.getBufferContent(content);
}
};
common.getTextContent = function getTextContent(content) {
switch (content.encoding) {
case 'base64':
return browserAtob(content.content);
default:
return content.content;
}
};
common.getBufferContent = function getBufferContent(content) {
switch (content.encoding) {
case 'base64':
return toByteArray_1(content.content);
default:
return content.content;
}
};
common.getMetaFromJcamp = (filename, content) => {
const extension = common.getExtension(filename);
let metaData = {};
if (extension === 'jdx' || extension === 'dx' || extension === 'jcamp') {
let textContent = common.getTextContent(content);
let parsed = convert(textContent, {
withoutXY: true,
keepRecordsRegExp: /cheminfo/i
}).flatten[0];
if (parsed && parsed.meta && parsed.meta.cheminfo) {
try {
let cheminfo = JSON.parse(parsed.meta.cheminfo);
if (cheminfo.meta) {
return cheminfo.meta;
}
} catch (e) {
// eslint-disable-next-line no-console
console.trace(e);
}
}
}
return metaData;
};
function process(filename, content) {
const extension = common.getExtension(filename);
let metaData = {};
if (extension === 'cdf' || extension === 'netcdf') {
let bufferContent = common.getBufferContent(content);
let parsed = src$1(bufferContent, {
meta: true
});
if (parsed.series.length === 1) {
metaData.detector = parsed.series[0].name;
}
}
return metaData;
}
var samplechromatogram = {
jpath: ['spectra', 'chromatogram'],
find: common.basenameFind,
getProperty: common.getTargetProperty,
process
};
var samplecyclicVoltammetry = {
jpath: ['spectra', 'cyclicVoltammetry'],
find: common.basenameFind,
getProperty: common.getTargetProperty,
process: common.getMetaFromJcamp
};
var sampledifferentialCentrifugalSedimentation = {
jpath: ['spectra', 'differentialCentrifugalSedimentation'],
find: common.basenameFind,
getProperty: common.getTargetProperty,
process: common.getMetaFromJcamp
};
var sampledifferentialScanningCalorimetry = {
jpath: ['spectra', 'differentialScanningCalorimetry'],
find: common.basenameFind,
getProperty: common.getTargetProperty,
process: common.getMetaFromJcamp
};
var sampleelementAnalysis = {
jpath: ['spectra', 'elementalAnalysis'],
find: common.basenameFind,
getProperty: common.getTargetProperty,
process: common.getMetaFromJcamp
};
var months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'];
/* eslint no-div-regex: 0*/
function genbankToJson(sequence) {
if (typeof sequence !== 'string') {
throw new TypeError('input must be a string');
}
let resultsArray = [];
let result;
let currentFeatureNote; // Genbank specification: https://www.ncbi.nlm.nih.gov/Sitemap/samplerecord.html
let genbankAnnotationKey = {
// Contains in order: locus name, sequence length, molecule type (e.g. DNA), genbank division (see 1-18 below), modification date
// locus definition has changed with time, use accession number for a unique identifier
LOCUS_TAG: 'LOCUS',
DEFINITION_TAG: 'DEFINITION',
// Accession tag
// Example: Z78533
ACCESSION_TAG: 'ACCESSION',
// The version tag contains 2 informations
// The accession number with a revision
// The GI (GenInfo Identifier), a ncbi sequential number
// Example: Z78533.1 GI:2765658
// Unicity garanteed with respect to sequence. If 1 nucleotide changes, the version is different.
VERSION_TAG: 'VERSION',
KEYWORDS_TAG: 'KEYWORDS',
// SEGMENT_TAG:"SEGMENT"
// Source is free text
SOURCE_TAG: 'SOURCE',
ORGANISM_TAG: 'ORGANISM',
REFERENCE_TAG: 'REFERENCE',
AUTHORS_TAG: 'AUTHORS',
CONSORTIUM_TAG: 'CONSRTM',
TITLE_TAG: 'TITLE',
// Can be multiple journal tags
JOURNAL_TAG: 'JOURNAL',
PUBMED_TAG: 'PUBMED',
REMARK_TAG: 'REMARK',
FEATURES_TAG: 'FEATURES',
BASE_COUNT_TAG: 'BASE COUNT',
// CONTIG_TAG: "CONTIG"
ORIGIN_TAG: 'ORIGIN',
END_SEQUENCE_TAG: '//'
}; // Genbank divisions
// 1. PRI - primate sequences
// 2. ROD - rodent sequences
// 3. MAM - other mammalian sequences
// 4. VRT - other vertebrate sequences
// 5. INV - invertebrate sequences
// 6. PLN - plant, fungal, and algal sequences
// 7. BCT - bacterial sequences
// 8. VRL - viral sequences
// 9. PHG - bacteriophage sequences
// 10. SYN - synthetic sequences
// 11. UNA - unannotated sequences
// 12. EST - EST sequences (expressed sequence tags)
// 13. PAT - patent sequences
// 14. STS - STS sequences (sequence tagged sites)
// 15. GSS - GSS sequences (genome survey sequences)
// 16. HTG - HTG sequences (high-throughput genomic sequences)
// 17. HTC - unfinished high-throughput cDNA sequencing
// 18. ENV - environmental sampling sequences
let lines = sequence.split(/\r?\n/);
let fieldName;
let subFieldType;
let featureLocationIndentation;
let lastLineWasFeaturesTag;
let lastLineWasLocation;
let hasFoundLocus = false;
for (let line of lines) {
if (line === null) break;
let lineFieldName = getLineFieldName(line);
let val = getLineVal(line);
let isSubKey = isSubKeyword(line);
let isKey = isKeyword(line);
if (lineFieldName === genbankAnnotationKey.END_SEQUENCE_TAG || isKey) {
fieldName = lineFieldName;
subFieldType = null;
} else if (isSubKey) {
subFieldType = lineFieldName;
} // IGNORE LINES: DO NOT EVEN PROCESS
if (line.trim() === '' || lineFieldName === ';') {
continue;
}
if (!hasFoundLocus && fieldName !== genbankAnnotationKey.LOCUS_TAG) {
// 'Genbank files must start with a LOCUS tag so this must not be a genbank'
break;
}
switch (fieldName) {
case genbankAnnotationKey.LOCUS_TAG:
hasFoundLocus = true;
parseLocus(line);
break;
case genbankAnnotationKey.FEATURES_TAG:
parseFeatures(line, lineFieldName, val);
break;
case genbankAnnotationKey.ORIGIN_TAG:
parseOrigin(line, lineFieldName);
break;
case genbankAnnotationKey.DEFINITION_TAG:
case genbankAnnotationKey.ACCESSION_TAG:
case genbankAnnotationKey.VERSION_TAG:
case genbankAnnotationKey.KEYWORDS_TAG:
parseMultiLineField(fieldName, line, fieldName.toLowerCase());
break;
case genbankAnnotationKey.SOURCE_TAG:
if (subFieldType === genbankAnnotationKey.ORGANISM_TAG) {
parseMultiLineField(subFieldType, line, 'organism');
} else {
parseMultiLineField(lineFieldName, line, 'source');
}
break;
case genbankAnnotationKey.REFERENCE_TAG:
if (lineFieldName === genbankAnnotationKey.REFERENCE_TAG) {
const ref = result.references || [];
result.references = ref;
ref.push({});
}
parseReference(line, subFieldType);
break;
case genbankAnnotationKey.END_SEQUENCE_TAG:
endSeq();
break;
}
} // catch the case where we've successfully started a sequence and parsed it, but endSeq isn't called correctly
if (resultsArray[resultsArray.length - 1] !== result) {
// current result isn't in resultsArray yet
// so we call endSeq here
endSeq();
}
return resultsArray;
function endSeq() {
// do some post processing clean-up
postProcessCurSeq(); // push the result into the resultsArray
resultsArray.push(result);
}
function getCurrentFeature() {
return result.features[result.features.length - 1];
}
function postProcessCurSeq() {
if (result && result.features) {
for (let i = 0; i < result.features.length; i++) {
result.features[i] = postProcessGenbankFeature(result.features[i]);
}
}
}
function parseOrigin(line, key) {
if (key !== genbankAnnotationKey.ORIGIN_TAG) {
let newLine = line.replace(/[\s]*[0-9]*/g, '');
result.sequence += newLine;
}
}
function parseLocus(line) {
result = {
features: [],
name: 'Untitled sequence',
sequence: '',
references: []
};
line = removeFieldName(genbankAnnotationKey.LOCUS_TAG, line);
const m = line.match(/^([^\s]+)\s+(\d+)\s+bp\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s*([^\s]+)?$/);
let locusName = m[1];
let size = +m[2];
let moleculeType = m[3];
let circular = m[4] === 'circular';
const seq = result;
let dateStr = '';
if (!m[6]) {
dateStr = m[5];
} else {
seq.genbankDivision = m[5];
dateStr = m[6];
}
seq.circular = circular;
seq.moleculeType = moleculeType;
const dateMatch = dateStr.match(/^(\d{2})-(.{3})-(\d{4})$/);
const date = new Date();
date.setFullYear(+dateMatch[3]);
date.setUTCMonth(months.indexOf(dateMatch[2].toUpperCase()));
date.setDate(+dateMatch[1]);
date.setUTCHours(12);
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);
seq.date = date.toISOString();
seq.name = locusName;
seq.size = size;
}
function removeFieldName(fName, line) {
line = line.replace(/^\s*/, '');
if (line.indexOf(fName) === 0) {
line = line.replace(fName, '');
}
return line.trim();
}
function parseReference(line, subType) {
const refs = result.references;
let lastRef = refs[refs.length - 1];
if (!subType) {
parseMultiLineField(genbankAnnotationKey.REFERENCE_TAG, line, 'description', lastRef);
} else {
parseMultiLineField(subType, line, subType.toLowerCase(), lastRef);
}
}
function parseFeatures(line, key, val) {
let strand; // FOR THE MAIN FEATURES LOCATION/QUALIFIER LINE
if (key === genbankAnnotationKey.FEATURES_TAG) {
lastLineWasFeaturesTag = true;
return;
}
if (lastLineWasFeaturesTag) {
// we need to get the indentation of feature locations
featureLocationIndentation = getLengthOfWhiteSpaceBeforeStartOfLetters(line); // set lastLineWasFeaturesTag to false
lastLineWasFeaturesTag = false;
} // FOR LOCATION && QUALIFIER LINES
if (isFeatureLineRunon(line, featureLocationIndentation)) {
// the line is a continuation of the above line
if (lastLineWasLocation) {
// the last line was a location, so the run-on line is expected to be a feature location as well
parseFeatureLocation(line.trim());
lastLineWasLocation = true;
} else {
// the last line was a note
if (currentFeatureNote) {
// append to the currentFeatureNote
currentFeatureNote[currentFeatureNote.length - 1] += line.trim().replace(/"/g, '');
}
lastLineWasLocation = false;
}
} else {
// New Element/Qualifier lines. Not runon lines.
if (isNote(line)) {
// is a new Feature Element (e.g. source, CDS) in the form of "[\s] KEY SEQLOCATION"
// is a FeatureQualifier in the /KEY="BLAH" format; could be multiple per Element
// Check that feature did not get skipped for missing location
if (getCurrentFeature()) {
parseFeatureNote(line);
lastLineWasLocation = false;
}
} else {
// the line is a location, so we make a new feature from it
if (val.match(/complement/g)) {
strand = -1;
} else {
strand = 1;
}
newFeature();
let feat = getCurrentFeature();
feat.type = key;
feat.strand = strand;
parseFeatureLocation(val);
lastLineWasLocation = true;
}
}
}
function newFeature() {
result.features.push({
notes: {}
});
}
function isNote(line) {
let qual = false;
/* if (line.charAt(21) === "/") {//T.H. Hard coded method
qual = true;
}*/
if (line.trim().charAt(0).match(/\//)) {
// searches based on looking for / in beginning of line
qual = true;
} else if (line.match(/^[\s]*\/[\w]+=[\S]+/)) {
// searches based on " /key=BLAH" regex
qual = true;
}
return qual;
}
function parseFeatureLocation(locStr) {
locStr = locStr.trim();
let locArr = [];
locStr.replace(/(\d+)/g, function (string, match) {
locArr.push(match);
});
let feat = getCurrentFeature();
feat.start = +locArr[0];
feat.end = locArr[1] === undefined ? +locArr[0] : +locArr[1];
}
function parseFeatureNote(line) {
let newLine, lineArr;
newLine = line.trim();
newLine = newLine.replace(/^\/|"$/g, '');
lineArr = newLine.split(/="|=/);
let val = lineArr[1];
if (val) {
val = val.replace(/\\/g, ' ');
if (line.match(/="/g)) {
val = val.replace(/".*/g, '');
} else if (val.match(/^\d+$/g)) {
val = +val;
}
}
let key = lineArr[0];
let currentNotes = getCurrentFeature().notes;
if (currentNotes[key]) {
// array already exists, so push value into it
currentNotes[key].push(val);
} else {
// array doesn't exist yet, so create it and populate it with the value
currentNotes[key] = [val];
}
currentFeatureNote = currentNotes[key];
}
function getLineFieldName(line) {
let arr;
line = line.trim();
arr = line.split(/[\s]+/);
return arr[0];
}
function parseMultiLineField(fName, line, resultKey, r) {
r = r || result;
let fieldValue = removeFieldName(fName, line);
r[resultKey] = r[resultKey] ? `${r[resultKey]} ` : '';
r[resultKey] += fieldValue;
}
function getLineVal(line) {
let arr;
if (line.indexOf('=') < 0) {
line = line.replace(/^[\s]*[\S]+[\s]+|[\s]+$/, '');
line = line.trim();
return line;
} else {
arr = line.split(/=/);
return arr[1];
}
}
function isKeyword(line) {
let isKey = false;
if (line.substr(0, 10).match(/^[\S]+/)) {
isKey = true;
}
return isKey;
}
function isSubKeyword(line) {
let isSubKey = false;
if (line.substr(0, 10).match(/^[\s]+[\S]+/)) {
isSubKey = true;
}
return isSubKey;
}
function postProcessGenbankFeature(feat) {
if (feat.notes.label) {
feat.name = feat.notes.label[0];
} else if (feat.notes.gene) {
feat.name = feat.notes.gene[0];
} else if (feat.notes.ApEinfo_label) {
feat.name = feat.notes.ApEinfo_label[0];
} else if (feat.notes.name) {
feat.name = feat.notes.name[0];
} else if (feat.notes.organism) {
feat.name = feat.notes.organism[0];
} else if (feat.notes.locus_tag) {
feat.name = feat.notes.locus_tag[0];
} else if (feat.notes.note) {
feat.name = feat.notes.note[0];
} else {
feat.name = 'Untitled Feature';
}
feat.name = typeof feat.name === 'string' ? feat.name : String(feat.name);
return feat;
}
}
function isFeatureLineRunon(line, featureLocationIndentation) {
let indentationOfLine = getLengthOfWhiteSpaceBeforeStartOfLetters(line);
if (featureLocationIndentation === indentationOfLine) {
// the feature location indentation calculated right after the feature tag
// cannot be the same as the indentation of the line
//
// FEATURES Location/Qualifiers
// rep_origin complement(1074..3302)
// 01234 <-- this is the indentation we're talking about
return false; // the line is NOT a run on
}
let trimmed = line.trim();
if (trimmed.charAt(0).match(/\//)) {
// the first char in the trimmed line cannot be a /
return false; // the line is NOT a run on
} // the line is a run on
return true; // run-on line example:
// FEATURES Location/Qualifiers
// rep_origin complement(1074..3302)
// /label=pSC101**
// /note="REP_ORIGIN REP_ORIGIN pSC101* aka pMPP6, gives plasm
// id number 3 -4 copies per cell, BglII site in pSC101* ori h <--run-on line!
// as been dele ted by quick change agatcT changed to agatcA g <--run-on line!
// iving pSC101* * pSC101* aka pMPP6, gives plasmid number 3-4 <--run-on line!
// copies p er cell, BglII site in pSC101* ori has been delet <--run-on line!
// ed by quic k change agatcT changed to agatcA giving pSC101* <--run-on line!
// * [pBbS0a-RFP]" <--run-on line!
// /gene="SC101** Ori"
// /note="pSC101* aka pMPP6, gives plasmid number 3-4 copies p
// er cell, BglII site in pSC101* ori has been deleted by qui
// c k change agatcT changed to agatcA giving pSC101**"
// /vntifkey="33"
}
function getLengthOfWhiteSpaceBeforeStartOfLetters(string) {
let match = /^\s*/.exec(string);
if (match !== null) {
return match[0].length;
} else {
return 0;
}
}
var src = genbankToJson;
var samplegenbank = {
find(genbank, filename) {
let reference = common.getBasename(filename);
return genbank.find(genbank => {
return common.getBasename(common.getFilename(genbank)) === reference;
});
},
getProperty(filename) {
return common.getTargetProperty(filename);
},
process(filename, content) {
let textContent = common.getTextContent(content);
let toReturn;
const parsed = src(textContent);
toReturn = {
seq: parsed.map(p => p.parsedSequence)
};
return toReturn;
},
jpath: ['biology', 'nucleic']
};
var samplegeneral = {
jpath: ['general'],
getEmpty() {
return {
description: '',
title: '',
name: [],
mf: '',
molfile: '',
mw: 0,
keyword: [],
meta: {},
sequence: '',
kind: ''
};
}
};
var samplehgPorosimetry = {
jpath: ['spectra', 'hgPorosimetry'],
find: common.basenameFind,
getProperty: common.getTargetProperty,
process: common.getMetaFromJcamp
};
var sampleimage = {
jpath: ['image'],
find: common.basenameFind,
getProperty: common.getTargetProperty
};
var sampleir = {
jpath: ['spectra', 'ir'],
find: common.basenameFind,
getProperty: common.getTargetProperty,
process: common.getMetaFromJcamp
};
var sampleisotherm = {
jpath: ['spectra', 'isotherm'],
find: common.basenameFind,
getProperty: common.getTargetProperty,
process: common.getMetaFromJcamp
};
var sampleiv = {
jpath: ['spectra', 'iv'],
find: common.basenameFind,
getProperty: common.getTargetProperty,
process: common.getMetaFromJcamp
};
var samplemass = {
jpath: ['spectra', 'mass'],
find: common.basenameFind,
getProperty: common.getTargetProperty,
process: common.getMetaFromJcamp
};
/**
* Returns a list of likely nuclei based on an experiment string
* This is really an hypothesis and should not be used
* @param {string} experiment
* @return {string[]}
*/
function getNucleusFrom2DExperiment(experiment) {
if (typeof experiment !== 'string') {
return [];
}
experiment = experiment.toLowerCase();
if (experiment.includes('jres')) {
return ['1H'];
}
if (experiment.includes('hmbc') || experiment.includes('hsqc')) {
return ['1H', '13C'];
}
return ['1H', '1H'];
}
/**
* Returns an experiment string based on a pulse sequence
* @param {string} pulse
* @return {string}
*/
function getSpectrumType(meta = {}, info = {}) {
if (meta === null) meta = {};
if (typeof meta === 'string') {
meta = {
pulse: meta
};
}
let spectyp;
if (Array.isArray(info.$SPECTYP)) {
spectyp = (info.$SPECTYP[0] || '').replace(/^<(.*)>$/, '$1').toLowerCase();
} else {
spectyp = (info.$SPECTYP || '').replace(/^<(.*)>$/, '$1').toLowerCase();
}
if (spectyp) return spectyp;
let pulse = meta.pulse;
if (typeof pulse !== 'string') {
return '';
}
pulse = pulse.toLowerCase();
if (pulse.includes('zg')) {
return '1d';
}
if (pulse.includes('hsqct') || pulse.includes('invi') && (pulse.includes('ml') || pulse.includes('di'))) {
return 'hsqctocsy';
}
if (pulse.includes('hsqc') || pulse.includes('invi')) {
return 'hsqc';
}
if (pulse.includes('hmbc') || pulse.includes('inv4') && pulse.includes('lp')) {
return 'hmbc';
}
if (pulse.includes('cosy')) {
return 'cosy';
}
if (pulse.includes('jres')) {
return 'jres';
}
if (pulse.includes('tocsy') || pulse.includes('mlev') || pulse.includes('dipsi')) {
return 'tocsy';
}
if (pulse.includes('noesy')) {
return 'noesy';
}
if (pulse.includes('roesy')) {
return 'roesy';
}
if (pulse.includes('dept')) {
return 'dept';
}
if (pulse.includes('jmod') || pulse.includes('apt')) {
return 'aptjmod';
}
return '';
}
function getMetaData(info, meta) {
const metadata = {
dimension: 1,
nucleus: [],
isFid: false,
isFt: false,
isComplex: false
};
maybeAdd(metadata, 'title', info.TITLE);
maybeAdd(metadata, 'solvent', info['.SOLVENTNAME']);
maybeAdd(metadata, 'pulse', info['.PULSESEQUENCE'] || info['.PULPROG'] || meta.PULPROG);
maybeAdd(metadata, 'experiment', getSpectrumType(metadata, info));
maybeAdd(metadata, 'temperature', parseFloat(meta.TE || info['.TE']));
maybeAdd(metadata, 'frequency', parseFloat(info['.OBSERVEFREQUENCY']));
maybeAdd(metadata, 'type', info.DATATYPE);
maybeAdd(metadata, 'probe', meta.PROBHD);
if (meta.FNTYPE !== undefined) {
maybeAdd(metadata, 'acquisitionMode', parseInt(meta.FNTYPE, 10));
}
maybeAdd(metadata, 'expno', parseInt(meta.EXPNO, 10));
if (metadata.type) {
if (metadata.type.toUpperCase().indexOf('FID') >= 0) {
metadata.isFid = true;
} else if (metadata.type.toUpperCase().indexOf('SPECTRUM') >= 0) {
metadata.isFt = true;
}
}
if (info['.NUCLEUS']) {
metadata.nucleus = info['.NUCLEUS'].split(',').map(nuc => nuc.trim());
} else if (info['.OBSERVENUCLEUS']) {
metadata.nucleus = [info['.OBSERVENUCLEUS'].replace(/[^A-Za-z0-9]/g, '')];
} else {
metadata.nucleus = getNucleusFrom2DExperiment(metadata.experiment);
}
metadata.dimension = metadata.nucleus.length;
if (info.SYMBOL) {
let symbols = info.SYMBOL.split(/[, ]+/);
if (symbols.includes('R') && symbols.includes('I')) {
metadata.isComplex = true;
}
}
if (meta.DATE) {
metadata.date = new Date(meta.DATE * 1000).toISOString();
}
return metadata;
}
function maybeAdd(obj, name, value) {
if (value !== undefined) {
if (typeof value === 'string') {
if (value.startsWith('<') && value.endsWith('>')) {
value = value.substring(1, value.length - 2);
}
obj[name] = value.trim();
} else {
obj[name] = value;
}
}
}
/**
* Object containing parsed metadata
* @name NMRMetadata
* @typedef {object} NMRMetadata
* @property {number} dimension
* @property {number[]} nucleus
* @property {string} title
* @property {string} solvent
* @property {string} pulse
* @property {string} experiment
* @property {number} temperature - Temperature in Kelvin
* @property {number} frequency
* @property {string} probe
* @property {string} acquisitionMode
* @property {number} expno - Experience number
* @property {string} date - Date in ISO string format
* @property {object} ranges
*/
const defaultOptions = {
computeRanges: false
};
const defaultRangesOptions = {
integrationSum: 100,
clean: 0.4,
compile: true,
integralType: 'sum'
};
const defaultPeaksOptions = {
thresholdFactor: 0.85
};
/**
* Returns a metadata object from JCAMP
* @param {string} jcampData
* @param {object} [options={}]
* @param {boolean} [options.computeRanges=false]
* @param {number} [options.ranges] - options for ranges computation
* @return {NMRMetadata} metadata
*/
function fromJcamp(jcampData, options = {}) {
options = { ...defaultOptions,
...options
};
const parsedJcamp = convert(jcampData, {
keepRecordsRegExp: /.*/,
canonicMetadataLabels: true,
withoutXY: false
}).flatten[0];
let metadata = getMetaData(parsedJcamp.info, parsedJcamp.meta);
if (options.computeRanges && metadata.isFt && metadata.dimension === 1 && metadata.nucleus[0] === '1H') {
let {
ranges = {},
impurities = {},
peakPicking = {}
} = options;
const rangesOptions = { ...defaultRangesOptions,
...ranges
};
const peaksOptions = { ...defaultPeaksOptions,
...peakPicking
};
if (metadata.solvent) {
impurities.solvent = metadata.solvent;
}
metadata.range = xyAutoRangesPicking(parsedJcamp.spectra[0].data, {
impurities,
ranges: rangesOptions,
peakPicking: peaksOptions
});
}
return metadata;
}
const isFid = /[^a-z]fid[^a-z]/i;
const replaceFid = /[^a-z]fid[^a-z]?/i;
var samplenmr = {
find: (nmr, filename) => {
let reference = getReference(filename);
return nmr.find(nmr => {
return getReference(common.getFilename(nmr)) === reference;
});
},
getProperty: filename => {
const extension = common.getExtension(filename);
if (extension === 'jdx' || extension === 'dx' || extension === 'jcamp') {
if (isFid.test(filename)) {
return 'jcampFID';
}
}
return common.getTargetProperty(filename);
},
process: (filename, content) => {
const extension = common.getExtension(filename);
let metaData = {};
if (extension === 'jdx' || extension === 'dx' || extension === 'jcamp') {
let textContent = common.getTextContent(content);
metaData = fromJcamp(textContent);
}
return metaData;
},
jpath: ['spectra', 'nmr']
};
const reg2 = /(.*)\.(.*)/;
function getReference(filename) {
if (typeof filename === 'undefined') return undefined;
let reference = common.getBasename(filename);
reference = reference.replace(reg2, '$1');
if (isFid.test(filename)) {
reference = reference.replace(replaceFid, '');
}
return reference;
}
var sampleoan = {
jpath: ['spectra', 'oan'],
find: common.basenameFind,
getProperty: common.getTargetProperty,
process: common.getMetaFromJcamp
};
var samplepelletHardness = {
jpath: ['spectra', 'pelletHardness'],
find: common.basenameFind,
getProperty: common.getTargetProperty,
process: common.getMetaFromJcamp
};
var samplephysical = {
jpath: ['physical'],
getEmpty() {
return {};
}
};
var sampleraman = {
jpath: ['spectra', 'raman'],
find: common.basenameFind,
getProperty: common.getTargetProperty,
process: common.getMetaFromJcamp
};
var samplethermogravimetricAnalysis = {
jpath: ['spectra', 'thermogravimetricAnalysis'],
find: common.basenameFind,
getProperty: common.getTargetProperty,
process: common.getMetaFromJcamp
};
var sampleuv = {
jpath: ['spectra', 'uv'],
find: common.basenameFind,
getProperty: common.getTargetProperty,
process: common.getMetaFromJcamp
};
var samplevideo = {
jpath: ['video'],
find: common.basenameFind,
getProperty: common.getTargetProperty
};
var samplexps = {
jpath: ['spectra', 'xps'],
find: common.basenameFind,
getProperty: common.getTargetProperty,
process: common.getMetaFromJcamp
};
var samplexray = {
jpath: ['spectra', 'xray'],
find: common.basenameFind,
getProperty: common.getTargetProperty,
process: common.getMetaFromJcamp
};
var samplexrd = {
jpath: ['spectra', 'xrd'],
find: common.basenameFind,
getProperty: common.getTargetProperty,
process: common.getMetaFromJcamp
};
var samplexrf = {
jpath: ['spectra', 'xrf'],
find: common.basenameFind,
getProperty: common.getTargetProperty,
process: common.getMetaFromJcamp
};
const lib = {};
lib['reaction'] = {};
lib['reaction']['general'] = reactiongeneral;
lib['sample'] = {};
lib['sample']['chromatogram'] = samplechromatogram;
lib['sample']['cyclicVoltammetry'] = samplecyclicVoltammetry;
lib['sample']['differentialCentrifugalSedimentation'] = sampledifferentialCentrifugalSedimentation;
lib['sample']['differentialScanningCalorimetry'] = sampledifferentialScanningCalorimetry;
lib['sample']['elementAnalysis'] = sampleelementAnalysis;
lib['sample']['genbank'] = samplegenbank;
lib['sample']['general'] = samplegeneral;
lib['sample']['hgPorosimetry'] = samplehgPorosimetry;
lib['sample']['image'] = sampleimage;
lib['sample']['ir'] = sampleir;
lib['sample']['isotherm'] = sampleisotherm;
lib['sample']['iv'] = sampleiv;
lib['sample']['mass'] = samplemass;
lib['sample']['nmr'] = samplenmr;
lib['sample']['oan'] = sampleoan;
lib['sample']['pelletHardness'] = samplepelletHardness;
lib['sample']['physical'] = samplephysical;
lib['sample']['raman'] = sampleraman;
lib['sample']['thermogravimetricAnalysis'] = samplethermogravimetricAnalysis;
lib['sample']['uv'] = sampleuv;
lib['sample']['video'] = samplevideo;
lib['sample']['xps'] = samplexps;
lib['sample']['xray'] = samplexray;
lib['sample']['xrd'] = samplexrd;
lib['sample']['xrf'] = samplexrf;
function getType(type, kind, custom) {
if (kind) {
if (lib[kind][type]) {
return Object.assign({}, defaultType, lib[kind][type], custom);
}
} else {
for (kind in lib) {
if (lib[kind][type]) {
return Object.assign({}, defaultType, lib[kind].default, lib[kind][type], custom);
}
}
}
return Object.assign({}, defaultType);
}
function getAllTypes(kind, custom) {
let all = [];
for (let type in lib[kind]) {
if (type !== 'default') {
all.push(getType(type, kind, custom));
}
}
return all;
}
/*
Modified from https://github.com/justmoon/node-extend
Copyright (c) 2014 Stefan Thomas
*/
/* eslint prefer-rest-params: 0 */
let hasOwn = Object.prototype.hasOwnProperty;
let toStr = Object.prototype.toString;
let isArray = function isArray(arr) {
if (typeof Array.isArray === 'function') {
return Array.isArray(arr);
}
return toStr.call(arr) === '[object Array]';
};
let isPlainObject = function isPlainObject(obj) {
if (!obj || toStr.call(obj) !== '[object Object]') {
return false;
}
let hasOwnConstructor = hasOwn.call(obj, 'constructor');
let hasIsPrototypeOf = obj.constructor && obj.constructor.prototype && hasOwn.call(obj.constructor.prototype, 'isPrototypeOf'); // Not own constructor property must be Object
if (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) {
return false;
} // Own properties are enumerated firstly, so to speed up,
// if last one is own, then all properties are own.
let key;
for (key in obj) {
/**/
}
return typeof key === 'undefined' || hasOwn.call(obj, key);
};
function defaults() {
let options, name, src, copy, copyIsArray, clone;
let target = arguments[0];
let i = 1;
let length = arguments.length;
let deep = false; // Handle a deep copy situation
if (typeof target === 'boolean') {
deep = target;
target = arguments[1] || {}; // skip the boolean and the target
i = 2;
} else if (typeof target !== 'object' && typeof target !== 'function' || target == null) {
target = {};
}
for (; i < length; ++i) {
options = arguments[i]; // Only deal with non-null/undefined values
if (options != null) {
// Extend the base object
for (name in options) {
src = target[name];
copy = options[name]; // Prevent never-ending loop
if (target !== copy) {
// Recurse if we're merging plain objects or arrays
if (deep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
if (copyIsArray) {
copyIsArray = false;
clone = src && isArray(src) ? src : [];
} else {
clone = src && isPlainObject(src) ? src : {};
} // Never move original objects, clone them
if (typeof target[name] === 'undefined') {
target[name] = defaults(deep, clone, copy);
} else {
defaults(deep, clone, copy);
} // Don't bring in undefined values
} else if (typeof copy !== 'undefined') {
if (typeof target[name] === 'undefined') {
target[name] = copy;
}
}
}
}
}
} // Return the modified object
return target;
}
const elnPlugin = {
util: common,
/**
*
* @param {*} type
* @param {*} doc
* @param {*} content
* @param {*} customMetadata
* @param {object} [options={}]
* @param {boolean} [options.keepContent=false]
*/
process: function (type, doc, content, customMetadata, options = {}) {
let filename = content.filename;
const typeProcessor = getType(type);
const arr = createFromJpath(doc, typeProcessor);
const entry = typeProcessor.find(arr, filename);
const property = typeProcessor.getProperty(filename, content);
if (property === undefined) {
throw new Error(`Could not get property of ${filename} (type ${type}`);
}
const metadata = typeProcessor.process(filename, content);
metadata[property] = {
filename: elnPlugin.getFilename(type, content.filename)
};
if (options.keepContent) {
metadata[property].data = common.getContent(content, property);
}
if (entry) {
Object.assign(entry, metadata, customMetadata);
} else {
Object.assign(metadata, customMetadata);
arr.push(metadata);
}
return doc;
},
getType: function (type, doc, kind) {
const typeProcessor = getType(type, kind);
return getFromJpath(doc, typeProcessor);
},
getFilename(type, filename) {
let match = /[^/]*$/.exec(filename);
if (match) filename = match[0];
const typeProcessor = getType(type);
const jpath = typeProcessor.jpath;
if (!jpath) throw new Error('No such type or no jpath');
return jpath.concat(filename).join('/');
},
getEmpty(kind, content) {
const typeProcessors = getAllTypes(kind);
if (!content) content = {};
for (let i = 0; i < typeProcessors.length; i++) {
createFromJpath(content, typeProcessors[i]);
}
return content;
},
defaults(kind, content) {
let empty = elnPlugin.getEmpty(kind);
defaults(true, content, empty);
return content;
}
};
function createFromJpath(doc, typeProcessor) {
const jpath = typeProcessor.jpath;
if (!jpath) {
throw new Error('createFromJpath: undefined jpath argument');
}
for (let i = 0; i < jpath.length; i++) {
if (doc[jpath[i]] === undefined) {
if (i !== jpath.length - 1) {
doc[jpath[i]] = {};
} else {
doc[jpath[i]] = typeProcessor.getEmpty();
}
}
doc = doc[jpath[i]];
}
if (jpath.length === 0) {
doc = Object.assign(doc, typeProcessor.getEmpty());
}
return doc;
}
function getFromJpath(doc, typeProcessor) {
if (!doc) return undefined;
const jpath = typeProcessor.jpath;
if (!jpath) throw new Error('getFromJpath: undefined jpath argument');
for (let i = 0; i < jpath.length; i++) {
if (doc[jpath[i]] === undefined) {
return undefined;
}
doc = doc[jpath[i]];
}
return doc;
}
return elnPlugin;
})));
//# sourceMappingURL=eln-plugin.js.map