/*
 * chromatography v9.0.0
 * Tools for storing, searching and analyzing GC/MS data
 * https://github.com/cheminfo/chromatography#readme
 *
 * Licensed under the MIT license.
 */
(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.Chromatography = {}));
})(this, (function (exports) { 'use strict';

    // eslint-disable-next-line @typescript-eslint/unbound-method
    const toString$2 = 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$2.call(value);
      return tag.endsWith('Array]') && !tag.includes('Big');
    }

    /**
     * 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 - options.
     * @returns median.
     */
    function xMedian(input, options = {}) {
      if (!isAnyArray$1(input)) {
        throw new TypeError('input must be an array');
      }
      const {
        exact = false,
        fromIndex = 0,
        toIndex = input.length
      } = options || {};
      const array = input.slice(fromIndex, toIndex);
      if (array.length === 0) {
        throw new TypeError('input must not be empty');
      }
      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 = 1
      } = 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 (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 value.
     * @param options - 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 = new Float64Array(array.length);
      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;
    }

    /**
     * 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');
      }
    }

    /**
     * Dot product between two arrays.
     * @param A - first array.
     * @param B - second array.
     */
    function xDotProduct(A, B) {
      xCheckLengths(A, B);
      let result = 0;
      for (let i = 0; i < A.length; i++) {
        result += A[i] * B[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);
    }

    /**
     * Downsample an array by averaging consecutive non-overlapping points (binning).
     * Either specify a fixed `binSize` or a target `numberOfPoints`.
     * @param array - input array.
     * @param options - options.
     * @returns downsampled array.
     */
    function xBinning(array, options = {}) {
      xCheck(array);
      const {
        binSize,
        numberOfPoints,
        keepFirstAndLast = true
      } = options;
      const {
        length
      } = array;
      if (numberOfPoints !== undefined) {
        if (binSize !== undefined) {
          throw new RangeError('binSize and numberOfPoints are mutually exclusive');
        }
        if (!Number.isInteger(numberOfPoints) || numberOfPoints <= 0) {
          throw new RangeError('numberOfPoints must be a positive integer');
        }
        if (numberOfPoints > length) {
          throw new RangeError('numberOfPoints must be <= array.length');
        }
        return binByNumberOfPoints(array, numberOfPoints, keepFirstAndLast);
      }
      const effectiveBinSize = binSize ?? 10;
      if (!Number.isInteger(effectiveBinSize) || effectiveBinSize <= 0) {
        throw new RangeError('binSize must be a positive integer');
      }
      if (effectiveBinSize === 1) {
        return Float64Array.from(array);
      }
      if (keepFirstAndLast) {
        if (length <= 2) {
          return Float64Array.from(array);
        }
        const innerLength = length - 2;
        const innerBinCount = Math.ceil(innerLength / effectiveBinSize);
        const output = new Float64Array(innerBinCount + 2);
        output[0] = array[0];
        output[output.length - 1] = array[length - 1];
        for (let i = 0, j = 1; i < innerLength; i += effectiveBinSize, j++) {
          const start = i + 1;
          const end = Math.min(start + effectiveBinSize, length - 1);
          let sum = 0;
          for (let k = start; k < end; k++) {
            sum += array[k];
          }
          output[j] = sum / (end - start);
        }
        return output;
      }
      const outputLength = Math.ceil(length / effectiveBinSize);
      const output = new Float64Array(outputLength);
      for (let i = 0, j = 0; i < length; i += effectiveBinSize, j++) {
        const end = Math.min(i + effectiveBinSize, length);
        let sum = 0;
        for (let k = i; k < end; k++) {
          sum += array[k];
        }
        output[j] = sum / (end - i);
      }
      return output;
    }
    function binByNumberOfPoints(array, numberOfPoints, keepFirstAndLast) {
      const {
        length
      } = array;
      const output = new Float64Array(numberOfPoints);
      if (keepFirstAndLast) {
        if (numberOfPoints < 2) {
          throw new RangeError('numberOfPoints must be >= 2 when keepFirstAndLast is true');
        }
        output[0] = array[0];
        output[numberOfPoints - 1] = array[length - 1];
        if (numberOfPoints === 2) {
          return output;
        }
        const innerLength = length - 2;
        const innerBins = numberOfPoints - 2;
        for (let j = 0; j < innerBins; j++) {
          const start = 1 + Math.floor(j * innerLength / innerBins);
          const end = 1 + Math.floor((j + 1) * innerLength / innerBins);
          let sum = 0;
          for (let k = start; k < end; k++) {
            sum += array[k];
          }
          output[j + 1] = sum / (end - start);
        }
        return output;
      }
      for (let j = 0; j < numberOfPoints; j++) {
        const start = Math.floor(j * length / numberOfPoints);
        const end = Math.floor((j + 1) * length / numberOfPoints);
        let sum = 0;
        for (let k = start; k < end; k++) {
          sum += array[k];
        }
        output[j] = sum / (end - start);
      }
      return output;
    }

    // Internal helper: not re-exported from `x/index.ts`, so it stays out of the public API.
    /**
     * Copy the data into an ascending-sorted `Float64Array`.
     * The library does not accept NaN: it corrupts ranks, quartiles and moments, so a NaN
     * in the input throws. Sorting groups every NaN at the end, so a single look at the
     * last element detects them.
     * @param array - data.
     * @returns ascending-sorted values.
     */
    function getSortedFloat64(array) {
      const sorted = Float64Array.from(array);
      sorted.sort();
      // NaN sorts to the end, so the last element tells us whether any NaN is present.
      if (Number.isNaN(sorted.at(-1))) {
        throw new Error('input must not contain NaN values');
      }
      return sorted;
    }

    /**
     * Calculating the box plot of the array
     * This function will interpolate the values and use the inclusive algorithm
     * @param array - data.
     * @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
      const sorted = getSortedFloat64(array);
      return boxPlotFromSorted(sorted);
    }
    /**
     * Calculating the box plot of an already-sorted array, without copying or re-sorting.
     * Internal helper: not re-exported from `x/index.ts`, so it stays out of the public API.
     * @param array - sorted data.
     * @returns q1, median, q3, min, max.
     */
    function boxPlotFromSorted(array) {
      const min = array[0];
      const max = array.at(-1);
      // need to deal with very close points otherwise it yields to incorrect results
      if (max - min <= Number.EPSILON) {
        // if one of the 2 numbers is an integer let's take this one
        const shortTestNumber = String(min).length < String(max).length ? min : max;
        return {
          min,
          q1: shortTestNumber,
          median: shortTestNumber,
          q3: shortTestNumber,
          max
        };
      }
      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,
        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
      };
    }

    /**
     * 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) {
      xCheck(array);
      const sorted = getSortedFloat64(array);
      return boxPlotWithOutliersFromSorted(sorted);
    }
    /**
     * Calculating the box plot with outliers of an already-sorted array, without copying
     * or re-sorting. Because the array is sorted, the outliers are the low prefix and the
     * high suffix, so they are reported in ascending order.
     * Internal helper: not re-exported from `x/index.ts`, so it stays out of the public API.
     * @param sorted - sorted data.
     * @returns - q1, median, q3, min, max, outliers
     */
    function boxPlotWithOutliersFromSorted(sorted) {
      return boxPlotWithOutliersAndBounds(sorted).boxPlot;
    }
    /**
     * Same computation as {@link boxPlotWithOutliersFromSorted}, but also returns the
     * non-outlier index range so callers that need the filtered values (mean, sd, …) can
     * reuse the bounds instead of scanning the array a second time.
     * Internal helper: not re-exported from `x/index.ts`, so it stays out of the public API.
     * @param sorted - sorted data.
     * @returns - the box plot and the non-outlier index range.
     */
    function boxPlotWithOutliersAndBounds(sorted) {
      const boxPlot = boxPlotFromSorted(sorted);
      if (boxPlot.max - boxPlot.min <= Number.EPSILON) {
        return {
          boxPlot: {
            ...boxPlot,
            lowerWhisker: boxPlot.min,
            upperWhisker: boxPlot.max,
            minWhisker: boxPlot.min,
            maxWhisker: boxPlot.max,
            iqr: 0,
            outliers: []
          },
          bounds: {
            lowIndex: 0,
            highIndex: sorted.length
          }
        };
      }
      const iqr = boxPlot.q3 - boxPlot.q1;
      const lowerWhisker = boxPlot.q1 - 1.5 * iqr;
      const upperWhisker = boxPlot.q3 + 1.5 * iqr;
      const bounds = getWhiskerBounds(sorted, lowerWhisker, upperWhisker);
      const {
        lowIndex,
        highIndex
      } = bounds;
      const outliers = [];
      for (let i = 0; i < lowIndex; i++) {
        outliers.push(sorted[i]);
      }
      for (let i = highIndex; i < sorted.length; i++) {
        outliers.push(sorted[i]);
      }
      return {
        boxPlot: {
          ...boxPlot,
          lowerWhisker,
          upperWhisker,
          minWhisker: sorted[lowIndex],
          maxWhisker: sorted[highIndex - 1],
          iqr,
          outliers
        },
        bounds
      };
    }
    /**
     * Locate the contiguous range `[lowIndex, highIndex)` of non-outliers in a sorted array.
     * Because the array is sorted, outliers are exactly the prefix below `lowerWhisker` and
     * the suffix above `upperWhisker`, so the scan only touches the outliers themselves.
     * Internal helper: not re-exported from `x/index.ts`, so it stays out of the public API.
     * @param sorted - sorted data.
     * @param lowerWhisker - values strictly below are low outliers.
     * @param upperWhisker - values strictly above are high outliers.
     * @returns the non-outlier index range.
     */
    function getWhiskerBounds(sorted, lowerWhisker, upperWhisker) {
      let lowIndex = 0;
      while (lowIndex < sorted.length && sorted[lowIndex] < lowerWhisker) {
        lowIndex++;
      }
      let highIndex = sorted.length;
      while (highIndex > lowIndex && sorted[highIndex - 1] > upperWhisker) {
        highIndex--;
      }
      return {
        lowIndex,
        highIndex
      };
    }

    /**
     * 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 (e) {}
    			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 = {};

    Object.defineProperty(matrix, '__esModule', {
      value: true
    });

    // eslint-disable-next-line @typescript-eslint/unbound-method
    const toString$1 = 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(value) {
      const tag = toString$1.call(value);
      return tag.endsWith('Array]') && !tag.includes('Big');
    }

    /**
     * Computes the maximum of the given values.
     *
     * @param input
     * @param options
     */
    function max(input, options = {}) {
      if (!isAnyArray(input)) {
        throw new TypeError('input must be an array');
      }
      if (input.length === 0) {
        throw new TypeError('input must not be empty');
      }
      const {
        fromIndex = 0,
        toIndex = input.length
      } = options;
      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');
      }
      let maxValue = input[fromIndex];
      for (let i = fromIndex + 1; i < toIndex; i++) {
        if (input[i] > maxValue) maxValue = input[i];
      }
      return maxValue;
    }

    /**
     * Computes the minimum of the given values.
     */
    function min(input, options = {}) {
      if (!isAnyArray(input)) {
        throw new TypeError('input must be an array');
      }
      if (input.length === 0) {
        throw new TypeError('input must not be empty');
      }
      const {
        fromIndex = 0,
        toIndex = input.length
      } = options;
      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');
      }
      let minValue = input[fromIndex];
      for (let i = fromIndex + 1; i < toIndex; i++) {
        if (input[i] < minValue) minValue = input[i];
      }
      return minValue;
    }

    /**
     * Rescale an array into a range.
     */
    function rescale(input, options = {}) {
      if (!isAnyArray(input)) {
        throw new TypeError('input must be an array');
      } else if (input.length === 0) {
        throw new TypeError('input must not be empty');
      }
      let output;
      if (options.output !== undefined) {
        if (!isAnyArray(options.output)) {
          throw new TypeError('output option must be an array if specified');
        }
        output = options.output;
      } else {
        output = new Array(input.length);
      }
      const currentMin = min(input);
      const currentMax = max(input);
      if (currentMin === currentMax) {
        throw new RangeError('minimum and maximum input values are equal. Cannot rescale a constant array');
      }
      const {
        min: minValue = options.autoMinMax ? currentMin : 0,
        max: maxValue = options.autoMinMax ? currentMax : 1
      } = options;
      if (minValue >= maxValue) {
        throw new RangeError('min option must be smaller than max option');
      }
      const factor = (maxValue - minValue) / (currentMax - currentMin);
      for (let i = 0; i < input.length; i++) {
        output[i] = (input[i] - currentMin) * factor + minValue;
      }
      return output;
    }
    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(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(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;
      }
      gram() {
        const rows = this.rows;
        const n = this.columns;

        // The Gram matrix `thisᵀ · this` is symmetric, so only its upper triangle is
        // accumulated (then mirrored) and the transpose is never materialized.
        // Row-streaming rank-1 updates read each row of `this` contiguously and skip
        // zero entries, so the cost scales with the number of non-zeros: it is as
        // fast as the dense version on dense matrices (the skip never fires) and far
        // faster on sparse ones.
        const gramData = new Float64Array(n * n);
        for (let r = 0; r < rows; r++) {
          for (let i = 0; i < n; i++) {
            const value = this.get(r, i);
            if (value === 0) continue;
            const offset = i * n;
            for (let j = i; j < n; j++) {
              gramData[offset + j] += value * this.get(r, j);
            }
          }
        }
        const result = new Matrix$1(n, n);
        for (let i = 0; i < n; i++) {
          const offset = i * n;
          for (let j = i; j < n; j++) {
            const value = gramData[offset + j];
            result.set(i, j, value);
            result.set(j, i, value);
          }
        }
        return result;
      }
      transposeMultiply(other) {
        other = Matrix$1.checkMatrix(other);
        if (this.rows !== other.rows) {
          throw new RangeError('the number of rows of the two matrices must be equal');
        }
        const n = this.columns;
        const p = other.columns;
        const result = new Matrix$1(n, p);
        const otherRow = new Float64Array(p);
        for (let r = 0; r < this.rows; r++) {
          for (let j = 0; j < p; j++) {
            otherRow[j] = other.get(r, j);
          }
          for (let i = 0; i < n; i++) {
            const value = this.get(r, i);
            if (value === 0) continue;
            const resultRow = result.data[i];
            for (let j = 0; j < p; j++) {
              resultRow[j] += value * otherRow[j];
            }
          }
        }
        return result;
      }
      mmulByTranspose(scale) {
        let m = this.rows;
        let n = this.columns;
        if (scale !== undefined && scale.length !== n) {
          throw new RangeError('scale must have one value per column');
        }
        let result = new Matrix$1(m, m);

        // result = this · diag(scale) · thisᵀ is symmetric, so only the upper
        // triangle is computed and mirrored, and the transpose is never
        // materialized. `scale` (one factor per column) is folded into one operand.
        let rowj = new Float64Array(n);
        for (let j = 0; j < m; j++) {
          if (scale === undefined) {
            for (let k = 0; k < n; k++) {
              rowj[k] = this.get(j, k);
            }
          } else {
            for (let k = 0; k < n; k++) {
              rowj[k] = scale[k] * this.get(j, k);
            }
          }
          for (let i = j; i < m; i++) {
            let s = 0;
            for (let k = 0; k < n; k++) {
              s += this.get(i, k) * rowj[k];
            }
            result.set(i, j, s);
            result.set(j, i, 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(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(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(mean)) {
                throw new TypeError('mean must be an array');
              }
              return varianceByRow(this, unbiased, mean);
            }
          case 'column':
            {
              if (!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(center)) {
                throw new TypeError('center must be an array');
              }
              centerByRow(this, center);
              return this;
            }
          case 'column':
            {
              if (!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(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(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(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(array)) {
        if (array[0] && 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);
      }
    }

    /**
     * Transpose a square matrix in place, without allocating a copy.
     * Used to restore the logical layout of decomposition outputs that were
     * accumulated in transposed storage for cache-sequential inner loops.
     * @param {import('../matrix').default} matrix - square matrix, mutated in place
     * @returns {import('../matrix').default} the same matrix
     */
    function transposeSquareInPlace(matrix) {
      const data = matrix.data;
      const n = matrix.rows;
      for (let i = 0; i < n; i++) {
        const rowI = data[i];
        for (let j = i + 1; j < n; j++) {
          const tmp = rowI[j];
          rowI[j] = data[j][i];
          data[j][i] = tmp;
        }
      }
      return matrix;
    }
    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);

        // Work on the transpose of the input so the hot inner loops (which iterate
        // over rows for a fixed column) scan memory sequentially in the row-major
        // backing store. `at` holds the transpose: at.get(j, i) === a.get(i, j)
        // where `a` is the logical m x n working matrix.
        let swapped = false;
        let at;
        if (m < n) {
          if (!autoTranspose) {
            // eslint-disable-next-line no-console
            console.warn('Computing SVD on a matrix with more columns than rows. Consider enabling autoTranspose');
            at = value.transpose();
          } else {
            at = value.clone();
            m = value.columns;
            n = value.rows;
            swapped = true;
            let aux = wantu;
            wantu = wantv;
            wantv = aux;
          }
        } else {
          at = value.transpose();
        }
        let nu = Math.min(m, n);
        let ni = Math.min(m + 1, n);
        let s = new Float64Array(ni);
        // U and V are stored transposed during the computation so the inner loops
        // (which always vary the row index) scan memory sequentially. They are
        // transposed back to their logical layout before being returned.
        // Ut.get(j, i) === U.get(i, j) and Vt.get(j, i) === V.get(i, j).
        let U = new Matrix$1(nu, m);
        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], at.get(k, i));
            }
            if (s[k] !== 0) {
              if (at.get(k, k) < 0) {
                s[k] = -s[k];
              }
              for (let i = k; i < m; i++) {
                at.set(k, i, at.get(k, i) / s[k]);
              }
              at.set(k, k, at.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 += at.get(k, i) * at.get(j, i);
              }
              t = -t / at.get(k, k);
              for (let i = k; i < m; i++) {
                at.set(j, i, at.get(j, i) + t * at.get(k, i));
              }
            }
            e[j] = at.get(j, k);
          }
          if (wantu && k < nct) {
            for (let i = k; i < m; i++) {
              U.set(k, i, at.get(k, i));
            }
          }
          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] * at.get(j, i);
                }
              }
              for (let j = k + 1; j < n; j++) {
                let t = -e[j] / e[k + 1];
                for (let i = k + 1; i < m; i++) {
                  at.set(j, i, at.get(j, i) + t * work[i]);
                }
              }
            }
            if (wantv) {
              for (let i = k + 1; i < n; i++) {
                V.set(k, i, e[i]);
              }
            }
          }
        }
        let p = Math.min(n, m + 1);
        if (nct < n) {
          s[nct] = at.get(nct, nct);
        }
        if (m < p) {
          s[p - 1] = 0;
        }
        if (nrt + 1 < p) {
          e[nrt] = at.get(p - 1, nrt);
        }
        e[p - 1] = 0;
        if (wantu) {
          for (let j = nct; j < nu; j++) {
            for (let i = 0; i < m; i++) {
              U.set(j, i, 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(k, i) * U.get(j, i);
                }
                t = -t / U.get(k, k);
                for (let i = k; i < m; i++) {
                  U.set(j, i, U.get(j, i) + t * U.get(k, i));
                }
              }
              for (let i = k; i < m; i++) {
                U.set(k, i, -U.get(k, i));
              }
              U.set(k, k, 1 + U.get(k, k));
              for (let i = 0; i < k - 1; i++) {
                U.set(k, i, 0);
              }
            } else {
              for (let i = 0; i < m; i++) {
                U.set(k, i, 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(k, i) * V.get(j, i);
                }
                t = -t / V.get(k, k + 1);
                for (let i = k + 1; i < n; i++) {
                  V.set(j, i, V.get(j, i) + t * V.get(k, i));
                }
              }
            }
            for (let i = 0; i < n; i++) {
              V.set(k, i, 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(j, i) + sn * V.get(p - 1, i);
                      V.set(p - 1, i, -sn * V.get(j, i) + cs * V.get(p - 1, i));
                      V.set(j, i, 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(j, i) + sn * U.get(k - 1, i);
                      U.set(k - 1, i, -sn * U.get(j, i) + cs * U.get(k - 1, i));
                      U.set(j, i, 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(j, i) + sn * V.get(j + 1, i);
                      V.set(j + 1, i, -sn * V.get(j, i) + cs * V.get(j + 1, i));
                      V.set(j, i, 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(j, i) + sn * U.get(j + 1, i);
                      U.set(j + 1, i, -sn * U.get(j, i) + cs * U.get(j + 1, i));
                      U.set(j, i, 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(k, i, -V.get(k, i));
                    }
                  }
                }
                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(k + 1, i);
                      V.set(k + 1, i, V.get(k, i));
                      V.set(k, i, t);
                    }
                  }
                  if (wantu && k < m - 1) {
                    for (let i = 0; i < m; i++) {
                      t = U.get(k + 1, i);
                      U.set(k + 1, i, U.get(k, i));
                      U.set(k, i, t);
                    }
                  }
                  k++;
                }
                p--;
                break;
              }
            // no default
          }
        }

        // Restore the logical (row-major) layout of the singular vectors, which were
        // accumulated in transposed storage for cache-sequential inner loops. V is
        // always square and U is square whenever the input is, so this is done in
        // place (no allocation) in the common case.
        U = U.isSquare() ? transposeSquareInPlace(U) : U.transpose();
        V = transposeSquareInPlace(V);
        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(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(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.transposeMultiply(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(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.transposeMultiply(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) {
          // tred2/tql2 access V almost exclusively down columns (the row index
          // varies in the hot loops). Storing V transposed turns those into
          // sequential row scans of the row-major backing store; we transpose it
          // back to the logical layout before returning. V.get(j, i) holds the
          // logical V(i, j).
          for (i = 0; i < n; i++) {
            for (j = 0; j < n; j++) {
              V.set(j, i, value.get(i, j));
            }
          }
          tred2(n, e, d, V);
          tql2(n, e, d, V);
          // V is square; restore the logical layout in place (no allocation).
          transposeSquareInPlace(V);
        } else {
          // The non-symmetric path (orthes/hqr2) has two O(n^3) phases with opposite
          // memory-layout preferences (the QR sweep favours column-major eigenvectors
          // while the back-transform favours row-major), so a single transposed
          // storage cannot help both. It is left in the original row-major layout.
          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(j, n - 1);
      }
      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(j, i - 1);
            V.set(j, i, 0);
            V.set(i, j, 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(i, j, f);
            g = e[j] + V.get(j, j) * f;
            for (k = j + 1; k <= i - 1; k++) {
              g += V.get(j, k) * d[k];
              e[k] += V.get(j, k) * 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(j, k, V.get(j, k) - (f * e[k] + g * d[k]));
            }
            d[j] = V.get(j, i - 1);
            V.set(j, i, 0);
          }
        }
        d[i] = h;
      }
      for (i = 0; i < n - 1; i++) {
        V.set(i, n - 1, 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(i + 1, k) / h;
          }
          for (j = 0; j <= i; j++) {
            g = 0;
            for (k = 0; k <= i; k++) {
              g += V.get(i + 1, k) * V.get(j, k);
            }
            for (k = 0; k <= i; k++) {
              V.set(j, k, V.get(j, k) - g * d[k]);
            }
          }
        }
        for (k = 0; k <= i; k++) {
          V.set(i + 1, k, 0);
        }
      }
      for (j = 0; j < n; j++) {
        d[j] = V.get(j, n - 1);
        V.set(j, n - 1, 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(i + 1, k);
                V.set(i + 1, k, s * V.get(i, k) + c * h);
                V.set(i, k, c * V.get(i, k) - 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(i, j);
            V.set(i, j, V.get(k, j));
            V.set(k, j, 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(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;
    var EVD$1 = 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;
    var NIPALS$1 = matrix.NIPALS = nipals;
    matrix.Nipals = nipals;
    matrix.QR = QrDecomposition;
    matrix.QrDecomposition = QrDecomposition;
    var SVD$1 = 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;
    matrix.inverse = inverse;
    matrix.linearDependencies = linearDependencies;
    matrix.pseudoInverse = pseudoInverse;
    var solve_1 = matrix.solve = solve$1;
    matrix.wrap = wrap;

    const EVD = EVD$1;
    const Matrix = Matrix_1;
    const MatrixTransposeView = MatrixTransposeView_1;
    const NIPALS = NIPALS$1;
    const SVD = SVD$1;
    _default.Matrix ? _default.Matrix : Matrix_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 - 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 (const value of values) {
        const x = value - 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.
     * @returns q1, median, q3, min, max.
     */
    function xDistributionStats(array) {
      const mean = xMean(array);
      return {
        ...xBoxPlotWithOutliers(array),
        mean,
        sd: xStandardDeviation(array, {
          mean
        }),
        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);
    }

    /**
     * Check the similarity between array created by xyEqualIntegrationVector
     * @param array1 - first equal integration vector.
     * @param array2 - second equal integration vector.
     * @param options - options.
     * @returns similarity score between 0 and 1.
     */
    function xEqualIntegrationVectorSimilarity(array1, array2, options = {}) {
      const {
        recenter = true,
        similarityFct = (a, b) => Math.abs(a - b) <= 1e-9 * Math.max(1, Math.abs(a), Math.abs(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;
    }

    /**
     *  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);

    // An FFT instance precomputes size-dependent twiddle factors and a bit-reversal
    // table; for a 64k transform that setup dominates the cost. A single shared
    // cache keeps one instance per size so every FFT-based function in the library
    // (spectra, matrices, Hilbert transforms…) reuses it instead of rebuilding the
    // tables on each call.
    class FFTCache {
      #maxSize;
      #instances = new Map();
      constructor(maxSize = 1) {
        this.#maxSize = maxSize;
      }
      get maxSize() {
        return this.#maxSize;
      }
      set maxSize(value) {
        if (!Number.isInteger(value) || value < 1) {
          throw new RangeError(`FFT cache size must be a positive integer, got ${value}.`);
        }
        this.#maxSize = value;
        // Honour the new bound right away: if already over it, drop everything,
        // matching the clear-when-full strategy used in get().
        if (this.#instances.size > value) this.#instances.clear();
      }
      get(size) {
        let fft = this.#instances.get(size);
        if (fft === undefined) {
          // Bound the cache so transforming many different sizes cannot grow it
          // without limit. Rather than track insertion order to evict a single
          // entry, drop everything once full: distinct sizes are rare, so the
          // common workload never hits this and stays at full speed.
          if (this.#instances.size >= this.#maxSize) this.#instances.clear();
          fft = new FFT$1(size);
          this.#instances.set(size, fft);
        }
        return fft;
      }
      clear() {
        this.#instances.clear();
      }
    }
    // Process-wide singleton: a single shared cache, like a static global, but kept
    // as an instance so its state stays fully encapsulated.
    const fftCache = new FFTCache();
    /**
     * Returns a cached `fft.js` instance for the given transform size, building it
     * on first use. Shared by every FFT-based function in the library so the
     * size-dependent lookup tables are computed once and reused across calls.
     * @param size - number of points of the transform.
     * @returns a reusable FFT instance for that size.
     */
    function getFFT(size) {
      return fftCache.get(size);
    }
    /**
     * Releases every cached FFT instance. The library keeps one FFT instance per
     * transform size to avoid rebuilding its lookup tables on each call; each
     * instance holds tables proportional to its size (≈1 MB for a 64k transform).
     * Call this to free that memory once you are done transforming — for example
     * after processing a batch of large spectra. Subsequent transforms simply
     * rebuild whatever sizes they need.
     */
    function clearFFTCache() {
      fftCache.clear();
    }
    /**
     * Sets the maximum number of distinct transform sizes kept in the shared FFT
     * cache (default 1). When the cache is full and a new size is requested, the
     * whole cache is dropped and rebuilt on demand. Lowering the limit below the
     * current number of cached sizes clears the cache immediately.
     * @param maxSize - maximum number of cached sizes; must be a positive integer.
     */
    function setFFTCacheMaxSize(maxSize) {
      fftCache.maxSize = maxSize;
    }

    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 - 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;
    }

    /**
     * Generate all combinations of choosing k items from n items.
     * @param n - total number of items.
     * @param k - number of items to choose.
     * @returns array of combinations, where each combination is an array of indices.
     */
    function getCombinations(n, k) {
      if (k === 0) return [[]];
      if (k > n) return [];
      const results = [];
      const current = [];
      function backtrack(start) {
        if (current.length === k) {
          results.push([...current]);
          return;
        }
        for (let i = start; i < n; i++) {
          current.push(i);
          backtrack(i + 1);
          current.pop();
        }
      }
      backtrack(0);
      return results;
    }

    /**
     * Generate all combinations of choosing k items from n items as an iterator.
     * Useful for large combination sets to avoid storing all combinations in memory.
     * @param n - total number of items.
     * @param k - number of items to choose.
     * @yields Each combination as an array of indices.
     */
    function* getCombinationsIterator(n, k) {
      if (k === 0) {
        yield [];
        return;
      }
      if (k > n) return;
      const current = [];
      function* backtrack(start) {
        if (current.length === k) {
          yield [...current];
          return;
        }
        for (let i = start; i < n; i++) {
          current.push(i);
          yield* backtrack(i + 1);
          current.pop();
        }
      }
      yield* backtrack(0);
    }

    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 - input number.
     */
    function isPowerOfTwo(n) {
      return n !== 0 && (n & n - 1) === 0;
    }

    /**
     * Get the size of the next power of two.
     * @param n - input number.
     */
    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 - object to resolve.
     * @returns the resolved object.
     */
    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 - object to transform.
     * @returns the transformed object.
     */
    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 - object to stringify.
     * @param replacer - optional replacer function.
     * @param space - optional indentation.
     * @returns JSON string.
     */
    function stringify(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
      } = options;
      if (learningRate === 0) {
        return weights;
      }
      const absResiduals = xAbsolute(xSubtract(yData, baseline));
      const medAbsRes = xMedian(absResiduals);
      const mad = 1.4826 * medAbsRes;
      const threshold = mad > 0 ? factorStd * mad : 1;
      const rawWeights = new Float64Array(absResiduals.length);
      for (let i = 0; i < absResiduals.length; i++) {
        rawWeights[i] = Math.exp(-((absResiduals[i] / threshold) ** 2));
      }
      const oneMinusLearningRate = 1 - learningRate;
      for (let i = 0; i < weights.length; i++) {
        let weight = weights[i];
        weight = (oneMinusLearningRate * weight + learningRate * rawWeights[i]) / 4;
        if (controlPoints && controlPoints[i] > 0) {
          weight *= 4;
        }
        weights[i] = weight;
      }
      weights[0] = 1;
      weights[weights.length - 1] = 1;
      return weights;
    }

    /*
     * This file is based on the code from "deep-cleaner" by darksinge
     * https://github.com/darksinge/deep-cleaner
     *
     * The following license applies:
     *
     * MIT License
     *
     * Copyright (c) 2021 darksinge
     *
     * 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.
     */
    /**
     * Recursively removes empty values from an object. This will also remove empty object or empty array.
     * @param object - the object being cleaned
     * @param options - optional object with options for cleaning.
     * @returns the cleaned object.
     */
    function recursiveRemoveEmptyAndNull(object, options = {}) {
      const {
        propertiesToRemove = [],
        ...otherOptions
      } = options;
      if (propertiesToRemove.length > 0) {
        for (const removeProperty of propertiesToRemove) {
          cleanCyclicObject(object, removeProperty, otherOptions);
        }
      } else {
        cleanCyclicObject(object, '', otherOptions);
      }
      return object;
    }
    /**
     * cleanCyclicObject Removes any undefined, null, or empty strings, arrays, or objects from `object`.
     *    Uses a `WeakMap` to keep track of objects that have been visited while recursively cleaning
     *    an object to prevent infinite recursive calls.
     * @param object - the object to be cleaned
     * @param propertiesToRemove - optional key to remove from `object`. If not specified, the default
     *    behavior is to remove "empty" values from `object`. A value is considered to be empty if it
     *    is one of the following:
     *      - empty strings
     *      - empty arrays
     *      - empty objects
     *      - values that are null
     *      - values that are undefined
     * @param removeProperty - key to remove from the object.
     * @param options - options.
     */
    function cleanCyclicObject(object, removeProperty, options = {}) {
      const visitedObjects = new WeakMap();
      function recursiveClean(object, parent, parentKey) {
        if (isObject(object)) {
          if (visitedObjects.has(object)) return;
          visitedObjects.set(object, null);
          for (const key of Reflect.ownKeys(object)) {
            if (removeProperty && key === removeProperty || !removeProperty && isEmpty(object[key])) {
              Reflect.deleteProperty(object, key);
            } else {
              recursiveClean(object[key], object, key);
            }
          }
          if (!removeProperty && isEmpty(object) && parent) {
            Reflect.deleteProperty(parent, parentKey);
          }
        } else if (isArray(object)) {
          if (visitedObjects.has(object)) return;
          visitedObjects.set(object, null);
          for (let i = 0; i < object.length; i++) {
            recursiveClean(object[i], object, i);
          }
          for (let i = object.length - 1; i >= 0; i--) {
            const arrayElement = object[i];
            if (isEmpty(arrayElement) && !(isArray(arrayElement) && arrayElement.length === 0 && options?.removeEmptyArrayAndObject === false)) {
              object.splice(i, 1);
            }
          }
          for (const key of Reflect.ownKeys(object)) {
            const isIndex = typeof key === 'string' && /^\d+$/.test(key);
            if (!isIndex) {
              const value = object[key];
              if (removeProperty && key === removeProperty || !removeProperty && isEmpty(value)) {
                Reflect.deleteProperty(object, key);
              } else {
                recursiveClean(value, object, key);
              }
            }
          }
          if (!removeProperty && object.length === 0 && parent && options?.removeEmptyArrayAndObject) {
            Reflect.deleteProperty(parent, parentKey);
          }
        }
      }
      recursiveClean(object);
    }
    /**
     * repr gets the string representation of `arg`
     * @param {} arg - unknown function argument
     * @returns a string representation of `arg`
     */
    function repr(arg) {
      return Object.prototype.toString.call(arg);
    }
    /**
     * Check if the argument is an array
     * @param {} arg - unknown function argument
     * @returns returns true if `arg` is an Array, false otherwise
     */
    function isArray(arg) {
      return Array.isArray ? Array.isArray(arg) : repr(arg) === '[object Array]';
    }
    /**
     * Check if the argument is an object
     * @param {} arg - unknown function argument
     * @returns returns true if `arg` is an object.
     */
    function isObject(arg) {
      return repr(arg) === '[object Object]';
    }
    /**
     * Check if the argument is a string
     * @param {} arg - unknown function argument
     * @returns returns true if `arg` is a String, false otherwise
     */
    function isString(arg) {
      return repr(arg) === '[object String]';
    }
    /**
     * Check if the argument is null.
     * @param {} arg - unknown function argument
     * @returns returns true if `arg` is of type Null, false otherwise
     */
    function isNull(arg) {
      return repr(arg) === '[object Null]';
    }
    /**
     * Check if the argument is undefined.
     * @param {} arg - unknown function argument
     * @returns returns true if `arg` is of type Undefined, false otherwise.
     */
    function isUndefined(arg) {
      return arg === undefined;
    }
    /**
     * Check if the argument is null, undefined, an empty string, array, or object.
     * @param {} arg - unknown function argument
     * @returns returns true if `arg` is an empty string,
     *  array, or object. Also returns true is `arg` is null or
     *  undefined. Returns false otherwise.
     */
    function isEmpty(arg) {
      return isUndefined(arg) || isNull(arg) || isString(arg) && arg.length === 0 || isArray(arg) && arg.length === 0 || isObject(arg) && Object.keys(arg).length === 0;
    }

    /**
     * Performs the Hilbert transform
     * @link https://en.wikipedia.org/wiki/Hilbert_transform
     * @param array - array containing values.
     * @param options - 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 = getFFT(length);
      // Single reusable buffer for FFT spectrum
      const spectrum = new Float64Array(length * 2);
      // Forward FFT
      fft.realTransform(spectrum, array);
      fft.completeSpectrum(spectrum);
      const half = length >> 1;
      // Zero Nyquist
      const j = half << 1;
      spectrum[j] = 0;
      spectrum[j + 1] = 0;
      // Negate negative frequencies
      for (let j = half + 1 << 1; j < spectrum.length; j += 2) {
        spectrum[j] = -spectrum[j];
        spectrum[j + 1] = -spectrum[j + 1];
      }
      const hilbertSignal = new Float64Array(length * 2);
      fft.inverseTransform(hilbertSignal, spectrum);
      // Extract imaginary part directly into output
      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 - options.
     * @param options.inClockwise - whether to apply clockwise rotation.
     * @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 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
      };
    }

    /**
     * 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.
     * @returns Whether the value is a valid array of numbers.
     */
    function xIsArray(input, options = {}) {
      const {
        minLength = 1
      } = options;
      return isAnyArray$1(input) && input.length > 0 && typeof input[0] === 'number' && input.length >= minLength;
    }

    /**
     * Check if the values are separated always by the same difference
     * @param array - monotone growing array of number.
     * @param options - 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
     * @deprecated The center of mass trees it compares are misleading, see `xyMassCenterVector`.
     * Use `xEqualIntegrationVectorSimilarity` together with `xyEqualIntegrationVector`.
     * @param array1 - first mass center vector.
     * @param array2 - second mass center vector.
     * @param options - options.
     * @returns similarity score between 0 and 1.
     */
    function xMassCenterVectorSimilarity(array1, array2, options = {}) {
      const {
        recenter = true,
        similarityFct = (a, b) => a === b ? 1 : 0
      } = options;
      return xEqualIntegrationVectorSimilarity(array1, array2, {
        recenter,
        similarityFct
      });
    }

    /**
     * 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 = Math.abs(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 - array of numbers.
     */
    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 (const element of input) {
        count = counts[element];
        if (count) {
          counts[element]++;
          count++;
        } else {
          counts[element] = 1;
          count = 1;
        }
        if (count > maxCount) {
          maxCount = count;
          maxValue = element;
        }
      }
      return maxValue;
    }

    /**
     * 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;
    }

    /**
     * Applies the Rayleigh inverse cumulative distribution for a single value.
     * @param input - cumulative probability value.
     * @returns the inverse value for the Rayleigh distribution.
     */
    function simpleNormInvMagnitude(input) {
      return -Math.sqrt(-2 * Math.log(1 - input));
    }

    /*
    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.
    */
    /* eslint-disable no-loss-of-precision */
    // 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];
    /**
     * Evaluates a polynomial at x using Horner's method.
     * @param c - array of polynomial coefficients.
     * @param x - evaluation point.
     * @returns polynomial value.
     */
    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 - input value.
     * @param v - shift value.
     * @param P - array of numerator polynomial coefficients.
     * @param Q - array of denominator polynomial coefficients.
     * @param Y - scale factor.
     * @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 the inverse of the normal cumulative distribution for a single value.
     * @param input - cumulative probability value.
     * @returns the inverse value for the normal distribution.
     */
    function simpleNormInvRaw(input) {
      return -1 * Math.SQRT2 * erfcinv(2 * input);
    }

    /**
     * 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,
        cutOffFromPositive = true,
        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 = createNegativeSign(input, firstNegativeValueIndex);
      const skyPoint = signPositive[0];
      const cutOff = options.cutOff ?? (cutOffFromPositive ? determineCutOff(signPositive, {
        magnitudeMode
      }) : null);
      const noiseLevelPositive = calculateNoiseLevel(signPositive, {
        factorStd,
        refine,
        magnitudeMode,
        cutOff
      });
      const noiseLevelNegative = calculateNoiseLevel(signNegative, {
        factorStd,
        refine,
        magnitudeMode,
        cutOff
      });
      return {
        positive: noiseLevelPositive,
        negative: noiseLevelNegative,
        snr: skyPoint / noiseLevelPositive,
        sanplot: generateSanPlot(input, {
          fromTo: {
            positive: {
              from: 0,
              to: lastPositiveValueIndex
            },
            negative: {
              from: firstNegativeValueIndex,
              to: input.length
            }
          }
        })
      };
    }
    function calculateNoiseLevel(sign, options) {
      const {
        factorStd,
        refine,
        magnitudeMode,
        cutOff
      } = options;
      if (sign.length === 0) return 0;
      const simpleNormInvValue = magnitudeMode ? simpleNormInvMagnitude : simpleNormInvRaw;
      const cutOffDist = cutOff || determineCutOff(sign, {
        magnitudeMode
      });
      let noiseLevel = sign[Math.floor(sign.length * cutOffDist)];
      let cloneSign = sign.slice();
      let cutOffSignalsIndex = -1;
      if (refine) {
        const cutOffSignals = noiseLevel * factorStd;
        cutOffSignalsIndex = sign.findIndex(e => e < cutOffSignals);
        if (cutOffSignalsIndex > -1) {
          cloneSign = sign.slice(cutOffSignalsIndex);
          noiseLevel = cloneSign[Math.floor(cloneSign.length * cutOffDist)];
        }
      }
      if (refine && cutOffSignalsIndex > -1) {
        const effectiveCutOffDist = (cutOffDist * cloneSign.length + cutOffSignalsIndex) / (cloneSign.length + cutOffSignalsIndex);
        const refinedCorrectionFactor = -1 * simpleNormInvValue(effectiveCutOffDist / 2);
        noiseLevel /= refinedCorrectionFactor;
      } else {
        const correctionFactor = -simpleNormInvValue(cutOffDist / 2);
        noiseLevel /= correctionFactor;
      }
      return noiseLevel;
    }
    /**
     * Estimates where the "noise region" begins in a SAN-plot-style sorted
     * intensity series (Sheberstov et al., Magn. Reson. Chem. 2020, 58, 466-472,
     * https://doi.org/10.1002/mrc.4882).
     *
     * `signPositive` is expected to already be sorted by decreasing absolute
     * magnitude (the paper's S+ or S- series). For each quantile fraction `i`
     * (fraction of points at or beyond that position, counting from the tail),
     * we invert the Gaussian (or Rayleigh, in magnitude mode) order-statistics
     * relationship to get a local estimate of the noise standard deviation
     * (sigma). This estimate is only expected to be stable/flat once we are
     * fully inside the true noise region; in the signal and artifact regions
     * it will be biased and noisy. We therefore scan candidate quantile windows
     * and pick the one where the sigma estimates are most self-consistent
     * (lowest variance) - a proxy for "where the noise region reliably starts".
     * <<<<<<< Updated upstream
     *
     * =======
     * >>>>>>> Stashed changes
     * @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;
      const inverseQuantileFn = magnitudeMode ? simpleNormInvMagnitude : simpleNormInvRaw;
      const indexMax = signPositive.length - 1;
      // For each quantile fraction, compute a local sigma estimate by inverting
      // the theoretical relationship between order statistics and the assumed
      // noise distribution (Gaussian or Rayleigh).
      const sigmaEstimates = [];
      for (let quantileFraction = 0.01; quantileFraction <= 0.99; quantileFraction += 0.01) {
        const index = Math.round(indexMax * quantileFraction);
        const sigma = -signPositive[index] / inverseQuantileFn(quantileFraction / 2);
        sigmaEstimates.push([quantileFraction, Math.abs(sigma)]);
      }
      const {
        from,
        to,
        step
      } = considerList;
      const halfWindow = step / 2;
      let bestVariance = Number.MAX_SAFE_INTEGER;
      let bestQuantileFraction = 0.5;
      for (let windowCenter = from; windowCenter <= to; windowCenter += step) {
        const windowFloor = windowCenter - halfWindow;
        const windowTop = windowCenter + halfWindow;
        let count = 0;
        let sum = 0;
        // First pass: compute mean
        for (const [q, absSigma] of sigmaEstimates) {
          if (q > windowFloor && q < windowTop) {
            sum += absSigma;
            count++;
          }
        }
        // Guard against empty windows.
        if (count === 0) continue;
        const meanSigma = sum / count;
        // Second pass: compute variance
        let variance = 0;
        for (const [q, absSigma] of sigmaEstimates) {
          if (q > windowFloor && q < windowTop) {
            variance += (absSigma - meanSigma) ** 2;
          }
        }
        if (variance < bestVariance) {
          bestVariance = variance;
          bestQuantileFraction = windowCenter;
        }
      }
      return bestQuantileFraction;
    }
    /**
     * 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
     * @param from
     * @returns A new Float64Array containing the processed data, scaled by the
     *          scaleFactor and sorted in descending order.
     */
    function createNegativeSign(array, from) {
      const length = array.length - from;
      const result = new Float64Array(length);
      for (let i = 0; i < length; i++) {
        result[i] = -array[array.length - 1 - i];
      }
      return result;
    }
    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;
        }
      }
      input.sort();
      input.reverse();
      return input;
    }

    /**
     * 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 - array of numbers.
     * @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 sum 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 (const value of input) {
        sumValue += Math.abs(value);
      }
      return sumValue;
    }

    /**
     * Pads an array symmetrically on both sides.
     * @param array - the array that will be padded
     * @param options - options
     * @returns padded Float64Array
     */
    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.
     * @returns q1, median, q3, min, max.
     */
    function xRobustDistributionStats(array) {
      xCheck(array);
      const sorted = getSortedFloat64(array);
      const {
        boxPlot,
        bounds
      } = boxPlotWithOutliersAndBounds(sorted);
      if (boxPlot.max - boxPlot.min <= Number.EPSILON) {
        return {
          ...boxPlot,
          mean: boxPlot.median,
          sd: sorted.length === 1 ? Number.NaN : 0,
          nb: sorted.length
        };
      }
      // non-outliers form a contiguous range in the sorted array, so we slice it
      // as a zero-copy view instead of allocating and filling a filtered array.
      const filteredArray = sorted.subarray(bounds.lowIndex, bounds.highIndex);
      const mean = xMean(filteredArray);
      return {
        ...boxPlot,
        mean,
        sd: xStandardDeviation(filteredArray, {
          mean
        }),
        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 - target number of elements.
     * @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 = length === 1 ? 0 : (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 = size <= 1 ? 0 : (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)) {
        array.sort();
        return array;
      } else if (Array.isArray(array)) {
        array.sort((a, b) => a - b);
        return array;
      }
      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)) {
        array.sort();
        array.reverse();
        return array;
      } else if (Array.isArray(array)) {
        array.sort((a, b) => b - a);
        return array;
      }
      throw new Error('trying to sort non array');
    }

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

    /**
     * Returns the median of the absolute matrix.
     * @param matrix - input 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);
    }

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

    /**
     * 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 = matrixClone(matrixInitial);
      const arrayOfValues = [];
      for (const key in dictionary) {
        arrayOfValues.push(dictionary[key]);
      }
      let k = xMaxValue(arrayOfValues);
      for (const row of matrix) {
        for (let j = 0; j < row.length; j++) {
          const value = row[j];
          if (typeof value === 'string') {
            if (value in dictionary) {
              row[j] = dictionary[value];
            } else {
              k++;
              dictionary[value] = k;
              row[j] = k;
            }
          }
        }
      }
      return matrix;
    }

    /**
     * Correlates every column of a matrix against a single reference column.
     * @param matrix - 2D matrix with at least 2 rows
     * @param index - column index used as the reference signal. Defaults to `0`.
     * @returns array of Pearson correlations, one value per column
     */
    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;
    }

    /**
     * Computes per-column box-plot statistics (min, Q1, median, Q3, max) for a matrix.
     * The matrix rows must be sorted in ascending order — results will be wrong otherwise.
     * Requires at least 5 rows.
     * @param matrix - 2D matrix whose rows are sorted in ascending order
     * @returns per-column box-plot statistics
     */
    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;
    }

    /**
     * Create a new matrix based on the size of the current one or by using specific dimensions.
     * @param options - 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;
    }

    /**
     * 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');
        }
      }
    }

    /*
    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 Int32Array(dimension);
        for (let k = 0; k < dimension; k++) {
          pinv[permutationEncoded[k]] = k;
        }
        // Build a permuted copy (kept as plain arrays for compatibility)
        const mt = new Array(nonZerosArray.length);
        for (let a = 0; a < nonZerosArray.length; ++a) {
          const [r, c, value] = nonZerosArray[a];
          const ar = pinv[r];
          const ac = pinv[c];
          mt[a] = ac < ar ? [ac, ar, value] : [ar, ac, value];
        }
        nonZerosArray = mt;
      } else {
        // use a typed identity permutation
        const p = new Int32Array(dimension);
        for (let i = 0; i < dimension; ++i) {
          p[i] = i;
        }
        permutationEncoded = p;
      }
      const nnz = nonZerosArray.length;
      const ap = new Int32Array(dimension + 1);
      const ai = new Int32Array(nnz);
      const ax = new Float64Array(nnz);
      const lnz = new Int32Array(dimension);
      for (let idx = 0; idx < nnz; idx++) {
        const col = nonZerosArray[idx][1];
        lnz[col]++;
      }
      ap[0] = 0;
      for (let i = 0; i < dimension; ++i) {
        ap[i + 1] = ap[i] + lnz[i];
      }
      const colOffset = new Int32Array(dimension);
      for (let idx = 0; idx < nnz; idx++) {
        const e = nonZerosArray[idx];
        const col = e[1];
        const adr = ap[col] + colOffset[col];
        ai[adr] = e[0];
        ax[adr] = e[2];
        colOffset[col]++;
      }
      const d = new Float64Array(dimension);
      const y = new Float64Array(dimension);
      const lp = new Int32Array(dimension + 1);
      const parent = new Int32Array(dimension);
      const lnzArray = new Int32Array(dimension);
      const flag = new Int32Array(dimension);
      const pattern = new Int32Array(dimension);
      const bp1 = new Float64Array(dimension);
      const x = new Float64Array(dimension);
      ldlSymbolic(dimension, ap, ai, lp, parent, lnzArray, flag);
      const nz = lp[dimension];
      const lx = new Float64Array(nz);
      const li = new Int32Array(nz);
      const result = ldlNumeric(dimension, ap, ai, ax, lp, parent, lnzArray, li, lx, d, y, pattern, flag);
      if (result === dimension) {
        return b => {
          ldlPerm(dimension, bp1, b, permutationEncoded);
          ldlLsolve(dimension, bp1, lp, li, lx);
          ldlDsolve(dimension, bp1, d);
          ldlLTsolve(dimension, bp1, lp, li, lx);
          ldlPermt(dimension, x, bp1, permutationEncoded);
          return x;
        };
      } else {
        return null;
      }
    }
    function ldlSymbolic(dimension, ap, ai, lp, parent, lnz, flag) {
      const apLoc = ap;
      const aiLoc = ai;
      const parentLoc = parent;
      const lnzLoc = lnz;
      const flagLoc = flag;
      for (let k = 0; k < dimension; k++) {
        parentLoc[k] = -1;
        flagLoc[k] = k;
        lnzLoc[k] = 0;
        const kk = k;
        const p2 = apLoc[kk + 1];
        for (let p = apLoc[kk]; p < p2; p++) {
          let i = aiLoc[p];
          if (i < k) {
            for (; flagLoc[i] !== k; i = parentLoc[i]) {
              if (parentLoc[i] === -1) parentLoc[i] = k;
              lnzLoc[i]++;
              flagLoc[i] = k;
            }
          }
        }
      }
      lp[0] = 0;
      for (let k = 0; k < dimension; k++) {
        lp[k + 1] = lp[k] + lnz[k];
      }
    }
    function ldlNumeric(dimension, ap, ai, ax, lp, parent, lnz, li, lx, d, y, pattern, flag) {
      const apLoc = ap;
      const aiLoc = ai;
      const axLoc = ax;
      const lpLoc = lp;
      const parentLoc = parent;
      const lnzLoc = lnz;
      const liLoc = li;
      const lxLoc = lx;
      const dLoc = d;
      const yLoc = y;
      const patternLoc = pattern;
      const flagLoc = flag;
      let yi, lKi;
      for (let k = 0; k < dimension; k++) {
        yLoc[k] = 0;
        let top = dimension;
        flagLoc[k] = k;
        lnzLoc[k] = 0;
        const kk = k;
        const p2col = apLoc[kk + 1];
        for (let p = apLoc[kk]; p < p2col; p++) {
          let i = aiLoc[p];
          if (i <= k) {
            yLoc[i] += axLoc[p];
            let len = 0;
            for (; flagLoc[i] !== k; i = parentLoc[i]) {
              patternLoc[len++] = i;
              flagLoc[i] = k;
            }
            while (len > 0) patternLoc[--top] = patternLoc[--len];
          }
        }
        dLoc[k] = yLoc[k];
        yLoc[k] = 0;
        for (; top < dimension; top++) {
          const i = patternLoc[top];
          yi = yLoc[i];
          yLoc[i] = 0;
          const p2 = lpLoc[i] + lnzLoc[i];
          let p;
          for (p = lpLoc[i]; p < p2; p++) {
            yLoc[liLoc[p]] -= lxLoc[p] * yi;
          }
          lKi = yi / dLoc[i];
          dLoc[k] -= lKi * yi;
          liLoc[p] = k;
          lxLoc[p] = lKi;
          lnzLoc[i]++;
        }
        if (dLoc[k] === 0) return k;
      }
      return dimension;
    }
    function ldlLsolve(dimension, x, lp, li, lx) {
      const lpLoc = lp;
      const liLoc = li;
      const lxLoc = lx;
      const xLoc = x;
      for (let j = 0; j < dimension; j++) {
        const p2 = lpLoc[j + 1];
        for (let p = lpLoc[j]; p < p2; p++) {
          xLoc[liLoc[p]] -= lxLoc[p] * xLoc[j];
        }
      }
    }
    function ldlDsolve(dimension, x, d) {
      const xLoc = x;
      const dLoc = d;
      for (let j = 0; j < dimension; j++) {
        xLoc[j] /= dLoc[j];
      }
    }
    function ldlLTsolve(dimension, x, lp, li, lx) {
      const lpLoc = lp;
      const liLoc = li;
      const lxLoc = lx;
      const xLoc = x;
      for (let j = dimension - 1; j >= 0; j--) {
        const p2 = lpLoc[j + 1];
        for (let p = lpLoc[j]; p < p2; p++) {
          xLoc[j] -= lxLoc[p] * xLoc[liLoc[p]];
        }
      }
    }
    function ldlPerm(dimension, x, b, permutationEncoded) {
      const perm = permutationEncoded;
      const xLoc = x;
      const bLoc = b;
      for (let j = 0; j < dimension; j++) {
        xLoc[j] = bLoc[perm[j]];
      }
    }
    function ldlPermt(dimension, x, b, permutationEncoded) {
      const perm = permutationEncoded;
      const xLoc = x;
      const bLoc = b;
      for (let j = 0; j < dimension; j++) {
        xLoc[perm[j]] = bLoc[j];
      }
    }

    /**
     * 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]);
          nbhd.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,
        endRow,
        startColumn,
        endColumn,
        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;
    }

    /**
     * Apply the Hilbert transform to each row of a real-valued matrix, reusing a
     * single FFT instance for all rows to avoid repeated instantiation overhead.
     * All rows must have the same length, which must be a power of two.
     * @param rows - array of real-valued Float64Array rows
     * @param options - options
     * @returns array of Hilbert-transformed Float64Array rows
     * @see DOI: 10.1109/TAU.1970.1162139 "Discrete Hilbert transform"
     */
    function matrixHilbertTransform(rows, options = {}) {
      if (rows.length === 0) return [];
      const size = rows[0].length;
      if (!isPowerOfTwo(size)) {
        throw new RangeError(`Row length must be a power of two. Got ${size}.`);
      }
      for (let j = 1; j < rows.length; j++) {
        if (rows[j].length !== size) {
          throw new RangeError(`All rows must have the same length. Expected ${size} but row ${j} has length ${rows[j].length}.`);
        }
      }
      // Single FFT instance reused across all rows
      const fft = getFFT(size);
      // Multiplier computed once — identical for every row of the same length
      const half = size >> 1;
      // Shared working buffers reused across all rows
      const fftResult = new Float64Array(size * 2);
      const hilbertSignal = new Float64Array(size * 2);
      const {
        output = matrixCreateEmpty({
          nbRows: rows.length,
          nbColumns: size
        })
      } = options;
      for (let j = 0; j < rows.length; j++) {
        const row = rows[j];
        fft.realTransform(fftResult, row);
        fft.completeSpectrum(fftResult);
        const idx = half << 1;
        fftResult[idx] = 0;
        fftResult[idx + 1] = 0;
        // Negate negative frequencies
        for (let c = half + 1 << 1; c < fftResult.length; c += 2) {
          fftResult[c] = -fftResult[c];
          fftResult[c + 1] = -fftResult[c + 1];
        }
        fft.inverseTransform(hilbertSignal, fftResult);
        const result = output[j];
        for (let i = 0; i < size; i++) {
          result[i] = hilbertSignal[i * 2 + 1];
        }
      }
      return output;
    }

    /**
     * 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 - input 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 - input 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 - input 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 - original matrix to decode.
     * @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 (const row of matrix) {
        for (let j = 0; j < matrix[0].length; j++) {
          const value = row[j];
          if (typeof value === 'number' && value in invertedDictionary) {
            row[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);
      const factor = max / currentMax;
      for (let column = 0; column < nbColumns; column++) {
        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;
    }

    /**
     * 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 new 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]);
      const permutationEncodedArray = matrixCuthillMckee(upperTriangularNonZeros, dimension);
      return {
        upperTriangularNonZeros,
        permutationEncodedArray
      };
    }

    function xWhittakerSmoother(yData, options = {}) {
      const {
        algorithm = 'thomas',
        ...restOptions
      } = options;
      if (algorithm === 'thomas') {
        return whittakerByThomas(yData, restOptions);
      }
      return whittakerByCholesky(yData, restOptions);
    }
    /**
     * @deprecated Use xWhittakerSmoother instead.
     * TODO: Remove in next major version.
     */
    const xWhitakerSmoother = xWhittakerSmoother;
    /**
     * 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 whittakerByCholesky(yData, options = {}) {
      const {
        lambda = 100,
        maxIterations = 100,
        tolerance = 1e-6,
        factorStd = 3,
        learningRate = 0.5
      } = options;
      const size = yData.length;
      const {
        controlPoints,
        weights
      } = getWeightsAndControlPoints(yData, options);
      const prevBaseline = new Float64Array(size);
      let iteration = 0;
      let delta = Infinity;
      let baseline = xEnsureFloat64(yData);
      const {
        upperTriangularNonZeros,
        permutationEncodedArray
      } = createSystemMatrix(size, lambda);
      while (iteration < maxIterations && delta > tolerance) {
        const {
          leftHandSide,
          rightHandSide
        } = addWeights(upperTriangularNonZeros, yData, weights);
        const cho = matrixCholeskySolver(leftHandSide, size, permutationEncodedArray);
        if (!cho) {
          return baseline;
        }
        const newBaseline = cho(rightHandSide);
        //weights is updated inplace
        calculateAdaptiveWeights(yData, newBaseline, weights, {
          controlPoints,
          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 {
        controlPoints
      } = options;
      const {
        weights = Float64Array.from({
          length: y.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
      };
    }
    function whittakerByThomas(yData, options = {}) {
      const {
        lambda = 100,
        maxIterations = 100,
        tolerance = 1e-6,
        factorStd = 3,
        learningRate = 0.5
      } = options;
      const n = yData.length;
      const y = xEnsureFloat64(yData);
      const {
        controlPoints,
        weights
      } = getWeightsAndControlPoints(yData, options);
      const prevBaseline = new Float64Array(n);
      let baseline = xEnsureFloat64(yData);
      // Precompute base diagonal and constant off-diagonals (−lambda)
      const baseDiag = new Float64Array(n);
      if (n === 1) {
        baseDiag[0] = lambda;
      } else {
        baseDiag[0] = lambda;
        for (let i = 1; i < n - 1; i++) baseDiag[i] = 2 * lambda;
        baseDiag[n - 1] = lambda;
      }
      const lower = new Float64Array(Math.max(0, n - 1));
      const upper = new Float64Array(Math.max(0, n - 1));
      for (let i = 0; i < lower.length; i++) {
        lower[i] = -lambda;
        upper[i] = -lambda;
      }
      const main = new Float64Array(n);
      const rhs = new Float64Array(n);
      const solution = new Float64Array(n);
      const workC = new Float64Array(Math.max(0, n - 1));
      const workD = new Float64Array(n);
      let iteration = 0;
      let delta = Infinity;
      /**
       * Iterative Whittaker smoothing implementation using the Thomas algorithm
       * (tridiagonal solver). The method constructs the system
       * [W + lambda * D'D] z = W * y where D is the second-difference operator
       * and W are adaptive weights. At each iteration the tridiagonal system is
       * solved with the Thomas algorithm and the weights are updated using
       * `calculateAdaptiveWeights` until convergence.
       *
       * Use this algorithm by passing `algorithm: 'thomas'` to `xWhittakerSmoother`.
       * It is more memory-efficient for large inputs because it avoids forming
       * the full banded matrix, but both implementations aim to produce the same
       * baseline result.
       * @param yData - input signal/spectrum
       * @param options - smoothing options (supports `lambda`, `maxIterations`,
       *                  `tolerance`, `factorStd`, `learningRate`, and optional
       *                  `controlPoints` / `weights` inherited from
       *                  `CalculateAdaptiveWeightsOptions`).
       * @returns baseline as a `Float64Array`.
       */
      while (iteration < maxIterations && delta > tolerance) {
        // Build main diagonal = baseDiag + weights, and RHS = weights * y
        for (let i = 0; i < n; i++) {
          main[i] = baseDiag[i] + weights[i];
          rhs[i] = weights[i] * y[i];
        }
        // Solve tridiagonal system in-place into `solution`
        solveTridiagonalFloat64(lower, main, upper, rhs, solution, workC, workD);
        calculateAdaptiveWeights(y, solution, weights, {
          controlPoints,
          learningRate,
          factorStd
        });
        delta = calculateDelta(solution, prevBaseline, n);
        prevBaseline.set(solution);
        baseline = xEnsureFloat64(solution);
        iteration++;
      }
      return baseline;
    }
    function solveTridiagonalFloat64(lower, diag, upper, rhs, out, cp, dp) {
      const n = diag.length;
      const x = out ?? new Float64Array(n);
      if (n === 0) return x;
      if (n === 1) {
        x[0] = rhs[0] / diag[0];
        return x;
      }
      const cprime = cp ?? new Float64Array(n - 1);
      const dprime = dp ?? new Float64Array(n);
      let denom = diag[0];
      cprime[0] = upper[0] / denom;
      dprime[0] = rhs[0] / denom;
      for (let i = 1; i < n - 1; i++) {
        denom = diag[i] - lower[i - 1] * cprime[i - 1];
        cprime[i] = upper[i] / denom;
        dprime[i] = (rhs[i] - lower[i - 1] * dprime[i - 1]) / denom;
      }
      denom = diag[n - 1] - lower[n - 2] * cprime[n - 2];
      dprime[n - 1] = (rhs[n - 1] - lower[n - 2] * dprime[n - 2]) / denom;
      x[n - 1] = dprime[n - 1];
      for (let i = n - 2; i >= 0; i--) {
        x[i] = dprime[i] - cprime[i] * x[i + 1];
      }
      return x;
    }

    /**
     * Class to manage a chromatographic series.
     */
    class ChromatogramSeries {
      name;
      #data;
      #dimension;
      #meta;
      constructor(array, dimension, options = {}) {
        const {
          meta = {}
        } = options;
        if (new.target === ChromatogramSeries) {
          throw new Error('You need to create either a 1D or 2D series');
        }
        this.#data = array;
        this.#dimension = dimension;
        this.#meta = meta;
        this.name = '';
      }
      getData() {
        return this.#data;
      }
      is1D() {
        return this.#dimension === 1;
      }
      is2D() {
        return this.#dimension === 2;
      }
      toJSON() {
        return {
          data: this.dataToJson(),
          meta: structuredClone(this.#meta)
        };
      }
      /**
       * Specify an array of index to keep
       * @param array
       */
      keep(array) {
        const newData = [];
        for (const i of array) {
          newData.push(this.#data[i]);
        }
        this.#data = newData;
        return this;
      }
    }

    class ChromatogramSeries1D extends ChromatogramSeries {
      constructor(array, options) {
        super(array, 1, options);
      }
      dataToJson() {
        return Array.from(this.getData());
      }
    }

    class ChromatogramSeries2D extends ChromatogramSeries {
      constructor(array, options) {
        super(array, 2, options);
      }
      dataToJson() {
        return this.getData().map(([masses, intensities]) => [Array.from(masses), Array.from(intensities)]);
      }
    }

    function seriesFromArray(array, options) {
      if (!isAnyArray$1(array)) {
        throw new TypeError('seriesFromArray requires as parameter an array of numbers or arrays');
      }
      if (typeof array[0] === 'number') {
        return new ChromatogramSeries1D(array, options);
      }
      if (!isAnyArray$1(array[0]) || !isAnyArray$1(array[0][0]) || !isAnyArray$1(array[0][1])) {
        throw new TypeError('seriesFromArray requires as parameter an array of numbers or array');
      }
      return new ChromatogramSeries2D(array, options);
    }

    function meanFilter(chromatogram, seriesName, options = {}) {
      const {
        factor = 2
      } = options;
      const series = chromatogram.getSeries2D(seriesName);
      const filtered = [];
      const data = series.getData();
      for (const datum of data) {
        filtered.push(applyFilter$1(datum, factor));
      }
      return seriesFromArray(filtered);
    }
    function applyFilter$1(series, factor) {
      const filtered = [[], []];
      if (series[1].length === 0) return filtered;
      const meanIntensity = factor * xMean(series[1]);
      for (let i = 0; i < series[0].length; i++) {
        if (series[1][i] > meanIntensity) {
          filtered[0].push(series[0][i]);
          filtered[1].push(series[1][i]);
        }
      }
      return filtered;
    }

    function percentageFilter(chromatogram, seriesName, options = {}) {
      const {
        percentage = 0.1
      } = options;
      const series = chromatogram.getSeries2D(seriesName);
      const filtered = [];
      const data = series.getData();
      for (const item of data) {
        filtered.push(applyFilter(item, percentage));
      }
      return seriesFromArray(filtered);
    }
    function applyFilter(series, percentage) {
      let basePeak;
      try {
        basePeak = xMaxValue(series[1]);
      } catch {
        basePeak = 0;
      }
      const filtered = [[], []];
      for (let i = 0; i < series[0].length; i++) {
        if (series[1][i] > percentage * basePeak) {
          filtered[0].push(series[0][i]);
          filtered[1].push(series[1][i]);
        }
      }
      return filtered;
    }

    var src$7 = {};

    var parse$3 = {};

    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,
            deltaNeutrons: isotope.nominal - mostAbundant
          };
          if (isotope.nominal === mostAbundant) {
            entry.mostAbundant = true;
          }
          exports.stableIsotopesObject[`${isotope.nominal}${element.symbol}`] = entry;
        }
      }
    })(stableIsotopesObject);

    var types$1 = {};

    Object.defineProperty(types$1, "__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$1, 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$3, "__esModule", {
      value: true
    });
    parse$3.parse = parse$2;
    const atom_sorter_1$1 = src$6;
    const chemical_elements_1$c = src$5;
    const chemical_groups_1$4 = src$4;
    const Kind_1$9 = Kind;
    const parseCharge_1 = parseCharge$1;
    // module level: the parsing loop would otherwise allocate it for each token
    const PREMULTIPLIER_KINDS = new Set([Kind_1$9.Kind.SALT, Kind_1$9.Kind.BEGIN, Kind_1$9.Kind.OPENING_PARENTHESIS]);
    /**
     * 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$2(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$9.Kind.BEGIN;
        while (this.i < mf.length) {
          if (this.result.length > 0 && this.result.at(-1).kind !== Kind_1$9.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 (PREMULTIPLIER_KINDS.has(lastKind)) {
              if (value.to) {
                throw new MFError(this.mf, this.i, 'Premultiplier may not contain a -');
              }
              this.result.push({
                kind: Kind_1$9.Kind.PRE_MULTIPLIER,
                value: value.from
              });
            } else if (lastKind === Kind_1$9.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$9.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$9.Kind.MULTIPLIER,
                value: value.from
              });
            }
            continue;
          } else if (char === '.') {
            // a point
            this.result.push({
              kind: Kind_1$9.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$9.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$9.Kind.CHARGE,
                value: charge
              });
            } else {
              this.result.push({
                kind: Kind_1$9.Kind.OPENING_PARENTHESIS,
                value: '('
              });
            }
          } else if (char === ')') {
            this.result.push({
              kind: Kind_1$9.Kind.CLOSING_PARENTHESIS,
              value: ')'
            });
          } else if (char === '[') {
            // defines an isotope
            let isotope = this.getIsotope(ascii);
            this.result.push({
              kind: Kind_1$9.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$9.Kind.ATOM) {
              let lastResult = this.result.at(-1);
              lastResult.kind = Kind_1$9.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$9.Kind.CHARGE,
              value: charge
            });
          } else if (char === '-') {
            // charge not in parenthesis
            let charge = this.getNonParenthesisCharge(ascii);
            this.result.push({
              kind: Kind_1$9.Kind.CHARGE,
              value: charge
            });
          } else if (char === '$') {
            // it is a comment after
            this.result.push({
              kind: Kind_1$9.Kind.COMMENT,
              value: this.mf.slice(this.i + 1)
            });
            break;
          } else {
            this.result.push({
              kind: Kind_1$9.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$9.Kind.OPENING_PARENTHESIS) counter++;
          if (line.kind === Kind_1$9.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$c.elementsObject[atom] || !this.expandGroups) {
          return [{
            kind: Kind_1$9.Kind.ATOM,
            value: atom
          }];
        }
        if (chemical_groups_1$4.groupsObject[atom]) {
          const group = chemical_groups_1$4.groupsObject[atom].mf;
          const expandedGroups = parse$2(group, {
            expandGroups: this.expandGroups
          });
          // need to surround with parenthesis
          return [{
            kind: Kind_1$9.Kind.OPENING_PARENTHESIS,
            value: '('
          }, ...expandedGroups, {
            kind: Kind_1$9.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).toSorted((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$1.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$8 = 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$8.Kind.MULTIPLIER:
            if (line.value !== 1) {
              result = {
                kind: Format_1$2.Format.SUBSCRIPT,
                value: String(line.value)
              };
              results.push(result);
            }
            break;
          case Kind_1$8.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$8.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$8.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$8.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$8.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$8.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$8.Kind.CHARGE && lines[1].kind === Kind_1$8.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$b = 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$b.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$a = src$5;
    const elements = new Set(Object.keys(chemical_elements_1$a.elementsObject).toSorted((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$9 = src$5;
    function getIsotopeRatioInfo(value) {
      let result = {
        mass: 0,
        monoisotopicMass: 0
      };
      let element = chemical_elements_1$9.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$8 = src$5;
    const chemical_groups_1$2 = src$4;
    const Kind_1$7 = 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$7.Kind.ISOTOPE:
              {
                let isotope = chemical_elements_1$8.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$7.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$7.Kind.ATOM:
              {
                let element = chemical_elements_1$8.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$7.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$7 = src$5;
    const Kind_1$6 = 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$6.Kind.ATOM:
              {
                let symbol = line.value;
                let element = chemical_elements_1$7.elementsObject[symbol];
                if (!element) {
                  throw new Error(`element unknown: ${symbol} - ${line}`);
                }
                addElement(elements, {
                  symbol,
                  number
                });
                break;
              }
            case Kind_1$6.Kind.ISOTOPE:
              {
                let element = chemical_elements_1$7.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$6 = 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$6.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$5 = Kind;
    function partToMF(part, options = {}) {
      let mf = [];
      for (let line of part) {
        switch (line.kind) {
          case Kind_1$5.Kind.ISOTOPE:
            if (line.multiplier !== 0) {
              mf.push(`[${line.value.isotope}${line.value.atom}]${line.multiplier !== 1 ? line.multiplier : ''}`);
            }
            break;
          case Kind_1$5.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$5.Kind.ATOM:
            if (line.multiplier !== 0) {
              mf.push(line.value + (line.multiplier !== 1 ? line.multiplier : ''));
            }
            break;
          case Kind_1$5.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$5 = src$5;
    const chemical_groups_1$1 = src$4;
    const Kind_1$4 = 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),
        [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$4.Kind.ATOM:
            {
              currentElement = line.value;
              let element = chemical_elements_1$5.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$4.Kind.ISOTOPE:
            {
              currentElement = line.value.atom;
              let isotope = chemical_elements_1$5.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$4.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$4.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$5.unsaturationsObject[currentElement] !== undefined) {
            unsaturation += chemical_elements_1$5.unsaturationsObject[currentElement] * line.multiplier;
          } else {
            validUnsaturation = false;
          }
        }
      }
      // need to calculate the observedMonoisotopicMass
      if (currentPart.charge) {
        currentPart[msemFieldName] = (currentPart[emFieldName] - currentPart.charge * chemical_elements_1$5.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$4 = src$5;
    const Kind_1$3 = 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$3.Kind.ISOTOPE:
            {
              let isotopeKey = line.value.isotope + line.value.atom;
              let isotope = chemical_elements_1$4.isotopesObject[isotopeKey];
              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
                }],
                naturalDeltaNeutrons: chemical_elements_1$4.stableIsotopesObject[isotopeKey]?.deltaNeutrons ?? 0
              });
              break;
            }
          case Kind_1$3.Kind.ISOTOPE_RATIO:
            {
              let element = chemical_elements_1$4.elementsAndStableIsotopesObject[line.value.atom];
              if (!element) throw new Error('unknown element:', line.value);
              let distribution = getDistribution(element.isotopes, line.value.ratio);
              let maxIndex = 0;
              for (let i = 1; i < distribution.length; i++) {
                if (distribution[i].y > distribution[maxIndex].y) {
                  maxIndex = i;
                }
              }
              let mostAbundantKey = Math.round(distribution[maxIndex].x) + line.value.atom;
              result.isotopes.push({
                atom: line.value.atom,
                number: line.multiplier,
                distribution,
                naturalDeltaNeutrons: chemical_elements_1$4.stableIsotopesObject[mostAbundantKey]?.deltaNeutrons ?? 0
              });
              break;
            }
          case Kind_1$3.Kind.ATOM:
            {
              let element = chemical_elements_1$4.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
                })),
                naturalDeltaNeutrons: 0
              });
              break;
            }
          case Kind_1$3.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$4.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$2 = 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$2.Kind.SALT,
          value: '•'
        });
        for (let partLine of part) {
          lines.push(partLine);
          if (partLine.multiplier) {
            lines.push({
              kind: Kind_1$2.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 = src$6;
    const chemical_groups_1 = src$4;
    const Kind_1$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$1.Kind.BEGIN;
      parts.push(currentPart);
      for (let line of lines) {
        switch (line.kind) {
          case Kind_1$1.Kind.ATOM:
          case Kind_1$1.Kind.ISOTOPE_RATIO:
          case Kind_1$1.Kind.ISOTOPE:
          case Kind_1$1.Kind.CHARGE:
            currentPart.lines.push({
              ...line,
              multiplier: 1
            });
            break;
          case Kind_1$1.Kind.OPENING_PARENTHESIS:
            openingParenthesis(currentPart);
            break;
          case Kind_1$1.Kind.CLOSING_PARENTHESIS:
            closingParenthesis(currentPart);
            break;
          case Kind_1$1.Kind.PRE_MULTIPLIER:
            preMultiplier(currentPart, line);
            break;
          case Kind_1$1.Kind.MULTIPLIER:
            postMultiplier(currentPart, line.value, previousKind);
            break;
          case Kind_1$1.Kind.SALT:
            globalPartMultiplier(currentPart);
            currentPart = createNewPart();
            parts.push(currentPart);
            break;
          case Kind_1$1.Kind.ANCHOR:
            // we ignore anchors to create the parts and canonized MF
            break;
          case Kind_1$1.Kind.COMMENT:
            // we ignore comments to create the parts and canonized MF
            break;
          case Kind_1$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$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$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$1.Kind.CHARGE) {
            if (currentKey !== key.key) {
              result.push({
                kind: Kind_1$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$1.Kind.CHARGE) return 1;
          if (b.kind === Kind_1$1.Kind.CHARGE) return -1;
          let atomA = a.kind === Kind_1$1.Kind.ATOM ? a.value : a.value.atom;
          let atomB = b.kind === Kind_1$1.Kind.ATOM ? b.value : b.value.atom;
          if (atomA !== atomB) return (0, atom_sorter_1.atomSorter)(atomA, atomB);
          // same atome but some isotopes ...
          if (a.kind === Kind_1$1.Kind.ATOM) return -1;
          if (b.kind === Kind_1$1.Kind.ATOM) return 1;
          if (a.kind === Kind_1$1.Kind.ISOTOPE) return -1;
          if (b.kind === Kind_1$1.Kind.ISOTOPE) return 1;
          if (a.kind === Kind_1$1.Kind.ISOTOPE_RATIO) return -1;
          if (b.kind === Kind_1$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$1.Kind.CHARGE:
          break;
        default:
          if (typeof line.value === 'string') {
            key.push(line.value);
          } else {
            for (let prop of Object.keys(line.value).toSorted()) {
              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 Kind_1 = Kind;
    const ensureCase_1 = ensureCase$1;
    const parse_1 = parse$3;
    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 = {}) {
        const {
          flattenLimit = 0
        } = options;
        if (options.ensureCase) {
          mf = (0, ensureCase_1.ensureCase)(mf);
        }
        this.parsed = (0, parse_1.parse)(mf);
        // This is definitely not the fastest way to do it but it allows to flatten the formula if needed and then parse it again to have a clean structure without multiplier ranges. We can consider doing it in one pass in the future if performance is an issue.
        if (flattenLimit > 0 && this.parsed.some(entry => entry.kind === Kind_1.Kind.MULTIPLIER_RANGE)) {
          try {
            const flattened = (0, flatten_1.flatten)(this.parsed, {
              limit: flattenLimit
            });
            this.parsed = (0, parse_1.parse)(flattened.join('.'));
          } catch (error) {
            // eslint-disable-next-line no-console
            console.error(error.toString());
          }
        }
        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$3;
    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$3, 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);

    function applyLockMass(chromatogram, mfs, options = {}) {
      const {
        oddReference = true,
        maxShift = 0.1
      } = options;
      // allows mf as string or array
      if (typeof mfs === 'string') {
        mfs = [mfs];
      }
      // calculate the mass reference values
      const referenceMass = mfs.map(mf => {
        const info = new src$7.MF(mf).getInfo();
        return info.observedMonoisotopicMass || info.monoisotopicMass;
      });
      const ms = chromatogram.getSeries2D('ms').getData();
      // check where is the reference values
      const referenceIndexShift = Number(oddReference);
      const msIndexShift = Number(!oddReference);
      const newSize = ms.length >> 1;
      const referencesCount = new Array(referenceMass.length).fill(0);
      // applying the changes for all the spectra
      let previousValidDifference = Number.MAX_VALUE;
      let usingPreviousValidDifference = false;
      for (let i = 0; i < newSize; i++) {
        const massIndex = 2 * i + msIndexShift;
        const referenceIndex = 2 * i + referenceIndexShift;
        // calculate the difference between theory and experimental (the smallest)
        let difference = Number.MAX_VALUE;
        let closestIndex = -1;
        for (let j = 0; j < referenceMass.length; j++) {
          for (const mass of ms[referenceIndex][0]) {
            if (Math.abs(difference) > Math.abs(referenceMass[j] - mass)) {
              difference = referenceMass[j] - mass;
              closestIndex = j;
            }
          }
        }
        if (Math.abs(difference) > maxShift && Math.abs(previousValidDifference) < maxShift) {
          difference = previousValidDifference;
          usingPreviousValidDifference = true;
        } else {
          usingPreviousValidDifference = false;
        }
        // apply identified lock mass
        if (Math.abs(difference) < maxShift) {
          previousValidDifference = difference;
          if (!usingPreviousValidDifference && closestIndex !== -1) {
            referencesCount[closestIndex] += 1;
          }
          for (let m = 0; m < ms[massIndex][0].length; m++) {
            ms[massIndex][0][m] += difference;
          }
        }
      }
      const referenceUsed = {
        total: newSize,
        totalFound: referencesCount.reduce((prev, current) => current + prev, 0),
        mfs: {},
        percent: 0
      };
      for (let r = 0; r < referenceMass.length; r++) {
        referenceUsed.mfs[mfs[r]] = referencesCount[r];
      }
      referenceUsed.percent = referenceUsed.totalFound / referenceUsed.total * 100;
      // remove the time and the mass spectra that contains the reference
      chromatogram.filter(index => index % 2 !== referenceIndexShift);
      return referenceUsed;
    }

    /**
     * Calculate bpc
     * @param chromatogram - GC/MS chromatogram where make the peak picking
     * @returns Calculated bpc
     */
    function calculateBpc(chromatogram) {
      const ms = chromatogram.getSeries2D('ms');
      const massSpectra = ms.getData();
      const bpc = [];
      for (const massSpectrum of massSpectra) {
        if (massSpectrum[1].length > 0) {
          bpc.push(xMaxValue(massSpectrum[1]));
        } else {
          bpc.push(0);
        }
      }
      return bpc;
    }

    /**
     * Calculate the Extracted Ion Chromatogram
     * @param chromatogram
     * @param targetMass
     * @param options
     * @returns
     */
    function calculateEic(chromatogram, targetMass, options = {}) {
      const {
        slotWidth = 1
      } = options;
      if (!targetMass) {
        throw new Error('targetMass must be defined and a number or string of comma separated numbers');
      }
      let targetMasses;
      if (typeof targetMass === 'number') {
        targetMasses = [targetMass];
      } else if (typeof targetMass === 'string') {
        targetMasses = targetMass.split(/[\t\n\r ,;]+/).map(Number);
      } else {
        targetMasses = targetMass;
      }
      for (const mass of targetMasses) {
        if (Number.isNaN(mass)) {
          throw new Error('targetMass must be defined and a number or string of comma separated numbers');
        }
      }
      const halfWidth = slotWidth / 2;
      const ms = chromatogram.getSeries2D('ms');
      const massSpectra = ms.getData();
      const result = new Array(massSpectra.length).fill(0);
      for (const mass of targetMasses) {
        for (let i = 0; i < massSpectra.length; i++) {
          const massSpectrum = massSpectra[i];
          for (let j = 0; j < massSpectrum[0].length; j++) {
            if (Math.abs(massSpectrum[0][j] - mass) <= halfWidth) {
              result[i] += massSpectrum[1][j];
            }
          }
        }
      }
      return result;
    }

    var src$3 = {};

    var IsotopicDistribution$1 = {};

    var src$2 = {};

    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$8 = src$7;
    function getEutrophicationPotential(mfString) {
      let parsed = new mf_parser_1$8.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$3 = src$5;
    function getMsem(em, charge) {
      if (charge > 0) {
        return em / charge - chemical_elements_1$3.ELECTRON_MASS;
      } else if (charge < 0) {
        return em / (charge * -1) + chemical_elements_1$3.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$7 = src$7;
    function getRangesForFragment(ranges) {
      ranges = structuredClone(ranges);
      if (typeof ranges === 'string') {
        // need to convert to ranges
        let parsed = (0, mf_parser_1$7.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$7.Kind.ATOM:
              if (parenthesisLevel === 0 && currentMF) {
                newRanges.push({
                  mf: currentMF
                });
                currentMF = '';
              }
              currentMF += item.value;
              break;
            case mf_parser_1$7.Kind.ISOTOPE:
              if (parenthesisLevel === 0 && currentMF) {
                newRanges.push({
                  mf: currentMF
                });
                currentMF = '';
              }
              currentMF += `[${item.value.isotope}${item.value.atom}]`;
              break;
            case mf_parser_1$7.Kind.MULTIPLIER:
              if (parenthesisLevel === 0 && currentMF) {
                newRanges.push({
                  mf: currentMF,
                  max: item.value
                });
                currentMF = '';
              } else {
                currentMF += item.value;
              }
              break;
            case mf_parser_1$7.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$7.Kind.OPENING_PARENTHESIS:
              if (parenthesisLevel === 0 && currentMF) {
                newRanges.push({
                  mf: currentMF
                });
                currentMF = '';
              }
              parenthesisLevel++;
              currentMF += '(';
              break;
            case mf_parser_1$7.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$6 = 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$6.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.toSorted((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$5 = src$7;
    function mfDiff(mfString1, mfString2) {
      let mf1 = new mf_parser_1$5.MF(mfString1).getInfo().atoms;
      let mf2 = new mf_parser_1$5.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$5.MF(mf).toMF();
    }

    var preprocessIonizations$1 = {};

    Object.defineProperty(preprocessIonizations$1, "__esModule", {
      value: true
    });
    preprocessIonizations$1.preprocessIonizations = preprocessIonizations;
    const mf_parser_1$4 = 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$4.MF(ionization).flatten();
        for (const part of parts) {
          const info = new mf_parser_1$4.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$3 = src$7;
    function preprocessRanges(ranges) {
      ranges = structuredClone(ranges);
      if (typeof ranges === 'string') {
        // need to convert to ranges
        let parsed = (0, mf_parser_1$3.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$3.Kind.ATOM:
              if (parenthesisLevel === 0 && currentMF) {
                current.mf += currentMF;
                currentMF = '';
              }
              currentMF += item.value;
              break;
            case mf_parser_1$3.Kind.ISOTOPE:
              if (parenthesisLevel === 0 && currentMF) {
                current.mf += currentMF;
                currentMF = '';
              }
              currentMF += `[${item.value.isotope}${item.value.atom}]`;
              break;
            case mf_parser_1$3.Kind.MULTIPLIER:
              if (parenthesisLevel === 0 && currentMF) {
                current.mf += currentMF + item.value;
                currentMF = '';
              } else {
                currentMF += item.value;
              }
              break;
            case mf_parser_1$3.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$3.Kind.OPENING_PARENTHESIS:
              if (parenthesisLevel === 0 && currentMF) {
                current.mf += currentMF;
                currentMF = '';
              }
              parenthesisLevel++;
              currentMF += '(';
              break;
            case mf_parser_1$3.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$3.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$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(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$2);

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

    /**
     * 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 - options.
     * @returns a new object {re:[], im:[]} unless inPlace=true.
     */
    function reimPhaseCorrection(data, phi0 = 0, phi1 = 0, options = {}) {
      const {
        reverse = false,
        inPlace = false
      } = options;
      phi0 = Number.isFinite(phi0) ? phi0 : 0;
      phi1 = Number.isFinite(phi1) ? phi1 : 0;
      const length = data.re.length;
      // Decide target arrays
      const re = data.re;
      const im = data.im;
      const outRe = inPlace ? re : new Float64Array(length);
      const outIm = inPlace ? im : new Float64Array(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);
      for (let i = 0; i < length; i++) {
        const r = re[i];
        const ii = im[i];
        outRe[i] = r * cosTheta - ii * sinTheta;
        outIm[i] = ii * cosTheta + r * sinTheta;
        // Recursive angle update (stable incremental rotation)
        const newCosTheta = cosTheta - (alpha * cosTheta + beta * sinTheta);
        const newSinTheta = sinTheta - (alpha * sinTheta - beta * cosTheta);
        cosTheta = newCosTheta;
        sinTheta = newSinTheta;
      }
      return {
        re: outRe,
        im: outIm
      };
    }

    /**
     * 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
     * @returns phased spectrum together with the zero- and first-order correction angles (ph0, ph1) in degrees
     */
    function reimAutoPhaseCorrection(data, options = {}) {
      const {
        magnitudeMode = true,
        minRegSize = 30,
        factorNoise = 3,
        maxDistanceToJoin = 256,
        reverse = false,
        inPlace = 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,
        inPlace
      });
      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]);
    }
    /**
     * Finds the best phase angle for a region by minimizing the negative area.
     * @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
      };
    }
    /**
     * Applies the Holoborodko smoothing derivative filter.
     * @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;
    }
    /**
     * Detects baseline regions using robust iterative noise estimation.
     * @param s - input signal array.
     * @param options - detection options.
     * @param options.magnitudeMode - whether to use magnitude mode.
     * @param options.maxDistanceToJoin - max distance to join nearby regions.
     * @param options.factorNoise - noise factor for cutoff.
     */
    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;
    }
    /**
     * Computes a weighted linear regression and returns [slope, intercept].
     * @param x - x values.
     * @param y - y values.
     * @param w - weights.
     */
    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;
    }

    /**
     * Circular shift that moves the zero-frequency component to the center of the array (FFT shift).
     * For the inverse, the shift is reversed to restore the original layout.
     * @param data - input array
     * @param inverse - if true, shifts by ceil(n/2) instead of floor(n/2) to undo a previous zeroShift
     * @returns shifted array
     */
    function zeroShift(data, inverse) {
      const middle = inverse ? Math.ceil(data.length / 2) : Math.floor(data.length / 2);
      return xRotate(data, middle);
    }

    /**
     * Computes the FFT of a complex spectrum.
     * @param data - complex spectrum.
     * @param options - options.
     * @returns FFT of complex spectrum.
     */
    function reimFFT(data, options = {}) {
      const {
        inverse = false,
        applyZeroShift = false,
        inPlace = 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 = getFFT(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);
      }
      if (inPlace) {
        for (let i = 0; i < csize; i += 2) {
          re[i >>> 1] = output[i];
          im[i >>> 1] = output[i + 1];
        }
        return data;
      }
      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
      };
    }

    /**
     * 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 = createDoubleArray(re.constructor, totalLength);
      const newIM = createDoubleArray(im.constructor, totalLength);
      for (let i = 0; i < re.length; i++) {
        newRE[i] = re[i];
        newIM[i] = im[i];
      }
      return {
        re: newRE,
        im: newIM
      };
    }

    /**
     * Apply FFT to an array of complex spectra, reusing a single FFT instance for
     * all elements to avoid repeated instantiation overhead.
     * All elements must have the same length.
     * @param data - array of complex spectra
     * @param options - options
     * @returns array of transformed complex spectra
     */
    function reimArrayFFT(data, options = {}) {
      if (data.length === 0) return [];
      const {
        inverse = false,
        applyZeroShift = false,
        inPlace = false
      } = options;
      const size = data[0].re.length;
      const csize = size << 1;
      for (let j = 1; j < data.length; j++) {
        if (data[j].re.length !== size || data[j].im.length !== size) {
          throw new RangeError(`All elements must have the same length. Expected ${size} but element ${j} has length ${data[j].re.length}.`);
        }
      }
      // Single FFT instance and working buffers reused across all spectra
      const fft = getFFT(size);
      const complexArray = new Float64Array(csize);
      const output = new Float64Array(csize);
      const results = new Array(data.length);
      for (let j = 0; j < data.length; j++) {
        const {
          re,
          im
        } = data[j];
        for (let i = 0; i < csize; i += 2) {
          complexArray[i] = re[i >>> 1];
          complexArray[i + 1] = im[i >>> 1];
        }
        const outRe = inPlace ? re : new Float64Array(size);
        const outIm = inPlace ? im : new Float64Array(size);
        if (inverse) {
          const input = applyZeroShift ? zeroShift(complexArray, true) : complexArray;
          fft.inverseTransform(output, input);
          for (let i = 0; i < csize; i += 2) {
            outRe[i >>> 1] = output[i];
            outIm[i >>> 1] = output[i + 1];
          }
        } else {
          fft.transform(output, complexArray);
          const source = applyZeroShift ? zeroShift(output) : output;
          for (let i = 0; i < csize; i += 2) {
            outRe[i >>> 1] = source[i];
            outIm[i >>> 1] = source[i + 1];
          }
        }
        results[j] = {
          re: outRe,
          im: outIm
        };
      }
      return results;
    }

    /**
     * Apply FFT to each row of a complex matrix, reusing a single FFT instance for
     * all rows to avoid repeated instantiation overhead.
     * All rows must have the same length.
     * @param data - complex matrix with re and im arrays of Float64Array rows
     * @param options - options
     * @returns complex matrix with transformed rows
     */
    function reimMatrixFFT(data, options = {}) {
      const {
        re,
        im
      } = data;
      const numRows = re.length;
      if (numRows === 0) return {
        re: [],
        im: []
      };
      const {
        inverse = false,
        applyZeroShift = false,
        inPlace = false
      } = options;
      const size = re[0].length;
      const csize = size << 1;
      for (let j = 0; j < numRows; j++) {
        if (re[j].length !== size || im[j].length !== size) {
          throw new RangeError(`All rows must have the same length. Expected ${size} but row ${j} has length ${re[j].length}.`);
        }
      }
      // Single FFT instance and working buffers reused across all rows
      const fft = getFFT(size);
      const complexArray = new Float64Array(csize);
      const output = new Float64Array(csize);
      const resultRe = new Array(numRows);
      const resultIm = new Array(numRows);
      for (let j = 0; j < numRows; j++) {
        const reRow = re[j];
        const imRow = im[j];
        for (let i = 0; i < csize; i += 2) {
          complexArray[i] = reRow[i >>> 1];
          complexArray[i + 1] = imRow[i >>> 1];
        }
        const outRe = inPlace ? reRow : new Float64Array(size);
        const outIm = inPlace ? imRow : new Float64Array(size);
        if (inverse) {
          const input = applyZeroShift ? zeroShift(complexArray, true) : complexArray;
          fft.inverseTransform(output, input);
          for (let i = 0; i < csize; i += 2) {
            outRe[i >>> 1] = output[i];
            outIm[i >>> 1] = output[i + 1];
          }
        } else {
          fft.transform(output, complexArray);
          const source = applyZeroShift ? zeroShift(output) : output;
          for (let i = 0; i < csize; i += 2) {
            outRe[i >>> 1] = source[i];
            outIm[i >>> 1] = source[i + 1];
          }
        }
        resultRe[j] = outRe;
        resultIm[j] = outIm;
      }
      return {
        re: resultRe,
        im: resultIm
      };
    }

    /**
     * Apply FFT to each column of a complex matrix, reusing a single FFT instance for
     * all columns to avoid repeated instantiation overhead.
     * All columns must have the same length (all rows must have the same number of columns).
     * @param data - complex matrix with re and im arrays of Float64Array rows
     * @param options - options
     * @returns complex matrix with transformed columns
     */
    function reimMatrixFFTByColumns(data, options = {}) {
      const {
        re,
        im
      } = data;
      const numRows = re.length;
      if (numRows === 0) return {
        re: [],
        im: []
      };
      const {
        inverse = false,
        applyZeroShift = false,
        inPlace = false
      } = options;
      const numColumns = re[0].length;
      const csize = numRows << 1;
      // Validate all rows have the same number of columns
      for (let j = 0; j < numRows; j++) {
        if (re[j].length !== numColumns || im[j].length !== numColumns) {
          throw new RangeError(`All rows must have the same length. Expected ${numColumns} but row ${j} has length ${re[j].length}.`);
        }
      }
      if (numColumns === 0) {
        return {
          re: new Array(numRows),
          im: new Array(numRows)
        };
      }
      // Single FFT instance and working buffers reused across all columns
      const fft = getFFT(numRows);
      const complexArray = new Float64Array(csize);
      const output = new Float64Array(csize);
      const resultRe = new Array(numRows);
      const resultIm = new Array(numRows);
      // Initialize result arrays
      for (let i = 0; i < numRows; i++) {
        resultRe[i] = inPlace ? re[i] : new Float64Array(numColumns);
        resultIm[i] = inPlace ? im[i] : new Float64Array(numColumns);
      }
      // Process each column
      for (let col = 0; col < numColumns; col++) {
        // Extract column data into complex array
        for (let row = 0; row < numRows; row++) {
          complexArray[row << 1] = re[row][col];
          complexArray[(row << 1) + 1] = im[row][col];
        }
        const colRe = new Float64Array(numRows);
        const colIm = new Float64Array(numRows);
        if (inverse) {
          const input = applyZeroShift ? zeroShift(complexArray, true) : complexArray;
          fft.inverseTransform(output, input);
          for (let i = 0; i < csize; i += 2) {
            colRe[i >>> 1] = output[i];
            colIm[i >>> 1] = output[i + 1];
          }
        } else {
          fft.transform(output, complexArray);
          const source = applyZeroShift ? zeroShift(output) : output;
          for (let i = 0; i < csize; i += 2) {
            colRe[i >>> 1] = source[i];
            colIm[i >>> 1] = source[i + 1];
          }
        }
        // Store transformed column back into result matrix
        for (let row = 0; row < numRows; row++) {
          resultRe[row][col] = colRe[row];
          resultIm[row][col] = colIm[row];
        }
      }
      return {
        re: resultRe,
        im: resultIm
      };
    }

    /**
     * Apply phase correction to a complex matrix along rows or columns.
     * All rows must have the same length.
     * @param data - complex matrix with re and im arrays of Float64Array rows
     * @param phi0 - angle in radians for zero order phase correction.
     * @param phi1 - angle in radians for first order phase correction.
     * @param options - options including direction ('rows' or 'columns', default 'rows').
     * @returns complex matrix with corrected rows or columns.
     */
    function reimMatrixPhaseCorrection(data, phi0 = 0, phi1 = 0, options = {}) {
      const {
        reverse = false,
        inPlace = false,
        direction = 'rows'
      } = options;
      phi0 = Number.isFinite(phi0) ? phi0 : 0;
      phi1 = Number.isFinite(phi1) ? phi1 : 0;
      const {
        re,
        im
      } = data;
      const numRows = re.length;
      if (numRows === 0) return {
        re: [],
        im: []
      };
      const numColumns = re[0].length;
      for (let j = 0; j < numRows; j++) {
        if (re[j].length !== numColumns || im[j].length !== numColumns) {
          throw new RangeError(`All rows must have the same length. Expected ${numColumns} but row ${j} has length ${re[j].length}.`);
        }
      }
      const resultRe = new Array(numRows);
      const resultIm = new Array(numRows);
      // Initialize result arrays
      for (let i = 0; i < numRows; i++) {
        resultRe[i] = inPlace ? re[i] : new Float64Array(numColumns);
        resultIm[i] = inPlace ? im[i] : new Float64Array(numColumns);
      }
      const size = direction === 'rows' ? numColumns : numRows;
      let delta = phi1 / (size - 1);
      if (reverse) {
        delta = -delta;
      }
      const alpha = 2 * Math.sin(delta / 2) ** 2;
      const beta = Math.sin(delta);
      if (direction === 'rows') {
        for (let j = 0; j < numRows; j++) {
          let firstAngle = phi0;
          if (reverse) {
            firstAngle += phi1;
          }
          let cosTheta = Math.cos(firstAngle);
          let sinTheta = Math.sin(firstAngle);
          for (let i = 0; i < numColumns; i++) {
            const r = re[j][i];
            const ii = im[j][i];
            resultRe[j][i] = r * cosTheta - ii * sinTheta;
            resultIm[j][i] = ii * cosTheta + r * sinTheta;
            const newCosTheta = cosTheta - (alpha * cosTheta + beta * sinTheta);
            const newSinTheta = sinTheta - (alpha * sinTheta - beta * cosTheta);
            cosTheta = newCosTheta;
            sinTheta = newSinTheta;
          }
        }
      } else {
        for (let col = 0; col < numColumns; col++) {
          let firstAngle = phi0;
          if (reverse) {
            firstAngle += phi1;
          }
          let cosTheta = Math.cos(firstAngle);
          let sinTheta = Math.sin(firstAngle);
          for (let row = 0; row < numRows; row++) {
            const r = re[row][col];
            const ii = im[row][col];
            resultRe[row][col] = r * cosTheta - ii * sinTheta;
            resultIm[row][col] = ii * cosTheta + r * sinTheta;
            const newCosTheta = cosTheta - (alpha * cosTheta + beta * sinTheta);
            const newSinTheta = sinTheta - (alpha * sinTheta - beta * cosTheta);
            cosTheta = newCosTheta;
            sinTheta = newSinTheta;
          }
        }
      }
      return {
        re: resultRe,
        im: resultIm
      };
    }

    /**
     * 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.
     * @returns aligned x, y1, y2 arrays.
     */
    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 {
          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((data1.x[i] * data1.y[i] + data2.x[j] * data2.y[j]) / (data1.y[i] + data2.y[j]));
              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 - data to check.
     * @param options - options.
     * @param options.minLength - minimum required length.
     */
    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 - data with x and y arrays.
     * @param options - 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.
     * @returns point with the highest y value in the range.
     */
    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 + 1; 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: x[0],
        x25: 0,
        x50: 0,
        x75: 0,
        x100: x.at(-1),
        xMode: 0,
        xMean: 0
      };
      // need to find the x values closest to STEPS/100
      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;
    }

    /**
     * Converts both x and y arrays to Float64Array if they are not already.
     * @param data - object containing x and y arrays.
     * @returns data with x and y as Float64Array.
     */
    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).
     * @returns filtered data with strictly increasing x values.
     */
    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
      };
    }

    /**
     * The data seen as a piecewise linear density, with the integration precomputed on every point.
     * @param data - object that contains property x (an ordered increasing array) and y (an array).
     * @param options - options.
     * @returns the cumulated integration over the selected window.
     */
    function getCumulativeDensity(data, options = {}) {
      const {
        x,
        y
      } = data;
      const {
        fromIndex,
        toIndex
      } = xGetFromToIndex(x, options);
      if (toIndex - fromIndex < 1) {
        throw new Error('the selected range must contain at least 2 points');
      }
      const {
        beginX,
        endX
      } = getBoundaries(x, options, fromIndex, toIndex);
      const length = toIndex - fromIndex + 1;
      const integral = new Float64Array(length);
      for (let i = 1; i < length; i++) {
        const previousIndex = fromIndex + i - 1;
        const currentIndex = fromIndex + i;
        integral[i] = integral[i - 1] + (y[previousIndex] + y[currentIndex]) / 2 * (x[currentIndex] - x[previousIndex]);
      }
      return {
        x,
        y,
        fromIndex,
        toIndex,
        integral,
        beginX,
        endX
      };
    }
    /**
     * Integration from the beginning of the range up to a specific x value, interpolating inside
     * the interval that contains it.
     * @param cumulative - cumulated integration of the density.
     * @param target - x value.
     * @returns the integration.
     */
    function evaluateCumulative(cumulative, target) {
      const {
        x,
        y,
        fromIndex,
        toIndex,
        integral
      } = cumulative;
      if (target <= x[fromIndex]) return 0;
      const lastIndex = toIndex - fromIndex;
      if (target >= x[toIndex]) return integral[lastIndex];
      let low = 0;
      let high = lastIndex;
      while (high - low > 1) {
        const middle = low + (high - low >> 1);
        if (x[fromIndex + middle] <= target) {
          low = middle;
        } else {
          high = middle;
        }
      }
      const startX = x[fromIndex + low];
      const startY = y[fromIndex + low];
      const width = target - startX;
      const targetY = startY + (y[fromIndex + low + 1] - startY) * width / (x[fromIndex + low + 1] - startX);
      return integral[low] + (startY + targetY) / 2 * width;
    }
    /**
     * Boundaries of the window, that may be wider than the data itself.
     * @param x - array of x values.
     * @param options - options.
     * @param fromIndex - first index of the selected range.
     * @param toIndex - last index of the selected range.
     * @returns the x values delimiting the window.
     */
    function getBoundaries(x, options, fromIndex, toIndex) {
      const {
        from,
        to
      } = options;
      const firstX = x[fromIndex];
      const lastX = x[toIndex];
      let beginX = options.fromIndex === undefined && from !== undefined ? from : firstX;
      let endX = options.toIndex === undefined && to !== undefined ? to : lastX;
      if (beginX > endX) [beginX, endX] = [endX, beginX];
      return {
        beginX: Math.min(beginX, firstX),
        endX: Math.max(endX, lastX)
      };
    }

    /**
     * Split a spectrum in parts that all have the same integration and return the x values of the splits.
     * The first entry splits the spectrum in two halves of equal integration, the two next ones split each half
     * again, etc. This is the same as looking for the 1/2, then 1/4 and 3/4, then 1/8, 3/8, 5/8 and 7/8 fractions
     * of the total integration, which is how it is computed.
     * This describes a spectrum by a small vector and should provide an efficient way to store and search
     * similar spectra, comparing the vectors with `xEqualIntegrationVectorSimilarity`.
     * A split never lands between the peaks: it follows the integration, so it points at the signal itself. The
     * noise of a long baseline does not drag it either, because a point is never weighted by its own x value.
     * The data is considered as a piecewise linear density, so the result varies continuously with the y values
     * and hardly depends on the sampling density.
     * The window may extend beyond the data, the missing points being considered to have a y value of zero.
     * @param data - object that contains property x (an ordered increasing array) and y (an array).
     * @param options - options.
     * @returns array of x values splitting the spectrum in parts of equal integration.
     */
    function xyEqualIntegrationVector(data, options = {}) {
      xyCheck(data, {
        minLength: 2
      });
      const {
        depth = 5
      } = options;
      const cumulative = getCumulativeDensity(data, options);
      const {
        beginX,
        endX
      } = cumulative;
      const total = evaluateCumulative(cumulative, endX);
      const results = new Float64Array((1 << depth) - 1);
      let index = 0;
      for (let level = 0; level < depth; level++) {
        const nbSlots = 1 << level;
        for (let slot = 0; slot < nbSlots; slot++) {
          const fraction = (2 * slot + 1) / (2 * nbSlots);
          results[index++] = findFraction(cumulative, beginX, endX, total * fraction);
        }
      }
      return results;
    }
    /**
     * Dichotomic search of the x value for which the integration reaches a target.
     * @param cumulative - cumulated integration of the density.
     * @param beginX - beginning of the window.
     * @param endX - end of the window.
     * @param target - integration to reach.
     * @returns the x value.
     */
    function findFraction(cumulative, beginX, endX, target) {
      let low = beginX;
      let high = endX;
      // the interval is halved 60 times, far below the precision of a float64
      for (let i = 0; i < 60; i++) {
        const middle = (low + high) / 2;
        if (evaluateCumulative(cumulative, middle) < target) {
          low = middle;
        } else {
          high = middle;
        }
      }
      return (low + high) / 2;
    }

    /**
     * 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
      });
      zones.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 returns a Number array of equally spaced numberOfPoints x values
     * distributed across the specified zones.
     * @param zones - array of from/to zones where x values should be distributed
     * @param numberOfPoints - total number of points to distribute across all zones
     * @param options - options
     * @returns array of equally spaced x values distributed across zones
     */
    function zonesToXEquallySpaced(zones, numberOfPoints, options = {}) {
      if (!zones || zones.length === 0) {
        throw new RangeError('zones array must not be empty');
      }
      if (numberOfPoints < 1) {
        throw new RangeError("'numberOfPoints' must be greater than 0");
      }
      const from = options.from ?? zones[0].from;
      //@ts-expect-error zones is tested before
      const to = options.to ?? zones.at(-1).to;
      if (from > to) {
        throw new RangeError('from should be less than or equal to to');
      }
      const normalizedZones = zonesNormalize(zones, {
        from,
        to,
        exclusions: []
      });
      const zonesWithPointsRes = zonesWithPoints(normalizedZones, numberOfPoints, {
        from,
        to
      }).filter(zone => zone.numberOfPoints);
      let xResult = [];
      for (const zone of zonesWithPointsRes) {
        if (!zone.numberOfPoints) {
          zone.numberOfPoints = 0;
        }
        const zoneXValues = Array.from(createFromToArray({
          from: zone.from,
          to: zone.to,
          length: zone.numberOfPoints,
          includeFrom: false,
          includeTo: false
        }));
        xResult = xResult.concat(zoneXValues);
      }
      return new Float64Array(xResult);
    }

    /**
     * Function that retrieves the getEquallySpacedData with the variant "slot".
     * @param x - x coordinates.
     * @param y - y coordinates.
     * @param from - start of the output x range.
     * @param to - end of the output x range.
     * @param numberOfPoints - number of output points.
     * @returns array of y values 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 - first x coordinate.
     * @param x1 - second x coordinate.
     * @param slope - slope of the line.
     * @param intercept - y-intercept of the line.
     * @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 - x coordinates.
     * @param y - y coordinates.
     * @param from - start of the output x range.
     * @param to - end of the output x range.
     * @param numberOfPoints - number of output points.
     * @returns array of y values 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);
    }

    /**
     * Resample a spectrum to equally spaced x points.
     * When `exclusions` are provided they take precedence and `zones` is ignored.
     * @param data - object containing x and y arrays
     * @param options - options
     * @returns resampled spectrum with equally spaced x values
     */
    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 extracted DataXY containing only the points within the specified zones.
     */
    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
      };
    }

    /**
     * Convert a flat interleaved array [x, y, x, y, ...] to a DataXY object.
     * @param data - flat array alternating x and y values.
     * @returns DataXY object with separate x and y arrays.
     */
    function xyFromInterleaved(data) {
      if (data.length % 2 !== 0) {
        throw new RangeError(`xyFromInterleaved: data length must be even, got ${data.length}`);
      }
      const length = data.length / 2;
      const x = new Float64Array(length);
      const y = new Float64Array(length);
      for (let i = 0; i < length; i++) {
        x[i] = data[2 * i];
        y[i] = data[2 * i + 1];
      }
      return {
        x,
        y
      };
    }

    /**
     * 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 - number of top peaks to keep.
     * @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;
      // @ts-expect-error -- x.at(-1) returns number | undefined but array is guaranteed non-empty here
      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);
        const floatY = Float64Array.from(data.y);
        floatY.sort();
        floatY.reverse();
        const threshold = floatY[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:[]}.
     * @returns data with x in ascending order (reversed in-place if descending).
     */
    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.toReversed(),
        y: y.toReversed()
      };
    }

    /**
     * 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 cumulative integral values.
     */
    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 integration 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;
    }

    /**
     * Interpolate y values at target x positions using linear interpolation.
     * This is useful for upsampling or resampling data to specific x positions.
     * Both the input data and target x values must be sorted in ascending order.
     * Uses a two-pointer approach with O(n + m) complexity.
     * @param data - object that contains property x (an ordered increasing array) and y (an array).
     * @param xTarget - target x positions where y values should be interpolated.
     * @returns interpolated y values at target x positions.
     */
    function xyInterpolateLinear(data, xTarget) {
      xyCheck(data);
      const {
        x,
        y
      } = data;
      if (x.length === 0) {
        return new Float64Array(0);
      }
      const result = new Float64Array(xTarget.length);
      let sparseIndex = 0;
      for (let i = 0; i < xTarget.length; i++) {
        const targetX = xTarget[i];
        // Move sparse index forward while we can
        while (sparseIndex < x.length - 1 && x[sparseIndex + 1] < targetX) {
          sparseIndex++;
        }
        // Handle edge cases
        if (sparseIndex === 0 && targetX <= x[0]) {
          // Before first point
          if (x.length === 1) {
            result[i] = y[0];
          } else {
            // Extrapolate using first two points
            const t = (targetX - x[0]) / (x[1] - x[0]);
            result[i] = y[0] * (1 - t) + y[1] * t;
          }
        } else if (sparseIndex >= x.length - 1) {
          // After last point
          result[i] = y[x.length - 1];
        } else {
          // Normal interpolation between two points
          const leftIdx = sparseIndex;
          const rightIdx = sparseIndex + 1;
          const t = (targetX - x[leftIdx]) / (x[rightIdx] - x[leftIdx]);
          result[i] = y[leftIdx] * (1 - t) + y[rightIdx] * t;
        }
      }
      return result;
    }

    /**
     * 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 joined x and summed y values.
     */
    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.
     * @returns weighted mean x value (center of mass).
     */
    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.
     * @deprecated A center of mass averages positions, so a split lands between the peaks instead of on them, and
     * every point is weighted by its own x value, which lets the noise of a long baseline drag the result.
     * Use `xyEqualIntegrationVector`, that splits the spectrum in parts of equal integration.
     * @param data - object that contains property x (an ordered increasing array) and y (an array).
     * @param options - 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 - options 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 + 1; i <= toIndex; i++) {
        if (y[i] > currentxyMaxY) currentxyMaxY = y[i];
      }
      return currentxyMaxY;
    }

    /**
     * Computes the weighted median of the x values, using the y values as weights.
     * This is the x value that splits the total weight (sum of y) into two equal halves.
     * If the cumulative weight lands exactly at 50%, the result is the average of the two surrounding x values.
     * @param data - x should be sorted in increasing order, y values are used as weights and should be non-negative.
     * @returns The weighted median x value, or NaN if the data is empty.
     */
    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) {
          if (i === x.length - 1) return x[i];
          return 0.5 * (x[i] + x[i + 1]);
        }
      }
      return Number.NaN;
    }

    /**
     * Computes the median of Y values in a sliding window around each point.
     * @param data - object with x and y arrays of the same length.
     * @param options - options for the median computation.
     * @returns A new DataXY with the same x values and smoothed y values.
     */
    function xyMedianY(data, options = {}) {
      const {
        windowSize = 5
      } = options;
      const {
        x,
        y
      } = data;
      const halfWindow = Math.floor(windowSize / 2);
      const result = new Float64Array(y.length);
      for (let i = 0; i < y.length; i++) {
        const fromIndex = Math.max(0, i - halfWindow);
        const toIndex = Math.min(y.length, i + halfWindow + 1);
        result[i] = xMedian(y, {
          exact: false,
          fromIndex,
          toIndex
        });
      }
      return {
        x,
        y: result
      };
    }

    /**
     * Computes the median of Y values in a sliding window around each target x position.
     * For each value in xValues, the closest index in data.x is found and the median
     * of the surrounding y values (within the window) is returned.
     * @param data - object with x (sorted in increasing order) and y arrays of the same length.
     * @param xValues - array of x positions at which to compute the median.
     * @param options - options for the median computation.
     * @returns A new DataXY with x = xValues and y = computed medians.
     */
    function xyMedianYAtXs(data, xValues, options = {}) {
      const {
        windowSize = 5
      } = options;
      const {
        x,
        y
      } = data;
      const halfWindow = Math.floor(windowSize / 2);
      const result = new Float64Array(xValues.length);
      for (let i = 0; i < xValues.length; i++) {
        const centerIndex = xFindClosestIndex(x, xValues[i]);
        const fromIndex = Math.max(0, centerIndex - halfWindow);
        const toIndex = Math.min(y.length, centerIndex + halfWindow + 1);
        result[i] = xMedian(y, {
          exact: false,
          fromIndex,
          toIndex
        });
      }
      return {
        x: xValues,
        y: result
      };
    }

    /**
     * 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 - options 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 + 1; 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
        };
      }
    }

    /**
     * Finds the real minimum y point using parabolic interpolation when possible.
     * @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
      });
      // @ts-expect-error -- x.at(-1) returns number | undefined but array is guaranteed non-empty here
      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
      };
      /**
       * Appends reduced x/y values for a zone to the output arrays.
       * @param fromIndex - start index in the input arrays.
       * @param  toIndex - end index in the input arrays.
       * @param zoneNbPoints - target number of points to keep in this zone.
       */
      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
      });
      // @ts-expect-error -- x.at(-1) returns number | undefined but array is guaranteed non-empty here
      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);
      }
      // @ts-expect-error -- x.at(-1) returns number | undefined but array is guaranteed non-empty here
      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
      };
    }

    /**
     *
     * Dong, Jian, et al. "An algorithm of filtering noises in multi-beam data based on rolling circle transform." 2019 2nd International Conference on Sustainable Energy, Environment and Information Engineering (SEEIE 2019). Atlantis Press, 2019.
     * DONG Jian, PENG Rencan, ZHANG Lihua, WANG Zhijun. An Algorithm of Filtering Noises in Multi-beam Data Based on Rolling Circle Transform[J]. Geomatics and Information Science of Wuhan University, 2016, 41(1): 86-92. DOI: 10.13203/j.whugis20130757
     * @param data - data with x and y arrays.
     * @param options - options.
     */
    function xyRollingCircleTransform(data, options = {}) {
      const {
        x
      } = data;
      let {
        y
      } = data;
      const {
        radius = 1,
        position = 'top',
        shifted = true
      } = options;
      if (position !== 'top' && position !== 'bottom') {
        throw new Error(`Invalid position: ${String(position)}`);
      }
      if (position === 'bottom') {
        y = y.slice();
        for (let i = 0; i < y.length; i++) {
          y[i] = -y[i];
        }
      }
      if (x.length === 0 || y.length === 0) {
        return new Float64Array();
      }
      const yCenters = new Float64Array(x.length);
      for (let i = 0; i < x.length; i++) {
        const x0 = x[i]; // x center of the current circle
        const fromX = xFindClosestIndex(x, x0 - radius);
        const toX = xFindClosestIndex(x, x0 + radius);
        // for the circle radius we need to evaluate the minimal vertical shift
        const y0 = y[i] + radius; // y center of the circle on top of peak
        let yShift = y0; // this is the minimal possible shift
        for (let j = fromX; j <= toX; j++) {
          const currentX = x[j];
          if (currentX < x0 - radius || currentX > x0 + radius) {
            continue;
          }
          const currentMinYShift = y[j] + Math.sqrt(radius ** 2 - (currentX - x0) ** 2);
          if (currentMinYShift > yShift) {
            yShift = currentMinYShift;
          }
        }
        yCenters[i] = yShift;
      }
      if (!shifted) {
        for (let i = 0; i < yCenters.length; i++) {
          yCenters[i] -= radius;
        }
      }
      if (position === 'bottom') {
        for (let i = 0; i < yCenters.length; i++) {
          yCenters[i] = -yCenters[i];
        }
      }
      return yCenters;
    }

    /**
     * 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 data with y values set to the given value in the specified zones.
     */
    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) {
        const floatX = Float64Array.from(x);
        const floatY = Float64Array.from(y);
        if (floatX.at(-1) < floatX[0]) {
          floatX.reverse();
          floatY.reverse();
        }
        return {
          x: floatX,
          y: floatY
        };
      }
      const xyObject = Array.from(x, (val, index) => ({
        x: val,
        y: y[index]
      }));
      xyObject.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 object to a flat interleaved array [x, y, x, y, ...].
     * @param data - DataXY object with x and y arrays.
     * @returns flat array alternating x and y values.
     */
    function xyToInterleaved(data) {
      xyCheck(data);
      const {
        x,
        y
      } = data;
      const result = new Float64Array(x.length * 2);
      for (let i = 0; i < x.length; i++) {
        result[2 * i] = x[i];
        result[2 * i + 1] = y[i];
      }
      return result;
    }

    /**
     * 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;
    }

    /**
     * Converts a DataXY object to an array of {x,y} point objects.
     * @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$1(data);
        default:
          throw new Error(`unknown algorithm: ${String(algorithm)}`);
      }
    }
    /**
     * Averages duplicate x values by averaging their y values.
     * @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
      };
    }
    /**
     * Merges duplicate x values by summing their y values.
     * @param data - input.
     * @returns result.
     */
    function sum$1(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 arrays, x has to be monotone.
     * @param data - object of kind {x:[], re:[], im:[]}
     * @returns sorted DataXReIm object.
     */
    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.toReversed(),
        re: re.toReversed(),
        im: im.toReversed()
      };
    }

    /**
     * This function makes a zero filling to re and im part.
     * @param data - object of kind {x:[], re:[], im:[]}
     * @param totalLength - final number of points
     * @returns zero-filled DataXReIm object.
     */
    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)
      };
    }

    /**
     * Computes common x slots from multiple DataXY spectra by grouping nearby x values.
     * @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));
      possibleXs.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
      };
    }
    /**
     * Finds the next minimum x value across all spectra and advances the corresponding position pointer.
     * @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);
      }
    }

    /**
     * Computes slots aligned to the first spectrum's x values, adding slots for any x values not covered.
     * @param data - data.
     * @param options - options.
     * @returns the slots, sorted by x value, with non-overlapping `[from, to)` ranges.
     */
    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;
      // Only the first-spectrum slots drive this pass; slots created for uncovered
      // x values are appended and merged in by the sort below, so we freeze the
      // count instead of iterating an array we are mutating.
      const firstSlotCount = slots.length;
      for (let i = 0; i < firstSlotCount; i++) {
        const slot = slots[i];
        while (currentPosition < otherXs.length && otherXs[currentPosition] < slot.to) {
          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
     * @returns the common `x` axis and one aligned `y` array per input spectrum.
     */
    function xyArrayAlignToFirst(data, options = {}) {
      const {
        delta = 1
      } = options;
      const slots = getSlotsToFirst(data, {
        delta
      });
      const x = new Float64Array(slots.length);
      for (let i = 0; i < slots.length; i++) {
        x[i] = slots[i].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;
      const selected = points.filter(point => point.x >= from && point.x <= to).map(point => {
        return {
          point
        };
      });
      selected.sort((a, b) => {
        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) {
          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;
      }
      toReturn.sort((a, b) => a.x - b.x);
      return toReturn;
    }

    /**
     * Joins points whose x values are within xError of each other using weighted averaging.
     * @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 - 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;
    }

    /**
     * Slots growing points into fixed-width buckets and sums their y values.
     * @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) {
      points.sort((a, b) => a.x - b.x);
      return points;
    }

    /**
     * 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;
    }

    /**
     * Converts an array of {x,y} point objects to a DataXY object.
     * @param points - array of {x,y} point objects.
     */
    function xyObjectToXY(points) {
      return {
        x: points.map(entry => entry.x),
        y: points.map(entry => entry.y)
      };
    }

    var lib$2 = /*#__PURE__*/Object.freeze({
        __proto__: null,
        calculateAdaptiveWeights: calculateAdaptiveWeights,
        clearFFTCache: clearFFTCache,
        createDoubleArray: createDoubleArray,
        createFromToArray: createFromToArray,
        createNumberArray: createNumberArray,
        createRandomArray: createRandomArray,
        createStepArray: createStepArray,
        getCombinations: getCombinations,
        getCombinationsIterator: getCombinationsIterator,
        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,
        matrixHilbertTransform: matrixHilbertTransform,
        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,
        recursiveRemoveEmptyAndNull: recursiveRemoveEmptyAndNull,
        recursiveResolve: recursiveResolve,
        recursiveUntypeArrays: recursiveUntypeArrays,
        reimAbsolute: reimAbsolute,
        reimArrayFFT: reimArrayFFT,
        reimAutoPhaseCorrection: reimAutoPhaseCorrection,
        reimFFT: reimFFT,
        reimMatrixFFT: reimMatrixFFT,
        reimMatrixFFTByColumns: reimMatrixFFTByColumns,
        reimMatrixPhaseCorrection: reimMatrixPhaseCorrection,
        reimPhaseCorrection: reimPhaseCorrection,
        reimZeroFilling: reimZeroFilling,
        setFFTCacheMaxSize: setFFTCacheMaxSize,
        stringify: stringify,
        xAbsolute: xAbsolute,
        xAbsoluteMedian: xAbsoluteMedian,
        xAbsoluteSum: xAbsoluteSum,
        xAdd: xAdd,
        xApplyFunctionStr: xApplyFunctionStr,
        xAutoCorrelation: xAutoCorrelation,
        xBinning: xBinning,
        xBoxPlot: xBoxPlot,
        xBoxPlotWithOutliers: xBoxPlotWithOutliers,
        xCheck: xCheck,
        xCheckLengths: xCheckLengths,
        xCorrelation: xCorrelation,
        xCostMatrix: xCostMatrix,
        xCrossCorrelation: xCrossCorrelation,
        xCumulative: xCumulative,
        xDistributionStats: xDistributionStats,
        xDivide: xDivide,
        xDotProduct: xDotProduct,
        xEnsureFloat64: xEnsureFloat64,
        xEqualIntegrationVectorSimilarity: xEqualIntegrationVectorSimilarity,
        xFindClosestIndex: xFindClosestIndex,
        xGetFromToIndex: xGetFromToIndex,
        xGetTargetIndex: xGetTargetIndex,
        xHilbertTransform: xHilbertTransform,
        xHistogram: xHistogram,
        xIsArray: xIsArray,
        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,
        xyEqualIntegrationVector: xyEqualIntegrationVector,
        xyEquallySpaced: xyEquallySpaced,
        xyExtract: xyExtract,
        xyFilter: xyFilter,
        xyFilterMinYValue: xyFilterMinYValue,
        xyFilterTopYValues: xyFilterTopYValues,
        xyFilterX: xyFilterX,
        xyFilterXPositive: xyFilterXPositive,
        xyFindClosestPoint: xyFindClosestPoint,
        xyFromInterleaved: xyFromInterleaved,
        xyGetNMaxY: xyGetNMaxY,
        xyGrowingX: xyGrowingX,
        xyIntegral: xyIntegral,
        xyIntegration: xyIntegration,
        xyInterpolateLinear: xyInterpolateLinear,
        xyJoinX: xyJoinX,
        xyMassCenter: xyMassCenter,
        xyMassCenterVector: xyMassCenterVector,
        xyMaxClosestYPoint: xyMaxClosestYPoint,
        xyMaxMerge: xyMaxMerge,
        xyMaxY: xyMaxY,
        xyMaxYPoint: xyMaxYPoint,
        xyMaximaY: xyMaximaY,
        xyMedian: xyMedian,
        xyMedianY: xyMedianY,
        xyMedianYAtXs: xyMedianYAtXs,
        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,
        xyRollingCircleTransform: xyRollingCircleTransform,
        xySetYValue: xySetYValue,
        xySortX: xySortX,
        xyToInterleaved: xyToInterleaved,
        xyToXYArray: xyToXYArray,
        xyToXYObject: xyToXYObject,
        xyUniqueX: xyUniqueX,
        xyWeightedMerge: xyWeightedMerge,
        zonesNormalize: zonesNormalize,
        zonesToXEquallySpaced: zonesToXEquallySpaced,
        zonesWithPoints: zonesWithPoints
    });

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

    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
    /**
     * Approximate inverse error function.
     * @param x - value in the range (-1, 1).
     * @returns erfinv(x).
     * @see https://en.wikipedia.org/wiki/Error_function#Inverse_functions
     */
    function erfinv(x) {
      const a = 0.147;
      if (x === 0) return 0;
      const ln1MinusXSqrd = Math.log(1 - x * x);
      const lnEtcBy2Plus2 = ln1MinusXSqrd / 2 + 2 / (Math.PI * a);
      const firstSqrt = Math.sqrt(lnEtcBy2Plus2 ** 2 - ln1MinusXSqrd / a);
      const secondSqrt = Math.sqrt(firstSqrt - lnEtcBy2Plus2);
      return secondSqrt * (x > 0 ? 1 : -1);
    }

    class Gaussian {
      /**
       * Full width at half maximum.
       * @default 500
       */
      fwhm;
      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'];
      }
      derivative(x) {
        const {
          fct,
          dx,
          dFwhm
        } = gaussianDerivative(x, this.fwhm);
        return {
          fct,
          dx,
          parameters: [dFwhm]
        };
      }
    }
    /**
     * Calculate the peak height for a given area and fwhm.
     * @param options - fwhm, area, and optional sd.
     * @returns the peak height.
     */
    function calculateGaussianHeight(options) {
      const {
        area = 1,
        sd
      } = options;
      let {
        fwhm = 500
      } = options;
      if (sd) fwhm = gaussianWidthToFWHM(2 * sd);
      return 2 * area / ROOT_PI_OVER_LN2 / fwhm;
    }
    /**
     * Evaluate the gaussian function centered at x=0.
     * @param x - position at which to evaluate.
     * @param fwhm - full width at half maximum.
     * @returns the intensity at x.
     */
    function gaussianFct(x, fwhm) {
      return Math.exp(GAUSSIAN_EXP_FACTOR * (x / fwhm) ** 2);
    }
    /**
     * Analytical value and partial derivatives of the gaussian function centered at x=0.
     * @param x - position at which to evaluate.
     * @param fwhm - full width at half maximum.
     * @returns the value `fct` and its partial derivatives with respect to `x` (`dx`) and `fwhm` (`dFwhm`).
     */
    function gaussianDerivative(x, fwhm) {
      const fct = gaussianFct(x, fwhm);
      const dx = 2 * GAUSSIAN_EXP_FACTOR * x / (fwhm * fwhm) * fct;
      const dFwhm = -2 * GAUSSIAN_EXP_FACTOR * x * x / (fwhm * fwhm * fwhm) * fct;
      return {
        fct,
        dx,
        dFwhm
      };
    }
    /**
     * Convert inflection-point width to full width at half maximum.
     * @param width - width between inflection points.
     * @returns full width at half maximum.
     */
    function gaussianWidthToFWHM(width) {
      return width * ROOT_2LN2;
    }
    /**
     * Convert full width at half maximum to inflection-point width.
     * @param fwhm - full width at half maximum.
     * @returns width between inflection points.
     */
    function gaussianFwhmToWidth(fwhm) {
      return fwhm / ROOT_2LN2;
    }
    /**
     * Calculate the area under a gaussian peak.
     * @param options - fwhm, height, and optional sd.
     * @returns the area.
     */
    function getGaussianArea(options) {
      const {
        sd,
        height = 1
      } = options;
      let {
        fwhm = 500
      } = options;
      if (sd) fwhm = gaussianWidthToFWHM(2 * sd);
      return height * ROOT_PI_OVER_LN2 * fwhm / 2;
    }
    /**
     * Calculate the half-width factor corresponding to a given area coverage fraction.
     * @param area - target area fraction (0–1). Defaults to `0.9999`.
     * @returns the factor by which to multiply fwhm to cover the given area.
     */
    function getGaussianFactor(area = 0.9999) {
      return Math.sqrt(2) * erfinv(area);
    }
    /**
     * Generate an intensity array for a gaussian shape.
     * @param shape - gaussian shape parameters (fwhm, sd).
     * @param options - sampling options (length, factor, height).
     * @returns Float64Array of intensity values.
     */
    function getGaussianData(shape = {}, options = {}) {
      const {
        sd
      } = shape;
      let {
        fwhm = 500
      } = shape;
      if (sd) fwhm = gaussianWidthToFWHM(2 * sd);
      const {
        factor = getGaussianFactor(),
        height = calculateGaussianHeight({
          fwhm
        })
      } = options;
      let {
        length
      } = options;
      if (!length) {
        length = Math.min(Math.ceil(fwhm * factor), 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++) {
        const value = gaussianFct(i - center, fwhm) * height;
        data[i] = value;
        data[length - 1 - i] = value;
      }
      return data;
    }

    class Lorentzian {
      /**
       * Full width at half maximum.
       * @default 500
       */
      fwhm;
      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'];
      }
      derivative(x) {
        const {
          fct,
          dx,
          dFwhm
        } = lorentzianDerivative(x, this.fwhm);
        return {
          fct,
          dx,
          parameters: [dFwhm]
        };
      }
    }
    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);
    };
    /**
     * Analytical value and partial derivatives of the lorentzian function centered at x=0.
     * @param x - position at which to evaluate.
     * @param fwhm - full width at half maximum.
     * @returns the value `fct` and its partial derivatives with respect to `x` (`dx`) and `fwhm` (`dFwhm`).
     */
    function lorentzianDerivative(x, fwhm) {
      const denominator = 4 * x * x + fwhm * fwhm;
      const fct = fwhm * fwhm / denominator;
      const dx = -8 * x * fwhm * fwhm / (denominator * denominator);
      const dFwhm = 8 * fwhm * x * x / (denominator * denominator);
      return {
        fct,
        dx,
        dFwhm
      };
    }
    const lorentzianWidthToFWHM = width => {
      return width * ROOT_THREE;
    };
    const lorentzianFwhmToWidth = fwhm => {
      return fwhm / ROOT_THREE;
    };
    const lorentzianQuantile = p => Math.tan(Math.PI * (p - 0.5));
    const getLorentzianFactor = (area = 0.9999) => {
      if (area >= 1) {
        throw new Error('area should be (0 - 1)');
      }
      const halfResidual = (1 - area) * 0.5;
      return (lorentzianQuantile(1 - halfResidual) - lorentzianQuantile(halfResidual)) / 2;
    };
    const getLorentzianData = (shape = {}, options = {}) => {
      const {
        fwhm = 500
      } = shape;
      const {
        factor = getLorentzianFactor(),
        height = calculateLorentzianHeight({
          fwhm,
          area: 1
        })
      } = options;
      let {
        length
      } = options;
      if (!length) {
        length = Math.min(Math.ceil(fwhm * factor), 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++) {
        const value = lorentzianFct(i - center, fwhm) * height;
        data[i] = value;
        data[length - 1 - i] = value;
      }
      return data;
    };

    class LorentzianDispersive {
      /**
       * Full width at half maximum.
       * @default 500
       */
      fwhm;
      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);
      }
      getArea() {
        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'];
      }
      derivative(x) {
        const {
          fct,
          dx,
          dFwhm
        } = lorentzianDispersiveDerivative(x, this.fwhm);
        return {
          fct,
          dx,
          parameters: [dFwhm]
        };
      }
    }
    const lorentzianDispersiveFct = (x, fwhm) => {
      return 2 * fwhm * x / (4 * x ** 2 + fwhm ** 2);
    };
    /**
     * Analytical value and partial derivatives of the dispersive lorentzian function centered at x=0.
     * @param x - position at which to evaluate.
     * @param fwhm - full width at half maximum.
     * @returns the value `fct` and its partial derivatives with respect to `x` (`dx`) and `fwhm` (`dFwhm`).
     */
    function lorentzianDispersiveDerivative(x, fwhm) {
      const denominator = 4 * x * x + fwhm * fwhm;
      const fct = 2 * fwhm * x / denominator;
      const dx = 2 * fwhm * (fwhm * fwhm - 4 * x * x) / (denominator * denominator);
      const dFwhm = 2 * x * (4 * x * x - fwhm * fwhm) / (denominator * denominator);
      return {
        fct,
        dx,
        dFwhm
      };
    }
    const getLorentzianDispersiveData = (shape = {}, options = {}) => {
      const {
        fwhm = 500
      } = shape;
      const {
        factor = getLorentzianFactor(),
        height = calculateLorentzianHeight({
          fwhm,
          area: 1
        })
      } = options;
      let {
        length
      } = options;
      if (!length) {
        length = Math.min(Math.ceil(fwhm * factor), 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++) {
        const value = lorentzianDispersiveFct(i - center, fwhm) * height;
        data[i] = value;
        data[length - 1 - i] = -value;
      }
      return data;
    };

    /**
     * Find the k factor for a pseudo-Voigt distribution such that the
     * cumulative probability pPseudoVoigt(k, mu) equals `pTarget`.
     *
     * Uses a simple bisection search (with exponential bracketing) to
     * invert the pseudo-Voigt cumulative function. Special cases:
     * - mu === 1 -> reduces to the gaussian case
     * - mu === 0 -> reduces to the lorentzian case
     * @param pTarget - Target cumulative probability in (0,1)
     * @param mu - Gaussian fraction in [0,1]
     * @param tol - Convergence tolerance
     * @param maxIter - Maximum number of bisection iterations
     * @returns the factor k such that pPseudoVoigt(k, mu) ~= pTarget
     */
    function pseudoVoigtFindFactor(pTarget, mu, tol = 1e-9, maxIter = 200) {
      if (pTarget <= 0 || pTarget >= 1) {
        throw new RangeError('pTarget must be in (0,1)');
      }
      if (mu === 1) {
        return getGaussianFactor(pTarget);
      } else if (mu === 0) {
        return getLorentzianFactor(pTarget);
      }
      // bisection
      let lo = 0;
      let hi = 10;
      let it = 0;
      while (pPseudoVoigt(hi, mu) < pTarget && it++ < 200) hi *= 2;
      for (let i = 0; i < maxIter; i++) {
        const mid = 0.5 * (lo + hi);
        const val = pPseudoVoigt(mid, mu);
        if (Math.abs(val - pTarget) < tol) return mid;
        if (val < pTarget) {
          lo = mid;
        } else {
          hi = mid;
        }
      }
      return 0.5 * (lo + hi);
    }
    function erf(x) {
      const sign = x < 0 ? -1 : 1;
      x = Math.abs(x);
      const a1 = 0.254829592;
      const a2 = -0.284496736;
      const a3 = 1.421413741;
      const a4 = -1.453152027;
      const a5 = 1.061405429;
      const p = 0.3275911;
      const t = 1 / (1 + p * x);
      const y = 1 - ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t * Math.exp(-x * x);
      return sign * y;
    }
    const sqrtLn2 = Math.sqrt(Math.log(2));
    function pGaussian(k) {
      return erf(k * sqrtLn2);
    }
    function pLorentz(k) {
      return 2 / Math.PI * Math.atan(k);
    }
    function pPseudoVoigt(k, mu) {
      return (1 - mu) * pLorentz(k) + mu * pGaussian(k);
    }

    class PseudoVoigt {
      fwhm;
      /**
       * Ratio of gaussian contribution in the shape
       * @default 0.5
       */
      mu;
      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, this.mu);
      }
      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'];
      }
      derivative(x) {
        const {
          fct,
          dx,
          dFwhm,
          dMu
        } = pseudoVoigtDerivative(x, this.fwhm, this.mu);
        return {
          fct,
          dx,
          parameters: [dFwhm, dMu]
        };
      }
    }
    const calculatePseudoVoigtHeight = (options = {}) => {
      const {
        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);
    };
    /**
     * Analytical value and partial derivatives of the pseudo-Voigt function centered at x=0.
     * @param x - position at which to evaluate.
     * @param fwhm - full width at half maximum.
     * @param mu - ratio of gaussian contribution in the shape.
     * @returns the value `fct` and its partial derivatives with respect to `x` (`dx`), `fwhm` (`dFwhm`) and `mu` (`dMu`).
     */
    function pseudoVoigtDerivative(x, fwhm, mu) {
      // gaussian and lorentzian derivative math is inlined (rather than calling
      // gaussianDerivative / lorentzianDerivative) to allocate a single object on
      // this hot path; the sub-calls would allocate three.
      const e = Math.exp(GAUSSIAN_EXP_FACTOR * (x / fwhm) ** 2);
      const denominator = 4 * x * x + fwhm * fwhm;
      const lorentz = fwhm * fwhm / denominator;
      const dEdt = 2 * GAUSSIAN_EXP_FACTOR * x / (fwhm * fwhm) * e;
      const dLdt = -8 * x * fwhm * fwhm / (denominator * denominator);
      const dEdfwhm = -2 * GAUSSIAN_EXP_FACTOR * x * x / (fwhm * fwhm * fwhm) * e;
      const dLdfwhm = 8 * fwhm * x * x / (denominator * denominator);
      return {
        fct: (1 - mu) * lorentz + mu * e,
        dx: (1 - mu) * dLdt + mu * dEdt,
        dFwhm: (1 - mu) * dLdfwhm + mu * dEdfwhm,
        dMu: e - lorentz
      };
    }
    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 pseudoVoigtFindFactor(area, mu);
    };
    const getPseudoVoigtData = (shape = {}, options = {}) => {
      const {
        fwhm = 500,
        mu = 0.5
      } = shape;
      const {
        factor = getPseudoVoigtFactor(0.999, mu)
      } = options;
      let {
        length,
        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), 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++) {
        const value = pseudoVoigtFct(i - center, fwhm, mu) * height;
        data[i] = value;
        data[length - 1 - i] = value;
      }
      return data;
    };

    /**
     * TCH-style pseudo-Voigt where gaussian and lorentzian widths are independent.
     * The effective fwhm and mixing parameter mu are derived from fwhmG and fwhmL
     * via the Thompson–Cox–Hastings approximation.
     */
    class PseudoVoigtTCH {
      _fwhmG;
      _fwhmL;
      _fwhm;
      _mu;
      _lorentzianWidthFraction;
      constructor(options = {}) {
        const {
          fwhmG,
          fwhmL,
          fwhm,
          mu = 0.5
        } = options;
        this._mu = mu;
        this._fwhm = 0;
        this._fwhmG = 0;
        this._fwhmL = 0;
        this._lorentzianWidthFraction = lorentzianWidthFraction(1 - mu);
        if (fwhmG && fwhmL) {
          this._fwhmG = fwhmG;
          this.fwhmL = fwhmL;
        } else if (fwhm) {
          this.fwhm = fwhm;
        }
      }
      set fwhmG(value) {
        const effectiveFwhm = computeEffectiveWidth(value, this._fwhmL);
        const lorentzianFraction = this._fwhmL / effectiveFwhm;
        this._fwhm = effectiveFwhm;
        this._mu = 1 - (1.36603 * lorentzianFraction - 0.47719 * lorentzianFraction * lorentzianFraction + 0.11116 * lorentzianFraction * lorentzianFraction * lorentzianFraction);
        this._fwhmG = value;
        this._lorentzianWidthFraction = lorentzianFraction;
      }
      get fwhmG() {
        return this._fwhmG;
      }
      set fwhmL(value) {
        const effectiveFwhm = computeEffectiveWidth(this._fwhmG, value);
        const lorentzianFraction = value / effectiveFwhm;
        this._fwhm = effectiveFwhm;
        this._mu = 1 - (1.36603 * lorentzianFraction - 0.47719 * lorentzianFraction * lorentzianFraction + 0.11116 * lorentzianFraction * lorentzianFraction * lorentzianFraction);
        this._fwhmL = value;
        this._lorentzianWidthFraction = lorentzianFraction;
      }
      get fwhmL() {
        return this._fwhmL;
      }
      set mu(value) {
        const lorentzianFraction = lorentzianWidthFraction(1 - value);
        this._lorentzianWidthFraction = lorentzianFraction;
        this._fwhmL = this._fwhm * lorentzianFraction;
        this._fwhmG = this._fwhm * (1 - lorentzianFraction);
        this._mu = value;
      }
      get mu() {
        return this._mu;
      }
      set fwhm(value) {
        const lorentzianFraction = this._lorentzianWidthFraction || lorentzianWidthFraction(1 - this._mu);
        this._fwhmL = value * lorentzianFraction;
        this._fwhmG = value * (1 - lorentzianFraction);
        this._fwhm = value;
      }
      get fwhm() {
        return this._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, this._mu);
      }
      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 ['fwhmG', 'fwhmL'];
      }
      derivative(x) {
        const {
          fct,
          dx,
          dFwhmG,
          dFwhmL
        } = pseudoVoigtTCHDerivative(x, this._fwhmG, this._fwhmL);
        return {
          fct,
          dx,
          parameters: [dFwhmG, dFwhmL]
        };
      }
    }
    /**
     * Analytical value and partial derivatives of the TCH pseudo-Voigt function centered at x=0.
     * The effective fwhm `F` and mixing `mu` are functions of `fwhmG` and `fwhmL`, so the
     * derivatives chain `∂fct/∂F` and `∂fct/∂mu` through `∂F/∂·` and `∂mu/∂·`.
     * @param x - position at which to evaluate.
     * @param fwhmG - full width at half maximum of the gaussian component.
     * @param fwhmL - full width at half maximum of the lorentzian component.
     * @returns the value `fct` and its partial derivatives with respect to `x` (`dx`), `fwhmG` (`dFwhmG`) and `fwhmL` (`dFwhmL`).
     */
    function pseudoVoigtTCHDerivative(x, fwhmG, fwhmL) {
      const effectiveFwhm = computeEffectiveWidth(fwhmG, fwhmL);
      const w = effectiveFwhm ** 5; // the polynomial under the 1/5 power
      // ∂w/∂fwhmG and ∂w/∂fwhmL (derivatives of the TCH width polynomial).
      const dwDfwhmG = 5 * fwhmG ** 4 + 10.77076 * fwhmG ** 3 * fwhmL + 7.28529 * fwhmG ** 2 * fwhmL ** 2 + 8.94326 * fwhmG * fwhmL ** 3 + 0.07842 * fwhmL ** 4;
      const dwDfwhmL = 2.69269 * fwhmG ** 4 + 4.85686 * fwhmG ** 3 * fwhmL + 13.41489 * fwhmG ** 2 * fwhmL ** 2 + 0.31368 * fwhmG * fwhmL ** 3 + 5 * fwhmL ** 4;
      // F = w^0.2  =>  ∂F/∂· = 0.2 * F / w * ∂w/∂·
      const dFwhmDfwhmG = 0.2 * effectiveFwhm * dwDfwhmG / w;
      const dFwhmDfwhmL = 0.2 * effectiveFwhm * dwDfwhmL / w;
      // lorentzian width fraction L = fwhmL / F
      const lorentzianFraction = fwhmL / effectiveFwhm;
      const dLorentzianFractionDfwhmG = -fwhmL / (effectiveFwhm * effectiveFwhm) * dFwhmDfwhmG;
      const dLorentzianFractionDfwhmL = 1 / effectiveFwhm - fwhmL / (effectiveFwhm * effectiveFwhm) * dFwhmDfwhmL;
      // mu = 1 - (1.36603 L - 0.47719 L^2 + 0.11116 L^3)
      const dPolyDfraction = 1.36603 - 0.95438 * lorentzianFraction + 0.33348 * lorentzianFraction * lorentzianFraction;
      const dMuDfwhmG = -dPolyDfraction * dLorentzianFractionDfwhmG;
      const dMuDfwhmL = -dPolyDfraction * dLorentzianFractionDfwhmL;
      const mu = 1 - (1.36603 * lorentzianFraction - 0.47719 * lorentzianFraction * lorentzianFraction + 0.11116 * lorentzianFraction * lorentzianFraction * lorentzianFraction);
      // pseudoVoigt value and its ∂/∂x, ∂/∂F (dFwhm), ∂/∂mu (dMu) at the effective
      // fwhm, inlined to allocate a single object on this hot path.
      const e = Math.exp(GAUSSIAN_EXP_FACTOR * (x / effectiveFwhm) ** 2);
      const denominator2 = 4 * x * x + effectiveFwhm * effectiveFwhm;
      const lorentz = effectiveFwhm * effectiveFwhm / denominator2;
      const dEdt = 2 * GAUSSIAN_EXP_FACTOR * x / (effectiveFwhm * effectiveFwhm) * e;
      const dLdt = -8 * x * effectiveFwhm * effectiveFwhm / (denominator2 * denominator2);
      const dEdfwhm = -2 * GAUSSIAN_EXP_FACTOR * x * x / (effectiveFwhm * effectiveFwhm * effectiveFwhm) * e;
      const dLdfwhm = 8 * effectiveFwhm * x * x / (denominator2 * denominator2);
      const dFwhm = (1 - mu) * dLdfwhm + mu * dEdfwhm;
      const dMu = e - lorentz;
      return {
        fct: (1 - mu) * lorentz + mu * e,
        dx: (1 - mu) * dLdt + mu * dEdt,
        dFwhmG: dFwhm * dFwhmDfwhmG + dMu * dMuDfwhmG,
        dFwhmL: dFwhm * dFwhmDfwhmL + dMu * dMuDfwhmL
      };
    }
    /**
     * Compute the effective FWHM from gaussian and lorentzian component widths
     * using the Thompson–Cox–Hastings approximation.
     * @param fwhmG - gaussian component FWHM.
     * @param fwhmL - lorentzian component FWHM.
     * @returns effective combined FWHM.
     */
    function computeEffectiveWidth(fwhmG, fwhmL) {
      return (fwhmG ** 5 + 2.69269 * fwhmG ** 4 * fwhmL + 2.42843 * fwhmG ** 3 * fwhmL ** 2 + 4.47163 * fwhmG ** 2 * fwhmL ** 3 + 0.07842 * fwhmG * fwhmL ** 4 + fwhmL ** 5) ** 0.2;
    }
    /**
     * Solve for the lorentzian width fraction fwhmL/fwhm given lorentzianFraction = 1 - mu,
     * using Newton's method on: 1.36603·x - 0.47719·x² + 0.11116·x³ = lorentzianFraction.
     * @param lorentzianFraction - TCH lorentzian mixing parameter (= 1 - mu).
     * @returns the lorentzian width fraction fwhmL/fwhm.
     */
    function lorentzianWidthFraction(lorentzianFraction) {
      let fraction = lorentzianFraction;
      for (let i = 0; i < 6; i++) {
        const f = 1.36603 * fraction - 0.47719 * fraction * fraction + 0.11116 * fraction * fraction * fraction - lorentzianFraction;
        const df = 1.36603 - 2 * 0.47719 * fraction + 3 * 0.11116 * fraction * fraction;
        fraction -= f / df;
      }
      return fraction;
    }

    /**
     * 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 https://www.ebyte.it/stan/Talk_ML_UserMeeting_SMASH_2010_GeneralizedLorentzian.html}
     */
    class GeneralizedLorentzian {
      /**
       * Full width at half maximum.
       * @default 500
       */
      fwhm;
      /**
       * kurtosis parameter of the shape, between -1 to 2
       * @default 1
       */
      gamma;
      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'];
      }
      derivative(x) {
        const {
          fct,
          dx,
          dFwhm,
          dGamma
        } = generalizedLorentzianDerivative(x, this.fwhm, this.gamma);
        return {
          fct,
          dx,
          parameters: [dFwhm, dGamma]
        };
      }
    }
    const calculateGeneralizedLorentzianHeight = ({
      fwhm = 1,
      gamma = 1,
      area = 1
    }) => {
      return area / fwhm / (3.14159 - 0.420894 * gamma) * 2;
    };
    /**
     * Calculate the area under a generalized Lorentzian peak (integral from Mathematica).
     * @param options - shape parameters including fwhm, height, and gamma.
     * @returns the area under the peak.
     */
    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);
    };
    /**
     * Analytical value and partial derivatives of the generalized lorentzian function centered at x=0.
     * @param x - position at which to evaluate.
     * @param fwhm - full width at half maximum.
     * @param gamma - kurtosis parameter of the shape.
     * @returns the value `fct` and its partial derivatives with respect to `x` (`dx`), `fwhm` (`dFwhm`) and `gamma` (`dGamma`).
     */
    function generalizedLorentzianDerivative(x, fwhm, gamma) {
      const u = (2 * x / fwhm) ** 2;
      const lorentzian = 1 / (1 + u); // A
      const rational = (1 + u / 2) / (1 + u + u * u); // B
      const fct = (1 - gamma) * lorentzian + gamma * rational;
      // dA/du and dB/du
      const dLorentzianDu = -1 / ((1 + u) * (1 + u));
      const denominator = 1 + u + u * u;
      const dRationalDu = -(0.5 + 2 * u + 0.5 * u * u) / (denominator * denominator);
      const dFctDu = (1 - gamma) * dLorentzianDu + gamma * dRationalDu;
      const duDx = 8 * x / (fwhm * fwhm);
      const duDfwhm = -8 * x * x / (fwhm * fwhm * fwhm);
      const dx = dFctDu * duDx;
      const dFwhm = dFctDu * duDfwhm;
      const dGamma = rational - lorentzian; // B - A
      return {
        fct,
        dx,
        dFwhm,
        dGamma
      };
    }
    const generalizedLorentzianWidthToFWHM = width => {
      return width * ROOT_THREE;
    };
    const generalizedLorentzianFwhmToWidth = fwhm => {
      return fwhm / ROOT_THREE;
    };
    const generalizedLorentzianQuantile = p => Math.tan(Math.PI * (p - 0.5));
    const getGeneralizedLorentzianFactor = (area = 0.9999) => {
      if (area >= 1) {
        throw new Error('area should be (0 - 1)');
      }
      const halfResidual = (1 - area) * 0.5;
      return (generalizedLorentzianQuantile(1 - halfResidual) - generalizedLorentzianQuantile(halfResidual)) / 2;
    };
    const getGeneralizedLorentzianData = (shape = {}, options = {}) => {
      const {
        fwhm = 500,
        gamma = 1
      } = shape;
      const {
        factor = getGeneralizedLorentzianFactor(),
        height = calculateGeneralizedLorentzianHeight({
          fwhm,
          area: 1,
          gamma
        })
      } = options;
      let {
        length
      } = options;
      if (!length) {
        length = Math.min(Math.ceil(fwhm * factor), 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++) {
        const value = generalizedLorentzianFct(i - center, fwhm, gamma) * height;
        data[i] = value;
        data[length - 1 - i] = value;
      }
      return data;
    };

    class Gaussian2D {
      fwhmX;
      fwhmY;
      constructor(options = {}) {
        const {
          sd
        } = options;
        let {
          fwhm = 20
        } = options;
        fwhm = ensureFWHM2D(fwhm, sd);
        this.fwhmX = fwhm.x;
        this.fwhmY = fwhm.y;
      }
      fct(x, y) {
        return gaussian2DFct(x, y, this.fwhmX, this.fwhmY);
      }
      getData(options = {}) {
        return getGaussian2DData({
          fwhm: {
            x: this.fwhmX,
            y: this.fwhmY
          }
        }, options);
      }
      getFactor(volume = 1) {
        return getGaussianFactor(volume);
      }
      getVolume(height = calculateGaussian2DHeight({
        fwhm: {
          x: this.fwhmX,
          y: this.fwhmY
        },
        volume: 1
      })) {
        return getGaussian2DVolume({
          fwhm: {
            x: this.fwhmX,
            y: this.fwhmY
          },
          height
        });
      }
      widthToFWHM(width) {
        return gaussianWidthToFWHM(width);
      }
      fwhmToWidth(fwhm) {
        return gaussianFwhmToWidth(fwhm);
      }
      calculateHeight(volume = 1) {
        return calculateGaussian2DHeight({
          volume,
          fwhm: {
            x: this.fwhmX,
            y: this.fwhmY
          }
        });
      }
      set fwhm(fwhm) {
        fwhm = ensureXYNumber$1(fwhm);
        this.fwhmX = fwhm.x;
        this.fwhmY = fwhm.y;
      }
    }
    const gaussian2DFct = (x, y, xFWHM, yFWHM) => {
      return Math.exp(GAUSSIAN_EXP_FACTOR * ((x / xFWHM) ** 2 + (y / yFWHM) ** 2));
    };
    const getGaussian2DData = (shape, options = {}) => {
      const {
        sd
      } = shape;
      let {
        fwhm = 50
      } = shape;
      fwhm = ensureFWHM2D(fwhm, sd);
      const {
        height = calculateGaussian2DHeight({
          fwhm,
          volume: 1
        })
      } = options;
      let {
        factor = getGaussianFactor(),
        length = {
          x: 0,
          y: 0
        }
      } = options;
      factor = ensureXYNumber$1(factor);
      length = ensureXYNumber$1(length);
      for (const axis of ['x', 'y']) {
        if (!length[axis]) {
          length[axis] = Math.min(Math.ceil(fwhm[axis] * factor[axis]), 2 ** 25 - 1);
          if (length[axis] % 2 === 0) length[axis]++;
        }
      }
      const xCenter = (length.x - 1) / 2;
      const yCenter = (length.y - 1) / 2;
      const data = new Array(length.x);
      for (let i = 0; i < length.x; i++) {
        data[i] = new Float64Array(length.y);
      }
      for (let i = 0; i < length.x; i++) {
        for (let j = 0; j < length.y; j++) {
          data[i][j] = gaussian2DFct(i - xCenter, j - yCenter, fwhm.x, fwhm.y) * height;
        }
      }
      return data;
    };
    const calculateGaussian2DHeight = (options = {}) => {
      const {
        sd,
        volume = 1
      } = options;
      let {
        fwhm = 50
      } = options;
      fwhm = ensureFWHM2D(fwhm, sd);
      return volume * Math.LN2 * 4 / (Math.PI * fwhm.y * fwhm.x);
    };
    const getGaussian2DVolume = (options = {}) => {
      const {
        sd,
        height = 1
      } = options;
      let {
        fwhm = 50
      } = options;
      fwhm = ensureFWHM2D(fwhm, sd);
      return height * Math.PI * fwhm.y * fwhm.x / Math.LN2 / 4;
    };
    function ensureXYNumber$1(input) {
      return typeof input !== 'object' ? {
        x: input,
        y: input
      } : {
        ...input
      };
    }
    function ensureFWHM2D(fwhm, sd) {
      if (sd !== undefined) {
        const sdObject = ensureXYNumber$1(sd);
        return {
          x: gaussianWidthToFWHM(2 * sdObject.x),
          y: gaussianWidthToFWHM(2 * sdObject.y)
        };
      } else if (fwhm !== undefined) {
        return ensureXYNumber$1(fwhm);
      } else {
        throw new Error('ensureFWHM2D must have either fwhm or sd defined');
      }
    }

    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 'pseudoVoigtTCH':
          return new PseudoVoigtTCH(shape);
        case 'lorentzianDispersive':
          return new LorentzianDispersive(shape);
        case 'generalizedLorentzian':
          return new GeneralizedLorentzian(shape);
        default:
          throw new Error(`Unknown distribution ${kind}`);
      }
    }

    function getShape2D(shape) {
      const {
        kind
      } = shape;
      switch (kind) {
        case 'gaussian':
          return new Gaussian2D(shape);
        default:
          throw new Error(`Unknown distribution ${kind}`);
      }
    }

    class BaseSpectrumGenerator {
      from;
      to;
      nbPoints;
      interval;
      peakWidthFct;
      shape;
      constructor(options = {}) {
        const {
          from = 0,
          to = 1000,
          nbPoints = 10001,
          peakWidthFct,
          shape = {
            kind: 'gaussian',
            fwhm: 5
          }
        } = options;
        assertNumber$1(from, 'from');
        assertNumber$1(to, 'to');
        assertInteger$1(nbPoints, 'nbPoints');
        if (to <= from) {
          throw new RangeError('to option must be larger than from');
        }
        if (peakWidthFct && typeof peakWidthFct !== 'function') {
          throw new TypeError('peakWidthFct option must be a function');
        }
        this.from = from;
        this.to = to;
        this.nbPoints = nbPoints;
        this.interval = (to - from) / (nbPoints - 1);
        this.peakWidthFct = peakWidthFct;
        this.shape = getShape1D(shape);
      }
      /**
       * Resolves a peak to its canonical parameters, cloning and configuring
       * separate shape instances for the left and right halves.
       */
      resolvePeak(peak, options) {
        let x0;
        let intensity;
        let peakFWHM;
        let peakWidth;
        let peakShapeOptions;
        if (Array.isArray(peak)) {
          [x0, intensity, peakFWHM, peakShapeOptions] = peak;
        } else {
          x0 = peak.x;
          intensity = peak.y;
          peakWidth = peak.width;
          peakShapeOptions = peak.shape;
        }
        let {
          shape: shapeOptions
        } = options;
        if (peakShapeOptions) {
          shapeOptions = shapeOptions ? {
            ...shapeOptions,
            ...peakShapeOptions
          } : peakShapeOptions;
        }
        const shape = shapeOptions ? getShape1D(shapeOptions) : cloneShape(this.shape);
        const {
          width
        } = options;
        let {
          widthLeft,
          widthRight
        } = options;
        const fwhm = peakFWHM !== undefined ? peakFWHM : peakWidth ? shape.widthToFWHM(peakWidth) : this.peakWidthFct ? this.peakWidthFct(x0) : width !== undefined ? width : shape.fwhm;
        if (!widthLeft) widthLeft = fwhm;
        if (!widthRight) widthRight = fwhm;
        if (!widthLeft || !widthRight) {
          throw new Error('Width left or right is undefined or zero');
        }
        const factor = options.factor === undefined ? shape.getFactor() : options.factor;
        const shapeLeft = cloneShape(shape);
        shapeLeft.fwhm = widthLeft;
        const shapeRight = cloneShape(shape);
        shapeRight.fwhm = widthRight;
        return {
          x0,
          intensity,
          shapeLeft,
          shapeRight,
          widthLeft,
          widthRight,
          factor
        };
      }
    }
    function cloneShape(shape) {
      return Object.assign(Object.create(Object.getPrototypeOf(shape)), shape);
    }
    function assertInteger$1(value, name) {
      if (!Number.isInteger(value)) {
        throw new TypeError(`${name} option must be an integer`);
      }
    }
    function assertNumber$1(value, name) {
      if (!Number.isFinite(value)) {
        throw new TypeError(`${name} option must be a number`);
      }
    }

    /**
     * Adds a baseline to the spectrum data.
     * @param data - The spectrum data to modify.
     * @param baselineFct - Function that generates the baseline value for a given x.
     * @returns The modified spectrum data.
     */
    function addBaseline(data, baselineFct) {
      if (!baselineFct) return data;
      const xs = data.x;
      const ys = data.y;
      for (let i = 0; i < xs.length; i++) {
        ys[i] += baselineFct(xs[i]);
      }
      return data;
    }

    /**
     * Adds noise to the spectrum data.
     * @param data - The spectrum data to modify.
     * @param options - Configuration for noise generation.
     * @returns The modified spectrum data.
     */
    function addNoise(data, options = {}) {
      const {
        seed = 0,
        distribution = 'normal',
        percent = 1
      } = options;
      const range = xMaxValue(data.y) * percent / 100;
      const noise = createRandomArray({
        distribution,
        seed,
        mean: 0,
        standardDeviation: range,
        range,
        length: data.x.length
      });
      data.y = xAdd(data.y, noise);
      return data;
    }

    class SpectrumGenerator extends BaseSpectrumGenerator {
      maxPeakHeight;
      data;
      constructor(options = {}) {
        super(options);
        this.maxPeakHeight = Number.MIN_SAFE_INTEGER;
        this.data = {
          x: new Float64Array(this.nbPoints),
          y: new Float64Array(this.nbPoints)
        };
        this.reset();
      }
      /**
       * Generates a spectrum from a list of peaks and returns it.
       * @param peaks - List of peaks to add.
       * @param options - Options for spectrum generation.
       * @returns The generated spectrum data.
       */
      generateSpectrum(peaks, options = {}) {
        const {
          noise,
          baseline,
          threshold,
          peakOptions
        } = options;
        this.addPeaks(peaks, peakOptions);
        if (baseline) this.addBaseline(baseline);
        if (noise) this.addNoise(noise);
        return this.getSpectrum({
          threshold
        });
      }
      /**
       * Add a series of peaks to the spectrum.
       * @param peaks - Peaks to add.
       * @param options - Options for adding peaks.
       * @returns The generator instance.
       */
      addPeaks(peaks, options) {
        if (!Array.isArray(peaks) && (typeof peaks !== 'object' || peaks.x === undefined || peaks.y === undefined || !Array.isArray(peaks.x) || !Array.isArray(peaks.y) || peaks.x.length !== peaks.y.length)) {
          throw new TypeError('peaks must be an array or an object containing x[] and y[]');
        }
        if (Array.isArray(peaks)) {
          for (const peak of peaks) {
            this.addPeak(peak, options);
          }
        } else {
          for (let i = 0; i < peaks.x.length; i++) {
            this.addPeak([peaks.x[i], peaks.y[i]], options);
          }
        }
        return this;
      }
      /**
       * Add a single peak to the spectrum.
       * A peak may be either defined as [x,y,fwhm,...] or as {x, y, shape}
       * @param peak - The peak to add, defined as array or object.
       * @param options - Options for adding the peak.
       * @returns The generator instance.
       */
      addPeak(peak, options = {}) {
        if (Array.isArray(peak) && peak.length < 2) {
          throw new Error('peak must be an array with two (or three) values or an object with {x,y,width?}');
        }
        if (!Array.isArray(peak) && (peak.x === undefined || peak.y === undefined)) {
          throw new Error('peak must be an array with two (or three) values or an object with {x,y,width?}');
        }
        const {
          x0,
          intensity,
          shapeLeft,
          shapeRight,
          widthLeft,
          widthRight,
          factor
        } = this.resolvePeak(peak, options);
        if (intensity > this.maxPeakHeight) this.maxPeakHeight = intensity;
        const firstValue = x0 - widthLeft / 2 * factor;
        const lastValue = x0 + widthRight / 2 * factor;
        const firstPoint = Math.max(0, Math.floor((firstValue - this.from) / this.interval));
        const lastPoint = Math.min(this.nbPoints - 1, Math.ceil((lastValue - this.from) / this.interval));
        const middlePoint = Math.round((x0 - this.from) / this.interval);
        // PEAK SHAPE MAY BE ASYMMETRIC (widthLeft and widthRight) !
        // we calculate the left part of the shape
        for (let index = firstPoint; index < Math.max(middlePoint, 0); index++) {
          this.data.y[index] += intensity * shapeLeft.fct(this.data.x[index] - x0);
        }
        // we calculate the right part of the gaussian
        for (let index = Math.min(middlePoint, lastPoint); index <= lastPoint; index++) {
          this.data.y[index] += intensity * shapeRight.fct(this.data.x[index] - x0);
        }
        return this;
      }
      /**
       * Add a baseline to the spectrum.
       * @param baselineFct - Mathematical function producing the baseline you want.
       * @returns The generator instance.
       */
      addBaseline(baselineFct) {
        addBaseline(this.data, baselineFct);
        return this;
      }
      /**
       * Add noise to the spectrum.
       * @param options - Configuration for noise generation.
       * @returns The generator instance.
       */
      addNoise(options) {
        addNoise(this.data, options);
        return this;
      }
      /**
       * Get the generated spectrum.
       * @param options - Options for getting the spectrum.
       * @returns The generated spectrum data.
       */
      getSpectrum(options = {}) {
        if (typeof options === 'boolean') {
          options = {
            copy: options
          };
        }
        const {
          copy = true,
          threshold = 0
        } = options;
        if (threshold) {
          const minPeakHeight = this.maxPeakHeight * threshold;
          const x = [];
          const y = [];
          for (let i = 0; i < this.data.x.length; i++) {
            if (this.data.y[i] >= minPeakHeight) {
              x.push(this.data.x[i]);
              y.push(this.data.y[i]);
            }
          }
          return {
            x: Float64Array.from(x),
            y: Float64Array.from(y)
          };
        }
        if (copy) {
          return {
            x: this.data.x.slice(),
            y: this.data.y.slice()
          };
        } else {
          return this.data;
        }
      }
      /**
       * Resets the generator with an empty spectrum.
       * @returns The generator instance.
       */
      reset() {
        if (this.nbPoints === 1) {
          this.data.x[0] = (this.from + this.to) / 2;
        } else {
          for (let i = 0; i < this.nbPoints; i++) {
            this.data.x[i] = this.from + i * this.interval;
          }
        }
        return this;
      }
    }

    const axis2D = ['x', 'y'];
    const peakCoordinates = ['x', 'y', 'z'];
    /**
     * Converts a width value to full width at half maximum (FWHM).
     * @param shape - The 2D shape to use for conversion.
     * @param width - The width value to convert.
     * @returns The converted FWHM value as an XYNumber object.
     */
    const convertWidthToFWHM = (shape, width) => {
      const widthData = ensureXYNumber(width);
      for (const key of axis2D) {
        widthData[key] = shape.widthToFWHM(widthData[key]);
      }
      return widthData;
    };
    class Spectrum2DGenerator {
      from;
      to;
      nbPoints;
      interval;
      data;
      maxPeakHeight;
      shape;
      peakWidthFct;
      constructor(options = {}) {
        const {
          peakWidthFct = () => 5,
          shape = {
            kind: 'gaussian'
          }
        } = options;
        let {
          from = 0,
          to = 100,
          nbPoints = 1001
        } = options;
        from = ensureXYNumber(from);
        to = ensureXYNumber(to);
        nbPoints = ensureXYNumber(nbPoints);
        for (const axis of axis2D) {
          assertNumber(from[axis], `from-${axis}`);
          assertNumber(to[axis], `to-${axis}`);
          assertInteger(nbPoints[axis], `nbPoints-${axis}`);
        }
        this.from = from;
        this.to = to;
        this.nbPoints = nbPoints;
        this.interval = calculeIntervals(from, to, nbPoints);
        this.peakWidthFct = peakWidthFct;
        this.maxPeakHeight = Number.MIN_SAFE_INTEGER;
        const shapeGenerator = getShape2D(shape);
        this.shape = shapeGenerator;
        this.data = {
          x: new Float64Array(nbPoints.x),
          y: new Float64Array(nbPoints.y),
          z: matrixCreateEmpty({
            nbRows: this.nbPoints.y,
            nbColumns: this.nbPoints.x
          })
        };
        for (const axis of axis2D) {
          if (this.to[axis] <= this.from[axis]) {
            throw new RangeError('to option must be larger than from');
          }
        }
        if (typeof this.peakWidthFct !== 'function') {
          throw new TypeError('peakWidthFct option must be a function');
        }
        this.reset();
      }
      /**
       * Adds multiple peaks to the 2D spectrum.
       * @param peaks - Array of peaks or peak series to add.
       * @param options - Options for adding peaks.
       * @returns The generator instance.
       */
      addPeaks(peaks, options) {
        if (!Array.isArray(peaks) && (typeof peaks !== 'object' || peaks.x === undefined || peaks.y === undefined || !Array.isArray(peaks.x) || !Array.isArray(peaks.y) || peaks.x.length !== peaks.y.length)) {
          throw new TypeError('peaks must be an array or an object containing x[] and y[]');
        }
        if (Array.isArray(peaks)) {
          for (const peak of peaks) {
            this.addPeak(peak, options);
          }
        } else {
          if (!Array.isArray(peaks.x) || !Array.isArray(peaks.y) || !Array.isArray(peaks.z)) {
            throw new TypeError('x, y, z must all be arrays');
          }
          const nbPeaks = peaks.x.length;
          if (peaks.y.length !== nbPeaks || peaks.z.length !== nbPeaks) {
            throw new Error('x, y, z should have the same length');
          }
          for (let i = 0; i < nbPeaks; i++) {
            this.addPeak([peaks.x[i], peaks.y[i], peaks.z[i]], options);
          }
        }
        return this;
      }
      /**
       * Adds a single peak to the 2D spectrum.
       * @param peak - Peak to add, can be array or object format.
       * @param options - Options for adding the peak.
       * @returns The generator instance.
       */
      addPeak(peak, options = {}) {
        if (Array.isArray(peak) && peak.length < 3) {
          throw new Error('peak must be an array with three (or four) values or an object with {x,y,z,width?}');
        }
        if (!Array.isArray(peak) && peakCoordinates.some(e => peak[e] === undefined)) {
          throw new Error('peak must be an array with three (or four) values or an object with {x,y,z,width?}');
        }
        let xPosition;
        let yPosition;
        let intensity;
        let peakFWHM;
        let peakWidth;
        let peakShapeOptions;
        if (Array.isArray(peak)) {
          [xPosition, yPosition, intensity, peakFWHM, peakShapeOptions] = peak;
        } else {
          xPosition = peak.x;
          yPosition = peak.y;
          intensity = peak.z;
          peakFWHM = peak.fwhm;
          peakWidth = peak.width;
          peakShapeOptions = peak.shape;
        }
        const position = {
          x: xPosition,
          y: yPosition
        };
        if (intensity > this.maxPeakHeight) this.maxPeakHeight = intensity;
        const {
          width
        } = options;
        let {
          shape: shapeOptions
        } = options;
        if (peakShapeOptions) {
          shapeOptions = shapeOptions ? {
            ...shapeOptions,
            ...peakShapeOptions
          } : peakShapeOptions;
        }
        const shape = shapeOptions ? getShape2D(shapeOptions) : Object.assign(Object.create(Object.getPrototypeOf(this.shape)), structuredClone(this.shape));
        let {
          fwhm = peakFWHM !== undefined ? peakFWHM : peakWidth ? convertWidthToFWHM(shape, peakWidth) : width ? convertWidthToFWHM(shape, width) : this.peakWidthFct(xPosition, yPosition)
        } = options;
        fwhm = ensureXYNumber(fwhm);
        let factor = options.factor === undefined ? shape.getFactor() : options.factor;
        factor = ensureXYNumber(factor);
        const firstPoint = {
          x: 0,
          y: 0
        };
        const lastPoint = {
          x: 0,
          y: 0
        };
        for (const axis of axis2D) {
          const first = position[axis] - fwhm[axis] / 2 * factor[axis];
          const last = position[axis] + fwhm[axis] / 2 * factor[axis];
          firstPoint[axis] = Math.max(0, Math.floor((first - this.from[axis]) / this.interval[axis]));
          lastPoint[axis] = Math.min(this.nbPoints[axis], Math.ceil((last - this.from[axis]) / this.interval[axis]));
        }
        shape.fwhm = fwhm;
        for (let xIndex = firstPoint.x; xIndex < lastPoint.x; xIndex++) {
          for (let yIndex = firstPoint.y; yIndex < lastPoint.y; yIndex++) {
            const value = intensity * shape.fct(this.data.x[xIndex] - position.x, this.data.y[yIndex] - position.y);
            if (Math.abs(value) > 1e-6) {
              this.data.z[yIndex][xIndex] += value;
            }
          }
        }
        return this;
      }
      /**
       * Gets the generated 2D spectrum data.
       * @param options - Options for getting the spectrum.
       * @returns The spectrum data object.
       */
      getSpectrum(options = {}) {
        if (typeof options === 'boolean') {
          options = {
            copy: options
          };
        }
        const {
          copy = true
        } = options;
        const minMaxZ = matrixMinMaxZ(this.data.z);
        return {
          minX: this.from.x,
          maxX: this.to.x,
          maxY: this.to.y,
          minY: this.from.y,
          minZ: minMaxZ.min,
          maxZ: minMaxZ.max,
          z: copy ? this.data.z.slice() : this.data.z
        };
      }
      /**
       * Resets the generator to initial state.
       * @returns The generator instance.
       */
      reset() {
        const spectrum = this.data;
        for (const axis of axis2D) {
          for (let i = 0; i < this.nbPoints[axis]; i++) {
            spectrum[axis][i] = this.from[axis] + i * this.interval[axis];
          }
        }
        for (const row of spectrum.z) {
          for (let j = 0; j < row.length; j++) {
            row[j] = 0;
          }
        }
        return this;
      }
    }
    /**
     * Generates a 2D spectrum with the given peaks.
     * @param peaks - Peaks to include in the spectrum.
     * @param options - Options for spectrum generation.
     * @returns The generated spectrum data.
     */
    function generateSpectrum2D(peaks, options = {}) {
      const {
        generator: generatorOptions,
        peaks: addPeaksOptions
      } = options;
      const generator = new Spectrum2DGenerator(generatorOptions);
      generator.addPeaks(peaks, addPeaksOptions);
      return generator.getSpectrum();
    }
    /**
     * Ensures the input is an XYNumber object.
     * @param input - Number or XYNumber to process.
     * @returns An XYNumber object.
     */
    function ensureXYNumber(input) {
      return typeof input !== 'object' ? {
        x: input,
        y: input
      } : {
        ...input
      };
    }
    /**
     * Calculates the intervals between points for both x and y axes.
     * @param from - Starting point coordinates.
     * @param to - Ending point coordinates.
     * @param nbPoints - Number of points in each dimension.
     * @returns The calculated intervals as an XYNumber object.
     */
    function calculeIntervals(from, to, nbPoints) {
      return {
        x: (to.x - from.x) / (nbPoints.x - 1),
        y: (to.y - from.y) / (nbPoints.y - 1)
      };
    }
    /**
     * Validates if a value is an integer.
     * @param value - Number to validate.
     * @param name - Name of the parameter for error message.
     * @throws {TypeError} If value is not an integer.
     */
    function assertInteger(value, name) {
      if (!Number.isInteger(value)) {
        throw new TypeError(`${name} option must be an integer`);
      }
    }
    /**
     * Validates if a value is a finite number.
     * @param value - Number to validate.
     * @param name - Name of the parameter for error message.
     * @throws {TypeError} If value is not a finite number.
     */
    function assertNumber(value, name) {
      if (!Number.isFinite(value)) {
        throw new TypeError(`${name} option must be a number`);
      }
    }

    /**
     * Generates a spectrum and returns it.
     * @param peaks - List of peaks to put in the spectrum.
     * @param options - Configuration for spectrum generation.
     * @returns The generated spectrum data.
     */
    function generateSpectrum(peaks, options = {}) {
      const {
        generator,
        ...rest
      } = options;
      return new SpectrumGenerator(generator).generateSpectrum(peaks, rest);
    }

    class SparseSpectrumGenerator extends BaseSpectrumGenerator {
      constructor(options = {}) {
        super(options);
      }
      /**
       * Generates a sparse spectrum containing only x values where at least one
       * peak contributes above the threshold. No dense array is allocated:
       * intervals are computed per peak first, then y is evaluated only for the
       * merged set of required x values.
       * @param peaks - List of peaks to include.
       * @param options - Options for sparse spectrum generation.
       * @returns Sparse spectrum data with x and y arrays covering only peak regions.
       */
      generateSparseSpectrum(peaks, options = {}) {
        if ('noise' in options) {
          throw new Error('noise is not supported for sparse spectrum generation');
        }
        const {
          baseline,
          peakOptions = {},
          threshold = 1e-7
        } = options;
        const peakArray = Array.isArray(peaks) ? peaks : Array.from(peaks.x, (x, i) => peaks.fwhm !== undefined ? [x, peaks.y[i], peaks.fwhm[i]] : [x, peaks.y[i]]);
        const resolvedPeaks = peakArray.map(peak => {
          const {
            x0,
            intensity,
            shapeLeft,
            shapeRight
          } = this.resolvePeak(peak, peakOptions);
          return {
            x0,
            intensity,
            shapeLeft,
            shapeRight,
            xFrom: x0 - computeHalfWidth(shapeLeft, threshold),
            xTo: x0 + computeHalfWidth(shapeRight, threshold)
          };
        }).filter(p => p.xFrom < this.to && p.xTo > this.from);
        if (resolvedPeaks.length === 0) {
          return {
            x: Float64Array.from([this.from, this.to]),
            y: baseline ? Float64Array.from([baseline(this.from), baseline(this.to)]) : new Float64Array(2)
          };
        }
        for (const peak of resolvedPeaks) {
          peak.xFrom = Math.max(this.from, peak.xFrom);
          peak.xTo = Math.min(this.to, peak.xTo);
        }
        resolvedPeaks.sort((a, b) => a.xFrom - b.xFrom);
        const mergedIntervals = [];
        for (const peak of resolvedPeaks) {
          const last = mergedIntervals.at(-1);
          if (last !== undefined && peak.xFrom <= last.xTo) {
            last.xTo = Math.max(last.xTo, peak.xTo);
            last.peaks.push(peak);
          } else {
            mergedIntervals.push({
              xFrom: peak.xFrom,
              xTo: peak.xTo,
              peaks: [peak]
            });
          }
        }
        const xResult = [this.from];
        const yResult = [0];
        // tracks the last grid index written to avoid duplicate guard points when
        // two intervals are only one step apart
        let lastWrittenIndex = 0;
        for (const interval of mergedIntervals) {
          const firstIndex = Math.max(0, Math.floor((interval.xFrom - this.from) / this.interval));
          const lastIndex = Math.min(this.nbPoints - 1, Math.ceil((interval.xTo - this.from) / this.interval));
          const guardBefore = firstIndex - 1;
          if (guardBefore >= 0 && guardBefore > lastWrittenIndex) {
            xResult.push(this.from + guardBefore * this.interval);
            yResult.push(0);
            lastWrittenIndex = guardBefore;
          }
          for (let i = firstIndex; i <= lastIndex; i++) {
            const x = this.from + i * this.interval;
            let y = 0;
            for (const peak of interval.peaks) {
              const shape = x < peak.x0 ? peak.shapeLeft : peak.shapeRight;
              y += peak.intensity * shape.fct(x - peak.x0);
            }
            xResult.push(x);
            yResult.push(y);
            lastWrittenIndex = i;
          }
          const guardAfter = lastIndex + 1;
          if (guardAfter < this.nbPoints) {
            xResult.push(this.from + guardAfter * this.interval);
            yResult.push(0);
            lastWrittenIndex = guardAfter;
          }
        }
        if (xResult[0] !== this.from) {
          xResult.unshift(this.from);
          yResult.unshift(0);
        }
        if (xResult[xResult.length - 1] !== this.to) {
          xResult.push(this.to);
          yResult.push(0);
        }
        if (baseline) {
          for (let i = 0; i < yResult.length; i++) {
            yResult[i] += baseline(xResult[i]);
          }
        }
        return {
          x: Float64Array.from(xResult),
          y: Float64Array.from(yResult)
        };
      }
    }
    /**
     * Finds the half-width d at which shape.fct(d) equals the given threshold via
     * binary search. Works for any monotonically decreasing shape function.
     */
    function computeHalfWidth(shape, threshold) {
      if (threshold <= 0) return Infinity;
      if (shape.fct(0) <= threshold) return 0;
      let high = shape.fwhm;
      while (shape.fct(high) > threshold) {
        high *= 2;
      }
      let low = 0;
      for (let i = 0; i < 64; i++) {
        const mid = (low + high) / 2;
        if (shape.fct(mid) > threshold) {
          low = mid;
        } else {
          high = mid;
        }
      }
      return (low + high) / 2;
    }

    /**
     * Generates a sparse spectrum containing only x values where at least one peak
     * contributes above the threshold. Unlike `generateSpectrum`, no dense array
     * is allocated: intervals are computed per peak first, then y is evaluated only
     * for the merged set of required x values.
     * @param peaks - List of peaks to include.
     * @param options - Configuration for sparse spectrum generation.
     * @returns Sparse spectrum data with x and y arrays covering only peak regions.
     */
    function generateSparseSpectrum(peaks, options = {}) {
      const {
        generator,
        ...rest
      } = options;
      return new SparseSpectrumGenerator(generator).generateSparseSpectrum(peaks, rest);
    }

    var lib$1 = /*#__PURE__*/Object.freeze({
        __proto__: null,
        BaseSpectrumGenerator: BaseSpectrumGenerator,
        SparseSpectrumGenerator: SparseSpectrumGenerator,
        Spectrum2DGenerator: Spectrum2DGenerator,
        SpectrumGenerator: SpectrumGenerator,
        generateSparseSpectrum: generateSparseSpectrum,
        generateSpectrum: generateSpectrum,
        generateSpectrum2D: generateSpectrum2D
    });

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

    var Distribution$1 = {};

    var closestPointX$1 = {};

    Object.defineProperty(closestPointX$1, "__esModule", {
      value: true
    });
    closestPointX$1.closestPointX = closestPointX;
    function closestPointX(array, target) {
      let low = 0;
      let high = array.length - 1;
      let middle = 0;
      while (high - low > 1) {
        middle = low + (high - low >> 1);
        if (array[middle].x < target) {
          low = middle;
        } else if (array[middle].x > target) {
          high = middle;
        } else {
          return array[middle];
        }
      }
      if (low < array.length - 1) {
        if (Math.abs(target - array[low].x) < Math.abs(array[low + 1].x - target)) {
          return array[low];
        } else {
          return array[low + 1];
        }
      } else {
        return array[low];
      }
    }

    var joinX$1 = {};

    /**
     * Join x values if there are similar
     */
    Object.defineProperty(joinX$1, "__esModule", {
      value: true
    });
    joinX$1.joinX = joinX;
    function joinX(self, threshold = Number.EPSILON) {
      // when we join we will use the center of mass
      if (self.array.length === 0) return [];
      self.sortX();
      let current = self.array[0];
      let result = [current];
      for (let i = 1; i < self.array.length; i++) {
        const item = self.array[i];
        if (item.x - current.x <= threshold) {
          // weighted sum
          current.x = item.y / (current.y + item.y) * (item.x - current.x) + current.x;
          current.y += item.y;
        } else {
          current = {
            ...item
          };
          result.push(current);
        }
      }
      self.array = result;
      self.ySorted = false;
      return self;
    }

    var multiply$1 = {};

    Object.defineProperty(multiply$1, "__esModule", {
      value: true
    });
    multiply$1.multiply = multiply;
    function multiply(a, b, options = {}) {
      const {
        minY = 1e-8,
        maxLines = 5000,
        deltaX = 1e-2
      } = options;
      const result = new a.constructor();
      a.sortY();
      b.sortY();
      for (let entryA of a.array) {
        for (let entryB of b.array) {
          let y = entryA.y * entryB.y;
          if (y > minY) {
            const composition = calculateComposition(entryA, entryB);
            if (composition) {
              result.push({
                x: entryA.x + entryB.x,
                y,
                composition
              });
            } else {
              result.push({
                x: entryA.x + entryB.x,
                y
              });
            }
          }
          if (result.length > maxLines * 2) {
            result.joinX(deltaX);
            result.topY(maxLines);
          }
        }
      }
      result.joinX(deltaX);
      result.topY(maxLines);
      a.move(result);
      return a;
    }
    function calculateComposition(entryA, entryB) {
      if (!entryA.composition || !entryB.composition) return;
      let toReturn = {};
      const keys = [...new Set(Object.keys(entryA.composition).concat(Object.keys(entryB.composition)))];
      for (let key of keys) {
        toReturn[key] = (entryA.composition[key] || 0) + (entryB.composition[key] || 0);
      }
      return toReturn;
    }

    var power$1 = {};

    // https://en.wikipedia.org/wiki/Exponentiation_by_squaring
    Object.defineProperty(power$1, "__esModule", {
      value: true
    });
    power$1.power = power;
    function power(array, p, options = {}) {
      if (p <= 0) throw new Error('power must be larger than 0');
      if (p === 1) return array;
      if (p === 2) {
        return array.square();
      }
      p--;
      let base = array.copy(); // linear time
      while (p !== 0) {
        if ((p & 1) !== 0) {
          array.multiply(base, options); // executed <= log2(p) times
        }
        p >>= 1;
        if (p !== 0) base.square(options); // executed <= log2(p) times
      }
      return array;
    }

    Object.defineProperty(Distribution$1, "__esModule", {
      value: true
    });
    Distribution$1.Distribution = void 0;
    const closestPointX_js_1 = closestPointX$1;
    const joinX_js_1 = joinX$1;
    const multiply_js_1 = multiply$1;
    const power_js_1 = power$1;
    /**
     * Internal class to deal with isotopic distribution calculations
     */
    class Distribution {
      constructor(array = []) {
        this.array = array;
        this.cache = getEmptyCache();
      }
      emptyCache() {
        if (this.cache.isEmpty) return;
        this.cache = getEmptyCache();
      }
      get length() {
        return this.array.length;
      }
      get xs() {
        return this.array.map(p => p.x);
      }
      get ys() {
        return this.array.map(p => p.y);
      }
      get sumY() {
        if (!Number.isNaN(this.cache.sumY)) return this.cache.sumY;
        let sumY = 0;
        for (let item of this.array) {
          sumY += item.y;
        }
        this.cache.sumY = sumY;
        this.cache.isEmpty = false;
        return sumY;
      }
      get minX() {
        if (!Number.isNaN(this.cache.minX)) return this.cache.minX;
        let minX = Number.POSITIVE_INFINITY;
        for (let item of this.array) {
          if (item.x < minX) {
            minX = item.x;
          }
        }
        this.cache.minX = minX;
        this.cache.isEmpty = false;
        return minX;
      }
      get maxX() {
        if (!Number.isNaN(this.cache.maxX)) return this.cache.maxX;
        let maxX = Number.NEGATIVE_INFINITY;
        for (let item of this.array) {
          if (item.x > maxX) {
            maxX = item.x;
          }
        }
        this.cache.maxX = maxX;
        this.cache.isEmpty = false;
        return maxX;
      }
      get minY() {
        if (!Number.isNaN(this.cache.minY)) return this.cache.minY;
        let minY = Number.POSITIVE_INFINITY;
        for (let item of this.array) {
          if (item.y < minY) {
            minY = item.y;
          }
        }
        this.cache.minY = minY;
        this.cache.isEmpty = false;
        return minY;
      }
      get maxY() {
        if (!Number.isNaN(this.cache.maxY)) return this.cache.maxY;
        let maxY = Number.NEGATIVE_INFINITY;
        for (let item of this.array) {
          if (item.y > maxY) {
            maxY = item.y;
          }
        }
        this.cache.maxY = maxY;
        this.cache.isEmpty = false;
        return maxY;
      }
      multiplyY(value) {
        for (const item of this.array) {
          item.y *= value;
        }
      }
      setArray(array) {
        this.array = array;
        this.emptyCache();
      }
      move(other) {
        this.array = other.array;
        this.emptyCache();
      }
      push(...points) {
        this.array.push(...points);
        this.emptyCache();
      }
      /**
       * Sort by ASCENDING x values
       * @returns {Distribution}
       */
      sortX() {
        this.cache.ySorted = false;
        if (this.cache.xSorted) return this;
        this.array.sort((a, b) => a.x - b.x);
        this.cache.xSorted = true;
        this.cache.isEmpty = false;
        return this;
      }
      /**
       * Sort by DESCENDING y values
       * @returns {Distribution}
       */
      sortY() {
        this.cache.xSorted = false;
        if (this.cache.ySorted) return this;
        this.array.sort((a, b) => b.y - a.y);
        this.cache.ySorted = true;
        this.cache.isEmpty = false;
        return this;
      }
      normalize() {
        const sum = this.sumY;
        for (let item of this.array) {
          item.y /= sum;
        }
        return this;
      }
      /**
       * Only keep a defined number of peaks
       * @param {number} limit
       * @returns
       */
      topY(limit) {
        if (!limit) return this;
        if (this.array.length <= limit) return this;
        this.sortY();
        this.array.splice(limit);
        return this;
      }
      /**
       * remove all the peaks under a defined relative threshold
       * @param {number} [relativeValue=0] Should be between 0 and 1. 0 means no peak will be removed, 1 means all peaks will be removed
       */
      threshold(relativeValue = 0) {
        if (!relativeValue) return this;
        const maxY = this.maxY;
        const threshold = maxY * relativeValue;
        this.array = this.array.filter(point => point.y >= threshold);
      }
      square(options = {}) {
        return this.multiply(this, options);
      }
      multiply(b, options) {
        return (0, multiply_js_1.multiply)(this, b, options);
      }
      power(p, options) {
        return (0, power_js_1.power)(this, p, options);
      }
      copy() {
        let distCopy = new Distribution();
        distCopy.cache = {
          ...this.cache
        };
        distCopy.array = structuredClone(this.array);
        return distCopy;
      }
      maxToOne() {
        if (this.array.length === 0) return this;
        let currentMax = this.maxY;
        for (let item of this.array) {
          item.y /= currentMax;
        }
        return this;
      }
      joinX(threshold) {
        return (0, joinX_js_1.joinX)(this, threshold);
      }
      append(distribution) {
        for (let item of distribution.array) {
          this.array.push(item);
        }
        this.emptyCache();
      }
      closestPointX(target) {
        this.sortX();
        return (0, closestPointX_js_1.closestPointX)(this.array, target);
      }
    }
    Distribution$1.Distribution = Distribution;
    function getEmptyCache() {
      return {
        isEmpty: true,
        xSorted: false,
        ySorted: false,
        minX: Number.NaN,
        maxX: Number.NaN,
        minY: Number.NaN,
        maxY: Number.NaN,
        sumY: Number.NaN
      };
    }

    var getDerivedCompositionInfo$1 = {};

    Object.defineProperty(getDerivedCompositionInfo$1, "__esModule", {
      value: true
    });
    getDerivedCompositionInfo$1.getDerivedCompositionInfo = getDerivedCompositionInfo;
    const chemical_elements_1$2 = src$5;
    const mf_parser_1$2 = src$7;
    /**
     * Calcultes informations about the isotopic composition explaining a specific mass
     * @param {Record<string, number>} composition
     * @returns
     */
    function getDerivedCompositionInfo(composition) {
      const shortComposition = {};
      let label = '';
      let shortLabel = '';
      let deltaNeutrons = 0;
      for (let key in composition) {
        let isotopeLabel = '';
        for (let i = 0; i < key.length; i++) {
          if (mf_parser_1$2.superscript[key[i]]) {
            isotopeLabel += mf_parser_1$2.superscript[key[i]];
          } else {
            isotopeLabel += key[i];
          }
        }
        if (composition[key] > 1) {
          const number = String(composition[key]);
          for (let i = 0; i < number.length; i++) {
            isotopeLabel += mf_parser_1$2.subscript[number[i]];
          }
        }
        label += isotopeLabel;
        const isotope = chemical_elements_1$2.stableIsotopesObject[key];
        deltaNeutrons += isotope.deltaNeutrons * composition[key];
        if (isotope.mostAbundant) continue;
        shortLabel += isotopeLabel;
        shortComposition[key] = composition[key];
      }
      return {
        label,
        shortComposition,
        shortLabel,
        deltaNeutrons
      };
    }

    Object.defineProperty(IsotopicDistribution$1, "__esModule", {
      value: true
    });
    IsotopicDistribution$1.IsotopicDistribution = void 0;
    const chemical_elements_1$1 = src$5;
    const mf_parser_1$1 = src$7;
    const mf_utilities_1$2 = src$2;
    const ml_spectra_processing_1$1 = require$$1;
    const spectrum_generator_1 = require$$4;
    const Distribution_1 = Distribution$1;
    const getDerivedCompositionInfo_1 = getDerivedCompositionInfo$1;
    const MINIMAL_FWHM = 1e-8;
    const MINIMAL_Y = 1e-8;
    /** @typedef {import('mf-parser').IsotopesInfo} IsotopesInfo */
    /** @typedef {import('mf-parser').MFInfo} MFInfo */
    /** @typedef {import('./IsotopicDistribution.types').XY} XY */
    /** @typedef {import('./IsotopicDistribution.types').IsotopicDistributionPart} IsotopicDistributionPart */
    /** @typedef {import('./IsotopicDistribution.types').IsotopicDistributionOptions} IsotopicDistributionOptions */
    /**
     * A class that allows to manage isotopic distribution
     */
    class IsotopicDistribution {
      /**
       * Class that manages isotopic distribution
       * @param {string|Array<any>} value - Molecular formula or an array of parts.
       * @param {IsotopicDistributionOptions} [options]
       */
      constructor(value, options = {}) {
        this.threshold = options.threshold;
        this.limit = options.limit;
        if (Array.isArray(value)) {
          this.parts = structuredClone(value);
          for (let part of this.parts) {
            part.confidence = 0;
            part.isotopesInfo = new mf_parser_1$1.MF(part.ionization?.mf ? `${part.mf}(${part.ionization.mf})` : part.mf || '').getIsotopesInfo();
          }
        } else {
          let mf = new mf_parser_1$1.MF(value, {
            ensureCase: options.ensureCase
          });
          let mfInfo = mf.getInfo();
          const ionizations = (0, mf_utilities_1$2.preprocessIonizations)(options.ionizations);
          /** @type {MFInfo} */
          const parts = 'parts' in mfInfo ? mfInfo.parts : [mfInfo];
          this.parts = [];
          for (let partOriginal of parts) {
            // we calculate information for each part
            for (const ionization of ionizations) {
              let part = structuredClone(partOriginal);
              part.em = part.monoisotopicMass; // TODO: To remove !!! we change the name !?
              part.isotopesInfo = new mf_parser_1$1.MF(ionization.mf ? `${part.mf}(${ionization.mf})` : part.mf || '').getIsotopesInfo();
              part.confidence = 0;
              let msInfo = (0, mf_utilities_1$2.getMsInfo)(part, {
                ionization
              });
              part.ionization = msInfo.ionization;
              part.ms = msInfo.ms;
              this.parts.push(part);
            }
          }
        }
        this.cachedDistribution = undefined;
        this.fwhm = options.fwhm === undefined ? 0.01 : options.fwhm;
        // if fwhm is under 1e-8 there are some artifacts in the spectra
        if (this.fwhm < MINIMAL_FWHM) this.fwhm = MINIMAL_FWHM;
        this.minY = options.minY === undefined ? MINIMAL_Y : options.minY;
        this.maxLines = options.maxLines || 5000;
        this.allowNeutral = options.allowNeutral === undefined ? true : options.allowNeutral;
      }
      /**
       * @returns {Array<IsotopicDistributionPart>}
       */
      getParts() {
        return this.parts;
      }
      /**
       * @return {Distribution} returns the total distribution (for all parts)
       */
      getDistribution() {
        if (this.cachedDistribution) return this.cachedDistribution;
        let options = {
          maxLines: this.maxLines,
          minY: this.minY,
          deltaX: this.fwhm
        };
        let finalDistribution = new Distribution_1.Distribution();
        this.confidence = 0;
        // TODO need to cache each part without ionization
        // in case of many ionization we don't need to recalculate everything !
        for (let part of this.parts) {
          let totalDistribution = new Distribution_1.Distribution([{
            x: 0,
            y: 1,
            composition: this.fwhm === MINIMAL_FWHM ? {} : undefined // should we calculate composition in isotopes of each peak
          }]);
          let partNaturalDeltaNeutrons = 0;
          for (let isotope of part.isotopesInfo.isotopes) {
            partNaturalDeltaNeutrons += (isotope.naturalDeltaNeutrons ?? 0) * isotope.number;
          }
          let charge = part.ms?.charge || part.isotopesInfo.charge || 0;
          let absoluteCharge = Math.abs(charge);
          if (charge || this.allowNeutral) {
            for (let isotope of part.isotopesInfo.isotopes) {
              if (isotope.number < 0) return {
                array: []
              };
              if (isotope.number > 0) {
                const newDistribution = structuredClone(isotope.distribution);
                if (this.fwhm === MINIMAL_FWHM) {
                  // add composition
                  for (const entry of newDistribution) {
                    entry.composition = {
                      [Math.round(entry.x) + isotope.atom]: 1
                    };
                  }
                }
                let distribution = new Distribution_1.Distribution(newDistribution);
                distribution.power(isotope.number, options);
                totalDistribution.multiply(distribution, options);
              }
            }
            this.confidence = 0;
            for (const item of totalDistribution.array) {
              this.confidence += item.y;
            }
            // we finally deal with the charge
            if (charge) {
              for (const e of totalDistribution.array) {
                e.x = (e.x - chemical_elements_1$1.ELECTRON_MASS * charge) / absoluteCharge;
              }
            }
            if (totalDistribution.array && totalDistribution.array.length > 0) {
              totalDistribution.sortX();
              part.fromX = totalDistribution.array[0].x;
              part.toX = totalDistribution.array.at(-1).x;
            }
            if (part.ms?.similarity?.factor) {
              totalDistribution.multiplyY(part.ms.similarity.factor);
            } else if (part.ms?.target?.intensity && part.ms?.target?.intensity !== 1) {
              // intensity is the value of the monoisotopic mass !
              // need to find the intensity of the peak corresponding
              // to the monoisotopic mass
              if (part.ms.target.mass) {
                let target = totalDistribution.closestPointX(part.ms.target.mass);
                totalDistribution.multiplyY(part.ms.target.intensity / target.y);
              } else {
                totalDistribution.multiplyY(part.ms.target.intensity);
              }
            } else if (part?.intensity && part?.intensity !== 1) {
              totalDistribution.multiplyY(part.intensity);
            }
            part.isotopicDistribution = totalDistribution.array;
            for (let entry of totalDistribution.array) {
              if (!entry.composition) continue;
              const info = (0, getDerivedCompositionInfo_1.getDerivedCompositionInfo)(entry.composition);
              info.deltaNeutrons -= partNaturalDeltaNeutrons;
              Object.assign(entry, info);
            }
            if (finalDistribution.array.length === 0) {
              finalDistribution = totalDistribution;
            } else {
              finalDistribution.append(totalDistribution);
            }
          }
        }
        if (finalDistribution) finalDistribution.joinX(this.fwhm);
        // if there is a threshold we will deal with it
        // and we will correct the confidence
        if (this.threshold || this.limit) {
          const sumBefore = finalDistribution.sumY;
          if (this.threshold) finalDistribution.threshold(this.threshold);
          if (this.limit) {
            finalDistribution.topY(this.limit);
            finalDistribution.sortX();
          }
          const sumAfter = finalDistribution.sumY;
          this.confidence = this.confidence * sumAfter / sumBefore;
        }
        this.confidence /= this.parts.length;
        this.cachedDistribution = finalDistribution;
        return finalDistribution;
      }
      /**
       *
       * @param {object} options
       * @param {string} [options.delimiter=', ']
       * @param {number} [options.numberXDecimals=4]
       * @param {number} [options.numberYDecimals=4]
       * @returns
       */
      getCSV(options) {
        return this.getText({
          ...options,
          delimiter: ', '
        });
      }
      /**
       *
       * @param {object} options
       * @param {string} [options.delimiter='\t']
       * @param {number} [options.numberXDecimals=4]
       * @param {number} [options.numberYDecimals=4]
       * @returns
       */
      getTSV(options) {
        return this.getText({
          ...options,
          delimiter: '\t'
        });
      }
      getTable(options = {}) {
        const {
          maxValue,
          xLabel = 'x',
          yLabel = 'y'
        } = options;
        let points = this.getDistribution().array;
        if (points.length === 0) return [];
        let factor = 1;
        if (maxValue) {
          let maxY = this.getMaxY(points);
          factor = maxValue / maxY;
        }
        return points.map(point => {
          let newPoint = {
            [xLabel]: point.x,
            [yLabel]: point.y * factor
          };
          return newPoint;
        });
      }
      /**
       *
       * @param {object} options
       * @param {string} [options.delimiter='\t']
       * @param {number} [options.numberXDecimals=4]
       * @param {number} [options.numberYDecimals=4]
       * @returns
       */
      getText(options = {}) {
        const {
          delimiter = '\t',
          numberXDecimals = 5,
          numberYDecimals = 3
        } = options;
        let points = this.getDistribution().array;
        let csv = [];
        for (let point of points) {
          csv.push(`${point.x.toFixed(numberXDecimals)}${delimiter}${(point.y * 100).toFixed(numberYDecimals)}`);
        }
        return csv.join('\n');
      }
      getMaxY(points) {
        let maxY = points[0].y;
        for (let point of points) {
          if (point.y > maxY) maxY = point.y;
        }
        return maxY;
      }
      getSumY(points) {
        let sumY = 0;
        for (let point of points) {
          sumY += point.y;
        }
        return sumY;
      }
      /**
       * Returns the isotopic distribution as an array of peaks
       * @param {object} [options={}]
       * @param {number} [options.maxValue=100]
       * @param {number} [options.sumValue] // if sumValue is defined, maxValue is ignored
       * @return {Array<{x:number,y:number}|{x:number,y:number,label:string,shortComposition:string,shortLabel:string,deltaNeutrons:number}>}
       */
      getPeaks(options = {}) {
        const {
          maxValue = 100,
          sumValue
        } = options;
        let peaks = this.getDistribution().array;
        if (peaks.length === 0) return [];
        let factor = 1;
        if (sumValue) {
          let sumY = this.getSumY(peaks);
          factor = sumY / sumValue;
          if (factor !== 1) {
            peaks = structuredClone(peaks);
            for (const peak of peaks) {
              peak.y = peak.y / factor;
            }
          }
        } else if (maxValue) {
          let maxY = this.getMaxY(peaks);
          if (maxValue !== maxY) {
            // we need to copy the array because we prefer no side effects
            peaks = structuredClone(peaks);
            for (const peak of peaks) {
              // we rescale the Y values by first dividing by maxY and then multiplying by maxValue in order to avoid approximation errors
              peak.y = peak.y / maxY * maxValue;
            }
          }
        }
        return peaks;
      }
      /**
       * Returns the isotopic distirubtion
       * @param {object} [options={}]
       * @param {number} [options.maxValue=100]
       * @param {number} [options.sumValue] // if sumValue is defined, maxValue is ignored
       * @return {{x:number[],y:number[]}|{x:number[],y:number[],label:string[],shortComposition:string[],shortLabel:string[],deltaNeutrons:[]}}
       */
      getXY(options = {}) {
        let peaks = this.getPeaks(options);
        if (peaks.length === 0) {
          return {
            x: [],
            y: []
          };
        }
        const result = {
          x: peaks.map(a => a.x),
          y: peaks.map(a => a.y)
        };
        for (let key of Object.keys(peaks[0]).filter(k => k !== 'x' && k !== 'y')) {
          result[key] = peaks.map(a => a[key]);
        }
        return result;
      }
      /**
       * Returns the isotopic distirubtion
       * @param {object} [options={}]
       * @param {number} [options.maxValue=100]
       * @param {number} [options.sumValue] // if sumValue is defined, maxValue is ignored
       * @return {import('cheminfo-types').MeasurementXYVariables} an object containing at least the 2 properties: x:[] and y:[]
       */
      getVariables(options = {}) {
        const xy = this.getXY(options);
        return {
          x: {
            data: xy.x,
            label: 'm/z',
            units: 'u'
          },
          y: {
            data: xy.y,
            label: 'Relative intensity',
            units: '%'
          }
        };
      }
      /**
       * Returns the isotopic distribution as the sum of gaussian
       * @param {object} [options={}]
       * @param {number} [options.gaussianWidth=10] // how good should look the gaussian ? By default we take 10 times the fwhm as number of points
       * @param {number} [options.threshold=0.00001] // minimal height to return point
       * @param {number} [options.maxLength=1e6] // maximal number of points; ignored when sparse is true
       * @param {number} [options.maxValue] // rescale Y to reach maxValue
       * @param {number} [options.from] // minimal x value, default to the first point - 2
       * @param {number} [options.to] // maximal x value, default to the last point + 2
       * @param {function} [options.peakWidthFct=(mz)=>(this.fwhm)]
       * @param {boolean} [options.sparse=false] // use SparseSpectrumGenerator; only generates points in peak regions, avoids maxLength limit
       * @return {XY} isotopic distribution as an object containing 2 properties: x:[] and y:[]
       */
      getGaussian(options = {}) {
        const {
          peakWidthFct = () => this.fwhm,
          threshold = 0.00001,
          gaussianWidth = 10,
          maxValue,
          maxLength = 1e6,
          sparse = false
        } = options;
        let peaks = this.getTable({
          maxValue
        });
        if (peaks.length === 0) return {
          x: [],
          y: []
        };
        const from = options.from || peaks[0].x - 2;
        const to = options.to || peaks.at(-1).x + 2;
        let spectrum;
        const nbPoints = Math.round((to - from) * gaussianWidth / this.fwhm + 1);
        const generatorOptions = {
          from,
          to,
          peakWidthFct,
          nbPoints
        };
        if (sparse) {
          const generator = new spectrum_generator_1.SparseSpectrumGenerator(generatorOptions);
          spectrum = generator.generateSparseSpectrum(peaks, {
            threshold
          });
        } else {
          if (nbPoints > maxLength) {
            throw new Error(`Number of points is over the maxLength: ${nbPoints}>${maxLength}`);
          }
          const generator = new spectrum_generator_1.SpectrumGenerator(generatorOptions);
          spectrum = generator.generateSpectrum(peaks, {
            threshold
          });
        }
        //TODO not clear why maxValue it can not be directly provided as a parameter in the generateSpectrum function but some tests would fail
        if (maxValue) {
          spectrum.y = (0, ml_spectra_processing_1$1.xNormed)(spectrum.y, {
            algorithm: 'max',
            value: maxValue,
            output: spectrum.y
          });
        }
        return spectrum;
      }
    }
    IsotopicDistribution$1.IsotopicDistribution = IsotopicDistribution;

    (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(IsotopicDistribution$1, exports);
    })(src$3);

    var src$1 = {};

    var generateMFs$1 = {};

    var src = {};

    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 = {};

    Object.defineProperty(msemMatcher$1, "__esModule", {
      value: true
    });
    msemMatcher$1.msemMatcher = msemMatcher;
    const mf_utilities_1$1 = src$2;
    const ml_spectra_processing_1 = require$$1;
    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.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 && src.__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 && src.__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);

    /**
     * Modify object a to join it with b and make the sum of each of the keys
     * @param {*} a
     * @param {*} source1
     *
     * @return {object}
     */

    function sum(target) {
      for (var i = 1; i < arguments.length; i++) {
        let toSum = arguments[i];
        for (var key of Object.keys(toSum)) {
          if (target[key]) {
            target[key] += toSum[key];
          } else {
            target[key] = toSum[key];
          }
        }
      }
      return target;
    }
    var lib = sum;

    var __importDefault = generateMFs$1 && generateMFs$1.__importDefault || function (mod) {
      return mod && mod.__esModule ? mod : {
        "default": mod
      };
    };
    Object.defineProperty(generateMFs$1, "__esModule", {
      value: true
    });
    generateMFs$1.generateMFs = generateMFs;
    const chemical_elements_1 = src$5;
    const mf_matcher_1 = src;
    const mf_parser_1 = src$7;
    const mf_utilities_1 = src$2;
    const sum_object_keys_1 = __importDefault(lib);
    /**
     * Generate all the possible combinations of molecular formula and calculate
     * for each of them the monoisotopic mass and observed monoisotopic mass (m/z)
     * In the molecular formula there may be a comment after the '$' symbol
     *
     * @param {Array}         ranges
     * @param {object}        [options={}]
     * @param {number}        [options.limit=10000000] - Maximum number of results
     * @param {boolean}       [options.estimate=false] - estimate the number of MF without filters
     * @param {boolean}       [options.canonizeMF=true] - Canonize molecular formula
     * @param {boolean}       [options.uniqueMFs=true] - Force canonization and make MF unique
     * @param {string}        [options.ionizations=''] - Comma separated list of ionizations (to charge the molecule)
     * @param {function}      [options.onStep] - Callback to do after each step
     * @param {import('mf-matcher').MSEMFilterOptions}        [options.filter={}]
     * @param {string}        [options.filterFct]
     * @param {object}        [options.links]
     * @param {boolean}       [options.links.filter] We filter all the MF that do not match the '*X'
     * @returns {Promise}
     */
    async function generateMFs(ranges, options = {}) {
      if (!Array.isArray(ranges)) {
        throw new Error('Ranges must be an array of string or object');
      }
      options = {
        ...options
      };
      let {
        limit = 100000,
        uniqueMFs = true,
        estimate = false,
        onStep,
        filterFct,
        canonizeMF,
        ionizations: rawIonizations
      } = options;
      let filterFctVariables = {};
      for (let i = 0; i < ranges.length; i++) {
        const range = ranges[i];
        if (typeof range === 'object' && range.name) {
          filterFctVariables[range.name] = i;
          ranges[i] = range.value;
        }
      }
      if (filterFct) {
        // we create a real javascript function
        let variables = Object.keys(filterFctVariables);
        variables.push('mm', 'mz', 'charge', 'unsaturation', 'atoms');
        // eslint-disable-next-line no-new-func
        filterFct = new Function(...variables, `return ${filterFct}`);
      }
      if (uniqueMFs === true) canonizeMF = true;
      if (canonizeMF === undefined) canonizeMF = true;
      const ionizations = (0, mf_utilities_1.preprocessIonizations)(rawIonizations);
      options = {
        ...options,
        filterFctVariables,
        filterFct,
        canonizeMF,
        ionizations
      };
      // we allow String delimited by ". or ;" instead of an array
      for (let i = 0; i < ranges.length; i++) {
        if (!Array.isArray(ranges[i])) {
          ranges[i] = ranges[i].split(/[,.]/);
        }
      }
      // we allow ranges in a string ...
      // problem with ranges is that we need to know to what the range applies
      for (let i = 0; i < ranges.length; i++) {
        let parts = ranges[i];
        let newParts = [];
        for (let j = 0; j < parts.length; j++) {
          let part = parts[j];
          if (part.match(/\d-[\d-]/)) {
            // deal with negative numbers
            // there are ranges ... we are in trouble !
            newParts = newParts.concat(new mf_parser_1.MF(part).flatten({
              groupIdentical: false,
              limit
            }));
          } else {
            newParts.push(parts[j]); // the part with the comments !
          }
        }
        ranges[i] = newParts;
      }
      if (estimate) {
        let total = ranges.reduce((previous, current) => previous * current.length, 1);
        return total * ionizations.length;
      }
      let results = [];
      let sizes = [];
      let currents = [];
      for (let i = 0; i < ranges.length; i++) {
        sizes.push(ranges[i].length - 1);
        currents.push(0);
      }
      let position = 0;
      let evolution = 0;
      while (position < currents.length) {
        if (currents[position] < sizes[position]) {
          if (onStep) await onStep(evolution);
          evolution++;
          appendResult(results, currents, ranges, options);
          currents[position]++;
          for (let i = 0; i < position; i++) {
            currents[i] = 0;
          }
          position = 0;
        } else {
          position++;
        }
        if (evolution > limit) {
          throw new Error(`You have reached the limit of ${limit}. You could still change this value using the limit option but it is likely to crash.`);
        }
      }
      appendResult(results, currents, ranges, options);
      if (uniqueMFs) {
        let uniqueMFsObject = {};
        for (const result of results) {
          uniqueMFsObject[result.mf + result.ionization.mf] = result;
        }
        results = Object.keys(uniqueMFsObject).map(k => uniqueMFsObject[k]);
      }
      results.sort((a, b) => a.em - b.em);
      return results;
    }
    let ems = {};
    // internal method used as a cache
    function getMonoisotopicMass(mfString) {
      if (!ems[mfString]) {
        // we need to calculate based on the mf but not very often ...
        let mf = new mf_parser_1.MF(mfString);
        let info = mf.getInfo();
        ems[mfString] = {
          em: info.monoisotopicMass,
          charge: info.charge,
          mw: info.mass,
          unsaturation: (info.unsaturation - 1) * 2,
          atoms: info.atoms
        };
      }
      return ems[mfString];
    }
    function getEMFromParts(parts, currents, ionization) {
      let charge = 0;
      let em = 0;
      let mw = 0;
      let unsaturation = 0;
      let validUnsaturation = true;
      let atoms = {};
      for (let i = 0; i < parts.length; i++) {
        let part = parts[i][currents[i]];
        if (part) {
          let info = getMonoisotopicMass(part);
          charge += info.charge;
          em += info.em;
          mw += info.mw;
          (0, sum_object_keys_1.default)(atoms, info.atoms);
          if (info.unsaturation && validUnsaturation) {
            unsaturation += info.unsaturation;
          }
        }
      }
      return {
        charge,
        em,
        mw,
        ionization,
        unsaturation: unsaturation / 2 + 1 ,
        atoms
      };
    }
    function appendResult(results, currents, keys, options = {}) {
      const {
        canonizeMF,
        filter,
        ionizations,
        filterFct,
        filterFctVariables,
        links = {}
      } = options;
      // this script is designed to combine molecular formula
      // that may contain comments after a "$" sign
      // therefore we should put all the comments at the ned
      if (links.filter) {
        let sharps = [];
        for (let i = 0; i < keys.length; i++) {
          let anchors = keys[i][currents[i]].match(/#\d+/g);
          if (anchors) sharps.push(...anchors);
        }
        if (sharps.length % 2 === 1) return;
        sharps = sharps.toSorted();
        for (let i = 0; i < sharps.length; i += 2) {
          if (sharps[i] !== sharps[i + 1]) return;
        }
      }
      for (let ionization of ionizations) {
        let result = getEMFromParts(keys, currents, ionization);
        if (filterFct) {
          let variables = [];
          for (let key in filterFctVariables) {
            variables.push(currents[filterFctVariables[key]]);
          }
          variables.push(result.em, (result.em + ionization.em - ionization.charge * chemical_elements_1.ELECTRON_MASS) / Math.abs(ionization.charge), result.charge + result.ionization.charge, result.unsaturation, result.atoms);
          if (!filterFct(...variables)) continue;
        }
        result.parts = [];
        result.mf = '';
        let comments = [];
        for (let i = 0; i < keys.length; i++) {
          let key = keys[i][currents[i]];
          if (key) {
            if (key.includes('$')) {
              comments.push(key.replace(/^[^$]*\$/, ''));
              key = key.replace(/\$.*/, '');
            }
            result.parts[i] = key;
            result.mf += key;
          }
        }
        if (comments.length > 0) {
          result.comment = comments.join(' ');
        }
        let match = (0, mf_matcher_1.msemMatcher)(result, filter);
        if (!match) continue;
        result.ms = match.ms;
        result.ionization = match.ionization;
        if (canonizeMF) {
          result.mf = new mf_parser_1.MF(result.mf).toMF();
        }
        results.push(result);
      }
    }

    (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(generateMFs$1, exports);
    })(src$1);

    /**
     * Calculate tic for specific molecular formula and ionizations
     *
     * The system will take all the peaks with an intensity over 5% (default value)
     * @param chromatogram - GC/MS chromatogram where make the peak picking
     * @param targetMF - mass for which to extract the spectrum
     * @param options
     * @returns Calculated mass for targetMass
     */
    async function calculateForMF(chromatogram, targetMF, options = {}) {
      const {
        threshold = 0.05,
        slotWidth = 1,
        ionizations = 'H+'
      } = options;
      if (typeof targetMF !== 'string') {
        throw new Error('targetMF must be defined and a string');
      }
      const mfInfos = await src$1.generateMFs([targetMF]);
      const mfs = mfInfos.map(info => info.mf);
      const halfWidth = slotWidth / 2;
      const ms = chromatogram.getSeries2D('ms');
      let masses = [];
      for (const mf of mfs) {
        const isotopicDistribution = new src$3.IsotopicDistribution(mf, {
          ionizations
        });
        // we add isotopicDistribution in all the parts
        isotopicDistribution.getDistribution();
        const parts = isotopicDistribution.getParts();
        // @ts-expect-error It seems that IsotopicDistributionPart is not correctly typed.
        masses = masses.concat(...parts.map(part => part.isotopicDistribution));
      }
      masses.sort((a, b) => a.x - b.x);
      masses = xyObjectSlotX(masses, {
        slotWidth
      }).filter(mass => mass.y > threshold);
      const massSpectra = ms.getData();
      const result = new Array(massSpectra.length).fill(0);
      for (const targetMass of masses) {
        for (let i = 0; i < massSpectra.length; i++) {
          const massSpectrum = massSpectra[i];
          for (let j = 0; j < massSpectrum[0].length; j++) {
            if (Math.abs(massSpectrum[0][j] - targetMass.x) <= halfWidth) {
              result[i] += massSpectrum[1][j];
            }
          }
        }
      }
      return result;
    }

    function calculateLength(chromatogram, seriesName) {
      const series2D = chromatogram.getSeries2D(seriesName);
      const spectra = series2D.getData();
      return spectra.map(spectrum => spectrum[0].length);
    }

    function calculateTic(chromatogram) {
      const ms = chromatogram.getSeries2D('ms');
      const massSpectra = ms.getData();
      const tic = [];
      for (const massSpectrum of massSpectra) {
        if (massSpectrum[1].length > 0) {
          tic.push(xSum(massSpectrum[1]));
        } else {
          tic.push(0);
        }
      }
      return tic;
    }

    function zeroInsteadOfNegative(X) {
      let rows = X.rows;
      let columns = X.columns;
      let newMatrix = new Matrix(X);
      for (let r = 0; r < rows; r++) {
        for (let c = 0; c < columns; c++) {
          if (newMatrix.get(r, c) < 0) {
            newMatrix.set(r, c, 0);
          }
        }
      }
      return newMatrix;
    }

    function checkMatrixS(data, originalMatrix) {
      let {
        A,
        S
      } = data;
      //check if is there at least one element cero
      let indices = [];
      let sum = S.sum('row');
      for (let i = 0; i < sum.length; i++) {
        if (sum[i] === 0) {
          indices.push(i);
          continue;
        } else {
          for (let j = 0; j < S.columns; j++) {
            if (isNaN(S.get(i, j))) {
              indices.push(i);
              break;
            }
          }
        }
      }
      // if there than just one zero or NaN element
      // run a NMF with the residual matrix Y - A*B
      if (indices.length > 0) {
        let temp = fastExtractNMF(originalMatrix.clone().subM(A.mmul(S)), indices.length);
        for (let i = 0; i < indices.length; i++) {
          for (let j = 0; j < S.columns; j++) {
            S.set(indices[i], j, temp.S.get(i, j));
          }
          for (let j = 0; j < A.rows; j++) {
            A.set(j, indices[i], temp.A.get(j, i));
          }
        }
      }
      return Object.assign({}, data, {
        A,
        S
      });
    }
    function fastExtractNMF(residual, r) {
      if (r <= 0) return {
        A: [],
        S: []
      };
      const {
        columns,
        rows
      } = residual;
      let A = Matrix.zeros(rows, r);
      let S = Matrix.zeros(r, columns);
      for (let i = 0; i < r; i++) {
        residual = zeroInsteadOfNegative(residual);
        if (residual.sum() === 0) continue;
        let res2 = Matrix.pow(residual, 2).sum('column');
        //find the max of the first column

        let maxIndex = 0;
        for (let j = 1; j < res2.length; j++) {
          if (res2[maxIndex] < res2[j]) maxIndex = j;
        }
        if (res2[maxIndex] > 0) {
          let sqrtMaxValue = Math.sqrt(res2[maxIndex]);
          for (let j = 0; j < rows; j++) {
            let value = residual.get(j, maxIndex) / sqrtMaxValue;
            A.set(j, i, value);
          }
          let temp = A.getColumnVector(i).transpose().mmul(residual);
          for (let j = 0; j < columns; j++) {
            S.set(i, j, Math.max(temp.get(0, j), 0));
          }
          let subtracting = A.getColumnVector(i).mmul(S.getRowVector(i));
          residual = residual.sub(subtracting);
        }
      }
      return {
        A,
        S
      };
    }

    function normBy(x, by = 'column') {
      let norms = Matrix.mul(x, x).sum(by);
      let length = norms.length;
      for (let i = 0; i < length; i++) {
        norms[i] = Math.sqrt(norms[i]);
      }
      return by === 'row' ? Matrix.from1DArray(length, 1, norms) : Matrix.from1DArray(1, length, norms);
    }

    function normProj(X, normLimits) {
      let norms;
      let r = X.rows;
      let c = X.columns;
      if (normLimits.rows === r) {
        norms = normBy(X, 'row');
        //select rows with norm > 0 then multiply twise by the min
        for (let i = 0; i < r; i++) {
          if (norms.get(i, 0) <= 0) continue;
          for (let j = 0; j < c; j++) {
            let value = X.get(i, j) * Math.min(norms.get(i, 0), normLimits.get(i, 0) / norms.get(i, 0));
            X.set(i, j, value);
          }
        }
      } else {
        norms = normBy(X, 'column');
        for (let i = 0; i < c; i++) {
          if (norms.get(0, i) <= 0) continue;
          for (let j = 0; j < r; j++) {
            let value = X.get(j, i) * Math.min(norms.get(0, i), normLimits.get(0, i) / norms.get(0, i));
            X.set(j, i, value);
          }
        }
      }
      return X;
    }

    function updateMatrixA(Ainit, S, originalMatrix, options) {
      let {
        maxFBIteration,
        toleranceFB,
        normConstrained = false,
        lambda
      } = options;
      let St = S.transpose();
      let H = S.mmul(St);
      let YSt = originalMatrix.mmul(St);
      let evd = new EVD(H, {
        assumeSymmetric: true
      });
      let L = Math.max(...evd.realEigenvalues);
      let A = Ainit;
      let prevA = A.clone();
      let t = 1;
      let gradient = a => a.mmul(H).sub(YSt);
      let proximal;
      if (normConstrained) {
        let normLimits = normBy(Ainit, 'column');
        proximal = (x, threshold) => normProj(zeroInsteadOfNegative(x.subS(threshold)), normLimits);
      } else {
        proximal = (x, threshold) => zeroInsteadOfNegative(x.subS(threshold));
      }
      for (let i = 0; i < maxFBIteration; i++) {
        let tNext = (1 + Math.sqrt(1 + 4 * t * t)) / 2;
        let w = (t - 1) / tNext;
        t = tNext;
        let B = Matrix.mul(A, w + 1).sub(Matrix.mul(prevA, w));
        prevA = A.clone();
        A = proximal(B.sub(gradient(B).divS(L)), lambda / L);
        if (Matrix.sub(prevA, A).norm() / A.norm() < toleranceFB) {
          break;
        }
      }
      return A;
    }

    function getMax(array = []) {
      let max = Number.MIN_SAFE_INTEGER;
      for (let i = 0; i < array.length; i++) {
        if (max < array[i]) max = array[i];
      }
      return max;
    }

    function updateMatrixS(A, Sinit, originalMatrix, lambda, options) {
      let {
        maxFBIteration,
        toleranceFB
      } = options;
      let At = A.transpose();
      let H = At.mmul(A);
      let AtY = At.mmul(originalMatrix);
      let evd = new EVD(H, {
        assumeSymmetric: true
      });
      let L = getMax(evd.realEigenvalues);
      let t = 1;
      let S = Sinit.clone();
      let prevS = S.clone();
      let gradient = s => H.mmul(s).sub(AtY);
      let proximal = (x, threshold) => zeroInsteadOfNegative(x.subS(threshold));
      for (let i = 0; i < maxFBIteration; i++) {
        let tNext = (1 + Math.sqrt(1 + 4 * t * t)) / 2;
        let w = (t - 1) / tNext;
        t = tNext;
        // R = S_k + w [S_k - S_(k-1)] = (1 + w) .* S_k - w .* S_(k-1)
        let R = Matrix.mul(S, 1 + w).sub(Matrix.mul(prevS, w));
        prevS = S.clone();
        S = proximal(R.sub(gradient(R).divS(L)), lambda / L);
        if (Matrix.sub(prevS, S).norm() / S.norm() < toleranceFB) {
          break;
        }
      }
      return S;
    }

    function initialize(originalMatrix, options = {}) {
      const {
        rank,
        randGenerator,
        maxInitFBIteration,
        toleranceFBInit,
        maxFBIteration,
        toleranceFB,
        normConstrained
      } = options;
      let result = {};
      let rows = originalMatrix.rows;
      result.A = Matrix.rand(rows, rank, {
        random: randGenerator
      });
      for (let iter = 0; iter < maxInitFBIteration; iter++) {
        //select columns with sum positive from A
        let sumC = result.A.sum('column');
        for (let i = 0; i < sumC.length; i++) {
          while (sumC[i] === 0) {
            sumC[i] = 0;
            for (let j = 0; j < rows; j++) {
              result.A.set(j, i, randGenerator());
              sumC[i] += result.A.get(j, i);
            }
          }
        }

        //resolve the system of equation Lx = D for x, then select just non negative values;
        result.S = zeroInsteadOfNegative(solve(result.A, originalMatrix));

        //select rows with positive sum by row
        let sumR = result.S.sum('row');
        let positiveSumRowIndexS = [];
        let positiveSumRowS = [];
        for (let i = 0; i < sumR.length; i++) {
          if (sumR[i] > 0) {
            positiveSumRowIndexS.push(i);
            positiveSumRowS.push(result.S.getRow(i));
          }
        }
        positiveSumRowS = Matrix.checkMatrix(positiveSumRowS);

        // solve the system of linear equation xL = D for x. knowing that D/L = (L'\D')'.
        let candidateA = zeroInsteadOfNegative(solve(positiveSumRowS.transpose(), originalMatrix.transpose()));

        //then, set the columns of A with an index equal to the row index with sum > 0 into S
        //this step complete the last transpose of D/L = (L'\D')'.
        for (let i = 0; i < positiveSumRowIndexS.length; i++) {
          let colCandidate = candidateA.getRow(i);
          for (let j = 0; j < rows; j++) {
            result.A.set(j, positiveSumRowIndexS[i], colCandidate[j]);
          }
        }
        let prevS = result.S.clone();
        result.S = updateMatrixS(result.A, result.S, originalMatrix, 0, {
          maxFBIteration,
          toleranceFB
        });
        result = checkMatrixS(result, originalMatrix);
        result.A = updateMatrixA(result.A, result.S, originalMatrix, 0);
        if (Matrix.sub(prevS, result.S).norm() / result.S.norm() < toleranceFBInit) {
          break;
        }
      }
      return result;
    }

    function normalize(data, options) {
      const {
        normOnA
      } = options;
      let DS = normBy(data.S.transpose(), 'column');
      let DA = normBy(data.A, 'column');
      let D = Matrix.mul(DS, DA);
      let onS, onA;
      if (normOnA) {
        onS = (index, c) => data.S.get(index, c) * D.get(0, index) / DS.get(0, index);
        onA = (index, r) => data.A.get(r, index) / DA.get(0, index);
      } else {
        onS = (index, c) => data.S.get(index, c) / DS.get(0, index);
        onA = (index, r) => data.A.get(r, index) * D.get(0, index) / DA.get(0, index);
      }
      const sColumns = data.S.columns;
      const aRows = data.A.rows;
      for (let index = 0; index < D.columns; index++) {
        let valueForS, valueForA;
        if (D.get(0, index) > 0) {
          valueForS = onS;
          valueForA = onA;
        } else {
          valueForA = () => 0;
          valueForS = () => 0;
        }
        for (let c = 0; c < sColumns; c++) {
          data.S.set(index, c, valueForS(index, c));
        }
        for (let r = 0; r < aRows; r++) {
          data.A.set(r, index, valueForA(index, r));
        }
      }
      return data;
    }

    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 getMedians(X, by) {
      let medians = [];
      let rows = X.rows;
      let columns = X.columns;
      switch (by) {
        case 'column':
          for (let i = 0; i < columns; i++) {
            medians.push(quickSelectMedian(X.getColumn(i)));
          }
          medians = Matrix.from1DArray(1, columns, medians);
          break;
        default:
          for (let i = 0; i < rows; i++) {
            medians.push(quickSelectMedian(X.getRow(i)));
          }
          medians = Matrix.from1DArray(rows, 1, medians);
      }
      return medians;
    }

    function dimMADstd(X, by) {
      let medians = getMedians(X, by);
      let matrix = X.clone();
      matrix = matrix.subColumnVector(medians.to1DArray());
      return Matrix.mul(getMedians(matrix.abs(), by), 1.4826);
    }

    function updateLambda(data, originalMatrix, options = {}) {
      let {
        refinementBeginning,
        tauMAD
      } = options;
      let {
        iteration,
        lambda,
        A,
        S
      } = data;
      if (refinementBeginning <= iteration) return lambda;
      let sigmaResidue;
      if (options.lambdaInf !== undefined) {
        sigmaResidue = options.lambdaInf / options.tauMAD;
      } else if (options.addStd !== undefined) {
        sigmaResidue = options.addStd;
      } else {
        let alY = Matrix.sub(originalMatrix, A.mmul(S)).to1DArray();
        let result = dimMADstd(Matrix.from1DArray(1, alY.length, alY), 'row');
        sigmaResidue = result.get(0, 0);
      }
      let nextLambda = Math.max(tauMAD * sigmaResidue, lambda - 1 / (refinementBeginning - iteration));
      return nextLambda;
    }

    /**
     * Performing non-negative matrix factorization solving argmin_(A >= 0, S >= 0) 1 / 2 * ||Y - AS||_2^2 + lambda * ||S||_1
     * @param {Matrix||Array<Array>} originalMatrix - Matrix to be separated.
     * @param {Number} rank - The maximum number of linearly independent column/row vectors in the matrix.
     * @param {Object} [options = {}] - Options of ngmca factorization method.
     * @param {Number} [options.maximumIteration = 500] - Maximum number of iterations.
     * @param {Number} [options.maxFBIteration = 80] - Maximum number of iterations of the Forward-Backward subroutine.
     * @param {Object} [options.randGenerator = Math.random] - Random number generator for the subroutine of initialization.
     * @param {Number} [options.maxInitFBIteration = 50] - Maximum number of iterations of the Forward-Backward subroutine at the initialization.
     * @param {Number} [options.toleranceFB = 1e-5] - relative difference tolerance for convergence of the Forward-Backward sub-iterations.
     * @param {Number} [options.toleranceFBInit = 0] - relative difference tolerance for convergence of the Forward-Backward sub-iterations at the initialization.
     * @param {Number} [options.phaseRatio = 0.8] - transition between decreasing thresholding phase and refinement phase in percent of the iterations.
     * @param {Number} [options.tauMAD = 1] - constant coefficient for the final threshold computation.
     * @param {Boolean} [options.useTranspose = false] - if true the originalMatrix is transposed.
     */

    function nGMCA(originalMatrix, rank, options = {}) {
      const {
        maximumIteration = 500,
        maxFBIteration = 80,
        maxInitFBIteration = 50,
        toleranceFBInit = 0,
        toleranceFB = 0.00001,
        phaseRatio = 0.8,
        randGenerator = Math.random,
        tauMAD = 1,
        useTranspose = false
      } = options;
      let {
        normConstrained = false
      } = options;
      originalMatrix = Matrix.checkMatrix(originalMatrix);
      if (useTranspose) originalMatrix = originalMatrix.transpose();
      let refinementBeginning = Math.floor(phaseRatio * maximumIteration);
      let data = initialize(originalMatrix, {
        rank,
        randGenerator,
        maxInitFBIteration,
        toleranceFBInit,
        maxFBIteration,
        toleranceFB
      });
      data = normalize(data, {
        normOnA: true
      });
      data.lambda = data.A.transpose().mmul(data.A.mmul(data.S).sub(originalMatrix)).abs().max();
      for (let iter = 0; iter < maximumIteration; iter++) {
        data.iteration = iter;
        data.S = updateMatrixS(data.A, data.S, originalMatrix, data.lambda, options);
        data = checkMatrixS(data, originalMatrix);
        data = normalize(data, {
          normOnA: false
        });
        if (iter > refinementBeginning) normConstrained = true;
        data.A = updateMatrixA(data.A, data.S, originalMatrix, {
          maxFBIteration,
          toleranceFB,
          normConstrained,
          lambda: 0
        });
        data = normalize(data, {
          normOnA: true
        });
        data.lambda = updateLambda(data, originalMatrix, {
          refinementBeginning,
          tauMAD
        });
      }
      if (useTranspose) {
        let temp = data.A.transpose();
        data.A = data.S.transpose();
        data.S = temp;
      }
      return data;
    }

    /**
     * Creates new PCA (Principal Component Analysis) from the dataset
     * @param {MaybeMatrix} dataset - dataset or covariance matrix.
     * @param {PCAOptions} [options]
     * @param {boolean} [options.isCovarianceMatrix=false] - true if the dataset is a covariance matrix.
     * @param {string} [options.method='SVD'] - select which method to use: SVD (default), covarianceMatrirx or NIPALS.
     * @param {number} [options.nCompNIPALS=2] - number of components to be computed with NIPALS.
     * @param {boolean} [options.center=true] - should the data be centered (subtract the mean).
     * @param {boolean} [options.scale=false] - should the data be scaled (divide by the standard deviation).
     * @param {boolean} [options.ignoreZeroVariance=false] - ignore columns with zero variance if `scale` is `true`.
     * */
    class PCA {
      constructor(dataset, options = {}, model) {
        /* eslint-disable @typescript-eslint/naming-convention */
        this.U = null;
        this.S = null;
        if (model) {
          this.center = model.center;
          this.scale = model.scale;
          this.means = model.means;
          this.stdevs = model.stdevs;
          this.U = Matrix.checkMatrix(model.U);
          this.S = model.S;
          this.R = model.R;
          this.excludedFeatures = model.excludedFeatures || [];
          return;
        }
        let datasetMatrix;
        if (Array.isArray(dataset)) {
          datasetMatrix = new Matrix(dataset);
        } else {
          datasetMatrix = new Matrix(dataset);
        }
        const {
          isCovarianceMatrix = false,
          method = 'SVD',
          nCompNIPALS = 2,
          center = true,
          scale = false,
          ignoreZeroVariance = false
        } = options;
        this.center = center;
        this.scale = scale;
        this.means = null;
        this.stdevs = null;
        this.excludedFeatures = [];
        if (isCovarianceMatrix) {
          // User provided a covariance matrix instead of dataset.
          this._computeFromCovarianceMatrix(datasetMatrix);
          return;
        }
        this._adjust(datasetMatrix, ignoreZeroVariance);
        switch (method) {
          case 'covarianceMatrix':
            {
              // User provided a dataset but wants us to compute and use the covariance matrix.
              const covarianceMatrix = new MatrixTransposeView(datasetMatrix).mmul(datasetMatrix).div(datasetMatrix.rows - 1);
              this._computeFromCovarianceMatrix(covarianceMatrix);
              break;
            }
          case 'NIPALS':
            {
              this._computeWithNIPALS(datasetMatrix, nCompNIPALS);
              break;
            }
          case 'SVD':
            {
              const svd = new SVD(datasetMatrix, {
                computeLeftSingularVectors: false,
                computeRightSingularVectors: true,
                autoTranspose: true
              });
              this.U = svd.rightSingularVectors;
              const singularValues = svd.diagonal;
              const eigenvalues = [];
              for (const singularValue of singularValues) {
                eigenvalues.push(singularValue * singularValue / (datasetMatrix.rows - 1));
              }
              this.S = eigenvalues;
              break;
            }
          default:
            {
              throw new Error(`unknown method: ${method}`);
            }
        }
      }
      /**
       * Load a PCA model from JSON
       * @param {PCAModel} model
       * @return {PCA}
       */
      static load(model) {
        if (typeof model.name !== 'string') {
          throw new TypeError('model must have a name property');
        }
        if (model.name !== 'PCA') {
          throw new RangeError(`invalid model: ${model.name}`);
        }
        return new PCA(undefined, undefined, model);
      }
      /**
       * Project the dataset into the PCA space
       * @param {MaybeMatrix} dataset
       * @param {PredictOptions} options
       * @return {Matrix} dataset projected in the PCA space
       */
      predict(dataset, options = {}) {
        const {
          nComponents = this.U.columns
        } = options;
        let datasetmatrix;
        if (Array.isArray(dataset)) {
          datasetmatrix = new Matrix(dataset);
        } else {
          datasetmatrix = new Matrix(dataset);
        }
        if (this.center) {
          datasetmatrix.subRowVector(this.means);
          if (this.scale) {
            for (let i of this.excludedFeatures) {
              datasetmatrix.removeColumn(i);
            }
            datasetmatrix.divRowVector(this.stdevs);
          }
        }
        let predictions = datasetmatrix.mmul(this.U);
        return predictions.subMatrix(0, predictions.rows - 1, 0, nComponents - 1);
      }
      /**
       * Calculates the inverse PCA transform
       * @param {Matrix} dataset
       * @return {Matrix} dataset projected in the PCA space
       */
      invert(dataset) {
        dataset = Matrix.checkMatrix(dataset);
        let inverse = dataset.mmul(this.U.transpose());
        if (this.center) {
          if (this.scale) {
            inverse.mulRowVector(this.stdevs);
          }
          inverse.addRowVector(this.means);
        }
        return inverse;
      }
      /**
       * Returns the proportion of variance for each component
       * @return {[number]}
       */
      getExplainedVariance() {
        let sum = 0;
        if (this.S) {
          for (const s of this.S) {
            sum += s;
          }
        }
        if (this.S) {
          return this.S.map(value => value / sum);
        }
        return [];
      }
      /**
       * Returns the cumulative proportion of variance
       * @return {[number]}
       */
      getCumulativeVariance() {
        let explained = this.getExplainedVariance();
        for (let i = 1; i < explained.length; i++) {
          explained[i] += explained[i - 1];
        }
        return explained;
      }
      /**
       * Returns the Eigenvectors of the covariance matrix
       * @returns {Matrix}
       */
      getEigenvectors() {
        return this.U;
      }
      /**
       * Returns the Eigenvalues (on the diagonal)
       * @returns {[number]}
       */
      getEigenvalues() {
        return this.S;
      }
      /**
       * Returns the standard deviations of the principal components
       * @returns {[number]}
       */
      getStandardDeviations() {
        return this.S.map(x => Math.sqrt(x));
      }
      /**
       * Returns the loadings matrix
       * @return {Matrix}
       */
      getLoadings() {
        return this.U.transpose();
      }
      /**
       * Export the current model to a JSON object
       * @return {Object} model
       */
      toJSON() {
        return {
          name: 'PCA',
          center: this.center,
          scale: this.scale,
          means: this.means,
          stdevs: this.stdevs,
          U: this.U,
          S: this.S,
          excludedFeatures: this.excludedFeatures
        };
      }
      _adjust(dataset, ignoreZeroVariance) {
        if (this.center) {
          const mean = dataset.mean('column');
          const stdevs = this.scale ? dataset.standardDeviation('column', {
            mean
          }) : null;
          this.means = mean;
          dataset.subRowVector(mean);
          if (this.scale) {
            for (let i = 0; i < stdevs.length; i++) {
              if (stdevs[i] === 0) {
                if (ignoreZeroVariance) {
                  dataset.removeColumn(i);
                  stdevs.splice(i, 1);
                  this.excludedFeatures.push(i);
                  i--;
                } else {
                  throw new RangeError(`Cannot scale the dataset (standard deviation is zero at index ${i}`);
                }
              }
            }
            this.stdevs = stdevs;
            dataset.divRowVector(stdevs);
          }
        }
      }
      _computeFromCovarianceMatrix(dataset) {
        const evd = new EVD(dataset, {
          assumeSymmetric: true
        });
        this.U = evd.eigenvectorMatrix;
        this.U.flipRows();
        this.S = evd.realEigenvalues;
        this.S.reverse();
      }
      _computeWithNIPALS(dataset, nCompNIPALS) {
        this.U = new Matrix(nCompNIPALS, dataset.columns);
        this.S = [];
        let x = dataset;
        for (let i = 0; i < nCompNIPALS; i++) {
          let dc = new NIPALS(x);
          this.U.setRow(i, dc.w.transpose());
          this.S.push(Math.pow(dc.s.get(0, 0), 2));
          x = dc.xResidual;
        }
        this.U = this.U.transpose(); // to be compatible with API
      }
    }

    /**
     * Estimate the number of pure components of each range by NIPALS PCA.
     * @param chromatogram - GC/MS chromatogram where make the estimation.
     * @param options - Options with range and the matrix.
     */
    function estimateNbPureComponents(chromatogram, options) {
      const {
        range
      } = options;
      let {
        matrix
      } = options;
      if (!matrix) matrix = chromatogram.getMzVsTimesMatrix(range).matrix;
      const pca = new PCA(matrix, {
        method: 'NIPALS',
        nCompNIPALS: 10,
        scale: true,
        ignoreZeroVariance: true
      });
      const s = pca.getExplainedVariance();
      let rank = 1;
      let cumulative = s[0];
      while ((cumulative - s[rank]) / cumulative < 0.88 && rank < s.length) {
        cumulative += s[rank];
        rank++;
      }
      return rank;
    }

    // @ts-expect-error This package is not typed.
    /**
     * Performing non-negative matrix factorization solving argmin_(A >= 0, S >= 0) 1 / 2 * ||Y - AS||_2^2 + lambda * ||S||_1
     * @param chromatogram - GC/MS chromatogram where make the estimation.
     * @param options - Options of ngmca factorization method
     */
    function deconvolution(chromatogram, options) {
      let {
        rank
      } = options;
      const {
        range,
        nmfOptions = {}
      } = options;
      const {
        matrix,
        mzAxis,
        times
      } = chromatogram.getMzVsTimesMatrix(range);
      if (!rank) rank = estimateNbPureComponents(chromatogram, {
        range,
        matrix
      });
      if (rank < 1) {
        throw new RangeError(`Rank should be a positive number for ${range.from} - ${range.to}`);
      }
      const result = nGMCA(matrix, rank, nmfOptions);
      const maxByRow = [];
      for (let i = 0; i < result.S.rows; i++) {
        maxByRow.push(result.S.maxRow(i));
      }
      result.S.scale('row', {
        scale: maxByRow
      });
      result.A.scale('column', {
        scale: maxByRow.map(e => 1 / e)
      });
      return {
        matrix,
        times,
        mzAxis,
        rank,
        profile: result.A,
        component: result.S
      };
    }

    function merge$1(chromatogram, options = {}) {
      const {
        mergeThreshold = 0.3,
        seriesName = 'ms',
        range
      } = options;
      const times = chromatogram.getTimes();
      const series = chromatogram.getSeries2D(seriesName);
      if (range && (range.from > chromatogram.lastTime || range.to < chromatogram.firstTime)) {
        return {
          x: [],
          y: [],
          from: {
            index: -1,
            time: 0
          },
          to: {
            index: -1,
            time: 0
          }
        };
      }
      const {
        fromIndex,
        toIndex
      } = xGetFromToIndex(times, range);
      const data = series.getData().slice(fromIndex, toIndex + 1).map(datum => xySortX({
        x: datum[0],
        y: datum[1]
      }));
      const {
        x,
        y
      } = xyArrayWeightedMerge(data, {
        delta: mergeThreshold
      });
      return {
        x,
        y,
        from: {
          index: fromIndex,
          time: times[fromIndex]
        },
        to: {
          index: toIndex,
          time: times[toIndex]
        }
      };
    }

    /**
     * Updates the system matrix with new weights and computes the weighted right-hand side vector.
     * @param matrix - The lower triangular non-zeros matrix in triplet format.
     * @param y - The original data vector.
     * @param weights - The weights vector.
     * @returns A tuple of the updated matrix and the weighted right-hand side vector.
     */
    function updateSystem(matrix, y, weights) {
      const nbPoints = y.length;
      const l = nbPoints - 1;
      const newMatrix = new Array(matrix.length);
      const newVector = new Float64Array(nbPoints);
      for (let i = 0; i < l; i++) {
        const w = weights[i];
        const diag = i * 2;
        const 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];
    }
    /**
     * Constructs the delta matrix and applies Cuthill-McKee permutation.
     * @param nbPoints - Number of data points.
     * @param lambda - Smoothing parameter.
     * @returns An object containing the lower triangular non-zeros matrix and the permutation array.
     */
    function getDeltaMatrix(nbPoints, lambda) {
      const matrix = [];
      const last = nbPoints - 1;
      for (let i = 0; i < last; i++) {
        matrix.push([i, i, lambda * 2], [i + 1, i, -1 * lambda]);
      }
      matrix[0][2] = lambda;
      matrix.push([last, last, lambda]);
      return {
        lowerTriangularNonZeros: matrix,
        permutationEncodedArray: matrixCuthillMckee(matrix, nbPoints)
      };
    }

    /**
     * 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 {@link https://github.com/zmzhang/airPLS/blob/main/airPLS_manuscript.pdf paper of airPLS}.
     * @param x - X axis data useful when control points or zones are submitted.
     * @param y - Original data.
     * @param options - Options object.
     * @returns An object containing the corrected data, baseline, iteration count, and error.
     */
    function airPLS(x, y, options = {}) {
      const {
        xWork,
        yWork,
        optionsWork,
        shouldDownsample
      } = getDownSampleData(x, y, options);
      const {
        weights,
        controlPoints
      } = getControlPoints(xWork, yWork, optionsWork);
      const {
        maxIterations = 100,
        lambda = 10,
        tolerance = 0.001
      } = optionsWork;
      let baseline = [];
      let iteration;
      let sumNegDifferences = Number.MAX_SAFE_INTEGER;
      const corrected = Float64Array.from(yWork);
      const stopCriterion = getStopCriterion(yWork, tolerance);
      const {
        length
      } = yWork;
      const {
        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++) {
        const [leftHandSide, rightHandSide] = updateSystem(lowerTriangularNonZeros, yWork, weights);
        const cho = matrixCholeskySolver(leftHandSide, length, permutationEncodedArray);
        if (cho === null) {
          throw new Error('Cholesky decomposition failed');
        }
        baseline = cho(rightHandSide);
        sumNegDifferences = applyCorrection(yWork, 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 = Math.abs(corrected[i]);
          if (controlPoints[i] < 1 && diff > threshold * 2) {
            weights[i] = 0;
          } else {
            weights[i] = Math.exp(-((diff / threshold) ** 2));
          }
        }
        weights[0] = 1;
        weights[l] = 1;
      }
      // Interpolate results back to original resolution if downsampling was applied
      let finalBaseline = baseline;
      let finalCorrected = corrected;
      if (shouldDownsample) {
        finalBaseline = xyInterpolateLinear({
          x: xWork,
          y: baseline
        }, x);
        finalCorrected = xSubtract(y, finalBaseline);
      }
      return {
        corrected: finalCorrected,
        baseline: finalBaseline,
        iteration,
        error: sumNegDifferences
      };
    }
    function applyCorrection(y, baseline, corrected) {
      let sumNegDifferences = 0;
      for (let i = 0; i < y.length; i++) {
        const diff = y[i] - baseline[i];
        if (diff < 0) sumNegDifferences += diff;
        corrected[i] = diff;
      }
      return sumNegDifferences;
    }
    function getDownSampleData(x, y, options = {}) {
      const {
        autoDownsample = false,
        maxResolution = 5000,
        controlPoints
      } = options;
      const shouldDownsample = autoDownsample && y.length > maxResolution;
      if (!shouldDownsample) {
        return {
          xWork: x,
          yWork: y,
          optionsWork: options,
          shouldDownsample
        };
      }
      const binSize = getDownsampleFactor(y.length, maxResolution);
      const xWork = xBinning(x, {
        binSize,
        keepFirstAndLast: true
      });
      const yWork = xBinning(y, {
        binSize,
        keepFirstAndLast: true
      });
      let optionsWork = options;
      // Downsample controlPoints if provided, to match downsampled x and y
      if (controlPoints) {
        const downsampledControlPoints = new Int8Array(xWork.length);
        for (let i = 0; i < x.length; i++) {
          if (controlPoints[i] > 0) {
            const closestIndex = xFindClosestIndex(xWork, x[i]);
            downsampledControlPoints[closestIndex] = 1;
          }
        }
        optionsWork = {
          ...options,
          controlPoints: downsampledControlPoints
        };
      }
      return {
        xWork,
        yWork,
        optionsWork,
        shouldDownsample
      };
    }
    function getStopCriterion(y, tolerance) {
      const sum = xAbsoluteSum(y);
      return tolerance * sum;
    }
    function getControlPoints(x, y, options = {}) {
      const {
        length
      } = x;
      const {
        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');
      }
      for (const range of zones) {
        let indexFrom = xFindClosestIndex(x, range.from);
        let indexTo = xFindClosestIndex(x, range.to);
        if (indexFrom > indexTo) [indexFrom, indexTo] = [indexTo, indexFrom];
        for (let i = indexFrom; i < indexTo; i++) {
          controlPoints[i] = 1;
        }
      }
      return {
        weights,
        controlPoints
      };
    }
    /**
     * Calculate the downsampling factor to reduce data to target resolution.
     * @param originalLength - Original data length.
     * @param targetResolution - Target number of points.
     * @returns Downsampling factor.
     */
    function getDownsampleFactor(originalLength, targetResolution) {
      return Math.max(1, Math.ceil(originalLength / targetResolution));
    }

    /**
     * Apply Savitzky Golay algorithm.
     * @param ys - Array of y values.
     * @param xs - Array of X or deltaX.
     * @param options - Options controlling window size, derivative and polynomial order.
     * @returns 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 (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;
    }
    /**
     * Compute the full weights matrix for every position inside the window.
     * @param m - Number of points.
     * @param n - Polynomial grade.
     * @param s - Derivative.
     * @returns Array of Float64Array weight vectors, one per position in the window.
     */
    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;
    }

    /**
     * Compute the `[left, right]` intervals around each peak by locating
     * consecutive extrema (minima / maxima) of the first derivative.
     * @param y - Spectrum y values.
     * @param x - Spectrum x values.
     * @param dY - First derivative of `y`.
     * @param dX - Sign of the x step (positive for increasing x, negative otherwise).
     * @returns `intervalL` / `intervalR` arrays defining each peak's inflection-point bounds.
     */
    function getMinMaxIntervalsDy(y, x, dY, dX) {
      let lastMax = null;
      let lastMin = null;
      const intervalL = [];
      const intervalR = [];
      for (let i = 1; i < y.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);
          }
        }
      }
      return {
        intervalL,
        intervalR
      };
    }

    /**
     * Find the `minData` index closest to the center of one interval,
     * above the intensity threshold and bounded by the half-width.
     * @param options - Interval, candidate indices and spectrum data.
     * @returns The picked candidate (`possible`) and the last visited index (`lastIndex`).
     */
    function tryMatchOneIntervalWithMinData(options) {
      const {
        x,
        lastK,
        minData,
        yThreshold,
        intervalWidth,
        intervalCenter,
        yData
      } = options;
      let minDistance = Number.POSITIVE_INFINITY;
      let possible = -1;
      let newLastIndex = lastK;
      for (let k = newLastIndex + 1; k < minData.length; k++) {
        const centerIndex = minData[k];
        if (yData[centerIndex] <= yThreshold) {
          continue;
        }
        const deltaX = x[centerIndex];
        const currentDistance = Math.abs(deltaX - intervalCenter);
        if (currentDistance < intervalWidth) {
          if (currentDistance < minDistance) {
            possible = k;
          }
          newLastIndex = k;
        }
        if (currentDistance >= minDistance) break;
        minDistance = currentDistance;
      }
      return {
        lastIndex: newLastIndex,
        possible
      };
    }

    /**
     * Peak detection that combines first-derivative zero-crossings and
     * second-derivative local minima to find peaks inside each interval.
     * @param input - Spectrum values together with its first and second derivatives.
     * @returns The detected peaks.
     */
    function autoAlgorithm(input) {
      const {
        x,
        y,
        yData,
        dY,
        ddY,
        dX,
        yThreshold
      } = input;
      const minddY = [];
      const crossDy = [];
      const {
        intervalL,
        intervalR
      } = getMinMaxIntervalsDy(y, x, dY, dX);
      for (let i = 1; i < y.length - 1; ++i) {
        if (dY[i] < 0 && dY[i + 1] > 0 || dY[i] > 0 && dY[i + 1] < 0) {
          // push the index of the element closer to zero
          crossDy.push(Math.abs(dY[i]) < Math.abs(dY[i + 1]) ? i : i + 1);
        }
        // Handle exact zero
        if (dY[i] === 0 && dY[i] < Math.abs(dY[i + 1]) && dY[i] < Math.abs(dY[i - 1])) {
          crossDy.push(i);
        }
        // Minimum in second derivative
        if (ddY[i] < ddY[i - 1] && ddY[i] < ddY[i + 1]) {
          minddY.push(i);
        }
      }
      const peaks = [];
      let lastK = -1;
      let lastJ = -1;
      for (let i = 0; i < intervalL.length; i++) {
        const intervalWidth = (intervalR[i].x - intervalL[i].x) / 2;
        const intervalCenter = (intervalR[i].x + intervalL[i].x) / 2;
        let yIndex = -1;
        let match = tryMatchOneIntervalWithMinData({
          x,
          yData,
          lastK,
          yThreshold,
          intervalWidth,
          intervalCenter,
          minData: crossDy
        });
        lastK = match.lastIndex;
        if (match.possible !== -1) {
          yIndex = crossDy[match.possible];
        } else {
          match = tryMatchOneIntervalWithMinData({
            x,
            yData,
            yThreshold,
            lastK: lastJ,
            intervalWidth,
            intervalCenter,
            minData: minddY
          });
          if (match.possible !== -1) {
            yIndex = minddY[match.possible];
          }
          lastJ = match.lastIndex;
        }
        if (yIndex !== -1) {
          const width = Math.abs(intervalR[i].x - intervalL[i].x);
          peaks.push({
            id: crypto.randomUUID(),
            x: x[yIndex],
            y: y[yIndex],
            width,
            index: yIndex,
            ddY: ddY[yIndex],
            inflectionPoints: {
              from: intervalL[i],
              to: intervalR[i]
            }
          });
        }
      }
      return peaks;
    }

    /**
     * Build the list of detected peaks by matching each `[left, right]` interval
     * with the best candidate index from `minData`.
     * @param options - Intervals, candidate indices and spectrum data.
     * @returns The detected peaks.
     */
    function getPeakFromIntervals(options) {
      let lastK = -1;
      const peaks = [];
      const {
        x,
        ddY,
        yData,
        yThreshold,
        intervalR,
        intervalL,
        minData
      } = options;
      for (let i = 0; i < intervalL.length; i++) {
        const intervalWidth = (intervalR[i].x - intervalL[i].x) / 2;
        const intervalCenter = (intervalR[i].x + intervalL[i].x) / 2;
        const {
          possible,
          lastIndex
        } = tryMatchOneIntervalWithMinData({
          x,
          lastK,
          minData,
          yThreshold,
          intervalWidth,
          intervalCenter,
          yData
        });
        if (possible !== -1) {
          const centerIndex = minData[possible];
          const width = Math.abs(intervalR[i].x - intervalL[i].x);
          peaks.push({
            id: crypto.randomUUID(),
            x: x[centerIndex],
            y: yData[centerIndex],
            width,
            index: centerIndex,
            ddY: ddY[centerIndex],
            inflectionPoints: {
              from: intervalL[i],
              to: intervalR[i]
            }
          });
        }
        lastK = lastIndex;
      }
      return peaks;
    }

    /**
     * Finds the indices where the first derivative crosses zero (sign change),
     * which are potential peak positions. This function does not detect zero-crossings
     * in regions with consecutive zero values in the derivative (flat regions).
     * @param input - Object containing the y values and their first derivative (dY).
     * @returns Array of indices where the first derivative crosses zero (excluding consecutive zeros).
     */
    function xGetCrossZeroPoints(input) {
      const {
        y,
        dY
      } = input;
      const crossDy = [];
      for (let i = 1; i < y.length - 1; ++i) {
        if (isLessAndGreaterThanZero(dY[i], dY[i + 1])) {
          // push the index of the element closer to zero
          crossDy.push(Math.abs(dY[i]) < Math.abs(dY[i + 1]) ? i : i + 1);
        } else if (
        // Handle exact zero
        dY[i] === 0 && isLessAndGreaterThanZero(dY[i - 1], dY[i + 1])) {
          crossDy.push(i);
        }
      }
      return crossDy;
    }
    function isLessAndGreaterThanZero(back, next) {
      return back < 0 && next > 0 || back > 0 && next < 0;
    }

    /**
     * Detect peaks using zero-crossings of the first derivative.
     * @param input - Spectrum values and its first/second derivatives.
     * @returns The detected peaks.
     */
    function firstDerivative(input) {
      const {
        y,
        x,
        dY,
        dX,
        yData,
        yThreshold,
        ddY
      } = input;
      const crossDy = xGetCrossZeroPoints(input);
      const {
        intervalL,
        intervalR
      } = getMinMaxIntervalsDy(y, x, dY, dX);
      return getPeakFromIntervals({
        minData: crossDy,
        intervalL,
        intervalR,
        x,
        yData,
        yThreshold,
        ddY
      });
    }

    /**
     * Detect peaks using local minima of the second derivative (inflection points).
     * @param input - Spectrum values and its first/second derivatives.
     * @returns The detected peaks.
     */
    function secondDerivative(input) {
      const {
        x,
        y,
        yData,
        dY,
        ddY,
        dX,
        yThreshold
      } = input;
      const minddY = [];
      const {
        intervalL,
        intervalR
      } = getMinMaxIntervalsDy(y, x, dY, dX);
      // By the intermediate value theorem We cannot find 2 consecutive maximum or minimum
      for (let i = 1; i < y.length - 1; ++i) {
        // Minimum in second derivative
        if (ddY[i] < ddY[i - 1] && ddY[i] < ddY[i + 1]) {
          minddY.push(i);
        }
      }
      return getPeakFromIntervals({
        minData: minddY,
        intervalL,
        intervalR,
        x,
        yData,
        yThreshold,
        ddY
      });
    }

    /**
     * Refine the `x` and `y` coordinates of each peak by running a quadratic
     * interpolation over the peak and its 3 closest neighbors.
     * The correction is performed in place.
     * @param data - Object with `x` and `y` arrays.
     * @param peaks - Peaks to refine (mutated in place).
     */
    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;
        }
        // Quadratic interpolation on log-intensities to refine the peak top.
        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 = Math.log10(y[currentIndex - 1]);
          const beta = Math.log10(y[currentIndex]);
          const gamma = 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 with `x` and `y` arrays. `x` must be monotone increasing.
     * @param options - Peak detection options.
     * @returns The detected peaks, sorted by ascending `x`.
     */
    function gsd(data, options = {}) {
      let {
        noiseLevel
      } = options;
      const {
        sgOptions = {
          windowSize: 9,
          polynomial: 3
        },
        smoothY = false,
        maxCriteria = true,
        maxAbsoluteRatio = 0,
        minMaxRatio = 0.00025,
        realTopDetection = false,
        peakDetectionAlgorithm = 'second'
      } = options;
      if (maxAbsoluteRatio < 0 || maxAbsoluteRatio > 1) {
        throw new Error('maxAbsoluteRatio must be between 0 and 1');
      }
      const {
        x
      } = data;
      let {
        y
      } = data;
      if (xIsMonotonic(x) !== 1) {
        throw new Error('GSD only accepts monotone increasing x values');
      }
      // Copy so the `maxCriteria` / clipping loops below don't mutate the caller's array.
      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 isEquallySpaced = xIsEquallySpaced(x);
      if (noiseLevel === undefined) {
        if (isEquallySpaced) {
          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] = -y[i];
        }
      }
      const maxAbsoluteValue = maxAbsoluteRatio > 0 ? maxAbsoluteRatio * xMaxAbsoluteValue(y) : noiseLevel;
      if (noiseLevel !== undefined) {
        for (let i = 0; i < y.length; i++) {
          if (y[i] < noiseLevel) {
            y[i] = noiseLevel;
          }
        }
      }
      const xValue = isEquallySpaced ? x[1] - x[0] : x;
      const yData = smoothY ? sgg(y, xValue, {
        ...sgOptions,
        derivative: 0
      }) : y;
      const {
        min: minY,
        max: maxY
      } = xMinMaxValues(yData);
      if (minY > maxY || minY === maxY) return [];
      const dY = sgg(y, xValue, {
        ...sgOptions,
        derivative: 1
      });
      const ddY = sgg(y, xValue, {
        ...sgOptions,
        derivative: 2
      });
      const yThreshold = xMaxValue([noiseLevel, minY + (maxY - minY) * minMaxRatio, maxAbsoluteValue]);
      const dX = x[1] - x[0];
      const peakData = {
        x,
        y,
        yData,
        dY,
        ddY,
        dX,
        yThreshold
      };
      let peaks = [];
      if (peakDetectionAlgorithm === 'first') {
        peaks = firstDerivative(peakData);
      } else if (peakDetectionAlgorithm === 'second') {
        peaks = secondDerivative(peakData);
      } else {
        peaks = autoAlgorithm(peakData);
      }
      if (realTopDetection) {
        optimizeTop({
          x,
          y: yData
        }, peaks);
      }
      for (const peak of peaks) {
        if (!maxCriteria) {
          peak.y *= -1;
          peak.ddY = peak.ddY * -1;
        }
      }
      peaks.sort((a, b) => {
        return a.x - b.x;
      });
      return peaks;
    }

    /**
     * Enlarge peaks while preventing overlap between them.
     * A typical application is chromatography peak picking.
     * We should not make the hypothesis that `x` is equidistant, because peaks
     * may not be symmetric once we add the `from` and `to` properties.
     * @param peakList - Peaks to broaden.
     * @param options - Broadening options.
     * @returns The broadened peaks.
     */
    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);
            shape.fwhm = shapeFct.widthToFWHM(width);
          }
        }
      }
      return peaks;
    }
    function mapPeaks(peaks, factor) {
      return peaks.map(peak => {
        const {
          id,
          shape,
          x,
          y,
          index,
          inflectionPoints
        } = peak;
        const xFrom = x - (x - inflectionPoints.from.x) * factor;
        const xTo = x + (inflectionPoints.to.x - x) * factor;
        let result = {
          x,
          y,
          index,
          width: xTo - xFrom,
          from: {
            x: xFrom
          },
          to: {
            x: xTo
          }
        };
        if (id) {
          result = {
            ...result,
            id
          };
        }
        if (shape) {
          result = {
            ...result,
            shape
          };
        }
        return result;
      });
    }

    /**
     * Returns the result of a peak picking in the chromatogram
     *
     * Make a peak picking on a chromatogram is not obvious because the baseline is often not close to 0
     * and it is therefore difficult to filter by height.
     * We will therefore consider as height of a peak 2 times the height between the top and the middle
     * of the inflection points.
     *
     * In order to calculate the noise the same problem occurs, baseline is not horizontal.
     * We therefore calculate the median absolute deviation after baseline correction using airpls.
     * This noise will be used to filter the peak based on the 'heightFilter'.
     * @param chromatogram
     * @param options
     * @returns
     */
    function getPeaks(chromatogram, options = {}) {
      const {
        heightFilter = 5,
        seriesName = 'tic',
        broadenPeaksOptions = {
          factor: 1,
          overlap: false
        }
      } = options;
      const series = chromatogram.getSeries1D(seriesName).getData();
      const times = chromatogram.getTimes();
      const dataXY = {
        x: times,
        y: series
      };
      // first peak selection
      let peakList = gsd(dataXY, {
        noiseLevel: 0,
        realTopDetection: false,
        smoothY: true,
        sgOptions: {
          windowSize: 5,
          polynomial: 2
        }
      });
      const noiseHeight = xMedianAbsoluteDeviation(airPLS(dataXY.x, dataXY.y).corrected).mad * heightFilter;
      peakList = peakList.filter(peak => (peak.y - (dataXY.y[peak.inflectionPoints.from.index] + dataXY.y[peak.inflectionPoints.to.index]) / 2) * 2 > noiseHeight);
      peakList.sort((a, b) => a.x - b.x);
      const broadenPeaksList = broadenPeaks(peakList, broadenPeaksOptions);
      return broadenPeaksList.map(peak => ({
        from: peak.from.x,
        to: peak.to.x,
        width: peak.width,
        retentionTime: peak.x,
        intensity: peak.y
      }));
    }

    function filter(chromatogram, callback, options = {}) {
      const {
        copy = false
      } = options;
      if (copy) {
        chromatogram = chromatogram.copy();
      }
      const times = chromatogram.getTimes();
      const newTimes = [];
      const indexToKeep = [];
      for (let i = 0; i < times.length; i++) {
        if (callback(i, times[i])) {
          indexToKeep.push(i);
          newTimes.push(times[i]);
        }
      }
      chromatogram.times = newTimes;
      for (const key of chromatogram.getSeriesNames()) {
        const series = chromatogram.getSeries(key);
        series.keep(indexToKeep);
      }
      return chromatogram;
    }

    function getClosestData(chromatogram, time, options = {}) {
      const {
        seriesName = 'ms'
      } = options;
      chromatogram.requiresSeries(seriesName);
      const closest = chromatogram.getClosestTime(time);
      return {
        rt: chromatogram.getTimes()[closest],
        index: closest,
        data: chromatogram.getSeries(seriesName).getData()[closest]
      };
    }

    /**
     * Return the submatrix, times, and mass X axis for each range.
     * @param chromatogram
     * @param range - from - to of the TIC
     * @returns submatrix, times and m/z axis of the range.
     */
    function getMzVsTimesMatrix(chromatogram, range) {
      const {
        from,
        to
      } = range;
      const fromIndex = chromatogram.getClosestTime(from);
      const toIndex = chromatogram.getClosestTime(to);
      const msSeries = chromatogram.getSeries2D('ms');
      const data = msSeries.getData().slice(fromIndex, toIndex);
      const times = chromatogram.times.slice(fromIndex, toIndex);
      const mzAxisSet = new Set();
      for (const spectrum of data) {
        for (const value of spectrum[0]) {
          mzAxisSet.add(Math.round(value));
        }
      }
      const mzAxis = Array.from(mzAxisSet);
      mzAxis.sort((a, b) => a - b);
      const nbPoints = mzAxis.length;
      const matrix = new Matrix(data.length, nbPoints);
      for (let i = 0; i < data.length; i++) {
        const element = data[i];
        for (let j = 0; j < element[0].length; j++) {
          const xValue = Math.round(element[0][j]);
          const index = xFindClosestIndex(mzAxis, xValue);
          matrix.set(i, index, element[1][j]);
        }
      }
      return {
        times,
        mzAxis,
        matrix
      };
    }

    function baselineCorrection(points, fromTo, kind) {
      const deltaTime = points.x[fromTo.toIndex] - points.x[fromTo.fromIndex];
      const fromHeight = points.y[fromTo.fromIndex];
      const toHeight = points.y[fromTo.toIndex];
      let baseline = 0;
      let from = 0;
      let to = 0;
      switch (kind) {
        case 'trapezoid':
          baseline = deltaTime * (fromHeight + toHeight) / 2;
          from = fromHeight;
          to = toHeight;
          break;
        case 'min':
          from = Math.min(fromHeight, toHeight);
          to = from;
          baseline = deltaTime * from;
          break;
        default:
          throw new Error(`Unknown baseline method "${String(kind)}"`);
      }
      return {
        value: baseline,
        from,
        to
      };
    }

    function integrate(chromatogram, ranges, options = {}) {
      const {
        baseline,
        seriesName = 'tic'
      } = options;
      if (!Array.isArray(ranges)) {
        throw new Error('Ranges must be an array of type [{from,to}]');
      }
      if (ranges.length === 0) {
        return [];
      }
      const series = chromatogram.getSeries1D(seriesName);
      const time = chromatogram.getTimes();
      const results = [];
      for (const range of ranges) {
        const fromTo = xGetFromToIndex(time, range);
        const integral = integrateRange({
          x: time,
          y: series.getData()
        }, fromTo, baseline);
        results.push(integral);
      }
      return results;
    }
    function integrateRange(points, fromTo, baseline) {
      const integration = xyIntegration(points, fromTo);
      if (baseline) {
        const correction = baselineCorrection(points, fromTo, baseline);
        return {
          integration: integration - correction.value,
          from: {
            time: points.x[fromTo.fromIndex],
            index: fromTo.fromIndex,
            baseline: correction.from
          },
          to: {
            time: points.x[fromTo.toIndex],
            index: fromTo.toIndex,
            baseline: correction.to
          }
        };
      } else {
        return {
          integration,
          from: {
            time: points.x[fromTo.fromIndex],
            index: fromTo.fromIndex,
            baseline: 0
          },
          to: {
            time: points.x[fromTo.toIndex],
            index: fromTo.toIndex,
            baseline: 0
          }
        };
      }
    }

    class Chromatogram {
      times;
      series;
      meta;
      /**
       * Construct a new chromatogram.
       * @param times - Array of time points.
       * @param series - Dictionary of series.
       * @param options
       */
      constructor(times, series, options = {}) {
        this.meta = options.meta || {};
        this.series = {};
        xCheck(times);
        this.times = times;
        if (series) {
          for (const [name, value] of Object.entries(series)) {
            this.addSeries(name, value);
          }
        }
      }
      /**
       * Returns the number of time points.
       */
      get length() {
        return this.times.length;
      }
      /**
       * Find the series from its name.
       * @param seriesName - Name of the series.
       */
      getSeries(seriesName) {
        this.requiresSeries(seriesName);
        return this.series[seriesName];
      }
      /**
       * Find the series from its name.
       * Throws an error if it is not a 1D series.
       * @param seriesName - Name of the series.
       */
      getSeries1D(seriesName) {
        const series = this.getSeries(seriesName);
        if (!series.is1D()) {
          throw new Error(`series "${seriesName}" is not a 1D series`);
        }
        return series;
      }
      /**
       * Find the series from its name.
       * Throws an error if it is not a 2D series.
       * @param seriesName - Name of the series.
       */
      getSeries2D(seriesName) {
        const series = this.getSeries(seriesName);
        if (!series.is2D()) {
          throw new Error(`series "${seriesName}" is not a 2D series`);
        }
        return series;
      }
      /**
       * Returns the names of all series.
       */
      getSeriesNames() {
        return Object.keys(this.series);
      }
      /**
       * Returns whether the chromatogram has an "ms" series.
       */
      hasMass() {
        return this.hasSeries('ms');
      }
      /**
       * Delete a series.
       */
      deleteSeries(seriesName) {
        this.requiresSeries(seriesName);
        // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
        delete this.series[seriesName];
        return this;
      }
      /**
       * Add a new series.
       * @param seriesName - Name of the series to add.
       * @param data - Series data.
       * @param options
       */
      addSeries(seriesName, data, options = {}) {
        if (this.hasSeries(seriesName) && !options.force) {
          throw new Error(`A series with name "${seriesName}" already exists`);
        }
        if (this.times.length !== data.length) {
          throw new Error(`The series size is not the same as the times size`);
        }
        const series = seriesFromArray(data, options);
        this.series[seriesName] = series;
        series.name = seriesName;
        return this;
      }
      /**
       * Returns whether the series exists.
       * @param seriesName - Name of the series to check.
       */
      hasSeries(seriesName) {
        return this.series[seriesName] !== undefined;
      }
      /**
       * Throws an error if the series does not exist.
       * @param seriesName - Name of the series to check.
       */
      requiresSeries(seriesName) {
        if (!this.hasSeries(seriesName)) {
          throw new Error(`The series "${seriesName}" does not exist`);
        }
      }
      /**
       * Returns the first time value.
       */
      get firstTime() {
        return this.times[0];
      }
      /**
       * Returns the last time value.
       */
      get lastTime() {
        return this.times[this.length - 1];
      }
      /**
       * Returns the time values.
       */
      getTimes() {
        return this.times;
      }
      /**
       * Assign the time values.
       * @param times - New time values.
       */
      setTimes(times) {
        if (times.length !== this.times.length) {
          throw new Error('New times must have the same length as the old ones');
        }
        this.times = times;
        return this;
      }
      /**
       * Modifies the time applying the conversion function.
       * @param conversionFunction
       */
      rescaleTime(conversionFunction) {
        this.times = this.times.map(time => conversionFunction(time));
        return this;
      }
      /**
       * Will filter the entries based on the index and time.
       * @param callback
       * @param options
       */
      filter(callback, options) {
        return filter(this, callback, options);
      }
      /**
       * Apply the GSD peak picking algorithm.
       * @param options
       * @returns - List of GSD objects
       */
      getPeaks(options) {
        return getPeaks(this, options);
      }
      /**
       * Calculate the "tic" series from the "ms" one.
       */
      calculateTic(options = {}) {
        if (!this.hasSeries('tic') || options.force) {
          const tic = calculateTic(this);
          this.addSeries('tic', tic, {
            force: true
          });
        }
        return this;
      }
      /**
       * Calculate length (number of points of each related information) and save it in the "length" series.
       * @param seriesName - Name of the series to make calculation on. It must be a 2D series.
       * @param options
       */
      calculateLength(seriesName, options = {}) {
        if (!this.hasSeries('length') || options.force) {
          const length = calculateLength(this, seriesName);
          this.addSeries('length', length, {
            force: true
          });
        }
        return this;
      }
      /**
       * Calculate the base peak chromatogram (BPC) and save it in the "bpc" series.
       * @param options
       */
      calculateBpc(options = {}) {
        if (!this.hasSeries('bpc') || options.force) {
          const bpc = calculateBpc(this);
          this.addSeries('bpc', bpc, {
            force: true
          });
        }
        return this;
      }
      /**
       * Calculate the extracted ion chromatogram (EIC) and save it in a new series.
       * @param targetMass - Mass or masses for which to extract the spectrum.
       * @param options
       * @returns - The created series.
       */
      calculateEic(targetMass, options = {}) {
        const {
          slotWidth = 1
        } = options;
        const {
          seriesName = `ms${String(targetMass)}±${slotWidth / 2 || 0.5}`,
          cache = false
        } = options;
        if (cache && this.hasSeries(seriesName)) {
          return this.getSeries1D(seriesName);
        }
        const result = calculateEic(this, targetMass, options);
        this.addSeries(seriesName, result, options);
        return this.getSeries1D(seriesName);
      }
      /**
       * Calculate mass spectrum by filtering for a molecular formula.
       * @param targetMF - MF for which to extract the spectrum.
       * @param options
       * @returns - The created series.
       */
      async calculateForMF(targetMF, options = {}) {
        const {
          slotWidth = 1
        } = options;
        const {
          seriesName = `${targetMF}(${options.ionizations || 'H+'})±${slotWidth / 2 || 0.5}${options.threshold ? `(${options.threshold})` : ''}`,
          cache = false
        } = options;
        if (cache && this.hasSeries(seriesName)) {
          return this.getSeries1D(seriesName);
        }
        const result = await calculateForMF(this, targetMF, options);
        this.addSeries(seriesName, result, options);
        return this.getSeries1D(seriesName);
      }
      /**
       * Returns an array containing the integral of various ranges
       * @param ranges
       * @param options
       */
      integrate(ranges, options) {
        return integrate(this, ranges, options);
      }
      /**
       * Returns a mass spectrum corresponding to the merge of a range.
       * @param options
       */
      merge(options) {
        return merge$1(this, options);
      }
      /**
       * Returns the index of the closest real time to the specified one.
       * @param time - Retention time.
       * @returns - Time index.
       */
      getClosestTime(time) {
        return xFindClosestIndex(this.getTimes(), time);
      }
      /**
       * Returns the closest mass spectrum to a specific retention time
       * @param time - Retention time
       * @param options
       */
      getClosestData(time, options) {
        return getClosestData(this, time, options);
      }
      /**
       * Returns a copy of the chromatogram.
       */
      copy() {
        const json = this.toJSON();
        return fromJSON(json);
      }
      /**
       * Filter the given 2D series based on its median value.
       * @param seriesName - Name of the series to filter.
       * @param options
       */
      meanFilter(seriesName, options = {}) {
        const {
          seriesName: newSeriesName = 'msMean'
        } = options;
        if (this.hasSeries(newSeriesName) && !options.force) {
          throw new Error(`A series with name "${seriesName}" already exists`);
        }
        const newSeries = meanFilter(this, seriesName, options);
        this.series[newSeriesName] = newSeries;
        return newSeries;
      }
      /**
       * Filter the given 2D series based on the percentage of the highest value
       * @param seriesName - Name of the series to filter.
       * @param options
       */
      percentageFilter(seriesName, options = {}) {
        const {
          seriesName: newSeriesName = 'msPercentage'
        } = options;
        if (this.hasSeries(newSeriesName) && !options.force) {
          throw new Error(`A series with name "${seriesName}" already exists`);
        }
        const newSeries = percentageFilter(this, seriesName, options);
        this.series[newSeriesName] = newSeries;
        return newSeries;
      }
      /**
       * Recalculates series for GC/MS with lock mass.
       * @param mfs - Reference molecular formula(s).
       * @param options
       */
      applyLockMass(mfs, options) {
        return applyLockMass(this, mfs, options);
      }
      /**
       * Return the submatrix, times, and mass x axis for each range
       */
      getMzVsTimesMatrix(range) {
        return getMzVsTimesMatrix(this, range);
      }
      /**
       * Performing non-negative matrix factorization solving
       * argmin_(A >= 0, S >= 0) 1 / 2 * ||Y - AS||_2^2 + lambda * ||S||_1
       */
      deconvolution(options) {
        return deconvolution(this, options);
      }
      /**
       * Returns a serializable representation of the chromatogram.
       */
      toJSON() {
        return {
          times: Array.from(this.times),
          series: Object.fromEntries(Object.entries(this.series).map(([name, series]) => [name, series.toJSON()]))
        };
      }
    }
    /**
     * Create a Chromatogram from a JSON representation.
     * @param json
     */
    function fromJSON(json) {
      const {
        series,
        times,
        meta = {}
      } = json;
      const chromatogram = new Chromatogram(times, undefined, {
        meta
      });
      if (Array.isArray(series)) {
        for (const serie of series) {
          chromatogram.addSeries(serie.name, serie.data, {
            meta: serie.meta
          });
        }
      } else {
        for (const key of Object.keys(series)) {
          chromatogram.addSeries(key, series[key].data, {
            meta: series[key].meta
          });
        }
      }
      return chromatogram;
    }

    /**
     * Append MS spectra to a list of peaks
     * @param chromatogram
     * @param peaks - Array of range {from:, to:}
     * @param options
     * @returns A copy of ranges with ms appended
     */
    function appendMass(chromatogram, peaks, options = {}) {
      const {
        mergeThreshold = 0.3,
        seriesName = 'ms'
      } = options;
      const result = [];
      // integrate MS
      for (const peak of peaks) {
        const massSpectrum = merge$1(chromatogram, {
          mergeThreshold,
          seriesName,
          range: peak
        });
        result.push({
          ...peak,
          ms: massSpectrum
        });
      }
      return result;
    }

    /**
     * Filters a mass object
     * @param massXYObject - Mass spectrum to filter
     * @param options
     * @returns Object with filtered x and y data.
     */
    function massFilter(massXYObject, options = {}) {
      const {
        thresholdFactor = 0,
        maxNumberPeaks = Number.MAX_VALUE,
        groupWidth = 0
      } = options;
      let max = -1;
      const massList = new Array(massXYObject.x.length);
      for (let i = 0; i < massXYObject.x.length; ++i) {
        massList[i] = {
          x: massXYObject.x[i],
          y: massXYObject.y[i]
        };
        if (massXYObject.y[i] > max) {
          max = massXYObject.y[i];
        }
      }
      // filters based in thresholdFactor
      max *= thresholdFactor;
      let filteredList = massList.filter(val => val.y > max);
      // filters based in maxNumberPeaks
      if (filteredList.length > maxNumberPeaks || groupWidth !== 0) {
        filteredList.sort((a, b) => b.y - a.y);
        // filters based in groupWidth
        filteredList = moreDistinct(filteredList, maxNumberPeaks, groupWidth);
        filteredList.sort((a, b) => a.x - b.x);
      }
      const ans = {
        x: new Array(filteredList.length),
        y: new Array(filteredList.length)
      };
      for (let i = 0; i < filteredList.length; ++i) {
        ans.x[i] = filteredList[i].x;
        ans.y[i] = filteredList[i].y;
      }
      return ans;
    }
    /**
     * Filters based on groupWidth.
     * @param list - Sorted list of XY-objects to be filtered
     * @param maxNumberPeaks - Maximum number of peaks for each mass spectra
     * @param groupWidth - When find a max can't be another max in a radius of this size
     * @returns List of XY-objects filtered
     */
    function moreDistinct(list, maxNumberPeaks, groupWidth) {
      let len = 0;
      if (maxNumberPeaks > list.length) {
        maxNumberPeaks = list.length;
      }
      const filteredList = new Array(maxNumberPeaks);
      for (let i = 0; i < list.length && len < maxNumberPeaks; ++i) {
        let outRange = true;
        for (let j = 0; j < len && outRange; ++j) {
          outRange = outRange && !(list[i].x > filteredList[j].x - groupWidth && list[i].x < filteredList[j].x + groupWidth);
        }
        if (outRange) {
          filteredList[len++] = list[i];
        }
      }
      filteredList.length = len;
      return filteredList;
    }

    /**
     * Given a list of ranges with ms, returns the weighted mass times abundance.
     * @param ranges
     * @param options
     * @returns List of mass and weighted mass times abundance objects
     */
    function vectorify(ranges, options = {}) {
      const {
        massPower = 3,
        intPower = 0.6
      } = options;
      const filter = options.thresholdFactor || options.maxNumberPeaks || options.groupWidth;
      const vector = new Array(ranges.length);
      if (filter) {
        const filterOptions = {
          thresholdFactor: options.thresholdFactor,
          maxNumberPeaks: options.maxNumberPeaks,
          groupWidth: options.groupWidth
        };
        for (let i = 0; i < ranges.length; ++i) {
          const len = ranges[i].ms.x.length;
          vector[i] = {
            x: ranges[i].ms.x,
            y: new Array(len)
          };
          for (let j = 0; j < len; ++j) {
            vector[i].y[j] = ranges[i].ms.x[j] ** massPower * ranges[i].ms.y[j] ** intPower;
          }
          vector[i] = massFilter(vector[i], filterOptions);
        }
      } else {
        for (let i = 0; i < ranges.length; ++i) {
          const len = ranges[i].ms.x.length;
          vector[i] = {
            x: ranges[i].ms.x,
            y: new Array(len)
          };
          for (let j = 0; j < len; ++j) {
            vector[i].y[j] = ranges[i].ms.x[j] ** massPower * ranges[i].ms.y[j] ** intPower;
          }
        }
      }
      return vector;
    }

    /**
     * Cosine similarity between two MS spectra.
     * This algorithm is optimized for missing mass values.
     * @param ms1x - Array of mass values for the first spectra
     * @param ms1y - Array of weighted abundance values for the first spectra
     * @param ms2x - Array of mass values for the second spectra
     * @param ms2y - Array of weighted abundance values for the second spectra
     * @returns Similarity between two MS spectra.
     */
    function cosineSimilarity(ms1x, ms1y, ms2x, ms2y) {
      let index1 = 0;
      let index2 = 0;
      let product = 0;
      let norm1 = 0;
      let norm2 = 0;
      while (index1 < ms1x.length || index2 < ms2x.length) {
        const w1 = ms1y[index1];
        const w2 = ms2y[index2];
        if (index2 === ms2x.length || ms1x[index1] < ms2x[index2]) {
          norm1 += w1 * w1;
          ++index1;
        } else if (index1 === ms1x.length || ms2x[index2] < ms1x[index1]) {
          norm2 += w2 * w2;
          ++index2;
        } else {
          product += w1 * w2;
          norm1 += w1 * w1;
          norm2 += w2 * w2;
          ++index1;
          ++index2;
        }
      }
      const norm1Norm2 = norm1 * norm2;
      if (norm1Norm2 === 0) {
        return 0;
      } else {
        return product * product / norm1Norm2;
      }
    }

    /**
     * Preprocessing task over the signalsj
     * @param chromatogram - Chromatogram to process
     * @param options - Options object (same as spectraComparison)
     * @returns Array of peaks, integrated mass spectra and weighted mass spectra
     */
    function preprocessing(chromatogram, options) {
      // peak picking
      let peaks = getPeaks(chromatogram, options);
      peaks = peaks.sort((a, b) => a.from - b.to);
      // integrate mass in the peaks
      const integratedMs = appendMass(chromatogram, peaks);
      const vector = vectorify(integratedMs, options);
      return {
        peaks,
        integratedMs,
        vector
      };
    }
    const defaultOptions$1 = {
      thresholdFactor: 0,
      maxNumberPeaks: Number.MAX_VALUE,
      groupWidth: 0,
      heightFilter: 2,
      massPower: 3,
      intPower: 0.6
    };
    /**
     * Returns the most similar peaks between two GC/MS and their similarities.
     * @returns Most similar peaks and their similarities.
     */
    function spectraComparison(chrom1, chrom2, options = {}) {
      const {
        similarityThreshold = 0.7
      } = options;
      options = {
        ...defaultOptions$1,
        ...options
      };
      // peak picking
      const reference = preprocessing(chrom1, options);
      const sample = preprocessing(chrom2, options);
      // similarity between MS
      const len = Math.max(sample.peaks.length, reference.peaks.length);
      const similarityPeaks = {
        chrom1: new Array(len),
        chrom2: new Array(len),
        similarity: new Array(len)
      };
      let similarLen = 0;
      // Similarity matrix
      for (let i = 0; i < sample.peaks.length; ++i) {
        let max = {
          similarity: -3,
          chrom1: reference.peaks[0],
          chrom2: sample.peaks[0]
        };
        let biggerCounter = 0;
        for (let j = 0; j < reference.peaks.length; ++j) {
          const sim = cosineSimilarity(sample.vector[i].x, sample.vector[i].y, reference.vector[j].x, reference.vector[j].y);
          if (sim > similarityThreshold && sim > max.similarity) {
            max = {
              similarity: sim,
              chrom1: reference.peaks[j],
              chrom2: sample.peaks[i]
            };
          }
          if (sim > similarityThreshold) {
            ++biggerCounter;
          }
        }
        if (biggerCounter === 1) {
          similarityPeaks.chrom1[similarLen] = max.chrom1;
          similarityPeaks.chrom2[similarLen] = max.chrom2;
          similarityPeaks.similarity[similarLen++] = max.similarity;
        }
      }
      similarityPeaks.chrom1.length = similarLen;
      similarityPeaks.chrom2.length = similarLen;
      const duplicates = {};
      for (let i = 0; i < similarLen; ++i) {
        if (duplicates[similarityPeaks.chrom1[i].retentionTime]) {
          duplicates[similarityPeaks.chrom1[i].retentionTime].push(i);
        } else {
          duplicates[similarityPeaks.chrom1[i].retentionTime] = [i];
        }
      }
      const peaksChrom1 = [];
      const peaksChrom2 = [];
      const peaksSimilarity = [];
      for (const val in duplicates) {
        if (duplicates[val].length === 1) {
          peaksChrom1.push(similarityPeaks.chrom1[duplicates[val][0]]);
          peaksChrom2.push(similarityPeaks.chrom2[duplicates[val][0]]);
          peaksSimilarity.push(similarityPeaks.similarity[duplicates[val][0]]);
        }
      }
      return {
        peaksFirst: peaksChrom1,
        peaksSecond: peaksChrom2,
        peaksSimilarity
      };
    }

    /**
     * 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 {
      degree;
      powers;
      coefficients;
      /**
       * Build a polynomial regression model from the given data set.
       * @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.
       * @param options - regression options
       * @example `new PolynomialRegression(x, y, 2)`, in this case, you can pass the option `interceptAtZero`, if you need it.
       * @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])`
       */
      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(x, y, degree, options);
          this.degree = result.degree;
          this.powers = result.powers;
          this.coefficients = result.coefficients;
        }
      }
      _predict(x) {
        const {
          coefficients,
          powers
        } = this;
        let y = 0;
        for (let k = 0; k < powers.length; k++) {
          const coefficient = coefficients[k] ?? 0;
          const power = powers[k] ?? 0;
          y += coefficient * x ** power;
        }
        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 = '';
          const coefficient = this.coefficients[k] ?? 0;
          const power = this.powers[k] ?? 0;
          if (coefficient !== 0) {
            if (power === 0) {
              str = maybeToPrecision(coefficient, precision);
            } else if (power === 1) {
              str = `${maybeToPrecision(coefficient, precision) + times}x`;
            } else {
              str = `${maybeToPrecision(coefficient, precision) + times}x${sup}${power}${closeSup}`;
            }
            if (coefficient > 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 - regression options
     * @returns degree, powers and coefficients of the fitted polynomial
     */
    function regress(x, y, degree, options = {}) {
      const n = x.length;
      let {
        interceptAtZero = false
      } = options;
      let powers = [];
      if (Array.isArray(degree)) {
        powers = degree;
        interceptAtZero = false;
      } 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;
      const F = new Matrix(n, nCoefficients);
      const Y = new Matrix([Array.from(y)]);
      for (let k = 0; k < nCoefficients; k++) {
        const power = powers[k] ?? 0;
        for (let i = 0; i < n; i++) {
          if (power === 0) {
            F.set(i, k, 1);
          } else {
            F.set(i, k, (x[i] ?? 0) ** power);
          }
        }
      }
      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
      };
    }

    /**
     * Aligns the time of the sample based on the regression with his reference value
     * @param reference - Array of peaks, integrated mass spectra and weighted mass spectra for the reference chromatogram
     * @param sample - Array of peaks, integrated mass spectra and weighted mass spectra for the sample chromatogram
     * @param options
     * @returns The scaled spectra.
     * * `scaleRegression`: The regression function to make the regression
     * * `stringFormula`: Regression equation
     * * `r2`: R2 quality number
     * * `error`: Vector of the difference between the spected value and the actual shift value
     */
    function scaleAlignment(reference, sample, options = {}) {
      const {
        computeQuality = false,
        polynomialDegree = 3
      } = options;
      const referenceTime = reference.map(val => val.retentionTime);
      const sampleTime = sample.map(val => val.retentionTime);
      const regression = new PolynomialRegression(sampleTime, referenceTime, polynomialDegree);
      const error = [];
      for (let i = 0; i < sample.length; i++) {
        error.push(reference[i].retentionTime - regression.predict(sample[i].retentionTime));
      }
      const ans = {
        scaleRegression: regression,
        r2: -1,
        error: []
      };
      if (computeQuality) {
        const score = regression.score(sampleTime, referenceTime);
        ans.r2 = score.r2;
        ans.error = error;
      }
      return ans;
    }

    /**
     * Calculates the Kovats retention index for a mass spectra of a n-alkane
     * @param ms - A mass spectra object
     * @param options
     * @returns Kovats retention index
     */
    function kovats(ms, options = {}) {
      const {
        threshold = 0.01
      } = options;
      // we normalize the data and filter them
      const maxY = xMaxValue(ms.y);
      const masses = [];
      const intensities = [];
      for (let i = 0; i < ms.x.length; i++) {
        if (ms.y[i] / maxY > threshold) {
          masses.push(ms.x[i]);
          intensities.push(ms.y[i] / maxY);
        }
      }
      // we find candidates
      const nbAlcaneMasses = [];
      let fragmentMasses = [];
      for (const mass of masses) {
        if ((mass - 2) % 14 === 0) {
          nbAlcaneMasses.push(mass);
        }
        if ((mass - 1) % 14 === 0) {
          fragmentMasses.push(mass);
        }
      }
      if (nbAlcaneMasses.length === 0) return null;
      const biggestMass = nbAlcaneMasses.sort((a, b) => b - a)[0];
      fragmentMasses = fragmentMasses.filter(mass => mass < biggestMass);
      return {
        index: 100 * (biggestMass - 2) / 14,
        numberFragments: fragmentMasses.length,
        percentFragments: fragmentMasses.length / ((biggestMass - 2) / 14)
      };
    }

    function appendKovats(peaks) {
      const result = [];
      for (const peak of peaks) {
        result.push({
          ...peak,
          kovats: kovats(peak.ms)
        });
      }
      return result;
    }

    var binarySearch = function (haystack, needle, comparator, low, high) {
      var mid, cmp;
      if (low === undefined) low = 0;else {
        low = low | 0;
        if (low < 0 || low >= haystack.length) throw new RangeError("invalid lower bound");
      }
      if (high === undefined) high = haystack.length - 1;else {
        high = high | 0;
        if (high < low || high >= haystack.length) throw new RangeError("invalid upper bound");
      }
      while (low <= high) {
        // The naive `low + high >>> 1` could fail for array lengths > 2**31
        // because `>>>` converts its operands to int32. `low + (high - low >>> 1)`
        // works for array lengths <= 2**32-1 which is also Javascript's max array
        // length.
        mid = low + (high - low >>> 1);
        cmp = +comparator(haystack[mid], needle, mid, haystack);

        // Too low.
        if (cmp < 0.0) low = mid + 1;

        // Too high.
        else if (cmp > 0.0) high = mid - 1;

        // Key found.
        else return mid;
      }

      // Key not found.
      return ~low;
    };
    var binarySearch$1 = /*@__PURE__*/getDefaultExportFromCjs(binarySearch);

    const ascIndex = (a, b) => a.index - b.index;
    const ascTime = (a, b) => a.time - b.time;
    /**
     * Returns a function that can convert from time to Kovats or from Kovats to time.
     * @param peaks - List of time-kovats reference peaks.
     * @param options
     */
    function getKovatsConversionFunction(peaks, options = {}) {
      const {
        revert = false
      } = options;
      const kovatsConversionTable = peaks.map(peak => ({
        time: peak.x,
        index: peak.kovats.index
      }));
      if (revert) {
        const indexes = kovatsConversionTable.sort(ascIndex);
        return index => {
          let position = binarySearch$1(indexes, {
            index
          }, ascIndex);
          if (position < 0) {
            position = ~position;
            // handle extreme cases
            if (position === 0 || position === indexes.length) {
              return 0;
            }
            const smallerAlcane = indexes[position - 1].time;
            const largerAlcane = indexes[position].time;
            return (index - indexes[position - 1].index) * (largerAlcane - smallerAlcane) / 100 + smallerAlcane;
          } else {
            return indexes[position].time;
          }
        };
      } else {
        const times = kovatsConversionTable.sort(ascTime);
        return time => {
          let position = binarySearch$1(times, {
            time
          }, ascTime);
          if (position < 0) {
            position = ~position;
            // handle extreme cases
            if (position === 0 || position === times.length) {
              return 0;
            }
            const smallerAlcane = times[position - 1].time;
            const largerAlcane = times[position].time;
            return 100 * (time - smallerAlcane) / (largerAlcane - smallerAlcane) + times[position - 1].index;
          } else {
            return times[position].index;
          }
        };
      }
    }

    /**
     * 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) {
        if (options.encoding) {
          return new TextDecoder(options.encoding).decode(blob);
        } else {
          return decodeText(blob);
        }
      }
      throw new TypeError(`blob must be a string, ArrayBuffer or ArrayBufferView`);
    }
    function decodeText(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 new TextDecoder('utf-16be').decode(uint8);
        }
        if (uint8[0] === 0xff && uint8[1] === 0xfe) {
          return new TextDecoder('utf-16le').decode(uint8);
        }
      }
      try {
        return new TextDecoder('utf-8', {
          fatal: true
        }).decode(uint8);
      } catch {
        return new TextDecoder('latin1').decode(uint8);
      }
    }

    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]));

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

    /**
     * Creates a new Chromatogram from a JCAMP string.
     * @param jcamp - String containing the JCAMP data
     */
    function fromJcamp(jcamp) {
      const data = pt(jcamp, {
        chromatogram: true
      }).flatten[0].chromatogram;
      return fromJSON(data);
    }

    /**
     * General internal parsing function
     * @param text - Csv or tsv strings.
     * @param options - Parsing options
     * @returns parsed text file with column information
     */
    function parse$1(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, please 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(/[\n\r]+/);
      let matrix = [];
      const info = [];
      let position = 0;
      for (let line of lines) {
        line = line.trim();
        // we will consider only lines that contains only numbers
        if (/\d+/.test(line) && /^[\d\t +,.;Ee-]+$/.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 => Number.parseFloat(value.replace(',', '.'))));
            position++;
          }
        } else if (line) {
          info.push({
            position,
            value: line
          });
        }
      }
      if (bestGuess) {
        if (matrix[0]?.length === 3 && options.xColumn === undefined && options.yColumn === undefined) {
          // is the first column a sequential number?
          let skipFirstColumn = true;
          for (let i = 0; i < matrix.length - 1; i++) {
            const currentFirst = matrix[i][0];
            const nextFirst = matrix[i + 1][0];
            if (Math.abs(currentFirst - nextFirst) !== 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);
        const yValues = result.y;
        for (let i = 0; i < yValues.length; i++) {
          yValues[i] = yValues[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$1(text, options).data;
    }

    /**
     * Creates a new Chromatogram from text data.
     * @param text - String containing the data as CSV or TSV.
     * @param options
     */
    function fromText(text, options = {}) {
      const data = parseXY(text, options);
      const time = data.x;
      const series = {
        intensity: data.y
      };
      return new Chromatogram(time, series);
    }

    function decode$1(bytes, encoding = 'utf8') {
      const decoder = new TextDecoder(encoding);
      return decoder.decode(bytes);
    }
    const encoder = new TextEncoder();
    function encode(str) {
      return encoder.encode(str);
    }

    const defaultByteLength = 1024 * 8;
    const hostBigEndian = (() => {
      const array = new Uint8Array(4);
      const view = new Uint32Array(array.buffer);
      return !((view[0] = 1) & array[0]);
    })();
    const typedArrays = {
      int8: globalThis.Int8Array,
      uint8: globalThis.Uint8Array,
      int16: globalThis.Int16Array,
      uint16: globalThis.Uint16Array,
      int32: globalThis.Int32Array,
      uint32: globalThis.Uint32Array,
      uint64: globalThis.BigUint64Array,
      int64: globalThis.BigInt64Array,
      float32: globalThis.Float32Array,
      float64: globalThis.Float64Array
    };
    class IOBuffer {
      /**
       * Reference to the internal ArrayBuffer object.
       */
      buffer;
      /**
       * Byte length of the internal ArrayBuffer.
       */
      byteLength;
      /**
       * Byte offset of the internal ArrayBuffer.
       */
      byteOffset;
      /**
       * Byte length of the internal ArrayBuffer.
       */
      length;
      /**
       * The current offset of the buffer's pointer.
       */
      offset;
      lastWrittenByte;
      littleEndian;
      _data;
      _mark;
      _marks;
      /**
       * Create a new IOBuffer.
       * @param data - The data to construct the IOBuffer with.
       * If data is a number, it will be the new buffer's length<br>
       * If data is `undefined`, the buffer will be initialized with a default length of 8Kb<br>
       * If data is an ArrayBuffer, SharedArrayBuffer, an ArrayBufferView (Typed Array), an IOBuffer instance,
       * or a Node.js Buffer, a view will be created over the underlying ArrayBuffer.
       * @param options - An object for the options.
       * @returns A new IOBuffer instance.
       */
      constructor(data = defaultByteLength, options = {}) {
        let dataIsGiven = false;
        if (typeof data === 'number') {
          data = new ArrayBuffer(data);
        } else {
          dataIsGiven = true;
          this.lastWrittenByte = data.byteLength;
        }
        const offset = options.offset ? options.offset >>> 0 : 0;
        const byteLength = data.byteLength - offset;
        let dvOffset = offset;
        if (ArrayBuffer.isView(data) || data instanceof IOBuffer) {
          if (data.byteLength !== data.buffer.byteLength) {
            dvOffset = data.byteOffset + offset;
          }
          data = data.buffer;
        }
        if (dataIsGiven) {
          this.lastWrittenByte = byteLength;
        } else {
          this.lastWrittenByte = 0;
        }
        this.buffer = data;
        this.length = byteLength;
        this.byteLength = byteLength;
        this.byteOffset = dvOffset;
        this.offset = 0;
        this.littleEndian = true;
        this._data = new DataView(this.buffer, dvOffset, byteLength);
        this._mark = 0;
        this._marks = [];
      }
      /**
       * Checks if the memory allocated to the buffer is sufficient to store more
       * bytes after the offset.
       * @param byteLength - The needed memory in bytes.
       * @returns `true` if there is sufficient space and `false` otherwise.
       */
      available(byteLength = 1) {
        return this.offset + byteLength <= this.length;
      }
      /**
       * Check if little-endian mode is used for reading and writing multi-byte
       * values.
       * @returns `true` if little-endian mode is used, `false` otherwise.
       */
      isLittleEndian() {
        return this.littleEndian;
      }
      /**
       * Set little-endian mode for reading and writing multi-byte values.
       * @returns This.
       */
      setLittleEndian() {
        this.littleEndian = true;
        return this;
      }
      /**
       * Check if big-endian mode is used for reading and writing multi-byte values.
       * @returns `true` if big-endian mode is used, `false` otherwise.
       */
      isBigEndian() {
        return !this.littleEndian;
      }
      /**
       * Switches to big-endian mode for reading and writing multi-byte values.
       * @returns This.
       */
      setBigEndian() {
        this.littleEndian = false;
        return this;
      }
      /**
       * Move the pointer n bytes forward.
       * @param n - Number of bytes to skip.
       * @returns This.
       */
      skip(n = 1) {
        this.offset += n;
        return this;
      }
      /**
       * Move the pointer n bytes backward.
       * @param n - Number of bytes to move back.
       * @returns This.
       */
      back(n = 1) {
        this.offset -= n;
        return this;
      }
      /**
       * Move the pointer to the given offset.
       * @param offset - The offset to move to.
       * @returns This.
       */
      seek(offset) {
        this.offset = offset;
        return this;
      }
      /**
       * Store the current pointer offset.
       * @see {@link IOBuffer#reset}
       * @returns This.
       */
      mark() {
        this._mark = this.offset;
        return this;
      }
      /**
       * Move the pointer back to the last pointer offset set by mark.
       * @see {@link IOBuffer#mark}
       * @returns This.
       */
      reset() {
        this.offset = this._mark;
        return this;
      }
      /**
       * Push the current pointer offset to the mark stack.
       * @see {@link IOBuffer#popMark}
       * @returns This.
       */
      pushMark() {
        this._marks.push(this.offset);
        return this;
      }
      /**
       * Pop the last pointer offset from the mark stack, and set the current
       * pointer offset to the popped value.
       * @see {@link IOBuffer#pushMark}
       * @returns This.
       */
      popMark() {
        const offset = this._marks.pop();
        if (offset === undefined) {
          throw new Error('Mark stack empty');
        }
        this.seek(offset);
        return this;
      }
      /**
       * Move the pointer offset back to 0.
       * @returns This.
       */
      rewind() {
        this.offset = 0;
        return this;
      }
      /**
       * Make sure the buffer has sufficient memory to write a given byteLength at
       * the current pointer offset.
       * If the buffer's memory is insufficient, this method will create a new
       * buffer (a copy) with a length that is twice (byteLength + current offset).
       * @param byteLength - The needed memory in bytes.
       * @returns This.
       */
      ensureAvailable(byteLength = 1) {
        if (!this.available(byteLength)) {
          const lengthNeeded = this.offset + byteLength;
          const newLength = lengthNeeded * 2;
          const newArray = new Uint8Array(newLength);
          newArray.set(new Uint8Array(this.buffer));
          this.buffer = newArray.buffer;
          this.length = newLength;
          this.byteLength = newLength;
          this._data = new DataView(this.buffer);
        }
        return this;
      }
      /**
       * Read a byte and return false if the byte's value is 0, or true otherwise.
       * Moves pointer forward by one byte.
       * @returns The read boolean.
       */
      readBoolean() {
        return this.readUint8() !== 0;
      }
      /**
       * Read a signed 8-bit integer and move pointer forward by 1 byte.
       * @returns The read byte.
       */
      readInt8() {
        return this._data.getInt8(this.offset++);
      }
      /**
       * Read an unsigned 8-bit integer and move pointer forward by 1 byte.
       * @returns The read byte.
       */
      readUint8() {
        return this._data.getUint8(this.offset++);
      }
      /**
       * Alias for {@link IOBuffer#readUint8}.
       * @returns The read byte.
       */
      readByte() {
        return this.readUint8();
      }
      /**
       * Read `n` bytes and move pointer forward by `n` bytes.
       * @param n - Number of bytes to read.
       * @returns The read bytes.
       */
      readBytes(n = 1) {
        return this.readArray(n, 'uint8');
      }
      /**
       * Creates an array of corresponding to the type `type` and size `size`.
       * For example type `uint8` will create a `Uint8Array`.
       * @param size - size of the resulting array
       * @param type - number type of elements to read
       * @returns The read array.
       */
      readArray(size, type) {
        const bytes = typedArrays[type].BYTES_PER_ELEMENT * size;
        const offset = this.byteOffset + this.offset;
        const slice = this.buffer.slice(offset, offset + bytes);
        if (this.littleEndian === hostBigEndian && type !== 'uint8' && type !== 'int8') {
          const slice = new Uint8Array(this.buffer.slice(offset, offset + bytes));
          slice.reverse();
          const returnArray = new typedArrays[type](slice.buffer);
          this.offset += bytes;
          returnArray.reverse();
          return returnArray;
        }
        const returnArray = new typedArrays[type](slice);
        this.offset += bytes;
        return returnArray;
      }
      /**
       * Read a 16-bit signed integer and move pointer forward by 2 bytes.
       * @returns The read value.
       */
      readInt16() {
        const value = this._data.getInt16(this.offset, this.littleEndian);
        this.offset += 2;
        return value;
      }
      /**
       * Read a 16-bit unsigned integer and move pointer forward by 2 bytes.
       * @returns The read value.
       */
      readUint16() {
        const value = this._data.getUint16(this.offset, this.littleEndian);
        this.offset += 2;
        return value;
      }
      /**
       * Read a 32-bit signed integer and move pointer forward by 4 bytes.
       * @returns The read value.
       */
      readInt32() {
        const value = this._data.getInt32(this.offset, this.littleEndian);
        this.offset += 4;
        return value;
      }
      /**
       * Read a 32-bit unsigned integer and move pointer forward by 4 bytes.
       * @returns The read value.
       */
      readUint32() {
        const value = this._data.getUint32(this.offset, this.littleEndian);
        this.offset += 4;
        return value;
      }
      /**
       * Read a 32-bit floating number and move pointer forward by 4 bytes.
       * @returns The read value.
       */
      readFloat32() {
        const value = this._data.getFloat32(this.offset, this.littleEndian);
        this.offset += 4;
        return value;
      }
      /**
       * Read a 64-bit floating number and move pointer forward by 8 bytes.
       * @returns The read value.
       */
      readFloat64() {
        const value = this._data.getFloat64(this.offset, this.littleEndian);
        this.offset += 8;
        return value;
      }
      /**
       * Read a 64-bit signed integer number and move pointer forward by 8 bytes.
       * @returns The read value.
       */
      readBigInt64() {
        const value = this._data.getBigInt64(this.offset, this.littleEndian);
        this.offset += 8;
        return value;
      }
      /**
       * Read a 64-bit unsigned integer number and move pointer forward by 8 bytes.
       * @returns The read value.
       */
      readBigUint64() {
        const value = this._data.getBigUint64(this.offset, this.littleEndian);
        this.offset += 8;
        return value;
      }
      /**
       * Read a 1-byte ASCII character and move pointer forward by 1 byte.
       * @returns The read character.
       */
      readChar() {
        // eslint-disable-next-line unicorn/prefer-code-point
        return String.fromCharCode(this.readInt8());
      }
      /**
       * Read `n` 1-byte ASCII characters and move pointer forward by `n` bytes.
       * @param n - Number of characters to read.
       * @returns The read characters.
       */
      readChars(n = 1) {
        let result = '';
        for (let i = 0; i < n; i++) {
          result += this.readChar();
        }
        return result;
      }
      /**
       * Read the next `n` bytes, return a UTF-8 decoded string and move pointer
       * forward by `n` bytes.
       * @param n - Number of bytes to read.
       * @returns The decoded string.
       */
      readUtf8(n = 1) {
        return decode$1(this.readBytes(n));
      }
      /**
       * Read the next `n` bytes, return a string decoded with `encoding` and move pointer
       * forward by `n` bytes.
       * If no encoding is passed, the function is equivalent to @see {@link IOBuffer#readUtf8}
       * @param n - Number of bytes to read.
       * @param encoding - The encoding to use. Default is 'utf8'.
       * @returns The decoded string.
       */
      decodeText(n = 1, encoding = 'utf8') {
        return decode$1(this.readBytes(n), encoding);
      }
      /**
       * Write 0xff if the passed value is truthy, 0x00 otherwise and move pointer
       * forward by 1 byte.
       * @param value - The value to write.
       * @returns This.
       */
      writeBoolean(value) {
        this.writeUint8(value ? 0xff : 0x00);
        return this;
      }
      /**
       * Write `value` as an 8-bit signed integer and move pointer forward by 1 byte.
       * @param value - The value to write.
       * @returns This.
       */
      writeInt8(value) {
        this.ensureAvailable(1);
        this._data.setInt8(this.offset++, value);
        this._updateLastWrittenByte();
        return this;
      }
      /**
       * Write `value` as an 8-bit unsigned integer and move pointer forward by 1
       * byte.
       * @param value - The value to write.
       * @returns This.
       */
      writeUint8(value) {
        this.ensureAvailable(1);
        this._data.setUint8(this.offset++, value);
        this._updateLastWrittenByte();
        return this;
      }
      /**
       * An alias for {@link IOBuffer#writeUint8}.
       * @param value - The value to write.
       * @returns This.
       */
      writeByte(value) {
        return this.writeUint8(value);
      }
      /**
       * Write all elements of `bytes` as uint8 values and move pointer forward by
       * `bytes.length` bytes.
       * @param bytes - The array of bytes to write.
       * @returns This.
       */
      writeBytes(bytes) {
        this.ensureAvailable(bytes.length);
        // eslint-disable-next-line @typescript-eslint/prefer-for-of
        for (let i = 0; i < bytes.length; i++) {
          this._data.setUint8(this.offset++, bytes[i]);
        }
        this._updateLastWrittenByte();
        return this;
      }
      /**
       * Write `value` as a 16-bit signed integer and move pointer forward by 2
       * bytes.
       * @param value - The value to write.
       * @returns This.
       */
      writeInt16(value) {
        this.ensureAvailable(2);
        this._data.setInt16(this.offset, value, this.littleEndian);
        this.offset += 2;
        this._updateLastWrittenByte();
        return this;
      }
      /**
       * Write `value` as a 16-bit unsigned integer and move pointer forward by 2
       * bytes.
       * @param value - The value to write.
       * @returns This.
       */
      writeUint16(value) {
        this.ensureAvailable(2);
        this._data.setUint16(this.offset, value, this.littleEndian);
        this.offset += 2;
        this._updateLastWrittenByte();
        return this;
      }
      /**
       * Write `value` as a 32-bit signed integer and move pointer forward by 4
       * bytes.
       * @param value - The value to write.
       * @returns This.
       */
      writeInt32(value) {
        this.ensureAvailable(4);
        this._data.setInt32(this.offset, value, this.littleEndian);
        this.offset += 4;
        this._updateLastWrittenByte();
        return this;
      }
      /**
       * Write `value` as a 32-bit unsigned integer and move pointer forward by 4
       * bytes.
       * @param value - The value to write.
       * @returns This.
       */
      writeUint32(value) {
        this.ensureAvailable(4);
        this._data.setUint32(this.offset, value, this.littleEndian);
        this.offset += 4;
        this._updateLastWrittenByte();
        return this;
      }
      /**
       * Write `value` as a 32-bit floating number and move pointer forward by 4
       * bytes.
       * @param value - The value to write.
       * @returns This.
       */
      writeFloat32(value) {
        this.ensureAvailable(4);
        this._data.setFloat32(this.offset, value, this.littleEndian);
        this.offset += 4;
        this._updateLastWrittenByte();
        return this;
      }
      /**
       * Write `value` as a 64-bit floating number and move pointer forward by 8
       * bytes.
       * @param value - The value to write.
       * @returns This.
       */
      writeFloat64(value) {
        this.ensureAvailable(8);
        this._data.setFloat64(this.offset, value, this.littleEndian);
        this.offset += 8;
        this._updateLastWrittenByte();
        return this;
      }
      /**
       * Write `value` as a 64-bit signed bigint and move pointer forward by 8
       * bytes.
       * @param value - The value to write.
       * @returns This.
       */
      writeBigInt64(value) {
        this.ensureAvailable(8);
        this._data.setBigInt64(this.offset, value, this.littleEndian);
        this.offset += 8;
        this._updateLastWrittenByte();
        return this;
      }
      /**
       * Write `value` as a 64-bit unsigned bigint and move pointer forward by 8
       * bytes.
       * @param value - The value to write.
       * @returns This.
       */
      writeBigUint64(value) {
        this.ensureAvailable(8);
        this._data.setBigUint64(this.offset, value, this.littleEndian);
        this.offset += 8;
        this._updateLastWrittenByte();
        return this;
      }
      /**
       * Write the charCode of `str`'s first character as an 8-bit unsigned integer
       * and move pointer forward by 1 byte.
       * @param str - The character to write.
       * @returns This.
       */
      writeChar(str) {
        // eslint-disable-next-line unicorn/prefer-code-point
        return this.writeUint8(str.charCodeAt(0));
      }
      /**
       * Write the charCodes of all `str`'s characters as 8-bit unsigned integers
       * and move pointer forward by `str.length` bytes.
       * @param str - The characters to write.
       * @returns This.
       */
      writeChars(str) {
        for (let i = 0; i < str.length; i++) {
          // eslint-disable-next-line unicorn/prefer-code-point
          this.writeUint8(str.charCodeAt(i));
        }
        return this;
      }
      /**
       * UTF-8 encode and write `str` to the current pointer offset and move pointer
       * forward according to the encoded length.
       * @param str - The string to write.
       * @returns This.
       */
      writeUtf8(str) {
        return this.writeBytes(encode(str));
      }
      /**
       * Export a Uint8Array view of the internal buffer.
       * The view starts at the byte offset and its length
       * is calculated to stop at the last written byte or the original length.
       * @returns A new Uint8Array view.
       */
      toArray() {
        return new Uint8Array(this.buffer, this.byteOffset, this.lastWrittenByte);
      }
      /**
       *  Get the total number of bytes written so far, regardless of the current offset.
       * @returns - Total number of bytes.
       */
      getWrittenByteLength() {
        return this.lastWrittenByte - this.byteOffset;
      }
      /**
       * Update the last written byte offset
       * @private
       */
      _updateLastWrittenByte() {
        if (this.offset > this.lastWrittenByte) {
          this.lastWrittenByte = this.offset;
        }
      }
    }

    const types = {
      BYTE: 1,
      CHAR: 2,
      SHORT: 3,
      INT: 4,
      FLOAT: 5,
      DOUBLE: 6
    };
    /**
     * Parse a number into their respective type
     * @param type - integer that represents the type
     * @return - parsed value of the type
     */
    function num2str(type) {
      switch (Number(type)) {
        case types.BYTE:
          return 'byte';
        case types.CHAR:
          return 'char';
        case types.SHORT:
          return 'short';
        case types.INT:
          return 'int';
        case types.FLOAT:
          return 'float';
        case types.DOUBLE:
          return 'double';
        default:
          return 'undefined';
      }
    }
    /**
     * Parse a number type identifier to his size in bytes
     * @param type - integer that represents the type
     * @return size of the type
     */
    function num2bytes(type) {
      switch (Number(type)) {
        case types.BYTE:
          return 1;
        case types.CHAR:
          return 1;
        case types.SHORT:
          return 2;
        case types.INT:
          return 4;
        case types.FLOAT:
          return 4;
        case types.DOUBLE:
          return 8;
        default:
          return -1;
      }
    }
    /**
     * Reverse search of num2str
     * @param type - string that represents the type
     * @return parsed value of the type
     */
    function str2num(type) {
      switch (String(type)) {
        case 'byte':
          return types.BYTE;
        case 'char':
          return types.CHAR;
        case 'short':
          return types.SHORT;
        case 'int':
          return types.INT;
        case 'float':
          return types.FLOAT;
        case 'double':
          return types.DOUBLE;
        /* istanbul ignore next */
        default:
          return -1;
      }
    }
    /**
     * Auxiliary function to read numeric data
     * @param size - Size of the element to read
     * @param bufferReader - Function to read next value
     * @return
     */
    function readNumber(size, bufferReader) {
      if (size !== 1) {
        const numbers = new Array(size);
        for (let i = 0; i < size; i++) {
          numbers[i] = bufferReader();
        }
        return numbers;
      } else {
        return bufferReader();
      }
    }
    /**
     * Given a type and a size reads the next element
     * @param buffer - Buffer for the file data
     * @param type - Type of the data to read
     * @param size - Size of the element to read
     * @return
     */
    function readType(buffer, type, size) {
      switch (type) {
        case types.BYTE:
          return Array.from(buffer.readBytes(size));
        case types.CHAR:
          return trimNull(buffer.readChars(size));
        case types.SHORT:
          return readNumber(size, buffer.readInt16.bind(buffer));
        case types.INT:
          return readNumber(size, buffer.readInt32.bind(buffer));
        case types.FLOAT:
          return readNumber(size, buffer.readFloat32.bind(buffer));
        case types.DOUBLE:
          return readNumber(size, buffer.readFloat64.bind(buffer));
        default:
          throw new Error(`non valid type ${type}`);
      }
    }
    /**
     * Removes null terminate value
     * @param value - String to trim
     * @return - Trimmed string
     */
    function trimNull(value) {
      if (value.charCodeAt(value.length - 1) === 0) {
        return value.substring(0, value.length - 1);
      }
      return value;
    }

    // const STREAMING = 4294967295;
    /**
     * Read data for the given non-record variable
     * @param buffer - Buffer for the file data
     * @param variable - Variable metadata
     * @return - Data of the element
     */
    function nonRecord(buffer, variable) {
      // variable type
      const type = str2num(variable.type);
      // size of the data
      const size = variable.size / num2bytes(type);
      // iterates over the data
      const data = new Array(size);
      for (let i = 0; i < size; i++) {
        data[i] = readType(buffer, type, 1);
      }
      return data;
    }
    /**
     * Read data for the given record variable
     * @param buffer - Buffer for the file data
     * @param variable - Variable metadata
     * @param recordDimension - Record dimension metadata
     * @return - Data of the element
     */
    function record(buffer, variable, recordDimension) {
      // variable type
      const type = str2num(variable.type);
      const width = variable.size ? variable.size / num2bytes(type) : 1;
      // size of the data
      // TODO streaming data
      const size = recordDimension.length;
      // iterates over the data
      const data = new Array(size);
      const step = recordDimension.recordStep;
      if (step) {
        for (let i = 0; i < size; i++) {
          const currentOffset = buffer.offset;
          data[i] = readType(buffer, type, width);
          buffer.seek(currentOffset + step);
        }
      } else {
        throw new Error('recordDimension.recordStep is undefined');
      }
      return data;
    }

    /**
     * Throws a non-valid NetCDF exception if the statement it's true
     * @ignore
     * @param statement - Throws if true
     * @param reason - Reason to throw
     */
    function notNetcdf(statement, reason) {
      if (statement) {
        throw new TypeError(`Not a valid NetCDF v3.x file: ${reason}`);
      }
    }
    /**
     * Moves 1, 2, or 3 bytes to next 4-byte boundary
     * @param buffer - Buffer for the file data
     */
    function padding(buffer) {
      if (buffer.offset % 4 !== 0) {
        buffer.skip(4 - buffer.offset % 4);
      }
    }
    /**
     * Reads the name
     * @param buffer - Buffer for the file data
     * @return Name
     */
    function readName(buffer) {
      // Read name
      const nameLength = buffer.readUint32();
      const name = buffer.readChars(nameLength);
      // validate name
      // TODO
      // Apply padding
      padding(buffer);
      return name;
    }

    // Grammar constants
    const ZERO = 0;
    const NC_DIMENSION = 10;
    const NC_VARIABLE = 11;
    const NC_ATTRIBUTE = 12;
    const NC_UNLIMITED = 0;
    /**
     * Reads the file header as @see {@link Header}
     * @param buffer - Buffer for the file data
     * @param version - Version of the file
     * @returns
     */
    function header(buffer, version) {
      const header = {
        version
      };
      const recordDimension = {
        length: buffer.readUint32()
      };
      const dimList = dimensionsList(buffer);
      if (!Array.isArray(dimList)) {
        recordDimension.id = dimList.recordId;
        recordDimension.name = dimList.recordName;
        header.dimensions = dimList.dimensions;
      }
      header.globalAttributes = attributesList(buffer);
      const variables = variablesList(buffer, recordDimension?.id, version);
      if (!Array.isArray(variables)) {
        header.variables = variables.variables;
        recordDimension.recordStep = variables.recordStep;
      }
      header.recordDimension = recordDimension;
      return header;
    }
    /**
     * List of dimensions
     * @param buffer - Buffer for the file data
     * @return List of dimensions
     */
    function dimensionsList(buffer) {
      const result = {};
      let recordId, recordName;
      const dimList = buffer.readUint32();
      let dimensions;
      if (dimList === ZERO) {
        notNetcdf(buffer.readUint32() !== ZERO, 'wrong empty tag for list of dimensions');
        return [];
      } else {
        notNetcdf(dimList !== NC_DIMENSION, 'wrong tag for list of dimensions');
        // Length of dimensions
        const dimensionSize = buffer.readUint32();
        dimensions = new Array(dimensionSize);
        //populate `name` and `size` for each dimension
        for (let dim = 0; dim < dimensionSize; dim++) {
          // Read name
          const name = readName(buffer);
          // Read dimension size
          const size = buffer.readUint32();
          if (size === NC_UNLIMITED) {
            // in netcdf 3 one field can be of size unlimited
            recordId = dim;
            recordName = name;
          }
          dimensions[dim] = {
            name,
            size
          };
        }
      }
      if (recordId !== undefined) {
        result.recordId = recordId;
      }
      if (recordName !== undefined) {
        result.recordName = recordName;
      }
      result.dimensions = dimensions;
      return result;
    }
    /**
     * List of attributes
     * @param buffer - Buffer for the file data
     * @return - List of attributes with:
     */
    function attributesList(buffer) {
      const gAttList = buffer.readUint32();
      let attributes;
      if (gAttList === ZERO) {
        notNetcdf(buffer.readUint32() !== ZERO, 'wrong empty tag for list of attributes');
        return [];
      } else {
        notNetcdf(gAttList !== NC_ATTRIBUTE, 'wrong tag for list of attributes');
        // Length of attributes
        const attributeSize = buffer.readUint32();
        attributes = new Array(attributeSize);
        // Populate `name`, `type` and `value` for each attribute
        for (let gAtt = 0; gAtt < attributeSize; gAtt++) {
          // Read name
          const name = readName(buffer);
          // Read type
          const type = buffer.readUint32();
          notNetcdf(type < 1 || type > 6, `non valid type ${type}`);
          // Read attribute
          const size = buffer.readUint32();
          const value = readType(buffer, type, size);
          // Apply padding
          padding(buffer);
          attributes[gAtt] = {
            name,
            type: num2str(type),
            value
          };
        }
      }
      return attributes;
    }
    /**
     * @param buffer - Buffer for the file data
     * @param recordId - Id of the unlimited dimension (also called record dimension)
     * This value may be undefined if there is no unlimited dimension
     * @param version - Version of the file
     * @return - Number of recordStep and list of variables @see {@link Variables}
     */
    function variablesList(buffer, recordId, version) {
      const varList = buffer.readUint32();
      let recordStep = 0;
      let variables;
      if (varList === ZERO) {
        notNetcdf(buffer.readUint32() !== ZERO, 'wrong empty tag for list of variables');
        return [];
      } else {
        notNetcdf(varList !== NC_VARIABLE, 'wrong tag for list of variables');
        // Length of variables
        const variableSize = buffer.readUint32();
        variables = new Array(variableSize);
        for (let v = 0; v < variableSize; v++) {
          // Read name
          const name = readName(buffer);
          // Read dimensionality of the variable
          const dimensionality = buffer.readUint32();
          // Index into the list of dimensions
          const dimensionsIds = new Array(dimensionality);
          for (let dim = 0; dim < dimensionality; dim++) {
            dimensionsIds[dim] = buffer.readUint32();
          }
          // Read variables size
          const attributes = attributesList(buffer);
          // Read type
          const type = buffer.readUint32();
          notNetcdf(type < 1 && type > 6, `non valid type ${type}`);
          // Read variable size
          // The 32-bit varSize field is not large enough to contain the size of variables that require
          // more than 2^32 - 4 bytes, so 2^32 - 1 is used in the varSize field for such variables.
          const varSize = buffer.readUint32();
          // Read offset
          let offset = buffer.readUint32();
          if (version === 2) {
            notNetcdf(offset > 0, 'offsets larger than 4GB not supported');
            offset = buffer.readUint32();
          }
          let record = false;
          // Count amount of record variables
          if (typeof recordId !== 'undefined' && dimensionsIds[0] === recordId) {
            recordStep += varSize;
            record = true;
          }
          variables[v] = {
            name,
            dimensions: dimensionsIds,
            attributes,
            type: num2str(type),
            size: varSize,
            offset,
            record
          };
        }
      }
      return {
        variables,
        recordStep
      };
    }

    function toString() {
      const result = [];
      result.push('DIMENSIONS');
      for (const dimension of this.dimensions) {
        result.push(`  ${dimension.name.padEnd(30)} = size: ${dimension.size}`);
      }
      result.push('');
      result.push('GLOBAL ATTRIBUTES');
      for (const attribute of this.globalAttributes) {
        result.push(`  ${attribute.name.padEnd(30)} = ${attribute.value}`);
      }
      const variables = JSON.parse(JSON.stringify(this.variables));
      result.push('');
      result.push('VARIABLES:');
      for (const variable of variables) {
        variable.value = this.getDataVariable(variable);
        let stringify = JSON.stringify(variable.value);
        if (stringify.length > 50) stringify = stringify.substring(0, 50);
        if (!isNaN(variable.value.length)) {
          stringify += ` (length: ${variable.value.length})`;
        }
        result.push(`  ${variable.name.padEnd(30)} = ${stringify}`);
      }
      return result.join('\n');
    }

    /**
     * Reads a NetCDF v3.x file
     * [See specification](https://www.unidata.ucar.edu/software/netcdf/docs/file_format_specifications.html)
     * @param data - ArrayBuffer or any Typed Array (including Node.js' Buffer from v4) with the data
     * @constructor
     */
    class NetCDFReader {
      constructor(data) {
        this.toString = toString;
        const buffer = new IOBuffer(data);
        buffer.setBigEndian();
        // Validate that it's a NetCDF file
        notNetcdf(buffer.readChars(3) !== 'CDF', 'should start with CDF');
        // Check the NetCDF format
        const version = buffer.readByte();
        notNetcdf(version > 2, 'unknown version');
        // Read the header
        this.header = header(buffer, version);
        this.buffer = buffer;
      }
      /**
       * @return - Version for the NetCDF format
       */
      get version() {
        if (this.header.version === 1) {
          return 'classic format';
        } else {
          return '64-bit offset format';
        }
      }
      /**
       * @return {object} - Metadata for the record dimension
       *  * `length`: Number of elements in the record dimension
       *  * `id`: Id number in the list of dimensions for the record dimension
       *  * `name`: String with the name of the record dimension
       *  * `recordStep`: Number with the record variables step size
       */
      get recordDimension() {
        return this.header.recordDimension;
      }
      /**
       * @return - Array - List of dimensions with:
       *  * `name`: String with the name of the dimension
       *  * `size`: Number with the size of the dimension
       */
      get dimensions() {
        return this.header.dimensions;
      }
      /**
       * @return - Array - List of global attributes with:
       *  * `name`: String with the name of the attribute
       *  * `type`: String with the type of the attribute
       *  * `value`: A number or string with the value of the attribute
       */
      get globalAttributes() {
        return this.header.globalAttributes;
      }
      /**
       * Returns the value of an attribute
       * @param - AttributeName
       * @return - Value of the attributeName or null
       */
      getAttribute(attributeName) {
        const attribute = this.globalAttributes.find(val => val.name === attributeName);
        if (attribute) return attribute.value;
        return null;
      }
      /**
       * Returns the value of a variable as a string
       * @param - variableName
       * @return - Value of the variable as a string or null
       */
      getDataVariableAsString(variableName) {
        const variable = this.getDataVariable(variableName);
        if (variable) return variable.join('');
        return null;
      }
      get variables() {
        return this.header.variables;
      }
      /**
       * Retrieves the data for a given variable
       * @param variableName - Name of the variable to search or variable object
       * @return The variable values
       */
      getDataVariable(variableName) {
        let variable;
        if (typeof variableName === 'string') {
          // search the variable
          variable = this.header.variables.find(val => {
            return val.name === variableName;
          });
        } else {
          variable = variableName;
        }
        // throws if variable not found
        if (variable === undefined) {
          throw new Error('Not a valid NetCDF v3.x file: variable not found');
        }
        // go to the offset position
        this.buffer.seek(variable.offset);
        if (variable.record) {
          // record variable case
          return record(this.buffer, variable, this.header.recordDimension);
        } else {
          // non-record variable case
          return nonRecord(this.buffer, variable);
        }
      }
      /**
       * Check if a dataVariable exists
       * @param variableName - Name of the variable to find
       * @return boolean
       */
      dataVariableExists(variableName) {
        const variable = this.header.variables.find(val => {
          return val.name === variableName;
        });
        return variable !== undefined;
      }
      /**
       * Check if an attribute exists
       * @param attributeName - Name of the attribute to find
       * @return boolean
       */
      attributeExists(attributeName) {
        const attribute = this.globalAttributes.find(val => val.name === attributeName);
        return attribute !== undefined;
      }
    }

    /* reader.toString() provides the following information
        DIMENSIONS
          point_number                   = size: 0
          scan_number                    = size: 60
          error_number                   = size: 1
          _64_byte_string                = size: 64

        GLOBAL ATTRIBUTES
          dataset_completeness           = C1
          ms_template_revision           = 1.0.1
          netcdf_revision                = 4.2
          languages                      = English
          administrative_comments        =
          netcdf_file_date_time_stamp    = 202003031432433600000
          experiment_date_time_stamp     = 202003031432433600000
          source_file_reference          = JC-012_cleavage test_Scan1_is1.datx 2020.03.03 14:32:43
          source_file_format             = Advion ExpressIon Compact Mass Spectrometer Data System
          source_file_date_time_stamp    = 202003031432433600000
          experiment_title               =
          experiment_type                = Continuum Mass Spectrum
          test_ionization_mode           = Electrospray Ionization
          test_ionization_polarity       = Positive Polarity
          sample_state                   = Other State
          test_separation_type           = No Chromatography
          test_ms_inlet                  = Direct Inlet Probe
          test_detector_type             = Electron Multiplier
          test_resolution_type           = Constant Resolution
          test_scan_function             = Mass Scan
          test_scan_direction            = Up
          test_scan_law                  = Linear
          raw_data_mass_format           = Float
          raw_data_time_format           = Double
          raw_data_intensity_format      = Float
          units                          = Seconds
          global_mass_min                = 9.949999809265137
          global_mass_max                = 1199.75
          actual_run_time_length         = 133.46099853515625
          starting_scan_number           = 1
          actual_delay_time              = 0
          raw_data_uniform_sampling_flag = 0

        VARIABLES:
          error_log                      = ["","","","","","","","","","","","","","","",""," (length: 64)
          scan_index                     = [0,3096,6282,9865,13409,16765,20281,23603,27099,30 (length: 60)
          point_count                    = [3096,3186,3583,3544,3356,3516,3322,3496,3351,3031 (length: 60)
          flag_count                     = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 60)
          actual_scan_number             = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 (length: 60)
          a_d_coaddition_factor          = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 60)
          a_d_sampling_rate              = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 60)
          inter_scan_time                = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 60)
          mass_range_min                 = [9.949999809265137,9.949999809265137,9.94999980926 (length: 60)
          mass_range_max                 = [164.6999969482422,169.1999969482422,189.050003051 (length: 60)
          scan_acquisition_time          = [0.08100000023841858,2.3420000076293945,4.60300016 (length: 60)
          scan_duration                  = [2.261000007390976,2.261000156402588,2.25999975204 (length: 60)
          resolution                     = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 60)
          time_range_min                 = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 60)
          time_range_max                 = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 60)
          total_intensity                = [4498210816,4468554240,5001547264,5405233152,50000 (length: 60)
          mass_values                    = [9.949999809265137,83.5,83.55000305175781,83.59999 (length: 199393)
          intensity_values               = [0,818716,462148,0,735558,952901,0,165241,421829,0 (length: 199393)
    */
    /**
     * Parses an Advion GC/MS NetCDF file and returns times with TIC and mass spectra.
     * @param reader - NetCDF reader instance
     * @returns Parsed times and series data
     */
    function advionGCMS(reader) {
      const time = reader.getDataVariable('scan_acquisition_time');
      const tic = reader.getDataVariable('total_intensity');
      const scanIndex = reader.getDataVariable('scan_index');
      const massValues = reader.getDataVariable('mass_values');
      const intensityValues = reader.getDataVariable('intensity_values');
      scanIndex.push(massValues.length);
      const ms = [];
      let index = 0;
      for (const end of scanIndex.slice(1)) {
        const start = index;
        index = end;
        ms.push([massValues.slice(start, end), intensityValues.slice(start, end)]);
      }
      return {
        times: time,
        series: [{
          name: 'tic',
          dimension: 1,
          data: tic
        }, {
          name: 'ms',
          dimension: 2,
          data: ms
        }]
      };
    }

    /* reader.toString() provides the following information
        GLOBAL ATTRIBUTES
          dataset_completeness           = C1+C2
          ms_template_revision           = 1.0.1
          netcdf_revision                = 2.3.2
          languages                      = English
          administrative_comments        = 1% CH2Cl2
          dataset_origin                 = Santa Clara, CA
          netcdf_file_date_time_stamp    = 20161012052159+0200
          experiment_title               = P071 Essence super BP
          experiment_date_time_stamp     = 20070923040800+0200
          operator_name                  = SC
          external_file_ref_0            = FIRE_RTL.M
          experiment_type                = Centroided Mass Spectrum
          number_of_times_processed      = 1
          number_of_times_calibrated     = 0
          sample_state                   = Other State
          test_separation_type           = No Chromatography
          test_ms_inlet                  = Capillary Direct
          test_ionization_mode           = Electron Impact
          test_ionization_polarity       = Positive Polarity
          test_detector_type             = Electron Multiplier
          test_resolution_type           = Constant Resolution
          test_scan_function             = Mass Scan
          test_scan_direction            = Up
          test_scan_law                  = Linear
          raw_data_mass_format           = Float
          raw_data_time_format           = Short
          raw_data_intensity_format      = Float

        VARIABLES:
          error_log                      = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 64)
          a_d_sampling_rate              = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 6401)
          a_d_coaddition_factor          = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 6402)
          scan_acquisition_time          = [5.25,5.84,6.428999999999999,7.019,7.609,8.199,8.7 (length: 6401)
          scan_duration                  = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 6401)
          inter_scan_time                = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 6401)
          resolution                     = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 6401)
          actual_scan_number             = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 (length: 6401)
          total_intensity                = [3134,3157,3085,3134,3093,3113,3061,3057,3030,3166 (length: 6401)
          mass_range_min                 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 6401)
          mass_range_max                 = [206.89999389648438,206.89999389648438,207,207.100 (length: 6401)
          time_range_min                 = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 6401)
          time_range_max                 = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 6401)
          scan_index                     = [0,11,22,33,44,55,66,76,88,99,111,122,134,145,156, (length: 6401)
          point_count                    = [11,11,11,11,11,11,10,12,11,12,11,12,11,11,11,11,1 (length: 6401)
          flag_count                     = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 6401)
          mass_values                    = [16,17,18.100000381469727,28,32,35,36,38,40,44.099 (length: 157201)
          time_values                    = [9.969209968386869e+36,9.969209968386869e+36,9.969 (length: 157201)
          intensity_values               = [37,293,1243,737,420,45,196,72,22,35,34,28,299,123 (length: 157201)
          instrument_name                = ["G","a","s"," ","C","h","r","o","m","a","t","o"," (length: 32)
          instrument_id                  = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
          instrument_mfr                 = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
          instrument_model               = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
          instrument_serial_no           = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
          instrument_sw_version          = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
          instrument_fw_version          = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
          instrument_os_version          = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
          instrument_app_version         = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
          instrument_comments            = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
    */
    /**
     * Parses an Agilent GC/MS NetCDF file and returns times with TIC and mass spectra.
     * @param reader - NetCDF reader instance
     * @returns Parsed times and series data
     */
    function agilentGCMS(reader) {
      const time = reader.getDataVariable('scan_acquisition_time');
      const tic = reader.getDataVariable('total_intensity');
      const pointCount = reader.getDataVariable('point_count');
      const massValues = reader.getDataVariable('mass_values');
      const intensityValues = reader.getDataVariable('intensity_values');
      const ms = [];
      let index = 0;
      for (const count of pointCount) {
        ms.push([massValues.slice(index, index + count), intensityValues.slice(index, index + count)]);
        index += count;
      }
      return {
        times: time,
        series: [{
          name: 'tic',
          dimension: 1,
          data: tic
        }, {
          name: 'ms',
          dimension: 2,
          data: ms
        }]
      };
    }

    /* reader.toString() provides the following information
        GLOBAL ATTRIBUTES
          dataset_completeness           = C1+C2
          aia_template_revision          = 1.0
          netcdf_revision                = 2.3
          languages                      = English only
          injection_date_time_stamp      = 20181030174305+0000
          HP_injection_time              = 30-Oct-18, 17:43:05
          experiment_title               = SequenceLine: 1  Inj: 1
          operator_name                  = SYSTEM
          separation_experiment_type     = liquid chromatography
          source_file_reference          = C:\CHEM32\1\DATA\MINGMING\MW-1-MEO-I IC-90 2018-10-30 17-42-13\MW-2-6-6 IC 90.D
          sample_name                    = MW-2-6-6 IC 90
          sample_id                      =
          detector_unit                  = mAU
          detection_method_name          = POS 3 IC 90-10 31 MIN.M
          detector_name                  = DAD1 A, Sig=254,4 Ref=360,100
          retention_unit                 = seconds

       VARIABLES:
          detector_maximum_value         = [130.9263458251953] (length: 1)
          detector_minimum_value         = [-0.1758841574192047] (length: 1)
          actual_run_time_length         = [1860] (length: 1)
          actual_delay_time              = [0.012000000104308128] (length: 1)
          actual_sampling_interval       = [0.4000000059604645] (length: 1)
          ordinate_values                = [-0.07588416337966919,-0.07525086402893066,-0.0740 (length: 4651)
          peak_retention_time            = [196.0651397705078,332.5663757324219,527.549865722 (length: 8)
          peak_start_time                = [186.81199645996094,239.21200561523438,502.4119873 (length: 8)
          peak_end_time                  = [220.81201171875,471.5176696777344,572.47869873046 (length: 8)
          peak_width                     = [4.974428176879883,62.90694808959961,11.9328641891 (length: 8)
          peak_area                      = [556.7650146484375,419.825439453125,66.56610107421 (length: 8)
          peak_area_percent              = [7.0321502685546875,5.302552223205566,0.8407546877 (length: 8)
          peak_height                    = [100.07515716552734,5.1860527992248535,4.827196121 (length: 8)
          peak_height_percent            = [29.76352310180664,1.5423927307128906,1.4356645345 (length: 8)
          peak_asymmetry                 = [1.4555920362472534,0.8351489901542664,1.707817316 (length: 8)
          baseline_start_time            = [186.81199645996094,239.21200561523438,502.4119873 (length: 8)
          baseline_start_value           = [1.9561424255371094,0.9857341647148132,1.127734780 (length: 8)
          baseline_stop_time             = [220.81201171875,471.5176696777344,572.47869873046 (length: 8)
          baseline_stop_value            = [1.1907591819763184,1.10896897315979,1.18347382545 (length: 8)
          peak_start_detection_code      = ["B","","B","","B","","B","","V","","B","","B","", (length: 16)
          peak_stop_detection_code       = ["B","","B","","B","","V","","B","","B","","B","", (length: 16)
          migration_time                 = [196.0651397705078,332.5663757324219,527.549865722 (length: 8)
          peak_area_square_root          = [23.595869064331055,20.489643096923828,8.158804893 (length: 8)
          manually_reintegrated_peaks    = [0,0,0,0,0,0,0,0] (length: 8)
    */
    /**
     * Parses an Agilent HPLC NetCDF file and returns times with detector data.
     * @param reader - NetCDF reader instance
     * @returns Parsed times and series data
     */
    function agilentHPLC(reader) {
      const intensities = reader.getDataVariable('ordinate_values');
      const numberPoints = intensities.length;
      const detector = reader.getAttribute('detector_name');
      let channel;
      if (detector.match(/dad/i)) {
        channel = `uv${Number(detector.replace(/.*Sig=(?<wavelength>\d+).*/, '$<wavelength>'))}`;
      } else if (detector.match(/tic/i)) {
        channel = 'tic';
      } else {
        channel = 'unknown';
      }
      const delayTime = reader.getDataVariable('actual_delay_time')[0];
      const runtimeLength = reader.getDataVariable('actual_run_time_length')[0];
      let samplingInterval;
      if (reader.dataVariableExists('actual_sampling_interval')) {
        samplingInterval = reader.getDataVariable('actual_sampling_interval')[0];
        if (Math.abs(delayTime + samplingInterval * numberPoints - runtimeLength) > 3) {
          throw new Error('The expected last time does not correspond to the runtimeLength');
        }
      } else {
        samplingInterval = (runtimeLength - delayTime) / numberPoints;
      }
      const times = [];
      let time = delayTime;
      for (let i = 0; i < numberPoints; i++) {
        times.push(time);
        time += samplingInterval;
      }
      return {
        times,
        series: [{
          name: channel,
          dimension: 1,
          data: intensities
        }]
      };
    }

    /**
     * Parses a generic AIA template NetCDF file and returns times with TIC data.
     * @param reader - NetCDF reader instance
     * @returns Parsed times and series data
     */
    function aiaTemplate(reader) {
      const tic = reader.getDataVariable('ordinate_values');
      const delayTime = Number(reader.getDataVariable('actual_delay_time'));
      const interval = Number(reader.getDataVariable('actual_sampling_interval'));
      const time = new Array(tic.length);
      for (let i = 0; i < tic.length; i++) {
        time[i] = delayTime + i * interval;
      }
      return {
        times: time,
        series: [{
          name: 'tic',
          dimension: 1,
          data: tic
        }]
      };
    }

    /* reader.toString() provides the following information
        GLOBAL ATTRIBUTES
          dataset_completeness           = C1+C2
          ms_template_revision           = 1.0.1
          netcdf_revision                = 2.3.2
          languages                      = English
          netcdf_file_date_time_stamp    = 20170428032023+0000
          experiment_title               = MS51762A16
        11829FC03_3__60_40
          experiment_date_time_stamp     = 20160930202145-0001
          operator_name                  = Begemann/Eikenberg/Roettger
          pre_experiment_program_name    = otofControl 3.4.16.0
          post_experiment_program_name   = Bruker Compass DataAnalysis 4.2
          source_file_reference          = X:\2016\MS5\1700\MS51762A16.d
          source_file_format             = Bruker Daltonics Data File
          experiment_type                = Centroided Mass Spectrum
          sample_state                   = Other State
          test_separation_type           = No Chromatography
          test_ms_inlet                  = Direct Inlet Probe
          test_ionization_mode           = Electrospray Ionization
          test_ionization_polarity       = Positive Polarity
          test_detector_type             = Electron Multiplier
          test_resolution_type           = Proportional Resolution
          test_scan_function             = Mass Scan
          test_scan_direction            = Up
          test_scan_law                  = Linear
          raw_data_mass_format           = Double
          raw_data_time_format           = Float
          raw_data_intensity_format      = Float
          units                          = Seconds
          scale_factor                   = 1

        VARIABLES:
          error_log                      = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 64)
          a_d_sampling_rate              = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 4513)
          a_d_coaddition_factor          = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 4514)
          scan_acquisition_time          = [0.329,0.73,1.132,1.534,1.936,2.337,2.739,3.14,3.5 (length: 4513)
          scan_duration                  = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 4513)
          inter_scan_time                = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 4513)
          resolution                     = [106.6623112889557,110.7855343519544,104.407495112 (length: 4513)
          actual_scan_number             = [0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34, (length: 4513)
          total_intensity                = [5297.4945068359375,6172.123912811279,5934.7557412 (length: 4513)
          mass_range_min                 = [49.99999997418507,49.99999997418507,49.9999999741 (length: 4513)
          mass_range_max                 = [1599.9999564432276,1599.9999564432276,1599.999956 (length: 4513)
          time_range_min                 = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 4513)
          time_range_max                 = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 4513)
          scan_index                     = [0,60,128,195,265,324,399,472,542,596,671,738,803, (length: 4513)
          point_count                    = [60,68,67,70,59,75,73,70,54,75,67,65,64,73,56,69,6 (length: 4513)
          flag_count                     = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 4513)
          mass_values                    = [51.53516375878996,95.32974890044508,106.334477231 (length: 1176507)
          intensity_values               = [76.99999237060547,80,90,78.99799346923828,80.9352 (length: 1176507)
          instrument_name                = ["m","i","c","r","O","T","O","F",""," "," "," ","  (length: 32)
          instrument_id                  = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
          instrument_mfr                 = ["B","r","u","k","e","r"," ","D","a","l","t","o"," (length: 32)
          instrument_model               = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
          instrument_serial_no           = ["2","1","3","7","5","0",".","1","0","3","5","9"," (length: 32)
          instrument_sw_version          = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
          instrument_fw_version          = [""," "," "," "," "," "," "," "," "," "," "," ","  (length: 32)
          instrument_os_version          = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
          instrument_app_version         = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
          instrument_comments            = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
    */
    /**
     * Parses a Bruker GC/MS NetCDF file and returns times with TIC and mass spectra.
     * @param reader - NetCDF reader instance
     * @returns Parsed times and series data
     */
    function brukerGCMS(reader) {
      const time = reader.getDataVariable('scan_acquisition_time');
      const tic = reader.getDataVariable('total_intensity');
      const scanIndex = reader.getDataVariable('scan_index');
      const massValues = reader.getDataVariable('mass_values');
      const intensityValues = reader.getDataVariable('intensity_values');
      scanIndex.push(massValues.length);
      const ms = [];
      let index = 0;
      for (const end of scanIndex.slice(1)) {
        const start = index;
        index = end;
        ms.push([massValues.slice(start, end), intensityValues.slice(start, end)]);
      }
      return {
        times: time,
        series: [{
          name: 'tic',
          dimension: 1,
          data: tic
        }, {
          name: 'ms',
          dimension: 2,
          data: ms
        }]
      };
    }

    /* reader.toString() provides the following information
        GLOBAL ATTRIBUTES
          dataset_completeness           = C1+C2
          ms_template_revision           = 1.0.1
          administrative_comments        =
          dataset_owner                  =
          experiment_title               =
          experiment_date_time_stamp     = 20150902041002+0100
          netcdf_file_date_time_stamp    = 20151026063419+0000
          experiment_type                = Centroided Mass Spectrum
          netcdf_revision                = 2.3.2
          operator_name                  = DSQ
          source_file_reference          = G:\FCO\FCO_CIO\K2\MP2013\T1 IL database 2013\9. IL Data Entry\12_HU_HIFS\IL database\RAW data\Lukoil-Disel-150901.RAW
          source_file_date_time_stamp    = 20150902041002+0100
          source_file_format             = Finnigan
          languages                      = English
          external_file_ref_0            =
          instrument_number              = 1
          sample_prep_comments           =
          sample_comments                = Lukoil Disel 0,5 % CS2 1 % inkt
          test_separation_type           =
          test_ms_inlet                  =
          test_ionization_mode           =
          test_ionization_polarity       = Positive Polarity
          test_detector_type             = Conversion Dynode Electron Multiplier
          test_scan_function             = Mass Scan
          test_scan_direction            =
          test_scan_law                  = Linear
          number_of_scans                = 11832
          raw_data_mass_format           = Double
          raw_data_intensity_format      = Long
          actual_run_time                = 3519.6410000000005
          actual_delay_time              = 82.328
          global_mass_min                = 0
          global_mass_max                = 450
          calibrated_mass_min            = 0
          calibrated_mass_max            = 0
          mass_axis_label                = M/Z
          intensity_axis_label           = Abundance

        VARIABLES:
          error_log                      = ["","","","","","","","","","","","","","","",""," (length: 64)
          instrument_name                = ["L","C","Q","","","","","","","","","","","",""," (length: 32)
          instrument_id                  = ["","","","","","","","","","","","","","","",""," (length: 32)
          instrument_mfr                 = ["F","i","n","n","i","g","a","n","-","M","A","T"," (length: 32)
          instrument_model               = ["","","","","","","","","","","","","","","",""," (length: 32)
          instrument_sw_version          = ["3",".","1","","","","","","","","","","","",""," (length: 32)
          instrument_os_version          = ["W","i","n","d","o","w","s"," ","V","i","s","t"," (length: 32)
          scan_index                     = [0,34,74,113,145,177,211,239,267,299,341,374,400,4 (length: 11832)
          point_count                    = [34,40,39,32,32,34,28,28,32,42,33,26,29,34,31,28,2 (length: 11832)
          flag_count                     = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 11832)
          a_d_sampling_rate              = [1000,1000,1000,1000,1000,1000,1000,1000,1000,1000 (length: 11832)
          scan_acquisition_time          = [82.328,82.625,82.76599999999999,83.063,83.188,83. (length: 11832)
          scan_duration                  = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 11832)
          mass_range_min                 = [35,16,35,16,35,16,35,16,35,16,35,16,35,16,35,16,3 (length: 11832)
          mass_range_max                 = [450,150,450,150,450,150,450,150,450,150,450,150,4 (length: 11832)
          scan_type                      = [65537,65537,65537,65537,65537,65537,65537,65537,6 (length: 11832)
          resolution                     = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 11832)
          total_intensity                = [375220,1054339,228245,576718,58280,288629,29815,1 (length: 11832)
          mass_values                    = [36.3023681640625,36.98402404785156,38.08326721191 (length: 1366002)
          intensity_values               = [335,287,331,266,2423,448,9009,833,261,661,4003,21 (length: 1366002)

    */
    /**
     * Parses a Finnigan GC/MS NetCDF file and returns times with TIC and mass spectra.
     * @param reader - NetCDF reader instance
     * @returns Parsed times and series data
     */
    function finniganGCMS(reader) {
      const time = reader.getDataVariable('scan_acquisition_time');
      const tic = reader.getDataVariable('total_intensity');
      const scanIndex = reader.getDataVariable('scan_index');
      const massValues = reader.getDataVariable('mass_values');
      const intensityValues = reader.getDataVariable('intensity_values');
      scanIndex.push(massValues.length);
      const ms = [];
      let index = 0;
      for (const end of scanIndex.slice(1)) {
        const start = index;
        index = end;
        ms.push([massValues.slice(start, end), intensityValues.slice(start, end)]);
      }
      return {
        times: time,
        series: [{
          name: 'tic',
          dimension: 1,
          data: tic
        }, {
          name: 'ms',
          dimension: 2,
          data: ms
        }]
      };
    }

    /* reader.toString() provides the following information
        GLOBAL ATTRIBUTES
          dataset_completeness           = C1+C2
          ms_template_revision           = 1.0.1
          netcdf_revision                = 2.3.2
          languages                      = English
          administrative_comments        =
          netcdf_file_date_time_stamp    = 20180913165502+0000
          experiment_title               =
          experiment_date_time_stamp     = 20180910165319+0000
          operator_name                  = Admin
          source_file_reference          = D:\GCMSsolution\Data\Chromatograms\Cato\bormann_CB000_Test2.qgd
          source_file_format             = Shimadzu GCMSsolution
          source_file_date_time_stamp    = 20180910165319+0000
          experiment_type                = Centroided Mass Spectrum
          sample_internal_id             =
          sample_comments                =
          sample_state                   = Other State
          test_separation_type           = Gas-Solid Chromatography
          test_ms_inlet                  = Other Probe
          test_ionization_mode           = Electron Impact
          test_ionization_polarity       = Positive Polarity
          test_electron_energy           = 70
          test_detector_type             = Electron Multiplier
          test_resolution_type           = Constant Resolution
          test_scan_function             = Mass Scan
          test_scan_direction            = Up
          test_scan_law                  = Quadratic
          test_scan_time                 = 0
          raw_data_mass_format           = Double
          raw_data_time_format           = Long
          raw_data_intensity_format      = Long
          units                          = Seconds
          scale_factor                   = 1
          long_name                      = Seconds
          starting_scan_number           = 0
          actual_run_time_length         = 1289
          actual_delay_time              = 0
          raw_data_uniform_sampling_flag = 1

        VARIABLES:
          error_log                      = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 64)
          a_d_sampling_rate              = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 3820)
          a_d_coaddition_factor          = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 3820)
          scan_acquisition_time          = [144,144.3,144.6,144.9,145.2,145.5,145.8,146.1,146 (length: 3820)
          scan_duration                  = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 3820)
          inter_scan_time                = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 3820)
          resolution                     = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 3820)
          actual_scan_number             = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,2 (length: 3820)
          total_intensity                = [63566,61702,61873,59738,58321,59001,59364,59871,6 (length: 3820)
          mass_range_min                 = [35,35,35,35,35,35,35,35,35,35,35,35,35,35,35,35,3 (length: 3820)
          mass_range_max                 = [500,500,500,500,500,500,500,500,500,500,500,500,5 (length: 3820)
          time_range_min                 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 3820)
          time_range_max                 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 3820)
          scan_index                     = [0,466,932,1398,1863,2329,2795,3260,3726,4192,4658 (length: 3820)
          point_count                    = [466,466,466,465,466,466,465,466,466,466,466,466,4 (length: 3820)
          flag_count                     = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 3820)
          mass_values                    = [35,36,37.1,38.1,39.1,40.15,41.1,42.1,43.15,44.1,4 (length: 1779397)
          intensity_values               = [26,111,412,785,3098,485,5772,7391,11213,711,687,1 (length: 1779397)
          instrument_name                = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
          instrument_id                  = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
          instrument_mfr                 = ["S","h","i","m","a","d","z","u"," ","C","o","r"," (length: 32)
          instrument_model               = ["G","C","M","S","","","","","","","","","","","", (length: 32)
          instrument_serial_no           = ["","","","","","","","","","","","","","","",""," (length: 32)
          instrument_sw_version          = ["4",".","2","0","","","","","","","","","","","", (length: 32)
          instrument_fw_version          = ["G","C","M","S","-","Q","P","2","0","1","0","1"," (length: 32)
          instrument_os_version          = ["W","i","n","d","o","w","s","","","","","","","", (length: 32)
          instrument_app_version         = ["G","C","M","S","s","o","l","u","t","i","o","n"," (length: 32)
          instrument_comments            = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
    */
    /**
     * Parses a Shimadzu GC/MS NetCDF file and returns times with TIC and mass spectra.
     * @param reader - NetCDF reader instance
     * @returns Parsed times and series data
     */
    function shimadzuGCMS(reader) {
      const time = reader.getDataVariable('scan_acquisition_time');
      const tic = reader.getDataVariable('total_intensity');
      const scanIndex = reader.getDataVariable('scan_index');
      const massValues = reader.getDataVariable('mass_values');
      const intensityValues = reader.getDataVariable('intensity_values');
      scanIndex.push(massValues.length);
      const ms = [];
      let index = 0;
      for (const end of scanIndex.slice(1)) {
        const start = index;
        index = end;
        ms.push([massValues.slice(start, end), intensityValues.slice(start, end)]);
      }
      return {
        times: time,
        series: [{
          name: 'tic',
          dimension: 1,
          data: tic
        }, {
          name: 'ms',
          dimension: 2,
          data: ms
        }]
      };
    }

    /**
     * Reads a NetCDF file and returns a formatted JSON with the data from it.
     * @param data - ArrayBuffer or any Typed Array (including Node.js' Buffer from v4) with the data
     * @returns JSON with the time, TIC and mass spectra values
     */
    function netcdfGcms(data) {
      const reader = new NetCDFReader(data);
      const globalAttributes = reader.globalAttributes;
      const instrumentMfr = reader.dataVariableExists('instrument_mfr') && reader.getDataVariableAsString('instrument_mfr');
      const datasetOrigin = reader.attributeExists('dataset_origin');
      const massValues = reader.dataVariableExists('mass_values');
      const detectorName = reader.getAttribute('detector_name');
      const aiaTemplateRevision = reader.attributeExists('aia_template_revision');
      const sourceFileFormat = reader.getAttribute('source_file_format');
      let result;
      if (massValues && datasetOrigin) {
        result = agilentGCMS(reader);
      } else if (massValues && instrumentMfr && instrumentMfr.match(/finnigan/i)) {
        result = finniganGCMS(reader);
      } else if (massValues && instrumentMfr && instrumentMfr.match(/bruker/i)) {
        result = brukerGCMS(reader);
      } else if (massValues && sourceFileFormat && String(sourceFileFormat).match(/shimadzu/i)) {
        result = shimadzuGCMS(reader);
      } else if (massValues && sourceFileFormat && String(sourceFileFormat).match(/advion/i)) {
        result = advionGCMS(reader);
      } else if (detectorName && String(detectorName).match(/(?:dad|tic)/i)) {
        result = agilentHPLC(reader);
      } else if (aiaTemplateRevision) {
        result = aiaTemplate(reader);
      } else {
        throw new TypeError('Unknown file format');
      }
      return {
        ...result,
        meta: addMeta(globalAttributes),
        variables: addVariables(reader)
      };
    }
    function addMeta(globalAttributes) {
      const meta = {};
      for (const item of globalAttributes) {
        meta[item.name] = item.value;
      }
      return meta;
    }
    function addVariables(reader) {
      return reader.variables.map(variable => ({
        ...variable,
        value: reader.getDataVariable(variable)
      }));
    }

    function fromNetCDF(netcdf) {
      return fromJSON(netcdfGcms(netcdf));
    }

    const utf8Decoder$1 = new TextDecoder();
    const decoder$5 = {
      decode: array => {
        return utf8Decoder$1.decode(array);
      }
    };
    const defaultOptions = {
      trimValues: true,
      attributesNodeName: '',
      ignoreAttributes: false,
      ignoreNameSpace: false,
      allowBooleanAttributes: false,
      parseAttributesString: true,
      textNodeName: '#text',
      arrayMode: false,
      cdataTagName: false,
      tagNameProcessor: name => name,
      attributeNameProcessor: name => `$${name}`,
      tagValueProcessor: value => {
        const string = decoder$5.decode(value).replaceAll('\r', '');
        return parseString(string);
      },
      attributeValueProcessor: value => parseString(value),
      stopNodes: []
    };

    class XMLNode {
      tagName;
      parent;
      children;
      attributes;
      bytes;
      startIndex;
      tagValueProcessor;
      cachedValue;
      constructor(tagName, parent, bytes, tagValueProcessor) {
        this.tagName = tagName;
        this.parent = parent;
        this.children = Object.create(null); //child tags
        this.attributes = Object.create(null); //attributes map
        this.bytes = bytes; //text only
        this.tagValueProcessor = tagValueProcessor;
        this.startIndex = -1;
      }
      append(toAppend) {
        if (this.bytes.length === 0) {
          this.bytes = toAppend;
          return;
        }
        const arrayConcat = new Uint8Array(this.bytes.length + toAppend.length);
        arrayConcat.set(this.bytes);
        arrayConcat.set(toAppend, this.bytes.length);
        this.bytes = arrayConcat;
      }
      get value() {
        if (this.cachedValue === undefined) {
          const value = this.tagValueProcessor(this.bytes, this);
          this.cachedValue = value;
        }
        return this.cachedValue;
      }
      addChild(child) {
        const existing = this.children[child.tagName];
        if (Array.isArray(existing)) {
          existing.push(child);
        } else {
          this.children[child.tagName] = [child];
        }
      }
    }

    function arrayIndexOf(array, referenceArray, index = 0) {
      let found = 0;
      let foundIndex = -1;
      for (let i = index; i < array.length && found < referenceArray.length; i++) {
        if (array[i] === referenceArray[found]) {
          if (!found) {
            foundIndex = i;
          }
          found++;
        } else if (found > 0) {
          let j = 0;
          for (; j <= found && array[foundIndex + j] === array[foundIndex + found]; j++);
          if (j < found + 1) {
            foundIndex = -1;
            found = 0;
          } else {
            foundIndex++;
          }
        } else {
          found = 0;
          foundIndex = -1;
        }
      }
      if (found !== referenceArray.length) {
        foundIndex = -1;
      }
      return foundIndex;
    }

    /* eslint-disable @typescript-eslint/no-unused-vars */
    function arrayTrim(array, arg) {
      let i = 0;
      let j = array.length - 1;
      for (; i < array.length && array[i] <= 0x20; i++);
      for (; j >= i && array[j] <= 0x20; j--);
      if (i === 0 && j === array.length - 1) return array;
      return array.subarray(i, j + 1);
    }

    const utf8Decoder = new TextDecoder();
    const decoder$4 = {
      decode: array => {
        return utf8Decoder.decode(array);
      }
    };

    /**
     * Search for the corresponding closing tag '>'
     * @param data
     * @param i
     * @returns
     */
    function closingIndexForOpeningTag(data, i) {
      let attrBoundary;
      let endIndex = 0;
      for (let index = i; index < data.length; index++) {
        let byte = data[index];
        if (attrBoundary) {
          if (byte === attrBoundary) attrBoundary = 0; //reset
        } else if (byte === 0x22 || byte === 0x27) {
          attrBoundary = byte;
        } else if (byte === 0x3e) {
          return {
            data: decoder$4.decode(data.subarray(i, i + endIndex)),
            index
          };
        } else if (byte === 0x09) {
          byte = 0x20;
        }
        endIndex++;
      }
      throw new Error('Could not find closing tag');
    }

    function findClosingIndex(xmlData, str, i, errMsg) {
      const closingIndex = arrayIndexOf(xmlData, str, i);
      if (closingIndex === -1) {
        throw new Error(errMsg);
      } else {
        return closingIndex + str.length - 1;
      }
    }

    function getAllMatches(string, regex) {
      return Array.from(string.matchAll(regex));
    }
    function isEmptySimpleObject(object) {
      // fastest implementation: https://jsbench.me/qfkqv692c8/1
      // eslint-disable-next-line no-unreachable-loop
      for (const key in object) {
        return false;
      }
      return true;
    }
    function isEmptyObject(object) {
      // fastest implementation: https://jsbench.me/qfkqv692c8/1
      // eslint-disable-next-line no-unreachable-loop
      for (const key in object) {
        return false;
      }
      return true;
    }
    /**
     * Copy all the properties of a into b.
     * @param target
     * @param source
     * @param arrayMode
     */
    function merge(target, source, arrayMode) {
      if (!source) return;
      for (const key in source) {
        const value = source[key];
        if (value === undefined) continue;
        if (arrayMode === 'strict') {
          target[key] = [value];
        } else {
          target[key] = value;
        }
      }
    }
    /**
     * Check if a tag name should be treated as array
     * @param tagName - the node tagName
     * @param arrayMode - the array mode option
     * @param parentTagName - the parent tag name
     * @returns true if node should be parsed as array
     */
    function isTagNameInArrayMode(tagName, arrayMode, parentTagName) {
      if (arrayMode === false) {
        return false;
      } else if (arrayMode instanceof RegExp) {
        return arrayMode.test(tagName);
      } else if (typeof arrayMode === 'function') {
        return arrayMode(tagName, parentTagName);
      }
      return arrayMode === 'strict';
    }

    const newLocal = String.raw`([^\s=]+)\s*(=\s*(['"])(.*?)\3)?`;
    const attrsRegx = new RegExp(newLocal, 'g');
    //Attributes are strings so no point in using arrayBuffers here
    function parseAttributesString(string, options) {
      const {
        ignoreAttributes
      } = options;
      if (ignoreAttributes) {
        return;
      }
      string = string.replaceAll(/\r?\n/g, ' ');
      const matches = getAllMatches(string, attrsRegx);
      // argument 1 is the key, argument 4 is the value
      const attributes = {};
      for (const match of matches) {
        const attributeName = resolveNameSpace(match[1], options);
        if (attributeName.length > 0) {
          if (match[4] !== undefined) {
            if (options.trimValues) {
              match[4] = match[4].trim();
            }
            if (options.attributeValueProcessor) {
              attributes[attributeName] = options.attributeValueProcessor(match[4], attributeName);
            }
          } else if (options.allowBooleanAttributes) {
            attributes[attributeName] = true;
          }
        }
      }
      if (isEmptySimpleObject(attributes)) return;
      return attributes;
    }
    function resolveNameSpace(tagName, options) {
      if (options.ignoreNameSpace) {
        const tags = tagName.split(':');
        const prefix = tagName.startsWith('/') ? '/' : '';
        if (tags[0] === 'xmlns') {
          return '';
        }
        if (tags.length === 2 && tags[1] !== undefined) {
          tagName = prefix + tags[1];
        }
      }
      return tagName;
    }

    function removeNameSpaceIfNeeded(tagName, options) {
      if (!options.ignoreNameSpace) {
        return tagName;
      }
      const colonIndex = tagName.indexOf(':');
      if (colonIndex !== -1) {
        tagName = tagName.slice(colonIndex + 1);
      }
      return tagName;
    }

    function getTraversable(xmlData, options) {
      const {
        tagValueProcessor
      } = options;
      const traversable = new XMLNode('!xml', undefined, new Uint8Array(0), tagValueProcessor);
      let currentNode = traversable;
      let dataSize = 0;
      let dataIndex = 0;
      for (let i = 0; i < xmlData.length; i++) {
        if (xmlData[i] === 0x3c) {
          // <
          const xmlData1 = xmlData[i + 1];
          const xmlData2 = xmlData[i + 2];
          if (xmlData1 === 0x2f) {
            // </ Closing Tag
            const closeIndex = findClosingIndex(xmlData, [0x3e],
            //>
            i, 'Closing Tag is not closed.');
            let tagName = decoder$4.decode(arrayTrim(xmlData.subarray(i + 2, closeIndex)));
            tagName = removeNameSpaceIfNeeded(tagName, options);
            if (currentNode) {
              currentNode.append(options.trimValues ? arrayTrim(xmlData.subarray(dataIndex, dataIndex + dataSize)) : xmlData.subarray(dataIndex, dataIndex + dataSize));
            }
            if (options.stopNodes?.length && options.stopNodes.includes(currentNode.tagName)) {
              currentNode.children = {};
              if (currentNode.attributes === undefined) {
                currentNode.attributes = {};
              }
              currentNode.bytes = xmlData.subarray(currentNode.startIndex + 1, i);
            }
            currentNode = currentNode.parent;
            i = closeIndex;
            dataSize = 0;
            dataIndex = i + 1;
          } else if (xmlData1 === 0x3f) {
            // <? PI, processing instruction
            i = findClosingIndex(xmlData, [0x3f, 0x3e], i, 'Pi Tag is not closed.');
          } else if (
          //!-- comment
          xmlData1 === 0x21 && xmlData2 === 0x2d && xmlData[i + 3] === 0x2d) {
            i = findClosingIndex(xmlData, [0x2d, 0x2d, 0x3e],
            //-->
            i, 'Comment is not closed.');
            if (currentNode && dataSize !== 0 && currentNode.tagName !== '!xml') {
              currentNode.append(options.trimValues ? arrayTrim(xmlData.subarray(dataIndex, dataSize + dataIndex)) : xmlData.subarray(dataIndex, dataSize + dataIndex));
            }
            dataSize = 0;
            dataIndex = i + 1;
            //!D
          } else if (xmlData1 === 0x21 && xmlData2 === 0x44) {
            // <!D
            const closeIndex = findClosingIndex(xmlData, [0x3e],
            //>
            i, 'DOCTYPE is not closed.');
            const tagExp = xmlData.subarray(i, closeIndex);
            if (arrayIndexOf(tagExp, [0x5b]) >= 0) {
              i = arrayIndexOf(xmlData, [0x5d, 0x3e], i) + 1;
            } else {
              i = closeIndex;
            } //![
          } else if (xmlData1 === 0x21 && xmlData2 === 0x5b) {
            // <![CDATA[some stuff]]>
            const closeIndex = findClosingIndex(xmlData, [0x5d, 0x5d, 0x3e],
            //]]>
            i, 'CDATA is not closed.') - 2;
            const tagExp = xmlData.subarray(i + 9, closeIndex);
            //considerations
            //1. CDATA will always have parent node
            //2. A tag with CDATA is not a leaf node so it's value would be string type.
            if (dataSize !== 0) {
              const value = options.trimValues ? arrayTrim(xmlData.subarray(dataIndex, dataIndex + dataSize)) : xmlData.subarray(dataIndex, dataIndex + dataSize);
              currentNode.append(value);
            }
            if (options.cdataTagName) {
              //add cdata node
              const childNode = new XMLNode(options.cdataTagName, currentNode, tagExp, tagValueProcessor);
              currentNode.addChild(childNode);
              //add rest value to parent node
              if (tagExp) {
                childNode.bytes = tagExp;
              }
            } else {
              currentNode.append(tagExp);
            }
            i = closeIndex + 2;
            dataSize = 0;
            dataIndex = i + 1;
          } else {
            //Opening a normal tag
            const parsedOpeningTag = closingIndexForOpeningTag(xmlData, i + 1);
            const tagData = parsedOpeningTag.data.replaceAll(/\r?\n|\t/g, ' ');
            const closeIndex = parsedOpeningTag.index;
            const separatorIndex = tagData.indexOf(' ');
            let shouldBuildAttributesMap = true;
            let tagName = separatorIndex !== -1 ? tagData.slice(0, Math.max(0, separatorIndex)).replace(/\s+$/, '') : tagData;
            let tagAttributes = separatorIndex !== -1 ? tagData.slice(separatorIndex + 1) : '';
            if (options.ignoreNameSpace) {
              const colonIndex = tagName.indexOf(':');
              if (colonIndex !== -1) {
                tagName = tagName.slice(colonIndex + 1);
                shouldBuildAttributesMap = tagName !== parsedOpeningTag.data.slice(colonIndex + 1);
              }
            }
            //save text to parent node
            if (currentNode && dataSize !== 0 && currentNode.tagName !== '!xml') {
              currentNode.append(options.trimValues ? arrayTrim(xmlData.subarray(dataIndex, dataIndex + dataSize)) : xmlData.subarray(dataIndex, dataIndex + dataSize));
            }
            if (tagData.length > 0 && tagData.endsWith('/')) {
              //selfClosing tag
              if (tagAttributes) {
                // <abc def="123"/>
                tagAttributes = tagAttributes.slice(0, Math.max(0, tagAttributes.length - 1));
              } else {
                // <abc/>
                tagName = tagName.slice(0, Math.max(0, tagName.length - 1));
              }
              const childNode = new XMLNode(tagName, currentNode, new Uint8Array(0), tagValueProcessor);
              if (tagAttributes) {
                childNode.attributes = parseAttributesString(tagAttributes, options);
              }
              currentNode.addChild(childNode);
            } else {
              //opening tag
              const childNode = new XMLNode(tagName, currentNode, new Uint8Array(0), tagValueProcessor);
              if (options.stopNodes?.length && options.stopNodes.includes(childNode.tagName)) {
                childNode.startIndex = closeIndex;
              }
              if (tagAttributes && shouldBuildAttributesMap) {
                childNode.attributes = parseAttributesString(tagAttributes, options);
              }
              currentNode.addChild(childNode);
              currentNode = childNode;
            }
            i = closeIndex;
            dataSize = 0;
            dataIndex = i + 1;
          }
        } else {
          dataSize++;
        }
      }
      return traversable;
    }

    /**
     *
     * @param node
     * @param options
     * @param parentTagName
     * @returns
     */
    function traversableToJSON(node, options, parentTagName) {
      const {
        arrayMode,
        tagNameProcessor,
        attributeNameProcessor,
        textNodeName
      } = options;
      const result = {};
      // when no child node or attr is present
      if ((!node.children || isEmptyObject(node.children)) && (!node.attributes || isEmptySimpleObject(node.attributes))) {
        return node.value;
      }
      // otherwise create a textnode if node has some text
      if (node.bytes.length > 0) {
        const asArray = isTagNameInArrayMode(node.tagName, arrayMode, parentTagName);
        result[textNodeName] = asArray ? [node.value] : node.value;
      }
      if (node.attributes && !isEmptySimpleObject(node.attributes)) {
        let attributes = options.parseAttributesString ? {} : node.attributes;
        if (attributeNameProcessor) {
          // need to rename the attributes
          const renamedAttributes = {};
          for (const attributeName in node.attributes) {
            const newAttributeName = attributeNameProcessor(attributeName);
            const value = node.attributes[attributeName];
            if (value !== undefined) {
              renamedAttributes[newAttributeName] = value;
            }
          }
          attributes = renamedAttributes;
        }
        if (options.attributesNodeName) {
          const encapsulatedAttributes = {
            [options.attributesNodeName]: attributes
          };
          attributes = encapsulatedAttributes;
        }
        merge(result, attributes, arrayMode);
      }
      for (const tagName in node.children) {
        const nodes = node.children[tagName];
        if (!nodes) continue;
        const newTagName = tagNameProcessor ? tagNameProcessor(tagName, nodes) : tagName;
        if (nodes.length > 1) {
          result[tagName] = [];
          for (const child of nodes) {
            result[newTagName].push(traversableToJSON(child, options, tagName));
          }
        } else {
          const firstNode = nodes[0];
          if (!firstNode) continue;
          const subResult = traversableToJSON(firstNode, options, tagName);
          const asArray = arrayMode === true && typeof subResult === 'object' || isTagNameInArrayMode(tagName, arrayMode, parentTagName);
          result[newTagName] = asArray ? [subResult] : subResult;
        }
      }
      return result;
    }

    /**
     * Parse an ArrayBuffer or Uint8Array representing an XML and return an object
     * @param xmlData
     * @param options
     */
    function parse(xmlData, options = {}) {
      if (typeof xmlData === 'string') {
        const encoder = new TextEncoder();
        xmlData = encoder.encode(xmlData);
      }
      if (!ArrayBuffer.isView(xmlData)) {
        xmlData = new Uint8Array(xmlData);
      }
      const realOptions = {
        ...defaultOptions,
        ...options
      };
      const traversable = getTraversable(xmlData, realOptions);
      return traversableToJSON(traversable, realOptions);
    }

    const base64codes$1 = Uint8Array.from([255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 0, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]);
    /**
     * Decode a base64-encoded byte sequence into the original bytes.
     * @param input - Uint8Array holding the base64 representation (one byte per
     *   ASCII character, length must be a multiple of 4, `=` padding allowed).
     * @returns a Uint8Array containing the decoded bytes.
     */
    function decode(input) {
      if (!ArrayBuffer.isView(input)) {
        input = new Uint8Array(input);
      }
      if (input.length % 4 !== 0) {
        throw new Error('Unable to parse base64 string.');
      }
      const output = new Uint8Array(3 * (input.length / 4));
      if (input.length === 0) return output;
      const missingOctets = input.at(-2) === 61 ? 2 : input.at(-1) === 61 ? 1 : 0;
      for (let i = 0, j = 0; i < input.length; i += 4, j += 3) {
        const buffer = base64codes$1[input[i]] << 18 | base64codes$1[input[i + 1]] << 12 | base64codes$1[input[i + 2]] << 6 | base64codes$1[input[i + 3]];
        output[j] = buffer >> 16;
        output[j + 1] = buffer >> 8 & 0xff;
        output[j + 2] = buffer & 0xff;
      }
      return output.subarray(0, output.length - missingOctets);
    }

    const base64codes = Uint8Array.from([65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 47]);

    /*
    3 bytes are encoded in 4 bytes of base64
    11111122 22223333 33444444
    We want to be the fastest possible, so we will use a lookup table to convert 12 bits to 2 bytes of base64
    But in order still to avoid one operation we will create 2 of those lookup tables.
    - One for 2222 11111122
    - One for 3333 33444444
    */
    // 2222 11111122
    const base64codes1 = new Uint32Array(64 * 64);
    for (let i = 0; i < 64; i++) {
      for (let j = 0; j < 64; j++) {
        const index = i << 2 | (j & 0x30) >> 4 | (j & 0x0f) << 8;
        base64codes1[index] = base64codes[i] | base64codes[j] << 8;
      }
    }
    // 3333 33444444 that we store on the bits 16->31 just to allow to make directly the OR with the previous value
    const base64codes2 = new Uint32Array(64 * 64);
    for (let i = 0; i < 64; i++) {
      for (let j = 0; j < 64; j++) {
        const index = i << 6 | j;
        base64codes2[index] = base64codes[i] << 16 | base64codes[j] << 24;
      }
    }

    async function inflate(zlibCompressedData) {
      const inputStream = new ReadableStream({
        start(controller) {
          controller.enqueue(zlibCompressedData);
          controller.close();
        }
      });
      const decompressedStream = inputStream.pipeThrough(new DecompressionStream('deflate'));
      const reader = decompressedStream.getReader();
      const chunks = [];
      let totalLength = 0;
      while (true) {
        // eslint-disable-next-line no-await-in-loop
        const {
          value,
          done
        } = await reader.read();
        if (done) break;
        chunks.push(value);
        totalLength += value.length;
      }

      // Combine chunks into a single Uint8Array
      const decompressedData = new Uint8Array(totalLength);
      let offset = 0;
      for (const chunk of chunks) {
        decompressedData.set(chunk, offset);
        offset += chunk.length;
      }
      return decompressedData;
    }

    async function decodeData(data, options = {}) {
      if (!(data instanceof Uint8Array)) {
        throw new TypeError('data should be an Uint8Array');
      }
      let {
        endian = 'little',
        precision,
        float = true,
        compression = '',
        base64 = true,
        ontologies = {}
      } = options;
      if ('MS:1000519' in ontologies) {
        precision = 32;
        float = false;
      }
      if ('MS:1000520' in ontologies) precision = 16;
      if ('MS:1000521' in ontologies) precision = 32;
      if ('MS:1000522' in ontologies) {
        float = false;
        precision = 64;
      }
      if ('MS:1000523' in ontologies) precision = 64;
      if ('MS:1000574' in ontologies) compression = 'zlib';
      if (base64) {
        data = decode(data);
      }
      switch (compression.toLowerCase()) {
        case 'zlib':
          data = await inflate(data);
          break;
        case '':
        case 'none':
          break;
        default:
          throw new Error(`Unknown compression algorithm: ${compression}`);
      }
      switch (endian.toLowerCase()) {
        case 'little':
          break;
        case 'network':
        case 'big':
          {
            // we will invert in place the data
            let step;
            switch (precision) {
              case 32:
                step = 4;
                break;
              case 64:
                step = 8;
                break;
              default:
                throw new Error('Can not process bigendian file');
            }
            for (let i = 0; i < data.length - data.length % step; i += step) {
              for (let j = 0; j < step / 2; j++) {
                const temp = data[i + j];
                data[i + j] = data[i + step - 1 - j];
                data[i + step - 1 - j] = temp;
              }
            }
          }
          break;
        default:
          throw new TypeError(`Attributes endian not correct: ${endian}`);
      }

      /*
           We should take care that the length of the Uint8Array is correct but the buffer
           may be a little bit bigger because when decoding base 64 it may end with = or ==
           and we plan the size in the buffer.
          */

      if (float) {
        switch (precision) {
          case 32:
            return new Float32Array(data.buffer, data.byteOffset, data.byteLength / Float32Array.BYTES_PER_ELEMENT);
          case 64:
            return new Float64Array(data.buffer, data.byteOffset, data.byteLength / Float64Array.BYTES_PER_ELEMENT);
          default:
            throw new TypeError(`Incorrect precision: ${precision}`);
        }
      } else {
        switch (precision) {
          case 32:
            return new Int32Array(data.buffer, data.byteOffset, data.byteLength / Int32Array.BYTES_PER_ELEMENT);
          case 64:
            return new BigInt64Array(data.buffer, data.byteOffset, data.byteLength / BigInt64Array.BYTES_PER_ELEMENT);
          default:
            throw new TypeError(`Incorrect precision: ${precision}`);
        }
      }
    }

    function parseCvParam$1(cvParam) {
      let result = {};
      if (!cvParam) return result;
      let cvParams;
      if (Array.isArray(cvParam)) {
        cvParams = cvParam;
      } else {
        cvParams = [cvParam];
      }
      for (let param of cvParams) {
        let attributes = param.attributes;
        if (attributes.name) {
          result[attributes.name.toLowerCase()] = {
            accession: attributes.accession,
            cvLabel: attributes.cvLabel,
            value: attributes.value,
            name: attributes.name
          };
        }
      }
      return result;
    }

    function processMetadata(parsed, metadata) {
      if (!parsed || !parsed.description) return;
      let description = parsed.description;
      if (description.dataProcessing) {
        let dataProcessing = description.dataProcessing;
        if (dataProcessing.software && dataProcessing.software.name) {
          metadata.software = dataProcessing.software.name;
        }
      }
      if (description.instrument) {
        let instrument = description.instrument;
        if (instrument.analyzerList && instrument.analyzerList.analyzer) {
          let analyzer = instrument.analyzerList.analyzer;
          let cvParam = parseCvParam$1(analyzer.cvParam);
          if (cvParam.analyzertype) {
            metadata.analyzer = cvParam.analyzertype.value;
          }
        }
        if (instrument.detector) {
          let detector = instrument.detector;
          let cvParam = parseCvParam$1(detector.cvParam);
          if (cvParam.detectortype) {
            metadata.detector = cvParam.detectortype.value;
          }
        }
      }
    }

    function processSpectrumList$2(parsed, times, msData) {
      if (!parsed || !parsed.spectrumList || !parsed.spectrumList.spectrum) return;
      let spectrumList = parsed.spectrumList.spectrum;
      for (let spectrum of spectrumList) {
        let info = parseCvParam$1(spectrum.spectrumDesc.spectrumSettings.spectrumInstrument.cvParam);
        times.push(info.timeinminutes.value);
        let mzArray = spectrum.mzArrayBinary.data['#text'] || [];
        let intensity = spectrum.intenArrayBinary.data['#text'] || [];
        msData.push([mzArray, intensity]);
      }
    }

    const decoder$3 = new TextDecoder();

    /**
     *
     * @param {*} arrayBuffer
     * @param {import('../Options.js').Options} [options={}]
     * @returns
     */
    async function parseMzData(arrayBuffer, options = {}) {
      const {
        logger = console
      } = options;
      const result = {
        metadata: {},
        times: [],
        series: {
          ms: {
            data: []
          }
        }
      };
      let parsed = parse(arrayBuffer, {
        attributesNodeName: 'attributes',
        attributeNameProcessor: attributeName => attributeName,
        tagValueProcessor: (value, node) => {
          if (node.tagName !== 'data') return decoder$3.decode(value);
          const promise = decodeData(node.bytes, node.attributes);
          // avoid unhandled promise rejection and swallow the error
          promise.catch(error => {
            logger.error('error decoding base64', error);
            return [];
          });
          return promise;
        }
      });
      await recursiveResolve(parsed);
      processMetadata(parsed.mzData, result.metadata);
      processSpectrumList$2(parsed.mzData, result.times, result.series.ms.data);
      return result;
    }

    function parseCvParam(cvParam) {
      let result = {};
      if (!cvParam) return result;
      let cvParams;
      if (Array.isArray(cvParam)) {
        cvParams = cvParam;
      } else {
        cvParams = [cvParam];
      }
      for (let parameter of cvParams) {
        let attributes = parameter.attributes;
        if (attributes.name) {
          result[attributes.accession] = attributes;
        }
      }
      return result;
    }

    function processSpectrumList$1(parsed, result) {
      if (!parsed || !parsed.run || !parsed.run.spectrumList || !parsed.run.spectrumList.spectrum) {
        return;
      }
      let spectrumList = parsed.run.spectrumList.spectrum;
      const msData = result.series.ms.data;
      for (let spectrum of spectrumList) {
        if (!spectrum.binaryDataArrayList) continue;
        let scanList = spectrum.scanList;
        if (Array.isArray(scanList)) throw new Error('Unsupported scanList');
        let scan = scanList.scan;
        if (typeof scan !== 'object') continue;
        if (Array.isArray(scan)) {
          throw new Error('processSpectrumList: scan may not be an array');
        }
        const cvParam = parseCvParam(scan.cvParam);
        result.times.push(cvParam['MS:1000016']?.value);
        result.xPositions.push(cvParam['IMS:1000050']?.value);
        result.yPositions.push(cvParam['IMS:1000051']?.value);
        const dataArrayList = spectrum.binaryDataArrayList.binaryDataArray;
        if (dataArrayList.length !== 2) {
          throw new Error('Can not decodeData because length !== 2');
        }
        const first = dataArrayList[0];
        const firstCVParams = parseCvParam(first.cvParam);
        const second = dataArrayList[1];
        const secondCVParams = parseCvParam(second.cvParam);

        // MS:1000514 - m/z array
        // MS:1000515 - intensity array
        if (firstCVParams['MS:1000514'] && secondCVParams['MS:1000515']) {
          msData.push([first.binary, second.binary]);
        }
        if (firstCVParams['MS:1000515'] && secondCVParams['MS:1000514']) {
          msData.push([second.binary, first.binary]);
        }
      }
    }

    const decoder$2 = new TextDecoder();

    // https://www.psidev.info/mzml
    // CV = Controlled vocabulary
    async function parseMzML(mzmlBuffer, options = {}) {
      const {
        logger = console
      } = options;
      let {
        rawData
      } = options;

      // need to allow Buffer, ArrayBuffer or TypedArray and convert to Uint8Array
      mzmlBuffer = new Uint8Array(mzmlBuffer);
      if (rawData) {
        rawData = new Uint8Array(rawData);
      }
      const rawDataUint8Array = rawData && new Uint8Array(rawData);
      const result = {
        metadata: {},
        times: [],
        xPositions: [],
        yPositions: [],
        series: {
          ms: {
            data: []
          }
        }
      };
      const referenceableParamGroups = {};
      let parsed = parse(mzmlBuffer, {
        attributesNodeName: 'attributes',
        attributeNameProcessor: attributeName => attributeName,
        tagNameProcessor: (name, nodes) => {
          switch (name) {
            case 'referenceableParamGroupList':
              {
                const children = nodes[0]?.children?.referenceableParamGroup;
                for (const group of children) {
                  const id = group.attributes?.id;
                  referenceableParamGroups[id] = group.children;
                }
              }
              break;
            case 'referenceableParamGroupRef':
              for (const node of nodes) {
                // need to append the references children to the parent
                const ref = node.attributes.ref;
                if (referenceableParamGroups[ref]) {
                  const parent = node.parent;
                  parent.children = parent.children || {};
                  for (const key in referenceableParamGroups[ref]) {
                    parent.children[key] = parent.children[key] || [];
                    parent.children[key].push(...referenceableParamGroups[ref][key]);
                  }
                }
              }
              break;
          }
          return name;
        },
        tagValueProcessor: (value, node) => {
          if (node.tagName !== 'binary') return decoder$2.decode(value);
          const referenceableParamGroupRefs = node.parent?.children?.referenceableParamGroupRef?.map(ref => ref.attributes.ref) || [];
          const ontologies = {};
          referenceableParamGroupRefs.forEach(ref => {
            if (referenceableParamGroups[ref]) {
              Object.assign(ontologies, referenceableParamGroups[ref]);
            }
          });
          node.parent.children.cvParam.forEach(cv => {
            ontologies[cv.attributes.accession] = cv.attributes.value;
          });
          let promise;
          if ('IMS:1000102' in ontologies) {
            // external data offset
            const offset = parseInt(ontologies['IMS:1000102'], 10);
            const encodedLength = parseInt(ontologies['IMS:1000104'], 10);
            promise = decodeData(rawDataUint8Array.subarray(offset, offset + encodedLength), {
              ontologies,
              base64: false
            });
          } else {
            promise = decodeData(node.bytes, {
              ontologies,
              base64: true
            });
          }

          // avoid unhandled promise rejection and swallow the error
          promise.catch(error => {
            logger.error('error decoding base64', error);
            return [];
          });
          return promise;
        }
      });
      // parsed file still contains promises
      await recursiveResolve(parsed);
      const mzML = parsed.mzML || parsed.indexedmzML.mzML;
      processSpectrumList$1(mzML, result);
      return result;
    }

    function processSpectrumList(parsed, times, msData) {
      if (!parsed.msRun.scan) return;
      let scanList = parsed.msRun.scan;
      if (Array.isArray(scanList) === false) scanList = [scanList];
      if (scanList[0].attributes) msData.info = [];
      for (let scan of scanList) {
        if (typeof scan !== 'object') continue;
        if (Array.isArray(scan)) {
          throw new Error('processSpectrumList: scan may not be an array');
        }
        const dataArray = scan.peaks['#text'];
        let length = dataArray.length / 2;
        let first = new Float64Array(length);
        let second = new Float64Array(length);
        for (let i = 0; i < length; i++) {
          first[i] = dataArray[i * 2];
          second[i] = dataArray[i * 2 + 1];
        }
        msData.data.push([first, second]);
        msData.info.push(scan.attributes);
        times.push(parseFloat(scan.attributes.retentionTime.replace(/(?:P*)(?:T*)(?:S*)/gi, '')));
      }
    }

    const decoder$1 = new TextDecoder();

    /**
     *
     * @param {*} arrayBuffer
     * @param {import('../Options.js').Options} [options]
     * @returns
     */
    async function parseMzXML(arrayBuffer, options = {}) {
      const {
        logger = console
      } = options;
      const result = {
        metadata: {},
        times: [],
        series: {
          ms: {
            data: []
          }
        }
      };
      let parsed = parse(arrayBuffer, {
        attributesNodeName: 'attributes',
        attributeNameProcessor: attributeName => attributeName,
        tagValueProcessor: (value, node) => {
          if (node.tagName !== 'peaks') return decoder$1.decode(value);
          const promise = decodeData(node.bytes, {
            precision: node.attributes.precision,
            endian: node.attributes.byteOrder,
            compression: node.attributes.compressionType
          });
          // avoid unhandled promise rejection and swallow the error
          promise.catch(error => {
            logger.error('error decoding base64', error);
            return [];
          });
          return promise;
        }
      });
      await recursiveResolve(parsed);
      processSpectrumList(parsed.mzXML, result.times, result.series.ms);
      return result;
    }

    const decoder = new TextDecoder();

    /**
     * Reads a mzData v1.05 file
     * @param {ArrayBuffer|string} xml - ArrayBuffer or String or any Typed Array (including Node.js' Buffer from v4) with the data
     * @param {import('./Options.js').Options} [options={}]
     * @return Promise<{{times: Array<number>, series: { ms: { data:Array<Array<number>>}}}}>
     */
    async function parseMZ(xml, options = {}) {
      if (typeof xml === 'string') {
        const encoder = new TextEncoder();
        xml = encoder.encode(xml);
      }
      if (!ArrayBuffer.isView(xml)) {
        xml = new Uint8Array(xml);
      }
      const header = xml.subarray ? decoder.decode(xml.subarray(0, 200)) : xml.substring(0, 200);
      if (header.includes('mzData')) {
        return parseMzData(xml, options);
      } else if (header.includes('mzML')) {
        return parseMzML(xml, options);
      } else if (header.includes('mzXML')) {
        return parseMzXML(xml, options);
      } else {
        throw new Error(`MZ parser: unknown format`);
      }
    }

    // @ts-expect-error mzdata is not typed
    /**
     * Creates a new Chromatogram from supported XML formats.
     * @param xml - String containing the XML chromatogram.
     */
    async function fromXML(xml) {
      return fromJSON(await parseMZ(xml));
    }

    const defaultPeakShape = {
      kind: 'gaussian',
      fwhm: 4
    };
    /**
     * Generate 1D chromatographic data from a list of peaks.
     * @param peaks - The list of peaks to include in the chromatogram.
     * @param options - Information about the chromatographic range to generate.
     * @returns The generated chromatographic data (times and intensities).
     */
    function generateChromatogram1D(peaks, options) {
      const nbPoints = Math.round((options.lastTime - options.firstTime) * options.scansPerTime) + 1;
      const generator = new SpectrumGenerator({
        from: options.firstTime,
        to: options.lastTime,
        nbPoints
      });
      const generatorPeaks = peaks.map(peak => {
        return {
          x: peak.retentionTime,
          y: peak.intensity,
          shape: peak.shape ?? defaultPeakShape
        };
      });
      generator.addPeaks(generatorPeaks);
      const data = generator.getSpectrum();
      return {
        times: data.x,
        intensities: data.y
      };
    }

    /**
     * Combine two mass spectra into a new one, adding the values for identical masses.
     * @param ms1 - First MS.
     * @param ms2 - Second MS.
     * @returns Combined MS.
     */
    function combineMassSpectra(ms1, ms2) {
      const x = [];
      const y = [];
      let index1 = 0;
      let index2 = 0;
      while (index1 < ms1.x.length && index2 < ms2.x.length) {
        const mass1 = ms1.x[index1];
        const mass2 = ms2.x[index2];
        if (mass1 < mass2) {
          x.push(mass1);
          y.push(ms1.y[index1]);
          index1++;
        } else if (mass2 < mass1) {
          x.push(mass2);
          y.push(ms2.y[index2]);
          index2++;
        } else {
          x.push(mass1);
          y.push(ms1.y[index1] + ms2.y[index2]);
          index1++;
          index2++;
        }
      }
      while (index1 < ms1.x.length) {
        x.push(ms1.x[index1]);
        y.push(ms1.y[index1]);
        index1++;
      }
      while (index2 < ms2.x.length) {
        x.push(ms2.x[index2]);
        y.push(ms2.y[index2]);
        index2++;
      }
      return {
        x,
        y
      };
    }

    /**
     * Generate mass spectra for a range of intensities by scaling the pure MS with
     * each intensity. Slots with an intensity of zero will contain empty mass spectra.
     * @param massSpectrum - The pure mass spectrum.
     * @param intensities - The scaling intensities.
     * @returns An array of mass spectra.
     */
    function generateMassSpectra(massSpectrum, intensities) {
      const massSpectra = [];
      for (const intensity of intensities) {
        if (intensity === 0) {
          massSpectra.push({
            x: [],
            y: []
          });
        } else {
          const newX = massSpectrum.x.slice();
          const newY = massSpectrum.y.map(value => value * intensity);
          massSpectra.push({
            x: newX,
            y: newY
          });
        }
      }
      return massSpectra;
    }

    /**
     * Generate 2D chromatographic data from a list of peaks.
     * @param peaks - The list of peaks to include in the chromatogram.
     * @param options - Information about the chromatographic range to generate.
     * @returns The generated chromatographic data (times and mass spectra).
     */
    function generateChromatogram2D(peaks, options) {
      const [firstPeak, ...otherPeaks] = peaks;
      const firstGc = generateChromatogram1D([firstPeak], options);
      const massSpectra = generateMassSpectra(firstPeak.ms, firstGc.intensities);
      for (const otherMolecule of otherPeaks) {
        const otherGc = generateChromatogram1D([otherMolecule], options);
        const otherMassSpectra = generateMassSpectra(otherMolecule.ms, otherGc.intensities);
        for (let i = 0; i < massSpectra.length; i++) {
          massSpectra[i] = combineMassSpectra(massSpectra[i], otherMassSpectra[i]);
        }
      }
      return {
        times: firstGc.times,
        ms: massSpectra
      };
    }

    exports.Chromatogram = Chromatogram;
    exports.appendKovats = appendKovats;
    exports.appendMass = appendMass;
    exports.cosineSimilarity = cosineSimilarity;
    exports.fromJSON = fromJSON;
    exports.fromJcamp = fromJcamp;
    exports.fromNetCDF = fromNetCDF;
    exports.fromText = fromText;
    exports.fromXML = fromXML;
    exports.generateChromatogram1D = generateChromatogram1D;
    exports.generateChromatogram2D = generateChromatogram2D;
    exports.getKovatsConversionFunction = getKovatsConversionFunction;
    exports.kovats = kovats;
    exports.massFilter = massFilter;
    exports.scaleAlignment = scaleAlignment;
    exports.spectraComparison = spectraComparison;
    exports.vectorify = vectorify;

}));
//# sourceMappingURL=chromatography.umd.js.map
