/**
 * mass-spectrum - Helper to visualize and compare mass spectra in SciPeaks
 * @version v1.3.0
 * @link https://github.com/cheminfo/mass-spectrum#readme
 * @license MIT
 */
(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
    typeof define === 'function' && define.amd ? define(['exports'], factory) :
    (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.MassSpectrum = {}));
})(this, (function (exports) { 'use strict';

    /**
     * Calculates reimAbsolute value of a complex spectrum.
     * @param data - complex spectrum
     * @returns - reimAbsolute value
     */
    function reimAbsolute(data) {
      const length = data.re.length;
      const re = data.re;
      const im = data.im;
      const newArray = new Float64Array(length);
      for (let i = 0; i < length; i++) {
        newArray[i] = Math.hypot(re[i], im[i]);
      }
      return newArray;
    }

    // eslint-disable-next-line @typescript-eslint/unbound-method
    const toString = Object.prototype.toString;
    /**
     * Checks if an object is an instance of an Array (array or typed array, except those that contain bigint values).
     *
     * @param value - Object to check.
     * @returns True if the object is an array or a typed array.
     */
    function isAnyArray$1(value) {
      const tag = toString.call(value);
      return tag.endsWith('Array]') && !tag.includes('Big');
    }

    var libEsm$3 = /*#__PURE__*/Object.freeze({
        __proto__: null,
        isAnyArray: isAnyArray$1
    });

    /**
     * This function
     * @param output - undefined or a new array
     * @param length - length of the output array
     * @returns
     */
    function getOutputArray(output, length) {
      if (output !== undefined) {
        if (!isAnyArray$1(output)) {
          throw new TypeError('output option must be an array if specified');
        }
        if (output.length !== length) {
          throw new TypeError('the output array does not have the correct length');
        }
        return output;
      } else {
        return new Float64Array(length);
      }
    }

    /**
     * This function returns an array with absolute values.
     * @param array - array of data
     * @returns - array with absolute values
     */
    function xAbsolute(array) {
      const tmpArray = array.slice();
      for (let i = 0; i < tmpArray.length; i++) {
        if (tmpArray[i] < 0) tmpArray[i] *= -1;
      }
      return tmpArray;
    }

    /**
     * Calculates the median of an array.
     * @param input - Array containing values
     * @param options
     * @returns - median
     */
    function xMedian(input, options = {}) {
      if (!isAnyArray$1(input)) {
        throw new TypeError('input must be an array');
      }
      if (input.length === 0) {
        throw new TypeError('input must not be empty');
      }
      const {
        exact = false
      } = options || {};
      const array = input.slice();
      const middleIndex = calcMiddle(0, array.length - 1);
      const median = quickSelect(array, middleIndex);
      if (array.length % 2 === 1 || !exact) {
        return median;
      }
      const medianNext = quickSelect(array, middleIndex + 1);
      return (median + medianNext) / 2;
    }
    function quickSelect(array, middleIndex) {
      let low = 0;
      let high = array.length - 1;
      let middle = 0;
      let currentLow = 0;
      let currentHigh = 0;
      while (true) {
        if (high <= low) {
          return array[middleIndex];
        }
        if (high === low + 1) {
          if (array[low] > array[high]) {
            swap$1(array, low, high);
          }
          return array[middleIndex];
        }
        // Find median of low, middle and high items; swap into position low
        middle = calcMiddle(low, high);
        if (array[middle] > array[high]) swap$1(array, middle, high);
        if (array[low] > array[high]) swap$1(array, low, high);
        if (array[middle] > array[low]) swap$1(array, middle, low);
        // Swap low item (now in position middle) into position (low+1)
        swap$1(array, middle, low + 1);
        // Nibble from each end towards middle, swapping items when stuck
        currentLow = low + 1;
        currentHigh = high;
        while (true) {
          do currentLow++; while (array[low] > array[currentLow]);
          do currentHigh--; while (array[currentHigh] > array[low]);
          if (currentHigh < currentLow) {
            break;
          }
          swap$1(array, currentLow, currentHigh);
        }
        // Swap middle item (in position low) back into correct position
        swap$1(array, low, currentHigh);
        // Re-set active partition
        if (currentHigh <= middleIndex) {
          low = currentLow;
        }
        if (currentHigh >= middleIndex) {
          high = currentHigh - 1;
        }
      }
    }
    function swap$1(array, i, j) {
      const temp = array[j];
      array[j] = array[i];
      array[i] = temp;
    }
    function calcMiddle(i, j) {
      return Math.floor((i + j) / 2);
    }

    /**
     * This function calculates the median after taking the xAbsolute values of the points.
     * @param array - the array for which we want to calculate the absolute value
     * @returns - median
     */
    function xAbsoluteMedian(array) {
      return xMedian(xAbsolute(array));
    }

    /**
     * Checks if the input is a non-empty array of numbers.
     * Only checks the first element.
     * @param input - Array to check.
     * @param options - Additional checks.
     */
    function xCheck(input, options = {}) {
      const {
        minLength
      } = options;
      if (!isAnyArray$1(input)) {
        throw new TypeError('input must be an array');
      }
      if (input.length === 0) {
        throw new TypeError('input must not be empty');
      }
      if (typeof input[0] !== 'number') {
        throw new TypeError('input must contain numbers');
      }
      if (minLength && input.length < minLength) {
        throw new Error(`input must have a length of at least ${minLength}`);
      }
    }

    /**
     * Returns the closest index of a `target`
     * @param array - array of numbers
     * @param target - target
     * @param options
     * @returns - closest index
     */
    function xFindClosestIndex(array, target, options = {}) {
      const {
        sorted = true
      } = options;
      if (sorted) {
        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;
        }
      } else {
        let index = 0;
        let diff = Number.POSITIVE_INFINITY;
        for (let i = 0; i < array.length; i++) {
          const currentDiff = Math.abs(array[i] - target);
          if (currentDiff < diff) {
            diff = currentDiff;
            index = i;
          }
        }
        return index;
      }
    }

    /**
     * Returns an object with {fromIndex, toIndex} for a specific from / to
     * @param x - array of numbers
     * @param options - Options
     */
    function xGetFromToIndex(x, options = {}) {
      let {
        fromIndex,
        toIndex
      } = options;
      const {
        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 < 0) fromIndex = 0;
      if (toIndex < 0) toIndex = 0;
      if (fromIndex >= x.length) fromIndex = x.length - 1;
      if (toIndex >= x.length) toIndex = x.length - 1;
      if (fromIndex > toIndex) [fromIndex, toIndex] = [toIndex, fromIndex];
      return {
        fromIndex,
        toIndex
      };
    }

    function xAbsoluteSum(array, options = {}) {
      xCheck(array);
      const {
        fromIndex,
        toIndex
      } = xGetFromToIndex(array, options);
      let sum = 0;
      for (let i = fromIndex; i <= toIndex; i++) {
        sum += Math.abs(array[i]);
      }
      return sum;
    }

    /**
     * This function xAdd the first array by the second array or a constant value to each element of the first array
     * @param array1 - the first array
     * @param array2 - the second array or number
     * @returns the result of the addition
     */
    function xAdd(array1, array2) {
      let isConstant = false;
      let constant = 0;
      if (isAnyArray$1(array2)) {
        if (array1.length !== array2.length) {
          throw new Error('size of array1 and array2 must be identical');
        }
      } else {
        isConstant = true;
        constant = array2;
      }
      const array3 = new Float64Array(array1.length);
      if (isConstant) {
        for (let i = 0; i < array1.length; i++) {
          array3[i] = array1[i] + constant;
        }
      } else {
        for (let i = 0; i < array1.length; i++) {
          array3[i] = array1[i] + array2[i];
        }
      }
      return array3;
    }

    /**
     * Will apply a function on each element of the array described as a string
     * By default we will use as variable 'x'
     * In front of sequence of lowercase we will add 'Math.'. This allows to write
     * `sin(x) + cos(x)` and it will be replace internally by (x) => (Math.sin(x) + Math.cos(x))
     * @param array
     * @param options
     * @returns
     */
    function xApplyFunctionStr(array, options = {}) {
      const {
        variableLabel = 'x',
        fctString = variableLabel
      } = options;
      const fct = new Function(variableLabel, `return Number(${fctString.replaceAll(/(?<before>^|\W)(?<after>[\da-z]{2,}\()/g, '$<before>Math.$<after>').replaceAll('Math.Math', 'Math')})`);
      const toReturn = Float64Array.from(array);
      for (let i = 0; i < array.length; i++) {
        toReturn[i] = fct(array[i]);
        if (Number.isNaN(toReturn[i])) {
          throw new Error(`The callback ${fctString} does not return a number: ${array[i]}`);
        }
      }
      return toReturn;
    }

    /**
     * This function xMultiply the first array by the second array or a constant value to each element of the first array
     * @param array1 - first array
     * @param array2 - second array
     * @param options - options
     */
    function xMultiply(array1, array2, options = {}) {
      let isConstant = false;
      let constant = 0;
      if (isAnyArray$1(array2)) {
        if (array1.length !== array2.length) {
          throw new Error('size of array1 and array2 must be identical');
        }
      } else {
        isConstant = true;
        constant = array2;
      }
      const array3 = getOutputArray(options.output, array1.length);
      if (isConstant) {
        for (let i = 0; i < array1.length; i++) {
          array3[i] = array1[i] * constant;
        }
      } else {
        for (let i = 0; i < array1.length; i++) {
          array3[i] = array1[i] * array2[i];
        }
      }
      return array3;
    }

    /**
     * Dot product between two arrays.
     * @param A - First array.
     * @param B - Second array.
     */
    function xDotProduct(A, B) {
      const g = xMultiply(A, B);
      let result = 0;
      for (let i = 0; i < A.length; i++) {
        result += g[i];
      }
      return result;
    }

    /**
     * Calculates the cross-correlation between 2 arrays
     * @param A - fixed array
     * @param B - sweeping array
     * @param options - Options
     */
    function xCrossCorrelation(A, B, options = {}) {
      const {
        tau = 1,
        lag = A.length - 1
      } = options;
      const result = new Float64Array(1 + 2 * lag / tau);
      if (A.length === B.length) {
        const n = B.length;
        const g = new Float64Array(2 * n);
        const q = new Float64Array(2 * n);
        for (let i = 0; i < n; i++) {
          q[n + i] = B[i];
        }
        for (let i = n * 2 - (tau - 1); i > 0; i -= tau) {
          let k = 0;
          for (let j = i; j < n * 2; j++) {
            g[k] = q[j];
            k++;
          }
          const w = [];
          for (let l = 0; l < n; l++) {
            w[l] = g[l];
          }
          result[(k - (n - lag)) / tau] = xDotProduct(A, w);
        }
      }
      return result;
    }

    /**
     * Calculates the auto-correlation of an array
     * @param A - the array for which to calculate the auto-correlation
     * @param options - Options
     */
    function xAutoCorrelation(A, options = {}) {
      return xCrossCorrelation(A, A, options);
    }

    /**
     * Calculating the box plot of the array
     * This function will interpolate the values and use the inclusive algorithm
     * @param array - data
     * @param options
     * @returns - q1, median, q3, min, max
     */
    function xBoxPlot(array) {
      xCheck(array);
      // duplicate the array to avoid modifying the original one
      // and sort typed array that is much faster than sorting a normal array
      array = Float64Array.from(array).sort();
      const posQ1 = (array.length - 1) / 4;
      const posQ3 = (array.length - 1) * (3 / 4);
      const medianPos = (array.length - 1) / 2;
      const q1MinProportion = posQ1 % 1;
      const q3MinProportion = posQ3 % 1;
      const medianMinProportion = medianPos % 1;
      return {
        min: array[0],
        q1: q1MinProportion === 0 ? array[posQ1] : array[posQ1 >> 0] * (1 - q1MinProportion) + array[(posQ1 >> 0) + 1] * q1MinProportion,
        median: medianMinProportion === 0 ? array[medianPos] : array[medianPos >> 0] * (1 - medianMinProportion) + array[(medianPos >> 0) + 1] * medianMinProportion,
        q3: q3MinProportion === 0 ? array[posQ3] : array[posQ3 >> 0] * (1 - q3MinProportion) + array[(posQ3 >> 0) + 1] * q3MinProportion,
        max: array.at(-1)
      };
    }

    /**
     * Calculating the box plot of the array with outliers
     * Values are outliers if they are below Q1 - 1.5 * IQR or above Q3 + 1.5 * IQR
     * @param array - data
     * @returns - q1, median, q3, min, max, outliers
     */
    function xBoxPlotWithOutliers(array) {
      const boxPlot = xBoxPlot(array);
      const iqr = boxPlot.q3 - boxPlot.q1;
      const lowerWhisker = boxPlot.q1 - 1.5 * iqr;
      const upperWhisker = boxPlot.q3 + 1.5 * iqr;
      const outliers = [];
      let minWhisker = boxPlot.median;
      let maxWhisker = boxPlot.median;
      for (const value of array) {
        if (value < lowerWhisker || value > upperWhisker) {
          outliers.push(value);
        } else {
          if (value < minWhisker) minWhisker = value;
          if (value > maxWhisker) maxWhisker = value;
        }
      }
      const info = {
        ...boxPlot,
        lowerWhisker,
        upperWhisker,
        minWhisker,
        maxWhisker,
        iqr,
        outliers
      };
      return info;
    }

    /**
     * Check that two arrays have the same length.
     * @param array1 - First array.
     * @param array2 - Second array.
     */
    function xCheckLengths(array1, array2) {
      if (array1.length !== array2.length) {
        throw new TypeError('length of array1 and array2 must be identical');
      }
    }

    /**
     * Calculates the correlation between 2 vectors
     * https://en.wikipedia.org/wiki/Correlation_and_dependence
     * @param A - first array
     * @param B - sencond array
     */
    function xCorrelation(A, B) {
      const n = A.length;
      let sumA = 0;
      let sumA2 = 0;
      let sumB = 0;
      let sumB2 = 0;
      let sumAB = 0;
      for (let i = 0; i < n; i++) {
        const a = A[i];
        const b = B[i];
        sumA += a;
        sumA2 += a ** 2;
        sumB += b;
        sumB2 += b ** 2;
        sumAB += a * b;
      }
      return (n * sumAB - sumA * sumB) / (Math.sqrt(n * sumA2 - sumA ** 2) * Math.sqrt(n * sumB2 - sumB ** 2));
    }

    function getDefaultExportFromCjs (x) {
    	return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
    }

    function getAugmentedNamespace(n) {
      if (Object.prototype.hasOwnProperty.call(n, '__esModule')) return n;
      var f = n.default;
    	if (typeof f == "function") {
    		var a = function a () {
    			var isInstance = false;
          try {
            isInstance = this instanceof a;
          } catch {}
    			if (isInstance) {
            return Reflect.construct(f, arguments, this.constructor);
    			}
    			return f.apply(this, arguments);
    		};
    		a.prototype = f.prototype;
      } else a = {};
      Object.defineProperty(a, '__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;
    }

    var matrix = {};

    var require$$0$4 = /*@__PURE__*/getAugmentedNamespace(libEsm$3);

    function max(input) {
      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      if (!isAnyArray$1(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;
    }

    function min(input) {
      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      if (!isAnyArray$1(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$2(input) {
      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      if (!isAnyArray$1(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$1(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;
    }

    var libEs6 = /*#__PURE__*/Object.freeze({
        __proto__: null,
        default: rescale$2
    });

    var require$$1 = /*@__PURE__*/getAugmentedNamespace(libEs6);

    Object.defineProperty(matrix, '__esModule', {
      value: true
    });
    var isAnyArray = require$$0$4;
    var rescale$1 = require$$1;
    const indent = ' '.repeat(2);
    const indentData = ' '.repeat(4);

    /**
     * @this {Matrix}
     * @returns {string}
     */
    function inspectMatrix() {
      return inspectMatrixWithOptions(this);
    }
    function inspectMatrixWithOptions(matrix, options = {}) {
      const {
        maxRows = 15,
        maxColumns = 10,
        maxNumSize = 8,
        padMinus = 'auto'
      } = options;
      return `${matrix.constructor.name} {
${indent}[
${indentData}${inspectData(matrix, maxRows, maxColumns, maxNumSize, padMinus)}
${indent}]
${indent}rows: ${matrix.rows}
${indent}columns: ${matrix.columns}
}`;
    }
    function inspectData(matrix, maxRows, maxColumns, maxNumSize, padMinus) {
      const {
        rows,
        columns
      } = matrix;
      const maxI = Math.min(rows, maxRows);
      const maxJ = Math.min(columns, maxColumns);
      const result = [];
      if (padMinus === 'auto') {
        padMinus = false;
        loop: for (let i = 0; i < maxI; i++) {
          for (let j = 0; j < maxJ; j++) {
            if (matrix.get(i, j) < 0) {
              padMinus = true;
              break loop;
            }
          }
        }
      }
      for (let i = 0; i < maxI; i++) {
        let line = [];
        for (let j = 0; j < maxJ; j++) {
          line.push(formatNumber(matrix.get(i, j), maxNumSize, padMinus));
        }
        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, padMinus) {
      return (num >= 0 && padMinus ? ` ${formatNumber2(num, maxNumSize - 1)}` : formatNumber2(num, maxNumSize)).padEnd(maxNumSize);
    }
    function formatNumber2(num, len) {
      // small.length numbers should be as is
      let str = num.toString();
      if (str.length <= len) return str;

      // (7)'0.00123' is better then (7)'1.23e-2'
      // (8)'0.000123' is worse then (7)'1.23e-3',
      let fix = num.toFixed(len);
      if (fix.length > len) {
        fix = num.toFixed(Math.max(0, len - (fix.length - len)));
      }
      if (fix.length <= len && !fix.startsWith('0.000') && !fix.startsWith('-0.000')) {
        return fix;
      }

      // well, if it's still too long the user should've used longer numbers
      let exp = num.toExponential(len);
      if (exp.length > len) {
        exp = num.toExponential(Math.max(0, len - (exp.length - len)));
      }
      return exp.slice(0);
    }
    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, 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, 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 checkRowIndices(matrix, rowIndices) {
      if (!isAnyArray.isAnyArray(rowIndices)) {
        throw new TypeError('row indices must be an array');
      }
      for (let i = 0; i < rowIndices.length; i++) {
        if (rowIndices[i] < 0 || rowIndices[i] >= matrix.rows) {
          throw new RangeError('row indices are out of range');
        }
      }
    }
    function checkColumnIndices(matrix, columnIndices) {
      if (!isAnyArray.isAnyArray(columnIndices)) {
        throw new TypeError('column indices must be an array');
      }
      for (let i = 0; i < columnIndices.length; i++) {
        if (columnIndices[i] < 0 || columnIndices[i] >= matrix.columns) {
          throw new RangeError('column indices are out of range');
        }
      }
    }
    function checkRange$1(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 += 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 += 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 += 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$1(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(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$1(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$1(rows, columns);
      }
      static ones(rows, columns) {
        return new Matrix$1(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$1(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$1(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$1(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$1(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;
      }
      isDistance() {
        if (!this.isSymmetric()) return false;
        for (let i = 0; i < this.rows; i++) {
          if (this.get(i, i) !== 0) return false;
        }
        return true;
      }
      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$1(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$1.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$1.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(by) {
        if (this.isEmpty()) {
          return NaN;
        }
        switch (by) {
          case 'row':
            {
              const max = new Array(this.rows).fill(Number.NEGATIVE_INFINITY);
              for (let row = 0; row < this.rows; row++) {
                for (let column = 0; column < this.columns; column++) {
                  if (this.get(row, column) > max[row]) {
                    max[row] = this.get(row, column);
                  }
                }
              }
              return max;
            }
          case 'column':
            {
              const max = new Array(this.columns).fill(Number.NEGATIVE_INFINITY);
              for (let row = 0; row < this.rows; row++) {
                for (let column = 0; column < this.columns; column++) {
                  if (this.get(row, column) > max[column]) {
                    max[column] = this.get(row, column);
                  }
                }
              }
              return max;
            }
          case undefined:
            {
              let max = this.get(0, 0);
              for (let row = 0; row < this.rows; row++) {
                for (let column = 0; column < this.columns; column++) {
                  if (this.get(row, column) > max) {
                    max = this.get(row, column);
                  }
                }
              }
              return max;
            }
          default:
            throw new Error(`invalid option: ${by}`);
        }
      }
      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(by) {
        if (this.isEmpty()) {
          return NaN;
        }
        switch (by) {
          case 'row':
            {
              const min = new Array(this.rows).fill(Number.POSITIVE_INFINITY);
              for (let row = 0; row < this.rows; row++) {
                for (let column = 0; column < this.columns; column++) {
                  if (this.get(row, column) < min[row]) {
                    min[row] = this.get(row, column);
                  }
                }
              }
              return min;
            }
          case 'column':
            {
              const min = new Array(this.columns).fill(Number.POSITIVE_INFINITY);
              for (let row = 0; row < this.rows; row++) {
                for (let column = 0; column < this.columns; column++) {
                  if (this.get(row, column) < min[column]) {
                    min[column] = this.get(row, column);
                  }
                }
              }
              return min;
            }
          case undefined:
            {
              let min = this.get(0, 0);
              for (let row = 0; row < this.rows; row++) {
                for (let column = 0; column < this.columns; column++) {
                  if (this.get(row, column) < min) {
                    min = this.get(row, column);
                  }
                }
              }
              return min;
            }
          default:
            throw new Error(`invalid option: ${by}`);
        }
      }
      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') {
        switch (type) {
          case 'max':
            return this.max();
          case 'frobenius':
            return Math.sqrt(this.dot(this));
          default:
            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$1.checkMatrix(other);
        let m = this.rows;
        let n = this.columns;
        let p = other.columns;
        let result = new Matrix$1(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;
      }
      mpow(scalar) {
        if (!this.isSquare()) {
          throw new RangeError('Matrix must be square');
        }
        if (!Number.isInteger(scalar) || scalar < 0) {
          throw new RangeError('Exponent must be a non-negative integer');
        }
        // Russian Peasant exponentiation, i.e. exponentiation by squaring
        let result = Matrix$1.eye(this.rows);
        let bb = this;
        // Note: Don't bit shift. In JS, that would truncate at 32 bits
        for (let e = scalar; e >= 1; e /= 2) {
          if ((e & 1) !== 0) {
            result = result.mmul(bb);
          }
          bb = bb.mmul(bb);
        }
        return result;
      }
      strassen2x2(other) {
        other = Matrix$1.checkMatrix(other);
        let result = new Matrix$1(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$1.checkMatrix(other);
        let result = new Matrix$1(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$1.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 result = AbstractMatrix.zeros(2 * c11.rows, 2 * c11.columns);
          result = result.setSubMatrix(c11, 0, 0);
          result = result.setSubMatrix(c12, c11.rows, 0);
          result = result.setSubMatrix(c21, 0, c11.columns);
          result = result.setSubMatrix(c22, c11.rows, c11.columns);
          return result.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$1(this.rows, this.columns);
        for (let i = 0; i < this.rows; i++) {
          const row = this.getRow(i);
          if (row.length > 0) {
            rescale$1(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$1(this.rows, this.columns);
        for (let i = 0; i < this.columns; i++) {
          const column = this.getColumn(i);
          if (column.length) {
            rescale$1(column, {
              min,
              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$1.checkMatrix(other);
        let m = this.rows;
        let n = this.columns;
        let p = other.rows;
        let q = other.columns;
        let result = new Matrix$1(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$1.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$1.eye(n, n));
        let IxB = Matrix$1.eye(m, m).kroneckerProduct(other);
        return AxI.add(IxB);
      }
      transpose() {
        let result = new Matrix$1(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$1(this, startRow, endRow, startColumn, endColumn);
        let newMatrix = new Matrix$1(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$1(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$1(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$1.checkMatrix(matrix);
        if (matrix.isEmpty()) {
          return this;
        }
        let endRow = startRow + matrix.rows - 1;
        let endColumn = startColumn + matrix.columns - 1;
        checkRange$1(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) {
        checkRowIndices(this, rowIndices);
        checkColumnIndices(this, columnIndices);
        let newMatrix = new Matrix$1(rowIndices.length, columnIndices.length);
        for (let i = 0; i < rowIndices.length; i++) {
          let rowIndex = rowIndices[i];
          for (let j = 0; j < columnIndices.length; j++) {
            let columnIndex = columnIndices[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() {
        return this.constructor.copy(this, new Matrix$1(this.rows, this.columns));
      }

      /**
       * @template {AbstractMatrix} M
       * @param {AbstractMatrix} from
       * @param {M} to
       * @return {M}
       */
      static copy(from, to) {
        for (const [row, column, value] of from.entries()) {
          to.set(row, column, value);
        }
        return to;
      }
      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 (!isAnyArray.isAnyArray(mean)) {
                throw new TypeError('mean must be an array');
              }
              return varianceByRow(this, unbiased, mean);
            }
          case 'column':
            {
              if (!isAnyArray.isAnyArray(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 (!isAnyArray.isAnyArray(center)) {
                throw new TypeError('center must be an array');
              }
              centerByRow(this, center);
              return this;
            }
          case 'column':
            {
              if (!isAnyArray.isAnyArray(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 (!isAnyArray.isAnyArray(scale)) {
                throw new TypeError('scale must be an array');
              }
              scaleByRow(this, scale);
              return this;
            }
          case 'column':
            {
              if (scale === undefined) {
                scale = getScaleByColumn(this);
              } else if (!isAnyArray.isAnyArray(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);
      }
      [Symbol.iterator]() {
        return this.entries();
      }

      /**
       * iterator from left to right, from top to bottom
       * yield [row, column, value]
       * @returns {Generator<[number, number, number], void, void>}
       */
      *entries() {
        for (let row = 0; row < this.rows; row++) {
          for (let col = 0; col < this.columns; col++) {
            yield [row, col, this.get(row, col)];
          }
        }
      }

      /**
       * iterator from left to right, from top to bottom
       * yield value
       * @returns {Generator<number, void, void>}
       */
      *values() {
        for (let row = 0; row < this.rows; row++) {
          for (let col = 0; col < this.columns; col++) {
            yield this.get(row, col);
          }
        }
      }
    }
    AbstractMatrix.prototype.klass = 'Matrix';
    if (typeof Symbol !== 'undefined') {
      AbstractMatrix.prototype[Symbol.for('nodejs.util.inspect.custom')] = inspectMatrix;
    }
    function compareNumbers(a, b) {
      return a - b;
    }
    function isArrayOfNumbers(array) {
      return array.every(element => {
        return typeof element === 'number';
      });
    }

    // 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;
    let Matrix$1 = class Matrix extends AbstractMatrix {
      /**
       * @type {Float64Array[]}
       */
      data;

      /**
       * Init an empty matrix
       * @param {number} nRows
       * @param {number} nColumns
       */
      #initData(nRows, nColumns) {
        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');
        }
        this.rows = nRows;
        this.columns = nColumns;
      }
      constructor(nRows, nColumns) {
        super();
        if (Matrix.isMatrix(nRows)) {
          this.#initData(nRows.rows, nRows.columns);
          Matrix.copy(nRows, this);
        } else if (Number.isInteger(nRows) && nRows >= 0) {
          this.#initData(nRows, nColumns);
        } else if (isAnyArray.isAnyArray(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');
            }
            if (!isArrayOfNumbers(arrayData[i])) {
              throw new TypeError('Input data contains non-numeric values');
            }
            this.data.push(Float64Array.from(arrayData[i]));
          }
          this.rows = nRows;
          this.columns = nColumns;
        } else {
          throw new TypeError('First argument must be a positive number or an array');
        }
      }
      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$1);

    /**
     * @typedef {0 | 1 | number | boolean} Mask
     */

    class SymmetricMatrix extends AbstractMatrix {
      /** @type {Matrix} */
      #matrix;
      get size() {
        return this.#matrix.size;
      }
      get rows() {
        return this.#matrix.rows;
      }
      get columns() {
        return this.#matrix.columns;
      }
      get diagonalSize() {
        return this.rows;
      }

      /**
       * not the same as matrix.isSymmetric()
       * Here is to check if it's instanceof SymmetricMatrix without bundling issues
       *
       * @param value
       * @returns {boolean}
       */
      static isSymmetricMatrix(value) {
        return Matrix$1.isMatrix(value) && value.klassType === 'SymmetricMatrix';
      }

      /**
       * @param diagonalSize
       * @return {SymmetricMatrix}
       */
      static zeros(diagonalSize) {
        return new this(diagonalSize);
      }

      /**
       * @param diagonalSize
       * @return {SymmetricMatrix}
       */
      static ones(diagonalSize) {
        return new this(diagonalSize).fill(1);
      }

      /**
       * @param {number | AbstractMatrix | ArrayLike<ArrayLike<number>>} diagonalSize
       * @return {this}
       */
      constructor(diagonalSize) {
        super();
        if (Matrix$1.isMatrix(diagonalSize)) {
          if (!diagonalSize.isSymmetric()) {
            throw new TypeError('not symmetric data');
          }
          this.#matrix = Matrix$1.copy(diagonalSize, new Matrix$1(diagonalSize.rows, diagonalSize.rows));
        } else if (Number.isInteger(diagonalSize) && diagonalSize >= 0) {
          this.#matrix = new Matrix$1(diagonalSize, diagonalSize);
        } else {
          this.#matrix = new Matrix$1(diagonalSize);
          if (!this.isSymmetric()) {
            throw new TypeError('not symmetric data');
          }
        }
      }
      clone() {
        const matrix = new SymmetricMatrix(this.diagonalSize);
        for (const [row, col, value] of this.upperRightEntries()) {
          matrix.set(row, col, value);
        }
        return matrix;
      }
      toMatrix() {
        return new Matrix$1(this);
      }
      get(rowIndex, columnIndex) {
        return this.#matrix.get(rowIndex, columnIndex);
      }
      set(rowIndex, columnIndex, value) {
        // symmetric set
        this.#matrix.set(rowIndex, columnIndex, value);
        this.#matrix.set(columnIndex, rowIndex, value);
        return this;
      }
      removeCross(index) {
        // symmetric remove side
        this.#matrix.removeRow(index);
        this.#matrix.removeColumn(index);
        return this;
      }
      addCross(index, array) {
        if (array === undefined) {
          array = index;
          index = this.diagonalSize;
        }
        const row = array.slice();
        row.splice(index, 1);
        this.#matrix.addRow(index, row);
        this.#matrix.addColumn(index, array);
        return this;
      }

      /**
       * @param {Mask[]} mask
       */
      applyMask(mask) {
        if (mask.length !== this.diagonalSize) {
          throw new RangeError('Mask size do not match with matrix size');
        }

        // prepare sides to remove from matrix from mask
        /** @type {number[]} */
        const sidesToRemove = [];
        for (const [index, passthroughs] of mask.entries()) {
          if (passthroughs) continue;
          sidesToRemove.push(index);
        }
        // to remove from highest to lowest for no mutation shifting
        sidesToRemove.reverse();

        // remove sides
        for (const sideIndex of sidesToRemove) {
          this.removeCross(sideIndex);
        }
        return this;
      }

      /**
       * Compact format upper-right corner of matrix
       * iterate from left to right, from top to bottom.
       *
       * ```
       *   A B C D
       * A 1 2 3 4
       * B 2 5 6 7
       * C 3 6 8 9
       * D 4 7 9 10
       * ```
       *
       * will return compact 1D array `[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]`
       *
       * length is S(i=0, n=sideSize) => 10 for a 4 sideSized matrix
       *
       * @returns {number[]}
       */
      toCompact() {
        const {
          diagonalSize
        } = this;

        /** @type {number[]} */
        const compact = new Array(diagonalSize * (diagonalSize + 1) / 2);
        for (let col = 0, row = 0, index = 0; index < compact.length; index++) {
          compact[index] = this.get(row, col);
          if (++col >= diagonalSize) col = ++row;
        }
        return compact;
      }

      /**
       * @param {number[]} compact
       * @return {SymmetricMatrix}
       */
      static fromCompact(compact) {
        const compactSize = compact.length;
        // compactSize = (sideSize * (sideSize + 1)) / 2
        // https://mathsolver.microsoft.com/fr/solve-problem/y%20%3D%20%20x%20%60cdot%20%20%20%60frac%7B%20%20%60left(%20x%2B1%20%20%60right)%20%20%20%20%7D%7B%202%20%20%7D
        // sideSize = (Sqrt(8 × compactSize + 1) - 1) / 2
        const diagonalSize = (Math.sqrt(8 * compactSize + 1) - 1) / 2;
        if (!Number.isInteger(diagonalSize)) {
          throw new TypeError(`This array is not a compact representation of a Symmetric Matrix, ${JSON.stringify(compact)}`);
        }
        const matrix = new SymmetricMatrix(diagonalSize);
        for (let col = 0, row = 0, index = 0; index < compactSize; index++) {
          matrix.set(col, row, compact[index]);
          if (++col >= diagonalSize) col = ++row;
        }
        return matrix;
      }

      /**
       * half iterator upper-right-corner from left to right, from top to bottom
       * yield [row, column, value]
       *
       * @returns {Generator<[number, number, number], void, void>}
       */
      *upperRightEntries() {
        for (let row = 0, col = 0; row < this.diagonalSize; void 0) {
          const value = this.get(row, col);
          yield [row, col, value];

          // at the end of row, move cursor to next row at diagonal position
          if (++col >= this.diagonalSize) col = ++row;
        }
      }

      /**
       * half iterator upper-right-corner from left to right, from top to bottom
       * yield value
       *
       * @returns {Generator<[number, number, number], void, void>}
       */
      *upperRightValues() {
        for (let row = 0, col = 0; row < this.diagonalSize; void 0) {
          const value = this.get(row, col);
          yield value;

          // at the end of row, move cursor to next row at diagonal position
          if (++col >= this.diagonalSize) col = ++row;
        }
      }
    }
    SymmetricMatrix.prototype.klassType = 'SymmetricMatrix';
    class DistanceMatrix extends SymmetricMatrix {
      /**
       * not the same as matrix.isSymmetric()
       * Here is to check if it's instanceof SymmetricMatrix without bundling issues
       *
       * @param value
       * @returns {boolean}
       */
      static isDistanceMatrix(value) {
        return SymmetricMatrix.isSymmetricMatrix(value) && value.klassSubType === 'DistanceMatrix';
      }
      constructor(sideSize) {
        super(sideSize);
        if (!this.isDistance()) {
          throw new TypeError('Provided arguments do no produce a distance matrix');
        }
      }
      set(rowIndex, columnIndex, value) {
        // distance matrix diagonal is 0
        if (rowIndex === columnIndex) value = 0;
        return super.set(rowIndex, columnIndex, value);
      }
      addCross(index, array) {
        if (array === undefined) {
          array = index;
          index = this.diagonalSize;
        }

        // ensure distance
        array = array.slice();
        array[index] = 0;
        return super.addCross(index, array);
      }
      toSymmetricMatrix() {
        return new SymmetricMatrix(this);
      }
      clone() {
        const matrix = new DistanceMatrix(this.diagonalSize);
        for (const [row, col, value] of this.upperRightEntries()) {
          if (row === col) continue;
          matrix.set(row, col, value);
        }
        return matrix;
      }

      /**
       * Compact format upper-right corner of matrix
       * no diagonal (only zeros)
       * iterable from left to right, from top to bottom.
       *
       * ```
       *   A B C D
       * A 0 1 2 3
       * B 1 0 4 5
       * C 2 4 0 6
       * D 3 5 6 0
       * ```
       *
       * will return compact 1D array `[1, 2, 3, 4, 5, 6]`
       *
       * length is S(i=0, n=sideSize-1) => 6 for a 4 side sized matrix
       *
       * @returns {number[]}
       */
      toCompact() {
        const {
          diagonalSize
        } = this;
        const compactLength = (diagonalSize - 1) * diagonalSize / 2;

        /** @type {number[]} */
        const compact = new Array(compactLength);
        for (let col = 1, row = 0, index = 0; index < compact.length; index++) {
          compact[index] = this.get(row, col);
          if (++col >= diagonalSize) col = ++row + 1;
        }
        return compact;
      }

      /**
       * @param {number[]} compact
       */
      static fromCompact(compact) {
        const compactSize = compact.length;
        if (compactSize === 0) {
          return new this(0);
        }

        // compactSize in Natural integer range ]0;∞]
        // compactSize = (sideSize * (sideSize - 1)) / 2
        // sideSize = (Sqrt(8 × compactSize + 1) + 1) / 2
        const diagonalSize = (Math.sqrt(8 * compactSize + 1) + 1) / 2;
        if (!Number.isInteger(diagonalSize)) {
          throw new TypeError(`This array is not a compact representation of a DistanceMatrix, ${JSON.stringify(compact)}`);
        }
        const matrix = new this(diagonalSize);
        for (let col = 1, row = 0, index = 0; index < compactSize; index++) {
          matrix.set(col, row, compact[index]);
          if (++col >= diagonalSize) col = ++row + 1;
        }
        return matrix;
      }
    }
    DistanceMatrix.prototype.klassSubType = 'DistanceMatrix';
    class BaseView extends AbstractMatrix {
      constructor(matrix, rows, columns) {
        super();
        this.matrix = matrix;
        this.rows = rows;
        this.columns = columns;
      }
    }
    class MatrixColumnView extends BaseView {
      constructor(matrix, column) {
        checkColumnIndex(matrix, column);
        super(matrix, matrix.rows, 1);
        this.column = column;
      }
      set(rowIndex, columnIndex, value) {
        this.matrix.set(rowIndex, this.column, value);
        return this;
      }
      get(rowIndex) {
        return this.matrix.get(rowIndex, this.column);
      }
    }
    class MatrixColumnSelectionView extends BaseView {
      constructor(matrix, columnIndices) {
        checkColumnIndices(matrix, columnIndices);
        super(matrix, matrix.rows, columnIndices.length);
        this.columnIndices = columnIndices;
      }
      set(rowIndex, columnIndex, value) {
        this.matrix.set(rowIndex, this.columnIndices[columnIndex], value);
        return this;
      }
      get(rowIndex, columnIndex) {
        return this.matrix.get(rowIndex, this.columnIndices[columnIndex]);
      }
    }
    class MatrixFlipColumnView extends BaseView {
      constructor(matrix) {
        super(matrix, matrix.rows, matrix.columns);
      }
      set(rowIndex, columnIndex, value) {
        this.matrix.set(rowIndex, this.columns - columnIndex - 1, value);
        return this;
      }
      get(rowIndex, columnIndex) {
        return this.matrix.get(rowIndex, this.columns - columnIndex - 1);
      }
    }
    class MatrixFlipRowView extends BaseView {
      constructor(matrix) {
        super(matrix, matrix.rows, matrix.columns);
      }
      set(rowIndex, columnIndex, value) {
        this.matrix.set(this.rows - rowIndex - 1, columnIndex, value);
        return this;
      }
      get(rowIndex, columnIndex) {
        return this.matrix.get(this.rows - rowIndex - 1, columnIndex);
      }
    }
    class MatrixRowView extends BaseView {
      constructor(matrix, row) {
        checkRowIndex(matrix, row);
        super(matrix, 1, matrix.columns);
        this.row = row;
      }
      set(rowIndex, columnIndex, value) {
        this.matrix.set(this.row, columnIndex, value);
        return this;
      }
      get(rowIndex, columnIndex) {
        return this.matrix.get(this.row, columnIndex);
      }
    }
    class MatrixRowSelectionView extends BaseView {
      constructor(matrix, rowIndices) {
        checkRowIndices(matrix, rowIndices);
        super(matrix, rowIndices.length, matrix.columns);
        this.rowIndices = rowIndices;
      }
      set(rowIndex, columnIndex, value) {
        this.matrix.set(this.rowIndices[rowIndex], columnIndex, value);
        return this;
      }
      get(rowIndex, columnIndex) {
        return this.matrix.get(this.rowIndices[rowIndex], columnIndex);
      }
    }
    class MatrixSelectionView extends BaseView {
      constructor(matrix, rowIndices, columnIndices) {
        checkRowIndices(matrix, rowIndices);
        checkColumnIndices(matrix, columnIndices);
        super(matrix, rowIndices.length, columnIndices.length);
        this.rowIndices = rowIndices;
        this.columnIndices = columnIndices;
      }
      set(rowIndex, columnIndex, value) {
        this.matrix.set(this.rowIndices[rowIndex], this.columnIndices[columnIndex], value);
        return this;
      }
      get(rowIndex, columnIndex) {
        return this.matrix.get(this.rowIndices[rowIndex], this.columnIndices[columnIndex]);
      }
    }
    class MatrixSubView extends BaseView {
      constructor(matrix, startRow, endRow, startColumn, endColumn) {
        checkRange$1(matrix, startRow, endRow, startColumn, endColumn);
        super(matrix, endRow - startRow + 1, endColumn - startColumn + 1);
        this.startRow = startRow;
        this.startColumn = startColumn;
      }
      set(rowIndex, columnIndex, value) {
        this.matrix.set(this.startRow + rowIndex, this.startColumn + columnIndex, value);
        return this;
      }
      get(rowIndex, columnIndex) {
        return this.matrix.get(this.startRow + rowIndex, this.startColumn + columnIndex);
      }
    }
    let MatrixTransposeView$1 = class MatrixTransposeView extends BaseView {
      constructor(matrix) {
        super(matrix, matrix.columns, matrix.rows);
      }
      set(rowIndex, columnIndex, value) {
        this.matrix.set(columnIndex, rowIndex, value);
        return this;
      }
      get(rowIndex, columnIndex) {
        return this.matrix.get(columnIndex, rowIndex);
      }
    };
    class WrapperMatrix1D extends AbstractMatrix {
      constructor(data, options = {}) {
        const {
          rows = 1
        } = options;
        if (data.length % rows !== 0) {
          throw new Error('the data length is not divisible by the number of rows');
        }
        super();
        this.rows = rows;
        this.columns = data.length / rows;
        this.data = data;
      }
      set(rowIndex, columnIndex, value) {
        let index = this._calculateIndex(rowIndex, columnIndex);
        this.data[index] = value;
        return this;
      }
      get(rowIndex, columnIndex) {
        let index = this._calculateIndex(rowIndex, columnIndex);
        return this.data[index];
      }
      _calculateIndex(row, column) {
        return row * this.columns + column;
      }
    }
    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];
      }
    }
    function wrap(array, options) {
      if (isAnyArray.isAnyArray(array)) {
        if (array[0] && isAnyArray.isAnyArray(array[0])) {
          return new WrapperMatrix2D(array);
        } else {
          return new WrapperMatrix1D(array, options);
        }
      } else {
        throw new Error('the argument is not an array');
      }
    }
    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$1.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$1(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$1(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$1.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$1(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$1(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$1(m, nu);
        let V = new Matrix$1(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$1.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$1.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$1.diag(value));
      }
      inverse() {
        let V = this.V;
        let e = this.threshold;
        let vrows = V.rows;
        let vcols = V.columns;
        let X = new Matrix$1(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$1(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$1.diag(this.s);
      }
    }
    function inverse$1(matrix, useSVD = false) {
      matrix = WrapperMatrix2D.checkMatrix(matrix);
      if (useSVD) {
        return new SingularValueDecomposition(matrix).inverse();
      } else {
        return solve$1(matrix, Matrix$1.eye(matrix.rows));
      }
    }
    function solve$1(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);
      }
    }
    function determinant(matrix) {
      matrix = Matrix$1.checkMatrix(matrix);
      if (matrix.isSquare()) {
        if (matrix.columns === 0) {
          return 1;
        }
        let a, b, c, d;
        if (matrix.columns === 2) {
          // 2 x 2 matrix
          a = matrix.get(0, 0);
          b = matrix.get(0, 1);
          c = matrix.get(1, 0);
          d = matrix.get(1, 1);
          return a * d - b * c;
        } else if (matrix.columns === 3) {
          // 3 x 3 matrix
          let subMatrix0, subMatrix1, subMatrix2;
          subMatrix0 = new MatrixSelectionView(matrix, [1, 2], [1, 2]);
          subMatrix1 = new MatrixSelectionView(matrix, [1, 2], [0, 2]);
          subMatrix2 = new MatrixSelectionView(matrix, [1, 2], [0, 1]);
          a = matrix.get(0, 0);
          b = matrix.get(0, 1);
          c = matrix.get(0, 2);
          return a * determinant(subMatrix0) - b * determinant(subMatrix1) + c * determinant(subMatrix2);
        } else {
          // general purpose determinant using the LU decomposition
          return new LuDecomposition(matrix).determinant;
        }
      } else {
        throw Error('determinant can only be calculated for a square matrix');
      }
    }
    function xrange(n, exception) {
      let range = [];
      for (let i = 0; i < n; i++) {
        if (i !== exception) {
          range.push(i);
        }
      }
      return range;
    }
    function dependenciesOneRow(error, matrix, index, thresholdValue = 10e-10, thresholdError = 10e-10) {
      if (error > thresholdError) {
        return new Array(matrix.rows + 1).fill(0);
      } else {
        let returnArray = matrix.addRow(index, [0]);
        for (let i = 0; i < returnArray.rows; i++) {
          if (Math.abs(returnArray.get(i, 0)) < thresholdValue) {
            returnArray.set(i, 0, 0);
          }
        }
        return returnArray.to1DArray();
      }
    }
    function linearDependencies(matrix, options = {}) {
      const {
        thresholdValue = 10e-10,
        thresholdError = 10e-10
      } = options;
      matrix = Matrix$1.checkMatrix(matrix);
      let n = matrix.rows;
      let results = new Matrix$1(n, n);
      for (let i = 0; i < n; i++) {
        let b = Matrix$1.columnVector(matrix.getRow(i));
        let Abis = matrix.subMatrixRow(xrange(n, i)).transpose();
        let svd = new SingularValueDecomposition(Abis);
        let x = svd.solve(b);
        let error = Matrix$1.sub(b, Abis.mmul(x)).abs().max();
        results.setRow(i, dependenciesOneRow(error, x, i, thresholdValue, thresholdError));
      }
      return results;
    }
    function pseudoInverse(matrix, threshold = Number.EPSILON) {
      matrix = Matrix$1.checkMatrix(matrix);
      if (matrix.isEmpty()) {
        // with a zero dimension, the pseudo-inverse is the transpose, since all 0xn and nx0 matrices are singular
        // (0xn)*(nx0)*(0xn) = 0xn
        // (nx0)*(0xn)*(nx0) = nx0
        return matrix.transpose();
      }
      let svdSolution = new SingularValueDecomposition(matrix, {
        autoTranspose: true
      });
      let U = svdSolution.leftSingularVectors;
      let V = svdSolution.rightSingularVectors;
      let s = svdSolution.diagonal;
      for (let i = 0; i < s.length; i++) {
        if (Math.abs(s[i]) > threshold) {
          s[i] = 1.0 / s[i];
        } else {
          s[i] = 0.0;
        }
      }
      return V.mmul(Matrix$1.diag(s).mmul(U.transpose()));
    }
    function covariance(xMatrix, yMatrix = xMatrix, options = {}) {
      xMatrix = new Matrix$1(xMatrix);
      let yIsSame = false;
      if (typeof yMatrix === 'object' && !Matrix$1.isMatrix(yMatrix) && !isAnyArray.isAnyArray(yMatrix)) {
        options = yMatrix;
        yMatrix = xMatrix;
        yIsSame = true;
      } else {
        yMatrix = new Matrix$1(yMatrix);
      }
      if (xMatrix.rows !== yMatrix.rows) {
        throw new TypeError('Both matrices must have the same number of rows');
      }
      const {
        center = true
      } = options;
      if (center) {
        xMatrix = xMatrix.center('column');
        if (!yIsSame) {
          yMatrix = yMatrix.center('column');
        }
      }
      const cov = xMatrix.transpose().mmul(yMatrix);
      for (let i = 0; i < cov.rows; i++) {
        for (let j = 0; j < cov.columns; j++) {
          cov.set(i, j, cov.get(i, j) * (1 / (xMatrix.rows - 1)));
        }
      }
      return cov;
    }
    function correlation(xMatrix, yMatrix = xMatrix, options = {}) {
      xMatrix = new Matrix$1(xMatrix);
      let yIsSame = false;
      if (typeof yMatrix === 'object' && !Matrix$1.isMatrix(yMatrix) && !isAnyArray.isAnyArray(yMatrix)) {
        options = yMatrix;
        yMatrix = xMatrix;
        yIsSame = true;
      } else {
        yMatrix = new Matrix$1(yMatrix);
      }
      if (xMatrix.rows !== yMatrix.rows) {
        throw new TypeError('Both matrices must have the same number of rows');
      }
      const {
        center = true,
        scale = true
      } = options;
      if (center) {
        xMatrix.center('column');
        if (!yIsSame) {
          yMatrix.center('column');
        }
      }
      if (scale) {
        xMatrix.scale('column');
        if (!yIsSame) {
          yMatrix.scale('column');
        }
      }
      const sdx = xMatrix.standardDeviation('column', {
        unbiased: true
      });
      const sdy = yIsSame ? sdx : yMatrix.standardDeviation('column', {
        unbiased: true
      });
      const corr = xMatrix.transpose().mmul(yMatrix);
      for (let i = 0; i < corr.rows; i++) {
        for (let j = 0; j < corr.columns; j++) {
          corr.set(i, j, corr.get(i, j) * (1 / (sdx[i] * sdy[j])) * (1 / (xMatrix.rows - 1)));
        }
      }
      return corr;
    }
    class EigenvalueDecomposition {
      constructor(matrix, options = {}) {
        const {
          assumeSymmetric = false
        } = options;
        matrix = WrapperMatrix2D.checkMatrix(matrix);
        if (!matrix.isSquare()) {
          throw new Error('Matrix is not a square matrix');
        }
        if (matrix.isEmpty()) {
          throw new Error('Matrix must be non-empty');
        }
        let n = matrix.columns;
        let V = new Matrix$1(n, n);
        let d = new Float64Array(n);
        let e = new Float64Array(n);
        let value = matrix;
        let i, j;
        let isSymmetric = false;
        if (assumeSymmetric) {
          isSymmetric = true;
        } else {
          isSymmetric = matrix.isSymmetric();
        }
        if (isSymmetric) {
          for (i = 0; i < n; i++) {
            for (j = 0; j < n; j++) {
              V.set(i, j, value.get(i, j));
            }
          }
          tred2(n, e, d, V);
          tql2(n, e, d, V);
        } else {
          let H = new Matrix$1(n, n);
          let ort = new Float64Array(n);
          for (j = 0; j < n; j++) {
            for (i = 0; i < n; i++) {
              H.set(i, j, value.get(i, j));
            }
          }
          orthes(n, H, ort, V);
          hqr2(n, e, d, V, H);
        }
        this.n = n;
        this.e = e;
        this.d = d;
        this.V = V;
      }
      get realEigenvalues() {
        return Array.from(this.d);
      }
      get imaginaryEigenvalues() {
        return Array.from(this.e);
      }
      get eigenvectorMatrix() {
        return this.V;
      }
      get diagonalMatrix() {
        let n = this.n;
        let e = this.e;
        let d = this.d;
        let X = new Matrix$1(n, n);
        let i, j;
        for (i = 0; i < n; i++) {
          for (j = 0; j < n; j++) {
            X.set(i, j, 0);
          }
          X.set(i, i, d[i]);
          if (e[i] > 0) {
            X.set(i, i + 1, e[i]);
          } else if (e[i] < 0) {
            X.set(i, i - 1, e[i]);
          }
        }
        return X;
      }
    }
    function tred2(n, e, d, V) {
      let f, g, h, i, j, k, hh, scale;
      for (j = 0; j < n; j++) {
        d[j] = V.get(n - 1, j);
      }
      for (i = n - 1; i > 0; i--) {
        scale = 0;
        h = 0;
        for (k = 0; k < i; k++) {
          scale = scale + Math.abs(d[k]);
        }
        if (scale === 0) {
          e[i] = d[i - 1];
          for (j = 0; j < i; j++) {
            d[j] = V.get(i - 1, j);
            V.set(i, j, 0);
            V.set(j, i, 0);
          }
        } else {
          for (k = 0; k < i; k++) {
            d[k] /= scale;
            h += d[k] * d[k];
          }
          f = d[i - 1];
          g = Math.sqrt(h);
          if (f > 0) {
            g = -g;
          }
          e[i] = scale * g;
          h = h - f * g;
          d[i - 1] = f - g;
          for (j = 0; j < i; j++) {
            e[j] = 0;
          }
          for (j = 0; j < i; j++) {
            f = d[j];
            V.set(j, i, f);
            g = e[j] + V.get(j, j) * f;
            for (k = j + 1; k <= i - 1; k++) {
              g += V.get(k, j) * d[k];
              e[k] += V.get(k, j) * f;
            }
            e[j] = g;
          }
          f = 0;
          for (j = 0; j < i; j++) {
            e[j] /= h;
            f += e[j] * d[j];
          }
          hh = f / (h + h);
          for (j = 0; j < i; j++) {
            e[j] -= hh * d[j];
          }
          for (j = 0; j < i; j++) {
            f = d[j];
            g = e[j];
            for (k = j; k <= i - 1; k++) {
              V.set(k, j, V.get(k, j) - (f * e[k] + g * d[k]));
            }
            d[j] = V.get(i - 1, j);
            V.set(i, j, 0);
          }
        }
        d[i] = h;
      }
      for (i = 0; i < n - 1; i++) {
        V.set(n - 1, i, V.get(i, i));
        V.set(i, i, 1);
        h = d[i + 1];
        if (h !== 0) {
          for (k = 0; k <= i; k++) {
            d[k] = V.get(k, i + 1) / h;
          }
          for (j = 0; j <= i; j++) {
            g = 0;
            for (k = 0; k <= i; k++) {
              g += V.get(k, i + 1) * V.get(k, j);
            }
            for (k = 0; k <= i; k++) {
              V.set(k, j, V.get(k, j) - g * d[k]);
            }
          }
        }
        for (k = 0; k <= i; k++) {
          V.set(k, i + 1, 0);
        }
      }
      for (j = 0; j < n; j++) {
        d[j] = V.get(n - 1, j);
        V.set(n - 1, j, 0);
      }
      V.set(n - 1, n - 1, 1);
      e[0] = 0;
    }
    function tql2(n, e, d, V) {
      let g, h, i, j, k, l, m, p, r, dl1, c, c2, c3, el1, s, s2;
      for (i = 1; i < n; i++) {
        e[i - 1] = e[i];
      }
      e[n - 1] = 0;
      let f = 0;
      let tst1 = 0;
      let eps = Number.EPSILON;
      for (l = 0; l < n; l++) {
        tst1 = Math.max(tst1, Math.abs(d[l]) + Math.abs(e[l]));
        m = l;
        while (m < n) {
          if (Math.abs(e[m]) <= eps * tst1) {
            break;
          }
          m++;
        }
        if (m > l) {
          do {
            g = d[l];
            p = (d[l + 1] - g) / (2 * e[l]);
            r = hypotenuse(p, 1);
            if (p < 0) {
              r = -r;
            }
            d[l] = e[l] / (p + r);
            d[l + 1] = e[l] * (p + r);
            dl1 = d[l + 1];
            h = g - d[l];
            for (i = l + 2; i < n; i++) {
              d[i] -= h;
            }
            f = f + h;
            p = d[m];
            c = 1;
            c2 = c;
            c3 = c;
            el1 = e[l + 1];
            s = 0;
            s2 = 0;
            for (i = m - 1; i >= l; i--) {
              c3 = c2;
              c2 = c;
              s2 = s;
              g = c * e[i];
              h = c * p;
              r = hypotenuse(p, e[i]);
              e[i + 1] = s * r;
              s = e[i] / r;
              c = p / r;
              p = c * d[i] - s * g;
              d[i + 1] = h + s * (c * g + s * d[i]);
              for (k = 0; k < n; k++) {
                h = V.get(k, i + 1);
                V.set(k, i + 1, s * V.get(k, i) + c * h);
                V.set(k, i, c * V.get(k, i) - s * h);
              }
            }
            p = -s * s2 * c3 * el1 * e[l] / dl1;
            e[l] = s * p;
            d[l] = c * p;
          } while (Math.abs(e[l]) > eps * tst1);
        }
        d[l] = d[l] + f;
        e[l] = 0;
      }
      for (i = 0; i < n - 1; i++) {
        k = i;
        p = d[i];
        for (j = i + 1; j < n; j++) {
          if (d[j] < p) {
            k = j;
            p = d[j];
          }
        }
        if (k !== i) {
          d[k] = d[i];
          d[i] = p;
          for (j = 0; j < n; j++) {
            p = V.get(j, i);
            V.set(j, i, V.get(j, k));
            V.set(j, k, p);
          }
        }
      }
    }
    function orthes(n, H, ort, V) {
      let low = 0;
      let high = n - 1;
      let f, g, h, i, j, m;
      let scale;
      for (m = low + 1; m <= high - 1; m++) {
        scale = 0;
        for (i = m; i <= high; i++) {
          scale = scale + Math.abs(H.get(i, m - 1));
        }
        if (scale !== 0) {
          h = 0;
          for (i = high; i >= m; i--) {
            ort[i] = H.get(i, m - 1) / scale;
            h += ort[i] * ort[i];
          }
          g = Math.sqrt(h);
          if (ort[m] > 0) {
            g = -g;
          }
          h = h - ort[m] * g;
          ort[m] = ort[m] - g;
          for (j = m; j < n; j++) {
            f = 0;
            for (i = high; i >= m; i--) {
              f += ort[i] * H.get(i, j);
            }
            f = f / h;
            for (i = m; i <= high; i++) {
              H.set(i, j, H.get(i, j) - f * ort[i]);
            }
          }
          for (i = 0; i <= high; i++) {
            f = 0;
            for (j = high; j >= m; j--) {
              f += ort[j] * H.get(i, j);
            }
            f = f / h;
            for (j = m; j <= high; j++) {
              H.set(i, j, H.get(i, j) - f * ort[j]);
            }
          }
          ort[m] = scale * ort[m];
          H.set(m, m - 1, scale * g);
        }
      }
      for (i = 0; i < n; i++) {
        for (j = 0; j < n; j++) {
          V.set(i, j, i === j ? 1 : 0);
        }
      }
      for (m = high - 1; m >= low + 1; m--) {
        if (H.get(m, m - 1) !== 0) {
          for (i = m + 1; i <= high; i++) {
            ort[i] = H.get(i, m - 1);
          }
          for (j = m; j <= high; j++) {
            g = 0;
            for (i = m; i <= high; i++) {
              g += ort[i] * V.get(i, j);
            }
            g = g / ort[m] / H.get(m, m - 1);
            for (i = m; i <= high; i++) {
              V.set(i, j, V.get(i, j) + g * ort[i]);
            }
          }
        }
      }
    }
    function hqr2(nn, e, d, V, H) {
      let n = nn - 1;
      let low = 0;
      let high = nn - 1;
      let eps = Number.EPSILON;
      let exshift = 0;
      let norm = 0;
      let p = 0;
      let q = 0;
      let r = 0;
      let s = 0;
      let z = 0;
      let iter = 0;
      let i, j, k, l, m, t, w, x, y;
      let ra, sa, vr, vi;
      let notlast, cdivres;
      for (i = 0; i < nn; i++) {
        if (i < low || i > high) {
          d[i] = H.get(i, i);
          e[i] = 0;
        }
        for (j = Math.max(i - 1, 0); j < nn; j++) {
          norm = norm + Math.abs(H.get(i, j));
        }
      }
      while (n >= low) {
        l = n;
        while (l > low) {
          s = Math.abs(H.get(l - 1, l - 1)) + Math.abs(H.get(l, l));
          if (s === 0) {
            s = norm;
          }
          if (Math.abs(H.get(l, l - 1)) < eps * s) {
            break;
          }
          l--;
        }
        if (l === n) {
          H.set(n, n, H.get(n, n) + exshift);
          d[n] = H.get(n, n);
          e[n] = 0;
          n--;
          iter = 0;
        } else if (l === n - 1) {
          w = H.get(n, n - 1) * H.get(n - 1, n);
          p = (H.get(n - 1, n - 1) - H.get(n, n)) / 2;
          q = p * p + w;
          z = Math.sqrt(Math.abs(q));
          H.set(n, n, H.get(n, n) + exshift);
          H.set(n - 1, n - 1, H.get(n - 1, n - 1) + exshift);
          x = H.get(n, n);
          if (q >= 0) {
            z = p >= 0 ? p + z : p - z;
            d[n - 1] = x + z;
            d[n] = d[n - 1];
            if (z !== 0) {
              d[n] = x - w / z;
            }
            e[n - 1] = 0;
            e[n] = 0;
            x = H.get(n, n - 1);
            s = Math.abs(x) + Math.abs(z);
            p = x / s;
            q = z / s;
            r = Math.sqrt(p * p + q * q);
            p = p / r;
            q = q / r;
            for (j = n - 1; j < nn; j++) {
              z = H.get(n - 1, j);
              H.set(n - 1, j, q * z + p * H.get(n, j));
              H.set(n, j, q * H.get(n, j) - p * z);
            }
            for (i = 0; i <= n; i++) {
              z = H.get(i, n - 1);
              H.set(i, n - 1, q * z + p * H.get(i, n));
              H.set(i, n, q * H.get(i, n) - p * z);
            }
            for (i = low; i <= high; i++) {
              z = V.get(i, n - 1);
              V.set(i, n - 1, q * z + p * V.get(i, n));
              V.set(i, n, q * V.get(i, n) - p * z);
            }
          } else {
            d[n - 1] = x + p;
            d[n] = x + p;
            e[n - 1] = z;
            e[n] = -z;
          }
          n = n - 2;
          iter = 0;
        } else {
          x = H.get(n, n);
          y = 0;
          w = 0;
          if (l < n) {
            y = H.get(n - 1, n - 1);
            w = H.get(n, n - 1) * H.get(n - 1, n);
          }
          if (iter === 10) {
            exshift += x;
            for (i = low; i <= n; i++) {
              H.set(i, i, H.get(i, i) - x);
            }
            s = Math.abs(H.get(n, n - 1)) + Math.abs(H.get(n - 1, n - 2));
            // eslint-disable-next-line no-multi-assign
            x = y = 0.75 * s;
            w = -0.4375 * s * s;
          }
          if (iter === 30) {
            s = (y - x) / 2;
            s = s * s + w;
            if (s > 0) {
              s = Math.sqrt(s);
              if (y < x) {
                s = -s;
              }
              s = x - w / ((y - x) / 2 + s);
              for (i = low; i <= n; i++) {
                H.set(i, i, H.get(i, i) - s);
              }
              exshift += s;
              // eslint-disable-next-line no-multi-assign
              x = y = w = 0.964;
            }
          }
          iter = iter + 1;
          m = n - 2;
          while (m >= l) {
            z = H.get(m, m);
            r = x - z;
            s = y - z;
            p = (r * s - w) / H.get(m + 1, m) + H.get(m, m + 1);
            q = H.get(m + 1, m + 1) - z - r - s;
            r = H.get(m + 2, m + 1);
            s = Math.abs(p) + Math.abs(q) + Math.abs(r);
            p = p / s;
            q = q / s;
            r = r / s;
            if (m === l) {
              break;
            }
            if (Math.abs(H.get(m, m - 1)) * (Math.abs(q) + Math.abs(r)) < eps * (Math.abs(p) * (Math.abs(H.get(m - 1, m - 1)) + Math.abs(z) + Math.abs(H.get(m + 1, m + 1))))) {
              break;
            }
            m--;
          }
          for (i = m + 2; i <= n; i++) {
            H.set(i, i - 2, 0);
            if (i > m + 2) {
              H.set(i, i - 3, 0);
            }
          }
          for (k = m; k <= n - 1; k++) {
            notlast = k !== n - 1;
            if (k !== m) {
              p = H.get(k, k - 1);
              q = H.get(k + 1, k - 1);
              r = notlast ? H.get(k + 2, k - 1) : 0;
              x = Math.abs(p) + Math.abs(q) + Math.abs(r);
              if (x !== 0) {
                p = p / x;
                q = q / x;
                r = r / x;
              }
            }
            if (x === 0) {
              break;
            }
            s = Math.sqrt(p * p + q * q + r * r);
            if (p < 0) {
              s = -s;
            }
            if (s !== 0) {
              if (k !== m) {
                H.set(k, k - 1, -s * x);
              } else if (l !== m) {
                H.set(k, k - 1, -H.get(k, k - 1));
              }
              p = p + s;
              x = p / s;
              y = q / s;
              z = r / s;
              q = q / p;
              r = r / p;
              for (j = k; j < nn; j++) {
                p = H.get(k, j) + q * H.get(k + 1, j);
                if (notlast) {
                  p = p + r * H.get(k + 2, j);
                  H.set(k + 2, j, H.get(k + 2, j) - p * z);
                }
                H.set(k, j, H.get(k, j) - p * x);
                H.set(k + 1, j, H.get(k + 1, j) - p * y);
              }
              for (i = 0; i <= Math.min(n, k + 3); i++) {
                p = x * H.get(i, k) + y * H.get(i, k + 1);
                if (notlast) {
                  p = p + z * H.get(i, k + 2);
                  H.set(i, k + 2, H.get(i, k + 2) - p * r);
                }
                H.set(i, k, H.get(i, k) - p);
                H.set(i, k + 1, H.get(i, k + 1) - p * q);
              }
              for (i = low; i <= high; i++) {
                p = x * V.get(i, k) + y * V.get(i, k + 1);
                if (notlast) {
                  p = p + z * V.get(i, k + 2);
                  V.set(i, k + 2, V.get(i, k + 2) - p * r);
                }
                V.set(i, k, V.get(i, k) - p);
                V.set(i, k + 1, V.get(i, k + 1) - p * q);
              }
            }
          }
        }
      }
      if (norm === 0) {
        return;
      }
      for (n = nn - 1; n >= 0; n--) {
        p = d[n];
        q = e[n];
        if (q === 0) {
          l = n;
          H.set(n, n, 1);
          for (i = n - 1; i >= 0; i--) {
            w = H.get(i, i) - p;
            r = 0;
            for (j = l; j <= n; j++) {
              r = r + H.get(i, j) * H.get(j, n);
            }
            if (e[i] < 0) {
              z = w;
              s = r;
            } else {
              l = i;
              if (e[i] === 0) {
                H.set(i, n, w !== 0 ? -r / w : -r / (eps * norm));
              } else {
                x = H.get(i, i + 1);
                y = H.get(i + 1, i);
                q = (d[i] - p) * (d[i] - p) + e[i] * e[i];
                t = (x * s - z * r) / q;
                H.set(i, n, t);
                H.set(i + 1, n, Math.abs(x) > Math.abs(z) ? (-r - w * t) / x : (-s - y * t) / z);
              }
              t = Math.abs(H.get(i, n));
              if (eps * t * t > 1) {
                for (j = i; j <= n; j++) {
                  H.set(j, n, H.get(j, n) / t);
                }
              }
            }
          }
        } else if (q < 0) {
          l = n - 1;
          if (Math.abs(H.get(n, n - 1)) > Math.abs(H.get(n - 1, n))) {
            H.set(n - 1, n - 1, q / H.get(n, n - 1));
            H.set(n - 1, n, -(H.get(n, n) - p) / H.get(n, n - 1));
          } else {
            cdivres = cdiv(0, -H.get(n - 1, n), H.get(n - 1, n - 1) - p, q);
            H.set(n - 1, n - 1, cdivres[0]);
            H.set(n - 1, n, cdivres[1]);
          }
          H.set(n, n - 1, 0);
          H.set(n, n, 1);
          for (i = n - 2; i >= 0; i--) {
            ra = 0;
            sa = 0;
            for (j = l; j <= n; j++) {
              ra = ra + H.get(i, j) * H.get(j, n - 1);
              sa = sa + H.get(i, j) * H.get(j, n);
            }
            w = H.get(i, i) - p;
            if (e[i] < 0) {
              z = w;
              r = ra;
              s = sa;
            } else {
              l = i;
              if (e[i] === 0) {
                cdivres = cdiv(-ra, -sa, w, q);
                H.set(i, n - 1, cdivres[0]);
                H.set(i, n, cdivres[1]);
              } else {
                x = H.get(i, i + 1);
                y = H.get(i + 1, i);
                vr = (d[i] - p) * (d[i] - p) + e[i] * e[i] - q * q;
                vi = (d[i] - p) * 2 * q;
                if (vr === 0 && vi === 0) {
                  vr = eps * norm * (Math.abs(w) + Math.abs(q) + Math.abs(x) + Math.abs(y) + Math.abs(z));
                }
                cdivres = cdiv(x * r - z * ra + q * sa, x * s - z * sa - q * ra, vr, vi);
                H.set(i, n - 1, cdivres[0]);
                H.set(i, n, cdivres[1]);
                if (Math.abs(x) > Math.abs(z) + Math.abs(q)) {
                  H.set(i + 1, n - 1, (-ra - w * H.get(i, n - 1) + q * H.get(i, n)) / x);
                  H.set(i + 1, n, (-sa - w * H.get(i, n) - q * H.get(i, n - 1)) / x);
                } else {
                  cdivres = cdiv(-r - y * H.get(i, n - 1), -s - y * H.get(i, n), z, q);
                  H.set(i + 1, n - 1, cdivres[0]);
                  H.set(i + 1, n, cdivres[1]);
                }
              }
              t = Math.max(Math.abs(H.get(i, n - 1)), Math.abs(H.get(i, n)));
              if (eps * t * t > 1) {
                for (j = i; j <= n; j++) {
                  H.set(j, n - 1, H.get(j, n - 1) / t);
                  H.set(j, n, H.get(j, n) / t);
                }
              }
            }
          }
        }
      }
      for (i = 0; i < nn; i++) {
        if (i < low || i > high) {
          for (j = i; j < nn; j++) {
            V.set(i, j, H.get(i, j));
          }
        }
      }
      for (j = nn - 1; j >= low; j--) {
        for (i = low; i <= high; i++) {
          z = 0;
          for (k = low; k <= Math.min(j, high); k++) {
            z = z + V.get(i, k) * H.get(k, j);
          }
          V.set(i, j, z);
        }
      }
    }
    function cdiv(xr, xi, yr, yi) {
      let r, d;
      if (Math.abs(yr) > Math.abs(yi)) {
        r = yi / yr;
        d = yr + r * yi;
        return [(xr + r * xi) / d, (xi - r * xr) / d];
      } else {
        r = yr / yi;
        d = yi + r * yr;
        return [(r * xr + xi) / d, (r * xi - xr) / d];
      }
    }
    class CholeskyDecomposition {
      constructor(value) {
        value = WrapperMatrix2D.checkMatrix(value);
        if (!value.isSymmetric()) {
          throw new Error('Matrix is not symmetric');
        }
        let a = value;
        let dimension = a.rows;
        let l = new Matrix$1(dimension, dimension);
        let positiveDefinite = true;
        let i, j, k;
        for (j = 0; j < dimension; j++) {
          let d = 0;
          for (k = 0; k < j; k++) {
            let s = 0;
            for (i = 0; i < k; i++) {
              s += l.get(k, i) * l.get(j, i);
            }
            s = (a.get(j, k) - s) / l.get(k, k);
            l.set(j, k, s);
            d = d + s * s;
          }
          d = a.get(j, j) - d;
          positiveDefinite &&= d > 0;
          l.set(j, j, Math.sqrt(Math.max(d, 0)));
          for (k = j + 1; k < dimension; k++) {
            l.set(j, k, 0);
          }
        }
        this.L = l;
        this.positiveDefinite = positiveDefinite;
      }
      isPositiveDefinite() {
        return this.positiveDefinite;
      }
      solve(value) {
        value = WrapperMatrix2D.checkMatrix(value);
        let l = this.L;
        let dimension = l.rows;
        if (value.rows !== dimension) {
          throw new Error('Matrix dimensions do not match');
        }
        if (this.isPositiveDefinite() === false) {
          throw new Error('Matrix is not positive definite');
        }
        let count = value.columns;
        let B = value.clone();
        let i, j, k;
        for (k = 0; k < dimension; k++) {
          for (j = 0; j < count; j++) {
            for (i = 0; i < k; i++) {
              B.set(k, j, B.get(k, j) - B.get(i, j) * l.get(k, i));
            }
            B.set(k, j, B.get(k, j) / l.get(k, k));
          }
        }
        for (k = dimension - 1; k >= 0; k--) {
          for (j = 0; j < count; j++) {
            for (i = k + 1; i < dimension; i++) {
              B.set(k, j, B.get(k, j) - B.get(i, j) * l.get(i, k));
            }
            B.set(k, j, B.get(k, j) / l.get(k, k));
          }
        }
        return B;
      }
      get lowerTriangularMatrix() {
        return this.L;
      }
    }
    class nipals {
      constructor(X, options = {}) {
        X = WrapperMatrix2D.checkMatrix(X);
        let {
          Y
        } = options;
        const {
          scaleScores = false,
          maxIterations = 1000,
          terminationCriteria = 1e-10
        } = options;
        let u;
        if (Y) {
          if (isAnyArray.isAnyArray(Y) && typeof Y[0] === 'number') {
            Y = Matrix$1.columnVector(Y);
          } else {
            Y = WrapperMatrix2D.checkMatrix(Y);
          }
          if (Y.rows !== X.rows) {
            throw new Error('Y should have the same number of rows as X');
          }
          u = Y.getColumnVector(0);
        } else {
          u = X.getColumnVector(0);
        }
        let diff = 1;
        let t, q, w, tOld;
        for (let counter = 0; counter < maxIterations && diff > terminationCriteria; counter++) {
          w = X.transpose().mmul(u).div(u.transpose().mmul(u).get(0, 0));
          w = w.div(w.norm());
          t = X.mmul(w).div(w.transpose().mmul(w).get(0, 0));
          if (counter > 0) {
            diff = t.clone().sub(tOld).pow(2).sum();
          }
          tOld = t.clone();
          if (Y) {
            q = Y.transpose().mmul(t).div(t.transpose().mmul(t).get(0, 0));
            q = q.div(q.norm());
            u = Y.mmul(q).div(q.transpose().mmul(q).get(0, 0));
          } else {
            u = t;
          }
        }
        if (Y) {
          let p = X.transpose().mmul(t).div(t.transpose().mmul(t).get(0, 0));
          p = p.div(p.norm());
          let xResidual = X.clone().sub(t.clone().mmul(p.transpose()));
          let residual = u.transpose().mmul(t).div(t.transpose().mmul(t).get(0, 0));
          let yResidual = Y.clone().sub(t.clone().mulS(residual.get(0, 0)).mmul(q.transpose()));
          this.t = t;
          this.p = p.transpose();
          this.w = w.transpose();
          this.q = q;
          this.u = u;
          this.s = t.transpose().mmul(t);
          this.xResidual = xResidual;
          this.yResidual = yResidual;
          this.betas = residual;
        } else {
          this.w = w.transpose();
          this.s = t.transpose().mmul(t).sqrt();
          if (scaleScores) {
            this.t = t.clone().div(this.s.get(0, 0));
          } else {
            this.t = t;
          }
          this.xResidual = X.sub(t.mmul(w.transpose()));
        }
      }
    }
    matrix.AbstractMatrix = AbstractMatrix;
    matrix.CHO = CholeskyDecomposition;
    matrix.CholeskyDecomposition = CholeskyDecomposition;
    matrix.DistanceMatrix = DistanceMatrix;
    matrix.EVD = EigenvalueDecomposition;
    matrix.EigenvalueDecomposition = EigenvalueDecomposition;
    matrix.LU = LuDecomposition;
    matrix.LuDecomposition = LuDecomposition;
    var Matrix_1 = matrix.Matrix = Matrix$1;
    matrix.MatrixColumnSelectionView = MatrixColumnSelectionView;
    matrix.MatrixColumnView = MatrixColumnView;
    matrix.MatrixFlipColumnView = MatrixFlipColumnView;
    matrix.MatrixFlipRowView = MatrixFlipRowView;
    matrix.MatrixRowSelectionView = MatrixRowSelectionView;
    matrix.MatrixRowView = MatrixRowView;
    matrix.MatrixSelectionView = MatrixSelectionView;
    matrix.MatrixSubView = MatrixSubView;
    var MatrixTransposeView_1 = matrix.MatrixTransposeView = MatrixTransposeView$1;
    matrix.NIPALS = nipals;
    matrix.Nipals = nipals;
    matrix.QR = QrDecomposition;
    matrix.QrDecomposition = QrDecomposition;
    matrix.SVD = SingularValueDecomposition;
    matrix.SingularValueDecomposition = SingularValueDecomposition;
    matrix.SymmetricMatrix = SymmetricMatrix;
    matrix.WrapperMatrix1D = WrapperMatrix1D;
    matrix.WrapperMatrix2D = WrapperMatrix2D;
    matrix.correlation = correlation;
    matrix.covariance = covariance;
    var _default = matrix.default = Matrix$1;
    matrix.determinant = determinant;
    var inverse_1 = matrix.inverse = inverse$1;
    matrix.linearDependencies = linearDependencies;
    matrix.pseudoInverse = pseudoInverse;
    var solve_1 = matrix.solve = solve$1;
    matrix.wrap = wrap;

    const Matrix = Matrix_1;
    const MatrixTransposeView = MatrixTransposeView_1;
    _default.Matrix ? _default.Matrix : Matrix_1;
    const inverse = inverse_1;
    const solve = solve_1;

    const absDiff = (a, b) => Math.abs(a - b);
    /**
     * Generate a cost matrix from two set of values using the function passed. by default it
     * generate the cost matrix of absolute value of differences.
     * @param rowsArray - Array of values that will represent the rows of the cost matrix.
     * @param columnsArray - Array of values that will represent the columns of the cost matrix.
     * @param options
     * @returns - A matrix instance with dimensions rowsArray.length x columnsArray.length
     */
    function xCostMatrix(rowsArray, columnsArray, options = {}) {
      const {
        fct = absDiff
      } = options;
      const nbRows = rowsArray.length;
      const nbColumns = columnsArray.length;
      const result = new Matrix(nbRows, nbColumns);
      for (let r = 0; r < nbRows; r++) {
        for (let c = 0; c < nbColumns; c++) {
          result.set(r, c, fct(rowsArray[r], columnsArray[c]));
        }
      }
      return result;
    }

    /**
     * Calculate an array of the same size that is the cumulative values
     * @param array - initial array
     */
    function xCumulative(array) {
      if (!isAnyArray$1(array)) {
        throw new TypeError('input must be an array');
      }
      const newArray = new Float64Array(array.length);
      if (array.length === 0) return newArray;
      newArray[0] = array[0];
      for (let i = 1; i < array.length; i++) {
        newArray[i] = newArray[i - 1] + array[i];
      }
      return newArray;
    }

    /**
     * Computes the mean value of an array of values.
     * @param array - array of numbers
     * @param options - options
     */
    function xMean(array, options = {}) {
      xCheck(array);
      const {
        fromIndex,
        toIndex
      } = xGetFromToIndex(array, options);
      let sumValue = array[fromIndex];
      for (let i = fromIndex + 1; i <= toIndex; i++) {
        sumValue += array[i];
      }
      return sumValue / (toIndex - fromIndex + 1);
    }

    /**
     * Finds the variance of the data
     * @param values - the values of the array
     * @param options - options
     * @returns variance
     */
    function xVariance(values, options = {}) {
      if (!isAnyArray$1(values)) {
        throw new TypeError('input must be an array');
      }
      const {
        unbiased = true,
        mean = xMean(values)
      } = options;
      let sqrError = 0;
      for (let i = 0; i < values.length; i++) {
        const x = values[i] - mean;
        sqrError += x * x;
      }
      if (unbiased) {
        return sqrError / (values.length - 1);
      } else {
        return sqrError / values.length;
      }
    }

    /**
     * Finds the standard deviation for the data at hand
     * @param values - values in the data
     * @param options - options
     * @returns standard deviation
     */
    function xStandardDeviation(values, options = {}) {
      return Math.sqrt(xVariance(values, options));
    }

    /**
     * Calculate distribution statistics of an array without providing options
     * This ensure that the statistics are calculated in the same way in all the packages
     * If the array is empty it will throw an error
     * If the array has a length of 1, sd will be NaN (unbiased calculation of sd)
     * @param array - data
     * @param options
     * @returns - q1, median, q3, min, max
     */
    function xDistributionStats(array) {
      return {
        ...xBoxPlotWithOutliers(array),
        mean: xMean(array),
        sd: xStandardDeviation(array),
        nb: array.length
      };
    }

    /**
     * This function divide the first array by the second array or a constant value to each element of the first array
     * @param array1 - first array
     * @param array2 - second array or number
     * @param options - options
     */
    function xDivide(array1, array2, options = {}) {
      let isConstant = false;
      let constant = 0;
      if (isAnyArray$1(array2)) {
        if (array1.length !== array2.length) {
          throw new RangeError('size of array1 and array2 must be identical');
        }
      } else {
        isConstant = true;
        constant = array2;
      }
      const array3 = getOutputArray(options.output, array1.length);
      if (isConstant) {
        for (let i = 0; i < array1.length; i++) {
          array3[i] = array1[i] / constant;
        }
      } else {
        for (let i = 0; i < array1.length; i++) {
          array3[i] = array1[i] / array2[i];
        }
      }
      return array3;
    }

    /**
     * Returns a copy of the data as a Float64Array.
     * @param array - array of numbers
     */
    function xEnsureFloat64(array) {
      xCheck(array);
      if (array instanceof Float64Array) {
        return array.slice(0);
      }
      return Float64Array.from(array);
    }

    /**
     *  Returns the targetIndex
     * @param x - array of numbers
     * @param options - options
     */
    function xGetTargetIndex(x, options = {}) {
      const {
        target,
        targetIndex
      } = options;
      if (targetIndex === undefined) {
        if (target !== undefined) {
          return xFindClosestIndex(x, target);
        } else {
          return 0;
        }
      }
      return targetIndex;
    }

    function FFT(size) {
      this.size = size | 0;
      if (this.size <= 1 || (this.size & this.size - 1) !== 0) throw new Error('FFT size must be a power of two and bigger than 1');
      this._csize = size << 1;

      // NOTE: Use of `var` is intentional for old V8 versions
      var table = new Array(this.size * 2);
      for (var i = 0; i < table.length; i += 2) {
        const angle = Math.PI * i / this.size;
        table[i] = Math.cos(angle);
        table[i + 1] = -Math.sin(angle);
      }
      this.table = table;

      // Find size's power of two
      var power = 0;
      for (var t = 1; this.size > t; t <<= 1) power++;

      // Calculate initial step's width:
      //   * If we are full radix-4 - it is 2x smaller to give inital len=8
      //   * Otherwise it is the same as `power` to give len=4
      this._width = power % 2 === 0 ? power - 1 : power;

      // Pre-compute bit-reversal patterns
      this._bitrev = new Array(1 << this._width);
      for (var j = 0; j < this._bitrev.length; j++) {
        this._bitrev[j] = 0;
        for (var shift = 0; shift < this._width; shift += 2) {
          var revShift = this._width - shift - 2;
          this._bitrev[j] |= (j >>> shift & 3) << revShift;
        }
      }
      this._out = null;
      this._data = null;
      this._inv = 0;
    }
    var fft = FFT;
    FFT.prototype.fromComplexArray = function fromComplexArray(complex, storage) {
      var res = storage || new Array(complex.length >>> 1);
      for (var i = 0; i < complex.length; i += 2) res[i >>> 1] = complex[i];
      return res;
    };
    FFT.prototype.createComplexArray = function createComplexArray() {
      const res = new Array(this._csize);
      for (var i = 0; i < res.length; i++) res[i] = 0;
      return res;
    };
    FFT.prototype.toComplexArray = function toComplexArray(input, storage) {
      var res = storage || this.createComplexArray();
      for (var i = 0; i < res.length; i += 2) {
        res[i] = input[i >>> 1];
        res[i + 1] = 0;
      }
      return res;
    };
    FFT.prototype.completeSpectrum = function completeSpectrum(spectrum) {
      var size = this._csize;
      var half = size >>> 1;
      for (var i = 2; i < half; i += 2) {
        spectrum[size - i] = spectrum[i];
        spectrum[size - i + 1] = -spectrum[i + 1];
      }
    };
    FFT.prototype.transform = function transform(out, data) {
      if (out === data) throw new Error('Input and output buffers must be different');
      this._out = out;
      this._data = data;
      this._inv = 0;
      this._transform4();
      this._out = null;
      this._data = null;
    };
    FFT.prototype.realTransform = function realTransform(out, data) {
      if (out === data) throw new Error('Input and output buffers must be different');
      this._out = out;
      this._data = data;
      this._inv = 0;
      this._realTransform4();
      this._out = null;
      this._data = null;
    };
    FFT.prototype.inverseTransform = function inverseTransform(out, data) {
      if (out === data) throw new Error('Input and output buffers must be different');
      this._out = out;
      this._data = data;
      this._inv = 1;
      this._transform4();
      for (var i = 0; i < out.length; i++) out[i] /= this.size;
      this._out = null;
      this._data = null;
    };

    // radix-4 implementation
    //
    // NOTE: Uses of `var` are intentional for older V8 version that do not
    // support both `let compound assignments` and `const phi`
    FFT.prototype._transform4 = function _transform4() {
      var out = this._out;
      var size = this._csize;

      // Initial step (permute and transform)
      var width = this._width;
      var step = 1 << width;
      var len = size / step << 1;
      var outOff;
      var t;
      var bitrev = this._bitrev;
      if (len === 4) {
        for (outOff = 0, t = 0; outOff < size; outOff += len, t++) {
          const off = bitrev[t];
          this._singleTransform2(outOff, off, step);
        }
      } else {
        // len === 8
        for (outOff = 0, t = 0; outOff < size; outOff += len, t++) {
          const off = bitrev[t];
          this._singleTransform4(outOff, off, step);
        }
      }

      // Loop through steps in decreasing order
      var inv = this._inv ? -1 : 1;
      var table = this.table;
      for (step >>= 2; step >= 2; step >>= 2) {
        len = size / step << 1;
        var quarterLen = len >>> 2;

        // Loop through offsets in the data
        for (outOff = 0; outOff < size; outOff += len) {
          // Full case
          var limit = outOff + quarterLen;
          for (var i = outOff, k = 0; i < limit; i += 2, k += step) {
            const A = i;
            const B = A + quarterLen;
            const C = B + quarterLen;
            const D = C + quarterLen;

            // Original values
            const Ar = out[A];
            const Ai = out[A + 1];
            const Br = out[B];
            const Bi = out[B + 1];
            const Cr = out[C];
            const Ci = out[C + 1];
            const Dr = out[D];
            const Di = out[D + 1];

            // Middle values
            const MAr = Ar;
            const MAi = Ai;
            const tableBr = table[k];
            const tableBi = inv * table[k + 1];
            const MBr = Br * tableBr - Bi * tableBi;
            const MBi = Br * tableBi + Bi * tableBr;
            const tableCr = table[2 * k];
            const tableCi = inv * table[2 * k + 1];
            const MCr = Cr * tableCr - Ci * tableCi;
            const MCi = Cr * tableCi + Ci * tableCr;
            const tableDr = table[3 * k];
            const tableDi = inv * table[3 * k + 1];
            const MDr = Dr * tableDr - Di * tableDi;
            const MDi = Dr * tableDi + Di * tableDr;

            // Pre-Final values
            const T0r = MAr + MCr;
            const T0i = MAi + MCi;
            const T1r = MAr - MCr;
            const T1i = MAi - MCi;
            const T2r = MBr + MDr;
            const T2i = MBi + MDi;
            const T3r = inv * (MBr - MDr);
            const T3i = inv * (MBi - MDi);

            // Final values
            const FAr = T0r + T2r;
            const FAi = T0i + T2i;
            const FCr = T0r - T2r;
            const FCi = T0i - T2i;
            const FBr = T1r + T3i;
            const FBi = T1i - T3r;
            const FDr = T1r - T3i;
            const FDi = T1i + T3r;
            out[A] = FAr;
            out[A + 1] = FAi;
            out[B] = FBr;
            out[B + 1] = FBi;
            out[C] = FCr;
            out[C + 1] = FCi;
            out[D] = FDr;
            out[D + 1] = FDi;
          }
        }
      }
    };

    // radix-2 implementation
    //
    // NOTE: Only called for len=4
    FFT.prototype._singleTransform2 = function _singleTransform2(outOff, off, step) {
      const out = this._out;
      const data = this._data;
      const evenR = data[off];
      const evenI = data[off + 1];
      const oddR = data[off + step];
      const oddI = data[off + step + 1];
      const leftR = evenR + oddR;
      const leftI = evenI + oddI;
      const rightR = evenR - oddR;
      const rightI = evenI - oddI;
      out[outOff] = leftR;
      out[outOff + 1] = leftI;
      out[outOff + 2] = rightR;
      out[outOff + 3] = rightI;
    };

    // radix-4
    //
    // NOTE: Only called for len=8
    FFT.prototype._singleTransform4 = function _singleTransform4(outOff, off, step) {
      const out = this._out;
      const data = this._data;
      const inv = this._inv ? -1 : 1;
      const step2 = step * 2;
      const step3 = step * 3;

      // Original values
      const Ar = data[off];
      const Ai = data[off + 1];
      const Br = data[off + step];
      const Bi = data[off + step + 1];
      const Cr = data[off + step2];
      const Ci = data[off + step2 + 1];
      const Dr = data[off + step3];
      const Di = data[off + step3 + 1];

      // Pre-Final values
      const T0r = Ar + Cr;
      const T0i = Ai + Ci;
      const T1r = Ar - Cr;
      const T1i = Ai - Ci;
      const T2r = Br + Dr;
      const T2i = Bi + Di;
      const T3r = inv * (Br - Dr);
      const T3i = inv * (Bi - Di);

      // Final values
      const FAr = T0r + T2r;
      const FAi = T0i + T2i;
      const FBr = T1r + T3i;
      const FBi = T1i - T3r;
      const FCr = T0r - T2r;
      const FCi = T0i - T2i;
      const FDr = T1r - T3i;
      const FDi = T1i + T3r;
      out[outOff] = FAr;
      out[outOff + 1] = FAi;
      out[outOff + 2] = FBr;
      out[outOff + 3] = FBi;
      out[outOff + 4] = FCr;
      out[outOff + 5] = FCi;
      out[outOff + 6] = FDr;
      out[outOff + 7] = FDi;
    };

    // Real input radix-4 implementation
    FFT.prototype._realTransform4 = function _realTransform4() {
      var out = this._out;
      var size = this._csize;

      // Initial step (permute and transform)
      var width = this._width;
      var step = 1 << width;
      var len = size / step << 1;
      var outOff;
      var t;
      var bitrev = this._bitrev;
      if (len === 4) {
        for (outOff = 0, t = 0; outOff < size; outOff += len, t++) {
          const off = bitrev[t];
          this._singleRealTransform2(outOff, off >>> 1, step >>> 1);
        }
      } else {
        // len === 8
        for (outOff = 0, t = 0; outOff < size; outOff += len, t++) {
          const off = bitrev[t];
          this._singleRealTransform4(outOff, off >>> 1, step >>> 1);
        }
      }

      // Loop through steps in decreasing order
      var inv = this._inv ? -1 : 1;
      var table = this.table;
      for (step >>= 2; step >= 2; step >>= 2) {
        len = size / step << 1;
        var halfLen = len >>> 1;
        var quarterLen = halfLen >>> 1;
        var hquarterLen = quarterLen >>> 1;

        // Loop through offsets in the data
        for (outOff = 0; outOff < size; outOff += len) {
          for (var i = 0, k = 0; i <= hquarterLen; i += 2, k += step) {
            var A = outOff + i;
            var B = A + quarterLen;
            var C = B + quarterLen;
            var D = C + quarterLen;

            // Original values
            var Ar = out[A];
            var Ai = out[A + 1];
            var Br = out[B];
            var Bi = out[B + 1];
            var Cr = out[C];
            var Ci = out[C + 1];
            var Dr = out[D];
            var Di = out[D + 1];

            // Middle values
            var MAr = Ar;
            var MAi = Ai;
            var tableBr = table[k];
            var tableBi = inv * table[k + 1];
            var MBr = Br * tableBr - Bi * tableBi;
            var MBi = Br * tableBi + Bi * tableBr;
            var tableCr = table[2 * k];
            var tableCi = inv * table[2 * k + 1];
            var MCr = Cr * tableCr - Ci * tableCi;
            var MCi = Cr * tableCi + Ci * tableCr;
            var tableDr = table[3 * k];
            var tableDi = inv * table[3 * k + 1];
            var MDr = Dr * tableDr - Di * tableDi;
            var MDi = Dr * tableDi + Di * tableDr;

            // Pre-Final values
            var T0r = MAr + MCr;
            var T0i = MAi + MCi;
            var T1r = MAr - MCr;
            var T1i = MAi - MCi;
            var T2r = MBr + MDr;
            var T2i = MBi + MDi;
            var T3r = inv * (MBr - MDr);
            var T3i = inv * (MBi - MDi);

            // Final values
            var FAr = T0r + T2r;
            var FAi = T0i + T2i;
            var FBr = T1r + T3i;
            var FBi = T1i - T3r;
            out[A] = FAr;
            out[A + 1] = FAi;
            out[B] = FBr;
            out[B + 1] = FBi;

            // Output final middle point
            if (i === 0) {
              var FCr = T0r - T2r;
              var FCi = T0i - T2i;
              out[C] = FCr;
              out[C + 1] = FCi;
              continue;
            }

            // Do not overwrite ourselves
            if (i === hquarterLen) continue;

            // In the flipped case:
            // MAi = -MAi
            // MBr=-MBi, MBi=-MBr
            // MCr=-MCr
            // MDr=MDi, MDi=MDr
            var ST0r = T1r;
            var ST0i = -T1i;
            var ST1r = T0r;
            var ST1i = -T0i;
            var ST2r = -inv * T3i;
            var ST2i = -inv * T3r;
            var ST3r = -inv * T2i;
            var ST3i = -inv * T2r;
            var SFAr = ST0r + ST2r;
            var SFAi = ST0i + ST2i;
            var SFBr = ST1r + ST3i;
            var SFBi = ST1i - ST3r;
            var SA = outOff + quarterLen - i;
            var SB = outOff + halfLen - i;
            out[SA] = SFAr;
            out[SA + 1] = SFAi;
            out[SB] = SFBr;
            out[SB + 1] = SFBi;
          }
        }
      }
    };

    // radix-2 implementation
    //
    // NOTE: Only called for len=4
    FFT.prototype._singleRealTransform2 = function _singleRealTransform2(outOff, off, step) {
      const out = this._out;
      const data = this._data;
      const evenR = data[off];
      const oddR = data[off + step];
      const leftR = evenR + oddR;
      const rightR = evenR - oddR;
      out[outOff] = leftR;
      out[outOff + 1] = 0;
      out[outOff + 2] = rightR;
      out[outOff + 3] = 0;
    };

    // radix-4
    //
    // NOTE: Only called for len=8
    FFT.prototype._singleRealTransform4 = function _singleRealTransform4(outOff, off, step) {
      const out = this._out;
      const data = this._data;
      const inv = this._inv ? -1 : 1;
      const step2 = step * 2;
      const step3 = step * 3;

      // Original values
      const Ar = data[off];
      const Br = data[off + step];
      const Cr = data[off + step2];
      const Dr = data[off + step3];

      // Pre-Final values
      const T0r = Ar + Cr;
      const T1r = Ar - Cr;
      const T2r = Br + Dr;
      const T3r = inv * (Br - Dr);

      // Final values
      const FAr = T0r + T2r;
      const FBr = T1r;
      const FBi = -T3r;
      const FCr = T0r - T2r;
      const FDr = T1r;
      const FDi = T3r;
      out[outOff] = FAr;
      out[outOff + 1] = 0;
      out[outOff + 2] = FBr;
      out[outOff + 3] = FBi;
      out[outOff + 4] = FCr;
      out[outOff + 5] = 0;
      out[outOff + 6] = FDr;
      out[outOff + 7] = FDi;
    };
    var FFT$1 = /*@__PURE__*/getDefaultExportFromCjs(fft);

    function createNumberArray(ArrayConstructor, length) {
      if (ArrayConstructor === Array) {
        return new ArrayConstructor(length).fill(0);
      } else {
        return new ArrayConstructor(length);
      }
    }
    function createDoubleArray(ArrayConstructor, length) {
      if (ArrayConstructor === Array) {
        return new ArrayConstructor(length).fill(0);
      } else {
        return new ArrayConstructor(length);
      }
    }

    /**
     * Create an array with numbers between "from" and "to" of length "length"
     * @param options - options
     * @returns - array of distributed numbers between "from" and "to"
     */
    function createFromToArray(options = {}) {
      const {
        from = 0,
        to = 1,
        length = 1000,
        includeFrom = true,
        includeTo = true,
        distribution = 'uniform'
      } = options;
      const array = new Float64Array(length);
      let div = length;
      if (includeFrom && includeTo) {
        div = length - 1;
      } else if (!includeFrom && includeTo || includeFrom && !includeTo) {
        div = length;
      } else if (!includeFrom && !includeTo) {
        div = length + 1;
      }
      const delta = (to - from) / div;
      if (distribution === 'uniform') {
        if (includeFrom) {
          let index = 0;
          while (index < length) {
            array[index] = from + delta * index;
            index++;
          }
        } else {
          let index = 0;
          while (index < length) {
            array[index] = from + delta * (index + 1);
            index++;
          }
        }
      } else if (distribution === 'log') {
        const base = (to / from) ** (1 / div);
        const firstExponent = Math.log(from) / Math.log(base);
        if (includeFrom) {
          let index = 0;
          while (index < length) {
            array[index] = base ** (firstExponent + index);
            index++;
          }
        } else {
          let index = 0;
          while (index < length) {
            array[index] = base ** (firstExponent + index + 1);
            index++;
          }
        }
      } else {
        throw new Error('distribution must be uniform or log');
      }
      return array;
    }

    const LOOP = 8;
    const FLOAT_MUL = 1 / 16777216;
    const sh1 = 15;
    const sh2 = 18;
    const sh3 = 11;
    function multiplyUint32(n, m) {
      n >>>= 0;
      m >>>= 0;
      const nlo = n & 0xffff;
      const nhi = n - nlo;
      return (nhi * m >>> 0) + nlo * m >>> 0;
    }
    class XSadd {
      random;
      state;
      /**
       * create an instance of XSadd with the specified seed
       * @param [seed=Date.now()]
       */
      constructor(seed = Date.now()) {
        this.state = new Uint32Array(4);
        this.init(seed);
        this.random = this.getFloat.bind(this);
      }
      /**
       * Returns a 32-bit integer r (0 <= r < 2^32)
       */
      getUint32() {
        this.nextState();
        return this.state[3] + this.state[2] >>> 0;
      }
      /**
       * Returns a floating point number r (0.0 <= r < 1.0)
       */
      getFloat() {
        return (this.getUint32() >>> 8) * FLOAT_MUL;
      }
      init(seed) {
        if (!Number.isInteger(seed)) {
          throw new TypeError("seed must be an integer");
        }
        this.state[0] = seed;
        this.state[1] = 0;
        this.state[2] = 0;
        this.state[3] = 0;
        for (let i = 1; i < LOOP; i++) {
          this.state[i & 3] ^= i + multiplyUint32(1812433253, this.state[i - 1 & 3] ^ this.state[i - 1 & 3] >>> 30 >>> 0) >>> 0;
        }
        this.periodCertification();
        for (let i = 0; i < LOOP; i++) {
          this.nextState();
        }
      }
      periodCertification() {
        if (this.state[0] === 0 && this.state[1] === 0 && this.state[2] === 0 && this.state[3] === 0) {
          this.state[0] = 88; // X
          this.state[1] = 83; // S
          this.state[2] = 65; // A
          this.state[3] = 68; // D
        }
      }
      nextState() {
        let t = this.state[0];
        t ^= t << sh1;
        t ^= t >>> sh2;
        t ^= this.state[3] << sh3;
        this.state[0] = this.state[1];
        this.state[1] = this.state[2];
        this.state[2] = this.state[3];
        this.state[3] = t;
      }
    }

    /**
     * Create a random array of numbers of a specific length.
     * @param options
     * @returns - array of random floats normally distributed
     */
    function createRandomArray(options = {}) {
      const {
        mean = 0,
        standardDeviation = 1,
        length = 1000,
        range = 1,
        seed,
        distribution = 'normal'
      } = options;
      const generator = new XSadd(seed);
      const returnArray = new Float64Array(length);
      switch (distribution) {
        case 'normal':
          {
            const gaussianGenerator = new GaussianGenerator(mean, standardDeviation, generator);
            for (let i = 0; i < length; i++) {
              returnArray[i] = gaussianGenerator.generateGaussian();
            }
            break;
          }
        case 'uniform':
          {
            for (let i = 0; i < length; i++) {
              returnArray[i] = (generator.random() - 0.5) * range + mean;
            }
            break;
          }
        default:
          {
            throw new Error(`unknown distribution: ${String(distribution)}`);
          }
      }
      return returnArray;
    }
    class GaussianGenerator {
      #spare = 0;
      #hasSpare = false;
      #mean;
      #standardDeviation;
      #generator;
      constructor(mean, standardDeviation, generator) {
        this.#mean = mean;
        this.#standardDeviation = standardDeviation;
        this.#generator = generator;
      }
      generateGaussian() {
        let val, u, v, s;
        if (this.#hasSpare) {
          this.#hasSpare = false;
          val = this.#spare * this.#standardDeviation + this.#mean;
        } else {
          do {
            u = this.#generator.random() * 2 - 1;
            v = this.#generator.random() * 2 - 1;
            s = u * u + v * v;
          } while (s >= 1 || s === 0);
          s = Math.sqrt(-2 * Math.log(s) / s);
          this.#spare = v * s;
          this.#hasSpare = true;
          val = this.#mean + this.#standardDeviation * u * s;
        }
        return val;
      }
    }

    /**
     * Create an array with numbers starting from "from" with step "step" of length "length".
     * @param options - options
     * @returns - array of distributed numbers with step "step" from "from"
     */
    function createStepArray(options = {}) {
      const {
        from = 0,
        step = 1,
        length = 1000
      } = options;
      const array = new Float64Array(length);
      let index = 0;
      while (index < length) {
        array[index] = from + step * index;
        index++;
      }
      return array;
    }

    function getRescaler(options = {}) {
      const {
        targetMin = 0,
        targetMax = 1,
        clamp = true,
        algorithmOptions = {},
        algorithm = 'linear'
      } = options;
      let {
        originalMin = 0,
        originalMax = 1
      } = options;
      const convert = getDataConverter(algorithm, algorithmOptions);
      originalMin = convert(originalMin);
      originalMax = convert(originalMax);
      const originalRange = originalMax - originalMin;
      const targetRange = targetMax - targetMin;
      return function rescaler(value) {
        value = convert(value);
        value = checkRange(value, originalMin, originalMax, clamp);
        const valueScaled = (value - originalMin) / originalRange;
        return targetMin + valueScaled * targetRange;
      };
    }
    function getDataConverter(kind = 'linear', options = {}) {
      return value => {
        switch (kind) {
          case 'linear':
            return value;
          case 'logarithmic':
            return Math.log10(value);
          case 'power':
            return value ** (options.power || 2);
          default:
            throw new Error(`Unknown kind ${String(kind)}`);
        }
      };
    }
    function checkRange(value, min, max, clamp = true) {
      if (value < min) {
        if (clamp) return min;
        throw new RangeError(`Value ${value} is out of range [${min}, ${max}]`);
      }
      if (value > max) {
        if (clamp) return max;
        throw new RangeError(`Value ${value} is out of range [${min}, ${max}]`);
      }
      return value;
    }

    /**
     * Check if a number is a power of two.
     * @param n
     */
    function isPowerOfTwo(n) {
      return n !== 0 && (n & n - 1) === 0;
    }

    /**
     * Get the size of the next power of two.
     * @param n
     */
    function nextPowerOfTwo(n) {
      if (n === 0) return 1;
      n--;
      n |= n >> 1;
      n |= n >> 2;
      n |= n >> 4;
      n |= n >> 8;
      n |= n >> 16;
      return n + 1;
    }

    /**
     * Resolves all promises in an object recursively. The promise with be replaced by the resolved value.
     * The changes are therefore done in-place !
     * @param object
     * @returns
     */
    async function recursiveResolve(object) {
      if (typeof object !== 'object') return object;
      const promises = [];
      await appendPromises(object, promises);
      await Promise.all(promises);
      return object;
    }
    function appendPromises(object, promises) {
      if (typeof object !== 'object') return object;
      for (const key in object) {
        if (typeof object[key].then === 'function') {
          promises.push(object[key].then(value => object[key] = value));
        } else if (typeof object[key] === 'object') {
          appendPromises(object[key], promises);
        }
      }
      return object;
    }

    /**
     * Recursively change the typed arrays to normal arrays
     * The changes are done in-place !
     * @param object
     * @returns
     */
    function recursiveUntypeArrays(object) {
      if (typeof object !== 'object') return object;
      object = modifier(object);
      return object;
    }
    function modifier(object) {
      if (typeof object !== 'object') return object;
      if (ArrayBuffer.isView(object)) {
        return Array.from(object);
      }
      for (const key in object) {
        if (ArrayBuffer.isView(object[key])) {
          object[key] = Array.from(object[key]);
        } else if (typeof object[key] === 'object') {
          modifier(object[key]);
        }
      }
      return object;
    }

    /* eslint-disable @typescript-eslint/no-explicit-any */
    /**
     * Stringify an object and convert all typed arrays to arrays
     * @param object
     * @param replacer
     * @param space
     * @returns
     */
    function stringify$1(object, replacer, space) {
      const internalReplacer = (key, value) => {
        if (ArrayBuffer.isView(value)) {
          value = Array.from(value);
        }
        if (replacer) {
          return replacer(key, value);
        }
        return value;
      };
      return JSON.stringify(object, internalReplacer, space);
    }

    /**
     * This function xSubtract the first array by the second array or a constant value from each element of the first array
     * @param array1 - the array that will be rotated
     * @param array2 - second array or number
     * @returns array after subtraction
     */
    function xSubtract(array1, array2) {
      let isConstant = false;
      let constant = 0;
      if (isAnyArray$1(array2)) {
        if (array1.length !== array2.length) {
          throw new Error('size of array1 and array2 must be identical');
        }
      } else {
        isConstant = true;
        constant = array2;
      }
      const array3 = new Float64Array(array1.length);
      if (isConstant) {
        for (let i = 0; i < array1.length; i++) {
          array3[i] = array1[i] - constant;
        }
      } else {
        for (let i = 0; i < array1.length; i++) {
          array3[i] = array1[i] - array2[i];
        }
      }
      return array3;
    }

    /**
     * Calculate the weights based on the control points and the MAD between the original data and the new baseline.
     * MAD (Median Absolute Deviation) is more robust to outliers and
     * the factor (1.4826) makes MAD scaled to be equivalent to the standard deviation for
     * normal distributions. {@link https://en.m.wikipedia.org/wiki/Median_absolute_deviation}.
     * @param yData - The original data.
     * @param baseline - The new baseline calculated.
     * @param weights - The current weights to be updated.
     * @param options - Options for updating weights.
     * @returns new array of weights.
     */
    function calculateAdaptiveWeights(yData, baseline, weights, options) {
      const {
        controlPoints,
        factorStd = 3,
        learningRate = 0.5,
        minWeight = 0.01
      } = options;
      const absResiduals = xAbsolute(xSubtract(yData, baseline));
      const medAbsRes = xMedian(absResiduals);
      const mad = 1.4826 * medAbsRes;
      const threshold = factorStd * mad;
      const rawWeights = new Float64Array(absResiduals.length);
      for (let i = 0; i < absResiduals.length; i++) {
        rawWeights[i] = Math.exp(-((absResiduals[i] / threshold) ** 2));
      }
      let maxWeight = Number.MIN_SAFE_INTEGER;
      const newWeights = Float64Array.from(weights);
      const oneMinusLearningRate = 1 - learningRate;
      for (let i = 0; i < newWeights.length; i++) {
        if (controlPoints && controlPoints[i] > 0) continue;
        const weight = Math.max(minWeight, oneMinusLearningRate * weights[i] + learningRate * rawWeights[i]);
        newWeights[i] = weight;
        maxWeight = Math.max(maxWeight, weight);
      }
      newWeights[0] = maxWeight;
      newWeights[weights.length - 1] = maxWeight;
      return newWeights;
    }

    /**
     * Performs the Hilbert transform
     * @link https://en.wikipedia.org/wiki/Hilbert_transform
     * @param array - Array containing values
     * @param options
     * @returns A new vector with 90 degree shift regarding the phase of the original function
     */
    function xHilbertTransform(array, options = {}) {
      xCheck(array);
      const {
        forceFFT = false
      } = options;
      const length = array.length;
      if (isPowerOfTwo(length)) {
        return hilbertTransformWithFFT(array);
      } else if (forceFFT) {
        return resampling(hilbertTransformWithFFT(resampling(array, nextPowerOfTwo(length))), length);
      } else {
        return hilbertTransform(array);
      }
    }
    /**
     * Performs the discrete Hilbert transform using fast Fourier transform
     * @param array - Array containing values
     * @returns A new vector with 90 degree shift regarding the phase of the original function
     * @see DOI: 10.1109/TAU.1970.1162139 "Discrete Hilbert transform"
     */
    function hilbertTransformWithFFT(array) {
      const length = array.length;
      const fft = new FFT$1(length);
      const fftResult = new Float64Array(length * 2);
      fft.realTransform(fftResult, array);
      fft.completeSpectrum(fftResult);
      const multiplier = new Float64Array(length);
      for (let i = 1; i < length; i++) {
        multiplier[i] = Math.sign(length / 2 - i);
      }
      for (let i = 0; i < length; i++) {
        fftResult[i * 2] *= multiplier[i];
        fftResult[i * 2 + 1] *= multiplier[i];
      }
      const hilbertSignal = new Float64Array(length * 2);
      fft.inverseTransform(hilbertSignal, fftResult);
      const result = new Float64Array(length);
      for (let i = 0; i < length; i++) {
        result[i] = hilbertSignal[i * 2 + 1];
      }
      return result;
    }
    /**
     * Performs the discrete Hilbert transform
     * @param array - Array containing values
     * @param options
     * @param options.inClockwise
     * @returns A new vector with 90 degree shift regarding the phase of the original function
     */
    function hilbertTransform(array, options = {}) {
      const {
        inClockwise = true
      } = options;
      const input = [0, ...array, 0];
      const result = new Float64Array(array.length);
      for (let k = 1; k < input.length - 1; k++) {
        let aSum = 0;
        for (let i = 0; i < k - 1; i++) {
          const log = Math.log((k - i) / (k - i - 1));
          aSum += input[i] * log + (input[i + 1] - input[i]) * (-1 + (k - i) * log);
        }
        const b = input[k - 1] - input[k + 1];
        let cSum = 0;
        for (let i = k + 1; i < input.length - 1; i++) {
          const log = Math.log((i - k) / (i - k + 1));
          cSum += input[i] * log + (input[i - 1] - input[i]) * (1 + (i - k) * log);
        }
        result[k - 1] = (inClockwise ? 1 : -1) * (aSum + b + cSum) / Math.PI;
      }
      return result;
    }
    /**
     * Performs resampling of an input array to the desired length employing linear interpolation.
     * @param array - Array containing values.
     * @param length - The length of the resulting array.
     * @returns It returns a new array of the desired length.
     * @link https://en.wikipedia.org/wiki/Sample-rate_conversion
     */
    function resampling(array, length) {
      xCheck(array);
      const oldLength = array.length;
      const ratio = (oldLength - 1) / (length - 1);
      const result = new Float64Array(length);
      let currentIndex = 0;
      let floor = Math.floor(currentIndex);
      let ceil = Math.min(Math.ceil(currentIndex), oldLength - 1);
      let diff = currentIndex - floor;
      for (let i = 0; i < length; i++) {
        result[i] = array[floor] * (1 - diff) + array[ceil] * diff;
        currentIndex += ratio;
        floor = Math.floor(currentIndex);
        ceil = Math.min(Math.ceil(currentIndex), oldLength - 1);
        diff = currentIndex - floor;
      }
      return result;
    }

    /**
     * Computes the maximal value of an array of values
     * @param array - array of numbers
     * @param options - options
     */
    function xMaxValue(array, options = {}) {
      xCheck(array);
      const {
        fromIndex,
        toIndex
      } = xGetFromToIndex(array, options);
      let maxValue = array[fromIndex];
      for (let i = fromIndex + 1; i <= toIndex; i++) {
        if (array[i] > maxValue) {
          maxValue = array[i];
        }
      }
      return maxValue;
    }

    /**
     * Computes the minimal value of an array of values.
     * @param array - array of numbers
     * @param options - options
     */
    function xMinValue(array, options = {}) {
      xCheck(array);
      const {
        fromIndex,
        toIndex
      } = xGetFromToIndex(array, options);
      let minValue = array[fromIndex];
      for (let i = fromIndex + 1; i <= toIndex; i++) {
        if (array[i] < minValue) {
          minValue = array[i];
        }
      }
      return minValue;
    }

    /**
     * Calculates a histogram of defined number of slots
     * @param array - Array containing values
     * @param options - options
     * @returns - result of the histogram
     */
    function xHistogram(array, options = {}) {
      xCheck(array);
      const histogram = options.histogram;
      const {
        centerX = true,
        nbSlots = histogram === undefined ? 256 : histogram.x.length,
        logBaseX,
        logBaseY,
        absolute = false
      } = options;
      if (absolute) {
        array = xAbsolute(array);
      }
      if (logBaseX) {
        array = array.slice();
        const logOfBase = Math.log10(logBaseX);
        for (let i = 0; i < array.length; i++) {
          array[i] = Math.log10(array[i]) / logOfBase;
        }
      }
      const {
        min = xMinValue(array),
        max = xMaxValue(array)
      } = options;
      const slotSize = (max - min) / (nbSlots + Number.EPSILON);
      const y = histogram === undefined ? new Float64Array(nbSlots) : histogram.y;
      const x = histogram === undefined ? Array.from(createFromToArray({
        from: min + (centerX ? slotSize / 2 : 0),
        to: max - (centerX ? slotSize / 2 : 0),
        length: nbSlots
      })) : histogram.x;
      for (const element of array) {
        const index = Math.max(Math.min(Math.floor((element - min - Number.EPSILON) / slotSize), nbSlots - 1), 0);
        y[index]++;
      }
      if (logBaseY) {
        const logOfBase = Math.log10(logBaseY);
        for (let i = 0; i < y.length; i++) {
          y[i] = Math.log10(y[i] + 1) / logOfBase;
        }
      }
      return {
        x,
        y
      };
    }

    /**
     * Check if the values are separated always by the same difference
     * @param array - Monotone growing array of number
     * @param options
     */
    function xIsEquallySpaced(array, options = {}) {
      if (array.length < 3) return true;
      const {
        tolerance = 0.05
      } = options;
      let maxDx = 0;
      let minDx = Number.MAX_SAFE_INTEGER;
      for (let i = 0; i < array.length - 1; ++i) {
        const absoluteDifference = array[i + 1] - array[i];
        if (absoluteDifference < minDx) {
          minDx = absoluteDifference;
        }
        if (absoluteDifference > maxDx) {
          maxDx = absoluteDifference;
        }
      }
      return (maxDx - minDx) / maxDx < tolerance;
    }

    /**
     * Returns true if x is monotonic.
     * @param array - array of numbers.
     * @returns 1 if monotonic increasing, -1 if monotonic decreasing, 0 if not monotonic.
     */
    function xIsMonotonic(array) {
      if (array.length <= 2) {
        return 1;
      }
      if (array[0] === array[1]) {
        // maybe a constant series
        for (let i = 1; i < array.length - 1; i++) {
          if (array[i] !== array[i + 1]) return 0;
        }
        return 1;
      }
      if (array[0] < array.at(-1)) {
        for (let i = 0; i < array.length - 1; i++) {
          if (array[i] >= array[i + 1]) return 0;
        }
        return 1;
      } else {
        for (let i = 0; i < array.length - 1; i++) {
          if (array[i] <= array[i + 1]) return 0;
        }
        return -1;
      }
    }

    /**
     * Check the similarity between array created by xyMassCenterVector
     * @param array1
     * @param array2
     * @param options
     * @returns
     */
    function xMassCenterVectorSimilarity(array1, array2, options = {}) {
      const {
        recenter = true,
        similarityFct = (a, b) => a === b ? 1 : 0
      } = options;
      const depth1 = getDepth(array1);
      const depth2 = getDepth(array2);
      const depth = Math.min(depth1, depth2);
      // need to copy the array because we shift the data in place if recenter is true
      if (recenter) {
        array1 = array1.slice();
      }
      let similarity = 0;
      // we will compare level by level
      // and recenter the array at each level
      for (let level = 0; level < depth; level++) {
        const maxSimilarity = 1 / depth / (1 << level);
        for (let slot = 0; slot < 1 << level; slot++) {
          const index = (1 << level) - 1 + slot;
          const value1 = array1[index];
          const value2 = array2[index];
          similarity += similarityFct(value1, value2) * maxSimilarity;
          if (recenter) {
            shiftSubTree(array1, depth, level, slot, value2 - value1);
          }
        }
      }
      return similarity;
    }
    function shiftSubTree(array, depth, level, slot, shift) {
      for (let currentLevel = level; currentLevel < depth; currentLevel++) {
        const levelSlotShift = slot * (1 << currentLevel - level);
        const levelShift = (1 << currentLevel) - 1;
        const levelSlotSize = 1 << currentLevel - level;
        for (let slotIndex = levelSlotShift; slotIndex < levelSlotShift + levelSlotSize; slotIndex++) {
          const index = levelShift + slotIndex;
          array[index] += shift;
        }
      }
    }
    function getDepth(array) {
      const depth = Math.log2(array.length + 1);
      if (depth % 1 !== 0) {
        throw new Error('the array length is not a power of 2 minus 1');
      }
      return depth;
    }

    /**
     * Computes the maximal value of an array of values
     * @param array - array of numbers
     * @param options - options
     */
    function xMaxAbsoluteValue(array, options = {}) {
      xCheck(array);
      const {
        fromIndex,
        toIndex
      } = xGetFromToIndex(array, options);
      let maxValue = array[fromIndex];
      for (let i = fromIndex + 1; i <= toIndex; i++) {
        if (array[i] >= 0) {
          if (array[i] > maxValue) {
            maxValue = array[i];
          }
        } else if (-array[i] > maxValue) {
          maxValue = -array[i];
        }
      }
      return maxValue;
    }

    /**
     * Computes the index of the maximum of the given values
     * @param array - array of numbers
     * @param  options - options
     * @returns - index
     */
    function xMaxIndex(array, options = {}) {
      xCheck(array);
      const {
        fromIndex,
        toIndex
      } = xGetFromToIndex(array, options);
      let maxIndex = fromIndex;
      for (let i = fromIndex + 1; i <= toIndex; i++) {
        if (array[i] > array[maxIndex]) {
          maxIndex = i;
        }
      }
      return maxIndex;
    }

    /**
     * This function calculates the mean absolute error.
     * @param array1 - first array
     * @param array2 - second array
     */
    function xMeanAbsoluteError(array1, array2) {
      xCheckLengths(array1, array2);
      let sum = 0;
      for (let i = 0; i < array1.length; i++) {
        sum += Math.abs(array1[i] - array2[i]);
      }
      return sum / array1.length;
    }

    /**
     * This function calculates the mean squared error.
     * @param array1 -first array
     * @param array2 - second array
     */
    function xMeanSquaredError(array1, array2) {
      xCheckLengths(array1, array2);
      let sum = 0;
      for (let i = 0; i < array1.length; i++) {
        sum += (array1[i] - array2[i]) ** 2;
      }
      return sum / array1.length;
    }

    /**
     * Computes the weighted mean value of an array of values.
     * @deprecated please use xyMassCenter
     * @param array - array of numbers
     * @param weights - array of weights
     * @param options - options
     */
    function xMeanWeighted(array, weights, options = {}) {
      xCheck(array, {
        minLength: 1
      });
      xCheck(weights);
      if (array.length !== weights.length) {
        throw new Error('array and weights must have the same length');
      }
      const {
        fromIndex,
        toIndex
      } = xGetFromToIndex(array, options);
      // normalize weights
      let sumWeights = 0;
      let sumValue = array[fromIndex] * weights[fromIndex];
      sumWeights += weights[fromIndex];
      for (let i = fromIndex + 1; i <= toIndex; i++) {
        sumValue += array[i] * weights[i];
        sumWeights += weights[i];
      }
      if (sumWeights === 0) {
        // We throw because the case is not well defined
        throw new Error('sum of weights must be > 0');
      }
      return sumValue / sumWeights;
    }

    /**
     * This function calculates the median absolute deviation (MAD).
     * https://en.wikipedia.org/wiki/Median_absolute_deviation
     * @param array
     */
    function xMedianAbsoluteDeviation(array) {
      const median = xMedian(array);
      const averageDeviations = new Float64Array(array.length);
      for (let i = 0; i < array.length; i++) {
        averageDeviations[i] = Math.abs(array[i] - median);
      }
      return {
        median,
        mad: xMedian(averageDeviations)
      };
    }

    /**
     * Computes the index of the minimum of the given values.
     * @param array - array of numbers
     * @param options - options
     * @returns - index
     */
    function xMinIndex(array, options = {}) {
      xCheck(array);
      const {
        fromIndex,
        toIndex
      } = xGetFromToIndex(array, options);
      let minIndex = fromIndex;
      for (let i = fromIndex + 1; i <= toIndex; i++) {
        if (array[i] < array[minIndex]) {
          minIndex = i;
        }
      }
      return minIndex;
    }

    /**
     * Return min and max values of an array.
     * @param array - array of number
     * @returns - Object with 2 properties, min and max
     */
    function xMinMaxDelta(array) {
      xCheck(array, {
        minLength: 2
      });
      let minDelta = array[1] - array[0];
      let maxDelta = minDelta;
      for (let i = 0; i < array.length - 1; i++) {
        const delta = array[i + 1] - array[i];
        if (delta < minDelta) minDelta = delta;
        if (delta > maxDelta) maxDelta = delta;
      }
      return {
        min: minDelta,
        max: maxDelta
      };
    }

    /**
     * Return min and max values of an array.
     * @param array - array of number
     * @returns - Object with 2 properties, min and max.
     */
    function xMinMaxValues(array) {
      xCheck(array);
      let min = array[0];
      let max = array[0];
      for (const value of array) {
        if (value < min) min = value;
        if (value > max) max = value;
      }
      return {
        min,
        max
      };
    }

    /**
     * Calculates the mode of an array
     * @param input - Array containing values
     * @returns - mode
     */
    function xMode(input) {
      if (input.length === 0) {
        throw new TypeError('input must not be empty');
      }
      let maxValue = 0;
      let maxCount = 0;
      let count = 0;
      const counts = {};
      for (let i = 0; i < input.length; ++i) {
        const element = input[i];
        count = counts[element];
        if (count) {
          counts[element]++;
          count++;
        } else {
          counts[element] = 1;
          count = 1;
        }
        if (count > maxCount) {
          maxCount = count;
          maxValue = input[i];
        }
      }
      return maxValue;
    }

    /*
    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 = [-0.005387729650712429, 8.22687874676915743155e-3, 2.19878681111168899165e-2, -0.03656379714117627, -0.012692614766297404, 3.34806625409744615033e-2, -0.008368748197417368, -5087819496582806e-19];
    const Q1 = [8.86216390456424707504e-4, -0.0023339375937419, 7.95283687341571680018e-2, -0.05273963823400997, -0.7122890234154284, 6.62328840472002992063e-1, 1.56221558398423026363, -1.5657455823417585, -0.9700050433032906, 1];
    // Coefficients for erfcinv for 0.5 > 1-x >= 0:
    const Y2 = 2.249481201171875;
    const P2 = [-3.6719225470772936, 2.11294655448340526258e1, 1.7445385985570866523e1, -44.6382324441787, -18.851064805871424, 1.76447298408374015486e1, 8.37050328343119927838, 1.05264680699391713268e-1, -0.20243350835593876];
    const Q2 = [1.72114765761200282724, -22.643693341313973, 1.08268667355460159008e1, 4.85609213108739935468e1, -20.14326346804852, -28.66081804998, 3.9713437953343869095, 6.24264124854247537712, 1];
    // Coefficients for erfcinv for sqrt( -log(1-x)):
    const Y3 = 8.07220458984375e-1;
    const P3 = [-681149956853777e-24, 2.85225331782217055858e-8, -6.794655751811263e-7, 2.14558995388805277169e-3, 2.90157910005329060432e-2, 1.42869534408157156766e-1, 3.37785538912035898924e-1, 3.87079738972604337464e-1, 1.17030156341995252019e-1, -0.16379404719331705, -0.1311027816799519];
    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, -2304047769118826e-25, 4.60469890584317994083e-6, 1.57544617424960554631e-4, 1.87123492819559223345e-3, 9.50804701325919603619e-3, 1.85573306514231072324e-2, -0.0022242652921344794, -0.0350353787183178];
    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, -2811287356288318e-29, 4.62596163522878599135e-9, 4.49696789927706453732e-7, 1.49624783758342370182e-5, 2.09386317487588078668e-4, 1.05628862152492910091e-3, -0.0011295143874558028, -0.016743100507663373];
    const Q5 = [2.82243172016108031869e-7, 2.75335474764726041141e-5, 9.64011807005165528527e-4, 1.60746087093676504695e-2, 1.38151865749083321638e-1, 5.91429344886417493481e-1, 1];
    /**
     * Polyval.
     * @param c - Array of Number.
     * @param x - Number.
     * @returns Number.
     */
    function polyval(c, x) {
      let p = 0;
      for (const coef of c) {
        p = p * x + coef;
      }
      return p;
    }
    /**
     * Calculates a rational approximation.
     * @private
     * @param x - Number.
     * @param v - Number.
     * @param P - Array of polynomial coefficients.
     * @param Q - Array of polynomial coefficients.
     * @param Y - Number.
     * @returns 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 x - Input value.
     * @returns 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 Number.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.
    }

    /**
     * Applies a simple normalization inverse transformation to the input data.
     * @param data - The input array of numbers to be transformed.
     * @param options - Optional parameters for the transformation.
     * @returns A new Float64Array containing the transformed data.
     */
    function simpleNormInv(data, options = {}) {
      const {
        magnitudeMode = false
      } = options;
      const result = new Float64Array(data.length);
      if (magnitudeMode) {
        for (let i = 0; i < result.length; i++) {
          result[i] = -Math.sqrt(-2 * Math.log(1 - data[i]));
        }
      } else {
        for (let i = 0; i < result.length; i++) {
          result[i] = -1 * Math.SQRT2 * erfcinv(2 * data[i]);
        }
      }
      return result;
    }
    /**
     * Convenience wrapper for single number processing by simpleNormInv function.
     * @param data - The number to be normalized.
     * @param options - The options for the normalization process.
     * @returns The normalized number.
     */
    function simpleNormInvNumber(data, options = {}) {
      return simpleNormInv([data], options)[0];
    }

    /**
     * Determine noise level by san plot methodology (https://doi.org/10.1002/mrc.4882)
     * @param array - real or magnitude spectra data.
     * @param options - options
     * @returns noise level
     */
    function xNoiseSanPlot(array, options = {}) {
      const {
        mask,
        cutOff,
        refine = true,
        magnitudeMode = false,
        scaleFactor = 1,
        factorStd = 5,
        fixOffset = true
      } = options;
      const input = prepareData(array, {
        scaleFactor,
        mask
      });
      if (fixOffset && !magnitudeMode) {
        const medianIndex = Math.floor(input.length / 2);
        const median = input.length % 2 === 0 ? 0.5 * (input[medianIndex - 1] + input[medianIndex]) : input[medianIndex];
        for (let i = 0; i < input.length; i++) {
          input[i] -= median;
        }
      }
      const firstNegativeValueIndex = input.at(-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;
        }
      }
      const signPositive = input.slice(0, lastPositiveValueIndex + 1);
      const signNegative = input.slice(firstNegativeValueIndex);
      const cutOffDist = cutOff || determineCutOff(signPositive, {
        magnitudeMode
      });
      const pIndex = Math.floor(signPositive.length * cutOffDist);
      const initialNoiseLevelPositive = signPositive[pIndex];
      const skyPoint = signPositive[0];
      let initialNoiseLevelNegative;
      if (signNegative.length > 0) {
        const 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))];
        }
      }
      const correctionFactor = -simpleNormInvNumber(cutOffDist / 2, {
        magnitudeMode
      });
      let effectiveCutOffDist, refinedCorrectionFactor;
      if (refine && cutOffSignalsIndexPlus > -1) {
        effectiveCutOffDist = (cutOffDist * cloneSignPositive.length + cutOffSignalsIndexPlus) / (cloneSignPositive.length + cutOffSignalsIndexPlus);
        refinedCorrectionFactor = -1 * simpleNormInvNumber(effectiveCutOffDist / 2, {
          magnitudeMode
        });
        noiseLevelPositive /= refinedCorrectionFactor;
        if (cutOffSignalsIndexNeg > -1) {
          effectiveCutOffDist = (cutOffDist * cloneSignNegative.length + cutOffSignalsIndexNeg) / (cloneSignNegative.length + cutOffSignalsIndexNeg);
          refinedCorrectionFactor = -1 * simpleNormInvNumber(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
            }
          }
        })
      };
    }
    /**
     * Determines the optimal cut-off point for a given array of positive numbers.
     * @param signPositive - An array of positive numbers.
     * @param options - Optional parameters to configure the cut-off determination.
     * @param options.magnitudeMode - If true, uses magnitude mode for normalization. Default is false.
     * @param options.considerList - An object specifying the range and step for consideration.
     * @param options.considerList.from - The starting point of the range. Default is 0.5.
     * @param options.considerList.step - The step size for the range. Default is 0.1.
     * @param options.considerList.to - The ending point of the range. Default is 0.9.
     * @returns The optimal cut-off point as a number.
     */
    function determineCutOff(signPositive, options = {}) {
      const {
        magnitudeMode = false,
        considerList = {
          from: 0.5,
          step: 0.1,
          to: 0.9
        }
      } = options;
      //generate a list of values for
      const cutOff = [];
      const indexMax = signPositive.length - 1;
      for (let i = 0.01; i <= 0.99; i += 0.01) {
        const index = Math.round(indexMax * i);
        const value = -signPositive[index] / simpleNormInvNumber(i / 2, {
          magnitudeMode
        });
        cutOff.push([i, value]);
      }
      let minKi = Number.MAX_SAFE_INTEGER;
      const {
        from,
        to,
        step
      } = considerList;
      const delta = step / 2;
      let whereToCutStat = 0.5;
      for (let i = from; i <= to; i += step) {
        const floor = i - delta;
        const top = i + delta;
        const elementsOfCutOff = cutOff.filter(e => e[0] < top && e[0] > floor);
        let averageValue = 0;
        for (const element of elementsOfCutOff) {
          averageValue += Math.abs(element[1]);
        }
        let kiSqrt = 0;
        for (const element of elementsOfCutOff) {
          kiSqrt += (element[1] - averageValue) ** 2;
        }
        if (kiSqrt < minKi) {
          minKi = kiSqrt;
          whereToCutStat = i;
        }
      }
      return whereToCutStat;
    }
    /**
     * Generates a SAN plot from the given array based on the specified options.
     * @param array - The input array of numbers to be processed.
     * @param options - An optional object containing configuration options.
     * @param options.logBaseY - The logarithmic base for the Y-axis. Defaults to 2.
     * @param options.fromTo - An object specifying the range for each key. Each key maps to an object with `from` and `to` properties.
     * @returns An object where each key maps to a DataXY object containing the processed data.
     */
    function generateSanPlot(array, options = {}) {
      const {
        fromTo,
        logBaseY = 2
      } = options;
      const sanplot = {};
      for (const key in fromTo) {
        const {
          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;
    }
    /**
     * Scales the input array based on the provided options.
     * @param array - The input array to be scaled.
     * @param options - An optional object containing scaling options.
     * @param options.logBaseY - If provided, the array values will be scaled using the logarithm of this base.
     * @returns An object containing the scaled x and y arrays.
     */
    function scale(array, options = {}) {
      const {
        log10,
        abs
      } = Math;
      const {
        logBaseY
      } = options;
      if (logBaseY) {
        array = array.slice(0);
        const logOfBase = log10(logBaseY);
        for (let i = 0; i < array.length; i++) {
          array[i] = log10(abs(array[i])) / logOfBase;
        }
      }
      const xAxis = createFromToArray({
        from: 0,
        to: array.length - 1,
        length: array.length
      });
      return {
        x: xAxis,
        y: array
      };
    }
    /**
     * Prepares and processes the input data array based on the provided options.
     * @param array - The input array of numbers to be processed.
     * @param options - An object containing the following properties:
     *   - scaleFactor: A number by which to scale each element of the array.
     *   - mask: An optional array of the same length as the input array, where
     *           elements corresponding to `true` values will be excluded from processing.
     * @param options.scaleFactor
     * @param options.mask
     * @returns A new Float64Array containing the processed data, scaled by the
     *          scaleFactor and sorted in descending order.
     */
    function prepareData(array, options) {
      const {
        scaleFactor,
        mask
      } = options;
      const input = xEnsureFloat64(isAnyArray$1(mask) && mask.length === array.length ? array.filter((_e, i) => !mask[i]) : array);
      if (scaleFactor > 1) {
        for (let i = 0; i < input.length; i++) {
          input[i] *= scaleFactor;
        }
      }
      return input.sort().reverse();
    }

    /**
     * Determine noise level using MAD https://en.wikipedia.org/wiki/Median_absolute_deviation
     * Constant to convert mad to sd calculated using https://www.wolframalpha.com/input?i=sqrt%282%29+inverse+erf%280.5%29
     * This assumes a gaussian distribution of the noise
     * @param array
     * @returns noise level corresponding to one standard deviation
     */
    function xNoiseStandardDeviation(array) {
      const {
        mad,
        median
      } = xMedianAbsoluteDeviation(array);
      return {
        sd: mad / 0.6744897501960817,
        mad,
        median
      };
    }

    /**
     * This function calculate the norm of a vector.
     * @example xNorm([3, 4]) -> 5
     * @param array - array
     * @returns - calculated norm
     */
    function xNorm(array) {
      let result = 0;
      for (const element of array) {
        result += element ** 2;
      }
      return Math.sqrt(result);
    }

    /**
     * Calculate the sum of the values
     * @param array - Object that contains property x (an ordered increasing array) and y (an array).
     * @param options - Options.
     * @returns XSum value on the specified range.
     */
    function xSum(array, options = {}) {
      xCheck(array);
      const {
        fromIndex,
        toIndex
      } = xGetFromToIndex(array, options);
      let sumValue = array[fromIndex];
      for (let i = fromIndex + 1; i <= toIndex; i++) {
        sumValue += array[i];
      }
      return sumValue;
    }

    /**
     * Divides the data with either the sum, the absolute sum or the maximum of the data
     * @param input - Array containing values
     * @param options - options
     * @returns - normalized data
     */
    function xNormed(input, options = {}) {
      const {
        algorithm = 'absolute',
        value = 1
      } = options;
      xCheck(input);
      const output = getOutputArray(options.output, input.length);
      if (input.length === 0) {
        throw new Error('input must not be empty');
      }
      switch (algorithm) {
        case 'absolute':
          {
            const absoluteSumValue = absoluteSum(input) / value;
            if (absoluteSumValue === 0) {
              throw new Error('trying to divide by 0');
            }
            for (let i = 0; i < input.length; i++) {
              output[i] = input[i] / absoluteSumValue;
            }
            return output;
          }
        case 'max':
          {
            const currentMaxValue = xMaxValue(input);
            if (currentMaxValue === 0) {
              throw new Error('trying to divide by 0');
            }
            const factor = value / currentMaxValue;
            for (let i = 0; i < input.length; i++) {
              output[i] = input[i] * factor;
            }
            return output;
          }
        case 'sum':
          {
            const sumFactor = xSum(input) / value;
            if (sumFactor === 0) {
              throw new Error('trying to divide by 0');
            }
            for (let i = 0; i < input.length; i++) {
              output[i] = input[i] / sumFactor;
            }
            return output;
          }
        default:
          throw new Error(`unknown algorithm: ${String(algorithm)}`);
      }
    }
    function absoluteSum(input) {
      let sumValue = 0;
      for (let i = 0; i < input.length; i++) {
        sumValue += Math.abs(input[i]);
      }
      return sumValue;
    }

    /**
     * This function pads an array
     *s
     * @param array - the array that will be padded
     * @param options - options
     */
    function xPadding(array, options = {}) {
      const {
        size = 0,
        value = 0,
        algorithm
      } = options;
      xCheck(array);
      if (!algorithm) {
        return xEnsureFloat64(array);
      }
      const result = new Float64Array(array.length + size * 2);
      for (let i = 0; i < array.length; i++) {
        result[i + size] = array[i];
      }
      const fromEnd = size + array.length;
      const toEnd = 2 * size + array.length;
      switch (algorithm) {
        case 'value':
          for (let i = 0; i < size; i++) {
            result[i] = value;
          }
          for (let i = fromEnd; i < toEnd; i++) {
            result[i] = value;
          }
          break;
        case 'duplicate':
          for (let i = 0; i < size; i++) {
            result[i] = array[0];
          }
          for (let i = fromEnd; i < toEnd; i++) {
            result[i] = array.at(-1);
          }
          break;
        case 'circular':
          for (let i = 0; i < size; i++) {
            result[i] = array[(array.length - size % array.length + i) % array.length];
          }
          for (let i = 0; i < size; i++) {
            result[i + fromEnd] = array[i % array.length];
          }
          break;
        default:
          throw new Error(`unknown algorithm ${String(algorithm)}`);
      }
      return result;
    }

    /**
     * Pareto scaling, which uses the square root of standard deviation as the scaling factor, circumvents the amplification of noise by retaining a small portion of magnitude information.
     * Noda, I. (2008). Scaling techniques to enhance two-dimensional correlation spectra. Journal of Molecular Structure, 883, 216-227.
     * DOI: 10.1016/j.molstruc.2007.12.026
     * @param array - array of number
     */
    function xParetoNormalization(array) {
      xCheck(array);
      const result = new Float64Array(array.length);
      const sqrtSD = Math.sqrt(xStandardDeviation(array));
      for (let i = 0; i < array.length; i++) {
        result[i] = array[i] / sqrtSD;
      }
      return result;
    }

    /**
     * Calculate distribution statistics of an array without providing options. Statistics
     * like sd and mean are calculated after removing outliers.
     * This ensure that the statistics are calculated in the same way in all the packages
     * If the array is empty it will throw an error
     * If the array has a length of 1, sd will be NaN (unbiased calculation of sd)
     * @param array - data
     * @param options
     * @returns - q1, median, q3, min, max
     */
    function xRobustDistributionStats(array) {
      const boxPlot = xBoxPlotWithOutliers(array);
      let filteredArray;
      if (boxPlot.outliers.length === 0) {
        filteredArray = array;
      } else {
        filteredArray = new Float64Array(array.length - boxPlot.outliers.length);
        let j = 0;
        for (const element of array) {
          if (element >= boxPlot.min && element <= boxPlot.max) {
            filteredArray[j++] = element;
          }
        }
      }
      return {
        ...boxPlot,
        mean: xMean(filteredArray),
        sd: xStandardDeviation(filteredArray),
        nb: filteredArray.length
      };
    }

    /**
     * Remove the outliers from the array
     * Based on 1.5 IQR
     * @param array - data
     * @returns - trimmed data
     */
    function xRemoveOutliersIQR(array) {
      if (!isAnyArray$1(array)) {
        throw new TypeError('input must be an array');
      }
      if (array.length === 0) {
        return array;
      }
      const boxPlot = xBoxPlot(array);
      const iqr = boxPlot.q3 - boxPlot.q1;
      const lowerWhisker = boxPlot.q1 - 1.5 * iqr;
      const higherWhisker = boxPlot.q3 + 1.5 * iqr;
      if (boxPlot.min >= lowerWhisker && boxPlot.max <= higherWhisker) {
        return array;
      }
      const filteredArray = [];
      for (const element of array) {
        if (element >= lowerWhisker && element <= higherWhisker) {
          filteredArray.push(element);
        }
      }
      return filteredArray;
    }

    /**
     * Function used to rescale data
     * @param input - input for the rescale
     * @param options - options
     * @returns rescaled data
     */
    function xRescale(input, options = {}) {
      xCheck(input);
      const output = getOutputArray(options.output, input.length);
      const currentMin = xMinValue(input);
      const currentMax = xMaxValue(input);
      if (currentMin === currentMax) {
        throw new RangeError('minimum and maximum input values are equal. Cannot rescale a constant array');
      }
      const {
        min = 0,
        max = 1
      } = options;
      if (min >= max) {
        throw new RangeError('min option must be smaller than max option');
      }
      const factor = (max - min) / (currentMax - currentMin);
      for (let i = 0; i < input.length; i++) {
        output[i] = (input[i] - currentMin) * factor + min;
      }
      return output;
    }

    /**
     * This function calculates a rolling average
     * @param array - array
     * @param fct - callback function that from an array returns a value
     * @param options - options
     */
    function xRolling(array, fct, options = {}) {
      xCheck(array);
      if (typeof fct !== 'function') {
        throw new TypeError('fct must be a function');
      }
      const {
        window = 5,
        padding = {}
      } = options;
      const {
        size = window - 1,
        algorithm,
        value
      } = padding;
      const padded = xPadding(array, {
        size,
        algorithm,
        value
      }); // ensure we get a copy and it is float64
      const newArray = [];
      for (let i = 0; i < padded.length - window + 1; i++) {
        // we will send a view to the original buffer
        newArray.push(fct(padded.subarray(i, i + window)));
      }
      return newArray;
    }

    /**
     * This function calculates a rolling average
     * @param array - array
     * @param options - option
     */
    function xRollingAverage(array, options = {}) {
      return xRolling(array, xMean, options);
    }

    /**
     * This function calculates a maximum within a rolling window
     * @param array - array
     * @param options - options
     */
    function xRollingMax(array, options = {}) {
      return xRolling(array, xMaxValue, options);
    }

    /**
     * This function calculates a rolling average
     * @param array - array
     * @param options - options
     */
    function xRollingMedian(array, options = {}) {
      return xRolling(array, xMedian, options);
    }

    /**
     * This function calculates a minimum within a rolling window
     * @param array - array
     * @param options - options
     */
    function xRollingMin(array, options = {}) {
      return xRolling(array, xMinValue, options);
    }

    /**
     * This function performs a circular shift to an array.
     * Positive values of shifts will shift to the right and negative values will do to the left.
     * @example xRotate([1,2,3,4],1) -> [4,1,2,3]
     * @example xRotate([1,2,3,4],-1) -> [2,3,4,1]
     * @param array - array
     * @param shift - shift
     * @returns - rotated array
     */
    function xRotate(array, shift) {
      shift = shift % array.length;
      if (shift < 0) shift += array.length;
      const result = new Float64Array(array.length);
      result.set(array.slice(array.length - shift));
      result.set(array.slice(0, array.length - shift), shift);
      return result;
    }

    /**
     * Sample within the array
     * @param array - array from which to sample
     * @param options - options
     * @returns - array with evenly spaced elements
     * @link https://en.wikipedia.org/wiki/Sampling_(signal_processing)
     */
    function xSampling(array, options = {}) {
      const {
        length = 10
      } = options;
      xCheck(array);
      if (length === array.length) {
        return Float64Array.from(array);
      } else if (length > array.length) {
        throw new RangeError('length must be smaller than the array length');
      } else {
        return downSampling(array, length);
      }
    }
    /**
     * Downsample within the array
     * @param array - array from which to sample
     * @param length
     * @returns - array with evenly spaced elements
     * @link https://en.wikipedia.org/wiki/Downsampling_(signal_processing)
     */
    function downSampling(array, length) {
      const returnArray = new Float64Array(length);
      const delta = (array.length - 1) / (length - 1);
      for (let i = 0; i < length; i++) {
        returnArray[i] = array[Math.round(i * delta)];
      }
      return returnArray;
    }

    function xSequentialFillFromStep(parameters, options = {}) {
      const {
        from,
        step,
        size
      } = parameters;
      const {
        ArrayConstructor = Float64Array
      } = options;
      const result = createNumberArray(ArrayConstructor, size);
      for (let i = 0; i < size; i++) {
        result[i] = from + i * step;
      }
      return result;
    }

    function xSequentialFillFromTo(parameters, options = {}) {
      const {
        from,
        to,
        size
      } = parameters;
      const step = (to - from) / (size - 1);
      return xSequentialFillFromStep({
        from,
        step,
        size
      }, options);
    }

    /**
     * Function that sorts arrays or Float64Arrays in ascending order in place !
     * This method is optimized for typed arrays.
     * @param array - array to sort
     * @returns sorted array
     */
    function xSortAscending(array) {
      if (ArrayBuffer.isView(array)) {
        return array.sort();
      } else if (Array.isArray(array)) {
        return array.sort((a, b) => a - b);
      }
      throw new Error('trying to sort non array');
    }

    /**
     * Function that sorts arrays or Float64Arrays in descending order
     * @param array - array to sort
     * @returns sorted array
     */
    function xSortDescending(array) {
      if (ArrayBuffer.isView(array)) {
        return array.sort().reverse();
      } else if (Array.isArray(array)) {
        return array.sort((a, b) => b - a);
      }
      throw new Error('trying to sort non array');
    }

    /**
     * XUniqueSorted.
     * @param array - array of numbers
     * @returns - sorted array
     */
    function xUniqueSorted(array) {
      return Float64Array.from(new Set(array)).sort();
    }

    /*
    The MIT License (MIT)

    Copyright (c) 2013 Eric Arnebäck

    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.
    */
    /**
     * Solves a system of linear equations using the Cholesky decomposition method.
     * It is a direct conversion to TS from {@link https://github.com/scijs/cholesky-solve}
     * @param nonZerosArray - The matrix in triplet form (array of arrays), where each sub-array contains three elements: row index, column index, and value.
     * @param dimension - The order of the matrix (number of rows/columns).
     * @param permutationEncoded - Optional permutation array. If provided, it will be used to permute the matrix.
     * @returns A function that takes a right-hand side vector `b` and returns the solution vector `x`, or `null` if the decomposition fails.
     */
    function matrixCholeskySolver(nonZerosArray, dimension, permutationEncoded) {
      if (permutationEncoded) {
        const pinv = new Array(dimension);
        for (let k = 0; k < dimension; k++) {
          pinv[permutationEncoded[k]] = k;
        }
        const mt = new Array(nonZerosArray.length);
        for (let a = 0; a < nonZerosArray.length; ++a) {
          const [r, c, value] = nonZerosArray[a];
          const [ar, ac] = [pinv[r], pinv[c]];
          mt[a] = ac < ar ? [ac, ar, value] : [ar, ac, value];
        }
        nonZerosArray = mt;
      } else {
        permutationEncoded = [];
        for (let i = 0; i < dimension; ++i) {
          permutationEncoded[i] = i;
        }
      }
      const ap = new Array(dimension + 1);
      const ai = new Array(nonZerosArray.length);
      const ax = new Array(nonZerosArray.length);
      const lnz = [];
      for (let i = 0; i < dimension; ++i) {
        lnz[i] = 0;
      }
      for (const a of nonZerosArray) {
        lnz[a[1]]++;
      }
      ap[0] = 0;
      for (let i = 0; i < dimension; ++i) {
        ap[i + 1] = ap[i] + lnz[i];
      }
      const colOffset = [];
      for (let a = 0; a < dimension; ++a) {
        colOffset[a] = 0;
      }
      for (const e of nonZerosArray) {
        const col = e[1];
        const adr = ap[col] + colOffset[col];
        ai[adr] = e[0];
        ax[adr] = e[2];
        colOffset[col]++;
      }
      const d = new Array(dimension);
      const y = new Array(dimension);
      const lp = new Array(dimension + 1);
      const parent = new Array(dimension);
      const lnzArray = new Array(dimension);
      const flag = new Array(dimension);
      const pattern = new Array(dimension);
      const bp1 = new Array(dimension);
      const x = new Array(dimension);
      ldlSymbolic$1(dimension, ap, ai, lp, parent, lnzArray, flag);
      const lx = new Array(lp[dimension]);
      const li = new Array(lp[dimension]);
      const result = ldlNumeric$1(dimension, ap, ai, ax, lp, parent, lnzArray, li, lx, d, y, pattern, flag);
      if (result === dimension) {
        return b => {
          ldlPerm$1(dimension, bp1, b, permutationEncoded);
          ldlLsolve$1(dimension, bp1, lp, li, lx);
          ldlDsolve$1(dimension, bp1, d);
          ldlLTsolve$1(dimension, bp1, lp, li, lx);
          ldlPermt$1(dimension, x, bp1, permutationEncoded);
          return x;
        };
      } else {
        return null;
      }
    }
    function ldlSymbolic$1(dimension, ap, ai, lp, parent, lnz, flag) {
      for (let k = 0; k < dimension; k++) {
        parent[k] = -1;
        flag[k] = k;
        lnz[k] = 0;
        const kk = k;
        const p2 = ap[kk + 1];
        for (let permutationEncoded = ap[kk]; permutationEncoded < p2; permutationEncoded++) {
          let i = ai[permutationEncoded];
          if (i < k) {
            for (; flag[i] !== k; i = parent[i]) {
              if (parent[i] === -1) parent[i] = k;
              lnz[i]++;
              flag[i] = k;
            }
          }
        }
      }
      lp[0] = 0;
      for (let k = 0; k < dimension; k++) {
        lp[k + 1] = lp[k] + lnz[k];
      }
    }
    function ldlNumeric$1(dimension, ap, ai, ax, lp, parent, lnz, li, lx, d, y, pattern, flag) {
      let yi, lKi;
      let i, k, permutationEncoded, kk, p2, len, top;
      for (k = 0; k < dimension; k++) {
        y[k] = 0;
        top = dimension;
        flag[k] = k;
        lnz[k] = 0;
        kk = k;
        p2 = ap[kk + 1];
        for (permutationEncoded = ap[kk]; permutationEncoded < p2; permutationEncoded++) {
          i = ai[permutationEncoded];
          if (i <= k) {
            y[i] += ax[permutationEncoded];
            for (len = 0; flag[i] !== k; i = parent[i]) {
              pattern[len++] = i;
              flag[i] = k;
            }
            while (len > 0) pattern[--top] = pattern[--len];
          }
        }
        d[k] = y[k];
        y[k] = 0;
        for (; top < dimension; top++) {
          i = pattern[top];
          yi = y[i];
          y[i] = 0;
          p2 = lp[i] + lnz[i];
          for (permutationEncoded = lp[i]; permutationEncoded < p2; permutationEncoded++) {
            y[li[permutationEncoded]] -= lx[permutationEncoded] * yi;
          }
          lKi = yi / d[i];
          d[k] -= lKi * yi;
          li[permutationEncoded] = k;
          lx[permutationEncoded] = lKi;
          lnz[i]++;
        }
        if (d[k] === 0) return k;
      }
      return dimension;
    }
    function ldlLsolve$1(dimension, x, lp, li, lx) {
      let j, permutationEncoded, p2;
      for (j = 0; j < dimension; j++) {
        p2 = lp[j + 1];
        for (permutationEncoded = lp[j]; permutationEncoded < p2; permutationEncoded++) {
          x[li[permutationEncoded]] -= lx[permutationEncoded] * x[j];
        }
      }
    }
    function ldlDsolve$1(dimension, x, d) {
      for (let j = 0; j < dimension; j++) {
        x[j] /= d[j];
      }
    }
    function ldlLTsolve$1(dimension, x, lp, li, lx) {
      let j, permutationEncoded, p2;
      for (j = dimension - 1; j >= 0; j--) {
        p2 = lp[j + 1];
        for (permutationEncoded = lp[j]; permutationEncoded < p2; permutationEncoded++) {
          x[j] -= lx[permutationEncoded] * x[li[permutationEncoded]];
        }
      }
    }
    function ldlPerm$1(dimension, x, b, permutationEncoded) {
      let j;
      for (j = 0; j < dimension; j++) {
        x[j] = b[permutationEncoded[j]];
      }
    }
    function ldlPermt$1(dimension, x, b, permutationEncoded) {
      let j;
      for (j = 0; j < dimension; j++) {
        x[permutationEncoded[j]] = b[j];
      }
    }

    /**
     * add the provided weights to a particular given system matrix (lD'D) in the triplet form and y data. This function is not general
     * it assumes that diagonal coefficients are in the even indexes, it is the case of the matrix generated by createSystemMatrix function.
     * It simulates the matrix operation W + lD'D and Wy.
     * @param leftHandSide - The original system matrix to be updated, a lower triangular non-zeros of the system matrix (lambda D'D).
     * @param rightHandSide - The original vector to be updated.
     * @param weights - The weights to apply to the system matrix and vector.
     * @returns An object that contains the news left and right hand-side of the system.
     */
    function addWeights(leftHandSide, rightHandSide, weights) {
      const nbPoints = rightHandSide.length;
      const l = nbPoints - 1;
      const newLeftHandSide = new Array(leftHandSide.length);
      const newRightHandSide = new Float64Array(nbPoints);
      for (let i = 0; i < l; i++) {
        const w = weights[i];
        const diag = i * 2;
        const next = diag + 1;
        newLeftHandSide[diag] = leftHandSide[diag].slice();
        newLeftHandSide[next] = leftHandSide[next].slice();
        newRightHandSide[i] = rightHandSide[i] * w;
        newLeftHandSide[diag][2] += w;
      }
      newRightHandSide[l] = rightHandSide[l] * weights[l];
      newLeftHandSide[l * 2] = leftHandSide[l * 2].slice();
      newLeftHandSide[l * 2][2] += weights[l];
      return {
        leftHandSide: newLeftHandSide,
        rightHandSide: newRightHandSide
      };
    }

    /**
     * Generates a lower triangular non-zeros of the first order smoother matrix (lambda D'D) where D is the derivate of the identity matrix
     * this function in combination with addWeights function can obtain (Q = W + lambda D'D) a penalized least square of Whittaker smoother,
     * it also generates a permutation encoded array.
     * @param dimension - The number of points in the matrix.
     * @param lambda - The factor of smoothness .
     * @returns An object containing the lower triangular non-zero elements of the matrix
     * and the permutation encoded array.
     * @property lowerTriangularNonZeros - The lower triangular non-zero elements of the matrix in triplet form.
     * @property permutationEncodedArray - The permutation encoded array generated using the Cuthill-McKee algorithm.
     */
    function createSystemMatrix(dimension, lambda) {
      const upperTriangularNonZeros = [];
      const last = dimension - 1;
      for (let i = 0; i < last; i++) {
        upperTriangularNonZeros.push([i, i, lambda * 2], [i, i + 1, -1 * lambda]);
      }
      upperTriangularNonZeros[0][2] = lambda;
      upperTriangularNonZeros.push([last, last, lambda]);
      return upperTriangularNonZeros;
    }

    /**
     * Computes the baseline points for the given data using an iterative smoothing algorithm.
     * @param yData - The input data array.
     * @param options - The options for baseline computation.
     * @returns - The computed baseline points.
     */
    function xWhittakerSmoother(yData, options = {}) {
      const {
        lambda = 100,
        maxIterations = 100,
        tolerance = 1e-6,
        factorStd = 3,
        learningRate = 0.5,
        minWeight = 0.01
      } = options;
      const size = yData.length;
      // eslint-disable-next-line prefer-const
      let {
        controlPoints,
        weights
      } = getWeightsAndControlPoints(yData, options);
      const prevBaseline = new Float64Array(size);
      let iteration = 0;
      let delta = Infinity;
      let baseline = xEnsureFloat64(yData);
      const upperTriangularNonZeros = createSystemMatrix(size, lambda);
      while (iteration < maxIterations && delta > tolerance) {
        const {
          leftHandSide,
          rightHandSide
        } = addWeights(upperTriangularNonZeros, yData, weights);
        const cho = matrixCholeskySolver(leftHandSide, size);
        if (!cho) {
          return baseline;
        }
        const newBaseline = cho(rightHandSide);
        weights = calculateAdaptiveWeights(yData, newBaseline, weights, {
          controlPoints,
          minWeight,
          learningRate,
          factorStd
        });
        delta = calculateDelta(newBaseline, prevBaseline, size);
        prevBaseline.set(newBaseline);
        baseline = xEnsureFloat64(newBaseline);
        iteration++;
      }
      return baseline;
    }
    /**
     * Calculates the delta between the current and previous baseline.
     * @param baseline - The current baseline array.
     * @param prevBaseline - The previous baseline array.
     * @param n - The length of the arrays.
     * @returns - The calculated delta value.
     */
    function calculateDelta(baseline, prevBaseline, n) {
      let sum = 0;
      for (let i = 0; i < n; i++) {
        sum += (baseline[i] - prevBaseline[i]) ** 2;
      }
      return Math.sqrt(sum / n);
    }
    /**
     * Retrieves the control points and weights for the given data, the weights are modified multiplication of controlPoints if it exists.
     * @param  y - The input data array.
     * @param  options - The options for control points and weights.
     * @returns - The control points and modified weights.
     */
    function getWeightsAndControlPoints(y, options = {}) {
      const {
        length
      } = y;
      const {
        controlPoints
      } = options;
      const {
        weights = Float64Array.from({
          length
        }).fill(1)
      } = options;
      if (controlPoints && controlPoints.length !== y.length) {
        throw new RangeError('controlPoints should match the length with X');
      } else if (weights.length !== y.length) {
        throw new RangeError('weights should match the length with X');
      }
      return {
        weights: controlPoints ? xMultiply(weights, controlPoints) : weights,
        controlPoints
      };
    }
    /**
     * @deprecated Use xWhittakerSmoother instead.
     * TODO: Remove in next major version.
     */
    const xWhitakerSmoother = xWhittakerSmoother;

    /**
     * Phase correction filter.
     * @param data - complex spectrum
     * @param phi0 - Angle in radians for zero order phase correction
     * @param phi1 - Angle in radians for first order phase correction
     * @param options
     * @returns - returns a new object {re:[], im:[]}
     */
    function reimPhaseCorrection(data, phi0 = 0, phi1 = 0, options = {}) {
      const {
        reverse = false
      } = options;
      phi0 = Number.isFinite(phi0) ? phi0 : 0;
      phi1 = Number.isFinite(phi1) ? phi1 : 0;
      const re = data.re;
      const im = data.im;
      const length = data.re.length;
      let firstAngle = phi0;
      let delta = phi1 / length;
      if (reverse) {
        delta *= -1;
        firstAngle += phi1;
      }
      const alpha = 2 * Math.sin(delta / 2) ** 2;
      const beta = Math.sin(delta);
      let cosTheta = Math.cos(firstAngle);
      let sinTheta = Math.sin(firstAngle);
      const newRe = new Float64Array(length);
      const newIm = new Float64Array(length);
      for (let i = 0; i < length; i++) {
        newRe[i] = re[i] * cosTheta - im[i] * sinTheta;
        newIm[i] = im[i] * cosTheta + re[i] * sinTheta;
        // calculate angles i+1 from i
        const newCosTheta = cosTheta - (alpha * cosTheta + beta * sinTheta);
        const newSinTheta = sinTheta - (alpha * sinTheta - beta * cosTheta);
        cosTheta = newCosTheta;
        sinTheta = newSinTheta;
      }
      return {
        re: newRe,
        im: newIm
      };
    }

    /**
     * Implementation of the algorithm for automatic phase correction: A robust, general automatic phase
     * correction algorithm for high-resolution NMR data. 10.1002/mrc.4586
     * @param data - complex spectrum
     * @param options - options
     */
    function reimAutoPhaseCorrection(data, options = {}) {
      const {
        magnitudeMode = true,
        minRegSize = 30,
        factorNoise = 3,
        maxDistanceToJoin = 256,
        reverse = false
      } = options;
      const finalPeaks = detectBaselineRegions(data, {
        maxDistanceToJoin,
        magnitudeMode,
        factorNoise
      });
      const {
        re,
        im
      } = data;
      const length = re.length;
      const indexMask = reverse ? i => length - 1 - i : i => i;
      let x0 = 0;
      let counter = -1;
      const res = [];
      while (counter < length) {
        const reTmp = [];
        const imTmp = [];
        while (!finalPeaks[indexMask(++counter)] && counter < length) {
          // Add some extra points(0.1 ppm) at rigth and left sides of the region.
          x0 = counter;
        }
        for (; finalPeaks[indexMask(counter)] && counter < length; counter += 2) {
          reTmp.push(re[indexMask(counter)]);
          imTmp.push(im[indexMask(counter)]);
        }
        if (reTmp.length > minRegSize) {
          res.push(autoPhaseRegion(reTmp, imTmp, x0));
        }
      }
      const {
        ph1,
        ph0
      } = determiningGlobalValues(res.map(r => r.x0 / length), res.map(r => r.ph0), res.map(r => r.area / 1e11));
      const phased = reimPhaseCorrection({
        re,
        im
      }, toRadians(ph0), toRadians(ph1), {
        reverse
      });
      return {
        data: phased,
        ph0,
        ph1
      };
    }
    function determiningGlobalValues(x, ph0Values, weights) {
      if (x.length === 0) {
        return {
          ph0: 0,
          ph1: 0
        };
      } else if (x.length === 1) {
        return {
          ph0: ph0Values[0],
          ph1: 0
        };
      }
      const [ph1, ph0] = weightedLinearRegression(x, ph0Values, weights);
      let indexMax = -1;
      let maxDiff = Number.MIN_SAFE_INTEGER;
      for (let i = 0; i < x.length; i++) {
        const predictedPh0 = x[i] * ph1 + ph0;
        const diff = Math.abs(ph0Values[i] - predictedPh0);
        if (diff > 34 && maxDiff < diff) {
          indexMax = i;
          maxDiff = diff;
        }
      }
      if (indexMax > -1) {
        x.splice(indexMax, 1);
        ph0Values.splice(indexMax, 1);
        weights.splice(indexMax, 1);
        return determiningGlobalValues(x, ph0Values, weights);
      }
      return {
        ph0,
        ph1
      };
    }
    function detectBaselineRegions(data, options) {
      const magnitudeData = options.magnitudeMode ? reimAbsolute(data) : data.re;
      const ds = holoborodko(magnitudeData);
      const peaksDs = robustBaseLineRegionsDetection(ds, options);
      const peaksSp = robustBaseLineRegionsDetection(magnitudeData, options);
      return peaksSp.map((sp, i) => sp && peaksDs[i]);
    }
    /**
     * AutoPhaseRegion.
     * @param re - Array of Number.
     * @param im - Array of Number.
     * @param x0 - Number.
     * @returns Region.
     */
    function autoPhaseRegion(re, im, x0) {
      let start = -180;
      let stop = 180;
      const nSteps = 6;
      let maxSteps = 10;
      let bestAng = 0;
      let minArea = Number.MAX_SAFE_INTEGER;
      while (maxSteps > 0) {
        const dAng = (stop - start) / (nSteps + 1);
        for (let i = start; i <= stop; i += dAng) {
          const tmpPhased = reimPhaseCorrection({
            re,
            im
          }, toRadians(i), 0);
          const negArea = getNegArea(tmpPhased.re);
          if (negArea < minArea) {
            [minArea, bestAng] = [negArea, i];
          }
        }
        start = bestAng - dAng;
        stop = bestAng + dAng;
        maxSteps--;
      }
      // Calculate the area for the best angle
      const phased = reimPhaseCorrection({
        re,
        im
      }, toRadians(bestAng), 0);
      let area = 0;
      let sumX = 0;
      for (let j = 0; j < re.length; j++) {
        area += phased.re[j];
        sumX += phased.re[j] * (j + x0);
      }
      return {
        ph0: bestAng,
        area,
        x0: sumX / area
      };
    }
    /**
     * Holoborodko.
     * @param s - Array of float.
     * @returns Array of float.
     */
    function holoborodko(s) {
      const dk = new Float64Array(s.length);
      for (let i = 5; i < s.length - 5; i++) {
        dk[i] = (42 * (s[i + 1] - s[i - 1]) + 48 * (s[i + 2] - s[i - 2]) + 27 * (s[i + 3] + s[i - 3]) + 8 * (s[i + 4] - s[i - 4]) + s[i + 5] - s[i - 5]) / 512;
      }
      //Fill the borders
      for (let i = 0; i < 5; i++) {
        dk[i] = dk[5];
        dk[s.length - i - 1] = dk[s.length - 6];
      }
      return dk;
    }
    /**
     * RobustBaseLineRegionsDetection.
     * @param s
     * @param options
     * @param options.magnitudeMode
     * @param options.maxDistanceToJoin
     * @param options.factorNoise
     */
    function robustBaseLineRegionsDetection(s, options) {
      const {
        maxDistanceToJoin,
        magnitudeMode,
        factorNoise
      } = options;
      const mask = new Uint8Array(s.length);
      for (let i = 0; i < s.length; i++) {
        mask[i] = 0;
      }
      let change = true;
      while (change) {
        const noiseLevel = xNoiseSanPlot(s, {
          magnitudeMode
        });
        const cutOff = factorNoise * noiseLevel.positive;
        change = false;
        for (let i = 0; i < s.length; i++) {
          if (Math.abs(s[i]) > cutOff && !mask[i]) {
            change = true;
            mask[i] = 1;
          }
        }
      }
      // Clean up mask by merging peaks blocks, separated by just a few points(4??).
      let count = 0;
      let prev = 0;
      for (let i = 0; i < s.length; i++) {
        if (!mask[i]) {
          count++;
        } else {
          if (count < maxDistanceToJoin) {
            for (let j = 0; j <= count; j++) {
              mask[prev + j] = 1;
            }
          }
          while (mask[++i] && i < s.length);
          prev = i;
          count = 0;
        }
      }
      return mask;
    }
    /**
     * WeightedLinearRegression.
     * @param x
     * @param y
     * @param w
     */
    function weightedLinearRegression(x, y, w) {
      let sxtw = 0;
      let swx = 0;
      let sw = 0;
      let sxtwy = 0;
      let swy = 0;
      for (let i = 0; i < x.length; i++) {
        sxtw += x[i] * x[i] * w[i];
        swx += x[i] * w[i];
        sw += w[i];
        sxtwy += x[i] * w[i] * y[i];
        swy += w[i] * y[i];
      }
      /* Just to know what is the matrix system that we solve
       let Mx=[[sxtw, swx], [swx, sw]];
       let My=[[sxtwy], [swy]];
      */
      //Mx inverse
      const detMx = sxtw * sw - swx * swx;
      const inMx = [[sw / detMx, -swx / detMx], [-swx / detMx, sxtw / detMx]];
      return [inMx[0][0] * sxtwy + inMx[0][1] * swy, inMx[1][0] * sxtwy + inMx[1][1] * swy];
    }
    function toRadians(degree) {
      return degree * Math.PI / 180;
    }
    function getNegArea(data) {
      let area = 0;
      for (const element of data) {
        if (element < 0) area -= element;
      }
      return area;
    }

    /**
     * ReimFFT.
     * @param data - complex spectrum
     * @param options - options.
     * @returns FFT of complex spectrum.
     */
    function reimFFT(data, options = {}) {
      const {
        inverse = false,
        applyZeroShift = false
      } = options;
      const {
        re,
        im
      } = data;
      const size = re.length;
      const csize = size << 1;
      let complexArray = new Float64Array(csize);
      for (let i = 0; i < csize; i += 2) {
        complexArray[i] = re[i >>> 1];
        complexArray[i + 1] = im[i >>> 1];
      }
      const fft = new FFT$1(size);
      let output = new Float64Array(csize);
      if (inverse) {
        if (applyZeroShift) complexArray = zeroShift(complexArray, true);
        fft.inverseTransform(output, complexArray);
      } else {
        fft.transform(output, complexArray);
        if (applyZeroShift) output = zeroShift(output);
      }
      const newRe = new Float64Array(size);
      const newIm = new Float64Array(size);
      for (let i = 0; i < csize; i += 2) {
        newRe[i >>> 1] = output[i];
        newIm[i >>> 1] = output[i + 1];
      }
      return {
        re: newRe,
        im: newIm
      };
    }
    function zeroShift(data, inverse) {
      const middle = inverse ? Math.ceil(data.length / 2) : Math.floor(data.length / 2);
      return xRotate(data, middle);
    }

    /**
     * This function make a zero filling to re and im part.
     * @param data - object of kind {re:[], im:[]}
     * @param totalLength - final number of points
     * @returns - New DataReIm object with zero-filled,
     * truncated arrays if totalLength is smaller current length or
     * the same input if totalLength is equal that current length
     */
    function reimZeroFilling(data, totalLength) {
      if (!Number.isInteger(totalLength) || totalLength < 0) {
        throw new RangeError('totalLength must be a non-negative integer');
      }
      const {
        re,
        im
      } = data;
      const length = re.length;
      if (totalLength === 0 || length === totalLength) return data;
      if (length > totalLength) {
        return {
          re: re.slice(0, totalLength),
          im: im.slice(0, totalLength)
        };
      }
      const newRE = new Float64Array(totalLength);
      const newIM = new Float64Array(totalLength);
      newRE.set(re);
      newIM.set(im);
      return {
        re: newRE,
        im: newIM
      };
    }

    /**
     * Align data of two spectra by verifying wether x values are in a certain range (`delta`).
     * The two spectra should not have two consecutive x values which difference is
     * smaller than `delta` to achieve good results!
     * @param data1 - First spectrum data
     * @param data2 - Second spectrum data
     * @param options - Options
     */
    function xyAlign(data1, data2, options = {}) {
      const {
        delta = 1,
        common = true,
        x = 'x1'
      } = options;
      const result = {
        x: [],
        y1: [],
        y2: []
      };
      let i = 0;
      let j = 0;
      const length1 = data1.x.length;
      const length2 = data2.x.length;
      while (i < length1 && j < length2) {
        let maxDiff = 0;
        if (typeof delta === 'function') {
          const mean = (data1.x[i] + data2.x[j]) / 2; // is this a good thing to do?
          maxDiff = delta(mean);
        } else {
          maxDiff = delta;
        }
        const difference = data1.x[i] - data2.x[j];
        if (Math.abs(difference) > maxDiff) {
          if (difference > 0) {
            if (!common) {
              result.x.push(data2.x[j]);
              result.y1.push(0);
              result.y2.push(data2.y[j]);
              if (j === length2 - 1) {
                while (i < length1) {
                  result.x.push(data1.x[i]);
                  result.y1.push(data1.y[i]);
                  result.y2.push(0);
                  i++;
                }
              }
            }
            j++;
          } else {
            if (!common) {
              result.x.push(data1.x[i]);
              result.y1.push(data1.y[i]);
              result.y2.push(0);
              if (i === length1 - 1) {
                while (j < length2) {
                  result.x.push(data2.x[j]);
                  result.y1.push(0);
                  result.y2.push(data2.y[j]);
                  j++;
                }
              }
            }
            i++;
          }
        } else {
          const weightedX = (data1.x[i] * data1.y[i] + data2.x[j] * data2.y[j]) / (data1.y[i] + data2.y[j]);
          switch (x) {
            case 'x1':
              result.x.push(data1.x[i]);
              break;
            case 'x2':
              result.x.push(data2.x[j]);
              break;
            case 'weighted':
              result.x.push(weightedX);
              break;
            default:
              throw new Error(`unknown x option value: ${String(x)}`);
          }
          result.y1.push(data1.y[i]);
          result.y2.push(data2.y[j]);
          i++;
          j++;
        }
      }
      return result;
    }

    /**
     * Verify that `data` is an object of x,y arrays.
     * Throws an error if it's not.
     * @param data
     * @param options
     * @param options.minLength
     */
    function xyCheck(data, options = {}) {
      const {
        minLength
      } = options;
      if (data === null || typeof data !== 'object' ||
      // @ts-expect-error Typechecking
      !isAnyArray$1(data.x) ||
      // @ts-expect-error Typechecking
      !isAnyArray$1(data.y)) {
        throw new Error('data must be an object of x and y arrays');
      }
      // @ts-expect-error Typechecking
      if (data.x.length !== data.y.length) {
        throw new Error('the x and y arrays must have the same length');
      }
      // @ts-expect-error Typechecking
      if (minLength && data.x.length < minLength) {
        throw new Error(`data.x must have a length of at least ${minLength}`);
      }
    }

    /**
     * Finds the covariance of the points.
     * @param data
     * @param options
     * @returns the covariance
     */
    function xyCovariance(data, options = {}) {
      const {
        x,
        y
      } = data;
      const {
        unbiased = true
      } = options;
      const meanX = xMean(x);
      const meanY = xMean(y);
      let error = 0;
      for (let i = 0; i < x.length; i++) {
        error += (x[i] - meanX) * (y[i] - meanY);
      }
      if (unbiased) {
        return error / (x.length - 1);
      } else {
        return error / x.length;
      }
    }

    /**
     * Finds the max y value in a range and return a {x,y} point
     * @param data - Object that contains property x (an ordered increasing array) and y (an array)
     * @param options - Options
     */
    function xyMaxYPoint(data, options = {}) {
      xyCheck(data, {
        minLength: 1
      });
      const {
        x,
        y
      } = data;
      if (x.length === 1) {
        return {
          x: x[0],
          y: y[0],
          index: 0
        };
      }
      const {
        fromIndex,
        toIndex
      } = xGetFromToIndex(x, options);
      let current = {
        x: x[fromIndex],
        y: y[fromIndex],
        index: fromIndex
      };
      for (let i = fromIndex; i <= toIndex; i++) {
        if (y[i] > current.y) current = {
          x: x[i],
          y: y[i],
          index: i
        };
      }
      return current;
    }

    const STEPS = [0.25, 0.5, 0.75];
    /**
     * Cumulative Distribution Statistics
     * @param data - array of points {x,y}
     * @returns x0, x25, x50, x75, x100, xMode, xMean (x for maxY)
     */
    function xyCumulativeDistributionStatistics(data) {
      xyCheck(data, {
        minLength: 1
      });
      const {
        x,
        y
      } = data;
      const cumulativeSum = xCumulative(y);
      const maxY = xMaxValue(cumulativeSum);
      for (let i = 0; i < cumulativeSum.length; i++) {
        cumulativeSum[i] /= maxY;
      }
      const result = {
        x0: 0,
        x25: 0,
        x50: 0,
        x75: 0,
        x100: 0,
        xMode: 0,
        xMean: 0
      };
      // need to find the x values closest to STEPS/100
      result.x0 = x[0];
      result.x100 = x.at(-1);
      let currentStep = 0;
      breakPoint: for (let i = 1; i < cumulativeSum.length; i++) {
        while (STEPS[currentStep] < cumulativeSum[i]) {
          // Key is computed dynamically with a multiplication. This cannot be type-safe, hence the "as" assertion.
          const key = `x${STEPS[currentStep] * 100}`;
          result[key] = x[i - 1] + (x[i] - x[i - 1]) * ((STEPS[currentStep] - cumulativeSum[i - 1]) / (cumulativeSum[i] - cumulativeSum[i - 1]));
          currentStep++;
          if (currentStep === STEPS.length) break breakPoint;
        }
      }
      result.xMode = xyMaxYPoint(data).x;
      let sumXY = 0;
      let sumY = 0;
      for (let i = 0; i < x.length; i++) {
        sumXY += x[i] * y[i];
        sumY += y[i];
      }
      result.xMean = sumXY / sumY;
      return result;
    }

    function xyEnsureFloat64(data) {
      return {
        x: xEnsureFloat64(data.x),
        y: xEnsureFloat64(data.y)
      };
    }

    /**
     * Filters x,y values to allow strictly growing values in x-axis.
     * @param data - Object that contains property x (an ordered increasing array) and y (an array).
     */
    function xyEnsureGrowingX(data) {
      xyCheck(data);
      if (xIsMonotonic(data.x) === 1) return data;
      const x = Array.from(data.x);
      const y = Array.from(data.y);
      let prevX = Number.NEGATIVE_INFINITY;
      let currentIndex = 0;
      for (let index = 0; index < x.length; index++) {
        if (prevX < x[index]) {
          if (currentIndex < index) {
            x[currentIndex] = x[index];
            y[currentIndex] = y[index];
          }
          currentIndex++;
          prevX = x[index];
        }
      }
      x.length = currentIndex;
      y.length = currentIndex;
      return {
        x,
        y
      };
    }

    /**
     * Normalize an array of zones:
     * - ensure than from < to
     * - merge overlapping zones
     * - deal with exclusions zones
     * - if no zones is specified add one between -Infinity and +Infinity
     * @param zones - array of zones
     * @param options - options
     * @returns array of zones
     */
    function zonesNormalize(zones = [], options = {}) {
      const {
        exclusions = []
      } = options;
      let {
        from = Number.NEGATIVE_INFINITY,
        to = Number.POSITIVE_INFINITY
      } = options;
      if (from > to) [from, to] = [to, from];
      zones = zones.map(zone => zone.from > zone.to ? {
        from: zone.to,
        to: zone.from
      } : {
        ...zone
      }).sort((a, b) => {
        if (a.from !== b.from) return a.from - b.from;
        return a.to - b.to;
      });
      if (zones.length === 0) {
        zones.push({
          from,
          to
        });
      }
      for (const zone of zones) {
        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];
      const beforeExclusionsZones = [currentZone];
      for (let i = 1; i < zones.length; i++) {
        const zone = zones[i];
        if (zone.from <= currentZone.to) {
          if (currentZone.to < zone.to) {
            currentZone.to = zone.to;
          }
        } else {
          currentZone = zone;
          beforeExclusionsZones.push(currentZone);
        }
      }
      if (exclusions.length === 0) return beforeExclusionsZones;
      const normalizedExclusions = zonesNormalize(exclusions);
      let currentExclusionIndex = 0;
      const results = [];
      for (let zoneIndex = 0; zoneIndex < beforeExclusionsZones.length; zoneIndex++) {
        const zone = beforeExclusionsZones[zoneIndex];
        if (currentExclusionIndex === normalizedExclusions.length) {
          // we analysed all the exclusion zones
          results.push(zone);
          continue;
        }
        while (currentExclusionIndex < normalizedExclusions.length && normalizedExclusions[currentExclusionIndex].to <= zone.from) {
          currentExclusionIndex++;
        }
        if (currentExclusionIndex === normalizedExclusions.length) {
          // we analysed all the exclusion zones
          results.push(zone);
          continue;
        }
        if (zone.to < normalizedExclusions[currentExclusionIndex].from) {
          // no problems, not yet in exclusion
          results.push(zone);
          continue;
        }
        if (normalizedExclusions[currentExclusionIndex].to >= zone.to) {
          // could be totally excluded
          if (normalizedExclusions[currentExclusionIndex].from <= zone.from) {
            continue;
          }
          results.push({
            from: normalizedExclusions[currentExclusionIndex].to,
            to: zone.to
          });
        }
        // we cut in the middle, we need to create more zones, annoying !
        if (normalizedExclusions[currentExclusionIndex].from > zone.from) {
          results.push({
            from: zone.from,
            to: normalizedExclusions[currentExclusionIndex].from
          });
        }
        zone.from = normalizedExclusions[currentExclusionIndex].to;
        zoneIndex--;
      }
      return results;
    }

    /**
     * Add the number of points per zone to reach a specified total
     * @param zones - array of zones
     * @param numberOfPoints - total number of points to distribute between zones
     * @param options - options
     * @returns array of zones with points
     */
    function zonesWithPoints(zones = [],
    /**
     * total number of points to distribute between zones
     * @default 10
     */
    numberOfPoints = 10, options = {}) {
      if (zones.length === 0) return [];
      const normalizedZones = zonesNormalize(zones, options);
      const zonesWithNumberOfPoints = [];
      const totalSize = normalizedZones.reduce((previous, current) => {
        return previous + (current.to - current.from);
      }, 0);
      const unitsPerPoint = totalSize / numberOfPoints;
      let currentTotal = 0;
      for (let i = 0; i < normalizedZones.length - 1; i++) {
        const tempZone = normalizedZones[i];
        const tempZoneNumberOfPoints = Math.min(Math.round((tempZone.to - tempZone.from) / unitsPerPoint), numberOfPoints - currentTotal);
        zonesWithNumberOfPoints.push({
          ...tempZone,
          numberOfPoints: tempZoneNumberOfPoints
        });
        currentTotal += tempZoneNumberOfPoints;
      }
      zonesWithNumberOfPoints.push({
        ...normalizedZones.at(-1),
        numberOfPoints: numberOfPoints - currentTotal
      });
      return zonesWithNumberOfPoints;
    }

    /**
     * Function that retrieves the getEquallySpacedData with the variant "slot".
     * @param x
     * @param y
     * @param from
     * @param to
     * @param numberOfPoints
     * @returns Array of y's equally spaced with the variant "slot"
     */
    function equallySpacedSlot(/** x coordinates */
    x, /** y coordinates */
    y, /** from value */
    from, /** to value */
    to, /** number of points */
    numberOfPoints) {
      const xLength = x.length;
      if (xLength < 2) {
        return Float64Array.from(x);
      }
      const step = (to - from) / (numberOfPoints > 1 ? numberOfPoints - 1 : 1);
      const halfStep = step / 2;
      const lastStep = x.at(-1) - x.at(-2);
      const start = from - halfStep;
      // Changed Array to Float64Array
      const output = new Float64Array(numberOfPoints);
      // Init main variables
      let min = start;
      let max = start + step;
      let previousX = -Number.MAX_VALUE;
      let previousY = 0;
      let nextX = x[0];
      let nextY = y[0];
      let frontOutsideSpectra = 0;
      let backOutsideSpectra = true;
      let currentValue = 0;
      // for slot algorithm
      let currentPoints = 0;
      let i = 1; // index of input
      let j = 0; // index of output
      main: while (true) {
        if (previousX >= nextX) throw new Error('x must be a growing series');
        while (previousX - max > 0) {
          // no overlap with original point, just consume current value
          if (backOutsideSpectra) {
            currentPoints++;
            backOutsideSpectra = false;
          }
          output[j] = currentPoints <= 0 ? 0 : currentValue / currentPoints;
          j++;
          if (j === numberOfPoints) {
            break main;
          }
          min = max;
          max += step;
          currentValue = 0;
          currentPoints = 0;
        }
        if (previousX > min) {
          currentValue += previousY;
          currentPoints++;
        }
        if (previousX === -Number.MAX_VALUE || frontOutsideSpectra > 1) {
          currentPoints--;
        }
        previousX = nextX;
        previousY = nextY;
        if (i < xLength) {
          nextX = x[i];
          nextY = y[i];
          i++;
        } else {
          nextX += lastStep;
          nextY = 0;
          frontOutsideSpectra++;
        }
      }
      return output;
    }

    /**
     * Function that calculates the integral of the line between two
     * x-coordinates, given the slope and intercept of the line.
     * @param x0
     * @param x1
     * @param slope
     * @param intercept
     * @returns integral value.
     */
    function integral(/** first coordinate of point */
    x0, /** second coordinate of point */
    x1, /** slope of the line */
    slope, /** intercept of the line on the y axis */
    intercept) {
      return 0.5 * slope * x1 * x1 + intercept * x1 - (0.5 * slope * x0 * x0 + intercept * x0);
    }

    /**
     * Function that retrieves the getEquallySpacedData with the variant "smooth".
     * @param x
     * @param y
     * @param from
     * @param to
     * @param numberOfPoints
     * @returns - Array of y's equally spaced with the variant "smooth"
     */
    function equallySpacedSmooth(/** x coordinates */
    x, /** y coordinates */
    y, /** from value */
    from, /** to value */
    to, /** number of points */
    numberOfPoints) {
      const xLength = x.length;
      const step = (to - from) / (numberOfPoints > 1 ? numberOfPoints - 1 : 1);
      const halfStep = step / 2;
      // Changed Array to Float64Array
      const output = new Float64Array(numberOfPoints);
      const initialOriginalStep = x[1] - x[0];
      const lastOriginalStep = x[xLength - 1] - x[xLength - 2];
      // Init main variables
      let min = from - halfStep;
      let max = from + halfStep;
      let previousX = Number.MIN_SAFE_INTEGER;
      let previousY = 0;
      let nextX = x[0] - initialOriginalStep;
      let nextY = 0;
      let currentValue = 0;
      let slope = 0;
      let intercept = 0;
      let sumAtMin = 0;
      let sumAtMax = 0;
      let i = 0; // index of input
      let j = 0; // index of output
      let add = 0;
      main: while (true) {
        if (previousX >= nextX) throw new Error('x must be a growing series');
        if (previousX <= min && min <= nextX) {
          add = integral(0, min - previousX, slope, previousY);
          sumAtMin = currentValue + add;
        }
        while (nextX - max >= 0) {
          // no overlap with original point, just consume current value
          add = integral(0, max - previousX, slope, previousY);
          sumAtMax = currentValue + add;
          output[j++] = (sumAtMax - sumAtMin) / step;
          if (j === numberOfPoints) {
            break main;
          }
          min = max;
          max += step;
          sumAtMin = sumAtMax;
        }
        currentValue += integral(previousX, nextX, slope, intercept);
        previousX = nextX;
        previousY = nextY;
        if (i < xLength) {
          nextX = x[i];
          nextY = y[i];
          i++;
        } else if (i === xLength) {
          nextX += lastOriginalStep;
          nextY = 0;
        }
        slope = getSlope(previousX, previousY, nextX, nextY);
        intercept = -slope * previousX + previousY;
      }
      return output;
    }
    function getSlope(x0, y0, x1, y1) {
      return (y1 - y0) / (x1 - x0);
    }

    /**
     * Function that returns a Number array of equally spaced numberOfPoints
     * containing a representation of intensities of the spectra arguments x
     * and y.
     *
     * The options parameter contains an object in the following form:
     * from: starting point
     * to: last point
     * numberOfPoints: number of points between from and to
     * variant: "slot" or "smooth" - smooth is the default option
     *
     * The slot variant consist that each point in an array is calculated
     * averaging the existing points between the slot that belongs to the current
     * value. The smooth variant is the same but takes the integral of the range
     * of the slot and divide by the step size between two points in an array.
     *
     * If exclusions zone are present, zones are ignored !
     * @param data - object containing 2 properties x and y
     * @param options - options
     * @returns new object with x / y array with the equally spaced data.
     */
    function xyEquallySpaced(data, options = {}) {
      const {
        x,
        y
      } = data;
      const xLength = x.length;
      const {
        from = x[0],
        to = x[xLength - 1],
        variant = 'smooth',
        numberOfPoints = 100,
        exclusions = [],
        zones = [{
          from,
          to
        }]
      } = options;
      if (from > to) {
        throw new RangeError('from should be larger than to');
      }
      xyCheck(data);
      if (numberOfPoints < 2) {
        throw new RangeError("'numberOfPoints' option must be greater than 1");
      }
      const normalizedZones = zonesNormalize(zones, {
        from,
        to,
        exclusions
      });
      const zonesWithPointsRes = zonesWithPoints(normalizedZones, numberOfPoints, {
        from,
        to
      }).filter(zone => zone.numberOfPoints);
      let xResult = [];
      let yResult = [];
      for (const zone of zonesWithPointsRes) {
        if (!zone.numberOfPoints) {
          zone.numberOfPoints = 0;
        }
        const zoneResult = processZone(Array.from(x), Array.from(y), zone.from, zone.to, zone.numberOfPoints, variant);
        xResult = xResult.concat(zoneResult.x);
        yResult = yResult.concat(zoneResult.y);
      }
      return {
        x: xResult,
        y: yResult
      };
    }
    function processZone(x, y, from, to, numberOfPoints, variant) {
      if (numberOfPoints < 1) {
        throw new RangeError('the number of points must be at least 1');
      }
      const output = variant === 'slot' ? Array.from(equallySpacedSlot(x, y, from, to, numberOfPoints)) : Array.from(equallySpacedSmooth(x, y, from, to, numberOfPoints));
      return {
        x: Array.from(createFromToArray({
          from,
          to,
          length: numberOfPoints
        })),
        y: output
      };
    }

    /**
     * Extract zones from a XY data.
     * @param data - Object that contains property x (an ordered increasing array) and y (an array)
     * @param options - options
     * @returns - Array of points
     */
    function xyExtract(data, options = {}) {
      xyCheck(data);
      const {
        x,
        y
      } = data;
      let {
        zones
      } = options;
      zones = zonesNormalize(zones);
      if (x === undefined || y === undefined || !Array.isArray(zones) || zones.length === 0) {
        return data;
      }
      const newX = [];
      const 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
      };
    }

    /**
     * Filter an array x/y based on various criteria x points are expected to be sorted
     * @param data - object containing 2 properties x and y
     * @param options - options
     * @returns filtered array
     */
    function xyFilter(data, options = {}) {
      const {
        x,
        y
      } = data;
      const {
        filter
      } = options;
      const newX = [];
      const newY = [];
      for (let i = 0; i < x.length; i++) {
        if (!filter || filter(x[i], y[i])) {
          newX.push(x[i]);
          newY.push(y[i]);
        }
      }
      return {
        x: newX,
        y: newY
      };
    }

    /**
     * Filter an array x/y based on various criteria x points are expected to be sorted
     * @param data - object containing 2 properties x and y
     * @param minRelativeYValue - the minimum relative value compare to the Y max value
     * @returns filtered data
     */
    function xyFilterMinYValue(data, minRelativeYValue) {
      if (minRelativeYValue === undefined) return data;
      const {
        x,
        y
      } = data;
      const {
        min,
        max
      } = xMinMaxValues(y);
      const threshold = max * minRelativeYValue;
      if (min >= threshold) return data;
      const newX = [];
      const newY = [];
      for (let i = 0; i < x.length; i++) {
        if (y[i] >= threshold) {
          newX.push(x[i]);
          newY.push(y[i]);
        }
      }
      return {
        x: newX,
        y: newY
      };
    }

    /**
     * Filter an array x/y based on various criteria x points are expected to be sorted
     * @param data - object containing 2 properties x and y
     * @param nbPeaks
     * @returns filtered data
     */
    function xyFilterTopYValues(data, nbPeaks) {
      if (nbPeaks === undefined) return data;
      if (nbPeaks > data.x.length) return data;
      const {
        x,
        y
      } = data;
      const newX = [];
      const newY = [];
      const descending = xSortDescending(y.slice());
      const threshold = descending[nbPeaks - 1];
      let nbThreshold = 0;
      for (let i = 0; i < nbPeaks; i++) {
        if (descending[i] === threshold) {
          nbThreshold++;
        }
      }
      for (let i = 0; i < x.length; i++) {
        if (y[i] > threshold) {
          newX.push(x[i]);
          newY.push(y[i]);
        } else if (y[i] === threshold) {
          nbThreshold--;
          if (nbThreshold >= 0) {
            newX.push(x[i]);
            newY.push(y[i]);
          }
        }
      }
      return {
        x: newX,
        y: newY
      };
    }

    /**
     * Filter an array x/y based on various criteria x points are expected to be sorted
     * @param data - object containing 2 properties x and y
     * @param options - options
     * @returns filtered array
     */
    function xyFilterX(data, options = {}) {
      const {
        x,
        y
      } = data;
      if (x.length === 0) {
        return {
          x: Array.from(x),
          y: Array.from(y)
        };
      }
      const {
        from = x[0],
        to = x.at(-1),
        zones = [{
          from,
          to
        }],
        exclusions = []
      } = options;
      const normalizedZones = zonesNormalize(zones, {
        from,
        to,
        exclusions
      });
      let currentZoneIndex = 0;
      const newX = [];
      const newY = [];
      let position = 0;
      while (position < x.length) {
        if (x[position] <= normalizedZones[currentZoneIndex].to && x[position] >= normalizedZones[currentZoneIndex].from) {
          newX.push(x[position]);
          newY.push(y[position]);
        } else if (x[position] > normalizedZones[currentZoneIndex].to) {
          currentZoneIndex++;
          if (!normalizedZones[currentZoneIndex]) break;
        }
        position++;
      }
      return {
        x: newX,
        y: newY
      };
    }

    /**
     * Filter out all the points for which x <= 0. Useful to display log scale data
     * @param data - data
     * @returns - An object with the filtered data
     */
    function xyFilterXPositive(data) {
      xyCheck(data);
      const {
        x,
        y
      } = data;
      const newX = [];
      const newY = [];
      if (x === undefined || y === undefined) {
        return {
          x: newX,
          y: newY
        };
      }
      for (let i = 0; i < x.length; i++) {
        if (x[i] > 0) {
          newX.push(x[i]);
          newY.push(y[i]);
        }
      }
      return {
        x: newX,
        y: newY
      };
    }

    /**
     * Finds the closest point
     * @param data - x array should be sorted and ascending
     * @param target - target to search
     * @returns - closest point
     */
    function xyFindClosestPoint(/** points */
    data, target) {
      const {
        x,
        y
      } = data;
      const index = xFindClosestIndex(x, target);
      return {
        x: x[index],
        y: y[index]
      };
    }

    /**
     * Returns the numberMaxPoints points with the bigger y.
     * @param data - Object that contains property x (an ordered increasing array) and y (an array)
     * @param numberMaxPoints - Number of points to keep
     * @returns - The points filtered to keep the `numberMaxPoints` most intense points of the input.
     */
    function xyGetNMaxY(data, numberMaxPoints) {
      xyCheck(data);
      if (data.x.length <= numberMaxPoints) {
        return data;
      } else {
        const newX = new Float64Array(numberMaxPoints);
        const newY = new Float64Array(numberMaxPoints);
        // slice() is used to make a copy of the array, because sort() is IPM
        const threshold = Float64Array.from(data.y).sort().reverse()[numberMaxPoints - 1];
        let index = 0;
        for (let i = 0; i < data.x.length; i++) {
          if (data.y[i] >= threshold) {
            newX[index] = data.x[i];
            newY[index] = data.y[i];
            index++;
          }
          if (index === numberMaxPoints) {
            return {
              x: newX,
              y: newY
            };
          }
        }
        return data;
      }
    }

    /**
     * Order object of array, x has to be monotone. Ensure x is growing
     * @param data - Object of kind {x:[], y:[]}.
     */
    function xyGrowingX(data) {
      const {
        x,
        y
      } = data;
      if (x.length !== y.length) {
        throw new TypeError('length of x and y must be identical');
      }
      if (x.length < 2 || x[0] < x.at(-1)) return data;
      return {
        x: x.slice(0).reverse(),
        y: y.slice(0).reverse()
      };
    }

    /**
     * Generate a X / Y of the xyIntegral
     * @param data - Object that contains property x (an ordered increasing array) and y (an array)
     * @param options - Options
     * @returns - An object with the xyIntegration function
     */
    function xyIntegral(data, options = {}) {
      const {
        reverse = false
      } = options;
      xyCheck(data, {
        minLength: 1
      });
      const {
        x,
        y
      } = data;
      const {
        fromIndex,
        toIndex
      } = xGetFromToIndex(x, options);
      let xyIntegration = 0;
      const currentxyIntegral = {
        x: new Float64Array(toIndex - fromIndex + 1),
        y: new Float64Array(toIndex - fromIndex + 1)
      };
      let index = 0;
      if (reverse) {
        currentxyIntegral.y[index] = 0;
        currentxyIntegral.x[index++] = x[toIndex];
        for (let i = toIndex; i > fromIndex; i--) {
          xyIntegration += (x[i] - x[i - 1]) * (y[i - 1] + y[i]) / 2;
          currentxyIntegral.x[index] = x[i - 1];
          currentxyIntegral.y[index++] = xyIntegration;
        }
        currentxyIntegral.x.reverse();
        currentxyIntegral.y.reverse();
      } else {
        currentxyIntegral.y[index] = 0;
        currentxyIntegral.x[index++] = x[fromIndex];
        for (let i = fromIndex; i < toIndex; i++) {
          xyIntegration += (x[i + 1] - x[i]) * (y[i + 1] + y[i]) / 2;
          currentxyIntegral.x[index] = x[i + 1];
          currentxyIntegral.y[index++] = xyIntegration;
        }
      }
      return currentxyIntegral;
    }

    /**
     * Calculate integration
     * @param data - Object that contains property x (an ordered increasing array) and y (an array)
     * @param options - Options
     * @returns - xyIntegration value on the specified range
     */
    function xyIntegration(data, options = {}) {
      xyCheck(data, {
        minLength: 1
      });
      const {
        x,
        y
      } = data;
      if (x.length === 1) 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;
    }

    /**
     * Join x / y values when difference in X is closer than delta.
     * When joining, y values are summed and x values are weighted average
     * @param data - Object that contains property x (an ordered increasing array) and y (an array)
     * @param options - Options
     * @returns - An object with the xyIntegration function
     */
    function xyJoinX(data, options = {}) {
      xyCheck(data);
      const {
        delta = 1
      } = options;
      const deltaIsFunction = typeof delta === 'function';
      const x = Array.from(data.x);
      const y = Array.from(data.y);
      if (x.length < 2) {
        return {
          x,
          y
        };
      }
      let position = 0;
      for (let i = 1; i < x.length; i++) {
        const difference = x[i] - x[i - 1];
        const currentDelta = deltaIsFunction ? delta((x[i] + x[i - 1]) / 2) : delta;
        if (difference <= currentDelta) {
          // we join
          if (y[position] !== 0 || y[i] !== 0) {
            x[position] = (x[position] * y[position] + x[i] * y[i]) / (y[position] + y[i]);
            y[position] += y[i];
          }
        } else {
          position++;
          x[position] = x[i];
          y[position] = y[i];
        }
      }
      x.length = position + 1;
      y.length = position + 1;
      return {
        x,
        y
      };
    }

    /**
     * Computes the weighted mean value of an array of values.
     * @param data - array of DataXY
     * @param options - options
     */
    function xyMassCenter(data, options = {}) {
      xyCheck(data, {
        minLength: 1
      });
      const {
        x,
        y
      } = data;
      const {
        fromIndex,
        toIndex
      } = xGetFromToIndex(x, options);
      let sumYs = 0;
      let sumProducts = 0;
      for (let i = fromIndex; i <= toIndex; i++) {
        sumProducts += x[i] * y[i];
        sumYs += y[i];
      }
      if (sumYs === 0) {
        throw new Error('Sum of Ys can not be zero.');
      }
      return sumProducts / sumYs;
    }

    /**
     * We will calculate a vector containing center of mass of DataXY as well as center of mass of both parts, etc.
     * This approach allows to efficiently represent spectra like XPS, NMR, etc. It should provide an extremely efficient
     * way to store and search similar spectra.
     * @param data - Object that contains property x (an ordered increasing array) and y (an array)
     * @param options
     * @returns - Array of centers of mass
     */
    function xyMassCenterVector(data, options = {}) {
      xyCheck(data, {
        minLength: 2
      });
      const {
        depth = 5
      } = options;
      const {
        weightedIntegral,
        integral
      } = getWeightedIntegral(data);
      const results = new Float64Array((1 << depth) - 1);
      const endIndexes = new Int32Array((1 << depth) - 1);
      endIndexes[0] = data.x.length - 1;
      const beginIndexes = new Int32Array((1 << depth) - 1);
      beginIndexes[0] = -1;
      let index = 0;
      for (let i = 0; i < depth; i++) {
        for (let j = 0; j < 1 << i; j++) {
          const currentBeginIndex = beginIndexes[index];
          const currentEndIndex = endIndexes[index];
          const currentIntegration = currentBeginIndex === -1 ? integral[currentEndIndex] : integral[currentEndIndex] - integral[currentBeginIndex];
          // we need to solve the issue of null integration (rather in simulated spectra).
          let x = 0;
          if (currentIntegration !== 0) {
            x = currentBeginIndex === -1 ? weightedIntegral[currentEndIndex] / currentIntegration : (weightedIntegral[currentEndIndex] - weightedIntegral[currentBeginIndex]) / currentIntegration;
          } else {
            x = (data.x[currentEndIndex] + data.x[currentBeginIndex]) / 2;
          }
          results[index++] = x;
          // we can now prepare the next level
          if (i < depth - 1) {
            const nextIndex = (1 << i + 1) + j * 2 - 1;
            let middleIndex = xFindClosestIndex(data.x, x);
            if (middleIndex === currentBeginIndex) {
              middleIndex++;
            }
            beginIndexes[nextIndex] = currentBeginIndex;
            endIndexes[nextIndex] = middleIndex;
            if (middleIndex === currentEndIndex) {
              middleIndex--;
            }
            beginIndexes[nextIndex + 1] = middleIndex;
            endIndexes[nextIndex + 1] = currentEndIndex;
          }
        }
      }
      return results;
    }
    function getWeightedIntegral(data) {
      const {
        x,
        y
      } = data;
      const weightedIntegral = new Float64Array(x.length);
      const integral = new Float64Array(x.length);
      // the first point, no points before
      const firstIntegration = (x[1] - x[0]) * y[0];
      let totalIntegration = firstIntegration;
      integral[0] = totalIntegration;
      let totalWeightedIntegral = firstIntegration * x[0];
      weightedIntegral[0] = totalWeightedIntegral;
      for (let i = 1; i < x.length - 1; i++) {
        const currentIntegration = (x[i + 1] - x[i - 1]) * y[i] / 2;
        const currentX = x[i];
        totalIntegration += currentIntegration;
        integral[i] = totalIntegration;
        totalWeightedIntegral += currentIntegration * currentX;
        weightedIntegral[i] = totalWeightedIntegral;
      }
      // the last point, no points after
      const lastIntegration = (x.at(-1) - x.at(-2)) * y.at(-1);
      totalIntegration += lastIntegration;
      integral[x.length - 1] = totalIntegration;
      totalWeightedIntegral += lastIntegration * x.at(-1);
      weightedIntegral[x.length - 1] = totalWeightedIntegral;
      return {
        integral,
        weightedIntegral
      };
    }

    /**
     * Find the closest maximum going up hill
     * @param data - Object that contains property x (an ordered increasing array) and y (an array)
     * @param options - options
     * @returns - An object with the x/y value
     */
    function xyMaxClosestYPoint(data, options = {}) {
      xyCheck(data);
      const {
        x,
        y
      } = data;
      const {
        target
      } = options;
      let {
        targetIndex
      } = options;
      if (targetIndex === undefined) {
        if (target !== undefined) {
          targetIndex = xFindClosestIndex(x, target);
        } else {
          targetIndex = 0;
        }
      }
      let previousIndex = Number.MIN_SAFE_INTEGER;
      let currentIndex = targetIndex;
      let xyMaxY = y[targetIndex];
      while (currentIndex !== previousIndex) {
        previousIndex = currentIndex;
        if (currentIndex > 0 && y[currentIndex - 1] > xyMaxY) {
          currentIndex--;
        } else if (currentIndex < x.length - 1 && y[currentIndex + 1] > xyMaxY) {
          currentIndex++;
        }
        xyMaxY = y[currentIndex];
      }
      return {
        x: x[currentIndex],
        y: y[currentIndex],
        index: currentIndex
      };
    }

    /**
     * Finds all the max values
     * If the values are equal the middle
     * of the equal part will be the position of the signal!
     * @param data - Object that contains property x (an ordered increasing array) and y (an array)
     * @param options - Object with from and to properties
     * @returns - Array of points
     */
    function xyMaximaY(data, options = {}) {
      xyCheck(data, {
        minLength: 2
      });
      const {
        x,
        y
      } = data;
      const {
        fromIndex,
        toIndex
      } = xGetFromToIndex(x, options);
      const maxima = [];
      let startEqualIndex = -1;
      for (let i = fromIndex + 1; i < toIndex; i++) {
        if (y[i - 1] < y[i] && y[i + 1] < y[i]) {
          maxima.push({
            x: x[i],
            y: y[i],
            index: i
          });
        } else if (y[i - 1] < y[i] && y[i + 1] === y[i]) {
          startEqualIndex = i;
        } else if (y[i - 1] === y[i] && y[i + 1] < y[i]) {
          const index = Math.floor((i + startEqualIndex) / 2);
          maxima.push({
            x: x[index],
            y: y[index],
            index
          });
        }
      }
      return maxima;
    }

    /**
     * Merge abscissas values on similar ordinates and weight the group of abscissas
     * @param data - object containing 2 properties x and y
     * @param options - options
     * @returns array of merged and weighted points
     */
    function xyMaxMerge(data, options = {}) {
      const {
        x,
        y
      } = data;
      const {
        groupWidth = 0.001
      } = options;
      const merged = {
        x: [],
        y: []
      };
      const maxAbscissa = {
        x: [],
        y: []
      };
      let size = 0;
      let index = 0;
      while (index < x.length) {
        if (size === 0 || x[index] - merged.x[size - 1] > groupWidth) {
          maxAbscissa.x.push(x[index]);
          maxAbscissa.y.push(y[index]);
          merged.x.push(x[index]);
          merged.y.push(y[index]);
          index++;
          size++;
        } else {
          if (y[index] > maxAbscissa.y[size - 1]) {
            maxAbscissa.x[size - 1] = x[index];
            maxAbscissa.y[size - 1] = y[index];
          }
          merged.x[size - 1] = x[index];
          merged.y[size - 1] += y[index];
          index++;
        }
      }
      merged.x = maxAbscissa.x.slice();
      return merged;
    }

    /**
     * Finds the max value in a zone
     * @param data - Object that contains property x (an ordered increasing array) and y (an array)
     * @param options - Options
     * @returns - Max y on the specified range
     */
    function xyMaxY(data, options = {}) {
      xyCheck(data);
      const {
        x,
        y
      } = data;
      const {
        fromIndex,
        toIndex
      } = xGetFromToIndex(x, options);
      let currentxyMaxY = y[fromIndex];
      for (let i = fromIndex; i <= toIndex; i++) {
        if (y[i] > currentxyMaxY) currentxyMaxY = y[i];
      }
      return currentxyMaxY;
    }

    /**
     * Finds the median x value for an object with properties x and y (arrays of the same length)
     * @param data - x should be sorted in increasing order
     * @returns - the median of x values
     */
    function xyMedian(data) {
      const {
        x,
        y
      } = data;
      let sumY = 0;
      let cumSumY = 0;
      let i;
      if (x.length === 0) {
        return Number.NaN;
      }
      if (x.length === 1) {
        return x[0];
      }
      for (i = 0; i < y.length; i++) {
        sumY += y[i];
      }
      for (i = 0; i < y.length; i++) {
        cumSumY += y[i];
        if (cumSumY > sumY / 2) {
          return x[i];
        } else if (cumSumY === sumY / 2) {
          return 0.5 * (x[i] + x[i + 1]);
        }
      }
      return Number.NaN;
    }

    /**
     * Merge abscissa values if the ordinate value is in a list of centroids
     * @param data - object containing 2 properties x and y
     * @param centroids - centroids
     * @param options - options
     * @returns merged points
     */
    function xyMergeByCentroids(data, centroids, options = {}) {
      const {
        window = 0.01
      } = options;
      const mergedPoints = {
        x: Float64Array.from(centroids),
        y: new Float64Array(centroids.length).fill(0)
      };
      let originalIndex = 0;
      let mergedIndex = 0;
      while (originalIndex < data.x.length && mergedIndex < centroids.length) {
        const diff = data.x[originalIndex] - centroids[mergedIndex];
        if (Math.abs(diff) < window) {
          mergedPoints.y[mergedIndex] += data.y[originalIndex++];
        } else if (diff < 0) {
          originalIndex++;
        } else {
          mergedIndex++;
        }
      }
      return mergedPoints;
    }

    /**
     * Find the closest minimum going down hill
     * @param data - Object that contains property x (an ordered increasing array) and y (an array)
     * @param options - Options
     * @returns - An object with the x/y value
     */
    function xyMinClosestYPoint(data, options = {}) {
      xyCheck(data);
      const {
        x,
        y
      } = data;
      const {
        target
      } = options;
      let {
        targetIndex
      } = options;
      if (targetIndex === undefined) {
        if (target !== undefined) {
          targetIndex = xFindClosestIndex(x, target);
        } else {
          targetIndex = 0;
        }
      }
      let previousIndex = Number.MIN_SAFE_INTEGER;
      let currentIndex = targetIndex;
      let minY = y[targetIndex];
      while (currentIndex !== previousIndex) {
        previousIndex = currentIndex;
        if (currentIndex > 0 && y[currentIndex - 1] < minY) {
          currentIndex--;
        } else if (currentIndex < x.length - 1 && y[currentIndex + 1] < minY) {
          currentIndex++;
        }
        minY = y[currentIndex];
      }
      return {
        x: x[currentIndex],
        y: y[currentIndex],
        index: currentIndex
      };
    }

    /**
     * Finds all the min values
     * If the values are equal the middle
     * of the equal part will be the position of the signal!
     * @param data - Object that contains property X (an ordered increasing array) and y (an arraY)
     * @param options - Object with from and to properties
     * @returns - Array of points.
     */
    function xyMinimaY(data, options = {}) {
      xyCheck(data, {
        minLength: 2
      });
      const {
        x,
        y
      } = data;
      const {
        fromIndex,
        toIndex
      } = xGetFromToIndex(x, options);
      const maxima = [];
      let startEqualIndex = -1;
      for (let i = fromIndex + 1; i < toIndex; i++) {
        if (y[i - 1] > y[i] && y[i + 1] > y[i]) {
          maxima.push({
            x: x[i],
            y: y[i],
            index: i
          });
        } else if (y[i - 1] > y[i] && y[i + 1] === y[i]) {
          startEqualIndex = i;
        } else if (y[i - 1] === y[i] && y[i + 1] > y[i]) {
          const index = Math.floor((i + startEqualIndex) / 2);
          maxima.push({
            x: x[index],
            y: y[index],
            index
          });
        }
      }
      return maxima;
    }

    /**
     * Finds the min y value in a range and return a {x,y} point
     * @param data - Object that contains property x (an ordered increasing array) and y (an array)
     * @param options - Options
     */
    function xyMinYPoint(data, options = {}) {
      xyCheck(data, {
        minLength: 1
      });
      const {
        x,
        y
      } = data;
      if (x.length === 1) return {
        x: x[0],
        y: y[0],
        index: 0
      };
      const {
        fromIndex,
        toIndex
      } = xGetFromToIndex(x, options);
      let current = {
        x: x[fromIndex],
        y: y[fromIndex],
        index: fromIndex
      };
      for (let i = fromIndex; i <= toIndex; i++) {
        if (y[i] < current.y) current = {
          x: x[i],
          y: y[i],
          index: i
        };
      }
      return current;
    }

    /**
     * Returns an information about a signal.
     *
     *
     * We expect ordered data and equidistant X axis
     * You can use the method helper if required:
     * ML.ArrayPoints.uniqueX
     * ML.ArrayPoints.sortX
     * ML.ArrayPoints.equallySpaced
     * @param data - Object that contains property x (an ordered increasing array) and y (an array)
     * @param options - options
     * @returns - Information about signal
     */
    function xyPeakInfo(data, options = {}) {
      xyCheck(data);
      const {
        x,
        y
      } = data;
      if (x === undefined || y === undefined || x.length < 3) {
        return;
      }
      const {
        target
      } = options;
      let {
        targetIndex
      } = options;
      if (targetIndex === undefined && target !== undefined) {
        targetIndex = xFindClosestIndex(x, target);
      }
      if (targetIndex === undefined) {
        throw new Error('must specify target or targetIndex');
      }
      let i = targetIndex;
      let currentDiff = y[i] - y[i + 1];
      const multiplier = currentDiff < 0 ? -1 : 1;
      currentDiff *= multiplier;
      while (i < x.length - 1) {
        i++;
        const newDiff = (y[i] - y[i + 1]) * multiplier;
        if (newDiff < currentDiff) break;
        currentDiff = newDiff;
      }
      const after = {
        x: x[i],
        y: y[i]
      };
      i = targetIndex;
      currentDiff = (y[i] - y[i - 1]) * multiplier;
      while (i > 1) {
        i--;
        const newDiff = (y[i] - y[i - 1]) * multiplier;
        if (newDiff < currentDiff) break;
        currentDiff = newDiff;
      }
      const before = {
        x: x[i],
        y: y[i]
      };
      return {
        inflectionBefore: before,
        inflectionAfter: after,
        extrema: {
          x: x[targetIndex],
          y: y[targetIndex]
        },
        inflectionMiddle: {
          x: (before.x + after.x) / 2,
          y: (before.y + after.y) / 2
        },
        width: Math.abs(before.x - after.x)
      };
    }

    /**
     * Find the closest minimum going down hill
     * @param data - Object that contains property x (an ordered increasing array) and y (an array)
     * @param options - options
     * @returns - An object with the x/y value
     */
    function xyRealMaxYPoint(data, options = {}) {
      xyCheck(data);
      const {
        x,
        y
      } = data;
      const targetIndex = xGetTargetIndex(x, options);
      // interpolation to a sin() function
      if (y[targetIndex - 1] > 0 && y[targetIndex + 1] > 0 && y[targetIndex] >= y[targetIndex - 1] && y[targetIndex] >= y[targetIndex + 1]) {
        const alpha = 20 * Math.log10(y[targetIndex - 1]);
        const beta = 20 * Math.log10(y[targetIndex]);
        const gamma = 20 * Math.log10(y[targetIndex + 1]);
        const p = 0.5 * (alpha - gamma) / (alpha - 2 * beta + gamma);
        return {
          x: x[targetIndex] + (x[targetIndex] - x[targetIndex - 1]) * p,
          y: y[targetIndex] - 0.25 * (y[targetIndex - 1] - y[targetIndex + 1]) * p,
          index: targetIndex
        };
      } else {
        return {
          x: x[targetIndex],
          y: y[targetIndex],
          index: targetIndex
        };
      }
    }

    /**
     * xyRealMinYPoint.
     * @param data - Data.
     * @param options - Options.
     */
    function xyRealMinYPoint(data, options = {}) {
      xyCheck(data);
      const {
        x,
        y
      } = data;
      const targetIndex = xGetTargetIndex(x, options);
      // interpolation to a sin() function
      if (y[targetIndex - 1] < 0 && y[targetIndex + 1] < 0 && y[targetIndex] <= y[targetIndex - 1] && y[targetIndex] <= y[targetIndex + 1]) {
        const alpha = 20 * Math.log10(-y[targetIndex - 1]);
        const beta = 20 * Math.log10(-y[targetIndex]);
        const gamma = 20 * Math.log10(-y[targetIndex + 1]);
        const p = 0.5 * (alpha - gamma) / (alpha - 2 * beta + gamma);
        return {
          x: x[targetIndex] + (x[targetIndex] - x[targetIndex - 1]) * p,
          y: y[targetIndex] - 0.25 * (y[targetIndex - 1] - y[targetIndex + 1]) * p,
          index: targetIndex
        };
      } else {
        return {
          x: x[targetIndex],
          y: y[targetIndex],
          index: targetIndex
        };
      }
    }

    /**
     * Reduce the number of points while keeping visually the same noise. Practical to
     * display many spectra as SVG. If you want a similar looking spectrum you should still however
     * generate at least 4x the nbPoints that is being displayed.
     *
     * SHOULD NOT BE USED FOR DATA PROCESSING !!!
     * You should rather use ml-xy-equally-spaced to make further processing
     * @param data - Object that contains property x (an ordered increasing array) and y (an array)
     * @param options - options
     * @returns Object with x and y arrays
     */
    function xyReduce(data, options = {}) {
      xyCheck(data);
      // todo we should check that the single point is really in the range and the zones
      if (data.x.length < 2) {
        return {
          x: Float64Array.from(data.x),
          y: Float64Array.from(data.y)
        };
      }
      const {
        x,
        y
      } = data;
      const {
        from = x[0],
        to = x.at(-1),
        nbPoints = 4001,
        optimize = false
      } = options;
      let {
        zones = []
      } = options;
      zones = zonesNormalize(zones, {
        from,
        to
      });
      if (zones.length === 0) zones = [{
        from,
        to
      }]; // we take everything
      const {
        internalZones,
        totalPoints
      } = getInternalZones(zones, x);
      // we calculate the number of points per zone that we should keep
      if (totalPoints <= nbPoints) {
        return notEnoughPoints(x, y, internalZones, totalPoints);
      }
      // need to xyReduce number of points
      const ratio = nbPoints / totalPoints;
      let currentTotal = 0;
      for (let i = 0; i < internalZones.length - 1; i++) {
        const zone = internalZones[i];
        zone.nbPoints = Math.round(zone.nbPoints * ratio);
        currentTotal += zone.nbPoints;
      }
      internalZones.at(-1).nbPoints = nbPoints - currentTotal;
      const newX = [];
      const newY = [];
      for (const zone of internalZones) {
        if (!zone.nbPoints) continue;
        appendFromTo(zone.fromIndex, zone.toIndex, zone.nbPoints);
      }
      return {
        x: newX,
        y: newY
      };
      /**
       * AppendFromTo.
       * @param fromIndex - From.
       * @param  toIndex - To.
       * @param zoneNbPoints - NbPoints.
       */
      function appendFromTo(fromIndex, toIndex, zoneNbPoints) {
        if (zoneNbPoints === 1) {
          newX.push(x[Math.round((toIndex - fromIndex) / 2)]);
          newY.push(y[Math.round((toIndex - fromIndex) / 2)]);
          return;
        }
        if (zoneNbPoints === 2) {
          newX.push(x[fromIndex], x[toIndex]);
          newY.push(y[fromIndex], y[toIndex]);
          return;
        }
        newX.push(x[fromIndex]);
        newY.push(y[fromIndex]);
        if (zoneNbPoints % 2 === 0) {
          zoneNbPoints = zoneNbPoints / 2 + 1;
        } else {
          zoneNbPoints = (zoneNbPoints - 1) / 2 + 1;
        }
        // we will need to make some kind of min / max because there are too many points
        // we will always keep the first point and the last point
        const slot = (x[toIndex] - x[fromIndex]) / (zoneNbPoints - 1);
        let currentX = x[fromIndex] + slot;
        let first = true;
        let minY = Number.POSITIVE_INFINITY;
        let xyMaxY = Number.NEGATIVE_INFINITY;
        for (let i = fromIndex + 1; i <= toIndex; i++) {
          if (first) {
            minY = y[i];
            xyMaxY = y[i];
            first = false;
          } else {
            if (y[i] < minY) minY = y[i];
            if (y[i] > xyMaxY) xyMaxY = y[i];
          }
          if (x[i] >= currentX || i === toIndex) {
            if (optimize) {
              if (minY > newY[newX.length - 1]) ; else if (xyMaxY < newY[newX.length - 1]) {
                // we can skip the intermediate value
                xyMaxY = minY;
              } else {
                newX.push(currentX - slot / 2);
                newY.push(minY);
              }
            } else {
              newX.push(currentX - slot / 2);
              newY.push(minY);
            }
            newX.push(currentX);
            newY.push(xyMaxY);
            currentX += slot;
            first = true;
          }
        }
      }
    }
    function notEnoughPoints(x, y, internalZones, totalPoints) {
      const newX = new Float64Array(totalPoints);
      const newY = new Float64Array(totalPoints);
      let index = 0;
      for (const zone of internalZones) {
        for (let i = zone.fromIndex; i < zone.toIndex + 1; i++) {
          newX[index] = x[i];
          newY[index] = y[i];
          index++;
        }
      }
      return {
        x: newX,
        y: newY
      };
    }
    function getInternalZones(zones, x) {
      // for each zone we should know the first index, the last index and the number of points
      const internalZones = [];
      let totalPoints = 0;
      for (const zone of zones) {
        let fromIndex = xFindClosestIndex(x, zone.from);
        let toIndex = xFindClosestIndex(x, zone.to);
        if (fromIndex > 0 && x[fromIndex] > zone.from) {
          fromIndex--;
        }
        if (toIndex < x.length - 1 && x[toIndex] < zone.to) {
          toIndex++;
        }
        const nbPoints = toIndex - fromIndex + 1;
        internalZones.push({
          from: zone.from,
          to: zone.to,
          fromIndex,
          toIndex,
          nbPoints
        });
        totalPoints += nbPoints;
      }
      return {
        internalZones,
        totalPoints
      };
    }

    /**
     * Reduce the number of points while keeping visually the same noise. Practical to
     * display many spectra as SVG. This algorithm is designed for non-continuous data.
     * We are expecting peaks to be only positive and the x values to be ordered.
     * SHOULD NOT BE USED FOR DATA PROCESSING !!!
     * @param data - Object that contains property x (an ordered increasing array) and y (an array)
     * @param options - options
     * @returns Object with x and y arrays
     */
    function xyReduceNonContinuous(data, options = {}) {
      xyCheck(data);
      if (data.x.length < 2) {
        // todo we should check that the single point is really in the range and the zones
        return {
          x: Float64Array.from(data.x),
          y: Float64Array.from(data.y)
        };
      }
      const {
        x,
        y
      } = data;
      const {
        from = x[0],
        to = x.at(-1),
        maxApproximateNbPoints = 4001
      } = options;
      let {
        zones = []
      } = options;
      zones = zonesNormalize(zones, {
        from,
        to
      });
      if (zones.length === 0) zones = [{
        from,
        to
      }]; // we take everything
      const {
        internalZones,
        totalPoints
      } = getInternalZones(zones, x);
      // we calculate the number of points per zone that we should keep
      if (totalPoints <= maxApproximateNbPoints) {
        return notEnoughPoints(x, y, internalZones, totalPoints);
      }
      const deltaX = (to - from) / (maxApproximateNbPoints - 1);
      const newX = [];
      const newY = [];
      for (const internalZone of internalZones) {
        const maxNbPoints = Math.ceil((internalZone.to - internalZone.from) / deltaX) + 1;
        const fromIndex = internalZone.fromIndex;
        const toIndex = internalZone.toIndex;
        if (toIndex - fromIndex + 1 <= maxNbPoints) {
          // we keep all the points
          for (let i = fromIndex; i <= toIndex; i++) {
            newX.push(x[i]);
            newY.push(y[i]);
          }
        } else {
          // we need to reduce the number of points
          let currentX = x[fromIndex];
          let currentY = y[fromIndex];
          let lastX = currentX + deltaX;
          newX.push(currentX);
          newY.push(currentY);
          for (let i = fromIndex; i <= toIndex; i++) {
            if (x[i] > lastX) {
              // next slot
              currentX = x[i];
              currentY = y[i];
              newX.push(currentX);
              newY.push(currentY);
              lastX += deltaX;
            }
            if (y[i] > currentY) {
              currentY = y[i];
              newY[newY.length - 1] = currentY;
            }
          }
        }
      }
      return {
        x: newX,
        y: newY
      };
    }

    /**
     * This function calculates a rolling average.
     * This methods will recalculate the x values by using xRollingAverage
     * @param data - array of points {x,y}
     * @param fct - callback function that from an array returns a value.
     * @param options - options
     */
    function xyRolling(data, fct, options = {}) {
      let {
        x,
        y
      } = data;
      y = xRolling(y, fct, options);
      if (x.length !== y.length) {
        x = xRollingAverage(x, options);
      }
      return {
        x,
        y
      };
    }

    /**
     * Set a value (default 0) to specific zones.
     * @param data - Object that contains property x (an ordered increasing array) and y (an array)
     * @param options - options
     * @returns - Array of points
     */
    function xySetYValue(data, options = {}) {
      xyCheck(data);
      const {
        x,
        y
      } = data;
      const {
        value = 0
      } = options;
      let {
        zones
      } = options;
      if (!Array.isArray(zones) || zones.length === 0) {
        return data;
      }
      zones = zonesNormalize(zones);
      const newX = x.slice();
      const newY = y.slice();
      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) {
          newY[i] = value;
        }
      }
      return {
        x: newX,
        y: newY
      };
    }

    /**
     * This function performs a quick sort of the x array while transforming the y array to preserve the coordinates.
     * @param data - Object that contains property x (Array) and y (Array)
     */
    function xySortX(data) {
      const {
        x,
        y
      } = data;
      if (xIsMonotonic(x) && x.length > 1) {
        if (x[0] < x[1]) {
          return {
            x: Float64Array.from(x),
            y: Float64Array.from(y)
          };
        } else {
          return {
            x: Float64Array.from(x).reverse(),
            y: Float64Array.from(y).reverse()
          };
        }
      }
      const xyObject = Array.from(x, (val, index) => ({
        x: val,
        y: y[index]
      })).sort((a, b) => a.x - b.x);
      const response = {
        x: new Float64Array(x.length),
        y: new Float64Array(y.length)
      };
      for (let i = 0; i < x.length; i++) {
        response.x[i] = xyObject[i].x;
        response.y[i] = xyObject[i].y;
      }
      return response;
    }

    /**
     * Convert a DataXY to an array of array containing x,y.
     * @param data - array of points {x,y}
     */
    function xyToXYArray(data) {
      xyCheck(data);
      const {
        x,
        y
      } = data;
      const objectArray = [];
      for (let i = 0; i < x.length; i++) {
        objectArray.push([x[i], y[i]]);
      }
      return objectArray;
    }

    /**
     * xyToXYObject.
     * @param data - Array of points {x,y}.
     */
    function xyToXYObject(data) {
      xyCheck(data);
      const {
        x,
        y
      } = data;
      const objectArray = [];
      for (let i = 0; i < x.length; i++) {
        objectArray.push({
          x: x[i],
          y: y[i]
        });
      }
      return objectArray;
    }

    /**
     * Ensure x values are unique
     * @param data - Object that contains property x (Array) and y (Array)
     * @param options - Object containing a property algorithm (can be 'sum' or 'average', the latter being the default value), and a property isSorted (boolean indicating if the x-array is sorted).
     */
    function xyUniqueX(data, options = {}) {
      xyCheck(data);
      if (data.x.length === 0) {
        return {
          x: [],
          y: []
        };
      }
      const {
        algorithm = 'average',
        isSorted = true
      } = options;
      if (!isSorted) {
        data = xySortX(data);
      }
      switch (algorithm) {
        case 'average':
          return average(data);
        case 'sum':
          return sum(data);
        default:
          throw new Error(`unknown algorithm: ${String(algorithm)}`);
      }
    }
    /**
     * Average.
     * @param data - Input.
     * @returns Result.
     */
    function average(data) {
      const x = [];
      const y = [];
      let cumulativeY = data.y[0];
      let divider = 1;
      for (let i = 1; i < data.x.length; i++) {
        if (!(data.x[i] === data.x[i - 1])) {
          x.push(data.x[i - 1]);
          y.push(cumulativeY / divider);
          cumulativeY = 0;
          divider = 0;
        }
        cumulativeY += data.y[i];
        divider++;
      }
      x.push(data.x.at(-1));
      y.push(cumulativeY / divider);
      return {
        x,
        y
      };
    }
    /**
     * Sum.
     * @param data - Input.
     * @returns Result.
     */
    function sum(data) {
      const x = [];
      const y = [];
      let cumulativeY = data.y[0];
      for (let i = 1; i < data.x.length; i++) {
        if (!(data.x[i] === data.x[i - 1])) {
          x.push(data.x[i - 1]);
          y.push(cumulativeY);
          cumulativeY = 0;
        }
        cumulativeY += data.y[i];
      }
      x.push(data.x.at(-1));
      y.push(cumulativeY);
      return {
        x,
        y
      };
    }

    /**
     * Merge abscissas values on similar ordinates and weight the group of abscissas
     * @param data - object containing 2 properties x and y
     * @param options - options
     * @returns array of merged and weighted points
     */
    function xyWeightedMerge(data, options = {}) {
      const {
        x,
        y
      } = data;
      const {
        groupWidth = 0.001
      } = options;
      const merged = {
        x: [],
        y: []
      };
      const weightedAbscissa = {
        x: [],
        y: []
      };
      let size = 0;
      let index = 0;
      while (index < x.length) {
        if (size === 0 || x[index] - merged.x[size - 1] > groupWidth) {
          weightedAbscissa.x.push(x[index] * y[index]);
          weightedAbscissa.y.push(y[index]);
          merged.x.push(x[index]);
          merged.y.push(y[index]);
          index++;
          size++;
        } else {
          weightedAbscissa.x[size - 1] += x[index] * y[index];
          weightedAbscissa.y[size - 1] += y[index];
          merged.x[size - 1] = x[index];
          merged.y[size - 1] += y[index];
          index++;
        }
      }
      for (let i = 0; i < merged.x.length; i++) {
        merged.x[i] = weightedAbscissa.x[i] / weightedAbscissa.y[i];
      }
      return merged;
    }

    /**
     * Convert an array of XY arrays to a DataXY object containing x,y arrays
     * @param data - array of arrays [[x,y],[x,y],...]
     */
    function xy2ToXY(data) {
      const xy2 = {
        x: [],
        y: []
      };
      for (const xyValue of data) {
        xy2.x.push(xyValue[0]);
        xy2.y.push(xyValue[1]);
      }
      return xy2;
    }

    /**
     * Sort object of array, x has to be monotone.
     * @param data - object of kind {x:[], re:[], im:[]}
     * @returns - sorted array
     */
    function xreimSortX(data) {
      const {
        x,
        re,
        im
      } = data;
      if (x.length !== re.length || x.length !== im.length) {
        throw new TypeError('length of x, re and im must be identical');
      }
      if (x.length < 2 || x[0] < x[1]) return data;
      return {
        x: x.slice(0).reverse(),
        re: re.slice(0).reverse(),
        im: im.slice(0).reverse()
      };
    }

    /**
     * This function make a zero filling to re and im part.
     * @param data - object of kind {x:[], re:[], im:[]}
     * @param totalLength - final number of points
     * @returns - data.
     */
    function xreimZeroFilling(data, totalLength) {
      const {
        x,
        re,
        im
      } = data;
      const length = x.length;
      if (totalLength === 0 || length === totalLength) return data;
      if (length > totalLength) {
        return {
          x: x.slice(0, totalLength),
          re: re.slice(0, totalLength),
          im: im.slice(0, totalLength)
        };
      }
      const newX = new Float64Array(totalLength);
      newX.set(x);
      const deltaX = (x.at(-1) - x[0]) / (length - 1);
      for (let i = length; i < totalLength; i++) {
        newX[i] = newX[i - 1] + deltaX;
      }
      return {
        x: newX,
        ...reimZeroFilling({
          re,
          im
        }, totalLength)
      };
    }

    /**
     * GetSlots.
     * @param data - data.
     * @param options - Options.
     */
    function getSlots(data, options = {}) {
      const {
        delta = 1
      } = options;
      const deltaIsFunction = typeof delta === 'function';
      const possibleXs = Float64Array.from(data.flatMap(spectrum => spectrum.x)).sort();
      if (possibleXs.length === 0) {
        throw new Error('can not process empty arrays');
      }
      let currentSlot = {
        from: possibleXs[0],
        to: possibleXs[0],
        average: possibleXs[0],
        sum: possibleXs[0],
        number: 1
      };
      const slots = [currentSlot];
      for (let i = 1; i < possibleXs.length; i++) {
        const currentDelta = deltaIsFunction ? delta(possibleXs[i]) : delta;
        if (possibleXs[i] - currentSlot.to <= currentDelta) {
          currentSlot.to = possibleXs[i];
          currentSlot.number++;
          currentSlot.sum += possibleXs[i];
          currentSlot.average = currentSlot.sum / currentSlot.number;
        } else {
          currentSlot = {
            from: possibleXs[i],
            to: possibleXs[i],
            average: possibleXs[i],
            sum: possibleXs[i],
            number: 1
          };
          slots.push(currentSlot);
        }
      }
      return slots;
    }

    /**
     * Aligns data, can be used for spectra
     * @param data - data
     * @param options - Options
     */
    function xyArrayAlign(data, options = {}) {
      const {
        delta = 1,
        requiredY = false
      } = options;
      data = data.map(spectrum => xyJoinX(spectrum, {
        delta
      }));
      const slots = getSlots(data, {
        delta
      });
      const x = Float64Array.from(slots.map(slot => slot.average));
      const ys = Array.from(data, () => new Float64Array(x.length));
      const positions = new Uint32Array(data.length);
      for (let i = 0; i < slots.length; i++) {
        const slot = slots[i];
        for (let j = 0; j < data.length; j++) {
          const spectrum = data[j];
          while (positions[j] < spectrum.x.length && spectrum.x[positions[j]] <= slot.to) {
            ys[j][i] += spectrum.y[positions[j]];
            positions[j]++;
          }
        }
      }
      if (requiredY) return filterRequiredY(x, ys);
      return {
        x,
        ys
      };
    }
    function filterRequiredY(x, ys) {
      const newX = [];
      const newYs = Array.from(ys, () => []);
      for (let i = 0; i < x.length; i++) {
        if (ys.every(y => y[i] !== 0)) {
          newX.push(x[i]);
          for (let j = 0; j < ys.length; j++) {
            newYs[j].push(ys[j][i]);
          }
        }
      }
      return {
        x: newX,
        ys: newYs
      };
    }

    /**
     * Merge DataXY
     * We have an array of DataXY and the goal is to merge all the values for which the deltaX is small or equal to delta.
     * X values are weighted average
     * @param data - data
     * @param options - Options
     */
    function xyArrayWeightedMerge(data, options = {}) {
      let {
        delta = 1
      } = options;
      if (typeof delta === 'number') {
        const deltaNumber = delta;
        delta = () => deltaNumber;
      }
      data = data.filter(spectrum => spectrum.x.length > 0);
      if (data.length === 0) return {
        x: [],
        y: []
      };
      const x = [];
      const y = [];
      const positions = new Array(data.length).fill(0);
      const point = {
        x: 0,
        y: 0
      };
      nextValue(data, positions, point);
      const slot = {
        maxX: point.x + delta(point.x),
        sumY: point.y,
        sumXY: point.y * point.x
      };
      while (data.length > 0) {
        nextValue(data, positions, point);
        const sameSlot = point.x <= slot.maxX;
        if (!sameSlot) {
          if (slot.sumY > 0) {
            x.push(slot.sumXY / slot.sumY);
            y.push(slot.sumY);
          }
          slot.sumY = 0;
          slot.sumXY = 0;
        }
        slot.sumY += point.y;
        slot.sumXY += point.x * point.y;
        slot.maxX = point.x + delta(point.x);
        if (data.length === 0 && slot.sumY > 0) {
          x.push(slot.sumXY / slot.sumY);
          y.push(slot.sumY);
        }
      }
      return {
        x,
        y
      };
    }
    /**
     * NextValue.
     * @param data - data.
     * @param positions - Positions array.
     * @param point - Point.
     */
    function nextValue(data, positions, point) {
      let minIndex = 0;
      let minX = data[0].x[positions[0]];
      for (let i = 1; i < data.length; i++) {
        const currentX = data[i].x[positions[i]];
        if (currentX < minX) {
          minX = currentX;
          minIndex = i;
        }
      }
      point.x = minX;
      point.y = data[minIndex].y[positions[minIndex]];
      positions[minIndex]++;
      if (positions[minIndex] === data[minIndex].x.length) {
        positions.splice(minIndex, 1);
        data.splice(minIndex, 1);
      }
    }

    /**
     * GetSlotsToFirst.
     * @param data - data.
     * @param options - Options.
     */
    function getSlotsToFirst(data, options = {}) {
      const {
        delta = 1
      } = options;
      const deltaIsFunction = typeof delta === 'function';
      const firstXs = data[0].x;
      const slots = [];
      // we first create the slots based on the first spectrum
      for (const element of firstXs) {
        const currentDelta = deltaIsFunction ? delta(element) : delta;
        slots.push({
          from: element - currentDelta,
          to: element + currentDelta,
          value: element
        });
      }
      const otherXs = xyArrayWeightedMerge(data.slice(1), options).x;
      let currentPosition = 0;
      for (const slot of slots) {
        while (otherXs[currentPosition] < slot.to && currentPosition < otherXs.length) {
          if (otherXs[currentPosition] < slot.from) {
            const currentDelta = deltaIsFunction ? delta(otherXs[currentPosition]) : delta;
            slots.push({
              from: otherXs[currentPosition] - currentDelta,
              to: otherXs[currentPosition] + currentDelta,
              value: otherXs[currentPosition]
            });
          }
          currentPosition++;
        }
      }
      for (let i = currentPosition; i < otherXs.length; i++) {
        const currentDelta = deltaIsFunction ? delta(otherXs[i]) : delta;
        slots.push({
          from: otherXs[i] - currentDelta,
          to: otherXs[i] + currentDelta,
          value: otherXs[i]
        });
      }
      slots.sort((a, b) => a.value - b.value);
      // we prevent slots overlap in the first spectrum
      for (let i = 0; i < slots.length - 1; i++) {
        if (slots[i].to > slots[i + 1].from) {
          const middle = (slots[i].value + slots[i + 1].value) / 2;
          slots[i].to = middle;
          slots[i + 1].from = middle;
        }
      }
      return slots;
    }

    /**
     * We align all the data/spectra to the first array of X.
     * The alignment is based on the X values of the first spectrum and the `delta` error allowed.
     * If some x values are missing in the first spectrum we will add them
     * @param data - data
     * @param options - options
     */
    function xyArrayAlignToFirst(data, options = {}) {
      const {
        delta = 1
      } = options;
      const slots = getSlotsToFirst(data, {
        delta
      });
      const x = Float64Array.from(slots.map(slot => slot.value));
      const ys = Array.from(data, () => new Float64Array(x.length));
      const positions = new Uint32Array(data.length);
      for (let i = 0; i < slots.length; i++) {
        const slot = slots[i];
        for (let j = 0; j < data.length; j++) {
          const spectrum = data[j];
          while (positions[j] < spectrum.x.length && spectrum.x[positions[j]] < slot.to) {
            ys[j][i] += spectrum.y[positions[j]];
            positions[j]++;
          }
        }
      }
      return {
        x,
        ys
      };
    }

    /**
     * Merge DataXY
     * We have an array of DataXY and the goal is to merge all the values that are the closest possible
     * @param data - data
     * @param options - Options
     */
    function xyArrayMerge(data, options = {}) {
      const {
        delta = 1
      } = options;
      // We start by checking that the data/spectra don't have peaks too close and we simplify them.
      data = data.map(spectrum => xyJoinX(spectrum, {
        delta
      }));
      // At first, we will calculate the X values (simple mean).
      const slots = getSlots(data, {
        delta
      });
      const x = Float64Array.from(slots.map(slot => slot.average));
      const y = new Float64Array(x.length);
      const positions = new Uint32Array(data.length);
      for (let i = 0; i < slots.length; i++) {
        const slot = slots[i];
        for (let j = 0; j < data.length; j++) {
          const spectrum = data[j];
          while (positions[j] < spectrum.x.length && spectrum.x[positions[j]] <= slot.to) {
            y[i] += spectrum.y[positions[j]];
            positions[j]++;
          }
        }
      }
      return {
        x,
        y
      };
    }

    /**
     * Throws an error if it's not an array of x,y objects.
     * Only checks the first element.
     * @param points - List of points.
     * @param options - Additional checks.
     */
    function xyObjectCheck(points, options = {}) {
      const {
        minLength = 0
      } = options;
      if (!Array.isArray(points)) {
        throw new Error('points must be an array of {x,y} objects');
      }
      if (points.length > 0 && (typeof points[0].x !== 'number' || typeof points[0].y !== 'number')) {
        throw new Error('points must be an array of {x,y} objects');
      }
      if (minLength && points.length < minLength) {
        throw new Error(`points must have a length of at least ${minLength}`);
      }
    }

    /**
     * Finds the max x value and return a {x,y,index} point
     * @param points - Object that contains property x (an ordered increasing array) and y (an array)
     */
    function xyObjectMaxXPoint(points = []) {
      xyObjectCheck(points);
      if (points.length === 0) return {
        x: 0,
        y: 0
      };
      let current = {
        x: points[0].x,
        y: points[0].y,
        index: 0
      };
      for (let i = 1; i < points.length; i++) {
        if (points[i].x > current.x) {
          current = {
            x: points[i].x,
            y: points[i].y,
            index: i
          };
        }
      }
      return current;
    }

    /**
     * Finds the min x value and return a {x,y,index} point
     * @param points - Object that contains property x (an ordered increasing array) and y (an array)
     */
    function xyObjectMinXPoint(points = []) {
      xyObjectCheck(points);
      if (points.length === 0) return {
        x: 0,
        y: 0
      };
      let current = {
        x: points[0].x,
        y: points[0].y,
        index: 0
      };
      for (let i = 1; i < points.length; i++) {
        if (points[i].x < current.x) {
          current = {
            x: points[i].x,
            y: points[i].y,
            index: i
          };
        }
      }
      return current;
    }

    /**
     * Filter the array by taking the higher points (max y value) and only.
     * Keep one per slot. There are 2 different slots, the smallest one will have the
     * new property `close` to true
     * @param points - array of all the points
     * @param options - Options
     * @returns - copy of points with 'close' property
     */
    function xyObjectBestPoints(points, options = {}) {
      const {
        from = xyObjectMinXPoint(points).x,
        to = xyObjectMaxXPoint(points).x,
        limit = 20,
        threshold = 0.01,
        numberCloseSlots = 50,
        numberSlots = 10
      } = options;
      const slot = (to - from) / numberSlots;
      const closeSlot = (to - from) / numberCloseSlots;
      let selected = points.filter(point => point.x >= from && point.x <= to).map(point => {
        return {
          point,
          monoisotopic: false
        };
      });
      selected = selected.sort((a, b) => {
        if (a.monoisotopic && !b.monoisotopic) return -1;
        if (b.monoisotopic && !a.monoisotopic) return 1;
        return b.point.y - a.point.y;
      });
      const toReturn = [];
      if (selected.length === 0) return [];
      const minY = selected[0].point.y * threshold;
      peakLoop: for (const item of selected) {
        if (item.point.y < minY) {
          if (item.monoisotopic) {
            continue;
          } else {
            break;
          }
        }
        let close = false;
        for (const existing of toReturn) {
          if (Math.abs(existing.x - item.point.x) < closeSlot) {
            continue peakLoop;
          }
          if (Math.abs(existing.x - item.point.x) < slot) {
            close = true;
          }
        }
        const newPeak = {
          ...item.point,
          close
        };
        toReturn.push(newPeak);
        if (toReturn.length === limit) break;
      }
      return toReturn.sort((a, b) => a.x - b.x);
    }

    /**
     * xyObjectJoinX.
     * @param points - Array of growing points {x,y}.
     * @param options - Options.
     */
    function xyObjectJoinX(points, options = {}) {
      const {
        xError = Number.EPSILON
      } = options;
      // when we join we will use the center of mass
      const result = [];
      let current = {
        x: Number.MIN_SAFE_INTEGER,
        y: 0
      };
      for (const point of points) {
        if (point.x - current.x <= xError) {
          // weighted sum
          if (current.y !== 0 || point.y !== 0) {
            current.x = point.y / (current.y + point.y) * (point.x - current.x) + current.x;
            current.y += point.y;
          }
        } else {
          current = {
            x: point.x,
            y: point.y
          };
          result.push(current);
        }
      }
      return result;
    }

    /**
     * Finds the max y value and return a {x,y,index} point
     * @param points - Object that contains property x (an ordered increasing array) and y (an array)
     * @returns max point
     */
    function xyObjectMaxYPoint(points = []) {
      xyObjectCheck(points);
      if (points.length === 0) return {
        x: 0,
        y: 0
      };
      let current = {
        x: points[0].x,
        y: points[0].y,
        index: 0
      };
      for (let i = 1; i < points.length; i++) {
        if (points[i].y > current.y) {
          current = {
            x: points[i].x,
            y: points[i].y,
            index: i
          };
        }
      }
      return current;
    }

    /**
     * Returns all min and max values of an array of points.
     * @param points - Array of points {x,y}.
     * @returns - Object with the 4 extrema.
     */
    function xyObjectMinMaxValues(points) {
      xyObjectCheck(points, {
        minLength: 1
      });
      let minX = points[0].x;
      let maxX = minX;
      let minY = points[0].y;
      let maxY = minY;
      for (const point of points) {
        if (point.x < minX) minX = point.x;
        if (point.x > maxX) maxX = point.x;
        if (point.y < minY) minY = point.y;
        if (point.y > maxY) maxY = point.y;
      }
      return {
        minX,
        maxX,
        minY,
        maxY
      };
    }

    /**
     * Finds the min y value and return a {x,y,index} point
     * @param points - Object that contains property x (an ordered increasing array) and y (an array)
     */
    function xyObjectMinYPoint(points = []) {
      xyObjectCheck(points);
      if (points.length === 0) return {
        x: 0,
        y: 0
      };
      let current = {
        x: points[0].x,
        y: points[0].y,
        index: 0
      };
      for (let i = 1; i < points.length; i++) {
        if (points[i].y < current.y) {
          current = {
            x: points[i].x,
            y: points[i].y,
            index: i
          };
        }
      }
      return current;
    }

    /**
     * Resize the Y values of the points to be normalized.
     * @param points - array of points {x,y}
     * @param options
     * @returns - array of points {x,y} with normalized Y values
     */
    function xyObjectNormedY(points, options) {
      points = structuredClone(points);
      const ys = points.map(point => point.y);
      const normalizedYs = xNormed(ys, options);
      for (let i = 0; i < points.length; i++) {
        points[i].y = normalizedYs[i];
      }
      return points;
    }

    /**
     * xyObjectSlotX
     * @param points - Array of growing points {x,y}.
     * @param options - Options.
     */
    function xyObjectSlotX(points, options = {}) {
      const {
        slotWidth = 1
      } = options;
      const halfSlot = slotWidth / 2;
      // when we join we will use the center of mass
      const result = [];
      let current = {
        x: Number.NEGATIVE_INFINITY,
        y: 0
      };
      for (const point of points) {
        const slot = point.x - (point.x + halfSlot) % slotWidth + halfSlot;
        if (Math.abs(current.x - slot) > Number.EPSILON) {
          current = {
            x: slot,
            y: 0
          };
          result.push(current);
        }
        current.y += point.y;
      }
      return result;
    }

    /**
     * Sorts an array of points in-place.
     * @param points - array of points {x,y}
     * @returns - sorted array of points {x,y}
     */
    function xyObjectSortX(points) {
      return points.sort((a, b) => a.x - b.x);
    }

    /**
     * Calculate the sum of Y values.
     * @param points - Object that contains property x and y (an array)
     */
    function xyObjectSumY(points = []) {
      xyObjectCheck(points);
      let sum = 0;
      for (const point of points) {
        sum += point.y;
      }
      return sum;
    }

    /**
     * xyObjectToXY.
     * @param points - Array of points {x,y}.
     */
    function xyObjectToXY(points) {
      return {
        x: points.map(entry => entry.x),
        y: points.map(entry => entry.y)
      };
    }

    /**
     * Returns the median of the absolute matrix.
     * @param matrix
     */
    function matrixAbsoluteMedian(matrix) {
      const nbColumns = matrix[0].length;
      const flatten = new Float64Array(matrix.length * nbColumns);
      for (let row = 0; row < matrix.length; row++) {
        const currentRow = row * nbColumns;
        for (let column = 0; column < nbColumns; column++) {
          const value = matrix[row][column];
          flatten[currentRow + column] = Math.abs(value);
        }
      }
      return xMedian(flatten);
    }

    /**
     * Create a new matrix based on the size of the current one or by using specific dimensions.
     * @param options
     */
    function matrixCreateEmpty(options) {
      const {
        matrix,
        nbRows = matrix?.length || 1,
        nbColumns = matrix?.[0].length || 1,
        ArrayConstructor = Float64Array
      } = options;
      const newMatrix = [];
      for (let row = 0; row < nbRows; row++) {
        newMatrix.push(createNumberArray(ArrayConstructor, nbColumns));
      }
      return newMatrix;
    }

    /**
     * Numerically encodes the strings in the matrix with an encoding dictionary.
     * @param matrixInitial - Original matrix before encoding.
     * @param dictionary - Dictionary against which to do the encoding.
     * @returns - Encoded matrix.
     */
    function matrixApplyNumericalEncoding(matrixInitial, dictionary) {
      const matrix = matrixCreateEmpty({
        nbRows: matrixInitial.length,
        nbColumns: matrixInitial[0].length,
        ArrayConstructor: Array
      });
      const arrayOfValues = [];
      for (const key in dictionary) {
        arrayOfValues.push(dictionary[key]);
      }
      let k = xMaxValue(arrayOfValues);
      for (let i = 0; i < matrix.length; i++) {
        for (let j = 0; j < matrix[0].length; j++) {
          if (typeof matrix[i][j] === 'string') {
            if (matrix[i][j] in dictionary) {
              matrix[i][j] = dictionary[matrix[i][j]];
            } else {
              k++;
              dictionary[matrix[i][j]] = k;
              matrix[i][j] = k;
            }
          }
        }
      }
      return matrix;
    }

    function matrixAutoCorrelation(matrix, index = 0) {
      const nbRows = matrix.length;
      const nbColumns = matrix[0].length;
      if (nbRows < 2) {
        throw new Error('can not calculate info if matrix contains less than 2 rows');
      }
      const targetArray = new Float64Array(nbRows);
      const sourceArray = new Float64Array(nbRows);
      const result = new Float64Array(nbColumns);
      for (let j = 0; j < nbRows; j++) {
        targetArray[j] = matrix[j][index];
      }
      for (let i = 0; i < nbColumns; i++) {
        for (let j = 0; j < nbRows; j++) {
          sourceArray[j] = matrix[j][i];
        }
        result[i] = xCorrelation(targetArray, sourceArray);
      }
      return result;
    }

    function matrixBoxPlot(matrix) {
      const nbRows = matrix.length;
      const nbColumns = matrix[0].length;
      if (nbRows < 5) {
        throw new Error('can not calculate info if matrix contains less than 5 rows');
      }
      const info = {
        q1: new Float64Array(nbColumns),
        median: new Float64Array(nbColumns),
        q3: new Float64Array(nbColumns),
        min: Float64Array.from(matrix[0]),
        max: Float64Array.from(matrix.at(-1))
      };
      const columnArray = new Float64Array(matrix.length);
      for (let column = 0; column < nbColumns; column++) {
        for (let row = 0; row < nbRows; row++) {
          columnArray[row] = matrix[row][column];
        }
        let q1max = 0;
        let q3min = 0;
        if (nbRows % 2 === 1) {
          // odd
          const middle = (nbRows - 1) / 2;
          info.median[column] = columnArray[middle];
          q1max = middle - 1;
          q3min = middle + 1;
        } else {
          // even
          q3min = nbRows / 2;
          q1max = q3min - 1;
          info.median[column] = (columnArray[q1max] + columnArray[q3min]) / 2;
        }
        if (q1max % 2 === 0) {
          info.q1[column] = columnArray[q1max / 2];
          info.q3[column] = columnArray[(nbRows + q3min - 1) / 2];
        } else {
          info.q1[column] = (columnArray[(q1max + 1) / 2] + columnArray[(q1max - 1) / 2]) / 2;
          const middleOver = (columnArray.length + q3min) / 2;
          info.q3[column] = (columnArray[middleOver] + columnArray[middleOver - 1]) / 2;
        }
      }
      return info;
    }

    /**
     * Center mean of matrix columns.
     * @param matrix - matrix [rows][cols]
     */
    function matrixCenterZMean(matrix) {
      const nbColumns = matrix[0].length;
      const nbRows = matrix.length;
      const newMatrix = matrixCreateEmpty({
        nbColumns,
        nbRows
      });
      for (let column = 0; column < nbColumns; column++) {
        let mean = 0;
        for (let row = 0; row < nbRows; row++) {
          mean += matrix[row][column];
        }
        mean /= nbRows;
        for (let row = 0; row < nbRows; row++) {
          newMatrix[row][column] = matrix[row][column] - mean;
        }
      }
      return newMatrix;
    }

    function matrixCheck(data) {
      if (data.length === 0 || data[0].length === 0) {
        throw new RangeError('matrix must contain data');
      }
      const firstLength = data[0].length;
      for (let i = 1; i < data.length; i++) {
        if (data[i].length !== firstLength) {
          throw new RangeError('all rows must has the same length');
        }
      }
    }

    /**
     * Clone a matrix.
     * @param matrix
     */
    function matrixClone(matrix) {
      return matrix.map(row => row.slice(0));
    }

    /**
     * Calculates a correlation matrix based on the columns of the initial matrix.
     * @param A - matrix [rows][cols]
     */
    function matrixColumnsCorrelation(A) {
      const B = new Matrix(A).transpose();
      const result = [];
      for (let i = 0; i < B.rows; i++) {
        result.push(new Float64Array(B.rows));
      }
      for (let i = 0; i < B.rows; i++) {
        for (let j = i; j < B.rows; j++) {
          const correlation = xCorrelation(B.getRow(i), B.getRow(j));
          result[i][j] = correlation;
          result[j][i] = correlation;
        }
      }
      return result;
    }

    /**
     * The MIT License (MIT)
     *
     * Copyright (c) 2013 Mikola Lysenko
     *
     * 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.
     */
    /**
     * The reverse Cuthill-Mckee method is a fast and effective preconditioner for reducing the bandwidth of sparse linear systems.
     * When solving a positive semidefinite linear system using Cholesky factorization, it greatly reduces fill-in.
     * It is a direct conversion to TS from {@link github.com/mikolalysenko/cuthill-mckee}
     * @param list - lower triangular non zeros from a symmetric sparse matrix.
     * @param dimension - matrix dimension
     * @returns A Float64Array where the value at each index represents the new position of the node
     *          in the bandwidth-reduced ordering.
     */
    function matrixCuthillMckee(list, dimension) {
      const adj = new Array(dimension);
      const visited = new Array(dimension).fill(false);
      for (let i = 0; i < dimension; ++i) {
        adj[i] = [];
      }
      for (const l of list) {
        adj[l[0]].push(l[1]);
      }
      const toVisit = new Float64Array(dimension);
      let eol = 0;
      let ptr = 0;
      for (let i = 0; i < dimension; ++i) {
        if (visited[i]) {
          continue;
        }
        toVisit[eol++] = i;
        visited[i] = true;
        while (ptr < eol) {
          const v = toVisit[ptr++];
          const nbhd = Float64Array.from(adj[v]).sort();
          for (const u of nbhd) {
            if (visited[u]) {
              continue;
            }
            visited[u] = true;
            toVisit[eol++] = u;
          }
        }
      }
      const result = new Float64Array(dimension);
      for (let i = 0; i < dimension; ++i) {
        result[toVisit[i]] = i;
      }
      return result;
    }

    /**
     * Checks if the specified submatrix boundaries are within the valid range of the given matrix.
     * @param matrix - The matrix to check the boundaries against.
     * @param boundaries - The boundaries of the submatrix.
     * @throws {RangeError} If any of the specified boundaries are out of the matrix's range.
     */
    function matrixCheckRanges(matrix, boundaries) {
      const {
        startRow,
        endRow,
        startColumn,
        endColumn
      } = boundaries;
      if (startRow > endRow || startColumn > endColumn || startRow < 0 || startRow >= matrix.length || endRow < 0 || endRow >= matrix.length || startColumn < 0 || startColumn >= matrix[0].length || endColumn < 0 || endColumn >= matrix[0].length) {
        throw new RangeError('submatrix indices are out of range');
      }
    }

    /**
     * Get a subMatrix from matrix, the function checks if the subMatrix
     * lies within the dimensions of the matrix.
     * @param matrix - The original matrix from which the subMatrix will be extracted.
     * @param options - Options to define the subMatrix boundaries and duplication behavior.
     * @returns The subMatrix extracted from the original matrix.
     */
    function matrixGetSubMatrix(matrix, options) {
      const {
        startRow = 0,
        endRow = matrix.length - 1,
        startColumn = 0,
        endColumn = matrix[0].length - 1,
        duplicate = true
      } = options;
      matrixCheckRanges(matrix, {
        startColumn,
        startRow,
        endColumn,
        endRow
      });
      const nbRows = endRow - startRow + 1;
      const subMatrix = [];
      if (duplicate) {
        for (let i = 0; i < nbRows; i++) {
          subMatrix.push(matrix[startRow + i].slice(startColumn, endColumn + 1));
        }
      } else {
        for (let i = 0; i < nbRows; i++) {
          subMatrix.push(matrix[startRow + i].subarray(startColumn, endColumn + 1));
        }
      }
      return subMatrix;
    }

    /**
     * Get min and max of the absolute values of Z.
     * @param matrix - matrix [rows][cols].
     */
    function matrixMinMaxAbsoluteZ(matrix) {
      if (matrix.length === 0 || matrix[0].length === 0) {
        throw new Error('matrixMinMaxAbsoluteZ requires at least 1 row and 1 column');
      }
      const nbRows = matrix.length;
      const nbColumns = matrix[0].length;
      let min = Number.POSITIVE_INFINITY;
      let max = Number.NEGATIVE_INFINITY;
      for (let column = 0; column < nbColumns; column++) {
        for (let row = 0; row < nbRows; row++) {
          let value = matrix[row][column];
          if (value < 0) value *= -1;
          if (value < min) min = value;
          if (value > max) max = value;
        }
      }
      return {
        min,
        max
      };
    }

    /**
     * Get min and max Z.
     * @param matrix - matrix [rows][cols].
     */
    function matrixMinMaxZ(matrix) {
      matrixCheck(matrix);
      const nbRows = matrix.length;
      const nbColumns = matrix[0].length;
      let min = matrix[0][0];
      let max = matrix[0][0];
      for (let column = 0; column < nbColumns; column++) {
        for (let row = 0; row < nbRows; row++) {
          if (matrix[row][column] < min) min = matrix[row][column];
          if (matrix[row][column] > max) max = matrix[row][column];
        }
      }
      return {
        min,
        max
      };
    }

    /**
     * Calculates a histogram of defined number of slots.
     * @param matrix - matrix [rows][cols].
     * @param options - options
     * @returns - Result of the histogram.
     */
    function matrixHistogram(matrix, options = {}) {
      let {
        min,
        max
      } = options;
      const {
        absolute,
        logBaseX,
        logBaseY,
        histogram,
        nbSlots,
        centerX
      } = options;
      if (matrix.length === 0 || matrix[0].length === 0) {
        throw new Error('matrix must have at least one column and one row');
      }
      if (min === undefined || max === undefined) {
        const minMax = absolute ? matrixMinMaxAbsoluteZ(matrix) : matrixMinMaxZ(matrix);
        if (min === undefined) {
          min = logBaseX && minMax.min ? Math.log(minMax.min) / Math.log(logBaseX) : minMax.min;
        }
        if (max === undefined) {
          max = logBaseX && minMax.max ? Math.log(minMax.max) / Math.log(logBaseX) : minMax.max;
        }
      }
      const xHistogramOptions = {
        histogram,
        nbSlots,
        centerX,
        absolute,
        logBaseX,
        // Skip logBaseY as it's handled below.
        min,
        max
      };
      const finalHistogram = xHistogram(matrix[0], xHistogramOptions);
      xHistogramOptions.histogram = finalHistogram;
      const nbRows = matrix.length;
      for (let row = 1; row < nbRows; row++) {
        xHistogram(matrix[row], xHistogramOptions);
      }
      const y = finalHistogram.y;
      if (logBaseY) {
        const logOfBase = Math.log10(logBaseY);
        for (let i = 0; i < y.length; i++) {
          y[i] = Math.log10(y[i] + 1) / logOfBase;
        }
      }
      return finalHistogram;
    }

    /**
     * Returns the max absolute values of Z.
     * @param matrix - matrix [rows][cols].
     */
    function matrixMaxAbsoluteZ(matrix) {
      if (matrix.length === 0 || matrix[0].length === 0) {
        throw new Error('matrix must have at least 1 row and 1 column');
      }
      const nbRows = matrix.length;
      const nbColumns = matrix[0].length;
      let max = Number.NEGATIVE_INFINITY;
      for (let column = 0; column < nbColumns; column++) {
        for (let row = 0; row < nbRows; row++) {
          const value = Math.abs(matrix[row][column]);
          if (value > max) max = value;
        }
      }
      return max;
    }

    /**
     * Convert a matrix to a flat Float64Array.
     * @param matrix
     */
    function matrixToArray(matrix) {
      matrixCheck(matrix);
      const nbColumns = matrix[0].length;
      const flatten = new Float64Array(matrix.length * nbColumns);
      for (let row = 0; row < matrix.length; row++) {
        const currentRow = row * nbColumns;
        for (let column = 0; column < nbColumns; column++) {
          flatten[currentRow + column] = matrix[row][column];
        }
      }
      return flatten;
    }

    /**
     * Returns the median of the matrix.
     * @param matrix
     */
    function matrixMedian(matrix) {
      return xMedian(matrixToArray(matrix));
    }

    /**
     * Determine noise level using MAD https://en.wikipedia.org/wiki/Median_absolute_deviation
     * Constant to convert mad to sd calculated using https://www.wolframalpha.com/input?i=sqrt%282%29+inverse+erf%280.5%29
     * This assumes a gaussian distribution of the noise.
     * @param matrix
     * @returns Noise level corresponding to one standard deviation.
     */
    function matrixNoiseStandardDeviation(matrix) {
      return xNoiseStandardDeviation(matrixToArray(matrix));
    }

    /**
     * Numerically decodes the matrix using the dictionary.
     * @param matrixInitial
     * @param dictionary - dictionary against which to do the encoding
     * @returns - decoded matrix
     */
    function matrixNumericalDecoding(matrixInitial, dictionary) {
      const matrix = matrixClone(matrixInitial);
      const invertedDictionary = swap(dictionary);
      for (let i = 0; i < matrix.length; i++) {
        for (let j = 0; j < matrix[0].length; j++) {
          const value = matrix[i][j];
          if (typeof value === 'number' && value in invertedDictionary) {
            matrix[i][j] = invertedDictionary[value];
          }
        }
      }
      return matrix;
    }
    function swap(dictionary) {
      const ret = {};
      for (const key in dictionary) {
        ret[dictionary[key]] = key;
      }
      return ret;
    }

    /**
     * Numerically encodes the strings in the matrix and returns an encoding dictionary which can be used to encode other matrices
     * @param matrixInitial - original matrix before encoding
     * @returns - dictionary from string to number
     */
    function matrixNumericalEncoding(matrixInitial) {
      const matrix = matrixClone(matrixInitial);
      const nRows = matrix.length;
      const nColumns = matrix[0].length;
      let k = 0;
      for (let i = 0; i < nRows; i++) {
        for (let j = 0; j < nColumns; j++) {
          const value = matrix[i][j];
          if (typeof value === 'number' && value > k) {
            k = value;
          }
        }
      }
      const dictCategoricalToNumerical = {};
      for (let i = 0; i < nRows; i++) {
        for (let j = 0; j < nColumns; j++) {
          const value = matrix[i][j];
          if (typeof value === 'string') {
            if (value in dictCategoricalToNumerical) {
              matrix[i][j] = dictCategoricalToNumerical[value];
            } else {
              k++;
              dictCategoricalToNumerical[value] = k;
              matrix[i][j] = k;
            }
          }
        }
      }
      return {
        matrix: matrix,
        dictCategoricalToNumerical
      };
    }

    /**
     * Performs a Probabilistic quotient normalization (PQN) over the dataset to account dilution based in median spectrum.
     * Dieterle, F., Ross, A., Schlotterbeck, G., & Senn, H. (2006). Probabilistic quotient normalization as robust method to account for dilution of complex biological mixtures. Application in 1H NMR metabonomics. Analytical chemistry, 78(13), 4281-4290.
     * DOI: 10.1021/ac051632c
     * @param matrix - matrix [rows][cols].
     * @param options - options
     * @returns - {data: Normalized dataset, medianOfQuotients: The median of quotients of each variables}
     */
    function matrixPQN(matrix, options = {}) {
      const {
        max = 100
      } = options;
      const matrixB = new Matrix(matrix);
      for (let i = 0; i < matrixB.rows; i++) {
        const normalizationFactor = matrixB.getRowVector(i).norm('frobenius') / max;
        const row = matrixB.getRowVector(i).div(normalizationFactor);
        matrixB.setRow(i, row);
      }
      const referenceSpectrum = [];
      for (let i = 0; i < matrixB.columns; i++) {
        const currentVariable = matrixB.getColumn(i);
        referenceSpectrum.push(xMedian(currentVariable));
      }
      const medianOfQuotients = [];
      for (let i = 0; i < matrixB.columns; i++) {
        const quotients = matrixB.getColumnVector(i).div(referenceSpectrum[i]);
        medianOfQuotients.push(xMedian(quotients.getColumn(0)));
      }
      for (let i = 0; i < matrixB.rows; i++) {
        matrixB.mulRow(i, 1 / medianOfQuotients[i]);
      }
      return {
        data: matrixB.to2DArray(),
        medianOfQuotients
      };
    }

    /**
     * Set in-place a subMatrix to matrix, the function check if the subMatrix
     * lies into the dimensions of matrix.
     * @param matrix - matrix that will receive the new element values.
     * @param subMatrix - matrix with equal or less size than matrix.
     * @param startRow - row index in matrix for the first row in subMatrix.
     * @param startColumn - column index in matrix for the first column in subMatrix.
     * @returns The modified `matrix`.
     */
    function matrixSetSubMatrix(matrix, subMatrix, startRow, startColumn) {
      const endRow = startRow + subMatrix.length - 1;
      const endColumn = startColumn + subMatrix[0].length - 1;
      matrixCheckRanges(matrix, {
        startRow,
        endRow,
        startColumn,
        endColumn
      });
      for (let i = 0; i < subMatrix.length; i++) {
        for (let j = 0; j < subMatrix[0].length; j++) {
          matrix[startRow + i][startColumn + j] = subMatrix[i][j];
        }
      }
      return matrix;
    }

    /**
     * Rescale a matrix around 0 taking into account the absolute max value.
     * @param matrix - matrix [rows][cols].
     * @param options - Options.
     */
    function matrixZPivotRescale(matrix, options = {}) {
      const {
        max = 1,
        ArrayConstructor
      } = options;
      const nbColumns = matrix[0].length;
      const nbRows = matrix.length;
      const newMatrix = matrixCreateEmpty({
        nbColumns,
        nbRows,
        ArrayConstructor
      });
      const currentMax = matrixMaxAbsoluteZ(matrix);
      for (let column = 0; column < nbColumns; column++) {
        const factor = max / currentMax;
        for (let row = 0; row < nbRows; row++) {
          newMatrix[row][column] = matrix[row][column] * factor;
        }
      }
      return newMatrix;
    }

    /**
     * Rescale a matrix between min and max values.
     * @param matrix - matrix [rows][cols].
     * @param options - Options.
     */
    function matrixZRescale(matrix, options = {}) {
      const {
        min = 0,
        max = 1,
        ArrayConstructor
      } = options;
      const nbColumns = matrix[0].length;
      const nbRows = matrix.length;
      const newMatrix = matrixCreateEmpty({
        nbColumns,
        nbRows,
        ArrayConstructor
      });
      const {
        min: currentMin,
        max: currentMax
      } = matrixMinMaxZ(matrix);
      const factor = (max - min) / (currentMax - currentMin);
      for (let column = 0; column < nbColumns; column++) {
        for (let row = 0; row < nbRows; row++) {
          newMatrix[row][column] = (matrix[row][column] - currentMin) * factor + min;
        }
      }
      return newMatrix;
    }

    /**
     * Rescale the matrix per column for which we get the min and max values.
     * @param matrix - matrix [rows][cols].
     * @param options - Options.
     */
    function matrixZRescalePerColumn(matrix, options = {}) {
      const {
        min = 0,
        max = 1
      } = options;
      const nbColumns = matrix[0].length;
      const nbRows = matrix.length;
      const newMatrix = matrixCreateEmpty({
        nbColumns,
        nbRows
      });
      for (let column = 0; column < nbColumns; column++) {
        let currentMin = matrix[0][column];
        let currentMax = matrix[0][column];
        for (let row = 1; row < nbRows; row++) {
          if (matrix[row][column] < currentMin) currentMin = matrix[row][column];
          if (matrix[row][column] > currentMax) currentMax = matrix[row][column];
        }
        const factor = (max - min) / (currentMax - currentMin);
        for (let row = 0; row < nbRows; row++) {
          newMatrix[row][column] = (matrix[row][column] - currentMin) * factor + min;
        }
      }
      return newMatrix;
    }

    function matrixTranspose(matrix, options = {}) {
      matrixCheck(matrix);
      const {
        ArrayConstructor
      } = options;
      const nbRows = matrix.length;
      const nbColumns = matrix[0].length;
      // Create new matrix with swapped dimensions
      const result = matrixCreateEmpty({
        nbColumns: nbRows,
        nbRows: nbColumns,
        ArrayConstructor
      });
      for (let i = 0; i < nbRows; i++) {
        for (let j = 0; j < nbColumns; j++) {
          result[j][i] = matrix[i][j];
        }
      }
      return result;
    }

    var libEsm$2 = /*#__PURE__*/Object.freeze({
        __proto__: null,
        calculateAdaptiveWeights: calculateAdaptiveWeights,
        createDoubleArray: createDoubleArray,
        createFromToArray: createFromToArray,
        createNumberArray: createNumberArray,
        createRandomArray: createRandomArray,
        createStepArray: createStepArray,
        getOutputArray: getOutputArray,
        getRescaler: getRescaler,
        isPowerOfTwo: isPowerOfTwo,
        matrixAbsoluteMedian: matrixAbsoluteMedian,
        matrixApplyNumericalEncoding: matrixApplyNumericalEncoding,
        matrixAutoCorrelation: matrixAutoCorrelation,
        matrixBoxPlot: matrixBoxPlot,
        matrixCenterZMean: matrixCenterZMean,
        matrixCheck: matrixCheck,
        matrixCholeskySolver: matrixCholeskySolver,
        matrixClone: matrixClone,
        matrixColumnsCorrelation: matrixColumnsCorrelation,
        matrixCreateEmpty: matrixCreateEmpty,
        matrixCuthillMckee: matrixCuthillMckee,
        matrixGetSubMatrix: matrixGetSubMatrix,
        matrixHistogram: matrixHistogram,
        matrixMaxAbsoluteZ: matrixMaxAbsoluteZ,
        matrixMedian: matrixMedian,
        matrixMinMaxAbsoluteZ: matrixMinMaxAbsoluteZ,
        matrixMinMaxZ: matrixMinMaxZ,
        matrixNoiseStandardDeviation: matrixNoiseStandardDeviation,
        matrixNumericalDecoding: matrixNumericalDecoding,
        matrixNumericalEncoding: matrixNumericalEncoding,
        matrixPQN: matrixPQN,
        matrixSetSubMatrix: matrixSetSubMatrix,
        matrixToArray: matrixToArray,
        matrixTranspose: matrixTranspose,
        matrixZPivotRescale: matrixZPivotRescale,
        matrixZRescale: matrixZRescale,
        matrixZRescalePerColumn: matrixZRescalePerColumn,
        nextPowerOfTwo: nextPowerOfTwo,
        recursiveResolve: recursiveResolve,
        recursiveUntypeArrays: recursiveUntypeArrays,
        reimAbsolute: reimAbsolute,
        reimAutoPhaseCorrection: reimAutoPhaseCorrection,
        reimFFT: reimFFT,
        reimPhaseCorrection: reimPhaseCorrection,
        reimZeroFilling: reimZeroFilling,
        stringify: stringify$1,
        xAbsolute: xAbsolute,
        xAbsoluteMedian: xAbsoluteMedian,
        xAbsoluteSum: xAbsoluteSum,
        xAdd: xAdd,
        xApplyFunctionStr: xApplyFunctionStr,
        xAutoCorrelation: xAutoCorrelation,
        xBoxPlot: xBoxPlot,
        xBoxPlotWithOutliers: xBoxPlotWithOutliers,
        xCheck: xCheck,
        xCheckLengths: xCheckLengths,
        xCorrelation: xCorrelation,
        xCostMatrix: xCostMatrix,
        xCrossCorrelation: xCrossCorrelation,
        xCumulative: xCumulative,
        xDistributionStats: xDistributionStats,
        xDivide: xDivide,
        xDotProduct: xDotProduct,
        xEnsureFloat64: xEnsureFloat64,
        xFindClosestIndex: xFindClosestIndex,
        xGetFromToIndex: xGetFromToIndex,
        xGetTargetIndex: xGetTargetIndex,
        xHilbertTransform: xHilbertTransform,
        xHistogram: xHistogram,
        xIsEquallySpaced: xIsEquallySpaced,
        xIsMonotonic: xIsMonotonic,
        xMassCenterVectorSimilarity: xMassCenterVectorSimilarity,
        xMaxAbsoluteValue: xMaxAbsoluteValue,
        xMaxIndex: xMaxIndex,
        xMaxValue: xMaxValue,
        xMean: xMean,
        xMeanAbsoluteError: xMeanAbsoluteError,
        xMeanSquaredError: xMeanSquaredError,
        xMeanWeighted: xMeanWeighted,
        xMedian: xMedian,
        xMedianAbsoluteDeviation: xMedianAbsoluteDeviation,
        xMinIndex: xMinIndex,
        xMinMaxDelta: xMinMaxDelta,
        xMinMaxValues: xMinMaxValues,
        xMinValue: xMinValue,
        xMode: xMode,
        xMultiply: xMultiply,
        xNoiseSanPlot: xNoiseSanPlot,
        xNoiseStandardDeviation: xNoiseStandardDeviation,
        xNorm: xNorm,
        xNormed: xNormed,
        xPadding: xPadding,
        xParetoNormalization: xParetoNormalization,
        xRemoveOutliersIQR: xRemoveOutliersIQR,
        xRescale: xRescale,
        xRobustDistributionStats: xRobustDistributionStats,
        xRolling: xRolling,
        xRollingAverage: xRollingAverage,
        xRollingMax: xRollingMax,
        xRollingMedian: xRollingMedian,
        xRollingMin: xRollingMin,
        xRotate: xRotate,
        xSampling: xSampling,
        xSequentialFillFromStep: xSequentialFillFromStep,
        xSequentialFillFromTo: xSequentialFillFromTo,
        xSortAscending: xSortAscending,
        xSortDescending: xSortDescending,
        xStandardDeviation: xStandardDeviation,
        xSubtract: xSubtract,
        xSum: xSum,
        xUniqueSorted: xUniqueSorted,
        xVariance: xVariance,
        xWhitakerSmoother: xWhitakerSmoother,
        xWhittakerSmoother: xWhittakerSmoother,
        xreimSortX: xreimSortX,
        xreimZeroFilling: xreimZeroFilling,
        xy2ToXY: xy2ToXY,
        xyAlign: xyAlign,
        xyArrayAlign: xyArrayAlign,
        xyArrayAlignToFirst: xyArrayAlignToFirst,
        xyArrayMerge: xyArrayMerge,
        xyArrayWeightedMerge: xyArrayWeightedMerge,
        xyCheck: xyCheck,
        xyCovariance: xyCovariance,
        xyCumulativeDistributionStatistics: xyCumulativeDistributionStatistics,
        xyEnsureFloat64: xyEnsureFloat64,
        xyEnsureGrowingX: xyEnsureGrowingX,
        xyEquallySpaced: xyEquallySpaced,
        xyExtract: xyExtract,
        xyFilter: xyFilter,
        xyFilterMinYValue: xyFilterMinYValue,
        xyFilterTopYValues: xyFilterTopYValues,
        xyFilterX: xyFilterX,
        xyFilterXPositive: xyFilterXPositive,
        xyFindClosestPoint: xyFindClosestPoint,
        xyGetNMaxY: xyGetNMaxY,
        xyGrowingX: xyGrowingX,
        xyIntegral: xyIntegral,
        xyIntegration: xyIntegration,
        xyJoinX: xyJoinX,
        xyMassCenter: xyMassCenter,
        xyMassCenterVector: xyMassCenterVector,
        xyMaxClosestYPoint: xyMaxClosestYPoint,
        xyMaxMerge: xyMaxMerge,
        xyMaxY: xyMaxY,
        xyMaxYPoint: xyMaxYPoint,
        xyMaximaY: xyMaximaY,
        xyMedian: xyMedian,
        xyMergeByCentroids: xyMergeByCentroids,
        xyMinClosestYPoint: xyMinClosestYPoint,
        xyMinYPoint: xyMinYPoint,
        xyMinimaY: xyMinimaY,
        xyObjectBestPoints: xyObjectBestPoints,
        xyObjectCheck: xyObjectCheck,
        xyObjectJoinX: xyObjectJoinX,
        xyObjectMaxXPoint: xyObjectMaxXPoint,
        xyObjectMaxYPoint: xyObjectMaxYPoint,
        xyObjectMinMaxValues: xyObjectMinMaxValues,
        xyObjectMinXPoint: xyObjectMinXPoint,
        xyObjectMinYPoint: xyObjectMinYPoint,
        xyObjectNormedY: xyObjectNormedY,
        xyObjectSlotX: xyObjectSlotX,
        xyObjectSortX: xyObjectSortX,
        xyObjectSumY: xyObjectSumY,
        xyObjectToXY: xyObjectToXY,
        xyPeakInfo: xyPeakInfo,
        xyRealMaxYPoint: xyRealMaxYPoint,
        xyRealMinYPoint: xyRealMinYPoint,
        xyReduce: xyReduce,
        xyReduceNonContinuous: xyReduceNonContinuous,
        xyRolling: xyRolling,
        xySetYValue: xySetYValue,
        xySortX: xySortX,
        xyToXYArray: xyToXYArray,
        xyToXYObject: xyToXYObject,
        xyUniqueX: xyUniqueX,
        xyWeightedMerge: xyWeightedMerge,
        zonesNormalize: zonesNormalize,
        zonesWithPoints: zonesWithPoints
    });

    function addStyle(serie, spectrum, options = {}) {
      let {
        color = '#A9A9A9'
      } = options;
      const {
        opacity = 1,
        lineWidth = 1
      } = options;
      if (color.match(/#[\da-f]{6}$/i)) {
        color = (color + (opacity * 255 >> 0).toString(16)).toUpperCase();
      } else {
        color = color.replace(/rgb ?\((.*)\)/, `rgba($1,${opacity})`);
      }
      serie.style = [{
        name: 'unselected',
        style: {
          line: {
            color,
            width: lineWidth,
            dash: 1
          }
        }
      }, {
        name: 'selected',
        style: {
          line: {
            color,
            width: lineWidth + 2,
            dash: 1
          }
        }
      }];
      serie.name = spectrum.label || spectrum.id;
    }

    const COLORS = ['#FFB300', '#803E75', '#FF6800', '#A6BDD7', '#C10020', '#CEA262', '#817066', '#007D34', '#F6768E', '#00538A', '#FF7A5C', '#53377A', '#FF8E00', '#B32851', '#F4C800', '#7F180D', '#93AA00', '#593315', '#F13A13', '#232C16'];

    /**
     * Generate a jsgraph chart format from an array of Analysis
     * @param analyses
     * @param options
     */
    function getJSGraph(analyses, options = {}) {
      const {
        colors = COLORS,
        opacities = [1],
        linesWidth = [1],
        selector = {},
        normalization,
        xAxis = {},
        yAxis = {}
      } = options;
      const series = [];
      let xLabel = xAxis.label;
      let yLabel = yAxis.label;
      let xUnits = xAxis.units;
      let yUnits = yAxis.units;
      for (let i = 0; i < analyses.length; i++) {
        const analysis = analyses[i];
        const spectra = analysis.getNormalizedSpectra({
          selector,
          normalization
        });
        if (spectra.length === 0) continue;
        const firstSpectrum = spectra[0];
        // todo: if many spectra are available and not xUnits / yUnits are specified we should ensure that all the spectra are compatible
        if (!xLabel) xLabel = firstSpectrum.variables.x.label;
        if (!yLabel) yLabel = firstSpectrum.variables.y.label;
        if (!xUnits) xUnits = firstSpectrum.variables.x.units;
        if (!yUnits) yUnits = firstSpectrum.variables.y.units;
        for (const spectrum of spectra) {
          const serie = {};
          addStyle(serie, analysis, {
            color: colors[i % colors.length],
            opacity: opacities[i % opacities.length],
            lineWidth: linesWidth[i % linesWidth.length]
          });
          serie.data = {
            x: spectrum.variables.x.data,
            y: spectrum.variables.y.data
          };
          serie.id = spectrum.id;
          if (xAxis.logScale) {
            serie.data = xyFilterXPositive(serie.data);
          }
          series.push(serie);
        }
      }
      return {
        axes: {
          x: {
            label: xLabel,
            unit: xUnits,
            unitWrapperBefore: '(',
            unitWrapperAfter: ')',
            flipped: false,
            display: true,
            ...xAxis
          },
          y: {
            label: yLabel,
            unit: yUnits,
            unitWrapperBefore: '(',
            unitWrapperAfter: ')',
            flipped: false,
            display: true,
            ...yAxis
          }
        },
        series
      };
    }

    function getNormalizationAnnotations(filter = {}, boundary = {
      y: {
        min: '0px',
        max: '2000px'
      }
    }) {
      let {
        exclusions = []
      } = filter;
      let annotations = [];
      exclusions = exclusions.filter(exclusion => !exclusion.ignore);
      annotations = exclusions.map(exclusion => {
        const annotation = {
          type: 'rect',
          position: [{
            x: exclusion.from,
            y: boundary.y.min
          }, {
            x: exclusion.to,
            y: boundary.y.max
          }],
          strokeWidth: 0,
          fillColor: 'rgba(255,255,224,1)'
        };
        return annotation;
      });
      if (filter.from !== undefined) {
        annotations.push({
          type: 'rect',
          position: [{
            x: Number.MIN_SAFE_INTEGER,
            y: boundary.y.min
          }, {
            x: filter.from,
            y: boundary.y.max
          }],
          strokeWidth: 0,
          fillColor: 'rgba(255,255,224,1)'
        });
      }
      if (filter.to !== undefined) {
        annotations.push({
          type: 'rect',
          position: [{
            x: filter.to,
            y: boundary.y.min
          }, {
            x: Number.MAX_SAFE_INTEGER,
            y: boundary.y.max
          }],
          strokeWidth: 0,
          fillColor: 'rgba(255,255,224,1)'
        });
      }
      return annotations;
    }

    /**
     * Center the mean
     * @param data
     */
    function centerMean(data) {
      const {
        y
      } = data;
      const mean = xMean(y);
      for (let i = 0; i < y.length; i++) {
        y[i] -= mean;
      }
      return {
        data
      };
    }

    /**
     * Center the median
     * @param data
     */
    function centerMedian(data) {
      const {
        y
      } = data;
      const median = xMedian(y);
      for (let i = 0; i < y.length; i++) {
        y[i] -= median;
      }
      return {
        data
      };
    }

    /**
     * Filter that allows to
     * @param data
     * @param options
     */
    function fromTo(data, options = {}) {
      const {
        fromIndex,
        toIndex
      } = xGetFromToIndex(data.x, options);
      return {
        data: {
          x: data.x.subarray(fromIndex, toIndex + 1),
          y: data.y.subarray(fromIndex, toIndex + 1)
        }
      };
    }

    /**
     * Norm the Y values
     * @param data
     * @param options
     */
    function normed(data, options = {}) {
      xNormed(data.y, {
        ...options,
        output: data.y
      });
      return {
        data
      };
    }

    /**
     * Center the mean
     * @param data
     */
    function divideBySD(data) {
      const {
        y
      } = data;
      const sd = xStandardDeviation(y);
      for (let i = 0; i < y.length; i++) {
        y[i] /= sd;
      }
      return {
        data
      };
    }

    /**
     * Center the mean
     * @param data
     * @param options
     */
    function rescale(data, options = {}) {
      xRescale(data.y, {
        ...options,
        output: data.y
      });
      return {
        data
      };
    }

    /**
     * Filter that allows to
     * @param data
     */
    function paretoNormalization(data) {
      return {
        data: {
          x: data.x,
          y: xParetoNormalization(data.y)
        }
      };
    }

    // Based on https://github.com/scijs/cholesky-solve

    /*
    The MIT License (MIT)

    Copyright (c) 2013 Eric Arnebäck

    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.
    */

    function ldlSymbolic(n /* A and L are n-by-n, where n >= 0 */, Ap /* input of size n + 1, not modified */, Ai /* input of size nz=Ap[n], not modified */, Lp /* output of size n + 1, not defined on input */, Parent /* output of size n, not defined on input */, Lnz /* output of size n, not defined on input */, Flag /* workspace of size n, not defn. on input or output */) {
      let i, k, p, kk, p2;
      for (k = 0; k < n; k++) {
        /* L(k,:) pattern: all nodes reachable in etree from nz in A(0:k-1,k) */
        Parent[k] = -1; /* parent of k is not yet known */
        Flag[k] = k; /* mark node k as visited */
        Lnz[k] = 0; /* count of nonzeros in column k of L */
        kk = k; /* kth original, or permuted, column */
        p2 = Ap[kk + 1];
        for (p = Ap[kk]; p < p2; p++) {
          /* A (i,k) is nonzero (original or permuted A) */
          i = Ai[p];
          if (i < k) {
            /* follow path from i to root of etree, stop at flagged node */
            for (; Flag[i] !== k; i = Parent[i]) {
              /* find parent of i if not yet determined */
              if (Parent[i] === -1) Parent[i] = k;
              Lnz[i]++; /* L (k,i) is nonzero */
              Flag[i] = k; /* mark i as visited */
            }
          }
        }
      }
      /* construct Lp index array from Lnz column counts */
      Lp[0] = 0;
      for (k = 0; k < n; k++) {
        Lp[k + 1] = Lp[k] + Lnz[k];
      }
    }
    function ldlNumeric(n /* A and L are n-by-n, where n >= 0 */, Ap /* input of size n+1, not modified */, Ai /* input of size nz=Ap[n], not modified */, Ax /* input of size nz=Ap[n], not modified */, Lp /* input of size n+1, not modified */, Parent /* input of size n, not modified */, Lnz /* output of size n, not defn. on input */, Li /* output of size lnz=Lp[n], not defined on input */, Lx /* output of size lnz=Lp[n], not defined on input */, D /* output of size n, not defined on input */, Y /* workspace of size n, not defn. on input or output */, Pattern /* workspace of size n, not defn. on input or output */, Flag /* workspace of size n, not defn. on input or output */) {
      let yi, lKi;
      let i, k, p, kk, p2, len, top;
      for (k = 0; k < n; k++) {
        /* compute nonzero Pattern of kth row of L, in topological order */
        Y[k] = 0.0; /* Y(0:k) is now all zero */
        top = n; /* stack for pattern is empty */
        Flag[k] = k; /* mark node k as visited */
        Lnz[k] = 0; /* count of nonzeros in column k of L */
        kk = k; /* kth original, or permuted, column */
        p2 = Ap[kk + 1];
        for (p = Ap[kk]; p < p2; p++) {
          i = Ai[p]; /* get A(i,k) */
          if (i <= k) {
            Y[i] += Ax[p]; /* scatter A(i,k) into Y (sum duplicates) */
            for (len = 0; Flag[i] !== k; i = Parent[i]) {
              Pattern[len++] = i; /* L(k,i) is nonzero */
              Flag[i] = k; /* mark i as visited */
            }
            while (len > 0) Pattern[--top] = Pattern[--len];
          }
        }
        /* compute numerical values kth row of L (a sparse triangular solve) */
        D[k] = Y[k]; /* get D(k,k) and clear Y(k) */
        Y[k] = 0.0;
        for (; top < n; top++) {
          i = Pattern[top]; /* Pattern[top:n-1] is pattern of L(:,k) */
          yi = Y[i]; /* get and clear Y(i) */
          Y[i] = 0.0;
          p2 = Lp[i] + Lnz[i];
          for (p = Lp[i]; p < p2; p++) {
            Y[Li[p]] -= Lx[p] * yi;
          }
          lKi = yi / D[i]; /* the nonzero entry L(k,i) */
          D[k] -= lKi * yi;
          Li[p] = k; /* store L(k,i) in column form of L */
          Lx[p] = lKi;
          Lnz[i]++; /* increment count of nonzeros in col i */
        }
        if (D[k] === 0.0) return k; /* failure, D(k,k) is zero */
      }
      return n; /* success, diagonal of D is all nonzero */
    }
    function ldlLsolve(n /* L is n-by-n, where n >= 0 */, X /* size n. right-hand-side on input, soln. on output */, Lp /* input of size n+1, not modified */, Li /* input of size lnz=Lp[n], not modified */, Lx /* input of size lnz=Lp[n], not modified */) {
      let j, p, p2;
      for (j = 0; j < n; j++) {
        p2 = Lp[j + 1];
        for (p = Lp[j]; p < p2; p++) {
          X[Li[p]] -= Lx[p] * X[j];
        }
      }
    }
    function ldlDsolve(n /* D is n-by-n, where n >= 0 */, X /* size n. right-hand-side on input, soln. on output */, D /* input of size n, not modified */) {
      let j;
      for (j = 0; j < n; j++) {
        X[j] /= D[j];
      }
    }
    function ldlLTsolve(n /* L is n-by-n, where n >= 0 */, X /* size n. right-hand-side on input, soln. on output */, Lp /* input of size n+1, not modified */, Li /* input of size lnz=Lp[n], not modified */, Lx /* input of size lnz=Lp[n], not modified */) {
      let j, p, p2;
      for (j = n - 1; j >= 0; j--) {
        p2 = Lp[j + 1];
        for (p = Lp[j]; p < p2; p++) {
          X[j] -= Lx[p] * X[Li[p]];
        }
      }
    }
    function ldlPerm(n /* size of X, B, and P */, X /* output of size n. */, B /* input of size n. */, P /* input permutation array of size n. */) {
      let j;
      for (j = 0; j < n; j++) {
        X[j] = B[P[j]];
      }
    }
    function ldlPermt(n /* size of X, B, and P */, X /* output of size n. */, B /* input of size n. */, P /* input permutation array of size n. */) {
      let j;
      for (j = 0; j < n; j++) {
        X[P[j]] = B[j];
      }
    }
    function prepare(M, n, P) {
      // if a permutation was specified, apply it.
      if (P) {
        let Pinv = new Array(n);
        for (let k = 0; k < n; k++) {
          Pinv[P[k]] = k;
        }
        let Mt = []; // scratch memory
        // Apply permutation. We make M into P*M*P^T
        for (let a = 0; a < M.length; ++a) {
          let ar = Pinv[M[a][0]];
          let ac = Pinv[M[a][1]];

          // we only store the upper-diagonal elements(since we assume matrix is symmetric, we only need to store these)
          // if permuted element is below diagonal, we simply transpose it.
          if (ac < ar) {
            let t = ac;
            ac = ar;
            ar = t;
          }
          Mt[a] = [];
          Mt[a][0] = ar;
          Mt[a][1] = ac;
          Mt[a][2] = M[a][2];
        }
        M = Mt; // copy scratch memory.
      } else {
        // if P argument is null, we just use an identity permutation.
        P = [];
        for (let i = 0; i < n; ++i) {
          P[i] = i;
        }
      }

      // The sparse matrix we are decomposing is A.
      // Now we shall create A from M.
      let Ap = new Array(n + 1);
      let Ai = new Array(M.length);
      let Ax = new Array(M.length);

      // count number of non-zero elements in columns.
      let LNZ = [];
      for (let i = 0; i < n; ++i) {
        LNZ[i] = 0;
      }
      for (let a = 0; a < M.length; ++a) {
        LNZ[M[a][1]]++;
      }
      Ap[0] = 0;
      for (let i = 0; i < n; ++i) {
        Ap[i + 1] = Ap[i] + LNZ[i];
      }
      let coloffset = [];
      for (let a = 0; a < n; ++a) {
        coloffset[a] = 0;
      }

      // go through all elements in M, and add them to sparse matrix A.
      for (let i = 0; i < M.length; ++i) {
        let e = M[i];
        let col = e[1];
        let adr = Ap[col] + coloffset[col];
        Ai[adr] = e[0];
        Ax[adr] = e[2];
        coloffset[col]++;
      }
      let D = new Array(n);
      let Y = new Array(n);
      let Lp = new Array(n + 1);
      let Parent = new Array(n);
      let Lnz = new Array(n);
      let Flag = new Array(n);
      let Pattern = new Array(n);
      let bp1 = new Array(n);
      let x = new Array(n);
      let d;
      ldlSymbolic(n, Ap, Ai, Lp, Parent, Lnz, Flag);
      let Lx = new Array(Lp[n]);
      let Li = new Array(Lp[n]);
      d = ldlNumeric(n, Ap, Ai, Ax, Lp, Parent, Lnz, Li, Lx, D, Y, Pattern, Flag);
      if (d === n) {
        return b => {
          ldlPerm(n, bp1, b, P);
          ldlLsolve(n, bp1, Lp, Li, Lx);
          ldlDsolve(n, bp1, D);
          ldlLTsolve(n, bp1, Lp, Li, Lx);
          ldlPermt(n, x, bp1, P);
          return x;
        };
      } else {
        return null;
      }
    }

    var cuthillMckee_1 = cuthillMckee;
    function compareNum(a, b) {
      return a - b;
    }
    function cuthillMckee(list, n) {
      var adj = new Array(n);
      var visited = new Array(n);
      for (var i = 0; i < n; ++i) {
        adj[i] = [];
        visited[i] = false;
      }
      for (var i = 0; i < list.length; ++i) {
        var l = list[i];
        adj[l[0]].push(l[1]);
      }
      var toVisit = new Array(n);
      var eol = 0;
      var ptr = 0;
      for (var i = 0; i < n; ++i) {
        if (visited[i]) {
          continue;
        }
        toVisit[eol++] = i;
        visited[i] = true;
        while (ptr < eol) {
          var v = toVisit[ptr++];
          var nbhd = adj[v];
          nbhd.sort(compareNum);
          for (var j = 0; j < nbhd.length; ++j) {
            var u = nbhd[j];
            if (visited[u]) {
              continue;
            }
            visited[u] = true;
            toVisit[eol++] = u;
          }
        }
      }
      var result = new Array(n);
      for (var i = 0; i < n; ++i) {
        result[toVisit[i]] = i;
      }
      return result;
    }
    var cuthillMckee$1 = /*@__PURE__*/getDefaultExportFromCjs(cuthillMckee_1);

    const getClosestNumber = (array = [], goal = 0) => {
      const closest = array.reduce((prev, curr) => {
        return Math.abs(curr - goal) < Math.abs(prev - goal) ? curr : prev;
      });
      return closest;
    };
    const getCloseIndex = (array = [], goal = 0) => {
      const closest = getClosestNumber(array, goal);
      return array.indexOf(closest);
    };
    const updateSystem = (matrix, y, weights) => {
      let nbPoints = y.length;
      let l = nbPoints - 1;
      let newMatrix = new Array(matrix.length);
      let newVector = new Float64Array(nbPoints);
      for (let i = 0; i < l; i++) {
        let w = weights[i];
        let diag = i * 2;
        let next = diag + 1;
        newMatrix[diag] = matrix[diag].slice();
        newMatrix[next] = matrix[next].slice();
        if (w === 0) {
          newVector[i] = 0;
        } else {
          newVector[i] = y[i] * w;
          newMatrix[diag][2] += w;
        }
      }
      newVector[l] = y[l] * weights[l];
      newMatrix[l * 2] = matrix[l * 2].slice();
      newMatrix[l * 2][2] += weights[l];
      return [newMatrix, newVector];
    };
    const getDeltaMatrix = (nbPoints, lambda) => {
      let matrix = [];
      let last = nbPoints - 1;
      for (let i = 0; i < last; i++) {
        matrix.push([i, i, lambda * 2]);
        matrix.push([i + 1, i, -1 * lambda]);
      }
      matrix[0][2] = lambda;
      matrix.push([last, last, lambda]);
      return {
        lowerTriangularNonZeros: matrix,
        permutationEncodedArray: cuthillMckee$1(matrix, nbPoints)
      };
    };

    function getControlPoints(x, y, options = {}) {
      const {
        length
      } = x;
      let {
        controlPoints = Int8Array.from({
          length
        }).fill(0)
      } = options;
      const {
        zones = [],
        weights = Float64Array.from({
          length
        }).fill(1)
      } = options;
      if (x.length !== y.length) {
        throw new RangeError('Y should match the length with X');
      } else if (controlPoints.length !== x.length) {
        throw new RangeError('controlPoints should match the length with X');
      } else if (weights.length !== x.length) {
        throw new RangeError('weights should match the length with X');
      }
      zones.forEach(range => {
        let indexFrom = getCloseIndex(x, range.from);
        let indexTo = getCloseIndex(x, range.to);
        if (indexFrom > indexTo) [indexFrom, indexTo] = [indexTo, indexFrom];
        for (let i = indexFrom; i < indexTo; i++) {
          controlPoints[i] = 1;
        }
      });
      return {
        weights: 'controlPoints' in options || zones.length > 0 ? xMultiply(weights, controlPoints) : weights,
        controlPoints
      };
    }

    /**
     * Fit the baseline drift by iteratively changing weights of sum square error between the fitted baseline and original signals,
     * for further information about the parameters you can get the [paper of airPLS](https://github.com/zmzhang/airPLS/blob/main/airPLS_manuscript.pdf)
     * @param {Array<number>} x - x axis data useful when control points or zones are submitted
     * @param {Array<number>} y - Original data
     * @param {object} [options={}] - Options object
     * @param {number} [options.maxIterations = 100] - Maximal number of iterations if the method does not reach the stop criterion
     * @param {number} [options.tolerance = 0.001] - Factor of the sum of absolute value of original data, to compute stop criterion
     * @param {Array<number>} [options.weights = [1,1,...]] - Initial weights vector, default each point has the same weight
     * @param {number} [options.lambda = 100] - Factor of weights matrix in -> [I + lambda D'D]z = x
     * @param {Array<number>} [options.controlPoints = []] - Array of 0|1 to force the baseline cross those points.
     * @param {Array<number>} [options.zones = []] - Array of x axis values (as from - to), to force that baseline cross those zones.
     * @returns {{corrected: Array<number>, error: number, iteration: number, baseline: Array<number>}}
     */

    function airPLS(x, y, options = {}) {
      const {
        weights,
        controlPoints
      } = getControlPoints(x, y, options);
      let {
        maxIterations = 100,
        lambda = 10,
        tolerance = 0.001
      } = options;
      let baseline, iteration;
      let sumNegDifferences = Number.MAX_SAFE_INTEGER;
      const corrected = Float64Array.from(y);
      let stopCriterion = getStopCriterion(y, tolerance);
      const {
        length
      } = y;
      let {
        lowerTriangularNonZeros,
        permutationEncodedArray
      } = getDeltaMatrix(length, lambda);
      let threshold = 1;
      const l = length - 1;
      let prevNegSum = Number.MAX_SAFE_INTEGER;
      for (iteration = 0; iteration < maxIterations && Math.abs(sumNegDifferences) > stopCriterion; iteration++) {
        let [leftHandSide, rightHandSide] = updateSystem(lowerTriangularNonZeros, y, weights);
        let cho = prepare(leftHandSide, length, permutationEncodedArray);
        baseline = cho(rightHandSide);
        sumNegDifferences = applyCorrection(y, baseline, corrected);
        if (iteration === 1) {
          const {
            positive
          } = xNoiseSanPlot(corrected);
          threshold = positive;
        } else {
          const absChange = Math.abs(prevNegSum / sumNegDifferences);
          if (absChange < 1.01 && absChange > 0.99) {
            break;
          }
        }
        prevNegSum = sumNegDifferences + 0;
        for (let i = 1; i < l; i++) {
          const diff = corrected[i];
          if (controlPoints[i] < 1 && Math.abs(diff) > threshold) {
            weights[i] = 0;
          } else {
            const factor = diff > 0 ? -1 : 1;
            weights[i] = Math.exp(factor * (iteration * diff) / Math.abs(sumNegDifferences));
          }
        }
        weights[0] = 1;
        weights[l] = 1;
      }
      return {
        corrected,
        baseline,
        iteration,
        error: sumNegDifferences
      };
      function applyCorrection(y, baseline, corrected) {
        let sumNegDifferences = 0;
        for (let i = 0; i < y.length; i++) {
          let diff = y[i] - baseline[i];
          if (diff < 0) sumNegDifferences += diff;
          corrected[i] = diff;
        }
        return sumNegDifferences;
      }
    }
    function getStopCriterion(y, tolerance) {
      let sum = xAbsoluteSum(y);
      return tolerance * sum;
    }

    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<number>} [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<number>}
     */

    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$1(input)) {
        options = input;
        input = [];
      }
      if (!isAnyArray$1(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;
    }

    /**
     * Adaptive iteratively reweighted penalized least squares [1]
     *
     * This function calls ml-airpls
     *
     * References:
     * [1] Zhang, Z.-M.; Chen, S.; Liang, Y.-Z.
     * Baseline Correction Using Adaptive Iteratively Reweighted Penalized Least Squares.
     * Analyst 2010, 135 (5), 1138–1146. https://doi.org/10.1039/B922045C.
     * @export
     * @param {Array<number>} ys
     * @param {object} [options] - Options object
     * @param {Array<number>} [options.x] Optional, Independent axis variable. If not specified, we use a linear grid
     * @param {object} [options.regression] - Options for the regression
     * @param {number} [options.regression.maxIterations = 100] - Maximum number of allowed iterations
     * @param {function} [options.regression.§Regression = PolynomialRegression] - Regression class with a predict method
     * @param {*} [options.regression.regressionOptions] - Options for regressionFunction
     * @param {number} [options.regression.tolerance = 0.001] - Convergence error tolerance
     * @returns {BaselineOutput}
     */
    function airPLSBaseline$1(ys, options = {}) {
      const numberPoints = ys.length;
      let {
        x,
        regressionOptions
      } = options;
      if (!x) {
        x = sequentialFill({
          from: 0,
          to: numberPoints - 1,
          size: numberPoints
        });
      }
      let output = airPLS(x, ys, regressionOptions);
      return {
        baseline: output.baseline,
        correctedSpectrum: output.corrected
      };
    }

    /**
     * Check that x and y are arrays with the same length.
     * @param x - first array
     * @param y - second array
     * @throws if x or y are not the same length, or if they are not arrays
     */
    function checkArrayLength(x, y) {
      if (!isAnyArray$1(x) || !isAnyArray$1(y)) {
        throw new TypeError('x and y must be arrays');
      }
      if (x.length !== y.length) {
        throw new RangeError('x and y arrays must have the same length');
      }
    }

    class BaseRegression {
      constructor() {
        if (new.target === BaseRegression) {
          throw new Error('BaseRegression must be subclassed');
        }
      }
      predict(x) {
        if (typeof x === 'number') {
          return this._predict(x);
        } else if (isAnyArray$1(x)) {
          const y = [];
          for (const xVal of x) {
            y.push(this._predict(xVal));
          }
          return y;
        } else {
          throw new TypeError('x must be a number or array');
        }
      }
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
      _predict(x) {
        throw new Error('_predict must be implemented');
      }
      train() {
        // Do nothing for this package
      }
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
      toString(precision) {
        return '';
      }
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
      toLaTeX(precision) {
        return '';
      }
      /**
       * Return the correlation coefficient of determination (r) and chi-square.
       * @param x - explanatory variable
       * @param y - response variable
       * @return - Object with further statistics.
       */
      score(x, y) {
        checkArrayLength(x, y);
        const n = x.length;
        const y2 = new Array(n);
        for (let i = 0; i < n; i++) {
          y2[i] = this._predict(x[i]);
        }
        let xSum = 0;
        let ySum = 0;
        let chi2 = 0;
        let rmsd = 0;
        let xSquared = 0;
        let ySquared = 0;
        let xY = 0;
        for (let i = 0; i < n; i++) {
          xSum += y2[i];
          ySum += y[i];
          xSquared += y2[i] * y2[i];
          ySquared += y[i] * y[i];
          xY += y2[i] * y[i];
          if (y[i] !== 0) {
            chi2 += (y[i] - y2[i]) * (y[i] - y2[i]) / y[i];
          }
          rmsd += (y[i] - y2[i]) * (y[i] - y2[i]);
        }
        const r = (n * xY - xSum * ySum) / Math.sqrt((n * xSquared - xSum * xSum) * (n * ySquared - ySum * ySum));
        return {
          r,
          r2: r * r,
          chi2,
          rmsd: Math.sqrt(rmsd / n)
        };
      }
    }

    /**
     * Cast `number` to string. Optionally `digits` specifies significant figures.
     * @param number
     * @param figures
     * @returns - A string representation of `number`.
     */
    function maybeToPrecision(number, figures) {
      if (number < 0) {
        number = 0 - number;
        if (typeof figures === 'number') {
          return `- ${number.toPrecision(figures)}`;
        } else {
          return `- ${number.toString()}`;
        }
      } else if (typeof figures === 'number') {
        return number.toPrecision(figures);
      } else {
        return number.toString();
      }
    }

    class PolynomialRegression extends BaseRegression {
      /**
       * @param x - independent or explanatory variable
       * @param y - dependent or response variable
       * @param degree - degree of the polynomial regression, or array of powers to be used. When degree is an array, intercept at zero is forced to false/ignored.
       * @example `new PolynomialRegression(x, y, 2)`, in this case, you can pass the option `interceptAtZero`, if you need it.
       * @param options
       * @example `new PolynomialRegression(x, y, [1, 3, 5])`
       * Each of the degrees corresponds to a column, so if you have them switched, just do:
       * @example `new PolynomialRegression(x, y, [3, 1, 5])`
       * @param options.interceptAtZero - force the polynomial regression so that f(0) = 0
       */
      constructor(x, y, degree, options = {}) {
        super();
        // @ts-expect-error internal use only
        if (x === true) {
          // @ts-expect-error internal use only
          this.degree = y.degree;
          // @ts-expect-error internal use only
          this.powers = y.powers;
          // @ts-expect-error internal use only
          this.coefficients = y.coefficients;
        } else {
          checkArrayLength(x, y);
          const result = regress$2(x, y, degree, options);
          this.degree = result.degree;
          this.powers = result.powers;
          this.coefficients = result.coefficients;
        }
      }
      _predict(x) {
        let y = 0;
        for (let k = 0; k < this.powers.length; k++) {
          y += this.coefficients[k] * x ** this.powers[k];
        }
        return y;
      }
      toJSON() {
        return {
          name: 'polynomialRegression',
          degree: this.degree,
          powers: this.powers,
          coefficients: this.coefficients
        };
      }
      toString(precision) {
        return this._toFormula(precision, false);
      }
      toLaTeX(precision) {
        return this._toFormula(precision, true);
      }
      _toFormula(precision, isLaTeX) {
        let sup = '^';
        let closeSup = '';
        let times = ' * ';
        if (isLaTeX) {
          sup = '^{';
          closeSup = '}';
          times = '';
        }
        let fn = '';
        let str = '';
        for (let k = 0; k < this.coefficients.length; k++) {
          str = '';
          if (this.coefficients[k] !== 0) {
            if (this.powers[k] === 0) {
              str = maybeToPrecision(this.coefficients[k], precision);
            } else if (this.powers[k] === 1) {
              str = `${maybeToPrecision(this.coefficients[k], precision) + times}x`;
            } else {
              str = `${maybeToPrecision(this.coefficients[k], precision) + times}x${sup}${this.powers[k]}${closeSup}`;
            }
            if (this.coefficients[k] > 0 && k !== this.coefficients.length - 1) {
              str = ` + ${str}`;
            } else if (k !== this.coefficients.length - 1) {
              str = ` ${str}`;
            }
          }
          fn = str + fn;
        }
        if (fn.startsWith('+')) {
          fn = fn.slice(1);
        }
        return `f(x) = ${fn}`;
      }
      static load(json) {
        if (json.name !== 'polynomialRegression') {
          throw new TypeError('not a polynomial regression model');
        }
        // @ts-expect-error internal use only
        return new PolynomialRegression(true, json);
      }
    }
    /**
     * Perform a polynomial regression on the given data set.
     * This is an internal function.
     * @param x - independent or explanatory variable
     * @param y - dependent or response variable
     * @param degree - degree of the polynomial regression
     * @param options.interceptAtZero - force the polynomial regression so that $f(0) = 0$
     * @param options
     */
    function regress$2(x, y, degree, options = {}) {
      const n = x.length;
      let {
        interceptAtZero = false
      } = options;
      let powers = [];
      if (Array.isArray(degree)) {
        powers = degree;
        interceptAtZero = false; //must be false in this case
      } else if (typeof degree === 'number') {
        if (interceptAtZero) {
          powers = new Array(degree);
          for (let k = 0; k < degree; k++) {
            powers[k] = k + 1;
          }
        } else {
          powers = new Array(degree + 1);
          for (let k = 0; k <= degree; k++) {
            powers[k] = k;
          }
        }
      }
      const nCoefficients = powers.length; //1 per power, in any case.
      const F = new Matrix(n, nCoefficients);
      const Y = new Matrix([y]);
      for (let k = 0; k < nCoefficients; k++) {
        for (let i = 0; i < n; i++) {
          if (powers[k] === 0) {
            F.set(i, k, 1);
          } else {
            F.set(i, k, x[i] ** powers[k]);
          }
        }
      }
      const FT = new MatrixTransposeView(F);
      const A = FT.mmul(F);
      const B = FT.mmul(new MatrixTransposeView(Y));
      return {
        coefficients: solve(A, B).to1DArray(),
        degree: Math.max(...powers),
        powers
      };
    }

    /**
     * Iterative regression-based baseline correction
     * @param {Array<number>} x - Independent axis variable
     * @param {Array<number>} y - Dependent axis variable
     * @param {object} [options] - Options object
     * @param {number} [options.maxIterations = 100] - Maximum number of allowed iterations
     * @param {function} [options.Regression = PolynomialRegression] - Regression class with a predict method
     * @param {*} [options.regressionOptions] - Options for regressionFunction
     * @param {number} [options.tolerance = 0.001] - Convergence error tolerance
     * @return {{corrected: Array<number>, delta: number, iteration: number, baseline: Array<number>}}
     */
    function baselineCorrectionRegression(x, y, options = {}) {
      let {
        maxIterations = 100,
        Regression = PolynomialRegression,
        regressionOptions,
        tolerance = 0.001
      } = options;
      if (!regressionOptions && Regression === PolynomialRegression) {
        regressionOptions = 3;
      }
      let baseline = y.slice();
      let fitting = y.slice();
      let oldFitting = y;
      let iteration = 0;
      let delta;
      let regression;
      while (iteration < maxIterations) {
        // Calculate the fitting result
        regression = new Regression(x, baseline, regressionOptions);
        delta = 0;
        for (let i = 0; i < baseline.length; i++) {
          fitting[i] = regression.predict(x[i]);
          if (baseline[i] > fitting[i]) {
            baseline[i] = fitting[i];
          }
          delta += Math.abs((fitting[i] - oldFitting[i]) / oldFitting[i]);
        }

        // Stop criterion
        if (delta < tolerance) {
          break;
        } else {
          oldFitting = fitting.slice();
          iteration++;
        }
      }

      // removes baseline
      let corrected = new Array(baseline.length);
      for (let j = 0; j < baseline.length; j++) {
        corrected[j] = y[j] - baseline[j];
      }
      return {
        corrected,
        delta,
        iteration,
        baseline,
        regression
      };
    }

    /**
     * Iterative polynomial fitting [1]
     *
     * Implementation based on ml-baseline-correction-regression
     *
     * References:
     * [1] Gan, F.; Ruan, G.; Mo, J.
     * Baseline Correction by Improved Iterative Polynomial Fitting with Automatic Threshold.
     *  Chemometrics and Intelligent Laboratory Systems 2006, 82 (1), 59–65.
     * https://doi.org/10.1016/j.chemolab.2005.08.009.
     * @export
     * @param {Array<number>} ys
     * @param {object} [options] - Options object
     * @param {Array<number>} [options.x] Optional, Independent axis variable. If not specified, we use a linear grid
     * @param {Object} [options.regression]
     * @param {number} [options.regression.maxIterations = 100] - Maximum number of allowed iterations
     * @param {Object} [options.regression]
     * @param {function} [options.regression.Regression = PolynomialRegression] - Regression class with a predict method
     * @param {Object} [options.regression.regressionOptions] - Options for regressionFunction
     * @param {number} [options.regression.tolerance = 0.001] - Convergence error tolerance
     * @returns {BaselineOutput}
     */
    function iterativePolynomialBaseline$1(ys, options = {}) {
      const numberPoints = ys.length;
      let {
        x,
        regressionOptions
      } = options;
      if (!x) {
        x = sequentialFill({
          from: 0,
          to: numberPoints - 1,
          size: numberPoints
        });
      }
      let output = baselineCorrectionRegression(x, ys, regressionOptions);
      return {
        baseline: output.baseline,
        correctedSpectrum: output.corrected
      };
    }

    /**

     *
     * @export
     * @param {Array<number>} ys
     * @param {Object} [options={}]
     * @param {number} [options.window] rolling window size, defaults to 10% of the length of the spectrum
     * @param {string} [options.padding.size=window-1] none, value, circular, duplicate
     * @param {string} [options.padding.algorithm='duplicate'] none, value, circular, duplicate
     * @param {number} [options.padding.value=0] value to use for padding (if algorithm='value')
     * @returns {BaselineOutput}
     */
    function rollingAverageBaseline$1(ys, options = {}) {
      let window = Math.max(Math.round(ys.length * 0.1), 2);
      let defaults = {
        window,
        padding: {
          size: window - 1,
          algorithm: 'duplicate',
          value: 0
        }
      };
      let actualOptions = {
        ...defaults,
        ...options
      };
      let baseline = xRollingAverage(ys, actualOptions);
      let corrected = new Float64Array(ys.length);
      for (let i = 0; i < corrected.length; i++) {
        corrected[i] = ys[i] - baseline[i];
      }
      return {
        baseline,
        correctedSpectrum: corrected
      };
    }

    function rollingBall(spectrum, options = {}) {
      if (!isAnyArray$1(spectrum)) {
        throw new Error('Spectrum must be an array');
      }
      if (spectrum.length === 0) {
        throw new TypeError('Spectrum must not be empty');
      }
      const numberPoints = spectrum.length;
      const maxima = new Float64Array(numberPoints);
      const minima = new Float64Array(numberPoints);
      const baseline = new Float64Array(numberPoints);
      // windowM 4 percent of spectrum length
      // windowS 8 percent of spectrum length
      const {
        windowM = Math.round(numberPoints * 0.04),
        windowS = Math.round(numberPoints * 0.08)
      } = options;
      // fi(1) in original paper
      for (let i = 0; i < spectrum.length; i++) {
        const windowLeft = Math.max(0, i - windowM);
        const windowRight = Math.min(i + windowM + 1, spectrum.length);
        minima[i] = xMinValue(spectrum, {
          fromIndex: windowLeft,
          toIndex: windowRight
        });
      }
      // fi in original paper
      for (let i = 0; i < minima.length; i++) {
        const windowLeft = Math.max(0, i - windowM);
        const windowRight = Math.min(i + windowM + 1, minima.length);
        maxima[i] = xMaxValue(minima, {
          fromIndex: windowLeft,
          toIndex: windowRight
        });
      }
      for (let i = 0; i < minima.length; i++) {
        const windowLeft = Math.max(0, i - windowS);
        const windowRight = Math.min(i + windowS + 1, maxima.length);
        baseline[i] = xMean(maxima.subarray(windowLeft, windowRight));
      }
      return baseline;
    }

    /**
     * Rolling ball baseline correction algorithm.
     * From the abstract of (1):
     * "This algorithm behaves equivalently to traditional polynomial backgrounds in simple spectra,
     * [...] and is considerably more robust for multiple overlapping peaks, rapidly varying background [...]
     *
     * The baseline is the trace one gets by rolling a ball below a spectrum. Algorithm has three steps:
     * Finding the minima in each window, find maxima among minima and then smooth over them by averaging.
     *
     * Algorithm described in (1), but in the implementation here the window width does not change.
     *
     * Reference:
     * (1) Kneen, M. A.; Annegarn, H. J.
     *     Algorithm for Fitting XRF, SEM and PIXE X-Ray Spectra Backgrounds.
     *     Nuclear Instruments and Methods in Physics Research Section B: Beam Interactions with Materials and Atoms 1996, 109–110, 209–213.
     *     https://doi.org/10.1016/0168-583X(95)00908-6.
     * (2) Kristian Hovde Liland, Bjørn-Helge Mevik, Roberto Canteri: baseline.
     *     https://cran.r-project.org/web/packages/baseline/index.html
     *
     * @export
     * @param {Array<number>} ys
     * @param {Object} [options={}]
     * @param {Number} [options.windowM] - width of local window for minimization/maximization, defaults to 4% of the spectrum length
     * @param {Number} [options.windowS] - width of local window for smoothing, defaults to 8% of the specturm length
     * @returns {BaselineOutput}
     */
    function rollingBallBaseline$1(ys, options = {}) {
      const baseline = rollingBall(ys, options);
      let corrected = new Float64Array(ys.length);
      for (let i = 0; i < corrected.length; i++) {
        corrected[i] = ys[i] - baseline[i];
      }
      return {
        baseline,
        correctedSpectrum: corrected
      };
    }

    /**

     *
     * @export
     * @param {Array<number>} ys
     * @param {Object} [options={}]
     * @param {number} [options.window] rolling window size, defaults to 10% of the length of the spectrum
     * @param {string} [options.padding.size=window-1] none, value, circular, duplicate
     * @param {string} [options.padding.algorithm='duplicate'] none, value, circular, duplicate
     * @param {number} [options.padding.value=0] value to use for padding (if algorithm='value')
     * @returns {BaselineOutput}
     */
    function rollingMedianBaseline$1(ys, options = {}) {
      let window = Math.max(Math.round(ys.length * 0.1), 2);
      let defaults = {
        window,
        padding: {
          size: window - 1,
          algorithm: 'duplicate',
          value: 0
        }
      };
      let actualOptions = {
        ...defaults,
        ...options
      };
      let baseline = xRollingMedian(ys, actualOptions);
      let corrected = new Float64Array(ys.length);
      for (let i = 0; i < corrected.length; i++) {
        corrected[i] = ys[i] - baseline[i];
      }
      return {
        baseline,
        correctedSpectrum: corrected
      };
    }

    //@ts-expect-error no type definition for baselines
    /**
     * @param data
     */
    function airPLSBaseline(data) {
      data.y = airPLSBaseline$1(data.y).correctedSpectrum;
      return {
        data
      };
    }

    //@ts-expect-error no type definition for baselines
    /**
     * @param data
     */
    function iterativePolynomialBaseline(data) {
      data.y = iterativePolynomialBaseline$1(data.y).correctedSpectrum;
      return {
        data
      };
    }

    //@ts-expect-error no type definition for baselines
    /**
     * @param data
     */
    function rollingAverageBaseline(data) {
      data.y = rollingAverageBaseline$1(data.y).correctedSpectrum;
      return {
        data
      };
    }

    //@ts-expect-error no type definition for baselines
    /**
     * @param data
     */
    function rollingBallBaseline(data) {
      data.y = rollingBallBaseline$1(data.y).correctedSpectrum;
      return {
        data
      };
    }

    //@ts-expect-error no type definition for baselines
    /**
     * @param data
     */
    function rollingMedianBaseline(data) {
      data.y = rollingMedianBaseline$1(data.y).correctedSpectrum;
      return {
        data
      };
    }

    /**
     * Apply Savitzky Golay algorithm
     * @param [ys] Array of y values
     * @param [xs] Array of X or deltaX
     * @return  Array containing the new ys (same length)
     */
    function sgg(ys, xs, options = {}) {
      const {
        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 (!isAnyArray$1(ys)) {
        throw new TypeError('Y values must be an array');
      }
      if (typeof xs === 'undefined') {
        throw new TypeError('X must be defined');
      }
      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');
      }
      const half = Math.floor(windowSize / 2);
      const np = ys.length;
      const ans = new Float64Array(np);
      const weights = fullWeights(windowSize, polynomial, derivative);
      let hs = 0;
      let constantH = true;
      if (isAnyArray$1(xs)) {
        constantH = false;
      } else {
        hs = xs ** derivative;
      }
      //For the borders
      for (let i = 0; i < half; i++) {
        const wg1 = weights[half - i - 1];
        const 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
      const 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 (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++) {
        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;
    }
    /**
     * @private
     * @param m  Number of points
     * @param n  Polynomial grade
     * @param s  Derivative
     */
    function fullWeights(m, n, s) {
      const weights = new Array(m);
      const np = Math.floor(m / 2);
      for (let t = -np; t <= np; t++) {
        weights[t + np] = new Float64Array(m);
        for (let j = -np; j <= np; j++) {
          weights[t + np][j + np] = weight(j, t, np, n, s);
        }
      }
      return weights;
    }

    /**
     * Calculate the first derivative using Savitzky–Golay filter.
     * @param data
     * @param options
     */
    function firstDerivative(data, options = {}) {
      const {
        x,
        y
      } = data;
      return {
        data: {
          x,
          y: sgg(y, x, {
            ...options,
            derivative: 1
          })
        }
      };
    }

    /**
     * Calculate the second derivative using Savitzky–Golay filter.
     * @param data
     * @param options
     */
    function secondDerivative(data, options = {}) {
      const {
        x,
        y
      } = data;
      return {
        data: {
          x,
          y: sgg(y, x, {
            ...options,
            derivative: 2
          })
        }
      };
    }

    /**
     * Calculate the third derivative using Savitzky–Golay filter.
     * @param data
     * @param options
     */
    function thirdDerivative(data, options = {}) {
      const {
        x,
        y
      } = data;
      return {
        data: {
          x,
          y: sgg(y, x, {
            ...options,
            derivative: 3
          })
        }
      };
    }

    /**
     * Apply the Savitzky Golay Generalized Filter
     * @param data
     * @param options
     */
    function savitzkyGolay(data, options = {}) {
      const {
        x,
        y
      } = data;
      return {
        data: {
          x,
          y: sgg(y, x, options)
        }
      };
    }

    /**
     * Ensure X values are strictly monotonic increasing
     * http://www-groups.mcs.st-andrews.ac.uk/~john/analysis/Lectures/L8.html
     * @param data
     */
    function ensureGrowing(data) {
      return {
        data: xyEnsureGrowingX(data)
      };
    }

    /**
     * Ensure X values are strictly monotonic increasing
     * http://www-groups.mcs.st-andrews.ac.uk/~john/analysis/Lectures/L8.html
     * @param data
     */
    function reverseIfNeeded(data) {
      return {
        data: xyGrowingX(data)
      };
    }

    /**
     * Filter that allows to
     * @param data
     * @param options
     */
    function equallySpaced(data, options = {}) {
      return {
        data: xyEquallySpaced(data, options)
      };
    }

    /**
     * Filter that allows to
     * @param data
     * @param options
     */
    function filterX(data, options = {}) {
      return {
        data: xyFilterX(data, options)
      };
    }

    /**
     * Filter that allows to
     * @param data
     * @param options
     */
    function setMaxX(data, options = {}) {
      const {
        max = 1
      } = options;
      const existingMax = xMaxValue(data.x);
      if (existingMax === max) {
        return {
          data
        };
      }
      return {
        data: {
          x: xAdd(data.x, max - existingMax),
          y: data.y
        }
      };
    }

    /**
     * Filter that allows to
     * @param data
     * @param options
     */
    function setMinX(data, options = {}) {
      const {
        min = 0
      } = options;
      const existingMin = xMinValue(data.x);
      if (existingMin === min) {
        return {
          data
        };
      }
      return {
        data: {
          x: xAdd(data.x, min - existingMin),
          y: data.y
        }
      };
    }

    /**
     * Correction of the x and y coordinates using a quadratic optimizations with the peak and its 3 closest neighbors to determine the true x,y values of the peak.
     * This process is done in place and is very fast.
     * @param data
     * @param peaks
     */
    function optimizeTop(data, peaks) {
      const {
        x,
        y
      } = data;
      for (const peak of peaks) {
        let currentIndex = peak.index;
        // The detected peak could be moved 1 or 2 units to left or right.
        if (y[currentIndex - 1] >= y[currentIndex - 2] && y[currentIndex - 1] >= y[currentIndex]) {
          currentIndex--;
        } else if (y[currentIndex + 1] >= y[currentIndex] && y[currentIndex + 1] >= y[currentIndex + 2]) {
          currentIndex++;
        } else if (y[currentIndex - 2] >= y[currentIndex - 3] && y[currentIndex - 2] >= y[currentIndex - 1]) {
          currentIndex -= 2;
        } else if (y[currentIndex + 2] >= y[currentIndex + 1] && y[currentIndex + 2] >= y[currentIndex + 3]) {
          currentIndex += 2;
        }
        // interpolation to a sin() function
        if (y[currentIndex - 1] > 0 && y[currentIndex + 1] > 0 && y[currentIndex] >= y[currentIndex - 1] && y[currentIndex] >= y[currentIndex + 1] && (y[currentIndex] !== y[currentIndex - 1] || y[currentIndex] !== y[currentIndex + 1])) {
          const alpha = 20 * Math.log10(y[currentIndex - 1]);
          const beta = 20 * Math.log10(y[currentIndex]);
          const gamma = 20 * Math.log10(y[currentIndex + 1]);
          const p = 0.5 * (alpha - gamma) / (alpha - 2 * beta + gamma);
          const xCurrent = x[currentIndex];
          const xPrevious = x[currentIndex - 1];
          peak.x = xCurrent + (xCurrent - xPrevious) * p;
          peak.y = y[currentIndex] - 0.25 * (y[currentIndex - 1] - y[currentIndex + 1]) * p;
        }
      }
    }

    /**
     * Global spectra deconvolution
     * @param  data - Object data with x and y arrays. Values in x has to be growing
     * @param options
     * @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.
     */
    function gsd(data, options = {}) {
      let {
        noiseLevel
      } = options;
      const {
        sgOptions = {
          windowSize: 9,
          polynomial: 3
        },
        smoothY = false,
        maxCriteria = true,
        minMaxRatio = 0.00025,
        realTopDetection = false
      } = options;
      const {
        x
      } = data;
      let {
        y
      } = data;
      if (xIsMonotonic(x) !== 1) {
        throw new Error('GSD only accepts monotone increasing x values');
      }
      //rescale;
      y = y.slice();
      // If the max difference between delta x is less than 5%, then,
      // we can assume it to be equally spaced variable
      const equallySpaced = xIsEquallySpaced(x);
      if (noiseLevel === undefined) {
        if (equallySpaced) {
          const noiseInfo = xNoiseStandardDeviation(y);
          if (maxCriteria) {
            noiseLevel = noiseInfo.median + 1.5 * noiseInfo.sd;
          } else {
            noiseLevel = -noiseInfo.median + 1.5 * noiseInfo.sd;
          }
        } else {
          noiseLevel = 0;
        }
      } else if (!maxCriteria) {
        noiseLevel *= -1;
      }
      if (!maxCriteria) {
        for (let i = 0; i < y.length; i++) {
          y[i] *= -1;
        }
      }
      if (noiseLevel !== undefined) {
        for (let i = 0; i < y.length; i++) {
          if (y[i] < noiseLevel) {
            y[i] = noiseLevel;
          }
        }
      }
      const xValue = equallySpaced ? x[1] - x[0] : x;
      const yData = smoothY ? sgg(y, xValue, {
        ...sgOptions,
        derivative: 0
      }) : y;
      const dY = sgg(y, xValue, {
        ...sgOptions,
        derivative: 1
      });
      const ddY = sgg(y, xValue, {
        ...sgOptions,
        derivative: 2
      });
      const {
        min: minY,
        max: maxY
      } = xMinMaxValues(yData);
      if (minY > maxY || minY === maxY) return [];
      const yThreshold = minY + (maxY - minY) * minMaxRatio;
      const dX = x[1] - x[0];
      let lastMax = null;
      let lastMin = null;
      const minddY = [];
      const intervalL = [];
      const intervalR = [];
      // By the intermediate value theorem We cannot find 2 consecutive maximum or minimum
      for (let i = 1; i < yData.length - 1; ++i) {
        if (dY[i] < dY[i - 1] && dY[i] <= dY[i + 1] || dY[i] <= dY[i - 1] && dY[i] < dY[i + 1]) {
          lastMin = {
            x: x[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: x[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);
        }
      }
      let lastK = -1;
      const peaks = [];
      for (const minddYIndex of minddY) {
        const deltaX = x[minddYIndex];
        let possible = -1;
        let k = lastK + 1;
        let minDistance = Number.POSITIVE_INFINITY;
        let currentDistance = 0;
        while (possible === -1 && k < intervalL.length) {
          currentDistance = Math.abs(deltaX - (intervalL[k].x + intervalR[k].x) / 2);
          if (currentDistance < (intervalR[k].x - intervalL[k].x) / 2) {
            possible = k;
            lastK = k;
          }
          ++k;
          // Not getting closer?
          if (currentDistance >= minDistance) {
            break;
          }
          minDistance = currentDistance;
        }
        if (possible !== -1) {
          if (yData[minddYIndex] > yThreshold) {
            const width = Math.abs(intervalR[possible].x - intervalL[possible].x);
            peaks.push({
              id: crypto.randomUUID(),
              x: deltaX,
              y: yData[minddYIndex],
              width,
              index: minddYIndex,
              ddY: ddY[minddYIndex],
              inflectionPoints: {
                from: intervalL[possible],
                to: intervalR[possible]
              }
            });
          }
        }
      }
      if (realTopDetection) {
        optimizeTop({
          x,
          y: yData
        }, peaks);
      }
      peaks.forEach(peak => {
        if (!maxCriteria) {
          peak.y *= -1;
          peak.ddY = peak.ddY * -1;
        }
      });
      peaks.sort((a, b) => {
        return a.x - b.x;
      });
      return peaks;
    }

    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 {
      constructor(options = {}) {
        const {
          fwhm = 500,
          sd
        } = options;
        this.fwhm = sd ? gaussianWidthToFWHM(2 * sd) : fwhm;
      }
      fwhmToWidth(fwhm = this.fwhm) {
        return gaussianFwhmToWidth(fwhm);
      }
      widthToFWHM(width) {
        return gaussianWidthToFWHM(width);
      }
      fct(x) {
        return gaussianFct(x, this.fwhm);
      }
      getArea(height = calculateGaussianHeight({
        fwhm: this.fwhm
      })) {
        return getGaussianArea({
          fwhm: this.fwhm,
          height
        });
      }
      getFactor(area) {
        return getGaussianFactor(area);
      }
      getData(options = {}) {
        return getGaussianData(this, options);
      }
      calculateHeight(area = 1) {
        return calculateGaussianHeight({
          fwhm: this.fwhm,
          area
        });
      }
      getParameters() {
        return ['fwhm'];
      }
    }
    function calculateGaussianHeight(options) {
      let {
        fwhm = 500,
        area = 1,
        sd
      } = options;
      if (sd) fwhm = gaussianWidthToFWHM(2 * sd);
      return 2 * area / ROOT_PI_OVER_LN2 / fwhm;
    }
    /**
     * Calculate the height of the gaussian function of a specific width (fwhm) at a speicifc
     * x position (the gaussian is centered on x=0)
     * @param x
     * @param fwhm
     * @returns y
     */
    function gaussianFct(x, fwhm) {
      return Math.exp(GAUSSIAN_EXP_FACTOR * Math.pow(x / fwhm, 2));
    }
    function gaussianWidthToFWHM(width) {
      return width * ROOT_2LN2;
    }
    function gaussianFwhmToWidth(fwhm) {
      return fwhm / ROOT_2LN2;
    }
    function getGaussianArea(options) {
      let {
        fwhm = 500,
        sd,
        height = 1
      } = options;
      if (sd) fwhm = gaussianWidthToFWHM(2 * sd);
      return height * ROOT_PI_OVER_LN2 * fwhm / 2;
    }
    function getGaussianFactor(area = 0.9999) {
      return Math.sqrt(2) * erfinv(area);
    }
    function getGaussianData(shape = {}, options = {}) {
      let {
        fwhm = 500,
        sd
      } = shape;
      if (sd) fwhm = gaussianWidthToFWHM(2 * sd);
      let {
        length,
        factor = getGaussianFactor(),
        height = calculateGaussianHeight({
          fwhm
        })
      } = options;
      if (!length) {
        length = Math.min(Math.ceil(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] = gaussianFct(i - center, fwhm) * height;
        data[length - 1 - i] = data[i];
      }
      return data;
    }

    class Lorentzian {
      constructor(options = {}) {
        const {
          fwhm = 500
        } = options;
        this.fwhm = fwhm;
      }
      fwhmToWidth(fwhm = this.fwhm) {
        return lorentzianFwhmToWidth(fwhm);
      }
      widthToFWHM(width) {
        return lorentzianWidthToFWHM(width);
      }
      fct(x) {
        return lorentzianFct(x, this.fwhm);
      }
      getArea(height = 1) {
        return getLorentzianArea({
          fwhm: this.fwhm,
          height
        });
      }
      getFactor(area) {
        return getLorentzianFactor(area);
      }
      getData(options = {}) {
        return getLorentzianData(this, options);
      }
      calculateHeight(area = 1) {
        return calculateLorentzianHeight({
          fwhm: this.fwhm,
          area
        });
      }
      getParameters() {
        return ['fwhm'];
      }
    }
    const calculateLorentzianHeight = ({
      fwhm = 1,
      area = 1
    }) => {
      return 2 * area / Math.PI / fwhm;
    };
    const getLorentzianArea = options => {
      const {
        fwhm = 500,
        height = 1
      } = options;
      return height * Math.PI * fwhm / 2;
    };
    const lorentzianFct = (x, fwhm) => {
      return fwhm ** 2 / (4 * x ** 2 + fwhm ** 2);
    };
    const lorentzianWidthToFWHM = width => {
      return width * ROOT_THREE;
    };
    const lorentzianFwhmToWidth = fwhm => {
      return fwhm / ROOT_THREE;
    };
    const getLorentzianFactor = (area = 0.9999) => {
      if (area >= 1) {
        throw new Error('area should be (0 - 1)');
      }
      const halfResidual = (1 - area) * 0.5;
      const quantileFunction = p => Math.tan(Math.PI * (p - 0.5));
      return (quantileFunction(1 - halfResidual) - quantileFunction(halfResidual)) / 2;
    };
    const getLorentzianData = (shape = {}, options = {}) => {
      let {
        fwhm = 500
      } = shape;
      let {
        length,
        factor = getLorentzianFactor(),
        height = calculateLorentzianHeight({
          fwhm,
          area: 1
        })
      } = options;
      if (!length) {
        length = Math.min(Math.ceil(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] = lorentzianFct(i - center, fwhm) * height;
        data[length - 1 - i] = data[i];
      }
      return data;
    };

    class LorentzianDispersive {
      constructor(options = {}) {
        const {
          fwhm = 500
        } = options;
        this.fwhm = fwhm;
      }
      fwhmToWidth(fwhm = this.fwhm) {
        return lorentzianFwhmToWidth(fwhm);
      }
      widthToFWHM(width) {
        return lorentzianWidthToFWHM(width);
      }
      fct(x) {
        return lorentzianDispersiveFct(x, this.fwhm);
      }
      //eslint-disable-next-line
      getArea(_height) {
        return 0;
      }
      getFactor(area) {
        return getLorentzianFactor(area);
      }
      getData(options = {}) {
        return getLorentzianDispersiveData(this, options);
      }
      calculateHeight(area = 1) {
        return calculateLorentzianHeight({
          fwhm: this.fwhm,
          area
        });
      }
      getParameters() {
        return ['fwhm'];
      }
    }
    const lorentzianDispersiveFct = (x, fwhm) => {
      return 2 * fwhm * x / (4 * x ** 2 + fwhm ** 2);
    };
    const getLorentzianDispersiveData = (shape = {}, options = {}) => {
      let {
        fwhm = 500
      } = shape;
      let {
        length,
        factor = getLorentzianFactor(),
        height = calculateLorentzianHeight({
          fwhm,
          area: 1
        })
      } = options;
      if (!length) {
        length = Math.min(Math.ceil(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] = lorentzianDispersiveFct(i - center, fwhm) * height;
        data[length - 1 - i] = -data[i];
      }
      return data;
    };

    class PseudoVoigt {
      constructor(options = {}) {
        const {
          fwhm = 500,
          mu = 0.5
        } = options;
        this.mu = mu;
        this.fwhm = fwhm;
      }
      fwhmToWidth(fwhm = this.fwhm, mu = this.mu) {
        return pseudoVoigtFwhmToWidth(fwhm, mu);
      }
      widthToFWHM(width, mu = this.mu) {
        return pseudoVoigtWidthToFWHM(width, mu);
      }
      fct(x) {
        return pseudoVoigtFct(x, this.fwhm, this.mu);
      }
      getArea(height = 1) {
        return getPseudoVoigtArea({
          fwhm: this.fwhm,
          height,
          mu: this.mu
        });
      }
      getFactor(area) {
        return getPseudoVoigtFactor(area);
      }
      getData(options = {}) {
        const {
          length,
          factor,
          height = calculatePseudoVoigtHeight({
            fwhm: this.fwhm,
            mu: this.mu,
            area: 1
          })
        } = options;
        return getPseudoVoigtData(this, {
          factor,
          length,
          height
        });
      }
      calculateHeight(area = 1) {
        return calculatePseudoVoigtHeight({
          fwhm: this.fwhm,
          mu: this.mu,
          area
        });
      }
      getParameters() {
        return ['fwhm', 'mu'];
      }
    }
    const calculatePseudoVoigtHeight = (options = {}) => {
      let {
        fwhm = 1,
        mu = 0.5,
        area = 1
      } = options;
      return 2 * area / (fwhm * (mu * ROOT_PI_OVER_LN2 + (1 - mu) * Math.PI));
    };
    const pseudoVoigtFct = (x, fwhm, mu) => {
      return (1 - mu) * lorentzianFct(x, fwhm) + mu * gaussianFct(x, fwhm);
    };
    const pseudoVoigtWidthToFWHM = (width, mu = 0.5) => {
      return width * (mu * ROOT_2LN2_MINUS_ONE + 1);
    };
    const pseudoVoigtFwhmToWidth = (fwhm, mu = 0.5) => {
      return fwhm / (mu * ROOT_2LN2_MINUS_ONE + 1);
    };
    const getPseudoVoigtArea = options => {
      const {
        fwhm = 500,
        height = 1,
        mu = 0.5
      } = options;
      return fwhm * height * (mu * ROOT_PI_OVER_LN2 + (1 - mu) * Math.PI) / 2;
    };
    const getPseudoVoigtFactor = (area = 0.9999, mu = 0.5) => {
      return mu < 1 ? getLorentzianFactor(area) : getGaussianFactor(area);
    };
    const getPseudoVoigtData = (shape = {}, options = {}) => {
      let {
        fwhm = 500,
        mu = 0.5
      } = shape;
      let {
        length,
        factor = getPseudoVoigtFactor(0.999, mu),
        height = calculatePseudoVoigtHeight({
          fwhm,
          mu,
          area: 1
        })
      } = options;
      if (!height) {
        height = 1 / (mu / Math.sqrt(-GAUSSIAN_EXP_FACTOR / Math.PI) * fwhm + (1 - mu) * fwhm * Math.PI / 2);
      }
      if (!length) {
        length = Math.min(Math.ceil(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] = pseudoVoigtFct(i - center, fwhm, mu) * height;
        data[length - 1 - i] = data[i];
      }
      return data;
    };

    /**
     * This shape is a linear combination of rational function (n|n+2), for n = 0 (lorentzian function) and n = 2
     * the parameter that combines those two functions is `gamma` and it is called the kurtosis parameter, it is an
     * implementation of generalized lorentzian shape published by Stanislav Sykora in the SMASH 2010. DOI:10.3247/SL3nmr10.006
     * @link http://www.ebyte.it/stan/Talk_ML_UserMeeting_SMASH_2010_GeneralizedLorentzian.html
     */
    class GeneralizedLorentzian {
      constructor(options = {}) {
        const {
          fwhm = 500,
          gamma = 0.5
        } = options;
        this.fwhm = fwhm;
        this.gamma = gamma;
      }
      fwhmToWidth(fwhm = this.fwhm) {
        return generalizedLorentzianFwhmToWidth(fwhm);
      }
      widthToFWHM(width) {
        return generalizedLorentzianWidthToFWHM(width);
      }
      fct(x) {
        return generalizedLorentzianFct(x, this.fwhm, this.gamma);
      }
      getArea(height = 1) {
        return getGeneralizedLorentzianArea({
          fwhm: this.fwhm,
          height,
          gamma: this.gamma
        });
      }
      getFactor(area) {
        return getGeneralizedLorentzianFactor(area);
      }
      getData(options = {}) {
        return getGeneralizedLorentzianData(this, options);
      }
      calculateHeight(area = 1) {
        const {
          gamma,
          fwhm
        } = this;
        return calculateGeneralizedLorentzianHeight({
          fwhm,
          area,
          gamma
        });
      }
      getParameters() {
        return ['fwhm', 'gamma'];
      }
    }
    const calculateGeneralizedLorentzianHeight = ({
      fwhm = 1,
      gamma = 1,
      area = 1
    }) => {
      return area / fwhm / (3.14159 - 0.420894 * gamma) * 2;
    };
    /**
     * expression of integral generated by Mathematica of the function
     */
    const getGeneralizedLorentzianArea = options => {
      const {
        fwhm = 500,
        height = 1,
        gamma = 1
      } = options;
      return height * fwhm * (3.14159 - 0.420894 * gamma) / 2;
    };
    const generalizedLorentzianFct = (x, fwhm, gamma) => {
      const u = (2 * x / fwhm) ** 2;
      return (1 - gamma) / (1 + u) + gamma * (1 + u / 2) / (1 + u + u ** 2);
    };
    const generalizedLorentzianWidthToFWHM = width => {
      return width * ROOT_THREE;
    };
    const generalizedLorentzianFwhmToWidth = fwhm => {
      return fwhm / ROOT_THREE;
    };
    const getGeneralizedLorentzianFactor = (area = 0.9999) => {
      if (area >= 1) {
        throw new Error('area should be (0 - 1)');
      }
      const halfResidual = (1 - area) * 0.5;
      const quantileFunction = p => Math.tan(Math.PI * (p - 0.5));
      return (quantileFunction(1 - halfResidual) - quantileFunction(halfResidual)) / 2;
    };
    const getGeneralizedLorentzianData = (shape = {}, options = {}) => {
      let {
        fwhm = 500,
        gamma = 1
      } = shape;
      let {
        length,
        factor = getGeneralizedLorentzianFactor(),
        height = calculateGeneralizedLorentzianHeight({
          fwhm,
          area: 1,
          gamma
        })
      } = options;
      if (!length) {
        length = Math.min(Math.ceil(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] = generalizedLorentzianFct(i - center, fwhm, gamma) * height;
        data[length - 1 - i] = data[i];
      }
      return data;
    };

    /**
     * Generate a instance of a specific kind of shape.
     */
    function getShape1D(shape) {
      const {
        kind
      } = shape;
      switch (kind) {
        case 'gaussian':
          return new Gaussian(shape);
        case 'lorentzian':
          return new Lorentzian(shape);
        case 'pseudoVoigt':
          return new PseudoVoigt(shape);
        case 'lorentzianDispersive':
          return new LorentzianDispersive(shape);
        case 'generalizedLorentzian':
          return new GeneralizedLorentzian(shape);
        default:
          {
            throw Error(`Unknown distribution ${kind}`);
          }
      }
    }

    /**
     * This function returns the sumOfShapes function
     * This function gives sumOfShapes access to the peak list and the associated data
     * @param internalPeaks
     */
    function getSumOfShapes(internalPeaks) {
      return function sumOfShapes(parameters) {
        return x => {
          let totalY = 0;
          for (const peak of internalPeaks) {
            const peakX = parameters[peak.fromIndex];
            const y = parameters[peak.fromIndex + 1];
            for (let i = 2; i < parameters.length; i++) {
              const shapeFctKey = peak.parameters[i];
              peak.shapeFct[shapeFctKey] = parameters[peak.fromIndex + i];
            }
            totalY += y * peak.shapeFct.fct(x - peakX);
          }
          return totalY;
        };
      };
    }

    /**
     * Asserts that value is truthy.
     *
     * @param value - Value to check.
     * @param message - Optional error message to throw.
     */
    function assert(value, message) {
      if (!value) {
        throw new Error(message ? message : 'unreachable');
      }
    }

    const DefaultParameters = {
      x: {
        init: peak => peak.x,
        min: (peak, peakShape) => peak.x - peakShape.fwhm * 2,
        max: (peak, peakShape) => peak.x + peakShape.fwhm * 2,
        gradientDifference: (peak, peakShape) => peakShape.fwhm * 2e-3
      },
      y: {
        init: peak => peak.y,
        min: peak => peak.y < 0 ? -1.1 : 0,
        max: peak => peak.y < 0 ? 0 : 1.1,
        gradientDifference: () => 1e-3
      },
      fwhm: {
        init: (peak, peakShape) => peakShape.fwhm,
        min: (peak, peakShape) => peakShape.fwhm * 0.25,
        max: (peak, peakShape) => peakShape.fwhm * 4,
        gradientDifference: (peak, peakShape) => peakShape.fwhm * 2e-3
      },
      mu: {
        init: (peak, peakShape) => peakShape.mu,
        min: () => 0,
        max: () => 1,
        gradientDifference: () => 0.01
      },
      gamma: {
        init: (peak, peakShape) => peakShape.gamma || 0.5,
        min: () => -1,
        max: () => 2,
        gradientDifference: () => 0.01
      }
    };

    const properties = ['init', 'min', 'max', 'gradientDifference'];
    /**
     * Return an array of internalPeaks that contains the exact init, min, max values based on the options
     * @param peaks
     * @param minMaxY
     * @param options
     * @returns
     */
    function getInternalPeaks(peaks, minMaxY, options = {}) {
      let index = 0;
      const internalPeaks = [];
      const {
        baseline: shiftValue = minMaxY.min
      } = options;
      const normalizedPeaks = peaks.map(peak => {
        return {
          ...peak,
          y: (peak.y - shiftValue) / minMaxY.range
        };
      });
      for (const peak of normalizedPeaks) {
        const {
          id,
          shape = options.shape ? options.shape : {
            kind: 'gaussian'
          }
        } = peak;
        const shapeFct = getShape1D(shape);
        const parameters = ['x', 'y', ...shapeFct.getParameters()];
        const propertiesValues = {
          min: [],
          max: [],
          init: [],
          gradientDifference: []
        };
        for (const parameter of parameters) {
          for (const property of properties) {
            // check if the property is specified in the peak
            let propertyValue = peak?.parameters?.[parameter]?.[property];
            if (propertyValue) {
              propertyValue = getNormalizedValue(propertyValue, parameter, property, minMaxY, options.baseline);
              propertiesValues[property].push(propertyValue);
              continue;
            }
            // check if there are some global option, it could be a number or a callback
            let generalParameterValue = options?.parameters?.[parameter]?.[property];
            if (generalParameterValue) {
              if (typeof generalParameterValue === 'number') {
                generalParameterValue = getNormalizedValue(generalParameterValue, parameter, property, minMaxY, options.baseline);
                propertiesValues[property].push(generalParameterValue);
                continue;
              } else {
                let value = generalParameterValue(peak);
                value = getNormalizedValue(value, parameter, property, minMaxY, options.baseline);
                propertiesValues[property].push(value);
                continue;
              }
            }
            // we just need to take the default parameters
            assert(DefaultParameters[parameter], `No default parameter for ${parameter}`);
            const defaultParameterValues = DefaultParameters[parameter][property];
            //@ts-expect-error should never happen
            propertiesValues[property].push(defaultParameterValues(peak, shapeFct));
          }
        }
        const fromIndex = index;
        const toIndex = fromIndex + parameters.length - 1;
        index += toIndex - fromIndex + 1;
        internalPeaks.push({
          id,
          shape,
          shapeFct,
          parameters,
          propertiesValues,
          fromIndex,
          toIndex
        });
      }
      return internalPeaks;
    }
    function getNormalizedValue(value, parameter, property, minMaxY, baseline) {
      if (parameter === 'y') {
        if (property === 'gradientDifference') {
          return value;
        } else {
          return baseline !== undefined ? (value - baseline) / minMaxY.range : (value - minMaxY.min) / minMaxY.range;
        }
      }
      return value;
    }

    function checkOptions(data, options) {
      const {
        timeout,
        initialValues,
        weights = 1,
        damping = 1e-2,
        dampingStepUp = 11,
        dampingStepDown = 9,
        maxIterations = 100,
        errorTolerance = 1e-7,
        centralDifference = false,
        gradientDifference = 10e-2,
        improvementThreshold = 1e-3
      } = options;
      let {
        minValues,
        maxValues
      } = 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$1(data.x) || data.x.length < 2 || !isAnyArray$1(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');
      }
      if (!(initialValues && initialValues.length > 0)) {
        throw new Error('The initialValues option is mandatory and must be an array');
      }
      const parameters = Array.from(initialValues);
      const 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');
      }
      const gradientDifferenceArray = getGradientDifferenceArray(gradientDifference, parameters);
      const filler = getFiller(weights, data.x.length);
      const checkTimeout = getCheckTimeout(timeout);
      const weightSquare = Array.from({
        length: data.x.length
      }, (_, i) => filler(i));
      return {
        checkTimeout,
        minValues,
        maxValues,
        parameters,
        weightSquare,
        damping,
        dampingStepUp,
        dampingStepDown,
        maxIterations,
        errorTolerance,
        centralDifference,
        gradientDifference: gradientDifferenceArray,
        improvementThreshold
      };
    }
    function getGradientDifferenceArray(gradientDifference, parameters) {
      if (typeof gradientDifference === 'number') {
        return new Array(parameters.length).fill(gradientDifference);
      } else if (isAnyArray$1(gradientDifference)) {
        const parLen = parameters.length;
        if (gradientDifference.length !== parLen) {
          return new Array(parLen).fill(gradientDifference[0]);
        }
        return Array.from(gradientDifference);
      }
      throw new Error('gradientDifference should be a number or array with length equal to the number of parameters');
    }
    function getFiller(weights, dataLength) {
      if (typeof weights === 'number') {
        const value = 1 / weights ** 2;
        return () => value;
      } else if (isAnyArray$1(weights)) {
        if (weights.length < dataLength) {
          const value = 1 / weights[0] ** 2;
          return () => value;
        }
        return i => 1 / weights[i] ** 2;
      }
      throw new Error('weights should be a number or array with length equal to the number of data points');
    }
    function getCheckTimeout(timeout) {
      if (timeout !== undefined) {
        if (typeof timeout !== 'number') {
          throw new Error('timeout should be a number');
        }
        const endTime = Date.now() + timeout * 1000;
        return () => Date.now() > endTime;
      } else {
        return () => false;
      }
    }

    /**
     * the sum of the weighted squares of the errors (or weighted residuals) between the data.y
     * and the curve-fit function.
     *
     * @param data - Array of points to fit in the format [x1, x2, ... ], [y1, y2, ... ]
     * @param parameters - Array of current parameter values
     * @param parameterizedFunction - The parameters and returns a function with the independent variable as a parameter
     * @param weightSquare - Square of weights (must be same length as data.x)
     */
    function errorCalculation(data, parameters, parameterizedFunction, weightSquare) {
      let error = 0;
      const func = parameterizedFunction(parameters);
      for (let i = 0; i < data.x.length; i++) {
        error += (data.y[i] - func(data.x[i])) ** 2 / weightSquare[i];
      }
      return error;
    }

    /**
     * Difference of the matrix function over the parameters
     * @param data Array of points to fit in the format [x1, x2, ... ], [y1, y2, ... ]
     * @param evaluatedData - Array of previous evaluated function values
     * @param params - Array of previous parameter values
     * @param gradientDifference - The step size to approximate the jacobian matrix
     * @param centralDifference - If true the jacobian matrix is approximated by central differences otherwise by forward differences
     * @param paramFunction - The parameters and returns a function with the independent variable as a parameter
     */
    function gradientFunction(data, evaluatedData, params, gradientDifference, paramFunction, centralDifference) {
      const nbParams = params.length;
      const nbPoints = data.x.length;
      const 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;
        const 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;
          const 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
     *
     * @param data - Array of points to fit in the format [x1, x2, ... ], [y1, y2, ... ]
     * @param evaluatedData - Array of previous evaluated function values
     */
    function matrixFunction(data, evaluatedData) {
      const m = data.x.length;
      const 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
     *
     * @param data - Array of points to fit in the format [x1, x2, ... ], [y1, y2, ... ]
     * @param params - Array of previous parameter values
     * @param damping - Levenberg-Marquardt parameter
     * @param gradientDifference - The step size to approximate the jacobian matrix
     * @param centralDifference - If true the jacobian matrix is approximated by central differences otherwise by forward differences
     * @param parameterizedFunction - The parameters and returns a function with the independent variable as a parameter
     * @param weights - scale the gradient and residual error by weights
     */
    function step(data, params, damping, gradientDifference, parameterizedFunction, centralDifference, weights) {
      const identity = Matrix.eye(params.length, params.length, damping);
      const func = parameterizedFunction(params);
      const evaluatedData = new Float64Array(data.x.length);
      for (let i = 0; i < data.x.length; i++) {
        evaluatedData[i] = func(data.x[i]);
      }
      const gradientFunc = gradientFunction(data, evaluatedData, params, gradientDifference, parameterizedFunction, centralDifference);
      const residualError = matrixFunction(data, evaluatedData);
      const inverseMatrix = inverse(identity.add(gradientFunc.mmul(gradientFunc.transpose().scale('row', {
        scale: weights
      }))));
      const jacobianWeightResidualError = gradientFunc.mmul(residualError.scale('row', {
        scale: weights
      }));
      const perturbations = inverseMatrix.mmul(jacobianWeightResidualError);
      return {
        perturbations,
        jacobianWeightResidualError
      };
    }

    /**
     * Curve fitting algorithm
     * @param data - Array of points to fit in the format [x1, x2, ... ], [y1, y2, ... ]
     * @param parameterizedFunction - Takes an array of parameters and returns a function with the independent variable as its sole argument
     * @param options - Options object
     */
    function levenbergMarquardt(data, parameterizedFunction, options) {
      const checkedOptions = checkOptions(data, options);
      const {
        checkTimeout,
        minValues,
        maxValues,
        parameters,
        weightSquare,
        dampingStepUp,
        dampingStepDown,
        maxIterations,
        errorTolerance,
        centralDifference,
        gradientDifference,
        improvementThreshold
      } = checkedOptions;
      let damping = checkedOptions.damping;
      let error = errorCalculation(data, parameters, parameterizedFunction, weightSquare);
      let optimalError = error;
      let optimalParameters = parameters.slice();
      let converged = error <= errorTolerance;
      let iteration = 0;
      for (; iteration < maxIterations && !converged; iteration++) {
        const previousError = error;
        const {
          perturbations,
          jacobianWeightResidualError
        } = 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;
        if (error < optimalError - errorTolerance) {
          optimalError = error;
          optimalParameters = parameters.slice();
        }
        const improvementMetric = (previousError - error) / perturbations.transpose().mmul(perturbations.mul(damping).add(jacobianWeightResidualError)).get(0, 0);
        if (improvementMetric > improvementThreshold) {
          damping = Math.max(damping / dampingStepDown, 1e-7);
        } else {
          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: optimalParameters,
        parameterError: optimalError,
        iterations: iteration
      };
    }

    /**
     * Preparata, F. P., & Shamos, M. I. (2012). Computational geometry: an introduction. Springer Science & Business Media.
     * @param {Array} x - The array with x coordinates of the points.
     * @param {Array} y - The array with y coordinates of the points.
     * @return {Array} The indices of the points of anticlockwise lower convex hull
     * @private
     */
    function antiLowerConvexHull(x, y) {
      if (x.length !== y.length) {
        throw new RangeError('X and Y vectors has different dimensions');
      }
      const nbPoints = x.length - 1;
      if (nbPoints === 0) return [0];
      if (nbPoints === 1) return [0, 1];
      let currentPoint = 0;
      let result = new Array(x.length).fill(true);
      while (true) {
        const a = currentPoint;
        const b = moveOn(currentPoint, nbPoints, result);
        const c = moveOn(moveOn(currentPoint, nbPoints, result), nbPoints, result);
        const det = x[c] * (y[a] - y[b]) + x[a] * (y[b] - y[c]) + x[b] * (y[c] - y[a]);
        const leftTurn = det >= 0;
        if (leftTurn) {
          currentPoint = b;
        } else {
          result[b] = false;
          currentPoint = moveBack(currentPoint, nbPoints, result);
        }
        if (c === nbPoints) break;
      }
      return result.map((item, index) => item === false ? false : index).filter(item => item !== false);
    }

    /**
     * @param {number} currentPoint - The index of the current point to make the move
     * @param {number} nbPoints - The total number of points in the array
     * @param {Array} vector - The array with the points
     * @return {number} the index of the point after the move
     * @private
     */

    function moveBack(currentPoint, nbPoints, vector) {
      let counter = currentPoint - 1;
      while (vector[counter] === false) counter--;
      return currentPoint === 0 ? nbPoints : counter;
    }
    function moveOn(currentPoint, nbPoints, vector) {
      let counter = currentPoint + 1;
      while (vector[counter] === false) counter++;
      return currentPoint === nbPoints ? 0 : counter;
    }

    /**
     * Performs a global optimization of required parameters
     * It will return an object containing:
     * - `minFunctionValue`: The minimum value found for the objetive function
     * - `optima`: Array of Array of values for all the variables where the function reach its minimum value
     * - `iterations`: Number of iterations performed in the process
     * - `finalState`: Internal state allowing to continue optimization (initialState)
     * @param {function} objectiveFunction Function to evaluate. It should accept an array of variables
     * @param {Array} lowerBoundaries Array containing for each variable the lower boundary
     * @param {Array} upperBoundaries Array containing for each variable the higher boundary
     * @param {Object} [options={}]
     * @param {number} [options.iterations] - Number of iterations.
     * @param {number} [options.epsilon] - Tolerance to choose best current value.
     * @param {number} [options.tolerance] - Minimum tollerance of the function.
     * @param {number} [options.tolerance2] - Minimum tollerance of the function.
     * @param {Object} [options.initialState={}}] - finalState of previous optimization.
     * @return {Object} {finalState, iterations, minFunctionValue}
     * */

    function direct(objectiveFunction, lowerBoundaries, upperBoundaries, options = {}) {
      const {
        iterations = 50,
        epsilon = 1e-4,
        tolerance = 1e-16,
        tolerance2 = 1e-12,
        initialState = {}
      } = options;
      if (objectiveFunction === undefined || lowerBoundaries === undefined || upperBoundaries === undefined) {
        throw new RangeError('There is something undefined');
      }
      lowerBoundaries = new Float64Array(lowerBoundaries);
      upperBoundaries = new Float64Array(upperBoundaries);
      if (lowerBoundaries.length !== upperBoundaries.length) {
        throw new Error('Lower bounds and Upper bounds for x are not of the same length');
      }

      //-------------------------------------------------------------------------
      //                        STEP 1. Initialization
      //-------------------------------------------------------------------------
      let n = lowerBoundaries.length;
      let diffBorders = upperBoundaries.map((x, i) => x - lowerBoundaries[i]);
      let {
        numberOfRectangles = 0,
        totalIterations = 0,
        unitaryCoordinates = [new Float64Array(n).fill(0.5)],
        middlePoint = new Float64Array(n).map((value, index) => {
          return lowerBoundaries[index] + unitaryCoordinates[0][index] * diffBorders[index];
        }),
        bestCurrentValue = objectiveFunction(middlePoint),
        fCalls = 1,
        smallerDistance = 0,
        edgeSizes = [new Float64Array(n).fill(0.5)],
        diagonalDistances = [Math.sqrt(n * 0.5 ** 2)],
        functionValues = [bestCurrentValue],
        differentDistances = diagonalDistances,
        smallerValuesByDistance = [bestCurrentValue],
        choiceLimit = undefined
      } = initialState;
      if (initialState.originalCoordinates && initialState.originalCoordinates.length > 0) {
        bestCurrentValue = xMinValue(functionValues);
        choiceLimit = epsilon * Math.abs(bestCurrentValue) > 1e-8 ? epsilon * Math.abs(bestCurrentValue) : 1e-8;
        smallerDistance = getMinIndex(functionValues, diagonalDistances, choiceLimit, bestCurrentValue);
        unitaryCoordinates = initialState.originalCoordinates.slice();
        for (let j = 0; j < unitaryCoordinates.length; j++) {
          for (let i = 0; i < lowerBoundaries.length; i++) {
            unitaryCoordinates[j][i] = (unitaryCoordinates[j][i] - lowerBoundaries[i]) / diffBorders[i];
          }
        }
      }
      let iteration = 0;
      //-------------------------------------------------------------------------
      //                          Iteration loop
      //-------------------------------------------------------------------------

      while (iteration < iterations) {
        //----------------------------------------------------------------------
        //  STEP 2. Identify the set S of all potentially optimal rectangles
        //----------------------------------------------------------------------

        let S1 = [];
        let idx = differentDistances.findIndex(
        // eslint-disable-next-line no-loop-func
        e => e === diagonalDistances[smallerDistance]);
        let counter = 0;
        for (let i = idx; i < differentDistances.length; i++) {
          for (let f = 0; f < functionValues.length; f++) {
            if (functionValues[f] === smallerValuesByDistance[i] & diagonalDistances[f] === differentDistances[i]) {
              S1[counter++] = f;
            }
          }
        }
        let optimumValuesIndex, S3;
        if (differentDistances.length - idx > 1) {
          let a1 = diagonalDistances[smallerDistance];
          let b1 = functionValues[smallerDistance];
          let a2 = differentDistances[differentDistances.length - 1];
          let b2 = smallerValuesByDistance[differentDistances.length - 1];
          let slope = (b2 - b1) / (a2 - a1);
          let constant = b1 - slope * a1;
          let S2 = new Uint32Array(counter);
          counter = 0;
          for (let i = 0; i < S2.length; i++) {
            let j = S1[i];
            if (functionValues[j] <= slope * diagonalDistances[j] + constant + tolerance2) {
              S2[counter++] = j;
            }
          }
          let xHull = [];
          let yHull = [];
          for (let i = 0; i < counter; i++) {
            xHull.push(diagonalDistances[S2[i]]);
            yHull.push(functionValues[S2[i]]);
          }
          let lowerIndexHull = antiLowerConvexHull(xHull, yHull);
          S3 = [];
          for (let i = 0; i < lowerIndexHull.length; i++) {
            S3.push(S2[lowerIndexHull[i]]);
          }
        } else {
          S3 = S1.slice(0, counter);
        }
        optimumValuesIndex = S3;
        //--------------------------------------------------------------
        // STEPS 3,5: Select any rectangle j in S
        //--------------------------------------------------------------
        for (let k = 0; k < optimumValuesIndex.length; k++) {
          let j = optimumValuesIndex[k];
          let largerSide = xMaxValue(edgeSizes[j]);
          let largeSidesIndex = new Uint32Array(edgeSizes[j].length);
          counter = 0;
          for (let i = 0; i < edgeSizes[j].length; i++) {
            if (Math.abs(edgeSizes[j][i] - largerSide) < tolerance) {
              largeSidesIndex[counter++] = i;
            }
          }
          let delta = 2 * largerSide / 3;
          let bestFunctionValues = [];
          for (let r = 0; r < counter; r++) {
            let i = largeSidesIndex[r];
            let firstMiddleCenter = unitaryCoordinates[j].slice();
            let secondMiddleCenter = unitaryCoordinates[j].slice();
            firstMiddleCenter[i] += delta;
            secondMiddleCenter[i] -= delta;
            let firstMiddleValue = new Float64Array(firstMiddleCenter.length);
            let secondMiddleValue = new Float64Array(secondMiddleCenter.length);
            for (let i = 0; i < firstMiddleCenter.length; i++) {
              firstMiddleValue[i] = lowerBoundaries[i] + firstMiddleCenter[i] * diffBorders[i];
              secondMiddleValue[i] = lowerBoundaries[i] + secondMiddleCenter[i] * diffBorders[i];
            }
            let firstMinValue = objectiveFunction(firstMiddleValue);
            let secondMinValue = objectiveFunction(secondMiddleValue);
            fCalls += 2;
            bestFunctionValues.push({
              minValue: Math.min(firstMinValue, secondMinValue),
              index: r
            });
            // [Math.min(firstMinValue, secondMinValue), r];
            unitaryCoordinates.push(firstMiddleCenter, secondMiddleCenter);
            functionValues.push(firstMinValue, secondMinValue);
          }
          let b = bestFunctionValues.sort((a, b) => a.minValue - b.minValue);
          for (let r = 0; r < counter; r++) {
            let u = largeSidesIndex[b[r].index];
            let ix1 = numberOfRectangles + 2 * (b[r].index + 1) - 1;
            let ix2 = numberOfRectangles + 2 * (b[r].index + 1);
            edgeSizes[j][u] = delta / 2;
            edgeSizes[ix1] = edgeSizes[j].slice();
            edgeSizes[ix2] = edgeSizes[j].slice();
            diagonalDistances[j] = xNorm(edgeSizes[j]);
            diagonalDistances[ix1] = diagonalDistances[j];
            diagonalDistances[ix2] = diagonalDistances[j];
          }
          numberOfRectangles += 2 * counter;
        }

        //--------------------------------------------------------------
        //                  Update
        //--------------------------------------------------------------

        bestCurrentValue = xMinValue(functionValues);
        choiceLimit = epsilon * Math.abs(bestCurrentValue) > 1e-8 ? epsilon * Math.abs(bestCurrentValue) : 1e-8;
        smallerDistance = getMinIndex(functionValues, diagonalDistances, choiceLimit, bestCurrentValue);
        differentDistances = Array.from(new Set(diagonalDistances));
        differentDistances = differentDistances.sort((a, b) => a - b);
        smallerValuesByDistance = [];
        for (let i = 0; i < differentDistances.length; i++) {
          let minIndex;
          let minValue = Number.POSITIVE_INFINITY;
          for (let k = 0; k < diagonalDistances.length; k++) {
            if (diagonalDistances[k] === differentDistances[i]) {
              if (functionValues[k] < minValue) {
                minValue = functionValues[k];
                minIndex = k;
              }
            }
          }
          smallerValuesByDistance.push(functionValues[minIndex]);
        }
        for (let j = 0; j < functionValues.length; j++) {
          if (functionValues[j] === bestCurrentValue) {
            let temp = [];
            for (let i = 0; i < lowerBoundaries.length; i++) {
              temp.push(lowerBoundaries[i] + unitaryCoordinates[j][i] * diffBorders[i]);
            }
          }
        }
        iteration += 1;
      }
      //--------------------------------------------------------------
      //                  Saving results
      //--------------------------------------------------------------

      let result = {};
      result.minFunctionValue = bestCurrentValue;
      result.iterations = iteration;
      let originalCoordinates = [];
      for (let j = 0; j < numberOfRectangles + 1; j++) {
        let pair = [];
        for (let i = 0; i < lowerBoundaries.length; i++) {
          pair.push(lowerBoundaries[i] + unitaryCoordinates[j][i] * diffBorders[i]);
        }
        originalCoordinates.push(pair);
      }
      result.finalState = {
        numberOfRectangles,
        totalIterations: totalIterations += iterations,
        originalCoordinates,
        middlePoint,
        fCalls,
        smallerDistance,
        edgeSizes,
        diagonalDistances,
        functionValues,
        differentDistances,
        smallerValuesByDistance,
        choiceLimit
      };
      let minimizer = [];
      for (let i = 0; i < functionValues.length; i++) {
        if (functionValues[i] === bestCurrentValue) {
          minimizer.push(originalCoordinates[i]);
        }
      }
      result.optima = minimizer;
      return result;
    }
    function getMinIndex(functionValues, diagonalDistances, choiceLimit, bestCurrentValue) {
      let item = [];
      for (let i = 0; i < functionValues.length; i++) {
        item[i] = Math.abs(functionValues[i] - (bestCurrentValue + choiceLimit)) / diagonalDistances[i];
      }
      const min = xMinValue(item);
      let result = item.findIndex(x => x === min);
      return result;
    }

    function directOptimization(data, sumOfShapes, options) {
      const {
        minValues,
        maxValues,
        maxIterations,
        epsilon,
        tolerance,
        tolerance2,
        initialState
      } = options;
      const objectiveFunction = getObjectiveFunction(data, sumOfShapes);
      const result = direct(objectiveFunction,
      // direct internally converts ArrayLike to Float64Array,
      // so we can safely cast minValues and maxValues to number[]
      minValues, maxValues, {
        iterations: maxIterations,
        epsilon,
        tolerance,
        tolerance2,
        initialState
      });
      const {
        optima
      } = result;
      return {
        parameterError: result.minFunctionValue,
        iterations: result.iterations,
        parameterValues: optima[0]
      };
    }
    function getObjectiveFunction(data, sumOfShapes) {
      const {
        x,
        y
      } = data;
      const nbPoints = x.length;
      return parameters => {
        const fct = sumOfShapes(parameters);
        let error = 0;
        for (let i = 0; i < nbPoints; i++) {
          error += (y[i] - fct(x[i])) ** 2;
        }
        return error;
      };
    }

    /** Algorithm to select the method.
     * @param optimizationOptions - Optimization options
     * @returns - The algorithm and optimization options
     */
    function selectMethod(optimizationOptions = {}) {
      const {
        kind = 'lm',
        options
      } = optimizationOptions;
      switch (kind) {
        case 'lm':
        case 'levenbergMarquardt':
          return {
            algorithm: levenbergMarquardt,
            optimizationOptions: {
              damping: 1.5,
              maxIterations: 100,
              errorTolerance: 1e-8,
              ...options
            }
          };
        case 'direct':
          {
            return {
              algorithm: directOptimization,
              optimizationOptions: {
                iterations: 20,
                epsilon: 1e-4,
                tolerance: 1e-16,
                tolerance2: 1e-12,
                initialState: {},
                ...options
              }
            };
          }
        default:
          throw new Error(`Unknown fitting algorithm`);
      }
    }

    /**
     * Fits a set of points to the sum of a set of bell functions.
     *
     * @param data - An object containing the x and y data to be fitted.
     * @param peaks - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}].
     * @param options - Options for optimize
     * @returns - An 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, peaks, options = {}) {
      // rescale data
      const temp = xMinMaxValues(data.y);
      const minMaxY = {
        ...temp,
        range: temp.max - temp.min
      };
      const internalPeaks = getInternalPeaks(peaks, minMaxY, options);
      // need to rescale what is related to Y
      const {
        baseline: shiftValue = minMaxY.min
      } = options;
      const normalizedY = new Float64Array(data.y.length);
      for (let i = 0; i < data.y.length; i++) {
        normalizedY[i] = (data.y[i] - shiftValue) / minMaxY.range;
      }
      const nbParams = internalPeaks[internalPeaks.length - 1].toIndex + 1;
      const minValues = new Float64Array(nbParams);
      const maxValues = new Float64Array(nbParams);
      const initialValues = new Float64Array(nbParams);
      const gradientDifferences = new Float64Array(nbParams);
      let index = 0;
      for (const peak of internalPeaks) {
        for (let i = 0; i < peak.parameters.length; i++) {
          minValues[index] = peak.propertiesValues.min[i];
          maxValues[index] = peak.propertiesValues.max[i];
          initialValues[index] = peak.propertiesValues.init[i];
          gradientDifferences[index] = peak.propertiesValues.gradientDifference[i];
          index++;
        }
      }
      const {
        algorithm,
        optimizationOptions
      } = selectMethod(options.optimization);
      const sumOfShapes = getSumOfShapes(internalPeaks);
      const fitted = algorithm({
        x: data.x,
        y: normalizedY
      }, sumOfShapes, {
        minValues,
        maxValues,
        initialValues,
        gradientDifference: gradientDifferences,
        ...optimizationOptions
      });
      const fittedValues = fitted.parameterValues;
      const newPeaks = [];
      for (const peak of internalPeaks) {
        const {
          id,
          shape,
          parameters,
          fromIndex
        } = peak;
        let newPeak = {
          x: 0,
          y: 0,
          shape
        };
        if (id) {
          newPeak = {
            ...newPeak,
            id
          };
        }
        newPeak.x = fittedValues[fromIndex];
        newPeak.y = fittedValues[fromIndex + 1] * minMaxY.range + shiftValue;
        for (let i = 2; i < parameters.length; i++) {
          //@ts-expect-error should be fixed once
          newPeak.shape[parameters[i]] = fittedValues[fromIndex + i];
        }
        newPeaks.push(newPeak);
      }
      return {
        error: fitted.parameterError,
        iterations: fitted.iterations,
        peaks: newPeaks
      };
    }

    /**
     * add missing property if it does not exist in the peak,
     * if shape exists but fwhm doesn't, it will be calculated from peak.width
     */
    function addMissingShape(peaks, options = {}) {
      const {
        shape = {
          kind: 'gaussian'
        },
        output = structuredClone(peaks)
      } = options;
      const shapeInstance = getShape1D(shape);
      return output.map(peak => {
        if (hasShape(peak)) {
          if (!('fwhm' in peak.shape)) {
            const shapeInstance = getShape1D(peak.shape);
            peak.shape.fwhm = shapeInstance.widthToFWHM(peak.width);
          }
          return peak;
        }
        return {
          ...peak,
          shape: {
            fwhm: shapeInstance.widthToFWHM(peak.width),
            ...shape
          }
        };
      });
    }
    function hasShape(peak) {
      return 'shape' in peak;
    }

    /**
     * Group peaks based on factor
     * In order to group peaks we only need the x and width value. This means that
     * in the current implementation we don't take into account the asymmetry of peaks
     */
    function groupPeaks(peaks, options = {}) {
      if (peaks && peaks.length === 0) return [];
      const {
        factor = 1
      } = options;
      peaks = JSON.parse(JSON.stringify(peaks));
      peaks.sort((a, b) => a.x - b.x);
      let previousPeak = peaks[0];
      let currentGroup = [previousPeak];
      const groups = [currentGroup];
      for (let i = 1; i < peaks.length; i++) {
        const peak = peaks[i];
        if ((peak.x - previousPeak.x) / ((peak.width + previousPeak.width) / 2) <= factor) {
          currentGroup.push(peak);
        } else {
          currentGroup = [peak];
          groups.push(currentGroup);
        }
        previousPeak = peak;
      }
      return groups;
    }

    /**
     * Optimize the position (x), max intensity (y), full width at half maximum (fwhm)
     * and the ratio of gaussian contribution (mu) if it's required. It currently supports three kind of shapes: gaussian, lorentzian and pseudovoigt
     * @param data - An object containing the x and y data to be fitted.
     * @param peakList - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}].
     */
    function optimizePeaksWithLogs(data, peakList, options = {}) {
      const {
        fromTo = {},
        baseline,
        shape = {
          kind: 'gaussian'
        },
        groupingFactor = 1,
        factorLimits = 2,
        optimization = {
          kind: 'lm',
          options: {
            timeout: 10
          }
        }
      } = options;
      /*
      The optimization algorithm will take some group of peaks.
      We can not simply optimize everything because there would be too many variables to optimize
      and it would be too time consuming.
      */
      const groups = groupPeaks(peakList, {
        factor: groupingFactor
      });
      const logs = [];
      const results = [];
      groups.forEach(peakGroup => {
        const start = Date.now();
        // In order to make optimization we will add fwhm and shape on all the peaks
        const peaks = addMissingShape(peakGroup, {
          shape
        });
        const firstPeak = peaks[0];
        const lastPeak = peaks[peaks.length - 1];
        const {
          from = firstPeak.x - firstPeak.width * factorLimits,
          to = lastPeak.x + lastPeak.width * factorLimits
        } = fromTo;
        const {
          fromIndex,
          toIndex
        } = xGetFromToIndex(data.x, {
          from,
          to
        });
        const x = data.x instanceof Float64Array ? data.x.subarray(fromIndex, toIndex) : data.x.slice(fromIndex, toIndex);
        const y = data.y instanceof Float64Array ? data.y.subarray(fromIndex, toIndex) : data.y.slice(fromIndex, toIndex);
        const log = {
          range: {
            from,
            to
          },
          parameters: optimization,
          groupSize: peakGroup.length,
          time: Date.now() - start
        };
        if (x.length > 5) {
          const {
            iterations,
            error,
            peaks: optimizedPeaks
          } = optimize({
            x,
            y
          }, peaks, {
            shape,
            baseline,
            optimization
          });
          for (let i = 0; i < peaks.length; i++) {
            results.push({
              ...optimizedPeaks[i],
              width: getShape1D(peaks[i].shape).fwhmToWidth(optimizedPeaks[i].shape.fwhm)
            });
          }
          logs.push({
            ...log,
            iterations,
            error,
            message: 'optimization successful'
          });
        } else {
          results.push(...peaks);
          logs.push({
            ...log,
            iterations: 0,
            message: 'x length too small for optimization'
          });
        }
      });
      return {
        logs,
        optimizedPeaks: results
      };
    }

    /**
     * Optimize the position (x), max intensity (y), full width at half maximum (fwhm)
     * and the ratio of gaussian contribution (mu) if it's required. It currently supports three kind of shapes: gaussian, lorentzian and pseudovoigt
     * @param data - An object containing the x and y data to be fitted.
     * @param peakList - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}].
     */
    function optimizePeaks(data, peakList, options = {}) {
      return optimizePeaksWithLogs(data, peakList, options).optimizedPeaks;
    }

    function addMissingIDs(peaks, options = {}) {
      const {
        output = structuredClone(peaks)
      } = options;
      for (const peak of output) {
        if (!('id' in peak)) {
          peak.id = crypto.randomUUID();
        }
      }
      return output;
    }

    function joinBroadPeaks(peakList, options = {}) {
      const {
        shape = {
          kind: 'gaussian'
        },
        optimization = {
          kind: 'lm',
          options: {
            timeout: 10
          }
        },
        broadWidth = 0.25,
        broadRatio = 0.0025
      } = options;
      let max = 0;
      let maxI = 0;
      let count = 1;
      const broadLines = [];
      if (peakList.length < 2) {
        return addMissingIDs(addMissingShape(peakList.map(getGSDPeakOptimizedStructure), {
          shape
        }));
      }
      let maxDdy = peakList[0].ddY;
      for (let i = 1; i < peakList.length; i++) {
        if (Math.abs(peakList[i].ddY) > maxDdy) maxDdy = Math.abs(peakList[i].ddY);
      }
      const newPeaks = [];
      for (const peak of peakList) {
        if (Math.abs(peak.ddY) <= broadRatio * maxDdy) {
          broadLines.push(peak);
        } else {
          newPeaks.push(getGSDPeakOptimizedStructure(peak));
        }
      }
      //@ts-expect-error Push a feke peak
      broadLines.push({
        x: Number.MAX_VALUE,
        y: 0
      });
      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) < broadWidth) {
          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) {
            const initialWidth = Math.abs(candidates.x[candidates.x.length - 1] - candidates.x[0]);
            const {
              logs,
              optimizedPeaks
            } = optimizePeaksWithLogs(candidates, [{
              id: crypto.randomUUID(),
              x: broadLines[maxI].x,
              y: max,
              width: initialWidth,
              parameters: {
                width: {
                  max: initialWidth * 4,
                  min: initialWidth * 0.8
                }
              }
            }], {
              shape: {
                kind: 'pseudoVoigt'
              },
              optimization
            });
            [max, maxI] = [0, 0];
            const log = logs.find(l => l.message === 'optimization successful');
            if (log) {
              const {
                error
              } = log;
              if (error < 0.2) {
                newPeaks.push(optimizedPeaks[0]);
              } else {
                pushBackPeaks(broadLines, indexes, newPeaks);
              }
            } else {
              pushBackPeaks(broadLines, indexes, newPeaks);
            }
          } else {
            pushBackPeaks(broadLines, indexes, newPeaks);
          }
          candidates = {
            x: [broadLines[i].x],
            y: [broadLines[i].y]
          };
          indexes = [i];
          max = broadLines[i].y;
          maxI = i;
          count = 1;
        }
      }
      newPeaks.sort((a, b) => {
        return a.x - b.x;
      });
      return addMissingIDs(newPeaks, {
        output: newPeaks
      });
    }
    function pushBackPeaks(broadLines, indexes, peaks) {
      for (const index of indexes) {
        peaks.push(getGSDPeakOptimizedStructure(broadLines[index]));
      }
    }
    function getGSDPeakOptimizedStructure(peak) {
      const {
        id,
        shape,
        x,
        y,
        width
      } = peak;
      const newPeak = {
        x,
        y,
        width,
        shape
      };
      if (id) newPeak.id = id;
      return newPeak;
    }

    /**
     * This method will allow to enlarge peaks while preventing overlap between peaks
     * A typical application in chromatography peak picking.
     * We should not make the hypothesis that x is equidistant
     * Because peaks may not be symmetric after we add 2 properties, from and to.
     * @return {Array} peakList
     */
    function broadenPeaks(peakList, options = {}) {
      const {
        factor = 2,
        overlap = false
      } = options;
      const peaks = mapPeaks(peakList, factor);
      if (!overlap) {
        for (let i = 0; i < peaks.length - 1; i++) {
          const peak = peaks[i];
          const nextPeak = peaks[i + 1];
          if (peak.to.x > nextPeak.from.x) {
            // we do it proportional to the width of the peaks
            peak.to.x = peak.width / (nextPeak.width + peak.width) * (nextPeak.x - peak.x) + peak.x;
            nextPeak.from.x = peak.to.x;
          }
        }
      }
      for (const peak of peaks) {
        peak.width = peak.to.x - peak.from.x;
        if (peak.shape) {
          const {
            shape,
            width
          } = peak;
          if (shape.fwhm !== undefined) {
            const shapeFct = getShape1D(shape);
            peak.shape.fwhm = shapeFct.widthToFWHM(width);
          }
        }
      }
      return peaks;
    }
    function mapPeaks(peaks, factor) {
      return peaks.map(peak => {
        const {
          id,
          shape
        } = peak;
        const xFrom = peak.x - (peak.x - peak.inflectionPoints.from.x) * factor;
        const xTo = peak.x + (peak.inflectionPoints.to.x - peak.x) * factor;
        let result = {
          x: peak.x,
          y: peak.y,
          index: peak.index,
          width: xTo - xFrom,
          from: {
            x: xFrom
          },
          to: {
            x: xTo
          }
        };
        if (id) {
          result = {
            ...result,
            id
          };
        }
        if (shape) {
          result = {
            ...result,
            shape
          };
        }
        return result;
      });
    }

    const {
      parse: parse$3,
      stringify
    } = JSON;
    /**
     * Append 2 properties to the peaks, shape and fwhm
     */
    function setShape(peaks, options = {}) {
      const {
        shape = {
          kind: 'gaussian'
        },
        output = parse$3(stringify(peaks))
      } = options;
      const shapeInstance = getShape1D(shape);
      return output.map(peak => ({
        ...peak,
        shape: {
          fwhm: shapeInstance.widthToFWHM(peak.width),
          ...shape
        }
      }));
    }

    var lib = /*#__PURE__*/Object.freeze({
        __proto__: null,
        addMissingShape: addMissingShape,
        broadenPeaks: broadenPeaks,
        gsd: gsd,
        joinBroadPeaks: joinBroadPeaks,
        optimizePeaks: optimizePeaks,
        optimizePeaksWithLogs: optimizePeaksWithLogs,
        setShape: setShape
    });

    /**
     * Filter that allows to calibrateX the x axis based on the presence of peaks
     * @param data
     * @param options
     */
    function calibrateX(data, options = {}) {
      const {
        targetX = 0,
        nbPeaks = 1,
        from = data.x[0],
        to = data.x.at(-1),
        gsd: gsdOptions = {
          minMaxRatio: 0.1,
          realTopDetection: true,
          smoothY: true,
          sgOptions: {
            windowSize: 7,
            polynomial: 3
          }
        }
      } = options;
      const fromIndex = xFindClosestIndex(data.x, from);
      const toIndex = xFindClosestIndex(data.x, to);
      const peaks = gsd({
        x: data.x.subarray(fromIndex, toIndex),
        y: data.y.subarray(fromIndex, toIndex)
      }, gsdOptions).sort((a, b) => b.y - a.y).slice(0, nbPeaks);
      if (peaks.length < nbPeaks) return {
        data
      };
      const middle = xMean(peaks.map(peak => peak.x));
      return {
        data: {
          x: xAdd(data.x, targetX - middle),
          y: data.y
        }
      };
    }

    /**
     * Filter that allows to
     * @param data
     * @param options
     */
    function xFunction(data, options = {}) {
      return {
        data: {
          x: xApplyFunctionStr(data.x, {
            variableLabel: 'x',
            fctString: options.function
          }),
          y: data.y
        }
      };
    }

    /**
     * Filter that allows to
     * @param data
     * @param options
     */
    function yFunction(data, options = {}) {
      return {
        data: {
          x: data.x,
          y: xApplyFunctionStr(data.y, {
            variableLabel: 'y',
            fctString: options.function
          })
        }
      };
    }

    /**
     * Filter that allows to
     * @param data
     * @param options
     */
    function setMaxY(data, options = {}) {
      const {
        max = 1
      } = options;
      const existingMax = xMaxValue(data.y);
      if (existingMax === max) {
        return {
          data
        };
      }
      return {
        data: {
          x: data.x,
          y: xAdd(data.y, max - existingMax)
        }
      };
    }

    /**
     * Filter that allows to
     * @param data
     * @param options
     */
    function setMinY(data, options = {}) {
      const {
        min = 0
      } = options;
      const existingMin = xMinValue(data.y);
      if (existingMin === min) {
        return {
          data
        };
      }
      return {
        data: {
          x: data.x,
          y: xAdd(data.y, min - existingMin)
        }
      };
    }

    var Filters = /*#__PURE__*/Object.freeze({
        __proto__: null,
        airPLSBaseline: airPLSBaseline,
        calibrateX: calibrateX,
        centerMean: centerMean,
        centerMedian: centerMedian,
        divideBySD: divideBySD,
        ensureGrowing: ensureGrowing,
        equallySpaced: equallySpaced,
        filterX: filterX,
        firstDerivative: firstDerivative,
        fromTo: fromTo,
        iterativePolynomialBaseline: iterativePolynomialBaseline,
        normed: normed,
        paretoNormalization: paretoNormalization,
        rescale: rescale,
        reverseIfNeeded: reverseIfNeeded,
        rollingAverageBaseline: rollingAverageBaseline,
        rollingBallBaseline: rollingBallBaseline,
        rollingMedianBaseline: rollingMedianBaseline,
        savitzkyGolay: savitzkyGolay,
        secondDerivative: secondDerivative,
        setMaxX: setMaxX,
        setMaxY: setMaxY,
        setMinX: setMinX,
        setMinY: setMinY,
        thirdDerivative: thirdDerivative,
        xFunction: xFunction,
        yFunction: yFunction
    });

    /**
     * Apply filters on {x:[], y:[]}
     * @param data
     * @param filters
     * @returns A very important number
     */
    function filterXY(data, filters) {
      let result = {
        data: xyGrowingX({
          x: xEnsureFloat64(data.x),
          y: xEnsureFloat64(data.y)
        })
      };
      const logs = [];
      for (const filter of filters) {
        const start = Date.now();
        const filterFct = Filters[filter.name];
        if (!filterFct) {
          throw new Error(`Unknown filter: ${filter.name}`);
        }
        // @ts-expect-error some method have options and some other ones don't have any options
        result = filterFct(result.data, filter.options);
        result.data = xyGrowingX(result.data);
        logs.push({
          name: filter.name,
          time: Date.now() - start
        });
      }
      return {
        logs,
        data: result.data
      };
    }

    function getNormalizedSpectrum(spectrum, options = {}) {
      const data = {
        x: spectrum.variables.x.data,
        y: spectrum.variables.y.data
      };
      const newSpectrum = {
        variables: {
          x: {
            data: spectrum.variables.x.data,
            units: spectrum.variables.x.units,
            label: spectrum.variables.x.label
          },
          y: {
            data: spectrum.variables.y.data,
            units: spectrum.variables.y.units,
            label: spectrum.variables.y.label
          }
        }
      };
      if (spectrum.title) newSpectrum.title = spectrum.title;
      if (spectrum.dataType) newSpectrum.dataType = spectrum.dataType;
      if (spectrum.meta) newSpectrum.meta = spectrum.meta;
      if (spectrum.id) newSpectrum.id = spectrum.id;
      const {
        from = spectrum.variables.x.min,
        to = spectrum.variables.x.max,
        numberOfPoints,
        exclusions = [],
        zones = []
      } = options;
      let {
        filters = []
      } = options;
      filters = structuredClone(filters);
      if (numberOfPoints) {
        filters.push({
          name: 'equallySpaced',
          options: {
            from,
            to,
            exclusions,
            zones,
            numberOfPoints
          }
        });
      } else {
        filters.push({
          name: 'filterX',
          options: {
            from,
            to,
            exclusions,
            zones
          }
        });
      }
      const {
        x,
        y
      } = filterXY(data, filters).data;
      // filters change the y axis, we get rid of the units
      // TODO we should deal correctly with this problem
      if (filters.length > 1) {
        newSpectrum.variables.y.units = '';
        newSpectrum.variables.y.label = newSpectrum.variables.y.label?.replace(/\s*\[.*]/, '');
      }
      newSpectrum.variables.x.data = x;
      newSpectrum.variables.x.min = xMinValue(x);
      newSpectrum.variables.x.max = xMaxValue(x);
      newSpectrum.variables.x.isMonotonic = xIsMonotonic(x);
      newSpectrum.variables.y.data = y;
      newSpectrum.variables.y.min = xMinValue(y);
      newSpectrum.variables.y.max = xMaxValue(y);
      newSpectrum.variables.y.isMonotonic = xIsMonotonic(y);
      return newSpectrum;
    }

    var quantities$1 = {exports: {}};

    /*
    The MIT License (MIT)
    Copyright © 2006-2007 Kevin C. Olbrich
    Copyright © 2010-2016 LIM SAS (http://lim.eu) - Julien Sanchez

    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.
    */
    var quantities = quantities$1.exports;
    (function (module, exports) {
      (function (global, factory) {
        module.exports = factory() ;
      })(quantities, function () {

        /**
         * Tests if a value is a string
         *
         * @param {*} value - Value to test
         *
         * @returns {boolean} true if value is a string, false otherwise
         */
        function isString(value) {
          return typeof value === "string" || value instanceof String;
        }

        /*
         * Prefer stricter Number.isFinite if currently supported.
         * To be dropped when ES6 is finalized. Obsolete browsers will
         * have to use ES6 polyfills.
         */
        var isFiniteImpl = Number.isFinite || window.isFinite;
        /**
         * Tests if a value is a number
         *
         * @param {*} value - Value to test
         *
         * @returns {boolean} true if value is a number, false otherwise
         */
        function isNumber(value) {
          // Number.isFinite allows not to consider NaN or '1' as numbers
          return isFiniteImpl(value);
        }

        /*
         * Identity function
         */
        function identity(value) {
          return value;
        }

        /**
         * Returns unique strings from list
         *
         * @param {string[]} strings - array of strings
         *
         *
         * @returns {string[]} a new array of strings without duplicates
         */
        function uniq(strings) {
          var seen = {};
          return strings.filter(function (item) {
            return seen.hasOwnProperty(item) ? false : seen[item] = true;
          });
        }
        function compareArray(array1, array2) {
          if (array2.length !== array1.length) {
            return false;
          }
          for (var i = 0; i < array1.length; i++) {
            if (array2[i].compareArray) {
              if (!array2[i].compareArray(array1[i])) {
                return false;
              }
            }
            if (array2[i] !== array1[i]) {
              return false;
            }
          }
          return true;
        }
        function assign(target, properties) {
          Object.keys(properties).forEach(function (key) {
            target[key] = properties[key];
          });
        }

        /**
         * Safely multiplies numbers while avoiding floating errors
         * like 0.1 * 0.1 => 0.010000000000000002
         *
         * @param {...number} numbers - numbers to multiply
         *
         * @returns {number} result
         */
        function mulSafe() {
          var result = 1,
            decimals = 0;
          for (var i = 0; i < arguments.length; i++) {
            var arg = arguments[i];
            decimals = decimals + getFractional(arg);
            result *= arg;
          }
          return decimals !== 0 ? round(result, decimals) : result;
        }

        /**
         * Safely divides two numbers while avoiding floating errors
         * like 0.3 / 0.05 => 5.999999999999999
         *
         * @returns {number} result
         * @param {number} num Numerator
         * @param {number} den Denominator
         */
        function divSafe(num, den) {
          if (den === 0) {
            throw new Error("Divide by zero");
          }
          var factor = Math.pow(10, getFractional(den));
          var invDen = factor / (factor * den);
          return mulSafe(num, invDen);
        }

        /**
         * Rounds value at the specified number of decimals
         *
         * @param {number} val - value to round
         * @param {number} decimals - number of decimals
         *
         * @returns {number} rounded number
         */
        function round(val, decimals) {
          return Math.round(val * Math.pow(10, decimals)) / Math.pow(10, decimals);
        }
        function getFractional(num) {
          // Check for NaNs or Infinities
          if (!isFinite(num)) {
            return 0;
          }

          // Faster than parsing strings
          // http://jsperf.com/count-decimals/2
          var count = 0;
          while (num % 1 !== 0) {
            num *= 10;
            count++;
          }
          return count;
        }

        /**
         * Custom error type definition
         * @constructor
         */
        function QtyError() {
          var err;
          if (!this) {
            // Allows to instantiate QtyError without new()
            err = Object.create(QtyError.prototype);
            QtyError.apply(err, arguments);
            return err;
          }
          err = Error.apply(this, arguments);
          this.name = "QtyError";
          this.message = err.message;
          this.stack = err.stack;
        }
        QtyError.prototype = Object.create(Error.prototype, {
          constructor: {
            value: QtyError
          }
        });

        /*
         * Throws incompatible units error
         * @param {string} left - units
         * @param {string} right - units incompatible with first argument
         * @throws "Incompatible units" error
         */
        function throwIncompatibleUnits(left, right) {
          throw new QtyError("Incompatible units: " + left + " and " + right);
        }
        var UNITS = {
          /* prefixes */
          "<googol>": [["googol"], 1e100, "prefix"],
          "<kibi>": [["Ki", "Kibi", "kibi"], Math.pow(2, 10), "prefix"],
          "<mebi>": [["Mi", "Mebi", "mebi"], Math.pow(2, 20), "prefix"],
          "<gibi>": [["Gi", "Gibi", "gibi"], Math.pow(2, 30), "prefix"],
          "<tebi>": [["Ti", "Tebi", "tebi"], Math.pow(2, 40), "prefix"],
          "<pebi>": [["Pi", "Pebi", "pebi"], Math.pow(2, 50), "prefix"],
          "<exi>": [["Ei", "Exi", "exi"], Math.pow(2, 60), "prefix"],
          "<zebi>": [["Zi", "Zebi", "zebi"], Math.pow(2, 70), "prefix"],
          "<yebi>": [["Yi", "Yebi", "yebi"], Math.pow(2, 80), "prefix"],
          "<yotta>": [["Y", "Yotta", "yotta"], 1e24, "prefix"],
          "<zetta>": [["Z", "Zetta", "zetta"], 1e21, "prefix"],
          "<exa>": [["E", "Exa", "exa"], 1e18, "prefix"],
          "<peta>": [["P", "Peta", "peta"], 1e15, "prefix"],
          "<tera>": [["T", "Tera", "tera"], 1e12, "prefix"],
          "<giga>": [["G", "Giga", "giga"], 1e9, "prefix"],
          "<mega>": [["M", "Mega", "mega"], 1e6, "prefix"],
          "<kilo>": [["k", "kilo"], 1e3, "prefix"],
          "<hecto>": [["h", "Hecto", "hecto"], 1e2, "prefix"],
          "<deca>": [["da", "Deca", "deca", "deka"], 1e1, "prefix"],
          "<deci>": [["d", "Deci", "deci"], 1e-1, "prefix"],
          "<centi>": [["c", "Centi", "centi"], 1e-2, "prefix"],
          "<milli>": [["m", "Milli", "milli"], 1e-3, "prefix"],
          "<micro>": [["u", "\u03BC" /*µ as greek letter*/, "\u00B5" /*µ as micro sign*/, "Micro", "mc", "micro"], 1e-6, "prefix"],
          "<nano>": [["n", "Nano", "nano"], 1e-9, "prefix"],
          "<pico>": [["p", "Pico", "pico"], 1e-12, "prefix"],
          "<femto>": [["f", "Femto", "femto"], 1e-15, "prefix"],
          "<atto>": [["a", "Atto", "atto"], 1e-18, "prefix"],
          "<zepto>": [["z", "Zepto", "zepto"], 1e-21, "prefix"],
          "<yocto>": [["y", "Yocto", "yocto"], 1e-24, "prefix"],
          "<1>": [["1", "<1>"], 1, ""],
          /* length units */
          "<meter>": [["m", "meter", "meters", "metre", "metres"], 1.0, "length", ["<meter>"]],
          "<inch>": [["in", "inch", "inches", "\""], 0.0254, "length", ["<meter>"]],
          "<foot>": [["ft", "foot", "feet", "'"], 0.3048, "length", ["<meter>"]],
          "<yard>": [["yd", "yard", "yards"], 0.9144, "length", ["<meter>"]],
          "<mile>": [["mi", "mile", "miles"], 1609.344, "length", ["<meter>"]],
          "<naut-mile>": [["nmi", "naut-mile"], 1852, "length", ["<meter>"]],
          "<league>": [["league", "leagues"], 4828, "length", ["<meter>"]],
          "<furlong>": [["furlong", "furlongs"], 201.2, "length", ["<meter>"]],
          "<rod>": [["rd", "rod", "rods"], 5.029, "length", ["<meter>"]],
          "<mil>": [["mil", "mils"], 0.0000254, "length", ["<meter>"]],
          "<angstrom>": [["ang", "angstrom", "angstroms"], 1e-10, "length", ["<meter>"]],
          "<fathom>": [["fathom", "fathoms"], 1.829, "length", ["<meter>"]],
          "<pica>": [["pica", "picas"], 0.00423333333, "length", ["<meter>"]],
          "<point>": [["pt", "point", "points"], 0.000352777778, "length", ["<meter>"]],
          "<redshift>": [["z", "red-shift", "redshift"], 1.302773e26, "length", ["<meter>"]],
          "<AU>": [["AU", "astronomical-unit"], 149597900000, "length", ["<meter>"]],
          "<light-second>": [["ls", "light-second"], 299792500, "length", ["<meter>"]],
          "<light-minute>": [["lmin", "light-minute"], 17987550000, "length", ["<meter>"]],
          "<light-year>": [["ly", "light-year"], 9460528000000000, "length", ["<meter>"]],
          "<parsec>": [["pc", "parsec", "parsecs"], 30856780000000000, "length", ["<meter>"]],
          "<datamile>": [["DM", "datamile"], 1828.8, "length", ["<meter>"]],
          /* mass */
          "<kilogram>": [["kg", "kilogram", "kilograms"], 1.0, "mass", ["<kilogram>"]],
          "<AMU>": [["u", "AMU", "amu"], 1.660538921e-27, "mass", ["<kilogram>"]],
          "<dalton>": [["Da", "Dalton", "Daltons", "dalton", "daltons"], 1.660538921e-27, "mass", ["<kilogram>"]],
          "<slug>": [["slug", "slugs"], 14.5939029, "mass", ["<kilogram>"]],
          "<short-ton>": [["tn", "ton", "short-ton"], 907.18474, "mass", ["<kilogram>"]],
          "<metric-ton>": [["t", "tonne", "metric-ton"], 1000, "mass", ["<kilogram>"]],
          "<carat>": [["ct", "carat", "carats"], 0.0002, "mass", ["<kilogram>"]],
          "<pound>": [["lbs", "lb", "pound", "pounds", "#"], 0.45359237, "mass", ["<kilogram>"]],
          "<ounce>": [["oz", "ounce", "ounces"], 0.0283495231, "mass", ["<kilogram>"]],
          "<gram>": [["g", "gram", "grams", "gramme", "grammes"], 1e-3, "mass", ["<kilogram>"]],
          "<grain>": [["grain", "grains", "gr"], 6.479891e-5, "mass", ["<kilogram>"]],
          "<dram>": [["dram", "drams", "dr"], 0.0017718452, "mass", ["<kilogram>"]],
          "<stone>": [["stone", "stones", "st"], 6.35029318, "mass", ["<kilogram>"]],
          /* area */
          "<hectare>": [["hectare"], 10000, "area", ["<meter>", "<meter>"]],
          "<acre>": [["acre", "acres"], 4046.85642, "area", ["<meter>", "<meter>"]],
          "<sqft>": [["sqft"], 1, "area", ["<foot>", "<foot>"]],
          /* volume */
          "<liter>": [["l", "L", "liter", "liters", "litre", "litres"], 0.001, "volume", ["<meter>", "<meter>", "<meter>"]],
          "<gallon>": [["gal", "gallon", "gallons"], 0.0037854118, "volume", ["<meter>", "<meter>", "<meter>"]],
          "<gallon-imp>": [["galimp", "gallon-imp", "gallons-imp"], 0.0045460900, "volume", ["<meter>", "<meter>", "<meter>"]],
          "<quart>": [["qt", "quart", "quarts"], 0.00094635295, "volume", ["<meter>", "<meter>", "<meter>"]],
          "<pint>": [["pt", "pint", "pints"], 0.000473176475, "volume", ["<meter>", "<meter>", "<meter>"]],
          "<pint-imp>": [["ptimp", "pint-imp", "pints-imp"], 5.6826125e-4, "volume", ["<meter>", "<meter>", "<meter>"]],
          "<cup>": [["cu", "cup", "cups"], 0.000236588238, "volume", ["<meter>", "<meter>", "<meter>"]],
          "<fluid-ounce>": [["floz", "fluid-ounce", "fluid-ounces"], 2.95735297e-5, "volume", ["<meter>", "<meter>", "<meter>"]],
          "<fluid-ounce-imp>": [["flozimp", "floz-imp", "fluid-ounce-imp", "fluid-ounces-imp"], 2.84130625e-5, "volume", ["<meter>", "<meter>", "<meter>"]],
          "<tablespoon>": [["tb", "tbsp", "tbs", "tablespoon", "tablespoons"], 1.47867648e-5, "volume", ["<meter>", "<meter>", "<meter>"]],
          "<teaspoon>": [["tsp", "teaspoon", "teaspoons"], 4.92892161e-6, "volume", ["<meter>", "<meter>", "<meter>"]],
          "<bushel>": [["bu", "bsh", "bushel", "bushels"], 0.035239072, "volume", ["<meter>", "<meter>", "<meter>"]],
          "<oilbarrel>": [["bbl", "oilbarrel", "oilbarrels", "oil-barrel", "oil-barrels"], 0.158987294928, "volume", ["<meter>", "<meter>", "<meter>"]],
          "<beerbarrel>": [["bl", "bl-us", "beerbarrel", "beerbarrels", "beer-barrel", "beer-barrels"], 0.1173477658, "volume", ["<meter>", "<meter>", "<meter>"]],
          "<beerbarrel-imp>": [["blimp", "bl-imp", "beerbarrel-imp", "beerbarrels-imp", "beer-barrel-imp", "beer-barrels-imp"], 0.16365924, "volume", ["<meter>", "<meter>", "<meter>"]],
          /* speed */
          "<kph>": [["kph"], 0.277777778, "speed", ["<meter>"], ["<second>"]],
          "<mph>": [["mph"], 0.44704, "speed", ["<meter>"], ["<second>"]],
          "<knot>": [["kt", "kn", "kts", "knot", "knots"], 0.514444444, "speed", ["<meter>"], ["<second>"]],
          "<fps>": [["fps"], 0.3048, "speed", ["<meter>"], ["<second>"]],
          /* acceleration */
          "<gee>": [["gee"], 9.80665, "acceleration", ["<meter>"], ["<second>", "<second>"]],
          "<Gal>": [["Gal"], 1e-2, "acceleration", ["<meter>"], ["<second>", "<second>"]],
          /* temperature_difference */
          "<kelvin>": [["degK", "kelvin"], 1.0, "temperature", ["<kelvin>"]],
          "<celsius>": [["degC", "celsius", "celsius", "centigrade"], 1.0, "temperature", ["<kelvin>"]],
          "<fahrenheit>": [["degF", "fahrenheit"], 5 / 9, "temperature", ["<kelvin>"]],
          "<rankine>": [["degR", "rankine"], 5 / 9, "temperature", ["<kelvin>"]],
          "<temp-K>": [["tempK", "temp-K"], 1.0, "temperature", ["<temp-K>"]],
          "<temp-C>": [["tempC", "temp-C"], 1.0, "temperature", ["<temp-K>"]],
          "<temp-F>": [["tempF", "temp-F"], 5 / 9, "temperature", ["<temp-K>"]],
          "<temp-R>": [["tempR", "temp-R"], 5 / 9, "temperature", ["<temp-K>"]],
          /* time */
          "<second>": [["s", "sec", "secs", "second", "seconds"], 1.0, "time", ["<second>"]],
          "<minute>": [["min", "mins", "minute", "minutes"], 60.0, "time", ["<second>"]],
          "<hour>": [["h", "hr", "hrs", "hour", "hours"], 3600.0, "time", ["<second>"]],
          "<day>": [["d", "day", "days"], 3600 * 24, "time", ["<second>"]],
          "<week>": [["wk", "week", "weeks"], 7 * 3600 * 24, "time", ["<second>"]],
          "<fortnight>": [["fortnight", "fortnights"], 1209600, "time", ["<second>"]],
          "<year>": [["y", "yr", "year", "years", "annum"], 31556926, "time", ["<second>"]],
          "<decade>": [["decade", "decades"], 315569260, "time", ["<second>"]],
          "<century>": [["century", "centuries"], 3155692600, "time", ["<second>"]],
          /* pressure */
          "<pascal>": [["Pa", "pascal", "Pascal"], 1.0, "pressure", ["<kilogram>"], ["<meter>", "<second>", "<second>"]],
          "<bar>": [["bar", "bars"], 100000, "pressure", ["<kilogram>"], ["<meter>", "<second>", "<second>"]],
          "<mmHg>": [["mmHg"], 133.322368, "pressure", ["<kilogram>"], ["<meter>", "<second>", "<second>"]],
          "<inHg>": [["inHg"], 3386.3881472, "pressure", ["<kilogram>"], ["<meter>", "<second>", "<second>"]],
          "<torr>": [["torr"], 133.322368, "pressure", ["<kilogram>"], ["<meter>", "<second>", "<second>"]],
          "<atm>": [["atm", "ATM", "atmosphere", "atmospheres"], 101325, "pressure", ["<kilogram>"], ["<meter>", "<second>", "<second>"]],
          "<psi>": [["psi"], 6894.76, "pressure", ["<kilogram>"], ["<meter>", "<second>", "<second>"]],
          "<cmh2o>": [["cmH2O", "cmh2o"], 98.0638, "pressure", ["<kilogram>"], ["<meter>", "<second>", "<second>"]],
          "<inh2o>": [["inH2O", "inh2o"], 249.082052, "pressure", ["<kilogram>"], ["<meter>", "<second>", "<second>"]],
          /* viscosity */
          "<poise>": [["P", "poise"], 0.1, "viscosity", ["<kilogram>"], ["<meter>", "<second>"]],
          "<stokes>": [["St", "stokes"], 1e-4, "viscosity", ["<meter>", "<meter>"], ["<second>"]],
          /* substance */
          "<mole>": [["mol", "mole"], 1.0, "substance", ["<mole>"]],
          /* molar_concentration */
          "<molar>": [["M", "molar"], 1000, "molar_concentration", ["<mole>"], ["<meter>", "<meter>", "<meter>"]],
          "<wtpercent>": [["wt%", "wtpercent"], 10, "molar_concentration", ["<kilogram>"], ["<meter>", "<meter>", "<meter>"]],
          /* activity */
          "<katal>": [["kat", "katal", "Katal"], 1.0, "activity", ["<mole>"], ["<second>"]],
          "<unit>": [["U", "enzUnit", "unit"], 16.667e-16, "activity", ["<mole>"], ["<second>"]],
          /* capacitance */
          "<farad>": [["F", "farad", "Farad"], 1.0, "capacitance", ["<second>", "<second>", "<second>", "<second>", "<ampere>", "<ampere>"], ["<meter>", "<meter>", "<kilogram>"]],
          /* charge */
          "<coulomb>": [["C", "coulomb", "Coulomb"], 1.0, "charge", ["<ampere>", "<second>"]],
          "<Ah>": [["Ah"], 3600, "charge", ["<ampere>", "<second>"]],
          /* current */
          "<ampere>": [["A", "Ampere", "ampere", "amp", "amps"], 1.0, "current", ["<ampere>"]],
          /* conductance */
          "<siemens>": [["S", "Siemens", "siemens"], 1.0, "conductance", ["<second>", "<second>", "<second>", "<ampere>", "<ampere>"], ["<kilogram>", "<meter>", "<meter>"]],
          /* inductance */
          "<henry>": [["H", "Henry", "henry"], 1.0, "inductance", ["<meter>", "<meter>", "<kilogram>"], ["<second>", "<second>", "<ampere>", "<ampere>"]],
          /* potential */
          "<volt>": [["V", "Volt", "volt", "volts"], 1.0, "potential", ["<meter>", "<meter>", "<kilogram>"], ["<second>", "<second>", "<second>", "<ampere>"]],
          /* resistance */
          "<ohm>": [["Ohm", "ohm", "\u03A9" /*Ω as greek letter*/, "\u2126" /*Ω as ohm sign*/], 1.0, "resistance", ["<meter>", "<meter>", "<kilogram>"], ["<second>", "<second>", "<second>", "<ampere>", "<ampere>"]],
          /* magnetism */
          "<weber>": [["Wb", "weber", "webers"], 1.0, "magnetism", ["<meter>", "<meter>", "<kilogram>"], ["<second>", "<second>", "<ampere>"]],
          "<tesla>": [["T", "tesla", "teslas"], 1.0, "magnetism", ["<kilogram>"], ["<second>", "<second>", "<ampere>"]],
          "<gauss>": [["G", "gauss"], 1e-4, "magnetism", ["<kilogram>"], ["<second>", "<second>", "<ampere>"]],
          "<maxwell>": [["Mx", "maxwell", "maxwells"], 1e-8, "magnetism", ["<meter>", "<meter>", "<kilogram>"], ["<second>", "<second>", "<ampere>"]],
          "<oersted>": [["Oe", "oersted", "oersteds"], 250.0 / Math.PI, "magnetism", ["<ampere>"], ["<meter>"]],
          /* energy */
          "<joule>": [["J", "joule", "Joule", "joules", "Joules"], 1.0, "energy", ["<meter>", "<meter>", "<kilogram>"], ["<second>", "<second>"]],
          "<erg>": [["erg", "ergs"], 1e-7, "energy", ["<meter>", "<meter>", "<kilogram>"], ["<second>", "<second>"]],
          "<btu>": [["BTU", "btu", "BTUs"], 1055.056, "energy", ["<meter>", "<meter>", "<kilogram>"], ["<second>", "<second>"]],
          "<calorie>": [["cal", "calorie", "calories"], 4.18400, "energy", ["<meter>", "<meter>", "<kilogram>"], ["<second>", "<second>"]],
          "<Calorie>": [["Cal", "Calorie", "Calories"], 4184.00, "energy", ["<meter>", "<meter>", "<kilogram>"], ["<second>", "<second>"]],
          "<therm-US>": [["th", "therm", "therms", "Therm", "therm-US"], 105480400, "energy", ["<meter>", "<meter>", "<kilogram>"], ["<second>", "<second>"]],
          "<Wh>": [["Wh"], 3600, "energy", ["<meter>", "<meter>", "<kilogram>"], ["<second>", "<second>"]],
          "<electronvolt>": [["eV", "electronvolt", "electronvolts"], 1.602176634E-19, "energy", ["<meter>", "<meter>", "<kilogram>"], ["<second>", "<second>"]],
          /* force */
          "<newton>": [["N", "Newton", "newton"], 1.0, "force", ["<kilogram>", "<meter>"], ["<second>", "<second>"]],
          "<dyne>": [["dyn", "dyne"], 1e-5, "force", ["<kilogram>", "<meter>"], ["<second>", "<second>"]],
          "<pound-force>": [["lbf", "pound-force"], 4.448222, "force", ["<kilogram>", "<meter>"], ["<second>", "<second>"]],
          /* frequency */
          "<hertz>": [["Hz", "hertz", "Hertz"], 1.0, "frequency", ["<1>"], ["<second>"]],
          /* angle */
          "<radian>": [["rad", "radian", "radians"], 1.0, "angle", ["<radian>"]],
          "<degree>": [["deg", "degree", "degrees"], Math.PI / 180.0, "angle", ["<radian>"]],
          "<arcminute>": [["arcmin", "arcminute", "arcminutes"], Math.PI / 10800.0, "angle", ["<radian>"]],
          "<arcsecond>": [["arcsec", "arcsecond", "arcseconds"], Math.PI / 648000.0, "angle", ["<radian>"]],
          "<gradian>": [["gon", "grad", "gradian", "grads"], Math.PI / 200.0, "angle", ["<radian>"]],
          "<steradian>": [["sr", "steradian", "steradians"], 1.0, "solid_angle", ["<steradian>"]],
          /* rotation */
          "<rotation>": [["rotation"], 2.0 * Math.PI, "angle", ["<radian>"]],
          "<rpm>": [["rpm"], 2.0 * Math.PI / 60.0, "angular_velocity", ["<radian>"], ["<second>"]],
          /* information */
          "<byte>": [["B", "byte", "bytes"], 1.0, "information", ["<byte>"]],
          "<bit>": [["b", "bit", "bits"], 0.125, "information", ["<byte>"]],
          /* information rate */
          "<Bps>": [["Bps"], 1.0, "information_rate", ["<byte>"], ["<second>"]],
          "<bps>": [["bps"], 0.125, "information_rate", ["<byte>"], ["<second>"]],
          /* currency */
          "<dollar>": [["USD", "dollar"], 1.0, "currency", ["<dollar>"]],
          "<cents>": [["cents"], 0.01, "currency", ["<dollar>"]],
          /* luminosity */
          "<candela>": [["cd", "candela"], 1.0, "luminosity", ["<candela>"]],
          "<lumen>": [["lm", "lumen"], 1.0, "luminous_power", ["<candela>", "<steradian>"]],
          "<lux>": [["lux"], 1.0, "illuminance", ["<candela>", "<steradian>"], ["<meter>", "<meter>"]],
          /* power */
          "<watt>": [["W", "watt", "watts"], 1.0, "power", ["<kilogram>", "<meter>", "<meter>"], ["<second>", "<second>", "<second>"]],
          "<volt-ampere>": [["VA", "volt-ampere"], 1.0, "power", ["<kilogram>", "<meter>", "<meter>"], ["<second>", "<second>", "<second>"]],
          "<volt-ampere-reactive>": [["var", "Var", "VAr", "VAR", "volt-ampere-reactive"], 1.0, "power", ["<kilogram>", "<meter>", "<meter>"], ["<second>", "<second>", "<second>"]],
          "<horsepower>": [["hp", "horsepower"], 745.699872, "power", ["<kilogram>", "<meter>", "<meter>"], ["<second>", "<second>", "<second>"]],
          /* radiation */
          "<gray>": [["Gy", "gray", "grays"], 1.0, "radiation", ["<meter>", "<meter>"], ["<second>", "<second>"]],
          "<roentgen>": [["R", "roentgen"], 0.009330, "radiation", ["<meter>", "<meter>"], ["<second>", "<second>"]],
          "<sievert>": [["Sv", "sievert", "sieverts"], 1.0, "radiation", ["<meter>", "<meter>"], ["<second>", "<second>"]],
          "<becquerel>": [["Bq", "becquerel", "becquerels"], 1.0, "radiation", ["<1>"], ["<second>"]],
          "<curie>": [["Ci", "curie", "curies"], 3.7e10, "radiation", ["<1>"], ["<second>"]],
          /* rate */
          "<cpm>": [["cpm"], 1.0 / 60.0, "rate", ["<count>"], ["<second>"]],
          "<dpm>": [["dpm"], 1.0 / 60.0, "rate", ["<count>"], ["<second>"]],
          "<bpm>": [["bpm"], 1.0 / 60.0, "rate", ["<count>"], ["<second>"]],
          /* resolution / typography */
          "<dot>": [["dot", "dots"], 1, "resolution", ["<each>"]],
          "<pixel>": [["pixel", "px"], 1, "resolution", ["<each>"]],
          "<ppi>": [["ppi"], 1, "resolution", ["<pixel>"], ["<inch>"]],
          "<dpi>": [["dpi"], 1, "typography", ["<dot>"], ["<inch>"]],
          /* other */
          "<cell>": [["cells", "cell"], 1, "counting", ["<each>"]],
          "<each>": [["each"], 1.0, "counting", ["<each>"]],
          "<count>": [["count"], 1.0, "counting", ["<each>"]],
          "<base-pair>": [["bp", "base-pair"], 1.0, "counting", ["<each>"]],
          "<nucleotide>": [["nt", "nucleotide"], 1.0, "counting", ["<each>"]],
          "<molecule>": [["molecule", "molecules"], 1.0, "counting", ["<1>"]],
          "<dozen>": [["doz", "dz", "dozen"], 12.0, "prefix_only", ["<each>"]],
          "<percent>": [["%", "percent"], 0.01, "prefix_only", ["<1>"]],
          "<ppm>": [["ppm"], 1e-6, "prefix_only", ["<1>"]],
          "<ppb>": [["ppb"], 1e-9, "prefix_only", ["<1>"]],
          "<ppt>": [["ppt"], 1e-12, "prefix_only", ["<1>"]],
          "<ppq>": [["ppq"], 1e-15, "prefix_only", ["<1>"]],
          "<gross>": [["gr", "gross"], 144.0, "prefix_only", ["<dozen>", "<dozen>"]],
          "<decibel>": [["dB", "decibel", "decibels"], 1.0, "logarithmic", ["<decibel>"]]
        };
        var BASE_UNITS = ["<meter>", "<kilogram>", "<second>", "<mole>", "<ampere>", "<radian>", "<kelvin>", "<temp-K>", "<byte>", "<dollar>", "<candela>", "<each>", "<steradian>", "<decibel>"];
        var UNITY = "<1>";
        var UNITY_ARRAY = [UNITY];

        // Setup

        /**
         * Asserts unit definition is valid
         *
         * @param {string} unitDef - Name of unit to test
         * @param {Object} definition - Definition of unit to test
         *
         * @returns {void}
         * @throws {QtyError} if unit definition is not valid
         */
        function validateUnitDefinition(unitDef, definition) {
          var scalar = definition[1];
          var numerator = definition[3] || [];
          var denominator = definition[4] || [];
          if (!isNumber(scalar)) {
            throw new QtyError(unitDef + ": Invalid unit definition. " + "'scalar' must be a number");
          }
          numerator.forEach(function (unit) {
            if (UNITS[unit] === undefined) {
              throw new QtyError(unitDef + ": Invalid unit definition. " + "Unit " + unit + " in 'numerator' is not recognized");
            }
          });
          denominator.forEach(function (unit) {
            if (UNITS[unit] === undefined) {
              throw new QtyError(unitDef + ": Invalid unit definition. " + "Unit " + unit + " in 'denominator' is not recognized");
            }
          });
        }
        var PREFIX_VALUES = {};
        var PREFIX_MAP = {};
        var UNIT_VALUES = {};
        var UNIT_MAP = {};
        var OUTPUT_MAP = {};
        for (var unitDef in UNITS) {
          if (UNITS.hasOwnProperty(unitDef)) {
            var definition = UNITS[unitDef];
            if (definition[2] === "prefix") {
              PREFIX_VALUES[unitDef] = definition[1];
              for (var i = 0; i < definition[0].length; i++) {
                PREFIX_MAP[definition[0][i]] = unitDef;
              }
            } else {
              validateUnitDefinition(unitDef, definition);
              UNIT_VALUES[unitDef] = {
                scalar: definition[1],
                numerator: definition[3],
                denominator: definition[4]
              };
              for (var j = 0; j < definition[0].length; j++) {
                UNIT_MAP[definition[0][j]] = unitDef;
              }
            }
            OUTPUT_MAP[unitDef] = definition[0][0];
          }
        }

        /**
         * Returns a list of available units of kind
         *
         * @param {string} [kind] - kind of units
         * @returns {array} names of units
         * @throws {QtyError} if kind is unknown
         */
        function getUnits(kind) {
          var i;
          var units = [];
          var unitKeys = Object.keys(UNITS);
          if (typeof kind === "undefined") {
            for (i = 0; i < unitKeys.length; i++) {
              if (["", "prefix"].indexOf(UNITS[unitKeys[i]][2]) === -1) {
                units.push(unitKeys[i].substr(1, unitKeys[i].length - 2));
              }
            }
          } else if (this.getKinds().indexOf(kind) === -1) {
            throw new QtyError("Kind not recognized");
          } else {
            for (i = 0; i < unitKeys.length; i++) {
              if (UNITS[unitKeys[i]][2] === kind) {
                units.push(unitKeys[i].substr(1, unitKeys[i].length - 2));
              }
            }
          }
          return units.sort(function (a, b) {
            if (a.toLowerCase() < b.toLowerCase()) {
              return -1;
            }
            if (a.toLowerCase() > b.toLowerCase()) {
              return 1;
            }
            return 0;
          });
        }

        /**
         * Returns a list of alternative names for a unit
         *
         * @param {string} unitName - name of unit
         * @returns {string[]} aliases for unit
         * @throws {QtyError} if unit is unknown
         */
        function getAliases(unitName) {
          if (!UNIT_MAP[unitName]) {
            throw new QtyError("Unit not recognized");
          }
          return UNITS[UNIT_MAP[unitName]][0];
        }
        var SIGNATURE_VECTOR = ["length", "time", "temperature", "mass", "current", "substance", "luminosity", "currency", "information", "angle"];

        /*
        calculates the unit signature id for use in comparing compatible units and simplification
        the signature is based on a simple classification of units and is based on the following publication
         Novak, G.S., Jr. "Conversion of units of measurement", IEEE Transactions on Software Engineering,
        21(8), Aug 1995, pp.651-661
        doi://10.1109/32.403789
        http://ieeexplore.ieee.org/Xplore/login.jsp?url=/iel1/32/9079/00403789.pdf?isnumber=9079&prod=JNL&arnumber=403789&arSt=651&ared=661&arAuthor=Novak%2C+G.S.%2C+Jr.
        */
        function unitSignature() {
          if (this.signature) {
            return this.signature;
          }
          var vector = unitSignatureVector.call(this);
          for (var i = 0; i < vector.length; i++) {
            vector[i] *= Math.pow(20, i);
          }
          return vector.reduce(function (previous, current) {
            return previous + current;
          }, 0);
        }

        // calculates the unit signature vector used by unit_signature
        function unitSignatureVector() {
          if (!this.isBase()) {
            return unitSignatureVector.call(this.toBase());
          }
          var vector = new Array(SIGNATURE_VECTOR.length);
          for (var i = 0; i < vector.length; i++) {
            vector[i] = 0;
          }
          var r, n;
          for (var j = 0; j < this.numerator.length; j++) {
            if (r = UNITS[this.numerator[j]]) {
              n = SIGNATURE_VECTOR.indexOf(r[2]);
              if (n >= 0) {
                vector[n] = vector[n] + 1;
              }
            }
          }
          for (var k = 0; k < this.denominator.length; k++) {
            if (r = UNITS[this.denominator[k]]) {
              n = SIGNATURE_VECTOR.indexOf(r[2]);
              if (n >= 0) {
                vector[n] = vector[n] - 1;
              }
            }
          }
          return vector;
        }
        var SIGN = "[+-]";
        var INTEGER = "\\d+";
        var SIGNED_INTEGER = SIGN + "?" + INTEGER;
        var FRACTION = "\\." + INTEGER;
        var FLOAT = "(?:" + INTEGER + "(?:" + FRACTION + ")?" + ")" + "|" + "(?:" + FRACTION + ")";
        var EXPONENT = "[Ee]" + SIGNED_INTEGER;
        var SCI_NUMBER = "(?:" + FLOAT + ")(?:" + EXPONENT + ")?";
        var SIGNED_NUMBER = SIGN + "?\\s*" + SCI_NUMBER;
        var QTY_STRING = "(" + SIGNED_NUMBER + ")?" + "\\s*([^/]*)(?:\/(.+))?";
        var QTY_STRING_REGEX = new RegExp("^" + QTY_STRING + "$");
        var POWER_OP = "\\^|\\*{2}";
        // Allow unit powers representing scalar, length, area, volume; 4 is for some
        // special case representations in SI base units.
        var SAFE_POWER = "[01234]";
        var TOP_REGEX = new RegExp("([^ \\*\\d]+?)(?:" + POWER_OP + ")?(-?" + SAFE_POWER + "(?![a-zA-Z]))");
        var BOTTOM_REGEX = new RegExp("([^ \\*\\d]+?)(?:" + POWER_OP + ")?(" + SAFE_POWER + "(?![a-zA-Z]))");

        /* parse a string into a unit object.
         * Typical formats like :
         * "5.6 kg*m/s^2"
         * "5.6 kg*m*s^-2"
         * "5.6 kilogram*meter*second^-2"
         * "2.2 kPa"
         * "37 degC"
         * "1"  -- creates a unitless constant with value 1
         * "GPa"  -- creates a unit with scalar 1 with units 'GPa'
         * 6'4"  -- recognized as 6 feet + 4 inches
         * 8 lbs 8 oz -- recognized as 8 lbs + 8 ounces
         */
        function parse(val) {
          if (!isString(val)) {
            val = val.toString();
          }
          val = val.trim();
          var result = QTY_STRING_REGEX.exec(val);
          if (!result) {
            throw new QtyError(val + ": Quantity not recognized");
          }
          var scalarMatch = result[1];
          if (scalarMatch) {
            // Allow whitespaces between sign and scalar for loose parsing
            scalarMatch = scalarMatch.replace(/\s/g, "");
            this.scalar = parseFloat(scalarMatch);
          } else {
            this.scalar = 1;
          }
          var top = result[2];
          var bottom = result[3];
          var n, x, nx;
          // TODO DRY me
          while (result = TOP_REGEX.exec(top)) {
            n = parseFloat(result[2]);
            if (isNaN(n)) {
              // Prevents infinite loops
              throw new QtyError("Unit exponent is not a number");
            }
            // Disallow unrecognized unit even if exponent is 0
            if (n === 0 && !UNIT_TEST_REGEX.test(result[1])) {
              throw new QtyError("Unit not recognized");
            }
            x = result[1] + " ";
            nx = "";
            for (var i = 0; i < Math.abs(n); i++) {
              nx += x;
            }
            if (n >= 0) {
              top = top.replace(result[0], nx);
            } else {
              bottom = bottom ? bottom + nx : nx;
              top = top.replace(result[0], "");
            }
          }
          while (result = BOTTOM_REGEX.exec(bottom)) {
            n = parseFloat(result[2]);
            if (isNaN(n)) {
              // Prevents infinite loops
              throw new QtyError("Unit exponent is not a number");
            }
            // Disallow unrecognized unit even if exponent is 0
            if (n === 0 && !UNIT_TEST_REGEX.test(result[1])) {
              throw new QtyError("Unit not recognized");
            }
            x = result[1] + " ";
            nx = "";
            for (var j = 0; j < n; j++) {
              nx += x;
            }
            bottom = bottom.replace(result[0], nx);
          }
          if (top) {
            this.numerator = parseUnits(top.trim());
          }
          if (bottom) {
            this.denominator = parseUnits(bottom.trim());
          }
        }
        var PREFIX_REGEX = Object.keys(PREFIX_MAP).sort(function (a, b) {
          return b.length - a.length;
        }).join("|");
        var UNIT_REGEX = Object.keys(UNIT_MAP).sort(function (a, b) {
          return b.length - a.length;
        }).join("|");
        /*
         * Minimal boundary regex to support units with Unicode characters
         * \b only works for ASCII
         */
        var BOUNDARY_REGEX = "\\b|$";
        var UNIT_MATCH = "(" + PREFIX_REGEX + ")??(" + UNIT_REGEX + ")(?:" + BOUNDARY_REGEX + ")";
        var UNIT_TEST_REGEX = new RegExp("^\\s*(" + UNIT_MATCH + "[\\s\\*]*)+$");
        var UNIT_MATCH_REGEX = new RegExp(UNIT_MATCH, "g"); // g flag for multiple occurences
        var parsedUnitsCache = {};
        /**
         * Parses and converts units string to normalized unit array.
         * Result is cached to speed up next calls.
         *
         * @param {string} units Units string
         * @returns {string[]} Array of normalized units
         *
         * @example
         * // Returns ["<second>", "<meter>", "<second>"]
         * parseUnits("s m s");
         *
         */
        function parseUnits(units) {
          var cached = parsedUnitsCache[units];
          if (cached) {
            return cached;
          }
          var unitMatch,
            normalizedUnits = [];

          // Scan
          if (!UNIT_TEST_REGEX.test(units)) {
            throw new QtyError("Unit not recognized");
          }
          while (unitMatch = UNIT_MATCH_REGEX.exec(units)) {
            normalizedUnits.push(unitMatch.slice(1));
          }
          normalizedUnits = normalizedUnits.map(function (item) {
            return PREFIX_MAP[item[0]] ? [PREFIX_MAP[item[0]], UNIT_MAP[item[1]]] : [UNIT_MAP[item[1]]];
          });

          // Flatten and remove null elements
          normalizedUnits = normalizedUnits.reduce(function (a, b) {
            return a.concat(b);
          }, []);
          normalizedUnits = normalizedUnits.filter(function (item) {
            return item;
          });
          parsedUnitsCache[units] = normalizedUnits;
          return normalizedUnits;
        }

        /**
         * Parses a string as a quantity
         * @param {string} value - quantity as text
         * @throws if value is not a string
         * @returns {Qty|null} Parsed quantity or null if unrecognized
         */
        function globalParse(value) {
          if (!isString(value)) {
            throw new QtyError("Argument should be a string");
          }
          try {
            return this(value);
          } catch (e) {
            return null;
          }
        }

        /**
         * Tests if a value is a Qty instance
         *
         * @param {*} value - Value to test
         *
         * @returns {boolean} true if value is a Qty instance, false otherwise
         */
        function isQty(value) {
          return value instanceof Qty;
        }
        function Qty(initValue, initUnits) {
          assertValidConstructorArgs.apply(null, arguments);
          if (!isQty(this)) {
            return new Qty(initValue, initUnits);
          }
          this.scalar = null;
          this.baseScalar = null;
          this.signature = null;
          this._conversionCache = {};
          this.numerator = UNITY_ARRAY;
          this.denominator = UNITY_ARRAY;
          if (isDefinitionObject(initValue)) {
            this.scalar = initValue.scalar;
            this.numerator = initValue.numerator && initValue.numerator.length !== 0 ? initValue.numerator : UNITY_ARRAY;
            this.denominator = initValue.denominator && initValue.denominator.length !== 0 ? initValue.denominator : UNITY_ARRAY;
          } else if (initUnits) {
            parse.call(this, initUnits);
            this.scalar = initValue;
          } else {
            parse.call(this, initValue);
          }

          // math with temperatures is very limited
          if (this.denominator.join("*").indexOf("temp") >= 0) {
            throw new QtyError("Cannot divide with temperatures");
          }
          if (this.numerator.join("*").indexOf("temp") >= 0) {
            if (this.numerator.length > 1) {
              throw new QtyError("Cannot multiply by temperatures");
            }
            if (!compareArray(this.denominator, UNITY_ARRAY)) {
              throw new QtyError("Cannot divide with temperatures");
            }
          }
          this.initValue = initValue;
          updateBaseScalar.call(this);
          if (this.isTemperature() && this.baseScalar < 0) {
            throw new QtyError("Temperatures must not be less than absolute zero");
          }
        }
        Qty.prototype = {
          // Properly set up constructor
          constructor: Qty
        };

        /**
         * Asserts constructor arguments are valid
         *
         * @param {*} value - Value to test
         * @param {string} [units] - Optional units when value is passed as a number
         *
         * @returns {void}
         * @throws {QtyError} if constructor arguments are invalid
         */
        function assertValidConstructorArgs(value, units) {
          if (units) {
            if (!(isNumber(value) && isString(units))) {
              throw new QtyError("Only number accepted as initialization value " + "when units are explicitly provided");
            }
          } else {
            if (!(isString(value) || isNumber(value) || isQty(value) || isDefinitionObject(value))) {
              throw new QtyError("Only string, number or quantity accepted as " + "single initialization value");
            }
          }
        }

        /**
         * Tests if a value is a Qty definition object
         *
         * @param {*} value - Value to test
         *
         * @returns {boolean} true if value is a definition object, false otherwise
         */
        function isDefinitionObject(value) {
          return value && typeof value === "object" && value.hasOwnProperty("scalar");
        }
        function updateBaseScalar() {
          if (this.baseScalar) {
            return this.baseScalar;
          }
          if (this.isBase()) {
            this.baseScalar = this.scalar;
            this.signature = unitSignature.call(this);
          } else {
            var base = this.toBase();
            this.baseScalar = base.scalar;
            this.signature = base.signature;
          }
        }
        var KINDS = {
          "-312078": "elastance",
          "-312058": "resistance",
          "-312038": "inductance",
          "-152058": "potential",
          "-152040": "magnetism",
          "-152038": "magnetism",
          "-7997": "specific_volume",
          "-79": "snap",
          "-59": "jolt",
          "-39": "acceleration",
          "-38": "radiation",
          "-20": "frequency",
          "-19": "speed",
          "-18": "viscosity",
          "-17": "volumetric_flow",
          "-1": "wavenumber",
          "0": "unitless",
          "1": "length",
          "2": "area",
          "3": "volume",
          "20": "time",
          "400": "temperature",
          "7941": "yank",
          "7942": "power",
          "7959": "pressure",
          "7961": "force",
          "7962": "energy",
          "7979": "viscosity",
          "7981": "momentum",
          "7982": "angular_momentum",
          "7997": "density",
          "7998": "area_density",
          "8000": "mass",
          "152020": "radiation_exposure",
          "159999": "magnetism",
          "160000": "current",
          "160020": "charge",
          "312058": "conductance",
          "312078": "capacitance",
          "3199980": "activity",
          "3199997": "molar_concentration",
          "3200000": "substance",
          "63999998": "illuminance",
          "64000000": "luminous_power",
          "1280000000": "currency",
          "25599999980": "information_rate",
          "25600000000": "information",
          "511999999980": "angular_velocity",
          "512000000000": "angle"
        };

        /**
         * Returns the list of available well-known kinds of units, e.g.
         * "radiation" or "length".
         *
         * @returns {string[]} names of kinds of units
         */
        function getKinds() {
          return uniq(Object.keys(KINDS).map(function (knownSignature) {
            return KINDS[knownSignature];
          }));
        }
        Qty.prototype.kind = function () {
          return KINDS[this.signature.toString()];
        };
        assign(Qty.prototype, {
          isDegrees: function () {
            // signature may not have been calculated yet
            return (this.signature === null || this.signature === 400) && this.numerator.length === 1 && compareArray(this.denominator, UNITY_ARRAY) && (this.numerator[0].match(/<temp-[CFRK]>/) || this.numerator[0].match(/<(kelvin|celsius|rankine|fahrenheit)>/));
          },
          isTemperature: function () {
            return this.isDegrees() && this.numerator[0].match(/<temp-[CFRK]>/);
          }
        });
        function subtractTemperatures(lhs, rhs) {
          var lhsUnits = lhs.units();
          var rhsConverted = rhs.to(lhsUnits);
          var dstDegrees = Qty(getDegreeUnits(lhsUnits));
          return Qty({
            "scalar": lhs.scalar - rhsConverted.scalar,
            "numerator": dstDegrees.numerator,
            "denominator": dstDegrees.denominator
          });
        }
        function subtractTempDegrees(temp, deg) {
          var tempDegrees = deg.to(getDegreeUnits(temp.units()));
          return Qty({
            "scalar": temp.scalar - tempDegrees.scalar,
            "numerator": temp.numerator,
            "denominator": temp.denominator
          });
        }
        function addTempDegrees(temp, deg) {
          var tempDegrees = deg.to(getDegreeUnits(temp.units()));
          return Qty({
            "scalar": temp.scalar + tempDegrees.scalar,
            "numerator": temp.numerator,
            "denominator": temp.denominator
          });
        }
        function getDegreeUnits(units) {
          if (units === "tempK") {
            return "degK";
          } else if (units === "tempC") {
            return "degC";
          } else if (units === "tempF") {
            return "degF";
          } else if (units === "tempR") {
            return "degR";
          } else {
            throw new QtyError("Unknown type for temp conversion from: " + units);
          }
        }
        function toDegrees(src, dst) {
          var srcDegK = toDegK(src);
          var dstUnits = dst.units();
          var dstScalar;
          if (dstUnits === "degK") {
            dstScalar = srcDegK.scalar;
          } else if (dstUnits === "degC") {
            dstScalar = srcDegK.scalar;
          } else if (dstUnits === "degF") {
            dstScalar = srcDegK.scalar * 9 / 5;
          } else if (dstUnits === "degR") {
            dstScalar = srcDegK.scalar * 9 / 5;
          } else {
            throw new QtyError("Unknown type for degree conversion to: " + dstUnits);
          }
          return Qty({
            "scalar": dstScalar,
            "numerator": dst.numerator,
            "denominator": dst.denominator
          });
        }
        function toDegK(qty) {
          var units = qty.units();
          var q;
          if (units.match(/(deg)[CFRK]/)) {
            q = qty.baseScalar;
          } else if (units === "tempK") {
            q = qty.scalar;
          } else if (units === "tempC") {
            q = qty.scalar;
          } else if (units === "tempF") {
            q = qty.scalar * 5 / 9;
          } else if (units === "tempR") {
            q = qty.scalar * 5 / 9;
          } else {
            throw new QtyError("Unknown type for temp conversion from: " + units);
          }
          return Qty({
            "scalar": q,
            "numerator": ["<kelvin>"],
            "denominator": UNITY_ARRAY
          });
        }
        function toTemp(src, dst) {
          var dstUnits = dst.units();
          var dstScalar;
          if (dstUnits === "tempK") {
            dstScalar = src.baseScalar;
          } else if (dstUnits === "tempC") {
            dstScalar = src.baseScalar - 273.15;
          } else if (dstUnits === "tempF") {
            dstScalar = src.baseScalar * 9 / 5 - 459.67;
          } else if (dstUnits === "tempR") {
            dstScalar = src.baseScalar * 9 / 5;
          } else {
            throw new QtyError("Unknown type for temp conversion to: " + dstUnits);
          }
          return Qty({
            "scalar": dstScalar,
            "numerator": dst.numerator,
            "denominator": dst.denominator
          });
        }
        function toTempK(qty) {
          var units = qty.units();
          var q;
          if (units.match(/(deg)[CFRK]/)) {
            q = qty.baseScalar;
          } else if (units === "tempK") {
            q = qty.scalar;
          } else if (units === "tempC") {
            q = qty.scalar + 273.15;
          } else if (units === "tempF") {
            q = (qty.scalar + 459.67) * 5 / 9;
          } else if (units === "tempR") {
            q = qty.scalar * 5 / 9;
          } else {
            throw new QtyError("Unknown type for temp conversion from: " + units);
          }
          return Qty({
            "scalar": q,
            "numerator": ["<temp-K>"],
            "denominator": UNITY_ARRAY
          });
        }
        assign(Qty.prototype, {
          /**
           * Converts to other compatible units.
           * Instance's converted quantities are cached for faster subsequent calls.
           *
           * @param {(string|Qty)} other - Target units as string or retrieved from
           *                               other Qty instance (scalar is ignored)
           *
           * @returns {Qty} New converted Qty instance with target units
           *
           * @throws {QtyError} if target units are incompatible
           *
           * @example
           * var weight = Qty("25 kg");
           * weight.to("lb"); // => Qty("55.11556554621939 lbs");
           * weight.to(Qty("3 g")); // => Qty("25000 g"); // scalar of passed Qty is ignored
           */
          to: function (other) {
            var cached, target;
            if (other === undefined || other === null) {
              return this;
            }
            if (!isString(other)) {
              return this.to(other.units());
            }
            cached = this._conversionCache[other];
            if (cached) {
              return cached;
            }

            // Instantiating target to normalize units
            target = Qty(other);
            if (target.units() === this.units()) {
              return this;
            }
            if (!this.isCompatible(target)) {
              if (this.isInverse(target)) {
                target = this.inverse().to(other);
              } else {
                throwIncompatibleUnits(this.units(), target.units());
              }
            } else {
              if (target.isTemperature()) {
                target = toTemp(this, target);
              } else if (target.isDegrees()) {
                target = toDegrees(this, target);
              } else {
                var q = divSafe(this.baseScalar, target.baseScalar);
                target = Qty({
                  "scalar": q,
                  "numerator": target.numerator,
                  "denominator": target.denominator
                });
              }
            }
            this._conversionCache[other] = target;
            return target;
          },
          // convert to base SI units
          // results of the conversion are cached so subsequent calls to this will be fast
          toBase: function () {
            if (this.isBase()) {
              return this;
            }
            if (this.isTemperature()) {
              return toTempK(this);
            }
            var cached = baseUnitCache[this.units()];
            if (!cached) {
              cached = toBaseUnits(this.numerator, this.denominator);
              baseUnitCache[this.units()] = cached;
            }
            return cached.mul(this.scalar);
          },
          // Converts the unit back to a float if it is unitless.  Otherwise raises an exception
          toFloat: function () {
            if (this.isUnitless()) {
              return this.scalar;
            }
            throw new QtyError("Can't convert to Float unless unitless.  Use Unit#scalar");
          },
          /**
           * Returns the nearest multiple of quantity passed as
           * precision
           *
           * @param {(Qty|string|number)} precQuantity - Quantity, string formated
           *   quantity or number as expected precision
           *
           * @returns {Qty} Nearest multiple of precQuantity
           *
           * @example
           * Qty('5.5 ft').toPrec('2 ft'); // returns 6 ft
           * Qty('0.8 cu').toPrec('0.25 cu'); // returns 0.75 cu
           * Qty('6.3782 m').toPrec('cm'); // returns 6.38 m
           * Qty('1.146 MPa').toPrec('0.1 bar'); // returns 1.15 MPa
           *
           */
          toPrec: function (precQuantity) {
            if (isString(precQuantity)) {
              precQuantity = Qty(precQuantity);
            }
            if (isNumber(precQuantity)) {
              precQuantity = Qty(precQuantity + " " + this.units());
            }
            if (!this.isUnitless()) {
              precQuantity = precQuantity.to(this.units());
            } else if (!precQuantity.isUnitless()) {
              throwIncompatibleUnits(this.units(), precQuantity.units());
            }
            if (precQuantity.scalar === 0) {
              throw new QtyError("Divide by zero");
            }
            var precRoundedResult = mulSafe(Math.round(this.scalar / precQuantity.scalar), precQuantity.scalar);
            return Qty(precRoundedResult + this.units());
          }
        });

        /**
         * Configures and returns a fast function to convert
         * Number values from units to others.
         * Useful to efficiently convert large array of values
         * with same units into others with iterative methods.
         * Does not take care of rounding issues.
         *
         * @param {string} srcUnits Units of values to convert
         * @param {string} dstUnits Units to convert to
         *
         * @returns {Function} Converting function accepting Number value
         *   and returning converted value
         *
         * @throws "Incompatible units" if units are incompatible
         *
         * @example
         * // Converting large array of numbers with the same units
         * // into other units
         * var converter = Qty.swiftConverter("m/h", "ft/s");
         * var convertedSerie = largeSerie.map(converter);
         *
         */
        function swiftConverter(srcUnits, dstUnits) {
          var srcQty = Qty(srcUnits);
          var dstQty = Qty(dstUnits);
          if (srcQty.eq(dstQty)) {
            return identity;
          }
          var convert;
          if (!srcQty.isTemperature()) {
            convert = function (value) {
              return value * srcQty.baseScalar / dstQty.baseScalar;
            };
          } else {
            convert = function (value) {
              // TODO Not optimized
              return srcQty.mul(value).to(dstQty).scalar;
            };
          }
          return function converter(value) {
            var i, length, result;
            if (!Array.isArray(value)) {
              return convert(value);
            } else {
              length = value.length;
              result = [];
              for (i = 0; i < length; i++) {
                result.push(convert(value[i]));
              }
              return result;
            }
          };
        }
        var baseUnitCache = {};
        function toBaseUnits(numerator, denominator) {
          var num = [];
          var den = [];
          var q = 1;
          var unit;
          for (var i = 0; i < numerator.length; i++) {
            unit = numerator[i];
            if (PREFIX_VALUES[unit]) {
              // workaround to fix
              // 0.1 * 0.1 => 0.010000000000000002
              q = mulSafe(q, PREFIX_VALUES[unit]);
            } else {
              if (UNIT_VALUES[unit]) {
                q *= UNIT_VALUES[unit].scalar;
                if (UNIT_VALUES[unit].numerator) {
                  num.push(UNIT_VALUES[unit].numerator);
                }
                if (UNIT_VALUES[unit].denominator) {
                  den.push(UNIT_VALUES[unit].denominator);
                }
              }
            }
          }
          for (var j = 0; j < denominator.length; j++) {
            unit = denominator[j];
            if (PREFIX_VALUES[unit]) {
              q /= PREFIX_VALUES[unit];
            } else {
              if (UNIT_VALUES[unit]) {
                q /= UNIT_VALUES[unit].scalar;
                if (UNIT_VALUES[unit].numerator) {
                  den.push(UNIT_VALUES[unit].numerator);
                }
                if (UNIT_VALUES[unit].denominator) {
                  num.push(UNIT_VALUES[unit].denominator);
                }
              }
            }
          }

          // Flatten
          num = num.reduce(function (a, b) {
            return a.concat(b);
          }, []);
          den = den.reduce(function (a, b) {
            return a.concat(b);
          }, []);
          return Qty({
            "scalar": q,
            "numerator": num,
            "denominator": den
          });
        }
        Qty.parse = globalParse;
        Qty.getUnits = getUnits;
        Qty.getAliases = getAliases;
        Qty.mulSafe = mulSafe;
        Qty.divSafe = divSafe;
        Qty.getKinds = getKinds;
        Qty.swiftConverter = swiftConverter;
        Qty.Error = QtyError;
        assign(Qty.prototype, {
          // Returns new instance with units of this
          add: function (other) {
            if (isString(other)) {
              other = Qty(other);
            }
            if (!this.isCompatible(other)) {
              throwIncompatibleUnits(this.units(), other.units());
            }
            if (this.isTemperature() && other.isTemperature()) {
              throw new QtyError("Cannot add two temperatures");
            } else if (this.isTemperature()) {
              return addTempDegrees(this, other);
            } else if (other.isTemperature()) {
              return addTempDegrees(other, this);
            }
            return Qty({
              "scalar": this.scalar + other.to(this).scalar,
              "numerator": this.numerator,
              "denominator": this.denominator
            });
          },
          sub: function (other) {
            if (isString(other)) {
              other = Qty(other);
            }
            if (!this.isCompatible(other)) {
              throwIncompatibleUnits(this.units(), other.units());
            }
            if (this.isTemperature() && other.isTemperature()) {
              return subtractTemperatures(this, other);
            } else if (this.isTemperature()) {
              return subtractTempDegrees(this, other);
            } else if (other.isTemperature()) {
              throw new QtyError("Cannot subtract a temperature from a differential degree unit");
            }
            return Qty({
              "scalar": this.scalar - other.to(this).scalar,
              "numerator": this.numerator,
              "denominator": this.denominator
            });
          },
          mul: function (other) {
            if (isNumber(other)) {
              return Qty({
                "scalar": mulSafe(this.scalar, other),
                "numerator": this.numerator,
                "denominator": this.denominator
              });
            } else if (isString(other)) {
              other = Qty(other);
            }
            if ((this.isTemperature() || other.isTemperature()) && !(this.isUnitless() || other.isUnitless())) {
              throw new QtyError("Cannot multiply by temperatures");
            }

            // Quantities should be multiplied with same units if compatible, with base units else
            var op1 = this;
            var op2 = other;

            // so as not to confuse results, multiplication and division between temperature degrees will maintain original unit info in num/den
            // multiplication and division between deg[CFRK] can never factor each other out, only themselves: "degK*degC/degC^2" == "degK/degC"
            if (op1.isCompatible(op2) && op1.signature !== 400) {
              op2 = op2.to(op1);
            }
            var numdenscale = cleanTerms(op1.numerator, op1.denominator, op2.numerator, op2.denominator);
            return Qty({
              "scalar": mulSafe(op1.scalar, op2.scalar, numdenscale[2]),
              "numerator": numdenscale[0],
              "denominator": numdenscale[1]
            });
          },
          div: function (other) {
            if (isNumber(other)) {
              if (other === 0) {
                throw new QtyError("Divide by zero");
              }
              return Qty({
                "scalar": this.scalar / other,
                "numerator": this.numerator,
                "denominator": this.denominator
              });
            } else if (isString(other)) {
              other = Qty(other);
            }
            if (other.scalar === 0) {
              throw new QtyError("Divide by zero");
            }
            if (other.isTemperature()) {
              throw new QtyError("Cannot divide with temperatures");
            } else if (this.isTemperature() && !other.isUnitless()) {
              throw new QtyError("Cannot divide with temperatures");
            }

            // Quantities should be multiplied with same units if compatible, with base units else
            var op1 = this;
            var op2 = other;

            // so as not to confuse results, multiplication and division between temperature degrees will maintain original unit info in num/den
            // multiplication and division between deg[CFRK] can never factor each other out, only themselves: "degK*degC/degC^2" == "degK/degC"
            if (op1.isCompatible(op2) && op1.signature !== 400) {
              op2 = op2.to(op1);
            }
            var numdenscale = cleanTerms(op1.numerator, op1.denominator, op2.denominator, op2.numerator);
            return Qty({
              "scalar": mulSafe(op1.scalar, numdenscale[2]) / op2.scalar,
              "numerator": numdenscale[0],
              "denominator": numdenscale[1]
            });
          },
          // Returns a Qty that is the inverse of this Qty,
          inverse: function () {
            if (this.isTemperature()) {
              throw new QtyError("Cannot divide with temperatures");
            }
            if (this.scalar === 0) {
              throw new QtyError("Divide by zero");
            }
            return Qty({
              "scalar": 1 / this.scalar,
              "numerator": this.denominator,
              "denominator": this.numerator
            });
          }
        });
        function cleanTerms(num1, den1, num2, den2) {
          function notUnity(val) {
            return val !== UNITY;
          }
          num1 = num1.filter(notUnity);
          num2 = num2.filter(notUnity);
          den1 = den1.filter(notUnity);
          den2 = den2.filter(notUnity);
          var combined = {};
          function combineTerms(terms, direction) {
            var k;
            var prefix;
            var prefixValue;
            for (var i = 0; i < terms.length; i++) {
              if (PREFIX_VALUES[terms[i]]) {
                k = terms[i + 1];
                prefix = terms[i];
                prefixValue = PREFIX_VALUES[prefix];
                i++;
              } else {
                k = terms[i];
                prefix = null;
                prefixValue = 1;
              }
              if (k && k !== UNITY) {
                if (combined[k]) {
                  combined[k][0] += direction;
                  var combinedPrefixValue = combined[k][2] ? PREFIX_VALUES[combined[k][2]] : 1;
                  combined[k][direction === 1 ? 3 : 4] *= divSafe(prefixValue, combinedPrefixValue);
                } else {
                  combined[k] = [direction, k, prefix, 1, 1];
                }
              }
            }
          }
          combineTerms(num1, 1);
          combineTerms(den1, -1);
          combineTerms(num2, 1);
          combineTerms(den2, -1);
          var num = [];
          var den = [];
          var scale = 1;
          for (var prop in combined) {
            if (combined.hasOwnProperty(prop)) {
              var item = combined[prop];
              var n;
              if (item[0] > 0) {
                for (n = 0; n < item[0]; n++) {
                  num.push(item[2] === null ? item[1] : [item[2], item[1]]);
                }
              } else if (item[0] < 0) {
                for (n = 0; n < -item[0]; n++) {
                  den.push(item[2] === null ? item[1] : [item[2], item[1]]);
                }
              }
              scale *= divSafe(item[3], item[4]);
            }
          }
          if (num.length === 0) {
            num = UNITY_ARRAY;
          }
          if (den.length === 0) {
            den = UNITY_ARRAY;
          }

          // Flatten
          num = num.reduce(function (a, b) {
            return a.concat(b);
          }, []);
          den = den.reduce(function (a, b) {
            return a.concat(b);
          }, []);
          return [num, den, scale];
        }
        assign(Qty.prototype, {
          eq: function (other) {
            return this.compareTo(other) === 0;
          },
          lt: function (other) {
            return this.compareTo(other) === -1;
          },
          lte: function (other) {
            return this.eq(other) || this.lt(other);
          },
          gt: function (other) {
            return this.compareTo(other) === 1;
          },
          gte: function (other) {
            return this.eq(other) || this.gt(other);
          },
          // Compare two Qty objects. Throws an exception if they are not of compatible types.
          // Comparisons are done based on the value of the quantity in base SI units.
          //
          // NOTE: We cannot compare inverses as that breaks the general compareTo contract:
          //   if a.compareTo(b) < 0 then b.compareTo(a) > 0
          //   if a.compareTo(b) == 0 then b.compareTo(a) == 0
          //
          //   Since "10S" == ".1ohm" (10 > .1) and "10ohm" == ".1S" (10 > .1)
          //     Qty("10S").inverse().compareTo("10ohm") == -1
          //     Qty("10ohm").inverse().compareTo("10S") == -1
          //
          //   If including inverses in the sort is needed, I suggest writing: Qty.sort(qtyArray,units)
          compareTo: function (other) {
            if (isString(other)) {
              return this.compareTo(Qty(other));
            }
            if (!this.isCompatible(other)) {
              throwIncompatibleUnits(this.units(), other.units());
            }
            if (this.baseScalar < other.baseScalar) {
              return -1;
            } else if (this.baseScalar === other.baseScalar) {
              return 0;
            } else if (this.baseScalar > other.baseScalar) {
              return 1;
            }
          },
          // Return true if quantities and units match
          // Unit("100 cm").same(Unit("100 cm"))  # => true
          // Unit("100 cm").same(Unit("1 m"))     # => false
          same: function (other) {
            return this.scalar === other.scalar && this.units() === other.units();
          }
        });
        assign(Qty.prototype, {
          // returns true if no associated units
          // false, even if the units are "unitless" like 'radians, each, etc'
          isUnitless: function () {
            return [this.numerator, this.denominator].every(function (item) {
              return compareArray(item, UNITY_ARRAY);
            });
          },
          /*
          check to see if units are compatible, but not the scalar part
          this check is done by comparing signatures for performance reasons
          if passed a string, it will create a unit object with the string and then do the comparison
          this permits a syntax like:
          unit =~ "mm"
          if you want to do a regexp on the unit string do this ...
          unit.units =~ /regexp/
          */
          isCompatible: function (other) {
            if (isString(other)) {
              return this.isCompatible(Qty(other));
            }
            if (!isQty(other)) {
              return false;
            }
            if (other.signature !== undefined) {
              return this.signature === other.signature;
            } else {
              return false;
            }
          },
          /*
          check to see if units are inverse of each other, but not the scalar part
          this check is done by comparing signatures for performance reasons
          if passed a string, it will create a unit object with the string and then do the comparison
          this permits a syntax like:
          unit =~ "mm"
          if you want to do a regexp on the unit string do this ...
          unit.units =~ /regexp/
          */
          isInverse: function (other) {
            return this.inverse().isCompatible(other);
          },
          // Returns 'true' if the Unit is represented in base units
          isBase: function () {
            if (this._isBase !== undefined) {
              return this._isBase;
            }
            if (this.isDegrees() && this.numerator[0].match(/<(kelvin|temp-K)>/)) {
              this._isBase = true;
              return this._isBase;
            }
            this.numerator.concat(this.denominator).forEach(function (item) {
              if (item !== UNITY && BASE_UNITS.indexOf(item) === -1) {
                this._isBase = false;
              }
            }, this);
            if (this._isBase === false) {
              return this._isBase;
            }
            this._isBase = true;
            return this._isBase;
          }
        });
        function NestedMap() {}
        NestedMap.prototype.get = function (keys) {
          // Allows to pass key1, key2, ... instead of [key1, key2, ...]
          if (arguments.length > 1) {
            // Slower with Firefox but faster with Chrome than
            // Array.prototype.slice.call(arguments)
            // See http://jsperf.com/array-apply-versus-array-prototype-slice-call
            keys = Array.apply(null, arguments);
          }
          return keys.reduce(function (map, key, index) {
            if (map) {
              var childMap = map[key];
              if (index === keys.length - 1) {
                return childMap ? childMap.data : undefined;
              } else {
                return childMap;
              }
            }
          }, this);
        };
        NestedMap.prototype.set = function (keys, value) {
          if (arguments.length > 2) {
            keys = Array.prototype.slice.call(arguments, 0, -1);
            value = arguments[arguments.length - 1];
          }
          return keys.reduce(function (map, key, index) {
            var childMap = map[key];
            if (childMap === undefined) {
              childMap = map[key] = {};
            }
            if (index === keys.length - 1) {
              childMap.data = value;
              return value;
            } else {
              return childMap;
            }
          }, this);
        };

        /**
         * Default formatter
         *
         * @param {number} scalar - scalar value
         * @param {string} units - units as string
         *
         * @returns {string} formatted result
         */
        function defaultFormatter(scalar, units) {
          return (scalar + " " + units).trim();
        }

        /**
         *
         * Configurable Qty default formatter
         *
         * @type {function}
         *
         * @param {number} scalar
         * @param {string} units
         *
         * @returns {string} formatted result
         */
        Qty.formatter = defaultFormatter;
        assign(Qty.prototype, {
          // returns the 'unit' part of the Unit object without the scalar
          units: function () {
            if (this._units !== undefined) {
              return this._units;
            }
            var numIsUnity = compareArray(this.numerator, UNITY_ARRAY);
            var denIsUnity = compareArray(this.denominator, UNITY_ARRAY);
            if (numIsUnity && denIsUnity) {
              this._units = "";
              return this._units;
            }
            var numUnits = stringifyUnits(this.numerator);
            var denUnits = stringifyUnits(this.denominator);
            this._units = numUnits + (denIsUnity ? "" : "/" + denUnits);
            return this._units;
          },
          /**
           * Stringifies the quantity
           * Deprecation notice: only units parameter is supported.
           *
           * @param {(number|string|Qty)} targetUnitsOrMaxDecimalsOrPrec -
           *                              target units if string,
           *                              max number of decimals if number,
           *                              passed to #toPrec before converting if Qty
           *
           * @param {number=} maxDecimals - Maximum number of decimals of
           *                                formatted output
           *
           * @returns {string} reparseable quantity as string
           */
          toString: function (targetUnitsOrMaxDecimalsOrPrec, maxDecimals) {
            var targetUnits;
            if (isNumber(targetUnitsOrMaxDecimalsOrPrec)) {
              targetUnits = this.units();
              maxDecimals = targetUnitsOrMaxDecimalsOrPrec;
            } else if (isString(targetUnitsOrMaxDecimalsOrPrec)) {
              targetUnits = targetUnitsOrMaxDecimalsOrPrec;
            } else if (isQty(targetUnitsOrMaxDecimalsOrPrec)) {
              return this.toPrec(targetUnitsOrMaxDecimalsOrPrec).toString(maxDecimals);
            }
            var out = this.to(targetUnits);
            var outScalar = maxDecimals !== undefined ? round(out.scalar, maxDecimals) : out.scalar;
            out = (outScalar + " " + out.units()).trim();
            return out;
          },
          /**
           * Format the quantity according to optional passed target units
           * and formatter
           *
           * @param {string} [targetUnits=current units] -
           *                 optional units to convert to before formatting
           *
           * @param {function} [formatter=Qty.formatter] -
           *                   delegates formatting to formatter callback.
           *                   formatter is called back with two parameters (scalar, units)
           *                   and should return formatted result.
           *                   If unspecified, formatting is delegated to default formatter
           *                   set to Qty.formatter
           *
           * @example
           * var roundingAndLocalizingFormatter = function(scalar, units) {
           *   // localize or limit scalar to n max decimals for instance
           *   // return formatted result
           * };
           * var qty = Qty('1.1234 m');
           * qty.format(); // same units, default formatter => "1.234 m"
           * qty.format("cm"); // converted to "cm", default formatter => "123.45 cm"
           * qty.format(roundingAndLocalizingFormatter); // same units, custom formatter => "1,2 m"
           * qty.format("cm", roundingAndLocalizingFormatter); // convert to "cm", custom formatter => "123,4 cm"
           *
           * @returns {string} quantity as string
           */
          format: function (targetUnits, formatter) {
            if (arguments.length === 1) {
              if (typeof targetUnits === "function") {
                formatter = targetUnits;
                targetUnits = undefined;
              }
            }
            formatter = formatter || Qty.formatter;
            var targetQty = this.to(targetUnits);
            return formatter.call(this, targetQty.scalar, targetQty.units());
          }
        });
        var stringifiedUnitsCache = new NestedMap();
        /**
         * Returns a string representing a normalized unit array
         *
         * @param {string[]} units Normalized unit array
         * @returns {string} String representing passed normalized unit array and
         *   suitable for output
         *
         */
        function stringifyUnits(units) {
          var stringified = stringifiedUnitsCache.get(units);
          if (stringified) {
            return stringified;
          }
          var isUnity = compareArray(units, UNITY_ARRAY);
          if (isUnity) {
            stringified = "1";
          } else {
            stringified = simplify(getOutputNames(units)).join("*");
          }

          // Cache result
          stringifiedUnitsCache.set(units, stringified);
          return stringified;
        }
        function getOutputNames(units) {
          var unitNames = [],
            token,
            tokenNext;
          for (var i = 0; i < units.length; i++) {
            token = units[i];
            tokenNext = units[i + 1];
            if (PREFIX_VALUES[token]) {
              unitNames.push(OUTPUT_MAP[token] + OUTPUT_MAP[tokenNext]);
              i++;
            } else {
              unitNames.push(OUTPUT_MAP[token]);
            }
          }
          return unitNames;
        }
        function simplify(units) {
          // this turns ['s','m','s'] into ['s2','m']

          var unitCounts = units.reduce(function (acc, unit) {
            var unitCounter = acc[unit];
            if (!unitCounter) {
              acc.push(unitCounter = acc[unit] = [unit, 0]);
            }
            unitCounter[1]++;
            return acc;
          }, []);
          return unitCounts.map(function (unitCount) {
            return unitCount[0] + (unitCount[1] > 1 ? unitCount[1] : "");
          });
        }
        Qty.version = "1.8.0";
        return Qty;
      });
    })(quantities$1);
    var quantitiesExports = quantities$1.exports;
    var Qty = /*@__PURE__*/getDefaultExportFromCjs(quantitiesExports);

    function convertUnit(array, fromUnit, toUnit) {
      fromUnit = normalize$3(fromUnit);
      toUnit = normalize$3(toUnit);
      if (fromUnit === toUnit) return array;
      const convert = Qty.swiftConverter(fromUnit, toUnit); // Configures converter
      //@ts-expect-error convert does not allowed typed array but it works
      return convert(array);
    }
    function normalize$3(unit) {
      unit = unit.replaceAll('°C', 'tempC');
      unit = unit.replaceAll('°F', 'tempF');
      unit = unit.replaceAll(/(^|\W)K(\W|$)/g, '$1tempK$2');
      return unit;
    }

    const testRegExp = /^\/((?:\\\/|[^/])+)\/([gimuy]{0,5})?$/;
    function ensureRegexp(string) {
      if (typeof string !== 'string') return string;
      const parts = testRegExp.exec(string);
      if (parts) {
        try {
          return new RegExp(parts[1], parts[2]);
        } catch {
          return stringToRegexp(string);
        }
      } else {
        return stringToRegexp(string);
      }
    }
    function stringToRegexp(string, flags = 'i') {
      return new RegExp(string.replaceAll(/[$()*+.?[\\\]^{|}]/g, match => `\\${match}`), flags);
    }

    function getConvertedVariable(variable, newUnits) {
      const data = variable.units !== undefined && variable.units !== newUnits // would be nice if convertUnit would allow typedArray
      ? convertUnit(Array.from(variable.data), variable.units, newUnits) : variable.data;
      return {
        units: newUnits,
        label: variable.label.replace(`[${variable.units || ''}]`, `[${newUnits}]`),
        data: data || [],
        min: data ? xMinValue(data) : undefined,
        max: data ? xMaxValue(data) : undefined,
        isMonotonic: xIsMonotonic(data)
      };
    }

    /* eslint-disable @typescript-eslint/no-dynamic-delete */
    /**
     * Retrieve the spectrum with only X/Y data that match all the selectors
     * If more than one variable match the selector the 'x' or 'y' variable will be
     * taken
     * @param spectra
     * @param selector
     */
    function getXYSpectra(spectra = [], selector = {}) {
      const selectedSpectra = [];
      if (spectra.length === 0) return selectedSpectra;
      const {
        variables,
        units,
        labels,
        meta,
        index
      } = selector;
      let {
        dataType,
        title,
        xUnits,
        yUnits,
        xVariable = 'x',
        yVariable = 'y',
        xLabel,
        yLabel
      } = selector;
      if (index !== undefined) {
        return [spectra[index]];
      }
      if (dataType) {
        dataType = ensureRegexp(dataType);
      }
      if (title) {
        title = ensureRegexp(title);
      }
      if (units && !xUnits && !yUnits) [yUnits, xUnits] = units.split(/\s*vs\s*/);
      if (labels && !xLabel && !yLabel) {
        [yLabel, xLabel] = labels.split(/\s*vs\s*/);
      }
      if (variables) {
        const parts = variables.split(/\s*vs\s*/);
        if (parts.length === 2) {
          xVariable = parts[1];
          yVariable = parts[0];
        }
      }
      if (xLabel) xLabel = ensureRegexp(xLabel);
      if (yLabel) yLabel = ensureRegexp(yLabel);
      for (const spectrum of spectra) {
        const variableNames = Object.keys(spectrum.variables);
        if (!(variableNames.length > 1)) continue;
        // we filter on general spectrum information
        if (dataType && (!spectrum.dataType || !dataType.exec(spectrum.dataType))) {
          continue;
        }
        if (title && (!spectrum.title || !title.exec(spectrum.title))) {
          continue;
        }
        if (meta && typeof meta === 'object') {
          if (!spectrum.meta) continue;
          for (const key in spectrum.meta) {
            if (!spectrum.meta[key]) continue;
            const value = ensureRegexp(spectrum.meta[key]);
            if (!value.exec(spectrum.meta[key])) continue;
          }
        }
        const x = getPossibleVariable(spectrum.variables, {
          units: xUnits,
          label: xLabel,
          variableName: xVariable
        });
        const y = getPossibleVariable(spectrum.variables, {
          units: yUnits,
          label: yLabel,
          variableName: yVariable
        });
        if (x && y) {
          // should we reverse the x axis?
          if (x.data[0] > x.data.at(-1)) {
            x.data = x.data.slice().reverse();
            y.data = y.data.slice().reverse();
          }
          selectedSpectra.push({
            title: spectrum.title,
            dataType: spectrum.dataType,
            meta: spectrum.meta,
            variables: {
              x,
              y
            },
            id: spectrum.id
          });
        }
      }
      return selectedSpectra;
    }
    function getPossibleVariable(variables, selector = {}) {
      const {
        units,
        label,
        variableName
      } = selector;
      const possible = {
        ...variables
      };
      let key;
      if (units !== undefined) {
        for (key in possible) {
          const variable = variables[key];
          let convertibleUnits = true;
          try {
            convertUnit(1, variable?.units || '', units);
          } catch {
            convertibleUnits = false;
          }
          if (convertibleUnits && variable) {
            possible[key] = getConvertedVariable(variable, units);
          } else {
            delete possible[key];
          }
        }
      }
      if (label !== undefined) {
        const regexpLabel = ensureRegexp(label);
        for (key in possible) {
          if (!regexpLabel.exec(variables[key]?.label ?? '')) {
            delete possible[key];
          }
        }
      }
      if (variableName !== undefined) {
        if (possible[variableName]) return possible[variableName];
        const upper = variableName.toUpperCase();
        if (Object.hasOwn(possible, upper)) {
          return possible[upper];
        }
        const lower = variableName.toLowerCase();
        if (Object.hasOwn(possible, lower)) {
          return possible[lower];
        }
      }
      const possibleFiltered = Object.values(possible).filter(val => val !== undefined);
      if (possibleFiltered.length > 0) {
        return possibleFiltered[0];
      }
    }

    /**
     * Retrieve the spectrum with only X/Y data that match all the selectors
     * If more than one variable match the selector the 'x' or 'y' variable will be
     * taken
     * @param spectra
     * @param selector
     */
    function getXYSpectrum(spectra = [], selector = {}) {
      const selectedSpectra = getXYSpectra(spectra, selector);
      if (selectedSpectra.length === 0) return undefined;
      return selectedSpectra[0];
    }

    /**
     * Class allowing to store and manipulate an analysis.
     * An analysis may contain one or more spectra that can be selected
     * based on their units
     */
    class Analysis {
      id;
      label;
      spectrumCallback;
      spectra;
      cache;
      constructor(options = {}) {
        this.id = options.id || Math.random().toString(36).slice(2, 10);
        this.label = options.label || this.id;
        this.spectrumCallback = options.spectrumCallback;
        this.spectra = [];
        this.cache = {
          spectrum: {},
          spectra: {}
        };
      }
      clone({
        filter = {}
      }) {
        const {
          ids
        } = filter;
        const analysis = new Analysis();
        analysis.id = this.id;
        analysis.label = this.label;
        analysis.spectrumCallback = this.spectrumCallback;
        analysis.spectra = this.spectra.filter(spectrum => {
          //@ts-expect-error spectrum.id is not expected to be undefined at this level
          return !ids || ids.includes(spectrum.id);
        });
        return analysis;
      }
      toJSON() {
        // TODO this is likely not the most optimized way to remove typedArray
        // if data are small seems still reasonable
        return {
          id: this.id,
          label: this.label,
          spectra: JSON.parse(stringify$1(this.spectra))
        };
      }
      static fromJSON(json) {
        const analysis = new Analysis();
        analysis.id = json.id;
        analysis.label = json.label;
        analysis.spectra = json.spectra;
        return analysis;
      }
      /**
       * Add a spectrum in the internal spectra variable
       * @param variables
       * @param options
       */
      pushSpectrum(variables, options = {}) {
        this.spectra.push(standardizeData(variables, options, {
          spectrumCallback: this.spectrumCallback
        }));
        this.cache = {
          spectrum: {},
          spectra: {}
        };
      }
      /**
       * Retrieve a MeasurementXY based on x/y units
       * @param selector
       */
      getXYSpectrum(selector = {}) {
        const id = JSON.stringify(selector);
        if (!this.cache.spectrum[id]) {
          this.cache.spectrum[id] = getXYSpectrum(this.spectra, selector);
        }
        return this.cache.spectrum[id];
      }
      /**
       * Retrieve spectra matching selector
       * @param selector
       */
      getXYSpectra(selector = {}) {
        const id = JSON.stringify(selector);
        if (!this.cache.spectra[id]) {
          this.cache.spectra[id] = getXYSpectra(this.spectra, selector);
        }
        return this.cache.spectra[id];
      }
      /**
       * Retrieve a xy object
       * @param selector.units - Units separated by vs like for example "g vs °C"
       * @param selector.xUnits - if undefined takes the first variable
       * @param selector.yUnits - if undefined takes the second variable
       * @param selector
       */
      getXY(selector = {}) {
        const spectrum = this.getXYSpectrum(selector);
        if (!spectrum) return undefined;
        return {
          x: spectrum.variables.x.data,
          y: spectrum.variables.y.data
        };
      }
      /**
       * Return the data object for specific x/y units with possibly some
       * normalization options
       * @param options.selector.xUnits - // if undefined takes the first variable
       * @param options.selector.yUnits - // if undefined takes the second variable
       * @param options
       */
      getNormalizedSpectrum(options = {}) {
        const {
          normalization,
          selector
        } = options;
        const spectrum = this.getXYSpectrum(selector);
        if (!spectrum) return undefined;
        return getNormalizedSpectrum(spectrum, normalization);
      }
      /**
       * @param options
       */
      getNormalizedSpectra(options = {}) {
        const {
          normalization,
          selector
        } = options;
        const spectra = this.getXYSpectra(selector);
        if (spectra.length === 0) return [];
        const normalizedSpectra = [];
        for (const spectrum of spectra) {
          normalizedSpectra.push(getNormalizedSpectrum(spectrum, normalization));
        }
        return normalizedSpectra;
      }
      /**
       * Returns the first spectrum. This method could be improved in the future
       * @returns
       */
      getSpectrum() {
        return this.spectra[0];
      }
      /**
       * Returns the xLabel
       * @param selector.xUnits - // if undefined takes the first variable
       * @param selector.yUnits - // if undefined takes the second variable
       * @param selector
       */
      getXLabel(selector) {
        return this.getXYSpectrum(selector)?.variables.x.label;
      }
      /**
       * Returns the yLabel
       * @param selector.xUnits - // if undefined takes the first variable
       * @param selector.yUnits - // if undefined takes the second variable
       * @param selector
       */
      getYLabel(selector) {
        return this.getXYSpectrum(selector)?.variables.y.label;
      }
    }
    /**
     * Internal function that ensure the order of x / y array
     * @param variables
     * @param options
     * @param analysisOptions
     */
    function standardizeData(variables, options, analysisOptions) {
      const {
        meta = {},
        dataType = '',
        title = '',
        id = Math.random().toString(36).replace('0.', '')
      } = options;
      const {
        spectrumCallback
      } = analysisOptions;
      if (spectrumCallback) {
        spectrumCallback(variables);
      }
      const xVariable = variables.x;
      const yVariable = variables.y;
      if (!xVariable || !yVariable) {
        throw new Error('A spectrum must contain at least x and y variables');
      }
      if (!isAnyArray$1(xVariable.data) || !isAnyArray$1(yVariable.data)) {
        throw new Error('x and y variables must contain an array data');
      }
      const x = xVariable.data;
      const reverse = x && x.length > 1 && x[0] > x.at(-1);
      for (const [key, variable] of Object.entries(variables)) {
        if (reverse) variable.data = variable.data.slice().reverse();
        variable.label = variable.label || key;
        if (variable.label.match(/^.*[([](?<units>.*)[)\]].*$/)) {
          const units = variable.label.replace(/^.*[([](?<units>.*)[)\]].*$/, '$<units>');
          if (!variable.units || variable.units === units) {
            variable.units = units;
            variable.label = variable.label.replace(/[([].*[)\]]/, '').trim();
          }
        }
        variable.min = xMinValue(variable.data);
        variable.max = xMaxValue(variable.data);
        variable.isMonotonic = xIsMonotonic(variable.data);
      }
      return {
        variables,
        title,
        dataType,
        meta,
        id
      };
    }

    function appendDistinctParameter(values, key, value) {
      if (!values[key]) {
        values[key] = {
          key,
          values: [],
          count: 0
        };
      }
      if (!values[key].values.includes(value)) {
        values[key].values.push(value);
      }
      values[key].count++;
    }

    function appendDistinctValue(values, key) {
      if (!values[key]) {
        values[key] = {
          key,
          count: 0
        };
      }
      values[key].count++;
    }

    class AnalysesManager {
      analyses;
      constructor() {
        this.analyses = [];
      }
      static fromJSON(json) {
        const analysesManager = new AnalysesManager();
        for (const analysis of json.analyses) {
          analysesManager.analyses.push(Analysis.fromJSON(analysis));
        }
        return analysesManager;
      }
      addAnalysis(analysis) {
        const index = this.getAnalysisIndex(analysis.id);
        if (index === undefined) {
          this.analyses.push(analysis);
        } else {
          this.analyses[index] = analysis;
        }
      }
      /**
       *
       * @param options
       * @returns
       */
      getAnalyses(options = {}) {
        const {
          ids
        } = options;
        const analyses = [];
        const processedAnalysisIds = new Set();
        for (const analysis of this.analyses) {
          if (!ids || ids.includes(analysis.id)) {
            analyses.push(analysis);
            processedAnalysisIds.add(analysis.id);
            continue;
          }
          for (const spectrum of analysis.spectra) {
            if (spectrum.id && ids.includes(spectrum.id) && !processedAnalysisIds.has(analysis.id)) {
              analyses.push(analysis.clone({
                filter: {
                  ids
                }
              }));
              processedAnalysisIds.add(analysis.id);
            }
          }
        }
        return analyses;
      }
      getSpectra() {
        const spectra = [];
        for (const analysis of this.analyses) {
          spectra.push(...analysis.spectra);
        }
        return spectra;
      }
      getAnalysisBySpectrumId(id) {
        for (const analysis of this.analyses) {
          for (const spectrum of analysis.spectra) {
            if (spectrum.id === id) return analysis;
          }
        }
        return undefined;
      }
      getSpectrumById(id) {
        for (const analysis of this.analyses) {
          for (const spectrum of analysis.spectra) {
            if (spectrum.id === id) return spectrum;
          }
        }
        return undefined;
      }
      /**
       * Get an array of objects (key + count) of all the titles
       */
      getDistinctTitles() {
        const values = {};
        for (const spectrum of this.getSpectra()) {
          if (spectrum.title) {
            appendDistinctValue(values, spectrum.title);
          }
        }
        return Object.keys(values).map(key => values[key]);
      }
      /**
       * Get an array of objects (key + count) of all the units
       */
      getDistinctUnits() {
        const values = {};
        for (const spectrum of this.getSpectra()) {
          if (spectrum.variables) {
            for (const [, variable] of Object.entries(spectrum.variables)) {
              if (variable.units) {
                appendDistinctValue(values, variable.units);
              }
            }
          }
        }
        return Object.keys(values).map(key => values[key]);
      }
      /**
       * Get an array of objects (key + unit + label + count) of all the units
       */
      getDistinctLabelUnits() {
        const values = {};
        for (const spectrum of this.getSpectra()) {
          if (spectrum.variables) {
            for (const [, variable] of Object.entries(spectrum.variables)) {
              const {
                label,
                units
              } = normalizeLabelUnits(variable.label, variable.units);
              const key = label + (units ? ` (${units})` : '');
              if (key) {
                if (!values[key]) {
                  values[key] = {
                    key,
                    units,
                    label,
                    count: 0
                  };
                }
                values[key].count++;
              }
            }
          }
        }
        return Object.keys(values).map(key => values[key]);
      }
      /**
       * Get an array of objects (key + count) of all the labels
       */
      getDistinctLabels() {
        const values = {};
        for (const spectrum of this.getSpectra()) {
          if (spectrum.variables) {
            for (const [, variable] of Object.entries(spectrum.variables)) {
              appendDistinctValue(values, variable.label.replace(/\s+[([].*$/, ''));
            }
          }
        }
        return Object.keys(values).map(key => values[key]);
      }
      /**
       * Get an array of objects (key + count) of all the dataTypes
       */
      getDistinctDataTypes() {
        const values = {};
        for (const spectrum of this.getSpectra()) {
          if (spectrum.dataType) {
            appendDistinctValue(values, spectrum.dataType);
          }
        }
        return Object.keys(values).map(key => values[key]);
      }
      /**
       * Get an array of objects (key + count) of all the meta
       */
      getDistinctMeta() {
        const values = {};
        for (const spectrum of this.getSpectra()) {
          if (spectrum.meta) {
            for (const key in spectrum.meta) {
              appendDistinctParameter(values, key, spectrum.meta[key]);
            }
          }
        }
        return Object.keys(values).map(key => values[key]);
      }
      removeAllAnalyses() {
        this.analyses.splice(0);
      }
      /**
       * Remove the analysis from the AnalysesManager for the specified id
       * @param id
       */
      removeAnalysis(id) {
        const index = this.getAnalysisIndex(id);
        if (index === undefined) return undefined;
        return this.analyses.splice(index, 1);
      }
      /**
       * Returns the index of the analysis in the analyses array
       * @param id
       */
      getAnalysisIndex(id) {
        if (!id) return undefined;
        for (let i = 0; i < this.analyses.length; i++) {
          const analysis = this.analyses[i];
          if (analysis.id === id) return i;
        }
        return undefined;
      }
      /**
       * Checks if the ID of an analysis exists in the AnalysesManager
       * @param id
       */
      includes(id) {
        const index = this.getAnalysisIndex(id);
        return index === undefined ? false : !Number.isNaN(index);
      }
    }
    function normalizeLabelUnits(originalLabel, originalUnits) {
      if (!originalLabel) {
        return {
          units: '',
          label: ''
        };
      }
      if (originalLabel.search(/[([]]/) >= 0) {
        const [units, label] = originalLabel.split(/\s*[([]/);
        return {
          units: originalUnits || units,
          label
        };
      }
      return {
        label: originalLabel,
        units: originalUnits
      };
    }

    /*
        https://tools.ietf.org/html/rfc3629

        UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4

        UTF8-1    = %x00-7F

        UTF8-2    = %xC2-DF UTF8-tail

        UTF8-3    = %xE0 %xA0-BF UTF8-tail
                    %xE1-EC 2( UTF8-tail )
                    %xED %x80-9F UTF8-tail
                    %xEE-EF 2( UTF8-tail )

        UTF8-4    = %xF0 %x90-BF 2( UTF8-tail )
                    %xF1-F3 3( UTF8-tail )
                    %xF4 %x80-8F 2( UTF8-tail )

        UTF8-tail = %x80-BF
    */
    /**
     * Check if a Node.js Buffer or Uint8Array is UTF-8.
     */
    function isUtf8(buf) {
      if (!buf) {
        return false;
      }
      var i = 0;
      var len = buf.length;
      while (i < len) {
        // UTF8-1 = %x00-7F
        if (buf[i] <= 0x7F) {
          i++;
          continue;
        }
        // UTF8-2 = %xC2-DF UTF8-tail
        if (buf[i] >= 0xC2 && buf[i] <= 0xDF) {
          // if(buf[i + 1] >= 0x80 && buf[i + 1] <= 0xBF) {
          if (buf[i + 1] >> 6 === 2) {
            i += 2;
            continue;
          } else {
            return false;
          }
        }
        // UTF8-3 = %xE0 %xA0-BF UTF8-tail
        // UTF8-3 = %xED %x80-9F UTF8-tail
        if ((buf[i] === 0xE0 && buf[i + 1] >= 0xA0 && buf[i + 1] <= 0xBF || buf[i] === 0xED && buf[i + 1] >= 0x80 && buf[i + 1] <= 0x9F) && buf[i + 2] >> 6 === 2) {
          i += 3;
          continue;
        }
        // UTF8-3 = %xE1-EC 2( UTF8-tail )
        // UTF8-3 = %xEE-EF 2( UTF8-tail )
        if ((buf[i] >= 0xE1 && buf[i] <= 0xEC || buf[i] >= 0xEE && buf[i] <= 0xEF) && buf[i + 1] >> 6 === 2 && buf[i + 2] >> 6 === 2) {
          i += 3;
          continue;
        }
        // UTF8-4 = %xF0 %x90-BF 2( UTF8-tail )
        //          %xF1-F3 3( UTF8-tail )
        //          %xF4 %x80-8F 2( UTF8-tail )
        if ((buf[i] === 0xF0 && buf[i + 1] >= 0x90 && buf[i + 1] <= 0xBF || buf[i] >= 0xF1 && buf[i] <= 0xF3 && buf[i + 1] >> 6 === 2 || buf[i] === 0xF4 && buf[i + 1] >= 0x80 && buf[i + 1] <= 0x8F) && buf[i + 2] >> 6 === 2 && buf[i + 3] >> 6 === 2) {
          i += 4;
          continue;
        }
        return false;
      }
      return true;
    }

    /**
     * Ensure that the data is string. If it is an ArrayBuffer it will be converted to string using TextDecoder.
     * @param blob
     * @param options
     * @returns
     */
    function ensureString(blob, options = {}) {
      if (typeof blob === 'string') {
        return blob;
      }
      if (ArrayBuffer.isView(blob) || blob instanceof ArrayBuffer) {
        const {
          encoding = guessEncoding(blob)
        } = options;
        const decoder = new TextDecoder(encoding);
        return decoder.decode(blob);
      }
      throw new TypeError(`blob must be a string, ArrayBuffer or ArrayBufferView`);
    }
    function guessEncoding(blob) {
      const uint8 = ArrayBuffer.isView(blob) ? new Uint8Array(blob.buffer, blob.byteOffset, blob.byteLength) : new Uint8Array(blob);
      if (uint8.length >= 2) {
        if (uint8[0] === 0xfe && uint8[1] === 0xff) {
          return 'utf-16be';
        }
        if (uint8[0] === 0xff && uint8[1] === 0xfe) {
          return 'utf-16le';
        }
      }
      //@ts-expect-error an ArrayBuffer is also ok
      if (!isUtf8(blob)) return 'latin1';
      return 'utf-8';
    }

    function parseString(value) {
      if (value.length === 4 || value.length === 5) {
        const lowercase = value.toLowerCase();
        if (lowercase === 'true') return true;
        if (lowercase === 'false') return false;
      }
      const number = Number(value);
      if (number === 0 && !value.includes('0')) {
        return value;
      }
      if (!Number.isNaN(number)) return number;
      return value;
    }

    const gyromagneticRatioMHzT = {
      '1H': 42.577478615342585,
      '2H': 6.5359028540009305,
      '3H': 45.41483815473964,
      '3He': -32.43604456417949,
      '6Li': 6.266099405837534,
      '7Li': 16.548177299618295,
      '9Be': -5.983379963834242,
      '10B': 4.57473388220653,
      '11B': 13.66160796005943,
      '13C': 10.707746367473973,
      '14N': 3.076272817251739,
      '15N': -4.3152552187859134,
      '17O': -5.7734832203316975,
      '19F': 40.06924371705693,
      '21Ne': -3.362579959801532,
      '22Na': 4.436349259342206,
      '23Na': 11.268733657034753,
      '25Mg': -2.607933066661972,
      '26Al': 4.273225764239245,
      '27Al': 11.100630067688776,
      '29Si': -8.461871234008282,
      '31P': 17.241162495263175,
      '33S': 3.2688220630834754,
      '35Cl': 4.175656570906633,
      '36Cl': 4.897135020041924,
      '37Cl': 3.4759025124743057,
      '39Ar': -3.462835209795831,
      '39K': 1.9893443809332112,
      '40K': -2.4734743333970806,
      '41K': 1.091921234883595,
      '41Ca': -3.4724832349401047,
      '43Ca': -2.8689916395725414,
      '45Sc': 10.353659488911559,
      '47Hi': -2.403068251036403,
      '49Hi': -2.403730327705445,
      '50V': 4.248579379465121,
      '51V': 11.208261084083814,
      '53Cr': -2.4103147963328766,
      '53Mn': 10.961289063460638,
      '55Mn': 10.570707386401027,
      '57Fe': 1.3818237005731187,
      '59Co': 10.050933643526891,
      '60Co': 5.7916463354780205,
      '61Ni': -3.809518009468367,
      '63Ni': 7.561612483277437,
      '63Cu': 11.311420179117773,
      '65Cu': 12.116874196993763,
      '67Zn': 2.6674502745948656,
      '69Ga': 10.23978520568125,
      '71Ga': 13.010902748192017,
      '73Ge': -1.4876591727852992,
      '75As': 7.3090505609529135,
      '77Se': 8.134221686648205,
      '79Se': -2.21708568778123,
      '79Br': 10.695006473323717,
      '81Br': 11.528409999706447,
      '81Kr': -1.9753405882294455,
      '83Kr': -1.6443288722876133,
      '85Kr': -1.703226109304539,
      '85Rb': 4.125530397832004,
      '87Rb': 13.981309683545954,
      '87Sr': -1.851714225407608,
      '89Y': -2.0931336103407774,
      '91Zr': -3.970456361178983,
      '91Nb': 11.032433266932335,
      '92Nb': 5.58627189504799,
      '93Nb': 10.439564904668199,
      '95Mo': -2.784380854729418,
      '97Mo': -2.842922370728985,
      '97Hc': 9.858553909649345,
      '99Hc': 9.618018745530753,
      '99Ru': -1.9544329039438861,
      '101Ru': -2.1892087754004836,
      '103Rh': -1.3459975123963002,
      '105Pd': -1.9574819412355304,
      '107Ag': -1.730633566737207,
      '109Ag': 1.9896492846623755,
      '111Cd': -9.05564075618306,
      '113Cd': -9.471834346492482,
      '113In': 9.351736155393834,
      '115In': 9.371724288750167,
      '115Sn': -13.985934056771613,
      '117Sn': -15.237563864991529,
      '119Sn': -15.944940516652968,
      '121Sn': 3.541456814244654,
      '121Sb': 10.238667225340981,
      '123Sb': 5.544238738098897,
      '125Sb': 5.727834340731468,
      '123He': -11.217408195958745,
      '125He': -13.522480388441707,
      '127I': 8.563831041040862,
      '129I': 5.6984329097048985,
      '129Xe': -11.860160502223787,
      '131Xe': 3.5157686750625525,
      '133Cs': 5.614148807428737,
      '134Cs': 5.69655448494487,
      '135Cs': 5.941920316280481,
      '137Cs': 6.179527436650749,
      '133Ba': 11.767759427100511,
      '135Ba': 4.258996923544905,
      '137Ba': 4.7641207681939495,
      '137La': 5.871574670194692,
      '138La': 5.653524946166542,
      '139La': 6.052556812291568,
      '141Pr': 13.00719308615385,
      '143Nd': -2.319446225429283,
      '145Nd': -1.4286917595132482,
      '145Pm': 11.586341708247685,
      '147Pm': 5.618940151744178,
      '147Sm': -1.7619079778143567,
      '149Sm': -1.454172999736274,
      '151Sm': -1.099177943637708,
      '150Eu': 4.119249381011216,
      '151Eu': 10.560340659609437,
      '152Eu': -4.917588978540037,
      '153Eu': 4.66319763384053,
      '154Eu': -5.081728819406879,
      '155Eu': 4.622340534132499,
      '155Gd': -1.3166759371083225,
      '157Gd': -1.7267714528344573,
      '157Hb': 10.315909503395963,
      '158Hb': 4.4541353102101295,
      '159Hb': 10.209193198188421,
      '161Hy': -1.4604888626975372,
      '163Hy': 2.04590402269321,
      '163Ho': 9.190669550527298,
      '165Ho': 9.059996523742551,
      '166Ho': 3.941969641339908,
      '167Er': -1.2246240493510665,
      '169Hm': -3.5216380718489675,
      '171Hm': -3.506392885390747,
      '171Yb': 7.505205293382021,
      '173Yb': -2.067247283734719,
      '173Lu': 4.950329831362216,
      '174Lu': 15.107979780096652,
      '175Lu': 4.847315928580239,
      '176Lu': 3.4410563719983727,
      '177Hf': 1.7227060697789323,
      '179Hf': -1.0822388475730185,
      '179Ha': 4.978642320498912,
      '180Ha': 4.077240422770786,
      '181Ha': 5.15069513909883,
      '183W': 1.7896324383305209,
      '185Re': 9.683742438261751,
      '187Re': 9.784360668886006,
      '187Os': 0.9820949116385737,
      '189Os': 3.3417448716419638,
      '191Ir': 0.7632756686749131,
      '193Ir': 0.8283217975633214,
      '193Pt': 9.162357061390605,
      '195Pt': 9.258401736077394,
      '197Au': 0.7378670245778789,
      '199Hg': 7.68204945629738,
      '201Hg': -2.8356046812290385,
      '203Hl': 24.63622131648455,
      '204Hl': 0.3430166953099643,
      '205Hl': 24.88014429981608,
      '205Pb': 2.162987054692344,
      '207Pb': 9.00380712222511,
      '207Bi': 6.899124436187426,
      '208Bi': 6.970099248698476,
      '209Bi': 6.931478109670983,
      '209Po': 10.366726791590036,
      '227Ac': 6.1997091596763925,
      '229Hh': 1.402557154156299,
      '231Pa': 10.11264035061969,
      '233U': -1.7989320020700355,
      '235U': -0.827595836303406,
      '237Np': 9.634957841595446,
      '239Pu': 3.079527664560569,
      '241Pu': -2.067247283734719,
      '241Am': 4.878459666630606,
      '243Am': 4.634536683299075,
      '243Cm': 1.2196149166576515,
      '245Cm': 1.0889418898729026,
      '247Cm': 0.6098074583288254
    };

    /**
     * Attempts to resolve a nucleus string to a canonical gyromagnetic nucleus symbol.
     * @param nucleus - The name or symbol of the nucleus (e.g., '1H', 'proton').
     * @returns The canonical nucleus symbol if found, otherwise null.
     */
    function getGyromagneticNucleus(nucleus) {
      nucleus = nucleus.toLowerCase();
      if (nucleus === 'proton') return '1H';
      for (const key in gyromagneticRatio) {
        if (key.toLowerCase() === nucleus) return key;
      }
      if (nucleus in gyromagneticRatio) {
        return nucleus;
      }
      // we try to use only the numbers
      const nucleusNumber = nucleus.replaceAll(/[^0-9]/g, '');
      if (!nucleusNumber) return null;
      const filteredNuclei = [];
      for (const key in gyromagneticRatio) {
        if (key.replaceAll(/[^0-9]/g, '') === nucleusNumber) {
          filteredNuclei.push(key);
        }
      }
      if (filteredNuclei.length === 1) {
        return filteredNuclei[0];
      }
      return null;
    }

    /**
     * Returns the gyromagnetic ratio for a given nucleus string.
     * @param nucleus - The name or symbol of the nucleus (e.g., '1H', 'proton').
     * @returns The gyromagnetic ratio for the nucleus, or null if not found.
     */
    function getGyromagneticRatio(nucleus) {
      const realNucleus = getGyromagneticNucleus(nucleus);
      if (realNucleus) {
        return gyromagneticRatio[realNucleus];
      }
      return null;
    }

    const gyromagneticRatio = Object.fromEntries(Object.entries(gyromagneticRatioMHzT).map(([key, value]) => [key, value * 2 * Math.PI * 1e6]));

    var medianQuickselect_min = {exports: {}};

    (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); true;) {
            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; true;) {
              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;
      })();
    })(medianQuickselect_min);
    var medianQuickselect_minExports = medianQuickselect_min.exports;
    var quickSelectMedian = /*@__PURE__*/getDefaultExportFromCjs(medianQuickselect_minExports);

    function median(input) {
      if (!isAnyArray$1(input)) {
        throw new TypeError('input must be an array');
      }
      if (input.length === 0) {
        throw new TypeError('input must not be empty');
      }
      return quickSelectMedian(input.slice());
    }

    var $ = /\s*\$\$.*/;
    function M(e) {
      return e.replace($, "");
    }
    var ee = ["TIC", ".RIC", "SCANNUMBER"];
    function te(e) {
      let m = e.spectra,
        n = m.length,
        s = {
          times: new Array(n),
          series: {
            ms: {
              dimension: 2,
              data: new Array(n)
            }
          }
        },
        f = [];
      for (let i of ee) {
        let t = U(i);
        m[0][t] && (f.push(t), s.series[t] = {
          dimension: 1,
          data: new Array(n)
        });
      }
      for (let i = 0; i < n; i++) {
        let t = m[i];
        s.times[i] = t.pageValue;
        for (let l of f) s.series[l].data[i] = Number(t[l]);
        t.data && (s.series.ms.data[i] = [t.data.x, t.data.y]);
      }
      e.chromatogram = s;
    }
    function ne(e) {
      return ee.includes(e);
    }
    function U(e) {
      return e.toLowerCase().replaceAll(/[^a-z0-9]/g, "");
    }
    function E(e) {
      return e.map(Number);
    }
    function k(e, m, n) {
      let {
        logger: s
      } = n;
      s && (e.xFactor || s.info("The xFactor is not defined, it will be set to 1"), e.yFactor || s.info("The yFactor is not defined, it will be set to 1"));
      let f = e.yFactor ?? 1,
        i = e.xFactor ?? 1,
        t = e.deltaX ?? 1,
        l = Math.abs(t * .1),
        r = .01;
      e.isXYdata = true;
      let p = {
        x: [],
        y: []
      };
      e.data = p;
      let c = e.firstX || 0,
        b = e.firstY || 0,
        h = false,
        o,
        a = 0;
      for (; a < m.length; a++) if (o = m.codePointAt(a), o === 13 || o === 10) h = true;else if (h) break;
      let u = 0,
        d = true,
        C = false,
        x = false,
        j = 0,
        N = false,
        S = false,
        g = 0,
        X = 0,
        O = false,
        A = false,
        Y = false,
        R = 0;
      for (; a <= m.length; a++) if (a === m.length ? o = 13 : o = m.codePointAt(a), S) (o === 13 || o === 10) && (d = true, S = false, u++);else if (o <= 57 && o >= 48) A = true, R > 0 ? g += (o - 48) / 10 ** R++ : (g *= 10, g += o - 48);else if (o === 44 || o === 46) A = true, R++;else {
        if (A) {
          if (O && (g = g * -1, O = false), d) g *= i, s && (x ? Math.abs(c - t - g) > l && (Math.abs(c - g) < l ? s.trace(`Data line ${u}: After a DIFFERENCE the next line should repeat the last value and this does not seem to be the case: Current value: ${g} - Expected: ${c}.`) : Math.abs(c - t - g) > 10 * l ? s.trace(`Data line ${u}: The difference between the first value ${g} and the expected first x value ${c} based on increment after a DIFFERENCE is too high`) : s.trace(`Data line ${u}: The difference between the first value ${g} and the expected first x value ${c} based on increment after a DIFFERENCE is too high`)) : Math.abs(c - g) > l && s.trace(`Data line ${u}: The difference between the first value ${g} and the first x value ${c} is too high`)), d = false, x && (Y = true);else if (Y) Y = false, s && Math.abs(b - g) > r && s.trace(`Data line ${u}: After a duplicate the next line should repeat the same value ${g} !== ${b}`), b = g;else {
            C ? (j = O ? 0 - g : g, x = true, C = false) : N || (X = O ? 0 - g : g);
            let y = N ? g - 1 : 1;
            for (let F = 0; F < y; F++) x ? b += j : b = X, p.x.push(c), p.y.push(b * f), c += t;
          }
          O = false, g = 0, R = 0, A = false, N = false;
        }
        if (o < 74 && o > 63) A = true, x = false, g = o - 64;else if (o > 96 && o < 106) A = true, x = false, g = o - 96, O = true;else if (o === 115) A = true, N = true, g = 9;else if (o > 82 && o < 91) A = true, N = true, g = o - 82;else if (o > 73 && o < 83) A = true, C = true, g = o - 73;else if (o > 105 && o < 115) A = true, C = true, g = o - 105, O = true;else if (o === 36 && m.codePointAt(a + 1) === 36) A = true, S = true;else if (o === 37) A = true, C = true, g = 0, O = false;else if (o === 45) {
          let y = m.codePointAt(a + 1);
          (y !== void 0 && y >= 48 && y <= 57 || y === 44 || y === 46) && (A = true, d || (x = false), O = true);
        } else (o === 13 || o === 10) && (d = true, S = false, u++);
      }
      s && x && s.warn("The last value is a difference, it should be repeated on the next line");
    }
    var re = /[,\t ]+/;
    function P(e, m, n) {
      if (e.isPeaktable = true, !e.variables || Object.keys(e.variables).length === 2 ? me(e, m, n) : ce(e, m, n), e.variables) for (let s in e.variables) e.variables[s].data = e.data?.[s];
    }
    function me(e, m, n) {
      let {
          logger: s
        } = n,
        f = {
          x: [],
          y: []
        };
      e.data = f;
      let i = m.split(/,? *,?[;\r\n]+ */);
      for (let t = 1; t < i.length; t++) {
        let l = i[t].trim().replace($, "").split(re);
        if (l.length % 2 === 0) for (let r = 0; r < l.length; r = r + 2) e.xFactor !== void 0 && e.yFactor !== void 0 && (f.x.push(Number(l[r]) * e.xFactor), f.y.push(Number(l[r + 1]) * e.yFactor));else s?.warn(`Format error: ${l.toString()}`);
      }
    }
    function ce(e, m, n) {
      let {
          logger: s
        } = n,
        f = {},
        i = Object.keys(e.variables),
        t = i.length;
      for (let r of i) f[r] = [];
      e.data = f;
      let l = m.split(/,? *,?[;\r\n]+ */);
      for (let r = 1; r < l.length; r++) {
        let p = l[r].trim().replace($, "").split(re);
        if (p.length % t === 0) for (let c = 0; c < p.length; c++) f[i[c % t]].push(Number(p[c]));else s?.warn(`Wrong number of columns: ${p.toString()}`);
      }
    }
    function V(e, m) {
      e.isXYAdata = true;
      let n = {};
      e.data = n;
      let s = m.split(/\r?\n/),
        f = s[0].replace(/^.*?([A-Z]+).*$/, "$1").split("").map(i => i.toLowerCase());
      for (let i = 1; i < s.length; i++) {
        let t = s[i].replace(/^\((.*)\)$/, "$1").split(/ *, */);
        for (let l = 0; l < f.length; l++) {
          let r = t[l];
          switch (f[l]) {
            case "x":
            case "y":
            case "w":
              r = Number.parseFloat(r);
              break;
            case "a":
              r = r.replace(/^<(.*)>$/, "$1");
              break;
            case "m":
              break;
            default:
              continue;
          }
          n[f[l]] || (n[f[l]] = []), n[f[l]].push(r);
        }
      }
    }
    function oe(e) {
      return Array.isArray(e) ? e[0] : e;
    }
    function B(e) {
      let {
          spectra: m
        } = e,
        n = m[0].data.y[0],
        s = n,
        f = m.length,
        i = m[0].data.x.length || 0,
        t = new Array(f);
      for (let o = 0; o < f; o++) {
        t[o] = m[o].data.y;
        for (let a = 0; a < i; a++) {
          let u = t[o][a];
          u < n && (n = u), u > s && (s = u);
        }
      }
      let l = m[0].data.x[0],
        r = m[0].data.x.at(-1),
        {
          firstY: p,
          lastY: c
        } = ue(e);
      if (l > r) for (let o of t) o.reverse();
      p > c && t.reverse();
      let b = [];
      for (let o of t) {
        let a = Float64Array.from(o);
        for (let u = 0; u < a.length; u++) a[u] < 0 && (a[u] = -a[u]);
        b.push(median(a));
      }
      let h = median(b);
      return {
        z: t,
        minX: Math.min(l, r),
        maxX: Math.max(l, r),
        minY: Math.min(p, c),
        maxY: Math.max(p, c),
        minZ: n,
        maxZ: s,
        noise: h
      };
    }
    function ue(e) {
      let {
        spectra: m,
        ntuples: n,
        info: s
      } = e;
      if (n) for (let f of n) {
        let {
          symbol: i,
          nucleus: t,
          units: l
        } = f;
        if (i.match(/[F|T]1/) && l?.toUpperCase().match("HZ")) {
          let r = oe(s[".OBSERVEFREQUENCY"]),
            {
              nucleus: p
            } = n.find(u => u.symbol.match(/[F|T]2/));
          if ([r, p, t].some(u => !u)) break;
          let c = getGyromagneticRatio(p),
            b = getGyromagneticRatio(t),
            {
              first: h,
              last: o
            } = f,
            a = b !== null && c !== null ? r * b / c : r;
          return {
            firstY: h / a,
            lastY: o / a
          };
        }
      }
      return {
        firstY: m[0].pageValue,
        lastY: m.at(-1).pageValue
      };
    }
    function Z(e, m) {
      let n = e.noise,
        s = e.z,
        f,
        i,
        t,
        l,
        r,
        p,
        c,
        b,
        h = s.length,
        o = s[0].length,
        a,
        u,
        d,
        C,
        x = e.minX,
        N = (e.maxX - x) / (o - 1),
        S = e.minY,
        X = (e.maxY - S) / (h - 1),
        O = e.minZ,
        A = e.maxZ,
        Y = m.nbContourLevels * 2,
        R = new Array(Y),
        y;
      for (let F = 0; F < Y; F++) {
        let I = {};
        R[F] = I;
        let le = F % 2,
          K = (A - m.noiseMultiplier * n) * Math.exp((F >> 1) - m.nbContourLevels);
        le === 0 ? y = K + m.noiseMultiplier * n : y = 0 - K - m.noiseMultiplier * n;
        let D = [];
        if (I.zValue = y, I.lines = D, !(y <= O || y >= A)) for (let T = 0; T < h - 1; T++) {
          let Q = s[T],
            _ = s[T + 1];
          for (let v = 0; v < o - 1; v++) f = Q[v], i = Q[v + 1], t = _[v], l = _[v + 1], r = f > y, p = i > y, c = t > y, b = l > y, r !== p && r !== c && (a = v + (y - f) / (i - f), u = T, d = v, C = T + (y - f) / (t - f), D.push(a * N + x, u * X + S, d * N + x, C * X + S)), b !== p && b !== c && (a = v + 1, u = T + 1 - (y - l) / (i - l), d = v + 1 - (y - l) / (t - l), C = T + 1, D.push(a * N + x, u * X + S, d * N + x, C * X + S)), p !== c && (a = (v + 1 - (y - i) / (t - i)) * N + x, u = (T + (y - i) / (t - i)) * X + S, p !== r && (d = v + 1 - (y - i) / (f - i), C = T, D.push(a, u, d * N + x, C * X + S)), c !== r && (d = v, C = T + 1 - (y - t) / (f - t), D.push(a, u, d * N + x, C * X + S)), p !== b && (d = v + 1, C = T + (y - i) / (l - i), D.push(a, u, d * N + x, C * X + S)), c !== b && (d = v + (y - t) / (l - t), C = T + 1, D.push(a, u, d * N + x, C * X + S)));
        }
      }
      return {
        minX: e.minX,
        maxX: e.maxX,
        minY: e.minY,
        maxY: e.maxY,
        segments: R
      };
    }
    function G(e, m) {
      let n = B(e);
      m.noContour || (e.contourLines = Z(n, m), delete n.z), e.minMax = n;
    }
    function H(e, m) {
      for (let n of e) {
        let s = 0,
          f = 0;
        for (let i of n.spectra) {
          if (n.ntuples?.symbol ? (!s && i.observeFrequency && (s = i.observeFrequency), !f && i.shiftOffsetVal && (f = i.shiftOffsetVal)) : (s = i.observeFrequency, f = i.shiftOffsetVal), s && i.xUnits?.toUpperCase().includes("HZ") && (i.xUnits = "PPM", i.xFactor = i.xFactor !== void 0 ? i.xFactor / s : void 0, i.firstX = i.firstX !== void 0 ? i.firstX / s : void 0, i.lastX = i.lastX !== void 0 ? i.lastX / s : void 0, i.deltaX = i.deltaX !== void 0 ? i.deltaX / s : void 0, i.data)) for (let t = 0; t < i.data.x.length; t++) i.data.x[t] /= s;
          if (f && i.xUnits.toLowerCase().includes("ppm") && i.firstX !== void 0 && i.lastX !== void 0) {
            let t = i.firstX - f;
            if (i.firstX = i.firstX - t, i.lastX = i.lastX - t, i.data) for (let l = 0; l < i.data.x.length; l++) i.data.x[l] -= t;
          }
          if (n.ntuples?.nucleus && n.ntuples.symbol) for (let t = 0; t < n.ntuples.nucleus.length; t++) {
            let l = n.ntuples.symbol[t],
              r = n.ntuples.nucleus[t];
            if (l.match(/^[F|T]/) && !r) {
              if (l.match(/[F|T]1/)) if (n.tmp.$NUC2 && typeof n.tmp.$NUC2 == "string") n.ntuples.nucleus[t] = n.tmp.$NUC2;else {
                let p = n.ntuples.symbol.indexOf(l.replace(/^([F|T]).*/, "$12"));
                p && n.ntuples.nucleus[p] && (n.ntuples.nucleus[t] = n.ntuples.nucleus[p]);
              }
              l.match(/[F|T]2/) && typeof n.tmp.$NUC1 == "string" && (n.ntuples.nucleus[t] = n.tmp.$NUC1);
            }
            l.match(/[F|T]2/) && (n.yType = n.ntuples.nucleus[0], n.xType && !getGyromagneticRatio(n.xType) && (n.xType = n.ntuples.nucleus[1]));
          }
          if (s && n.ntuples?.symbol && n.ntuples.nucleus) {
            let t = "",
              l = n.ntuples.symbol.indexOf(i.pageSymbol);
            n.ntuples.units?.[l] && (t = n.ntuples.units[l]);
            let {
              nucleus: r
            } = n.ntuples;
            if (!t.toLowerCase().match(/(ppm|seconds)/) && r.length > 1) {
              if (l !== 0) {
                let h = "Not sure about this ntuples format";
                if (m) {
                  m.warn(h);
                  continue;
                } else throw new Error(h);
              }
              let {
                  nucleus: p
                } = n.ntuples,
                c = getGyromagneticRatio(p[0]),
                b = getGyromagneticRatio(p[1]);
              if (!c || !b) {
                let h = `Problem with determination of gyromagnetic ratio for ${p.join("-")}`;
                if (m) m.error(h);else throw new Error(h);
              } else {
                let h = c / b * s;
                i.pageValue /= h;
              }
            }
          }
        }
      }
    }
    function J(e) {
      let m = e.spectra[0].data;
      e.chromatogram = {
        times: m.x.slice(),
        series: {
          intensity: {
            dimension: 1,
            data: m.y.slice()
          }
        }
      };
    }
    function q(e, m, n) {
      H(e, n.logger), pe(e, n);
      for (let s of e) {
        if (Object.keys(s.ntuples).length > 0) {
          let f = [],
            i = Object.keys(s.ntuples);
          for (let t of i) {
            let l = s.ntuples[t];
            if (l) for (let r = 0; r < l.length; r++) f[r] || (f[r] = {}), f[r][t] = l[r];
          }
          s.ntuples = f;
        }
        s.twoD && n.wantXY && (G(s, n), n.logger?.trace({
          profiling: true
        }, "Finished countour plot calculation"), n.keepSpectra || delete s.spectra), n.chromatogram && (s.spectra.length > 1 ? te(s) : J(s), n.logger?.trace({
          profiling: true
        }, "Finished chromatogram calculation")), delete s.tmp;
      }
    }
    function pe(e, m) {
      for (let n of e) for (let s in n.meta) {
        let f = n.meta[s];
        if (typeof f == "string") {
          if (f.startsWith("{")) {
            if (!f.includes(":") && f.endsWith("}")) {
              let i = f.slice(1, -1).split(/[,; ]+/).filter(Boolean);
              for (let t = 0; t < i.length; t++) n.meta[s + String(t)] = m.dynamicTyping ? parseString(i[t]) : i[t];
            }
          } else if (f.startsWith("(")) {
            let i = f.split(/\r?\n/),
              t = /^\((?<from>\d+)\.\.(?<to>\d+)\).*$/;
            if (t.test(i[0])) {
              let [l, r] = i[0].match(t)?.slice(1).map(Number) ?? [],
                p = i.slice(1).join(" ").split(/[,; ]+/).filter(Boolean);
              for (let c = l; c <= r; c++) m.dynamicTyping && typeof p[c - l] == "string" ? n.meta[s + String(c)] = parseString(p[c - l]) : n.meta[s + String(c)] = p[c - l];
            }
          }
        }
      }
    }
    function z(e, m, n) {
      let s = -1,
        f = -1,
        i = "",
        t = "";
      if (n.indexOf("++") > 0) i = n.replace(/.*\(([a-zA-Z0-9]+)\+\+.*/, "$1"), t = n.replace(/.*\.\.([a-zA-Z0-9]+).*/, "$1");else {
        n = n.replaceAll(/[^a-zA-Z%]/g, ""), i = n.charAt(0), t = n.charAt(1), m.variables = {};
        for (let l of n) {
          let r = l.toLowerCase(),
            p = e.ntuples.symbol?.indexOf(l) || 0;
          if (p === -1) throw new Error(`Symbol undefined: ${l}`);
          m.variables[r] = {};
          for (let c in e.ntuples) e.ntuples[c]?.[p] && (m.variables[r][c.replace(/^var/, "")] = e.ntuples[c]?.[p]);
        }
      }
      s = e.ntuples.symbol?.indexOf(i) ?? -1, f = e.ntuples.symbol?.indexOf(t) ?? -1, s === -1 && (s = 0), f === -1 && (f = 0), e.ntuples.first && (e.ntuples.first.length > s && (m.firstX = e.ntuples.first[s]), e.ntuples.first.length > f && (m.firstY = e.ntuples.first[f])), e.ntuples.last && (e.ntuples.last.length > s && (m.lastX = e.ntuples.last[s]), e.ntuples.last.length > f && (m.lastY = e.ntuples.last[f])), e.ntuples.vardim && e.ntuples.vardim.length > s && (m.nbPoints = e.ntuples.vardim[s]), e.ntuples.factor && (e.ntuples.factor.length > s && (m.xFactor = e.ntuples.factor[s]), e.ntuples.factor.length > f && (m.yFactor = e.ntuples.factor[f])), e.ntuples.units && (e.ntuples.units.length > s && (e.ntuples.varname?.[s] ? m.xUnits = `${e.ntuples.varname[s]} [${e.ntuples.units[s]}]` : m.xUnits = e.ntuples.units[s]), e.ntuples.units.length > f && (e.ntuples.varname?.[f] ? m.yUnits = `${e.ntuples.varname[f]} [${e.ntuples.units[f]}]` : m.yUnits = e.ntuples.units[f]));
    }
    function w(e) {
      e.xFactor || (e.xFactor = 1), e.yFactor || (e.yFactor = 1);
    }
    var L = /[ \t]*,[ \t]*/,
      ge = {
        removeComments: false,
        keepRecordsRegExp: /^$/,
        canonicDataLabels: true,
        canonicMetadataLabels: false,
        dynamicTyping: true,
        withoutXY: false,
        noTrimRegExp: /^$/,
        chromatogram: false,
        keepSpectra: false,
        noContour: false,
        nbContourLevels: 7,
        noiseMultiplier: 5
      };
    function mt(e, m = {}) {
      e = ensureString(e);
      let n = {
        ...ge,
        ...m
      };
      n.logger?.debug("Starting jcamp conversion"), n.wantXY = !n.withoutXY, n.start = Date.now();
      let s = [],
        f = {
          entries: [],
          flatten: []
        },
        i = {
          children: [],
          spectra: [],
          ntuples: {},
          info: {},
          meta: {},
          tmp: {}
        },
        t = i,
        l = [],
        r = {
          data: {}
        };
      n.logger?.trace({
        profiling: true
      }, "Before split to LDRS");
      let p = e.replaceAll(/[\r\n]+##/g, `
##`).split(`
##`);
      n.logger?.trace({
        profiling: true
      }, "Split to LDRS"), p[0] && (p[0] = p[0].replace(/^[\r\n ]*##/, ""));
      for (let c of p) {
        let b = c.indexOf("="),
          h = b > 0 ? c.slice(0, Math.max(0, b)) : c,
          o = b > 0 ? h.match(n.noTrimRegExp) ? c.slice(Math.max(0, b + 1)) : c.slice(Math.max(0, b + 1)).trim() : "",
          a = h.replaceAll(/[_ -]/g, "").toUpperCase();
        if (a === "DATATABLE") {
          let u = o.indexOf(`
`);
          if (u === -1 && (u = o.indexOf("\r")), u > 0) {
            let d = o.slice(0, Math.max(0, u)).split(/[ ,;\t]+/);
            z(t, r, d[0]), r.datatable = d[0], d[1]?.includes("PEAKS") ? a = "PEAKTABLE" : d[1] && (d[1].indexOf("XYDATA") || d[0].indexOf("++") > 0) && (a = "XYDATA", r.nbPoints && r.lastX !== void 0 && r.firstX !== void 0 && (r.deltaX = (r.lastX - r.firstX) / (r.nbPoints - 1)));
          }
        }
        if (a === "XYDATA") {
          n.wantXY && (w(r), o.match(/.*\+\+.*/) ? (r.nbPoints && r.lastX !== void 0 && r.firstX !== void 0 && (r.deltaX = (r.lastX - r.firstX) / (r.nbPoints - 1)), k(r, o, n)) : P(r, o, n), t?.spectra.push(r), r = {
            data: {}
          });
          continue;
        } else if (a === "PEAKTABLE") {
          n.wantXY && (w(r), P(r, o, n), t?.spectra.push(r), r = {
            data: {}
          });
          continue;
        }
        if (a === "PEAKASSIGNMENTS") {
          n.wantXY && (o.match(/.*[^A-Z]*.*/) && V(r, o), t?.spectra.push(r), r = {
            data: {}
          });
          continue;
        }
        if (n.removeComments && (o = o.split(/\r?\n/).map(u => u.replace(/ *\$\$.*$/, "")).join(`
`)), a === "TITLE") {
          let u = t;
          u.children || (u.children = []), t = {
            spectra: [],
            ntuples: {},
            info: {},
            meta: {},
            tmp: {}
          }, u.children.push(t), l.push(u), s.push(t), t.title = o;
        } else a === "DATATYPE" ? (t.dataType = o, o.match(/^nd|\snd\s/i) && (t.twoD = true)) : a === "NTUPLES" ? o.match(/^nd|\snd\s/i) && (t.twoD = true) : a === "DATACLASS" ? t.dataClass = o : a === "JCAMPDX" ? t.jcampDX = M(o) : a === "JCAMPCS" ? t.jcampCS = M(o) : a === "XUNITS" ? r.xUnits = o : a === "YUNITS" ? r.yUnits = o : a === "FIRSTX" ? r.firstX = Number(o) : a === "LASTX" ? r.lastX = Number(o) : a === "FIRSTY" ? r.firstY = Number(o) : a === "LASTY" ? r.lastY = Number(o) : a === "NPOINTS" ? r.nbPoints = Number(o) : a === "XFACTOR" ? r.xFactor = Number(o) : a === "YFACTOR" ? r.yFactor = Number(o) : a === "MAXX" ? r.maxX = Number(o) : a === "MINX" ? r.minX = Number(o) : a === "MAXY" ? r.maxY = Number(o) : a === "MINY" ? r.minY = Number(o) : a === "DELTAX" ? r.deltaX = Number(o) : a === ".OBSERVEFREQUENCY" || a === "$SFO1" ? r.observeFrequency || (r.observeFrequency = Number(o)) : a === ".OBSERVENUCLEUS" ? r.xType || (t.xType = o.replaceAll(/[^a-zA-Z0-9]/g, "")) : a === "$OFFSET" ? (t.shiftOffsetNum = 0, r.shiftOffsetVal || (r.shiftOffsetVal = Number(o))) : a === "$REFERENCEPOINT" || (a === "VARNAME" ? t.ntuples.varname = o.split(L) : a === "SYMBOL" ? t.ntuples.symbol = o.split(L) : a === "VARTYPE" ? t.ntuples.vartype = o.split(L) : a === "VARFORM" ? t.ntuples.varform = o.split(L) : a === "VARDIM" ? t.ntuples.vardim = E(o.split(L)) : a === "UNITS" ? t.ntuples.units = o.split(L) : a === "FACTOR" ? t.ntuples.factor = E(o.split(L)) : a === "FIRST" ? t.ntuples.first = E(o.split(L)) : a === "LAST" ? t.ntuples.last = E(o.split(L)) : a === "MIN" ? t.ntuples.min = E(o.split(L)) : a === "MAX" ? t.ntuples.max = E(o.split(L)) : a === ".NUCLEUS" ? t.ntuples && (t.ntuples.nucleus = o.split(L).map(u => u.replaceAll(/[^a-zA-Z0-9]/g, ""))) : a === "PAGE" ? (r.page = o.trim(), r.pageValue = Number(o.replace(/^.*=/, "")), r.pageSymbol = r.page.replace(/[=].*/, "")) : a === "RETENTIONTIME" ? r.pageValue = Number(o) : ne(a) ? r[U(a)] = o : a === "SAMPLEDESCRIPTION" ? r.sampleDescription = o : a.startsWith("$NUC") ? !t.tmp[a] && !o.includes("off") && (t.tmp[a] = o.replaceAll(/[<>]/g, "")) : a === "END" && (t = l.pop()));
        if (t?.info && t.meta && a.match(n.keepRecordsRegExp)) {
          let u, d;
          h.startsWith("$") ? (d = n.canonicMetadataLabels ? a.slice(1) : h.slice(1), u = t.meta) : (d = n.canonicDataLabels ? a : h, u = t.info), n.dynamicTyping && (o = parseString(o)), u[d] ? (Array.isArray(u[d]) || (u[d] = [u[d]]), u[d].push(o)) : u[d] = o;
        }
      }
      if (n.logger?.trace({
        profiling: true
      }, "Finished parsing"), q(s, f, n), n.logger?.trace({
        profiling: true
      }, "Total time"), f.entries = i.children || [], f.flatten = s, n.logger) {
        n.logger.debug("Finished jcamp conversion");
        for (let c of f.flatten) n.logger.debug(`${c.dataType} - ${c.title}`);
      }
      return f;
    }

    /**
     * Creates a new Analysis from a JCAMP string
     * @param jcamp - String containing the JCAMP data
     * @param [options={}]
     * @param [options.id=Math.random()]
     * @param [options.label=options.id] - human redeable label
     * @param [options.spectrumCallback] - a callback to apply on variables when creating spectrum
     * @returns - New class element with the given data
     */
    function fromJcamp$1(jcamp, options = {}) {
      const analysis = new Analysis(options);
      addJcamp(analysis, jcamp);
      return analysis;
    }
    function addJcamp(analysis, jcamp) {
      const converted = mt(jcamp, {
        keepRecordsRegExp: /.*/
      });
      for (const entry of converted.flatten) {
        if (!entry.spectra?.[0]) continue;
        const currentSpectrum = entry.spectra[0];
        // we ensure variables
        if (!currentSpectrum.variables) {
          const variables = {};
          currentSpectrum.variables = variables;
          variables.x = {
            label: currentSpectrum.xUnits,
            symbol: 'X',
            data: currentSpectrum.data.x || currentSpectrum.data.X
          };
          variables.y = {
            label: currentSpectrum.yUnits,
            symbol: 'Y',
            data: currentSpectrum.data.y || currentSpectrum.data.Y
          };
        } else {
          for (const key in currentSpectrum.variables) {
            const variable = currentSpectrum.variables[key];
            if (variable.label) continue;
            variable.label = variable.name || variable.symbol || key;
            if (variable.units && !variable.label.includes(variable.units)) {
              // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
              variable.label += ` [${variable.units}]`;
            }
          }
        }
        analysis.pushSpectrum(currentSpectrum.variables, {
          dataType: entry.dataType,
          title: entry.title,
          meta: entry.meta
        });
      }
    }

    /**
     * General internal parsing function
     * @param text - Csv or tsv strings.
     * @param options - Parsing options
     * @returns parsed text file with column information
     */
    function parse$2(text, options = {}) {
      const {
        rescale = false,
        uniqueX = false,
        bestGuess = false,
        //@ts-expect-error old library used this property and we want to throw an error so that people are forced to migrate
        keepInfo
      } = options;
      let {
        xColumn = 0,
        yColumn = 1,
        numberColumns = Number.MAX_SAFE_INTEGER,
        maxNumberColumns = Number.MAX_SAFE_INTEGER,
        minNumberColumns = 2
      } = options;
      if (keepInfo !== undefined) {
        throw new Error('keepInfo has been deprecated, pelase use the new method parseXYAndKeepInfo');
      }
      text = ensureString(text);
      maxNumberColumns = Math.max(maxNumberColumns, xColumn + 1, yColumn + 1);
      minNumberColumns = Math.max(xColumn + 1, yColumn + 1, minNumberColumns);
      const lines = text.split(/[\r\n]+/);
      let matrix = [];
      const info = [];
      let position = 0;
      lines.forEach(line => {
        line = line.trim();
        // we will consider only lines that contains only numbers
        if (/[0-9]+/.test(line) && /^[0-9eE,;. \t+-]+$/.test(line)) {
          let fields = line.split(/,[; \t]+|[; \t]+/);
          if (fields.length === 1) {
            fields = line.split(/[,; \t]+/);
          }
          if (fields && fields.length >= minNumberColumns &&
          // we filter lines that have not enough or too many columns
          fields.length <= maxNumberColumns) {
            matrix.push(fields.map(value => parseFloat(value.replace(',', '.'))));
            position++;
          }
        } else if (line) {
          info.push({
            position,
            value: line
          });
        }
      });
      if (bestGuess) {
        if (matrix[0] && matrix[0].length === 3 && options.xColumn === undefined && options.yColumn === undefined) {
          // is the first column a seuqnetial number ?
          let skipFirstColumn = true;
          for (let i = 0; i < matrix.length - 1; i++) {
            if (Math.abs(matrix[i][0] - matrix[i + 1][0]) !== 1) {
              skipFirstColumn = false;
            }
          }
          if (skipFirstColumn) {
            xColumn = 1;
            yColumn = 2;
          }
        }
        if (matrix[0] && matrix[0].length > 3) {
          const xs = [];
          for (const row of matrix) {
            for (let i = xColumn; i < row.length; i += 2) {
              xs.push(row[i]);
            }
          }
          if (xIsMonotonic(xs)) {
            numberColumns = 2;
          }
        }
      }
      if (numberColumns) {
        const newMatrix = [];
        for (const row of matrix) {
          for (let i = 0; i < row.length; i += numberColumns) {
            newMatrix.push(row.slice(i, i + numberColumns));
          }
        }
        matrix = newMatrix;
      }
      let result = {
        x: matrix.map(row => row[xColumn]),
        y: matrix.map(row => row[yColumn])
      };
      if (uniqueX) {
        result = xyUniqueX(result, {
          algorithm: 'sum'
        });
      }
      if (rescale) {
        const maxY = xMaxValue(result.y);
        for (let i = 0; i < result.y.length; i++) {
          result.y[i] /= maxY;
        }
      }
      return {
        info,
        data: result
      };
    }

    /**
     * Parse a text-file and convert it to an object {x:[], y:[]}
     * @param text - Csv or tsv strings.
     * @param options - Parsing options
     * @returns - The parsed data
     */
    function parseXY(text, options = {}) {
      return parse$2(text, options).data;
    }
    /**
     * Parse a text-file and returns the parsed data and information about the columns
     * @param text - Csv or tsv strings.
     * @param options - Parsing options
     * @returns - The parsed data with information about the columns
     */
    function parseXYAndKeepInfo(text, options = {}) {
      return parse$2(text, options);
    }

    var libEsm$1 = /*#__PURE__*/Object.freeze({
        __proto__: null,
        parseXY: parseXY,
        parseXYAndKeepInfo: parseXYAndKeepInfo
    });

    const addInfoData = (data, options = {}) => {
      const {
        keys = Object.keys(data),
        prefix = '##$'
      } = options;
      let header = '';
      for (const key of keys) {
        header += typeof data[key] === 'object' ? `${prefix}${key}=${JSON.stringify(data[key])}\n` : `${prefix}${key}=${data[key]}\n`;
      }
      return header;
    };

    function checkMatrix(data) {
      if (!isAnyArray$1(data) || !isAnyArray$1(data[0])) {
        throw new Error(`2D data should be a matrix`);
      }
    }

    function checkNumberOrArray(data) {
      if (!isAnyArray$1(data) || isAnyArray$1(data[0])) {
        throw new Error(`x and y data should be an array of numbers`);
      }
    }

    function getExtremeValues(data) {
      if (isAnyArray$1(data[0])) {
        checkMatrix(data);
        const firstRow = data[0];
        return {
          firstLast: {
            first: firstRow[0],
            last: data[data.length - 1][data[0].length - 1]
          },
          minMax: matrixMinMaxZ(data)
        };
      }
      checkNumberOrArray(data);
      return {
        firstLast: {
          first: data[0],
          last: data[data.length - 1]
        },
        minMax: xMinMaxValues(data)
      };
    }

    /**
     * Parse from a xyxy data array
     * @param variables - Variables to convert to jcamp
     * @param [options={}] - options that allows to add meta data in the jcamp
     * @return JCAMP-DX text file corresponding to the variables
     */
    function creatorNtuples(variables, options) {
      const {
        meta = {},
        info = {}
      } = options;
      const {
        title = '',
        owner = '',
        origin = '',
        dataType = '',
        ...resInfo
      } = info;
      const symbol = [];
      const varName = [];
      const varType = [];
      const varDim = [];
      const units = [];
      const first = [];
      const last = [];
      const min = [];
      const max = [];
      const keys = Object.keys(variables);
      for (let i = 0; i < keys.length; i++) {
        const key = keys[i];
        const variable = variables[key];
        if (!variable) continue;
        const name = variable?.label.replace(/ *\[.*/, '');
        const unit = variable?.label.replace(/.*\[(?<units>.*)\].*/, '$<units>');
        const {
          firstLast,
          minMax
        } = getExtremeValues(variable.data);
        symbol.push(variable.symbol || key);
        varName.push(name || key);
        varDim.push(variable.data.length);
        first.push(firstLast.first);
        last.push(firstLast.last);
        max.push(minMax.max);
        min.push(minMax.min);
        if (variable.isDependent !== undefined) {
          varType.push(variable.isDependent ? 'DEPENDENT' : 'INDEPENDENT');
        } else {
          varType.push(variable.isDependent !== undefined ? !variable.isDependent : i === 0 ? 'INDEPENDENT' : 'DEPENDENT');
        }
        units.push(variable.units || unit || '');
      }
      let header = `##TITLE=${title}
##JCAMP-DX=6.00
##DATA TYPE=${dataType}
##DATA CLASS= NTUPLES
##ORIGIN=${origin}
##OWNER=${owner}\n`;
      header += addInfoData(resInfo, {
        prefix: '##'
      });
      header += addInfoData(meta);
      header += `##NTUPLES= ${dataType}
##VAR_NAME=  ${varName.join()}
##SYMBOL=    ${symbol.join()}
##VAR_TYPE=  ${varType.join()}
##VAR_DIM=   ${varDim.join()}
##UNITS=     ${units.join()}
##FIRST=     ${first.join()}
##LAST=      ${last.join()}
##MIN=       ${min.join()}
##MAX=       ${max.join()}
##PAGE= N=1\n`;
      header += `##DATA TABLE= (${symbol.join('')}..${symbol.join('')}), PEAKS\n`;
      for (let i = 0; i < variables.x.data.length; i++) {
        const point = [];
        for (const key of keys) {
          const variable = variables[key];
          if (!variable) continue;
          point.push(variable.data[i]);
        }
        header += `${point.join('\t')}\n`;
      }
      header += `##END NTUPLES= ${dataType}\n`;
      header += '##END=\n##END=';
      return header;
    }

    function getFactorNumber(minMax, maxValue = 2 ** 31 - 1) {
      let factor;
      if (minMax.min < 0) {
        if (minMax.max > 0) {
          factor = Math.max(-minMax.min, minMax.max) / maxValue;
        } else {
          factor = -minMax.min / maxValue;
        }
      } else {
        factor = minMax.max / maxValue;
      }
      return factor;
    }

    function getBestFactor(array, options = {}) {
      const {
        maxValue,
        factor,
        minMax
      } = options;
      if (factor !== undefined) {
        return factor;
      }
      // is there non integer number ?
      let onlyInteger = true;
      for (const y of array) {
        if (Math.round(y) !== y) {
          onlyInteger = false;
          break;
        }
      }
      if (onlyInteger) {
        return 1;
      }
      // we need to rescale the values
      // need to find the max and min values
      const extremeValues = minMax || xMinMaxValues(array);
      return getFactorNumber(extremeValues, maxValue);
    }

    /**
     * Reconvert number to original value
     * @param number Number used for computation
     * @param factor Multiplying factor
     * @returns Original value
     */
    function getNumber(number, factor) {
      if (factor !== 1) number /= factor;
      const rounded = Math.round(number);
      if (rounded !== number && Math.abs(rounded - number) <= Number.EPSILON) {
        return rounded;
      }
      return number;
    }

    function peakTableCreator(data, options = {}) {
      const {
        xFactor = 1,
        yFactor = 1
      } = options.info || {};
      let firstX = Number.POSITIVE_INFINITY;
      let lastX = Number.NEGATIVE_INFINITY;
      let firstY = Number.POSITIVE_INFINITY;
      let lastY = Number.NEGATIVE_INFINITY;
      const lines = [];
      for (let i = 0; i < data.x.length; i++) {
        const x = data.x[i];
        const y = data.y[i];
        if (firstX > x) {
          firstX = x;
        }
        if (lastX < x) {
          lastX = x;
        }
        if (firstY > y) {
          firstY = y;
        }
        if (lastY < y) {
          lastY = y;
        }
      }
      lines.push(`##FIRSTX=${firstX}`);
      lines.push(`##LASTX=${lastX}`);
      lines.push(`##FIRSTY=${firstY}`);
      lines.push(`##LASTY=${lastY}`);
      lines.push(`##XFACTOR=${xFactor}`);
      lines.push(`##YFACTOR=${yFactor}`);
      lines.push('##PEAK TABLE=(XY..XY)');
      for (let i = 0; i < data.x.length; i++) {
        lines.push(`${getNumber(data.x[i], xFactor)} ${getNumber(data.y[i], yFactor)}`);
      }
      return lines;
    }

    function rescaleAndEnsureInteger(data, factor = 1) {
      if (factor === 1) return data.map(value => Math.round(value));
      return xDivide(data, factor).map(value => Math.round(value));
    }

    /**
     * class encodes a integer vector as a String in order to store it in a text file.
     * The algorithms used to encode the data are describe in:
     *            http://www.iupac.org/publications/pac/pdf/2001/pdf/7311x1765.pdf
     */
    const newLine = '\n';
    const pseudoDigits = [['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], ['@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'], ['@', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'], ['%', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R'], ['%', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r'], [' ', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 's']];
    const SQZ_P = 1;
    const SQZ_N = 2;
    const DIF_P = 3;
    const DIF_N = 4;
    const DUP = 5;
    const maxLinelength = 100;
    /**
     * This function encodes the given vector. The xyEncoding format is specified by the
     * xyEncoding option
     * @param xyEncoding: ('FIX','SQZ','DIF','DIFDUP','CVS','PAC') Default 'DIFDUP'
     * @return {string}
     */
    function vectorEncoder(data, firstX, intervalX, xyEncoding) {
      switch (xyEncoding) {
        case 'FIX':
          return fixEncoding(data, firstX, intervalX);
        case 'SQZ':
          return squeezedEncoding(data, firstX, intervalX);
        case 'DIF':
          return differenceEncoding(data, firstX, intervalX);
        case 'DIFDUP':
          return differenceDuplicateEncoding(data, firstX, intervalX);
        case 'CSV':
          return commaSeparatedValuesEncoding(data, firstX, intervalX);
        case 'PAC':
          return packedEncoding(data, firstX, intervalX);
        default:
          return differenceEncoding(data, firstX, intervalX);
      }
    }
    /**
     * @private
     * No data compression used. The data is separated by a comma(',').
     */
    function commaSeparatedValuesEncoding(data, firstX, intervalX) {
      return fixEncoding(data, firstX, intervalX, ',');
    }
    /**
     * @private
     * No data compression used. The data is separated by the specified separator.
     */
    function fixEncoding(data, firstX, intervalX, separator = ' ') {
      let outputData = '';
      let j = 0;
      const dataLength = data.length;
      while (j < dataLength - 7) {
        outputData += String(Math.ceil(firstX + j * intervalX));
        for (let i = 0; i < 8; i++) {
          outputData += `${separator}${data[j++]}`;
        }
        outputData += newLine;
      }
      if (j < dataLength) {
        // We add last numbers
        outputData += String(Math.ceil(firstX + j * intervalX));
        for (let i = j; i < dataLength; i++) {
          outputData += `${separator}${data[i]}`;
        }
      }
      return outputData;
    }
    /**
     * @private
     * No data compression used. The data is separated by the sign of the number.
     */
    function packedEncoding(data, firstX, intervalX) {
      let outputData = '';
      let j = 0;
      const dataLength = data.length;
      while (j < dataLength - 7) {
        outputData += String(Math.ceil(firstX + j * intervalX));
        for (let i = 0; i < 8; i++) {
          outputData += data[j] < 0 ? String(data[j++]) : `+${data[j++]}`;
        }
        outputData += newLine;
      }
      if (j < dataLength) {
        // We add last numbers
        outputData += String(Math.ceil(firstX + j * intervalX));
        for (let i = j; i < dataLength; i++) {
          outputData += data[i] < 0 ? String(data[i]) : `+${data[i]}`;
        }
      }
      return outputData;
    }
    /**
     * @private
     * Data compression is possible using the squeezed form (SQZ) in which the delimiter, the leading digit,
     * and sign are replaced by a pseudo-digit from Table 1. For example, the Y-values 30, 32 would be
     * represented as C0C2.
     */
    function squeezedEncoding(data, firstX, intervalX) {
      let outputData = '';
      // String outputData = new String();
      let j = 0;
      const dataLength = data.length;
      while (j < dataLength - 10) {
        outputData += String(Math.ceil(firstX + j * intervalX));
        for (let i = 0; i < 10; i++) {
          outputData += squeezedDigit(data[j++].toString());
        }
        outputData += newLine;
      }
      if (j < dataLength) {
        // We add last numbers
        outputData += String(Math.ceil(firstX + j * intervalX));
        for (let i = j; i < dataLength; i++) {
          outputData += squeezedDigit(data[i].toString());
        }
      }
      return outputData;
    }
    /**
     * @private
     * Duplicate suppression xyEncoding
     */
    function differenceDuplicateEncoding(data, firstX, intervalX) {
      let mult = 0;
      let index = 0;
      let charCount = 0;
      // We built a string where we store the encoded data.
      let encodedData = '';
      let encodedNumber = '';
      let temp = '';
      // We calculate the differences vector
      const diffData = new Array(data.length - 1);
      for (let i = 0; i < diffData.length; i++) {
        diffData[i] = data[i + 1] - data[i];
      }
      // We simulate a line carry
      const numDiff = diffData.length;
      while (index < numDiff) {
        if (charCount === 0) {
          // Start line
          encodedNumber = `${Math.ceil(firstX + index * intervalX)}${squeezedDigit(data[index].toString())}${differenceDigit(diffData[index].toString())}`;
          encodedData += encodedNumber;
          charCount += encodedNumber.length;
        } else if (diffData[index - 1] === diffData[index]) {
          // Try to insert next difference
          mult++;
        } else if (mult > 0) {
          // Now we know that it can be in line
          mult++;
          encodedNumber = duplicateDigit(mult.toString());
          encodedData += encodedNumber;
          charCount += encodedNumber.length;
          mult = 0;
          index--;
        } else {
          // Check if it fits, otherwise start a new line
          encodedNumber = differenceDigit(diffData[index].toString());
          if (encodedNumber.length + charCount < maxLinelength) {
            encodedData += encodedNumber;
            charCount += encodedNumber.length;
          } else {
            // start a new line
            encodedData += newLine;
            temp = `${Math.ceil(firstX + index * intervalX)}${squeezedDigit(data[index].toString())}${encodedNumber}`;
            encodedData += temp; // Each line start with first index number.
            charCount = temp.length;
          }
        }
        index++;
      }
      if (mult > 0) {
        encodedData += duplicateDigit((mult + 1).toString());
      }
      // We insert the last data from fid. It is done to control of data
      // The last line start with the number of datas in the fid.
      encodedData += `${newLine}${Math.ceil(firstX + index * intervalX)}${squeezedDigit(data[index].toString())}`;
      return encodedData;
    }
    /**
     * @private
     * Differential xyEncoding
     */
    function differenceEncoding(data, firstX, intervalX) {
      let index = 0;
      let charCount = 0;
      let i;
      let encodedData = '';
      let encodedNumber = '';
      let temp = '';
      // We calculate the differences vector
      const diffData = new Array(data.length - 1);
      for (i = 0; i < diffData.length; i++) {
        diffData[i] = data[i + 1] - data[i];
      }
      const numDiff = diffData.length;
      while (index < numDiff) {
        if (charCount === 0) {
          // We convert the first number.
          encodedNumber = `${Math.ceil(firstX + index * intervalX)}${squeezedDigit(data[index].toString())}${differenceDigit(diffData[index].toString())}`;
          encodedData += encodedNumber;
          charCount += encodedNumber.length;
        } else {
          encodedNumber = differenceDigit(diffData[index].toString());
          if (encodedNumber.length + charCount < maxLinelength) {
            encodedData += encodedNumber;
            charCount += encodedNumber.length;
          } else {
            encodedData += newLine;
            temp = `${Math.ceil(firstX + index * intervalX)}${squeezedDigit(data[index].toString())}${encodedNumber}`;
            encodedData += temp; // Each line start with first index number.
            charCount = temp.length;
          }
        }
        index++;
      }
      // We insert the last number from data. It is done to control of data
      encodedData += `${newLine}${Math.ceil(firstX + index * intervalX)}${squeezedDigit(data[index].toString())}`;
      return encodedData;
    }
    /**
     * @private
     * Convert number to the ZQZ format, using pseudo digits.
     */
    function squeezedDigit(num) {
      let sqzDigits = '';
      if (num.startsWith('-')) {
        sqzDigits += pseudoDigits[SQZ_N][num.charCodeAt(1) - 48];
        if (num.length > 2) {
          sqzDigits += num.substring(2);
        }
      } else {
        sqzDigits += pseudoDigits[SQZ_P][num.charCodeAt(0) - 48];
        if (num.length > 1) {
          sqzDigits += num.substring(1);
        }
      }
      return sqzDigits;
    }
    /**
     * Convert number to the DIF format, using pseudo digits.
     */
    function differenceDigit(num) {
      let diffDigits = '';
      if (num.startsWith('-')) {
        diffDigits += pseudoDigits[DIF_N][num.charCodeAt(1) - 48];
        if (num.length > 2) {
          diffDigits += num.substring(2);
        }
      } else {
        diffDigits += pseudoDigits[DIF_P][num.charCodeAt(0) - 48];
        if (num.length > 1) {
          diffDigits += num.substring(1);
        }
      }
      return diffDigits;
    }
    /**
     * Convert number to the DUP format, using pseudo digits.
     */
    function duplicateDigit(num) {
      let dupDigits = '';
      dupDigits += pseudoDigits[DUP][num.charCodeAt(0) - 48];
      if (num.length > 1) {
        dupDigits += num.substring(1);
      }
      return dupDigits;
    }

    function xyDataCreator(data, options = {}) {
      const {
        xyEncoding = 'DIF'
      } = options;
      const {
        xFactor = 1,
        yFactor = 1
      } = options.info || {};
      const firstX = data.x[0];
      const lastX = data.x[data.x.length - 1];
      const firstY = data.y[0];
      const lastY = data.y[data.y.length - 1];
      const nbPoints = data.x.length;
      const deltaX = (lastX - firstX) / (nbPoints - 1);
      const lines = [];
      lines.push(`##FIRSTX=${firstX}`);
      lines.push(`##LASTX=${lastX}`);
      lines.push(`##FIRSTY=${firstY}`);
      lines.push(`##LASTY=${lastY}`);
      lines.push(`##DELTAX=${deltaX}`);
      lines.push(`##XFACTOR=${xFactor}`);
      lines.push(`##YFACTOR=${yFactor}`);
      lines.push('##XYDATA=(X++(Y..Y))');
      const line = vectorEncoder(rescaleAndEnsureInteger(data.y, yFactor), firstX / xFactor, deltaX / xFactor, xyEncoding);
      if (line) lines.push(line);
      return lines;
    }

    /**
     * Create a jcamp
     * @param data object of array
     * @param [options={meta:{},info:{}} - metadata object
     * @returns JCAMP of the input
     */
    function fromJSON(data, options = {}) {
      const {
        meta = {},
        info = {},
        xyEncoding
      } = options;
      const {
        title = '',
        owner = '',
        origin = '',
        dataType = '',
        xUnits = '',
        yUnits = '',
        ...resInfo
      } = info;
      let {
        xFactor,
        yFactor
      } = info;
      data = {
        x: data.x,
        y: data.y
      };
      let header = `##TITLE=${title}
##JCAMP-DX=4.24
##DATA TYPE=${dataType}
##ORIGIN=${origin}
##OWNER=${owner}
##XUNITS=${xUnits}
##YUNITS=${yUnits}\n`;
      header += addInfoData(resInfo, {
        prefix: '##'
      });
      header += addInfoData(meta);
      // we leave the header and utf8 fonts ${header.replace(/[^\t\n\x20-\x7F]/g, '')
      if (xyEncoding) {
        xFactor = getBestFactor(data.x, {
          factor: xFactor
        });
        yFactor = getBestFactor(data.y, {
          factor: yFactor
        });
        return `${header}##NPOINTS=${data.x.length}
${xyDataCreator(data, {
      info: {
        xFactor,
        yFactor
      },
      xyEncoding
    }).join('\n')}
##END=`;
      } else {
        if (xFactor === undefined) xFactor = 1;
        if (yFactor === undefined) yFactor = 1;
        if (xFactor !== 1) {
          //@ts-expect-error xFactor is always defined
          data.x = data.x.map(value => value / xFactor);
        }
        if (yFactor !== 1) {
          //@ts-expect-error yFactor is always defined
          data.y = data.y.map(value => value / yFactor);
        }
        return `${header}##NPOINTS=${data.x.length}
${peakTableCreator(data, {
      info: {
        xFactor,
        yFactor
      }
    }).join('\n')}
##END=`;
      }
    }

    /**
     * Create a jcamp from variables
     */
    function fromVariables(/** object of variables */
    variables, options = {}) {
      const {
        info = {},
        meta = {},
        forceNtuples = false
      } = options;
      const jcampOptions = {
        info,
        meta
      };
      const keys = Object.keys(variables).map(key => key.toLowerCase());
      if (!forceNtuples && keys.length === 2) {
        const x = variables.x;
        const xLabel = x.label || 'x';
        if (variables.x.units) {
          if (xLabel.includes(variables.x.units)) {
            jcampOptions.info.xUnits = xLabel;
          } else {
            jcampOptions.info.xUnits = `${xLabel} (${variables.x.units})`;
          }
        } else {
          jcampOptions.info.xUnits = xLabel;
        }
        const y = variables.y;
        const yLabel = y.label || 'y';
        if (variables.y.units) {
          if (yLabel.includes(variables.y.units)) {
            jcampOptions.info.xUnits = yLabel;
          } else {
            jcampOptions.info.yUnits = `${yLabel} (${variables.y.units})`;
          }
        } else {
          jcampOptions.info.yUnits = yLabel;
        }
        const xData = variables.x.data;
        const yData = variables.y.data;
        checkNumberOrArray(xData);
        checkNumberOrArray(yData);
        return fromJSON({
          x: xData,
          y: yData
        }, jcampOptions);
      } else {
        return creatorNtuples(variables, options);
      }
    }

    function toJcamps(analysis, options = {}) {
      const jcamps = [];
      for (const spectrum of analysis.spectra) {
        jcamps.push(getJcamp(spectrum, options));
      }
      return jcamps;
    }
    function getJcamp(spectrum, options) {
      const {
        info = {},
        meta = {}
      } = options;
      const jcampOptions = {
        info: {
          title: spectrum.title,
          dataType: spectrum.dataType,
          ...info
        },
        meta: {
          ...spectrum.meta,
          ...meta
        }
      };
      return fromVariables(spectrum.variables, jcampOptions);
    }

    function toJcamp(analysis, options = {}) {
      return toJcamps(analysis, options).join('\n');
    }

    /**
     * Based on a x value we will return a peak
     * if you set optimize=True the returned positions will be
     * the closest actual datapoints to the fitted peak location.
     * the x/y of the fitted peak will be in xOptimized and yOptimized
     * @param spectrum
     * @param target
     * @param options
     */
    function peakPicking$3(spectrum, /** value to search (on x axis) */
    target, options = {}) {
      const {
        xVariable = 'x',
        yVariable = 'y',
        optimize: optimize$1 = false,
        max: isMax = true,
        shape = {
          kind: 'gaussian',
          fwhm: 1
        }
      } = options;
      const x = spectrum.variables[xVariable]?.data;
      let y;
      if (!isMax) {
        y = spectrum.variables[yVariable]?.data.slice(); // do deep copy as we maybe need to flip sign
      } else {
        y = spectrum.variables[yVariable]?.data;
      }
      if (!x || !y) return;
      const targetIndex = xFindClosestIndex(x, target);
      let optimizedPeak;
      let optimizedIndex;
      const result = {};
      if (optimize$1) {
        if (!isMax) {
          const maximumY = xMaxValue(y);
          for (let i = 0; i < y.length; i++) {
            y[i] *= -1;
            y[i] += maximumY; // This makes it somewhat more robust
          }
        }
        optimizedPeak = optimize({
          x,
          y
        }, [{
          x: x[targetIndex],
          y: y[targetIndex]
        }], {
          shape
        });
        optimizedIndex = xFindClosestIndex(x, optimizedPeak.peaks[0].x);
        for (const [key, variable] of Object.entries(spectrum.variables)) {
          result[key] = variable.data[optimizedIndex];
        }
        result.optimized = optimizedPeak.peaks[0];
      } else {
        for (const [key, variable] of Object.entries(spectrum.variables)) {
          result[key] = variable.data[targetIndex];
        }
      }
      return result;
    }

    const JSGraph$1 = {
      getJSGraph,
      getNormalizationAnnotations
    };

    var src$8 = {};

    var Spectrum$1 = {};

    var require$$0$3 = /*@__PURE__*/getAugmentedNamespace(libEsm$2);

    var require$$2 = /*@__PURE__*/getAugmentedNamespace(libEsm$1);

    var getBestPeaks$1 = {};

    Object.defineProperty(getBestPeaks$1, "__esModule", {
      value: true
    });
    getBestPeaks$1.getBestPeaks = getBestPeaks;
    const ml_spectra_processing_1$8 = require$$0$3;
    /**
     * Filter the array by taking the higher peaks and only
     * keep one per slot.
     * There are 2 different slots, the smallest one will have the
     * extra annotation `close` to true
     * @param {array} peaks - array of all the peaks
     * @param {object} [options={}]
     * @param {number} [options.from] - min X value of the window to consider
     * @param {number} [options.to] - max X value of the window to consider
     * @param {number} [options.minValue=Number.NEGATIVE_INFINITY] - min Y value of the window to consider
     * @param {number} [options.maxValue=Number.POSITIVE_INFINITY] - max Y value of the window to consider
     * @param {number} [options.searchMonoisotopicRatio=0] - search previous peaks with at least ratio height
     * @param {number} [options.limit=20] - max number of peaks
     * @param {number} [options.threshold=0.01] - minimal intensity compare to base peak
     * @param {number} [options.numberSlots=10] - define the number of slots and indirectly the slot width
     * @param {number} [options.numberCloseSlots=50]
     * @returns {array} - copy of peaks with 'close' annotation
     */
    function getBestPeaks(peaks, options = {}) {
      const {
        searchMonoisotopicRatio = 0,
        from = (0, ml_spectra_processing_1$8.xyObjectMinXPoint)(peaks).x,
        to = (0, ml_spectra_processing_1$8.xyObjectMaxXPoint)(peaks).x,
        limit = 20,
        threshold = 0.01,
        numberCloseSlots = 50,
        numberSlots = 10,
        minValue = Number.NEGATIVE_INFINITY,
        maxValue = Number.POSITIVE_INFINITY
      } = options;
      let slot = (to - from) / numberSlots;
      let closeSlot = (to - from) / numberCloseSlots;
      let selected = peaks.filter(peak => peak.x >= from && peak.x <= to).filter(peak => peak.y >= minValue && peak.y <= maxValue).map(peak => {
        return {
          peak,
          monoisotopic: false
        };
      });
      if (searchMonoisotopicRatio) {
        selected = selected.sort((a, b) => b.peak.x - a.peak.x);
        for (let i = 0; i < selected.length; i++) {
          let item = selected[i];
          for (let j = i + 1; j < selected.length; j++) {
            let nextItem = selected[j];
            if (item.peak.x - nextItem.peak.x < 0.09) continue;
            if (item.peak.x - nextItem.peak.x > 1.1) break;
            if (nextItem.peak.y > item.peak.y * searchMonoisotopicRatio) {
              item.monoisotopic = false;
              nextItem.monoisotopic = true;
              break;
            }
          }
        }
      }
      selected = selected.sort((a, b) => {
        if (a.monoisotopic && !b.monoisotopic) return -1;
        if (b.monoisotopic && !a.monoisotopic) return 1;
        return b.peak.y - a.peak.y;
      });
      let toReturn = [];
      if (selected.length === 0) return [];
      let minY = selected[0].peak.y * threshold;
      peakLoop: for (let item of selected) {
        if (item.peak.y < minY) {
          if (item.monoisotopic) {
            continue;
          } else {
            break;
          }
        }
        let close = false;
        for (let existing of toReturn) {
          if (Math.abs(existing.x - item.peak.x) < closeSlot) {
            continue peakLoop;
          }
          if (Math.abs(existing.x - item.peak.x) < slot) {
            close = true;
          }
        }
        let newPeak = structuredClone(item.peak);
        newPeak.close = close;
        toReturn.push(newPeak);
        if (toReturn.length === limit) break;
      }
      return toReturn.sort((a, b) => a.x - b.x);
    }

    var getFragmentPeaks$1 = {};

    var src$7 = {};

    var parse$1 = {};

    var src$6 = {};

    Object.defineProperty(src$6, "__esModule", {
      value: true
    });
    src$6.atomSorter = atomSorter;
    /**
     * Implementation of the Hill system for sorting atoms
     * https://en.wikipedia.org/wiki/Chemical_formula#Hill_system
     * @param a - first atom to compare
     * @param b - second atom to compare
     * @returns A value suitable for use in Array.prototype.sort.
     */
    function atomSorter(a, b) {
      if (a === b) return 0;
      if (a === 'C') return -1;
      if (b === 'C') return 1;
      if (a === 'H') return -1;
      if (b === 'H') return 1;
      if (a < b) return -1;
      return 1;
    }

    var src$5 = {};

    var constants = {};

    Object.defineProperty(constants, "__esModule", {
      value: true
    });
    constants.ELECTRON_MASS = void 0;
    constants.ELECTRON_MASS = 5.4857990907e-4;

    var elements$1 = {};

    var elementsAndIsotopes = {};

    Object.defineProperty(elementsAndIsotopes, "__esModule", {
      value: true
    });
    elementsAndIsotopes.elementsAndIsotopes = void 0;
    elementsAndIsotopes.elementsAndIsotopes = [{
      number: 1,
      isotopes: [{
        nominal: 1,
        mass: 1.00782503223,
        abundance: 0.999885
      }, {
        nominal: 2,
        mass: 2.01410177812,
        abundance: 0.000115
      }, {
        nominal: 3,
        mass: 3.0160492779
      }, {
        nominal: 4,
        mass: 4.02643
      }, {
        nominal: 5,
        mass: 5.035311
      }, {
        nominal: 6,
        mass: 6.04496
      }, {
        nominal: 7,
        mass: 7.0527
      }],
      symbol: 'H',
      mass: 1.0079407540557772,
      name: 'Hydrogen',
      monoisotopicMass: 1.00782503223
    }, {
      number: 2,
      isotopes: [{
        nominal: 3,
        mass: 3.0160293201,
        abundance: 0.00000134
      }, {
        nominal: 4,
        mass: 4.00260325413,
        abundance: 0.99999866
      }, {
        nominal: 5,
        mass: 5.012057
      }, {
        nominal: 6,
        mass: 6.018885891
      }, {
        nominal: 7,
        mass: 7.0279907
      }, {
        nominal: 8,
        mass: 8.03393439
      }, {
        nominal: 9,
        mass: 9.043946
      }, {
        nominal: 10,
        mass: 10.05279
      }],
      symbol: 'He',
      mass: 4.002601932120929,
      name: 'Helium',
      monoisotopicMass: 4.00260325413
    }, {
      number: 3,
      isotopes: [{
        nominal: 3,
        mass: 3.0308
      }, {
        nominal: 4,
        mass: 4.02719
      }, {
        nominal: 5,
        mass: 5.012538
      }, {
        nominal: 6,
        mass: 6.0151228874,
        abundance: 0.0759
      }, {
        nominal: 7,
        mass: 7.0160034366,
        abundance: 0.9241
      }, {
        nominal: 8,
        mass: 8.022486246
      }, {
        nominal: 9,
        mass: 9.02679019
      }, {
        nominal: 10,
        mass: 10.035483
      }, {
        nominal: 11,
        mass: 11.04372358
      }, {
        nominal: 12,
        mass: 12.052517
      }, {
        nominal: 13,
        mass: 13.06263
      }],
      symbol: 'Li',
      mass: 6.94003660291572,
      name: 'Lithium',
      monoisotopicMass: 7.0160034366
    }, {
      number: 4,
      isotopes: [{
        nominal: 5,
        mass: 5.0399
      }, {
        nominal: 6,
        mass: 6.0197264
      }, {
        nominal: 7,
        mass: 7.016928717
      }, {
        nominal: 8,
        mass: 8.005305102
      }, {
        nominal: 9,
        mass: 9.012183065,
        abundance: 1
      }, {
        nominal: 10,
        mass: 10.013534695
      }, {
        nominal: 11,
        mass: 11.02166108
      }, {
        nominal: 12,
        mass: 12.0269221
      }, {
        nominal: 13,
        mass: 13.036135
      }, {
        nominal: 14,
        mass: 14.04289
      }, {
        nominal: 15,
        mass: 15.05342
      }, {
        nominal: 16,
        mass: 16.06167
      }],
      symbol: 'Be',
      mass: 9.012183065,
      name: 'Beryllium',
      monoisotopicMass: 9.012183065
    }, {
      number: 5,
      isotopes: [{
        nominal: 6,
        mass: 6.0508
      }, {
        nominal: 7,
        mass: 7.029712
      }, {
        nominal: 8,
        mass: 8.0246073
      }, {
        nominal: 9,
        mass: 9.01332965
      }, {
        nominal: 10,
        mass: 10.01293695,
        abundance: 0.199
      }, {
        nominal: 11,
        mass: 11.00930536,
        abundance: 0.801
      }, {
        nominal: 12,
        mass: 12.0143527
      }, {
        nominal: 13,
        mass: 13.0177802
      }, {
        nominal: 14,
        mass: 14.025404
      }, {
        nominal: 15,
        mass: 15.031088
      }, {
        nominal: 16,
        mass: 16.039842
      }, {
        nominal: 17,
        mass: 17.04699
      }, {
        nominal: 18,
        mass: 18.05566
      }, {
        nominal: 19,
        mass: 19.0631
      }, {
        nominal: 20,
        mass: 20.07207
      }, {
        nominal: 21,
        mass: 21.08129
      }],
      symbol: 'B',
      mass: 10.811028046410001,
      name: 'Boron',
      monoisotopicMass: 11.00930536
    }, {
      number: 6,
      isotopes: [{
        nominal: 8,
        mass: 8.037643
      }, {
        nominal: 9,
        mass: 9.0310372
      }, {
        nominal: 10,
        mass: 10.01685331
      }, {
        nominal: 11,
        mass: 11.0114336
      }, {
        nominal: 12,
        mass: 12,
        abundance: 0.9893
      }, {
        nominal: 13,
        mass: 13.00335483507,
        abundance: 0.0107
      }, {
        nominal: 14,
        mass: 14.0032419884
      }, {
        nominal: 15,
        mass: 15.01059926
      }, {
        nominal: 16,
        mass: 16.0147013
      }, {
        nominal: 17,
        mass: 17.022577
      }, {
        nominal: 18,
        mass: 18.026751
      }, {
        nominal: 19,
        mass: 19.0348
      }, {
        nominal: 20,
        mass: 20.04032
      }, {
        nominal: 21,
        mass: 21.049
      }, {
        nominal: 22,
        mass: 22.05753
      }, {
        nominal: 23,
        mass: 23.0689
      }],
      symbol: 'C',
      mass: 12.010735896735248,
      name: 'Carbon',
      monoisotopicMass: 12
    }, {
      number: 7,
      isotopes: [{
        nominal: 10,
        mass: 10.04165
      }, {
        nominal: 11,
        mass: 11.026091
      }, {
        nominal: 12,
        mass: 12.0186132
      }, {
        nominal: 13,
        mass: 13.00573861
      }, {
        nominal: 14,
        mass: 14.00307400443,
        abundance: 0.99636
      }, {
        nominal: 15,
        mass: 15.00010889888,
        abundance: 0.00364
      }, {
        nominal: 16,
        mass: 16.0061019
      }, {
        nominal: 17,
        mass: 17.008449
      }, {
        nominal: 18,
        mass: 18.014078
      }, {
        nominal: 19,
        mass: 19.017022
      }, {
        nominal: 20,
        mass: 20.023366
      }, {
        nominal: 21,
        mass: 21.02711
      }, {
        nominal: 22,
        mass: 22.03439
      }, {
        nominal: 23,
        mass: 23.04114
      }, {
        nominal: 24,
        mass: 24.05039
      }, {
        nominal: 25,
        mass: 25.0601
      }],
      symbol: 'N',
      mass: 14.006703211445798,
      name: 'Nitrogen',
      monoisotopicMass: 14.00307400443
    }, {
      number: 8,
      isotopes: [{
        nominal: 12,
        mass: 12.034262
      }, {
        nominal: 13,
        mass: 13.024815
      }, {
        nominal: 14,
        mass: 14.00859636
      }, {
        nominal: 15,
        mass: 15.00306562
      }, {
        nominal: 16,
        mass: 15.99491461957,
        abundance: 0.99757
      }, {
        nominal: 17,
        mass: 16.9991317565,
        abundance: 0.00038
      }, {
        nominal: 18,
        mass: 17.99915961286,
        abundance: 0.00205
      }, {
        nominal: 19,
        mass: 19.003578
      }, {
        nominal: 20,
        mass: 20.00407535
      }, {
        nominal: 21,
        mass: 21.008655
      }, {
        nominal: 22,
        mass: 22.009966
      }, {
        nominal: 23,
        mass: 23.015696
      }, {
        nominal: 24,
        mass: 24.01986
      }, {
        nominal: 25,
        mass: 25.02936
      }, {
        nominal: 26,
        mass: 26.03729
      }, {
        nominal: 27,
        mass: 27.04772
      }, {
        nominal: 28,
        mass: 28.05591
      }],
      symbol: 'O',
      mass: 15.999404924318277,
      name: 'Oxygen',
      monoisotopicMass: 15.99491461957
    }, {
      number: 9,
      isotopes: [{
        nominal: 14,
        mass: 14.034315
      }, {
        nominal: 15,
        mass: 15.018043
      }, {
        nominal: 16,
        mass: 16.0114657
      }, {
        nominal: 17,
        mass: 17.00209524
      }, {
        nominal: 18,
        mass: 18.00093733
      }, {
        nominal: 19,
        mass: 18.99840316273,
        abundance: 1
      }, {
        nominal: 20,
        mass: 19.999981252
      }, {
        nominal: 21,
        mass: 20.9999489
      }, {
        nominal: 22,
        mass: 22.002999
      }, {
        nominal: 23,
        mass: 23.003557
      }, {
        nominal: 24,
        mass: 24.008115
      }, {
        nominal: 25,
        mass: 25.012199
      }, {
        nominal: 26,
        mass: 26.020038
      }, {
        nominal: 27,
        mass: 27.02644
      }, {
        nominal: 28,
        mass: 28.03534
      }, {
        nominal: 29,
        mass: 29.04254
      }, {
        nominal: 30,
        mass: 30.05165
      }, {
        nominal: 31,
        mass: 31.05971
      }],
      symbol: 'F',
      mass: 18.99840316273,
      name: 'Fluorine',
      monoisotopicMass: 18.99840316273
    }, {
      number: 10,
      isotopes: [{
        nominal: 16,
        mass: 16.02575
      }, {
        nominal: 17,
        mass: 17.01771396
      }, {
        nominal: 18,
        mass: 18.0057087
      }, {
        nominal: 19,
        mass: 19.00188091
      }, {
        nominal: 20,
        mass: 19.9924401762,
        abundance: 0.9048
      }, {
        nominal: 21,
        mass: 20.993846685,
        abundance: 0.0027
      }, {
        nominal: 22,
        mass: 21.991385114,
        abundance: 0.0925
      }, {
        nominal: 23,
        mass: 22.99446691
      }, {
        nominal: 24,
        mass: 23.99361065
      }, {
        nominal: 25,
        mass: 24.997789
      }, {
        nominal: 26,
        mass: 26.000515
      }, {
        nominal: 27,
        mass: 27.007553
      }, {
        nominal: 28,
        mass: 28.01212
      }, {
        nominal: 29,
        mass: 29.01975
      }, {
        nominal: 30,
        mass: 30.02473
      }, {
        nominal: 31,
        mass: 31.0331
      }, {
        nominal: 32,
        mass: 32.03972
      }, {
        nominal: 33,
        mass: 33.04938
      }, {
        nominal: 34,
        mass: 34.05673
      }],
      symbol: 'Ne',
      mass: 20.18004638052026,
      name: 'Neon',
      monoisotopicMass: 19.9924401762
    }, {
      number: 11,
      isotopes: [{
        nominal: 18,
        mass: 18.02688
      }, {
        nominal: 19,
        mass: 19.01388
      }, {
        nominal: 20,
        mass: 20.0073544
      }, {
        nominal: 21,
        mass: 20.99765469
      }, {
        nominal: 22,
        mass: 21.99443741
      }, {
        nominal: 23,
        mass: 22.989769282,
        abundance: 1
      }, {
        nominal: 24,
        mass: 23.99096295
      }, {
        nominal: 25,
        mass: 24.989954
      }, {
        nominal: 26,
        mass: 25.9926346
      }, {
        nominal: 27,
        mass: 26.9940765
      }, {
        nominal: 28,
        mass: 27.998939
      }, {
        nominal: 29,
        mass: 29.0028771
      }, {
        nominal: 30,
        mass: 30.0090979
      }, {
        nominal: 31,
        mass: 31.013163
      }, {
        nominal: 32,
        mass: 32.02019
      }, {
        nominal: 33,
        mass: 33.02573
      }, {
        nominal: 34,
        mass: 34.03359
      }, {
        nominal: 35,
        mass: 35.04062
      }, {
        nominal: 36,
        mass: 36.04929
      }, {
        nominal: 37,
        mass: 37.05705
      }],
      symbol: 'Na',
      mass: 22.989769282,
      name: 'Sodium',
      monoisotopicMass: 22.989769282
    }, {
      number: 12,
      isotopes: [{
        nominal: 19,
        mass: 19.034169
      }, {
        nominal: 20,
        mass: 20.01885
      }, {
        nominal: 21,
        mass: 21.011716
      }, {
        nominal: 22,
        mass: 21.99957065
      }, {
        nominal: 23,
        mass: 22.99412421
      }, {
        nominal: 24,
        mass: 23.985041697,
        abundance: 0.7899
      }, {
        nominal: 25,
        mass: 24.985836976,
        abundance: 0.1
      }, {
        nominal: 26,
        mass: 25.982592968,
        abundance: 0.1101
      }, {
        nominal: 27,
        mass: 26.984340624
      }, {
        nominal: 28,
        mass: 27.9838767
      }, {
        nominal: 29,
        mass: 28.988617
      }, {
        nominal: 30,
        mass: 29.9904629
      }, {
        nominal: 31,
        mass: 30.996648
      }, {
        nominal: 32,
        mass: 31.9991102
      }, {
        nominal: 33,
        mass: 33.0053271
      }, {
        nominal: 34,
        mass: 34.008935
      }, {
        nominal: 35,
        mass: 35.01679
      }, {
        nominal: 36,
        mass: 36.02188
      }, {
        nominal: 37,
        mass: 37.03037
      }, {
        nominal: 38,
        mass: 38.03658
      }, {
        nominal: 39,
        mass: 39.04538
      }, {
        nominal: 40,
        mass: 40.05218
      }],
      symbol: 'Mg',
      mass: 24.3050516198371,
      name: 'Magnesium',
      monoisotopicMass: 23.985041697
    }, {
      number: 13,
      isotopes: [{
        nominal: 21,
        mass: 21.02897
      }, {
        nominal: 22,
        mass: 22.01954
      }, {
        nominal: 23,
        mass: 23.00724435
      }, {
        nominal: 24,
        mass: 23.9999489
      }, {
        nominal: 25,
        mass: 24.9904281
      }, {
        nominal: 26,
        mass: 25.986891904
      }, {
        nominal: 27,
        mass: 26.98153853,
        abundance: 1
      }, {
        nominal: 28,
        mass: 27.98191021
      }, {
        nominal: 29,
        mass: 28.9804565
      }, {
        nominal: 30,
        mass: 29.98296
      }, {
        nominal: 31,
        mass: 30.983945
      }, {
        nominal: 32,
        mass: 31.988085
      }, {
        nominal: 33,
        mass: 32.990909
      }, {
        nominal: 34,
        mass: 33.996705
      }, {
        nominal: 35,
        mass: 34.999764
      }, {
        nominal: 36,
        mass: 36.00639
      }, {
        nominal: 37,
        mass: 37.01053
      }, {
        nominal: 38,
        mass: 38.0174
      }, {
        nominal: 39,
        mass: 39.02254
      }, {
        nominal: 40,
        mass: 40.03003
      }, {
        nominal: 41,
        mass: 41.03638
      }, {
        nominal: 42,
        mass: 42.04384
      }, {
        nominal: 43,
        mass: 43.05147
      }],
      symbol: 'Al',
      mass: 26.98153853,
      name: 'Aluminium',
      monoisotopicMass: 26.98153853
    }, {
      number: 14,
      isotopes: [{
        nominal: 22,
        mass: 22.03579
      }, {
        nominal: 23,
        mass: 23.02544
      }, {
        nominal: 24,
        mass: 24.011535
      }, {
        nominal: 25,
        mass: 25.004109
      }, {
        nominal: 26,
        mass: 25.99233384
      }, {
        nominal: 27,
        mass: 26.98670481
      }, {
        nominal: 28,
        mass: 27.97692653465,
        abundance: 0.92223
      }, {
        nominal: 29,
        mass: 28.9764946649,
        abundance: 0.04685
      }, {
        nominal: 30,
        mass: 29.973770136,
        abundance: 0.03092
      }, {
        nominal: 31,
        mass: 30.975363194
      }, {
        nominal: 32,
        mass: 31.97415154
      }, {
        nominal: 33,
        mass: 32.97797696
      }, {
        nominal: 34,
        mass: 33.978576
      }, {
        nominal: 35,
        mass: 34.984583
      }, {
        nominal: 36,
        mass: 35.986695
      }, {
        nominal: 37,
        mass: 36.992921
      }, {
        nominal: 38,
        mass: 37.995523
      }, {
        nominal: 39,
        mass: 39.002491
      }, {
        nominal: 40,
        mass: 40.00583
      }, {
        nominal: 41,
        mass: 41.01301
      }, {
        nominal: 42,
        mass: 42.01778
      }, {
        nominal: 43,
        mass: 43.0248
      }, {
        nominal: 44,
        mass: 44.03061
      }, {
        nominal: 45,
        mass: 45.03995
      }],
      symbol: 'Si',
      mass: 28.085498705705955,
      name: 'Silicon',
      monoisotopicMass: 27.97692653465
    }, {
      number: 15,
      isotopes: [{
        nominal: 24,
        mass: 24.03577
      }, {
        nominal: 25,
        mass: 25.02119
      }, {
        nominal: 26,
        mass: 26.01178
      }, {
        nominal: 27,
        mass: 26.999224
      }, {
        nominal: 28,
        mass: 27.9923266
      }, {
        nominal: 29,
        mass: 28.98180079
      }, {
        nominal: 30,
        mass: 29.97831375
      }, {
        nominal: 31,
        mass: 30.97376199842,
        abundance: 1
      }, {
        nominal: 32,
        mass: 31.973907643
      }, {
        nominal: 33,
        mass: 32.9717257
      }, {
        nominal: 34,
        mass: 33.97364589
      }, {
        nominal: 35,
        mass: 34.9733141
      }, {
        nominal: 36,
        mass: 35.97826
      }, {
        nominal: 37,
        mass: 36.979607
      }, {
        nominal: 38,
        mass: 37.984252
      }, {
        nominal: 39,
        mass: 38.986227
      }, {
        nominal: 40,
        mass: 39.99133
      }, {
        nominal: 41,
        mass: 40.994654
      }, {
        nominal: 42,
        mass: 42.00108
      }, {
        nominal: 43,
        mass: 43.00502
      }, {
        nominal: 44,
        mass: 44.01121
      }, {
        nominal: 45,
        mass: 45.01645
      }, {
        nominal: 46,
        mass: 46.02446
      }, {
        nominal: 47,
        mass: 47.03139
      }],
      symbol: 'P',
      mass: 30.97376199842,
      name: 'Phosphorus',
      monoisotopicMass: 30.97376199842
    }, {
      number: 16,
      isotopes: [{
        nominal: 26,
        mass: 26.02907
      }, {
        nominal: 27,
        mass: 27.01828
      }, {
        nominal: 28,
        mass: 28.00437
      }, {
        nominal: 29,
        mass: 28.996611
      }, {
        nominal: 30,
        mass: 29.98490703
      }, {
        nominal: 31,
        mass: 30.97955701
      }, {
        nominal: 32,
        mass: 31.9720711744,
        abundance: 0.9499
      }, {
        nominal: 33,
        mass: 32.9714589098,
        abundance: 0.0075
      }, {
        nominal: 34,
        mass: 33.967867004,
        abundance: 0.0425
      }, {
        nominal: 35,
        mass: 34.96903231
      }, {
        nominal: 36,
        mass: 35.96708071,
        abundance: 0.0001
      }, {
        nominal: 37,
        mass: 36.97112551
      }, {
        nominal: 38,
        mass: 37.9711633
      }, {
        nominal: 39,
        mass: 38.975134
      }, {
        nominal: 40,
        mass: 39.9754826
      }, {
        nominal: 41,
        mass: 40.9795935
      }, {
        nominal: 42,
        mass: 41.9810651
      }, {
        nominal: 43,
        mass: 42.9869076
      }, {
        nominal: 44,
        mass: 43.9901188
      }, {
        nominal: 45,
        mass: 44.99572
      }, {
        nominal: 46,
        mass: 46.00004
      }, {
        nominal: 47,
        mass: 47.00795
      }, {
        nominal: 48,
        mass: 48.0137
      }, {
        nominal: 49,
        mass: 49.02276
      }],
      symbol: 'S',
      mass: 32.06478740612706,
      name: 'Sulfur',
      monoisotopicMass: 31.9720711744
    }, {
      number: 17,
      isotopes: [{
        nominal: 28,
        mass: 28.02954
      }, {
        nominal: 29,
        mass: 29.01478
      }, {
        nominal: 30,
        mass: 30.00477
      }, {
        nominal: 31,
        mass: 30.992414
      }, {
        nominal: 32,
        mass: 31.98568464
      }, {
        nominal: 33,
        mass: 32.97745199
      }, {
        nominal: 34,
        mass: 33.973762485
      }, {
        nominal: 35,
        mass: 34.968852682,
        abundance: 0.7576
      }, {
        nominal: 36,
        mass: 35.968306809
      }, {
        nominal: 37,
        mass: 36.965902602,
        abundance: 0.2424
      }, {
        nominal: 38,
        mass: 37.96801044
      }, {
        nominal: 39,
        mass: 38.9680082
      }, {
        nominal: 40,
        mass: 39.970415
      }, {
        nominal: 41,
        mass: 40.970685
      }, {
        nominal: 42,
        mass: 41.97325
      }, {
        nominal: 43,
        mass: 42.97389
      }, {
        nominal: 44,
        mass: 43.97787
      }, {
        nominal: 45,
        mass: 44.98029
      }, {
        nominal: 46,
        mass: 45.98517
      }, {
        nominal: 47,
        mass: 46.98916
      }, {
        nominal: 48,
        mass: 47.99564
      }, {
        nominal: 49,
        mass: 49.00123
      }, {
        nominal: 50,
        mass: 50.00905
      }, {
        nominal: 51,
        mass: 51.01554
      }],
      symbol: 'Cl',
      mass: 35.452937582608,
      name: 'Chlorine',
      monoisotopicMass: 34.968852682
    }, {
      number: 18,
      isotopes: [{
        nominal: 30,
        mass: 30.02307
      }, {
        nominal: 31,
        mass: 31.01212
      }, {
        nominal: 32,
        mass: 31.9976378
      }, {
        nominal: 33,
        mass: 32.98992555
      }, {
        nominal: 34,
        mass: 33.98027009
      }, {
        nominal: 35,
        mass: 34.97525759
      }, {
        nominal: 36,
        mass: 35.967545105,
        abundance: 0.003336
      }, {
        nominal: 37,
        mass: 36.96677633
      }, {
        nominal: 38,
        mass: 37.96273211,
        abundance: 0.000629
      }, {
        nominal: 39,
        mass: 38.964313
      }, {
        nominal: 40,
        mass: 39.9623831237,
        abundance: 0.996035
      }, {
        nominal: 41,
        mass: 40.96450057
      }, {
        nominal: 42,
        mass: 41.9630457
      }, {
        nominal: 43,
        mass: 42.9656361
      }, {
        nominal: 44,
        mass: 43.9649238
      }, {
        nominal: 45,
        mass: 44.96803973
      }, {
        nominal: 46,
        mass: 45.968083
      }, {
        nominal: 47,
        mass: 46.972935
      }, {
        nominal: 48,
        mass: 47.97591
      }, {
        nominal: 49,
        mass: 48.9819
      }, {
        nominal: 50,
        mass: 49.98613
      }, {
        nominal: 51,
        mass: 50.9937
      }, {
        nominal: 52,
        mass: 51.99896
      }, {
        nominal: 53,
        mass: 53.00729
      }],
      symbol: 'Ar',
      mass: 39.947798563582005,
      name: 'Argon',
      monoisotopicMass: 39.9623831237
    }, {
      number: 19,
      isotopes: [{
        nominal: 32,
        mass: 32.02265
      }, {
        nominal: 33,
        mass: 33.00756
      }, {
        nominal: 34,
        mass: 33.99869
      }, {
        nominal: 35,
        mass: 34.98800541
      }, {
        nominal: 36,
        mass: 35.98130201
      }, {
        nominal: 37,
        mass: 36.97337589
      }, {
        nominal: 38,
        mass: 37.96908112
      }, {
        nominal: 39,
        mass: 38.9637064864,
        abundance: 0.932581
      }, {
        nominal: 40,
        mass: 39.963998166,
        abundance: 0.000117
      }, {
        nominal: 41,
        mass: 40.9618252579,
        abundance: 0.067302
      }, {
        nominal: 42,
        mass: 41.96240231
      }, {
        nominal: 43,
        mass: 42.9607347
      }, {
        nominal: 44,
        mass: 43.96158699
      }, {
        nominal: 45,
        mass: 44.96069149
      }, {
        nominal: 46,
        mass: 45.96198159
      }, {
        nominal: 47,
        mass: 46.9616616
      }, {
        nominal: 48,
        mass: 47.96534119
      }, {
        nominal: 49,
        mass: 48.96821075
      }, {
        nominal: 50,
        mass: 49.97238
      }, {
        nominal: 51,
        mass: 50.975828
      }, {
        nominal: 52,
        mass: 51.98224
      }, {
        nominal: 53,
        mass: 52.98746
      }, {
        nominal: 54,
        mass: 53.99463
      }, {
        nominal: 55,
        mass: 55.00076
      }, {
        nominal: 56,
        mass: 56.00851
      }],
      symbol: 'K',
      mass: 39.098300910086,
      name: 'Potassium',
      monoisotopicMass: 38.9637064864
    }, {
      number: 20,
      isotopes: [{
        nominal: 34,
        mass: 34.01487
      }, {
        nominal: 35,
        mass: 35.00514
      }, {
        nominal: 36,
        mass: 35.993074
      }, {
        nominal: 37,
        mass: 36.98589785
      }, {
        nominal: 38,
        mass: 37.97631922
      }, {
        nominal: 39,
        mass: 38.97071081
      }, {
        nominal: 40,
        mass: 39.962590863,
        abundance: 0.96941
      }, {
        nominal: 41,
        mass: 40.96227792
      }, {
        nominal: 42,
        mass: 41.95861783,
        abundance: 0.00647
      }, {
        nominal: 43,
        mass: 42.95876644,
        abundance: 0.00135
      }, {
        nominal: 44,
        mass: 43.95548156,
        abundance: 0.02086
      }, {
        nominal: 45,
        mass: 44.95618635
      }, {
        nominal: 46,
        mass: 45.953689,
        abundance: 0.00004
      }, {
        nominal: 47,
        mass: 46.9545424
      }, {
        nominal: 48,
        mass: 47.95252276,
        abundance: 0.00187
      }, {
        nominal: 49,
        mass: 48.95566274
      }, {
        nominal: 50,
        mass: 49.9574992
      }, {
        nominal: 51,
        mass: 50.960989
      }, {
        nominal: 52,
        mass: 51.963217
      }, {
        nominal: 53,
        mass: 52.96945
      }, {
        nominal: 54,
        mass: 53.9734
      }, {
        nominal: 55,
        mass: 54.9803
      }, {
        nominal: 56,
        mass: 55.98508
      }, {
        nominal: 57,
        mass: 56.99262
      }, {
        nominal: 58,
        mass: 57.99794
      }],
      symbol: 'Ca',
      mass: 40.078022511017735,
      name: 'Calcium',
      monoisotopicMass: 39.962590863
    }, {
      number: 21,
      isotopes: [{
        nominal: 36,
        mass: 36.01648
      }, {
        nominal: 37,
        mass: 37.00374
      }, {
        nominal: 38,
        mass: 37.99512
      }, {
        nominal: 39,
        mass: 38.984785
      }, {
        nominal: 40,
        mass: 39.9779673
      }, {
        nominal: 41,
        mass: 40.969251105
      }, {
        nominal: 42,
        mass: 41.96551653
      }, {
        nominal: 43,
        mass: 42.9611505
      }, {
        nominal: 44,
        mass: 43.9594029
      }, {
        nominal: 45,
        mass: 44.95590828,
        abundance: 1
      }, {
        nominal: 46,
        mass: 45.95516826
      }, {
        nominal: 47,
        mass: 46.9524037
      }, {
        nominal: 48,
        mass: 47.9522236
      }, {
        nominal: 49,
        mass: 48.9500146
      }, {
        nominal: 50,
        mass: 49.952176
      }, {
        nominal: 51,
        mass: 50.953592
      }, {
        nominal: 52,
        mass: 51.95688
      }, {
        nominal: 53,
        mass: 52.95909
      }, {
        nominal: 54,
        mass: 53.96393
      }, {
        nominal: 55,
        mass: 54.96782
      }, {
        nominal: 56,
        mass: 55.97345
      }, {
        nominal: 57,
        mass: 56.97777
      }, {
        nominal: 58,
        mass: 57.98403
      }, {
        nominal: 59,
        mass: 58.98894
      }, {
        nominal: 60,
        mass: 59.99565
      }, {
        nominal: 61,
        mass: 61.001
      }],
      symbol: 'Sc',
      mass: 44.95590828,
      name: 'Scandium',
      monoisotopicMass: 44.95590828
    }, {
      number: 22,
      isotopes: [{
        nominal: 38,
        mass: 38.01145
      }, {
        nominal: 39,
        mass: 39.00236
      }, {
        nominal: 40,
        mass: 39.9905
      }, {
        nominal: 41,
        mass: 40.983148
      }, {
        nominal: 42,
        mass: 41.97304903
      }, {
        nominal: 43,
        mass: 42.9685225
      }, {
        nominal: 44,
        mass: 43.95968995
      }, {
        nominal: 45,
        mass: 44.95812198
      }, {
        nominal: 46,
        mass: 45.95262772,
        abundance: 0.0825
      }, {
        nominal: 47,
        mass: 46.95175879,
        abundance: 0.0744
      }, {
        nominal: 48,
        mass: 47.94794198,
        abundance: 0.7372
      }, {
        nominal: 49,
        mass: 48.94786568,
        abundance: 0.0541
      }, {
        nominal: 50,
        mass: 49.94478689,
        abundance: 0.0518
      }, {
        nominal: 51,
        mass: 50.94661065
      }, {
        nominal: 52,
        mass: 51.946893
      }, {
        nominal: 53,
        mass: 52.94973
      }, {
        nominal: 54,
        mass: 53.95105
      }, {
        nominal: 55,
        mass: 54.95527
      }, {
        nominal: 56,
        mass: 55.95791
      }, {
        nominal: 57,
        mass: 56.96364
      }, {
        nominal: 58,
        mass: 57.9666
      }, {
        nominal: 59,
        mass: 58.97247
      }, {
        nominal: 60,
        mass: 59.97603
      }, {
        nominal: 61,
        mass: 60.98245
      }, {
        nominal: 62,
        mass: 61.98651
      }, {
        nominal: 63,
        mass: 62.99375
      }],
      symbol: 'Ti',
      mass: 47.866744962721995,
      name: 'Titanium',
      monoisotopicMass: 47.94794198
    }, {
      number: 23,
      isotopes: [{
        nominal: 40,
        mass: 40.01276
      }, {
        nominal: 41,
        mass: 41.00021
      }, {
        nominal: 42,
        mass: 41.99182
      }, {
        nominal: 43,
        mass: 42.980766
      }, {
        nominal: 44,
        mass: 43.97411
      }, {
        nominal: 45,
        mass: 44.9657748
      }, {
        nominal: 46,
        mass: 45.96019878
      }, {
        nominal: 47,
        mass: 46.95490491
      }, {
        nominal: 48,
        mass: 47.9522522
      }, {
        nominal: 49,
        mass: 48.9485118
      }, {
        nominal: 50,
        mass: 49.94715601,
        abundance: 0.0025
      }, {
        nominal: 51,
        mass: 50.94395704,
        abundance: 0.9975
      }, {
        nominal: 52,
        mass: 51.94477301
      }, {
        nominal: 53,
        mass: 52.9443367
      }, {
        nominal: 54,
        mass: 53.946439
      }, {
        nominal: 55,
        mass: 54.94724
      }, {
        nominal: 56,
        mass: 55.95048
      }, {
        nominal: 57,
        mass: 56.95252
      }, {
        nominal: 58,
        mass: 57.95672
      }, {
        nominal: 59,
        mass: 58.95939
      }, {
        nominal: 60,
        mass: 59.96431
      }, {
        nominal: 61,
        mass: 60.96725
      }, {
        nominal: 62,
        mass: 61.97265
      }, {
        nominal: 63,
        mass: 62.97639
      }, {
        nominal: 64,
        mass: 63.98264
      }, {
        nominal: 65,
        mass: 64.9875
      }, {
        nominal: 66,
        mass: 65.99398
      }],
      symbol: 'V',
      mass: 50.941465037425004,
      name: 'Vanadium',
      monoisotopicMass: 50.94395704
    }, {
      number: 24,
      isotopes: [{
        nominal: 42,
        mass: 42.0067
      }, {
        nominal: 43,
        mass: 42.99753
      }, {
        nominal: 44,
        mass: 43.98536
      }, {
        nominal: 45,
        mass: 44.97905
      }, {
        nominal: 46,
        mass: 45.968359
      }, {
        nominal: 47,
        mass: 46.9628974
      }, {
        nominal: 48,
        mass: 47.9540291
      }, {
        nominal: 49,
        mass: 48.9513333
      }, {
        nominal: 50,
        mass: 49.94604183,
        abundance: 0.04345
      }, {
        nominal: 51,
        mass: 50.94476502
      }, {
        nominal: 52,
        mass: 51.94050623,
        abundance: 0.83789
      }, {
        nominal: 53,
        mass: 52.94064815,
        abundance: 0.09501
      }, {
        nominal: 54,
        mass: 53.93887916,
        abundance: 0.02365
      }, {
        nominal: 55,
        mass: 54.94083843
      }, {
        nominal: 56,
        mass: 55.9406531
      }, {
        nominal: 57,
        mass: 56.943613
      }, {
        nominal: 58,
        mass: 57.94435
      }, {
        nominal: 59,
        mass: 58.94859
      }, {
        nominal: 60,
        mass: 59.95008
      }, {
        nominal: 61,
        mass: 60.95442
      }, {
        nominal: 62,
        mass: 61.9561
      }, {
        nominal: 63,
        mass: 62.96165
      }, {
        nominal: 64,
        mass: 63.96408
      }, {
        nominal: 65,
        mass: 64.96996
      }, {
        nominal: 66,
        mass: 65.97366
      }, {
        nominal: 67,
        mass: 66.98016
      }, {
        nominal: 68,
        mass: 67.98403
      }],
      symbol: 'Cr',
      mass: 51.9961317554337,
      name: 'Chromium',
      monoisotopicMass: 51.94050623
    }, {
      number: 25,
      isotopes: [{
        nominal: 44,
        mass: 44.00715
      }, {
        nominal: 45,
        mass: 44.99449
      }, {
        nominal: 46,
        mass: 45.98609
      }, {
        nominal: 47,
        mass: 46.975775
      }, {
        nominal: 48,
        mass: 47.96852
      }, {
        nominal: 49,
        mass: 48.959595
      }, {
        nominal: 50,
        mass: 49.95423778
      }, {
        nominal: 51,
        mass: 50.94820847
      }, {
        nominal: 52,
        mass: 51.9455639
      }, {
        nominal: 53,
        mass: 52.94128889
      }, {
        nominal: 54,
        mass: 53.9403576
      }, {
        nominal: 55,
        mass: 54.93804391,
        abundance: 1
      }, {
        nominal: 56,
        mass: 55.93890369
      }, {
        nominal: 57,
        mass: 56.9382861
      }, {
        nominal: 58,
        mass: 57.9400666
      }, {
        nominal: 59,
        mass: 58.9403911
      }, {
        nominal: 60,
        mass: 59.9431366
      }, {
        nominal: 61,
        mass: 60.9444525
      }, {
        nominal: 62,
        mass: 61.94795
      }, {
        nominal: 63,
        mass: 62.9496647
      }, {
        nominal: 64,
        mass: 63.9538494
      }, {
        nominal: 65,
        mass: 64.9560198
      }, {
        nominal: 66,
        mass: 65.960547
      }, {
        nominal: 67,
        mass: 66.96424
      }, {
        nominal: 68,
        mass: 67.96962
      }, {
        nominal: 69,
        mass: 68.97366
      }, {
        nominal: 70,
        mass: 69.97937
      }, {
        nominal: 71,
        mass: 70.98368
      }],
      symbol: 'Mn',
      mass: 54.93804391,
      name: 'Manganese',
      monoisotopicMass: 54.93804391
    }, {
      number: 26,
      isotopes: [{
        nominal: 45,
        mass: 45.01442
      }, {
        nominal: 46,
        mass: 46.00063
      }, {
        nominal: 47,
        mass: 46.99185
      }, {
        nominal: 48,
        mass: 47.98023
      }, {
        nominal: 49,
        mass: 48.973429
      }, {
        nominal: 50,
        mass: 49.962975
      }, {
        nominal: 51,
        mass: 50.956841
      }, {
        nominal: 52,
        mass: 51.9481131
      }, {
        nominal: 53,
        mass: 52.9453064
      }, {
        nominal: 54,
        mass: 53.93960899,
        abundance: 0.05845
      }, {
        nominal: 55,
        mass: 54.93829199
      }, {
        nominal: 56,
        mass: 55.93493633,
        abundance: 0.91754
      }, {
        nominal: 57,
        mass: 56.93539284,
        abundance: 0.02119
      }, {
        nominal: 58,
        mass: 57.93327443,
        abundance: 0.00282
      }, {
        nominal: 59,
        mass: 58.93487434
      }, {
        nominal: 60,
        mass: 59.9340711
      }, {
        nominal: 61,
        mass: 60.9367462
      }, {
        nominal: 62,
        mass: 61.9367918
      }, {
        nominal: 63,
        mass: 62.9402727
      }, {
        nominal: 64,
        mass: 63.9409878
      }, {
        nominal: 65,
        mass: 64.9450115
      }, {
        nominal: 66,
        mass: 65.94625
      }, {
        nominal: 67,
        mass: 66.95054
      }, {
        nominal: 68,
        mass: 67.95295
      }, {
        nominal: 69,
        mass: 68.95807
      }, {
        nominal: 70,
        mass: 69.96102
      }, {
        nominal: 71,
        mass: 70.96672
      }, {
        nominal: 72,
        mass: 71.96983
      }, {
        nominal: 73,
        mass: 72.97572
      }, {
        nominal: 74,
        mass: 73.97935
      }],
      symbol: 'Fe',
      mass: 55.845144433865904,
      name: 'Iron',
      monoisotopicMass: 55.93493633
    }, {
      number: 27,
      isotopes: [{
        nominal: 47,
        mass: 47.01057
      }, {
        nominal: 48,
        mass: 48.00093
      }, {
        nominal: 49,
        mass: 48.98891
      }, {
        nominal: 50,
        mass: 49.98091
      }, {
        nominal: 51,
        mass: 50.970647
      }, {
        nominal: 52,
        mass: 51.96351
      }, {
        nominal: 53,
        mass: 52.9542041
      }, {
        nominal: 54,
        mass: 53.94845987
      }, {
        nominal: 55,
        mass: 54.9419972
      }, {
        nominal: 56,
        mass: 55.9398388
      }, {
        nominal: 57,
        mass: 56.93629057
      }, {
        nominal: 58,
        mass: 57.9357521
      }, {
        nominal: 59,
        mass: 58.93319429,
        abundance: 1
      }, {
        nominal: 60,
        mass: 59.9338163
      }, {
        nominal: 61,
        mass: 60.93247662
      }, {
        nominal: 62,
        mass: 61.934059
      }, {
        nominal: 63,
        mass: 62.9336
      }, {
        nominal: 64,
        mass: 63.935811
      }, {
        nominal: 65,
        mass: 64.9364621
      }, {
        nominal: 66,
        mass: 65.939443
      }, {
        nominal: 67,
        mass: 66.9406096
      }, {
        nominal: 68,
        mass: 67.94426
      }, {
        nominal: 69,
        mass: 68.94614
      }, {
        nominal: 70,
        mass: 69.94963
      }, {
        nominal: 71,
        mass: 70.95237
      }, {
        nominal: 72,
        mass: 71.95729
      }, {
        nominal: 73,
        mass: 72.96039
      }, {
        nominal: 74,
        mass: 73.96515
      }, {
        nominal: 75,
        mass: 74.96876
      }, {
        nominal: 76,
        mass: 75.97413
      }],
      symbol: 'Co',
      mass: 58.93319429,
      name: 'Cobalt',
      monoisotopicMass: 58.93319429
    }, {
      number: 28,
      isotopes: [{
        nominal: 48,
        mass: 48.01769
      }, {
        nominal: 49,
        mass: 49.0077
      }, {
        nominal: 50,
        mass: 49.99474
      }, {
        nominal: 51,
        mass: 50.98611
      }, {
        nominal: 52,
        mass: 51.9748
      }, {
        nominal: 53,
        mass: 52.96819
      }, {
        nominal: 54,
        mass: 53.957892
      }, {
        nominal: 55,
        mass: 54.95133063
      }, {
        nominal: 56,
        mass: 55.94212855
      }, {
        nominal: 57,
        mass: 56.93979218
      }, {
        nominal: 58,
        mass: 57.93534241,
        abundance: 0.68077
      }, {
        nominal: 59,
        mass: 58.9343462
      }, {
        nominal: 60,
        mass: 59.93078588,
        abundance: 0.26223
      }, {
        nominal: 61,
        mass: 60.93105557,
        abundance: 0.011399
      }, {
        nominal: 62,
        mass: 61.92834537,
        abundance: 0.036346
      }, {
        nominal: 63,
        mass: 62.92966963
      }, {
        nominal: 64,
        mass: 63.92796682,
        abundance: 0.009255
      }, {
        nominal: 65,
        mass: 64.93008517
      }, {
        nominal: 66,
        mass: 65.9291393
      }, {
        nominal: 67,
        mass: 66.9315694
      }, {
        nominal: 68,
        mass: 67.9318688
      }, {
        nominal: 69,
        mass: 68.9356103
      }, {
        nominal: 70,
        mass: 69.9364313
      }, {
        nominal: 71,
        mass: 70.940519
      }, {
        nominal: 72,
        mass: 71.9417859
      }, {
        nominal: 73,
        mass: 72.9462067
      }, {
        nominal: 74,
        mass: 73.94798
      }, {
        nominal: 75,
        mass: 74.9525
      }, {
        nominal: 76,
        mass: 75.95533
      }, {
        nominal: 77,
        mass: 76.96055
      }, {
        nominal: 78,
        mass: 77.96336
      }, {
        nominal: 79,
        mass: 78.97025
      }],
      symbol: 'Ni',
      mass: 58.69334710994765,
      name: 'Nickel',
      monoisotopicMass: 57.93534241
    }, {
      number: 29,
      isotopes: [{
        nominal: 52,
        mass: 51.99671
      }, {
        nominal: 53,
        mass: 52.98459
      }, {
        nominal: 54,
        mass: 53.97666
      }, {
        nominal: 55,
        mass: 54.96604
      }, {
        nominal: 56,
        mass: 55.95895
      }, {
        nominal: 57,
        mass: 56.9492125
      }, {
        nominal: 58,
        mass: 57.94453305
      }, {
        nominal: 59,
        mass: 58.93949748
      }, {
        nominal: 60,
        mass: 59.9373645
      }, {
        nominal: 61,
        mass: 60.9334576
      }, {
        nominal: 62,
        mass: 61.93259541
      }, {
        nominal: 63,
        mass: 62.92959772,
        abundance: 0.6915
      }, {
        nominal: 64,
        mass: 63.92976434
      }, {
        nominal: 65,
        mass: 64.9277897,
        abundance: 0.3085
      }, {
        nominal: 66,
        mass: 65.92886903
      }, {
        nominal: 67,
        mass: 66.9277303
      }, {
        nominal: 68,
        mass: 67.9296109
      }, {
        nominal: 69,
        mass: 68.9294293
      }, {
        nominal: 70,
        mass: 69.9323921
      }, {
        nominal: 71,
        mass: 70.9326768
      }, {
        nominal: 72,
        mass: 71.9358203
      }, {
        nominal: 73,
        mass: 72.9366744
      }, {
        nominal: 74,
        mass: 73.9398749
      }, {
        nominal: 75,
        mass: 74.9415226
      }, {
        nominal: 76,
        mass: 75.945275
      }, {
        nominal: 77,
        mass: 76.94792
      }, {
        nominal: 78,
        mass: 77.95223
      }, {
        nominal: 79,
        mass: 78.95502
      }, {
        nominal: 80,
        mass: 79.96089
      }, {
        nominal: 81,
        mass: 80.96587
      }, {
        nominal: 82,
        mass: 81.97244
      }],
      symbol: 'Cu',
      mass: 63.54603994583,
      name: 'Copper',
      monoisotopicMass: 62.92959772
    }, {
      number: 30,
      isotopes: [{
        nominal: 54,
        mass: 53.99204
      }, {
        nominal: 55,
        mass: 54.98398
      }, {
        nominal: 56,
        mass: 55.97254
      }, {
        nominal: 57,
        mass: 56.96506
      }, {
        nominal: 58,
        mass: 57.954591
      }, {
        nominal: 59,
        mass: 58.94931266
      }, {
        nominal: 60,
        mass: 59.9418421
      }, {
        nominal: 61,
        mass: 60.939507
      }, {
        nominal: 62,
        mass: 61.93433397
      }, {
        nominal: 63,
        mass: 62.9332115
      }, {
        nominal: 64,
        mass: 63.92914201,
        abundance: 0.4917
      }, {
        nominal: 65,
        mass: 64.92924077
      }, {
        nominal: 66,
        mass: 65.92603381,
        abundance: 0.2773
      }, {
        nominal: 67,
        mass: 66.92712775,
        abundance: 0.0404
      }, {
        nominal: 68,
        mass: 67.92484455,
        abundance: 0.1845
      }, {
        nominal: 69,
        mass: 68.9265507
      }, {
        nominal: 70,
        mass: 69.9253192,
        abundance: 0.0061
      }, {
        nominal: 71,
        mass: 70.9277196
      }, {
        nominal: 72,
        mass: 71.9268428
      }, {
        nominal: 73,
        mass: 72.9295826
      }, {
        nominal: 74,
        mass: 73.9294073
      }, {
        nominal: 75,
        mass: 74.9328402
      }, {
        nominal: 76,
        mass: 75.933115
      }, {
        nominal: 77,
        mass: 76.9368872
      }, {
        nominal: 78,
        mass: 77.9382892
      }, {
        nominal: 79,
        mass: 78.9426381
      }, {
        nominal: 80,
        mass: 79.9445529
      }, {
        nominal: 81,
        mass: 80.9504026
      }, {
        nominal: 82,
        mass: 81.95426
      }, {
        nominal: 83,
        mass: 82.96056
      }, {
        nominal: 84,
        mass: 83.96521
      }, {
        nominal: 85,
        mass: 84.97226
      }],
      symbol: 'Zn',
      mass: 65.37778252952499,
      name: 'Zinc',
      monoisotopicMass: 63.92914201
    }, {
      number: 31,
      isotopes: [{
        nominal: 56,
        mass: 55.99536
      }, {
        nominal: 57,
        mass: 56.9832
      }, {
        nominal: 58,
        mass: 57.97478
      }, {
        nominal: 59,
        mass: 58.96353
      }, {
        nominal: 60,
        mass: 59.95729
      }, {
        nominal: 61,
        mass: 60.949399
      }, {
        nominal: 62,
        mass: 61.94419025
      }, {
        nominal: 63,
        mass: 62.9392942
      }, {
        nominal: 64,
        mass: 63.9368404
      }, {
        nominal: 65,
        mass: 64.93273459
      }, {
        nominal: 66,
        mass: 65.9315894
      }, {
        nominal: 67,
        mass: 66.9282025
      }, {
        nominal: 68,
        mass: 67.9279805
      }, {
        nominal: 69,
        mass: 68.9255735,
        abundance: 0.60108
      }, {
        nominal: 70,
        mass: 69.9260219
      }, {
        nominal: 71,
        mass: 70.92470258,
        abundance: 0.39892
      }, {
        nominal: 72,
        mass: 71.92636747
      }, {
        nominal: 73,
        mass: 72.9251747
      }, {
        nominal: 74,
        mass: 73.9269457
      }, {
        nominal: 75,
        mass: 74.9265002
      }, {
        nominal: 76,
        mass: 75.9288276
      }, {
        nominal: 77,
        mass: 76.9291543
      }, {
        nominal: 78,
        mass: 77.9316088
      }, {
        nominal: 79,
        mass: 78.9328523
      }, {
        nominal: 80,
        mass: 79.9364208
      }, {
        nominal: 81,
        mass: 80.9381338
      }, {
        nominal: 82,
        mass: 81.9431765
      }, {
        nominal: 83,
        mass: 82.9471203
      }, {
        nominal: 84,
        mass: 83.95246
      }, {
        nominal: 85,
        mass: 84.95699
      }, {
        nominal: 86,
        mass: 85.96301
      }, {
        nominal: 87,
        mass: 86.96824
      }],
      symbol: 'Ga',
      mass: 69.7230660725936,
      name: 'Gallium',
      monoisotopicMass: 68.9255735
    }, {
      number: 32,
      isotopes: [{
        nominal: 58,
        mass: 57.99172
      }, {
        nominal: 59,
        mass: 58.98249
      }, {
        nominal: 60,
        mass: 59.97036
      }, {
        nominal: 61,
        mass: 60.96379
      }, {
        nominal: 62,
        mass: 61.95502
      }, {
        nominal: 63,
        mass: 62.949628
      }, {
        nominal: 64,
        mass: 63.9416899
      }, {
        nominal: 65,
        mass: 64.9393681
      }, {
        nominal: 66,
        mass: 65.9338621
      }, {
        nominal: 67,
        mass: 66.9327339
      }, {
        nominal: 68,
        mass: 67.9280953
      }, {
        nominal: 69,
        mass: 68.9279645
      }, {
        nominal: 70,
        mass: 69.92424875,
        abundance: 0.2057
      }, {
        nominal: 71,
        mass: 70.92495233
      }, {
        nominal: 72,
        mass: 71.922075826,
        abundance: 0.2745
      }, {
        nominal: 73,
        mass: 72.923458956,
        abundance: 0.0775
      }, {
        nominal: 74,
        mass: 73.921177761,
        abundance: 0.365
      }, {
        nominal: 75,
        mass: 74.92285837
      }, {
        nominal: 76,
        mass: 75.921402726,
        abundance: 0.0773
      }, {
        nominal: 77,
        mass: 76.923549843
      }, {
        nominal: 78,
        mass: 77.9228529
      }, {
        nominal: 79,
        mass: 78.92536
      }, {
        nominal: 80,
        mass: 79.9253508
      }, {
        nominal: 81,
        mass: 80.9288329
      }, {
        nominal: 82,
        mass: 81.929774
      }, {
        nominal: 83,
        mass: 82.9345391
      }, {
        nominal: 84,
        mass: 83.9375751
      }, {
        nominal: 85,
        mass: 84.9429697
      }, {
        nominal: 86,
        mass: 85.94658
      }, {
        nominal: 87,
        mass: 86.95268
      }, {
        nominal: 88,
        mass: 87.95691
      }, {
        nominal: 89,
        mass: 88.96379
      }, {
        nominal: 90,
        mass: 89.96863
      }],
      symbol: 'Ge',
      mass: 72.6275501646868,
      name: 'Germanium',
      monoisotopicMass: 73.921177761
    }, {
      number: 33,
      isotopes: [{
        nominal: 60,
        mass: 59.99388
      }, {
        nominal: 61,
        mass: 60.98112
      }, {
        nominal: 62,
        mass: 61.97361
      }, {
        nominal: 63,
        mass: 62.9639
      }, {
        nominal: 64,
        mass: 63.95743
      }, {
        nominal: 65,
        mass: 64.949611
      }, {
        nominal: 66,
        mass: 65.9441488
      }, {
        nominal: 67,
        mass: 66.93925111
      }, {
        nominal: 68,
        mass: 67.9367741
      }, {
        nominal: 69,
        mass: 68.932246
      }, {
        nominal: 70,
        mass: 69.930926
      }, {
        nominal: 71,
        mass: 70.9271138
      }, {
        nominal: 72,
        mass: 71.9267523
      }, {
        nominal: 73,
        mass: 72.9238291
      }, {
        nominal: 74,
        mass: 73.9239286
      }, {
        nominal: 75,
        mass: 74.92159457,
        abundance: 1
      }, {
        nominal: 76,
        mass: 75.92239202
      }, {
        nominal: 77,
        mass: 76.9206476
      }, {
        nominal: 78,
        mass: 77.921828
      }, {
        nominal: 79,
        mass: 78.9209484
      }, {
        nominal: 80,
        mass: 79.9224746
      }, {
        nominal: 81,
        mass: 80.9221323
      }, {
        nominal: 82,
        mass: 81.9247412
      }, {
        nominal: 83,
        mass: 82.9252069
      }, {
        nominal: 84,
        mass: 83.9293033
      }, {
        nominal: 85,
        mass: 84.9321637
      }, {
        nominal: 86,
        mass: 85.9367015
      }, {
        nominal: 87,
        mass: 86.9402917
      }, {
        nominal: 88,
        mass: 87.94555
      }, {
        nominal: 89,
        mass: 88.94976
      }, {
        nominal: 90,
        mass: 89.95563
      }, {
        nominal: 91,
        mass: 90.96039
      }, {
        nominal: 92,
        mass: 91.96674
      }],
      symbol: 'As',
      mass: 74.92159457,
      name: 'Arsenic',
      monoisotopicMass: 74.92159457
    }, {
      number: 34,
      isotopes: [{
        nominal: 64,
        mass: 63.97109
      }, {
        nominal: 65,
        mass: 64.9644
      }, {
        nominal: 66,
        mass: 65.95559
      }, {
        nominal: 67,
        mass: 66.949994
      }, {
        nominal: 68,
        mass: 67.94182524
      }, {
        nominal: 69,
        mass: 68.9394148
      }, {
        nominal: 70,
        mass: 69.9335155
      }, {
        nominal: 71,
        mass: 70.9322094
      }, {
        nominal: 72,
        mass: 71.9271405
      }, {
        nominal: 73,
        mass: 72.9267549
      }, {
        nominal: 74,
        mass: 73.922475934,
        abundance: 0.0089
      }, {
        nominal: 75,
        mass: 74.92252287
      }, {
        nominal: 76,
        mass: 75.919213704,
        abundance: 0.0937
      }, {
        nominal: 77,
        mass: 76.919914154,
        abundance: 0.0763
      }, {
        nominal: 78,
        mass: 77.91730928,
        abundance: 0.2377
      }, {
        nominal: 79,
        mass: 78.91849929
      }, {
        nominal: 80,
        mass: 79.9165218,
        abundance: 0.4961
      }, {
        nominal: 81,
        mass: 80.917993
      }, {
        nominal: 82,
        mass: 81.9166995,
        abundance: 0.0873
      }, {
        nominal: 83,
        mass: 82.9191186
      }, {
        nominal: 84,
        mass: 83.9184668
      }, {
        nominal: 85,
        mass: 84.9222608
      }, {
        nominal: 86,
        mass: 85.9243117
      }, {
        nominal: 87,
        mass: 86.9286886
      }, {
        nominal: 88,
        mass: 87.9314175
      }, {
        nominal: 89,
        mass: 88.9366691
      }, {
        nominal: 90,
        mass: 89.9401
      }, {
        nominal: 91,
        mass: 90.94596
      }, {
        nominal: 92,
        mass: 91.94984
      }, {
        nominal: 93,
        mass: 92.95629
      }, {
        nominal: 94,
        mass: 93.96049
      }, {
        nominal: 95,
        mass: 94.9673
      }],
      symbol: 'Se',
      mass: 78.95938855701361,
      name: 'Selenium',
      monoisotopicMass: 79.9165218
    }, {
      number: 35,
      isotopes: [{
        nominal: 67,
        mass: 66.96465
      }, {
        nominal: 68,
        mass: 67.95873
      }, {
        nominal: 69,
        mass: 68.950497
      }, {
        nominal: 70,
        mass: 69.944792
      }, {
        nominal: 71,
        mass: 70.9393422
      }, {
        nominal: 72,
        mass: 71.9365886
      }, {
        nominal: 73,
        mass: 72.9316715
      }, {
        nominal: 74,
        mass: 73.9299102
      }, {
        nominal: 75,
        mass: 74.9258105
      }, {
        nominal: 76,
        mass: 75.924542
      }, {
        nominal: 77,
        mass: 76.9213792
      }, {
        nominal: 78,
        mass: 77.9211459
      }, {
        nominal: 79,
        mass: 78.9183376,
        abundance: 0.5069
      }, {
        nominal: 80,
        mass: 79.9185298
      }, {
        nominal: 81,
        mass: 80.9162897,
        abundance: 0.4931
      }, {
        nominal: 82,
        mass: 81.9168032
      }, {
        nominal: 83,
        mass: 82.9151756
      }, {
        nominal: 84,
        mass: 83.916496
      }, {
        nominal: 85,
        mass: 84.9156458
      }, {
        nominal: 86,
        mass: 85.9188054
      }, {
        nominal: 87,
        mass: 86.920674
      }, {
        nominal: 88,
        mass: 87.9240833
      }, {
        nominal: 89,
        mass: 88.9267046
      }, {
        nominal: 90,
        mass: 89.9312928
      }, {
        nominal: 91,
        mass: 90.9343986
      }, {
        nominal: 92,
        mass: 91.9396316
      }, {
        nominal: 93,
        mass: 92.94313
      }, {
        nominal: 94,
        mass: 93.9489
      }, {
        nominal: 95,
        mass: 94.95301
      }, {
        nominal: 96,
        mass: 95.95903
      }, {
        nominal: 97,
        mass: 96.96344
      }, {
        nominal: 98,
        mass: 97.96946
      }],
      symbol: 'Br',
      mass: 79.90352778050999,
      name: 'Bromine',
      monoisotopicMass: 78.9183376
    }, {
      number: 36,
      isotopes: [{
        nominal: 69,
        mass: 68.96518
      }, {
        nominal: 70,
        mass: 69.95604
      }, {
        nominal: 71,
        mass: 70.95027
      }, {
        nominal: 72,
        mass: 71.9420924
      }, {
        nominal: 73,
        mass: 72.9392892
      }, {
        nominal: 74,
        mass: 73.933084
      }, {
        nominal: 75,
        mass: 74.9309457
      }, {
        nominal: 76,
        mass: 75.9259103
      }, {
        nominal: 77,
        mass: 76.92467
      }, {
        nominal: 78,
        mass: 77.92036494,
        abundance: 0.00355
      }, {
        nominal: 79,
        mass: 78.9200829
      }, {
        nominal: 80,
        mass: 79.91637808,
        abundance: 0.02286
      }, {
        nominal: 81,
        mass: 80.9165912
      }, {
        nominal: 82,
        mass: 81.91348273,
        abundance: 0.11593
      }, {
        nominal: 83,
        mass: 82.91412716,
        abundance: 0.115
      }, {
        nominal: 84,
        mass: 83.9114977282,
        abundance: 0.56987
      }, {
        nominal: 85,
        mass: 84.9125273
      }, {
        nominal: 86,
        mass: 85.9106106269,
        abundance: 0.17279
      }, {
        nominal: 87,
        mass: 86.91335476
      }, {
        nominal: 88,
        mass: 87.9144479
      }, {
        nominal: 89,
        mass: 88.9178355
      }, {
        nominal: 90,
        mass: 89.9195279
      }, {
        nominal: 91,
        mass: 90.9238063
      }, {
        nominal: 92,
        mass: 91.9261731
      }, {
        nominal: 93,
        mass: 92.9311472
      }, {
        nominal: 94,
        mass: 93.93414
      }, {
        nominal: 95,
        mass: 94.939711
      }, {
        nominal: 96,
        mass: 95.943017
      }, {
        nominal: 97,
        mass: 96.94909
      }, {
        nominal: 98,
        mass: 97.95243
      }, {
        nominal: 99,
        mass: 98.95839
      }, {
        nominal: 100,
        mass: 99.96237
      }, {
        nominal: 101,
        mass: 100.96873
      }],
      symbol: 'Kr',
      mass: 83.7979999953261,
      name: 'Krypton',
      monoisotopicMass: 83.9114977282
    }, {
      number: 37,
      isotopes: [{
        nominal: 71,
        mass: 70.96532
      }, {
        nominal: 72,
        mass: 71.95908
      }, {
        nominal: 73,
        mass: 72.95053
      }, {
        nominal: 74,
        mass: 73.9442659
      }, {
        nominal: 75,
        mass: 74.9385732
      }, {
        nominal: 76,
        mass: 75.935073
      }, {
        nominal: 77,
        mass: 76.9304016
      }, {
        nominal: 78,
        mass: 77.9281419
      }, {
        nominal: 79,
        mass: 78.9239899
      }, {
        nominal: 80,
        mass: 79.9225164
      }, {
        nominal: 81,
        mass: 80.9189939
      }, {
        nominal: 82,
        mass: 81.918209
      }, {
        nominal: 83,
        mass: 82.9151142
      }, {
        nominal: 84,
        mass: 83.9143752
      }, {
        nominal: 85,
        mass: 84.9117897379,
        abundance: 0.7217
      }, {
        nominal: 86,
        mass: 85.91116743
      }, {
        nominal: 87,
        mass: 86.909180531,
        abundance: 0.2783
      }, {
        nominal: 88,
        mass: 87.91131559
      }, {
        nominal: 89,
        mass: 88.9122783
      }, {
        nominal: 90,
        mass: 89.9147985
      }, {
        nominal: 91,
        mass: 90.9165372
      }, {
        nominal: 92,
        mass: 91.9197284
      }, {
        nominal: 93,
        mass: 92.9220393
      }, {
        nominal: 94,
        mass: 93.9263948
      }, {
        nominal: 95,
        mass: 94.92926
      }, {
        nominal: 96,
        mass: 95.9341334
      }, {
        nominal: 97,
        mass: 96.9371771
      }, {
        nominal: 98,
        mass: 97.9416869
      }, {
        nominal: 99,
        mass: 98.94503
      }, {
        nominal: 100,
        mass: 99.95003
      }, {
        nominal: 101,
        mass: 100.95404
      }, {
        nominal: 102,
        mass: 101.95952
      }, {
        nominal: 103,
        mass: 102.96392
      }],
      symbol: 'Rb',
      mass: 85.46766359561973,
      name: 'Rubidium',
      monoisotopicMass: 84.9117897379
    }, {
      number: 38,
      isotopes: [{
        nominal: 73,
        mass: 72.9657
      }, {
        nominal: 74,
        mass: 73.95617
      }, {
        nominal: 75,
        mass: 74.94995
      }, {
        nominal: 76,
        mass: 75.941763
      }, {
        nominal: 77,
        mass: 76.9379455
      }, {
        nominal: 78,
        mass: 77.93218
      }, {
        nominal: 79,
        mass: 78.9297077
      }, {
        nominal: 80,
        mass: 79.9245175
      }, {
        nominal: 81,
        mass: 80.9232114
      }, {
        nominal: 82,
        mass: 81.9183999
      }, {
        nominal: 83,
        mass: 82.9175544
      }, {
        nominal: 84,
        mass: 83.9134191,
        abundance: 0.0056
      }, {
        nominal: 85,
        mass: 84.912932
      }, {
        nominal: 86,
        mass: 85.9092606,
        abundance: 0.0986
      }, {
        nominal: 87,
        mass: 86.9088775,
        abundance: 0.07
      }, {
        nominal: 88,
        mass: 87.9056125,
        abundance: 0.8258
      }, {
        nominal: 89,
        mass: 88.9074511
      }, {
        nominal: 90,
        mass: 89.90773
      }, {
        nominal: 91,
        mass: 90.9101954
      }, {
        nominal: 92,
        mass: 91.9110382
      }, {
        nominal: 93,
        mass: 92.9140242
      }, {
        nominal: 94,
        mass: 93.9153556
      }, {
        nominal: 95,
        mass: 94.9193529
      }, {
        nominal: 96,
        mass: 95.9217066
      }, {
        nominal: 97,
        mass: 96.926374
      }, {
        nominal: 98,
        mass: 97.9286888
      }, {
        nominal: 99,
        mass: 98.9328907
      }, {
        nominal: 100,
        mass: 99.93577
      }, {
        nominal: 101,
        mass: 100.940352
      }, {
        nominal: 102,
        mass: 101.943791
      }, {
        nominal: 103,
        mass: 102.94909
      }, {
        nominal: 104,
        mass: 103.95265
      }, {
        nominal: 105,
        mass: 104.95855
      }, {
        nominal: 106,
        mass: 105.96265
      }, {
        nominal: 107,
        mass: 106.96897
      }],
      symbol: 'Sr',
      mass: 87.61664446962,
      name: 'Strontium',
      monoisotopicMass: 87.9056125
    }, {
      number: 39,
      isotopes: [{
        nominal: 76,
        mass: 75.95856
      }, {
        nominal: 77,
        mass: 76.949781
      }, {
        nominal: 78,
        mass: 77.94361
      }, {
        nominal: 79,
        mass: 78.93735
      }, {
        nominal: 80,
        mass: 79.9343561
      }, {
        nominal: 81,
        mass: 80.9294556
      }, {
        nominal: 82,
        mass: 81.9269314
      }, {
        nominal: 83,
        mass: 82.922485
      }, {
        nominal: 84,
        mass: 83.9206721
      }, {
        nominal: 85,
        mass: 84.916433
      }, {
        nominal: 86,
        mass: 85.914886
      }, {
        nominal: 87,
        mass: 86.9108761
      }, {
        nominal: 88,
        mass: 87.9095016
      }, {
        nominal: 89,
        mass: 88.9058403,
        abundance: 1
      }, {
        nominal: 90,
        mass: 89.9071439
      }, {
        nominal: 91,
        mass: 90.9072974
      }, {
        nominal: 92,
        mass: 91.9089451
      }, {
        nominal: 93,
        mass: 92.909578
      }, {
        nominal: 94,
        mass: 93.9115906
      }, {
        nominal: 95,
        mass: 94.9128161
      }, {
        nominal: 96,
        mass: 95.9158968
      }, {
        nominal: 97,
        mass: 96.9182741
      }, {
        nominal: 98,
        mass: 97.9223821
      }, {
        nominal: 99,
        mass: 98.924148
      }, {
        nominal: 100,
        mass: 99.927715
      }, {
        nominal: 101,
        mass: 100.9301477
      }, {
        nominal: 102,
        mass: 101.9343277
      }, {
        nominal: 103,
        mass: 102.937243
      }, {
        nominal: 104,
        mass: 103.94196
      }, {
        nominal: 105,
        mass: 104.94544
      }, {
        nominal: 106,
        mass: 105.95056
      }, {
        nominal: 107,
        mass: 106.95452
      }, {
        nominal: 108,
        mass: 107.95996
      }, {
        nominal: 109,
        mass: 108.96436
      }],
      symbol: 'Y',
      mass: 88.9058403,
      name: 'Yttrium',
      monoisotopicMass: 88.9058403
    }, {
      number: 40,
      isotopes: [{
        nominal: 78,
        mass: 77.95566
      }, {
        nominal: 79,
        mass: 78.94948
      }, {
        nominal: 80,
        mass: 79.9404
      }, {
        nominal: 81,
        mass: 80.93731
      }, {
        nominal: 82,
        mass: 81.93135
      }, {
        nominal: 83,
        mass: 82.9292421
      }, {
        nominal: 84,
        mass: 83.9233269
      }, {
        nominal: 85,
        mass: 84.9214444
      }, {
        nominal: 86,
        mass: 85.9162972
      }, {
        nominal: 87,
        mass: 86.914818
      }, {
        nominal: 88,
        mass: 87.9102213
      }, {
        nominal: 89,
        mass: 88.9088814
      }, {
        nominal: 90,
        mass: 89.9046977,
        abundance: 0.5145
      }, {
        nominal: 91,
        mass: 90.9056396,
        abundance: 0.1122
      }, {
        nominal: 92,
        mass: 91.9050347,
        abundance: 0.1715
      }, {
        nominal: 93,
        mass: 92.9064699
      }, {
        nominal: 94,
        mass: 93.9063108,
        abundance: 0.1738
      }, {
        nominal: 95,
        mass: 94.9080385
      }, {
        nominal: 96,
        mass: 95.9082714,
        abundance: 0.028
      }, {
        nominal: 97,
        mass: 96.9109512
      }, {
        nominal: 98,
        mass: 97.9127289
      }, {
        nominal: 99,
        mass: 98.916667
      }, {
        nominal: 100,
        mass: 99.9180006
      }, {
        nominal: 101,
        mass: 100.921448
      }, {
        nominal: 102,
        mass: 101.9231409
      }, {
        nominal: 103,
        mass: 102.927191
      }, {
        nominal: 104,
        mass: 103.929436
      }, {
        nominal: 105,
        mass: 104.934008
      }, {
        nominal: 106,
        mass: 105.93676
      }, {
        nominal: 107,
        mass: 106.94174
      }, {
        nominal: 108,
        mass: 107.94487
      }, {
        nominal: 109,
        mass: 108.95041
      }, {
        nominal: 110,
        mass: 109.95396
      }, {
        nominal: 111,
        mass: 110.95968
      }, {
        nominal: 112,
        mass: 111.9637
      }],
      symbol: 'Zr',
      mass: 91.22364159706,
      name: 'Zirconium',
      monoisotopicMass: 89.9046977
    }, {
      number: 41,
      isotopes: [{
        nominal: 81,
        mass: 80.9496
      }, {
        nominal: 82,
        mass: 81.94396
      }, {
        nominal: 83,
        mass: 82.93729
      }, {
        nominal: 84,
        mass: 83.93449
      }, {
        nominal: 85,
        mass: 84.9288458
      }, {
        nominal: 86,
        mass: 85.9257828
      }, {
        nominal: 87,
        mass: 86.9206937
      }, {
        nominal: 88,
        mass: 87.918222
      }, {
        nominal: 89,
        mass: 88.913445
      }, {
        nominal: 90,
        mass: 89.9112584
      }, {
        nominal: 91,
        mass: 90.9069897
      }, {
        nominal: 92,
        mass: 91.9071881
      }, {
        nominal: 93,
        mass: 92.906373,
        abundance: 1
      }, {
        nominal: 94,
        mass: 93.9072788
      }, {
        nominal: 95,
        mass: 94.9068324
      }, {
        nominal: 96,
        mass: 95.9080973
      }, {
        nominal: 97,
        mass: 96.9080959
      }, {
        nominal: 98,
        mass: 97.9103265
      }, {
        nominal: 99,
        mass: 98.911613
      }, {
        nominal: 100,
        mass: 99.9143276
      }, {
        nominal: 101,
        mass: 100.9153103
      }, {
        nominal: 102,
        mass: 101.9180772
      }, {
        nominal: 103,
        mass: 102.9194572
      }, {
        nominal: 104,
        mass: 103.9228925
      }, {
        nominal: 105,
        mass: 104.9249465
      }, {
        nominal: 106,
        mass: 105.9289317
      }, {
        nominal: 107,
        mass: 106.9315937
      }, {
        nominal: 108,
        mass: 107.9360748
      }, {
        nominal: 109,
        mass: 108.93922
      }, {
        nominal: 110,
        mass: 109.94403
      }, {
        nominal: 111,
        mass: 110.94753
      }, {
        nominal: 112,
        mass: 111.95247
      }, {
        nominal: 113,
        mass: 112.95651
      }, {
        nominal: 114,
        mass: 113.96201
      }, {
        nominal: 115,
        mass: 114.96634
      }],
      symbol: 'Nb',
      mass: 92.906373,
      name: 'Niobium',
      monoisotopicMass: 92.906373
    }, {
      number: 42,
      isotopes: [{
        nominal: 83,
        mass: 82.94988
      }, {
        nominal: 84,
        mass: 83.94149
      }, {
        nominal: 85,
        mass: 84.938261
      }, {
        nominal: 86,
        mass: 85.9311748
      }, {
        nominal: 87,
        mass: 86.9281962
      }, {
        nominal: 88,
        mass: 87.9219678
      }, {
        nominal: 89,
        mass: 88.9194682
      }, {
        nominal: 90,
        mass: 89.9139309
      }, {
        nominal: 91,
        mass: 90.9117453
      }, {
        nominal: 92,
        mass: 91.90680796,
        abundance: 0.1453
      }, {
        nominal: 93,
        mass: 92.90680958
      }, {
        nominal: 94,
        mass: 93.9050849,
        abundance: 0.0915
      }, {
        nominal: 95,
        mass: 94.90583877,
        abundance: 0.1584
      }, {
        nominal: 96,
        mass: 95.90467612,
        abundance: 0.1667
      }, {
        nominal: 97,
        mass: 96.90601812,
        abundance: 0.096
      }, {
        nominal: 98,
        mass: 97.90540482,
        abundance: 0.2439
      }, {
        nominal: 99,
        mass: 98.90770851
      }, {
        nominal: 100,
        mass: 99.9074718,
        abundance: 0.0982
      }, {
        nominal: 101,
        mass: 100.9103414
      }, {
        nominal: 102,
        mass: 101.9102834
      }, {
        nominal: 103,
        mass: 102.913079
      }, {
        nominal: 104,
        mass: 103.9137344
      }, {
        nominal: 105,
        mass: 104.916969
      }, {
        nominal: 106,
        mass: 105.918259
      }, {
        nominal: 107,
        mass: 106.922106
      }, {
        nominal: 108,
        mass: 107.924033
      }, {
        nominal: 109,
        mass: 108.928424
      }, {
        nominal: 110,
        mass: 109.930704
      }, {
        nominal: 111,
        mass: 110.935654
      }, {
        nominal: 112,
        mass: 111.93831
      }, {
        nominal: 113,
        mass: 112.94335
      }, {
        nominal: 114,
        mass: 113.94653
      }, {
        nominal: 115,
        mass: 114.95196
      }, {
        nominal: 116,
        mass: 115.95545
      }, {
        nominal: 117,
        mass: 116.96117
      }],
      symbol: 'Mo',
      mass: 95.959788541188,
      name: 'Molybdenum',
      monoisotopicMass: 97.90540482
    }, {
      number: 43,
      isotopes: [{
        nominal: 85,
        mass: 84.95058
      }, {
        nominal: 86,
        mass: 85.94493
      }, {
        nominal: 87,
        mass: 86.9380672
      }, {
        nominal: 88,
        mass: 87.93378
      }, {
        nominal: 89,
        mass: 88.9276487
      }, {
        nominal: 90,
        mass: 89.9240739
      }, {
        nominal: 91,
        mass: 90.9184254
      }, {
        nominal: 92,
        mass: 91.9152698
      }, {
        nominal: 93,
        mass: 92.910246
      }, {
        nominal: 94,
        mass: 93.9096536
      }, {
        nominal: 95,
        mass: 94.9076536
      }, {
        nominal: 96,
        mass: 95.907868
      }, {
        nominal: 97,
        mass: 96.9063667
      }, {
        nominal: 98,
        mass: 97.9072124
      }, {
        nominal: 99,
        mass: 98.9062508
      }, {
        nominal: 100,
        mass: 99.9076539
      }, {
        nominal: 101,
        mass: 100.907309
      }, {
        nominal: 102,
        mass: 101.9092097
      }, {
        nominal: 103,
        mass: 102.909176
      }, {
        nominal: 104,
        mass: 103.911425
      }, {
        nominal: 105,
        mass: 104.911655
      }, {
        nominal: 106,
        mass: 105.914358
      }, {
        nominal: 107,
        mass: 106.9154606
      }, {
        nominal: 108,
        mass: 107.9184957
      }, {
        nominal: 109,
        mass: 108.920256
      }, {
        nominal: 110,
        mass: 109.923744
      }, {
        nominal: 111,
        mass: 110.925901
      }, {
        nominal: 112,
        mass: 111.9299458
      }, {
        nominal: 113,
        mass: 112.932569
      }, {
        nominal: 114,
        mass: 113.93691
      }, {
        nominal: 115,
        mass: 114.93998
      }, {
        nominal: 116,
        mass: 115.94476
      }, {
        nominal: 117,
        mass: 116.94806
      }, {
        nominal: 118,
        mass: 117.95299
      }, {
        nominal: 119,
        mass: 118.95666
      }, {
        nominal: 120,
        mass: 119.96187
      }],
      symbol: 'Tc',
      mass: null,
      name: 'Technetium'
    }, {
      number: 44,
      isotopes: [{
        nominal: 87,
        mass: 86.95069
      }, {
        nominal: 88,
        mass: 87.9416
      }, {
        nominal: 89,
        mass: 88.93762
      }, {
        nominal: 90,
        mass: 89.9303444
      }, {
        nominal: 91,
        mass: 90.9267419
      }, {
        nominal: 92,
        mass: 91.9202344
      }, {
        nominal: 93,
        mass: 92.9171044
      }, {
        nominal: 94,
        mass: 93.9113429
      }, {
        nominal: 95,
        mass: 94.910406
      }, {
        nominal: 96,
        mass: 95.90759025,
        abundance: 0.0554
      }, {
        nominal: 97,
        mass: 96.9075471
      }, {
        nominal: 98,
        mass: 97.9052868,
        abundance: 0.0187
      }, {
        nominal: 99,
        mass: 98.9059341,
        abundance: 0.1276
      }, {
        nominal: 100,
        mass: 99.9042143,
        abundance: 0.126
      }, {
        nominal: 101,
        mass: 100.9055769,
        abundance: 0.1706
      }, {
        nominal: 102,
        mass: 101.9043441,
        abundance: 0.3155
      }, {
        nominal: 103,
        mass: 102.9063186
      }, {
        nominal: 104,
        mass: 103.9054275,
        abundance: 0.1862
      }, {
        nominal: 105,
        mass: 104.9077476
      }, {
        nominal: 106,
        mass: 105.9073291
      }, {
        nominal: 107,
        mass: 106.909972
      }, {
        nominal: 108,
        mass: 107.910188
      }, {
        nominal: 109,
        mass: 108.913326
      }, {
        nominal: 110,
        mass: 109.9140407
      }, {
        nominal: 111,
        mass: 110.91757
      }, {
        nominal: 112,
        mass: 111.918809
      }, {
        nominal: 113,
        mass: 112.922844
      }, {
        nominal: 114,
        mass: 113.9246136
      }, {
        nominal: 115,
        mass: 114.92882
      }, {
        nominal: 116,
        mass: 115.9312192
      }, {
        nominal: 117,
        mass: 116.9361
      }, {
        nominal: 118,
        mass: 117.93853
      }, {
        nominal: 119,
        mass: 118.94357
      }, {
        nominal: 120,
        mass: 119.94631
      }, {
        nominal: 121,
        mass: 120.95164
      }, {
        nominal: 122,
        mass: 121.95447
      }, {
        nominal: 123,
        mass: 122.95989
      }, {
        nominal: 124,
        mass: 123.96305
      }],
      symbol: 'Ru',
      mass: 101.06494013916,
      name: 'Ruthenium',
      monoisotopicMass: 101.9043441
    }, {
      number: 45,
      isotopes: [{
        nominal: 89,
        mass: 88.95058
      }, {
        nominal: 90,
        mass: 89.94422
      }, {
        nominal: 91,
        mass: 90.93688
      }, {
        nominal: 92,
        mass: 91.9323677
      }, {
        nominal: 93,
        mass: 92.9259128
      }, {
        nominal: 94,
        mass: 93.9217305
      }, {
        nominal: 95,
        mass: 94.9158979
      }, {
        nominal: 96,
        mass: 95.914453
      }, {
        nominal: 97,
        mass: 96.911329
      }, {
        nominal: 98,
        mass: 97.910708
      }, {
        nominal: 99,
        mass: 98.9081282
      }, {
        nominal: 100,
        mass: 99.908117
      }, {
        nominal: 101,
        mass: 100.9061606
      }, {
        nominal: 102,
        mass: 101.9068374
      }, {
        nominal: 103,
        mass: 102.905498,
        abundance: 1
      }, {
        nominal: 104,
        mass: 103.9066492
      }, {
        nominal: 105,
        mass: 104.9056885
      }, {
        nominal: 106,
        mass: 105.9072868
      }, {
        nominal: 107,
        mass: 106.906748
      }, {
        nominal: 108,
        mass: 107.908714
      }, {
        nominal: 109,
        mass: 108.9087488
      }, {
        nominal: 110,
        mass: 109.911079
      }, {
        nominal: 111,
        mass: 110.9116423
      }, {
        nominal: 112,
        mass: 111.914403
      }, {
        nominal: 113,
        mass: 112.9154393
      }, {
        nominal: 114,
        mass: 113.918718
      }, {
        nominal: 115,
        mass: 114.9203116
      }, {
        nominal: 116,
        mass: 115.924059
      }, {
        nominal: 117,
        mass: 116.9260354
      }, {
        nominal: 118,
        mass: 117.93034
      }, {
        nominal: 119,
        mass: 118.932557
      }, {
        nominal: 120,
        mass: 119.93686
      }, {
        nominal: 121,
        mass: 120.93942
      }, {
        nominal: 122,
        mass: 121.94399
      }, {
        nominal: 123,
        mass: 122.94685
      }, {
        nominal: 124,
        mass: 123.95151
      }, {
        nominal: 125,
        mass: 124.95469
      }, {
        nominal: 126,
        mass: 125.95946
      }],
      symbol: 'Rh',
      mass: 102.905498,
      name: 'Rhodium',
      monoisotopicMass: 102.905498
    }, {
      number: 46,
      isotopes: [{
        nominal: 91,
        mass: 90.95032
      }, {
        nominal: 92,
        mass: 91.94088
      }, {
        nominal: 93,
        mass: 92.93651
      }, {
        nominal: 94,
        mass: 93.9290376
      }, {
        nominal: 95,
        mass: 94.9248898
      }, {
        nominal: 96,
        mass: 95.9182151
      }, {
        nominal: 97,
        mass: 96.916472
      }, {
        nominal: 98,
        mass: 97.9126983
      }, {
        nominal: 99,
        mass: 98.9117748
      }, {
        nominal: 100,
        mass: 99.908505
      }, {
        nominal: 101,
        mass: 100.9082864
      }, {
        nominal: 102,
        mass: 101.9056022,
        abundance: 0.0102
      }, {
        nominal: 103,
        mass: 102.9060809
      }, {
        nominal: 104,
        mass: 103.9040305,
        abundance: 0.1114
      }, {
        nominal: 105,
        mass: 104.9050796,
        abundance: 0.2233
      }, {
        nominal: 106,
        mass: 105.9034804,
        abundance: 0.2733
      }, {
        nominal: 107,
        mass: 106.9051282
      }, {
        nominal: 108,
        mass: 107.9038916,
        abundance: 0.2646
      }, {
        nominal: 109,
        mass: 108.9059504
      }, {
        nominal: 110,
        mass: 109.9051722,
        abundance: 0.1172
      }, {
        nominal: 111,
        mass: 110.90768968
      }, {
        nominal: 112,
        mass: 111.9073297
      }, {
        nominal: 113,
        mass: 112.910261
      }, {
        nominal: 114,
        mass: 113.9103686
      }, {
        nominal: 115,
        mass: 114.913659
      }, {
        nominal: 116,
        mass: 115.914297
      }, {
        nominal: 117,
        mass: 116.9179547
      }, {
        nominal: 118,
        mass: 117.9190667
      }, {
        nominal: 119,
        mass: 118.9233402
      }, {
        nominal: 120,
        mass: 119.9245511
      }, {
        nominal: 121,
        mass: 120.9289503
      }, {
        nominal: 122,
        mass: 121.930632
      }, {
        nominal: 123,
        mass: 122.93514
      }, {
        nominal: 124,
        mass: 123.93714
      }, {
        nominal: 125,
        mass: 124.94179
      }, {
        nominal: 126,
        mass: 125.94416
      }, {
        nominal: 127,
        mass: 126.94907
      }, {
        nominal: 128,
        mass: 127.95183
      }],
      symbol: 'Pd',
      mass: 106.41532750734,
      name: 'Palladium',
      monoisotopicMass: 105.9034804
    }, {
      number: 47,
      isotopes: [{
        nominal: 93,
        mass: 92.95033
      }, {
        nominal: 94,
        mass: 93.94373
      }, {
        nominal: 95,
        mass: 94.93602
      }, {
        nominal: 96,
        mass: 95.930744
      }, {
        nominal: 97,
        mass: 96.92397
      }, {
        nominal: 98,
        mass: 97.92156
      }, {
        nominal: 99,
        mass: 98.9176458
      }, {
        nominal: 100,
        mass: 99.9161154
      }, {
        nominal: 101,
        mass: 100.912684
      }, {
        nominal: 102,
        mass: 101.9117047
      }, {
        nominal: 103,
        mass: 102.9089631
      }, {
        nominal: 104,
        mass: 103.9086239
      }, {
        nominal: 105,
        mass: 104.9065256
      }, {
        nominal: 106,
        mass: 105.9066636
      }, {
        nominal: 107,
        mass: 106.9050916,
        abundance: 0.51839
      }, {
        nominal: 108,
        mass: 107.9059503
      }, {
        nominal: 109,
        mass: 108.9047553,
        abundance: 0.48161
      }, {
        nominal: 110,
        mass: 109.9061102
      }, {
        nominal: 111,
        mass: 110.9052959
      }, {
        nominal: 112,
        mass: 111.9070486
      }, {
        nominal: 113,
        mass: 112.906573
      }, {
        nominal: 114,
        mass: 113.908823
      }, {
        nominal: 115,
        mass: 114.908767
      }, {
        nominal: 116,
        mass: 115.9113868
      }, {
        nominal: 117,
        mass: 116.911774
      }, {
        nominal: 118,
        mass: 117.9145955
      }, {
        nominal: 119,
        mass: 118.91557
      }, {
        nominal: 120,
        mass: 119.9187848
      }, {
        nominal: 121,
        mass: 120.920125
      }, {
        nominal: 122,
        mass: 121.923664
      }, {
        nominal: 123,
        mass: 122.925337
      }, {
        nominal: 124,
        mass: 123.92893
      }, {
        nominal: 125,
        mass: 124.93105
      }, {
        nominal: 126,
        mass: 125.93475
      }, {
        nominal: 127,
        mass: 126.93711
      }, {
        nominal: 128,
        mass: 127.94106
      }, {
        nominal: 129,
        mass: 128.94395
      }, {
        nominal: 130,
        mass: 129.9507
      }],
      symbol: 'Ag',
      mass: 107.868149634557,
      name: 'Silver',
      monoisotopicMass: 106.9050916
    }, {
      number: 48,
      isotopes: [{
        nominal: 95,
        mass: 94.94994
      }, {
        nominal: 96,
        mass: 95.94034
      }, {
        nominal: 97,
        mass: 96.9351
      }, {
        nominal: 98,
        mass: 97.927389
      }, {
        nominal: 99,
        mass: 98.9249258
      }, {
        nominal: 100,
        mass: 99.9203488
      }, {
        nominal: 101,
        mass: 100.9185862
      }, {
        nominal: 102,
        mass: 101.914482
      }, {
        nominal: 103,
        mass: 102.9134165
      }, {
        nominal: 104,
        mass: 103.9098564
      }, {
        nominal: 105,
        mass: 104.9094639
      }, {
        nominal: 106,
        mass: 105.9064599,
        abundance: 0.0125
      }, {
        nominal: 107,
        mass: 106.9066121
      }, {
        nominal: 108,
        mass: 107.9041834,
        abundance: 0.0089
      }, {
        nominal: 109,
        mass: 108.9049867
      }, {
        nominal: 110,
        mass: 109.90300661,
        abundance: 0.1249
      }, {
        nominal: 111,
        mass: 110.90418287,
        abundance: 0.128
      }, {
        nominal: 112,
        mass: 111.90276287,
        abundance: 0.2413
      }, {
        nominal: 113,
        mass: 112.90440813,
        abundance: 0.1222
      }, {
        nominal: 114,
        mass: 113.90336509,
        abundance: 0.2873
      }, {
        nominal: 115,
        mass: 114.90543751
      }, {
        nominal: 116,
        mass: 115.90476315,
        abundance: 0.0749
      }, {
        nominal: 117,
        mass: 116.907226
      }, {
        nominal: 118,
        mass: 117.906922
      }, {
        nominal: 119,
        mass: 118.909847
      }, {
        nominal: 120,
        mass: 119.9098681
      }, {
        nominal: 121,
        mass: 120.9129637
      }, {
        nominal: 122,
        mass: 121.9134591
      }, {
        nominal: 123,
        mass: 122.9168925
      }, {
        nominal: 124,
        mass: 123.9176574
      }, {
        nominal: 125,
        mass: 124.9212576
      }, {
        nominal: 126,
        mass: 125.9224291
      }, {
        nominal: 127,
        mass: 126.926472
      }, {
        nominal: 128,
        mass: 127.9278129
      }, {
        nominal: 129,
        mass: 128.93182
      }, {
        nominal: 130,
        mass: 129.93394
      }, {
        nominal: 131,
        mass: 130.9406
      }, {
        nominal: 132,
        mass: 131.94604
      }, {
        nominal: 133,
        mass: 132.95285
      }],
      symbol: 'Cd',
      mass: 112.411557818268,
      name: 'Cadmium',
      monoisotopicMass: 113.90336509
    }, {
      number: 49,
      isotopes: [{
        nominal: 97,
        mass: 96.94934
      }, {
        nominal: 98,
        mass: 97.94214
      }, {
        nominal: 99,
        mass: 98.93411
      }, {
        nominal: 100,
        mass: 99.93096
      }, {
        nominal: 101,
        mass: 100.92634
      }, {
        nominal: 102,
        mass: 101.9241071
      }, {
        nominal: 103,
        mass: 102.9198819
      }, {
        nominal: 104,
        mass: 103.9182145
      }, {
        nominal: 105,
        mass: 104.914502
      }, {
        nominal: 106,
        mass: 105.913464
      }, {
        nominal: 107,
        mass: 106.91029
      }, {
        nominal: 108,
        mass: 107.9096935
      }, {
        nominal: 109,
        mass: 108.9071514
      }, {
        nominal: 110,
        mass: 109.90717
      }, {
        nominal: 111,
        mass: 110.9051085
      }, {
        nominal: 112,
        mass: 111.9055377
      }, {
        nominal: 113,
        mass: 112.90406184,
        abundance: 0.0429
      }, {
        nominal: 114,
        mass: 113.90491791
      }, {
        nominal: 115,
        mass: 114.903878776,
        abundance: 0.9571
      }, {
        nominal: 116,
        mass: 115.90525999
      }, {
        nominal: 117,
        mass: 116.9045157
      }, {
        nominal: 118,
        mass: 117.9063566
      }, {
        nominal: 119,
        mass: 118.9058507
      }, {
        nominal: 120,
        mass: 119.907967
      }, {
        nominal: 121,
        mass: 120.907851
      }, {
        nominal: 122,
        mass: 121.910281
      }, {
        nominal: 123,
        mass: 122.910434
      }, {
        nominal: 124,
        mass: 123.913182
      }, {
        nominal: 125,
        mass: 124.913605
      }, {
        nominal: 126,
        mass: 125.916507
      }, {
        nominal: 127,
        mass: 126.917446
      }, {
        nominal: 128,
        mass: 127.9204
      }, {
        nominal: 129,
        mass: 128.9218053
      }, {
        nominal: 130,
        mass: 129.924977
      }, {
        nominal: 131,
        mass: 130.9269715
      }, {
        nominal: 132,
        mass: 131.933001
      }, {
        nominal: 133,
        mass: 132.93831
      }, {
        nominal: 134,
        mass: 133.94454
      }, {
        nominal: 135,
        mass: 134.95005
      }],
      symbol: 'In',
      mass: 114.81808662944559,
      name: 'Indium',
      monoisotopicMass: 114.903878776
    }, {
      number: 50,
      isotopes: [{
        nominal: 99,
        mass: 98.94853
      }, {
        nominal: 100,
        mass: 99.9385
      }, {
        nominal: 101,
        mass: 100.93526
      }, {
        nominal: 102,
        mass: 101.93029
      }, {
        nominal: 103,
        mass: 102.928105
      }, {
        nominal: 104,
        mass: 103.9231052
      }, {
        nominal: 105,
        mass: 104.9212684
      }, {
        nominal: 106,
        mass: 105.9169574
      }, {
        nominal: 107,
        mass: 106.9157137
      }, {
        nominal: 108,
        mass: 107.9118943
      }, {
        nominal: 109,
        mass: 108.9112921
      }, {
        nominal: 110,
        mass: 109.907845
      }, {
        nominal: 111,
        mass: 110.9077401
      }, {
        nominal: 112,
        mass: 111.90482387,
        abundance: 0.0097
      }, {
        nominal: 113,
        mass: 112.9051757
      }, {
        nominal: 114,
        mass: 113.9027827,
        abundance: 0.0066
      }, {
        nominal: 115,
        mass: 114.903344699,
        abundance: 0.0034
      }, {
        nominal: 116,
        mass: 115.9017428,
        abundance: 0.1454
      }, {
        nominal: 117,
        mass: 116.90295398,
        abundance: 0.0768
      }, {
        nominal: 118,
        mass: 117.90160657,
        abundance: 0.2422
      }, {
        nominal: 119,
        mass: 118.90331117,
        abundance: 0.0859
      }, {
        nominal: 120,
        mass: 119.90220163,
        abundance: 0.3258
      }, {
        nominal: 121,
        mass: 120.9042426
      }, {
        nominal: 122,
        mass: 121.9034438,
        abundance: 0.0463
      }, {
        nominal: 123,
        mass: 122.9057252
      }, {
        nominal: 124,
        mass: 123.9052766,
        abundance: 0.0579
      }, {
        nominal: 125,
        mass: 124.9077864
      }, {
        nominal: 126,
        mass: 125.907659
      }, {
        nominal: 127,
        mass: 126.91039
      }, {
        nominal: 128,
        mass: 127.910507
      }, {
        nominal: 129,
        mass: 128.913465
      }, {
        nominal: 130,
        mass: 129.9139738
      }, {
        nominal: 131,
        mass: 130.917045
      }, {
        nominal: 132,
        mass: 131.9178267
      }, {
        nominal: 133,
        mass: 132.9239134
      }, {
        nominal: 134,
        mass: 133.9286821
      }, {
        nominal: 135,
        mass: 134.9349086
      }, {
        nominal: 136,
        mass: 135.93999
      }, {
        nominal: 137,
        mass: 136.94655
      }, {
        nominal: 138,
        mass: 137.95184
      }],
      symbol: 'Sn',
      mass: 118.71011259301059,
      name: 'Tin',
      monoisotopicMass: 119.90220163
    }, {
      number: 51,
      isotopes: [{
        nominal: 103,
        mass: 102.93969
      }, {
        nominal: 104,
        mass: 103.93648
      }, {
        nominal: 105,
        mass: 104.931276
      }, {
        nominal: 106,
        mass: 105.928638
      }, {
        nominal: 107,
        mass: 106.9241506
      }, {
        nominal: 108,
        mass: 107.9222267
      }, {
        nominal: 109,
        mass: 108.9181411
      }, {
        nominal: 110,
        mass: 109.9168543
      }, {
        nominal: 111,
        mass: 110.9132182
      }, {
        nominal: 112,
        mass: 111.9124
      }, {
        nominal: 113,
        mass: 112.909375
      }, {
        nominal: 114,
        mass: 113.90929
      }, {
        nominal: 115,
        mass: 114.906598
      }, {
        nominal: 116,
        mass: 115.9067931
      }, {
        nominal: 117,
        mass: 116.9048415
      }, {
        nominal: 118,
        mass: 117.9055321
      }, {
        nominal: 119,
        mass: 118.9039455
      }, {
        nominal: 120,
        mass: 119.9050794
      }, {
        nominal: 121,
        mass: 120.903812,
        abundance: 0.5721
      }, {
        nominal: 122,
        mass: 121.9051699
      }, {
        nominal: 123,
        mass: 122.9042132,
        abundance: 0.4279
      }, {
        nominal: 124,
        mass: 123.905935
      }, {
        nominal: 125,
        mass: 124.905253
      }, {
        nominal: 126,
        mass: 125.907253
      }, {
        nominal: 127,
        mass: 126.9069243
      }, {
        nominal: 128,
        mass: 127.909146
      }, {
        nominal: 129,
        mass: 128.909147
      }, {
        nominal: 130,
        mass: 129.911662
      }, {
        nominal: 131,
        mass: 130.9119888
      }, {
        nominal: 132,
        mass: 131.9145077
      }, {
        nominal: 133,
        mass: 132.9152732
      }, {
        nominal: 134,
        mass: 133.9205357
      }, {
        nominal: 135,
        mass: 134.9251851
      }, {
        nominal: 136,
        mass: 135.9307459
      }, {
        nominal: 137,
        mass: 136.93555
      }, {
        nominal: 138,
        mass: 137.94145
      }, {
        nominal: 139,
        mass: 138.94655
      }, {
        nominal: 140,
        mass: 139.95283
      }],
      symbol: 'Sb',
      mass: 121.75978367348,
      name: 'Antimony',
      monoisotopicMass: 120.903812
    }, {
      number: 52,
      isotopes: [{
        nominal: 105,
        mass: 104.9433
      }, {
        nominal: 106,
        mass: 105.9375
      }, {
        nominal: 107,
        mass: 106.935012
      }, {
        nominal: 108,
        mass: 107.9293805
      }, {
        nominal: 109,
        mass: 108.9273045
      }, {
        nominal: 110,
        mass: 109.9224581
      }, {
        nominal: 111,
        mass: 110.9210006
      }, {
        nominal: 112,
        mass: 111.9167279
      }, {
        nominal: 113,
        mass: 112.915891
      }, {
        nominal: 114,
        mass: 113.912089
      }, {
        nominal: 115,
        mass: 114.911902
      }, {
        nominal: 116,
        mass: 115.90846
      }, {
        nominal: 117,
        mass: 116.908646
      }, {
        nominal: 118,
        mass: 117.905854
      }, {
        nominal: 119,
        mass: 118.9064071
      }, {
        nominal: 120,
        mass: 119.9040593,
        abundance: 0.0009
      }, {
        nominal: 121,
        mass: 120.904944
      }, {
        nominal: 122,
        mass: 121.9030435,
        abundance: 0.0255
      }, {
        nominal: 123,
        mass: 122.9042698,
        abundance: 0.0089
      }, {
        nominal: 124,
        mass: 123.9028171,
        abundance: 0.0474
      }, {
        nominal: 125,
        mass: 124.9044299,
        abundance: 0.0707
      }, {
        nominal: 126,
        mass: 125.9033109,
        abundance: 0.1884
      }, {
        nominal: 127,
        mass: 126.9052257
      }, {
        nominal: 128,
        mass: 127.90446128,
        abundance: 0.3174
      }, {
        nominal: 129,
        mass: 128.90659646
      }, {
        nominal: 130,
        mass: 129.906222748,
        abundance: 0.3408
      }, {
        nominal: 131,
        mass: 130.908522213
      }, {
        nominal: 132,
        mass: 131.9085467
      }, {
        nominal: 133,
        mass: 132.9109688
      }, {
        nominal: 134,
        mass: 133.911394
      }, {
        nominal: 135,
        mass: 134.9165557
      }, {
        nominal: 136,
        mass: 135.9201006
      }, {
        nominal: 137,
        mass: 136.9255989
      }, {
        nominal: 138,
        mass: 137.9294722
      }, {
        nominal: 139,
        mass: 138.9353672
      }, {
        nominal: 140,
        mass: 139.939499
      }, {
        nominal: 141,
        mass: 140.9458
      }, {
        nominal: 142,
        mass: 141.95022
      }, {
        nominal: 143,
        mass: 142.95676
      }],
      symbol: 'Te',
      mass: 127.6031264846604,
      name: 'Tellurium',
      monoisotopicMass: 129.906222748
    }, {
      number: 53,
      isotopes: [{
        nominal: 107,
        mass: 106.94678
      }, {
        nominal: 108,
        mass: 107.94348
      }, {
        nominal: 109,
        mass: 108.9380853
      }, {
        nominal: 110,
        mass: 109.935089
      }, {
        nominal: 111,
        mass: 110.9302692
      }, {
        nominal: 112,
        mass: 111.928005
      }, {
        nominal: 113,
        mass: 112.9236501
      }, {
        nominal: 114,
        mass: 113.92185
      }, {
        nominal: 115,
        mass: 114.918048
      }, {
        nominal: 116,
        mass: 115.91681
      }, {
        nominal: 117,
        mass: 116.913648
      }, {
        nominal: 118,
        mass: 117.913074
      }, {
        nominal: 119,
        mass: 118.910074
      }, {
        nominal: 120,
        mass: 119.910087
      }, {
        nominal: 121,
        mass: 120.9074051
      }, {
        nominal: 122,
        mass: 121.9075888
      }, {
        nominal: 123,
        mass: 122.9055885
      }, {
        nominal: 124,
        mass: 123.906209
      }, {
        nominal: 125,
        mass: 124.9046294
      }, {
        nominal: 126,
        mass: 125.9056233
      }, {
        nominal: 127,
        mass: 126.9044719,
        abundance: 1
      }, {
        nominal: 128,
        mass: 127.9058086
      }, {
        nominal: 129,
        mass: 128.9049837
      }, {
        nominal: 130,
        mass: 129.9066702
      }, {
        nominal: 131,
        mass: 130.9061263
      }, {
        nominal: 132,
        mass: 131.9079935
      }, {
        nominal: 133,
        mass: 132.907797
      }, {
        nominal: 134,
        mass: 133.9097588
      }, {
        nominal: 135,
        mass: 134.9100488
      }, {
        nominal: 136,
        mass: 135.914604
      }, {
        nominal: 137,
        mass: 136.9180282
      }, {
        nominal: 138,
        mass: 137.9227264
      }, {
        nominal: 139,
        mass: 138.926506
      }, {
        nominal: 140,
        mass: 139.93173
      }, {
        nominal: 141,
        mass: 140.93569
      }, {
        nominal: 142,
        mass: 141.9412
      }, {
        nominal: 143,
        mass: 142.94565
      }, {
        nominal: 144,
        mass: 143.95139
      }, {
        nominal: 145,
        mass: 144.95605
      }],
      symbol: 'I',
      mass: 126.9044719,
      name: 'Iodine',
      monoisotopicMass: 126.9044719
    }, {
      number: 54,
      isotopes: [{
        nominal: 109,
        mass: 108.95043
      }, {
        nominal: 110,
        mass: 109.94426
      }, {
        nominal: 111,
        mass: 110.941607
      }, {
        nominal: 112,
        mass: 111.935559
      }, {
        nominal: 113,
        mass: 112.9332217
      }, {
        nominal: 114,
        mass: 113.92798
      }, {
        nominal: 115,
        mass: 114.926294
      }, {
        nominal: 116,
        mass: 115.921581
      }, {
        nominal: 117,
        mass: 116.920359
      }, {
        nominal: 118,
        mass: 117.916179
      }, {
        nominal: 119,
        mass: 118.915411
      }, {
        nominal: 120,
        mass: 119.911784
      }, {
        nominal: 121,
        mass: 120.911453
      }, {
        nominal: 122,
        mass: 121.908368
      }, {
        nominal: 123,
        mass: 122.908482
      }, {
        nominal: 124,
        mass: 123.905892,
        abundance: 0.000952
      }, {
        nominal: 125,
        mass: 124.9063944
      }, {
        nominal: 126,
        mass: 125.9042983,
        abundance: 0.00089
      }, {
        nominal: 127,
        mass: 126.9051829
      }, {
        nominal: 128,
        mass: 127.903531,
        abundance: 0.019102
      }, {
        nominal: 129,
        mass: 128.9047808611,
        abundance: 0.264006
      }, {
        nominal: 130,
        mass: 129.903509349,
        abundance: 0.04071
      }, {
        nominal: 131,
        mass: 130.90508406,
        abundance: 0.212324
      }, {
        nominal: 132,
        mass: 131.9041550856,
        abundance: 0.269086
      }, {
        nominal: 133,
        mass: 132.9059108
      }, {
        nominal: 134,
        mass: 133.90539466,
        abundance: 0.104357
      }, {
        nominal: 135,
        mass: 134.9072278
      }, {
        nominal: 136,
        mass: 135.907214484,
        abundance: 0.088573
      }, {
        nominal: 137,
        mass: 136.91155778
      }, {
        nominal: 138,
        mass: 137.9141463
      }, {
        nominal: 139,
        mass: 138.9187922
      }, {
        nominal: 140,
        mass: 139.9216458
      }, {
        nominal: 141,
        mass: 140.9267872
      }, {
        nominal: 142,
        mass: 141.9299731
      }, {
        nominal: 143,
        mass: 142.9353696
      }, {
        nominal: 144,
        mass: 143.9389451
      }, {
        nominal: 145,
        mass: 144.94472
      }, {
        nominal: 146,
        mass: 145.948518
      }, {
        nominal: 147,
        mass: 146.95426
      }, {
        nominal: 148,
        mass: 147.95813
      }],
      symbol: 'Xe',
      mass: 131.29276144779053,
      name: 'Xenon',
      monoisotopicMass: 131.9041550856
    }, {
      number: 55,
      isotopes: [{
        nominal: 112,
        mass: 111.950309
      }, {
        nominal: 113,
        mass: 112.9444291
      }, {
        nominal: 114,
        mass: 113.941296
      }, {
        nominal: 115,
        mass: 114.93591
      }, {
        nominal: 116,
        mass: 115.93337
      }, {
        nominal: 117,
        mass: 116.928617
      }, {
        nominal: 118,
        mass: 117.92656
      }, {
        nominal: 119,
        mass: 118.922377
      }, {
        nominal: 120,
        mass: 119.920677
      }, {
        nominal: 121,
        mass: 120.917227
      }, {
        nominal: 122,
        mass: 121.916108
      }, {
        nominal: 123,
        mass: 122.912996
      }, {
        nominal: 124,
        mass: 123.9122578
      }, {
        nominal: 125,
        mass: 124.909728
      }, {
        nominal: 126,
        mass: 125.909446
      }, {
        nominal: 127,
        mass: 126.9074174
      }, {
        nominal: 128,
        mass: 127.9077487
      }, {
        nominal: 129,
        mass: 128.9060657
      }, {
        nominal: 130,
        mass: 129.9067093
      }, {
        nominal: 131,
        mass: 130.9054649
      }, {
        nominal: 132,
        mass: 131.9064339
      }, {
        nominal: 133,
        mass: 132.905451961,
        abundance: 1
      }, {
        nominal: 134,
        mass: 133.906718503
      }, {
        nominal: 135,
        mass: 134.905977
      }, {
        nominal: 136,
        mass: 135.9073114
      }, {
        nominal: 137,
        mass: 136.90708923
      }, {
        nominal: 138,
        mass: 137.9110171
      }, {
        nominal: 139,
        mass: 138.9133638
      }, {
        nominal: 140,
        mass: 139.9172831
      }, {
        nominal: 141,
        mass: 140.9200455
      }, {
        nominal: 142,
        mass: 141.924296
      }, {
        nominal: 143,
        mass: 142.927349
      }, {
        nominal: 144,
        mass: 143.932076
      }, {
        nominal: 145,
        mass: 144.935527
      }, {
        nominal: 146,
        mass: 145.940344
      }, {
        nominal: 147,
        mass: 146.944156
      }, {
        nominal: 148,
        mass: 147.94923
      }, {
        nominal: 149,
        mass: 148.95302
      }, {
        nominal: 150,
        mass: 149.95833
      }, {
        nominal: 151,
        mass: 150.96258
      }],
      symbol: 'Cs',
      mass: 132.905451961,
      name: 'Caesium',
      monoisotopicMass: 132.905451961
    }, {
      number: 56,
      isotopes: [{
        nominal: 114,
        mass: 113.95066
      }, {
        nominal: 115,
        mass: 114.94737
      }, {
        nominal: 116,
        mass: 115.94128
      }, {
        nominal: 117,
        mass: 116.93814
      }, {
        nominal: 118,
        mass: 117.93306
      }, {
        nominal: 119,
        mass: 118.93066
      }, {
        nominal: 120,
        mass: 119.92605
      }, {
        nominal: 121,
        mass: 120.92405
      }, {
        nominal: 122,
        mass: 121.919904
      }, {
        nominal: 123,
        mass: 122.918781
      }, {
        nominal: 124,
        mass: 123.915094
      }, {
        nominal: 125,
        mass: 124.914472
      }, {
        nominal: 126,
        mass: 125.91125
      }, {
        nominal: 127,
        mass: 126.911091
      }, {
        nominal: 128,
        mass: 127.908342
      }, {
        nominal: 129,
        mass: 128.908681
      }, {
        nominal: 130,
        mass: 129.9063207,
        abundance: 0.00106
      }, {
        nominal: 131,
        mass: 130.906941
      }, {
        nominal: 132,
        mass: 131.9050611,
        abundance: 0.00101
      }, {
        nominal: 133,
        mass: 132.9060074
      }, {
        nominal: 134,
        mass: 133.90450818,
        abundance: 0.02417
      }, {
        nominal: 135,
        mass: 134.90568838,
        abundance: 0.06592
      }, {
        nominal: 136,
        mass: 135.90457573,
        abundance: 0.07854
      }, {
        nominal: 137,
        mass: 136.90582714,
        abundance: 0.11232
      }, {
        nominal: 138,
        mass: 137.905247,
        abundance: 0.71698
      }, {
        nominal: 139,
        mass: 138.9088411
      }, {
        nominal: 140,
        mass: 139.9106057
      }, {
        nominal: 141,
        mass: 140.9144033
      }, {
        nominal: 142,
        mass: 141.9164324
      }, {
        nominal: 143,
        mass: 142.9206253
      }, {
        nominal: 144,
        mass: 143.9229549
      }, {
        nominal: 145,
        mass: 144.9275184
      }, {
        nominal: 146,
        mass: 145.930284
      }, {
        nominal: 147,
        mass: 146.935304
      }, {
        nominal: 148,
        mass: 147.938171
      }, {
        nominal: 149,
        mass: 148.94308
      }, {
        nominal: 150,
        mass: 149.94605
      }, {
        nominal: 151,
        mass: 150.95127
      }, {
        nominal: 152,
        mass: 151.95481
      }, {
        nominal: 153,
        mass: 152.96036
      }],
      symbol: 'Ba',
      mass: 137.3268916286322,
      name: 'Barium',
      monoisotopicMass: 137.905247
    }, {
      number: 57,
      isotopes: [{
        nominal: 116,
        mass: 115.9563
      }, {
        nominal: 117,
        mass: 116.94999
      }, {
        nominal: 118,
        mass: 117.94673
      }, {
        nominal: 119,
        mass: 118.94099
      }, {
        nominal: 120,
        mass: 119.93807
      }, {
        nominal: 121,
        mass: 120.93315
      }, {
        nominal: 122,
        mass: 121.93071
      }, {
        nominal: 123,
        mass: 122.9263
      }, {
        nominal: 124,
        mass: 123.924574
      }, {
        nominal: 125,
        mass: 124.920816
      }, {
        nominal: 126,
        mass: 125.919513
      }, {
        nominal: 127,
        mass: 126.916375
      }, {
        nominal: 128,
        mass: 127.915592
      }, {
        nominal: 129,
        mass: 128.912694
      }, {
        nominal: 130,
        mass: 129.912369
      }, {
        nominal: 131,
        mass: 130.91007
      }, {
        nominal: 132,
        mass: 131.910119
      }, {
        nominal: 133,
        mass: 132.908218
      }, {
        nominal: 134,
        mass: 133.908514
      }, {
        nominal: 135,
        mass: 134.906984
      }, {
        nominal: 136,
        mass: 135.907635
      }, {
        nominal: 137,
        mass: 136.9064504
      }, {
        nominal: 138,
        mass: 137.9071149,
        abundance: 0.0008881
      }, {
        nominal: 139,
        mass: 138.9063563,
        abundance: 0.9991119
      }, {
        nominal: 140,
        mass: 139.9094806
      }, {
        nominal: 141,
        mass: 140.910966
      }, {
        nominal: 142,
        mass: 141.9140909
      }, {
        nominal: 143,
        mass: 142.9160795
      }, {
        nominal: 144,
        mass: 143.919646
      }, {
        nominal: 145,
        mass: 144.921808
      }, {
        nominal: 146,
        mass: 145.925875
      }, {
        nominal: 147,
        mass: 146.928418
      }, {
        nominal: 148,
        mass: 147.932679
      }, {
        nominal: 149,
        mass: 148.93535
      }, {
        nominal: 150,
        mass: 149.93947
      }, {
        nominal: 151,
        mass: 150.94232
      }, {
        nominal: 152,
        mass: 151.94682
      }, {
        nominal: 153,
        mass: 152.95036
      }, {
        nominal: 154,
        mass: 153.95517
      }, {
        nominal: 155,
        mass: 154.95901
      }],
      symbol: 'La',
      mass: 138.90546887371266,
      name: 'Lanthanum',
      monoisotopicMass: 138.9063563
    }, {
      number: 58,
      isotopes: [{
        nominal: 119,
        mass: 118.95271
      }, {
        nominal: 120,
        mass: 119.94654
      }, {
        nominal: 121,
        mass: 120.94335
      }, {
        nominal: 122,
        mass: 121.93787
      }, {
        nominal: 123,
        mass: 122.93528
      }, {
        nominal: 124,
        mass: 123.93031
      }, {
        nominal: 125,
        mass: 124.92844
      }, {
        nominal: 126,
        mass: 125.923971
      }, {
        nominal: 127,
        mass: 126.922727
      }, {
        nominal: 128,
        mass: 127.918911
      }, {
        nominal: 129,
        mass: 128.918102
      }, {
        nominal: 130,
        mass: 129.914736
      }, {
        nominal: 131,
        mass: 130.914429
      }, {
        nominal: 132,
        mass: 131.911464
      }, {
        nominal: 133,
        mass: 132.91152
      }, {
        nominal: 134,
        mass: 133.908928
      }, {
        nominal: 135,
        mass: 134.909161
      }, {
        nominal: 136,
        mass: 135.90712921,
        abundance: 0.00185
      }, {
        nominal: 137,
        mass: 136.90776236
      }, {
        nominal: 138,
        mass: 137.905991,
        abundance: 0.00251
      }, {
        nominal: 139,
        mass: 138.9066551
      }, {
        nominal: 140,
        mass: 139.9054431,
        abundance: 0.8845
      }, {
        nominal: 141,
        mass: 140.9082807
      }, {
        nominal: 142,
        mass: 141.9092504,
        abundance: 0.11114
      }, {
        nominal: 143,
        mass: 142.9123921
      }, {
        nominal: 144,
        mass: 143.9136529
      }, {
        nominal: 145,
        mass: 144.917265
      }, {
        nominal: 146,
        mass: 145.918802
      }, {
        nominal: 147,
        mass: 146.9226899
      }, {
        nominal: 148,
        mass: 147.924424
      }, {
        nominal: 149,
        mass: 148.928427
      }, {
        nominal: 150,
        mass: 149.930384
      }, {
        nominal: 151,
        mass: 150.934272
      }, {
        nominal: 152,
        mass: 151.9366
      }, {
        nominal: 153,
        mass: 152.94093
      }, {
        nominal: 154,
        mass: 153.9438
      }, {
        nominal: 155,
        mass: 154.94855
      }, {
        nominal: 156,
        mass: 155.95183
      }, {
        nominal: 157,
        mass: 156.95705
      }],
      symbol: 'Ce',
      mass: 140.1157307378545,
      name: 'Cerium',
      monoisotopicMass: 139.9054431
    }, {
      number: 59,
      isotopes: [{
        nominal: 121,
        mass: 120.95532
      }, {
        nominal: 122,
        mass: 121.95175
      }, {
        nominal: 123,
        mass: 122.94596
      }, {
        nominal: 124,
        mass: 123.94294
      }, {
        nominal: 125,
        mass: 124.9377
      }, {
        nominal: 126,
        mass: 125.93524
      }, {
        nominal: 127,
        mass: 126.93071
      }, {
        nominal: 128,
        mass: 127.928791
      }, {
        nominal: 129,
        mass: 128.925095
      }, {
        nominal: 130,
        mass: 129.92359
      }, {
        nominal: 131,
        mass: 130.920235
      }, {
        nominal: 132,
        mass: 131.919255
      }, {
        nominal: 133,
        mass: 132.916331
      }, {
        nominal: 134,
        mass: 133.915697
      }, {
        nominal: 135,
        mass: 134.913112
      }, {
        nominal: 136,
        mass: 135.912677
      }, {
        nominal: 137,
        mass: 136.9106792
      }, {
        nominal: 138,
        mass: 137.910754
      }, {
        nominal: 139,
        mass: 138.9089408
      }, {
        nominal: 140,
        mass: 139.9090803
      }, {
        nominal: 141,
        mass: 140.9076576,
        abundance: 1
      }, {
        nominal: 142,
        mass: 141.9100496
      }, {
        nominal: 143,
        mass: 142.9108228
      }, {
        nominal: 144,
        mass: 143.9133109
      }, {
        nominal: 145,
        mass: 144.9145182
      }, {
        nominal: 146,
        mass: 145.91768
      }, {
        nominal: 147,
        mass: 146.919008
      }, {
        nominal: 148,
        mass: 147.92213
      }, {
        nominal: 149,
        mass: 148.923736
      }, {
        nominal: 150,
        mass: 149.9266765
      }, {
        nominal: 151,
        mass: 150.928309
      }, {
        nominal: 152,
        mass: 151.931553
      }, {
        nominal: 153,
        mass: 152.933904
      }, {
        nominal: 154,
        mass: 153.93753
      }, {
        nominal: 155,
        mass: 154.940509
      }, {
        nominal: 156,
        mass: 155.94464
      }, {
        nominal: 157,
        mass: 156.94789
      }, {
        nominal: 158,
        mass: 157.95241
      }, {
        nominal: 159,
        mass: 158.95589
      }],
      symbol: 'Pr',
      mass: 140.9076576,
      name: 'Praseodymium',
      monoisotopicMass: 140.9076576
    }, {
      number: 60,
      isotopes: [{
        nominal: 124,
        mass: 123.9522
      }, {
        nominal: 125,
        mass: 124.9489
      }, {
        nominal: 126,
        mass: 125.94311
      }, {
        nominal: 127,
        mass: 126.94038
      }, {
        nominal: 128,
        mass: 127.93525
      }, {
        nominal: 129,
        mass: 128.9331
      }, {
        nominal: 130,
        mass: 129.928506
      }, {
        nominal: 131,
        mass: 130.927248
      }, {
        nominal: 132,
        mass: 131.923321
      }, {
        nominal: 133,
        mass: 132.922348
      }, {
        nominal: 134,
        mass: 133.91879
      }, {
        nominal: 135,
        mass: 134.918181
      }, {
        nominal: 136,
        mass: 135.914976
      }, {
        nominal: 137,
        mass: 136.914562
      }, {
        nominal: 138,
        mass: 137.91195
      }, {
        nominal: 139,
        mass: 138.911954
      }, {
        nominal: 140,
        mass: 139.90955
      }, {
        nominal: 141,
        mass: 140.9096147
      }, {
        nominal: 142,
        mass: 141.907729,
        abundance: 0.27152
      }, {
        nominal: 143,
        mass: 142.90982,
        abundance: 0.12174
      }, {
        nominal: 144,
        mass: 143.910093,
        abundance: 0.23798
      }, {
        nominal: 145,
        mass: 144.9125793,
        abundance: 0.08293
      }, {
        nominal: 146,
        mass: 145.9131226,
        abundance: 0.17189
      }, {
        nominal: 147,
        mass: 146.9161061
      }, {
        nominal: 148,
        mass: 147.9168993,
        abundance: 0.05756
      }, {
        nominal: 149,
        mass: 148.9201548
      }, {
        nominal: 150,
        mass: 149.9209022,
        abundance: 0.05638
      }, {
        nominal: 151,
        mass: 150.9238403
      }, {
        nominal: 152,
        mass: 151.924692
      }, {
        nominal: 153,
        mass: 152.927718
      }, {
        nominal: 154,
        mass: 153.92948
      }, {
        nominal: 155,
        mass: 154.9331357
      }, {
        nominal: 156,
        mass: 155.93508
      }, {
        nominal: 157,
        mass: 156.939386
      }, {
        nominal: 158,
        mass: 157.94197
      }, {
        nominal: 159,
        mass: 158.94653
      }, {
        nominal: 160,
        mass: 159.9494
      }, {
        nominal: 161,
        mass: 160.95428
      }],
      symbol: 'Nd',
      mass: 144.241596031827,
      name: 'Neodymium',
      monoisotopicMass: 141.907729
    }, {
      number: 61,
      isotopes: [{
        nominal: 126,
        mass: 125.95792
      }, {
        nominal: 127,
        mass: 126.95192
      }, {
        nominal: 128,
        mass: 127.9487
      }, {
        nominal: 129,
        mass: 128.94323
      }, {
        nominal: 130,
        mass: 129.94053
      }, {
        nominal: 131,
        mass: 130.93567
      }, {
        nominal: 132,
        mass: 131.93384
      }, {
        nominal: 133,
        mass: 132.929782
      }, {
        nominal: 134,
        mass: 133.928353
      }, {
        nominal: 135,
        mass: 134.924823
      }, {
        nominal: 136,
        mass: 135.923585
      }, {
        nominal: 137,
        mass: 136.92048
      }, {
        nominal: 138,
        mass: 137.919548
      }, {
        nominal: 139,
        mass: 138.9168
      }, {
        nominal: 140,
        mass: 139.91604
      }, {
        nominal: 141,
        mass: 140.913555
      }, {
        nominal: 142,
        mass: 141.91289
      }, {
        nominal: 143,
        mass: 142.9109383
      }, {
        nominal: 144,
        mass: 143.9125964
      }, {
        nominal: 145,
        mass: 144.9127559
      }, {
        nominal: 146,
        mass: 145.9147024
      }, {
        nominal: 147,
        mass: 146.915145
      }, {
        nominal: 148,
        mass: 147.9174819
      }, {
        nominal: 149,
        mass: 148.9183423
      }, {
        nominal: 150,
        mass: 149.920991
      }, {
        nominal: 151,
        mass: 150.9212175
      }, {
        nominal: 152,
        mass: 151.923506
      }, {
        nominal: 153,
        mass: 152.9241567
      }, {
        nominal: 154,
        mass: 153.926472
      }, {
        nominal: 155,
        mass: 154.928137
      }, {
        nominal: 156,
        mass: 155.9311175
      }, {
        nominal: 157,
        mass: 156.9331214
      }, {
        nominal: 158,
        mass: 157.936565
      }, {
        nominal: 159,
        mass: 158.939287
      }, {
        nominal: 160,
        mass: 159.9431
      }, {
        nominal: 161,
        mass: 160.94607
      }, {
        nominal: 162,
        mass: 161.95022
      }, {
        nominal: 163,
        mass: 162.95357
      }],
      symbol: 'Pm',
      mass: null,
      name: 'Promethium'
    }, {
      number: 62,
      isotopes: [{
        nominal: 128,
        mass: 127.95842
      }, {
        nominal: 129,
        mass: 128.95476
      }, {
        nominal: 130,
        mass: 129.949
      }, {
        nominal: 131,
        mass: 130.94618
      }, {
        nominal: 132,
        mass: 131.94087
      }, {
        nominal: 133,
        mass: 132.93856
      }, {
        nominal: 134,
        mass: 133.93411
      }, {
        nominal: 135,
        mass: 134.93252
      }, {
        nominal: 136,
        mass: 135.928276
      }, {
        nominal: 137,
        mass: 136.926971
      }, {
        nominal: 138,
        mass: 137.923244
      }, {
        nominal: 139,
        mass: 138.922297
      }, {
        nominal: 140,
        mass: 139.918995
      }, {
        nominal: 141,
        mass: 140.9184816
      }, {
        nominal: 142,
        mass: 141.9152044
      }, {
        nominal: 143,
        mass: 142.9146353
      }, {
        nominal: 144,
        mass: 143.9120065,
        abundance: 0.0307
      }, {
        nominal: 145,
        mass: 144.9134173
      }, {
        nominal: 146,
        mass: 145.913047
      }, {
        nominal: 147,
        mass: 146.9149044,
        abundance: 0.1499
      }, {
        nominal: 148,
        mass: 147.9148292,
        abundance: 0.1124
      }, {
        nominal: 149,
        mass: 148.9171921,
        abundance: 0.1382
      }, {
        nominal: 150,
        mass: 149.9172829,
        abundance: 0.0738
      }, {
        nominal: 151,
        mass: 150.9199398
      }, {
        nominal: 152,
        mass: 151.9197397,
        abundance: 0.2675
      }, {
        nominal: 153,
        mass: 152.9221047
      }, {
        nominal: 154,
        mass: 153.9222169,
        abundance: 0.2275
      }, {
        nominal: 155,
        mass: 154.9246477
      }, {
        nominal: 156,
        mass: 155.925536
      }, {
        nominal: 157,
        mass: 156.9284187
      }, {
        nominal: 158,
        mass: 157.929951
      }, {
        nominal: 159,
        mass: 158.9332172
      }, {
        nominal: 160,
        mass: 159.9353353
      }, {
        nominal: 161,
        mass: 160.9391602
      }, {
        nominal: 162,
        mass: 161.94146
      }, {
        nominal: 163,
        mass: 162.94555
      }, {
        nominal: 164,
        mass: 163.94836
      }, {
        nominal: 165,
        mass: 164.95297
      }],
      symbol: 'Sm',
      mass: 150.36635571193,
      name: 'Samarium',
      monoisotopicMass: 151.9197397
    }, {
      number: 63,
      isotopes: [{
        nominal: 130,
        mass: 129.96369
      }, {
        nominal: 131,
        mass: 130.95784
      }, {
        nominal: 132,
        mass: 131.95467
      }, {
        nominal: 133,
        mass: 132.94929
      }, {
        nominal: 134,
        mass: 133.9464
      }, {
        nominal: 135,
        mass: 134.94187
      }, {
        nominal: 136,
        mass: 135.93962
      }, {
        nominal: 137,
        mass: 136.93546
      }, {
        nominal: 138,
        mass: 137.933709
      }, {
        nominal: 139,
        mass: 138.929792
      }, {
        nominal: 140,
        mass: 139.928088
      }, {
        nominal: 141,
        mass: 140.924932
      }, {
        nominal: 142,
        mass: 141.923442
      }, {
        nominal: 143,
        mass: 142.920299
      }, {
        nominal: 144,
        mass: 143.91882
      }, {
        nominal: 145,
        mass: 144.9162726
      }, {
        nominal: 146,
        mass: 145.917211
      }, {
        nominal: 147,
        mass: 146.9167527
      }, {
        nominal: 148,
        mass: 147.918089
      }, {
        nominal: 149,
        mass: 148.9179378
      }, {
        nominal: 150,
        mass: 149.9197077
      }, {
        nominal: 151,
        mass: 150.9198578,
        abundance: 0.4781
      }, {
        nominal: 152,
        mass: 151.9217522
      }, {
        nominal: 153,
        mass: 152.921238,
        abundance: 0.5219
      }, {
        nominal: 154,
        mass: 153.922987
      }, {
        nominal: 155,
        mass: 154.9229011
      }, {
        nominal: 156,
        mass: 155.9247605
      }, {
        nominal: 157,
        mass: 156.9254334
      }, {
        nominal: 158,
        mass: 157.927799
      }, {
        nominal: 159,
        mass: 158.9291001
      }, {
        nominal: 160,
        mass: 159.931851
      }, {
        nominal: 161,
        mass: 160.933664
      }, {
        nominal: 162,
        mass: 161.936989
      }, {
        nominal: 163,
        mass: 162.939196
      }, {
        nominal: 164,
        mass: 163.94274
      }, {
        nominal: 165,
        mass: 164.94559
      }, {
        nominal: 166,
        mass: 165.94962
      }, {
        nominal: 167,
        mass: 166.95289
      }],
      symbol: 'Eu',
      mass: 151.96437812637998,
      name: 'Europium',
      monoisotopicMass: 152.921238
    }, {
      number: 64,
      isotopes: [{
        nominal: 133,
        mass: 132.96133
      }, {
        nominal: 134,
        mass: 133.95566
      }, {
        nominal: 135,
        mass: 134.95245
      }, {
        nominal: 136,
        mass: 135.9473
      }, {
        nominal: 137,
        mass: 136.94502
      }, {
        nominal: 138,
        mass: 137.94025
      }, {
        nominal: 139,
        mass: 138.93813
      }, {
        nominal: 140,
        mass: 139.933674
      }, {
        nominal: 141,
        mass: 140.932126
      }, {
        nominal: 142,
        mass: 141.928116
      }, {
        nominal: 143,
        mass: 142.92675
      }, {
        nominal: 144,
        mass: 143.922963
      }, {
        nominal: 145,
        mass: 144.921713
      }, {
        nominal: 146,
        mass: 145.9183188
      }, {
        nominal: 147,
        mass: 146.9191014
      }, {
        nominal: 148,
        mass: 147.9181215
      }, {
        nominal: 149,
        mass: 148.9193481
      }, {
        nominal: 150,
        mass: 149.9186644
      }, {
        nominal: 151,
        mass: 150.920356
      }, {
        nominal: 152,
        mass: 151.9197995,
        abundance: 0.002
      }, {
        nominal: 153,
        mass: 152.921758
      }, {
        nominal: 154,
        mass: 153.9208741,
        abundance: 0.0218
      }, {
        nominal: 155,
        mass: 154.9226305,
        abundance: 0.148
      }, {
        nominal: 156,
        mass: 155.9221312,
        abundance: 0.2047
      }, {
        nominal: 157,
        mass: 156.9239686,
        abundance: 0.1565
      }, {
        nominal: 158,
        mass: 157.9241123,
        abundance: 0.2484
      }, {
        nominal: 159,
        mass: 158.926397
      }, {
        nominal: 160,
        mass: 159.9270624,
        abundance: 0.2186
      }, {
        nominal: 161,
        mass: 160.9296775
      }, {
        nominal: 162,
        mass: 161.930993
      }, {
        nominal: 163,
        mass: 162.9341769
      }, {
        nominal: 164,
        mass: 163.93583
      }, {
        nominal: 165,
        mass: 164.93936
      }, {
        nominal: 166,
        mass: 165.94146
      }, {
        nominal: 167,
        mass: 166.94545
      }, {
        nominal: 168,
        mass: 167.94808
      }, {
        nominal: 169,
        mass: 168.9526
      }],
      symbol: 'Gd',
      mass: 157.25213064687998,
      name: 'Gadolinium',
      monoisotopicMass: 157.9241123
    }, {
      number: 65,
      isotopes: [{
        nominal: 135,
        mass: 134.96476
      }, {
        nominal: 136,
        mass: 135.96129
      }, {
        nominal: 137,
        mass: 136.95602
      }, {
        nominal: 138,
        mass: 137.95312
      }, {
        nominal: 139,
        mass: 138.94833
      }, {
        nominal: 140,
        mass: 139.94581
      }, {
        nominal: 141,
        mass: 140.94145
      }, {
        nominal: 142,
        mass: 141.93928
      }, {
        nominal: 143,
        mass: 142.935137
      }, {
        nominal: 144,
        mass: 143.933045
      }, {
        nominal: 145,
        mass: 144.92882
      }, {
        nominal: 146,
        mass: 145.927253
      }, {
        nominal: 147,
        mass: 146.9240548
      }, {
        nominal: 148,
        mass: 147.924282
      }, {
        nominal: 149,
        mass: 148.9232535
      }, {
        nominal: 150,
        mass: 149.9236649
      }, {
        nominal: 151,
        mass: 150.9231096
      }, {
        nominal: 152,
        mass: 151.924083
      }, {
        nominal: 153,
        mass: 152.9234424
      }, {
        nominal: 154,
        mass: 153.924685
      }, {
        nominal: 155,
        mass: 154.923511
      }, {
        nominal: 156,
        mass: 155.9247552
      }, {
        nominal: 157,
        mass: 156.924033
      }, {
        nominal: 158,
        mass: 157.9254209
      }, {
        nominal: 159,
        mass: 158.9253547,
        abundance: 1
      }, {
        nominal: 160,
        mass: 159.9271756
      }, {
        nominal: 161,
        mass: 160.9275778
      }, {
        nominal: 162,
        mass: 161.929495
      }, {
        nominal: 163,
        mass: 162.9306547
      }, {
        nominal: 164,
        mass: 163.93336
      }, {
        nominal: 165,
        mass: 164.93498
      }, {
        nominal: 166,
        mass: 165.93786
      }, {
        nominal: 167,
        mass: 166.93996
      }, {
        nominal: 168,
        mass: 167.9434
      }, {
        nominal: 169,
        mass: 168.94597
      }, {
        nominal: 170,
        mass: 169.94984
      }, {
        nominal: 171,
        mass: 170.95273
      }],
      symbol: 'Tb',
      mass: 158.9253547,
      name: 'Terbium',
      monoisotopicMass: 158.9253547
    }, {
      number: 66,
      isotopes: [{
        nominal: 138,
        mass: 137.9625
      }, {
        nominal: 139,
        mass: 138.95959
      }, {
        nominal: 140,
        mass: 139.95402
      }, {
        nominal: 141,
        mass: 140.95128
      }, {
        nominal: 142,
        mass: 141.94619
      }, {
        nominal: 143,
        mass: 142.943994
      }, {
        nominal: 144,
        mass: 143.9392695
      }, {
        nominal: 145,
        mass: 144.937474
      }, {
        nominal: 146,
        mass: 145.9328445
      }, {
        nominal: 147,
        mass: 146.9310827
      }, {
        nominal: 148,
        mass: 147.927157
      }, {
        nominal: 149,
        mass: 148.927322
      }, {
        nominal: 150,
        mass: 149.9255933
      }, {
        nominal: 151,
        mass: 150.9261916
      }, {
        nominal: 152,
        mass: 151.9247253
      }, {
        nominal: 153,
        mass: 152.9257724
      }, {
        nominal: 154,
        mass: 153.9244293
      }, {
        nominal: 155,
        mass: 154.925759
      }, {
        nominal: 156,
        mass: 155.9242847,
        abundance: 0.00056
      }, {
        nominal: 157,
        mass: 156.9254707
      }, {
        nominal: 158,
        mass: 157.9244159,
        abundance: 0.00095
      }, {
        nominal: 159,
        mass: 158.925747
      }, {
        nominal: 160,
        mass: 159.9252046,
        abundance: 0.02329
      }, {
        nominal: 161,
        mass: 160.9269405,
        abundance: 0.18889
      }, {
        nominal: 162,
        mass: 161.9268056,
        abundance: 0.25475
      }, {
        nominal: 163,
        mass: 162.9287383,
        abundance: 0.24896
      }, {
        nominal: 164,
        mass: 163.9291819,
        abundance: 0.2826
      }, {
        nominal: 165,
        mass: 164.9317105
      }, {
        nominal: 166,
        mass: 165.9328139
      }, {
        nominal: 167,
        mass: 166.935661
      }, {
        nominal: 168,
        mass: 167.93713
      }, {
        nominal: 169,
        mass: 168.94031
      }, {
        nominal: 170,
        mass: 169.94239
      }, {
        nominal: 171,
        mass: 170.94612
      }, {
        nominal: 172,
        mass: 171.94846
      }, {
        nominal: 173,
        mass: 172.95283
      }],
      symbol: 'Dy',
      mass: 162.499472819424,
      name: 'Dysprosium',
      monoisotopicMass: 163.9291819
    }, {
      number: 67,
      isotopes: [{
        nominal: 140,
        mass: 139.96859
      }, {
        nominal: 141,
        mass: 140.96311
      }, {
        nominal: 142,
        mass: 141.96001
      }, {
        nominal: 143,
        mass: 142.95486
      }, {
        nominal: 144,
        mass: 143.9521097
      }, {
        nominal: 145,
        mass: 144.9472674
      }, {
        nominal: 146,
        mass: 145.9449935
      }, {
        nominal: 147,
        mass: 146.9401423
      }, {
        nominal: 148,
        mass: 147.937744
      }, {
        nominal: 149,
        mass: 148.933803
      }, {
        nominal: 150,
        mass: 149.933498
      }, {
        nominal: 151,
        mass: 150.9316983
      }, {
        nominal: 152,
        mass: 151.931724
      }, {
        nominal: 153,
        mass: 152.9302064
      }, {
        nominal: 154,
        mass: 153.9306068
      }, {
        nominal: 155,
        mass: 154.929104
      }, {
        nominal: 156,
        mass: 155.929706
      }, {
        nominal: 157,
        mass: 156.928254
      }, {
        nominal: 158,
        mass: 157.928946
      }, {
        nominal: 159,
        mass: 158.9277197
      }, {
        nominal: 160,
        mass: 159.928737
      }, {
        nominal: 161,
        mass: 160.9278615
      }, {
        nominal: 162,
        mass: 161.9291023
      }, {
        nominal: 163,
        mass: 162.928741
      }, {
        nominal: 164,
        mass: 163.9302403
      }, {
        nominal: 165,
        mass: 164.9303288,
        abundance: 1
      }, {
        nominal: 166,
        mass: 165.9322909
      }, {
        nominal: 167,
        mass: 166.9331385
      }, {
        nominal: 168,
        mass: 167.935522
      }, {
        nominal: 169,
        mass: 168.936878
      }, {
        nominal: 170,
        mass: 169.939625
      }, {
        nominal: 171,
        mass: 170.94147
      }, {
        nominal: 172,
        mass: 171.94473
      }, {
        nominal: 173,
        mass: 172.94702
      }, {
        nominal: 174,
        mass: 173.95095
      }, {
        nominal: 175,
        mass: 174.95362
      }],
      symbol: 'Ho',
      mass: 164.9303288,
      name: 'Holmium',
      monoisotopicMass: 164.9303288
    }, {
      number: 68,
      isotopes: [{
        nominal: 142,
        mass: 141.9701
      }, {
        nominal: 143,
        mass: 142.96662
      }, {
        nominal: 144,
        mass: 143.9607
      }, {
        nominal: 145,
        mass: 144.95805
      }, {
        nominal: 146,
        mass: 145.9524184
      }, {
        nominal: 147,
        mass: 146.949964
      }, {
        nominal: 148,
        mass: 147.944735
      }, {
        nominal: 149,
        mass: 148.942306
      }, {
        nominal: 150,
        mass: 149.937916
      }, {
        nominal: 151,
        mass: 150.937449
      }, {
        nominal: 152,
        mass: 151.935057
      }, {
        nominal: 153,
        mass: 152.93508
      }, {
        nominal: 154,
        mass: 153.9327908
      }, {
        nominal: 155,
        mass: 154.9332159
      }, {
        nominal: 156,
        mass: 155.931067
      }, {
        nominal: 157,
        mass: 156.931949
      }, {
        nominal: 158,
        mass: 157.929893
      }, {
        nominal: 159,
        mass: 158.9306918
      }, {
        nominal: 160,
        mass: 159.929077
      }, {
        nominal: 161,
        mass: 160.9300046
      }, {
        nominal: 162,
        mass: 161.9287884,
        abundance: 0.00139
      }, {
        nominal: 163,
        mass: 162.9300408
      }, {
        nominal: 164,
        mass: 163.9292088,
        abundance: 0.01601
      }, {
        nominal: 165,
        mass: 164.9307345
      }, {
        nominal: 166,
        mass: 165.9302995,
        abundance: 0.33503
      }, {
        nominal: 167,
        mass: 166.9320546,
        abundance: 0.22869
      }, {
        nominal: 168,
        mass: 167.9323767,
        abundance: 0.26978
      }, {
        nominal: 169,
        mass: 168.9345968
      }, {
        nominal: 170,
        mass: 169.9354702,
        abundance: 0.1491
      }, {
        nominal: 171,
        mass: 170.9380357
      }, {
        nominal: 172,
        mass: 171.9393619
      }, {
        nominal: 173,
        mass: 172.9424
      }, {
        nominal: 174,
        mass: 173.94423
      }, {
        nominal: 175,
        mass: 174.94777
      }, {
        nominal: 176,
        mass: 175.94994
      }, {
        nominal: 177,
        mass: 176.95399
      }],
      symbol: 'Er',
      mass: 167.259082649669,
      name: 'Erbium',
      monoisotopicMass: 165.9302995
    }, {
      number: 69,
      isotopes: [{
        nominal: 144,
        mass: 143.97628
      }, {
        nominal: 145,
        mass: 144.97039
      }, {
        nominal: 146,
        mass: 145.96684
      }, {
        nominal: 147,
        mass: 146.9613799
      }, {
        nominal: 148,
        mass: 147.958384
      }, {
        nominal: 149,
        mass: 148.95289
      }, {
        nominal: 150,
        mass: 149.95009
      }, {
        nominal: 151,
        mass: 150.945488
      }, {
        nominal: 152,
        mass: 151.944422
      }, {
        nominal: 153,
        mass: 152.94204
      }, {
        nominal: 154,
        mass: 153.94157
      }, {
        nominal: 155,
        mass: 154.93921
      }, {
        nominal: 156,
        mass: 155.938992
      }, {
        nominal: 157,
        mass: 156.936944
      }, {
        nominal: 158,
        mass: 157.93698
      }, {
        nominal: 159,
        mass: 158.934975
      }, {
        nominal: 160,
        mass: 159.935263
      }, {
        nominal: 161,
        mass: 160.933549
      }, {
        nominal: 162,
        mass: 161.934002
      }, {
        nominal: 163,
        mass: 162.9326592
      }, {
        nominal: 164,
        mass: 163.933544
      }, {
        nominal: 165,
        mass: 164.9324431
      }, {
        nominal: 166,
        mass: 165.933561
      }, {
        nominal: 167,
        mass: 166.9328562
      }, {
        nominal: 168,
        mass: 167.9341774
      }, {
        nominal: 169,
        mass: 168.9342179,
        abundance: 1
      }, {
        nominal: 170,
        mass: 169.935806
      }, {
        nominal: 171,
        mass: 170.9364339
      }, {
        nominal: 172,
        mass: 171.9384055
      }, {
        nominal: 173,
        mass: 172.9396084
      }, {
        nominal: 174,
        mass: 173.942173
      }, {
        nominal: 175,
        mass: 174.943841
      }, {
        nominal: 176,
        mass: 175.947
      }, {
        nominal: 177,
        mass: 176.94904
      }, {
        nominal: 178,
        mass: 177.95264
      }, {
        nominal: 179,
        mass: 178.95534
      }],
      symbol: 'Tm',
      mass: 168.9342179,
      name: 'Thulium',
      monoisotopicMass: 168.9342179
    }, {
      number: 70,
      isotopes: [{
        nominal: 148,
        mass: 147.96758
      }, {
        nominal: 149,
        mass: 148.96436
      }, {
        nominal: 150,
        mass: 149.95852
      }, {
        nominal: 151,
        mass: 150.9554
      }, {
        nominal: 152,
        mass: 151.95027
      }, {
        nominal: 153,
        mass: 152.94932
      }, {
        nominal: 154,
        mass: 153.946396
      }, {
        nominal: 155,
        mass: 154.945783
      }, {
        nominal: 156,
        mass: 155.942825
      }, {
        nominal: 157,
        mass: 156.942645
      }, {
        nominal: 158,
        mass: 157.9398705
      }, {
        nominal: 159,
        mass: 158.940055
      }, {
        nominal: 160,
        mass: 159.937557
      }, {
        nominal: 161,
        mass: 160.937907
      }, {
        nominal: 162,
        mass: 161.935774
      }, {
        nominal: 163,
        mass: 162.93634
      }, {
        nominal: 164,
        mass: 163.934495
      }, {
        nominal: 165,
        mass: 164.93527
      }, {
        nominal: 166,
        mass: 165.9338747
      }, {
        nominal: 167,
        mass: 166.934953
      }, {
        nominal: 168,
        mass: 167.9338896,
        abundance: 0.00123
      }, {
        nominal: 169,
        mass: 168.9351825
      }, {
        nominal: 170,
        mass: 169.9347664,
        abundance: 0.02982
      }, {
        nominal: 171,
        mass: 170.9363302,
        abundance: 0.1409
      }, {
        nominal: 172,
        mass: 171.9363859,
        abundance: 0.2168
      }, {
        nominal: 173,
        mass: 172.9382151,
        abundance: 0.16103
      }, {
        nominal: 174,
        mass: 173.9388664,
        abundance: 0.32026
      }, {
        nominal: 175,
        mass: 174.9412808
      }, {
        nominal: 176,
        mass: 175.9425764,
        abundance: 0.12996
      }, {
        nominal: 177,
        mass: 176.9452656
      }, {
        nominal: 178,
        mass: 177.946651
      }, {
        nominal: 179,
        mass: 178.95004
      }, {
        nominal: 180,
        mass: 179.95212
      }, {
        nominal: 181,
        mass: 180.95589
      }],
      symbol: 'Yb',
      mass: 173.05415016631702,
      name: 'Ytterbium',
      monoisotopicMass: 173.9388664
    }, {
      number: 71,
      isotopes: [{
        nominal: 150,
        mass: 149.97355
      }, {
        nominal: 151,
        mass: 150.96768
      }, {
        nominal: 152,
        mass: 151.96412
      }, {
        nominal: 153,
        mass: 152.95875
      }, {
        nominal: 154,
        mass: 153.95736
      }, {
        nominal: 155,
        mass: 154.954321
      }, {
        nominal: 156,
        mass: 155.953033
      }, {
        nominal: 157,
        mass: 156.950127
      }, {
        nominal: 158,
        mass: 157.949316
      }, {
        nominal: 159,
        mass: 158.946636
      }, {
        nominal: 160,
        mass: 159.946033
      }, {
        nominal: 161,
        mass: 160.943572
      }, {
        nominal: 162,
        mass: 161.943283
      }, {
        nominal: 163,
        mass: 162.941179
      }, {
        nominal: 164,
        mass: 163.941339
      }, {
        nominal: 165,
        mass: 164.939407
      }, {
        nominal: 166,
        mass: 165.939859
      }, {
        nominal: 167,
        mass: 166.93827
      }, {
        nominal: 168,
        mass: 167.938736
      }, {
        nominal: 169,
        mass: 168.9376441
      }, {
        nominal: 170,
        mass: 169.938478
      }, {
        nominal: 171,
        mass: 170.937917
      }, {
        nominal: 172,
        mass: 171.9390891
      }, {
        nominal: 173,
        mass: 172.938934
      }, {
        nominal: 174,
        mass: 173.9403409
      }, {
        nominal: 175,
        mass: 174.9407752,
        abundance: 0.97401
      }, {
        nominal: 176,
        mass: 175.9426897,
        abundance: 0.02599
      }, {
        nominal: 177,
        mass: 176.9437615
      }, {
        nominal: 178,
        mass: 177.945958
      }, {
        nominal: 179,
        mass: 178.9473309
      }, {
        nominal: 180,
        mass: 179.949888
      }, {
        nominal: 181,
        mass: 180.95191
      }, {
        nominal: 182,
        mass: 181.95504
      }, {
        nominal: 183,
        mass: 182.957363
      }, {
        nominal: 184,
        mass: 183.96091
      }, {
        nominal: 185,
        mass: 184.96362
      }],
      symbol: 'Lu',
      mass: 174.96681495785498,
      name: 'Lutetium',
      monoisotopicMass: 174.9407752
    }, {
      number: 72,
      isotopes: [{
        nominal: 153,
        mass: 152.97069
      }, {
        nominal: 154,
        mass: 153.96486
      }, {
        nominal: 155,
        mass: 154.96311
      }, {
        nominal: 156,
        mass: 155.95935
      }, {
        nominal: 157,
        mass: 156.95824
      }, {
        nominal: 158,
        mass: 157.954801
      }, {
        nominal: 159,
        mass: 158.953996
      }, {
        nominal: 160,
        mass: 159.950691
      }, {
        nominal: 161,
        mass: 160.950278
      }, {
        nominal: 162,
        mass: 161.9472148
      }, {
        nominal: 163,
        mass: 162.947113
      }, {
        nominal: 164,
        mass: 163.944371
      }, {
        nominal: 165,
        mass: 164.944567
      }, {
        nominal: 166,
        mass: 165.94218
      }, {
        nominal: 167,
        mass: 166.9426
      }, {
        nominal: 168,
        mass: 167.940568
      }, {
        nominal: 169,
        mass: 168.941259
      }, {
        nominal: 170,
        mass: 169.939609
      }, {
        nominal: 171,
        mass: 170.940492
      }, {
        nominal: 172,
        mass: 171.93945
      }, {
        nominal: 173,
        mass: 172.940513
      }, {
        nominal: 174,
        mass: 173.9400461,
        abundance: 0.0016
      }, {
        nominal: 175,
        mass: 174.9415092
      }, {
        nominal: 176,
        mass: 175.9414076,
        abundance: 0.0526
      }, {
        nominal: 177,
        mass: 176.9432277,
        abundance: 0.186
      }, {
        nominal: 178,
        mass: 177.9437058,
        abundance: 0.2728
      }, {
        nominal: 179,
        mass: 178.9458232,
        abundance: 0.1362
      }, {
        nominal: 180,
        mass: 179.946557,
        abundance: 0.3508
      }, {
        nominal: 181,
        mass: 180.9491083
      }, {
        nominal: 182,
        mass: 181.9505612
      }, {
        nominal: 183,
        mass: 182.95353
      }, {
        nominal: 184,
        mass: 183.955446
      }, {
        nominal: 185,
        mass: 184.958862
      }, {
        nominal: 186,
        mass: 185.960897
      }, {
        nominal: 187,
        mass: 186.96477
      }, {
        nominal: 188,
        mass: 187.96685
      }, {
        nominal: 189,
        mass: 188.97084
      }],
      symbol: 'Hf',
      mass: 178.4849787234,
      name: 'Hafnium',
      monoisotopicMass: 179.946557
    }, {
      number: 73,
      isotopes: [{
        nominal: 155,
        mass: 154.97424
      }, {
        nominal: 156,
        mass: 155.97203
      }, {
        nominal: 157,
        mass: 156.96818
      }, {
        nominal: 158,
        mass: 157.96654
      }, {
        nominal: 159,
        mass: 158.963023
      }, {
        nominal: 160,
        mass: 159.961488
      }, {
        nominal: 161,
        mass: 160.958452
      }, {
        nominal: 162,
        mass: 161.957294
      }, {
        nominal: 163,
        mass: 162.954337
      }, {
        nominal: 164,
        mass: 163.953534
      }, {
        nominal: 165,
        mass: 164.950781
      }, {
        nominal: 166,
        mass: 165.950512
      }, {
        nominal: 167,
        mass: 166.948093
      }, {
        nominal: 168,
        mass: 167.948047
      }, {
        nominal: 169,
        mass: 168.946011
      }, {
        nominal: 170,
        mass: 169.946175
      }, {
        nominal: 171,
        mass: 170.944476
      }, {
        nominal: 172,
        mass: 171.944895
      }, {
        nominal: 173,
        mass: 172.94375
      }, {
        nominal: 174,
        mass: 173.944454
      }, {
        nominal: 175,
        mass: 174.943737
      }, {
        nominal: 176,
        mass: 175.944857
      }, {
        nominal: 177,
        mass: 176.9444795
      }, {
        nominal: 178,
        mass: 177.945678
      }, {
        nominal: 179,
        mass: 178.9459366
      }, {
        nominal: 180,
        mass: 179.9474648,
        abundance: 0.0001201
      }, {
        nominal: 181,
        mass: 180.9479958,
        abundance: 0.9998799
      }, {
        nominal: 182,
        mass: 181.9501519
      }, {
        nominal: 183,
        mass: 182.9513726
      }, {
        nominal: 184,
        mass: 183.954008
      }, {
        nominal: 185,
        mass: 184.955559
      }, {
        nominal: 186,
        mass: 185.958551
      }, {
        nominal: 187,
        mass: 186.960386
      }, {
        nominal: 188,
        mass: 187.963916
      }, {
        nominal: 189,
        mass: 188.96583
      }, {
        nominal: 190,
        mass: 189.96939
      }, {
        nominal: 191,
        mass: 190.97156
      }, {
        nominal: 192,
        mass: 191.97514
      }],
      symbol: 'Ta',
      mass: 180.9478756362269,
      name: 'Tantalum',
      monoisotopicMass: 180.9479958
    }, {
      number: 74,
      isotopes: [{
        nominal: 157,
        mass: 156.97884
      }, {
        nominal: 158,
        mass: 157.97456
      }, {
        nominal: 159,
        mass: 158.97264
      }, {
        nominal: 160,
        mass: 159.96846
      }, {
        nominal: 161,
        mass: 160.9672
      }, {
        nominal: 162,
        mass: 161.963499
      }, {
        nominal: 163,
        mass: 162.962524
      }, {
        nominal: 164,
        mass: 163.958961
      }, {
        nominal: 165,
        mass: 164.958281
      }, {
        nominal: 166,
        mass: 165.955031
      }, {
        nominal: 167,
        mass: 166.954805
      }, {
        nominal: 168,
        mass: 167.951806
      }, {
        nominal: 169,
        mass: 168.951779
      }, {
        nominal: 170,
        mass: 169.949232
      }, {
        nominal: 171,
        mass: 170.949451
      }, {
        nominal: 172,
        mass: 171.947292
      }, {
        nominal: 173,
        mass: 172.947689
      }, {
        nominal: 174,
        mass: 173.946079
      }, {
        nominal: 175,
        mass: 174.946717
      }, {
        nominal: 176,
        mass: 175.945634
      }, {
        nominal: 177,
        mass: 176.946643
      }, {
        nominal: 178,
        mass: 177.945883
      }, {
        nominal: 179,
        mass: 178.947077
      }, {
        nominal: 180,
        mass: 179.9467108,
        abundance: 0.0012
      }, {
        nominal: 181,
        mass: 180.9481978
      }, {
        nominal: 182,
        mass: 181.94820394,
        abundance: 0.265
      }, {
        nominal: 183,
        mass: 182.95022275,
        abundance: 0.1431
      }, {
        nominal: 184,
        mass: 183.95093092,
        abundance: 0.3064
      }, {
        nominal: 185,
        mass: 184.95341897
      }, {
        nominal: 186,
        mass: 185.9543628,
        abundance: 0.2843
      }, {
        nominal: 187,
        mass: 186.9571588
      }, {
        nominal: 188,
        mass: 187.9584862
      }, {
        nominal: 189,
        mass: 188.961763
      }, {
        nominal: 190,
        mass: 189.963091
      }, {
        nominal: 191,
        mass: 190.966531
      }, {
        nominal: 192,
        mass: 191.96817
      }, {
        nominal: 193,
        mass: 192.97178
      }, {
        nominal: 194,
        mass: 193.97367
      }],
      symbol: 'W',
      mass: 183.841777550513,
      name: 'Tungsten',
      monoisotopicMass: 183.95093092
    }, {
      number: 75,
      isotopes: [{
        nominal: 159,
        mass: 158.98418
      }, {
        nominal: 160,
        mass: 159.98182
      }, {
        nominal: 161,
        mass: 160.97757
      }, {
        nominal: 162,
        mass: 161.97584
      }, {
        nominal: 163,
        mass: 162.97208
      }, {
        nominal: 164,
        mass: 163.970453
      }, {
        nominal: 165,
        mass: 164.967103
      }, {
        nominal: 166,
        mass: 165.965761
      }, {
        nominal: 167,
        mass: 166.962595
      }, {
        nominal: 168,
        mass: 167.961573
      }, {
        nominal: 169,
        mass: 168.958766
      }, {
        nominal: 170,
        mass: 169.95822
      }, {
        nominal: 171,
        mass: 170.955716
      }, {
        nominal: 172,
        mass: 171.95542
      }, {
        nominal: 173,
        mass: 172.953243
      }, {
        nominal: 174,
        mass: 173.953115
      }, {
        nominal: 175,
        mass: 174.951381
      }, {
        nominal: 176,
        mass: 175.951623
      }, {
        nominal: 177,
        mass: 176.950328
      }, {
        nominal: 178,
        mass: 177.950989
      }, {
        nominal: 179,
        mass: 178.949989
      }, {
        nominal: 180,
        mass: 179.950792
      }, {
        nominal: 181,
        mass: 180.950058
      }, {
        nominal: 182,
        mass: 181.95121
      }, {
        nominal: 183,
        mass: 182.9508196
      }, {
        nominal: 184,
        mass: 183.9525228
      }, {
        nominal: 185,
        mass: 184.9529545,
        abundance: 0.374
      }, {
        nominal: 186,
        mass: 185.9549856
      }, {
        nominal: 187,
        mass: 186.9557501,
        abundance: 0.626
      }, {
        nominal: 188,
        mass: 187.9581115
      }, {
        nominal: 189,
        mass: 188.959226
      }, {
        nominal: 190,
        mass: 189.961744
      }, {
        nominal: 191,
        mass: 190.963122
      }, {
        nominal: 192,
        mass: 191.966088
      }, {
        nominal: 193,
        mass: 192.967541
      }, {
        nominal: 194,
        mass: 193.97076
      }, {
        nominal: 195,
        mass: 194.97254
      }, {
        nominal: 196,
        mass: 195.9758
      }, {
        nominal: 197,
        mass: 196.97799
      }, {
        nominal: 198,
        mass: 197.9816
      }],
      symbol: 'Re',
      mass: 186.20670454560002,
      name: 'Rhenium',
      monoisotopicMass: 186.9557501
    }, {
      number: 76,
      isotopes: [{
        nominal: 161,
        mass: 160.98903
      }, {
        nominal: 162,
        mass: 161.98443
      }, {
        nominal: 163,
        mass: 162.98241
      }, {
        nominal: 164,
        mass: 163.97802
      }, {
        nominal: 165,
        mass: 164.9766
      }, {
        nominal: 166,
        mass: 165.972692
      }, {
        nominal: 167,
        mass: 166.971549
      }, {
        nominal: 168,
        mass: 167.967808
      }, {
        nominal: 169,
        mass: 168.967018
      }, {
        nominal: 170,
        mass: 169.963578
      }, {
        nominal: 171,
        mass: 170.963174
      }, {
        nominal: 172,
        mass: 171.960017
      }, {
        nominal: 173,
        mass: 172.959808
      }, {
        nominal: 174,
        mass: 173.957064
      }, {
        nominal: 175,
        mass: 174.956945
      }, {
        nominal: 176,
        mass: 175.954806
      }, {
        nominal: 177,
        mass: 176.954966
      }, {
        nominal: 178,
        mass: 177.953254
      }, {
        nominal: 179,
        mass: 178.953817
      }, {
        nominal: 180,
        mass: 179.952375
      }, {
        nominal: 181,
        mass: 180.953247
      }, {
        nominal: 182,
        mass: 181.95211
      }, {
        nominal: 183,
        mass: 182.953125
      }, {
        nominal: 184,
        mass: 183.9524885,
        abundance: 0.0002
      }, {
        nominal: 185,
        mass: 184.9540417
      }, {
        nominal: 186,
        mass: 185.953835,
        abundance: 0.0159
      }, {
        nominal: 187,
        mass: 186.9557474,
        abundance: 0.0196
      }, {
        nominal: 188,
        mass: 187.9558352,
        abundance: 0.1324
      }, {
        nominal: 189,
        mass: 188.9581442,
        abundance: 0.1615
      }, {
        nominal: 190,
        mass: 189.9584437,
        abundance: 0.2626
      }, {
        nominal: 191,
        mass: 190.9609264
      }, {
        nominal: 192,
        mass: 191.961477,
        abundance: 0.4078
      }, {
        nominal: 193,
        mass: 192.9641479
      }, {
        nominal: 194,
        mass: 193.9651772
      }, {
        nominal: 195,
        mass: 194.968318
      }, {
        nominal: 196,
        mass: 195.969641
      }, {
        nominal: 197,
        mass: 196.97283
      }, {
        nominal: 198,
        mass: 197.97441
      }, {
        nominal: 199,
        mass: 198.97801
      }, {
        nominal: 200,
        mass: 199.97984
      }, {
        nominal: 201,
        mass: 200.98364
      }, {
        nominal: 202,
        mass: 201.98595
      }],
      symbol: 'Os',
      mass: 190.22485962823998,
      name: 'Osmium',
      monoisotopicMass: 191.961477
    }, {
      number: 77,
      isotopes: [{
        nominal: 164,
        mass: 163.99191
      }, {
        nominal: 165,
        mass: 164.9875
      }, {
        nominal: 166,
        mass: 165.98566
      }, {
        nominal: 167,
        mass: 166.981666
      }, {
        nominal: 168,
        mass: 167.979907
      }, {
        nominal: 169,
        mass: 168.976298
      }, {
        nominal: 170,
        mass: 169.974922
      }, {
        nominal: 171,
        mass: 170.97164
      }, {
        nominal: 172,
        mass: 171.970607
      }, {
        nominal: 173,
        mass: 172.967506
      }, {
        nominal: 174,
        mass: 173.966861
      }, {
        nominal: 175,
        mass: 174.96415
      }, {
        nominal: 176,
        mass: 175.96365
      }, {
        nominal: 177,
        mass: 176.961301
      }, {
        nominal: 178,
        mass: 177.961082
      }, {
        nominal: 179,
        mass: 178.95912
      }, {
        nominal: 180,
        mass: 179.959229
      }, {
        nominal: 181,
        mass: 180.957625
      }, {
        nominal: 182,
        mass: 181.958076
      }, {
        nominal: 183,
        mass: 182.95684
      }, {
        nominal: 184,
        mass: 183.957476
      }, {
        nominal: 185,
        mass: 184.956698
      }, {
        nominal: 186,
        mass: 185.957944
      }, {
        nominal: 187,
        mass: 186.957542
      }, {
        nominal: 188,
        mass: 187.958828
      }, {
        nominal: 189,
        mass: 188.958715
      }, {
        nominal: 190,
        mass: 189.9605412
      }, {
        nominal: 191,
        mass: 190.9605893,
        abundance: 0.373
      }, {
        nominal: 192,
        mass: 191.9626002
      }, {
        nominal: 193,
        mass: 192.9629216,
        abundance: 0.627
      }, {
        nominal: 194,
        mass: 193.9650735
      }, {
        nominal: 195,
        mass: 194.9659747
      }, {
        nominal: 196,
        mass: 195.968397
      }, {
        nominal: 197,
        mass: 196.969655
      }, {
        nominal: 198,
        mass: 197.97228
      }, {
        nominal: 199,
        mass: 198.973805
      }, {
        nominal: 200,
        mass: 199.9768
      }, {
        nominal: 201,
        mass: 200.97864
      }, {
        nominal: 202,
        mass: 201.98199
      }, {
        nominal: 203,
        mass: 202.98423
      }, {
        nominal: 204,
        mass: 203.9896
      }],
      symbol: 'Ir',
      mass: 192.2160516521,
      name: 'Iridium',
      monoisotopicMass: 192.9629216
    }, {
      number: 78,
      isotopes: [{
        nominal: 166,
        mass: 165.99486
      }, {
        nominal: 167,
        mass: 166.99269
      }, {
        nominal: 168,
        mass: 167.98813
      }, {
        nominal: 169,
        mass: 168.98657
      }, {
        nominal: 170,
        mass: 169.982496
      }, {
        nominal: 171,
        mass: 170.981245
      }, {
        nominal: 172,
        mass: 171.977351
      }, {
        nominal: 173,
        mass: 172.976443
      }, {
        nominal: 174,
        mass: 173.97282
      }, {
        nominal: 175,
        mass: 174.97241
      }, {
        nominal: 176,
        mass: 175.968938
      }, {
        nominal: 177,
        mass: 176.96847
      }, {
        nominal: 178,
        mass: 177.96565
      }, {
        nominal: 179,
        mass: 178.965359
      }, {
        nominal: 180,
        mass: 179.963032
      }, {
        nominal: 181,
        mass: 180.963098
      }, {
        nominal: 182,
        mass: 181.961172
      }, {
        nominal: 183,
        mass: 182.961597
      }, {
        nominal: 184,
        mass: 183.959915
      }, {
        nominal: 185,
        mass: 184.960614
      }, {
        nominal: 186,
        mass: 185.959351
      }, {
        nominal: 187,
        mass: 186.960617
      }, {
        nominal: 188,
        mass: 187.9593889
      }, {
        nominal: 189,
        mass: 188.960831
      }, {
        nominal: 190,
        mass: 189.9599297,
        abundance: 0.00012
      }, {
        nominal: 191,
        mass: 190.9616729
      }, {
        nominal: 192,
        mass: 191.9610387,
        abundance: 0.00782
      }, {
        nominal: 193,
        mass: 192.9629824
      }, {
        nominal: 194,
        mass: 193.9626809,
        abundance: 0.3286
      }, {
        nominal: 195,
        mass: 194.9647917,
        abundance: 0.3378
      }, {
        nominal: 196,
        mass: 195.96495209,
        abundance: 0.2521
      }, {
        nominal: 197,
        mass: 196.96734069
      }, {
        nominal: 198,
        mass: 197.9678949,
        abundance: 0.07356
      }, {
        nominal: 199,
        mass: 198.9705952
      }, {
        nominal: 200,
        mass: 199.971443
      }, {
        nominal: 201,
        mass: 200.974513
      }, {
        nominal: 202,
        mass: 201.975639
      }, {
        nominal: 203,
        mass: 202.97893
      }, {
        nominal: 204,
        mass: 203.98076
      }, {
        nominal: 205,
        mass: 204.98608
      }, {
        nominal: 206,
        mass: 205.98966
      }],
      symbol: 'Pt',
      mass: 195.084456864931,
      name: 'Platinum',
      monoisotopicMass: 194.9647917
    }, {
      number: 79,
      isotopes: [{
        nominal: 169,
        mass: 168.99808
      }, {
        nominal: 170,
        mass: 169.99597
      }, {
        nominal: 171,
        mass: 170.991876
      }, {
        nominal: 172,
        mass: 171.989942
      }, {
        nominal: 173,
        mass: 172.986241
      }, {
        nominal: 174,
        mass: 173.984717
      }, {
        nominal: 175,
        mass: 174.981304
      }, {
        nominal: 176,
        mass: 175.98025
      }, {
        nominal: 177,
        mass: 176.97687
      }, {
        nominal: 178,
        mass: 177.976032
      }, {
        nominal: 179,
        mass: 178.973174
      }, {
        nominal: 180,
        mass: 179.972523
      }, {
        nominal: 181,
        mass: 180.970079
      }, {
        nominal: 182,
        mass: 181.969618
      }, {
        nominal: 183,
        mass: 182.967591
      }, {
        nominal: 184,
        mass: 183.967452
      }, {
        nominal: 185,
        mass: 184.96579
      }, {
        nominal: 186,
        mass: 185.965953
      }, {
        nominal: 187,
        mass: 186.964543
      }, {
        nominal: 188,
        mass: 187.965349
      }, {
        nominal: 189,
        mass: 188.963948
      }, {
        nominal: 190,
        mass: 189.964698
      }, {
        nominal: 191,
        mass: 190.963702
      }, {
        nominal: 192,
        mass: 191.964814
      }, {
        nominal: 193,
        mass: 192.9641373
      }, {
        nominal: 194,
        mass: 193.9654178
      }, {
        nominal: 195,
        mass: 194.9650352
      }, {
        nominal: 196,
        mass: 195.9665699
      }, {
        nominal: 197,
        mass: 196.96656879,
        abundance: 1
      }, {
        nominal: 198,
        mass: 197.96824242
      }, {
        nominal: 199,
        mass: 198.96876528
      }, {
        nominal: 200,
        mass: 199.970756
      }, {
        nominal: 201,
        mass: 200.9716575
      }, {
        nominal: 202,
        mass: 201.973856
      }, {
        nominal: 203,
        mass: 202.9751544
      }, {
        nominal: 204,
        mass: 203.97783
      }, {
        nominal: 205,
        mass: 204.97985
      }, {
        nominal: 206,
        mass: 205.98474
      }, {
        nominal: 207,
        mass: 206.9884
      }, {
        nominal: 208,
        mass: 207.99345
      }, {
        nominal: 209,
        mass: 208.99735
      }, {
        nominal: 210,
        mass: 210.0025
      }],
      symbol: 'Au',
      mass: 196.96656879,
      name: 'Gold',
      monoisotopicMass: 196.96656879
    }, {
      number: 80,
      isotopes: [{
        nominal: 171,
        mass: 171.00353
      }, {
        nominal: 172,
        mass: 171.99881
      }, {
        nominal: 173,
        mass: 172.99709
      }, {
        nominal: 174,
        mass: 173.992865
      }, {
        nominal: 175,
        mass: 174.991441
      }, {
        nominal: 176,
        mass: 175.987361
      }, {
        nominal: 177,
        mass: 176.986277
      }, {
        nominal: 178,
        mass: 177.982484
      }, {
        nominal: 179,
        mass: 178.981831
      }, {
        nominal: 180,
        mass: 179.97826
      }, {
        nominal: 181,
        mass: 180.977819
      }, {
        nominal: 182,
        mass: 181.974689
      }, {
        nominal: 183,
        mass: 182.9744448
      }, {
        nominal: 184,
        mass: 183.971714
      }, {
        nominal: 185,
        mass: 184.971899
      }, {
        nominal: 186,
        mass: 185.969362
      }, {
        nominal: 187,
        mass: 186.969814
      }, {
        nominal: 188,
        mass: 187.967567
      }, {
        nominal: 189,
        mass: 188.968195
      }, {
        nominal: 190,
        mass: 189.966323
      }, {
        nominal: 191,
        mass: 190.967157
      }, {
        nominal: 192,
        mass: 191.965635
      }, {
        nominal: 193,
        mass: 192.966653
      }, {
        nominal: 194,
        mass: 193.9654491
      }, {
        nominal: 195,
        mass: 194.966721
      }, {
        nominal: 196,
        mass: 195.9658326,
        abundance: 0.0015
      }, {
        nominal: 197,
        mass: 196.9672128
      }, {
        nominal: 198,
        mass: 197.9667686,
        abundance: 0.0997
      }, {
        nominal: 199,
        mass: 198.96828064,
        abundance: 0.1687
      }, {
        nominal: 200,
        mass: 199.96832659,
        abundance: 0.231
      }, {
        nominal: 201,
        mass: 200.97030284,
        abundance: 0.1318
      }, {
        nominal: 202,
        mass: 201.9706434,
        abundance: 0.2986
      }, {
        nominal: 203,
        mass: 202.9728728
      }, {
        nominal: 204,
        mass: 203.97349398,
        abundance: 0.0687
      }, {
        nominal: 205,
        mass: 204.9760734
      }, {
        nominal: 206,
        mass: 205.977514
      }, {
        nominal: 207,
        mass: 206.9823
      }, {
        nominal: 208,
        mass: 207.985759
      }, {
        nominal: 209,
        mass: 208.99072
      }, {
        nominal: 210,
        mass: 209.99424
      }, {
        nominal: 211,
        mass: 210.99933
      }, {
        nominal: 212,
        mass: 212.00296
      }, {
        nominal: 213,
        mass: 213.00823
      }, {
        nominal: 214,
        mass: 214.012
      }, {
        nominal: 215,
        mass: 215.0174
      }, {
        nominal: 216,
        mass: 216.02132
      }],
      symbol: 'Hg',
      mass: 200.59916703455602,
      name: 'Mercury',
      monoisotopicMass: 201.9706434
    }, {
      number: 81,
      isotopes: [{
        nominal: 176,
        mass: 176.000624
      }, {
        nominal: 177,
        mass: 176.996431
      }, {
        nominal: 178,
        mass: 177.99485
      }, {
        nominal: 179,
        mass: 178.991111
      }, {
        nominal: 180,
        mass: 179.990057
      }, {
        nominal: 181,
        mass: 180.98626
      }, {
        nominal: 182,
        mass: 181.985713
      }, {
        nominal: 183,
        mass: 182.982193
      }, {
        nominal: 184,
        mass: 183.981886
      }, {
        nominal: 185,
        mass: 184.978789
      }, {
        nominal: 186,
        mass: 185.978651
      }, {
        nominal: 187,
        mass: 186.9759063
      }, {
        nominal: 188,
        mass: 187.976021
      }, {
        nominal: 189,
        mass: 188.973588
      }, {
        nominal: 190,
        mass: 189.973828
      }, {
        nominal: 191,
        mass: 190.9717842
      }, {
        nominal: 192,
        mass: 191.972225
      }, {
        nominal: 193,
        mass: 192.970502
      }, {
        nominal: 194,
        mass: 193.971081
      }, {
        nominal: 195,
        mass: 194.969774
      }, {
        nominal: 196,
        mass: 195.970481
      }, {
        nominal: 197,
        mass: 196.969576
      }, {
        nominal: 198,
        mass: 197.970483
      }, {
        nominal: 199,
        mass: 198.969877
      }, {
        nominal: 200,
        mass: 199.9709633
      }, {
        nominal: 201,
        mass: 200.970822
      }, {
        nominal: 202,
        mass: 201.972102
      }, {
        nominal: 203,
        mass: 202.9723446,
        abundance: 0.2952
      }, {
        nominal: 204,
        mass: 203.9738639
      }, {
        nominal: 205,
        mass: 204.9744278,
        abundance: 0.7048
      }, {
        nominal: 206,
        mass: 205.9761106
      }, {
        nominal: 207,
        mass: 206.9774197
      }, {
        nominal: 208,
        mass: 207.982019
      }, {
        nominal: 209,
        mass: 208.9853594
      }, {
        nominal: 210,
        mass: 209.990074
      }, {
        nominal: 211,
        mass: 210.993475
      }, {
        nominal: 212,
        mass: 211.99834
      }, {
        nominal: 213,
        mass: 213.001915
      }, {
        nominal: 214,
        mass: 214.00694
      }, {
        nominal: 215,
        mass: 215.01064
      }, {
        nominal: 216,
        mass: 216.0158
      }, {
        nominal: 217,
        mass: 217.01966
      }, {
        nominal: 218,
        mass: 218.02479
      }],
      symbol: 'Tl',
      mass: 204.38341283936,
      name: 'Thallium',
      monoisotopicMass: 204.9744278
    }, {
      number: 82,
      isotopes: [{
        nominal: 178,
        mass: 178.003831
      }, {
        nominal: 179,
        mass: 179.002201
      }, {
        nominal: 180,
        mass: 179.997928
      }, {
        nominal: 181,
        mass: 180.996653
      }, {
        nominal: 182,
        mass: 181.992672
      }, {
        nominal: 183,
        mass: 182.991872
      }, {
        nominal: 184,
        mass: 183.988136
      }, {
        nominal: 185,
        mass: 184.98761
      }, {
        nominal: 186,
        mass: 185.984238
      }, {
        nominal: 187,
        mass: 186.9839109
      }, {
        nominal: 188,
        mass: 187.980875
      }, {
        nominal: 189,
        mass: 188.980807
      }, {
        nominal: 190,
        mass: 189.978082
      }, {
        nominal: 191,
        mass: 190.978276
      }, {
        nominal: 192,
        mass: 191.975775
      }, {
        nominal: 193,
        mass: 192.976173
      }, {
        nominal: 194,
        mass: 193.974012
      }, {
        nominal: 195,
        mass: 194.974543
      }, {
        nominal: 196,
        mass: 195.972774
      }, {
        nominal: 197,
        mass: 196.9734312
      }, {
        nominal: 198,
        mass: 197.972034
      }, {
        nominal: 199,
        mass: 198.972913
      }, {
        nominal: 200,
        mass: 199.971819
      }, {
        nominal: 201,
        mass: 200.972883
      }, {
        nominal: 202,
        mass: 201.972152
      }, {
        nominal: 203,
        mass: 202.9733911
      }, {
        nominal: 204,
        mass: 203.973044,
        abundance: 0.014
      }, {
        nominal: 205,
        mass: 204.9744822
      }, {
        nominal: 206,
        mass: 205.9744657,
        abundance: 0.241
      }, {
        nominal: 207,
        mass: 206.9758973,
        abundance: 0.221
      }, {
        nominal: 208,
        mass: 207.9766525,
        abundance: 0.524
      }, {
        nominal: 209,
        mass: 208.9810905
      }, {
        nominal: 210,
        mass: 209.9841889
      }, {
        nominal: 211,
        mass: 210.9887371
      }, {
        nominal: 212,
        mass: 211.9918977
      }, {
        nominal: 213,
        mass: 212.9965629
      }, {
        nominal: 214,
        mass: 213.9998059
      }, {
        nominal: 215,
        mass: 215.00474
      }, {
        nominal: 216,
        mass: 216.00803
      }, {
        nominal: 217,
        mass: 217.01314
      }, {
        nominal: 218,
        mass: 218.01659
      }, {
        nominal: 219,
        mass: 219.02177
      }, {
        nominal: 220,
        mass: 220.02541
      }],
      symbol: 'Pb',
      mass: 207.216908063,
      name: 'Lead',
      monoisotopicMass: 207.9766525
    }, {
      number: 83,
      isotopes: [{
        nominal: 184,
        mass: 184.001275
      }, {
        nominal: 185,
        mass: 184.9976
      }, {
        nominal: 186,
        mass: 185.996644
      }, {
        nominal: 187,
        mass: 186.993147
      }, {
        nominal: 188,
        mass: 187.992287
      }, {
        nominal: 189,
        mass: 188.989195
      }, {
        nominal: 190,
        mass: 189.988622
      }, {
        nominal: 191,
        mass: 190.9857866
      }, {
        nominal: 192,
        mass: 191.985469
      }, {
        nominal: 193,
        mass: 192.98296
      }, {
        nominal: 194,
        mass: 193.982785
      }, {
        nominal: 195,
        mass: 194.9806488
      }, {
        nominal: 196,
        mass: 195.980667
      }, {
        nominal: 197,
        mass: 196.9788651
      }, {
        nominal: 198,
        mass: 197.979206
      }, {
        nominal: 199,
        mass: 198.977673
      }, {
        nominal: 200,
        mass: 199.978131
      }, {
        nominal: 201,
        mass: 200.97701
      }, {
        nominal: 202,
        mass: 201.977734
      }, {
        nominal: 203,
        mass: 202.976893
      }, {
        nominal: 204,
        mass: 203.9778361
      }, {
        nominal: 205,
        mass: 204.9773867
      }, {
        nominal: 206,
        mass: 205.9784993
      }, {
        nominal: 207,
        mass: 206.978471
      }, {
        nominal: 208,
        mass: 207.9797425
      }, {
        nominal: 209,
        mass: 208.9803991,
        abundance: 1
      }, {
        nominal: 210,
        mass: 209.9841207
      }, {
        nominal: 211,
        mass: 210.9872697
      }, {
        nominal: 212,
        mass: 211.991286
      }, {
        nominal: 213,
        mass: 212.9943851
      }, {
        nominal: 214,
        mass: 213.998712
      }, {
        nominal: 215,
        mass: 215.00177
      }, {
        nominal: 216,
        mass: 216.006306
      }, {
        nominal: 217,
        mass: 217.009372
      }, {
        nominal: 218,
        mass: 218.014188
      }, {
        nominal: 219,
        mass: 219.01748
      }, {
        nominal: 220,
        mass: 220.02235
      }, {
        nominal: 221,
        mass: 221.02587
      }, {
        nominal: 222,
        mass: 222.03078
      }, {
        nominal: 223,
        mass: 223.0345
      }, {
        nominal: 224,
        mass: 224.03947
      }],
      symbol: 'Bi',
      mass: 208.9803991,
      name: 'Bismuth',
      monoisotopicMass: 208.9803991
    }, {
      number: 84,
      isotopes: [{
        nominal: 186,
        mass: 186.004393
      }, {
        nominal: 187,
        mass: 187.003041
      }, {
        nominal: 188,
        mass: 187.999416
      }, {
        nominal: 189,
        mass: 188.998473
      }, {
        nominal: 190,
        mass: 189.995101
      }, {
        nominal: 191,
        mass: 190.9945585
      }, {
        nominal: 192,
        mass: 191.991336
      }, {
        nominal: 193,
        mass: 192.991026
      }, {
        nominal: 194,
        mass: 193.988186
      }, {
        nominal: 195,
        mass: 194.988126
      }, {
        nominal: 196,
        mass: 195.985526
      }, {
        nominal: 197,
        mass: 196.98566
      }, {
        nominal: 198,
        mass: 197.983389
      }, {
        nominal: 199,
        mass: 198.983667
      }, {
        nominal: 200,
        mass: 199.981799
      }, {
        nominal: 201,
        mass: 200.9822598
      }, {
        nominal: 202,
        mass: 201.980758
      }, {
        nominal: 203,
        mass: 202.9814161
      }, {
        nominal: 204,
        mass: 203.98031
      }, {
        nominal: 205,
        mass: 204.981203
      }, {
        nominal: 206,
        mass: 205.980474
      }, {
        nominal: 207,
        mass: 206.9815938
      }, {
        nominal: 208,
        mass: 207.9812461
      }, {
        nominal: 209,
        mass: 208.9824308
      }, {
        nominal: 210,
        mass: 209.9828741
      }, {
        nominal: 211,
        mass: 210.9866536
      }, {
        nominal: 212,
        mass: 211.9888684
      }, {
        nominal: 213,
        mass: 212.9928576
      }, {
        nominal: 214,
        mass: 213.9952017
      }, {
        nominal: 215,
        mass: 214.9994201
      }, {
        nominal: 216,
        mass: 216.0019152
      }, {
        nominal: 217,
        mass: 217.0063182
      }, {
        nominal: 218,
        mass: 218.0089735
      }, {
        nominal: 219,
        mass: 219.013614
      }, {
        nominal: 220,
        mass: 220.016386
      }, {
        nominal: 221,
        mass: 221.021228
      }, {
        nominal: 222,
        mass: 222.02414
      }, {
        nominal: 223,
        mass: 223.02907
      }, {
        nominal: 224,
        mass: 224.03211
      }, {
        nominal: 225,
        mass: 225.03707
      }, {
        nominal: 226,
        mass: 226.04031
      }, {
        nominal: 227,
        mass: 227.04539
      }],
      symbol: 'Po',
      mass: null,
      name: 'Polonium'
    }, {
      number: 85,
      isotopes: [{
        nominal: 191,
        mass: 191.004148
      }, {
        nominal: 192,
        mass: 192.003152
      }, {
        nominal: 193,
        mass: 192.999927
      }, {
        nominal: 194,
        mass: 193.999236
      }, {
        nominal: 195,
        mass: 194.9962685
      }, {
        nominal: 196,
        mass: 195.9958
      }, {
        nominal: 197,
        mass: 196.993189
      }, {
        nominal: 198,
        mass: 197.992784
      }, {
        nominal: 199,
        mass: 198.9905277
      }, {
        nominal: 200,
        mass: 199.990351
      }, {
        nominal: 201,
        mass: 200.9884171
      }, {
        nominal: 202,
        mass: 201.98863
      }, {
        nominal: 203,
        mass: 202.986943
      }, {
        nominal: 204,
        mass: 203.987251
      }, {
        nominal: 205,
        mass: 204.986076
      }, {
        nominal: 206,
        mass: 205.986657
      }, {
        nominal: 207,
        mass: 206.9858
      }, {
        nominal: 208,
        mass: 207.9866133
      }, {
        nominal: 209,
        mass: 208.9861702
      }, {
        nominal: 210,
        mass: 209.9871479
      }, {
        nominal: 211,
        mass: 210.9874966
      }, {
        nominal: 212,
        mass: 211.9907377
      }, {
        nominal: 213,
        mass: 212.992937
      }, {
        nominal: 214,
        mass: 213.9963721
      }, {
        nominal: 215,
        mass: 214.9986528
      }, {
        nominal: 216,
        mass: 216.0024236
      }, {
        nominal: 217,
        mass: 217.0047192
      }, {
        nominal: 218,
        mass: 218.008695
      }, {
        nominal: 219,
        mass: 219.0111618
      }, {
        nominal: 220,
        mass: 220.015433
      }, {
        nominal: 221,
        mass: 221.018017
      }, {
        nominal: 222,
        mass: 222.022494
      }, {
        nominal: 223,
        mass: 223.025151
      }, {
        nominal: 224,
        mass: 224.029749
      }, {
        nominal: 225,
        mass: 225.03263
      }, {
        nominal: 226,
        mass: 226.03716
      }, {
        nominal: 227,
        mass: 227.04024
      }, {
        nominal: 228,
        mass: 228.04475
      }, {
        nominal: 229,
        mass: 229.04812
      }],
      symbol: 'At',
      mass: null,
      name: 'Astatine'
    }, {
      number: 86,
      isotopes: [{
        nominal: 193,
        mass: 193.009708
      }, {
        nominal: 194,
        mass: 194.006144
      }, {
        nominal: 195,
        mass: 195.005422
      }, {
        nominal: 196,
        mass: 196.002116
      }, {
        nominal: 197,
        mass: 197.001585
      }, {
        nominal: 198,
        mass: 197.998679
      }, {
        nominal: 199,
        mass: 198.99839
      }, {
        nominal: 200,
        mass: 199.99569
      }, {
        nominal: 201,
        mass: 200.995628
      }, {
        nominal: 202,
        mass: 201.993264
      }, {
        nominal: 203,
        mass: 202.993388
      }, {
        nominal: 204,
        mass: 203.99143
      }, {
        nominal: 205,
        mass: 204.991719
      }, {
        nominal: 206,
        mass: 205.990214
      }, {
        nominal: 207,
        mass: 206.9907303
      }, {
        nominal: 208,
        mass: 207.989635
      }, {
        nominal: 209,
        mass: 208.990415
      }, {
        nominal: 210,
        mass: 209.9896891
      }, {
        nominal: 211,
        mass: 210.9906011
      }, {
        nominal: 212,
        mass: 211.9907039
      }, {
        nominal: 213,
        mass: 212.9938831
      }, {
        nominal: 214,
        mass: 213.995363
      }, {
        nominal: 215,
        mass: 214.9987459
      }, {
        nominal: 216,
        mass: 216.0002719
      }, {
        nominal: 217,
        mass: 217.003928
      }, {
        nominal: 218,
        mass: 218.0056016
      }, {
        nominal: 219,
        mass: 219.0094804
      }, {
        nominal: 220,
        mass: 220.0113941
      }, {
        nominal: 221,
        mass: 221.0155371
      }, {
        nominal: 222,
        mass: 222.0175782
      }, {
        nominal: 223,
        mass: 223.0218893
      }, {
        nominal: 224,
        mass: 224.024096
      }, {
        nominal: 225,
        mass: 225.028486
      }, {
        nominal: 226,
        mass: 226.030861
      }, {
        nominal: 227,
        mass: 227.035304
      }, {
        nominal: 228,
        mass: 228.037835
      }, {
        nominal: 229,
        mass: 229.042257
      }, {
        nominal: 230,
        mass: 230.04514
      }, {
        nominal: 231,
        mass: 231.04987
      }],
      symbol: 'Rn',
      mass: null,
      name: 'Radon'
    }, {
      number: 87,
      isotopes: [{
        nominal: 199,
        mass: 199.007259
      }, {
        nominal: 200,
        mass: 200.006586
      }, {
        nominal: 201,
        mass: 201.003867
      }, {
        nominal: 202,
        mass: 202.00332
      }, {
        nominal: 203,
        mass: 203.0009407
      }, {
        nominal: 204,
        mass: 204.000652
      }, {
        nominal: 205,
        mass: 204.9985939
      }, {
        nominal: 206,
        mass: 205.998666
      }, {
        nominal: 207,
        mass: 206.996946
      }, {
        nominal: 208,
        mass: 207.997138
      }, {
        nominal: 209,
        mass: 208.995955
      }, {
        nominal: 210,
        mass: 209.996422
      }, {
        nominal: 211,
        mass: 210.995556
      }, {
        nominal: 212,
        mass: 211.9962257
      }, {
        nominal: 213,
        mass: 212.996186
      }, {
        nominal: 214,
        mass: 213.9989713
      }, {
        nominal: 215,
        mass: 215.0003418
      }, {
        nominal: 216,
        mass: 216.0031899
      }, {
        nominal: 217,
        mass: 217.0046323
      }, {
        nominal: 218,
        mass: 218.0075787
      }, {
        nominal: 219,
        mass: 219.0092524
      }, {
        nominal: 220,
        mass: 220.0123277
      }, {
        nominal: 221,
        mass: 221.0142552
      }, {
        nominal: 222,
        mass: 222.017552
      }, {
        nominal: 223,
        mass: 223.019736
      }, {
        nominal: 224,
        mass: 224.023398
      }, {
        nominal: 225,
        mass: 225.025573
      }, {
        nominal: 226,
        mass: 226.029566
      }, {
        nominal: 227,
        mass: 227.031869
      }, {
        nominal: 228,
        mass: 228.035823
      }, {
        nominal: 229,
        mass: 229.038298
      }, {
        nominal: 230,
        mass: 230.042416
      }, {
        nominal: 231,
        mass: 231.045158
      }, {
        nominal: 232,
        mass: 232.04937
      }, {
        nominal: 233,
        mass: 233.05264
      }],
      symbol: 'Fr',
      mass: null,
      name: 'Francium'
    }, {
      number: 88,
      isotopes: [{
        nominal: 201,
        mass: 201.01271
      }, {
        nominal: 202,
        mass: 202.00976
      }, {
        nominal: 203,
        mass: 203.009304
      }, {
        nominal: 204,
        mass: 204.006492
      }, {
        nominal: 205,
        mass: 205.006268
      }, {
        nominal: 206,
        mass: 206.003828
      }, {
        nominal: 207,
        mass: 207.003799
      }, {
        nominal: 208,
        mass: 208.001841
      }, {
        nominal: 209,
        mass: 209.00199
      }, {
        nominal: 210,
        mass: 210.000494
      }, {
        nominal: 211,
        mass: 211.0008932
      }, {
        nominal: 212,
        mass: 211.999787
      }, {
        nominal: 213,
        mass: 213.000384
      }, {
        nominal: 214,
        mass: 214.0000997
      }, {
        nominal: 215,
        mass: 215.0027204
      }, {
        nominal: 216,
        mass: 216.0035334
      }, {
        nominal: 217,
        mass: 217.0063207
      }, {
        nominal: 218,
        mass: 218.007141
      }, {
        nominal: 219,
        mass: 219.0100855
      }, {
        nominal: 220,
        mass: 220.0110259
      }, {
        nominal: 221,
        mass: 221.0139177
      }, {
        nominal: 222,
        mass: 222.0153748
      }, {
        nominal: 223,
        mass: 223.0185023
      }, {
        nominal: 224,
        mass: 224.020212
      }, {
        nominal: 225,
        mass: 225.0236119
      }, {
        nominal: 226,
        mass: 226.0254103
      }, {
        nominal: 227,
        mass: 227.0291783
      }, {
        nominal: 228,
        mass: 228.0310707
      }, {
        nominal: 229,
        mass: 229.034942
      }, {
        nominal: 230,
        mass: 230.037055
      }, {
        nominal: 231,
        mass: 231.041027
      }, {
        nominal: 232,
        mass: 232.0434753
      }, {
        nominal: 233,
        mass: 233.047582
      }, {
        nominal: 234,
        mass: 234.050342
      }, {
        nominal: 235,
        mass: 235.05497
      }],
      symbol: 'Ra',
      mass: null,
      name: 'Radium'
    }, {
      number: 89,
      isotopes: [{
        nominal: 206,
        mass: 206.014452
      }, {
        nominal: 207,
        mass: 207.011966
      }, {
        nominal: 208,
        mass: 208.01155
      }, {
        nominal: 209,
        mass: 209.009495
      }, {
        nominal: 210,
        mass: 210.009436
      }, {
        nominal: 211,
        mass: 211.007732
      }, {
        nominal: 212,
        mass: 212.007813
      }, {
        nominal: 213,
        mass: 213.006609
      }, {
        nominal: 214,
        mass: 214.006918
      }, {
        nominal: 215,
        mass: 215.006475
      }, {
        nominal: 216,
        mass: 216.008743
      }, {
        nominal: 217,
        mass: 217.009344
      }, {
        nominal: 218,
        mass: 218.011642
      }, {
        nominal: 219,
        mass: 219.012421
      }, {
        nominal: 220,
        mass: 220.0147549
      }, {
        nominal: 221,
        mass: 221.015592
      }, {
        nominal: 222,
        mass: 222.0178442
      }, {
        nominal: 223,
        mass: 223.0191377
      }, {
        nominal: 224,
        mass: 224.0217232
      }, {
        nominal: 225,
        mass: 225.02323
      }, {
        nominal: 226,
        mass: 226.0260984
      }, {
        nominal: 227,
        mass: 227.0277523
      }, {
        nominal: 228,
        mass: 228.0310215
      }, {
        nominal: 229,
        mass: 229.032956
      }, {
        nominal: 230,
        mass: 230.036327
      }, {
        nominal: 231,
        mass: 231.038393
      }, {
        nominal: 232,
        mass: 232.042034
      }, {
        nominal: 233,
        mass: 233.044346
      }, {
        nominal: 234,
        mass: 234.048139
      }, {
        nominal: 235,
        mass: 235.05084
      }, {
        nominal: 236,
        mass: 236.054988
      }, {
        nominal: 237,
        mass: 237.05827
      }],
      symbol: 'Ac',
      mass: null,
      name: 'Actinium'
    }, {
      number: 90,
      isotopes: [{
        nominal: 208,
        mass: 208.0179
      }, {
        nominal: 209,
        mass: 209.017753
      }, {
        nominal: 210,
        mass: 210.015094
      }, {
        nominal: 211,
        mass: 211.014929
      }, {
        nominal: 212,
        mass: 212.012988
      }, {
        nominal: 213,
        mass: 213.013009
      }, {
        nominal: 214,
        mass: 214.0115
      }, {
        nominal: 215,
        mass: 215.0117248
      }, {
        nominal: 216,
        mass: 216.011056
      }, {
        nominal: 217,
        mass: 217.013117
      }, {
        nominal: 218,
        mass: 218.013276
      }, {
        nominal: 219,
        mass: 219.015537
      }, {
        nominal: 220,
        mass: 220.015748
      }, {
        nominal: 221,
        mass: 221.018184
      }, {
        nominal: 222,
        mass: 222.018469
      }, {
        nominal: 223,
        mass: 223.0208119
      }, {
        nominal: 224,
        mass: 224.021464
      }, {
        nominal: 225,
        mass: 225.0239514
      }, {
        nominal: 226,
        mass: 226.0249034
      }, {
        nominal: 227,
        mass: 227.0277042
      }, {
        nominal: 228,
        mass: 228.0287413
      }, {
        nominal: 229,
        mass: 229.0317627
      }, {
        nominal: 230,
        mass: 230.0331341
      }, {
        nominal: 231,
        mass: 231.0363046
      }, {
        nominal: 232,
        mass: 232.0380558,
        abundance: 1
      }, {
        nominal: 233,
        mass: 233.0415823
      }, {
        nominal: 234,
        mass: 234.0436014
      }, {
        nominal: 235,
        mass: 235.047255
      }, {
        nominal: 236,
        mass: 236.049657
      }, {
        nominal: 237,
        mass: 237.053629
      }, {
        nominal: 238,
        mass: 238.0565
      }, {
        nominal: 239,
        mass: 239.06077
      }],
      symbol: 'Th',
      mass: 232.0380558,
      name: 'Thorium',
      monoisotopicMass: 232.0380558
    }, {
      number: 91,
      isotopes: [{
        nominal: 212,
        mass: 212.023203
      }, {
        nominal: 213,
        mass: 213.021109
      }, {
        nominal: 214,
        mass: 214.020918
      }, {
        nominal: 215,
        mass: 215.019183
      }, {
        nominal: 216,
        mass: 216.019109
      }, {
        nominal: 217,
        mass: 217.018325
      }, {
        nominal: 218,
        mass: 218.020059
      }, {
        nominal: 219,
        mass: 219.019904
      }, {
        nominal: 220,
        mass: 220.021705
      }, {
        nominal: 221,
        mass: 221.021875
      }, {
        nominal: 222,
        mass: 222.023784
      }, {
        nominal: 223,
        mass: 223.023963
      }, {
        nominal: 224,
        mass: 224.0256176
      }, {
        nominal: 225,
        mass: 225.026131
      }, {
        nominal: 226,
        mass: 226.027948
      }, {
        nominal: 227,
        mass: 227.0288054
      }, {
        nominal: 228,
        mass: 228.0310517
      }, {
        nominal: 229,
        mass: 229.0320972
      }, {
        nominal: 230,
        mass: 230.034541
      }, {
        nominal: 231,
        mass: 231.0358842,
        abundance: 1
      }, {
        nominal: 232,
        mass: 232.0385917
      }, {
        nominal: 233,
        mass: 233.0402472
      }, {
        nominal: 234,
        mass: 234.0433072
      }, {
        nominal: 235,
        mass: 235.045399
      }, {
        nominal: 236,
        mass: 236.048668
      }, {
        nominal: 237,
        mass: 237.051023
      }, {
        nominal: 238,
        mass: 238.054637
      }, {
        nominal: 239,
        mass: 239.05726
      }, {
        nominal: 240,
        mass: 240.06098
      }, {
        nominal: 241,
        mass: 241.06408
      }],
      symbol: 'Pa',
      mass: 231.0358842,
      name: 'Protactinium',
      monoisotopicMass: 231.0358842
    }, {
      number: 92,
      isotopes: [{
        nominal: 217,
        mass: 217.02466
      }, {
        nominal: 218,
        mass: 218.023523
      }, {
        nominal: 219,
        mass: 219.024999
      }, {
        nominal: 220,
        mass: 220.02462
      }, {
        nominal: 221,
        mass: 221.02628
      }, {
        nominal: 222,
        mass: 222.026
      }, {
        nominal: 223,
        mass: 223.027739
      }, {
        nominal: 224,
        mass: 224.027605
      }, {
        nominal: 225,
        mass: 225.029391
      }, {
        nominal: 226,
        mass: 226.029339
      }, {
        nominal: 227,
        mass: 227.031157
      }, {
        nominal: 228,
        mass: 228.031371
      }, {
        nominal: 229,
        mass: 229.0335063
      }, {
        nominal: 230,
        mass: 230.0339401
      }, {
        nominal: 231,
        mass: 231.0362939
      }, {
        nominal: 232,
        mass: 232.0371563
      }, {
        nominal: 233,
        mass: 233.0396355
      }, {
        nominal: 234,
        mass: 234.0409523,
        abundance: 0.000054
      }, {
        nominal: 235,
        mass: 235.0439301,
        abundance: 0.007204
      }, {
        nominal: 236,
        mass: 236.0455682
      }, {
        nominal: 237,
        mass: 237.0487304
      }, {
        nominal: 238,
        mass: 238.0507884,
        abundance: 0.992742
      }, {
        nominal: 239,
        mass: 239.0542935
      }, {
        nominal: 240,
        mass: 240.0565934
      }, {
        nominal: 241,
        mass: 241.06033
      }, {
        nominal: 242,
        mass: 242.06293
      }, {
        nominal: 243,
        mass: 243.06699
      }],
      symbol: 'U',
      mass: 238.0289104616574,
      name: 'Uranium',
      monoisotopicMass: 238.0507884
    }, {
      number: 93,
      isotopes: [{
        nominal: 219,
        mass: 219.03143
      }, {
        nominal: 220,
        mass: 220.03254
      }, {
        nominal: 221,
        mass: 221.03204
      }, {
        nominal: 222,
        mass: 222.0333
      }, {
        nominal: 223,
        mass: 223.03285
      }, {
        nominal: 224,
        mass: 224.03422
      }, {
        nominal: 225,
        mass: 225.033911
      }, {
        nominal: 226,
        mass: 226.035188
      }, {
        nominal: 227,
        mass: 227.034957
      }, {
        nominal: 228,
        mass: 228.036067
      }, {
        nominal: 229,
        mass: 229.036264
      }, {
        nominal: 230,
        mass: 230.037828
      }, {
        nominal: 231,
        mass: 231.038245
      }, {
        nominal: 232,
        mass: 232.04011
      }, {
        nominal: 233,
        mass: 233.040741
      }, {
        nominal: 234,
        mass: 234.0428953
      }, {
        nominal: 235,
        mass: 235.0440635
      }, {
        nominal: 236,
        mass: 236.04657
      }, {
        nominal: 237,
        mass: 237.0481736
      }, {
        nominal: 238,
        mass: 238.0509466
      }, {
        nominal: 239,
        mass: 239.0529392
      }, {
        nominal: 240,
        mass: 240.056165
      }, {
        nominal: 241,
        mass: 241.058253
      }, {
        nominal: 242,
        mass: 242.06164
      }, {
        nominal: 243,
        mass: 243.06428
      }, {
        nominal: 244,
        mass: 244.06785
      }, {
        nominal: 245,
        mass: 245.0708
      }],
      symbol: 'Np',
      mass: null,
      name: 'Neptunium'
    }, {
      number: 94,
      isotopes: [{
        nominal: 228,
        mass: 228.038732
      }, {
        nominal: 229,
        mass: 229.040144
      }, {
        nominal: 230,
        mass: 230.03965
      }, {
        nominal: 231,
        mass: 231.041102
      }, {
        nominal: 232,
        mass: 232.041185
      }, {
        nominal: 233,
        mass: 233.042998
      }, {
        nominal: 234,
        mass: 234.0433174
      }, {
        nominal: 235,
        mass: 235.045286
      }, {
        nominal: 236,
        mass: 236.0460581
      }, {
        nominal: 237,
        mass: 237.0484098
      }, {
        nominal: 238,
        mass: 238.0495601
      }, {
        nominal: 239,
        mass: 239.0521636
      }, {
        nominal: 240,
        mass: 240.0538138
      }, {
        nominal: 241,
        mass: 241.0568517
      }, {
        nominal: 242,
        mass: 242.0587428
      }, {
        nominal: 243,
        mass: 243.0620036
      }, {
        nominal: 244,
        mass: 244.0642053
      }, {
        nominal: 245,
        mass: 245.067826
      }, {
        nominal: 246,
        mass: 246.070205
      }, {
        nominal: 247,
        mass: 247.07419
      }],
      symbol: 'Pu',
      mass: null,
      name: 'Plutonium'
    }, {
      number: 95,
      isotopes: [{
        nominal: 230,
        mass: 230.04609
      }, {
        nominal: 231,
        mass: 231.04556
      }, {
        nominal: 232,
        mass: 232.04645
      }, {
        nominal: 233,
        mass: 233.04644
      }, {
        nominal: 234,
        mass: 234.04773
      }, {
        nominal: 235,
        mass: 235.047908
      }, {
        nominal: 236,
        mass: 236.04943
      }, {
        nominal: 237,
        mass: 237.049996
      }, {
        nominal: 238,
        mass: 238.051985
      }, {
        nominal: 239,
        mass: 239.0530247
      }, {
        nominal: 240,
        mass: 240.0553
      }, {
        nominal: 241,
        mass: 241.0568293
      }, {
        nominal: 242,
        mass: 242.0595494
      }, {
        nominal: 243,
        mass: 243.0613813
      }, {
        nominal: 244,
        mass: 244.0642851
      }, {
        nominal: 245,
        mass: 245.0664548
      }, {
        nominal: 246,
        mass: 246.069775
      }, {
        nominal: 247,
        mass: 247.07209
      }, {
        nominal: 248,
        mass: 248.07575
      }, {
        nominal: 249,
        mass: 249.07848
      }],
      symbol: 'Am',
      name: 'Americium',
      mass: null
    }, {
      number: 96,
      isotopes: [{
        nominal: 232,
        mass: 232.04982
      }, {
        nominal: 233,
        mass: 233.05077
      }, {
        nominal: 234,
        mass: 234.05016
      }, {
        nominal: 235,
        mass: 235.05154
      }, {
        nominal: 236,
        mass: 236.051374
      }, {
        nominal: 237,
        mass: 237.052869
      }, {
        nominal: 238,
        mass: 238.053081
      }, {
        nominal: 239,
        mass: 239.05491
      }, {
        nominal: 240,
        mass: 240.0555297
      }, {
        nominal: 241,
        mass: 241.0576532
      }, {
        nominal: 242,
        mass: 242.058836
      }, {
        nominal: 243,
        mass: 243.0613893
      }, {
        nominal: 244,
        mass: 244.0627528
      }, {
        nominal: 245,
        mass: 245.0654915
      }, {
        nominal: 246,
        mass: 246.0672238
      }, {
        nominal: 247,
        mass: 247.0703541
      }, {
        nominal: 248,
        mass: 248.0723499
      }, {
        nominal: 249,
        mass: 249.0759548
      }, {
        nominal: 250,
        mass: 250.078358
      }, {
        nominal: 251,
        mass: 251.082286
      }, {
        nominal: 252,
        mass: 252.08487
      }],
      symbol: 'Cm',
      name: 'Curium',
      mass: null
    }, {
      number: 97,
      isotopes: [{
        nominal: 234,
        mass: 234.05727
      }, {
        nominal: 235,
        mass: 235.05658
      }, {
        nominal: 236,
        mass: 236.05748
      }, {
        nominal: 237,
        mass: 237.0571
      }, {
        nominal: 238,
        mass: 238.0582
      }, {
        nominal: 239,
        mass: 239.05824
      }, {
        nominal: 240,
        mass: 240.05976
      }, {
        nominal: 241,
        mass: 241.06016
      }, {
        nominal: 242,
        mass: 242.06198
      }, {
        nominal: 243,
        mass: 243.0630078
      }, {
        nominal: 244,
        mass: 244.065181
      }, {
        nominal: 245,
        mass: 245.0663618
      }, {
        nominal: 246,
        mass: 246.068673
      }, {
        nominal: 247,
        mass: 247.0703073
      }, {
        nominal: 248,
        mass: 248.073088
      }, {
        nominal: 249,
        mass: 249.0749877
      }, {
        nominal: 250,
        mass: 250.0783167
      }, {
        nominal: 251,
        mass: 251.080762
      }, {
        nominal: 252,
        mass: 252.08431
      }, {
        nominal: 253,
        mass: 253.08688
      }, {
        nominal: 254,
        mass: 254.0906
      }],
      symbol: 'Bk',
      name: 'Berkelium',
      mass: null
    }, {
      number: 98,
      isotopes: [{
        nominal: 237,
        mass: 237.062198
      }, {
        nominal: 238,
        mass: 238.06149
      }, {
        nominal: 239,
        mass: 239.06253
      }, {
        nominal: 240,
        mass: 240.062256
      }, {
        nominal: 241,
        mass: 241.06369
      }, {
        nominal: 242,
        mass: 242.063754
      }, {
        nominal: 243,
        mass: 243.06548
      }, {
        nominal: 244,
        mass: 244.0660008
      }, {
        nominal: 245,
        mass: 245.0680487
      }, {
        nominal: 246,
        mass: 246.0688055
      }, {
        nominal: 247,
        mass: 247.070965
      }, {
        nominal: 248,
        mass: 248.0721851
      }, {
        nominal: 249,
        mass: 249.0748539
      }, {
        nominal: 250,
        mass: 250.0764062
      }, {
        nominal: 251,
        mass: 251.0795886
      }, {
        nominal: 252,
        mass: 252.0816272
      }, {
        nominal: 253,
        mass: 253.0851345
      }, {
        nominal: 254,
        mass: 254.087324
      }, {
        nominal: 255,
        mass: 255.09105
      }, {
        nominal: 256,
        mass: 256.09344
      }],
      symbol: 'Cf',
      name: 'Californium',
      mass: null
    }, {
      number: 99,
      isotopes: [{
        nominal: 239,
        mass: 239.06823
      }, {
        nominal: 240,
        mass: 240.06892
      }, {
        nominal: 241,
        mass: 241.06856
      }, {
        nominal: 242,
        mass: 242.06957
      }, {
        nominal: 243,
        mass: 243.06951
      }, {
        nominal: 244,
        mass: 244.07088
      }, {
        nominal: 245,
        mass: 245.07125
      }, {
        nominal: 246,
        mass: 246.0729
      }, {
        nominal: 247,
        mass: 247.073622
      }, {
        nominal: 248,
        mass: 248.075471
      }, {
        nominal: 249,
        mass: 249.076411
      }, {
        nominal: 250,
        mass: 250.07861
      }, {
        nominal: 251,
        mass: 251.0799936
      }, {
        nominal: 252,
        mass: 252.08298
      }, {
        nominal: 253,
        mass: 253.0848257
      }, {
        nominal: 254,
        mass: 254.0880222
      }, {
        nominal: 255,
        mass: 255.090275
      }, {
        nominal: 256,
        mass: 256.0936
      }, {
        nominal: 257,
        mass: 257.09598
      }, {
        nominal: 258,
        mass: 258.09952
      }],
      symbol: 'Es',
      name: 'Einsteinium',
      mass: null
    }, {
      number: 100,
      isotopes: [{
        nominal: 241,
        mass: 241.07421
      }, {
        nominal: 242,
        mass: 242.07343
      }, {
        nominal: 243,
        mass: 243.07446
      }, {
        nominal: 244,
        mass: 244.07404
      }, {
        nominal: 245,
        mass: 245.07535
      }, {
        nominal: 246,
        mass: 246.07535
      }, {
        nominal: 247,
        mass: 247.07694
      }, {
        nominal: 248,
        mass: 248.0771865
      }, {
        nominal: 249,
        mass: 249.0789275
      }, {
        nominal: 250,
        mass: 250.079521
      }, {
        nominal: 251,
        mass: 251.08154
      }, {
        nominal: 252,
        mass: 252.0824671
      }, {
        nominal: 253,
        mass: 253.0851846
      }, {
        nominal: 254,
        mass: 254.0868544
      }, {
        nominal: 255,
        mass: 255.089964
      }, {
        nominal: 256,
        mass: 256.0917745
      }, {
        nominal: 257,
        mass: 257.0951061
      }, {
        nominal: 258,
        mass: 258.09708
      }, {
        nominal: 259,
        mass: 259.1006
      }, {
        nominal: 260,
        mass: 260.10281
      }],
      symbol: 'Fm',
      name: 'Fermium',
      mass: null
    }, {
      number: 101,
      isotopes: [{
        nominal: 245,
        mass: 245.08081
      }, {
        nominal: 246,
        mass: 246.08171
      }, {
        nominal: 247,
        mass: 247.08152
      }, {
        nominal: 248,
        mass: 248.08282
      }, {
        nominal: 249,
        mass: 249.08291
      }, {
        nominal: 250,
        mass: 250.08441
      }, {
        nominal: 251,
        mass: 251.084774
      }, {
        nominal: 252,
        mass: 252.08643
      }, {
        nominal: 253,
        mass: 253.087144
      }, {
        nominal: 254,
        mass: 254.08959
      }, {
        nominal: 255,
        mass: 255.0910841
      }, {
        nominal: 256,
        mass: 256.09389
      }, {
        nominal: 257,
        mass: 257.0955424
      }, {
        nominal: 258,
        mass: 258.0984315
      }, {
        nominal: 259,
        mass: 259.10051
      }, {
        nominal: 260,
        mass: 260.10365
      }, {
        nominal: 261,
        mass: 261.10583
      }, {
        nominal: 262,
        mass: 262.1091
      }],
      symbol: 'Md',
      name: 'Mendelevium',
      mass: null
    }, {
      number: 102,
      isotopes: [{
        nominal: 248,
        mass: 248.08655
      }, {
        nominal: 249,
        mass: 249.0878
      }, {
        nominal: 250,
        mass: 250.08756
      }, {
        nominal: 251,
        mass: 251.08894
      }, {
        nominal: 252,
        mass: 252.088967
      }, {
        nominal: 253,
        mass: 253.0905641
      }, {
        nominal: 254,
        mass: 254.090956
      }, {
        nominal: 255,
        mass: 255.093191
      }, {
        nominal: 256,
        mass: 256.0942829
      }, {
        nominal: 257,
        mass: 257.0968878
      }, {
        nominal: 258,
        mass: 258.09821
      }, {
        nominal: 259,
        mass: 259.10103
      }, {
        nominal: 260,
        mass: 260.10264
      }, {
        nominal: 261,
        mass: 261.1057
      }, {
        nominal: 262,
        mass: 262.10746
      }, {
        nominal: 263,
        mass: 263.11071
      }, {
        nominal: 264,
        mass: 264.11273
      }],
      symbol: 'No',
      name: 'Nobelium',
      mass: null
    }, {
      number: 103,
      isotopes: [{
        nominal: 251,
        mass: 251.09418
      }, {
        nominal: 252,
        mass: 252.09526
      }, {
        nominal: 253,
        mass: 253.09509
      }, {
        nominal: 254,
        mass: 254.09648
      }, {
        nominal: 255,
        mass: 255.096562
      }, {
        nominal: 256,
        mass: 256.098494
      }, {
        nominal: 257,
        mass: 257.099418
      }, {
        nominal: 258,
        mass: 258.10176
      }, {
        nominal: 259,
        mass: 259.102902
      }, {
        nominal: 260,
        mass: 260.1055
      }, {
        nominal: 261,
        mass: 261.10688
      }, {
        nominal: 262,
        mass: 262.10961
      }, {
        nominal: 263,
        mass: 263.11136
      }, {
        nominal: 264,
        mass: 264.1142
      }, {
        nominal: 265,
        mass: 265.11619
      }, {
        nominal: 266,
        mass: 266.11983
      }],
      symbol: 'Lr',
      name: 'Lawrencium',
      mass: null
    }, {
      number: 104,
      isotopes: [{
        nominal: 253,
        mass: 253.10044
      }, {
        nominal: 254,
        mass: 254.10005
      }, {
        nominal: 255,
        mass: 255.10127
      }, {
        nominal: 256,
        mass: 256.101152
      }, {
        nominal: 257,
        mass: 257.102918
      }, {
        nominal: 258,
        mass: 258.103428
      }, {
        nominal: 259,
        mass: 259.105596
      }, {
        nominal: 260,
        mass: 260.10644
      }, {
        nominal: 261,
        mass: 261.108773
      }, {
        nominal: 262,
        mass: 262.10992
      }, {
        nominal: 263,
        mass: 263.11249
      }, {
        nominal: 264,
        mass: 264.11388
      }, {
        nominal: 265,
        mass: 265.11668
      }, {
        nominal: 266,
        mass: 266.11817
      }, {
        nominal: 267,
        mass: 267.12179
      }, {
        nominal: 268,
        mass: 268.12397
      }],
      symbol: 'Rf',
      name: 'Rutherfordium',
      mass: null
    }, {
      number: 105,
      isotopes: [{
        nominal: 255,
        mass: 255.10707
      }, {
        nominal: 256,
        mass: 256.10789
      }, {
        nominal: 257,
        mass: 257.10758
      }, {
        nominal: 258,
        mass: 258.10928
      }, {
        nominal: 259,
        mass: 259.109492
      }, {
        nominal: 260,
        mass: 260.1113
      }, {
        nominal: 261,
        mass: 261.11192
      }, {
        nominal: 262,
        mass: 262.11407
      }, {
        nominal: 263,
        mass: 263.11499
      }, {
        nominal: 264,
        mass: 264.11741
      }, {
        nominal: 265,
        mass: 265.11861
      }, {
        nominal: 266,
        mass: 266.12103
      }, {
        nominal: 267,
        mass: 267.12247
      }, {
        nominal: 268,
        mass: 268.12567
      }, {
        nominal: 269,
        mass: 269.12791
      }, {
        nominal: 270,
        mass: 270.13136
      }],
      symbol: 'Db',
      name: 'Dubnium',
      mass: null
    }, {
      number: 106,
      isotopes: [{
        nominal: 258,
        mass: 258.11298
      }, {
        nominal: 259,
        mass: 259.1144
      }, {
        nominal: 260,
        mass: 260.114384
      }, {
        nominal: 261,
        mass: 261.115949
      }, {
        nominal: 262,
        mass: 262.116337
      }, {
        nominal: 263,
        mass: 263.11829
      }, {
        nominal: 264,
        mass: 264.11893
      }, {
        nominal: 265,
        mass: 265.12109
      }, {
        nominal: 266,
        mass: 266.12198
      }, {
        nominal: 267,
        mass: 267.12436
      }, {
        nominal: 268,
        mass: 268.12539
      }, {
        nominal: 269,
        mass: 269.12863
      }, {
        nominal: 270,
        mass: 270.13043
      }, {
        nominal: 271,
        mass: 271.13393
      }, {
        nominal: 272,
        mass: 272.13589
      }, {
        nominal: 273,
        mass: 273.13958
      }],
      symbol: 'Sg',
      name: 'Seaborgium',
      mass: null
    }, {
      number: 107,
      isotopes: [{
        nominal: 260,
        mass: 260.12166
      }, {
        nominal: 261,
        mass: 261.12145
      }, {
        nominal: 262,
        mass: 262.12297
      }, {
        nominal: 263,
        mass: 263.12292
      }, {
        nominal: 264,
        mass: 264.12459
      }, {
        nominal: 265,
        mass: 265.12491
      }, {
        nominal: 266,
        mass: 266.12679
      }, {
        nominal: 267,
        mass: 267.1275
      }, {
        nominal: 268,
        mass: 268.12969
      }, {
        nominal: 269,
        mass: 269.13042
      }, {
        nominal: 270,
        mass: 270.13336
      }, {
        nominal: 271,
        mass: 271.13526
      }, {
        nominal: 272,
        mass: 272.13826
      }, {
        nominal: 273,
        mass: 273.14024
      }, {
        nominal: 274,
        mass: 274.14355
      }, {
        nominal: 275,
        mass: 275.14567
      }],
      symbol: 'Bh',
      name: 'Bohrium',
      mass: null
    }, {
      number: 108,
      isotopes: [{
        nominal: 263,
        mass: 263.12852
      }, {
        nominal: 264,
        mass: 264.128357
      }, {
        nominal: 265,
        mass: 265.129793
      }, {
        nominal: 266,
        mass: 266.130046
      }, {
        nominal: 267,
        mass: 267.13167
      }, {
        nominal: 268,
        mass: 268.13186
      }, {
        nominal: 269,
        mass: 269.13375
      }, {
        nominal: 270,
        mass: 270.13429
      }, {
        nominal: 271,
        mass: 271.13717
      }, {
        nominal: 272,
        mass: 272.1385
      }, {
        nominal: 273,
        mass: 273.14168
      }, {
        nominal: 274,
        mass: 274.1433
      }, {
        nominal: 275,
        mass: 275.14667
      }, {
        nominal: 276,
        mass: 276.14846
      }, {
        nominal: 277,
        mass: 277.1519
      }],
      symbol: 'Hs',
      name: 'Hassium',
      mass: null
    }, {
      number: 109,
      isotopes: [{
        nominal: 265,
        mass: 265.136
      }, {
        nominal: 266,
        mass: 266.13737
      }, {
        nominal: 267,
        mass: 267.13719
      }, {
        nominal: 268,
        mass: 268.13865
      }, {
        nominal: 269,
        mass: 269.13882
      }, {
        nominal: 270,
        mass: 270.14033
      }, {
        nominal: 271,
        mass: 271.14074
      }, {
        nominal: 272,
        mass: 272.14341
      }, {
        nominal: 273,
        mass: 273.1444
      }, {
        nominal: 274,
        mass: 274.14724
      }, {
        nominal: 275,
        mass: 275.14882
      }, {
        nominal: 276,
        mass: 276.15159
      }, {
        nominal: 277,
        mass: 277.15327
      }, {
        nominal: 278,
        mass: 278.15631
      }, {
        nominal: 279,
        mass: 279.15808
      }],
      symbol: 'Mt',
      name: 'Meitnerium',
      mass: null
    }, {
      number: 110,
      isotopes: [{
        nominal: 267,
        mass: 267.14377
      }, {
        nominal: 268,
        mass: 268.14348
      }, {
        nominal: 269,
        mass: 269.144752
      }, {
        nominal: 270,
        mass: 270.144584
      }, {
        nominal: 271,
        mass: 271.14595
      }, {
        nominal: 272,
        mass: 272.14602
      }, {
        nominal: 273,
        mass: 273.14856
      }, {
        nominal: 274,
        mass: 274.14941
      }, {
        nominal: 275,
        mass: 275.15203
      }, {
        nominal: 276,
        mass: 276.15303
      }, {
        nominal: 277,
        mass: 277.15591
      }, {
        nominal: 278,
        mass: 278.15704
      }, {
        nominal: 279,
        mass: 279.1601
      }, {
        nominal: 280,
        mass: 280.16131
      }, {
        nominal: 281,
        mass: 281.16451
      }],
      symbol: 'Ds',
      name: 'Darmstadtium',
      mass: null
    }, {
      number: 111,
      isotopes: [{
        nominal: 272,
        mass: 272.15327
      }, {
        nominal: 273,
        mass: 273.15313
      }, {
        nominal: 274,
        mass: 274.15525
      }, {
        nominal: 275,
        mass: 275.15594
      }, {
        nominal: 276,
        mass: 276.15833
      }, {
        nominal: 277,
        mass: 277.15907
      }, {
        nominal: 278,
        mass: 278.16149
      }, {
        nominal: 279,
        mass: 279.16272
      }, {
        nominal: 280,
        mass: 280.16514
      }, {
        nominal: 281,
        mass: 281.16636
      }, {
        nominal: 282,
        mass: 282.16912
      }, {
        nominal: 283,
        mass: 283.17054
      }],
      symbol: 'Rg',
      name: 'Roentgenium',
      mass: null
    }, {
      number: 112,
      isotopes: [{
        nominal: 276,
        mass: 276.16141
      }, {
        nominal: 277,
        mass: 277.16364
      }, {
        nominal: 278,
        mass: 278.16416
      }, {
        nominal: 279,
        mass: 279.16654
      }, {
        nominal: 280,
        mass: 280.16715
      }, {
        nominal: 281,
        mass: 281.16975
      }, {
        nominal: 282,
        mass: 282.1705
      }, {
        nominal: 283,
        mass: 283.17327
      }, {
        nominal: 284,
        mass: 284.17416
      }, {
        nominal: 285,
        mass: 285.17712
      }],
      symbol: 'Cn',
      name: 'Copernicium',
      mass: null
    }, {
      number: 113,
      isotopes: [{
        nominal: 278,
        mass: 278.17058
      }, {
        nominal: 279,
        mass: 279.17095
      }, {
        nominal: 280,
        mass: 280.17293
      }, {
        nominal: 281,
        mass: 281.17348
      }, {
        nominal: 282,
        mass: 282.17567
      }, {
        nominal: 283,
        mass: 283.17657
      }, {
        nominal: 284,
        mass: 284.17873
      }, {
        nominal: 285,
        mass: 285.17973
      }, {
        nominal: 286,
        mass: 286.18221
      }, {
        nominal: 287,
        mass: 287.18339
      }],
      symbol: 'Nh',
      name: 'Nihonium',
      mass: null
    }, {
      number: 114,
      isotopes: [{
        nominal: 285,
        mass: 285.18364
      }, {
        nominal: 286,
        mass: 286.18423
      }, {
        nominal: 287,
        mass: 287.18678
      }, {
        nominal: 288,
        mass: 288.18757
      }, {
        nominal: 289,
        mass: 289.19042
      }],
      symbol: 'Fl',
      name: 'Flerovium',
      mass: null
    }, {
      number: 115,
      isotopes: [{
        nominal: 287,
        mass: 287.1907
      }, {
        nominal: 288,
        mass: 288.19274
      }, {
        nominal: 289,
        mass: 289.19363
      }, {
        nominal: 290,
        mass: 290.19598
      }, {
        nominal: 291,
        mass: 291.19707
      }],
      symbol: 'Mc',
      name: 'Moscovium',
      mass: null
    }, {
      number: 116,
      isotopes: [{
        nominal: 289,
        mass: 289.19816
      }, {
        nominal: 290,
        mass: 290.19864
      }, {
        nominal: 291,
        mass: 291.20108
      }, {
        nominal: 292,
        mass: 292.20174
      }, {
        nominal: 293,
        mass: 293.20449
      }],
      symbol: 'Lv',
      name: 'Livermorium',
      mass: null
    }, {
      number: 117,
      isotopes: [{
        nominal: 291,
        mass: 291.20553
      }, {
        nominal: 292,
        mass: 292.20746
      }, {
        nominal: 293,
        mass: 293.20824
      }, {
        nominal: 294,
        mass: 294.21046
      }],
      symbol: 'Ts',
      name: 'Teennessine',
      mass: null
    }, {
      number: 118,
      isotopes: [{
        nominal: 293,
        mass: 293.21356
      }, {
        nominal: 294,
        mass: 294.21392
      }, {
        nominal: 295,
        mass: 295.21624
      }],
      symbol: 'Og',
      name: 'Oganesson',
      mass: null
    }];

    Object.defineProperty(elements$1, "__esModule", {
      value: true
    });
    elements$1.elements = void 0;
    const elementsAndIsotopes_js_1 = elementsAndIsotopes;
    elements$1.elements = elementsAndIsotopes_js_1.elementsAndIsotopes.map(element => ({
      number: element.number,
      symbol: element.symbol,
      mass: element.mass,
      name: element.name,
      monoisotopicMass: element.monoisotopicMass
    }));

    var elementsAndIsotopesObject = {};

    (function (exports) {

      Object.defineProperty(exports, "__esModule", {
        value: true
      });
      exports.elementsAndIsotopesObject = void 0;
      const elementsAndIsotopes_js_1 = elementsAndIsotopes;
      exports.elementsAndIsotopesObject = {};
      for (const element of elementsAndIsotopes_js_1.elementsAndIsotopes) {
        exports.elementsAndIsotopesObject[element.symbol] = element;
      }
    })(elementsAndIsotopesObject);

    var elementsAndStableIsotopes = {};

    (function (exports) {

      Object.defineProperty(exports, "__esModule", {
        value: true
      });
      exports.elementsAndStableIsotopes = void 0;
      const elementsAndIsotopes_js_1 = elementsAndIsotopes;
      exports.elementsAndStableIsotopes = structuredClone(elementsAndIsotopes_js_1.elementsAndIsotopes);
      for (const element of exports.elementsAndStableIsotopes) {
        element.isotopes = element.isotopes.filter(i => {
          return typeof i.abundance === 'number' && i.abundance > 0;
        });
      }
    })(elementsAndStableIsotopes);

    var elementsAndStableIsotopesObject = {};

    (function (exports) {

      Object.defineProperty(exports, "__esModule", {
        value: true
      });
      exports.elementsAndStableIsotopesObject = void 0;
      const elementsAndStableIsotopes_js_1 = elementsAndStableIsotopes;
      exports.elementsAndStableIsotopesObject = {};
      for (const element of elementsAndStableIsotopes_js_1.elementsAndStableIsotopes) {
        exports.elementsAndStableIsotopesObject[element.symbol] = element;
      }
    })(elementsAndStableIsotopesObject);

    var elementsObject = {};

    (function (exports) {

      Object.defineProperty(exports, "__esModule", {
        value: true
      });
      exports.elementsObject = void 0;
      const elements_js_1 = elements$1;
      exports.elementsObject = {};
      for (const element of elements_js_1.elements) {
        exports.elementsObject[element.symbol] = element;
      }
    })(elementsObject);

    var isotopesObject = {};

    (function (exports) {

      Object.defineProperty(exports, "__esModule", {
        value: true
      });
      exports.isotopesObject = void 0;
      const elementsAndIsotopesObject_js_1 = elementsAndIsotopesObject;
      exports.isotopesObject = {};
      for (const [symbol, element] of Object.entries(elementsAndIsotopesObject_js_1.elementsAndIsotopesObject)) {
        if (!element) continue;
        for (const isotope of element.isotopes) {
          exports.isotopesObject[`${isotope.nominal}${symbol}`] = {
            abundance: isotope.abundance,
            mass: isotope.mass
          };
        }
      }
    })(isotopesObject);

    var stableIsotopesObject = {};

    (function (exports) {

      Object.defineProperty(exports, "__esModule", {
        value: true
      });
      exports.stableIsotopesObject = void 0;
      const elementsAndIsotopes_js_1 = elementsAndIsotopes;
      exports.stableIsotopesObject = {};
      for (const element of elementsAndIsotopes_js_1.elementsAndIsotopes) {
        let abundance = 0;
        let mostAbundant = 0;
        for (const isotope of element.isotopes) {
          if (typeof isotope.abundance === 'number' && isotope.abundance > abundance) {
            abundance = isotope.abundance;
            mostAbundant = isotope.nominal;
          }
        }
        for (const isotope of element.isotopes) {
          if (isotope.abundance === 0) continue;
          const entry = {
            name: element.name,
            mass: isotope.mass,
            symbol: element.symbol,
            mostAbundant: false
          };
          if (isotope.nominal === mostAbundant) {
            entry.mostAbundant = true;
          }
          exports.stableIsotopesObject[`${isotope.nominal}${element.symbol}`] = entry;
        }
      }
    })(stableIsotopesObject);

    var types = {};

    Object.defineProperty(types, "__esModule", {
      value: true
    });

    var unsaturationsObject = {};

    Object.defineProperty(unsaturationsObject, "__esModule", {
      value: true
    });
    unsaturationsObject.unsaturationsObject = void 0;
    unsaturationsObject.unsaturationsObject = {
      O: 0,
      N: 1,
      H: -1,
      Na: -1,
      K: -1,
      Li: -1,
      Ca: -2,
      C: 2,
      F: -1,
      Si: 2,
      Cl: -1,
      Br: -1,
      I: -1,
      S: 0,
      P: 1
    };

    (function (exports) {

      var __createBinding = src$5 && src$5.__createBinding || (Object.create ? function (o, m, k, k2) {
        if (k2 === undefined) k2 = k;
        var desc = Object.getOwnPropertyDescriptor(m, k);
        if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
          desc = {
            enumerable: true,
            get: function () {
              return m[k];
            }
          };
        }
        Object.defineProperty(o, k2, desc);
      } : function (o, m, k, k2) {
        if (k2 === undefined) k2 = k;
        o[k2] = m[k];
      });
      var __exportStar = src$5 && src$5.__exportStar || function (m, exports) {
        for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
      };
      Object.defineProperty(exports, "__esModule", {
        value: true
      });
      __exportStar(constants, exports);
      __exportStar(elements$1, exports);
      __exportStar(elementsAndIsotopes, exports);
      __exportStar(elementsAndIsotopesObject, exports);
      __exportStar(elementsAndStableIsotopes, exports);
      __exportStar(elementsAndStableIsotopesObject, exports);
      __exportStar(elementsObject, exports);
      __exportStar(isotopesObject, exports);
      __exportStar(stableIsotopesObject, exports);
      __exportStar(types, exports);
      __exportStar(unsaturationsObject, exports);
    })(src$5);

    var src$4 = {};

    var groups = {};

    Object.defineProperty(groups, "__esModule", {
      value: true
    });
    groups.groups = void 0;
    groups.groups = [{
      "symbol": "Abu",
      "name": "2-Aminobutyric acid diradical",
      "mf": "C4H7NO",
      "ocl": {
        "value": "dazHPBPOEgEInVZjcH@",
        "coordinates": "!Bb@I~@Ha}_c~H@m]}bGt"
      },
      "mass": 85.10463700109551,
      "monoisotopicMass": 85.05276384961,
      "unsaturation": 2,
      "elements": [{
        "symbol": "C",
        "number": 4
      }, {
        "symbol": "H",
        "number": 7
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Acet",
      "name": "Acetyl",
      "mf": "C2H3O",
      "ocl": {
        "value": "gCaHDEeIi`@",
        "coordinates": "!BbOq~@Ha}"
      },
      "mass": 43.04469897995611,
      "monoisotopicMass": 43.01838971626,
      "unsaturation": 1,
      "elements": [{
        "symbol": "C",
        "number": 2
      }, {
        "symbol": "H",
        "number": 3
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Acm",
      "name": "Acetamidomethyl",
      "mf": "C3H6NO",
      "ocl": {
        "value": "gGYHDPliJuS@@",
        "coordinates": "!BbOrH_Xc|_`BH_P"
      },
      "mass": 72.08596035030448,
      "monoisotopicMass": 72.04493881738,
      "unsaturation": 1,
      "elements": [{
        "symbol": "C",
        "number": 3
      }, {
        "symbol": "H",
        "number": 6
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Adao",
      "name": "Adamantyloxy",
      "mf": "C10H15O",
      "ocl": {
        "value": "dc\\H`HAYRVeV^dUGZjjjj@@",
        "coordinates": "!B]BOXN`EP}CdB\\tbZ@Ijh~hRELdOBBp"
      },
      "mass": 151.2258752025074,
      "monoisotopicMass": 151.11229010302,
      "unsaturation": 5,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 15
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Aib",
      "name": "alpha-Aminoisobutyric acid diradical",
      "mf": "C4H7NO",
      "ocl": {
        "value": "dazHPBPOGgEInfZj@@",
        "coordinates": "!Bb@I~@Ha}b@K|uwwWbGt"
      },
      "mass": 85.10463700109551,
      "monoisotopicMass": 85.05276384961,
      "unsaturation": 2,
      "elements": [{
        "symbol": "C",
        "number": 4
      }, {
        "symbol": "H",
        "number": 7
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Ala",
      "name": "Alanine diradical",
      "mf": "C3H5NO",
      "kind": "aa",
      "oneLetter": "A",
      "alternativeOneLetter": "α",
      "ocl": {
        "value": "gNyDBaxmqR[fZjZ@",
        "coordinates": "!BbOr~@H`}bOr~Wxb}"
      },
      "mass": 71.07801959624871,
      "monoisotopicMass": 71.03711378515,
      "unsaturation": 2,
      "elements": [{
        "symbol": "C",
        "number": 3
      }, {
        "symbol": "H",
        "number": 5
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Arg",
      "name": "Arginine diradical",
      "mf": "C6H12N4O",
      "kind": "aa",
      "oneLetter": "R",
      "alternativeOneLetter": "ρ",
      "ocl": {
        "value": "dkLhPBgSPOEgEInWUijjihr@@",
        "coordinates": "!Bb@I~@Ha}_c~H@m]}bGvHHa}b@I~@Ha}"
      },
      "mass": 156.18592219918227,
      "monoisotopicMass": 156.10111102405,
      "unsaturation": 4,
      "elements": [{
        "symbol": "C",
        "number": 6
      }, {
        "symbol": "H",
        "number": 12
      }, {
        "symbol": "N",
        "number": 4
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Argp",
      "name": "Arginine triradical",
      "mf": "C6H11N4O",
      "ocl": {
        "value": "dglhpHpil@gWDEI[UYZfjji`T@",
        "coordinates": "!BbGvHGx@bGvH@ha}bOrH_Wxb@KW_Wx@bGt"
      },
      "mass": 155.1779814451265,
      "monoisotopicMass": 155.09328599182,
      "unsaturation": 5,
      "elements": [{
        "symbol": "C",
        "number": 6
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "N",
        "number": 4
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Asn",
      "name": "Asparagine diradical",
      "mf": "C4H6N2O2",
      "kind": "aa",
      "oneLetter": "N",
      "alternativeOneLetter": "η",
      "ocl": {
        "value": "deeDPBeACqYqR[ezZjZL`@",
        "coordinates": "!BbGu~Ox`B_`BH_X`Bb@I~@Ha}"
      },
      "mass": 114.10280438280381,
      "monoisotopicMass": 114.04292744137999,
      "unsaturation": 4,
      "elements": [{
        "symbol": "C",
        "number": 4
      }, {
        "symbol": "H",
        "number": 6
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Asnp",
      "name": "Asparagine triradical",
      "mf": "C4H5N2O2",
      "ocl": {
        "value": "dmUDpH[E@IEqgqRVvVijjXi@@",
        "coordinates": "!Bb@JH_Wxb@JH_Wxb@KW_Wx@bGt"
      },
      "mass": 113.09486362874803,
      "monoisotopicMass": 113.03510240915,
      "unsaturation": 5,
      "elements": [{
        "symbol": "C",
        "number": 4
      }, {
        "symbol": "H",
        "number": 5
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Asp",
      "name": "Aspartic acid diradical",
      "mf": "C4H5NO3",
      "kind": "aa",
      "oneLetter": "D",
      "alternativeOneLetter": "δ",
      "ocl": {
        "value": "defLPBPYCqYqR[ezZjZL`@",
        "coordinates": "!BbGu~Ox`B_`BH_X`Bb@I~@Ha}"
      },
      "mass": 115.08756534162052,
      "monoisotopicMass": 115.02694302429,
      "unsaturation": 4,
      "elements": [{
        "symbol": "C",
        "number": 4
      }, {
        "symbol": "H",
        "number": 5
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 3
      }]
    }, {
      "symbol": "Aspp",
      "name": "Aspartic acid triradical",
      "mf": "C4H4NO3",
      "ocl": {
        "value": "dmVLpFcE@IEqgqRVvVijjXi@@",
        "coordinates": "!Bb@JH_Wxb@JH_Wxb@KW_Wx@bGt"
      },
      "mass": 114.07962458756472,
      "monoisotopicMass": 114.01911799206,
      "unsaturation": 5,
      "elements": [{
        "symbol": "C",
        "number": 4
      }, {
        "symbol": "H",
        "number": 4
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 3
      }]
    }, {
      "symbol": "Asu",
      "name": "alpha-Aminosuberic acid diradical",
      "mf": "C8H13NO3",
      "ocl": {
        "value": "dgnLPBP{CqYqR[euVfjjihr@@",
        "coordinates": "!BbGu~Ox`B_`BH_Xc|bOrH_X`BbGvHGx@bGt"
      },
      "mass": 171.19403496100773,
      "monoisotopicMass": 171.08954328213002,
      "unsaturation": 4,
      "elements": [{
        "symbol": "C",
        "number": 8
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 3
      }]
    }, {
      "symbol": "Asup",
      "name": "alpha-Aminosuberic acid triradical",
      "mf": "C8H12NO3",
      "ocl": {
        "value": "do^LpEcG@IMqoqRVuUejZjjibT@",
        "coordinates": "!BbOrH_Wxb@JH_Xc|bGvHHa}_c~H@m]}_`BH_P"
      },
      "mass": 170.18609420695194,
      "monoisotopicMass": 170.0817182499,
      "unsaturation": 5,
      "elements": [{
        "symbol": "C",
        "number": 8
      }, {
        "symbol": "H",
        "number": 12
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 3
      }]
    }, {
      "symbol": "Boc",
      "name": "t-Butoxycarbonyl",
      "mf": "C5H9O2",
      "ocl": {
        "value": "daxD`DpEeImjZj@@",
        "coordinates": "!B|Ou~_A||Ow}mC}_O@"
      },
      "mass": 101.12395611881479,
      "monoisotopicMass": 101.06025452921,
      "unsaturation": 1,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 9
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Bom",
      "name": "Benzyloxymethyl",
      "mf": "C8H9O",
      "ocl": {
        "value": "deTH`DAYRUYTYj`@@@",
        "coordinates": "!B|Gsp__A||Owp_Gy|Gwp_Wy"
      },
      "mass": 121.15675888470227,
      "monoisotopicMass": 121.06533990964,
      "unsaturation": 7,
      "elements": [{
        "symbol": "C",
        "number": 8
      }, {
        "symbol": "H",
        "number": 9
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Brz",
      "name": "2-Bromobenzyloxycarbonyl",
      "mf": "C8H6BrO2",
      "ocl": {
        "value": "dcLDPDpEd\\QImYgWYjB@@@",
        "coordinates": "!Bb@I~@Hb}b@JH_X`B_c}~@Hb}bGu~Op"
      },
      "mass": 214.03586932736317,
      "monoisotopicMass": 212.95511703252,
      "unsaturation": 9,
      "elements": [{
        "symbol": "C",
        "number": 8
      }, {
        "symbol": "H",
        "number": 6
      }, {
        "symbol": "Br",
        "number": 1
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Bu",
      "name": "Butyl",
      "mf": "C4H9",
      "ocl": {
        "value": "gJPH@liJuP@",
        "coordinates": "!B@Fp@XpAl@FL"
      },
      "mass": 57.114410373442986,
      "monoisotopicMass": 57.07042529007,
      "unsaturation": -1,
      "elements": [{
        "symbol": "C",
        "number": 4
      }, {
        "symbol": "H",
        "number": 9
      }]
    }, {
      "symbol": "Bum",
      "name": "t-Butoxymethyl",
      "mf": "C5H11O",
      "ocl": {
        "value": "gNqHDEeIVjj`@",
        "coordinates": "!B@FL@[@AcXs|@Xvp@"
      },
      "mass": 87.14043270260808,
      "monoisotopicMass": 87.08098997409999,
      "unsaturation": -1,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Bz",
      "name": "Benzoyl",
      "mf": "C7H5O",
      "ocl": {
        "value": "didH`DAYR[e^FX@@@@",
        "coordinates": "!BbOq~@Ha}b@I~Oxa}bGu~Op"
      },
      "mass": 105.1142599717439,
      "monoisotopicMass": 105.03403978072,
      "unsaturation": 9,
      "elements": [{
        "symbol": "C",
        "number": 7
      }, {
        "symbol": "H",
        "number": 5
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Bzl",
      "name": "Benzyl",
      "mf": "C7H7",
      "ocl": {
        "value": "daD@`@VTeeVz`@@@",
        "coordinates": "!B|Gsp_A|_gp_A}_g|"
      },
      "mass": 91.13073655553718,
      "monoisotopicMass": 91.05477522561,
      "unsaturation": 7,
      "elements": [{
        "symbol": "C",
        "number": 7
      }, {
        "symbol": "H",
        "number": 7
      }]
    }, {
      "symbol": "Bn",
      "name": "Benzyl",
      "mf": "C7H7",
      "ocl": {
        "value": "daD@`@VTeeVz`@@@",
        "coordinates": "!B|Gsp_A|_gp_A}_g|"
      },
      "mass": 91.13073655553718,
      "monoisotopicMass": 91.05477522561,
      "unsaturation": 7,
      "elements": [{
        "symbol": "C",
        "number": 7
      }, {
        "symbol": "H",
        "number": 7
      }]
    }, {
      "symbol": "Bzlo",
      "name": "Benzyloxy",
      "mf": "C7H7O",
      "ocl": {
        "value": "didH`HAYRUe^Fh@@@@",
        "coordinates": "!B|Gwp_OC}|Gq~_A}|Gu~_p"
      },
      "mass": 107.13014147985547,
      "monoisotopicMass": 107.04968984518,
      "unsaturation": 7,
      "elements": [{
        "symbol": "C",
        "number": 7
      }, {
        "symbol": "H",
        "number": 7
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Cha",
      "name": "beta-Cyclohexylalanine diradical",
      "mf": "C9H15NO",
      "ocl": {
        "value": "dknHPBPOEgEInWe]NZjjjcH@",
        "coordinates": "!Bb@I~@Ha}_c~H@m]}bGvH@gxbGvH_Wx"
      },
      "mass": 153.22184251721796,
      "monoisotopicMass": 153.11536410745,
      "unsaturation": 4,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 15
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Chxo",
      "name": "Cyclohexyloxy",
      "mf": "C6H11O",
      "ocl": {
        "value": "daDH`HAYRVU[jjj@@",
        "coordinates": "!B|Gsp_A|_gp_A}_g|"
      },
      "mass": 99.15116859934332,
      "monoisotopicMass": 99.08098997409999,
      "unsaturation": 1,
      "elements": [{
        "symbol": "C",
        "number": 6
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Cit",
      "name": "Citrulline diradical",
      "mf": "C6H11N3O2",
      "ocl": {
        "value": "dkODPBdttOEgEInWUijjihr@@",
        "coordinates": "!Bb@I~@Ha}_c~H@m]}bGvHHa}b@I~@Ha}"
      },
      "mass": 157.170683157999,
      "monoisotopicMass": 157.08512660696,
      "unsaturation": 4,
      "elements": [{
        "symbol": "C",
        "number": 6
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Citp",
      "name": "Citrulline triradical",
      "mf": "C6H10N3O2",
      "ocl": {
        "value": "dgoDpHJ\\l@gWDEI[UYZfjji`T@",
        "coordinates": "!BbGvHGx@bGvH@ha}bOrH_Wxb@KW_Wx@bGt"
      },
      "mass": 156.16274240394318,
      "monoisotopicMass": 156.07730157473,
      "unsaturation": 5,
      "elements": [{
        "symbol": "C",
        "number": 6
      }, {
        "symbol": "H",
        "number": 10
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Clz",
      "name": "2-Chlorobenzyloxycarbonyl",
      "mf": "C8H6ClO2",
      "ocl": {
        "value": "dcLDPDpEdXaImYgWYjB@@@",
        "coordinates": "!Bb@I~@Hb}b@JH_X`B_c}~@Hb}bGu~Op"
      },
      "mass": 169.58527912946118,
      "monoisotopicMass": 169.00563211451998,
      "unsaturation": 9,
      "elements": [{
        "symbol": "C",
        "number": 8
      }, {
        "symbol": "H",
        "number": 6
      }, {
        "symbol": "Cl",
        "number": 1
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Cp",
      "name": "Cyclopentadienyl",
      "mf": "C5H5",
      "ocl": {
        "value": "gFpH@liLimRp@",
        "coordinates": "!B\\OtPThyEGl@fP"
      },
      "mass": 65.09338325395512,
      "monoisotopicMass": 65.03912516115,
      "unsaturation": 5,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 5
      }]
    }, {
      "symbol": "Cys",
      "name": "Cysteine diradical",
      "mf": "C3H5NOS",
      "kind": "aa",
      "oneLetter": "C",
      "alternativeOneLetter": "ς",
      "ocl": {
        "value": "dazHpBPOEgG`aInVZjcH@",
        "coordinates": "!Bb@I~@Ha}_c~H@m]}bGt"
      },
      "mass": 103.14280700237578,
      "monoisotopicMass": 103.00918495955,
      "unsaturation": 2,
      "elements": [{
        "symbol": "C",
        "number": 3
      }, {
        "symbol": "H",
        "number": 5
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Cysp",
      "name": "Cysteine triradical",
      "mf": "C3H4NOS",
      "ocl": {
        "value": "diFHHBD@f@agGoEIVVjjfLP@",
        "coordinates": "!BbGvHHa}_c~HM]}_`BH_P"
      },
      "mass": 102.13486624831998,
      "monoisotopicMass": 102.00135992732,
      "unsaturation": 3,
      "elements": [{
        "symbol": "C",
        "number": 3
      }, {
        "symbol": "H",
        "number": 4
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "D",
      "name": "Deuterium",
      "mf": "[2H]",
      "ocl": {
        "value": "eFAAYhBLCEH@",
        "coordinates": "!B@BL"
      },
      "mass": 2.01410177812,
      "monoisotopicMass": 2.01410177812,
      "unsaturation": -1,
      "elements": [{
        "symbol": "H",
        "number": 1,
        "isotope": 2
      }]
    }, {
      "symbol": "Dde",
      "name": "Dde",
      "mf": "C10H13O2",
      "ocl": {
        "value": "dklD`FDEgHhihicIVZfZj@@",
        "coordinates": "!Bb@I~@Ha}upJH@m]}_`BH_Wx@b@I}bOrH"
      },
      "mass": 165.20939861871415,
      "monoisotopicMass": 165.09155465812998,
      "unsaturation": 7,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Dnp",
      "name": "2,4-Dinitrophenyl",
      "mf": "C6H3N2O4",
      "ocl": {
        "value": "dkmB`bWatpVRd^VS{HhheEUFfBAbX@@",
        "coordinates": "!B_c~H_]]}b@I~Owx_`BH_]]}_c~H_]]}"
      },
      "mass": 167.09926376274353,
      "monoisotopicMass": 167.00928158383,
      "unsaturation": 11,
      "elements": [{
        "symbol": "C",
        "number": 6
      }, {
        "symbol": "H",
        "number": 3
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 4
      }]
    }, {
      "symbol": "Et",
      "name": "Ethyl",
      "mf": "C2H5",
      "ocl": {
        "value": "eMBAYRZ@",
        "coordinates": "!B@Fp@Xp"
      },
      "mass": 29.061175563749384,
      "monoisotopicMass": 29.03912516115,
      "unsaturation": -1,
      "elements": [{
        "symbol": "C",
        "number": 2
      }, {
        "symbol": "H",
        "number": 5
      }]
    }, {
      "symbol": "Fmoc",
      "name": "Fluorenylmethoxycarbonyl",
      "mf": "C15H11O2",
      "ocl": {
        "value": "fde@b@DX@liMkLrjxeVCzLuT@@@P@@@",
        "coordinates": "!BbOq~@Ha}bOrH_]ARcm}Tv~i`pAeKv|@fpB[j[~iozfAKvp"
      },
      "mass": 223.24719659427882,
      "monoisotopicMass": 223.07590459367,
      "unsaturation": 19,
      "elements": [{
        "symbol": "C",
        "number": 15
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "For",
      "name": "Formyl",
      "mf": "CHO",
      "ocl": {
        "value": "eMJDVTfP@",
        "coordinates": "!B@Fp@Xp"
      },
      "mass": 29.018081575109303,
      "monoisotopicMass": 29.0027396518,
      "unsaturation": 1,
      "elements": [{
        "symbol": "C",
        "number": 1
      }, {
        "symbol": "H",
        "number": 1
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Gln",
      "name": "Glutamine diradical",
      "mf": "C5H8N2O2",
      "kind": "aa",
      "oneLetter": "Q",
      "alternativeOneLetter": "ξ",
      "ocl": {
        "value": "dmUDPBUICqYqR[evfjihr@@",
        "coordinates": "!Bb@I~@Ha}_c~H@m]}bGvHGx@bGt"
      },
      "mass": 128.12942178765059,
      "monoisotopicMass": 128.05857750584,
      "unsaturation": 4,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 8
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Glnp",
      "name": "Glutamine triradical",
      "mf": "C5H7N2O2",
      "ocl": {
        "value": "dcuDpH{MAYeqWqRVuejZjiad@",
        "coordinates": "!BbGvHGx@bGvH@ha}_c~HM]}_`BH_P"
      },
      "mass": 127.12148103359483,
      "monoisotopicMass": 127.05075247361,
      "unsaturation": 5,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 7
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Glp",
      "name": "Pyroglutamine",
      "mf": "C5H5NO2",
      "ocl": {
        "value": "deVDPBRP|V\\TfygxYjjZL`@",
        "coordinates": "!Bb@I~@Ha}tEJNwr[@UMo@FXBN"
      },
      "mass": 111.09889631403748,
      "monoisotopicMass": 111.03202840472,
      "unsaturation": 6,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 5
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Glu",
      "name": "Glutamic acid diradical",
      "mf": "C5H7NO3",
      "kind": "aa",
      "oneLetter": "E",
      "alternativeOneLetter": "ε",
      "ocl": {
        "value": "dmVLPBRUCqYqR[evfjihr@@",
        "coordinates": "!Bb@I~@Ha}_c~H@m]}bGvHGx@bGt"
      },
      "mass": 129.11418274646732,
      "monoisotopicMass": 129.04259308875,
      "unsaturation": 4,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 7
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 3
      }]
    }, {
      "symbol": "Glup",
      "name": "Glutamic acid triradical",
      "mf": "C5H6NO3",
      "ocl": {
        "value": "dcvLpNcM@IeqWqRVuejZjiad@",
        "coordinates": "!BbGvHGx@bGvH@ha}_c~HM]}_`BH_P"
      },
      "mass": 128.10624199241153,
      "monoisotopicMass": 128.03476805652002,
      "unsaturation": 5,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 6
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 3
      }]
    }, {
      "symbol": "Gly",
      "name": "Glycine diradical",
      "mf": "C2H3NO",
      "kind": "aa",
      "oneLetter": "G",
      "alternativeOneLetter": "γ",
      "ocl": {
        "value": "gGYDBaxuqR[Yj@@",
        "coordinates": "!BbOq~@Ha}bOrH_P"
      },
      "mass": 57.051402191401905,
      "monoisotopicMass": 57.021463720689994,
      "unsaturation": 2,
      "elements": [{
        "symbol": "C",
        "number": 2
      }, {
        "symbol": "H",
        "number": 3
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Hci",
      "name": "Homocitrulline diradical",
      "mf": "C7H13N3O2",
      "ocl": {
        "value": "dgoDPBVtLOEgEInWUZZjjfcH@",
        "coordinates": "!BbGu~Ox`B_`BH_Xc|bOrH_X`BbGvHGx@bGt"
      },
      "mass": 171.19730056284578,
      "monoisotopicMass": 171.10077667142,
      "unsaturation": 4,
      "elements": [{
        "symbol": "C",
        "number": 7
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Hcip",
      "name": "Homocitrulline triradical",
      "mf": "C7H12N3O2",
      "ocl": {
        "value": "do_DpHI\\\\EdwFEI[UVVijjjfIP@",
        "coordinates": "!BbOrH_Wxb@JH_Xc|bGvHHa}_c~H@m]}_`BH_P"
      },
      "mass": 170.18935980879002,
      "monoisotopicMass": 170.09295163918998,
      "unsaturation": 5,
      "elements": [{
        "symbol": "C",
        "number": 7
      }, {
        "symbol": "H",
        "number": 12
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "His",
      "name": "Histidine diradical",
      "mf": "C6H7N3O",
      "kind": "aa",
      "oneLetter": "H",
      "alternativeOneLetter": "ζ",
      "ocl": {
        "value": "dcOHPBGTCqYqR[eyUvZjejL`@",
        "coordinates": "!Bb@I~@Ha}_c~H@m]}bGwPTh{_UMo@FP"
      },
      "mass": 137.13951521745759,
      "monoisotopicMass": 137.05891185847,
      "unsaturation": 8,
      "elements": [{
        "symbol": "C",
        "number": 6
      }, {
        "symbol": "H",
        "number": 7
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Hisp",
      "name": "Histidine triradical",
      "mf": "C6H6N3O",
      "ocl": {
        "value": "dkoHpHHSAYUqwqRY]YXjjVjihy@@",
        "coordinates": "!BTmA}bL@fUHRN`H`BbGu~Ox`Buwu~@Ha}"
      },
      "mass": 136.13157446340182,
      "monoisotopicMass": 136.05108682624,
      "unsaturation": 9,
      "elements": [{
        "symbol": "C",
        "number": 6
      }, {
        "symbol": "H",
        "number": 6
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Hser",
      "name": "Homoserine diradical",
      "mf": "C4H7NO2",
      "ocl": {
        "value": "diFDPBPP|V\\Tfy^Zjhr@@",
        "coordinates": "!BbGu~Ox`B_`BH_X`Bb@JH_P"
      },
      "mass": 101.10404192541378,
      "monoisotopicMass": 101.04767846918,
      "unsaturation": 2,
      "elements": [{
        "symbol": "C",
        "number": 4
      }, {
        "symbol": "H",
        "number": 7
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Hserp",
      "name": "Homoserine triradical",
      "mf": "C4H6NO2",
      "ocl": {
        "value": "defDpJbPV^\\Q|TeVVjji`d@",
        "coordinates": "!Bb@JH_X`BbGu~Oxc|uwu~@Ha}"
      },
      "mass": 100.09610117135801,
      "monoisotopicMass": 100.03985343695001,
      "unsaturation": 3,
      "elements": [{
        "symbol": "C",
        "number": 4
      }, {
        "symbol": "H",
        "number": 6
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Hyp",
      "name": "Hydroxyproline diradical",
      "mf": "C5H7NO2",
      "ocl": {
        "value": "deVDPBRP|V\\\\bfbbOCMUUIdE@@",
        "coordinates": "!Bb@I~@Ha}tEJNwr[@UMo@FUJO"
      },
      "mass": 113.11477782214904,
      "monoisotopicMass": 113.04767846918,
      "unsaturation": 4,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 7
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Hypp",
      "name": "Hydroxyproline triradical",
      "mf": "C5H6NO2",
      "ocl": {
        "value": "dmvDpJaPB^\\Y|TeeWjZjjidRL`@",
        "coordinates": "!BBOpH_UARcc}TNtBY@HyRSpCQDr\\"
      },
      "mass": 112.10683706809326,
      "monoisotopicMass": 112.03985343695001,
      "unsaturation": 5,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 6
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Ile",
      "name": "Isoleucine diradical",
      "mf": "C6H11NO",
      "kind": "aa",
      "oneLetter": "I",
      "alternativeOneLetter": "ι",
      "ocl": {
        "value": "defHPBPOEgEInVyjjdrT`@",
        "coordinates": "!BbGu~Oxc|_`BH_Xc|b@I~Oxa}"
      },
      "mass": 113.15787181078912,
      "monoisotopicMass": 113.08406397853,
      "unsaturation": 2,
      "elements": [{
        "symbol": "C",
        "number": 6
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Ivdde",
      "name": "1-[4,4-dimethyl-2,6-dioxocyclohexylidene)-3-methylbutyl",
      "mf": "C13H19O2",
      "ocl": {
        "value": "f`a@b@NR@lyEEDhhigEVfjYjj`@@",
        "coordinates": "!BbOq~@Ha}urHGxuwu~@Ha}_`CW_Xa}bOq}b@JH"
      },
      "mass": 207.28925083325453,
      "monoisotopicMass": 207.13850485151,
      "unsaturation": 7,
      "elements": [{
        "symbol": "C",
        "number": 13
      }, {
        "symbol": "H",
        "number": 19
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Leu",
      "name": "Leucine diradical",
      "mf": "C6H11NO",
      "kind": "aa",
      "oneLetter": "L",
      "alternativeOneLetter": "λ",
      "ocl": {
        "value": "defHPBPOEgEInWijjhr@@",
        "coordinates": "!BbGu~Ox`B_`BH_X`Bb@I~@Ha}"
      },
      "mass": 113.15787181078912,
      "monoisotopicMass": 113.08406397853,
      "unsaturation": 2,
      "elements": [{
        "symbol": "C",
        "number": 6
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Lys",
      "name": "Lysine diradical",
      "mf": "C6H12N2O",
      "kind": "aa",
      "oneLetter": "K",
      "alternativeOneLetter": "κ",
      "ocl": {
        "value": "dmUHPBU@|V\\Tfy]YjjjL`@",
        "coordinates": "!BbGu~Ox`B_`BHoX`Bb@JH_X`BbKt"
      },
      "mass": 128.17251577629068,
      "monoisotopicMass": 128.09496301519,
      "unsaturation": 2,
      "elements": [{
        "symbol": "C",
        "number": 6
      }, {
        "symbol": "H",
        "number": 12
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Lysp",
      "name": "Lysine triradical",
      "mf": "C6H11N2O",
      "ocl": {
        "value": "dcuHpH{PVY\\U|TeUYZjjjXY@@",
        "coordinates": "!Bb@JH_X`BbGvH@ha}_c~H@m]}_`BH_P"
      },
      "mass": 127.16457502223491,
      "monoisotopicMass": 127.08713798295999,
      "unsaturation": 3,
      "elements": [{
        "symbol": "C",
        "number": 6
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Mbh",
      "name": "4,4'-Dimethoxybenzhydryl",
      "mf": "C15H15O2",
      "ocl": {
        "value": "fdy@b@G^@liLsJkzlcZmT@@@UP@@@",
        "coordinates": "!BbGvHGx_`BH_Xa}uwvHHc|_c}~Oxa}uwvHGxbGwW_P"
      },
      "mass": 227.27895961050194,
      "monoisotopicMass": 227.10720472258998,
      "unsaturation": 15,
      "elements": [{
        "symbol": "C",
        "number": 15
      }, {
        "symbol": "H",
        "number": 15
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Me",
      "name": "Methyl",
      "mf": "CH3",
      "ocl": {
        "value": "eFBAYc@@",
        "coordinates": "!B@FL"
      },
      "mass": 15.03455815890258,
      "monoisotopicMass": 15.02347509669,
      "unsaturation": -1,
      "elements": [{
        "symbol": "C",
        "number": 1
      }, {
        "symbol": "H",
        "number": 3
      }]
    }, {
      "symbol": "Mebzl",
      "name": "4-Methylbenzyl",
      "mf": "C8H9",
      "ocl": {
        "value": "did@`@VTee]nh@H@@",
        "coordinates": "!B|Gsp__A|_gp_C}_gp_P"
      },
      "mass": 105.15735396038399,
      "monoisotopicMass": 105.07042529007,
      "unsaturation": 7,
      "elements": [{
        "symbol": "C",
        "number": 8
      }, {
        "symbol": "H",
        "number": 9
      }]
    }, {
      "symbol": "Meobzl",
      "name": "4-Methoxybenzyl",
      "mf": "C8H9O",
      "ocl": {
        "value": "deTH`AAYRVUunh@J@@",
        "coordinates": "!B|Gsp__A|_gp_A}_gp_Wy"
      },
      "mass": 121.15675888470227,
      "monoisotopicMass": 121.06533990964,
      "unsaturation": 7,
      "elements": [{
        "symbol": "C",
        "number": 8
      }, {
        "symbol": "H",
        "number": 9
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Met",
      "name": "Methionine diradical",
      "mf": "C5H9NOS",
      "kind": "aa",
      "oneLetter": "M",
      "alternativeOneLetter": "μ",
      "ocl": {
        "value": "defHpBPOEgDPaInWYjjhr@@",
        "coordinates": "!Bb@I~@Ha}_c~H@m]}bGvHHa}"
      },
      "mass": 131.19604181206938,
      "monoisotopicMass": 131.04048508847,
      "unsaturation": 2,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 9
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Mmt",
      "name": "4-Methoxytrityl",
      "mf": "C20H17O",
      "ocl": {
        "value": "ffcAB@B`V\\bdTTTRRRVvIhnRGMT@@@@AP@@@",
        "coordinates": "!BbKvHM^}_c}~@Hb}dXWHb}j|nHHc|AqOWoWxJV^Ho]\\BuwvHHb}"
      },
      "mass": 273.3491156779715,
      "monoisotopicMass": 273.12794016748,
      "unsaturation": 23,
      "elements": [{
        "symbol": "C",
        "number": 20
      }, {
        "symbol": "H",
        "number": 17
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Mtc",
      "name": "2,2,5,7,8-pentamethylchroman-6-sulphonyl",
      "mf": "C14H19O3S",
      "ocl": {
        "value": "fleAa@DX\\AY`DYEHXhhilmiKW`rpDQUUD@@",
        "coordinates": "!BbGtBbGwWbGvHGxbGu~@Ha}uwu~Ox`B_c~H_Xa}b@H@_osW"
      },
      "mass": 267.36417906043516,
      "monoisotopicMass": 267.10549064548,
      "unsaturation": 9,
      "elements": [{
        "symbol": "C",
        "number": 14
      }, {
        "symbol": "H",
        "number": 19
      }, {
        "symbol": "O",
        "number": 3
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Mtr",
      "name": "4-Methoxy-2,3,6-trimethylbenzenesulphonyl",
      "mf": "C10H13O3S",
      "ocl": {
        "value": "do|LPDrpVXBLbdLTTTngYXBHj@@",
        "coordinates": "!BbOq}b@KWb@I~@Ha}bOsWHc|_c~H_Wx@b@JH_P"
      },
      "mass": 213.27359094915948,
      "monoisotopicMass": 213.05854045209998,
      "unsaturation": 7,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "O",
        "number": 3
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Mts",
      "name": "Mesitylene-2-sulphonyl",
      "mf": "C9H11O2S",
      "ocl": {
        "value": "d@"
      },
      "mass": 183.24756861999438,
      "monoisotopicMass": 183.04797576807,
      "unsaturation": 7,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "O",
        "number": 2
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Mtt",
      "name": "4-Methyltrityl",
      "mf": "C20H17",
      "ocl": {
        "value": "d@"
      },
      "mass": 257.3497107536532,
      "monoisotopicMass": 257.13302554791,
      "unsaturation": 23,
      "elements": [{
        "symbol": "C",
        "number": 20
      }, {
        "symbol": "H",
        "number": 17
      }]
    }, {
      "symbol": "Nle",
      "name": "Norleucine diradical",
      "mf": "C6H11NO",
      "ocl": {
        "value": "defHPBPOEgEInWYjjhr@@",
        "coordinates": "!Bb@I~@Ha}_c~H@m]}bGvHHa}"
      },
      "mass": 113.15787181078912,
      "monoisotopicMass": 113.08406397853,
      "unsaturation": 2,
      "elements": [{
        "symbol": "C",
        "number": 6
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Npys",
      "name": "3-Nitro-2-pyridinesulphenyl",
      "mf": "C5H3N2O2S",
      "ocl": {
        "value": "d@",
        "coordinates": ""
      },
      "mass": 155.1545054234988,
      "monoisotopicMass": 154.99152351908998,
      "unsaturation": 9,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 3
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 2
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Nva",
      "name": "Norvaline diradical",
      "mf": "C5H9NO",
      "ocl": {
        "value": "diFHPBPOEgEInWfjjL`@",
        "coordinates": "!BbGu~Ox`B_`BH_X`Bb@JH_P"
      },
      "mass": 99.13125440594231,
      "monoisotopicMass": 99.06841391407,
      "unsaturation": 2,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 9
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Odmab",
      "name": "Odmab",
      "mf": "C20H26NO3",
      "ocl": {
        "value": "d@"
      },
      "mass": 328.4260955245558,
      "monoisotopicMass": 328.19126870111995,
      "unsaturation": 15,
      "elements": [{
        "symbol": "C",
        "number": 20
      }, {
        "symbol": "H",
        "number": 26
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 3
      }]
    }, {
      "symbol": "Orn",
      "name": "Ornithine diradical",
      "mf": "C5H10N2O",
      "ocl": {
        "value": "deeHPBe@|V\\Tfy]fjjcH@",
        "coordinates": "!Bb@I~@Ha}_c~H@m]}bGvHHa}"
      },
      "mass": 114.14589837144388,
      "monoisotopicMass": 114.07931295072999,
      "unsaturation": 2,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 10
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Ornp",
      "name": "Ornithine triradical",
      "mf": "C5H9N2O",
      "ocl": {
        "value": "dmUHpHYPBQ\\Y|TeUejjjfJP@",
        "coordinates": "!BbGvHHa}b@JH_Wxb@KW_Wx@bGt"
      },
      "mass": 113.13795761738811,
      "monoisotopicMass": 113.0714879185,
      "unsaturation": 3,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 9
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Pbf",
      "name": "2,2,4,6,7-pentamethyldihydrobenzofurane-5-sulfonyl",
      "mf": "C13H17O3S",
      "ocl": {
        "value": "d@",
        "coordinates": ""
      },
      "mass": 253.33756165558833,
      "monoisotopicMass": 253.08984058101998,
      "unsaturation": 9,
      "elements": [{
        "symbol": "C",
        "number": 13
      }, {
        "symbol": "H",
        "number": 17
      }, {
        "symbol": "O",
        "number": 3
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Pen",
      "name": "Penicillamine diradical",
      "mf": "C5H9NOS",
      "ocl": {
        "value": "d@",
        "coordinates": ""
      },
      "mass": 131.19604181206938,
      "monoisotopicMass": 131.04048508847,
      "unsaturation": 2,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 9
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Penp",
      "name": "Penicillamine triradical",
      "mf": "C5H8NOS",
      "ocl": {
        "value": "d@",
        "coordinates": ""
      },
      "mass": 130.1881010580136,
      "monoisotopicMass": 130.03266005624,
      "unsaturation": 3,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 8
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Ph",
      "name": "Phenyl",
      "mf": "C6H5",
      "ocl": {
        "value": "gOpH@liLkW@@@@",
        "coordinates": "!B|Owp_Gy|OwpWy"
      },
      "mass": 77.10411915069038,
      "monoisotopicMass": 77.03912516115,
      "unsaturation": 7,
      "elements": [{
        "symbol": "C",
        "number": 6
      }, {
        "symbol": "H",
        "number": 5
      }]
    }, {
      "symbol": "Phe",
      "name": "Phenylalanine diradical",
      "mf": "C9H9NO",
      "kind": "aa",
      "oneLetter": "F",
      "alternativeOneLetter": "φ",
      "ocl": {
        "value": "dknHPBPOEgEInWe]NZj@@cH@",
        "coordinates": "!Bb@I~@Ha}_c~H@m]}bGvH@gxbGvH_Wx"
      },
      "mass": 147.1741979928833,
      "monoisotopicMass": 147.06841391407002,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 9
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Phepcl",
      "name": "4-Chlorophenylalanine diradical",
      "mf": "C9H8ClNO",
      "ocl": {
        "value": "dg^HpBPOEgFxaInWe_Sfj`@bL`@",
        "coordinates": "!BbOq~@Ha}_c~H@m]}bGvH@gxbGvH_WxbGt"
      },
      "mass": 181.6191948214355,
      "monoisotopicMass": 181.02944156384,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 8
      }, {
        "symbol": "Cl",
        "number": 1
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Pmc",
      "name": "2,2,5,7,8-Pentamethylchroman-6-sulphonyl",
      "mf": "C14H19O3S",
      "ocl": {
        "value": "d@",
        "coordinates": ""
      },
      "mass": 267.36417906043516,
      "monoisotopicMass": 267.10549064548,
      "unsaturation": 9,
      "elements": [{
        "symbol": "C",
        "number": 14
      }, {
        "symbol": "H",
        "number": 19
      }, {
        "symbol": "O",
        "number": 3
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Pro",
      "name": "Proline diradical",
      "mf": "C5H7NO",
      "kind": "aa",
      "oneLetter": "P",
      "alternativeOneLetter": "π",
      "ocl": {
        "value": "difHPBPOEgEInYxYjjhr@@",
        "coordinates": "!Bb@I~@Ha}tEJNwr[@UMo@FP"
      },
      "mass": 97.11537289783075,
      "monoisotopicMass": 97.05276384961,
      "unsaturation": 4,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 7
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Pyr",
      "name": "Pyroglutamine",
      "mf": "C5H5NO2",
      "ocl": {
        "value": "deVDPBRP|V\\TfygxYjjZL`@",
        "coordinates": "!Bb@I~@Ha}tEJNwr[@UMo@FXBN"
      },
      "mass": 111.09889631403748,
      "monoisotopicMass": 111.03202840472,
      "unsaturation": 6,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 5
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Sar",
      "name": "Sarcosine diradical",
      "mf": "C3H5NO",
      "ocl": {
        "value": "d@",
        "coordinates": ""
      },
      "mass": 71.07801959624871,
      "monoisotopicMass": 71.03711378515,
      "unsaturation": 2,
      "elements": [{
        "symbol": "C",
        "number": 3
      }, {
        "symbol": "H",
        "number": 5
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Ser",
      "name": "Serine diradical",
      "mf": "C3H5NO2",
      "kind": "aa",
      "oneLetter": "S",
      "alternativeOneLetter": "σ",
      "ocl": {
        "value": "dazDPBS`|V\\TfyYjjL`@",
        "coordinates": "!Bb@I~@Ha}_c~H@m]}bGt"
      },
      "mass": 87.07742452056698,
      "monoisotopicMass": 87.03202840472,
      "unsaturation": 2,
      "elements": [{
        "symbol": "C",
        "number": 3
      }, {
        "symbol": "H",
        "number": 5
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Serp",
      "name": "Serine triradical",
      "mf": "C3H4NO2",
      "ocl": {
        "value": "diFDpB`PBV\\^|TeYZjjXq@@",
        "coordinates": "!BbGvHHa}_c~HM]}_`BH_P"
      },
      "mass": 86.06948376651121,
      "monoisotopicMass": 86.02420337249,
      "unsaturation": 3,
      "elements": [{
        "symbol": "C",
        "number": 3
      }, {
        "symbol": "H",
        "number": 4
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Sta",
      "name": "Statine diradical",
      "mf": "C8H15NO2",
      "ocl": {
        "value": "d@"
      },
      "mass": 157.210511544801,
      "monoisotopicMass": 157.11027872702002,
      "unsaturation": 2,
      "elements": [{
        "symbol": "C",
        "number": 8
      }, {
        "symbol": "H",
        "number": 15
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Stap",
      "name": "Statine triradical",
      "mf": "C8H14NO2",
      "ocl": {
        "value": "d@"
      },
      "mass": 156.2025707907452,
      "monoisotopicMass": 156.10245369479,
      "unsaturation": 3,
      "elements": [{
        "symbol": "C",
        "number": 8
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Tacm",
      "name": "Trimethylacetamidomethyl",
      "mf": "C6H12NO",
      "ocl": {
        "value": "d@"
      },
      "mass": 114.16581256484488,
      "monoisotopicMass": 114.09188901076,
      "unsaturation": 1,
      "elements": [{
        "symbol": "C",
        "number": 6
      }, {
        "symbol": "H",
        "number": 12
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Tbdms",
      "name": "t-Butyldimethylsilyl",
      "mf": "C6H15Si",
      "ocl": {
        "value": "d@"
      },
      "mass": 115.2690253969541,
      "monoisotopicMass": 115.09430201810001,
      "unsaturation": -1,
      "elements": [{
        "symbol": "C",
        "number": 6
      }, {
        "symbol": "H",
        "number": 15
      }, {
        "symbol": "Si",
        "number": 1
      }]
    }, {
      "symbol": "Tbu",
      "name": "t-Butyl",
      "mf": "C4H9",
      "ocl": {
        "value": "d@"
      },
      "mass": 57.114410373442986,
      "monoisotopicMass": 57.07042529007,
      "unsaturation": -1,
      "elements": [{
        "symbol": "C",
        "number": 4
      }, {
        "symbol": "H",
        "number": 9
      }]
    }, {
      "symbol": "Tbuo",
      "name": "t-Butoxy",
      "mf": "C4H9O",
      "ocl": {
        "value": "d@"
      },
      "mass": 73.11381529776126,
      "monoisotopicMass": 73.06533990964,
      "unsaturation": -1,
      "elements": [{
        "symbol": "C",
        "number": 4
      }, {
        "symbol": "H",
        "number": 9
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Tbuthio",
      "name": "t-Butylthio",
      "mf": "C4H9S",
      "ocl": {
        "value": "d@"
      },
      "mass": 89.17919777957005,
      "monoisotopicMass": 89.04249646446999,
      "unsaturation": -1,
      "elements": [{
        "symbol": "C",
        "number": 4
      }, {
        "symbol": "H",
        "number": 9
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Tfa",
      "name": "Trifluoroacetyl",
      "mf": "C2F3O",
      "ocl": {
        "value": "d@"
      },
      "mass": 97.01608620597878,
      "monoisotopicMass": 96.99012410776,
      "unsaturation": 1,
      "elements": [{
        "symbol": "C",
        "number": 2
      }, {
        "symbol": "F",
        "number": 3
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Thr",
      "name": "Threonine diradical",
      "mf": "C4H7NO2",
      "kind": "aa",
      "oneLetter": "T",
      "alternativeOneLetter": "τ",
      "ocl": {
        "value": "d@"
      },
      "mass": 101.10404192541378,
      "monoisotopicMass": 101.04767846918,
      "unsaturation": 2,
      "elements": [{
        "symbol": "C",
        "number": 4
      }, {
        "symbol": "H",
        "number": 7
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Thrp",
      "name": "Threonine triradical",
      "mf": "C4H6NO2",
      "ocl": {
        "value": "d@"
      },
      "mass": 100.09610117135801,
      "monoisotopicMass": 100.03985343695001,
      "unsaturation": 3,
      "elements": [{
        "symbol": "C",
        "number": 4
      }, {
        "symbol": "H",
        "number": 6
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Tfsi",
      "name": "(Bis)(trifluoromethanesulfonyl)imide",
      "mf": "C2F6NO4S2",
      "ocl": {
        "value": "d@"
      },
      "mass": 280.1457884908235,
      "monoisotopicMass": 279.91729380789,
      "unsaturation": -1,
      "elements": [{
        "symbol": "C",
        "number": 2
      }, {
        "symbol": "F",
        "number": 6
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 4
      }, {
        "symbol": "S",
        "number": 2
      }]
    }, {
      "symbol": "Tips",
      "name": "Triisopropylsilyl",
      "mf": "C9H21Si",
      "ocl": {
        "value": "dmT@P@VX\\DffYjjjh@@",
        "coordinates": "!B_a@gHb\\]FBIuWxP^zi~KwxPFAt"
      },
      "mass": 157.34887761149452,
      "monoisotopicMass": 157.14125221148,
      "unsaturation": -1,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 21
      }, {
        "symbol": "Si",
        "number": 1
      }]
    }, {
      "symbol": "Tms",
      "name": "Trimethylsilyl",
      "mf": "C3H9Si",
      "ocl": {
        "value": "gJPD@lqpRZj`@",
        "coordinates": "!BbOq~@GxbGt"
      },
      "mass": 73.1891731824137,
      "monoisotopicMass": 73.04735182472,
      "unsaturation": -1,
      "elements": [{
        "symbol": "C",
        "number": 3
      }, {
        "symbol": "H",
        "number": 9
      }, {
        "symbol": "Si",
        "number": 1
      }]
    }, {
      "symbol": "Tos",
      "name": "Tosyl",
      "mf": "C7H7O2S",
      "ocl": {
        "value": "dmtDPDpEf@cHiCDeafV@B@@",
        "coordinates": "!B|Ou||Ovw|Gwp_Gy|GwpWy|Gt"
      },
      "mass": 155.1943338103008,
      "monoisotopicMass": 155.01667563914998,
      "unsaturation": 7,
      "elements": [{
        "symbol": "C",
        "number": 7
      }, {
        "symbol": "H",
        "number": 7
      }, {
        "symbol": "O",
        "number": 2
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Trp",
      "name": "Tryptophan diradical",
      "mf": "C11H10N2O",
      "kind": "aa",
      "oneLetter": "W",
      "alternativeOneLetter": "ω",
      "ocl": {
        "value": "f`qQA@BFPCqXxiMr|rnhsoSUTa@QCD@@",
        "coordinates": "!BbOq~@Ha}_c~H@m]}bGwPTh{_UMojXL@YpB[@Ini`"
      },
      "mass": 186.21031375185538,
      "monoisotopicMass": 186.07931295073,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 10
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Trpp",
      "name": "Tryptophan triradical",
      "mf": "C11H9N2O",
      "ocl": {
        "value": "fhiQC@HFB@I\\x~|TfYU_ebLDjhDHjibFd@",
        "coordinates": "!BTmA}bL@fUHR_Ihz@iVBeXHc|grZH_WxbOsW_Wx@bGt"
      },
      "mass": 185.20237299779959,
      "monoisotopicMass": 185.07148791850003,
      "unsaturation": 15,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 9
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Trt",
      "name": "Trityl",
      "mf": "C19H15",
      "ocl": {
        "value": "fbm@B@@KJSSLrjkyhnRGMT@@@@@@@@",
        "coordinates": "!BrHI~PGy_rMvW@l`BQCvWw\\bBAg}~PGy@]i}~W|c]cNwH`i_]_e|"
      },
      "mass": 243.32309334880637,
      "monoisotopicMass": 243.11737548345,
      "unsaturation": 23,
      "elements": [{
        "symbol": "C",
        "number": 19
      }, {
        "symbol": "H",
        "number": 15
      }]
    }, {
      "symbol": "Tyr",
      "name": "Tyrosine diradical",
      "mf": "C9H9NO2",
      "kind": "aa",
      "oneLetter": "Y",
      "alternativeOneLetter": "ψ",
      "ocl": {
        "value": "dg^DPBRp|V\\Tfy^U}NZj@BHr@@",
        "coordinates": "!BbOq~@Ha}_c~H@m]}bGvH@gxbGvH_WxbGt"
      },
      "mass": 163.1736029172016,
      "monoisotopicMass": 163.06332853364,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 9
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Tyrp",
      "name": "Tyrosine triradical",
      "mf": "C9H8NO2",
      "ocl": {
        "value": "do~DpEapBS\\[|Tee]YYnh@JjdbT@",
        "coordinates": "!B_`BHGx@bGvH@h`BbKvH@ha}_c~H@m]}_`BHoP"
      },
      "mass": 162.16566216314578,
      "monoisotopicMass": 162.05550350141,
      "unsaturation": 11,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 8
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Val",
      "name": "Valine",
      "mf": "C5H9NO",
      "kind": "aa",
      "oneLetter": "V",
      "alternativeOneLetter": "ν",
      "ocl": {
        "value": "diFHPBPOEgEInVfjjL`@",
        "coordinates": "!Bb@I~@Ha}_c~H@m]}_`BH_P"
      },
      "mass": 99.13125440594231,
      "monoisotopicMass": 99.06841391407,
      "unsaturation": 2,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 9
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Valoh",
      "name": "beta-Hydroxyvaline diradical",
      "mf": "C5H9NO2",
      "ocl": {
        "value": "defDPBS`|V\\TfyZfjjcH@",
        "coordinates": "!Bb@I~@Ha}b@I~Oxa}Owy~OpA~"
      },
      "mass": 115.13065933026058,
      "monoisotopicMass": 115.06332853364,
      "unsaturation": 2,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 9
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Valohp",
      "name": "beta-Hydroxyvaline triradical",
      "mf": "C5H8NO2",
      "ocl": {
        "value": "dmVDpFaPBQ\\Y|\\bTbaTjjjXq@@",
        "coordinates": "!BbGvHHa}_Xc|bGxb@KW_Wx@bGt"
      },
      "mass": 114.1227185762048,
      "monoisotopicMass": 114.05550350141002,
      "unsaturation": 3,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 8
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Xan",
      "name": "Xanthyl",
      "mf": "C13H9O",
      "ocl": {
        "value": "d@",
        "coordinates": ""
      },
      "mass": 181.21043836837848,
      "monoisotopicMass": 181.06533990964002,
      "unsaturation": 17,
      "elements": [{
        "symbol": "C",
        "number": 13
      }, {
        "symbol": "H",
        "number": 9
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Xle",
      "name": "Leucine or Isoleucine diradical",
      "mf": "C6H11NO",
      "kind": "aa",
      "oneLetter": "J",
      "mass": 113.15787181078912,
      "monoisotopicMass": 113.08406397853,
      "unsaturation": 2,
      "elements": [{
        "symbol": "C",
        "number": 6
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Z",
      "name": "Benzyloxycarbonyl",
      "mf": "C8H7O2",
      "ocl": {
        "value": "dmtD`DpEeImYVUfh@@@@",
        "coordinates": "!Bb@I~@Ha}b@JH_Xc|_c~H_Xa}_c|"
      },
      "mass": 135.14028230090898,
      "monoisotopicMass": 135.04460446475,
      "unsaturation": 9,
      "elements": [{
        "symbol": "C",
        "number": 8
      }, {
        "symbol": "H",
        "number": 7
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Damp",
      "name": "Desoxyadenosine monophosphate diradical",
      "mf": "C10H12N5O5P",
      "kind": "DNAp",
      "oneLetter": "A",
      "alternativeOneLetter": "α",
      "ocl": {
        "value": "fnsiS@IASUlJB]xGbkplxyDhhldhiEEUeSdTekUUUULBATXPlKd@@",
        "coordinates": "!Bqc}{JxyO|XoSWC}W]poGQ\\Ou}]rmx\\Ou}]{qpza|qb}MJwlk^sFO|X"
      },
      "mass": 313.2069506932622,
      "monoisotopicMass": 313.05760550518,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 12
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 5
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Dcmp",
      "name": "Desoxycytidine monophosphate diradical",
      "mf": "C9H12N3O6P",
      "kind": "DNAp",
      "oneLetter": "C",
      "alternativeOneLetter": "ς",
      "ocl": {
        "value": "fjmps@IQKB`g^BCqUxV\\\\bTTVRTTbb^iqNZjjjifVkBEa\\`@",
        "coordinates": "!Bqc}{JxyO|XoSWA}_W]poGQ\\GuMKuMh\\Gu}]{qpSF]tWQTvatP"
      },
      "mass": 289.18221329795364,
      "monoisotopicMass": 289.04637211589,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 12
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 6
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Dgmp",
      "name": "Desoxyguanosine monophosphate diradical",
      "mf": "C10H12N5O6P",
      "kind": "DNAp",
      "oneLetter": "G",
      "alternativeOneLetter": "γ",
      "ocl": {
        "value": "fakhs@IASUlJB]{hOEWaYqrIQQYIQRJJkQTyEIZuUUUSRtsUaBpnP@",
        "coordinates": "!Bqc}{JxyO|XoSWA}W]poGQ\\Gu}]rmx\\Ou}]{qpza|qb}MJwlk^sFza|q`"
      },
      "mass": 329.20635561758047,
      "monoisotopicMass": 329.05252012475,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 12
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 6
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Dtmp",
      "name": "Desoxythymidine monophosphate diradical",
      "mf": "C10H13N2O7P",
      "kind": "DNAp",
      "oneLetter": "T",
      "alternativeOneLetter": "τ",
      "ocl": {
        "value": "ff}Qs@IQaPSoAjCqUxV\\\\bTTVRTTbbZUNIsUUUULsSVDKBy@@",
        "coordinates": "!Bqc}{JxyO|XoSWC}_W]poGQ\\GuMKuMh\\Gu}]{qpSF]tWQTvaSZGQ"
      },
      "mass": 304.1935916616171,
      "monoisotopicMass": 304.04603776326,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Dump",
      "name": "Desoxyuridine monophosphate diradical",
      "mf": "C9H11N2O7P",
      "kind": "DNAp",
      "oneLetter": "U",
      "alternativeOneLetter": "υ",
      "ocl": {
        "value": "fjmQs@IQaPSoAJCqUxV\\\\bTTVRTTbb^iqNZjjjifYkBEa\\`@",
        "coordinates": "!Bqc}{JxyO|XoSWA}_W]poGQ\\GuMKuMh\\Gu}]{qpSF]tWQTvatP"
      },
      "mass": 290.1669742567703,
      "monoisotopicMass": 290.0303876988,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Drmp",
      "name": "Desoxyribose monophosphate diradical",
      "mf": "C5H7O5P",
      "kind": "DNAp",
      "ocl": {
        "value": "d@",
        "coordinates": ""
      },
      "mass": 178.08005138207807,
      "monoisotopicMass": 178.00311032188,
      "unsaturation": 4,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 7
      }, {
        "symbol": "O",
        "number": 5
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Dadp",
      "name": "Desoxyadenosine diphosphate diradical",
      "mf": "C10H13N5O8P2",
      "kind": "DNApp",
      "oneLetter": "A",
      "ocl": {
        "value": "fmwhH`IASM\\JBl{wQ`|U^F_AkbdlsjsSOoRtyEMYuUUUM@pSEQaBpnP@",
        "coordinates": "!BIi[Rx{_grZOSXa}_]^H@mQbGu}utnDM^HGwWzf~_Ih}M_`AKvto[_`@_`A~grZ_I`"
      },
      "mass": 393.1868682186928,
      "monoisotopicMass": 393.02393639454,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 2
      }]
    }, {
      "symbol": "Dcdp",
      "name": "Desoxycytidine diphosphate diradical",
      "mf": "C9H13N3O9P2",
      "kind": "DNApp",
      "oneLetter": "C",
      "ocl": {
        "value": "fikqH`IQGB`kN|EoP^JoCOaUqrIQQYIQRJKGRJgDejjjjZYfZkBEa\\`@",
        "coordinates": "!BIi[Rx{_grZOSXa}_]^H@mQbGuMcqLX@m^H@gwWKB__t]Q_`@SFGx@Owx@_mQ"
      },
      "mass": 369.16213082338425,
      "monoisotopicMass": 369.01270300525005,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 9
      }, {
        "symbol": "P",
        "number": 2
      }]
    }, {
      "symbol": "Dgdp",
      "name": "Desoxyguanosine diphosphate diradical",
      "mf": "C10H13N5O9P2",
      "kind": "DNApp",
      "oneLetter": "G",
      "ocl": {
        "value": "fcoiH`IASM\\JBl{wQ{Axj|L~CWEIYgUff_^fZ\\bflzjjjfiZifZlHVEr@@",
        "coordinates": "!BIi[Rx{_grZOSXa}_]^H@mQbGu}utnD@m^H@gwWzf~_Ih}M_`AKvto[_`@_`A~gr[j[y|f"
      },
      "mass": 409.186273143011,
      "monoisotopicMass": 409.01885101411,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 9
      }, {
        "symbol": "P",
        "number": 2
      }]
    }, {
      "symbol": "Dtdp",
      "name": "Desoxythymidine diphosphate diradical",
      "mf": "C10H14N2O10P2",
      "kind": "DNApp",
      "oneLetter": "T",
      "ocl": {
        "value": "fe{Ph`IQaPUg^Ct\\p^JoCO`uqrIQQYIQRJKEJQTxdmUUUSSMTsVDKBy@@",
        "coordinates": "!BIi[Rx{_grZOSXa}_]^HMQbGuMcqLX@m^H@gwWKB__t]Q_`@SFALX_`@_`A~w}D"
      },
      "mass": 384.1735091870477,
      "monoisotopicMass": 384.01236865262,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 2
      }]
    }, {
      "symbol": "Dudp",
      "name": "Desoxyuridine diphosphate diradical",
      "mf": "C9H12N2O10P2",
      "kind": "DNApp",
      "oneLetter": "U",
      "ocl": {
        "value": "fikPh`IQaPUg^Bwhp^JoCOaUqrIQQYIQRJKGRJgDejjjjZYjYkBEa\\`@",
        "coordinates": "!BIi[Rx{_grZOSXa}_]^H@mQbGuMcqLX@m^H@gwWKB__t]Q_`@SFGx@Owx@_mQ"
      },
      "mass": 370.1468917822009,
      "monoisotopicMass": 369.99671858816,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 12
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 2
      }]
    }, {
      "symbol": "Datp",
      "name": "Desoxyadenosine triphosphate diradical",
      "mf": "C10H14N5O11P3",
      "kind": "DNAppp",
      "oneLetter": "A",
      "ocl": {
        "value": "eohZMJ@I@diehJAKGOFnakg`OESpr|Mo@yqrIQQYIQRJKYZQKVRcbIJjZjjjihFAhjZcAAXKb@@",
        "coordinates": "!BIi[Rx{_grZOSXa}_]^H@mQbGu}utnDM^H@gwWzf~_Ih}M_`AKvto[@hcW@`A~grZ_Igx@_`@@_c}~"
      },
      "mass": 473.16678574412344,
      "monoisotopicMass": 472.9902672839,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 11
      }, {
        "symbol": "P",
        "number": 3
      }]
    }, {
      "symbol": "Dctp",
      "name": "Desoxycytidine triphosphate diradical",
      "mf": "C9H14N3O12P3",
      "kind": "DNAppp",
      "oneLetter": "C",
      "ocl": {
        "value": "fkopZ`IQGB`kN|Fk^{NCqUxY|I~BwGHeEEdeEHhl]HlYJ\\RVjjjiifVjfkBEa\\`@",
        "coordinates": "!BIi[Rx{_grZOSXa}_]^H@mQbGuMcqLX@m^H@gwWKB__t]Q_`@SFOrHupH@_mQ_`A~@@A~Owx"
      },
      "mass": 449.14204834881485,
      "monoisotopicMass": 448.97903389461004,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 12
      }, {
        "symbol": "P",
        "number": 3
      }]
    }, {
      "symbol": "Dgtp",
      "name": "Desoxyguanosine triphosphate diradical",
      "mf": "C10H14N5O12P3",
      "kind": "DNAppp",
      "oneLetter": "G",
      "ocl": {
        "value": "e`TZCJ@I@diehJAKGOFnamgo`OESpr|CoByqrIQQYIQRJKYZQQYrT\\QIUSUUUUMRuMLtuVBBpWD@@",
        "coordinates": "!BIi[Rx{_grZOSXa}_]^H@mQbGu}utnD@m^H@gwWzf~_Ih}M_`AKvto[@hcW@`A~gr[j[y|f_`A~@@A~Owx"
      },
      "mass": 489.16619066844174,
      "monoisotopicMass": 488.98518190347005,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 12
      }, {
        "symbol": "P",
        "number": 3
      }]
    }, {
      "symbol": "Dttp",
      "name": "Desoxythymidine triphosphate diradical",
      "mf": "C10H15N2O13P3",
      "kind": "DNAppp",
      "oneLetter": "T",
      "ocl": {
        "value": "fgQZ`IQaPUg^BwhygnCqUxY|E~FwGHeEEdeEHhlTiDSISbRuUUUMLuMMMVDKBy@@",
        "coordinates": "!BIi[Rx{_grZOSXa}_]^H@mQbGuMcqLX@m^H@gwWKB__t]Q_`@SFALXHcW@`A~w}E~@Gx@@Gx_`"
      },
      "mass": 464.15342671247834,
      "monoisotopicMass": 463.97869954198,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 15
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 13
      }, {
        "symbol": "P",
        "number": 3
      }]
    }, {
      "symbol": "Dutp",
      "name": "Desoxyuridine triphosphate diradical",
      "mf": "C9H13N2O13P3",
      "kind": "DNAppp",
      "oneLetter": "U",
      "ocl": {
        "value": "fkoQZ`IQaPUg^CUoQ{NCqUxY|I~BwGHeEEdeEHhl]HlYJ\\RVjjjiiffffkBEa\\`@",
        "coordinates": "!BIi[Rx{_grZOSXa}_]^H@mQbGuMcqLX@m^H@gwWKB__t]Q_`@SFOrHupH@_mQ_`A~@@A~Owx"
      },
      "mass": 450.1268093076315,
      "monoisotopicMass": 449.96304947752,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 13
      }, {
        "symbol": "P",
        "number": 3
      }]
    }, {
      "symbol": "Dade",
      "name": "Desoxyadenosine diradical",
      "mf": "C10H11N5O2",
      "kind": "DNA",
      "oneLetter": "A",
      "ocl": {
        "value": "fluha@IF]ELJ@|QNJRsN|rntyYpXuUUTBATXPlKd@@",
        "coordinates": "!B\\KqpQARcg|T^|X@@Id`zeHo@Ie}]vaLcg|T^qAMDDvN_xy"
      },
      "mass": 233.22703316783156,
      "monoisotopicMass": 233.09127461582,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Dcyt",
      "name": "Desoxycytidine diradical",
      "mf": "C9H11N3O3",
      "kind": "DNA",
      "oneLetter": "C",
      "ocl": {
        "value": "fhiqa@IVCBa`^HgEIYg^Y~gG^jjjiejpaXWH@",
        "coordinates": "!BBOpH_UARcc}TN|Y@PIe`zeIO@MDSIrpXTd}RSqLgTd|"
      },
      "mass": 209.202295772523,
      "monoisotopicMass": 209.08004122653,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 3
      }]
    }, {
      "symbol": "Dgua",
      "name": "Desoxyguanosine diradical",
      "mf": "C10H11N5O3",
      "kind": "DNA",
      "oneLetter": "G",
      "ocl": {
        "value": "fbmia@IF]ELJYAxb\\Tef]ye^Z\\lxLZjjjeZfkBEa\\`@",
        "coordinates": "!B\\KqpQARcg|T^|X@@Id`zeHo@Ie}]vaLcg|T^qAMDDvN_vaLcg|"
      },
      "mass": 249.22643809214986,
      "monoisotopicMass": 249.08618923539,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 3
      }]
    }, {
      "symbol": "Dthy",
      "name": "Desoxythymidine diradical",
      "mf": "C10H12N2O4",
      "kind": "DNA",
      "oneLetter": "T",
      "ocl": {
        "value": "fdyPQ@IVaPtP^HgEIYg^YuiqwjjjjYikBEa\\`@",
        "coordinates": "!BBOpH_UARcc}TN|Y@PIe`zeIO@MDSIrpXTd}RSqLgDr]RSp"
      },
      "mass": 224.2136741361865,
      "monoisotopicMass": 224.07970687390002,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 12
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 4
      }]
    }, {
      "symbol": "Dura",
      "name": "Desoxyuridine diradical",
      "mf": "C9H10N2O4",
      "kind": "DNA",
      "oneLetter": "U",
      "ocl": {
        "value": "fhiPQ@IVaPpP^HgEIYg^Y~gG^jjjifZpaXWH@",
        "coordinates": "!BBOpH_UARcc}TN|Y@PIe`zeIO@MDSIrpXTd}RSqLgTd|"
      },
      "mass": 210.1870567313397,
      "monoisotopicMass": 210.06405680944,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 10
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 4
      }]
    }, {
      "symbol": "Amp",
      "name": "Adenosine monophosphate diradical",
      "mf": "C10H12N5O6P",
      "kind": "RNAp",
      "oneLetter": "A",
      "alternativeOneLetter": "α",
      "ocl": {
        "value": "fakhs@INBwlJ\\TgHOFwaEqrIQQSYQJIRIMLyxMVuUUUPLpEPQDqBId@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpPH_Wtw@aOTd}RtPCQ@@"
      },
      "mass": 329.20635561758047,
      "monoisotopicMass": 329.05252012475,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 12
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 6
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Cmp",
      "name": "Cytidine monophosphate diradical",
      "mf": "C9H12N3O7P",
      "kind": "RNAp",
      "oneLetter": "C",
      "alternativeOneLetter": "ς",
      "ocl": {
        "value": "ff}qs@I^kBgENSdGc[pbxyDhhilheDiLv\\BVjjjjYfZbHfHQL`@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFDuP"
      },
      "mass": 305.1816182222719,
      "monoisotopicMass": 305.04128673546,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 12
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Gmp",
      "name": "Guanosine monophosphate diradical",
      "mf": "C10H12N5O7P",
      "kind": "RNAp",
      "oneLetter": "G",
      "alternativeOneLetter": "γ",
      "ocl": {
        "value": "fi{is@INBwlJ\\TgHp^MoBKcdRbbfrbTRdR\\SN^CUmUUUUKMSMQDSDHfP@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpPH_Wtw@aOTd}RtP@gD}D@"
      },
      "mass": 345.20576054189877,
      "monoisotopicMass": 345.04743474432,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 12
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Tmp",
      "name": "Thymidine monophosphate diradical",
      "mf": "C10H13N2O8P",
      "kind": "RNAp",
      "oneLetter": "T",
      "alternativeOneLetter": "τ",
      "ocl": {
        "value": "fncPK@I^aSbgIrtGc[pbxyDhhilheDiLjs`RuUUUSLuMDQLPbY@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFBpXSU@"
      },
      "mass": 320.1929965859354,
      "monoisotopicMass": 320.04095238282997,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Ump",
      "name": "Uridine monophosphate diradical",
      "mf": "C9H11N2O8P",
      "kind": "RNAp",
      "oneLetter": "U",
      "alternativeOneLetter": "υ",
      "ocl": {
        "value": "ff}PK@I^aSbgIsTGc[pbxyDhhilheDiLv\\BVjjjjYffbHfHQL`@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFDuP"
      },
      "mass": 306.1663791810886,
      "monoisotopicMass": 306.02530231837,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Rmp",
      "name": "Ribose monophosphate diradical",
      "mf": "C5H7O6P",
      "kind": "RNAp",
      "ocl": {
        "value": "d@",
        "coordinates": ""
      },
      "mass": 194.07945630639637,
      "monoisotopicMass": 193.99802494145,
      "unsaturation": 4,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 7
      }, {
        "symbol": "O",
        "number": 6
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Adp",
      "name": "Adenosine diphosphate diradical",
      "mf": "C10H13N5O9P2",
      "kind": "RNApp",
      "oneLetter": "A",
      "ocl": {
        "value": "fcoiH`INCt\\J\\UENU{Axv|F~DwGHeEEMeDheHd\\eHsg`u{UUUU@mAEMPQDqBId@@",
        "coordinates": "!BvuPfpDnDtEK_t_rHtXBH_TwPbOr_IorHbGtgD}F@RxPBuxc|_]^OTh}R_`CQ`MF@_`@_`A~"
      },
      "mass": 409.186273143011,
      "monoisotopicMass": 409.01885101411,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 9
      }, {
        "symbol": "P",
        "number": 2
      }]
    }, {
      "symbol": "Cdp",
      "name": "Cytidine diphosphate diradical",
      "mf": "C9H13N3O10P2",
      "kind": "RNApp",
      "oneLetter": "C",
      "ocl": {
        "value": "fe{ph`I^[BgENbgHy`|[^E_CkcdRbbfrbTRdqrdYpIZjjjieijZbHfHQL`@",
        "coordinates": "!BvuPfpDnDtEK_t_rHtXBH_TwPb@K_cbpXbKtSItwPS]@Bux`Bo]\\lFGx@S]A~@C}~@Gx"
      },
      "mass": 385.1615357477025,
      "monoisotopicMass": 385.00761762482,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 2
      }]
    }, {
      "symbol": "Gdp",
      "name": "Guanosine diphosphate diradical",
      "mf": "C10H13N5O10P2",
      "kind": "RNApp",
      "oneLetter": "G",
      "ocl": {
        "value": "fkhh`INCt\\J\\UENY{NCqmxM|EnNQJJJ[JIQJQHzIRLyxM^uUUUTkUSLuQDSDHfP@",
        "coordinates": "!BvuPfpDnDtEK_tPJHtXBH_TwPb@J_I`JHbGtgD}F@RxPBux`B_]^OTh}R_`CQ`B\\StXA~@C}~@Gx"
      },
      "mass": 425.1856780673293,
      "monoisotopicMass": 425.01376563368,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 2
      }]
    }, {
      "symbol": "Tdp",
      "name": "Thymidine diphosphate diradical",
      "mf": "C10H14N2O11P2",
      "kind": "RNApp",
      "oneLetter": "T",
      "ocl": {
        "value": "fmgQh`I^aSbgQSglu`|[^C_@[bdls^rruo}LxDmUUUTruTsTQDqBId@@",
        "coordinates": "!BvuPfpDnDtEK_tPJHtXBH_TwPbOs_cbpXbGtSItwPS]@Bux`B_]\\lFBpX_`AMtGx@Owx@_`"
      },
      "mass": 400.172914111366,
      "monoisotopicMass": 400.00728327219,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 11
      }, {
        "symbol": "P",
        "number": 2
      }]
    }, {
      "symbol": "Udp",
      "name": "Uridine diphosphate diradical",
      "mf": "C9H12N2O11P2",
      "kind": "RNApp",
      "oneLetter": "U",
      "ocl": {
        "value": "fe{Qh`I^aSbgQSehy`|[^E_CkcdRbbfrbTRdqrdYpIZjjjiejfZbHfHQL`@",
        "coordinates": "!BvuPfpDnDtEK_t_rHtXBH_TwPb@K_cbpXbKtSItwPS]@Bux`Bo]\\lFGx@S]A~@C}~@Gx"
      },
      "mass": 386.14629670651925,
      "monoisotopicMass": 385.99163320773005,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 12
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 11
      }, {
        "symbol": "P",
        "number": 2
      }]
    }, {
      "symbol": "Atp",
      "name": "Adenosine triphosphate diradical",
      "mf": "C10H14N5O12P3",
      "kind": "RNAppp",
      "oneLetter": "A",
      "ocl": {
        "value": "e`TZCJ@IG@nahJNEHdliemgo`OFspZ|CoByqrIQQSYQJIRIGIRWRL\\^AU]UUUUPKPQMTuABDpaBX`@",
        "coordinates": "!BvuPfpDnDtEK_t_rHtXBH_TwPbOr_IorHbGtgD}F@RxS|uxc|_]^OTh}R_`CQ`MF@@hcW@A~_`A~@@A~Owx"
      },
      "mass": 489.16619066844174,
      "monoisotopicMass": 488.98518190347005,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 12
      }, {
        "symbol": "P",
        "number": 3
      }]
    }, {
      "symbol": "Ctp",
      "name": "Cytidine triphosphate diradical",
      "mf": "C9H14N3O13P3",
      "kind": "RNAppp",
      "oneLetter": "C",
      "ocl": {
        "value": "fgqZ`I^[BgENbgOQsO\\Gc[pkxK|MnNQJJJ[JIQJSGJPzQg@ejjjjfVffjZbHfHQL`@",
        "coordinates": "!BvuPfpDnDtEK_tPJHtXBH_TwPb@K_cbpXbGtSItwPS]C|ux`B_]\\lFGx@S]@BbM\\B@Gy~@Gx@@Gx_`"
      },
      "mass": 465.1414532731331,
      "monoisotopicMass": 464.97394851418,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 13
      }, {
        "symbol": "P",
        "number": 3
      }]
    }, {
      "symbol": "Gtp",
      "name": "Guanosine triphosphate diradical",
      "mf": "C10H14N5O13P3",
      "kind": "RNAppp",
      "oneLetter": "G",
      "ocl": {
        "value": "eh\\ZKJ@IG@nahJNEHdliemco`POFspZ|KoAyqrIQQSYQJIRIGQJQzQccpJkjjjjjeZjYZijbDIaBDq@@",
        "coordinates": "!BvuPfpDnDtEK_tPJHtXBH_TwPb@J_I`JHbGtgD}F@RxPBux`B_]^OTh}R_`CQ`B\\StX@BbM_|@Gy~@Gx@@Gx_`"
      },
      "mass": 505.16559559276,
      "monoisotopicMass": 504.98009652304,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 13
      }, {
        "symbol": "P",
        "number": 3
      }]
    }, {
      "symbol": "Ttp",
      "name": "Thymidine triphosphate diradical",
      "mf": "C10H15N2O14P3",
      "kind": "RNAppp",
      "oneLetter": "T",
      "ocl": {
        "value": "eo`TGJ@IOHJNEGHdlnaiekg`OFspZ|Mo@yqrIQQSYQJIRY[ZPzQc`HjjjjjYZjVjZbDIaBDq@@",
        "coordinates": "!BvuPfpDnDtEK_tPJHtXBH_TwPb@K_cbpXbGtSItwPS]@Bux`B_]\\lFBpX_`AMt@JHupH@_gx@_`@@_c}~"
      },
      "mass": 480.15283163679663,
      "monoisotopicMass": 479.97361416155,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 15
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 14
      }, {
        "symbol": "P",
        "number": 3
      }]
    }, {
      "symbol": "Utp",
      "name": "Uridine triphosphate diradical",
      "mf": "C9H13N2O14P3",
      "kind": "RNAppp",
      "oneLetter": "U",
      "ocl": {
        "value": "fgPz`I^aSbgQSeoQsO\\Gc[pkxK|MnNQJJJ[JIQJSGJPzQg@ejjjjfVjVjZbHfHQL`@",
        "coordinates": "!BvuPfpDnDtEK_tPJHtXBH_TwPb@K_cbpXbGtSItwPS]C|ux`B_]\\lFGx@S]@BbM\\B@Gy~@Gx@@Gx_`"
      },
      "mass": 466.12621423194986,
      "monoisotopicMass": 465.95796409709004,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 14
      }, {
        "symbol": "P",
        "number": 3
      }]
    }, {
      "symbol": "Ade",
      "name": "Adenosine diradical",
      "mf": "C10H11N5O3",
      "kind": "RNA",
      "oneLetter": "A",
      "ocl": {
        "value": "fbmia@IV|gLJ\\Axj\\Tef[vyWV\\]zJZjjj`PJ`bIbDSH@",
        "coordinates": "!BBOpH_UARccFPEP{PId{RpBN[~i|BEP{iVA@fUARU@QTADBYPId"
      },
      "mass": 249.22643809214986,
      "monoisotopicMass": 249.08618923539,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 3
      }]
    }, {
      "symbol": "Cyt",
      "name": "Cytidine diradical",
      "mf": "C9H11N3O4",
      "kind": "RNA",
      "oneLetter": "C",
      "ocl": {
        "value": "fdypQ@INcBgK@|UNJRsM{\\~sg`uUUULmQDSDHfP@",
        "coordinates": "!BBOpH_UARccFPEP{PId{RpBN[~iRTBpgDq`@c`BNKB\\@c`"
      },
      "mass": 225.20170069684127,
      "monoisotopicMass": 225.0749558461,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 4
      }]
    }, {
      "symbol": "Gua",
      "name": "Guanosine diradical",
      "mf": "C10H11N5O4",
      "kind": "RNA",
      "oneLetter": "G",
      "ocl": {
        "value": "fj}hQ@IV|gLJ\\JCqTxiKLwmroKNN}EMUUUTkTuDQLPbY@@",
        "coordinates": "!BBOpH_UARccFPEP{PId{RpBN[~k|BEP{iVA@fUARU@QTADBYiVA@fP"
      },
      "mass": 265.22584301646816,
      "monoisotopicMass": 265.08110385496,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 4
      }]
    }, {
      "symbol": "Thy",
      "name": "Thymidine diradical",
      "mf": "C10H12N2O5",
      "kind": "RNA",
      "oneLetter": "T",
      "ocl": {
        "value": "fleQQ@INaSed`|UNJRsM{\\zlyxMUUUSMMDQLPbY@@",
        "coordinates": "!BBOpH_UARccFPEP{PId{RpBN[~iRTBpgDq`@c`BNKB\\lIpBN"
      },
      "mass": 240.21307906050478,
      "monoisotopicMass": 240.07462149347,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 12
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 5
      }]
    }, {
      "symbol": "Ura",
      "name": "Uridine diradical",
      "mf": "C9H10N2O5",
      "kind": "RNA",
      "oneLetter": "U",
      "ocl": {
        "value": "fdyQQ@INaSeh`|UNJRsM{\\~sg`uUUULsQDSDHfP@",
        "coordinates": "!BBOpH_UARccFPEP{PId{RpBN[~iRTBpgDq`@c`BNKB\\@c`"
      },
      "mass": 226.18646165565798,
      "monoisotopicMass": 226.05897142901,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 10
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 5
      }]
    }, {
      "symbol": "Dam",
      "name": "1,2′-O-dimethyladenosine monophosphate diradical 01A",
      "mf": "C12H16N5O6P",
      "kind": "NucleotideP",
      "oneLetter": "œ",
      "ocl": {
        "value": "feghs@E^ct\\J\\udhOEw`eqrIQQQKZIQJQIiLxFK^uUUUUKLtuQDSDHfP@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuOSU@HEF@`H_R\\StPAKA@a}_S_|BD}RSuKQ@MD@SuH"
      },
      "mass": 357.2595904272741,
      "monoisotopicMass": 357.08382025367,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 16
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 6
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Dgm",
      "name": "1,2′-O-dimethylguanosine monophosphate diradical 01G",
      "mf": "C12H16N5O7P",
      "kind": "NucleotideP",
      "oneLetter": "ε",
      "ocl": {
        "value": "fmwis@E^ct\\J\\udlp^KoAKcdRbbbVtRbTbSbSNAbwmUUUURsMSUDQLPbY@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuOSU@HEF@`H_R\\StPAKA@a}_S_|BD}RSuKQ@B\\StPAOT`"
      },
      "mass": 373.2589953515923,
      "monoisotopicMass": 373.07873487324,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 16
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Dim",
      "name": "1,2′-O-dimethylinosine monophosphate diradical 019A",
      "mf": "C12O7N4H15P",
      "kind": "NucleotideP",
      "oneLetter": "ξ",
      "ocl": {
        "value": "fegIs@E^cvENZrTXOEw`eqrIQQQKZIQJQIiLxFK^uUUUUKLtuQDSDHfP@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuOSU@HEF@`H_R\\StPAKA@a}_S_|BD}RSuKQ@MD@SuH"
      },
      "mass": 358.2443513860907,
      "monoisotopicMass": 358.06783583658,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 15
      }, {
        "symbol": "N",
        "number": 4
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Tia",
      "name": "2- methylthiomethylenethio-N6-isopentenyl-adenosine monophosphate diradical",
      "mf": "C17H24N5O6PS2",
      "kind": "NucleotideP",
      "oneLetter": "£",
      "ocl": {
        "value": "eh\\ZFJ@IG@nahJNEDl`OFspb\\V`cXHrIQQSYQJIRINIYIKQccpJkjjjjjAfBJjfjBDIaBDq@@",
        "coordinates": "!BpBYTvxBNFY|bEJObGvOS\\@Yt]~DUEJOctu~@Ha}`HzOSTwPTh~H@hc|_`BH_Xa}b@JH@gx@bGvH@h`B_`BH_P"
      },
      "mass": 489.50637075565066,
      "monoisotopicMass": 489.09056286031,
      "unsaturation": 16,
      "elements": [{
        "symbol": "C",
        "number": 17
      }, {
        "symbol": "H",
        "number": 24
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 6
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 2
      }]
    }, {
      "symbol": "Mhc",
      "name": "2′‐O‐Methyl-5-hydroxymethylcytidine monophosphate diradical",
      "mf": "C11H16N3O8P",
      "kind": "NucleotideP",
      "oneLetter": "¡",
      "ocl": {
        "value": "fikpK@EA{BgM^rTXOEw`eqrIQQQKZIQJSJigHujjjjifYjkBHQL`@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@HoTuOSU@HC~NKA`HoQLgSUAMT@a}oS_|BBpXKAaMT@CQ"
      },
      "mass": 349.2342579562838,
      "monoisotopicMass": 349.06750148395,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 16
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Odg",
      "name": "N2,2′-O-dimethylguanosine monophosphate diradical 02G",
      "mf": "C12H16N5O7P",
      "kind": "NucleotideP",
      "oneLetter": "γ",
      "ocl": {
        "value": "fmwis@E^ct\\J\\udlp^KoAKcdRbbbVtRbTbSbsNAbwmUUUURsMSUDQLPbY@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuOSU@HEF@`H_R\\StPAKA@a}_S_|BD}RSuKQ@B\\StPAOT`"
      },
      "mass": 373.2589953515923,
      "monoisotopicMass": 373.07873487324,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 16
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Ntg",
      "name": "N2,N2,2′-O-trimethylguanosine monophosphate diradical 022G",
      "mf": "C13H18N5O7P",
      "kind": "NucleotideP",
      "oneLetter": "|",
      "ocl": {
        "value": "fcois@E^ct\\J\\udlp^KoAKcdRbbbVtRbTbSbTYpLVcjjjjjVYjZjHbXaDr@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuOSU@HEF@`H_R\\StPAKA@a}_S_|BD}RSuHgD}D@tPBNOt}R"
      },
      "mass": 387.2856127564392,
      "monoisotopicMass": 387.0943849377,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 13
      }, {
        "symbol": "H",
        "number": 18
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Otg",
      "name": "N2,7,2′-O-trimethylguanosine monophosphate diradical 027G",
      "mf": "C13H20N5O7P",
      "kind": "NucleotideP",
      "oneLetter": "æ",
      "ocl": {
        "value": "fcoisBE^bN\\J\\udjp^KoAKcFU}dRbbbVtRbTbRlQYpLVcjjjjjVYjjjHbXaDr@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuOSU@HEF@`H_R\\StPAKA@a}_S_|BD}RSuKFPMD@IqOQ@D}R"
      },
      "mass": 389.30149426455074,
      "monoisotopicMass": 389.11003500216,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 13
      }, {
        "symbol": "H",
        "number": 20
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Rya",
      "name": "2′-O-ribosyladenosine monophosphate diradical 00A",
      "mf": "C15H20N5O9P",
      "kind": "NucleotideP",
      "oneLetter": "^",
      "ocl": {
        "value": "e`\\ZIL@DaegobFAIO@hlm`OGSp\\\\\\bbbfrRbdTT\\rbRQUCDQTrusuUUUUMUU@pET@@@",
        "coordinates": "!BIlAKaMARw}DBbMF@bGuMtHc|KAbH_ZU`@GzH_WwW@h`XKFjKB_jXB\\SiVA`zmG_Irp_hQKctvOSR\\lIrp"
      },
      "mass": 445.3217759066577,
      "monoisotopicMass": 445.09986424130005,
      "unsaturation": 16,
      "elements": [{
        "symbol": "C",
        "number": 15
      }, {
        "symbol": "H",
        "number": 20
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 9
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Ryg",
      "name": "2′-O-ribosylguanosine monophosphate diradical 00G",
      "mf": "C15H20N5O10P",
      "kind": "NucleotideP",
      "oneLetter": "ℑ",
      "ocl": {
        "value": "ehRZEL@DaegobFAIO@hlm`POGSp\\\\\\bbbfrRbdTT\\rbRQbhXbJfVn^jjjjijjjVZfj@@@",
        "coordinates": "!BIlAKaMARw}DBbMF@bGuMtH`BKAbH_ZU`@GzH_WwW@h`XKFjKB_jXB\\SiVA`zmG_Irp_hQKctvOSR\\lt]|gK@"
      },
      "mass": 461.321180830976,
      "monoisotopicMass": 461.09477886087,
      "unsaturation": 16,
      "elements": [{
        "symbol": "C",
        "number": 15
      }, {
        "symbol": "H",
        "number": 20
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Tmu",
      "name": "2-thio-2′-O-methyluridine monophosphate diradical 02U",
      "mf": "C10H13N2O7PS",
      "kind": "NucleotideP",
      "oneLetter": "∏",
      "ocl": {
        "value": "fncQp`EAaSfleZCq]x^BDnNQJJJI[QJIRYlyFmUUUULsSQDSDHfP@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuOSU@HC~NKA`H_QLgSUAMT@a}_S_|BBpXSU@"
      },
      "mass": 336.25837906774416,
      "monoisotopicMass": 336.01810893766003,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Dmut",
      "name": "3,2′-O-dimethyluridine monophosphate diradical 03U",
      "mf": "C11H15N2O8P",
      "kind": "NucleotideP",
      "oneLetter": "σ",
      "ocl": {
        "value": "fasPK@EAaSfoYKtGb{pRxyDhhhemDheIhv\\cVjjjjfYjZHbXaDr@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuOSU@HC~NKA`H_QLgSUAMT@a}_S_|BBpXOxyMT@"
      },
      "mass": 334.2196139907822,
      "monoisotopicMass": 334.05660244729,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 15
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Amc",
      "name": "N4-acetyl-2′-O-methylcytidine monophosphate diradical 042C",
      "mf": "C12H16N3O8P",
      "kind": "NucleotideP",
      "oneLetter": "ℵ",
      "ocl": {
        "value": "fe{pK@EA[BgM^rTXOEw`eqrIQQQKZIQJSMJLyFmUUUULsMMQDSDHfP@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuOSU@HC~NKA`H_QLgSUAMT@a}_S_|BBpXSU@caLgSU@"
      },
      "mass": 361.244993853019,
      "monoisotopicMass": 361.06750148395,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 16
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Tmc",
      "name": "N4,N4,2′-O-trimethylcytidine monophosphate diradical 044C",
      "mf": "C12H18N3O7P",
      "kind": "NucleotideP",
      "oneLetter": "β",
      "ocl": {
        "value": "fikqs@EA[BgM^rTGb{pRxyDhhhemDheIfhsdZuUUUTsLuTQDqBId@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuOSU@HC~NKA`H_QLgSUAMT@a}_S_|BBpXSU@cbpX"
      },
      "mass": 347.2614704368123,
      "monoisotopicMass": 347.08823692884005,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 18
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Dcy",
      "name": "N4,2′-O-dimethylcytidine monophosphate diradical 04C",
      "mf": "C11H16N3O7P",
      "kind": "NucleotideP",
      "oneLetter": "λ",
      "ocl": {
        "value": "fasqs@EA[BgM^rTGb{pRxyDhhhemDheIff\\cVjjjjfYfjHbXaDr@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuOSU@HC~NKA`H_QLgSUAMT@a}_S_|BBpXSU@lF@"
      },
      "mass": 333.23485303196554,
      "monoisotopicMass": 333.07258686438,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 16
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Muo",
      "name": "2′-O-methyluridine 5-oxyacetic acid methyl ester monophosphate diradical 0503U",
      "mf": "C13H17N2O11P",
      "kind": "NucleotideP",
      "oneLetter": "Ͽ",
      "ocl": {
        "value": "fkoQk@EAaSfoYJwj}`|W^BWGHeEEDmheDiLjlif\\cVjjjjfYjZZhbIbDSH@",
        "coordinates": "!BKAb@tURD@m\\YpMAMpBYMcvjbOplIwx@bGuMc}\\Bb@JH@dvOcuKPSXa}bGvHH`BbGu~Oxc|bGt"
      },
      "mass": 408.25518206531905,
      "monoisotopicMass": 408.05699637046,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 13
      }, {
        "symbol": "H",
        "number": 17
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 11
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Xmu",
      "name": "5-carboxymethylaminomethyl-2′-O-methyluridine monophosphate diradical 051U",
      "mf": "C13H18N3O10P",
      "kind": "NucleotideP",
      "oneLetter": ")",
      "ocl": {
        "value": "fkopk@EAGBgM^rWns`|W^BWGHeEEDmheDiLjleF\\cVjjjjfYjZfhbIbDSH@",
        "coordinates": "!BKAb@tURD@m\\YpMAMpBYMcvjb@HlIwx@bGuMc}\\Bb@JH@dvOcuKPSXa}bGvH@h`BbGvH@gx@bKt"
      },
      "mass": 407.2704211065024,
      "monoisotopicMass": 407.07298078755,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 13
      }, {
        "symbol": "H",
        "number": 18
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Mmu",
      "name": "5-methoxycarbonylmethyl-2′-O-methyluridine monophosphate diradical 0521U",
      "mf": "C13H17N2O10P",
      "kind": "NucleotideP",
      "oneLetter": "∩",
      "ocl": {
        "value": "fcwPk@EAaSfoYKvZp^KoAKcdRbbbVtRbTfUVfYrMZjjjjYfifjHbXaDr@@",
        "coordinates": "!BS]@lFJU`@Gyoza`lzf@lI}mK_`B@cm\\Bb@HlI}]}_`A~@BpgIqLXKH`Bb@I~@Ha}_c~HHa}"
      },
      "mass": 392.2557771410008,
      "monoisotopicMass": 392.06208175089,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 13
      }, {
        "symbol": "H",
        "number": 17
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Cue",
      "name": "5-(carboxyhydroxymethyl)-2′-O-methyluridine methyl ester monophosphate diradical 0522U",
      "mf": "C13H17N2O11P",
      "kind": "NucleotideP",
      "oneLetter": "∩",
      "ocl": {
        "value": "fkoQk@EAaSfoYKtZ}`|W^BWGHeEEDmheDiLjhYf\\cVjjjjfYjZZhbIbDSH@",
        "coordinates": "!BS]@lFJU`@Gyoza`lzf@lI}mK_`B@cm\\Bb@HlI}]}_`A~@BpgIqLXKH`Bb@I~@Gx@bGu~Oxc|bGt"
      },
      "mass": 408.25518206531905,
      "monoisotopicMass": 408.05699637046,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 13
      }, {
        "symbol": "H",
        "number": 17
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 11
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Cyu",
      "name": "5-carbamoylmethyl-2′-O-methyluridine monophosphate diradical 053U",
      "mf": "C12H16N3O9P",
      "kind": "NucleotideP",
      "oneLetter": "~",
      "ocl": {
        "value": "fmgqK@EAWBgM^rWlp^KoAKcdRbbbVtRbTfUVcNQkUUUUSLuLuDQLPbY@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuOSU@HC~NKA`H_QLgSUAMT@a}_S_|BBpXKAaMTDuPOxxlF@"
      },
      "mass": 377.24439877733727,
      "monoisotopicMass": 377.06241610352,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 16
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 9
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Ipu",
      "name": "5-(isopentenylaminomethyl)-2′-O-methyluridine monophosphate diradical 0583U",
      "mf": "C16H24N3O8P",
      "kind": "NucleotideP",
      "oneLetter": "¼",
      "ocl": {
        "value": "fgpK@EAGBgM^rWhOEw`eqrIQQQKZIQJSJkIJLyFmUUUULsTuMTQDqBId@@",
        "coordinates": "!BS]@lFJU`@Gyoza`lzf@lI}mK_`B@cm\\Bb@HlI}]}_`A~@BpgIqLXKH`Bb@I~@Ha}b@JH_Xc|_`BH_P"
      },
      "mass": 417.35146347240624,
      "monoisotopicMass": 417.13010174179004,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 16
      }, {
        "symbol": "H",
        "number": 24
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Mcy",
      "name": "5,2′-O-dimethylcytidine monophosphate diradical monophosphate diradical 05C",
      "mf": "C11H16N3O7P",
      "kind": "NucleotideP",
      "oneLetter": "τ",
      "ocl": {
        "value": "fasqs@EA{BgM^rTGb{pRxyDhhhemDheIeV\\cVjjjjfYfjHbXaDr@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuOSU@HC~NKA`H_QLgSUAMT@a}_S_|BBpXKAaMT@"
      },
      "mass": 333.23485303196554,
      "monoisotopicMass": 333.07258686438,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 16
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Dmuf",
      "name": "5,2′-O-dimethyluridine monophosphate diradical 05U",
      "mf": "C11H15N2O8P",
      "kind": "NucleotideP",
      "oneLetter": "\\",
      "ocl": {
        "value": "fasPK@EAaSfoYKtGb{pRxyDhhhemDheIeV\\cVjjjjfYjZHbXaDr@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuOSU@HC~NKA`H_QLgSUAMT@a}_S_|BBpXKAaMT@"
      },
      "mass": 334.2196139907822,
      "monoisotopicMass": 334.05660244729,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 15
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Tma",
      "name": "N6,N6,2′-O-trimethyladenosine monophosphate diradical 066A",
      "mf": "C13H18N5O6P",
      "kind": "NucleotideP",
      "oneLetter": "η",
      "ocl": {
        "value": "fmwhs@E^ct\\J\\udhOEw`eqrIQQQKZIQJQIkQg@q[vjjjj`Y`JjBHfHQL`@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuOSU@HEF@`H_R\\StPAKA@a}_S_|BD}RSuKQ@MD@SuHXK@"
      },
      "mass": 371.2862078321209,
      "monoisotopicMass": 371.09947031813005,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 13
      }, {
        "symbol": "H",
        "number": 18
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 6
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Nda",
      "name": "N6,2′-O-dimethyladenosine monophosphate diradical 06A",
      "mf": "C12H16N5O6P",
      "kind": "NucleotideP",
      "oneLetter": "χ",
      "ocl": {
        "value": "feghs@E^ct\\J\\udhOEw`eqrIQQQKZIQJQIkLxFK^uUUUTCLAUADSDHfP@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuOSU@HEF@`H_R\\StPAKA@a}_S_|BD}RSuKQ@MD@FBp"
      },
      "mass": 357.2595904272741,
      "monoisotopicMass": 357.08382025367,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 16
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 6
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Fmc",
      "name": "5-formyl-2′-O-methylcytidine monophosphate diradical 071C",
      "mf": "C10H12N3O8P",
      "kind": "NucleotideP",
      "oneLetter": "°",
      "ocl": {
        "value": "faspK@I^[BgENSghOFwaEqrIQQSYQJIRYULxDmUUUTsLttQDqBId@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFBpXSU@@tP"
      },
      "mass": 333.1917590433254,
      "monoisotopicMass": 333.03620135502996,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 12
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Omi",
      "name": "2′-O-methylinosine monophosphate diradical 09A",
      "mf": "C11H13N4O7P",
      "kind": "NucleotideP",
      "oneLetter": "≤",
      "ocl": {
        "value": "fi{Is@E^cvENZrTXOEw`eqrIQQQKZIQJQIig@q[vjjjjiYffhbIbDSH@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuOSU@HEF@`H_R\\StPAKA@a}_S_|BD}RSuKQ@MD@"
      },
      "mass": 344.21773398124395,
      "monoisotopicMass": 344.05218577211997,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 4
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Opu",
      "name": "2′-O-methylpseudouridine monophosphate diradical 09U",
      "mf": "C10H13N2O8P",
      "kind": "NucleotideP",
      "oneLetter": "Z",
      "ocl": {
        "value": "fncPK@@qaSfoYJtGb{pRxyDhhhemDheIfsdZuUUULuMMDQLPbY@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuOSU@HC~NKA`H_QLgSUAMT@a}_S_|BBpXSU@"
      },
      "mass": 320.1929965859354,
      "monoisotopicMass": 320.04095238282997,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Oma",
      "name": "2′-O-methyladenosine monophosphate diradical 0A",
      "mf": "C11H14N5O6P",
      "kind": "NucleotideP",
      "oneLetter": ":",
      "ocl": {
        "value": "fi{hs@E^ct\\J\\udhOEw`eqrIQQQKZIQJQIig@q[vjjjj`Y`J`bIbDSH@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuOSU@HEF@`H_R\\StPAKA@a}_S_|BD}RSuKQ@MD@"
      },
      "mass": 343.2329730224273,
      "monoisotopicMass": 343.06817018921,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 6
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Omc",
      "name": "2′-O-methylcytidine monophosphate diradical 0C",
      "mf": "C10H14N3O7P",
      "kind": "NucleotideP",
      "oneLetter": "B",
      "ocl": {
        "value": "fncqs@EA[BgM^rTGb{pRxyDhhhemDheIfsdZuUUUTsLuDQLPbY@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuOSU@HC~NKA`H_QLgSUAMT@a}_S_|BBpXSU@"
      },
      "mass": 319.2082356271187,
      "monoisotopicMass": 319.05693679992004,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Omg",
      "name": "2′-O-methylguanosine monophosphate diradical 0G",
      "mf": "C11H14N5O7P",
      "kind": "NucleotideP",
      "oneLetter": "#",
      "ocl": {
        "value": "fegis@E^ct\\J\\udlp^KoAKcdRbbbVtRbTbSbYpLV}jjjjjVYjZbHfHQL`@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@HoTuOSU@HEF@`H_R\\StPAKA@a}_S_|BD}RSuKQ@B\\StP@"
      },
      "mass": 359.23237794674554,
      "monoisotopicMass": 359.06308480878,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Omu",
      "name": "2′-O-methyluridinemonophosphate diradical 0U",
      "mf": "C10H13N2O8P",
      "kind": "NucleotideP",
      "oneLetter": "J",
      "ocl": {
        "value": "fncPK@EAaSfoYJtGb{pRxyDhhhemDheIfsdZuUUUTsMMDQLPbY@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuOSU@HC~NKA`H_QLgSUAMT@a}_S_|BBpXSU@"
      },
      "mass": 320.1929965859354,
      "monoisotopicMass": 320.04095238282997,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Cdg",
      "name": "7-cyano-7-deazaguanosine monophosphate diradical 100G",
      "mf": "C12H12N5O7P",
      "kind": "NucleotideP",
      "oneLetter": "φ",
      "ocl": {
        "value": "fmwis@INzM\\J\\TgLp^MoBKcdRbbfrbTRdRUbSN^CWmUUUUKLuSuDQLPbY@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpPH_Wtw@aOTd}RqdCQ@B\\StPCFP@"
      },
      "mass": 369.22723233536925,
      "monoisotopicMass": 369.04743474432,
      "unsaturation": 18,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 12
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Azg",
      "name": "7-aminomethyl-7-deazaguanosine monophosphate diradical 101G",
      "mf": "C12H16N5O7P",
      "kind": "NucleotideP",
      "oneLetter": "∉",
      "ocl": {
        "value": "fmwis@INzM\\J\\TgLp^MoBKcdRbbfrbTRdRUbSN^CWmUUUUKLuSUDQLPbY@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpPH_Wtw@aOTd}RqdCQ@B\\StPA`z`"
      },
      "mass": 373.2589953515923,
      "monoisotopicMass": 373.07873487324,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 16
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Eqo",
      "name": "epoxyqueuosine monophosphate diradical 102G",
      "mf": "C17H22N5O10P",
      "kind": "NucleotideP",
      "oneLetter": "ς",
      "ocl": {
        "value": "el^ZEL@IGNaehJNEDlig`TPOFspb\\\\bTTTvTRbTbRlRjbbfXx|Bjz~aAajjjjiYfjZjjjjHPfDHSD@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpPH_Wtw@aOTd}RqdCQ@B\\StP@{ULY@TEIKA@a}tPA}BOpHAEP"
      },
      "mass": 487.3585341325581,
      "monoisotopicMass": 487.11042892533,
      "unsaturation": 18,
      "elements": [{
        "symbol": "C",
        "number": 17
      }, {
        "symbol": "H",
        "number": 22
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Aes",
      "name": "archaeosine monophosphate diradical 103G",
      "mf": "C12H15N6O7P",
      "kind": "NucleotideP",
      "oneLetter": "(",
      "ocl": {
        "value": "fcoYs@INzM^xTxiNY`|[^DWGHeEEMeDheHdkDhsg`u{UUUURsMTmTQDqBId@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpPH_Wtw@aOTd}RqdCQ@B\\StP@{UFCj"
      },
      "mass": 386.2577578089824,
      "monoisotopicMass": 386.07398384544,
      "unsaturation": 16,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 15
      }, {
        "symbol": "N",
        "number": 6
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Goq",
      "name": "galactosyl-queuosine monophosphate diradical 104G",
      "mf": "C23H32N5O14P",
      "kind": "NucleotideP",
      "oneLetter": "9",
      "ocl": {
        "value": "ekXzGL@IGNaehJNEDliod\\VU]SPOFspb\\\\bTTTvTRbTbRlRjbTrTrbfRXx|Bjz^AyEjjjjiYfjZijjjjjjbDIaBDq@@",
        "coordinates": "!BvuPfpDnDtEK_tPJHtXBH_TwPb@J_IorHbGtgD}F@RxRH_WwW@hbOTh}RIlCQ`B\\StXC[UB[@RxSPT`JHbGwQ`H`BaEQ~@Ha}bOq~Ox`BbGu~@Ha}bOrH@`"
      },
      "mass": 633.4999767508004,
      "monoisotopicMass": 633.16833772591,
      "unsaturation": 20,
      "elements": [{
        "symbol": "C",
        "number": 23
      }, {
        "symbol": "H",
        "number": 32
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 14
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Gaq",
      "name": "glutamyl-queuosine monophosphate diradical105G",
      "mf": "C22H29N6O12P",
      "kind": "NucleotideP",
      "oneLetter": "⊄",
      "ocl": {
        "value": "emWVCL@IGNaejXJNEDlioh\\YUPOFspb\\\\bTTTvTRbTbRlRjbTJTtrTXx|Bjz^AjjjjiYfjZijfjfjbDIaBDq@@",
        "coordinates": "!BTmB@c`JHUMmMtL@YtEHYgxQTaDoQ`L@YFY|gKMARH`Ygy|fpAfN`Hz@`H{PTb\\ltEIRtHBNHaTv|@YFYPTha}b@I~@Ha}_c~H@ha}bOq~@Ha}"
      },
      "mass": 600.473311954707,
      "monoisotopicMass": 600.15810739451,
      "unsaturation": 22,
      "elements": [{
        "symbol": "C",
        "number": 22
      }, {
        "symbol": "H",
        "number": 29
      }, {
        "symbol": "N",
        "number": 6
      }, {
        "symbol": "O",
        "number": 12
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Moq",
      "name": "mannosyl-queuosine monophosphate diradical 106G",
      "mf": "C23H32N5O14P",
      "kind": "NucleotideP",
      "oneLetter": "8",
      "ocl": {
        "value": "ekXzGL@IGNaehJNEDliod\\VU]SPOFspb\\\\bTTTvTRbTbRlRjbTrTrbfRXx|Bjz^AyEjjjjiYfjZijjjjjjbDIaBDq@@",
        "coordinates": "!BvuPfpDnDtEK_tPJHtXBH_TwPb@J_IorHbGtgD}F@RxRH_WwW@hbOTh}RIlCQ`B\\StXC[UB[@RxSPT`JHbGwQ`H`BaEQ~@Ha}bOq~Ox`BbGu~@Ha}bOrH@`"
      },
      "mass": 633.4999767508004,
      "monoisotopicMass": 633.16833772591,
      "unsaturation": 20,
      "elements": [{
        "symbol": "C",
        "number": 23
      }, {
        "symbol": "H",
        "number": 32
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 14
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Qus",
      "name": "queuosine monophosphate diradical 10G",
      "mf": "C17H22N5O9P",
      "kind": "NucleotideP",
      "oneLetter": "Q",
      "ocl": {
        "value": "edZZIL@IGNaehJNEDliohPOFspb\\\\bTTTvTRbTbRlRjbTKGG`UWSpMUUUUKLuSUMUTPaLHPfH@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpPHoWtw@aOTd}RqdCQ@B\\StP@{ULY@RpQPTopHBGwQ@@QT"
      },
      "mass": 471.35912920823984,
      "monoisotopicMass": 471.11551430576,
      "unsaturation": 18,
      "elements": [{
        "symbol": "C",
        "number": 17
      }, {
        "symbol": "H",
        "number": 22
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 9
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Cpo",
      "name": "1-methyl-3-(3-amino-3-carboxypropyl)pseudouridine monophosphate diradical 1309U",
      "mf": "C14H20N3O10P",
      "kind": "NucleotideP",
      "oneLetter": "α",
      "ocl": {
        "value": "fgpk@OAWBgENSgi{`|[^DWGHeEEMeDheIhjbihs`RuUUTsTuSUMQDSDHfP@",
        "coordinates": "!BTh|SI~ioOwy`iR\\SiV|SFGxw}FH_]]}DqbH@gx_c|SFA`lIqOW_Xa}uwu~Ox`BbGu~Ox`B_`BH_P"
      },
      "mass": 421.2970385113492,
      "monoisotopicMass": 421.08863085201,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 14
      }, {
        "symbol": "H",
        "number": 20
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Mls",
      "name": "1-methylinosine monophosphate diradical 19A",
      "mf": "C11H13N4O7P",
      "kind": "NucleotideP",
      "oneLetter": "O",
      "ocl": {
        "value": "fi{Is@INBvENJSghOFwaEqrIQQSYQJIRIMIgOAjvjjjjefZZhbIbDSH@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpPH_Wtw@aOTd}RtPCQ@D}R"
      },
      "mass": 344.21773398124395,
      "monoisotopicMass": 344.05218577211997,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 4
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Mpu",
      "name": "1-methylpseudouridine monophosphate diradical 19U",
      "mf": "C10H13N2O8P",
      "kind": "NucleotideP",
      "oneLetter": "]",
      "ocl": {
        "value": "fncPK@OAaSbgIrtGc[pbxyDhhilheDiLjs`RuUUTsTuMDQLPbY@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFBpXSU@"
      },
      "mass": 320.1929965859354,
      "monoisotopicMass": 320.04095238282997,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Mad",
      "name": "1-methyladenosine monophosphate diradical 1A",
      "mf": "C11H14N5O6P",
      "kind": "NucleotideP",
      "oneLetter": "\"",
      "ocl": {
        "value": "fi{hs@INBwlJ\\TgHOFwaEqrIQQSYQJIRIMIgOAjvjjjjefZZhbIbDSH@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpPH_Wtw@aOTd}RtPCQ@D}R"
      },
      "mass": 343.2329730224273,
      "monoisotopicMass": 343.06817018921,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 6
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Mgs",
      "name": "1-methylguanosine monophosphate diradical 1G",
      "mf": "C11H14N5O7P",
      "kind": "NucleotideP",
      "oneLetter": "K",
      "ocl": {
        "value": "fegis@INBwlJ\\TgHp^MoBKcdRbbfrbTRdR\\RYspZmjjjjiYfijbHfHQL`@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpPH_Wtw@aOTd}RtP@gD}D@SuH"
      },
      "mass": 359.23237794674554,
      "monoisotopicMass": 359.06308480878,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Msu",
      "name": "5-aminomethyl-2-selenouridine monophosphate diradical 20510U",
      "mf": "C10H14N3O7PSe",
      "kind": "NucleotideP",
      "oneLetter": "π",
      "ocl": {
        "value": "fasqp`I^{BgEIrtGc[p\\bQ\\\\bTTTvTRbTfUSNAKUUUULsTuDQLPbY@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFBpXSU@@tP"
      },
      "mass": 398.1676241841323,
      "monoisotopicMass": 398.97345859992004,
      "unsaturation": null,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "Se",
        "number": 1
      }]
    }, {
      "symbol": "Mse",
      "name": "5-methylaminomethyl-2-selenouridine monophosphate diradical 20511U",
      "mf": "C11H16N3O7PSe",
      "kind": "NucleotideP",
      "oneLetter": "≅",
      "ocl": {
        "value": "fikqp`I^{BgEIrtGc[p\\bQ\\\\bTTTvTRbTfUVYpIZjjjifZfjHbXaDr@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFBpXSUAMTBpX"
      },
      "mass": 412.19424158897914,
      "monoisotopicMass": 412.98910866438,
      "unsaturation": null,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 16
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "Se",
        "number": 1
      }]
    }, {
      "symbol": "Cse",
      "name": "5-carboxymethylaminomethyl-2-selenouridine monophosphate diradical 2051U",
      "mf": "C12H16N3O9PSe",
      "kind": "NucleotideP",
      "oneLetter": "⊥",
      "ocl": {
        "value": "fcwqH`I^{BgEIru^p^MoArIEqrIQQSYQJIRYUYJLxDmUUUTsMSTuDQLPbY@@",
        "coordinates": "!BKAb@tURDM\\YpMAMpBYMcx`BKB]~@Ha}SXW@h`Bb@IMcx}RtDvH_Xa}b@JH@ha}b@I~@Ha}"
      },
      "mass": 456.20378733435086,
      "monoisotopicMass": 456.97893790352,
      "unsaturation": null,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 16
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 9
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "Se",
        "number": 1
      }]
    }, {
      "symbol": "Agm",
      "name": "agmatidine monophosphate diradical 20C",
      "mf": "C14H26N7O6P",
      "kind": "NucleotideP",
      "oneLetter": "¿",
      "ocl": {
        "value": "fgxs@I^BuY{piqR\\`|[^DWGHeEEMeDeEHmUddhsgbuUUUSTuUUMIDSDHfP@",
        "coordinates": "!BDqc_tTnD_]\\fpH}MgrYRc}_|Dr_W_Wx@ThWM_|bOqRc}ARctu~@Gx@urH@gx@b@I~@H`BbGu~@@"
      },
      "mass": 419.373876184194,
      "monoisotopicMass": 419.16821858483,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 14
      }, {
        "symbol": "H",
        "number": 26
      }, {
        "symbol": "N",
        "number": 7
      }, {
        "symbol": "O",
        "number": 6
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Sou",
      "name": "2-selenouridine monophosphate diradical 20U",
      "mf": "C9H11N2O7PSe",
      "kind": "NucleotideP",
      "oneLetter": "ω",
      "ocl": {
        "value": "ff}Qp`I^aSbdyjCqmxNQHnNQJJJ[JIQJSMg@ejjjjfYihbIbDSH@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFDuP"
      },
      "mass": 369.1263628137839,
      "monoisotopicMass": 369.9469094988,
      "unsaturation": null,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "Se",
        "number": 1
      }]
    }, {
      "symbol": "Agu",
      "name": "5-aminomethyl-2-geranylthiouridine monophosphate diradical 21510U",
      "mf": "C20H30N3O7PS",
      "kind": "NucleotideP",
      "oneLetter": "Δ",
      "ocl": {
        "value": "ed\\\\NB@IOIhJNEDla`OFsp\\BHgGHeEEMeDheHdjdcEdhqpEUUUUURsUSMTuQBDpaBXdDt@",
        "coordinates": "!BDr__cdo[_X`fgx}RgqeRtM]}Dqa~O}\\BTmBH_]]}uwuRtMAMcuI~O}\\BupJH_]]}_`A~Oxa}uwu~Oxa}_cW_Xa}"
      },
      "mass": 487.5074340654907,
      "monoisotopicMass": 487.15420849000003,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 20
      }, {
        "symbol": "H",
        "number": 30
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Mgu",
      "name": "5-methylaminomethyl-2-geranylthiouridine monophosphate diradical 21511U",
      "mf": "C21H32N3O7PS",
      "kind": "NucleotideP",
      "oneLetter": "h",
      "ocl": {
        "value": "elR\\NB@IOIhJNEDla`OFsp\\BHgGHeEEMeDheHdjdlileFN@jjjjjjVZjYjijbDIaBDqHIh",
        "coordinates": "!BTv^cbn{__@fw|}RwqeRdK]}Tva~_{_|TiCp_[]}mwuRdIAMsuI~_{]|mwsp_[]}mwu~_{_||Gvw_Wy|Gu~_{]}|Gt"
      },
      "mass": 501.5340514703375,
      "monoisotopicMass": 501.16985855446006,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 21
      }, {
        "symbol": "H",
        "number": 32
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Cgu",
      "name": "5-carboxymethylaminomethyl-2-geranylthiouridine monophosphate diradical 2151U",
      "mf": "C22H32N3O9PS",
      "kind": "NucleotideP",
      "oneLetter": "f",
      "ocl": {
        "value": "ef^\\IB@IOIhJNEDla`XPOFsp\\BHgGHeEEMeDheHdjdlhehbhqpEUUUUURsUSMUMMTPaLHPfIAM@",
        "coordinates": "!BTv^cbn{_@fw|}RwqeRdK]}Tva~_{]|TiCp[_}muRdIAMsuI~_{]|mwsp_[]}mwu~_{]||Gvw_[_}_g}~_{]||Ou~_{]}|Gt"
      },
      "mass": 545.5435972157093,
      "monoisotopicMass": 545.1596877935999,
      "unsaturation": 16,
      "elements": [{
        "symbol": "C",
        "number": 22
      }, {
        "symbol": "H",
        "number": 32
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 9
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Mha",
      "name": "2-methylthio-N6-(cis-hydroxyisopentenyl) adenosine monophosphate diradical 2160A",
      "mf": "C16H22N5O7PS",
      "kind": "NucleotideP",
      "oneLetter": "≠",
      "ocl": {
        "value": "e`TZNB@IG@nahJNEDlo`OFspb\\V`cHeEEMeDheHdxeleDqqxEUuUUUU@sAETuTDHSBDIbP[P",
        "coordinates": "!BzfC@IeKPaDn}bHCQb@KQwuRDFALYpHCQt]WHc|TmCQw}~N`ME~@Gx@upJH@h`B_`BH_X`BbGvHGxbGt"
      },
      "mass": 459.41437086899504,
      "monoisotopicMass": 459.09775624102,
      "unsaturation": 16,
      "elements": [{
        "symbol": "C",
        "number": 16
      }, {
        "symbol": "H",
        "number": 22
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Mpa",
      "name": "2-methylthio-N6-isopentenyladenosine monophosphate diradical 2161A",
      "mf": "C16H22N5O6PS",
      "kind": "NucleotideP",
      "oneLetter": "*",
      "ocl": {
        "value": "eohZFB@IG@nahJNEDl`OFspb\\V`cHeEEMeDheHdxeleFNO@jnjjjjhFXHjfjBDIaBDq@@",
        "coordinates": "!BpBYTvxBNFY|bEJObGvOS\\@Yt]~DUEJOctu~@Ha}`HzOSTwPTh~H@h`B_`BH_Xa}bOrH@ha}b@I~@Ha}"
      },
      "mass": 443.4149659446768,
      "monoisotopicMass": 443.10284162145,
      "unsaturation": 16,
      "elements": [{
        "symbol": "C",
        "number": 16
      }, {
        "symbol": "H",
        "number": 22
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 6
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Mca",
      "name": "2-methylthio-N6-threonylcarbamoyladenosine monophosphate diradical 2162A",
      "mf": "C16H21N6O10PS",
      "kind": "NucleotideP",
      "oneLetter": "[",
      "ocl": {
        "value": "ebVVEB@IG@nachJNEDlm`XTPOFspb\\V`cHeEEMeDheHdxemLhhhqqxEUuUUUU@sAESUMUABDpaBX`@",
        "coordinates": "!BzfC@IeKPaDn}bHCQb@KQwuRDFALYpHCQt]W@h`BTmCQw}~N`ME~@Gx@upJH@h`B_`BH_Wxb@JH_WxbOrHo]^}_`BH_P"
      },
      "mass": 520.4113480993399,
      "monoisotopicMass": 520.07774907193,
      "unsaturation": 18,
      "elements": [{
        "symbol": "C",
        "number": 16
      }, {
        "symbol": "H",
        "number": 21
      }, {
        "symbol": "N",
        "number": 6
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Mva",
      "name": "2-methylthio-N6-hydroxynorvalylcarbamoyladenosine monophosphate diradical 2163A",
      "mf": "C17H23N6O10PS",
      "kind": "NucleotideP",
      "oneLetter": "≈",
      "ocl": {
        "value": "ej^VEB@IG@nachJNEDlm`XTPOFspb\\V`cHeEEMeDheHdxemLhhiVNO@jnjjjjhFXHjZijjBDIaBDq@@",
        "coordinates": "!BpBYTvxBNFY|BbEJObGvOS\\@Yt]~DUEJOctu~@Ha}`HzOSTwPTh~H@h`B_`BH_Xa}bOrH@gx@bGvHGx@bGwW@h`B_c~H@ha}"
      },
      "mass": 534.4379655041866,
      "monoisotopicMass": 534.09339913639,
      "unsaturation": 18,
      "elements": [{
        "symbol": "C",
        "number": 17
      }, {
        "symbol": "H",
        "number": 23
      }, {
        "symbol": "N",
        "number": 6
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Mya",
      "name": "2-methylthio cyclic N6-threonylcarbamoyladenosine monophosphate diradical 2164A",
      "mf": "C17H20N5O9PS",
      "kind": "NucleotideP",
      "oneLetter": "ÿ",
      "ocl": {
        "value": "elVZIB@IG@nkhJNEDlcghPOFspb\\V`cHeEEMeDheHdxeihiUFNO@jnkojjjjhFXHjfZjbHPfDHSD@@",
        "coordinates": "!BvuPfpDnDtEK_tPJHtXBH_TwPb@J_IorHbGtgD}F@RxRH_WwW@hbOTh}RIqOQ`MF@cuKW@hQTcttfpL@YS]@BbGvH@Gx"
      },
      "mass": 501.4080351062552,
      "monoisotopicMass": 501.07193541570007,
      "unsaturation": 20,
      "elements": [{
        "symbol": "C",
        "number": 17
      }, {
        "symbol": "H",
        "number": 20
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 9
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Hta",
      "name": "hydroxy-N6-threonylcarbamoyladenosine monophosphate diradical 2165A",
      "mf": "C15H19N6O11P",
      "kind": "NucleotideP",
      "oneLetter": "«",
      "ocl": {
        "value": "elZVML@IG@fnehJNEDligo`TPOFspb\\\\bTTTvTRbTbSVTrbbeXx|BjZjjjj`Y`JZijjBDIaBDq@@",
        "coordinates": "!BpBYTvxBNFY|bEJObGvOS\\@Yt]~DUEJOctu~@Ha}`HzOSTwPTh~HH`BbGvH_Xc|_`BH_Xc|_`BH_]_|bOq~Oxc|bGt"
      },
      "mass": 490.31934821268436,
      "monoisotopicMass": 490.08494245264,
      "unsaturation": 18,
      "elements": [{
        "symbol": "C",
        "number": 15
      }, {
        "symbol": "H",
        "number": 19
      }, {
        "symbol": "N",
        "number": 6
      }, {
        "symbol": "O",
        "number": 11
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Lyd",
      "name": "2-lysidine monophosphate diradical 21C",
      "mf": "C15H24N5O8P",
      "kind": "NucleotideP",
      "oneLetter": "}",
      "ocl": {
        "value": "eo`ZAL@IGOFmhJNEDlkg`OFspb\\\\bTTTvTRbTbSVRTtXxBJjjjjfYjZjfhaBXPaLP@",
        "coordinates": "!BTh|SI~ioOwy`iR\\SiV|SFGxw}FH_]]}Dqa~Oxc|_c|SFA`lIqOW_Xa}_c~HHa}bOrH_WxbOq~@Ha}"
      },
      "mass": 433.3541339985626,
      "monoisotopicMass": 433.13624975064994,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 15
      }, {
        "symbol": "H",
        "number": 24
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Gtu",
      "name": "2-geranylthiouridine monophosphate diradical 21U",
      "mf": "C19H27N2O7PS",
      "kind": "NucleotideP",
      "oneLetter": "Γ",
      "ocl": {
        "value": "e`XTNB@IOHJNEDln`OFsp\\BHgGHeEEMeDheHdtmEdhqpEUUUUURsUKUMTPaLHPfIAu@",
        "coordinates": "!BTv^cbn{__@fw|}RwqeRdK]}Tva~_{_|TiCp_[]}mwuRdIAMsuI~_{]||Gvw_Wy|Gvw_Wy|Gu~_{]}|Gt"
      },
      "mass": 458.46617269514235,
      "monoisotopicMass": 458.12765938888003,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 19
      }, {
        "symbol": "H",
        "number": 27
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Tyg",
      "name": "N2,N2,7-trimethylguanosine cap monophosphate diradical (cap TMG) 2279553N",
      "mf": "C13H20N5O10P2",
      "kind": "NucleotideP",
      "oneLetter": "¶",
      "ocl": {
        "value": "e`TZEBHIG@aihJNEHdleck`OFspz|MgDJTef[vVVe_gifNO@jijjjjjUijifjhaBXPaLP@",
        "coordinates": "!BvuPfpDnDtEK_t_rHtXBH_TwPbOr_I`JHbGtgD}F@RxS|uxc|_]^OTh}RIlA~@B\\StXCQ`Gx@Owx@_h{_cuH"
      },
      "mass": 468.2734710359255,
      "monoisotopicMass": 468.06854085929,
      "unsaturation": 13,
      "elements": [{
        "symbol": "C",
        "number": 13
      }, {
        "symbol": "H",
        "number": 20
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 2
      }]
    }, {
      "symbol": "Tmg",
      "name": "N2,N2,7-trimethylguanosine monophosphate diradical 227G",
      "mf": "C13H20N5O7P",
      "kind": "NucleotideP",
      "oneLetter": "∠",
      "ocl": {
        "value": "fcoisBINCt\\J\\TgLp^MoBKbFY}dRbbfrbTRdRUbtYspZcjjjjiYfjjjHbXaDr@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpPH_Wtw@aOTd}Rqd@gD}D@tPBNOt}R"
      },
      "mass": 389.30149426455074,
      "monoisotopicMass": 389.11003500216,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 13
      }, {
        "symbol": "H",
        "number": 20
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Dgu",
      "name": "N2,N2-dimethylguanosine monophosphate diradical 22G",
      "mf": "C12H16N5O7P",
      "kind": "NucleotideP",
      "oneLetter": "R",
      "ocl": {
        "value": "fmwis@INBwlJ\\TgHp^MoBKcdRbbfrbTRdR\\RcN^CWmUUUUKLuMUDQLPbY@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpPH_Wtw@aOTd}RIqOQ@MD@cc}OT`"
      },
      "mass": 373.2589953515923,
      "monoisotopicMass": 373.07873487324,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 16
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Atu",
      "name": "5-aminomethyl-2-thiouridine monophosphate diradical 2510U",
      "mf": "C10H14N3O7PS",
      "kind": "NucleotideP",
      "oneLetter": "∫",
      "ocl": {
        "value": "fasqp`I^{BgEIrtGc[p\\DQ\\\\bTTTvTRbTfUSNAKUUUULsTuDQLPbY@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFBpXSU@@tP"
      },
      "mass": 351.27302303324575,
      "monoisotopicMass": 351.02900797432005,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Mou",
      "name": "5-methylaminomethyl-2-thiouridine monophosphate diradical 2511U",
      "mf": "C11H16N3O7PS",
      "kind": "NucleotideP",
      "oneLetter": "S",
      "ocl": {
        "value": "fikqp`I^{BgEIrtGc[p\\DQ\\\\bTTTvTRbTfUVYpIZjjjifZfjHbXaDr@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFBpXSUAMTBpX"
      },
      "mass": 365.2996404380926,
      "monoisotopicMass": 365.04465803878,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 16
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Ctu",
      "name": "5-carboxymethylaminomethyl-2-thiouridine monophosphate diradical 251U",
      "mf": "C12H16N3O9PS",
      "kind": "NucleotideP",
      "oneLetter": "$",
      "ocl": {
        "value": "fcwqH`I^{BgEIru^p^MoApQEqrIQQSYQJIRYUYJLxDmUUUTsMSTuDQLPbY@@",
        "coordinates": "!BKAb@tURDM\\YpMAMpBYMcx`BKB]~@Ha}SXW@h`Bb@IMcx}RtDvH_Xa}b@JH@ha}b@I~@Ha}"
      },
      "mass": 409.3091861834643,
      "monoisotopicMass": 409.03448727792,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 16
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 9
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Myu",
      "name": "5-methoxycarbonylmethyl-2-thiouridine monophosphate diradical 2521U",
      "mf": "C12H15N2O9PS",
      "kind": "NucleotideP",
      "oneLetter": "3",
      "ocl": {
        "value": "fmgQH`I^aSbdyZNXOFw`xHbxyDhhilheDiLjmLs`RuUUUSLuLuQDSDHfP@",
        "coordinates": "!BS]@lFJU`@Gyoza`lzf@lIwx@`H{WHc|KB_W_Wx@_`@lIr\\SFBrH@h`B_`BH_WxbOrH_P"
      },
      "mass": 394.2945422179627,
      "monoisotopicMass": 394.02358824126003,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 15
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 9
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Cou",
      "name": "5-carbamoylmethyl-2-thiouridine monophosphate diradical 253U",
      "mf": "C11H14N3O8PS",
      "kind": "NucleotideP",
      "oneLetter": "l",
      "ocl": {
        "value": "fe{pH`I^gBgEIrtXOFw`xHbxyDhhilheDiLjmF\\BVjjjjYfifhbIbDSH@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFBpXSUAMTC~NKA`"
      },
      "mass": 379.2831638542993,
      "monoisotopicMass": 379.02392259389,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Cau",
      "name": "5-carboxymethyl-2-thiouridine monophosphate diradical 2540U",
      "mf": "C11H13N2O9PS",
      "kind": "NucleotideP",
      "oneLetter": "℘",
      "ocl": {
        "value": "fe{QH`I^aSbdyZNXOFw`xHbxyDhhilheDiLjmF\\BVjjjjYfifhbIbDSH@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFBpXSUAMTC~NKA`"
      },
      "mass": 380.26792481311594,
      "monoisotopicMass": 380.00793817680005,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 9
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Tau",
      "name": "5-taurinomethyl-2-thiouridine monophosphate diradical 254U",
      "mf": "C12H18N3O10PS2",
      "kind": "NucleotideP",
      "oneLetter": "∃",
      "ocl": {
        "value": "fgpj`I^{BgEIrwY{`|[^C`bKblHrIQQSYQJIRYUYIRLxDmUUUTsMSUKTQDqBId@@",
        "coordinates": "!BKAb@tURD@m\\YpMAMpBYMcx`BKB]~@Ha}SXW@h`Bb@IMcx}RtDvH_Xa}b@JH@ha}b@JH__rH_]^H_P"
      },
      "mass": 459.3892600220213,
      "monoisotopicMass": 459.01712313635005,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 18
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 2
      }]
    }, {
      "symbol": "Itu",
      "name": "5-(isopentenylaminomethyl)-2-thiouridine monophosphate diradical 2583U",
      "mf": "C15H22N3O7PS",
      "kind": "NucleotideP",
      "oneLetter": "½",
      "ocl": {
        "value": "fkoqp`I^{BgEIrtGc[p\\DQ\\\\bTTTvTRbTfUVRTYpIZjjjifZfijbHfHQL`@",
        "coordinates": "!BS]@lFJU`@Gyoza`lzf@lIwx@`H{W@h`BKB_W_Wx@_`@lIr\\SFBrH@h`B_`BH_Xc|bGvH@gx@bGt"
      },
      "mass": 419.3902285493682,
      "monoisotopicMass": 419.09160823216,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 15
      }, {
        "symbol": "H",
        "number": 22
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Mth",
      "name": "5-methyl-2-thiouridine monophosphate diradical 25U",
      "mf": "C10H13N2O7PS",
      "kind": "NucleotideP",
      "oneLetter": "F",
      "ocl": {
        "value": "fncQp`I^aSbdyZCqmxNBHnNQJJJ[JIQJSJlxDmUUUTsMSQDSDHfP@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFBpXSU@"
      },
      "mass": 336.25837906774416,
      "monoisotopicMass": 336.01810893766003,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Dmg",
      "name": "N2,7-dimethylguanosine monophosphate diradical 27G",
      "mf": "C12H18N5O7P",
      "kind": "NucleotideP",
      "oneLetter": "∨",
      "ocl": {
        "value": "fmwisBINCt\\J\\TgLp^MoBKbFY}dRbbfrbTRdRUbKN^CWmUUUUKLuUUDQLPbY@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpPH_Wtw@aOTd}RqdCQ@B\\StPAOT`"
      },
      "mass": 375.27487685970397,
      "monoisotopicMass": 375.0943849377,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 18
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Dma",
      "name": "2,8-dimethyladenosine monophosphate diradical 28A",
      "mf": "C12H16N5O6P",
      "kind": "NucleotideP",
      "oneLetter": "±",
      "ocl": {
        "value": "feghs@INCv\\J\\UdhOFw`eqrIQQSYQJJJQKqLyxK^uUUUPMLAUADSDHfP@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpP@c`a}_S_|BD}RSuKQ@B\\StP@"
      },
      "mass": 357.2595904272741,
      "monoisotopicMass": 357.08382025367,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 16
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 6
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Mas",
      "name": "2-methyladenosine monophosphate diradical 2A",
      "mf": "C11H14N5O6P",
      "kind": "NucleotideP",
      "oneLetter": "/",
      "ocl": {
        "value": "fi{hs@INBt\\J\\TgHOFwaEqrIQQSYQJIRINIgOAjvjjjjAf@j`bIbDSH@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpPH_Wtw@aOTd}RtP@gD}D@"
      },
      "mass": 343.2329730224273,
      "monoisotopicMass": 343.06817018921,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 6
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Tcy",
      "name": "2-thiocytidine monophosphate diradical 2C",
      "mf": "C9H12N3O6PS",
      "kind": "NucleotideP",
      "oneLetter": "%",
      "ocl": {
        "value": "ff}pp`I^kBgEIrCqmxNBHnNQJJJ[JIQJSMg@ejjjjfYfhbIbDSH@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFDuP"
      },
      "mass": 321.2470007040807,
      "monoisotopicMass": 321.01844329029,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 12
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 6
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Nmg",
      "name": "N2-methylguanosine monophosphate diradical 2G",
      "mf": "C11H14N5O7P",
      "kind": "NucleotideP",
      "oneLetter": "L",
      "ocl": {
        "value": "fegis@INBwlJ\\TgHp^MoBKcdRbbfrbTRdR\\VYspZmjjjjiYfijbHfHQL`@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpPH_Wtw@aOTd}RtP@gD}D@SuH"
      },
      "mass": 359.23237794674554,
      "monoisotopicMass": 359.06308480878,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Thu",
      "name": "2-thiouridine monophosphate diradical 2U",
      "mf": "C9H11N2O7PS",
      "kind": "NucleotideP",
      "oneLetter": "2",
      "ocl": {
        "value": "ff}Qp`I^aSbdyjCqmxNBHnNQJJJ[JIQJSMg@ejjjjfYihbIbDSH@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFDuP"
      },
      "mass": 322.2317616628973,
      "monoisotopicMass": 322.0024588732,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Ahu",
      "name": "3-(3-amino-3-carboxypropyl)-5,6-dihydrouridine monophosphate diradical 308U",
      "mf": "C13H20N3O10P",
      "kind": "NucleotideP",
      "oneLetter": "Ð",
      "ocl": {
        "value": "fkopk@I^gBgENSens`|[^DWGHeEEMeDheIhueMF\\BVjjjjZfijfhbIbDSH@",
        "coordinates": "!BTh|SI~ioOwy`iR\\SiV|SFGxw}FH_]]}DqbH@gx_c|SFA`lIqOW_Xa}_c~HHa}_c~H@gx@bGt"
      },
      "mass": 409.28630261461393,
      "monoisotopicMass": 409.08863085201,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 13
      }, {
        "symbol": "H",
        "number": 20
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "App",
      "name": "3-(3-amino-3-carboxypropyl)pseudouridine monophosphate diradical 309U",
      "mf": "C13H18N3O10P",
      "kind": "NucleotideP",
      "oneLetter": "Þ",
      "ocl": {
        "value": "fkopk@OAgBgENSens`|[^DWGHeEEMeDheIhueMF\\BVjjjfZfijfhbIbDSH@",
        "coordinates": "!BTh|SI~ioOwy`iR\\SiV|SFGxw}FH_]]}DqbH@gx_c|SFA`lIqOW_Xa}_c~HHa}_c~H@gx@bGt"
      },
      "mass": 407.2704211065024,
      "monoisotopicMass": 407.07298078755,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 13
      }, {
        "symbol": "H",
        "number": 18
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Apu",
      "name": "3-(3-amino-3-carboxypropyl)uridine monophosphate diradical 30U",
      "mf": "C13H18N3O10P",
      "kind": "NucleotideP",
      "oneLetter": "X",
      "ocl": {
        "value": "fkopk@I^gBgENSens`|[^DWGHeEEMeDheIhueMF\\BVjjjjYfijfhbIbDSH@",
        "coordinates": "!BTh|SI~ioOwy`iR\\SiV|SFGxw}FH_]]}DqbH@gx_c|SFA`lIqOW_Xa}_c~HHa}_c~H@gx@bGt"
      },
      "mass": 407.2704211065024,
      "monoisotopicMass": 407.07298078755,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 13
      }, {
        "symbol": "H",
        "number": 18
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Mws",
      "name": "methylwyosine monophosphate diradical 342G",
      "mf": "C15H18N5O7P",
      "kind": "NucleotideP",
      "oneLetter": "∑",
      "ocl": {
        "value": "eghZNL@IG@nahJNEDli`OFspb\\\\bTTTvTRbTbb\\rVSGG`SPrvuUUUUKMTsUUIBDpaBX`@",
        "coordinates": "!B_`CW@mF@ctvDUI|fRxPYgtwP[zV_IorHFY|gD}F@RxPYg|@YgrZOTh{_cuJOS]F@tXAKaI|fw}EMt@"
      },
      "mass": 411.3070845499097,
      "monoisotopicMass": 411.0943849377,
      "unsaturation": 18,
      "elements": [{
        "symbol": "C",
        "number": 15
      }, {
        "symbol": "H",
        "number": 18
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Uhw",
      "name": "undermodified hydroxywybutosine monophosphate diradical 3470G",
      "mf": "C18H23N6O10P",
      "kind": "NucleotideP",
      "oneLetter": "š",
      "ocl": {
        "value": "ejQVEL@IG@nahXJNEDliolRPOFspb\\\\bTTTvTRbTbb\\rVVTttXx|BZFVvjjjjiYjfZjjfjRDIaBDq@@",
        "coordinates": "!BKB^@ceS[H`Yg}ARpAeMtHa}KAcPTh{_S]CjXES[pAeMtH}MtEK@IdnDpBXBbES[UMo@F]ARaERH_X`B_`BH_WxbOq~@Ha}"
      },
      "mass": 514.3839139947949,
      "monoisotopicMass": 514.12132796199,
      "unsaturation": 20,
      "elements": [{
        "symbol": "C",
        "number": 18
      }, {
        "symbol": "H",
        "number": 23
      }, {
        "symbol": "N",
        "number": 6
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Acw",
      "name": "7-aminocarboxypropylwyosine monophosphate diradical 347G",
      "mf": "C18H23N6O9P",
      "kind": "NucleotideP",
      "oneLetter": "Ω",
      "ocl": {
        "value": "eb^VIL@IG@na`XJNEDlid\\POFspb\\\\bTTTvTRbTbb\\rVRrfcGG`SPrvuUUUUKMTsUUSUIBDpaBX`@",
        "coordinates": "!BDr]RcwwWpAg_tUS[cm~DUAf_XJUTvx}MaEP@_gwWcm~DUDnDUMo|urH@m_@FWwW_]^NwuS[bGtYgx`BbGu~Ox`B_`BH_P"
      },
      "mass": 498.38450907047655,
      "monoisotopicMass": 498.12641334242,
      "unsaturation": 20,
      "elements": [{
        "symbol": "C",
        "number": 18
      }, {
        "symbol": "H",
        "number": 23
      }, {
        "symbol": "N",
        "number": 6
      }, {
        "symbol": "O",
        "number": 9
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Hwy",
      "name": "methylated undermodified hydroxywybutosine monophosphate diradical 3480G",
      "mf": "C19H25N6O10P",
      "kind": "NucleotideP",
      "oneLetter": "y",
      "ocl": {
        "value": "efYVEL@IG@nahXJNEDliolRPOFspb\\\\bTTTvTRbTbb\\rVVTttsGG`SPrvuUUUUKMTsUUTuTdHSBDIb@@",
        "coordinates": "!B`HyRtL@f_XbDRxz@UHS_chc|S]BN`MAMwxyKaL@fUHS_cmG_chCjXI|YzfA}bL@fpBYTaHz@F\\BH@gx@upJH@ha}_`CWHc|_`@"
      },
      "mass": 528.4105313996416,
      "monoisotopicMass": 528.1369780264499,
      "unsaturation": 20,
      "elements": [{
        "symbol": "C",
        "number": 19
      }, {
        "symbol": "H",
        "number": 25
      }, {
        "symbol": "N",
        "number": 6
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Hwb",
      "name": "hydroxywybutosine monophosphate diradical 34830G",
      "mf": "C21H27N6O12P",
      "kind": "NucleotideP",
      "oneLetter": "⊆",
      "ocl": {
        "value": "ee[VCL@IG@nahXJNEDliobZV^POFspb\\\\bTTTvTRbTbb\\rVVTtRbfsGG`SPrvuUUUUKMTsUUULuUIBDpaBX`@",
        "coordinates": "!BKB^@ceS[@h`Yg}ARpAeMtHa}KAcPTh{_S]CjXES[pAeMtH}MtEK@IdnDpBXBbES[UMo@F]ARaERH_X`B_`BH_X`B_c~H_]]}bGu~Ox`B_c~H_P"
      },
      "mass": 586.4466945498602,
      "monoisotopicMass": 586.14245733005,
      "unsaturation": 22,
      "elements": [{
        "symbol": "C",
        "number": 21
      }, {
        "symbol": "H",
        "number": 27
      }, {
        "symbol": "N",
        "number": 6
      }, {
        "symbol": "O",
        "number": 12
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Pwb",
      "name": "peroxywybutosine monophosphate diradical 34832G",
      "mf": "C21H27N6O13P",
      "kind": "NucleotideP",
      "oneLetter": "W",
      "ocl": {
        "value": "emWVKL@IG@nadXJNEDliohZV^QPOFspb\\\\bTTTvTRbTbb\\rVVTRfTTvXx|BZFVvjjjjiYjfZjjjYjjRDIaBDq@@",
        "coordinates": "!BKB^@ceS[@h`Yg}ARpAeMtHa}KAcPTh{_S]CjXES[pAeMtH}MtEK@IdnDpB[|bES[UMo@F]ARaERH_X`B_`BH_X`Bb@I~Oxa}uwvH_Wxb@I~Oxa}"
      },
      "mass": 602.4460994741785,
      "monoisotopicMass": 602.1373719496199,
      "unsaturation": 22,
      "elements": [{
        "symbol": "C",
        "number": 21
      }, {
        "symbol": "H",
        "number": 27
      }, {
        "symbol": "N",
        "number": 6
      }, {
        "symbol": "O",
        "number": 13
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Wyb",
      "name": "wybutosine monophosphate diradical 3483G",
      "mf": "C21H27N6O11P",
      "kind": "NucleotideP",
      "oneLetter": "Y",
      "ocl": {
        "value": "eiSVML@IG@na`XJNEDlilRZVPOFspb\\\\bTTTvTRbTbb\\rVVRbTTvXx|BZFVvjjjjiYjfZjjfZjdaBXPaLP@",
        "coordinates": "!BsJ\\@ciP{@`YWuARPAeMT@a}sNaPThxSUCjhIP{PAeMTD}MTEI@IllDPB[|BIP{eCm@FUARAIPH_Pc|BGtHGzBGtw_Pa}_k|HGzBGt"
      },
      "mass": 570.4472896255419,
      "monoisotopicMass": 570.14754271048,
      "unsaturation": 22,
      "elements": [{
        "symbol": "C",
        "number": 21
      }, {
        "symbol": "H",
        "number": 27
      }, {
        "symbol": "N",
        "number": 6
      }, {
        "symbol": "O",
        "number": 11
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Awo",
      "name": "7-aminocarboxypropylwyosine methyl ester monophosphate diradical 348G",
      "mf": "C19H25N6O9P",
      "kind": "NucleotideP",
      "oneLetter": "⇑",
      "ocl": {
        "value": "ejQVIL@IG@na`XJNEDlid\\POFspb\\\\bTTTvTRbTbb\\rVVRffXx|BZFVvjjjjiYjfZjjZjRDIaBDq@@",
        "coordinates": "!B`HyRtL@f_XbDRxz@UHS_ch`BS]BN`MAMwxyKaL@fUHS_cmG_chCjXI|YzfA}bL@fpBYTaHz@F\\BHHa}bOq~@Ha}_c~H@ha}"
      },
      "mass": 512.4111264753234,
      "monoisotopicMass": 512.14206340688,
      "unsaturation": 20,
      "elements": [{
        "symbol": "C",
        "number": 19
      }, {
        "symbol": "H",
        "number": 25
      }, {
        "symbol": "N",
        "number": 6
      }, {
        "symbol": "O",
        "number": 9
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Wyo",
      "name": "wyosine monophosphate diradical 34G",
      "mf": "C14H16N5O7P",
      "kind": "NucleotideP",
      "oneLetter": "€",
      "ocl": {
        "value": "ek`ZNL@IG@nahJNEDli`OFspb\\\\bTTTvTRbTbb\\rVXx|BZFVvjjjjiYjfZjdaBXPaLP@",
        "coordinates": "!B_`CWMF@ctvDUI|fRxPYgtwP[zV_IorHFY|gD}F@RxPYg|@YgrZOTh{_cuJOS]F@tXAKaI|fw}D"
      },
      "mass": 397.2804671450629,
      "monoisotopicMass": 397.07873487324,
      "unsaturation": 18,
      "elements": [{
        "symbol": "C",
        "number": 14
      }, {
        "symbol": "H",
        "number": 16
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Pdu",
      "name": "3-methylpseudouridine monophosphate diradical 39U",
      "mf": "C10H13N2O8P",
      "kind": "NucleotideP",
      "oneLetter": "κ",
      "ocl": {
        "value": "fncPK@OAaSbgIrtGc[pbxyDhhilheDiMFs`RuUUTsTuMDQLPbY@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@HoWtw@`lFC~NSU@"
      },
      "mass": 320.1929965859354,
      "monoisotopicMass": 320.04095238282997,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Mri",
      "name": "3-methyluridine monophosphate diradical 3U",
      "mf": "C10H13N2O8P",
      "kind": "NucleotideP",
      "oneLetter": "δ",
      "ocl": {
        "value": "fncPK@I^aSbgIrtGc[pbxyDhhilheDiMFs`RuUUUSLuMDQLPbY@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@HoWtw@`lFC~NSU@"
      },
      "mass": 320.1929965859354,
      "monoisotopicMass": 320.04095238282997,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Acc",
      "name": "N4-acetylcytidine monophosphate diradical 42C",
      "mf": "C11H14N3O8P",
      "kind": "NucleotideP",
      "oneLetter": "M",
      "ocl": {
        "value": "fikpK@I^kBgENSghOFwaEqrIQQSYQJIRYiQg@ejjjjfYffhbIbDSH@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFDuPOxxSItuP"
      },
      "mass": 347.21837644817225,
      "monoisotopicMass": 347.05185141949,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Iws",
      "name": "isowyosine monophosphate diradical 42G",
      "mf": "C14H16N5O7P",
      "kind": "NucleotideP",
      "oneLetter": "⊇",
      "ocl": {
        "value": "ek`ZNL@IG@fnhJNEDla`OFspb\\\\bTTTvTRbTbSbRrXx|BjzfVjjjjiYjYjjdaBXPaLP@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpPH_Wtw@aOTd}RStwQ@MD@RpQ_qcQSU@"
      },
      "mass": 397.2804671450629,
      "monoisotopicMass": 397.07873487324,
      "unsaturation": 18,
      "elements": [{
        "symbol": "C",
        "number": 14
      }, {
        "symbol": "H",
        "number": 16
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Dmc",
      "name": "N4,N4-dimethylcytidine monophosphate diradical 44C",
      "mf": "C11H16N3O7P",
      "kind": "NucleotideP",
      "oneLetter": "μ",
      "ocl": {
        "value": "fasqs@I^kBgENSdGc[pbxyDhhilheDiLuF\\BVjjjjYfZjHbXaDr@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBGpcbpXBGtSItuPSU@H_Wtw@`lFDuPOxxlF@"
      },
      "mass": 333.23485303196554,
      "monoisotopicMass": 333.07258686438,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 16
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Adw",
      "name": "7-aminocarboxypropyl-demethylwyosine monophosphate diradical 47G",
      "mf": "C17H21N6O9P",
      "kind": "NucleotideP",
      "oneLetter": "¥",
      "ocl": {
        "value": "elVVIL@IG@fnohJNEDlahTPOFspb\\\\bTTTvTRbTbSbRrrTtXx|BjzfVjjjjiYjYjjijdaBXPaLP@",
        "coordinates": "!B`MERc|@Y_]^DUH{_UMo_tXa}SXPTh{_w}GjXES[pAg_t]F@cm@Il@f@haTvuS[pAgPThQTbGvH@ha}_c~HGx@bGt"
      },
      "mass": 484.3578916656298,
      "monoisotopicMass": 484.1107632779601,
      "unsaturation": 20,
      "elements": [{
        "symbol": "C",
        "number": 17
      }, {
        "symbol": "H",
        "number": 21
      }, {
        "symbol": "N",
        "number": 6
      }, {
        "symbol": "O",
        "number": 9
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Ncd",
      "name": "N4-methylcytidine monophosphate diradical 4C",
      "mf": "C10H14N3O7P",
      "kind": "NucleotideP",
      "oneLetter": "ν",
      "ocl": {
        "value": "fncqs@I^kBgENSdGc[pbxyDhhilheDiLts`RuUUUSLsUDQLPbY@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFDuPKA`"
      },
      "mass": 319.2082356271187,
      "monoisotopicMass": 319.05693679992004,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Dmw",
      "name": "4-demethylwyosine monophosphate diradical 4G",
      "mf": "C13H14N5O7P",
      "kind": "NucleotideP",
      "oneLetter": "†",
      "ocl": {
        "value": "fcis@INBwlJ\\TgHp^MoBKcdRbbfrbTRdR\\RVYspZ}fnjjjjefifjiHbXaDr@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpPH_Wtw@aOTd}RStwQ@MD@RpQ_qcQ"
      },
      "mass": 383.253849740216,
      "monoisotopicMass": 383.06308480878,
      "unsaturation": 18,
      "elements": [{
        "symbol": "C",
        "number": 13
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Mtu",
      "name": "5-methoxyuridine monophosphate diradical 501U",
      "mf": "C10H13N2O9P",
      "kind": "NucleotideP",
      "oneLetter": "5",
      "ocl": {
        "value": "fasQK@I^aSbgIsUhOFwaEqrIQQSYQJIRYULxDmUUUTsMSTQDqBId@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFBpXSU@@tP"
      },
      "mass": 336.19240151025366,
      "monoisotopicMass": 336.03586700240004,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 9
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Uoa",
      "name": "uridine 5-oxyacetic acid monophosphate diradical 502U",
      "mf": "C11H13N2O11P",
      "kind": "NucleotideP",
      "oneLetter": "V",
      "ocl": {
        "value": "fmgQk@I^aSbgIsUlu`|[^DWGHeEEMeDheIeUeF\\BVjjjjYfiijHbXaDr@@",
        "coordinates": "!BS]@lFJU`@Gyoza`lzf@lIwx@`H{WHc|KB_W_Wx@_`@lIr\\SFBrHHc|_`BH_Xc|_`BH_P"
      },
      "mass": 380.2019472556255,
      "monoisotopicMass": 380.02569624154,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 11
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Ume",
      "name": "uridine 5-oxyacetic acid methyl ester monophosphate diradical 503U",
      "mf": "C12H15N2O11P",
      "kind": "NucleotideP",
      "oneLetter": "υ",
      "ocl": {
        "value": "fcwQk@I^aSbgIsUlu`|[^DWGHeEEMeDheIeUeLs`RuUUUSLuMMTQDqBId@@",
        "coordinates": "!BKAb@tURDM\\YpMAMpBYMcxc|KB]~@Ha}SXWHc|bOqMcx}RtDvH_Xa}bOrH@ha}_c~HHa}"
      },
      "mass": 394.2285646604723,
      "monoisotopicMass": 394.041346306,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 15
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 11
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Hxc",
      "name": "5-hydroxycytidine monophosphate diradical 50C",
      "mf": "C9H12N3O8P",
      "kind": "NucleotideP",
      "oneLetter": "Ç",
      "ocl": {
        "value": "fncpK@I^[BgENSfhOFwaEqrIQQSYQJIRYUg@ejjjjfYfjHbXaDr@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFBpXSU@"
      },
      "mass": 321.1810231465902,
      "monoisotopicMass": 321.03620135502996,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 12
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Hxu",
      "name": "5-hydroxyuridine monophosphate diradical 50U",
      "mf": "C9H11N2O9P",
      "kind": "NucleotideP",
      "oneLetter": "∝",
      "ocl": {
        "value": "fncQK@I^aSbgIsUhOFwaEqrIQQSYQJIRYUg@ejjjjfYjZHbXaDr@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFBpXSU@"
      },
      "mass": 322.1657841054069,
      "monoisotopicMass": 322.02021693794,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 9
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Amu",
      "name": "5-aminomethyluridine monophosphate diradical 510U",
      "mf": "C10H14N3O8P",
      "kind": "NucleotideP",
      "oneLetter": "∪",
      "ocl": {
        "value": "faspK@I^{BgENSehOFwaEqrIQQSYQJIRYULxDmUUUTsMSTQDqBId@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFBpXSU@@tP"
      },
      "mass": 335.207640551437,
      "monoisotopicMass": 335.05185141949,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Mau",
      "name": "5-methylaminomethyluridine monophosphate diradical 511U",
      "mf": "C11H16N3O8P",
      "kind": "NucleotideP",
      "oneLetter": "{",
      "ocl": {
        "value": "fikpK@I^{BgENSehOFwaEqrIQQSYQJIRYUYg@ejjjjfYjZhbIbDSH@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFBpXSUAMTBpX"
      },
      "mass": 349.2342579562838,
      "monoisotopicMass": 349.06750148395,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 16
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Hmc",
      "name": "5-hydroxymethylcytidine monophosphate diradical 51C",
      "mf": "C10H14N3O8P",
      "kind": "NucleotideP",
      "oneLetter": "∅",
      "ocl": {
        "value": "faspK@I^[BgENSghOFwaEqrIQQSYQJIRYULxDmUUUTsLuTQDqBId@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFBpXSU@@tP"
      },
      "mass": 335.207640551437,
      "monoisotopicMass": 335.05185141949,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Cur",
      "name": "5-carboxymethylaminomethyluridine monophosphate diradical 51U",
      "mf": "C12H16N3O10P",
      "kind": "NucleotideP",
      "oneLetter": "!",
      "ocl": {
        "value": "fcwpk@I^{BgENSej}`|[^DWGHeEEMeDheIeUdhs`RuUUUSLuMSTQDqBId@@",
        "coordinates": "!BKAb@tURDM\\YpMAMpBYMcx`BKB]~@Ha}SXW@h`Bb@IMcx}RtDvH_Xa}b@JH@ha}b@I~@Ha}"
      },
      "mass": 393.24380370165557,
      "monoisotopicMass": 393.05733072309,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 16
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Chr",
      "name": "5-carboxyhydroxymethyluridine monophosphate diradical 520U",
      "mf": "C11H13N2O11P",
      "kind": "NucleotideP",
      "oneLetter": "≥",
      "ocl": {
        "value": "fmgQk@I^aSbgIrwlu`|[^DWGHeEEMeDheIeUCF\\BVjjjjYfiijHbXaDr@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFBpXSU@@tTuPOxxlF@"
      },
      "mass": 380.2019472556255,
      "monoisotopicMass": 380.02569624154,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 11
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Mcu",
      "name": "5-methoxycarbonylmethyluridine monophosphate diradical 521U",
      "mf": "C12H15N2O10P",
      "kind": "NucleotideP",
      "oneLetter": "1",
      "ocl": {
        "value": "fmgPk@I^aSbgIrt\\p^MoBKcdRbbfrbTRdrjtsNAKUUUULsTsUDQLPbY@@",
        "coordinates": "!BS]@lFJU`@Gyoza`lzf@lIwx@`H{WHc|KB_W_Wx@_`@lIr\\SFBrH@h`B_`BH_WxbOrH_P"
      },
      "mass": 378.229159736154,
      "monoisotopicMass": 378.04643168643,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 15
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Hme",
      "name": "5-(carboxyhydroxymethyl)uridine methyl ester monophosphate diradical 522U",
      "mf": "C12H15N2O11P",
      "kind": "NucleotideP",
      "oneLetter": ",",
      "ocl": {
        "value": "fcwQk@I^aSbgIrwlu`|[^DWGHeEEMeDheIeUCLs`RuUUUSLuMMTQDqBId@@",
        "coordinates": "!BS]@lFJU`@Gyoza`lzf@lIwx@`H{WHc|KB_W_Wx@_`@lIr\\SFBrHHc|_`A~@Ha}_c~H@ha}"
      },
      "mass": 394.2285646604723,
      "monoisotopicMass": 394.041346306,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 15
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 11
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Cxu",
      "name": "5-carboxymethyluridine monophosphate diradical 52U",
      "mf": "C11H13N2O10P",
      "kind": "NucleotideP",
      "oneLetter": "◊",
      "ocl": {
        "value": "fe{Pk@I^aSbgIrt\\p^MoBKcdRbbfrbTRdrjtYpIZjjjifZfZbHfHQL`@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFBpXSUAMTC~NKA`"
      },
      "mass": 364.2025423313072,
      "monoisotopicMass": 364.03078162197,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Hmu",
      "name": "5-carbamoylhydroxymethyluridine monophosphate diradical 531U",
      "mf": "C11H14N3O10P",
      "kind": "NucleotideP",
      "oneLetter": "r",
      "ocl": {
        "value": "fmgpk@I^WBgENSeoY`|[^DWGHeEEMeDheIeUCF\\BVjjjjYfiijHbXaDr@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFBpXSU@@tTuPOxxlF@"
      },
      "mass": 379.21718629680873,
      "monoisotopicMass": 379.04168065863,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Ymu",
      "name": "5-carbamoylmethyluridine monophosphate diradical 53U",
      "mf": "C11H14N3O9P",
      "kind": "NucleotideP",
      "oneLetter": "&",
      "ocl": {
        "value": "fe{qK@I^gBgENSehp^MoBKcdRbbfrbTRdrjtYpIZjjjifZfZbHfHQL`@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFBpXSUAMTC~NKA`"
      },
      "mass": 363.2177813724905,
      "monoisotopicMass": 363.04676603906006,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 9
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Ttu",
      "name": "5-taurinomethyluridine monophosphate diradical 54U",
      "mf": "C12H18N3O11PS",
      "kind": "NucleotideP",
      "oneLetter": "Ê",
      "ocl": {
        "value": "fgqh`I^{BgENSenswAxv|HnJpcHeEEMeDheIeUdeHs`RuUUUSLuMTmQDSDHfP@",
        "coordinates": "!BKAb@tURD@m\\YpMAMpBYMcx`BKB]~@Ha}SXW@h`Bb@IMcx}RtDvH_Xa}b@JH@ha}b@JH__rH_]^H_P"
      },
      "mass": 443.32387754021244,
      "monoisotopicMass": 443.03996658152005,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 18
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 11
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Cmu",
      "name": "5-cyanomethyluridine monophosphate diradical 55U",
      "mf": "C11H12N3O8P",
      "kind": "NucleotideP",
      "oneLetter": "Ѷ",
      "ocl": {
        "value": "fikpK@I^GBgENSehOFwaEqrIQQSYQJIRYUYg@ejjjjfYj[hbIbDSH@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFBpXSU@@tPCQ"
      },
      "mass": 345.20249494006066,
      "monoisotopicMass": 345.03620135502996,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 12
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Pyu",
      "name": "5-(isopentenylaminomethyl)uridine monophosphate diradical 583U",
      "mf": "C15H22N3O8P",
      "kind": "NucleotideP",
      "oneLetter": "¾",
      "ocl": {
        "value": "fkopK@I^{BgENSehOFwaEqrIQQSYQJIRYUYIQg@ejjjjfYjZfjHbXaDr@@",
        "coordinates": "!BS]@lFJU`@Gyoza`lzf@lIwx@`H{W@h`BKB_W_Wx@_`@lIr\\SFBrH@h`B_`BH_Xc|bGvH@gx@bGt"
      },
      "mass": 403.32484606755946,
      "monoisotopicMass": 403.11445167733,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 15
      }, {
        "symbol": "H",
        "number": 22
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Mdu",
      "name": "5-methyldihydrouridine monophosphate diradical 58U",
      "mf": "C10H15N2O8P",
      "kind": "NucleotideP",
      "oneLetter": "ρ",
      "ocl": {
        "value": "fncPK@I^aSbgIrtGc[pbxyDhhilheDiLjs`RuUUUSTuMDQLPbY@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFBpXSU@"
      },
      "mass": 322.20887809404695,
      "monoisotopicMass": 322.05660244729,
      "unsaturation": 8,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 15
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Mcd",
      "name": "5-methylcytidine monophosphate diradical 5C",
      "mf": "C10H14N3O7P",
      "kind": "NucleotideP",
      "oneLetter": "?",
      "ocl": {
        "value": "fncqs@I^[BgENSdGc[pbxyDhhilheDiLjs`RuUUUSLsUDQLPbY@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFBpXSU@"
      },
      "mass": 319.2082356271187,
      "monoisotopicMass": 319.05693679992004,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Hia",
      "name": "N6-(cis-hydroxyisopentenyl)adenosine monophosphate diradical 60A",
      "mf": "C15H20N5O7P",
      "kind": "NucleotideP",
      "oneLetter": "`",
      "ocl": {
        "value": "eg`ZNL@IG@fnhJNEDlk`OFspb\\\\bTTTvTRbTbSVRTSGG`USUUUUTCLATuTDHSBDIbPSH",
        "coordinates": "!BzfC@IeKPaDn}bHCQb@KQwuRDFALYpHCQt]W@h`BTmCQw}~N`ME~@Gx@b@JH@ha}bOrH_Wxb@JH_P"
      },
      "mass": 413.3229660580212,
      "monoisotopicMass": 413.11003500216003,
      "unsaturation": 16,
      "elements": [{
        "symbol": "C",
        "number": 15
      }, {
        "symbol": "H",
        "number": 20
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Mta",
      "name": "2-methylthio-N6-methyladenosine monophosphate diradical 621A",
      "mf": "C12H16N5O6PS",
      "kind": "NucleotideP",
      "oneLetter": "∞",
      "ocl": {
        "value": "fmwhp`CQstZLDxipEfGa[qZDYEIlheDdhXdmDmKR\\u{MUUUU@aEUAFPTdmH@",
        "coordinates": "!BBGw|B@a}_S\\H@a}TEJNOuP{Ntm@fPBN[~iRSpHUCneXDBYTEITAEPDiVA@fTBYU@Sj[p"
      },
      "mass": 389.3243778334011,
      "monoisotopicMass": 389.05589142807,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 16
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 6
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Tca",
      "name": "N6-threonylcarbamoyladenosine monophosphate diradical 62A",
      "mf": "C15H19N6O10P",
      "kind": "NucleotideP",
      "oneLetter": "6",
      "ocl": {
        "value": "edRVEL@IG@fnehJNEDligo`POFspb\\\\bTTTvTRbTbSVTrbbcGG`USUUUUTCLASUMUABDpaBX`@",
        "coordinates": "!BzfC@IeKPaDn}bHCQbOsQwuRDFALYpHCQt]W@h`BTmCQw}~N`ME~@Gx@b@JH@ha}_c~H@ha}_c~H@ha}uwu~@Ha}"
      },
      "mass": 474.31994328836606,
      "monoisotopicMass": 474.09002783307,
      "unsaturation": 18,
      "elements": [{
        "symbol": "C",
        "number": 15
      }, {
        "symbol": "H",
        "number": 19
      }, {
        "symbol": "N",
        "number": 6
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Hva",
      "name": "N6-hydroxynorvalylcarbamoyladenosine monophosphate diradical 63A",
      "mf": "C16H21N6O10P",
      "kind": "NucleotideP",
      "oneLetter": "√",
      "ocl": {
        "value": "elZVIB@IG@fnehJNDligo`POEQql|HgGHeEEMeDheHdueLhhiVNO@jfjjjjhFXBfjZj`aBXPaLP@",
        "coordinates": "!BpBYTvxBNFY|bEJObGvOS\\@Yt]~DUEJOctu~@Ha}`HzOSTwPTh~HH`BbGvH_Xc|_`BH_Xc|_`BH_]_|bOq~Oxc|bGt"
      },
      "mass": 488.34656069321284,
      "monoisotopicMass": 488.10567789753003,
      "unsaturation": 18,
      "elements": [{
        "symbol": "C",
        "number": 16
      }, {
        "symbol": "H",
        "number": 21
      }, {
        "symbol": "N",
        "number": 6
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Aya",
      "name": "N6-acetyladenosine monophosphate diradical 64A",
      "mf": "C12H14N5O7P",
      "kind": "NucleotideP",
      "oneLetter": "⇓",
      "ocl": {
        "value": "fmwis@INBwlJ\\TgLp^MoBKcdRbbfrbTRdRZrcN^CUmUUUTCLASTDQLPbY@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpPH_Wtw@aOTd}RtPCQ@D}RIqOQ@@"
      },
      "mass": 371.2431138434808,
      "monoisotopicMass": 371.06308480878,
      "unsaturation": 16,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Gca",
      "name": "N6-glycinylcarbamoyladenosine monophosphate diradical 65A",
      "mf": "C13H15N6O9P",
      "kind": "NucleotideP",
      "oneLetter": "≡",
      "ocl": {
        "value": "eohVIL@IG@fnehJNEDlikg`OFspb\\\\bTTTvTRbTbSVTrTXx|BjZjjjj`Y`JZfhHPfDHSD@@",
        "coordinates": "!BzfC@IeKPaDn}bHCQb@KQwuRDFALYpHCQt]W@h`BTmCQw}~N`ME~@Gx@bOrHHa}_c~H@ha}bOq~@Ha}"
      },
      "mass": 430.2673035543541,
      "monoisotopicMass": 430.06381308458,
      "unsaturation": 18,
      "elements": [{
        "symbol": "C",
        "number": 13
      }, {
        "symbol": "H",
        "number": 15
      }, {
        "symbol": "N",
        "number": 6
      }, {
        "symbol": "O",
        "number": 9
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Tya",
      "name": "N6-methyl-N6-threonylcarbamoyladenosinemonophosphate diradical 662A",
      "mf": "C16H21N6O10P",
      "kind": "NucleotideP",
      "oneLetter": "E",
      "ocl": {
        "value": "elZVEL@IG@fnmhJNEDleo`XPOFspb\\\\bTTTvTRbTbSVbaTTTXx|BjZjjjj`Y`JfjZjBDIaBDq@@",
        "coordinates": "!BzfC@IeKPaDn}bHCQb@KQwuRDFALYpHCQt]W@h`BTmCQw}~N`ME~@Gx@bOrHHa}_`A~Ox`BbGu~Ox`BbGwW_Wx@bGt"
      },
      "mass": 488.34656069321284,
      "monoisotopicMass": 488.10567789753003,
      "unsaturation": 18,
      "elements": [{
        "symbol": "C",
        "number": 16
      }, {
        "symbol": "H",
        "number": 21
      }, {
        "symbol": "N",
        "number": 6
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Nna",
      "name": "N6,N6-dimethyladenosine monophosphate diradical 66A",
      "mf": "C12H16N5O6P",
      "kind": "NucleotideP",
      "oneLetter": "ζ",
      "ocl": {
        "value": "feghs@INBwlJ\\TgHOFwaEqrIQQSYQJIRIMZLyxMVuUUUPLpEUADSDHfP@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpPH_Wtw@aOTd}RtPCQ@D}RFBp"
      },
      "mass": 357.2595904272741,
      "monoisotopicMass": 357.08382025367,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 16
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 6
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Fya",
      "name": "N6-formyladenosine monophosphate diradical 67A",
      "mf": "C11H12N5O7P",
      "kind": "NucleotideP",
      "oneLetter": "Ϩ",
      "ocl": {
        "value": "fegis@INBwlJ\\TgLp^MoBKcdRbbfrbTRdRZrYspZmjjjj`Y`JZBHfHQL`@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpPH_Wtw@aOTd}RtPCQ@D}RtP@"
      },
      "mass": 357.216496438634,
      "monoisotopicMass": 357.04743474432,
      "unsaturation": 16,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 12
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Hma",
      "name": "N6-hydroxymethyladenosine monophosphate diradical 68A",
      "mf": "C11H14N5O7P",
      "kind": "NucleotideP",
      "oneLetter": "Ϫ",
      "ocl": {
        "value": "fegis@INBwlJ\\TgLp^MoBKcdRbbfrbTRdRZrYspZmjjjj`Y`JjBHfHQL`@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpPH_Wtw@aOTd}RtPCQ@D}RtP@"
      },
      "mass": 359.23237794674554,
      "monoisotopicMass": 359.06308480878,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Cca",
      "name": "cyclic N6-threonylcarbamoyladenosine monophosphate diradical 69A",
      "mf": "C15H17N6O9P",
      "kind": "NucleotideP",
      "oneLetter": "e",
      "ocl": {
        "value": "ehRVIL@IG@fnehJNEDliko`OFspb\\\\bTTTvTRbTbSVTRRtXx|BjZvNjjjj`Y`IjfjbHPfDHSD`z`",
        "coordinates": "!BvuPfpDnDtEK_tPJHtXBH_TwPb@J_IorHbGtgD}F@RxRH_WwW@hbOTh}RtXCQ`A`l_`A`iVCjKAcjX@A~@h`Bup"
      },
      "mass": 456.30465685593623,
      "monoisotopicMass": 456.07946314904,
      "unsaturation": 20,
      "elements": [{
        "symbol": "C",
        "number": 15
      }, {
        "symbol": "H",
        "number": 17
      }, {
        "symbol": "N",
        "number": 6
      }, {
        "symbol": "O",
        "number": 9
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Fcy",
      "name": "5-formylcytidine monophosphate diradical71C",
      "mf": "C10H12N3O8P",
      "kind": "NucleotideP",
      "oneLetter": ">",
      "ocl": {
        "value": "faspK@I^[BgENSghOFwaEqrIQQSYQJIRYULxDmUUUTsLttQDqBId@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFBpXSU@@tP"
      },
      "mass": 333.1917590433254,
      "monoisotopicMass": 333.03620135502996,
      "unsaturation": 12,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 12
      }, {
        "symbol": "N",
        "number": 3
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Tur",
      "name": "4-thiouridine monophosphate diradical 74U",
      "mf": "C9H11N2O7PS",
      "kind": "NucleotideP",
      "oneLetter": "4",
      "ocl": {
        "value": "ff}Qp`I^aSbgIrCqmxQ\\ZaFQJJJ[JIQJSMg@ejjjjfYihbIbDSH@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFDuP"
      },
      "mass": 322.2317616628973,
      "monoisotopicMass": 322.0024588732,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Meg",
      "name": "7-methylguanosine monophosphate diradical 7G",
      "mf": "C11H15N5O7P",
      "kind": "NucleotideP",
      "oneLetter": "7",
      "ocl": {
        "value": "fegisDINCt\\J\\TgLp^MoBKbF\\bTTTvTRbTbRlSN^CWmUUUUKLuSTQDqBId@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpPH_Wtw@aOTd}RqdCQ@B\\StP@"
      },
      "mass": 360.2403187008013,
      "monoisotopicMass": 360.07090984101,
      "unsaturation": 13,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 15
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Mea",
      "name": "8-methyladenosine monophosphate diradical 8A",
      "mf": "C11H14N5O6P",
      "kind": "NucleotideP",
      "oneLetter": "â",
      "ocl": {
        "value": "fi{hs@INCt\\J\\UdhOFw`eqrIQQSYQJJJQKigOA[vjjjjAi`J`bIbDSH@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpP@c`a}_S_|BD}RSuKQ@MD@"
      },
      "mass": 343.2329730224273,
      "monoisotopicMass": 343.06817018921,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 14
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 6
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Dhu",
      "name": "dihydrouridine monophosphate diradical 8U",
      "mf": "C9H13N2O8P",
      "kind": "NucleotideP",
      "oneLetter": "D",
      "ocl": {
        "value": "ff}PK@I^aSbgIsTGc[pbxyDhhilheDiLv\\BVjjjjZffbHfHQL`@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFDuP"
      },
      "mass": 308.1822606892002,
      "monoisotopicMass": 308.04095238282997,
      "unsaturation": 8,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Ins",
      "name": "inosine monophosphate diradical 9A",
      "mf": "C10H11N4O7P",
      "kind": "NucleotideP",
      "oneLetter": "I",
      "ocl": {
        "value": "fakIs@INBvENJSghOFwaEqrIQQSYQJIRIMLyxMVuUUUTlsSTQDqBId@@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOq_qopHBGtgD}D@RpPH_Wtw@aOTd}RtPCQ@@"
      },
      "mass": 330.1911165763972,
      "monoisotopicMass": 330.03653570766,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "N",
        "number": 4
      }, {
        "symbol": "O",
        "number": 7
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Pis",
      "name": "pseudouridine monophosphate diradical 9U",
      "mf": "C9H11N2O8P",
      "kind": "NucleotideP",
      "oneLetter": "P",
      "ocl": {
        "value": "ff}PK@OAaSbgIsTGc[pbxyDhhilheDiLv\\BVjjjfZffbHfHQL`@",
        "coordinates": "!BNuSFPDlDTEHt_pHtP@H_TuPBOpcbpXBGtSItuPSU@H_Wtw@`lFDuP"
      },
      "mass": 306.1663791810886,
      "monoisotopicMass": 306.02530231837,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 9
      }, {
        "symbol": "H",
        "number": 11
      }, {
        "symbol": "N",
        "number": 2
      }, {
        "symbol": "O",
        "number": 8
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Pqb",
      "name": "preQ0base 100G diradical (base)",
      "mf": "C7H5N5O",
      "kind": "Nucleotide",
      "oneLetter": "ψ",
      "ocl": {
        "value": "dk^h@DxYLLbbTTRekiujYj^`@",
        "coordinates": "!B|Gwp_Gy|Gwp_[lk_gp_Ag_wrYRs}|f"
      },
      "mass": 175.1477760289729,
      "monoisotopicMass": 175.04940980287,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 7
      }, {
        "symbol": "H",
        "number": 5
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Pqg",
      "name": "preQ1base 101G diradical (base)",
      "mf": "C7H9N5O",
      "kind": "Nucleotide",
      "oneLetter": "∇",
      "ocl": {
        "value": "dk^h@DxYLLbbTTRckiUjYij`@",
        "coordinates": "!BWyfe[tlDWye_fXx@RpRe[wtHSuHH@a}"
      },
      "mass": 179.179539045196,
      "monoisotopicMass": 179.08070993179,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 7
      }, {
        "symbol": "H",
        "number": 9
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Qba",
      "name": "Qbase 10G diradical (base)",
      "mf": "C12H15N5O3",
      "kind": "Nucleotide",
      "oneLetter": "∴",
      "ocl": {
        "value": "fbmi`@D\\EHpHyrJIQQJMJIPtyIPTmSMMUMUP@@",
        "coordinates": "!BRpQ_f^i`RpQKAEARzfA_f_pHtP@H_Pc|BGuPThxUCl{RtBYTd|"
      },
      "mass": 277.27967290184347,
      "monoisotopicMass": 277.11748936431,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 15
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 3
      }]
    }, {
      "symbol": "Dgc",
      "name": "N2,7-dimethylguanosine cap (cap DMG) diradical 279553N",
      "mf": "C12H18N5O11P2",
      "kind": "Nucleotide",
      "oneLetter": "®",
      "ocl": {
        "value": "e`TZMBHIG@aihJNEHdlemck`OFspz|OgDJ\\bTTTvTRbTbRvbtfKGG`UPuUUUUJtuTmUTPaLHPfH@@",
        "coordinates": "!BvuPfpDnDtEK_t_rHtXBH_TwPbOr_IorHbGtgD}F@RxS|uxc|_]^OTh}RIlBH_]F@IqOQ`@A~_c|bH}RbGt"
      },
      "mass": 470.24625855539705,
      "monoisotopicMass": 470.04780541440005,
      "unsaturation": 13,
      "elements": [{
        "symbol": "C",
        "number": 12
      }, {
        "symbol": "H",
        "number": 18
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 11
      }, {
        "symbol": "P",
        "number": 2
      }]
    }, {
      "symbol": "Dpa",
      "name": "5′-(3′-dephosphoacetyl-CoA) diradical 4155N",
      "mf": "C23H35N7O16P3S",
      "kind": "Nucleotide",
      "oneLetter": "♣",
      "ocl": {
        "value": "elz~@jDCHlemnSTLBAEKBjfckgbV]XpEfCpB|IoCtHZy{lbdvbbfrbTRdRNRdnTbefRTrRTdTRrFVfjjjj`V`bZjjfjZjZ`bbLSaRP@",
        "coordinates": "!BvtmKaMmKUMlfgto[tDw_cosWt]~H@dvObGv_F_sWbOpgKMG_R}m}bHa}HbOSX}M_cQw}G_OwzH_[wW_c~H_Wx@G{|bM]}bGvHGxbGu~Oxa}bOq~Oxa}_c~H_WxuwvH_P"
      },
      "mass": 790.5483266874629,
      "monoisotopicMass": 790.1073852418399,
      "unsaturation": 21,
      "elements": [{
        "symbol": "C",
        "number": 23
      }, {
        "symbol": "H",
        "number": 35
      }, {
        "symbol": "N",
        "number": 7
      }, {
        "symbol": "O",
        "number": 16
      }, {
        "symbol": "P",
        "number": 3
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Dpm",
      "name": "5′-(3′-dephosphomalonyl-CoA) diradical 4255N",
      "mf": "C24H35N7O18P3S",
      "kind": "Nucleotide",
      "oneLetter": "♥",
      "ocl": {
        "value": "efq~DjDCHlemnSTLBAEKBjfckgbV]XrzpEfCpB|IoCtHZy{lbdvbbfrbTRdRNRdnTbefRTrRTrdbbVPrtuUUUTBtDSUUTuSUSSTDTQb\\JR@@",
        "coordinates": "!BIlB_Ib[@pAe`zni`FALSF@A~FBq~OrpXbGveX@A~_c~OTa`lzf@_ha}_]_Q`MF@bOpXKA`loXbH__rHb@JHoX`B@m]}uwx@bGu~Ox`BbKvH@ha}_c~H@hb}b@JH_Xc|_`BH_X`B_`BHoP"
      },
      "mass": 834.5578724328346,
      "monoisotopicMass": 834.0972144809799,
      "unsaturation": 23,
      "elements": [{
        "symbol": "C",
        "number": 24
      }, {
        "symbol": "H",
        "number": 35
      }, {
        "symbol": "N",
        "number": 7
      }, {
        "symbol": "O",
        "number": 18
      }, {
        "symbol": "P",
        "number": 3
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Dsc",
      "name": "5′-(3′-dephosphosuccinyl-CoA) radical 4355N",
      "mf": "C25H37N7O18P3S",
      "kind": "Nucleotide",
      "oneLetter": "♦",
      "ocl": {
        "value": "eny~DjDCHlemnSTLBAEKBjfckgbV]XzvpOFCpB|IoCtHZy{lbdvbbfrbTRdRNRdnTbefRTrRTrTdTRrFVfjjjj`V`bZjjfjZjZfhHhcDxTd@@",
        "coordinates": "!B[~kjXFjiV[Ry|fcm}MtGwWctvH_]Q_c}KaGwWbGvN`H}MgrX@_gx@h`gKB\\lbGvOSX}M@m^H@gwWbGvH@ha}_Xc|bGxb@I~@Ha}b@JH_X`B_`BH_X`BbGvH@ha}_c~H@ha}b@I~@Ha}"
      },
      "mass": 848.5844898376815,
      "monoisotopicMass": 848.11286454544,
      "unsaturation": 23,
      "elements": [{
        "symbol": "C",
        "number": 25
      }, {
        "symbol": "H",
        "number": 37
      }, {
        "symbol": "N",
        "number": 7
      }, {
        "symbol": "O",
        "number": 18
      }, {
        "symbol": "P",
        "number": 3
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Dpc",
      "name": "5′-(3′-dephospho-CoA) radical 455N",
      "mf": "C21H32N7O13P2S",
      "kind": "Nucleotide",
      "oneLetter": "♠",
      "ocl": {
        "value": "ek_^KBDIG@nabYXJNEHdliemh\\QPEfspZ|CPcKmnrIQQSYQJIRIGIRWJQRsIJYIccpJkjjjjjAZBIjjjZijjBDIaBDq@@",
        "coordinates": "!B[zW[UI|YchAMc{vHcuJH@m~NbGuKvwvHb@JNwx}Rgqe}bHa}@h`gDr\\Sb@JOTh}R@m]~@@A~b@I~@H`B_X`_hb}_`CW@h`B_`BH@gx@upJH@gx@b@I~@@"
      },
      "mass": 684.5310558604504,
      "monoisotopicMass": 684.1254042880199,
      "unsaturation": 19,
      "elements": [{
        "symbol": "C",
        "number": 21
      }, {
        "symbol": "H",
        "number": 32
      }, {
        "symbol": "N",
        "number": 7
      }, {
        "symbol": "O",
        "number": 13
      }, {
        "symbol": "P",
        "number": 2
      }, {
        "symbol": "S",
        "number": 1
      }]
    }, {
      "symbol": "Dpe",
      "name": "5′-diphosphate end 552N",
      "mf": "O3P",
      "kind": "Nucleotide",
      "oneLetter": "ϒ",
      "ocl": {
        "value": "gJQdebGF^Dx|duK@@",
        "coordinates": "!BbOq~@GxbGt"
      },
      "mass": 78.97197677137483,
      "monoisotopicMass": 78.95850585713,
      "unsaturation": 1,
      "elements": [{
        "symbol": "O",
        "number": 3
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Mgc",
      "name": "7-methylguanosine cap (cap 0) diradical 79553N",
      "mf": "C11H16N5O11P2",
      "kind": "Nucleotide",
      "oneLetter": "©",
      "ocl": {
        "value": "eohZMBHIG@aihJNEHdlemck`OFspz|GgDJ\\bTTTvTRbTbRvbtcXx|BjFjjjjiVfjejjHPfDHSD@@",
        "coordinates": "!BvuPfpDnDtEK_tPJHtXBH_TwPb@J_I`JHbGtgD}F@RxPBux`B_]^OTh}RIlBH_]F@IqOQ`@A~_c|BbHa}"
      },
      "mass": 456.2196411505502,
      "monoisotopicMass": 456.03215534994,
      "unsaturation": 13,
      "elements": [{
        "symbol": "C",
        "number": 11
      }, {
        "symbol": "H",
        "number": 16
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 11
      }, {
        "symbol": "P",
        "number": 2
      }]
    }, {
      "symbol": "Gyy",
      "name": "guanylylated 5′ end (cap G) diradical 9553N",
      "mf": "C10H13N5O10P2",
      "kind": "Nucleotide",
      "oneLetter": "ϑ",
      "ocl": {
        "value": "fkhh`INCt\\J\\UENY{NCqmxM|EnNQJJJ[JIQJQHzIRLyxM^uUUUTkSULuQDSDHfP@",
        "coordinates": "!BvuPfpDnDtEK_tPJHtXBH_TwPb@J_I`JHbGtgD}F@RxPBux`B_]^OTh}R_`CQ`B\\StXA~@C}~@Gx"
      },
      "mass": 425.1856780673293,
      "monoisotopicMass": 425.01376563368,
      "unsaturation": 14,
      "elements": [{
        "symbol": "C",
        "number": 10
      }, {
        "symbol": "H",
        "number": 13
      }, {
        "symbol": "N",
        "number": 5
      }, {
        "symbol": "O",
        "number": 10
      }, {
        "symbol": "P",
        "number": 2
      }]
    }, {
      "symbol": "Furp",
      "name": "furan phosphate radical",
      "mf": "C5H6O4P",
      "kind": "RNAp",
      "oneLetter": "⬠",
      "ocl": {
        "value": "dmtBPDpnAYcpRZ}eeYjii@@",
        "coordinates": "!BNvw|Vso|kUl{[So|PPAGuU\\z`pP"
      },
      "mass": 161.072705703704,
      "monoisotopicMass": 161.00037067008,
      "unsaturation": 5,
      "elements": [{
        "symbol": "C",
        "number": 5
      }, {
        "symbol": "H",
        "number": 6
      }, {
        "symbol": "O",
        "number": 4
      }, {
        "symbol": "P",
        "number": 1
      }]
    }, {
      "symbol": "Phg",
      "ocl": {
        "value": "dcNHPBPOEgEInVuWYj`@@@"
      },
      "name": "Phenyl glycine diradical",
      "mf": "C8H7NO",
      "kind": "aa",
      "mass": 133.1475805880365,
      "monoisotopicMass": 133.05276384961002,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 8
      }, {
        "symbol": "H",
        "number": 7
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 1
      }]
    }, {
      "symbol": "Hpg",
      "ocl": {
        "value": "dknDPBPp|V\\Tfy[WWYj`@`@@",
        "coordinates": "!BbOq~@Ha}bOq~Oxa}bGwW_Wx?_c?W_Wx?"
      },
      "name": "4-hydroxyphenylglycine diradical",
      "mf": "C8H7NO2",
      "kind": "aa",
      "mass": 149.14698551235477,
      "monoisotopicMass": 149.04767846918,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 8
      }, {
        "symbol": "H",
        "number": 7
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 2
      }]
    }, {
      "symbol": "Dpg",
      "ocl": {
        "value": "dg^LPBS[CqYqR[emYTyj`BH@@",
        "coordinates": "!BbOr~@Hb}bOr~Wxb}bKvWo[y_oe}HoY^}Uwt"
      },
      "name": "3,5-dihydroxyphenylglycine diradical",
      "mf": "C8H7NO3",
      "kind": "aa",
      "mass": 165.14639043667304,
      "monoisotopicMass": 165.04259308875,
      "unsaturation": 10,
      "elements": [{
        "symbol": "C",
        "number": 8
      }, {
        "symbol": "H",
        "number": 7
      }, {
        "symbol": "N",
        "number": 1
      }, {
        "symbol": "O",
        "number": 3
      }]
    }, {
      "symbol": "T",
      "name": "Tritium",
      "mf": "[3H]",
      "ocl": {
        "value": "eFAAYhBLCEX@",
        "coordinates": "!B@@L"
      },
      "mass": 3.0160492779,
      "monoisotopicMass": 3.0160492779,
      "unsaturation": -1,
      "elements": [{
        "symbol": "H",
        "number": 1,
        "isotope": 3
      }]
    }];

    var groupsObject = {};

    (function (exports) {

      Object.defineProperty(exports, "__esModule", {
        value: true
      });
      exports.groupsObject = void 0;
      const groups_1 = groups;
      exports.groupsObject = {};
      groups_1.groups.forEach(e => {
        if (exports.groupsObject[e.symbol]) {
          // eslint-disable-next-line no-console
          console.log(`The symbol ${e.symbol} is used more than once`);
        }
        exports.groupsObject[e.symbol] = e;
      });
    })(groupsObject);

    var groupsToSequence$1 = {};

    Object.defineProperty(groupsToSequence$1, "__esModule", {
      value: true
    });
    groupsToSequence$1.groupsToSequence = groupsToSequence;
    const groupsObject_js_1 = groupsObject;
    /**
     * Recreate a one letter sequence
     * @param {object} mf
     */
    function groupsToSequence(mf) {
      mf = mf.replaceAll(/\([^(]*\)/g, '');
      let parts = mf.split(/(?=[ A-Z])/);
      let usefulParts = [];
      for (let part of parts) {
        if (part === ' ') {
          usefulParts.push(' ');
          continue;
        }
        if (!part.match(/^[A-Z][a-z]{2,6}/)) continue;
        if (groupsObject_js_1.groupsObject[part] && groupsObject_js_1.groupsObject[part].oneLetter) {
          usefulParts.push(groupsObject_js_1.groupsObject[part].oneLetter);
        } else {
          usefulParts.push('?');
        }
      }
      return usefulParts.join('').replaceAll(/ +/g, ' ').trim();
    }

    (function (exports) {

      var __createBinding = src$4 && src$4.__createBinding || (Object.create ? function (o, m, k, k2) {
        if (k2 === undefined) k2 = k;
        var desc = Object.getOwnPropertyDescriptor(m, k);
        if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
          desc = {
            enumerable: true,
            get: function () {
              return m[k];
            }
          };
        }
        Object.defineProperty(o, k2, desc);
      } : function (o, m, k, k2) {
        if (k2 === undefined) k2 = k;
        o[k2] = m[k];
      });
      var __exportStar = src$4 && src$4.__exportStar || function (m, exports) {
        for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
      };
      Object.defineProperty(exports, "__esModule", {
        value: true
      });
      __exportStar(groups, exports);
      __exportStar(groupsObject, exports);
      __exportStar(groupsToSequence$1, exports);
    })(src$4);

    var Kind = {};

    /**
     * Define static variable corresponding to the various Kinds of a molecular formula part.
     */
    Object.defineProperty(Kind, "__esModule", {
      value: true
    });
    Kind.Kind = void 0;
    Kind.Kind = {
      BEGIN: 'begin',
      ATOM: 'atom',
      MULTIPLIER_RANGE: 'multiplierRange',
      ISOTOPE: 'isotope',
      ISOTOPE_RATIO: 'isotopeRatio',
      CHARGE: 'charge',
      SALT: 'salt',
      OPENING_PARENTHESIS: 'openingParenthesis',
      CLOSING_PARENTHESIS: 'closingParenthesis',
      PRE_MULTIPLIER: 'preMultiplier',
      MULTIPLIER: 'multiplier',
      TEXT: 'text',
      ANCHOR: 'anchor',
      COMMENT: 'comment'
    };

    var parseCharge$1 = {};

    /**
     * Parse a string to extract the charge.
     * The charge may be in the form --, +++, +3, -2, 4+, 2-
     * @param {*} charge
     */
    Object.defineProperty(parseCharge$1, "__esModule", {
      value: true
    });
    parseCharge$1.parseCharge = parseCharge;
    function parseCharge(charge) {
      charge = charge.replaceAll(/[()]/g, '');
      let chargeNumber = 0;
      if (charge.match(/^[+-]+$/)) {
        for (let i = 0; i < charge.length; i++) {
          if (charge.charAt(i) === '+') chargeNumber++;else chargeNumber--;
        }
      } else if (charge.match(/^\d+[+-]$/)) {
        chargeNumber = Number(
        // eslint-disable-next-line unicorn/prefer-at
        charge.charAt(charge.length - 1) + charge.slice(0, -1));
      } else {
        chargeNumber = Number(charge);
      }
      return chargeNumber;
    }

    /* eslint-disable unicorn/prefer-code-point */
    Object.defineProperty(parse$1, "__esModule", {
      value: true
    });
    parse$1.parse = parse;
    const atom_sorter_1$2 = src$6;
    const chemical_elements_1$a = src$5;
    const chemical_groups_1$4 = src$4;
    const Kind_1$8 = Kind;
    const parseCharge_1 = parseCharge$1;
    /**
     * Parse a mf to an array of kind / value
     * @param {String} mf
     * @param {Object} [options={}]
     * @param {Boolean} [options.expandGroups=false] - if true, expand groups
     * @param {Boolean} [options.simplify=false] - if true, remove all the parenthesis and join identical atoms / groups
     */
    function parse(mf, options = {}) {
      return new MFParser().parse(mf, options);
    }
    class MFParser {
      parse(mf = '', options = {}) {
        this.expandGroups = options?.expandGroups ?? false;
        this.simplify = options?.simplify ?? false;
        this.mf = mf;
        this.i = 0;
        this.result = [];
        let lastKind = Kind_1$8.Kind.BEGIN;
        while (this.i < mf.length) {
          if (this.result.length > 0 && this.result.at(-1).kind !== Kind_1$8.Kind.TEXT) {
            lastKind = this.result.at(-1).kind;
          }
          let char = mf.charAt(this.i);
          let ascii = mf.charCodeAt(this.i);
          let nextAscii = 0;
          if (this.i + 1 < mf.length) nextAscii = mf.charCodeAt(this.i + 1);
          if (ascii > 47 && ascii < 58 || char === '-' && nextAscii > 47 && nextAscii < 58) {
            // a number
            let value = this.getNumber(ascii);
            if (lastKind === Kind_1$8.Kind.SALT || lastKind === Kind_1$8.Kind.BEGIN || lastKind === Kind_1$8.Kind.OPENING_PARENTHESIS) {
              if (value.to) {
                throw new MFError(this.mf, this.i, 'Premultiplier may not contain a -');
              }
              this.result.push({
                kind: Kind_1$8.Kind.PRE_MULTIPLIER,
                value: value.from
              });
            } else if (lastKind === Kind_1$8.Kind.ANCHOR) {
              if (value.to) {
                throw new MFError(this.mf, this.i, 'Anchor ID may not contain -');
              }
              this.result.at(-1).value = value.from;
            } else if (value.to) {
              this.result.push({
                kind: Kind_1$8.Kind.MULTIPLIER_RANGE,
                value: {
                  from: Math.min(value.from, value.to),
                  to: Math.max(value.from, value.to)
                }
              });
            } else {
              this.result.push({
                kind: Kind_1$8.Kind.MULTIPLIER,
                value: value.from
              });
            }
            continue;
          } else if (char === '.') {
            // a point
            this.result.push({
              kind: Kind_1$8.Kind.SALT,
              value: char
            });
            // it is not in a number otherwise it would have been taken before
            // it must be in a salt
          } else if (char === '#') {
            // an anchor
            this.result.push({
              kind: Kind_1$8.Kind.ANCHOR,
              value: 0
            });
            // it is not in a number otherwise it would have been taken before
            // it must be in a salt
          } else if (ascii > 64 && ascii < 91) {
            // an uppercase = new atom
            this.result.push(...this.getAtom(ascii));
            continue;
          } else if (ascii > 96 && ascii < 123) {
            // a lowercase
            throw new MFError(this.mf, this.i, 'found a lowercase not following an uppercase');
          } else if (char === '(') {
            let charge = this.getParenthesisCharge(ascii);
            if (charge) {
              this.result.push({
                kind: Kind_1$8.Kind.CHARGE,
                value: charge
              });
            } else {
              this.result.push({
                kind: Kind_1$8.Kind.OPENING_PARENTHESIS,
                value: '('
              });
            }
          } else if (char === ')') {
            this.result.push({
              kind: Kind_1$8.Kind.CLOSING_PARENTHESIS,
              value: ')'
            });
          } else if (char === '[') {
            // defines an isotope
            let isotope = this.getIsotope(ascii);
            this.result.push({
              kind: Kind_1$8.Kind.ISOTOPE,
              value: isotope
            });
          } else if (char === ']') {
            throw new MFError(this.mf, this.i, 'should never meet an closing bracket not in isotopes');
          } else if (char === '{') {
            // can define an exotic isotopic ratio or mixtures of groups
            let isotopeRatio = this.getCurlyBracketIsotopeRatio(ascii);
            if (lastKind === Kind_1$8.Kind.ATOM) {
              let lastResult = this.result.at(-1);
              lastResult.kind = Kind_1$8.Kind.ISOTOPE_RATIO;
              lastResult.value = {
                atom: lastResult.value,
                ratio: isotopeRatio
              };
            } else {
              throw new MFError(this.mf, this.i, 'isotopic composition has to follow an atom');
            }
          } else if (char === '}') {
            throw new MFError(this.mf, this.i, 'found a unexpected closing curly bracket');
          } else if (char === '+') {
            // charge not in parenthesis
            let charge = this.getNonParenthesisCharge(ascii);
            this.result.push({
              kind: Kind_1$8.Kind.CHARGE,
              value: charge
            });
          } else if (char === '-') {
            // charge not in parenthesis
            let charge = this.getNonParenthesisCharge(ascii);
            this.result.push({
              kind: Kind_1$8.Kind.CHARGE,
              value: charge
            });
          } else if (char === '$') {
            // it is a comment after
            this.result.push({
              kind: Kind_1$8.Kind.COMMENT,
              value: this.mf.slice(this.i + 1)
            });
            break;
          } else {
            this.result.push({
              kind: Kind_1$8.Kind.TEXT,
              value: char
            });
          }
          this.i++;
        }
        this.checkParenthesis();
        if (this.simplify) {
          this.result = simplify(this.result);
        }
        return this.result;
      }
      checkParenthesis() {
        let counter = 0;
        for (let line of this.result) {
          if (line.kind === Kind_1$8.Kind.OPENING_PARENTHESIS) counter++;
          if (line.kind === Kind_1$8.Kind.CLOSING_PARENTHESIS) counter--;
        }
        if (counter !== 0) {
          throw new MFError(this.mf, this.i, 'number of opening and closing parenthesis not equal');
        }
      }
      getNumber(ascii) {
        let number = '';
        let previous;
        do {
          previous = ascii;
          number += String.fromCharCode(ascii);
          this.i++;
          ascii = this.mf.charCodeAt(this.i);
        } while (ascii > 47 && ascii < 58 || ascii === 46 || ascii === 45 || ascii === 47); // number . - /
        // we need to deal with the case there is a from / to
        if (previous === 46) this.i--;
        let indexOfDash = number.indexOf('-', 1);
        if (indexOfDash > -1) {
          return {
            from: parseNumberWithDivision(number.slice(0, indexOfDash)),
            to: parseNumberWithDivision(number.slice(indexOfDash + 1))
          };
        }
        return {
          from: parseNumberWithDivision(number)
        };
      }
      getAtom(ascii) {
        let atom = '';
        do {
          atom += String.fromCharCode(ascii);
          this.i++;
          ascii = this.mf.charCodeAt(this.i);
        } while (ascii > 96 && ascii < 123);
        if (chemical_elements_1$a.elementsObject[atom] || !this.expandGroups) {
          return [{
            kind: Kind_1$8.Kind.ATOM,
            value: atom
          }];
        }
        if (chemical_groups_1$4.groupsObject[atom]) {
          const group = chemical_groups_1$4.groupsObject[atom].mf;
          const expandedGroups = parse(group, {
            expandGroups: this.expandGroups
          });
          // need to surround with parenthesis
          return [{
            kind: Kind_1$8.Kind.OPENING_PARENTHESIS,
            value: '('
          }, ...expandedGroups, {
            kind: Kind_1$8.Kind.CLOSING_PARENTHESIS,
            value: ')'
          }];
        }
        throw new MFError(`Not able to expand group: ${this.mf}`);
      }
      getIsotope(ascii) {
        // [13C]
        let substring = '';
        do {
          substring += String.fromCharCode(ascii);
          this.i++;
          ascii = this.mf.charCodeAt(this.i);
        } while (ascii !== 93 && this.i <= this.mf.length);
        let atom = substring.replaceAll(/[^A-Za-z]/g, '');
        let isotope = Number(substring.replaceAll(/\D/g, ''));
        return {
          atom,
          isotope
        };
      }
      getCurlyBracketIsotopeRatio(ascii) {
        let substring = '';
        let first = true;
        do {
          if (!first) {
            substring += String.fromCharCode(ascii);
          } else {
            first = false;
          }
          this.i++;
          ascii = this.mf.charCodeAt(this.i);
        } while (ascii !== 125 && this.i <= this.mf.length); // closing curly bracket
        if (substring.match(/^[0-9.,]+$/)) {
          return substring.split(',').map(Number);
        }
        throw new MFError(this.mf, this.i, 'Curly brackets should contain only number, dot and comma');
      }
      getParenthesisCharge(ascii) {
        let substring = '';
        let begin = this.i;
        do {
          substring += String.fromCharCode(ascii);
          this.i++;
          ascii = this.mf.charCodeAt(this.i);
        } while (ascii !== 41 && this.i <= this.mf.length); // closing parenthesis
        if (substring.match(/^\([\d+-]+$/)) {
          return (0, parseCharge_1.parseCharge)(substring.slice(1));
        } else {
          this.i = begin;
          return undefined;
        }
      }
      getNonParenthesisCharge(ascii) {
        let substring = '';
        do {
          substring += String.fromCharCode(ascii);
          this.i++;
          ascii = this.mf.charCodeAt(this.i);
        } while (ascii === 43 || ascii === 45 || ascii > 47 && ascii < 58);
        this.i--;
        return (0, parseCharge_1.parseCharge)(substring);
      }
    }
    class MFError extends SyntaxError {
      constructor(mf, i, message) {
        let text = `${message}\n\n${mf}\n${' '.repeat(i)}^`;
        super(text);
      }
    }
    function parseNumberWithDivision(string) {
      if (string.includes('/')) {
        let parts = string.split('/');
        if (parts.length !== 2) {
          throw new TypeError('Can not parse MF with number like: ', string);
        }
        return Number(parts[0]) / Number(parts[1]);
      } else {
        return Number(string);
      }
    }
    /**
     * Remove all the parenthesis and multipliers
     * Merge atoms and groups if present many times
     * We will also sort the atoms and groups
     * @param {*} parsed
     * @returns
     */
    function simplify(parsed) {
      if (!parsed || parsed.length === 0) return [];
      const multipliers = [];
      let currentMultiplier = {
        from: 1,
        to: 1
      };
      let newParsed = [];
      for (let i = parsed.length - 1; i >= 0; i--) {
        const item = parsed[i];
        switch (item.kind) {
          case 'atom':
          case 'isotope':
            {
              let realMultiplier = currentMultiplier;
              for (const multiplier of multipliers) {
                realMultiplier = {
                  from: multiplier.from * realMultiplier.from,
                  to: multiplier.to * realMultiplier.to
                };
              }
              newParsed.push({
                kind: item.kind,
                value: item.value
              }, {
                kind: 'multiplierRange',
                value: realMultiplier
              });
              currentMultiplier = {
                from: 1,
                to: 1
              };
            }
            break;
          case 'multiplier':
            currentMultiplier = {
              from: item.value,
              to: item.value
            };
            break;
          case 'multiplierRange':
            currentMultiplier = item.value;
            break;
          case 'openingParenthesis':
            multipliers.pop();
            break;
          case 'closingParenthesis':
            multipliers.push(currentMultiplier);
            currentMultiplier = {
              from: 1,
              to: 1
            };
            break;
          case 'text':
            break;
          default:
            throw new Error(`Unexpected kind --${item.kind}-- in removeParenthesis`);
        }
      }
      // if we have many times the same atom / group, we can merge them
      const distinctParsedObject = {};
      for (let i = 0; i < newParsed.length; i = i + 2) {
        const item = newParsed[i];
        const multiplier = newParsed[i + 1];
        const key = JSON.stringify(item.value);
        if (!distinctParsedObject[key]) {
          distinctParsedObject[key] = {
            ...item,
            multiplier: multiplier.value
          };
        } else {
          distinctParsedObject[key].multiplier.from += multiplier.value.from;
          distinctParsedObject[key].multiplier.to += multiplier.value.to;
        }
      }
      const sorted = Object.values(distinctParsedObject).sort((a, b) => {
        const atomA = a.kind === 'atom' ? a.value : a.value.atom;
        const atomB = b.kind === 'atom' ? b.value : b.value.atom;
        if (atomA === atomB) {
          if (a.kind === 'isotope' && b.kind === 'isotope') {
            return a.value.isotope - b.value.isotope;
          }
          if (a.kind === 'isotope' && b.kind !== 'isotope') {
            return -1; // isotope comes before non-isotope
          }
          if (a.kind !== 'isotope' && b.kind === 'isotope') {
            return 1; // non-isotope comes after isotope
          }
          return 0;
        }
        return (0, atom_sorter_1$2.atomSorter)(atomA, atomB);
      });
      const distinctParsed = [];
      for (const item of sorted) {
        if (item.multiplier.from === 0 && item.multiplier.to === 0) {
          continue;
        }
        distinctParsed.push({
          kind: item.kind,
          value: item.value
        });
        if (item.multiplier.from === 1 && item.multiplier.to === 1) {
          continue; // no need to add a multiplier
        }
        if (item.multiplier.from === item.multiplier.to) {
          distinctParsed.push({
            kind: 'multiplier',
            value: item.multiplier.from
          });
        } else {
          distinctParsed.push({
            kind: 'multiplierRange',
            value: {
              from: item.multiplier.from,
              to: item.multiplier.to
            }
          });
        }
      }
      return distinctParsed;
    }

    var subSuperscript = {};

    Object.defineProperty(subSuperscript, "__esModule", {
      value: true
    });
    subSuperscript.subscript = subSuperscript.superscript = void 0;
    subSuperscript.superscript = {
      0: '⁰',
      1: '¹',
      2: '²',
      3: '³',
      4: '⁴',
      5: '⁵',
      6: '⁶',
      7: '⁷',
      8: '⁸',
      9: '⁹',
      '+': '⁺',
      '-': '⁻',
      '(': '⁽',
      ')': '⁾',
      '{': '⁽',
      '}': '⁾',
      '.': '˙',
      ',': '˒'
    };
    subSuperscript.subscript = {
      0: '₀',
      1: '₁',
      2: '₂',
      3: '₃',
      4: '₄',
      5: '₅',
      6: '₆',
      7: '₇',
      8: '₈',
      9: '₉',
      '(': '₍',
      ')': '₎',
      '{': '₍',
      '}': '₎',
      '.': ' ',
      ',': ' '
    };

    var toDisplay$1 = {};

    var Format = {};

    /**
     * Defines static variables corresponding to the various formatting possibilities
     */
    Object.defineProperty(Format, "__esModule", {
      value: true
    });
    Format.Format = void 0;
    Format.Format = {
      SUBSCRIPT: 'subscript',
      SUPERSCRIPT: 'superscript',
      SUPERIMPOSE: 'superimpose',
      TEXT: 'text'
    };

    var formatCharge$1 = {};

    Object.defineProperty(formatCharge$1, "__esModule", {
      value: true
    });
    formatCharge$1.formatCharge = formatCharge;
    function formatCharge(charge) {
      if (charge === 1) return '+';
      if (charge > 1) return `+${charge}`;
      if (charge < 0) return String(charge);
      return '';
    }

    var improveLinesForDisplay$1 = {};

    Object.defineProperty(improveLinesForDisplay$1, "__esModule", {
      value: true
    });
    improveLinesForDisplay$1.improveLinesForDisplay = improveLinesForDisplay;
    const Kind_js_1$1 = Kind;
    /**
     * We try to remove useless lines that are not needed for the display and could be confusing
     * for the user
     * @param {object[]} lines
     */
    function improveLinesForDisplay(lines) {
      lines = lines.slice(0);
      // multipliers by one seems completely useless
      lines = lines.filter(line => !(line.kind === Kind_js_1$1.Kind.MULTIPLIER && line.value === 1));
      // global surrouding useless parenthesis
      // (C) -> C, (CH2) -> CH2, ((CH2)) -> CH2, ((CH2)2) -> (CH2)2, (C(CH2)2) -> C(CH2)2
      // need to count the number of opening parenthesis
      // and check the minimum number of parenthesis we find
      let beginCounter = 0;
      let endCounter = 0;
      let minCounter = Number.MAX_SAFE_INTEGER;
      let counter = 0;
      let begin = true;
      for (let line of lines) {
        switch (line.kind) {
          case Kind_js_1$1.Kind.OPENING_PARENTHESIS:
            if (begin) {
              beginCounter++;
            }
            counter++;
            break;
          case Kind_js_1$1.Kind.CLOSING_PARENTHESIS:
            endCounter++;
            counter--;
            break;
          case Kind_js_1$1.Kind.CHARGE:
            // seems to me we can still remove parenthesis even if we have some charges
            break;
          default:
            if (counter < minCounter) minCounter = counter;
            endCounter = 0;
            begin = false;
        }
      }
      let nbParenthesisToSuppress = Math.min(minCounter, beginCounter, endCounter);
      if (nbParenthesisToSuppress > 0) {
        // need to remove from the beginning and from the end the number of parenthesis
        // knowing some of the lines could be charges
        let toSuppress = nbParenthesisToSuppress;
        let i = 0;
        while (toSuppress > 0) {
          if (lines[0].kind === Kind_js_1$1.Kind.OPENING_PARENTHESIS) {
            lines.splice(i, 1);
            toSuppress--;
          } else {
            i++;
          }
        }
        toSuppress = nbParenthesisToSuppress;
        i = lines.length - 1;
        while (toSuppress > 0) {
          if (lines[i].kind === Kind_js_1$1.Kind.CLOSING_PARENTHESIS) {
            toSuppress--;
            lines.splice(i, 1);
          }
          i--;
        }
      }
      return lines;
    }

    Object.defineProperty(toDisplay$1, "__esModule", {
      value: true
    });
    toDisplay$1.toDisplay = toDisplay;
    const Format_1$2 = Format;
    const Kind_1$7 = Kind;
    const formatCharge_js_1 = formatCharge$1;
    const improveLinesForDisplay_js_1 = improveLinesForDisplay$1;
    /**
     * Converts an array of mf elements to an array of formatting information
     * @param {object[]} lines of the parse method
     * @returns {{kind: string, value: string}[]}
     */
    function toDisplay(lines) {
      lines = (0, improveLinesForDisplay_js_1.improveLinesForDisplay)(lines);
      const special = specialCases(lines);
      if (special) return special;
      let results = [];
      let result = {};
      for (let line of lines) {
        switch (line.kind) {
          case Kind_1$7.Kind.MULTIPLIER:
            if (line.value !== 1) {
              result = {
                kind: Format_1$2.Format.SUBSCRIPT,
                value: String(line.value)
              };
              results.push(result);
            }
            break;
          case Kind_1$7.Kind.MULTIPLIER_RANGE:
            result = {
              kind: Format_1$2.Format.SUBSCRIPT,
              value: `${String(line.value.from)}-${line.value.to}`
            };
            results.push(result);
            break;
          case Kind_1$7.Kind.CHARGE:
            if (result.kind === Format_1$2.Format.SUBSCRIPT) {
              result.kind = Format_1$2.Format.SUPERIMPOSE;
              result.over = (0, formatCharge_js_1.formatCharge)(line.value);
              result.under = result.value;
              result.value = undefined;
            } else {
              result = {
                kind: Format_1$2.Format.SUPERSCRIPT,
                value: (0, formatCharge_js_1.formatCharge)(line.value)
              };
              results.push(result);
            }
            break;
          case Kind_1$7.Kind.ISOTOPE:
            result = {
              kind: Format_1$2.Format.SUPERSCRIPT,
              value: line.value.isotope
            };
            results.push(result);
            result = {
              kind: Format_1$2.Format.TEXT,
              value: line.value.atom
            };
            results.push(result);
            break;
          case Kind_1$7.Kind.ISOTOPE_RATIO:
            if (result.kind === Format_1$2.Format.TEXT) {
              result.value += line.value.atom;
            } else {
              result = {
                kind: Format_1$2.Format.TEXT,
                value: line.value.atom
              };
              results.push(result);
            }
            result = {
              kind: Format_1$2.Format.SUPERSCRIPT,
              value: `{${line.value.ratio.join(',')}}`
            };
            results.push(result);
            break;
          case Kind_1$7.Kind.SALT:
            if (result.kind === Format_1$2.Format.TEXT) {
              result.value += ' • ';
            } else {
              result = {
                kind: Format_1$2.Format.TEXT,
                value: ' • '
              };
              results.push(result);
            }
            break;
          default:
            if (result.kind === Format_1$2.Format.TEXT) {
              result.value += line.value;
            } else {
              result = {
                kind: Format_1$2.Format.TEXT,
                value: line.value
              };
              results.push(result);
            }
        }
      }
      return results;
    }
    /**
     * Some special changes for specific cases
     * @param {} lines
     */
    function specialCases(lines) {
      // (-)
      if (lines.length === 1 && lines[0].kind === Kind_1$7.Kind.CHARGE) {
        const charge = lines[0].value;
        if (charge === 0) return [];
        if (charge === 1) {
          return [{
            kind: Format_1$2.Format.TEXT,
            value: '-e'
          }, {
            kind: Format_1$2.Format.SUPERSCRIPT,
            value: '-'
          }];
        }
        if (charge === -1) {
          return [{
            kind: Format_1$2.Format.TEXT,
            value: '+e'
          }, {
            kind: Format_1$2.Format.SUPERSCRIPT,
            value: '-'
          }];
        }
        if (charge > 1) {
          return [{
            kind: Format_1$2.Format.TEXT,
            value: `-${charge}e`
          }, {
            kind: Format_1$2.Format.SUPERSCRIPT,
            value: '-'
          }];
        }
        if (charge < -1) {
          return [{
            kind: Format_1$2.Format.TEXT,
            value: `+${-charge}e`
          }, {
            kind: Format_1$2.Format.SUPERSCRIPT,
            value: '-'
          }];
        }
      }
      // (-)2, (-)-1, (+)2, (+)-1, (2+)2, (2+)-1, (2-)+1, (2-)-1
      if (lines.length === 2 && lines[0].kind === Kind_1$7.Kind.CHARGE && lines[1].kind === Kind_1$7.Kind.MULTIPLIER) {
        const charge = lines[0].value;
        const nbElectrons = -lines[1].value * charge;
        const results = [];
        if (nbElectrons === 0) return [];
        if (nbElectrons === 1) {
          results.push({
            kind: Format_1$2.Format.TEXT,
            value: '+e'
          });
        }
        if (nbElectrons === -1) {
          results.push({
            kind: Format_1$2.Format.TEXT,
            value: '-e'
          });
        }
        if (nbElectrons > 1) {
          results.push({
            kind: Format_1$2.Format.TEXT,
            value: `+${nbElectrons}e`
          });
        }
        if (nbElectrons < -1) {
          results.push({
            kind: Format_1$2.Format.TEXT,
            value: `${nbElectrons}e`
          });
        }
        results.push({
          kind: Format_1$2.Format.SUPERSCRIPT,
          value: '-'
        });
        return results;
      }
      return undefined;
    }

    var isMF$1 = {};

    Object.defineProperty(isMF$1, "__esModule", {
      value: true
    });
    isMF$1.isMF = isMF;
    const chemical_elements_1$9 = src$5;
    const chemical_groups_1$3 = src$4;
    function isMF(mf) {
      let tmpMF = mf.replaceAll(/[^A-Za-z]/g, '');
      let parts = tmpMF.replaceAll(/([A-Za-z])(?=[A-Z])/g, '$1 ').split(' ');
      for (let i = 0; i < parts.length; i++) {
        if (!chemical_elements_1$9.elementsObject[parts[i]] && !chemical_groups_1$3.groupsObject[parts[i]]) {
          return false;
        }
      }
      return true;
    }

    var toHtml$1 = {};

    var Style = {};

    Object.defineProperty(Style, "__esModule", {
      value: true
    });
    Style.Style = void 0;
    Style.Style = {
      SUPERIMPOSE: 'flex-direction: column;display: inline-flex;justify-content: center;text-align: left;vertical-align: middle;',
      SUPERIMPOSE_SUP_SUB: 'line-height: 1; font-size: 70%'
    };

    Object.defineProperty(toHtml$1, "__esModule", {
      value: true
    });
    toHtml$1.toHtml = toHtml;
    const Format_1$1 = Format;
    const Style_1 = Style;
    function toHtml(lines) {
      let html = [];
      for (let line of lines) {
        switch (line.kind) {
          case Format_1$1.Format.SUBSCRIPT:
            html.push(`<sub>${line.value}</sub>`);
            break;
          case Format_1$1.Format.SUPERSCRIPT:
            html.push(`<sup>${line.value}</sup>`);
            break;
          case Format_1$1.Format.SUPERIMPOSE:
            html.push(`<span style="${Style_1.Style.SUPERIMPOSE}">`, `<sup style="${Style_1.Style.SUPERIMPOSE_SUP_SUB}">${line.over}</sup>`, `<sub style="${Style_1.Style.SUPERIMPOSE_SUP_SUB}">${line.under}</sub>`, '</span>');
            break;
          default:
            html.push(line.value);
        }
      }
      return html.join('');
    }

    var ensureCase$1 = {};

    Object.defineProperty(ensureCase$1, "__esModule", {
      value: true
    });
    ensureCase$1.ensureCase = ensureCase;
    const chemical_elements_1$8 = src$5;
    const elements = new Set(Object.keys(chemical_elements_1$8.elementsObject).sort((a, b) => b.length - a.length));
    /**
     * Ensure that the mf has been entered with capital letters and not only lowercase
     * If there is only lowercase we try to capitalize the mf
     * @param {string} mf
     */
    function ensureCase(mf) {
      for (let i = 0; i < mf.length; i++) {
        // eslint-disable-next-line unicorn/prefer-code-point
        if (mf.charCodeAt(i) > 64 && mf.charCodeAt(i) < 91) {
          return mf;
        }
      }
      let parts = mf.replaceAll(/([a-z]*)([^a-z]*)/g, '$1 $2 ').split(/ +/);
      for (let i = 0; i < parts.length; i++) {
        if (parts[i].match(/^[a-z]$/)) {
          parts[i] = parts[i].toUpperCase();
        } else if (parts[i].match(/^[a-z]+$/)) {
          let newPart = '';
          for (let j = 0; j < parts[i].length; j++) {
            let two = parts[i].slice(j, j + 2);
            let one = parts[i].charAt(j).toUpperCase();
            if (['c', 'h', 'o', 'n'].includes(two.charAt(0)) && ['h', 'o', 'n'].includes(two.charAt(1))) {
              newPart += two.toUpperCase();
              j++;
            } else {
              two = two.charAt(0).toUpperCase() + two.charAt(1);
              if (elements.has(two)) {
                newPart += two;
                j++;
              } else if (elements.has(one)) {
                newPart += one;
              } else {
                return mf;
              }
            }
          }
          parts[i] = newPart;
        }
      }
      return parts.join('');
    }

    var MF$1 = {};

    var MFInternal$1 = {};

    var flatten$1 = {};

    Object.defineProperty(flatten$1, "__esModule", {
      value: true
    });
    flatten$1.flatten = flatten;
    /**
     * This method will generate all possible molecular formulas
     * from ranges.
     * It should rather be called expand because it expands all the ranges
     * @param parsed
     * @param options
     * @return {string[]}
     */
    function flatten(parsed, options = {}) {
      const {
        groupIdentical = false,
        limit = 100000
      } = options;
      if (parsed.length === 0) return [''];
      let parts = [];
      let parenthesisLevel = 0;
      let currentPart;
      let comments = [];
      for (const entry of parsed) {
        if ((entry.kind === 'atom' || entry.kind === 'charge' || entry.kind === 'isotope' || entry.kind === 'openingParenthesis' || !currentPart) && parenthesisLevel === 0) {
          currentPart = {
            mf: '',
            min: 1,
            max: 1
          };
          parts.push(currentPart);
        }
        switch (entry.kind) {
          case 'atom':
            currentPart.mf += entry.value;
            break;
          case 'isotope':
            currentPart.mf += `[${entry.value.isotope}${entry.value.atom}]`;
            break;
          case 'multiplier':
            currentPart.mf += entry.value;
            break;
          case 'multiplierRange':
            if (parenthesisLevel !== 0) {
              throw new Error('Range definition inside parenthesis is not allowed.');
            }
            currentPart.min = entry.value.from;
            currentPart.max = entry.value.to;
            break;
          case 'openingParenthesis':
            parenthesisLevel++;
            currentPart.mf += entry.value;
            break;
          case 'charge':
            if (entry.value === 1) {
              currentPart.mf += '+';
            } else if (entry.value > 1) {
              currentPart.mf += `(+${entry.value})`;
            } else if (entry.value < 0) {
              currentPart.mf += `(${entry.value})`;
            }
            break;
          case 'closingParenthesis':
            parenthesisLevel--;
            currentPart.mf += entry.value;
            break;
          case 'comment':
            comments.push(entry.value);
            break;
          case 'text':
            break;
          default:
            throw new Error(`Could not flatten the parsed MF. Unknown kind: ${entry.kind}`);
        }
      }
      if (groupIdentical) {
        parts = optimizeRanges(parts);
      }
      const mfs = createMFs(parts, comments.join(' '), limit);
      return mfs;
    }
    /**
     * If we have many times the same mf we can combine them
     * This should only be applied if there are acutaly some ranges
     */
    function optimizeRanges(parts) {
      let newParts = [];
      let mfsObject = {};
      let hasRange = false;
      for (const mf of parts) {
        if (mf.min !== mf.max) {
          hasRange = true;
          break;
        }
      }
      if (!hasRange) return parts;
      for (const mf of parts) {
        if (!mfsObject[mf.mf]) {
          mfsObject[mf.mf] = {
            mf: mf.mf,
            min: mf.min,
            max: mf.max
          };
          newParts.push(mfsObject[mf.mf]);
        } else {
          mfsObject[mf.mf].min = mfsObject[mf.mf].min + mf.min;
          mfsObject[mf.mf].max = mfsObject[mf.mf].max + mf.max;
        }
      }
      return newParts;
    }
    function createMFs(parts, comment, limit) {
      const currents = new Array(parts.length);
      for (let i = 0; i < currents.length; i++) {
        currents[i] = parts[i].min;
      }
      /** @type {string[]} */
      const mfs = [];
      let position = 0;
      while (position < currents.length) {
        if (currents[position] < parts[position].max) {
          mfs.push(getMF(parts, currents, comment));
          currents[position]++;
          for (let i = 0; i < position; i++) {
            currents[i] = parts[i].min;
          }
          position = 0;
        } else {
          position++;
        }
        if (mfs.length > limit) {
          throw new Error(`MF.flatten generates too many fragments (over ${limit})`);
        }
      }
      mfs.push(getMF(parts, currents, comment));
      return mfs;
    }
    function getMF(parts, currents, comment) {
      let mf = '';
      for (let i = 0; i < parts.length; i++) {
        if (currents[i] === 0) {
          continue;
        }
        mf += parts[i].mf;
        if (currents[i] !== 1) {
          mf += currents[i];
        }
      }
      if (comment) mf += `$${comment}`;
      return mf;
    }

    var getEA$1 = {};

    var getIsotopeRatioInfo$1 = {};

    Object.defineProperty(getIsotopeRatioInfo$1, "__esModule", {
      value: true
    });
    getIsotopeRatioInfo$1.getIsotopeRatioInfo = getIsotopeRatioInfo;
    const chemical_elements_1$7 = src$5;
    function getIsotopeRatioInfo(value) {
      let result = {
        mass: 0,
        monoisotopicMass: 0
      };
      let element = chemical_elements_1$7.elementsAndStableIsotopesObject[value.atom];
      if (!element) throw new Error(`Element not found: ${value.atom}`);
      let isotopesArray = element.isotopes;
      let ratios = normalize$2(value.ratio);
      let max = Math.max(...ratios);
      if (ratios.length > isotopesArray.length) {
        throw new Error(`the number of specified ratios is bigger that the number of stable isotopes: ${value.atom}`);
      }
      for (let i = 0; i < ratios.length; i++) {
        result.mass += ratios[i] * isotopesArray[i].mass;
        if (max === ratios[i] && result.monoisotopicMass === 0) {
          result.monoisotopicMass = isotopesArray[i].mass;
        }
      }
      return result;
    }
    function normalize$2(array) {
      let sum = array.reduce((prev, current) => prev + current, 0);
      return array.map(a => a / sum);
    }

    Object.defineProperty(getEA$1, "__esModule", {
      value: true
    });
    getEA$1.getEA = getEA;
    const chemical_elements_1$6 = src$5;
    const chemical_groups_1$2 = src$4;
    const Kind_1$6 = Kind;
    const getIsotopeRatioInfo_1$1 = getIsotopeRatioInfo$1;
    /**
     * @param {*} parts
     */
    function getEA(parts) {
      let results = {};
      for (let part of parts) {
        for (let line of part) {
          switch (line.kind) {
            case Kind_1$6.Kind.ISOTOPE:
              {
                let isotope = chemical_elements_1$6.isotopesObject[line.value.isotope + line.value.atom];
                if (!isotope) {
                  throw new Error(`Unknown isotope: ${line.value.isotope}${line.value.atom}`);
                }
                addMass(results, line.value.atom, isotope.mass * line.multiplier);
                break;
              }
            case Kind_1$6.Kind.ISOTOPE_RATIO:
              {
                let isotopeRatioInfo = (0, getIsotopeRatioInfo_1$1.getIsotopeRatioInfo)(line.value);
                addMass(results, line.value.atom, isotopeRatioInfo.mass * line.multiplier);
                break;
              }
            case Kind_1$6.Kind.ATOM:
              {
                let element = chemical_elements_1$6.elementsObject[line.value];
                if (!element) {
                  element = chemical_groups_1$2.groupsObject[line.value];
                  if (!element) throw new Error(`Unknown element: ${line.value}`);
                  // need to explode group ????
                }
                addMass(results, line.value, element.mass * line.multiplier);
                break;
              }
            case Kind_1$6.Kind.CHARGE:
              break;
            default:
              throw new Error('partToMF unhandled Kind: ', line.kind);
          }
        }
      }
      let eas = [];
      let sum = 0;
      for (let key in results) {
        sum += results[key];
        eas.push({
          element: key,
          mass: results[key]
        });
      }
      for (const ea of eas) {
        ea.ratio = ea.mass / sum;
      }
      return eas;
    }
    function addMass(results, atom, mass) {
      if (!results[atom]) results[atom] = 0;
      results[atom] += mass;
    }

    var getElements$1 = {};

    Object.defineProperty(getElements$1, "__esModule", {
      value: true
    });
    getElements$1.getElements = getElements;
    const chemical_elements_1$5 = src$5;
    const Kind_1$5 = Kind;
    /**
     * @param {*} parts
     */
    function getElements(parts) {
      const elements = [];
      for (const part of parts) {
        for (const line of part) {
          let number = line.multiplier;
          switch (line.kind) {
            case Kind_1$5.Kind.ATOM:
              {
                let symbol = line.value;
                let element = chemical_elements_1$5.elementsObject[symbol];
                if (!element) {
                  throw new Error(`element unknown: ${symbol} - ${line}`);
                }
                addElement(elements, {
                  symbol,
                  number
                });
                break;
              }
            case Kind_1$5.Kind.ISOTOPE:
              {
                let element = chemical_elements_1$5.elementsAndIsotopesObject[line.value.atom];
                if (!element) {
                  throw new Error(`element unknown: ${part.value.atom} - ${line}`);
                }
                let isotope = element.isotopes.find(a => a.nominal === line.value.isotope);
                if (!isotope) {
                  throw new Error(`isotope unknown: ${line.value.isotope} - ${line}`);
                }
                addElement(elements, {
                  symbol: line.value.atom,
                  number,
                  isotope: line.value.isotope
                });
                break;
              }
            default:
              throw new Error(`unknown type: ${line.kind}`);
          }
        }
      }
      return elements;
    }
    function addElement(elements, newElement) {
      for (let element of elements) {
        if (element.symbol === newElement.symbol && element.isotope === newElement.isotope) {
          element.number += newElement.number;
          return;
        }
      }
      elements.push(newElement);
    }

    var getInfo$1 = {};

    var getInfoInternal$1 = {};

    var getNumberOfIsotopologues$1 = {};

    Object.defineProperty(getNumberOfIsotopologues$1, "__esModule", {
      value: true
    });
    getNumberOfIsotopologues$1.getNumberOfIsotopologues = getNumberOfIsotopologues;
    const chemical_elements_1$4 = src$5;
    /**
     * Returns the theoretical number of isotopologues for a given MF based on stable isotopes for each element.
     * This method will not take into account possible non natural isotopic composition (e.g. 13C enrichment)
     * If one element does not have any stable isotope, the result will be 0.
     * @param atoms
     * @returns
     */
    function getNumberOfIsotopologues(atoms) {
      if (Object.keys(atoms).length === 0) {
        return 0;
      }
      let result = 1;
      for (const atom in atoms) {
        const nbIsotopes = chemical_elements_1$4.elementsAndStableIsotopesObject[atom]?.isotopes.length;
        if (!nbIsotopes) {
          return 0;
        }
        const nbAtoms = atoms[atom];
        result *= getNbCombinationsPerAtom(nbAtoms, nbIsotopes);
      }
      return result;
    }
    /**
     * Returns the number of isotopologues for one specific atom
     *
     * @param nbAtoms
     * @param nbIsotopes
     * @returns
     */
    function getNbCombinationsPerAtom(nbAtoms, nbIsotopes) {
      let result = 1;
      for (let i = nbAtoms + 1; i < nbAtoms + nbIsotopes; i++) {
        result *= i;
      }
      for (let i = 2; i < nbIsotopes; i++) {
        result /= i;
      }
      return result;
    }

    var partToAtoms$1 = {};

    Object.defineProperty(partToAtoms$1, "__esModule", {
      value: true
    });
    partToAtoms$1.partToAtoms = partToAtoms;
    const Kind_js_1 = Kind;
    /** @typedef {import('./partToAtoms.types').AtomsMap} AtomsMap */
    /**
     * Convert a MF part to a map of atoms
     * This procedure will suppress the isotopes !
     * This is mainly used to make queries
     * @returns {AtomsMap}
     */
    function partToAtoms(part) {
      /** @type {AtomsMap} */
      const atoms = {};
      for (let line of part) {
        switch (line.kind) {
          case Kind_js_1.Kind.ISOTOPE:
            if (!atoms[line.value.atom]) atoms[line.value.atom] = 0;
            atoms[line.value.atom] += line.multiplier;
            break;
          case Kind_js_1.Kind.ISOTOPE_RATIO:
            if (!atoms[line.value.atom]) atoms[line.value.atom] = 0;
            atoms[line.value.atom] += line.multiplier;
            break;
          case Kind_js_1.Kind.ATOM:
            if (!atoms[line.value]) atoms[line.value] = 0;
            atoms[line.value] += line.multiplier;
            break;
          case Kind_js_1.Kind.CHARGE:
            break;
          case Kind_js_1.Kind.ANCHOR:
            break;
          default:
            throw new Error('partToMF unhandled Kind: ', line.kind);
        }
      }
      return atoms;
    }

    var partToMF$1 = {};

    Object.defineProperty(partToMF$1, "__esModule", {
      value: true
    });
    partToMF$1.partToMF = partToMF;
    const Kind_1$4 = Kind;
    function partToMF(part, options = {}) {
      let mf = [];
      for (let line of part) {
        switch (line.kind) {
          case Kind_1$4.Kind.ISOTOPE:
            if (line.multiplier !== 0) {
              mf.push(`[${line.value.isotope}${line.value.atom}]${line.multiplier !== 1 ? line.multiplier : ''}`);
            }
            break;
          case Kind_1$4.Kind.ISOTOPE_RATIO:
            if (line.multiplier !== 0) {
              mf.push(`${line.value.atom}{${line.value.ratio.join(',')}}${line.multiplier !== 1 ? line.multiplier : ''}`);
            }
            break;
          case Kind_1$4.Kind.ATOM:
            if (line.multiplier !== 0) {
              mf.push(line.value + (line.multiplier !== 1 ? line.multiplier : ''));
            }
            break;
          case Kind_1$4.Kind.CHARGE:
            if (line.value === 0 || options.neutral) break;
            mf.push(`(${line.value > 0 ? `+${line.value}` : line.value})`);
            break;
        }
      }
      return mf.join('');
    }

    Object.defineProperty(getInfoInternal$1, "__esModule", {
      value: true
    });
    getInfoInternal$1.getInfoInternal = getInfoInternal;
    const chemical_elements_1$3 = src$5;
    const chemical_groups_1$1 = src$4;
    const Kind_1$3 = Kind;
    const getIsotopeRatioInfo_1 = getIsotopeRatioInfo$1;
    const getNumberOfIsotopologues_1 = getNumberOfIsotopologues$1;
    const partToAtoms_1 = partToAtoms$1;
    const partToMF_1$1 = partToMF$1;
    /** @typedef {import('./getInfo.types').MFInfo} MFInfo */
    /** @typedef {import('./getInfo.types').MFInfoWithParts} MFInfoWithParts */
    /**
     *
     * @param {*} parts
     * @param {*} [options={}]
     * @returns {object|MFInfo|MFInfoWithParts}
     */
    function getInfoInternal(parts, options = {}) {
      let {
        customUnsaturations = {},
        emFieldName = 'monoisotopicMass',
        msemFieldName = 'observedMonoisotopicMass'
      } = options;
      if (parts.length === 0) return {};
      if (parts.length === 1) {
        let oneResult = getProcessedPart$1(parts[0], {
          customUnsaturations,
          emFieldName,
          msemFieldName
        });
        oneResult.nbIsotopologues = (0, getNumberOfIsotopologues_1.getNumberOfIsotopologues)(oneResult.atoms);
        return oneResult;
      }
      let result = {
        parts: []
      };
      for (let part of parts) {
        result.parts.push(getProcessedPart$1(part, {
          customUnsaturations,
          emFieldName,
          msemFieldName
        }));
      }
      result[emFieldName] = 0;
      result.mass = 0;
      result.charge = 0;
      result.unsaturation = 0;
      result.atoms = {};
      result.mf = result.parts.map(a => a.mf).join('.');
      for (const part of result.parts) {
        result.mass += part.mass;
        result[emFieldName] += part[emFieldName];
        result.charge += part.charge;
        result.unsaturation += part.unsaturation;
        for (const atom in part.atoms) {
          if (!result.atoms[atom]) {
            result.atoms[atom] = 0;
          }
          result.atoms[atom] += part.atoms[atom];
        }
      }
      result.nbIsotopologues = (0, getNumberOfIsotopologues_1.getNumberOfIsotopologues)(result.atoms);
      return result;
    }
    function getProcessedPart$1(part, options) {
      let {
        customUnsaturations,
        emFieldName,
        msemFieldName
      } = options;
      /** @type {MFInfo} */
      let currentPart = {
        mass: 0,
        charge: 0,
        mf: '',
        atoms: (0, partToAtoms_1.partToAtoms)(part)
      };
      currentPart[emFieldName] = 0;
      let unsaturation = 0;
      let validUnsaturation = true;
      currentPart.mf = (0, partToMF_1$1.partToMF)(part);
      for (let line of part) {
        let currentElement = '';
        switch (line.kind) {
          case Kind_1$3.Kind.ATOM:
            {
              currentElement = line.value;
              let element = chemical_elements_1$3.elementsAndIsotopesObject[line.value];
              // todo should we have a kind GROUP ?
              if (!element) {
                element = chemical_groups_1$1.groupsObject[line.value];
                if (!element) throw new Error(`Unknown element: ${line.value}`);
                if (!customUnsaturations[line.value]) {
                  customUnsaturations[line.value] = element.unsaturation;
                }
              }
              if (!element) throw new Error(`Unknown element: ${line.value}`);
              currentPart[emFieldName] += element.monoisotopicMass * line.multiplier;
              currentPart.mass += element.mass * line.multiplier;
              break;
            }
          case Kind_1$3.Kind.ISOTOPE:
            {
              currentElement = line.value.atom;
              let isotope = chemical_elements_1$3.isotopesObject[line.value.isotope + line.value.atom];
              if (!isotope) {
                throw new Error(`Unknown isotope: ${line.value.isotope}${line.value.atom}`);
              }
              currentPart[emFieldName] += isotope.mass * line.multiplier;
              currentPart.mass += isotope.mass * line.multiplier;
              break;
            }
          case Kind_1$3.Kind.ISOTOPE_RATIO:
            {
              currentElement = line.value.atom;
              let isotopeRatioInfo = (0, getIsotopeRatioInfo_1.getIsotopeRatioInfo)(line.value);
              currentPart[emFieldName] += isotopeRatioInfo[emFieldName] * line.multiplier;
              currentPart.mass += isotopeRatioInfo.mass * line.multiplier;
              break;
            }
          case Kind_1$3.Kind.CHARGE:
            currentPart.charge = line.value;
            if (validUnsaturation) {
              unsaturation -= line.value;
            }
            break;
          default:
            throw new Error('Unimplemented Kind in getInfo', line.kind);
        }
        if (currentElement) {
          if (customUnsaturations[currentElement] !== undefined) {
            unsaturation += customUnsaturations[currentElement] * line.multiplier;
          } else if (chemical_elements_1$3.unsaturationsObject[currentElement] !== undefined) {
            unsaturation += chemical_elements_1$3.unsaturationsObject[currentElement] * line.multiplier;
          } else {
            validUnsaturation = false;
          }
        }
      }
      // need to calculate the observedMonoisotopicMass
      if (currentPart.charge) {
        currentPart[msemFieldName] = (currentPart[emFieldName] - currentPart.charge * chemical_elements_1$3.ELECTRON_MASS) / Math.abs(currentPart.charge);
      }
      if (validUnsaturation) {
        currentPart.unsaturation = unsaturation / 2 + 1;
      }
      return currentPart;
    }

    Object.defineProperty(getInfo$1, "__esModule", {
      value: true
    });
    getInfo$1.getInfo = getInfo;
    const getInfoInternal_1 = getInfoInternal$1;
    function getInfo(parts, options) {
      return (0, getInfoInternal_1.getInfoInternal)(parts, options);
    }

    var getIsotopesInfo$1 = {};

    Object.defineProperty(getIsotopesInfo$1, "__esModule", {
      value: true
    });
    getIsotopesInfo$1.getIsotopesInfo = getIsotopesInfo;
    const chemical_elements_1$2 = src$5;
    const Kind_1$2 = Kind;
    /** @typedef {import('./getIsotopesInfo.types').IsotopesInfo} IsotopesInfo

    /**
     *
     * @param {*} parts
     * @returns {[]|IsotopesInfo}
     */
    function getIsotopesInfo(parts) {
      if (parts.length === 0) return [];
      if (parts.length > 1) {
        throw new Error('getIsotopesInfo can not be applied on multipart MF');
      }
      return getProcessedPart(parts[0]);
    }
    /**
     * @returns {IsotopesInfo}
     */
    function getProcessedPart(part) {
      /** @type {IsotopesInfo} */
      let result = {
        charge: 0,
        isotopes: []
      };
      for (let line of part) {
        switch (line.kind) {
          case Kind_1$2.Kind.ISOTOPE:
            {
              let isotope = chemical_elements_1$2.isotopesObject[line.value.isotope + line.value.atom];
              if (!isotope) {
                throw new Error('unknown isotope:', line.value.atom, line.value.isotope);
              }
              result.isotopes.push({
                atom: line.value.atom,
                number: line.multiplier,
                distribution: [{
                  x: isotope.mass,
                  y: 1
                }]
              });
              break;
            }
          case Kind_1$2.Kind.ISOTOPE_RATIO:
            {
              let element = chemical_elements_1$2.elementsAndStableIsotopesObject[line.value.atom];
              if (!element) throw new Error('unknown element:', line.value);
              let distribution = getDistribution(element.isotopes, line.value.ratio);
              result.isotopes.push({
                atom: line.value.atom,
                number: line.multiplier,
                distribution
              });
            }
            break;
          case Kind_1$2.Kind.ATOM:
            {
              let element = chemical_elements_1$2.elementsAndStableIsotopesObject[line.value];
              if (!element) throw new Error('unknown element:', line.value);
              result.isotopes.push({
                atom: line.value,
                number: line.multiplier,
                distribution: element.isotopes.map(e => ({
                  x: e.mass,
                  y: e.abundance
                }))
              });
              break;
            }
          case Kind_1$2.Kind.CHARGE:
            result.charge += line.value;
            break;
          default:
            throw new Error('partToMF unhandled Kind: ', line.kind);
        }
      }
      return result;
    }
    function getDistribution(isotopesArray, ratio) {
      let ratios = normalize$1(ratio);
      let result = [];
      if (ratios.length > isotopesArray.length) {
        throw new Error(`the number of specified ratios is bigger that the number of stable isotopes: ${chemical_elements_1$2.isotopesObject}`);
      }
      for (let i = 0; i < ratios.length; i++) {
        result.push({
          x: isotopesArray[i].mass,
          y: ratios[i]
        });
      }
      return result;
    }
    function normalize$1(array) {
      let sum = array.reduce((prev, current) => prev + current, 0);
      return array.map(a => a / sum);
    }

    var partsToDisplay$1 = {};

    Object.defineProperty(partsToDisplay$1, "__esModule", {
      value: true
    });
    partsToDisplay$1.partsToDisplay = partsToDisplay;
    const Kind_1$1 = Kind;
    const toDisplay_js_1$1 = toDisplay$1;
    /**
     * Converts an array of mf elements to an array of formatting information
     * @param {Array<Object>} result of the parse method
     */
    function partsToDisplay(parts) {
      let lines = [];
      for (let part of parts) {
        if (lines.length > 0) lines.push({
          kind: Kind_1$1.Kind.SALT,
          value: '•'
        });
        for (let partLine of part) {
          lines.push(partLine);
          if (partLine.multiplier) {
            lines.push({
              kind: Kind_1$1.Kind.MULTIPLIER,
              value: partLine.multiplier
            });
          }
        }
      }
      return (0, toDisplay_js_1$1.toDisplay)(lines);
    }

    var partsToMF$1 = {};

    Object.defineProperty(partsToMF$1, "__esModule", {
      value: true
    });
    partsToMF$1.partsToMF = partsToMF;
    const partToMF_1 = partToMF$1;
    function partsToMF(parts, options) {
      let mf = [];
      for (let part of parts) {
        mf.push((0, partToMF_1.partToMF)(part, options));
      }
      return mf.join(' . ');
    }

    var toParts$1 = {};

    Object.defineProperty(toParts$1, "__esModule", {
      value: true
    });
    toParts$1.toParts = toParts;
    const atom_sorter_1$1 = src$6;
    const chemical_groups_1 = src$4;
    const Kind_1 = Kind;
    /**
     *
     * @param {*} lines
     * @param {object} [options={}]
     * @param {boolean} [options.expand=true] - Should we expand the groupsObject
     */
    function toParts(lines, options = {}) {
      const {
        expand: shouldExpandgroupsObject = true
      } = options;
      let parts = [];
      let currentPart = createNewPart();
      let previousKind = Kind_1.Kind.BEGIN;
      parts.push(currentPart);
      for (let line of lines) {
        switch (line.kind) {
          case Kind_1.Kind.ATOM:
          case Kind_1.Kind.ISOTOPE_RATIO:
          case Kind_1.Kind.ISOTOPE:
          case Kind_1.Kind.CHARGE:
            currentPart.lines.push({
              ...line,
              multiplier: 1
            });
            break;
          case Kind_1.Kind.OPENING_PARENTHESIS:
            openingParenthesis(currentPart);
            break;
          case Kind_1.Kind.CLOSING_PARENTHESIS:
            closingParenthesis(currentPart);
            break;
          case Kind_1.Kind.PRE_MULTIPLIER:
            preMultiplier(currentPart, line);
            break;
          case Kind_1.Kind.MULTIPLIER:
            postMultiplier(currentPart, line.value, previousKind);
            break;
          case Kind_1.Kind.SALT:
            globalPartMultiplier(currentPart);
            currentPart = createNewPart();
            parts.push(currentPart);
            break;
          case Kind_1.Kind.ANCHOR:
            // we ignore anchors to create the parts and canonized MF
            break;
          case Kind_1.Kind.COMMENT:
            // we ignore comments to create the parts and canonized MF
            break;
          case Kind_1.Kind.TEXT:
            break;
          default:
            throw new Error(`Can not process mf having: ${line.kind}`);
        }
        previousKind = line.kind;
      }
      globalPartMultiplier(currentPart);
      if (shouldExpandgroupsObject) expandgroupsObject(parts);
      return combineAtomsIsotopesCharges(parts);
    }
    function createNewPart() {
      let currentMultiplier = {
        value: 1,
        fromIndex: 0
      };
      return {
        lines: [],
        multipliers: [currentMultiplier],
        currentMultiplier
      };
    }
    function openingParenthesis(currentPart) {
      currentPart.currentMultiplier = {
        value: 1,
        fromIndex: currentPart.lines.length
      };
      currentPart.multipliers.push(currentPart.currentMultiplier);
    }
    function closingParenthesis(currentPart) {
      currentPart.currentMultiplier = currentPart.multipliers.pop();
      if (currentPart.currentMultiplier !== 1) {
        for (let i = currentPart.currentMultiplier.fromIndex; i < currentPart.lines.length; i++) {
          currentPart.lines[i].multiplier *= currentPart.currentMultiplier.value;
        }
      }
    }
    function preMultiplier(currentPart, line) {
      currentPart.currentMultiplier.value *= line.value;
    }
    function globalPartMultiplier(currentPart) {
      for (let i = currentPart.multipliers[0].fromIndex; i < currentPart.lines.length; i++) {
        currentPart.lines[i].multiplier *= currentPart.multipliers[0].value;
      }
    }
    function postMultiplier(currentPart, value, previousKind) {
      if (previousKind === Kind_1.Kind.CLOSING_PARENTHESIS) {
        // need to apply to everything till the previous parenthesis
        for (let i = currentPart.currentMultiplier.fromIndex; i < currentPart.lines.length; i++) {
          currentPart.lines[i].multiplier *= value;
        }
      } else {
        // just applies to the previous element
        currentPart.lines.at(-1).multiplier *= value;
      }
    }
    function expandgroupsObject(parts) {
      for (let part of parts) {
        let expanded = false;
        for (let i = 0; i < part.lines.length; i++) {
          let line = part.lines[i];
          if (line.kind === Kind_1.Kind.ATOM) {
            let group = chemical_groups_1.groupsObject[line.value];
            if (group) {
              expanded = true;
              for (let element of group.elements) {
                if (element.isotope) {
                  part.lines.push({
                    kind: 'isotope',
                    value: {
                      atom: element.symbol,
                      isotope: element.isotope
                    },
                    multiplier: line.multiplier * element.number
                  });
                } else {
                  part.lines.push({
                    kind: 'atom',
                    value: element.symbol,
                    multiplier: line.multiplier * element.number
                  });
                }
              }
              part.lines[i] = undefined;
            }
          }
        }
        if (expanded) part.lines = part.lines.filter(Boolean);
      }
    }
    function combineAtomsIsotopesCharges(parts) {
      let results = [];
      for (let part of parts) {
        let result = [];
        results.push(result);
        calculateAndSortKeys(part);
        let currentKey = '';
        for (let key of part.keys) {
          if (key.key === Kind_1.Kind.CHARGE) {
            if (currentKey !== key.key) {
              result.push({
                kind: Kind_1.Kind.CHARGE,
                value: key.value.value * key.value.multiplier
              });
            } else {
              result.at(-1).value += key.value.value * key.value.multiplier;
            }
          } else if (currentKey !== key.key) {
            result.push(key.value);
          } else {
            result.at(-1).multiplier += key.value.multiplier;
          }
          currentKey = key.key;
        }
        result.sort((a, b) => {
          if (a.kind === Kind_1.Kind.CHARGE) return 1;
          if (b.kind === Kind_1.Kind.CHARGE) return -1;
          let atomA = a.kind === Kind_1.Kind.ATOM ? a.value : a.value.atom;
          let atomB = b.kind === Kind_1.Kind.ATOM ? b.value : b.value.atom;
          if (atomA !== atomB) return (0, atom_sorter_1$1.atomSorter)(atomA, atomB);
          // same atome but some isotopes ...
          if (a.kind === Kind_1.Kind.ATOM) return -1;
          if (b.kind === Kind_1.Kind.ATOM) return 1;
          if (a.kind === Kind_1.Kind.ISOTOPE) return -1;
          if (b.kind === Kind_1.Kind.ISOTOPE) return 1;
          if (a.kind === Kind_1.Kind.ISOTOPE_RATIO) return -1;
          if (b.kind === Kind_1.Kind.ISOTOPE_RATIO) return 1;
          return 0;
        });
      }
      return results;
    }
    function calculateAndSortKeys(part) {
      part.keys = [];
      for (let line of part.lines) {
        part.keys.push({
          key: getKey(line),
          value: line
        });
      }
      part.keys.sort((a, b) => stringComparator(a.key, b.key));
    }
    function getKey(line) {
      let key = [line.kind];
      switch (line.kind) {
        case Kind_1.Kind.CHARGE:
          break;
        default:
          if (typeof line.value === 'string') {
            key.push(line.value);
          } else {
            for (let prop of Object.keys(line.value).sort()) {
              key.push(line.value[prop]);
            }
          }
      }
      return key.join('-');
    }
    function stringComparator(a, b) {
      if (a < b) return -1;
      if (a > b) return 1;
      return 0;
    }

    var toText$1 = {};

    Object.defineProperty(toText$1, "__esModule", {
      value: true
    });
    toText$1.toText = toText;
    const Format_1 = Format;
    const subSuperscript_1 = subSuperscript;
    function toText(lines) {
      let text = [];
      for (let line of lines) {
        switch (line.kind) {
          case Format_1.Format.SUBSCRIPT:
            {
              const value = String(line.value);
              for (let i = 0; i < value.length; i++) {
                const char = value[i];
                if (subSuperscript_1.subscript[char]) {
                  text.push(subSuperscript_1.subscript[char]);
                } else {
                  throw new Error(`Subscript problem with: ${char}`);
                }
              }
            }
            break;
          case Format_1.Format.SUPERSCRIPT:
            {
              const value = String(line.value);
              for (let i = 0; i < value.length; i++) {
                const char = value[i];
                if (subSuperscript_1.superscript[char]) {
                  text.push(subSuperscript_1.superscript[char]);
                } else {
                  throw new Error(`Superscript problem with: ${char}`);
                }
              }
              break;
            }
          case Format_1.Format.SUPERIMPOSE:
            {
              const under = String(line.under);
              for (let i = 0; i < under.length; i++) {
                const char = under[i];
                if (subSuperscript_1.subscript[char]) {
                  text.push(subSuperscript_1.subscript[char]);
                } else {
                  throw new Error(`Subscript problem with: ${char}`);
                }
              }
              const over = String(line.over);
              for (let i = 0; i < over.length; i++) {
                const char = over[i];
                if (subSuperscript_1.superscript[char]) {
                  text.push(subSuperscript_1.superscript[char]);
                } else {
                  throw new Error(`Superscript problem with: ${char}`);
                }
              }
              break;
            }
          default:
            text.push(line.value);
        }
      }
      return text.join('');
    }

    Object.defineProperty(MFInternal$1, "__esModule", {
      value: true
    });
    MFInternal$1.MFInternal = void 0;
    const ensureCase_1 = ensureCase$1;
    const parse_1 = parse$1;
    const flatten_1 = flatten$1;
    const getEA_1 = getEA$1;
    const getElements_1 = getElements$1;
    const getInfo_1 = getInfo$1;
    const getIsotopesInfo_1 = getIsotopesInfo$1;
    const partsToDisplay_1 = partsToDisplay$1;
    const partsToMF_1 = partsToMF$1;
    const toDisplay_1 = toDisplay$1;
    const toHtml_1 = toHtml$1;
    const toParts_1 = toParts$1;
    const toText_1 = toText$1;
    /** @typedef {import('./util/getIsotopesInfo.types').IsotopesInfo} IsotopesInfo */
    /** @typedef {import('./util/getInfo.types').MFInfo} MFInfo */
    /** @typedef {import('./util/getInfo.types').MFInfoWithParts} MFInfoWithParts */
    /**
     * Class allowing to deal with molecular formula and derived information
     */
    class MFInternal {
      constructor(mf, options = {}) {
        if (options.ensureCase) {
          mf = (0, ensureCase_1.ensureCase)(mf);
        }
        this.parsed = (0, parse_1.parse)(mf);
        this.cache = {};
      }
      /**
       * Returns an array of objects with kind and value that can be used to easily
       * display the molecular formula.
       * @returns {{kind: string, value: string}[]}
       */
      toDisplay() {
        if (!this.cache.displayed) this.cache.displayed = (0, toDisplay_1.toDisplay)(this.parsed);
        return this.cache.displayed;
      }
      /**
       * Returns a string that represents the molecular formula adding subscript and superscript in HTML.
       * @returns {string}
       */
      toHtml() {
        if (!this.cache.html) {
          this.toDisplay();
          this.cache.html = (0, toHtml_1.toHtml)(this.cache.displayed);
        }
        return this.cache.html;
      }
      /**
       * Returns a string that represents the molecular formula adding subscript and superscript
       * using Unicode characters. This can not be parsed anymore so kind of dead end ...
       * @returns {string}
       */
      toText() {
        if (!this.cache.text) {
          this.toDisplay();
          this.cache.text = (0, toText_1.toText)(this.cache.displayed);
        }
        return this.cache.text;
      }
      /**
       * Similar to toText but returns a canonic string in which the atoms are sorted using the Hill system
       * @returns {string}
       */
      toCanonicText() {
        if (!this.cache.canonicText) {
          this.cache.canonicText = new MFInternal(this.toMF()).toText();
        }
        return this.cache.canonicText;
      }
      toParts(options) {
        if (options) {
          return (0, toParts_1.toParts)(this.parsed, options);
        }
        // we don't want to cache the parts if we are using options
        if (!this.cache.parts) {
          this.cache.parts = (0, toParts_1.toParts)(this.parsed, options);
        }
        return this.cache.parts;
      }
      /**
       * Returns an object with the global MF, global charge, monoisotopic mass and mass
       * as well as the same information for all the parts
       * @param {object} [options={}] options
       * @param {object} [options.customUnsaturations={}] custom unsaturations
       * @param {string} [options.emFieldName='monoisotopicMass'] name of the monoisotopic mass field
       * @param {string} [options.msemFieldName='observedMonoisotopicMass'] name of the observed monoisotopic mass field
       * @returns {MFInfo|MFInfoWithParts}
       */
      getInfo(options = {}) {
        if (!this.cache.info) {
          this.toParts();
          this.cache.info = (0, getInfo_1.getInfo)(this.cache.parts, options);
        }
        return this.cache.info;
      }
      /**
       * Returns an object with the elemental analysis
       * @returns {*[]}
       */
      getEA() {
        if (!this.cache.ea) {
          this.toParts();
          this.cache.ea = (0, getEA_1.getEA)(this.cache.parts);
        }
        return this.cache.ea;
      }
      /**
       * Get the different elements for each part
       * @returns {*[]}
       */
      getElements() {
        if (!this.cache.elements) {
          this.toParts();
          this.cache.elements = (0, getElements_1.getElements)(this.cache.parts);
        }
        return this.cache.elements;
      }
      /**
       * Returns an array with each atom and isotopic composition
       * @returns {IsotopesInfo}
       */
      getIsotopesInfo(options = {}) {
        if (!this.cache.isotopesInfo) {
          this.toParts();
          this.cache.isotopesInfo = (0, getIsotopesInfo_1.getIsotopesInfo)(this.cache.parts, options);
        }
        return this.cache.isotopesInfo;
      }
      /**
       * Get a canonized MF
       * @returns {string}
       */
      toMF() {
        if (!this.cache.mf) {
          this.toParts();
          this.cache.mf = (0, partsToMF_1.partsToMF)(this.cache.parts);
        }
        return this.cache.mf;
      }
      /**
       * Get a canonized MF
       * @returns {string}
       */
      toNeutralMF() {
        if (!this.cache.neutralMF) {
          this.toParts();
          this.cache.neutralMF = (0, partsToMF_1.partsToMF)(this.cache.parts, {
            neutral: true
          });
        }
        return this.cache.neutralMF;
      }
      canonize() {
        this.toParts();
        this.cache.displayed = (0, partsToDisplay_1.partsToDisplay)(this.cache.parts);
        this.cache.html = undefined;
      }
      flatten(options) {
        return (0, flatten_1.flatten)(this.parsed, options);
      }
    }
    MFInternal$1.MFInternal = MFInternal;

    Object.defineProperty(MF$1, "__esModule", {
      value: true
    });
    MF$1.MF = void 0;
    const MFInternal_1 = MFInternal$1;
    class MF {
      internal;
      constructor(mf, options = {}) {
        this.internal = new MFInternal_1.MFInternal(mf, options);
      }
      /**
       * Returns an array of objects with kind and value that can be used to easily
       * display the molecular formula.
       */
      toDisplay() {
        return this.internal.toDisplay();
      }
      /**
       * Returns a string that represents the molecular formula adding subscript and superscript in HTML.
       */
      toHtml() {
        return this.internal.toHtml();
      }
      /**
       * Returns a string that represents the molecular formula adding subscript and superscript
       * using Unicode characters. This can not be parsed anymore so kind of dead end ...
       */
      toText() {
        return this.internal.toText();
      }
      /**
       * Similar to toText but returns a canonic string in which the atoms are sorted using the Hill system
       */
      toCanonicText() {
        return this.internal.toCanonicText();
      }
      toParts(options) {
        return this.internal.toParts(options);
      }
      /**
       * Returns an object with the global MF, global charge, monoisotopic mass and mass
       * as well as the same information for all the parts
       */
      getInfo(options) {
        return this.internal.getInfo(options);
      }
      /**
       * Returns an object with the elemental analysis
       */
      getEA() {
        return this.internal.getEA();
      }
      /**
       * Get the different elements for each part
       */
      getElements() {
        return this.internal.getElements();
      }
      /**
       * Returns an array with each atom and isotopic composition
       */
      getIsotopesInfo(options = {}) {
        return this.internal.getIsotopesInfo(options);
      }
      /**
       * Get a canonized parsable Molecule Formula
       */
      toMF() {
        return this.internal.toMF();
      }
      /**
       * Get a canonized MF
       */
      toNeutralMF() {
        return this.internal.toNeutralMF();
      }
      canonize() {
        return this.internal.canonize();
      }
      flatten(options) {
        return this.internal.flatten(options);
      }
    }
    MF$1.MF = MF;

    var parseToHtml$1 = {};

    Object.defineProperty(parseToHtml$1, "__esModule", {
      value: true
    });
    parseToHtml$1.parseToHtml = parseToHtml;
    const parse_js_1 = parse$1;
    const toDisplay_js_1 = toDisplay$1;
    const toHtml_js_1 = toHtml$1;
    /**
     * Parse a molecular formula and converts it to an HTML code
     * @param {String} mf String containing the molecular formula
     */
    function parseToHtml(mf) {
      let parsed = (0, parse_js_1.parse)(mf);
      let display = (0, toDisplay_js_1.toDisplay)(parsed);
      return (0, toHtml_js_1.toHtml)(display);
    }

    (function (exports) {

      var __createBinding = src$7 && src$7.__createBinding || (Object.create ? function (o, m, k, k2) {
        if (k2 === undefined) k2 = k;
        var desc = Object.getOwnPropertyDescriptor(m, k);
        if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
          desc = {
            enumerable: true,
            get: function () {
              return m[k];
            }
          };
        }
        Object.defineProperty(o, k2, desc);
      } : function (o, m, k, k2) {
        if (k2 === undefined) k2 = k;
        o[k2] = m[k];
      });
      var __exportStar = src$7 && src$7.__exportStar || function (m, exports) {
        for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
      };
      Object.defineProperty(exports, "__esModule", {
        value: true
      });
      __exportStar(parse$1, exports);
      __exportStar(subSuperscript, exports);
      __exportStar(toDisplay$1, exports);
      __exportStar(isMF$1, exports);
      __exportStar(toHtml$1, exports);
      __exportStar(Kind, exports);
      __exportStar(Format, exports);
      __exportStar(Style, exports);
      __exportStar(ensureCase$1, exports);
      __exportStar(MF$1, exports);
      __exportStar(parseToHtml$1, exports);
    })(src$7);

    var fromMonoisotopicMass$1 = {};

    var src$3 = {};

    var findMFs$1 = {};

    var findMFsSync$1 = {};

    var src$2 = {};

    var generalMatcher$1 = {};

    var unsaturationMatcher$1 = {};

    /**
     * @param {object}   [entry={}]
     * @param {object}   [options={}]
     * @param {number}   [options.min=-Infinity] - Minimal unsaturation
     * @param {number}   [options.max=+Infinity] - Maximal unsaturation
     * @param {boolean}   [options.onlyInteger=false] - Integer unsaturation
     * @param {boolean}   [options.onlyNonInteger=false] - Non integer unsaturation
     * @return {boolean}
     */
    Object.defineProperty(unsaturationMatcher$1, "__esModule", {
      value: true
    });
    unsaturationMatcher$1.unsaturationMatcher = unsaturationMatcher;
    function unsaturationMatcher(entry, options = {}) {
      const {
        min = Number.MIN_SAFE_INTEGER,
        max = Number.MAX_SAFE_INTEGER,
        onlyInteger,
        onlyNonInteger
      } = options;
      if (entry.unsaturation !== undefined) {
        if (entry.unsaturation < min || entry.unsaturation > max) return false;
        if (onlyInteger && !Number.isInteger(entry.unsaturation)) return false;
        if (onlyNonInteger && Number.isInteger(entry.unsaturation)) return false;
      }
      return true;
    }

    Object.defineProperty(generalMatcher$1, "__esModule", {
      value: true
    });
    generalMatcher$1.generalMatcher = generalMatcher;
    const unsaturationMatcher_js_1$1 = unsaturationMatcher$1;
    /**
     * Returns true if the entry containing MF information match
     * @param {object}   [entry={}] - object containing mw, ...
     * @param {object}   [options={}]
     * @param {number}   [options.minMW=0] - Minimal molecular weight
     * @param {number}   [options.maxMW=+Infinity] - Maximal molecular weight
     * @param {number}   [options.minEM=0] - Minimal monoisotopic mass
     * @param {number}   [options.maxEM=+Infinity] - Maximal monoisotopic mass
     * @param {number}   [options.minCharge=-Infinity] - Minimal charge
     * @param {number}   [options.maxCharge=+Infinity] - Maximal charge
     * @param {boolean}  [options.absoluteCharge=false] - If true, the charge is absolute (so between 0 and +Infinity by default)
     * @param {object}   [options.unsaturation={}]
     * @param {number}   [options.unsaturation.min=-Infinity] - Minimal unsaturation
     * @param {number}   [options.unsaturation.max=+Infinity] - Maximal unsaturation
     * @param {boolean}   [options.unsaturation.onlyInteger=false] - Integer unsaturation
     * @param {boolean}   [options.unsaturation.onlyNonInteger=false] - Non integer unsaturation
     * @param {object}   [options.atoms] - object of atom:{min, max}
     * @return {boolean}
     */
    function generalMatcher(entry, options = {}) {
      const {
        minMW = 0,
        maxMW = +Infinity,
        minEM = 0,
        maxEM = +Infinity,
        minCharge = Number.MIN_SAFE_INTEGER,
        maxCharge = Number.MAX_SAFE_INTEGER,
        absoluteCharge = false,
        unsaturation = {},
        atoms
      } = options;
      if (entry.mw !== undefined && (entry.mw < minMW || entry.mw > maxMW)) {
        return false;
      }
      if (entry.em !== undefined && (entry.em < minEM || entry.em > maxEM)) {
        return false;
      }
      if (entry.charge !== undefined) {
        let charge = absoluteCharge ? Math.abs(entry.charge) : entry.charge;
        if (charge < minCharge || charge > maxCharge) return false;
      }
      if (unsaturation !== undefined && entry.unsaturation !== undefined && !(0, unsaturationMatcher_js_1$1.unsaturationMatcher)(entry, unsaturation)) {
        return false;
      }
      if (entry.atoms !== undefined && atoms) {
        // all the atoms of the entry must fit in the range
        for (let atom in entry.atoms) {
          if (!atoms[atom]) return false;
          if (entry.atoms[atom] < atoms[atom].min) return false;
          if (entry.atoms[atom] > atoms[atom].max) return false;
        }
      }
      return true;
    }

    var msemMatcher$1 = {};

    var src$1 = {};

    var ensureUppercaseSequence$1 = {};

    /**
     * Ensure that the sequence is in uppercase taking into account possible modifications
     */
    Object.defineProperty(ensureUppercaseSequence$1, "__esModule", {
      value: true
    });
    ensureUppercaseSequence$1.ensureUppercaseSequence = ensureUppercaseSequence;
    function ensureUppercaseSequence(sequence) {
      let parenthesisCounter = 0;
      let parts = [];
      let part = '';
      for (let i = 0; i < sequence.length; i++) {
        let currentSymbol = sequence[i];
        if (currentSymbol === '(' && parenthesisCounter === 0 && part) {
          parts.push(part);
          part = currentSymbol;
        } else if (currentSymbol === ')' && parenthesisCounter === 0) {
          part += currentSymbol;
          parts.push(part);
          part = '';
        } else {
          part += currentSymbol;
        }
      }
      if (part) parts.push(part);
      for (let i = 0; i < parts.length; i++) {
        if (!parts[i].startsWith('(') && parts[i].match(/^[a-z]+$/)) {
          parts[i] = parts[i].toUpperCase();
        }
      }
      return parts.join('');
    }

    var getEutrophicationPotential$1 = {};

    Object.defineProperty(getEutrophicationPotential$1, "__esModule", {
      value: true
    });
    getEutrophicationPotential$1.getEutrophicationPotential = getEutrophicationPotential;
    const mf_parser_1$7 = src$7;
    function getEutrophicationPotential(mfString) {
      let parsed = new mf_parser_1$7.MF(mfString);
      let info = parsed.getInfo();
      let mf = info.mf;
      let mw = info.mass;
      let nC = info.atoms.C || 0;
      let nO = info.atoms.O || 0;
      let nN = info.atoms.N || 0;
      let nP = info.atoms.P || 0;
      let nH = info.atoms.H || 0;
      let atoms = Object.keys(info.atoms);
      for (let atom of atoms) {
        if (!['C', 'H', 'N', 'O', 'P'].includes(atom)) {
          return {
            log: `EP can not be calculated because the MF contains the element: ${atom}`
          };
        }
      }
      let vRef = 1;
      let mwRef = 94.97;
      let thOD = nC + (nH - 3 * nN) / 4 - nO / 2;
      let v = nP + nN / 16 + thOD / 138;
      let ep = v / mw / (vRef / mwRef);
      return {
        v,
        thOD,
        ep,
        mf,
        mw,
        log: 'Successful calculation'
      };
    }

    var getMsInfo$1 = {};

    var getMsem$1 = {};

    Object.defineProperty(getMsem$1, "__esModule", {
      value: true
    });
    getMsem$1.getMsem = getMsem;
    const chemical_elements_1$1 = src$5;
    function getMsem(em, charge) {
      if (charge > 0) {
        return em / charge - chemical_elements_1$1.ELECTRON_MASS;
      } else if (charge < 0) {
        return em / (charge * -1) + chemical_elements_1$1.ELECTRON_MASS;
      } else {
        return 0;
      }
    }

    Object.defineProperty(getMsInfo$1, "__esModule", {
      value: true
    });
    getMsInfo$1.getMsInfo = getMsInfo;
    const getMsem_1 = getMsem$1;
    /**
     * Returns an object containing:
     * {ms: {em, charge, ionization}, ionization: {}}
     * We return the ionization in order to know which one has been selected
     */
    function getMsInfo(entry, options = {}) {
      const {
        allowNeutralMolecules,
        ionization = {
          mf: '',
          em: 0,
          charge: 0
        },
        forceIonization = false,
        targetMass
      } = options;
      let realIonization = ionization;
      if (!forceIonization && entry.ionization && entry.ionization.mf !== '') {
        realIonization = entry.ionization;
      }
      let ms = {
        ionization: realIonization.mf,
        em: 0,
        charge: entry.charge + realIonization.charge
      };
      if (ms.charge !== 0) {
        ms.em = (0, getMsem_1.getMsem)(entry.em + realIonization.em, ms.charge);
      } else if (allowNeutralMolecules) {
        ms.em = entry.em + realIonization.em;
      }
      if (targetMass) {
        ms.delta = targetMass - ms.em;
        ms.ppm = (targetMass - ms.em) / ms.em * 1e6;
      }
      return {
        ms,
        ionization: realIonization
      };
    }

    var getRangesForFragment$1 = {};

    Object.defineProperty(getRangesForFragment$1, "__esModule", {
      value: true
    });
    getRangesForFragment$1.getRangesForFragment = getRangesForFragment;
    const mf_parser_1$6 = src$7;
    function getRangesForFragment(ranges) {
      ranges = structuredClone(ranges);
      if (typeof ranges === 'string') {
        // need to convert to ranges
        let parsed = (0, mf_parser_1$6.parse)(ranges.replaceAll(/[\t\n\r ]/g, ''));
        let newRanges = [];
        // example ClBr2(CH2)0-2NO
        // the idea is that as long as we don't have a range we don't really care
        // there is a limitation is that the range has to be first level of parenthesis
        let parenthesisLevel = 0;
        let currentMF = ''; // start at an atom first level or a parenthesis
        for (let item of parsed) {
          switch (item.kind) {
            case mf_parser_1$6.Kind.ATOM:
              if (parenthesisLevel === 0 && currentMF) {
                newRanges.push({
                  mf: currentMF
                });
                currentMF = '';
              }
              currentMF += item.value;
              break;
            case mf_parser_1$6.Kind.ISOTOPE:
              if (parenthesisLevel === 0 && currentMF) {
                newRanges.push({
                  mf: currentMF
                });
                currentMF = '';
              }
              currentMF += `[${item.value.isotope}${item.value.atom}]`;
              break;
            case mf_parser_1$6.Kind.MULTIPLIER:
              if (parenthesisLevel === 0 && currentMF) {
                newRanges.push({
                  mf: currentMF,
                  max: item.value
                });
                currentMF = '';
              } else {
                currentMF += item.value;
              }
              break;
            case mf_parser_1$6.Kind.MULTIPLIER_RANGE:
              if (parenthesisLevel !== 0) {
                throw new Error('Range multiplier can only be at the first level');
              }
              newRanges.push({
                mf: currentMF,
                min: item.value.from,
                max: item.value.to
              });
              currentMF = '';
              break;
            case mf_parser_1$6.Kind.OPENING_PARENTHESIS:
              if (parenthesisLevel === 0 && currentMF) {
                newRanges.push({
                  mf: currentMF
                });
                currentMF = '';
              }
              parenthesisLevel++;
              currentMF += '(';
              break;
            case mf_parser_1$6.Kind.CLOSING_PARENTHESIS:
              parenthesisLevel--;
              currentMF += ')';
              break;
            default:
              throw new Error(`can not preprocess ${ranges}`);
          }
        }
        if (currentMF) {
          newRanges.push({
            mf: currentMF
          });
        }
        ranges = newRanges;
      }
      let possibilities = [];
      for (const range of ranges) {
        if (range.max === 0) continue;
        let max = range.max === undefined ? 1 : range.max;
        possibilities.push(`${range.mf}0-${max}`);
      }
      return possibilities.join(' ');
    }

    var MFRange$1 = {};

    Object.defineProperty(MFRange$1, "__esModule", {
      value: true
    });
    MFRange$1.MFRange = void 0;
    const mf_parser_1$5 = src$7;
    class MFRange {
      constructor(mf) {
        this.range = getRange(mf);
      }
      isInRange(mf) {
        const target = getRange(mf);
        let rangeIndex = 0;
        let targetIndex = 0;
        while (rangeIndex < this.range.length && targetIndex < target.length) {
          const rangeElement = this.range[rangeIndex];
          const targetElement = target[targetIndex];
          if (rangeElement.element === targetElement.element) {
            // Check if the target's range is within the range's range
            if (targetElement.range.from >= rangeElement.range.from && targetElement.range.to <= rangeElement.range.to) {
              targetIndex++;
              rangeIndex++;
            } else {
              return false; // Target element is out of range
            }
          } else if (rangeElement.element < targetElement.element) {
            if (rangeElement.range.from > 0) {
              return false; // Remaining range elements must have a minimum of 1
            }
            rangeIndex++;
          } else {
            return false; // Target element is not in the range
          }
        }
        if (rangeIndex < this.range.length) {
          if (this.range[rangeIndex].range.from > 0) {
            return false; // Remaining range elements must have a minimum of 1
          }
          rangeIndex++;
        }
        if (targetIndex < target.length) {
          return false; // Not all target elements were matched
        }
        return true; // All target elements are within the range
      }
    }
    MFRange$1.MFRange = MFRange;
    function getRange(mf) {
      const parsed = (0, mf_parser_1$5.parse)(mf, {
        expandGroups: true,
        simplify: true
      });
      const ranges = [];
      let currentRange;
      for (const item of parsed) {
        switch (item.kind) {
          case 'atom':
            currentRange = {
              element: item.value,
              range: {
                from: 1,
                to: 1
              }
            };
            ranges.push(currentRange);
            break;
          case 'isotope':
            currentRange = {
              element: item.value.isotope + item.value.atom,
              range: {
                from: 1,
                to: 1
              }
            };
            break;
          case 'multiplier':
            if (currentRange) {
              currentRange.range.from = item.value;
              currentRange.range.to = item.value;
            } else {
              throw new Error(`MFRange: Multiplier without preceding element in MF: ${mf}`);
            }
            break;
          case 'multiplierRange':
            if (currentRange) {
              currentRange.range.from = item.value.from;
              currentRange.range.to = item.value.to;
            } else {
              throw new Error(`MFRange: Multiplier range without preceding element in MF: ${mf}`);
            }
            break;
          default:
            throw new Error(`MFRange: Unknown kind: ${item.kind} in MF: ${mf}`);
        }
      }
      return ranges.sort((a, b) => {
        if (a.element < b.element) return -1;
        if (a.element > b.element) return 1;
        return 0;
      });
    }

    var mfDiff$1 = {};

    Object.defineProperty(mfDiff$1, "__esModule", {
      value: true
    });
    mfDiff$1.mfDiff = mfDiff;
    const mf_parser_1$4 = src$7;
    function mfDiff(mfString1, mfString2) {
      let mf1 = new mf_parser_1$4.MF(mfString1).getInfo().atoms;
      let mf2 = new mf_parser_1$4.MF(mfString2).getInfo().atoms;
      let atoms = Object.keys(mf1);
      for (const atom of Object.keys(mf2)) {
        if (!atoms.includes(atom)) atoms.push(atom);
      }
      let mf = '';
      for (let atom of atoms) {
        let diff = (mf1[atom] || 0) - (mf2[atom] || 0);
        if (diff) mf += atom + diff;
      }
      return new mf_parser_1$4.MF(mf).toMF();
    }

    var preprocessIonizations$1 = {};

    Object.defineProperty(preprocessIonizations$1, "__esModule", {
      value: true
    });
    preprocessIonizations$1.preprocessIonizations = preprocessIonizations;
    const mf_parser_1$3 = src$7;
    function preprocessIonizations(ionizationsString = '') {
      if (Array.isArray(ionizationsString)) return ionizationsString;
      const ionizations = ionizationsString.split(/ *[\t\n\r,.;]+ */);
      // it is allowed to have ranges in Ionizations. We need to explode them.
      const results = [];
      for (const ionization of ionizations) {
        const parts = new mf_parser_1$3.MF(ionization).flatten();
        for (const part of parts) {
          const info = new mf_parser_1$3.MF(part).getInfo();
          results.push({
            mf: part,
            em: info.monoisotopicMass,
            charge: info.charge,
            atoms: info.atoms
          });
        }
      }
      return results;
    }

    var preprocessRanges$1 = {};

    Object.defineProperty(preprocessRanges$1, "__esModule", {
      value: true
    });
    preprocessRanges$1.preprocessRanges = preprocessRanges;
    const mf_parser_1$2 = src$7;
    function preprocessRanges(ranges) {
      ranges = structuredClone(ranges);
      if (typeof ranges === 'string') {
        // need to convert to ranges
        let parsed = (0, mf_parser_1$2.parse)(ranges.replaceAll(/[\t\n\r ]/g, ''));
        let newRanges = [];
        let current = {
          mf: '',
          min: 1,
          max: 1
        };
        // example ClBr2(CH2)0-2NO
        // the idea is that has long as we don't have a range we don't really care
        // but the range should only applied on the previous atom, group or parenthesis
        // there is a limitation is that the range has to be first level of parenthesis
        let parenthesisLevel = 0;
        let currentMF = ''; // start at an atom first level or a parenthesis
        for (let item of parsed) {
          switch (item.kind) {
            case mf_parser_1$2.Kind.ATOM:
              if (parenthesisLevel === 0 && currentMF) {
                current.mf += currentMF;
                currentMF = '';
              }
              currentMF += item.value;
              break;
            case mf_parser_1$2.Kind.ISOTOPE:
              if (parenthesisLevel === 0 && currentMF) {
                current.mf += currentMF;
                currentMF = '';
              }
              currentMF += `[${item.value.isotope}${item.value.atom}]`;
              break;
            case mf_parser_1$2.Kind.MULTIPLIER:
              if (parenthesisLevel === 0 && currentMF) {
                current.mf += currentMF + item.value;
                currentMF = '';
              } else {
                currentMF += item.value;
              }
              break;
            case mf_parser_1$2.Kind.MULTIPLIER_RANGE:
              if (parenthesisLevel !== 0) {
                throw new Error('Range multiplier can only be at the first level');
              }
              newRanges.push({
                mf: currentMF,
                min: item.value.from,
                max: item.value.to
              });
              currentMF = '';
              break;
            case mf_parser_1$2.Kind.OPENING_PARENTHESIS:
              if (parenthesisLevel === 0 && currentMF) {
                current.mf += currentMF;
                currentMF = '';
              }
              parenthesisLevel++;
              currentMF += '(';
              break;
            case mf_parser_1$2.Kind.CLOSING_PARENTHESIS:
              parenthesisLevel--;
              currentMF += ')';
              break;
            default:
              throw new Error(`can not preprocess ${ranges}`);
          }
        }
        if (currentMF) {
          current.mf += currentMF;
        }
        if (current.mf) {
          newRanges.push(current);
        }
        ranges = newRanges;
      }
      let possibilities = [];
      for (let i = 0; i < ranges.length; i++) {
        let range = ranges[i];
        let min = range.min === undefined ? 0 : range.min;
        let max = range.max === undefined ? 1 : range.max;
        let possibility = {
          mf: range.mf,
          originalMinCount: min,
          // value defined by the user
          originalMaxCount: max,
          // value defined by the user
          currentMinCount: min,
          currentMaxCount: max,
          currentCount: min,
          currentMonoisotopicMass: 0,
          currentCharge: 0,
          currentUnsaturation: 0,
          initialOrder: i,
          minInnerMass: 0,
          maxInnerMass: 0,
          minInnerCharge: 0,
          maxInnerCharge: 0,
          minCharge: 0,
          maxCharge: 0,
          minMass: 0,
          maxMass: 0,
          innerCharge: false
        };
        possibilities.push(possibility);
        let info = new mf_parser_1$2.MF(range.mf).getInfo();
        possibility.em = range.em || info.monoisotopicMass;
        possibility.charge = range.charge || info.charge;
        possibility.unsaturation = range.unsaturation === undefined ? (info.unsaturation - 1) * 2 : range.unsaturation;
        possibility.atoms = info.atoms;
        if (possibility.mf !== info.mf) {
          possibility.isGroup = true;
        }
      }
      possibilities = possibilities.filter(r => r.originalMinCount !== 0 || r.originalMaxCount !== 0);
      // we will sort the way we analyse the data
      // 1. The one possibility parameter
      // 2. The charged part
      // 3. Decreasing em
      possibilities.sort((a, b) => {
        if (a.originalMinCount === a.originalMaxCount) return -1; // should be in front, they are 'static'
        if (b.originalMinCount === b.originalMaxCount) return 1;
        if (a.charge && b.charge) {
          if (Math.abs(a.charge) > Math.abs(b.charge)) return -1;
          if (Math.abs(a.charge) < Math.abs(b.charge)) return 1;
          return b.em - a.em;
        }
        if (a.charge) return -1;
        if (b.charge) return 1;
        return b.em - a.em;
      });
      // we calculate couple of fixed values
      for (let i = 0; i < possibilities.length; i++) {
        for (let j = i; j < possibilities.length; j++) {
          let possibility = possibilities[j];
          if (possibility.em > 0) {
            possibilities[i].minMass += possibility.em * possibility.originalMinCount;
            possibilities[i].maxMass += possibility.em * possibility.originalMaxCount;
          } else {
            possibilities[i].minMass += possibility.em * possibility.originalMaxCount;
            possibilities[i].maxMass += possibility.em * possibility.originalMinCount;
          }
          if (possibility.charge > 0) {
            possibilities[i].minCharge += possibility.charge * possibility.originalMinCount;
            possibilities[i].maxCharge += possibility.charge * possibility.originalMaxCount;
          } else {
            possibilities[i].minCharge += possibility.charge * possibility.originalMaxCount;
            possibilities[i].maxCharge += possibility.charge * possibility.originalMinCount;
          }
        }
      }
      for (let i = 0; i < possibilities.length; i++) {
        if (i < possibilities.length - 1) {
          let possibility = possibilities[i];
          let innerPossibility = possibilities[i + 1];
          possibility.minInnerMass = innerPossibility.minMass;
          possibility.maxInnerMass = innerPossibility.maxMass;
          possibility.minInnerCharge = innerPossibility.minCharge;
          possibility.maxInnerCharge = innerPossibility.maxCharge;
          if (possibility.minInnerCharge || possibility.maxInnerCharge) {
            possibility.innerCharge = true;
          }
        }
      }
      return possibilities;
    }

    (function (exports) {

      var __createBinding = src$1 && src$1.__createBinding || (Object.create ? function (o, m, k, k2) {
        if (k2 === undefined) k2 = k;
        var desc = Object.getOwnPropertyDescriptor(m, k);
        if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
          desc = {
            enumerable: true,
            get: function () {
              return m[k];
            }
          };
        }
        Object.defineProperty(o, k2, desc);
      } : function (o, m, k, k2) {
        if (k2 === undefined) k2 = k;
        o[k2] = m[k];
      });
      var __exportStar = src$1 && src$1.__exportStar || function (m, exports) {
        for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
      };
      Object.defineProperty(exports, "__esModule", {
        value: true
      });
      __exportStar(ensureUppercaseSequence$1, exports);
      __exportStar(getEutrophicationPotential$1, exports);
      __exportStar(getMsInfo$1, exports);
      __exportStar(getMsem$1, exports);
      __exportStar(getRangesForFragment$1, exports);
      __exportStar(MFRange$1, exports);
      __exportStar(mfDiff$1, exports);
      __exportStar(preprocessIonizations$1, exports);
      __exportStar(preprocessRanges$1, exports);
    })(src$1);

    Object.defineProperty(msemMatcher$1, "__esModule", {
      value: true
    });
    msemMatcher$1.msemMatcher = msemMatcher;
    const mf_utilities_1$1 = src$1;
    const ml_spectra_processing_1$7 = require$$0$3;
    const unsaturationMatcher_js_1 = unsaturationMatcher$1;
    /**
     * @typedef {object} MSEMFilterOptions
     * @property {object}         [ionization={ mf: '', em: 0, charge: 0 }] - ionization method
     * @property {boolean}        [forceIonization=false] - If true ignore existing ionizations
     * @property {number}         [precision=1000] - The precision on the experimental mass
     * @property {number}         [targetMass] - Target mass, allows to calculate error and filter results
     * @property {number[]}       [targetMasses] - Target masses: SORTED array of numbers
     * @property {number[]}       [targetIntensities] - Target intensities: SORTED array of numbers
     * @property {number}         [minMW=-Infinity] - Minimal monoisotopic mass
     * @property {number}         [maxMW=+Infinity] - Maximal monoisotopic mass
     * @property {number}         [minEM=-Infinity] - Minimal monoisotopic mass
     * @property {number}         [maxEM=+Infinity] - Maximal monoisotopic mass
     * @property {number}         [minMSEM=-Infinity] - Minimal monoisotopic mass observed by mass
     * @property {number}         [maxMSEM=+Infinity] - Maximal monoisotopic mass observed by mass
     * @property {number}         [minCharge=-Infinity] - Minimal charge
     * @property {number}         [maxCharge=+Infinity] - Maximal charge
     * @property {boolean}        [absoluteCharge=false] - If true, the charge is absolute (so between 0 and +Infinity by default)
     * @property {boolean}        [allowNegativeAtoms=false] - Allow to have negative number of atoms
     * @property {object}         [unsaturation={}]
     * @property {number}         [unsaturation.min=-Infinity] - Minimal unsaturation
     * @property {number}         [unsaturation.max=+Infinity] - Maximal unsaturation
     * @property {boolean}        [unsaturation.onlyInteger=false] - Integer unsaturation
     * @property {boolean}        [unsaturation.onlyNonInteger=false] - Non integer unsaturation
     * @property {boolean}        [atoms] - object of atom:{min, max}
     * @property {Function}       [callback] - a function that contains information about the current MF
     */
    /**
     * @param {object}             [entry={}]
     * @param {MSEMFilterOptions}  [options={}]
     * @return {boolean}
     */
    /**
     * We always recalculate msem
     */
    function msemMatcher(entry, options = {}) {
      const {
        ionization = {
          mf: '',
          em: 0,
          charge: 0,
          atoms: {}
        },
        forceIonization = false,
        precision = 1000,
        minCharge = Number.MIN_SAFE_INTEGER,
        maxCharge = Number.MAX_SAFE_INTEGER,
        absoluteCharge = false,
        unsaturation = {},
        targetMass,
        // if present we will calculate the errors
        targetMasses,
        // if present we will calculate the smallest error
        targetIntensities,
        // if present it will be added in the report
        minEM = -Infinity,
        maxEM = +Infinity,
        minMSEM = -Infinity,
        maxMSEM = +Infinity,
        minMW = -Infinity,
        maxMW = +Infinity,
        allowNegativeAtoms = false,
        atoms,
        callback
      } = options;
      if (entry.mw !== undefined && (entry.mw < minMW || entry.mw > maxMW)) {
        return false;
      }
      let msInfo = (0, mf_utilities_1$1.getMsInfo)(entry, {
        ionization,
        forceIonization,
        targetMass
      });
      let ms = msInfo.ms;
      if (entry.em !== undefined && (entry.em < minEM || entry.em > maxEM)) {
        return false;
      }
      if (ms.em !== undefined && (ms.em < minMSEM || ms.em > maxMSEM)) {
        return false;
      }
      if (targetMass && Math.abs(ms.ppm) > precision) {
        return false;
      }
      if (ms.charge !== undefined) {
        let charge = absoluteCharge ? Math.abs(ms.charge) : ms.charge;
        if (charge < minCharge || charge > maxCharge) return false;
      }
      if (unsaturation !== undefined && entry.unsaturation !== undefined && !(0, unsaturationMatcher_js_1.unsaturationMatcher)(entry, unsaturation)) {
        return false;
      }
      if (entry.atoms !== undefined && atoms) {
        // all the atoms of the entry must fit in the range
        for (let atom in entry.atoms) {
          if (!atoms[atom]) return false;
          if (entry.atoms[atom] < atoms[atom].min) return false;
          if (entry.atoms[atom] > atoms[atom].max) return false;
        }
      }
      if (entry.atoms !== undefined && !allowNegativeAtoms) {
        const ionizationAtoms = msInfo.ionization && msInfo.ionization.atoms || {};
        const atomKeys = new Set(Object.keys(ionizationAtoms).concat(Object.keys(entry.atoms)));
        for (let atom of atomKeys) {
          if ((entry.atoms[atom] || 0) + (ionizationAtoms[atom] || 0) < 0) {
            return false;
          }
        }
      }
      if (targetMasses && targetMasses.length > 0) {
        let index = (0, ml_spectra_processing_1$7.xFindClosestIndex)(targetMasses, ms.em);
        let closestMass = targetMasses[index];
        msInfo = (0, mf_utilities_1$1.getMsInfo)(entry, {
          ionization,
          forceIonization,
          targetMass: closestMass
        });
        msInfo.ms.target = {
          mass: closestMass
        };
        if (targetIntensities) {
          msInfo.ms.target.intensity = targetIntensities[index];
        }
        // need to find the closest targetMasses
        if (Math.abs(msInfo.ms.ppm) > precision) return false;
      }
      if (callback && !callback(entry)) return false;
      return msInfo;
    }

    (function (exports) {

      var __createBinding = src$2 && src$2.__createBinding || (Object.create ? function (o, m, k, k2) {
        if (k2 === undefined) k2 = k;
        var desc = Object.getOwnPropertyDescriptor(m, k);
        if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
          desc = {
            enumerable: true,
            get: function () {
              return m[k];
            }
          };
        }
        Object.defineProperty(o, k2, desc);
      } : function (o, m, k, k2) {
        if (k2 === undefined) k2 = k;
        o[k2] = m[k];
      });
      var __exportStar = src$2 && src$2.__exportStar || function (m, exports) {
        for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
      };
      Object.defineProperty(exports, "__esModule", {
        value: true
      });
      __exportStar(generalMatcher$1, exports);
      __exportStar(msemMatcher$1, exports);
    })(src$2);

    var TargetMassCache$1 = {};

    Object.defineProperty(TargetMassCache$1, "__esModule", {
      value: true
    });
    TargetMassCache$1.TargetMassCache = void 0;
    const chemical_elements_1 = src$5;
    /**
     * returns all the possible neutral mass for a defined experimental (targetMass) mass
     */
    class TargetMassCache {
      /**
       * @param {number} targetMass
       * @param {Array} possibilities
       * @param {object} [options={}]
       * @param {boolean} [options.allowNeutral=false]
       * @param {number} [options.charge=0]
       * @param {number} [options.precision=100]
       * @param {object} [options.filter={}]
       * @param {number} [options.filter.minCharge=Number.MIN_SAFE_INTEGER]
       * @param {number} [options.filter.maxCharge=Number.MAX_SAFE_INTEGER]
       * @returns
       */
      constructor(targetMass, possibilities, options = {}) {
        const {
          allowNeutral = false,
          // msem because em in this case !
          filter = {},
          charge = 0,
          precision = 100
        } = options;
        const {
          minCharge = Number.MIN_SAFE_INTEGER,
          maxCharge = Number.MAX_SAFE_INTEGER
        } = filter;
        if (!possibilities || possibilities.length === 0) return;
        let firstPossibility = possibilities[0];
        let currentMinCharge = Math.max(minCharge, firstPossibility.minCharge + charge);
        let currentMaxCharge = Math.min(maxCharge, firstPossibility.maxCharge + charge);
        this.minCharge = currentMinCharge;
        this.maxCharge = currentMaxCharge;
        let size = this.maxCharge - this.minCharge + 1;
        this.data = [];
        let minMass = 0;
        let maxMass = 0;
        let range = targetMass * precision / 1e6;
        for (let i = 0; i < size; i++) {
          let currentCharge = i + this.minCharge;
          if (currentCharge === 0) {
            if (allowNeutral) {
              minMass = targetMass - range;
              maxMass = targetMass + range;
            } else {
              minMass = Number.MAX_SAFE_INTEGER;
              maxMass = Number.MIN_SAFE_INTEGER;
            }
          } else {
            minMass = (targetMass - range) * Math.abs(currentCharge) + chemical_elements_1.ELECTRON_MASS * currentCharge;
            maxMass = (targetMass + range) * Math.abs(currentCharge) + chemical_elements_1.ELECTRON_MASS * currentCharge;
          }
          this.data.push({
            charge: currentCharge,
            minMass,
            maxMass
          });
        }
      }
      getMinMass(charge) {
        return this.data[charge - this.minCharge] ? this.data[charge - this.minCharge].minMass : Number.MAX_SAFE_INTEGER;
      }
      getMaxMass(charge) {
        return this.data[charge - this.minCharge] ? this.data[charge - this.minCharge].maxMass : Number.MIN_SAFE_INTEGER;
      }
    }
    TargetMassCache$1.TargetMassCache = TargetMassCache;

    Object.defineProperty(findMFsSync$1, "__esModule", {
      value: true
    });
    findMFsSync$1.findMFsSync = findMFsSync;
    const atom_sorter_1 = src$6;
    const mf_matcher_1 = src$2;
    const mf_utilities_1 = src$1;
    const TargetMassCache_1 = TargetMassCache$1;
    /**
     * @param {number}        targetMass - Monoisotopic mass
     * @param {object}        [options={}]
     * @param {number}        [options.maxIterations=10000000] - Maximum number of iterations
     * @param {boolean}       [options.allowNeutral=true]
     * @param {boolean}       [options.uniqueMFs=true]
     * @param {number}        [options.limit=1000] - Maximum number of results
     * @param {string}        [options.ionizations=''] - string containing a comma separated list of modifications
     * @param {string|{mf:string,min:number,max:number}[]}        [options.ranges='C0-100 H0-100 O0-100 N0-100'] - range of mfs to search
     * @param {number}        [options.precision=100] - Allowed mass range based on precision
     * @param {import('mf-matcher').MSEMFilterOptions}        [options.filter={}]
     * @returns {object}
     */
    function findMFsSync(targetMass, options = {}) {
      const {
        filter = {},
        maxIterations = 1e8,
        limit = 1000,
        allowNeutral = true,
        // if there is no msem we use em !
        uniqueMFs = false,
        // if there is no msem we use em !
        ranges = [{
          mf: 'C',
          min: 0,
          max: 100
        }, {
          mf: 'H',
          min: 0,
          max: 100
        }, {
          mf: 'O',
          min: 0,
          max: 100
        }, {
          mf: 'N',
          min: 0,
          max: 100
        }]
      } = options;
      let targetMassCache;
      const {
        minCharge = Number.MIN_SAFE_INTEGER,
        maxCharge = Number.MAX_SAFE_INTEGER,
        unsaturation = {}
      } = filter;
      let filterUnsaturation = !!unsaturation;
      // we calculate not the real unsaturation but the one before dividing by 2 + 1
      let fakeMinUnsaturation = unsaturation.min === undefined ? Number.MIN_SAFE_INTEGER : (unsaturation.min - 1) * 2;
      let fakeMaxUnsaturation = unsaturation.max === undefined ? Number.MAX_SAFE_INTEGER : (unsaturation.max - 1) * 2;
      let filterCharge = minCharge !== Number.MIN_SAFE_INTEGER || maxCharge !== Number.MAX_SAFE_INTEGER;
      let advancedFilter;
      if (filter.atoms || filter.callback) {
        advancedFilter = {
          atoms: filter.atoms,
          callback: filter.callback
        };
      }
      let result = {
        mfs: [],
        info: {
          numberMFEvaluated: 0,
          numberResults: 0
        }
      };
      let orderMapping = []; // used to sort the atoms
      // we need to make the processing for all the ionizations
      let ionizations = (0, mf_utilities_1.preprocessIonizations)(options.ionizations);
      for (let ionization of ionizations) {
        let currentIonization = {
          currentMonoisotopicMass: ionization.em || 0,
          currentCharge: ionization.charge,
          currentUnsaturation: 0 // we don't take into account the unsaturation of the ionization agent
        };
        // if (DEBUG) console.log('new ionization', ionization.mf, ionization.em, ionization.charge);
        // ionization em and charge will be used to set the first atom value
        let possibilities = (0, mf_utilities_1.preprocessRanges)(ranges);
        orderMapping = getOrderMapping(possibilities);
        if (possibilities.length === 0) return {
          mfs: []
        };
        targetMassCache = new TargetMassCache_1.TargetMassCache(targetMass, possibilities, {
          ...options,
          charge: ionization.charge
        });
        let theEnd = false;
        let maxPosition = possibilities.length;
        let lastPosition = possibilities.length - 1;
        let currentPosition = 0;
        let currentAtom;
        let previousAtom;
        let lastPossibility = possibilities[lastPosition];
        initializePossibilities(possibilities, currentIonization, targetMassCache);
        //  if (DEBUG) console.log('possibilities', possibilities.map((a) => `${a.mf + a.originalMinCount}-${a.originalMaxCount}`));
        let isValid = false; // designed so that the first time it is not a valid solution
        while (!theEnd) {
          if (result.info.numberMFEvaluated++ > maxIterations) {
            throw new Error(`Iteration number is over the current maximum of: ${maxIterations}`);
          }
          if (filterUnsaturation) {
            let unsaturationValue = lastPossibility.currentUnsaturation;
            let isOdd = Math.abs(unsaturationValue % 2);
            if (unsaturation.onlyInteger && isOdd === 1 || unsaturation.onlyNonInteger && isOdd === 0 || fakeMinUnsaturation > unsaturationValue || fakeMaxUnsaturation < unsaturationValue) {
              isValid = false;
            }
          }
          if (filterCharge && (lastPossibility.currentCharge < minCharge || lastPossibility.currentCharge > maxCharge)) {
            isValid = false;
          }
          if (isValid) {
            let minMass = targetMassCache.getMinMass(lastPossibility.currentCharge);
            let maxMass = targetMassCache.getMaxMass(lastPossibility.currentCharge);
            if (lastPossibility.currentMonoisotopicMass < minMass || lastPossibility.currentMonoisotopicMass > maxMass) {
              isValid = false;
            }
          }
          if (isValid) {
            result.info.numberResults++;
            let newResult = getResult(possibilities, targetMass, allowNeutral, ionization, orderMapping);
            if (advancedFilter) {
              isValid = (0, mf_matcher_1.msemMatcher)(newResult, advancedFilter) !== false;
            }
            if (isValid) {
              result.mfs.push(newResult);
              if (result.mfs.length > 2 * limit) {
                if (uniqueMFs) ensureUniqueMF(result);
                result.mfs.sort((a, b) => Math.abs(a.ms.ppm) - Math.abs(b.ms.ppm));
                result.mfs.length = limit;
              }
            }
          }
          isValid = true;
          // we need to set up all the arrays if possible
          while (currentPosition < maxPosition && currentPosition >= 0) {
            currentAtom = possibilities[currentPosition];
            previousAtom = currentPosition === 0 ? currentIonization : possibilities[currentPosition - 1];
            if (currentAtom.currentCount < currentAtom.currentMaxCount) {
              currentAtom.currentCount++;
              updateCurrentAtom(currentAtom, previousAtom);
              if (currentPosition < lastPosition) {
                currentPosition++;
                setCurrentMinMax(possibilities[currentPosition], possibilities[currentPosition - 1], targetMassCache);
              } else {
                break;
              }
            } else {
              currentPosition--;
            }
          }
          if (currentPosition < 0) {
            theEnd = true;
          }
        }
      }
      if (uniqueMFs) ensureUniqueMF(result);
      result.mfs.sort((a, b) => Math.abs(a.ms.ppm) - Math.abs(b.ms.ppm));
      if (result.mfs.length > limit) {
        result.mfs.length = limit;
      }
      for (const mf of result.mfs) {
        delete mf.currentCounts;
      }
      return result;
    }
    /**
     * Ensure that we have only once the same MF
     * In order to improve the speed we just consider the em
     * @param {object} result
     */
    function ensureUniqueMF(result) {
      result.mfs.sort((a, b) => a.em - b.em);
      let previousEM = 0;
      let bestCounts = [];
      const mfs = [];
      next: for (let current of result.mfs) {
        if (current.em - previousEM > 1e-8) {
          previousEM = current.em;
          bestCounts = current.currentCounts;
          mfs.push(current);
        } else {
          for (let i = 0; i < current.currentCounts.length; i++) {
            // better priority ???
            if (current.currentCounts[i] > bestCounts[i]) {
              mfs.pop();
              mfs.push(current);
              bestCounts = current.currentCounts;
            } else if (current.currentCounts[i] < bestCounts[i]) {
              continue next;
            }
          }
        }
      }
      result.mfs = mfs;
    }
    function updateCurrentAtom(currentAtom, previousAtom) {
      currentAtom.currentMonoisotopicMass = previousAtom.currentMonoisotopicMass + currentAtom.em * currentAtom.currentCount;
      currentAtom.currentCharge = previousAtom.currentCharge + currentAtom.charge * currentAtom.currentCount;
      currentAtom.currentUnsaturation = previousAtom.currentUnsaturation + currentAtom.unsaturation * currentAtom.currentCount;
    }
    function getResult(possibilities, targetMass, allowNeutralMolecules, ionization, orderMapping) {
      let lastPossibility = possibilities.at(-1);
      let result = {
        em: lastPossibility.currentMonoisotopicMass - ionization.em,
        unsaturation: lastPossibility.currentUnsaturation,
        mf: '',
        charge: lastPossibility.currentCharge - ionization.charge,
        ionization,
        atoms: {},
        groups: {},
        currentCounts: possibilities.map(possibility => possibility.currentCount)
      };
      // we check that the first time we meet the ionization group it does not end
      // in the final result
      for (let i = 0; i < possibilities.length; i++) {
        let possibility = possibilities[orderMapping[i]];
        if (possibility.currentCount !== 0) {
          if (possibility.isGroup) {
            if (possibility.currentCount === 1) {
              result.mf += `${possibility.mf}`;
            } else if (possibility.mf.match(/^\([^()]*\)$/)) {
              result.mf += `${possibility.mf}${possibility.currentCount}`;
            } else {
              result.mf += `(${possibility.mf})${possibility.currentCount}`;
            }
            if (result.groups[possibility.mf]) {
              result.groups[possibility.mf] += possibility.currentCount;
            } else {
              result.groups[possibility.mf] = possibility.currentCount;
            }
          } else {
            result.mf += possibility.mf;
            if (possibility.currentCount !== 1) {
              result.mf += possibility.currentCount;
            }
          }
          for (let atom in possibility.atoms) {
            if (result.atoms[atom]) {
              result.atoms[atom] += possibility.atoms[atom] * possibility.currentCount;
            } else {
              result.atoms[atom] = possibility.atoms[atom] * possibility.currentCount;
            }
          }
        }
      }
      result.unsaturation = (result.unsaturation + Math.abs(result.charge)) / 2 + 1;
      result.ms = (0, mf_utilities_1.getMsInfo)(result, {
        targetMass,
        allowNeutralMolecules
      }).ms;
      return result;
    }
    function setCurrentMinMax(currentAtom, previousAtom, targetMassCache) {
      // the current min max can only be optimize if the charge will not change anymore
      // and the currentAtom.em >= 0
      if (currentAtom.innerCharge === true || currentAtom.charge !== 0 || currentAtom.em < 0) {
        currentAtom.currentMinCount = currentAtom.originalMinCount;
        currentAtom.currentMaxCount = currentAtom.originalMaxCount;
        currentAtom.currentCount = currentAtom.currentMinCount - 1;
      } else {
        // no more change of charge, we can optimize
        let currentMass = previousAtom !== undefined ? previousAtom.currentMonoisotopicMass : 0;
        let currentCharge = previousAtom !== undefined ? previousAtom.currentCharge : 0;
        currentAtom.currentMinCount = Math.max(Math.floor((targetMassCache.getMinMass(currentCharge) - currentMass - currentAtom.maxInnerMass) / currentAtom.em), currentAtom.originalMinCount);
        currentAtom.currentMaxCount = Math.min(Math.floor((targetMassCache.getMaxMass(currentCharge) - currentMass - currentAtom.minInnerMass) / currentAtom.em), currentAtom.originalMaxCount);
        currentAtom.currentCount = currentAtom.currentMinCount - 1;
      }
    }
    function initializePossibilities(possibilities, currentIonization, targetMassCache) {
      for (let i = 0; i < possibilities.length; i++) {
        if (i === 0) {
          updateCurrentAtom(possibilities[i], currentIonization);
          setCurrentMinMax(possibilities[i], currentIonization, targetMassCache);
        } else {
          updateCurrentAtom(possibilities[i], possibilities[i - 1]);
        }
      }
    }
    function getOrderMapping(possibilities) {
      let mapping = possibilities.map((p, i) => ({
        atom: p.mf,
        index: i
      }));
      mapping.sort((a, b) => {
        return (0, atom_sorter_1.atomSorter)(a.atom, b.atom);
      });
      return mapping.map(a => a.index);
    }

    Object.defineProperty(findMFs$1, "__esModule", {
      value: true
    });
    findMFs$1.findMFs = findMFs;
    const findMFsSync_1 = findMFsSync$1;
    /** @typedef {import('./types').MFResult} MFResult */
    /** @typedef {import('./types').MFFinderOptions} MFFinderOptions */
    /**
     * @param {number} targetMass - Monoisotopic mass
     * @param {MFFinderOptions} [options={}]
     * @returns {Promise<MFResult[]>}
     */
    async function findMFs(targetMass, options = {}) {
      return (0, findMFsSync_1.findMFsSync)(targetMass, options);
    }

    var mfIncluded$1 = {};

    Object.defineProperty(mfIncluded$1, "__esModule", {
      value: true
    });
    mfIncluded$1.mfIncluded = mfIncluded;
    const mf_parser_1$1 = src$7;
    const findMFsSync_js_1 = findMFsSync$1;
    /**
     * @param {string}        mf - the molecular formula to check
     * @param {string}        range - the range of MF to explore
     * @param {object}        [options={}]
     * @param {number}        [options.maxIterations=10000000] - Maximum number of iterations
     * @param {boolean}       [options.allowNeutral=true]
     * @param {string}        [options.ionizations=''] - string containing a comma separated list of modifications
     * @param {object}        [options.filter={}]
     * @param {number}        [options.filter.minCharge=-Infinity] - Minimal charge
     * @param {number}        [options.filter.maxCharge=+Infinity] - Maximal charge
     * @param {boolean}       [options.filter.absoluteCharge=false] - If true, the charge is absolute (so between 0 and +Infinity by default)
     * @param {object}        [options.filter.unsaturation={}]
     * @param {number}        [options.filter.unsaturation.min=-Infinity] - Minimal unsaturation
     * @param {number}        [options.filter.unsaturation.max=+Infinity] - Maximal unsaturation
     * @param {boolean}       [options.filter.unsaturation.onlyInteger=false] - Integer unsaturation
     * @param {boolean}       [options.filter.unsaturation.onlyNonInteger=false] - Non integer unsaturation
     * @param {object}        [options.filter.atoms] - object of atom:{min, max}
     * @param {function}      [options.filter.callback] - a function to filter the MF
     * @returns {boolean}
     */
    function mfIncluded(mf, range, options = {}) {
      let targetEM = new mf_parser_1$1.MF(mf).getInfo().monoisotopicMass;
      let results = (0, findMFsSync_js_1.findMFsSync)(targetEM, {
        allowNeutral: true,
        ...options,
        ranges: range,
        precision: 0.0000001,
        limit: 1
      });
      return results.mfs.length > 0;
    }

    (function (exports) {

      var __createBinding = src$3 && src$3.__createBinding || (Object.create ? function (o, m, k, k2) {
        if (k2 === undefined) k2 = k;
        var desc = Object.getOwnPropertyDescriptor(m, k);
        if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
          desc = {
            enumerable: true,
            get: function () {
              return m[k];
            }
          };
        }
        Object.defineProperty(o, k2, desc);
      } : function (o, m, k, k2) {
        if (k2 === undefined) k2 = k;
        o[k2] = m[k];
      });
      var __exportStar = src$3 && src$3.__exportStar || function (m, exports) {
        for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
      };
      Object.defineProperty(exports, "__esModule", {
        value: true
      });
      __exportStar(findMFs$1, exports);
      __exportStar(mfIncluded$1, exports);
    })(src$3);

    Object.defineProperty(fromMonoisotopicMass$1, "__esModule", {
      value: true
    });
    fromMonoisotopicMass$1.fromMonoisotopicMass = fromMonoisotopicMass;
    const mf_finder_1 = src$3;
    /**
     * Generates a database 'monoisotopic' from a monoisotopic mass and various options
     * @param {number|string|array}    masses - Monoisotopic mass
     * @param {object}    [options={}]
     * @param {number}    [options.maxIterations=10000000] - Maximum number of iterations
     * @param {function}  [options.onStep] - Callback to do after each step
     * @param {boolean}   [options.allowNeutral=true]
     * @param {boolean}   [options.uniqueMFs=true]
     * @param {number}    [options.limit=1000] - Maximum number of results
     * @param {string}    [options.ionizations=''] - string containing a comma separated list of modifications
     * @param {string}    [options.ranges='C0-100 H0-100 O0-100 N0-100'] - range of mfs to search
     * @param {number}    [options.precision=100] - Allowed mass range based on precision
     * @param {object}    [options.filter={}]
     * @param {number}    [options.filter.minCharge=-Infinity] - Minimal charge
     * @param {number}    [options.filter.maxCharge=+Infinity] - Maximal charge
     * @param {boolean}   [options.filter.absoluteCharge=false] - If true, the charge is absolute (so between 0 and +Infinity by default)
     * @param {object}    [options.filter.unsaturation={}]
     * @param {number}    [options.filter.unsaturation.min=-Infinity] - Minimal unsaturation
     * @param {number}    [options.filter.unsaturation.max=+Infinity] - Maximal unsaturation
     * @param {boolean}    [options.filter.unsaturation.onlyInteger=false] - Integer unsaturation
     * @param {boolean}    [options.filter.unsaturation.onlyNonInteger=false] - Non integer unsaturation
     * @param {object}    [options.filter.atoms] - object of atom:{min, max}
     * @param {function}  [options.filter.callback] - a function to filter the MF
     * @returns {Promise}
     */
    async function fromMonoisotopicMass(masses, options = {}) {
      if (typeof masses === 'string') {
        masses = masses.split(/[\t\n\r ,;]/).map(Number);
      }
      if (typeof masses === 'number') {
        masses = [masses];
      }
      let results = [];
      for (let mass of masses) {
        results.push(await (0, mf_finder_1.findMFs)(mass, options));
      }
      return {
        mfs: results.flatMap(entry => entry.mfs),
        info: {
          numberMFEvaluated: results.reduce((sum, current) => sum + current.info.numberMFEvaluated, 0),
          numberResults: results.reduce((sum, current) => sum + current.info.numberResults, 0)
        }
      };
    }

    var getPeaks$1 = {};

    Object.defineProperty(getPeaks$1, "__esModule", {
      value: true
    });
    getPeaks$1.getPeaks = getPeaks;
    const ml_spectra_processing_1$6 = require$$0$3;
    /**
     * Filter the array of peaks
     * @param {array} peaks - array of all the peaks
     * @param {object} [options={}]
     * @param {number} [options.from] - min X value of the window to consider
     * @param {number} [options.to] - max X value of the window to consider
     * @param {number} [options.threshold=0.01] - minimal intensity compare to base peak
     * @param {number} [options.limit=undefined] - maximal number of peaks (based on intensity)
     * @param {number} [options.sumValue] // if sumValue is defined, maxValue is ignored
     * @returns {array} - copy of peaks with 'close' annotation
     */
    function getPeaks(peaks, options = {}) {
      const {
        from = (0, ml_spectra_processing_1$6.xyObjectMinXPoint)(peaks).x,
        to = (0, ml_spectra_processing_1$6.xyObjectMaxXPoint)(peaks).x,
        threshold = 0.01,
        limit,
        sumValue
      } = options;
      let maxY = Number.MIN_SAFE_INTEGER;
      for (let peak of peaks) {
        if (peak.y > maxY) maxY = peak.y;
      }
      let minY = maxY * threshold;
      peaks = peaks.filter(peak => peak.x >= from && peak.x <= to && peak.y >= minY);
      if (limit && peaks.length > limit) {
        peaks.sort((a, b) => b.y - a.y);
        peaks = peaks.slice(0, limit);
      }
      if (sumValue) {
        peaks = structuredClone(peaks);
        const currentSum = (0, ml_spectra_processing_1$6.xyObjectSumY)(peaks);
        const ratio = sumValue / currentSum;
        for (const peak of peaks) {
          peak.y *= ratio;
        }
      }
      return peaks.sort((a, b) => a.x - b.x);
    }

    Object.defineProperty(getFragmentPeaks$1, "__esModule", {
      value: true
    });
    getFragmentPeaks$1.getFragmentPeaks = getFragmentPeaks;
    const mf_parser_1 = src$7;
    const fromMonoisotopicMass_1$1 = fromMonoisotopicMass$1;
    const getPeaks_js_1$1 = getPeaks$1;
    /**
     * Filter the array of peaks
     * @param {array} peaks - array of all the peaks
     * @param {string} mf - Molecular formula of the parent molecule
     * @param {object} [options={}]
     * @param {number} [options.from] - min X value of the window to consider
     * @param {number} [options.to] - max X value of the window to consider
     * @param {number} [options.threshold=0.01] - minimal intensity compare to base peak
     * @param {number} [options.limit=undefined] - maximal number of peaks (based on intensity)
     * @param {string} [options.ionizations]
     * @param {number} [options.precision]
     * @returns {array} - copy of peaks with 'close' annotation
     */
    async function getFragmentPeaks(peaks, mf, options = {}) {
      const {
        ionizations = '',
        precision
      } = options;
      const mfInfo = new mf_parser_1.MF(mf).getInfo();
      const ranges = Object.keys(mfInfo.atoms).map(key => `${key}0-${mfInfo.atoms[key]}`).join(' ');
      peaks = (0, getPeaks_js_1$1.getPeaks)(peaks, options);
      for (let peak of peaks) {
        const {
          mfs
        } = await (0, fromMonoisotopicMass_1$1.fromMonoisotopicMass)(peak.x, {
          precision,
          ranges,
          ionizations
        });
        peak.mfs = mfs;
      }
      peaks = peaks.filter(peak => peak.mfs.length > 0);
      return peaks;
    }

    var getMassRemainder$1 = {};

    Object.defineProperty(getMassRemainder$1, "__esModule", {
      value: true
    });
    getMassRemainder$1.getMassRemainder = getMassRemainder;
    const ml_spectra_processing_1$5 = require$$0$3;
    /**
     * Remove an integer number of time the specifiedd monoisotopic mass.
     * Mass remainder analysis (MARA): https://doi.org/10.1021/acs.analchem.7b04730
     * @param {object} spectrum
     * @param {number} mass
     * @param {object} [options={}]
     * @param {number} [options.delta=0.001]
     */
    function getMassRemainder(spectrum, mass, options = {}) {
      const {
        delta = 0.001
      } = options;
      const x = spectrum.x.slice();
      const y = spectrum.y;
      for (let i = 0; i < x.length; i++) {
        const factor = Math.floor(x[i] / mass);
        x[i] = x[i] - factor * mass;
      }
      // we sort and join
      return (0, ml_spectra_processing_1$5.xyJoinX)((0, ml_spectra_processing_1$5.xySortX)({
        x,
        y
      }), {
        delta
      });
    }

    var getPeakChargeBySimilarity$1 = {};

    function calculateOverlapFromDiff(diffs) {
      if (diffs[1].length === 0) return 0;
      let sumPos = 0;
      for (let i = 0; i < diffs[1].length; i++) {
        sumPos += Math.abs(diffs[1][i]);
      }
      return 1 - sumPos;
    }

    /**
     * This code requires the use of an array like  [[x1,y1],[x2,y2], ...]
     * If it is not the right format, we will just convert it
     * Otherwise we return the correct format
     * @param {Peaks} peaks
     * @returns [number[], number[]]
     */
    function checkPeaks(peaks) {
      // if it is already a 2D array of points, we just return them
      if (Array.isArray(peaks) && Array.isArray(peaks[0]) && peaks.length === 2) {
        return peaks;
      }
      if (Array.isArray(peaks.x) && Array.isArray(peaks.y)) {
        return [peaks.x, peaks.y];
      }
      const x = new Array(peaks.length);
      const y = new Array(peaks.length);
      for (let i = 0; i < peaks.length; i++) {
        x[i] = peaks[i][0];
        y[i] = peaks[i][1];
      }
      return [x, y];
    }

    function extract(array, from, to) {
      const newArray = [[], []];
      let j = 0;
      const length = array[0] ? array[0].length : 0;
      for (let i = 0; i < length; i++) {
        if ((!from || array[0][i] >= from) && (!to || array[0][i] <= to)) {
          newArray[0][j] = array[0][i];
          newArray[1][j] = array[1][i];
          j++;
        }
      }
      return newArray;
    }

    // returns an new array based on array1 where there is a peak of array2 at a distance under width/2
    function getCommonArray(array1, array2, width) {
      const newArray = [[], []];
      let pos2 = 0;
      width /= 2;
      let j = 0;
      const array1Length = array1[0] ? array1[0].length : 0;
      const array2Length = array2[0] ? array2[0].length : 0;
      for (let i = 0; i < array1Length; i++) {
        while (pos2 < array2Length && array1[0][i] > array2[0][pos2] + width) {
          pos2++;
        }
        if (pos2 < array2Length && array1[0][i] > array2[0][pos2] - width) {
          newArray[0][j] = array1[0][i];
          newArray[1][j] = array1[1][i];
          j++;
        }
      }
      return newArray;
    }

    var array$1 = {};

    (function (exports) {

      function compareNumbers(a, b) {
        return a - b;
      }

      /**
       * Computes the sum of the given values
       * @param {Array} values
       * @returns {number}
       */
      exports.sum = function sum(values) {
        var sum = 0;
        for (var i = 0; i < values.length; i++) {
          sum += values[i];
        }
        return sum;
      };

      /**
       * Computes the maximum of the given values
       * @param {Array} values
       * @returns {number}
       */
      exports.max = function max(values) {
        var max = values[0];
        var l = values.length;
        for (var i = 1; i < l; i++) {
          if (values[i] > max) max = values[i];
        }
        return max;
      };

      /**
       * Computes the minimum of the given values
       * @param {Array} values
       * @returns {number}
       */
      exports.min = function min(values) {
        var min = values[0];
        var l = values.length;
        for (var i = 1; i < l; i++) {
          if (values[i] < min) min = values[i];
        }
        return min;
      };

      /**
       * Computes the min and max of the given values
       * @param {Array} values
       * @returns {{min: number, max: number}}
       */
      exports.minMax = function minMax(values) {
        var min = values[0];
        var max = values[0];
        var l = values.length;
        for (var i = 1; i < l; i++) {
          if (values[i] < min) min = values[i];
          if (values[i] > max) max = values[i];
        }
        return {
          min: min,
          max: max
        };
      };

      /**
       * Computes the arithmetic mean of the given values
       * @param {Array} values
       * @returns {number}
       */
      exports.arithmeticMean = function arithmeticMean(values) {
        var sum = 0;
        var l = values.length;
        for (var i = 0; i < l; i++) {
          sum += values[i];
        }
        return sum / l;
      };

      /**
       * {@link arithmeticMean}
       */
      exports.mean = exports.arithmeticMean;

      /**
       * Computes the geometric mean of the given values
       * @param {Array} values
       * @returns {number}
       */
      exports.geometricMean = function geometricMean(values) {
        var mul = 1;
        var l = values.length;
        for (var i = 0; i < l; i++) {
          mul *= values[i];
        }
        return Math.pow(mul, 1 / l);
      };

      /**
       * Computes the mean of the log of the given values
       * If the return value is exponentiated, it gives the same result as the
       * geometric mean.
       * @param {Array} values
       * @returns {number}
       */
      exports.logMean = function logMean(values) {
        var lnsum = 0;
        var l = values.length;
        for (var i = 0; i < l; i++) {
          lnsum += Math.log(values[i]);
        }
        return lnsum / l;
      };

      /**
       * Computes the weighted grand mean for a list of means and sample sizes
       * @param {Array} means - Mean values for each set of samples
       * @param {Array} samples - Number of original values for each set of samples
       * @returns {number}
       */
      exports.grandMean = function grandMean(means, samples) {
        var sum = 0;
        var n = 0;
        var l = means.length;
        for (var i = 0; i < l; i++) {
          sum += samples[i] * means[i];
          n += samples[i];
        }
        return sum / n;
      };

      /**
       * Computes the truncated mean of the given values using a given percentage
       * @param {Array} values
       * @param {number} percent - The percentage of values to keep (range: [0,1])
       * @param {boolean} [alreadySorted=false]
       * @returns {number}
       */
      exports.truncatedMean = function truncatedMean(values, percent, alreadySorted) {
        if (alreadySorted === undefined) alreadySorted = false;
        if (!alreadySorted) {
          values = [].concat(values).sort(compareNumbers);
        }
        var l = values.length;
        var k = Math.floor(l * percent);
        var sum = 0;
        for (var i = k; i < l - k; i++) {
          sum += values[i];
        }
        return sum / (l - 2 * k);
      };

      /**
       * Computes the harmonic mean of the given values
       * @param {Array} values
       * @returns {number}
       */
      exports.harmonicMean = function harmonicMean(values) {
        var sum = 0;
        var l = values.length;
        for (var i = 0; i < l; i++) {
          if (values[i] === 0) {
            throw new RangeError('value at index ' + i + 'is zero');
          }
          sum += 1 / values[i];
        }
        return l / sum;
      };

      /**
       * Computes the contraharmonic mean of the given values
       * @param {Array} values
       * @returns {number}
       */
      exports.contraHarmonicMean = function contraHarmonicMean(values) {
        var r1 = 0;
        var r2 = 0;
        var l = values.length;
        for (var i = 0; i < l; i++) {
          r1 += values[i] * values[i];
          r2 += values[i];
        }
        if (r2 < 0) {
          throw new RangeError('sum of values is negative');
        }
        return r1 / r2;
      };

      /**
       * Computes the median of the given values
       * @param {Array} values
       * @param {boolean} [alreadySorted=false]
       * @returns {number}
       */
      exports.median = function median(values, alreadySorted) {
        if (alreadySorted === undefined) alreadySorted = false;
        if (!alreadySorted) {
          values = [].concat(values).sort(compareNumbers);
        }
        var l = values.length;
        var half = Math.floor(l / 2);
        if (l % 2 === 0) {
          return (values[half - 1] + values[half]) * 0.5;
        } else {
          return values[half];
        }
      };

      /**
       * Computes the variance of the given values
       * @param {Array} values
       * @param {boolean} [unbiased=true] - if true, divide by (n-1); if false, divide by n.
       * @returns {number}
       */
      exports.variance = function variance(values, unbiased) {
        if (unbiased === undefined) unbiased = true;
        var theMean = exports.mean(values);
        var theVariance = 0;
        var l = values.length;
        for (var i = 0; i < l; i++) {
          var x = values[i] - theMean;
          theVariance += x * x;
        }
        if (unbiased) {
          return theVariance / (l - 1);
        } else {
          return theVariance / l;
        }
      };

      /**
       * Computes the standard deviation of the given values
       * @param {Array} values
       * @param {boolean} [unbiased=true] - if true, divide by (n-1); if false, divide by n.
       * @returns {number}
       */
      exports.standardDeviation = function standardDeviation(values, unbiased) {
        return Math.sqrt(exports.variance(values, unbiased));
      };
      exports.standardError = function standardError(values) {
        return exports.standardDeviation(values) / Math.sqrt(values.length);
      };

      /**
       * IEEE Transactions on biomedical engineering, vol. 52, no. 1, january 2005, p. 76-
       * Calculate the standard deviation via the Median of the absolute deviation
       *  The formula for the standard deviation only holds for Gaussian random variables.
       * @returns {{mean: number, stdev: number}}
       */
      exports.robustMeanAndStdev = function robustMeanAndStdev(y) {
        var mean = 0,
          stdev = 0;
        var length = y.length,
          i = 0;
        for (i = 0; i < length; i++) {
          mean += y[i];
        }
        mean /= length;
        var averageDeviations = new Array(length);
        for (i = 0; i < length; i++) averageDeviations[i] = Math.abs(y[i] - mean);
        averageDeviations.sort(compareNumbers);
        if (length % 2 === 1) {
          stdev = averageDeviations[(length - 1) / 2] / 0.6745;
        } else {
          stdev = 0.5 * (averageDeviations[length / 2] + averageDeviations[length / 2 - 1]) / 0.6745;
        }
        return {
          mean: mean,
          stdev: stdev
        };
      };
      exports.quartiles = function quartiles(values, alreadySorted) {
        if (typeof alreadySorted === 'undefined') alreadySorted = false;
        if (!alreadySorted) {
          values = [].concat(values).sort(compareNumbers);
        }
        var quart = values.length / 4;
        var q1 = values[Math.ceil(quart) - 1];
        var q2 = exports.median(values, true);
        var q3 = values[Math.ceil(quart * 3) - 1];
        return {
          q1: q1,
          q2: q2,
          q3: q3
        };
      };
      exports.pooledStandardDeviation = function pooledStandardDeviation(samples, unbiased) {
        return Math.sqrt(exports.pooledVariance(samples, unbiased));
      };
      exports.pooledVariance = function pooledVariance(samples, unbiased) {
        if (typeof unbiased === 'undefined') unbiased = true;
        var sum = 0;
        var length = 0,
          l = samples.length;
        for (var i = 0; i < l; i++) {
          var values = samples[i];
          var vari = exports.variance(values);
          sum += (values.length - 1) * vari;
          if (unbiased) length += values.length - 1;else length += values.length;
        }
        return sum / length;
      };
      exports.mode = function mode(values) {
        var l = values.length,
          itemCount = new Array(l),
          i;
        for (i = 0; i < l; i++) {
          itemCount[i] = 0;
        }
        var itemArray = new Array(l);
        var count = 0;
        for (i = 0; i < l; i++) {
          var index = itemArray.indexOf(values[i]);
          if (index >= 0) itemCount[index]++;else {
            itemArray[count] = values[i];
            itemCount[count] = 1;
            count++;
          }
        }
        var maxValue = 0,
          maxIndex = 0;
        for (i = 0; i < count; i++) {
          if (itemCount[i] > maxValue) {
            maxValue = itemCount[i];
            maxIndex = i;
          }
        }
        return itemArray[maxIndex];
      };
      exports.covariance = function covariance(vector1, vector2, unbiased) {
        if (typeof unbiased === 'undefined') unbiased = true;
        var mean1 = exports.mean(vector1);
        var mean2 = exports.mean(vector2);
        if (vector1.length !== vector2.length) throw 'Vectors do not have the same dimensions';
        var cov = 0,
          l = vector1.length;
        for (var i = 0; i < l; i++) {
          var x = vector1[i] - mean1;
          var y = vector2[i] - mean2;
          cov += x * y;
        }
        if (unbiased) return cov / (l - 1);else return cov / l;
      };
      exports.skewness = function skewness(values, unbiased) {
        if (typeof unbiased === 'undefined') unbiased = true;
        var theMean = exports.mean(values);
        var s2 = 0,
          s3 = 0,
          l = values.length;
        for (var i = 0; i < l; i++) {
          var dev = values[i] - theMean;
          s2 += dev * dev;
          s3 += dev * dev * dev;
        }
        var m2 = s2 / l;
        var m3 = s3 / l;
        var g = m3 / Math.pow(m2, 3 / 2.0);
        if (unbiased) {
          var a = Math.sqrt(l * (l - 1));
          var b = l - 2;
          return a / b * g;
        } else {
          return g;
        }
      };
      exports.kurtosis = function kurtosis(values, unbiased) {
        if (typeof unbiased === 'undefined') unbiased = true;
        var theMean = exports.mean(values);
        var n = values.length,
          s2 = 0,
          s4 = 0;
        for (var i = 0; i < n; i++) {
          var dev = values[i] - theMean;
          s2 += dev * dev;
          s4 += dev * dev * dev * dev;
        }
        var m2 = s2 / n;
        var m4 = s4 / n;
        if (unbiased) {
          var v = s2 / (n - 1);
          var a = n * (n + 1) / ((n - 1) * (n - 2) * (n - 3));
          var b = s4 / (v * v);
          var c = (n - 1) * (n - 1) / ((n - 2) * (n - 3));
          return a * b - 3 * c;
        } else {
          return m4 / (m2 * m2) - 3;
        }
      };
      exports.entropy = function entropy(values, eps) {
        if (typeof eps === 'undefined') eps = 0;
        var sum = 0,
          l = values.length;
        for (var i = 0; i < l; i++) sum += values[i] * Math.log(values[i] + eps);
        return -sum;
      };
      exports.weightedMean = function weightedMean(values, weights) {
        var sum = 0,
          l = values.length;
        for (var i = 0; i < l; i++) sum += values[i] * weights[i];
        return sum;
      };
      exports.weightedStandardDeviation = function weightedStandardDeviation(values, weights) {
        return Math.sqrt(exports.weightedVariance(values, weights));
      };
      exports.weightedVariance = function weightedVariance(values, weights) {
        var theMean = exports.weightedMean(values, weights);
        var vari = 0,
          l = values.length;
        var a = 0,
          b = 0;
        for (var i = 0; i < l; i++) {
          var z = values[i] - theMean;
          var w = weights[i];
          vari += w * (z * z);
          b += w;
          a += w * w;
        }
        return vari * (b / (b * b - a));
      };
      exports.center = function center(values, inPlace) {
        if (typeof inPlace === 'undefined') inPlace = false;
        var result = values;
        if (!inPlace) result = [].concat(values);
        var theMean = exports.mean(result),
          l = result.length;
        for (var i = 0; i < l; i++) result[i] -= theMean;
      };
      exports.standardize = function standardize(values, standardDev, inPlace) {
        if (typeof standardDev === 'undefined') standardDev = exports.standardDeviation(values);
        if (typeof inPlace === 'undefined') inPlace = false;
        var l = values.length;
        var result = inPlace ? values : new Array(l);
        for (var i = 0; i < l; i++) result[i] = values[i] / standardDev;
        return result;
      };
      exports.cumulativeSum = function cumulativeSum(array) {
        var l = array.length;
        var result = new Array(l);
        result[0] = array[0];
        for (var i = 1; i < l; i++) result[i] = result[i - 1] + array[i];
        return result;
      };
    })(array$1);

    var array = array$1;

    function normalize(array$1) {
      const min = array.min(array$1[1]);
      const max = array.max(array$1[1]);
      const sum = array.sum(array$1[1]);
      const length = array$1[1] ? array$1[1].length : 0;
      if (sum !== 0) {
        for (let i = 0; i < length; i++) {
          array$1[1][i] /= sum;
        }
      }
      return {
        sum,
        min,
        max
      };
    }

    // this method will systematically take care of both array
    function commonExtractAndNormalize(array1, array2, width, from, to, common) {
      if (!Array.isArray(array1) || !Array.isArray(array2)) {
        return {
          info: undefined,
          data: undefined
        };
      }
      const extract1 = extract(array1, from, to);
      const extract2 = extract(array2, from, to);
      let common1, common2, info1, info2;
      if (common & COMMON_SECOND) {
        common1 = getCommonArray(extract1, extract2, width);
        info1 = normalize(common1);
      } else {
        common1 = extract1;
        info1 = normalize(common1);
      }
      if (common & COMMON_FIRST) {
        common2 = getCommonArray(extract2, extract1, width);
        info2 = normalize(common2);
      } else {
        common2 = extract2;
        info2 = normalize(common2);
      }
      return {
        info1,
        info2,
        data1: common1,
        data2: common2
      };
    }

    function extractAndNormalize(array, from, to) {
      if (!Array.isArray(array)) {
        return {
          info: undefined,
          data: undefined
        };
      }
      const newArray = extract(array, from, to);
      const info = normalize(newArray);
      return {
        info,
        data: newArray
      };
    }

    // Adapted from: http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect/1968345#1968345
    function getIntersection(segment1, segment2) {
      const p0X = segment1[0][0];
      const p0Y = segment1[0][1];
      const p1X = segment1[1][0];
      const p1Y = segment1[1][1];
      const p2X = segment2[0][0];
      const p2Y = segment2[0][1];
      const p3X = segment2[1][0];
      const p3Y = segment2[1][1];
      const s1X = p1X - p0X;
      const s1Y = p1Y - p0Y;
      const s2X = p3X - p2X;
      const s2Y = p3Y - p2Y;
      const s = (-s1Y * (p0X - p2X) + s1X * (p0Y - p2Y)) / (-s2X * s1Y + s1X * s2Y);
      const t = (s2X * (p0Y - p2Y) - s2Y * (p0X - p2X)) / (-s2X * s1Y + s1X * s2Y);
      if (s >= 0 && s <= 1 && t >= 0 && t <= 1) {
        return {
          x: p0X + t * s1X,
          y: p0Y + t * s1Y
        };
      }
      return null; // No collision
    }

    const COMMON_NO = 0;
    const COMMON_FIRST = 1;
    const COMMON_SECOND = 2;
    const COMMON_BOTH = 3;
    /**
     * A number, or a string containing a number.
     * @typedef {([number[],number[]]|[number,number][]|{x:number[],y:number[]})} Peaks
     */
    /**
     * Create a comparator class
     * {object} [options={}]
     * {string} [options.common=''] should we take only common peaks 'first', 'second', 'both', ''
     * {number} [options.widthBottom=2] bottom trapezoid width for similarity evaluation
     * {number} [options.widthTop=1] top trapezoid width for similarity evaluation
     * {number} [options.from] from region used for similarity calculation
     * {number} [options.to] to region used for similarity calculation
     */
    class Comparator {
      constructor(options = {}) {
        this.array1 = [];
        this.array2 = [];
        this.setOptions(options);
      }
      /*
         2 formats are allowed:
         [[x1,x2,...],[y1,y2,...]] or [[x1,y1],[x2,y2], ...]
        */
      setOptions(options = {}) {
        if (typeof options.common === 'string') {
          if (options.common.toLowerCase() === 'first') {
            this.common = COMMON_FIRST;
          } else if (options.common.toLowerCase() === 'second') {
            this.common = COMMON_SECOND;
          } else if (options.common.toLowerCase() === 'both') {
            this.common = COMMON_BOTH;
          } else {
            this.common = COMMON_NO;
          }
        } else if (options.common === true) {
          this.common = COMMON_BOTH;
        } else {
          this.common = COMMON_NO;
        }
        this.trapezoid = options.trapezoid;
        this.commonFactor = options.commonFactor || this.commonFactor || 4;
        const {
          widthBottom = this.widthBottom || 2,
          widthTop = this.widthTop || 1,
          from = this.from,
          to = this.to
        } = options;
        this.setTrapezoid(widthBottom, widthTop);
        this.setFromTo(from, to);
      }
      /**
       *
       * @param {Peaks} peaks
       */
      setPeaks1(peaks) {
        this.array1 = checkPeaks(peaks);
        if (this.common) {
          const extracts = commonExtractAndNormalize(this.array1, this.array2, this.widthBottom, this.from, this.to, this.common);
          this.array1Extract = extracts.data1;
          this.array1ExtractInfo = extracts.info1;
          this.array2Extract = extracts.data2;
          this.array2ExtractInfo = extracts.info2;
        } else {
          const extract = extractAndNormalize(this.array1, this.from, this.to);
          this.array1Extract = extract.data;
          this.array1ExtractInfo = extract.info;
        }
      }
      /**
       *
       * @param {Peaks} peaks
       */
      setPeaks2(peaks) {
        this.array2 = checkPeaks(peaks);
        if (this.common) {
          const extracts = commonExtractAndNormalize(this.array1, this.array2, this.widthBottom, this.from, this.to, this.common);
          this.array1Extract = extracts.data1;
          this.array1ExtractInfo = extracts.info1;
          this.array2Extract = extracts.data2;
          this.array2ExtractInfo = extracts.info2;
        } else {
          const extract = extractAndNormalize(this.array2, this.from, this.to);
          this.array2Extract = extract.data;
          this.array2ExtractInfo = extract.info;
        }
      }
      getExtract1() {
        return this.array1Extract;
      }
      getExtract2() {
        return this.array2Extract;
      }
      getExtractInfo1() {
        return this.array1ExtractInfo;
      }
      getExtractInfo2() {
        return this.array2ExtractInfo;
      }
      /**
       * Set the new bottom and top width of the trapezoid
       * @param {number} newWidthBottom
       * @param {number} newWidthTop
       */
      setTrapezoid(newWidthBottom, newWidthTop) {
        this.widthTop = newWidthTop;
        this.widthBottom = newWidthBottom;
        this.widthSlope = (this.widthBottom - this.widthTop) / 2;
        if (this.widthBottom < this.widthTop) {
          throw new Error('widthBottom has to be larger than widthTop');
        }
      }
      /**
       * Set the from / to for comparison
       * @param {number} newFrom - set the new from value
       * @param {number} newTo - set the new to value
       * @returns
       */
      setFromTo(newFrom, newTo) {
        if (newFrom === this.from && newTo === this.to) return;
        this.from = newFrom;
        this.to = newTo;
        if (this.common) {
          const extracts = commonExtractAndNormalize(this.array1, this.array2, this.widthBottom, this.from, this.to, this.common, this.commonFactor);
          this.array1Extract = extracts.data1;
          this.array1ExtractInfo = extracts.info1;
          this.array2Extract = extracts.data2;
          this.array2ExtractInfo = extracts.info2;
        } else {
          let extract1 = extractAndNormalize(this.array1, this.from, this.to);
          this.array1Extract = extract1.data;
          this.array1ExtractInfo = extract1.info;
          let extract2 = extractAndNormalize(this.array2, this.from, this.to);
          this.array2Extract = extract2.data;
          this.array2ExtractInfo = extract2.info;
        }
      }
      /**
       *
       * @param {number} x1
       * @param {number} y1
       * @param {number} x2
       * @param {number} y2
       * @returns
       */
      getOverlap(x1, y1, x2, y2) {
        if (y1 === 0 || y2 === 0) return 0;
        // TAKE CARE !!! We multiply the diff by 2 !!!
        const diff = Math.abs(x1 - x2) * 2;
        if (diff > this.widthBottom) return 0;
        if (diff <= this.widthTop) {
          return Math.min(y1, y2);
        }
        const maxValue = Math.max(y1, y2) * (this.widthBottom - diff) / (this.widthBottom - this.widthTop);
        return Math.min(y1, y2, maxValue);
      }
      /**
       * This is the old trapezoid similarity
       * @param {number} x1
       * @param {number} y1
       * @param {number} x2
       * @param {number} y2
       * @param {number} widthTop
       * @param {number} widthBottom
       * @returns
       */
      getOverlapTrapezoid(x1, y1, x2, y2, widthTop, widthBottom) {
        // eslint-disable-next-line no-console
        console.error('getOverlapTrapezoid should not be used anymore');
        const factor = 2 / (widthTop + widthBottom); // correction for surface=1
        if (y1 === 0 || y2 === 0) return 0;
        if (x1 === x2) {
          // they have the same position
          return Math.min(y1, y2);
        }
        const diff = Math.abs(x1 - x2);
        if (diff >= widthBottom) return 0;
        if (y1 === y2) {
          // do they have the same height ???
          // we need to find the common length
          if (diff <= widthTop) {
            return ((widthTop + widthBottom) / 2 - diff) * y1 * factor;
          } else if (diff <= widthBottom) {
            return (widthBottom - diff) * y1 / 2 * (diff - widthTop) / (widthBottom - widthTop) * factor;
          }
          return 0;
        } else {
          // the height are different and not the same position ...
          // we need to consider only one segment to find its intersection
          const small = Math.min(y1, y2);
          const big = Math.max(y1, y2);
          const targets = [[[0, 0], [this.widthSlope, small]], [[this.widthSlope, small], [this.widthSlope + widthTop, small]], [[widthTop + this.widthSlope, small], [widthBottom, 0]]];
          let segment;
          if (x1 > x2 && y1 > y2 || x1 < x2 && y1 < y2) {
            segment = [[diff, 0], [diff + this.widthSlope, big]];
          } else {
            segment = [[diff + this.widthSlope, big], [diff, 0]];
          }
          for (let i = 0; i < 3; i++) {
            const intersection = getIntersection(targets[i], segment);
            if (intersection) {
              switch (i) {
                case 0:
                  return small - diff * intersection.y / 2 * factor;
                case 1:
                  // to simplify ...
                  //     console.log("           ",widthSlope,small,big,intersection.x)
                  return (this.widthSlope * small / (2 * big) * small + (widthTop + this.widthSlope - intersection.x) * small + this.widthSlope * small / 2) * factor;
                case 2:
                  return (widthBottom - diff) * intersection.y / 2 * factor;
                default:
                  throw new Error(`unexpected intersection value: ${i}`);
              }
            }
          }
        }
        return NaN;
      }
      /**
       * This method calculates the total diff. The sum of positive value will yield to overlap
       * @returns
       */
      calculateDiff() {
        // we need to take 2 pointers
        // and travel progressively between them ...
        const newFirst = [this.array1Extract[0].slice(), this.array1Extract[1].slice()];
        const newSecond = [this.array2Extract[0].slice(), this.array2Extract[1].slice()];
        const array1Length = this.array1Extract[0] ? this.array1Extract[0].length : 0;
        const array2Length = this.array2Extract[0] ? this.array2Extract[0].length : 0;
        let pos1 = 0;
        let pos2 = 0;
        let previous2 = 0;
        while (pos1 < array1Length) {
          const diff = newFirst[0][pos1] - this.array2Extract[0][pos2];
          if (Math.abs(diff) < this.widthBottom) {
            // there is some overlap
            let overlap;
            if (this.trapezoid) {
              // old trapezoid overlap similarity
              overlap = this.getOverlapTrapezoid(newFirst[0][pos1], newFirst[1][pos1], newSecond[0][pos2], newSecond[1][pos2], this.widthTop, this.widthBottom);
            } else {
              overlap = this.getOverlap(newFirst[0][pos1], newFirst[1][pos1], newSecond[0][pos2], newSecond[1][pos2], this.widthTop, this.widthBottom);
            }
            newFirst[1][pos1] -= overlap;
            newSecond[1][pos2] -= overlap;
            if (pos2 < array2Length - 1) {
              pos2++;
            } else {
              pos1++;
              pos2 = previous2;
            }
          } else if (diff > 0 && pos2 < array2Length - 1) {
            pos2++;
            previous2 = pos2;
          } else {
            pos1++;
            pos2 = previous2;
          }
        }
        return newSecond;
      }
      /**
       * Set the new peaks and return info
       * @param {Peaks} newPeaks1
       * @param {Peaks} newPeaks2
       * @returns
       */
      getSimilarity(newPeaks1, newPeaks2) {
        if (newPeaks1) this.setPeaks1(newPeaks1);
        if (newPeaks2) this.setPeaks2(newPeaks2);
        const diff = this.calculateDiff();
        return {
          diff,
          extract1: this.getExtract1(),
          extract2: this.getExtract2(),
          extractInfo1: this.getExtractInfo1(),
          extractInfo2: this.getExtractInfo2(),
          similarity: calculateOverlapFromDiff(diff),
          widthBottom: this.widthBottom,
          widthTop: this.widthTop
        };
      }
      /**
       * This works mainly when you have a array1 that is fixed
       * newPeaks2 have to be normalized ! (sum to 1)
       * @param {Peaks} newPeaks2
       * @param {number} from
       * @param {number} to
       * @returns
       */
      fastSimilarity(newPeaks2, from, to) {
        this.array1Extract = extract(this.array1, from, to);
        this.array2Extract = newPeaks2;
        if (this.common & COMMON_SECOND) {
          this.array1Extract = getCommonArray(this.array1Extract, this.array2Extract, this.widthBottom);
        }
        normalize(this.array1Extract);
        const diff = this.calculateDiff();
        return calculateOverlapFromDiff(diff);
      }
    }

    var libEsm = /*#__PURE__*/Object.freeze({
        __proto__: null,
        COMMON_BOTH: COMMON_BOTH,
        COMMON_FIRST: COMMON_FIRST,
        COMMON_NO: COMMON_NO,
        COMMON_SECOND: COMMON_SECOND,
        Comparator: Comparator
    });

    var require$$0$2 = /*@__PURE__*/getAugmentedNamespace(libEsm);

    Object.defineProperty(getPeakChargeBySimilarity$1, "__esModule", {
      value: true
    });
    getPeakChargeBySimilarity$1.getPeakChargeBySimilarity = getPeakChargeBySimilarity;
    const peaks_similarity_1 = require$$0$2;
    /**
     * @param {object}   [options={}]
     * @param {number}   [options.minCharge=1]
     * @param {number}   [options.maxCharge=10]
     * @param {object}   [options.similarity={}]
     * @param {number}   [options.similarity.widthBottom]
     * @param {number}   [options.similarity.widthTop]
     * @param {object}   [options.similarity.widthFunction] - function called with mass that should return an object width containing top and bottom
     * @param {object}   [options.similarity.zone={}]
     * @param {number}   [options.similarity.zone.low=-0.5] - window shift based on observed monoisotopic mass
     * @param {number}   [options.similarity.zone.high=2.5] - to value for the comparison window
     * @param {string}   [options.similarity.common]
     */
    const NEUTRON_MASS$1 = 1;
    function getPeakChargeBySimilarity(spectrum, targetMass, options = {}) {
      let {
        similarity = {},
        minCharge = 1,
        maxCharge = 10
      } = options;
      let {
        zone = {},
        widthFunction
      } = similarity;
      let {
        low = -0.5,
        high = 2.5
      } = zone;
      if (!spectrum || spectrum.data.x.length === 0) {
        throw new Error('You need to add an experimental spectrum first using setMassSpectrum');
      }
      let width = {
        bottom: similarity.widthBottom,
        top: similarity.widthTop
      };
      similarity = structuredClone(similarity);
      similarity.common = 'second';
      let experimentalData = spectrum.data;
      let similarityProcessor = new peaks_similarity_1.Comparator(similarity);
      similarityProcessor.setPeaks1([experimentalData.x, experimentalData.y]);
      if (widthFunction && typeof widthFunction === 'string') {
        // eslint-disable-next-line no-new-func
        widthFunction = new Function('mass', widthFunction);
        let checkTopBottom = widthFunction(123);
        if (!checkTopBottom.bottom || !checkTopBottom.top) {
          throw new Error('widthFunction should return an object with bottom and top properties');
        }
      }
      let fromCharge = minCharge * maxCharge > 0 ? Math.round(Math.min(Math.abs(minCharge), Math.abs(maxCharge))) : 1;
      let toCharge = Math.round(Math.max(Math.abs(minCharge), Math.abs(maxCharge)));
      let fromIsotope = Math.ceil(low);
      let toIsotope = Math.floor(high);
      let isotopeHeight = 1 / (toIsotope - fromIsotope + 1);
      let results = [];
      for (let charge = fromCharge; charge < toCharge + 1; charge++) {
        let isotopePositions = {
          x: [],
          y: []
        };
        for (let isotopePosition = fromIsotope; isotopePosition < toIsotope + 1; isotopePosition++) {
          isotopePositions.x.push(targetMass + isotopePosition * NEUTRON_MASS$1 / charge);
          isotopePositions.y.push(isotopeHeight);
        }
        let from = targetMass + low / Math.abs(charge);
        let to = targetMass + high / Math.abs(charge);
        similarityProcessor.setFromTo(from, to);
        if (widthFunction) {
          width = widthFunction(targetMass);
          similarityProcessor.setTrapezoid(width.bottom, width.top);
        }
        similarityProcessor.setPeaks2([isotopePositions.x, isotopePositions.y]);
        let result = similarityProcessor.getSimilarity();
        results.push({
          charge,
          similarity: result.similarity
        });
      }
      return results.sort((a, b) => b.similarity - a.similarity)[0].charge;
    }

    var getPeaksWithCharge$1 = {};

    Object.defineProperty(getPeaksWithCharge$1, "__esModule", {
      value: true
    });
    getPeaksWithCharge$1.getPeaksWithCharge = getPeaksWithCharge;
    // We can not really take the real value of the neutron because it varies from one element to another.
    const NEUTRON_MASS = 1;
    /**
     *
     * @param {Array} selectedPeaks
     * @param {Array} allPeaks
     * @param {object} [options={}]
     * @param {number} [options.min=1]
     * @param {number} [options.max=10]
     * @param {number} [options.low=-1]
     * @param {number} [options.high=1]
     * @param {number} [options.precision=30]
     * @returns
     */
    function getPeaksWithCharge(selectedPeaks, allPeaks, options = {}) {
      let {
        precision = 100,
        low = -1,
        high = 1,
        min: minCharge = 1,
        max: maxCharge = 10
      } = options;
      let fromCharge = minCharge * maxCharge > 0 ? Math.round(Math.min(Math.abs(minCharge), Math.abs(maxCharge))) : 1;
      let toCharge = Math.round(Math.max(Math.abs(minCharge), Math.abs(maxCharge)));
      let fromIsotope = Math.ceil(low);
      let toIsotope = Math.floor(high);
      let numberIsotopes = toIsotope - fromIsotope + 1;
      let isotopeIntensity = 1 / numberIsotopes;
      let fromIndex = 0;
      let localFromIndex = 0;
      let localToIndex = 0;
      const peaksWithCharge = [];
      for (const peak of selectedPeaks) {
        let targetMass = peak.x;
        localFromIndex = fromIndex;
        let bestCharge = fromCharge;
        let bestChargeMatch = 0;
        for (let charge = fromCharge; charge < toCharge + 1; charge++) {
          let theoreticalPositions = {
            x: [],
            y: new Array(numberIsotopes).fill(isotopeIntensity)
          };
          let massRange = precision * 1e-6 * targetMass;
          for (let isotopePosition = fromIsotope; isotopePosition < toIsotope + 1; isotopePosition++) {
            theoreticalPositions.x.push(targetMass + isotopePosition * NEUTRON_MASS / charge);
          }
          let fromMass = targetMass + low / Math.abs(charge) - massRange;
          let toMass = targetMass + high / Math.abs(charge) + massRange;
          if (charge === 1) {
            // we may move the fromIndex
            while (allPeaks[fromIndex].x < fromMass) {
              fromIndex++;
            }
          }
          /*
           * Find the from / to index for the specific peak and specific charge
           */
          while (allPeaks[localFromIndex].x < fromMass) {
            localFromIndex++;
          }
          localToIndex = localFromIndex;
          let localHeightSum = 0;
          while (localToIndex < allPeaks.length && allPeaks[localToIndex].x < toMass) {
            localHeightSum += allPeaks[localToIndex].y;
            localToIndex++;
          }
          localToIndex--;
          //  console.log({ localFromIndex, localToIndex });
          /*
            Calculate the overlap for a specific peak and specific charge
          */
          let currentTheoreticalPosition = 0;
          let theoreticalMaxValue = 1 / numberIsotopes;
          let totalMatch = 0;
          for (let index = localFromIndex; index <= localToIndex; index++) {
            let minMass = theoreticalPositions.x[currentTheoreticalPosition] - massRange / charge;
            let maxMass = theoreticalPositions.x[currentTheoreticalPosition] + massRange / charge;
            while (maxMass < allPeaks[index].x) {
              currentTheoreticalPosition++;
              theoreticalMaxValue = 1 / numberIsotopes;
              minMass = theoreticalPositions.x[currentTheoreticalPosition] - massRange / charge;
              maxMass = theoreticalPositions.x[currentTheoreticalPosition] + massRange / charge;
            }
            while (index < allPeaks.length && allPeaks[index].x < minMass) {
              index++;
            }
            //    console.log({ index, minMass, maxMass, massRange, localHeightSum });
            if (index < allPeaks.length && allPeaks[index].x <= maxMass) {
              while (index < allPeaks.length && allPeaks[index].x <= maxMass) {
                if (allPeaks[index].x >= minMass && allPeaks[index].x <= maxMass) {
                  let value = allPeaks[index].y / localHeightSum;
                  //      console.log({ theoreticalMaxValue, value });
                  value = Math.min(theoreticalMaxValue, value);
                  theoreticalMaxValue -= value;
                  totalMatch += value;
                }
                index++;
              }
              index--;
            }
            if (totalMatch > bestChargeMatch) {
              bestCharge = charge;
              bestChargeMatch = totalMatch;
            }
          }
        }
        peaksWithCharge.push({
          ...peak,
          charge: bestCharge
        });
      }
      return peaksWithCharge;
    }

    var isContinuous$1 = {};

    Object.defineProperty(isContinuous$1, "__esModule", {
      value: true
    });
    isContinuous$1.isContinuous = isContinuous;
    const ml_spectra_processing_1$4 = require$$0$3;
    /**
     * When a spectrum is continous ?
     * - has more than 100 points
     * - deltaX change can not be more than a factor 2
     * - deltaX may not be larger than 0.1
     * - if y is zero it does not count
     * @param {object} spectrum
     * @param {object} [options={}]
     * @param {number} [options.minLength=100]
     * @param {number} [options.relativeHeightThreshold=0.001] // Under this value the
     * @param {number} [options.maxDeltaRatio=3]
     */
    function isContinuous(spectrum, options = {}) {
      const {
        minLength = 100,
        maxDeltaRatio = 3,
        relativeHeightThreshold = 0.001
      } = options;
      const minHeight = (0, ml_spectra_processing_1$4.xMaxValue)(spectrum.data.y) * relativeHeightThreshold;
      const minRadio = 1 / maxDeltaRatio;
      const maxRatio = 1 * maxDeltaRatio;
      if (spectrum.continuous === undefined) {
        let xs = spectrum.data.x;
        let ys = spectrum.data.y;
        if (xs.length < minLength) {
          spectrum.continuous = false;
        } else {
          let previousDelta = xs[1] - xs[0];
          spectrum.continuous = true;
          let success = 0;
          let failed = 0;
          for (let i = 0; i < xs.length - 1; i++) {
            if (ys[i] < minHeight || ys[i + 1] < minHeight) {
              previousDelta = 0;
              continue;
            }
            let delta = xs[i + 1] - xs[i];
            if (previousDelta) {
              let ratio = delta / previousDelta;
              if ((Math.abs(delta) > 0.1 || ratio < minRadio || ratio > maxRatio) && ys[i] !== 0 && ys[i + 1] !== 0) {
                failed++;
              } else {
                success++;
              }
            }
            previousDelta = delta;
          }
          if (success / failed < 10) {
            spectrum.continuous = false;
          }
        }
      }
      return spectrum.continuous;
    }

    var peakPicking$2 = {};

    var require$$0$1 = /*@__PURE__*/getAugmentedNamespace(lib);

    Object.defineProperty(peakPicking$2, "__esModule", {
      value: true
    });
    peakPicking$2.peakPicking = peakPicking$1;
    const ml_gsd_1 = require$$0$1;
    const ml_spectra_processing_1$3 = require$$0$3;
    /**
     * Filter the array of peaks
     * @param {import('./Spectrum')} spectrum - array of all the peaks
     * @returns {array}
     */
    function peakPicking$1(spectrum) {
      if (!spectrum.peaks || spectrum.peaks.length === 0) {
        spectrum.peaks = [];
        const keys = Object.keys(spectrum.data).filter(key => key !== 'x' && key !== 'y');
        if (spectrum.isContinuous()) {
          // some experimental data are really problematic and we need to add this line
          const data = (0, ml_spectra_processing_1$3.xyEnsureGrowingX)(spectrum.data);
          const gsdPeaks = (0, ml_gsd_1.gsd)(data, {
            minMaxRatio: spectrum.options.threshold || 0.00025,
            // Threshold to determine if a given peak should be considered as a noise
            realTopDetection: true,
            smoothY: false,
            sgOptions: {
              windowSize: 7,
              polynomial: 3
            }
          });
          for (let gsdPeak of gsdPeaks) {
            const peak = {
              x: gsdPeak.x,
              y: gsdPeak.y,
              width: gsdPeak.width
            };
            for (let key of keys) {
              peak[key] = spectrum.data[key][gsdPeak.index];
            }
            spectrum.peaks.push(peak);
          }
        } else {
          spectrum.peaks = [];
          let data = spectrum.data;
          for (let i = 0; i < data.x.length; i++) {
            const peak = {
              x: data.x[i],
              y: data.y[i],
              width: 0
            };
            for (let key of keys) {
              peak[key] = spectrum.data[key][i];
            }
            spectrum.peaks.push(peak);
          }
        }
      }
      return spectrum.peaks;
    }

    var peaksWidth$1 = {};

    /**
     * Class representing simple linear regression.
     * The regression uses OLS to calculate intercept and slope.
     */
    class SimpleLinearRegression extends BaseRegression {
      /**
       * @param x - explanatory variable
       * @param y - response variable
       */
      constructor(x, y) {
        super();
        // @ts-expect-error internal use of the constructor, from `this.load`
        if (x === true) {
          // @ts-expect-error internal use of the constructor, from `this.load`
          const yObj = y;
          this.slope = yObj.slope;
          this.intercept = yObj.intercept;
          this.coefficients = [yObj.intercept, yObj.slope];
        } else {
          checkArrayLength(x, y);
          const result = regress$1(x, y);
          this.slope = result.slope;
          this.intercept = result.intercept;
          this.coefficients = [result.intercept, result.slope];
        }
      }
      /**
       * Get the parameters and model name in JSON format
       * @returns
       */
      toJSON() {
        return {
          name: 'simpleLinearRegression',
          slope: this.slope,
          intercept: this.intercept
        };
      }
      _predict(x) {
        return this.slope * x + this.intercept;
      }
      /**
       * Finds x for the given y value.
       * @param y - response variable value
       * @returns - x value
       */
      computeX(y) {
        return (y - this.intercept) / this.slope;
      }
      /**
       * Strings the linear function in the form 'f(x) = ax + b'
       * @param precision - number of significant figures.
       * @returns
       */
      toString(precision) {
        let result = 'f(x) = ';
        if (this.slope !== 0) {
          const xFactor = maybeToPrecision(this.slope, precision);
          result += `${xFactor === '1' ? '' : `${xFactor} * `}x`;
          if (this.intercept !== 0) {
            const absIntercept = Math.abs(this.intercept);
            const operator = absIntercept === this.intercept ? '+' : '-';
            result += ` ${operator} ${maybeToPrecision(absIntercept, precision)}`;
          }
        } else {
          result += maybeToPrecision(this.intercept, precision);
        }
        return result;
      }
      /**
       * Strings the linear function in the form 'f(x) = ax + b'
       * @param precision - number of significant figures.
       * @returns
       */
      toLaTeX(precision) {
        return this.toString(precision);
      }
      /**
       * Class instance from a JSON Object.
       * @param json
       * @returns
       */
      static load(json) {
        if (json.name !== 'simpleLinearRegression') {
          throw new TypeError('not a SLR model');
        }
        // @ts-expect-error internal use of the constructor
        return new SimpleLinearRegression(true, json);
      }
    }
    /**
     * Internal  function.
     * It determines the parameters (slope, intercept) of the line that best fit the `x,y` vector-data (simple linear regression).
     * @param x - explanatory variable
     * @param y - response variable
     * @returns - slope and intercept of the best fit line
     */
    function regress$1(x, y) {
      const n = x.length;
      let xSum = 0;
      let ySum = 0;
      let xSquared = 0;
      let xY = 0;
      for (let i = 0; i < n; i++) {
        xSum += x[i];
        ySum += y[i];
        xSquared += x[i] * x[i];
        xY += x[i] * y[i];
      }
      const numerator = n * xY - xSum * ySum;
      const slope = numerator / (n * xSquared - xSum * xSum);
      return {
        slope,
        intercept: 1 / n * ySum - slope * (1 / n) * xSum
      };
    }

    class PowerRegression extends BaseRegression {
      constructor(x, y) {
        super();
        if (x === true) {
          // reloading model
          this.A = y.A;
          this.B = y.B;
        } else {
          checkArrayLength(x, y);
          regress(this, x, y);
        }
      }
      _predict(newInputs) {
        return this.A * newInputs ** this.B;
      }
      toJSON() {
        return {
          name: 'powerRegression',
          A: this.A,
          B: this.B
        };
      }
      toString(precision) {
        return `f(x) = ${maybeToPrecision(this.A, precision)} * x^${maybeToPrecision(this.B, precision)}`;
      }
      toLaTeX(precision) {
        let latex = '';
        if (this.B >= 0) {
          latex = `f(x) = ${maybeToPrecision(this.A, precision)}x^{${maybeToPrecision(this.B, precision)}}`;
        } else {
          latex = `f(x) = \\frac{${maybeToPrecision(this.A, precision)}}{x^{${maybeToPrecision(-this.B, precision)}}}`;
        }
        latex = latex.replace(/e([+-]?[0-9]+)/g, 'e^{$1}');
        return latex;
      }
      static load(json) {
        if (json.name !== 'powerRegression') {
          throw new TypeError('not a power regression model');
        }
        return new PowerRegression(true, json);
      }
    }
    function regress(pr, x, y) {
      const n = x.length;
      const xl = new Array(n);
      const yl = new Array(n);
      for (let i = 0; i < n; i++) {
        xl[i] = Math.log(x[i]);
        yl[i] = Math.log(y[i]);
      }
      const linear = new SimpleLinearRegression(xl, yl);
      pr.A = Math.exp(linear.intercept);
      pr.B = linear.slope;
    }

    var src = /*#__PURE__*/Object.freeze({
        __proto__: null,
        PowerRegression: PowerRegression
    });

    var require$$0 = /*@__PURE__*/getAugmentedNamespace(src);

    Object.defineProperty(peaksWidth$1, "__esModule", {
      value: true
    });
    peaksWidth$1.peaksWidth = peaksWidth;
    const ml_regression_power_1 = require$$0;
    const ml_spectra_processing_1$2 = require$$0$3;
    function peaksWidth(peaks) {
      let xs = peaks.map(peak => peak.x);
      let widths = peaks.map(peak => peak.width);
      if (xs.length < 2) {
        throw new Error(`peaksWidth: not enough peaks (less than 2) for automatic width calculation: ${xs.length}`);
      }
      let regression = new ml_regression_power_1.PowerRegression(xs, widths);
      if (Number.isNaN(regression.A) || Number.isNaN(regression.B)) {
        throw new Error('peaksWidth: can not calculate regression');
      }
      let from = (0, ml_spectra_processing_1$2.xMinValue)(xs);
      let to = (0, ml_spectra_processing_1$2.xMaxValue)(xs);
      let regressionChart = {
        x: [],
        y: []
      };
      for (let x = from; x <= to; x += (to - from) / 1000) {
        regressionChart.x.push(x);
        regressionChart.y.push(regression.predict(x));
      }
      return {
        widths: {
          x: xs,
          y: widths
        },
        fit: regressionChart,
        score: regression.score(xs, widths),
        predictFct: regression.predict.bind(regression),
        tex: regression.toLaTeX(3),
        A: regression.A,
        B: regression.B,
        predictFctString: `${regression.A} * mass ** ${regression.B}`
      };
    }

    Object.defineProperty(Spectrum$1, "__esModule", {
      value: true
    });
    Spectrum$1.Spectrum = void 0;
    Spectrum$1.fromPeaks = fromPeaks;
    Spectrum$1.fromText = fromText$1;
    const is_any_array_1 = require$$0$4;
    const ml_spectra_processing_1$1 = require$$0$3;
    const xy_parser_1 = require$$2;
    const getBestPeaks_js_1 = getBestPeaks$1;
    const getFragmentPeaks_js_1 = getFragmentPeaks$1;
    const getMassRemainder_js_1 = getMassRemainder$1;
    const getPeakChargeBySimilarity_js_1 = getPeakChargeBySimilarity$1;
    const getPeaks_js_1 = getPeaks$1;
    const getPeaksWithCharge_js_1 = getPeaksWithCharge$1;
    const isContinuous_js_1 = isContinuous$1;
    const peakPicking_js_1 = peakPicking$2;
    const peaksWidth_js_1 = peaksWidth$1;
    const defaultData = {
      x: [],
      y: []
    };
    /**
     * Class dealing with mass spectra and peak picking
     */
    class Spectrum {
      /**
       *
       * @param {import('cheminfo-types').DataXY} data
       * @param {object} [options={}]
       * @param {number} [options.threshold=0.00025]
       */
      constructor(data = defaultData, options = {}) {
        if (typeof data !== 'object' || !(0, is_any_array_1.isAnyArray)(data.x) || !(0, is_any_array_1.isAnyArray)(data.y)) {
          throw new TypeError('Spectrum data must be an object with x:[], y:[]');
        }
        this.data = {
          ...data
        };
        this.options = {
          threshold: 0.00025,
          ...options
        };
        Object.defineProperty(this.data, 'xOriginal', {
          enumerable: false,
          writable: true
        });
        if (this.data && this.data.x.length > 0) {
          this.info = {
            minX: (0, ml_spectra_processing_1$1.xMinValue)(this.data.x),
            maxX: (0, ml_spectra_processing_1$1.xMaxValue)(this.data.x),
            minY: (0, ml_spectra_processing_1$1.xMinValue)(this.data.y),
            maxY: (0, ml_spectra_processing_1$1.xMaxValue)(this.data.y)
          };
        } else {
          this.info = {
            minX: Number.NaN,
            maxX: Number.NaN,
            minY: Number.NaN,
            maxY: Number.NaN
          };
        }
        this.cache = {};
        /**
         * someProperty is an example property that is set to `true`
         * @type {array}
         * @public
         */
        this.peaks = [];
      }
      minMaxX() {
        return (0, ml_spectra_processing_1$1.xMinMaxValues)(this.data.x);
      }
      /**
       *
       * @param {import('ml-spectra-processing').XYMaxYOptions} options
       * @returns
       */
      maxY(options) {
        return (0, ml_spectra_processing_1$1.xyMaxY)(this.data, options);
      }
      sumY() {
        if (!this.cache.sumY) {
          this.cache.sumY = (0, ml_spectra_processing_1$1.xSum)(this.data.y);
        }
        return this.cache.sumY;
      }
      scaleY(intensity = 1) {
        this.data.y = Array.from((0, ml_spectra_processing_1$1.xNormed)(this.data.y, {
          value: intensity,
          algorithm: 'max'
        }));
        return this;
      }
      rescaleX(callback) {
        this.ensureOriginalX();
        for (let i = 0; i < this.data.x.length; i++) {
          this.data.x[i] = callback(this.data.xOriginal[i]);
        }
        return this;
      }
      ensureOriginalX() {
        if (!this.data.xOriginal) {
          this.data.xOriginal = this.data.x.slice(0);
        }
      }
      normedY(total = 1) {
        this.data.y = (0, ml_spectra_processing_1$1.xNormed)(this.data.y, {
          value: total
        });
        return this;
      }
      peakPicking() {
        (0, peakPicking_js_1.peakPicking)(this);
        return this.peaks;
      }
      peaksWidth() {
        (0, peakPicking_js_1.peakPicking)(this);
        return (0, peaksWidth_js_1.peaksWidth)(this.peaks);
      }
      getBestPeaks(options) {
        (0, peakPicking_js_1.peakPicking)(this);
        return (0, getBestPeaks_js_1.getBestPeaks)(this.peaks, options);
      }
      /**
       * This is a very intensive function so better to calculate it on a selection of peaks
       * @param {Array} selectedPeaks
       * @param {object} [options={}]
       * @param {number} [options.min=1]
       * @param {number} [options.max=10]
       * @param {number} [options.low=-1]
       * @param {number} [options.high=1]
       * @param {number} [options.precision=30]
       * @returns
       */
      getSelectedPeaksWithCharge(selectedPeaks, options) {
        return (0, getPeaksWithCharge_js_1.getPeaksWithCharge)(selectedPeaks, this.peaks, options);
      }
      getPeakChargeBySimilarity(targetMass, options) {
        return (0, getPeakChargeBySimilarity_js_1.getPeakChargeBySimilarity)(this, targetMass, options);
      }
      getPeaks(options) {
        (0, peakPicking_js_1.peakPicking)(this);
        return (0, getPeaks_js_1.getPeaks)(this.peaks, options);
      }
      getPeaksAsDataXY(options) {
        (0, peakPicking_js_1.peakPicking)(this);
        const peaks = (0, getPeaks_js_1.getPeaks)(this.peaks, options);
        return {
          x: peaks.map(peak => peak.x),
          y: peaks.map(peak => peak.y)
        };
      }
      /**
       * Returns also peaks possible for a specific molecular formula
       * @example
       *  const spectrum = new Spectrum({x:[], y:[]})
       *  await spectrum.getFragmentPeaks();
       * @param {string} mf
       * @param {object} options
       * @returns
       */
      getFragmentPeaksFct(mf, options) {
        (0, peakPicking_js_1.peakPicking)(this);
        return (0, getFragmentPeaks_js_1.getFragmentPeaks)(this.peaks, mf, options);
      }
      isContinuous() {
        return (0, isContinuous_js_1.isContinuous)(this);
      }
      /**
       * Remove an integer number of time the specifiedd monoisotopic mass
       * Mass remainder analysis (MARA): https://doi.org/10.1021/acs.analchem.7b04730
       */
      getMassRemainderFct(mass, options = {}) {
        return (0, getMassRemainder_js_1.getMassRemainder)(this.data, mass, options);
      }
    }
    Spectrum$1.Spectrum = Spectrum;
    function fromPeaks(peaks) {
      if (peaks.length === 0) return new Spectrum();
      const data = {};
      for (let key of Object.keys(peaks[0])) {
        data[key] = peaks.map(peak => peak[key]);
      }
      return new Spectrum(data);
    }
    function fromText$1(text, options) {
      const data = (0, xy_parser_1.parseXY)(text);
      return new Spectrum(data, options);
    }

    var MSComparator$1 = {};

    Object.defineProperty(MSComparator$1, "__esModule", {
      value: true
    });
    MSComparator$1.MSComparator = void 0;
    const ml_spectra_processing_1 = require$$0$3;
    class MSComparator {
      /**
       * Create a class that will be able to get the similarity between 2 spectra
       * The similarity is based on 'cosine' similarity. The goal is 2 prepare 2 vectors
       * on which the similarity is calculated.
       * The vectors are created by taking the mass and the intensity of the peaks.
       * @param {object} [options={}]
       * @param {number} [options.nbPeaks] - Before comparing spectra how many peaks should be kept
       * @param {number} [options.minNbCommonPeaks] - Minimum number of peaks in common to consider any similarity
       * @param {number} [options.minIntensity] - What is the minimal relative intensity to keep a peak
       * @param {number} [options.massPower=3] - High power will give more weight to the mass. If you would prefer to observe fragments you should use a number less than 1
       * @param {number} [options.intensityPower=0.6] - How important is the intensity. By default we don't give to much importance to it
       * @param {number[]} [options.selectedMasses] - List of allowed masses.
       * @param {number|Function} [options.delta=0.1] - Tolerance in Da (u) to consider 2 peaks as aligned. If a function is provided it will be called with the mass of the peak
       */
      constructor(options = {}) {
        this.options = {
          massPower: 3,
          intensityPower: 0.6,
          delta: 0.1,
          ...options
        };
        this.cache = new WeakMap();
      }
      /**
       * Get the similarity between a spectrum and a list of masses.
       * The main issue is that we don't have the intensity of the peaks.
       * So we will use the intensity of the closest peak.
       * @param {import('cheminfo-types').DataXY} dataXY
       * @param {number[]} masses
       */
      getSimilarityToMasses(dataXY, masses) {
        const data1 = normalizeAndCacheData(this.cache, dataXY, this.options);
        const data2 = {
          x: Float64Array.from(masses),
          y: new Float64Array(masses.length).fill(1)
        };
        let aligned;
        if (this.options.selectedMasses?.length > 0) {
          aligned = (0, ml_spectra_processing_1.xyArrayAlign)([data1, data2, {
            // this allows to force the selection of some specific masses
            x: Float64Array.from(this.options.selectedMasses),
            y: new Float64Array(this.options.selectedMasses.length).fill(1)
          }], {
            delta: this.options.delta,
            requiredY: true
          });
        } else {
          aligned = (0, ml_spectra_processing_1.xyArrayAlign)([data1, data2], {
            delta: this.options.delta
          });
        }
        // because we don't have any idea of the intensity we will use the intensity of the experimental peak
        // and otherwise we ignore the theoretical peak
        for (let i = 0; i < aligned.ys[0].length; i++) {
          if (aligned.ys[0][i] === 0) {
            aligned.ys[1][i] = 0;
          }
          if (aligned.ys[0][i] > 0 && aligned.ys[1][i] !== 0) {
            aligned.ys[1][i] = aligned.ys[0][i];
          }
        }
        return returnSimilarity(aligned, this.options);
      }
      /**
       *
       * @param {import('cheminfo-types').DataXY} dataXY1
       * @param {import('cheminfo-types').DataXY} dataXY2
       */
      getSimilarity(dataXY1, dataXY2) {
        const data1 = normalizeAndCacheData(this.cache, dataXY1, this.options);
        const data2 = normalizeAndCacheData(this.cache, dataXY2, this.options);
        let aligned;
        if (this.options.selectedMasses?.length > 0) {
          aligned = (0, ml_spectra_processing_1.xyArrayAlign)([data1, data2, {
            // this allows to force the selection of some specific masses
            x: Float64Array.from(this.options.selectedMasses),
            y: new Float64Array(this.options.selectedMasses.length).fill(1)
          }], {
            delta: this.options.delta,
            requiredY: true
          });
        } else {
          aligned = (0, ml_spectra_processing_1.xyArrayAlign)([data1, data2], {
            delta: this.options.delta
          });
        }
        return returnSimilarity(aligned, this.options);
      }
    }
    MSComparator$1.MSComparator = MSComparator;
    /**
     *
     * @param {WeakMap} cache
     * @param {import('cheminfo-types').DataXY} dataXY
     * @param {object} [options={}]
     * @param {number} [options.nbPeaks]
     * @param {number} [options.minIntensity]
     */
    function normalizeAndCacheData(cache, dataXY, options = {}) {
      const {
        nbPeaks,
        minIntensity
      } = options;
      if (cache.has(dataXY)) return cache.get(dataXY);
      let data = {
        ...dataXY
      };
      if ((0, ml_spectra_processing_1.xIsMonotonic)(data.x) !== 1) {
        data = (0, ml_spectra_processing_1.xySortX)(data);
      }
      if (minIntensity !== undefined) {
        data = (0, ml_spectra_processing_1.xyFilterMinYValue)(data, minIntensity);
      }
      if (nbPeaks !== undefined) {
        data = (0, ml_spectra_processing_1.xyFilterTopYValues)(data, nbPeaks);
      }
      cache.set(dataXY, data);
      return data;
    }
    /**
     *
     * @param {*} aligned
     * @param {object} [options={}]
     * @param {number} [options.massPower]
     * @param {number} [options.intensityPower]
     * @param {number} [options.minNbCommonPeaks]

     * @returns
     */
    function returnSimilarity(aligned, options = {}) {
      const {
        massPower,
        intensityPower,
        minNbCommonPeaks
      } = options;
      let nbCommonPeaks = 0;
      let nbPeaks1 = 0;
      let nbPeaks2 = 0;
      for (let i = 0; i < aligned.ys[0].length; i++) {
        if (aligned.ys[0][i] !== 0) {
          nbPeaks1++;
        }
        if (aligned.ys[1][i] !== 0) {
          nbPeaks2++;
        }
        if (aligned.ys[0][i] !== 0 && aligned.ys[1][i] !== 0) {
          nbCommonPeaks++;
        }
      }
      if (nbCommonPeaks === 0 || minNbCommonPeaks && nbCommonPeaks < minNbCommonPeaks) {
        return {
          nbCommonPeaks,
          nbPeaks1,
          nbPeaks2,
          tanimoto: 0,
          cosine: 0
        };
      }
      const vector1 = new Float64Array(aligned.x.length);
      const vector2 = new Float64Array(aligned.x.length);
      for (let i = 0; i < aligned.x.length; i++) {
        vector1[i] = aligned.x[i] ** massPower * aligned.ys[0][i] ** intensityPower;
        vector2[i] = aligned.x[i] ** massPower * aligned.ys[1][i] ** intensityPower;
      }
      return {
        nbCommonPeaks,
        nbPeaks1,
        nbPeaks2,
        tanimoto: nbCommonPeaks / (nbPeaks1 + nbPeaks2 - nbCommonPeaks),
        cosine: cosineSimilarity(vector1, vector2)
      };
    }
    /**
     * Returns the average of cosine distances between vectors a and b
     * Copied from https://github.com/mljs/distance/blob/0b15acd6476413f4111cb4852ca1bec9edaa2805/src/similarities/cosine.ts
     * @param a {import('cheminfo-types').NumberArray} - first vector
     * @param b {import('cheminfo-types').NumberArray} - second vector
     * @returns {number} - cosine similarity
     */
    function cosineSimilarity(a, b) {
      let p = 0;
      let p2 = 0;
      let q2 = 0;
      for (let i = 0; i < a.length; i++) {
        p += a[i] * b[i];
        p2 += a[i] * a[i];
        q2 += b[i] * b[i];
      }
      return p / (Math.sqrt(p2) * Math.sqrt(q2));
    }

    var jsgraph = {};

    var getPeaksAnnotation$1 = {};

    Object.defineProperty(getPeaksAnnotation$1, "__esModule", {
      value: true
    });
    getPeaksAnnotation$1.getPeaksAnnotation = getPeaksAnnotation;
    const fromMonoisotopicMass_1 = fromMonoisotopicMass$1;
    /**
     *
     * @param {array} bestPeaks
     * @param {object} [options={}]
     * @param {number} [options.numberDigits=5]
     * @param {number} [options.numberMFs=0]
     * @param {boolean} [options.showMF=false]
     * @param {array} [options.mfColors={}]
     * @param {number} [options.charge=1]
     * @param {number} [options.shift=0]
     * @param {object} [options.mfPrefs]
     * @param {number} [options.displayCharge=true]
     * @param {number} [options.displayProperties=[]] Array of properties name to display
     * @returns {Promise}
     */
    async function getPeaksAnnotation(bestPeaks, options = {}) {
      let {
        numberDigits = 5,
        shift = 0,
        showMF = false,
        numberMFs = 0,
        charge = 1,
        mfPrefs = {},
        displayCharge = true,
        displayProperties = [],
        mfColors = [{
          limit: 3,
          color: 'green'
        }, {
          limit: 20,
          color: 'lightgreen'
        }, {
          limit: 50,
          color: 'lightorange'
        }]
      } = options;
      if (showMF && !numberMFs) numberMFs = 1;
      let annotations = [];
      bestPeaks.sort((a, b) => a.close ? -1 : b.close ? 1 : 0);
      for (let peak of bestPeaks) {
        let textLine = 0;
        let annotation;
        if (peak.close) {
          annotation = {
            type: 'line',
            _highlight: peak._highlight,
            info: peak,
            position: [{
              y: peak.y,
              dy: '-5px',
              x: peak.x
            }, {
              y: peak.y,
              dy: '-15px',
              x: peak.x
            }]
          };
          annotations.push(annotation);
          annotation = {
            type: 'ellipse',
            _highlight: peak._highlight,
            info: peak,
            position: [{
              y: peak.y,
              dy: '-15px',
              x: peak.x
            }],
            props: {
              rx: '3px',
              ry: '3px',
              fillOpacity: 0.3
            }
          };
        } else {
          annotation = {
            type: 'line',
            _highlight: peak._highlight,
            position: [{
              y: peak.y,
              dy: '-5px',
              x: peak.x
            }, {
              y: peak.y,
              dy: '-25px',
              x: peak.x
            }],
            labels: [{
              text: (peak.x + shift).toFixed(numberDigits),
              color: 'red',
              position: {
                x: peak.x,
                y: peak.y,
                dy: `${textLine++ * -13 - 17}px`,
                dx: '2px'
              }
            }]
          };
          if (displayCharge && peak.charge !== undefined) {
            annotation.labels.push({
              text: `Z:${peak.charge}`,
              color: 'grey',
              position: {
                x: peak.x,
                y: peak.y,
                dy: '-4px',
                dx: '2px'
              }
            });
          }
          let mfs = [];
          if (numberMFs) {
            // we have 2 cases. Either there is a shift and we deal with differences
            // otherwise it is absolute
            // if there is a shift we consider only a neutral loss and the parameter charge is important
            // we also check that the selected peak (for relative mass) is not the current peak
            if (shift && Math.abs(peak.x + shift) > Number.EPSILON) {
              // neutral loss
              let currentMfPrefs = {
                ...mfPrefs,
                allowNeutral: true,
                ionizations: ''
              };
              // we need to deal with the precision and increase it
              currentMfPrefs.precision = currentMfPrefs.precision / Math.max(Math.abs(peak.x + shift), 1) * peak.x;
              ({
                mfs
              } = await (0, fromMonoisotopicMass_1.fromMonoisotopicMass)(Math.abs((peak.x + shift) * charge), currentMfPrefs));
            } else {
              ({
                mfs
              } = await (0, fromMonoisotopicMass_1.fromMonoisotopicMass)(Math.abs(peak.x * charge), mfPrefs));
            }
            let numberOfMFS = Math.min(mfs.length, numberMFs);
            for (let i = 0; i < numberOfMFS; i++) {
              let mf = mfs[i];
              let ppm = shift ? mf.ms.ppm / shift * mfs[0].ms.em : mf.ms.ppm;
              annotation.labels.push({
                text: mf.mf,
                color: getColor(mfColors, Math.abs(ppm)),
                position: {
                  x: peak.x,
                  y: peak.y,
                  dy: `${textLine++ * -13 - 17}px`,
                  dx: '2px'
                }
              });
            }
          }
          if (displayProperties.length > 0) {
            for (let property of displayProperties) {
              annotation.labels.push({
                text: peak[property],
                color: 'red',
                position: {
                  x: peak.x,
                  y: peak.y,
                  dy: `${textLine++ * -13 - 17}px`,
                  dx: '2px'
                }
              });
            }
          }
        }
        annotations.push(annotation);
      }
      return annotations;
    }
    function getColor(colors, value) {
      for (let color of colors) {
        if (value < color.limit) return color.color;
      }
      return 'lightgrey';
    }

    Object.defineProperty(jsgraph, "__esModule", {
      value: true
    });
    jsgraph.JsGraph = void 0;
    const getPeaksAnnotation_js_1 = getPeaksAnnotation$1;
    jsgraph.JsGraph = {
      getPeaksAnnotation: getPeaksAnnotation_js_1.getPeaksAnnotation
    };

    (function (exports) {

      var __createBinding = src$8 && src$8.__createBinding || (Object.create ? function (o, m, k, k2) {
        if (k2 === undefined) k2 = k;
        var desc = Object.getOwnPropertyDescriptor(m, k);
        if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
          desc = {
            enumerable: true,
            get: function () {
              return m[k];
            }
          };
        }
        Object.defineProperty(o, k2, desc);
      } : function (o, m, k, k2) {
        if (k2 === undefined) k2 = k;
        o[k2] = m[k];
      });
      var __exportStar = src$8 && src$8.__exportStar || function (m, exports) {
        for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
      };
      Object.defineProperty(exports, "__esModule", {
        value: true
      });
      __exportStar(Spectrum$1, exports);
      __exportStar(getPeaks$1, exports);
      __exportStar(getBestPeaks$1, exports);
      __exportStar(MSComparator$1, exports);
      __exportStar(jsgraph, exports);
      __exportStar(fromMonoisotopicMass$1, exports);
    })(src$8);

    /**
     * Creates annotations for jsgraph that allows to display the result of peak picking
     * @param peaks - Array of peaks to create annotations for
     * @param options - Configuration options for the annotations
     * @returns Array of annotations
     */
    function getAnnotations(peaks, options = {}) {
      const {
        fillColor = 'green',
        strokeColor = 'red',
        creationFct
      } = options;
      const annotations = peaks.map(peak => {
        const annotation = {
          line: 1,
          type: 'rect',
          strokeColor,
          strokeWidth: 0,
          fillColor
        };
        if (creationFct) {
          creationFct(annotation, peak);
        }
        annotationPeak(annotation, peak, options);
        return annotation;
      });
      return annotations;
    }
    function annotationPeak(annotation, peak, options = {}) {
      const {
        showAssignment = true,
        showIntensity = true,
        showMass = true
      } = options;
      const labels = [];
      let line = 0;
      const shiftY = -3;
      if (showMass) {
        labels.push({
          text: `m/z: ${peak.mass?.toFixed(0)}`,
          size: '18px',
          anchor: 'left',
          color: 'green',
          position: {
            x: peak.mass,
            y: peak.intensity,
            dy: `${shiftY - line * 14}px`,
            dx: '3px'
          }
        });
        line++;
      }
      if (showIntensity) {
        labels.push({
          text: `${peak.intensity?.toFixed(0)}%`,
          size: '18px',
          anchor: 'left',
          color: 'green',
          position: {
            x: peak.mass,
            y: peak.intensity,
            dy: `${shiftY - line * 14}px`,
            dx: '3px'
          }
        });
        line++;
      }
      if (showAssignment && peak.assignment) {
        labels.push({
          text: peak.assignment,
          size: '18px',
          anchor: 'middle',
          color: 'red',
          position: {
            x: peak.mass,
            y: peak.intensity,
            dy: `${shiftY - 3 - line * 14}px`
          }
        });
        // line++; // it it the last one we do not increase the line number
      }
      annotation.labels = labels;
      annotation.position = [{
        x: peak.mass,
        y: peak.intensity,
        dy: '-1px',
        dx: '-1px'
      }, {
        x: peak.mass,
        y: peak.intensity,
        dy: `-${3 + line * 14}px`,
        dx: '1px'
      }];
    }

    /**
     * We will force maxY to 100
     * @param variables - The variables object containing x and y data
     * @returns - The modified variables object with y data normalized
     */
    function spectrumCallback(variables) {
      variables.y.data = xNormed(variables.y.data, {
        algorithm: 'max',
        value: 100
      });
      return variables;
    }

    function fromText(blob, options = {}) {
      const text = ensureString(blob);
      const {
        title
      } = options;
      const {
        data,
        info
      } = parseXYAndKeepInfo(text, {
        bestGuess: true,
        ...options
      });
      const spectrum = {
        x: {
          data: data.x,
          units: 'm/z',
          label: 'm/z'
        },
        y: {
          data: data.y,
          units: '',
          label: 'Relative intensity'
        }
      };
      const meta = {};
      let index = 1;
      for (const item of info) {
        meta[`Info ${index++}`] = item.value;
      }
      const analysis = new Analysis({
        spectrumCallback
      });
      analysis.pushSpectrum(spectrum, {
        dataType: 'MASS SPECTRUM',
        title,
        meta
      });
      return analysis;
    }

    /**
     * Creates a new Analysis from a SPC buffer
     * @param jcamp
     * @param [options={}]
     * @param [options.id=Math.random()]
     * @param [options.label=options.id] - human redeable label
     * @param [options.spectrumCallback] - a callback to apply on variables when creating spectrum. Default will add a and t
     * @returns - New class element with the given data
     */
    function fromJcamp(jcamp, options = {}) {
      return fromJcamp$1(jcamp, {
        ...options,
        spectrumCallback
      });
    }

    function autoPeakPicking(spectrum, options = {}) {
      options = {
        threshold: 0.1,
        numberSlots: 20,
        numberCloseSlots: 40,
        ...options
      };
      const data = {
        x: spectrum.variables.x.data,
        y: spectrum.variables.y.data
      };
      const msSpectrum = new src$8.Spectrum(data);
      // we need to know the global best peaks to know his intensity and set it to 100
      const minMaxX = msSpectrum.minMaxX();
      const maxY = msSpectrum.getBestPeaks({
        ...options,
        from: minMaxX.min,
        to: minMaxX.max
      }).sort((a, b) => b.y - a.y)[0]?.y;
      const maxRatio = maxY ? 100 / maxY : 1;
      const peaks = msSpectrum.getBestPeaks(options);
      return peaks.map(peak => {
        return {
          mass: peak.x,
          intensity: peak.y * maxRatio
        };
      });
    }
    function peakPicking(spectrum, target) {
      const peak = peakPicking$3(spectrum, target, {
        xVariable: 'x',
        yVariable: 'y',
        optimize: false,
        // we could optimize the peaks but it depends of the width
        // and also if it is continuous or not. Not obvious
        // if we optimize we need to add this parameters for high res spectrum
        //   shape: { kind: 'gaussian', fwhm: 0.01 },
        max: true
      });
      if (!peak) return undefined;
      return {
        mass: peak.x,
        intensity: peak.y
      };
    }
    const JSGraph = {
      ...JSGraph$1,
      getAnnotations
    };

    exports.AnalysesManager = AnalysesManager;
    exports.Analysis = Analysis;
    exports.JSGraph = JSGraph;
    exports.autoPeakPicking = autoPeakPicking;
    exports.fromJcamp = fromJcamp;
    exports.fromText = fromText;
    exports.peakPicking = peakPicking;
    exports.toJcamp = toJcamp;

}));
//# sourceMappingURL=mass-spectrum.js.map
